Zum Inhalt springen

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.

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.

POST /llm/chat — normal
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).

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 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.

GET /llm/providers — was ist konfiguriert
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 openai oder openai-compatible — andere Typen überspringt NewRegistry stillschweigend (kein Fehler, der Anbieter fehlt danach einfach).
  • Unbekannter Name → 400 LLM provider "…" not found (registry.go, Get).
  • Leere baseURL fällt auf https://api.openai.com/v1 zurück; der API-Schlüssel wandert server-seitig in den Authorization: Bearer-Header — GET /llm/providers liefert ihn nie aus (apiKey hat json:"-", setup.go baut bewusst nur name, type, defaultModel, models in die Antwort).
  • maxTokensPerRequest (> 0) deckelt still: Fehlt maxTokens oder 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.

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 eine primaryOrgId hat — sonst entfällt die Prüfung komplett. Org-Fehltexte tragen ein org -Präfix (org LLM token budget exhausted …, org LLM budget period expired …), damit du in der 429 erkennst, welche Ebene blockiert.
  • tokenBudget: 0 heißt auf dieser Ebene unbegrenzt — ersetzt aber nicht den Pflicht-Eintrag auf Benutzerebene.
  • Prüfreihenfolge pro Aufruf: erst Benutzer-Budget (CheckBudget), dann — nur bei gesetzter primaryOrgId — Org-Budget (CheckBudgetIfSet). Erst danach geht die Anfrage an den Provider.
  • Jeder Aufruf schreibt tokensUsed bei Benutzer und Org fort (jeweils $set auf llmBudgets) plus eine Zeile in llm_usage (usage_model.go, Collection llm_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); totalTokens ist die Summe, estimated immer true.
  • Anwendung: Nicht-Streaming übernimmt die Schätzung nur, wenn usage.totalTokens == 0. Im Stream hängt die usage am letzten Chunk (der Provider sendet sie wegen stream_options: {include_usage: true} mit); fehlt sie, schätzt der Server aus deinen Nachrichten plus dem akkumulierten Stream-Text.

Fehlerfall normal — Budget aufgebraucht:

POST /llm/chat — 429 bei leerem Budget
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)"}

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)