Models
Ein Agent braucht ein Sprachmodell, um zu arbeiten. OpenBot enthält kein Modell. Du verbindest eins. OpenBot speichert dafür zwei Arten von Einträgen:
- Ein Model beschreibt ein Sprachmodell, zum Beispiel
gpt-5: seinen Namen, seine Optionen und seine Grenzen. Ein Model gehört nicht zu einem Anbieter. - Ein Model Provider (Modell-Anbieter) ist ein Dienst für Modelle mit seinem API-Schlüssel. Ein Anbieter verknüpft die Models, die er anbietet. Eine Verknüpfung kann manche Werte des Model nur für diesen Anbieter ändern.
Du fügst beide über die API hinzu. OpenBot speichert sie in der Datenbank. So nutzt jede Instanz dieselben.
Die Platform oder eine Organization besitzt jedes Model und jeden Anbieter:
| Besitzer | Wer es hinzufügt | Wer es nutzen kann |
|---|---|---|
| Organization | Eine Organization, mit ihrem eigenen Schlüssel | Nur diese Organization. Das heißt “Bring your own key” (bring deinen eigenen Schlüssel mit). |
| Platform | Ein Admin der Platform, mit dem Schlüssel der Platform | Die Organizations in ihrer availability. Siehe Platform. |
Eine Organization kann ein Model eines Anbieters nur nutzen, wenn sie sowohl den Anbieter als auch das Model nutzen darf.
Jedes Model hat einen von zwei Typen:
- Ein
language-Model schreibt Text und ruft Werkzeuge auf. Es führt Sessions aus, beurteilt Rules und lernt Agent-Memory. - Ein
decision-Model wählt nur eine Antwort aus einer festen Liste. Es schreibt keinen Text. Jev von TypeSafe ist ein Beispiel. Siehe Decision-Model.
Wenn du OpenBot aus Code startest, kannst du auch eingebaute Models angeben. Siehe Eingebaute Models.
Ein Model hinzufügen
POST /v1/organization/models fügt deiner Organization ein Model hinzu. Es braucht model_providers.write.
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "id": "qwen3-32b", "name": "Qwen3 32B", "reasoningEfforts": [], "serviceTiers": [], "contextWindow": 131072 }' \ https://api.example.com/v1/organization/models| Feld | Regeln |
|---|---|
id |
Pflicht. 1 bis 200 Zeichen. Sie ist für ihren Besitzer eindeutig, und du kannst sie nicht ändern. Ein Anbieter schickt sie als Namen des Modells, außer die Verknüpfung setzt upstream. |
name |
Pflicht. Ein Name für Menschen. |
reasoningEfforts |
Pflicht. Die Werte für reasoningEffort, die das Model annimmt. Kann leer sein. |
serviceTiers |
Pflicht. Die Werte für serviceTier, die das Model annimmt. Kann leer sein. |
contextWindow |
Optional. Die Größe des Kontextfensters, in Tokens. |
maxOutputTokens |
Optional. Die meisten Tokens, die das Model in einer Antwort schreiben kann. |
Wenn deine Organization schon ein Model mit dieser id hat, gibt OpenBot 409 mit ModelConflict zurück.
| Route | Was sie tut |
|---|---|
GET /v1/organization/models |
Listet die Models deiner Organization auf. Geteilte Models sind nicht in dieser Liste. |
GET /v1/organization/models/:modelId |
Liest ein Model. |
PATCH /v1/organization/models/:modelId |
Ändert ein Model. Der Body braucht expectedVersion. Schicke null für eine Grenze, um sie zu entfernen. |
DELETE /v1/organization/models/:modelId |
Entfernt ein Model. |
Lesen braucht model_providers.read. Änderungen brauchen model_providers.write.
Du kannst ein Model nicht entfernen, solange einer deiner Anbieter es verknüpft. OpenBot gibt 409 mit ModelInUse zurück. Entferne zuerst die Verknüpfung.
Einen Anbieter hinzufügen
POST /v1/organization/model_providers fügt deiner Organization einen Anbieter hinzu. Es braucht model_providers.write.
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "kind": "openai-compat", "name": "Local vLLM", "endpoint": "https://llm.example.com/v1", "apiKey": "sk-...", "models": [ { "model": "qwen3-32b", "modelOwner": "organization", "upstream": "Qwen/Qwen3-32B" } ] }' \ https://api.example.com/v1/organization/model_providers| Feld | Regeln |
|---|---|
kind |
Pflicht. Die API, die der Dienst spricht: openai, anthropic oder openai-compat. Siehe die Tabelle unten. |
name |
Pflicht. 1 bis 120 Zeichen. |
endpoint |
Pflicht für openai-compat. Optional für die anderen. Eine Basis-URL mit http oder https, oft mit /v1 am Ende. Kein Nutzername, kein Passwort, keine Query und kein Teil mit #. |
apiKey |
Pflicht. OpenBot speichert ihn verschlüsselt und schickt ihn nur an den Endpoint. |
models |
Pflicht. 0 bis 200 Verknüpfungen. Die Reihenfolge ist die Reihenfolge der Liste der Models. |
kind |
API |
|---|---|
openai |
OpenAI Responses API. |
anthropic |
Anthropic Messages API. |
openai-compat |
OpenAI Chat Completions API und Dienste, die sie nachbauen. |
Jede Verknüpfung hat diese Felder:
| Feld | Regeln |
|---|---|
model |
Pflicht. Die id des Model. Ein Anbieter verknüpft jede Model-ID nur einmal. |
modelOwner |
Pflicht. organization für ein Model deiner Organization oder platform für ein geteiltes Model. |
upstream |
Optional. Der Name des Modells, den der Dienst bekommt, wenn er nicht die id des Model ist. |
reasoningEfforts |
Optional. Ersetzt die reasoningEfforts des Model für diesen Anbieter. |
serviceTiers |
Optional. Ersetzt die serviceTiers des Model für diesen Anbieter. |
contextWindow |
Optional. Ersetzt das contextWindow des Model für diesen Anbieter. |
maxOutputTokens |
Optional. Ersetzt maxOutputTokens des Model für diesen Anbieter. |
Ein Anbieter deiner Organization kann deine eigenen Models verknüpfen und die geteilten Models, die die Platform dir zur Verfügung stellt. Für jedes andere Model gibt OpenBot 404 mit ModelNotFound zurück.
OpenBot antwortet mit dem Anbieter. Seine id beginnt mit prov_. Eine Anbieter-ID ist in der ganzen Platform eindeutig. Eine Auswahl eines Model nennt sie als provider.
Der API-Schlüssel kommt nie zurück. Die Antwort zeigt stattdessen apiKey: { set, hint }. Siehe Platform.
Einen Anbieter ändern oder entfernen
| Route | Was sie tut |
|---|---|
GET /v1/organization/model_providers |
Listet die Anbieter auf. |
GET /v1/organization/model_providers/:providerId |
Liest einen Anbieter. |
PATCH /v1/organization/model_providers/:providerId |
Ändert einen Anbieter. |
DELETE /v1/organization/model_providers/:providerId |
Entfernt einen Anbieter. |
Lesen braucht model_providers.read. Änderungen brauchen model_providers.write.
Der Body von PATCH braucht expectedVersion, also die version, die du gelesen hast. Wenn jemand den Anbieter geändert hat, nachdem du ihn gelesen hast, gibt OpenBot 409 mit ModelProviderConflict zurück. Lass apiKey weg, um den aktuellen Schlüssel zu behalten. Schicke models, um alle Verknüpfungen des Anbieters zu ersetzen.
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"expectedVersion":0,"apiKey":"sk-new..."}' \ https://api.example.com/v1/organization/model_providers/prov_4c1e...Der nächste Aufruf eines Modells nach einer Änderung nutzt den neuen Schlüssel, Endpoint oder die neuen Verknüpfungen. OpenBot baut jedes Model einmal und baut es neu, wenn sich sein Anbieter ändert.
Du kannst einen Anbieter nicht entfernen, solange rulesModel, agentMemoryModel oder decisionModel in den Einstellungen der Organization ihn nennt. OpenBot gibt 409 mit ModelProviderInUse zurück. Ändere zuerst die Einstellung.
Ein Model auswählen
GET /v1/organization/model_catalog listet die Models, die deine Organization jetzt nutzen kann. Es braucht models.use.
Jeder Eintrag ist ein Model eines Anbieters, mit den Werten der Verknüpfung schon angewendet. Ein Model, das zwei Anbieter verknüpfen, erscheint zweimal.
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ https://api.example.com/v1/organization/model_catalog{ "providers": [ { "id": "prov_4c1e...", "owner": "organization", "kind": "openai-compat", "name": "Local vLLM" } ], "models": [ { "provider": "prov_4c1e...", "id": "qwen3-32b", "owner": "organization", "name": "Qwen3 32B", "reasoningEfforts": [], "serviceTiers": [], "contextWindow": 131072, "price": { "model": "qwen3-32b", "timeZone": "UTC", "tiers": [ { "upToInputTokens": null, "inputUncached": 400000, "inputCacheRead": 100000, "inputCacheWrite": 0, "output": 1200000, "outputReasoning": null } ], "periods": [], "effectiveFrom": "2026-10-20T08:00:00.000Z" } } ], "decisionModel": null}price ist der aktuelle Preis des Model bei diesem Anbieter. Er fehlt, wenn niemand einen Preis gesetzt hat. Siehe Preise.
Eine Auswahl eines Model nennt den Anbieter und die Model-ID:
{ "_tag": "Config.Selection", "provider": "prov_4c1e...", "model": "qwen3-32b" }OpenBot wählt nie ein Model für dich. Es gibt kein Standard-Model. Jede Nutzung nennt ihr eigenes Model:
- Eine neue Session nutzt
settings.modelaus der Anfrage. Ohne diesen Wert nutzt die Session dasmodelihres Agents. Wenn keiner von beiden ein Model gibt, gibt OpenBot400mitSessionModelInvalidund dem Grundmissingzurück. Dein Main Agent hat am Anfang kein Model. Setze also zuerst seinmodelmitPATCH /v1/agents/:id. - Das Prüf-Modell für Rules nutzt
rulesModelin den Einstellungen der Organization. Wenn esnullist, gibt das Prüf-Modell keine Antwort. Dann entscheidet eine Person oder ein Agent. - Agent-Memory nutzt
agentMemoryModelin denselben Einstellungen. Wenn esnullist, lernen Agents nichts.
rulesModel und agentMemoryModel müssen language-Models sein. Der Katalog nennt den type jedes Model, und eine Session kann nur language-Models wählen.
Ältere Versionen von OpenBot hatten defaultModel und helperModel. Beim Update gibt OpenBot das alte defaultModel jedem Agent, der kein Model hat. rulesModel und agentMemoryModel bekommen das alte Helfer-Modell: helperModel, sonst defaultModel. Wenn beide null waren, bleiben die neuen Werte leer, und du musst sie setzen.
So gibst du zum Beispiel deinem Main Agent ein Model. Schicke seine aktuelle revision als expected:
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" -H 'Content-Type: application/json' \ -d '{"expected":0,"model":{"_tag":"Config.Selection","provider":"prov_4c1e...","model":"qwen3-32b"}}' \ https://api.example.com/v1/agents/$AGENT_IDEine Session kann ihre eigene Auswahl speichern. Schicke die aktuelle Revision der Session als expected:
curl -X PUT -H "Authorization: Bearer $OPENBOT_TOKEN" -H 'Content-Type: application/json' \ -d '{"expected":3,"selection":{"_tag":"Config.Selection","provider":"prov_4c1e...","model":"qwen3-32b"}}' \ https://api.example.com/v1/sessions/$SESSION_ID/modelGET /v1/sessions/:id/models zeigt dieselben Models für eine Session. Alle Felder findest du in der API-Referenz für Sessions.
Um festzulegen, welche Anbieter ein Member auswählen darf, nutze models.use auf openbot.org.<org>.model.<providerId>. Siehe Berechtigungen.
Decision-Model
decisionModel in den Einstellungen der Organization nennt das Decision-Model (Entscheidungs-Model) deiner Organization. Es muss ein decision-Model sein, das einer deiner Anbieter anbietet. Sonst gibt OpenBot 400 mit SettingsInvalid zurück. null heißt: Deine Organization hat kein Decision-Model.
Um zum Beispiel Jev über TypeSafe zu nutzen, füge das Model hinzu, dann einen Anbieter, der es verknüpft, und dann die Einstellung:
curl -H "Authorization: Bearer $OPENBOT_TOKEN" -H "Content-Type: application/json" \ -d '{ "id": "jev-latest", "type": "decision", "name": "Jev", "reasoningEfforts": [], "serviceTiers": [] }' \ https://api.example.com/v1/organization/models
curl -H "Authorization: Bearer $OPENBOT_TOKEN" -H "Content-Type: application/json" \ -d '{ "kind": "typesafe", "name": "TypeSafe", "endpoint": "https://typesafe.example.com/v1", "apiKey": "...", "models": [{ "model": "jev-latest", "modelOwner": "organization" }] }' \ https://api.example.com/v1/organization/model_providers
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" -H "Content-Type: application/json" \ -d '{"expectedVersion":3,"decisionModel":{"_tag":"Config.Selection","provider":"prov_9a2b...","model":"jev-latest"}}' \ https://api.example.com/v1/organization/settingsGET /v1/organization/model_catalog zeigt die Einstellung als decisionModel.
OpenBot ruft einen typesafe-Anbieter mit dem TypeSafe-Client von Effect (@effect/ai-typesafe) auf. Es schickt jede Anfrage an <endpoint>/systemone, mit dem API-Schlüssel als Bearer-Token, und schickt upstream als Namen des Model. OpenBot baut den Client einmal für jeden Anbieter und jedes Model und baut ihn neu, wenn sich der Anbieter ändert.
OpenBot kann ein Decision-Model noch nicht über einen openai-compat-Anbieter fragen. Du kannst es dort verknüpfen, aber nutze einen typesafe-Anbieter, um es zu fragen.
Noch kein Teil von OpenBot fragt das Decision-Model.
Ohne Model arbeiten
Eine Organization ohne Anbieter kann ihre Sessions trotzdem lesen, umbenennen, forken und löschen. Sie kann keine neue Session erstellen, weil eine neue Session ein Model braucht. Siehe Ein Model auswählen.
Arbeit, die ein Modell braucht, schlägt mit dem Status 503 und dem Fehler SessionExecutionUnavailable fehl. Dazu gehört:
- einen Turn starten mit
POST /v1/sessions/:id/turns, - ein Model auswählen mit
PUT /v1/sessions/:id/model, - einen Turn nach einer Entscheidung oder nach delegierter Arbeit fortsetzen.
{ "_tag": "SessionExecutionUnavailable", "session": "..." }Eingebaute Models
Wenn du OpenBot aus deinem eigenen Programm startest, kannst du mit der Option builtinModels Models im Code angeben. Siehe Aus Code starten.
- Jede Organization kann sie nutzen.
- Die API kann sie nicht ändern.
- Sie erscheinen in der Liste der Models neben den gespeicherten Anbietern.
builtinModels hat drei Felder:
| Feld | Was es ist |
|---|---|
providers |
Die Anbieter, die erscheinen sollen, zum Beispiel { id, name }. |
models |
Die Models, die erscheinen sollen, als Werte vom Typ Config.Model. |
resolve |
Eine Funktion, die eine Auswahl bekommt und ein Effect mit einem LanguageModel zurückgibt. |
Dieses Beispiel gibt ein Anthropic-Modell an, mit einem Schlüssel aus der Umgebung deines Programms:
import { NodeRuntime } from "@effect/platform-node"import { Config, Model, Provider } from "@openbot-build/core"import { AnthropicProvider, EnvironmentConfiguration, Server,} from "@openbot-build/openbot"import { Context, Effect, Layer, Redacted } from "effect"import { LanguageModel } from "effect/ai"import { FetchHttpClient } from "effect/http"
const selection = Config.Selection.make({ provider: Provider.Id.make("house"), model: Model.Id.make("claude-sonnet-4-5"),})
NodeRuntime.runMain( Effect.scoped( Effect.gen(function* () { const settings = yield* EnvironmentConfiguration.load // Baue das Modell einmal. Es lebt so lange wie das Programm. const layer = Layer.unwrap( AnthropicProvider.make({ max_tokens: 4096 }).model(selection), ).pipe( Layer.provide( AnthropicProvider.layerClient({ apiKey: Redacted.make(process.env.ANTHROPIC_API_KEY ?? ""), }), ), Layer.provide(FetchHttpClient.layer), ) const claude = Context.get( yield* Layer.build(layer), LanguageModel.LanguageModel, ) const builtinModels = { providers: [{ id: selection.provider, name: "House models" }], models: [ Config.Model.make({ provider: selection.provider, id: selection.model, name: "Claude Sonnet 4.5", reasoningEfforts: [], serviceTiers: [], }), ], resolve: (requested: Config.Selection) => requested.model === selection.model ? Effect.succeed(claude) : Effect.fail( new Config.ModelConfigurationError({ reason: "model-not-found", message: "This model is not available", }), ), } return yield* Layer.launch( Server.layer({ ...settings, builtinModels }).pipe( Layer.provide(FetchHttpClient.layer), ), ) }), ),)Wenn resolve fehlschlägt, gibt das Starten eines Turn 503 zurück.
Der Einstiegspunkt für die Entwicklung nutzt builtinModels für seine Test-Modelle. Ein vollständiges Beispiel findest du in packages/openbot/src/app/DevelopmentModel.ts.
Anbieter-Module
Ein Anbieter-Modul macht aus einem Modell-Namen ein LanguageModel für eine Art von API. @openbot-build/openbot exportiert drei davon. Jedes hat dieselben Teile:
| Teil | Was er tut |
|---|---|
make(defaults?) |
Erstellt einen Anbieter. defaults sind Request-Optionen für jeden Aufruf. |
provider.model(selection) |
Gibt den Model-Layer für eine Auswahl zurück. Er schickt keine Requests. |
layerClient(options) |
Erstellt den HTTP-Client. Gib apiKey an, und apiUrl für eine andere Adresse. |
layer(tag, defaults?) |
Legt den Anbieter in einen Service, den du benennst. |
| Modul | Anbieter-ID | API |
|---|---|---|
OpenAiProvider |
openai |
OpenAI Responses API. ResponsesCompaction fügt die eingebaute Kontext-Verdichtung hinzu. |
OpenAiCompatProvider |
openai-compat |
OpenAI Chat Completions API und Dienste, die sie nachbauen. |
AnthropicProvider |
anthropic |
Anthropic Messages API. |
OpenBot nutzt diese Module auch für die gespeicherten Anbieter. Kontext-Verdichtung (Context Compaction) macht einen langen Verlauf kürzer, damit er in das Kontextfenster des Modells passt. Ein kurzes Beispiel für jeden Anbieter findest du unter Pakete.
Models für die Entwicklung
Der Server für die Entwicklung (devenv shell -- pnpm dev) ruft kein echtes Modell auf. Er nutzt stattdessen Test-Modelle. Sie geben jedes Mal dieselbe Antwort. So kannst du Clients ohne Kosten testen.
Die Anbieter-ID ist development. Die Model-IDs sind:
| Model | Was es tut |
|---|---|
mock-text |
Der Standard. Gibt sofort einen festen Text zurück. |
mock-slow |
Gibt Text langsam über zehn Sekunden zurück. Nutze es, um Streams und Unterbrechungen zu testen. |
mock-tool |
Ruft ein harmloses Test-Tool auf, das auf einen Request wartet. |
mock-app |
Ruft ein Tool der Test-App auf und gibt ihren echten Inhalt zurück. |
mock-failure |
Schickt etwas Text und schlägt dann absichtlich mit einem Fehler des Modells fehl. |
Die Token-Zahlen in ihren Antworten sind nicht echt. Um eins auszuwählen, speichere es in einer Session:
curl -X PUT -H "Authorization: Bearer $OPENBOT_TOKEN" -H 'Content-Type: application/json' \ -d '{"expected":1,"selection":{"_tag":"Config.Selection","provider":"development","model":"mock-slow"}}' \ https://localhost:8443/v1/sessions/$SESSION_ID/modelDer Einstiegspunkt für die Entwicklung gibt diese Models mit builtinModels an. So kann jede Organization der Platform für die Entwicklung sie nutzen. Das Programm für den Betrieb nutzt sie nie.