Text und Streaming
Textmodelle im offiziellen Format von OpenAI, Anthropic oder Google Gemini aufrufen und dabei Gespräche, Tools, Streams und Kosten sauber handhaben.
Verfügbares Textmodell auswählen
Lesen Sie die Modelle authentifiziert über /api/v1/models?modality=text&includeSchema=1 und senden Sie nur dann eine Anfrage, wenn enabled und available beide true sind. Tools, multimodale Nachrichten, Reasoning-Felder und strukturierte Ausgaben müssen zusätzlich im inputSchema des Modells unterstützt werden. Protokollkompatibilität garantiert nicht jede Modellfunktion.
Dieselbe Modell-ID funktioniert mit jedem der unten aufgeführten Protokolle – unabhängig davon, von welchem Hersteller das Modell stammt. Sie können also ein Modell eines anderen Herstellers im Anthropic-Format aufrufen oder ein Nicht-Google-Modell im Gemini-Format.
Unterstützte Protokolle
Alle vier Textprotokolle teilen sich denselben API-Schlüssel, denselben Modellkatalog sowie Rate Limits, Parametervalidierung und Abrechnung. Unterschiedlich ist nur das Format von Anfrage und Antwort. Nehmen Sie einfach das Protokoll, das Ihr bestehender Code oder Ihr SDK bereits spricht.
| Protokoll | Endpunkt | Header für den Schlüssel |
|---|---|---|
| OpenAI Chat Completions | POST /v1/chat/completions | Authorization: Bearer |
| OpenAI Responses | POST /v1/responses | Authorization: Bearer |
| Anthropic Messages | POST /v1/messages | x-api-key oder Authorization: Bearer |
| Google Gemini | POST /v1beta/models/{model}:generateContent | x-goog-api-key oder Authorization: Bearer |
| Google Gemini Streaming | POST /v1beta/models/{model}:streamGenerateContent | x-goog-api-key oder Authorization: Bearer |
| Nativer Stream | POST /api/v1/jobs/stream | Authorization: Bearer |
Antworten und Fehler unter /v1 und /v1beta folgen dem jeweiligen Protokoll und kommen ohne das native Envelope {code,msg,data,request_id}. Parsen Sie sie daher nicht mit einem Client, der ausschließlich body.data liest. Im Gemini-Format steht die Modell-ID im URL-Pfad; Schrägstriche in der ID bleiben unverändert und müssen nicht kodiert werden.
Der Schlüssel gehört ausschließlich in einen Request-Header. x-api-key und x-goog-api-key gelten nur unter /v1 und /v1beta; die native API unter /api/v1 akzeptiert nur Authorization: Bearer. Ein Schlüssel im Query-Parameter ?key= wird grundsätzlich nicht akzeptiert: Steht ein Schlüssel einmal in der URL, bleibt er in Browserverlauf, Proxyservern und Zugriffslogs zurück. Die offiziellen SDKs übertragen den Schlüssel ohnehin per Header und sind davon nicht betroffen.
Mit den offiziellen SDKs anbinden
Ersetzen Sie nur Basis-URL und Schlüssel; ansonsten verwenden Sie jedes SDK wie gewohnt.
| SDK | Basis-URL |
|---|---|
OpenAI (openai) | https://api.spicyapi.ai/v1 |
Anthropic (anthropic) | https://api.spicyapi.ai |
Google GenAI (google-genai) | https://api.spicyapi.ai |
Die SDKs von Anthropic und Google hängen /v1 bzw. /v1beta selbst an, deshalb enthält ihre Basis-URL keinen Versionspfad. Beim OpenAI-SDK gehört /v1 dagegen zur Basis-URL. Alle drei Python-Beispiele lesen SPICY_API_KEY und das aus dem Live-Katalog gewählte SPICY_MODEL aus Umgebungsvariablen.
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai/v1")
reply = client.chat.completions.create(
model=os.environ["SPICY_MODEL"],
messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(reply.choices[0].message.content)import os
import anthropic
client = anthropic.Anthropic(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai")
message = client.messages.create(
model=os.environ["SPICY_MODEL"],
max_tokens=256,
messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(message.content[0].text)import os
from google import genai
from google.genai import types
client = genai.Client(
api_key=os.environ["SPICY_API_KEY"],
http_options=types.HttpOptions(base_url="https://api.spicyapi.ai"),
)
response = client.models.generate_content(
model=os.environ["SPICY_MODEL"],
contents="Explain a rainbow in one sentence.",
)
print(response.text)Damit Wiederholungsversuche nach Netzwerkproblemen nicht doppelt berechnet werden, erzeugen Sie je Nutzeraktion einen Idempotency-Key, speichern ihn dauerhaft und senden ihn über die Option des SDKs für eigene Header mit (bei den SDKs von OpenAI und Anthropic heißt sie extra_headers).
Parameterzuordnung je Protokoll
Felder der Protokollebene werden zuerst auf die einheitlichen Parameternamen der Plattform abgebildet und danach gegen das inputSchema des gewählten Modells validiert. Parameter, die das Modell nicht unterstützt, führen zu 400; sie werden nicht stillschweigend ignoriert und trotzdem abgerechnet. Die Tabelle zeigt nur die Zuordnung, „—“ bedeutet, dass es im Protokoll kein entsprechendes Feld gibt. Ob ein bestimmtes Modell einen Parameter annimmt und welcher Wertebereich gilt, legt sein inputSchema fest.
| Einheitlicher Parameter | Chat Completions | Responses | Messages | Gemini |
|---|---|---|---|---|
messages | messages | instructions, input | system, messages | systemInstruction, contents |
max_tokens | max_tokens, max_completion_tokens | max_output_tokens | max_tokens | generationConfig.maxOutputTokens |
temperature | temperature | temperature | temperature | generationConfig.temperature |
top_p | top_p | top_p | top_p | generationConfig.topP |
tools | tools | tools (Typ function) | tools (benutzerdefinierte Tools) | tools[].functionDeclarations |
tool_choice | tool_choice | tool_choice | tool_choice | toolConfig.functionCallingConfig |
response_format | response_format | text.format | — | generationConfig.responseMimeType plus responseSchema oder responseJsonSchema |
reasoning_effort | reasoning_effort | reasoning.effort | — | generationConfig.thinkingConfig |
Auch Felder, die nicht in der Tabelle stehen, werden auf einheitliche Parameter abgebildet und für das Modell validiert; was im inputSchema fehlt, führt zu 400. Akzeptiert ein Modell etwa kein stop, führen stop bei Chat, stop_sequences bei Messages und generationConfig.stopSequences bei Gemini jeweils zu 400. Derzeit führt kein Textmodell seed in seinem inputSchema, daher führen auch seed bei Chat und generationConfig.seed bei Gemini zu 400.
Einige Ausnahmen:
- Felder, die nur auf der Ursprungsplattform des Protokolls eine Bedeutung haben, werden ignoriert und wirken sich nicht auf die Generierung aus: bei Chat
user,metadataundstore, bei Responsesuser,metadata,storeundinclude, bei Messagesmetadata. - Responses unterstützt
previous_response_idnicht; übergeben Sie den vollständigen Verlauf ininput. - Bei Gemini wird
safetySettingsangenommen, bleibt aber wirkungslos.cachedContentsowie serverseitig ausgeführte Tools wiegoogleSearchundcodeExecutionwerden nicht unterstützt und führen zu 400. Bilder übergeben Sie perinlineData(base64) oderfileData.fileUri(HTTPS-URL); ob ein Modell Bilder annimmt, legt sein inputSchema fest.
Chat-Stream senden
Setzen Sie serverseitig SPICY_API_KEY, SPICY_MODEL aus dem Live-Katalog und einen gespeicherten SPICY_IDEMPOTENCY_KEY für diesen Vorgang. Das Beispiel benötigt jq. Sie können den Prompt ersetzen; die Eingabefelder müssen weiterhin dem aktuellen Schema entsprechen.
jq -n --arg model "$SPICY_MODEL" '{
model: $model,
messages: [{role: "user", content: "Explain a rainbow in one sentence."}],
stream: true
}' > chat-request.json
curl --fail-with-body --no-buffer --max-time 120 \
https://api.spicyapi.ai/v1/chat/completions \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
--data-binary @chat-request.jsonGemini-Anfrage senden
Es gelten dieselben Umgebungsvariablen wie im vorigen Abschnitt. Die Modell-ID steht in der URL, der Request-Body enthält kein model. Setzen Sie die URL in doppelte Anführungszeichen und schreiben Sie ${SPICY_MODEL}, damit die Shell den nachfolgenden Doppelpunkt nicht als Variablenmodifikator interpretiert.
jq -n '{
systemInstruction: {parts: [{text: "Answer in one sentence."}]},
contents: [{role: "user", parts: [{text: "Explain a rainbow."}]}],
generationConfig: {temperature: 0.7, maxOutputTokens: 256}
}' > gemini-request.json
curl --fail-with-body --no-buffer --max-time 120 \
"https://api.spicyapi.ai/v1beta/models/${SPICY_MODEL}:streamGenerateContent?alt=sse" \
-H "x-goog-api-key: $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
--data-binary @gemini-request.jsonOhne Streaming ersetzen Sie :streamGenerateContent?alt=sse durch :generateContent und lesen die Antwort aus candidates[0].content.parts[].text; der Verbrauch steht in usageMetadata. Fehlt bei einer Streaming-Anfrage alt=sse, kommt die Antwort als JSON-Array, das schrittweise geschrieben wird. Die offiziellen SDKs setzen alt=sse selbst.
Gespräche und Tool-Aufrufe
Lesen Sie bei Chat-Antworten ohne Stream choices[0].message, bei SSE choices[].delta pro vollständigem Ereignis. TCP-Fragmente sind keine JSON-Grenzen. Tool-Argumente können über mehrere Chunks verteilt ankommen: Setzen Sie tool_calls nach index vollständig zusammen, bevor Sie sie prüfen und ausführen. Bewahren Sie die assistant-Nachricht auf und geben Sie die Ergebnisse mit der passenden tool_call_id zurück. Responses verlangt die gesamte Historie in input und unterstützt previous_response_id nicht. Funktionsergebnisse werden als function_call_output mit der passenden call_id zurückgegeben. Messages verwendet text-, tool_use- und tool_result-Blöcke in content; das Chat-Nachrichtenformat lässt sich dort nicht direkt übernehmen.
Bei Gemini wechseln sich in contents die beiden Rollen user und model ab. Möchte das Modell ein Tool aufrufen, gibt es einen functionCall-Part zurück. Nach der Ausführung verpacken Sie das Ergebnis in einen functionResponse-Part und senden ihn im nächsten user-Eintrag zurück; name muss mit dem des Aufrufs übereinstimmen.
Abschluss, Fehler und Verbindungsabbruch
| Protokoll | Regulärer Abschluss | Verbrauch |
|---|---|---|
| Chat Completions | data: [DONE] | usage im letzten Chunk |
| Responses | Ereignis response.completed | response.usage im Ereignis |
| Messages | message_stop nach message_delta | usage in message_delta |
| Gemini | Letztes Ereignis mit finishReason, kein [DONE] | usageMetadata |
Prüfen Sie vor Beginn des Streams HTTP-Status und Content-Type. Fehler kommen als JSON im Format des jeweiligen Protokolls:
| Protokoll | Fehler-Body |
|---|---|
| Chat Completions, Responses | {"error": {"message", "type", "param", "code"}} |
| Messages | {"type": "error", "error": {"type", "message"}} |
| Gemini | {"error": {"code", "message", "status"}} |
Die HTTP-Statuscodes sind in allen vier Protokollen identisch. Bei Gemini enthält status die Statusnamen von Google: 400 ist INVALID_ARGUMENT, 401 UNAUTHENTICATED, 402 FAILED_PRECONDITION, 403 PERMISSION_DENIED, 404 NOT_FOUND, 429 RESOURCE_EXHAUSTED und 503 UNAVAILABLE. Werten Sie im Code Statuscode und Fehlertyp aus, nicht den Text in message.
Tritt nach Beginn des Streams ein Fehler auf, senden Chat und Gemini ein Ereignis mit einem error-Objekt, Responses ein error-Ereignis und Messages event: error. Ihr Client muss diese Fehlerereignisse ebenso behandeln wie Timeouts und vorzeitige Verbindungsabbrüche. Deaktivieren Sie Proxy-Pufferung. Das Schließen des Browsers oder ein AbortSignal beendet nur den Empfang; das bedeutet weder eine Stornierung noch eine zugesagte Erstattung. Trennt der Client die Verbindung selbst, wird bei verlässlichen Verbrauchsdaten auf dem Server trotzdem der tatsächlich angefallene Verbrauch abgerechnet. Fehlen Verbrauchsdaten, darf das Schließen der Verbindung nicht als kostenloser Abschluss gewertet werden.
Angebote und Abrechnung
Sollen Nutzer das genaue Angebot und den Höchstbetrag vorab bestätigen, verwenden Sie die native Abfolge jobs/quote → jobs/stream mit identischem model/input sowie quoteId und expectedCost. /v1 und /v1beta akzeptieren diese nativen Angebotsfelder nicht und verwenden den Preis bei Annahme. Maßgeblich sind Aufgaben- und Abrechnungsdaten, nicht das Text-Endsignal. reasoning_tokens ist bereits in completion_tokens enthalten und darf nicht nochmals addiert werden.
Weitere Anfrageformate
Diese Beispiele zeigen Protokollstrukturen. Ersetzen Sie MODEL_ID_FROM_CATALOG durch ein aktuelles Modell, das die Felder unterstützt; senden Sie keinen Platzhalter.
{
"model": "MODEL_ID_FROM_CATALOG",
"input": "Explain a rainbow in one sentence.",
"stream": false
}{
"model": "MODEL_ID_FROM_CATALOG",
"max_tokens": 256,
"messages": [{"role": "user", "content": "Explain a rainbow in one sentence."}],
"stream": false
}Weiterlesen
Kostenschätzungen und Protokollkompatibilität · Abrechnung · Integration für den Produktivbetrieb · Fehlerdiagnose und Wiederherstellung

