Wenn Netflix weiß, was du gerade fühlstGenerative KI nutzt emotionales Feedback
Ein Gastbeitrag von
Jens Jakobsson *
6 min Lesedauer
Generative KI kann Inhalte in Echtzeit auf der Grundlage von Gesichtsausdrücken erstellen. Wie das funktioniert und wie der Code dazu aussieht, verrät Datenmanagement-Spezialist Solita.
Eine KI, die emotionales Feedback analysiert, um generierte Inhalte anzupassen – Solita hat sich mit einer solchen Anwendung befasst.
Wenn man sich nicht mehr aktiv für einen von 20 Filmen entscheiden muss, sondern der Fernseher einem die Präferenz gleichsam von den Augen abliest – dann steckt dahinter nicht zwingend Grusel-Science-Fiction. Vielmehr geht es hier um eine einfache Applikation, die Gesichtsausdrücke analysiert, um auf Basis emotionaler Reaktionen Inhalte in Echtzeit zu erstellen.
Der Anspruch: Eine Person liest eine Geschichte, die sich aufgrund der jeweiligen Reaktion spontan verändert. Hierfür verwendet die Anwendung – wer hätte es gedacht? – generative KI (GenAI). Um eine solche Applikation umzusetzen, nutzten wir bei Solita (seit Kurzem Microsoft Advanced Specialist für AI und Machine Learning) den Azure-KI-Stack.
Bilderfassung und Abruf von Gesichtsausdrücken
Um das Ziel zu erreichen, sind zunächst einige technische Schritte erforderlich. Ein Python-Skript (siehe Abbildung), das auf einem Edge-Gerät in der Azure-Cloud läuft, wird in einer Endlosschleife ausgeführt. Das Gerät nimmt ein Bild auf, ruft den „Azure Cognitive Services FaceAPI“-Endpunkt auf und schreibt die Antwort auf die Stimmung in einen Azure Blob Storage-Container.
Kontinuierliche Dateneingabe mit Azure Databricks und Spark Streaming
Für die weitere reibungslose Datenverarbeitung nutzen wir das Spark-Framework. Man stelle sich dafür nicht nur ein Gesicht vor, sondern 10.000 Personen an 10.000 verschiedenen Orten – macht 100 Millionen Stimmungen, die alle ein bis zwei Sekunden verarbeitet werden müssen.
Spark ermöglicht das parallele und verteilte Verarbeiten entsprechend großer Datenströme. Der folgende Code nimmt die Daten aus einem Cloud-Speicher auf und schreibt sie als Stream in eine Delta-Tabelle.
Von dort übernimmt ein weiterer Stream. Er greift über die Chat Completion API (einen Teil der Azure OpenAI Suite) auf das Modell zu, verwendet das Sentiment und den Nachrichtenverlauf in der Eingabeaufforderung und schreibt die Antwort in eine nächste Deltatabelle. Ergebnis ist die auf dem Sentiment basierende generierte Story. Der Nachrichtenhistorie folgend, würde die Konversation so aussehen:
Systemnachricht (für Kontext, Anweisungen oder andere Informationen, die für den Anwendungsfall relevant sind).
Aufforderungsnachricht (vom User)
Antwort (vom Assistenten)
Aufforderungsnachricht (vom User)
etc.
Die Nachrichtenliste wäre dann die Eingangsnachricht für das Modell. Die untenstehende Abbildung zeigt die anfängliche Nachrichtenliste. Sie wird im Verlauf der Story immer umfangreichen, da die Konversation an sie angehängt wird.
# Initial message list write to Delta table message_list = [ {"role": "system", "content": "You are an AI storyteller that helps people get in the right mood."), ("role": "user", "content": "Please provide the beginning of the Shrek story as is from the movie transcript'), ("role": "assistant", "content": "Once upon a time there was a lovely princess. But she had an enchantment upon her of a fearful sort which could only be broken by love's first kiss. She was locked away in a castle guarded by a terrible fire-breathing dragon. Many brave knights had attempted to free her from this dreadful prison, but none prevailed. She waited in the dragon's keep in the highest room of the tallest tower."}]
Im Folgenden erstellt man eine benutzerdefinierte Funktion, die die auf der Stimmung basierende Benutzernachricht zur Nachrichtenliste hinzufügt. Sie fordert das Modell zur Vervollständigung mit Hilfe der Nachrichtenliste auf, fügt die Antwort zur Nachrichtenliste hinzu und gibt sie zurück.
def openai_api_call(user_message): # Set API variables openai.api_type = "azure" openai.api_base = https://<RESOURCE>.openai.azure.com/ openai.api_version = <VERSION> openai.api_key = <KEY> try: # Add user message to message list message_list.append({"role": "user", "content": user_message}) response = openai.ChatCompletion.create( engine = <engine>, messages = message_list, temperature=0.7, max_tokens=800, top_p=0.95, frequency_penalty=0, presence_penalty=0, stop=None) resp_message = response.choices[0].message.content.strip() # Add assistant response to message list message_list.append("role": "assistant", "content": resp_message}) return resp_message except openai.error.InvalidRequestError as e: return None# Register function as UDF openai_api_call_udf = udf(openai_api_call)
Die obige benutzerdefinierte Funktion verwendet eine Benutzernachricht als Eingabe. Wir erstellen nun eine benutzerdefinierte Funktion, die sie erzeugt. Die Funktion nimmt das Sentiment als Eingabe und erstellt die Benutzernachricht je nach Emotion.
def write_user_message(emotion): if emotion == "happy": user_message = "I am happy about the content, please continue the Shrek story in the same fashion rom where you left off." elif emotion == "sad": user_message = "I am not satisfied about the content, please modify the Shrek story to be more dramatic and exciting but continue from where you left off." return user_message # Register function as UDF write_user_message_udf = udf(write_user_message)
An dieser Stelle nun kann der Stream starten und dem KI-Erzähler Leben einhauchen. Wir verwenden eine processBatch()-Funktion im Databricks Spark Structured Streaming, was mehrere Gründe hat: Sie eignet sich für die Stapelverarbeitung innerhalb einer Streaming-Anwendung. Führt man API-Aufrufe innerhalb einer Spark Structured Streaming-Anwendung durch, kann es Situationen geben, in denen es praktischer ist, einen Batch zu erstellen, bevor man den API-Aufruf durchführt. Gründe dafür sind API-Ratenbegrenzung und -Effizienz, Atomizität und Konsistenz.
def process_batch(df, epoch_id): # Sort batch and store the latest record in a DataFrame df = df.orderBy(df["load_time_bronze"].desc()).limit(1) # Call write_user_message UDF -> get user message based on emotion and add as column df = df.withColumn("user_message", write_user_message_udf(df.leading_emotion)) # Call openai_api_call UDF -> get response message and add as column df = df.withColumn("response_message", openai_api_call_udf(df.user_message)) # Write the data to Delta table df.write.format("delta").mode("append").saveAsTable(delta_table_silver) # Read bronze delta table, add and drop columns df = (spark.readStream \ .format("delta") \ .option("inferColumnTypes", "true") \ .table(delta_table_bronze) \ .withColumn("leading_emotion", when(col("happiness") == greatest(col("happiness"), col("sadness")), lit("happy")) \ .otherwise(lit("sad"))) \ .withColumn("load_time_silver", current_timestamp()) \ .drop("anger", "contempt", "disgust", "fear", "neutral", "surprise"))# Define a streaming query to process the data using the process_batch function and wait for the query to terminate df.writeStream \ .foreachBatch(process_batch) \ .option("checkpointLocation", checkpoint_delta) \ .start() \ .awaitTermination()
Datenfluss vom Bild bis zur Fertigstellung.
(Bild: Solita)
Nun, da klar ist, wie die Daten durch die verschiedenen Komponenten fließen, können wir die Lösung entwerfen.
Learnings
LLM-basierte Anwendungen lassen sich recht einfach mit bereits vorhandenen Komponenten von Cloud-Anbietern erstellen. Der größte Aufwand besteht darin, sie benutzerfreundlich zu gestalten und das Modell hinter dem User Interface so aufzubauen, dass es wie erwartet funktioniert. Durch Feinabstimmung des LLM lässt sich dieses für den speziellen Anwendungsfall noch relevanter und effektiver zu machen.
Diese Anwendung kann in verschiedenen Aspekten des täglichen Lebens äußerst hilfreich sein – nicht nur für Einzelpersonen, die gerne maßgeschneiderte Geschichten lesen, sondern auch in Unternehmen oder in der Öffentlichkeit, z. B. bei der Erstellung von Reden, für die Entwicklung maßgeschneiderter Marketingkampagnen oder bei der Generierung von Witzen für einen Stand-up-Comedian – natürlich in Echtzeit und stimmungsangepasst. Die Latenzzeit der Anwendung von einem aufgenommenen Bild bis zur generierten Geschichte betrug etwa sechs bis sieben Sekunden, was als nahezu Echtzeit gelten darf.
Stand: 08.12.2025
Es ist für uns eine Selbstverständlichkeit, dass wir verantwortungsvoll mit Ihren personenbezogenen Daten umgehen. Sofern wir personenbezogene Daten von Ihnen erheben, verarbeiten wir diese unter Beachtung der geltenden Datenschutzvorschriften. Detaillierte Informationen finden Sie in unserer Datenschutzerklärung.
Einwilligung in die Verwendung von Daten zu Werbezwecken
Ich bin damit einverstanden, dass die Vogel IT-Medien GmbH, Max-Josef-Metzger-Straße 21, 86157 Augsburg, einschließlich aller mit ihr im Sinne der §§ 15 ff. AktG verbundenen Unternehmen (im weiteren: Vogel Communications Group) meine E-Mail-Adresse für die Zusendung von Newslettern und Werbung nutzt. Auflistungen der jeweils zugehörigen Unternehmen können hier abgerufen werden.
Der Newsletterinhalt erstreckt sich dabei auf Produkte und Dienstleistungen aller zuvor genannten Unternehmen, darunter beispielsweise Fachzeitschriften und Fachbücher, Veranstaltungen und Messen sowie veranstaltungsbezogene Produkte und Dienstleistungen, Print- und Digital-Mediaangebote und Services wie weitere (redaktionelle) Newsletter, Gewinnspiele, Lead-Kampagnen, Marktforschung im Online- und Offline-Bereich, fachspezifische Webportale und E-Learning-Angebote. Wenn auch meine persönliche Telefonnummer erhoben wurde, darf diese für die Unterbreitung von Angeboten der vorgenannten Produkte und Dienstleistungen der vorgenannten Unternehmen und Marktforschung genutzt werden.
Meine Einwilligung umfasst zudem die Verarbeitung meiner E-Mail-Adresse und Telefonnummer für den Datenabgleich zu Marketingzwecken mit ausgewählten Werbepartnern wie z.B. LinkedIN, Google und Meta. Hierfür darf die Vogel Communications Group die genannten Daten gehasht an Werbepartner übermitteln, die diese Daten dann nutzen, um feststellen zu können, ob ich ebenfalls Mitglied auf den besagten Werbepartnerportalen bin. Die Vogel Communications Group nutzt diese Funktion zu Zwecken des Retargeting (Upselling, Crossselling und Kundenbindung), der Generierung von sog. Lookalike Audiences zur Neukundengewinnung und als Ausschlussgrundlage für laufende Werbekampagnen. Weitere Informationen kann ich dem Abschnitt „Datenabgleich zu Marketingzwecken“ in der Datenschutzerklärung entnehmen.
Falls ich im Internet auf Portalen der Vogel Communications Group einschließlich deren mit ihr im Sinne der §§ 15 ff. AktG verbundenen Unternehmen geschützte Inhalte abrufe, muss ich mich mit weiteren Daten für den Zugang zu diesen Inhalten registrieren. Im Gegenzug für diesen gebührenlosen Zugang zu redaktionellen Inhalten dürfen meine Daten im Sinne dieser Einwilligung für die hier genannten Zwecke verwendet werden. Dies gilt nicht für den Datenabgleich zu Marketingzwecken.
Recht auf Widerruf
Mir ist bewusst, dass ich diese Einwilligung jederzeit für die Zukunft widerrufen kann. Durch meinen Widerruf wird die Rechtmäßigkeit der aufgrund meiner Einwilligung bis zum Widerruf erfolgten Verarbeitung nicht berührt. Um meinen Widerruf zu erklären, kann ich als eine Möglichkeit das unter https://contact.vogel.de abrufbare Kontaktformular nutzen. Sofern ich einzelne von mir abonnierte Newsletter nicht mehr erhalten möchte, kann ich darüber hinaus auch den am Ende eines Newsletters eingebundenen Abmeldelink anklicken. Weitere Informationen zu meinem Widerrufsrecht und dessen Ausübung sowie zu den Folgen meines Widerrufs finde ich in der Datenschutzerklärung.
Natürlich gab es auch einige technische Hürden. So waren die Verfolgung der Sitzung und Speicherung der vorherigen Antworten des Modells bei der Verarbeitung von Daten mit einem Spark-Stream etwas mühsam. Die globale Variable message_list konnte ihren Wert zwischen den Stapeln nicht beibehalten. Offenbar behalten globale Variablen bei Verwendung der Funktion processBatch() in Spark Structured Streaming ihre Werte zwischen den Stapeln nicht bei, da jeder Stapel unabhängig verarbeitet wird.