Zum Hauptinhalt springen

OpenAI-kompatible API

Premium-Funktion

BasePeak.AI stellt eine OpenAI-kompatible /v1-Schnittstelle bereit, die die offiziellen openai-SDKs für Python und Node ohne Anpassung sprechen. Jede Anfrage richtet sich an einen Ihrer Agenten (ausgewählt über den Header X-BPAI-Agent); das konfigurierte Modell und die Werkzeuge des Agenten treten an die Stelle des Feldes model im Anfragekörper.

Nutzen Sie diese Schnittstelle, um bestehenden OpenAI-Code auf einen Agenten zu richten. Wenn Sie Werkzeugaufrufe, Schrittwechsel und Rückfragen des Agenten benötigen – die diese Schnittstelle bewusst verbirgt –, verwenden Sie stattdessen die Invoke-API.

Basis-URL und Authentifizierung

Jede Plattforminstanz läuft unter ihrem eigenen Hostnamen, etwa https://your-instance.platform.basepeak.ai. Die OpenAI-Schnittstelle liegt unter /v1/…. SDKs akzeptieren dies direkt als base_url bzw. baseURL.

Authentifiziert wird mit einem sk-bpai-…-Bearer-Token mit eingeschränktem Geltungsbereich. Das vollständige Token-Modell steht unter API-Schlüssel. Die minimale Anfrage sieht so aus:

POST /v1/chat/completions HTTP/1.1
Host: your-instance.platform.basepeak.ai
Authorization: Bearer sk-bpai-1-42-…
X-BPAI-Agent: a17jlm7
Content-Type: application/json

{"messages":[{"role":"user","content":"Hallo"}]}

BasePeak.AI entfernt den Authorization-Header nach der Authentifizierung, sodass er weder den nachgelagerten Handler noch Logs erreicht.

Endpunkte

MethodePfadAuthScope-PrüfungHinweise
POST/v1/chat/completionsAPI-Schlüsselagent:chat:<X-BPAI-Agent>Mit und ohne Streaming.
GET/v1/modelsAPI-SchlüsselListet die Modelle der Plattform. Keine Scope-Prüfung.
GET/v1/models/{id}API-SchlüsselEinzelnes Modell.
POST/v1/embeddingsAPI-SchlüsselLeitet an das konfigurierte Embedding-Modell weiter.
POST/v1/audio/transcriptionsAPI-SchlüsselLeitet an das konfigurierte Transkriptionsmodell weiter.

Nur /v1/chat/completions prüft den agentenbezogenen Scope des Schlüssels. Die übrigen Routen akzeptieren jedes gültige sk-bpai-…-Token.

POST /v1/chat/completions

HeaderErforderlichZweck
AuthorizationjaBearer sk-bpai-<user>-<key>-<secret>
X-BPAI-AgentjaAgenten-ID (a17jlm7), Alias oder Thread-ID.
X-BPAI-Thread-IdoptionalEinen bestehenden Thread fortsetzen statt einen neuen anzulegen.
Content-Typejaapplication/json

Die Antwort enthält immer X-BPAI-Thread-Id (bei neuen und fortgesetzten Threads), sodass das SDK den Wert festhalten und beim nächsten Zug zurückgeben kann.

Der Scope wird wörtlich aus X-BPAI-Agent gebildet

Der benötigte Scope lautet agent:chat:<was in X-BPAI-Agent stand>. Da dieser Header auch eine Thread-ID akzeptiert, erhält ein auf agent:chat:a17jlm7 beschränkter Schlüssel dort ein 403 – nötig wäre agent:chat:<thread-id> oder ein Platzhalter.

Senden Sie den Agenten in X-BPAI-Agent und den Thread in X-BPAI-Thread-Id; diese Kombination funktioniert mit einem normalen agentenbezogenen Schlüssel.

Anfragekörper

{
"model": "ignored",
"messages": [
{"role": "system", "content": "Optionaler Systemkontext"},
{"role": "user", "content": "Hallo"}
],
"stream": false
}
  • messages – erforderlich, nicht leer. Der Text der letzten user-Nachricht wird zur Eingabe für den Agenten. Fehlt sie, wird die letzte nicht leere Nachricht beliebiger Rolle verwendet. Der Verlauf wird nicht aus diesem Array rekonstruiert – frühere Züge mitzusenden verschafft dem Agenten keinen Kontext. Der Zustand liegt im Thread; senden Sie für zustandsbehafteten Chat X-BPAI-Thread-Id, siehe Threads.
  • model – wird akzeptiert und ignoriert. Verwendet wird das konfigurierte Modell des Agenten. SDK-Clients, die model="gpt-4" fest verdrahten, funktionieren weiter.
  • streamfalse (oder nicht gesetzt) liefert einen JSON-Umschlag; true schaltet auf das SSE-Chunk-Format von OpenAI um.
  • Weitere Felder (temperature, top_p, tools, …) – werden akzeptiert (unbekannte Felder ignoriert der JSON-Decoder), aber nicht berücksichtigt. Maßgeblich ist das Manifest des Agenten.

messages[].content

Entweder ein JSON-String oder das strukturierte Content-Array von OpenAI:

{"role": "user", "content": [
{"type": "text", "text": "Beschreibe dieses Bild"}
]}

Einträge mit type: text werden aneinandergefügt. Andere Typen (image_url, input_audio) werden stillschweigend übersprungen – diese Schnittstelle ist reiner Text, und Sie erhalten keinen Fehler, der Ihnen sagt, dass ein Bild den Agenten nie erreicht hat. Wege, einem Agenten Dokumente zu geben, stehen unter Dateien.

Antwort ohne Streaming

{
"id": "chatcmpl-fa2c08d9e1b3",
"object": "chat.completion",
"created": 1717459200,
"model": "a17jlm7",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Hallo!"},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
}

Hinweise:

  • model spiegelt die gesendete Agenten-Referenz zurück (das tatsächliche Modell ergibt sich aus der Konfiguration des Agenten und wird hier nicht offengelegt).
  • usage ist aus SDK-Kompatibilität vorhanden, die Token-Zähler sind aber noch nicht angebunden – sie werden als Nullen ausgeliefert. Das ist vorwärtskompatibel: Werte zu füllen ändert das Schema nicht.

Streaming-Wire-Format

Mit stream: true wird auf text/event-stream im Chunk-Format von OpenAI umgeschaltet. Die Reihenfolge ist:

  1. Antwort-HeaderContent-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, X-Accel-Buffering: no (verhindert Pufferung durch Proxys), X-BPAI-Thread-Id: <thread>. Der Status ist immer 200.
  2. Rollen-Chunk – kündigt die Assistenten-Rolle an.
  3. Null oder mehr Inhalts-Chunks – jeder trägt ein Fragment in delta.content.
  4. Null oder mehr SSE-Kommentarzeilen – Ereignisse ohne Inhalt (Werkzeugaufrufe, Rückfragen, Schrittmarken) erscheinen als : <kind>\n\n. SSE-Parser verwerfen Kommentare gemäß Spezifikation; für Betreiber sind sie beim Mitlesen des Rohstroms sichtbar.
  5. Abschluss-Chunk – leeres Delta, finish_reason: "stop".
  6. data: [DONE]\n\n – fehlt dieses Sentinel, endete der Strom unerwartet.

Chunk-Formen

Rollen-Chunk:

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":…,"model":"a17jlm7","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

Inhalts-Chunk:

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":…,"model":"a17jlm7","choices":[{"index":0,"delta":{"content":"Hallo"},"finish_reason":null}]}

Abschluss-Chunk:

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":…,"model":"a17jlm7","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Detail zum Wire-Format. finish_reason ist auf nicht abschließenden Chunks null, niemals der leere String – das OpenAI-Python-SDK deutet "" als Strom-Ende, sodass ein leerer String den Strom abschneiden würde.

Sichtbarkeit für Betreiber

Ereignisse ohne Inhalt (Werkzeugaufrufe, Rückfragen usw.) werden zu SSE-Kommentaren:

: tool_call

: step

Jeder konforme SSE-Parser verwirft sie, sodass der SDK-Iterator nur Inhalt und Abschluss sieht. Beim Mitlesen mit curl -N sind sie sichtbar.

Fehler mitten im Strom

Scheitert der Agent während des Streamings, sendet der Server einen Fehlerrahmen im OpenAI-Format und schließt die Verbindung ohne [DONE]:

data: {"error":{"message":"agent timed out","type":"server_error","code":"agent_error","param":null}}

Das entspricht dem Verhalten von OpenAI. SDKs melden dies als Ausnahme beim Strom-Ende; manuelle Konsumenten erkennen es daran, dass der letzte data:-Rahmen ein error-Feld enthält.

Verbindungsabbruch des Clients

Schließt der Client die Verbindung, erkennt der Server den Schreibfehler beim nächsten Chunk, beendet das Streaming und bricht den zugrunde liegenden Agentenlauf ab. Es werden weder ein Abschluss-Chunk noch [DONE] gesendet – die Verbindung ist bereits fort. Die Verbindung zu schließen ist damit der Weg, einen Lauf clientseitig abzubrechen.

Fehler

Fehler vor dem Streaming

Werden als einzelner JSON-Umschlag mit passendem Statuscode geliefert, auch wenn der Anfragekörper stream: true enthielt (die SSE-Header sind dann noch nicht gesendet).

{
"error": {
"message": "messages array is required and must not be empty.",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request"
}
}
StatuscodeWann
400invalid_requestBody nicht parsebar, messages fehlt, Inhalt nicht extrahierbar.
400missing_agent_headerX-BPAI-Agent leer.
401invalid_api_keyKein Bearer oder kein sk-bpai-…-Token. Ein fehlerhaftes oder unbekanntes sk-bpai-…-Token wird vorher abgelehnt, als application/problem+json – siehe API-Schlüssel – Fehlerbehebung.
403agent_forbiddenDas Konto des Schlüssels hat keinen Zugriff auf den Agenten. Ein Scope-Fehler liefert ebenfalls 403, aber als application/problem+json (authz/scope-missing) statt in diesem Umschlag.
404model_not_foundAgent nicht auflösbar (Alias unbekannt, ID falsch geschrieben).
500internal_errorServerseitig: Datenbankausfall bei der Agentenauflösung, Fehler beim Start des Laufs.
502agent_error (nur ohne Streaming)Der Agentenlauf scheiterte. Teilausgaben werden verworfen, dies kann also auch auftreten, nachdem der Agent schon Inhalt erzeugt hatte.

Fehler mitten im Strom

Nur bei stream: true. Als ein SSE-Datenrahmen mit derselben Umschlagstruktur, ohne Abschluss-Chunk und ohne [DONE]. Siehe Streaming-Wire-Format.

Unterschiede zur OpenAI-API

AspektOpenAIBasePeak.AI /v1
Auth-HeaderAuthorization: Bearer sk-…Authorization: Bearer sk-bpai-…
ModellauswahlFeld model im BodyAgentenauswahl über X-BPAI-Agentmodel wird ignoriert.
Zustand über mehrere ZügeZustandslos; der Client sendet den vollen VerlaufServerseitigX-BPAI-Thread-Id setzt fort.
stream:trueChunked SSEGleiche Struktur, byteweise kompatibel mit den SDK-Iteratoren.
WerkzeugaufrufeFunction-Call-Deltas in choices[].delta.tool_callsAls SSE-Kommentare, für SDK-Parser unsichtbar; nicht als Deltas.
Token-Verbrauch im StromOptionales usage im Abschluss-ChunkNoch nicht gefüllt; Feld reserviert.
FehlerumschlägeGemischt (HTTP-Fehler und Fehler-Chunks)Gleiche Struktur; Fehler im Strom entsprechen dem OpenAI-Format.

Beispiele

Python – ohne Streaming

from openai import OpenAI

client = OpenAI(
api_key="sk-bpai-1-42-…",
base_url="https://your-instance.platform.basepeak.ai/v1",
default_headers={"X-BPAI-Agent": "a17jlm7"},
)

raw = client.chat.completions.with_raw_response.create(
model="ignored",
messages=[{"role": "user", "content": "Hauptstadt von Deutschland?"}],
)
thread = raw.headers.get("x-bpai-thread-id") # Antwort-Header
resp = raw.parse() # der ChatCompletion-Body
print(resp.choices[0].message.content)

Python – mit Streaming

from openai import OpenAI

client = OpenAI(api_key="…", base_url="…/v1",
default_headers={"X-BPAI-Agent": "a17jlm7"})

stream = client.chat.completions.create(
model="ignored",
messages=[{"role": "user", "content": "Streame mir eine Antwort."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()

Python – Thread fortsetzen

# Zug 1 – neuer Thread.
raw1 = client.chat.completions.with_raw_response.create(
model="ignored",
messages=[{"role": "user", "content": "Was ist die Hauptstadt von Deutschland?"}],
)
thread = raw1.headers.get("x-bpai-thread-id") # Antwort-Header
r1 = raw1.parse()

# Zug 2 – Thread fortsetzen; der Agent erinnert sich an Zug 1.
client2 = OpenAI(api_key="…", base_url="…/v1",
default_headers={"X-BPAI-Agent": "a17jlm7",
"X-BPAI-Thread-Id": thread})
r2 = client2.chat.completions.create(
model="ignored",
messages=[{"role": "user", "content": "Und die von Frankreich?"}],
)
print(r2.choices[0].message.content) # „Die Hauptstadt von Frankreich ist Paris.“

curl – Streaming, zeilenweise

curl -N \
-H "Authorization: Bearer sk-bpai-1-42-…" \
-H "X-BPAI-Agent: a17jlm7" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Streame mir ein Haiku"}],"stream":true}' \
https://your-instance.platform.basepeak.ai/v1/chat/completions

Ausgabe (gekürzt):

data: {"id":"chatcmpl-…","choices":[{"delta":{"role":"assistant"},…}]}

data: {"id":"chatcmpl-…","choices":[{"delta":{"content":"Kirsch"},…}]}

data: {"id":"chatcmpl-…","choices":[{"delta":{"content":"blüten"},…}]}

: step

data: {"id":"chatcmpl-…","choices":[{"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Node – mit Streaming

import OpenAI from "openai";

const client = new OpenAI({
apiKey: "sk-bpai-1-42-…",
baseURL: "https://your-instance.platform.basepeak.ai/v1",
defaultHeaders: { "X-BPAI-Agent": "a17jlm7" },
});

const stream = await client.chat.completions.create({
model: "ignored",
messages: [{ role: "user", content: "Streame mir einen Fakt." }],
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
process.stdout.write("\n");

Weiterführende Dokumentation