OpenAI-kompatible API
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
| Methode | Pfad | Auth | Scope-Prüfung | Hinweise |
|---|---|---|---|---|
POST | /v1/chat/completions | API-Schlüssel | agent:chat:<X-BPAI-Agent> | Mit und ohne Streaming. |
GET | /v1/models | API-Schlüssel | – | Listet die Modelle der Plattform. Keine Scope-Prüfung. |
GET | /v1/models/{id} | API-Schlüssel | – | Einzelnes Modell. |
POST | /v1/embeddings | API-Schlüssel | – | Leitet an das konfigurierte Embedding-Modell weiter. |
POST | /v1/audio/transcriptions | API-Schlüssel | – | Leitet 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
Header
| Header | Erforderlich | Zweck |
|---|---|---|
Authorization | ja | Bearer sk-bpai-<user>-<key>-<secret> |
X-BPAI-Agent | ja | Agenten-ID (a17jlm7), Alias oder Thread-ID. |
X-BPAI-Thread-Id | optional | Einen bestehenden Thread fortsetzen statt einen neuen anzulegen. |
Content-Type | ja | application/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.
X-BPAI-Agent gebildetDer 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 letztenuser-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 ChatX-BPAI-Thread-Id, siehe Threads.model– wird akzeptiert und ignoriert. Verwendet wird das konfigurierte Modell des Agenten. SDK-Clients, diemodel="gpt-4"fest verdrahten, funktionieren weiter.stream–false(oder nicht gesetzt) liefert einen JSON-Umschlag;trueschaltet 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:
modelspiegelt die gesendete Agenten-Referenz zurück (das tatsächliche Modell ergibt sich aus der Konfiguration des Agenten und wird hier nicht offengelegt).usageist 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:
- Antwort-Header –
Content-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 immer200. - Rollen-Chunk – kündigt die Assistenten-Rolle an.
- Null oder mehr Inhalts-Chunks – jeder trägt ein Fragment in
delta.content. - 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. - Abschluss-Chunk – leeres Delta,
finish_reason: "stop". 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_reasonist auf nicht abschließenden Chunksnull, 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"
}
}
| Status | code | Wann |
|---|---|---|
| 400 | invalid_request | Body nicht parsebar, messages fehlt, Inhalt nicht extrahierbar. |
| 400 | missing_agent_header | X-BPAI-Agent leer. |
| 401 | invalid_api_key | Kein 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. |
| 403 | agent_forbidden | Das 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. |
| 404 | model_not_found | Agent nicht auflösbar (Alias unbekannt, ID falsch geschrieben). |
| 500 | internal_error | Serverseitig: Datenbankausfall bei der Agentenauflösung, Fehler beim Start des Laufs. |
| 502 | agent_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
| Aspekt | OpenAI | BasePeak.AI /v1 |
|---|---|---|
| Auth-Header | Authorization: Bearer sk-… | Authorization: Bearer sk-bpai-… |
| Modellauswahl | Feld model im Body | Agentenauswahl über X-BPAI-Agent – model wird ignoriert. |
| Zustand über mehrere Züge | Zustandslos; der Client sendet den vollen Verlauf | Serverseitig – X-BPAI-Thread-Id setzt fort. |
stream:true | Chunked SSE | Gleiche Struktur, byteweise kompatibel mit den SDK-Iteratoren. |
| Werkzeugaufrufe | Function-Call-Deltas in choices[].delta.tool_calls | Als SSE-Kommentare, für SDK-Parser unsichtbar; nicht als Deltas. |
| Token-Verbrauch im Strom | Optionales usage im Abschluss-Chunk | Noch nicht gefüllt; Feld reserviert. |
| Fehlerumschläge | Gemischt (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
- API-Überblick – die passende Schnittstelle wählen
- API-Schlüssel – Token-Modell, Scopes, Audit
- Invoke-API – Werkzeugaufrufe und Schritte im Strom
- Threads – mehrere Züge und parallele Unterhaltungen
- Dateien – einem Agenten Dokumente geben