Verständnis von Elementen und Nachrichten in der Response-API von OpenAI
Erklärt, wie die Responses API von OpenAI die Ausgaben des Modells in Elemente statt in Nachrichten umorganisiert und warum dieser Wandel für die Aufrufung von Tools sowie agierende Arbeitsabläufe wichtig ist.
Chat Completions: Auf Nachrichten ausgerichtet
Chat Completions ist seit Langem das bevorzugte Format für den Austausch mit LLMs. Ein typischer Ablauf sieht so aus:
response = client.chat.completions.create(
model="...",
messages=[
{
"role": "user",
"content": "Explain RAG"
}
]
)
Man sendet eine Liste von Nachrichten, und das Modell gibt die nächste Assistentennachricht zurück. Das ist ziemlich einfach. Die Responses API sieht hingegen von Anfang an etwas anders aus.
response = client.responses.create(
model="...",
input="Explain RAG"
)
Auf den ersten Blick könnte es sich dabei nur um einen anderen Endpunkt handeln, bei dem input anstelle von messages verwendet wird. Doch der eigentliche Unterschied liegt in der zugrunde liegenden Abstraktion, die jede API verwendet. Chat Completions konzentriert sich auf Messages, während die Responses API nach Items und Antworten organisiert ist. Sobald Tools und Agenten ins Spiel kommen, wird dieser Unterschied immer wichtiger.
Ein Gespräch unter Chat Completions ist lediglich ein Array von Nachrichtenobjekten.
messages = [
{
"role": "system",
"content": "Act as a helpful AI assistant."
},
{
"role": "user",
"content": "What is a vector database?"
}
]
Jede Nachricht enthält eine Rolle sowie einen Inhalt.
Die typischen Rollen sind:
systemuserassistant
Man kann den Ablauf wie folgt vorstellen:
Messages (list)-> Model -> Assistant Message
Das Modell nimmt das bisherige Gespräch entgegen und erzeugt die nächste Nachricht des Assistenten. Ein minimales Gesprächsloch sieht so aus:
# Human message appended to the messages list
messages.append({
"role": "user",
"content": user_input
})
response = client.chat.completions.create(
model="...",
messages=messages
)
# AI message appended to the messages list
messages.append(
response.choices[0].message
)
Ihre Anwendung ist dafür verantwortlich, den Nachrichtenverlauf zu verwalten – sei es im Speicher, in einer Datenbank oder auf jede andere von Ihnen gewählte Weise. Jede neue Benachrichtigung des Nutzers wird dieser Liste hinzugefügt, die vollständige Liste wird an das Modell gesendet, und die Antwort des Modells wird wiederum hinzugefügt. Dieses Muster entspricht natürlicherweise der Funktionsweise von Chat-Interfaces.
User Message (str)->
Message History (list[dict])->
Model (llm)->
Assistant Message (str)->
Message History (list[dict])
Für die Erstellung von reinem Text sowie typische Chat-Anwendungen ist diese Konfiguration völlig ausreichend. Doch ihre Grenzen treten zutage, sobald eine mit einem LLM ausgestattete Anwendung mehr als nur Gespräche führen muss.
LLM-Anwendungen = Nicht nur Chat-Anwendungen
Nehmen wir einen solchen Auftrag:
Erkundigen Sie sich nach aktuellen Entwicklungen in der KI, erstellen Sie eine Zusammenfassung der wichtigsten Punkte und senden Sie diese Zusammenfassung in meine Eingangspost.
Diese Anfrage zu erfüllen bedeutet, dass das Modell eine Websuche auslösen und mit einem E-Mail-Dienst verbunden werden muss.
Sofort beinhaltet der Ausführungspfad mehr als nur einen einfachen Austausch zwischen Benutzernachricht und Assistentennachricht.
User Request ->
Model ->
Web Search ->
Search Results ->
Summarize (Model)->
Send Mail ->
Final Response
Komplexere Anwendungen können auf mehreren verschiedenen, miteinander verbundenen Tools beruhen.
Zu diesem Zeitpunkt ist das Modell ein aktiver Teil eines umfassenderen Ausführungspipelines und nicht nur ein Textgenerator. Ein Teil dessen, was es ausgibt, soll niemals den Endnutzer erreichen – er dient ausschließlich der internen Verarbeitung. Das Modell kann ein Tool aufrufen, und das Ergebnis dieses Toolaufrufs muss möglicherweise weiter verarbeitet werden, was wiederum einen weiteren Toolaufruf auslösen kann. Erst wenn all das abgeschlossen ist, gibt das Modell eine endgültige Antwort ab – und selbst diese Antwort muss nicht unbedingt in Form einer Textnachricht vorliegen.
Der Ablauf lässt sich nicht mehr auf Folgendes reduzieren:
Messages (list)-> Model -> Assistant Message
Es gibt nun Zwischenergebnisse sowie auf Anwendungs-Ebene durchgeführte Aktionen, die als Teil des Kontexts gespeichert werden müssen – genau hier wird eine rein auf Nachrichten basierende Abstraktion zu eng.
Nicht jede Modellausgabe = Nachricht
Sobald ein Modell mit Tools verbunden ist, besteht der größte Teil dessen Ausgaben aus Funktionsaufrufen statt aus konversationalen Texten.
Nehmen wir das als Beispiel:
Function Call
Name: get_weather
Arguments:
{
"city": "Bengaluru"
}
Dies ist nichts, was der Benutzer lesen soll – es handelt sich um eine Anweisung an Ihre Anwendung. Ihr Code ruft die entsprechende Funktion auf und gibt das Ergebnis zurück an das Modell, wobei dieses Ergebnis wiederum für den Benutzer bestimmt sein kann oder auch nicht.
In der Praxis kann ein Modell während eines Laufs mindestens zwei verschiedene Arten von Ausgaben erzeugen:
- Funktionsaufruf
- Nachricht
Beides unter dem einzigen Begriff „Assistentennachricht“ zusammenzufassen, spiegelt nicht wider, was tatsächlich während der Ausführung geschieht. Diese Diskrepanz ist das zentrale Designproblem, das die Responses API beheben will.
Responses API: Eine andere Abstraktion
Anstatt um den Austausch von Nachrichten organisiert zu sein, basiert die Responses API auf dem Konzept einer Antwort, die mehrere Ausgabenelemente zusammenfassen kann.
response = client.responses.create(
model="...",
input="Explain LangGraph"
)
print(response.output)
# response.output is a list of output items.
Bei einer einfachen Textanfrage könnte diese Ausgabe nur eine einzige Nachricht sein. Bei Anwendungen mit Agenten oder Tools kann hingegen eine Antwort verschiedene Elementtypen enthalten. Eine vereinfachte Darstellung dieser Struktur sieht so aus:
Response
| Reasoning Item
| Function Call Item
| Message Item
In diesem Modell ist eine Nachricht nur einer von mehreren möglichen Ausgabetypen und nicht das Ganze dessen, was eine Antwort darstellt.
Unterschied:
Unterschied:
Messages (list)-> Model -> Assistant Message
Chat-Completions
Input -> Model -> Response
Response:
| Output Item
| Output Item
| Output Item
Responses API
Mit Chat Completions wird das Gespräch selbst als Modellobjekt verwendet, während die Responses API die Ausführung des Modells als eine aus Ausgabenelementen bestehende Antwort darstellt. Bei einer einfachen Textergänzung spielt dieser Unterschied kaum eine Rolle. Er wird bedeutend, sobald Werkzeuge, reasoning-fähige Modelle oder agierende Workflows zum Einsatz kommen.
Nachrichten vs. Elemente
Der eigentliche Unterschied zwischen den beiden APIs zeigt sich darin, wie jede von ihnen ihre Antwort strukturiert.
In Chat Completions dreht sich alles um die Nachricht.
print(response.choices[0].message.content)
# The generated text is inside the assistant message.
# response
# | choices
# | message
# | content
Die Responses API organisiert ihre Ausgabe anders.
print(response.output)
# A simplified structure:
# response
# | output
# | reasoning
# | function_call
# | message
# The important difference is that output is not a list of messages,
# but output items.
Eine Nachricht ist nur eine Art von Element; eine Funktionsaufruf ist eine andere Art; und Modelle, die Reasoning unterstützen, können ebenfalls Reasoning-Elemente erzeugen. Dies verändert die Sichtweise darauf, was ein Modell tatsächlich zurückgibt.
Chat Completions
Model Output = Assistant Message
Responses API
Model Output = List of Output Items
- The structure which is useful for tool calling.
Werkzeugaufrufe als Ausgabenelemente
Nehmen Sie eine einfache Funktion, die das Wetter anzeigt (das klassische Beispiel, das überall verwendet wird).
def get_weather(city: str):
return f"The weather in {city} is 28°C"
# The weather is ofcoure hardcoded.
Sie können diese Funktion als Tool-Definition dem Modell zur Verfügung stellen.
tools = [
{
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"]
}
}
]
Diese Tool-Definition wird zusammen mit Ihrer Anfrage übermittelt.
response = client.responses.create(
model="...",
tools=tools,
input="What is the weather in Bengaluru?"
)
Von hier aus hat das Modell zwei mögliche Vorgehensweisen.
Option 1: Generate a message
Option 2: Call get_weather
Da der tatsächliche Wetterwert der Funktion nicht in ihrer Definition enthalten ist, benötigt das Modell aktuelle Daten und sendet daher einen Funktionsaufruf anstelle einer direkten Antwort.
Function Call Item
name: get_weather
arguments:
{
"city": "Bengaluru"
}
Den Funktionsaufruf können Sie finden, indem Sie durch die Ausgabeelemente der Antwort iterieren.
for item in response.output:
if item.type == "function_call":
print(item.name)
print(item.arguments)
Dadurch erhalten Sie:
get_weather
{"city":"Bengaluru"}
Zu diesem Zeitpunkt hat das Modell noch keine endgültige Antwort ergeben, es hat lediglich eine Aktion angefordert; die Ausführung der Funktion selbst liegt in Ihrer Anwendung.
result = get_weather("Bengaluru")
Das Ergebnis muss anschließend wieder an das Modell zurückgesendet werden.
Ausgabe der Funktionsaufruf
Das Ergebnis eines Tools wird mit einem Element vom Typ function_call_output dargestellt.
tool_output = {
"type": "function_call_output",
"call_id": item.call_id,
"output": result
}
Das Feld call_id verknüpft diese Ausgabe mit dem spezifischen Funktionsaufruf, der sie angefordert hat.
Function Call
| call_id: call_123
Application Executes Tool
Function Call Output
| call_id: call_123
Diese Zuordnung wird unerlässlich, sobald mehrere Tools gleichzeitig aufgerufen werden, beispielsweise eine Anfrage nach dem Wetter in zwei verschiedenen Städten gleichzeitig.
Der grundlegende Ausführungszyklus für Tools
Anwendungen, die auf Tools basieren, folgen in der Regel einem wiederholten Zyklus.
User Input ->
Model ->
Response Output Items ->
Check for Function Calls ->
Execute Functions ->
Create Function Call Outputs ->
Model ->
Final Response
So sieht das in etwa im Code aus.
response = client.responses.create(
model="...",
input=user_input,
tools=tools
)
while True:
function_calls = [
item
for item in response.output
if item.type == "function_call"
]
if not function_calls:
break
tool_outputs = []
for call in function_calls:
result = execute_tool(
call.name,
call.arguments
)
tool_outputs.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": result
})
response = client.responses.create(
model="...",
previous_response_id=response.id,
input=tool_outputs,
tools=tools
)
Der Zyklus setzt sich solange fort, wie das Modell weiterhin Funktionsaufrufe zurückgibt. Sobald es keine Tool-Aufrufe mehr anfordert, enthält die Antwort die endgültige Ausgabe des Modells.
Model ->
Function Call ->
Tool Result ->
Model ->
Function Call ->
Tool Result ->
Model ->
Message
Diese Schleife bildet die Grundlage für die meisten Implementierungen von tool-basierten Agenten.
Fazit
Die Responses API ist nicht einfach nur eine neu benannte Schnittstelle zu Chat Completions. Sie bietet eine Struktur, die genauer widerspiegelt, wie moderne LLM-Anwendungen funktionieren, wenn Tools, logisches Denken und mehrere Ausgabetypen involviert sind. Chat Completions bleibt eine solide Wahl für einfache konversationelle Anwendungsfälle. Sobald Ihr Workflow jedoch agentenbasiert wird, eignet sich die Responses API besser. Nachrichten kümmern sich um die Konversation; Elemente kümmern sich um die Ausführung. Das ist die ganze Idee.
Zusätzliche Literatur
- Verständnis von KI-Agenten: Ziele, Werkzeuge, Speicher und der Agentenzyklus — Eine für Anfänger geeignete Erklärung, wie sich KI-Agenten von Chatbots unterscheiden, mit Schwerpunkt auf den Kernkomponenten, dem Entscheidungszyklus, den Autonomieebenen sowie praktischen Anwendungsfällen.
- Strukturelle Schutzmechanismen für KI-Agenten: Im Inneren des ResolveFlow-Pipelines — Erklärt, wie ein auf LangGraph basierender Agent durch Prüfungen auf Codeebene statt durch Prompt-Anweisungen eine Trennung zwischen Denken und Ausführen gewährleistet, einschließlich eines auftretenden Abruffehlers im Laufe des Prozesses.