mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 16:56:42 +00:00
192 lines
6.8 KiB
Markdown
192 lines
6.8 KiB
Markdown
# 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 <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:
|
||
|
||
```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.
|