Files
pv-agent/docs/API.md
T

10 KiB
Raw Blame History

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 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_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:

{
  "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.
  • 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 im knowledge-Modus unveränderlich; im Review-Modus gilt knowledge_base_plus_review_context.
  • ratings_enabled zeigt, ob diese Antwort über /v1/ratings bewertet werden kann.
  • mode spiegelt den Anfragemodus; plausibility ist 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 1128 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.