# 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-Tiefe `top_k` und den festen Modus `knowledge`; - 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 | belegte Wissensantwort | | `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: ```bash 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 ```bash 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: ```http Authorization: Bearer ``` - Ist `PV_ADMIN_API_KEY` leer, verwendet `/v1/reindex` den normalen `PV_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.0` ab. Ein exponiertes Deployment muss mindestens `PV_API_KEY` setzen. - 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: ```json { "question": "Wie hoch ist der steuerfreie Tagesgeldsatz?", "top_k": 8, "mode": "knowledge" } ``` 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): ```json { "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. - `sources` enthält nur tatsächlich zitierte Quellen, nicht sämtliche Retrieval-Treffer. - `conflicts` enthält mit `⚠` markierte, bereits in der belegten Antwort vorkommende Konfliktpassagen und deren Quellen-IDs. - `clarification_question` wird aus einer abschließenden Rückfrage übernommen. - `assumptions` und `alternatives` sind 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_only` ist in v1 unveränderlich. Der Client darf `verified=false` nicht als normale Fachantwort darstellen. Empfohlen ist ein sichtbarer Warnzustand ohne automatische Folgeverarbeitung. ## Request-Korrelation Ein Client kann einen technisch neutralen Header mitsenden: ```http 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: 1. Service-URL und Key verschlüsselt bzw. als Deployment-Secret verwalten; 2. Benutzerzugriff in Odoo über eine eigene Gruppe steuern; 3. nur die Frage und eine nicht personenbezogene `X-Request-ID` senden; 4. `answer`, `sources`, `conflicts` und `clarification_question` rendern; 5. `status` und `verified` zwingend auswerten; 6. 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.