LLM nutzen
Der tibi-server chattet für dein Projekt: Zusammenfassen, Entwürfe, eigene Features hinter deinem Backend. Abgrenzung: Diese Seite beschreibt die API. Die Chat-Oberfläche im Admin (Knopf, Verlauf, Stimme, Werkzeuge) steht unter KI-Funktionen — dort läuft alles über den Proxy, hier rufst du Provider direkt.
Chat aufrufen
Abschnitt betitelt „Chat aufrufen“Zwei Routen (setup.go): POST /api/v1/llm/chat chattet, GET /api/v1/llm/providers listet Anbieter (ohne Secrets).
Anmeldung wie bei REST (siehe Rechte und Tokens); dein PAT braucht den LLM-Scope.
curl -s -X POST http://localhost:8080/api/v1/llm/chat \ -H 'Content-Type: application/json' \ -H 'X-Auth-Token: tibi_pat_DEIN_TOKEN' \ -d '{"provider":"<anbieter>","model":"<modell>", "messages":[{"role":"user","content":"Fasse diesen Absatz zusammen: …"}]}'# → {"id":"…","model":"<modell>","choices":[{"index":0,# "message":{"role":"assistant","content":"…"},"finishReason":"stop"}],# "usage":{"promptTokens":12,"completionTokens":30,"totalTokens":42}}Pflichtfelder (llm_chat.go): provider und messages.
model ist optional — dann gilt das Default-Modell (nächster Abschnitt).
curl -N -X POST http://localhost:8080/api/v1/llm/chat \ -H 'Content-Type: application/json' \ -H 'X-Auth-Token: tibi_pat_DEIN_TOKEN' \ -d '{"provider":"<anbieter>","messages":[{"role":"user","content":"Hallo"}], "stream":true}'# → data: {"id":"…","model":"…","choices":[{"index":0,"delta":{"role":"assistant","content":"Hal"}}]}# → data: {"id":"…","choices":[{"index":0,"delta":{"content":"lo"}}]}# → data: [DONE]Format: Content-Type: text/event-stream, Chunks als data: <JSON>, Ende als data: [DONE].
Fehler im Stream kommen als data: {"error":"…"}.
Optionale Felder (models.go, ChatRequest): temperature, maxTokens, stream.
Zugriff: Dein PAT braucht den Scope llm.chat (config/scope.go, ScopeLLMChat).
Fehlt Anmeldung oder Scope, antwortet der Handler 403 — noch vor jeder Provider- oder Budget-Prüfung (llm_chat.go: GetAuth + EnforceScope).
Anbieter und Modelle
Abschnitt betitelt „Anbieter und Modelle“Anbieter stehen in der Server-config.yml (llm.providers, Typ openai oder openai-compatible).
Namen, defaultModel, models-Allowlist und maxTokensPerRequest legt der Betrieb fest — keine erfundenen Namen verwenden.
curl -s http://localhost:8080/api/v1/llm/providers \ -H 'X-Auth-Token: tibi_pat_DEIN_TOKEN'# → [{"name":"<anbieter>","type":"openai-compatible",# "defaultModel":"<modell>","models":["<modell>", …]}]Modell-Auflösung (openai.go, resolveModel):
| Anfrage | Ergebnis |
|---|---|
model gesetzt und in models |
Genau dieses Modell |
model leer |
defaultModel des Providers |
| Weder noch | 400 no model specified and no default model configured |
model nicht in der Allowlist |
400 model … is not allowed, available models: … |
models-Allowlist leer |
Keine Prüfung — jedes Modell passiert (len(p.models) > 0 schaltet die Prüfung erst ein) |
Provider-Mechanik (registry.go, openai.go, server.go):
- Registriert wird nur Typ
openaioderopenai-compatible— andere Typen überspringtNewRegistrystillschweigend (kein Fehler, der Anbieter fehlt danach einfach). - Unbekannter Name →
400 LLM provider "…" not found(registry.go,Get). - Leere
baseURLfällt aufhttps://api.openai.com/v1zurück; der API-Schlüssel wandert server-seitig in denAuthorization: Bearer-Header —GET /llm/providersliefert ihn nie aus (apiKeyhatjson:"-",setup.gobaut bewusst nurname,type,defaultModel,modelsin die Antwort). maxTokensPerRequest(> 0) deckelt still: FehltmaxTokensoder liegt es darüber, sendet der Server den konfigurierten Wert an den Provider (buildRequest). Das schützt vor Riesen-Antworten, steht aber nicht in der API-Antwort — plane deine Budgets mit dem konfigurierten Wert, nicht mit deinem Wunschwert.
Budgets
Abschnitt betitelt „Budgets“Jeder Aufruf kostet Token aus deinem Budget — sonst antwortet der Server 429.
Budget-Typ (models/user.go, LLMBudget):
| Feld | Bedeutung |
|---|---|
provider |
Für welchen Anbieter das Budget gilt |
tokenBudget |
Gesamtk approbierte Token (0 = unbegrenzt auf dieser Ebene) |
tokensUsed |
Bereits verbrauchte Token (wächst pro Aufruf) |
periodStart / periodEnd |
Zeitraum; nach periodEnd ist Schluss |
Regeln (quota.go, dazu Reihenfolge in llm_chat.go):
- Benutzer-Budget ist Pflicht. Kein Eintrag für den Provider →
429 no LLM budget configured for provider "…". tokensUsed >= tokenBudget→429 LLM token budget exhausted for provider "…" (used X / Y).periodEndüberschritten →429 LLM budget period expired for provider "…".- Org-Budget ist optional (
CheckBudgetIfSet): Nur wenn deine Org einen Eintrag hat, zählt er zusätzlich. Die Org wird nur gelesen, wenn dein Konto eineprimaryOrgIdhat — sonst entfällt die Prüfung komplett. Org-Fehltexte tragen einorg-Präfix (org LLM token budget exhausted …,org LLM budget period expired …), damit du in der429erkennst, welche Ebene blockiert. tokenBudget: 0heißt auf dieser Ebene unbegrenzt — ersetzt aber nicht den Pflicht-Eintrag auf Benutzerebene.- Prüfreihenfolge pro Aufruf: erst Benutzer-Budget (
CheckBudget), dann — nur bei gesetzterprimaryOrgId— Org-Budget (CheckBudgetIfSet). Erst danach geht die Anfrage an den Provider. - Jeder Aufruf schreibt
tokensUsedbei Benutzer und Org fort (jeweils$setaufllmBudgets) plus eine Zeile inllm_usage(usage_model.go, Collectionllm_usage): Benutzer (userId), Org (orgId), Provider, Modell, Prompt-/Completion-/Gesamt-Token, Zeit (timestamp). Schlägt das Buchen fehl, merkst du nichts: Beide Handler ignorieren den Fehler (_ = qm.RecordUsage(…)), die Antwort bleibt gültig.
Token-Schätzung, wenn der Provider keine usage liefert: ca. 4 Zeichen ≈ 1 Token, plus 4 Token Rahmen pro Nachricht (EstimateUsage).
Geschätzte Werte tragen estimated: true.
EstimateUsage im Detail (models.go):
EstimateTokens(text)=len(text) / 4(Byte-Länge, Integer-Division), mindestens 1 bei nicht-leerem Text.- Pro Nachricht:
EstimateTokens(role) + EstimateTokens(content) + 4(Rahmen). - Completion:
EstimateTokens(Antworttext);totalTokensist die Summe,estimatedimmertrue. - Anwendung: Nicht-Streaming übernimmt die Schätzung nur, wenn
usage.totalTokens == 0. Im Stream hängt dieusageam letzten Chunk (der Provider sendet sie wegenstream_options: {include_usage: true}mit); fehlt sie, schätzt der Server aus deinen Nachrichten plus dem akkumulierten Stream-Text.
Fehlerfall normal — Budget aufgebraucht:
curl -s -X POST http://localhost:8080/api/v1/llm/chat \ -H 'Content-Type: application/json' \ -H 'X-Auth-Token: tibi_pat_DEIN_TOKEN' \ -d '{"provider":"<anbieter>","messages":[{"role":"user","content":"Hallo"}]}'# → HTTP 429 {"error":"LLM token budget exhausted for provider \"<anbieter>\" (used 10000 / 10000)"}Fehlerfall Streaming — Abbruch mitten im Stream:
curl -N -X POST http://localhost:8080/api/v1/llm/chat \ -H 'Content-Type: application/json' \ -H 'X-Auth-Token: tibi_pat_DEIN_TOKEN' \ -d '{"provider":"<anbieter>","messages":[{"role":"user","content":"Hallo"}], "stream":true}'# → data: {"id":"…","model":"…","choices":[{"index":0,"delta":{"role":"assistant","content":"Hal"}}]}# → data: {"error":"…"} # statt weiterer Chunks + [DONE]Dazu die zweite Fehlerlage: Scheitert schon der Stream-Aufbau (Provider down, Modell falsch), kommt gar kein SSE — sondern direkt 502 (Text nur, wenn der Betreiber keine generische Fehlermeldung gesetzt hat: response.error.message in der Server-Config ersetzt den Body durch einen festen Text). Das [DONE] gibt es nur nach vollständigem Lesen (io.EOF).
| Antwort | Ursache | Lösung |
|---|---|---|
403 |
Keine Anmeldung oder LLM-Scope fehlt | Token und Rechte prüfen |
400 provider is required / messages are required |
Pflichtfeld fehlt | Beide Felder senden |
400 provider … not found |
Anbietername falsch | GET /llm/providers lesen, Namen kopieren |
400 model … is not allowed, available models: … |
Modell nicht freigegeben | Modell aus der Allowlist nehmen oder leer lassen |
400 LLM provider "…" not found |
Anbietername falsch oder Typ nicht registriert | GET /llm/providers lesen, Namen kopieren |
400 no model specified and no default model configured |
Weder model noch defaultModel gesetzt |
model mitsenden oder Betrieb um defaultModel bitten |
429 Budget |
Kein Budget, aufgebraucht oder Zeitraum abgelaufen (org -Präfix = Org-Ebene) |
Budget prüfen / beim Betrieb anfragen |
502 |
Provider antwortet nicht oder Stream bricht ab | Später erneut versuchen, maxTokens senken |
413 |
Body zu groß | Nachrichten kürzen |
Budget-Verwaltung (wer darf was): llmBudgets stehen am Benutzer (models/user.go) und an der Org. Nur System-Admins ändern sie frei — Org-Admins verlieren das Feld still (llmBudgets wird auf nil gesetzt bzw. aus Änderungen gestrichen), Team-Admins und Mitglieder bekommen beim Schreibversuch updating llmBudgets only allowed for admins. Budget-Erhöhungen setzen System-Admins (llmBudgets am Benutzer oder an der Org).
Verifiziert am 68b4278 2026-10-17 (tibi-server: setup.go LLM-Routen + Providers-Antwort ohne Secrets, llm_chat.go inkl. 429/502/413-Mapping und RecordUsage, registry.go Typ-Filter, provider.go, models.go EstimateUsage, openai.go resolveModel + buildRequest-Clamp + include_usage, quota.go CheckBudget/CheckBudgetIfSet/RecordUsage, usage_model.go llm_usage, user.go LLMBudget + Feldschutz, config/scope.go llm.chat, server.go ConfigLLMProvider)