\n\n\n\n Meine Reise beim Aufbau von Conversational AI mit SDKs - AgntBox Meine Reise beim Aufbau von Conversational AI mit SDKs - AgntBox \n

Meine Reise beim Aufbau von Conversational AI mit SDKs

📖 12 min read2,229 wordsUpdated Mar 27, 2026

Hey zusammen, Nina hier, zurück aus meinem digitalen Labor (das, seien wir ehrlich, hauptsächlich mein Küchentisch mit viel lauwarmem Kaffee ist). Heute möchte ich über etwas sprechen, das in meinen Slack-Kanälen herumgeht und meine späten Codiersitzungen heimsucht: die manchmal frustrierende, oft brillante Welt der AI SDKs, insbesondere wenn man versucht, einen Konversationsagenten zu bauen, der tatsächlich wie eine echte Unterhaltung wirkt, nicht nur wie ein glorifizierter FAQ-Bot.

Meine besondere Besessenheit in letzter Zeit galt der Integration der Assistants API von OpenAI. Ich weiß, was ihr denkt: „Nina, Assistants API? Das ist alte Nachrichten!“ Und ja, ihr habt nicht Unrecht. Sie ist schon eine Weile draußen. Aber hört mir zu: Ich bin nicht hier, um euch ein einfaches „Wie ruft man client.beta.assistants.create() auf?“ Tutorial zu geben. Das haben wir alle schon gesehen. Mein Fokus liegt heute auf einem spezifischen, kniffligen Problem, auf das ich gestoßen bin, und wie ich mit dem SDK gekämpft habe, um es zu dem zu bekommen, was ich wollte: statusbehaftete, mehrstufige Gespräche mit benutzerdefinierten Werkzeugen zu verwalten, ohne meinen Verstand (oder den Kontext meines Nutzers) zu verlieren. Denkt über einfache Fragen und Antworten hinaus. Denkt an komplexe Arbeitsabläufe, Folgefragen und dynamische Werkzeugaufrufe je nach sich entwickelnder Benutzerabsicht.

Der Zustand meines Verstands: Warum die Assistants API (immer noch) wichtig ist

Vor der Assistants API fühlte es sich oft so an, als wäre man ein Jongleur mit zu vielen Bällen in der Luft, wenn man etwas Statusbehaftetes mit den Modellen von OpenAI baute. Man war selbst dafür verantwortlich, die Nachrichtenhistorie zu verwalten, Kontext durch Prompt-Engineering zu erstellen und Werkzeugaufrufe alleine zu orchestrieren. Es war… ermüdend. Die Assistants API versprach, einen Großteil dieser Last von unseren Schultern zu nehmen, indem sie Threads, Nachrichten und sogar die Orchestrierung von Werkzeugen verwaltet. Und für einfache Fälle funktioniert das hervorragend.

Mein aktuelles Projekt umfasst den Aufbau eines „intelligenten“ persönlichen Finanzassistenten. Nicht nur einen, der dir deinen Kontostand sagt, sondern einen, der dir hilft, Budgets zu planen, Anlagestrategien basierend auf deiner Risikobereitschaft vorzuschlagen und sogar zukünftige Finanzszenarien zu simulieren. Das erfordert viel Hin und Her, das Erinnern an frühere Aussagen („Ich möchte für ein Haus sparen“, „Mein Einkommen beträgt X“, „Was ist, wenn ich in Y investiere?“) und entscheidend ist es, spezifische Funktionen (wie das Abrufen von Echtzeit-Aktiendaten oder das Ausführen eines Budgetprognose-Algorithmus) zur richtigen Zeit aufzurufen.

Mein erster Versuch war ein glorreiches Chaos. Ich habe die gesamte Nachrichtenhistorie hin und her geschickt, versucht, komplexe Anweisungen in den Systemprompt zu quetschen und manuell zu überprüfen, ob ein Werkzeugaufruf benötigt wurde. Es funktionierte, war aber zerbrechlich, langsam und ein Albtraum beim Debuggen. Das war der Moment, als ich beschloss, mich wirklich auf die Assistants API und ihr SDK zu konzentrieren.

Die Umarmung des SDK: Threads, Nachrichten und das schwer fassbare `run`

Der Kern der Assistants API ist das Konzept von `Threads` und `Messages`. Du erstellst einen `Thread` für ein Gespräch, fügst `Messages` hinzu und „führst“ den `Assistant` in diesem `Thread` aus. Der `Assistant` verarbeitet dann die Nachrichten, entscheidet, ob er ein Werkzeug aufrufen muss, und generiert eine Antwort. Einfach genug, oder?

Hier wird es interessant. Mein Finanzassistent muss Dinge wie:

  • Ziele nachverfolgen: „Ich möchte 50.000 $ für eine Anzahlung sparen.“
  • Informationen sammeln: „Wie hoch ist dein aktuelles monatliches Einkommen?“
  • Berechnungen durchführen: „Wie lange wird es dauern, wenn ich 1.000 $ pro Monat spare?“
  • Ratschläge geben: „Überlege, dein Portfolio mit kostengünstigen Indexfonds zu diversifizieren.“

Jedes dieser Dinge erfordert oft einen benutzerdefinierten Werkzeugaufruf. Zum Beispiel `calculate_savings_timeline(goal_amount, monthly_income, monthly_savings)`. Die Herausforderung besteht nicht nur darin, das Werkzeug zu definieren; es geht darum, den Assistant dazu zu bringen:

  1. Zu erkennen, dass er das Werkzeug benötigt.
  2. Alle notwendigen Parameter zu identifizieren (auch wenn sie über mehrere Nutzer-Nachrichten verteilt sind).
  3. Den Nutzer höflich nach fehlenden Parametern zu fragen.
  4. Das Werkzeug auszuführen und die Ergebnisse einzubeziehen.

Das Parameterproblem: Wenn der Assistant mehr Informationen anfordert

Nehmen wir an, ein Nutzer sagt: „Wie lange wird es dauern, bis ich für ein Haus gespart habe?“ Mein `calculate_savings_timeline` Werkzeug benötigt `goal_amount`, `monthly_income` und `monthly_savings`. Der Assistant, der intelligent ist, wird oft erkennen, dass Informationen fehlen. Das SDK wird dir durch das `run` Objekt sagen, dass es `requires_action` ist.

Mein ursprünglicher Ansatz war, einfach auf den Status `requires_action` zu warten und dem Nutzer dann eine allgemeine „Welche Informationen benötigst du?“ Frage zu stellen. Das war holprig. Der Nutzer wusste oft nicht, welche Parameter das Werkzeug benötigte. Ein besserer Ansatz, auf den ich schließlich gestoßen bin, bestand darin, die `tool_calls` innerhalb des `requires_action` Status zu überprüfen.


def submit_tool_outputs(client, thread_id, run_id, tool_outputs):
 return client.beta.threads.runs.submit_tool_outputs(
 thread_id=thread_id,
 run_id=run_id,
 tool_outputs=tool_outputs
 )

# ... innerhalb deiner Gesprächsschleife ...

if run.status == 'requires_action':
 tool_outputs = []
 missing_params = {}
 for tool_call in run.required_action.submit_tool_outputs.tool_calls:
 function_name = tool_call.function.name
 arguments = json.loads(tool_call.function.arguments)

 # Hier ist der Trick: Überprüfen, ob ein erforderlicher Parameter fehlt
 # Das geht davon aus, dass deine Werkzeuge mit einem Schema definiert sind
 # Zur Vereinfachung nehmen wir an, dass wir wissen, dass 'monthly_savings' immer benötigt wird
 if function_name == "calculate_savings_timeline" and "monthly_savings" not in arguments:
 missing_params['monthly_savings'] = 'Wie viel planst du, jeden Monat zu sparen?'
 
 # ... weitere anspruchsvolle Parameterüberprüfungen hier ...

 if missing_params:
 # Informiere den Nutzer, was fehlt
 user_prompt = "Ich benötige ein wenig mehr Informationen, um dir dabei zu helfen. "
 for param, question in missing_params.items():
 user_prompt += f"{question} "
 return {"status": "waiting_for_user_input", "prompt": user_prompt}
 else:
 # Alle Parameter sind vorhanden, führe das Werkzeug aus
 output = execute_tool_function(function_name, arguments) # Deine Funktion, um die tatsächliche Werkzeuglogik auszuführen
 tool_outputs.append({
 "tool_call_id": tool_call.id,
 "output": json.dumps(output)
 })
 
 if tool_outputs:
 run = submit_tool_outputs(client, thread.id, run.id, tool_outputs)
 # Polling des Runs fortsetzen, bis er abgeschlossen ist oder neue Aktionen benötigt
 else:
 # Dieser Fall behandelt, ob wir fehlende Parameter erkannt und den Nutzer gefragt haben
 pass

Dieser Code-Schnipsel ist eine vereinfachte Version, aber die Kernidee besteht darin, `requires_action` abzufangen, zu schauen, was der Assistant wills, und wenn ihm Argumente fehlen, den Nutzer gezielt danach zu fragen. Das macht das Gespräch viel natürlicher, wie ein Mensch, der klärende Fragen stellt, anstatt einer generischen „Fehlermeldung“.

Der schwer fassbare Zustand: Den Überblick über Runden behalten

Eines der größten Probleme war sicherzustellen, dass, wenn ein Nutzer ein Stück Information bereitstellt („Mein Einkommen beträgt 5.000 $ pro Monat“), der Assistant sich daran erinnert, dass sie für nachfolgende Werkzeugaufrufe im selben Gespräch verwendet wird, selbst wenn sie nicht sofort genutzt wird. Die Assistants API verwaltet das ziemlich gut innerhalb eines `Thread`, indem sie die Nachrichtenhistorie behält. Manchmal möchte man jedoch explizit darauf hinweisen oder Hintergrundinformationen geben, die kein direkter Teil einer Nutzer-Nachricht sind.

Ich fand mich oft dabei, die `metadata` der `Thread` und `Message` Objekte öfter zu verwenden, als ich zunächst dachte. Wenn der Nutzer zum Beispiel seine Risikobereitschaft ausdrücklich angibt, könnte ich das in den Metadaten des Threads speichern. Während der Assistant diese Metadaten nicht direkt „liest“ in seinem Denkprozess, sind sie für meine Anwendungslogik von unschätzbarem Wert, wenn ich Argumente für Werkzeugaufrufe vorbefüllen oder Entscheidungen darüber treffen muss, welche Werkzeuge überhaupt relevant sind.

Ein weiteres Muster, das ich übernommen habe, war das Einspeisen von „systemähnlichen“ Nachrichten in den Thread, wenn ich den Assistant ausdrücklich an bestimmte Fakten erinnern musste, die aus einem Werkzeugaufruf oder einem internen Prozess abgeleitet wurden. Zum Beispiel, nachdem `calculate_savings_timeline` zurückgibt, dass es 10 Jahre dauern wird, könnte ich eine Nachricht wie diese hinzufügen: `{„role“: „user“, „content“: „Der Sparplan des Nutzers zeigt einen Zeitraum von 10 Jahren an, um sein Ziel zu erreichen.“}`. Das kommt nicht direkt vom Nutzer, aber es hilft, den Kontext für die nachfolgenden Antworten des Assistants zu verstärken.

Umgang mit Werkzeugausgaben und nachfolgenden Aktionen

Wenn ein Werkzeug ausgeführt wird, erhält man eine Ausgabe. Der Assistant nimmt dann diese Ausgabe und generiert eine Antwort. Aber was passiert, wenn die Werkzeugausgabe selbst eine neue Reihe von Fragen oder einen neuen Werkzeugaufruf auslöst? Zum Beispiel könnte das `calculate_savings_timeline` Werkzeug zurückgeben, dass das Ziel mit den aktuellen Einsparungen unerreichbar ist. Der Assistant sollte dann idealerweise vorschlagen: „Vielleicht sollten wir Wege betrachten, um deine monatlichen Einsparungen zu erhöhen oder deinen Zielbetrag zu reduzieren.“ Das geht nicht nur darum, Text auszugeben; es geht darum, logische Schritte zu verknüpfen.

Die Assistants API SDK verwaltet das hervorragend, indem sie das `run` Objekt lebendig hält. Nachdem du `submit_tool_outputs` aufgerufen hast, wechselt das `run` oft zurück zu `in_progress` und könnte dann ein neues `requires_action` (für einen weiteren Werkzeugaufruf) oder `completed` generieren. Meine Hauptschleife zur Verwaltung des Gesprächsflusses sieht etwa so aus:


def get_assistant_response(client, thread_id, assistant_id, user_message):
 client.beta.threads.messages.create(
 thread_id=thread_id,
 role="user",
 content=user_message
 )

 run = client.beta.threads.runs.create(
 thread_id=thread_id,
 assistant_id=assistant_id
 )

 while run.status in ['queued', 'in_progress', 'cancelling']:
 time.sleep(1) # Sei ein guter Nutzer, überlastet die API nicht
 run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)

 if run.status == 'completed':
 messages = client.beta.threads.messages.list(thread_id=thread_id, order="desc")
 for msg in messages.data:
 if msg.role == "assistant":
 # Finde die letzte Nachricht des Assistenten, oft die erste
 return msg.content[0].text.value # Gehe davon aus, dass es sich um Textinhalt handelt
 return "Keine Antwort vom Assistenten."

 elif run.status == 'requires_action':
 tool_outputs = []
 for tool_call in run.required_action.submit_tool_outputs.tool_calls:
 function_name = tool_call.function.name
 arguments = json.loads(tool_call.function.arguments)
 
 # Hier kommt deine benutzerdefinierte Logik für die Behandlung fehlender Parameter oder die Ausführung von Tools
 # Für dieses vereinfachte Beispiel nehmen wir an, dass alle Parameter vorhanden sind und wir sie ausführen
 print(f"Assistent möchte {function_name} mit Argumenten aufrufen: {arguments}")
 
 output = execute_tool_function(function_name, arguments) # Deine Logik zur Ausführung des Tools
 tool_outputs.append({
 "tool_call_id": tool_call.id,
 "output": json.dumps(output)
 })
 
 # Übermittle die Tool-Ausgaben und setze den Lauf fort
 run = client.beta.threads.runs.submit_tool_outputs(
 thread_id=thread_id,
 run_id=run.id,
 tool_outputs=tool_outputs
 )
 # Rufe diese Funktion rekursiv auf, um den aktualisierten Lauf zu verarbeiten
 # Sei vorsichtig mit der Rekursionstiefe in der Produktion, eventuell eine Schleife nötig
 return get_assistant_response(client, thread_id, assistant_id, "") # Leere Nachricht übergeben
 
 elif run.status == 'failed':
 return f"Assistentenlauf fehlgeschlagen: {run.last_error.message}"
 
 else:
 return f"Unerwarteter Laufstatus: {run.status}"

Die `get_assistant_response` Funktion zeigt, obwohl vereinfacht, den Kern der Schleife. Der Schlüssel ist, dass `requires_action` kein totales Ende ist. Es ist eine Gelegenheit für deine Anwendung, dazwischenzugehen, das angeforderte Tool auszuführen und dann die Ergebnisse zurück an den Assistenten zu übermitteln, um seine Argumentation fortzusetzen. Dieses geschlossene Feedback ist es, was wirklich zustandsorientierte, dynamische Gespräche möglich macht.

Handlungsrelevante Erkenntnisse für dein nächstes AI-Assistentenprojekt

  1. Nutze den Lebenszyklus von `run.status`: Überprüfe nicht nur auf `completed`. `requires_action` ist dein Freund, kein Fehler. Baue solide Handler für jeden relevanten Status.
  2. Überprüfe `tool_calls` auf fehlende Parameter: Anstatt generische Eingabeaufforderungen zu verwenden, gehe in `run.required_action.submit_tool_outputs.tool_calls` hinein, um *genau* zu verstehen, was der Assistent benötigt. Dies hebt dein Nutzererlebnis an.
  3. Strategische Verwendung von systemähnlichen Nachrichten: Wenn deine interne Anwendungslogik neue Fakten ableitet oder bestimmten Kontext betonen muss, ziehe in Betracht, „user“-Nachrichten, die diese Fakten repräsentieren, einzufügen. Der Assistent wird sie als Teil der Thread-Historie verarbeiten.
  4. Metadaten für den Anwendungszustand: Während der Assistent möglicherweise nicht direkt Thread- oder Nachrichtenmetadaten für seine Argumentation „liest“, ist es ein leistungsstarker Ort für deine Anwendung, um relevante Zustände zur Konversation zu speichern und abzurufen. Denke an Benutzerpräferenzen, aktuelle Sitzungsvariablen usw.
  5. Teste Randfälle für die Tool-Orchestrierung: Was passiert, wenn ein Tool fehlschlägt? Was, wenn der Nutzer seine Meinung während des Toolaufrufs ändert? Gestalte deine `execute_tool_function` und die Fehlerbehandlung innerhalb der `requires_action`-Schleife sorgfältig.

Der Bau wirklich intelligenter, konversationaler AI-Agenten geht über die Auswahl des neuesten Modells hinaus. Es geht darum, die SDKs, die sie bereitstellen, geschickt zu verwenden, um die Komplexitäten menschlicher Interaktionen zu managen. Die OpenAI Assistants API, mit etwas durchdachtem Engineering rund um ihr Zustandsmanagement und die Tool-Orchestrierung, kann dein Konversationserlebnis wirklich über einfaches Abwechseln hinausheben. Es ist immer noch eine Reise, und ich lerne weiterhin neue Tricks, aber ich hoffe, diese Erkenntnisse ersparen dir einige lange Nächte und kalte Kaffees!

Verwandte Artikel

🕒 Published:

🧰
Written by Jake Chen

Software reviewer and AI tool expert. Independently tests and benchmarks AI products. No sponsored reviews — ever.

Learn more →
Browse Topics: AI & Automation | Comparisons | Dev Tools | Infrastructure | Security & Monitoring

See Also

AgntaiAgntapiBot-1Agntdev
Scroll to Top