mirror of
http://100.103.83.12:3003/fegger/pv-agent.git
synced 2026-09-17 16:06:23 +00:00
feat(agent): harden API-first service
This commit is contained in:
+168
@@ -0,0 +1,168 @@
|
||||
# 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 |
|
||||
|---|---|---|---|
|
||||
| `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.
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user