10 KiB
PV RAG Agent — HTTP API v1
Zweck und Grenze
Die API ist der eigenständige Zugriffspunkt für CLI-, Web- und spätere Odoo-Clients. Version 1 ist ein zustandsloser Wissensdienst:
- verarbeitet ausschließlich
question, optionale Retrieval-Tiefetop_kund den festen Modusknowledge; - antwortet ausschließlich aus der Layer-2-Wissensbasis;
- nimmt keine Mandanten-, Mitarbeiter- oder Abrechnungsobjekte entgegen;
- speichert keinen Gesprächsverlauf;
- nutzt keine Websuche und keine externen Tools.
Personenbezogene Lohndaten dürfen nicht in den Fragetext eingebettet werden. Die spätere Odoo-Lohndatenintegration bekommt einen getrennten Vertrag mit Mandantenbindung, Rollenprüfung, Datenminimierung, Auditierung und definierter Aufbewahrung. Ein allgemeiner API-Key allein reicht dafür nicht aus.
Endpunkte
| Methode | Pfad | Auth | Zweck |
|---|---|---|---|
GET |
/ |
Eingabe im UI | Test-Frontend |
POST |
/v1/ask |
Service-Key | Wissensantwort (mode=knowledge) oder Plausibilitätsprüfung (mode=review) |
POST |
/v1/ratings |
Service-Key | Antwort bewerten |
POST |
/v1/comments |
Service-Key | Kommentar zu einer Antwort protokollieren |
GET |
/v1/health |
öffentlich | Readiness ohne interne Hostdetails |
POST |
/v1/reindex |
Admin-Key | Index nach KB-Änderung neu aufbauen |
Die bisherigen Pfade /ask, /health und /reindex bleiben vorläufig als
deprecated Kompatibilitätsrouten erhalten und liefern denselben Vertrag.
Test-Frontend
Das dependency-freie Frontend wird vom API-Prozess same-origin unter /
ausgeliefert. Dadurch sind keine CORS-Freigaben nötig. Es zeigt Dienststatus,
Antwort, zitierte Quellen, Quellenkonflikte, Rückfragen und technische
Grounding-Metadaten. Modellantworten werden nur als Text gerendert; HTML aus
einer Antwort wird nicht ausgeführt.
Für den vorgesehenen Tailscale-Host:
export PV_API_KEY='service-key-aus-secret-store'
python -m agent.cli serve --host 100.103.83.12
Danach ist die Oberfläche unter http://100.103.83.12:8080/ erreichbar. Der
Service-Key wird vom Benutzer im Frontend eingegeben und niemals serverseitig
in HTML oder JavaScript eingebettet. Optional speichert ihn die Oberfläche nur
im sessionStorage des aktuellen Browser-Tabs; Chatverlauf und Fragen werden
nicht im Browser gespeichert. Außerhalb eines verschlüsselten Tailnets ist vor
den Dienst ein TLS-Reverse-Proxy zu setzen.
Authentisierung
export PV_API_KEY='service-key-aus-secret-store'
export PV_ADMIN_API_KEY='separater-admin-key-aus-secret-store'
python -m agent.cli serve --host 127.0.0.1
Requests senden den Key als Bearer-Token:
Authorization: Bearer <key>
- Ist
PV_ADMIN_API_KEYleer, verwendet/v1/reindexden normalenPV_API_KEY. - Sind beide Variablen leer, läuft der Dienst zur lokalen Entwicklung ohne
Authentisierung. Der CLI-Start lehnt in diesem Zustand nicht-lokale Bind-
Adressen wie
0.0.0.0ab. Ein exponiertes Deployment muss mindestensPV_API_KEYsetzen. - Keys gehören in einen Secret Store bzw. eine nicht versionierte Umgebungsdatei; sie dürfen nicht in Odoo-Quellcode oder Git liegen.
- TLS wird am Reverse Proxy bzw. Service Mesh terminiert. Bearer-Tokens dürfen nicht unverschlüsselt über fremde Netze übertragen werden.
POST /v1/ask
Request:
{
"question": "Wie hoch ist der steuerfreie Tagesgeldsatz?",
"top_k": 8,
"mode": "knowledge"
}
mode=review — Odoo-Plausibilitätsprüfung (M4.2)
Mit PV_REVIEW_MODE=true nimmt der Dienst einen schema-gebundenen Odoo-
Kontext an und prüft das von Odoo vorgegebene Ergebnis gegen die Wissensbasis:
{
"question": "Prüfe die geplante Auszahlung gegen die Regeln.",
"mode": "review",
"context": {
"facts": [
{"key": "bruttolohn_monat", "value": "3000 EUR"},
{"key": "freibetrag_620_verbraucht", "value": "340 EUR", "note": "Jahr 2026"}
],
"computation": {
"label": "AG-Kosten Barauszahlung",
"result": "612,31 EUR",
"basis": "SVDG + DB/DZ auf 500 EUR",
"components": [{"key": "svdg_gesamt", "value": "549,50 EUR"}]
}
}
}
Grenzen: facts max. 40 (key-Muster [a-z0-9_.-], value ≤ 200 Zeichen),
components max. 40; keine freien Objekte. Ohne PV_REVIEW_MODE liefert
mode=review HTTP 422; context außerhalb des Review-Modus ebenfalls.
Der Antworttext endet mit einem Abschnitt Plausibilitätsprüfung:; daraus
extrahiert der Dienst strukturiert:
"plausibility": {
"verdict": "implausible",
"checks": [
{"status": "warn", "aspect": "Freibetrag 620",
"detail": "erwartet 280 EUR steuerfrei [lb-son-04] — erhalten 500 EUR",
"source_ids": ["lb-son-04"]}
]
}
Semantik: plausible (Checks ohne Warn), implausible (mind. ein ⚠-Check),
not_checkable (kein gültiger Check, z. B. fehlender Kontext). Odoo bleibt
autoritativ für Zahlen — der Agent korrigiert nichts stillschweigend. Das
grounding.data_scope ist im Review-Modus
knowledge_base_plus_review_context.
Unbekannte Felder werden mit HTTP 422 abgewiesen. Das ist insbesondere die
technische Vertragsgrenze gegen ad-hoc-Felder wie employee_data oder
payroll_context.
Response (gekürzt):
{
"api_version": "v1",
"request_id": "odoo-request-123",
"status": "answered",
"question": "Wie hoch ist der steuerfreie Tagesgeldsatz?",
"answer": "... [lb-rei-09] ...",
"refused": false,
"verified": true,
"citations": ["lb-rei-09"],
"sources": [
{
"id": "lb-rei-09",
"title": "...",
"section": "Kernwerte & Fristen",
"stand": "2026-01",
"work": "..."
}
],
"conflicts": [],
"assumptions": [],
"clarification_question": null,
"alternatives": [],
"answer_type": "specific",
"planned": false,
"planned_queries": [],
"grounding": {
"data_scope": "knowledge_base_only",
"citations_verified": true,
"context_count": 8,
"regenerations": 0
},
"n_context": 8,
"model": "qwen3.8:27b",
"latency_ms": 38800,
"regenerations": 0
}
Semantik
status=answered: beantwortet und zitierseitig verifiziert.status=refused: Wissensbasis deckt die Frage nicht; sichere Verweigerung.status=uncertain: Regenerierung konnte die Grounding-Regeln nicht erfüllen.sourcesenthält nur tatsächlich zitierte Quellen, nicht sämtliche Retrieval-Treffer.conflictsenthält mit⚠markierte, bereits in der belegten Antwort vorkommende Konfliktpassagen und deren Quellen-IDs.clarification_questionwird aus einer abschließenden Rückfrage übernommen.assumptionsundalternativessind bereits stabile Vertragsfelder, bleiben in v1 aber leer. Der Dienst errät diese Strukturen nicht aus Freitext; ihre spätere Befüllung benötigt einen eigenen belegbaren Generierungsvertrag.grounding.data_scope=knowledge_base_onlyist im knowledge-Modus unveränderlich; im Review-Modus giltknowledge_base_plus_review_context.ratings_enabledzeigt, ob diese Antwort über/v1/ratingsbewertet werden kann.modespiegelt den Anfragemodus;plausibilityist nur im Review-Modus gesetzt.
Der Client darf verified=false nicht als normale Fachantwort darstellen.
Empfohlen ist ein sichtbarer Warnzustand ohne automatische Folgeverarbeitung.
POST /v1/ratings
Eine zuvor protokollierte Antwort kann über ihre request_id bewertet werden:
{
"request_id": "odoo-request-123",
"rating": "up",
"feedback": "Die Quellen beantworten die Frage nachvollziehbar."
}
rating ist up oder down, feedback optional und auf 1.000 Zeichen
begrenzt. Pro Antwort wird eine Bewertung gespeichert; ein weiterer Request
aktualisiert sie. Unbekannte Request-IDs liefern 404, deaktiviertes Audit
503. Das Frontend blendet die Bewertungsfunktion nur bei
ratings_enabled=true ein.
POST /v1/comments
Kommentare sind von der Daumenbewertung unabhängig. Pro Antwort können mehrere Kommentare protokolliert werden:
{
"request_id": "odoo-request-123",
"comment": "Bitte diesen Fall in das Goldset aufnehmen."
}
Der Kommentar wird getrimmt, darf nicht leer sein und ist auf 2.000 Zeichen
begrenzt. Die Antwort enthält eine fortlaufende comment_id. Unbekannte
Request-IDs liefern 404, deaktiviertes Audit 503.
Audit-Protokoll
Bei PV_AUDIT_ENABLED=true werden Interaktionen und Bewertungen in der über
PV_AUDIT_DB_PATH festgelegten SQLite-Datei gespeichert. Mehrere Kommentare pro Antwort liegen
in der Tabelle comments. Mit
PV_AUDIT_STDOUT=true werden strukturierte JSON-Ereignisse zusätzlich mit dem
Präfix AUDIT nach stdout geschrieben. PV_AUDIT_LOG_CONTENT=false entfernt
Freitext einschließlich Frage, Antwort, Quellenbeschreibungen, Konflikten,
Suchplan und Bewertungskommentar; technische Metriken und KB-IDs bleiben.
API-Key und Authorization-Header werden nie protokolliert.
Request-Korrelation
Ein Client kann einen technisch neutralen Header mitsenden:
X-Request-ID: odoo-request-123
Erlaubt sind 1–128 Zeichen aus A-Z, a-z, 0-9, ., _, :, -.
Ungültige oder fehlende IDs werden durch eine zufällige ID ersetzt. Die ID
wird im Response-Header und Response-Body zurückgegeben. Keine Namen,
Personalnummern oder sonstigen personenbezogenen Daten als Request-ID nutzen.
Fehlerverhalten
401: fehlender oder falscher Bearer-Key;422: ungültiger Request bzw. nicht erlaubte Felder;503: Index, Modellserver oder Reindex vorübergehend nicht verfügbar.
Öffentliche Fehlerantworten enthalten keine internen Hosts, Dateipfade oder
Exception-Texte. Der Server loggt technische Fehler mit request_id.
Odoo-Anbindung der Wissens-API
Odoo soll /v1/ask serverseitig aufrufen, nicht direkt aus dem Browser:
- Service-URL und Key verschlüsselt bzw. als Deployment-Secret verwalten;
- Benutzerzugriff in Odoo über eine eigene Gruppe steuern;
- nur die Frage und eine nicht personenbezogene
X-Request-IDsenden; answer,sources,conflictsundclarification_questionrendern;statusundverifiedzwingend auswerten;- keine eigene Retrieval- oder Grounding-Logik in Odoo duplizieren.
Die Payroll-Datenintegration wird nicht durch zusätzliche freie JSON-Felder an
/v1/ask umgesetzt. Sie benötigt mindestens: Odoo-seitige Datensatzregeln,
service-seitige Tenant-Bindung, erlaubte Datenprojektionen statt Rohobjekten,
Zweckbindung, Audit-Events, kurze Aufbewahrung und Tests gegen Cross-Tenant-
Datenabfluss.