This commit is contained in:
team 1
2026-04-15 10:49:21 +02:00
parent 21b60b8542
commit 51bc781826
5 changed files with 1374 additions and 975 deletions

574
README.md
View File

@@ -1,298 +1,484 @@
# RAG-System Technische Projektdokumentation
# README.md
Version: 2.0
Stand: Februar 2026
Status: Code-validierte Referenzarchitektur
# RetrieX
> Hinweis: Das System wird fachlich als **RetrieX** bezeichnet.
> Im Repository existieren aus historischen Gründen noch einzelne Bezeichner wie `RAG`, `rag.zip` oder `RAG_SYSTEM_OVERVIEW.md`.
## Überblick
RetrieX ist ein dokumentenbasiertes Assistenzsystem auf Basis von Retrieval-Augmented Generation.
Das System beantwortet Nutzeranfragen nicht rein frei, sondern kombiniert:
- versionierte Wissensdokumente
- deterministische Ingest- und Indexierungslogik
- hybrides Retrieval mit Routing
- optionale Shopware-Live-Produktsuche
- LLM-basierte Antwortformulierung
- SSE-Streaming für die Browserausgabe
Der aktuelle Stand ist kein generischer Chatbot, sondern eine kontrollierte Wissens- und Antwortpipeline.
---
# 1. Ziel des Systems
## Technologiestack
Das System implementiert ein deterministisches Retrieval-Augmented-Generation (RAG) Framework auf Basis versionierter Dokumente.
### Backend
Leitprinzip:
- PHP 8.2
- Symfony 7.4
- Doctrine ORM / Doctrine Migrations
- Symfony Messenger
- Symfony HttpClient
- Twig
> „Wir nutzen KI nicht, um kreativ zu raten, sondern um verlässlich auf Basis Ihres Wissens zu antworten.“
### Python-Layer
Das System stellt sicher:
- SentenceTransformers
- FAISS
- FastAPI
- uvicorn
- Versionierte Dokumentverwaltung
- Deterministisches Chunking
- Streamingfähige NDJSON Source of Truth
- Vollständiger FAISS-Rebuild bei strukturellen Änderungen
- Persistenter Python Vector-Service
- Governance-stabile Ingest- und Reindex-Mechanik
### Zusätzliche Libraries
- `fivefilters/readability.php` für URL-Inhaltsauswertung
- `smalot/pdfparser` für PDF-Textgewinnung
- `league/commonmark` für Markdown-Rendering
---
# 2. Architekturüberblick
## Kernidee des Systems
## 2.1 Hauptkomponenten
RetrieX trennt klar zwischen:
Symfony (PHP)
- Dokumentverwaltung
- Versionierung
- Ingest-Orchestrierung
- Job-System
- Admin-Oberfläche
- PromptBuilder
- VectorSearchClient
1. **Primärquellen**
Dokumente, Dokumentversionen, aktive System-Prompts, Modellkonfigurationen, optionale Shopdaten
Python Layer
- Embedding-Modell (SentenceTransformers)
- FAISS Index
- Vector-Service (FastAPI + uvicorn)
- CLI-Fallback Scripts
2. **Index- und Retrieval-Ebene**
`index.ndjson`, `index_meta.json`, `index_runtime.json`, `vector.index`, `tags.ndjson`, `vector_tags.index`
Persistenz
- index.ndjson (Single Source of Truth)
- index_meta.json (Struktur-Metadaten)
- vector.index (FAISS)
- tags.ndjson (optional)
- vector_tags.index (optional)
3. **Orchestrierung**
Anfrageannahme, Kontextaufbau, URL-Analyse, Retrieval, Commerce-Erkennung, Prompt-Aufbau, Streaming
4. **Ausgabe**
Token-Streaming an das Frontend über Server-Sent Events
---
# 3. Verzeichnisstruktur (verifiziert)
## Aktuell unterstützte Dokumentformate
bin/
config/
migrations/
public/
python/
src/
templates/
var/
Der verifizierte aktuelle Loader unterstützt:
Python-Vektorlayer:
- PDF
- TXT
- MD
python/
vector_service.py
vector_control.py
vector_ingest.py
vector_search.py
vector_ingest_tags.py
vector_search_tags.py
Wichtig:
Im aktuellen Code-Stand ist **kein produktiver DOCX-Loader** in der eigentlichen Ingest-Pipeline sichtbar. Die README sollte deshalb bewusst nur die real verifizierten Formate nennen.
---
# 4. Source of Truth Konzept
## Laufzeitfluss einer Anfrage
## 4.1 index.ndjson
Der zentrale Browser-Endpunkt ist:
Streamingfähiges NDJSON-Format:
- `POST /ask-sse`
- Eine JSON-Zeile pro Chunk
- Kein JSON-Array
- Append-fähig
- Speicheroptimiert für >200k Chunks
Die Anfrage läuft im aktuellen Stand vereinfacht so:
## 4.2 index_meta.json
Beinhaltet:
- index_version
- embedding_model
- embedding_dimension
- chunk_size
- overlap
- scoring_version
- index_format
Bei strukturellen Änderungen wird ein Global Reindex erzwungen.
1. `AskSseController` nimmt die Anfrage entgegen
2. `ClientIdResolver` bestimmt eine stabile Client-ID
3. Antwort wird als **SSE-Stream** geöffnet
4. `AgentRunner` orchestriert den kompletten Ablauf
5. Optional wird URL-Inhalt aus dem Prompt extrahiert
6. `RetrieverInterface` bzw. `NdjsonHybridRetriever` holt Wissenskontext
7. Optional wird Commerce-Intent erkannt und Shopware durchsucht
8. `PromptBuilder` baut den finalen LLM-Prompt
9. `OllamaClient` streamt die Modellantwort
10. Die Antwort wird chunkweise ins Frontend gesendet
11. Der Turn wird in der Gesprächshistorie persistiert
---
# 5. Dokument-Lifecycle
## Gesprächskontext
Dokument
→ Version
→ Aktivierung
→ IngestJob (QUEUE)
→ Chunking
→ NDJSON Compaction by document_id
→ Full FAISS Rebuild
→ Status INDEXED
`ContextService` verwaltet die Konversationshistorie dateibasiert.
Wichtige Regel:
Eigenschaften:
Es existiert immer nur eine aktive Version pro Dokument.
- Nutzerhistorie wird pro Client-ID in einer Textdatei gespeichert
- abgeschlossene Turns werden append-only geschrieben
- regulärer Kontext und Full-Context sind getrennt vorgesehen
- der aktuelle SSE-Flow ruft `AgentRunner->run(..., true)` auf und nutzt damit **vollen Kontext**
Standardlogik im aktuellen Stand:
- normale Historie: letzte 20 Zeilen
- voller Kontext: letzte 500 Zeilen
---
# 6. Ingest-System
## URL-Auswertung
## 6.1 Job-Typen
Wenn im Prompt eine URL vorkommt, extrahiert `UrlAnalyzer` den Inhalt der **ersten** URL.
- DOCUMENT_UPLOAD
- DOCUMENT_VERSION_ACTIVATE
- GLOBAL_REINDEX
Aktuelle Eigenschaften:
## 6.2 Aktivierung
- Timeout-basiertes Laden
- `Readability`-basierte Extraktion des lesbaren Inhalts
- HTML wird entfernt
- Whitespace wird normalisiert
- Ausgabe wird auf maximal 5000 Zeichen begrenzt
Beim Aktivieren einer Version:
- DB-Status wird geändert
- IngestStatus → PENDING
- IngestJob wird erzeugt
- CLI-Execution via:
```
bin/console mto:agent:ingest:run <jobId>
```
LockService verhindert Parallel-Ingest.
Der extrahierte Text wird als zusätzlicher unterstützender Wissensblock in den finalen Prompt aufgenommen.
---
# 7. Retrieval
## Ingest- und Indexarchitektur
## 7.1 Architektur
### Zielbild
Hybrid im Sinne von:
RetrieX nutzt eine deterministische Ingest-Architektur mit NDJSON als operativer Wissensbasis und FAISS als Vektorindex.
- FAISS Vektor-Retrieval
- Optionales Tag-Routing (separater Tag-Index)
### Zentrale Dateien
Kein klassisches Keyword-Retrieval mehr aktiv.
- `var/knowledge/index.ndjson`
- `var/knowledge/index_meta.json`
- `var/knowledge/index_runtime.json`
- `var/knowledge/vector.index`
- `var/knowledge/vector.index.meta.json`
- `var/knowledge/tags.ndjson`
- `var/knowledge/vector_tags.index`
- `var/knowledge/vector_tags.index.meta.json`
## 7.2 Ablauf
### Bedeutung
1. Anfrage
2. VectorSearchClient → Python Service
3. FAISS Similarity Search
4. Top-K Chunks
5. PromptBuilder
6. LLM Response
#### `index.ndjson`
Operative Chunk-Basis des Systems:
- eine JSON-Zeile pro Chunk
- streamingfähig
- geeignet für append und full rewrite
- Grundlage für den Vector-Rebuild
#### `index_meta.json`
Struktur-Metadaten des Index, u. a.:
- `index_version`
- `created_at`
- `chunk_size`
- `chunk_overlap`
- `embedding_model`
- `embedding_dimension`
- `scoring_version`
- weitere Strukturfelder aus der aktiven Index-Konfiguration
#### `index_runtime.json`
Laufzeitdaten, u. a.:
- `chunk_count`
- `last_rebuild_at`
- optionale Commit-Marker wie `last_tags_rebuild_at`
---
# 8. Python Vector Service
## Dokument-Lifecycle
FastAPI Service mit uvicorn.
Der aktuelle Dokumentfluss ist technisch so aufgebaut:
Steuerung über:
1. Dokumentversion wird aktiviert oder ingestiert
2. Guardrails prüfen die Strukturverträglichkeit
3. alte Chunks des Dokuments werden entfernt
4. neue Chunks werden streamingfähig geschrieben
5. der komplette Vektorindex wird neu gebaut
6. Runtime-Stats werden atomar aktualisiert
7. Status der Version wird auf `INDEXED` gesetzt
```
bin/console mto:agent:vector:control
```
Wichtige Eigenschaft:
Mögliche Aktionen:
- --install
- --start
- --stop
- --reload
- --status
Service lädt:
- Embedding-Modell einmalig
- FAISS Index einmalig
- Hält alles persistent im RAM
CLI-Fallback bleibt verfügbar.
- Pro Dokument gibt es fachlich eine aktive Version
- Chunks sind abgeleitete Artefakte, keine Primärdaten
---
# 9. Embedding
## Ingest-Services im aktuellen Stand
Verwendetes Modell ist konfigurierbar.
### `GuardrailValidator`
Embedding-Dimension wird aus Modell bestimmt und im index_meta.json gespeichert.
Prüft über `IndexMetaManager`, ob der aktuelle Index strukturell zur aktiven Konfiguration passt.
Bei Dimensionsänderung:
Wenn sich relevante Strukturparameter geändert haben, wird lokaler Ingest blockiert.
→ Guardrail blockiert lokalen Ingest
→ Global Reindex erforderlich
### `ChunkWriteService`
Kapselt die Schreibzugriffe auf den Chunk-Bestand:
- Gesamtanzahl zählen
- Chunks nach `document_id` kompaktieren
- neue Chunks anhängen
- vollständigen Rewrite durchführen
### `VectorRebuildService`
Führt den vollständigen FAISS-Rebuild aus und aktualisiert anschließend die Runtime-Metadaten.
### `IngestFlow`
Orchestriert:
- Dokument-Ingest
- Global Reindex
- Dokumentlöschung inklusive Rebuild
---
# 10. Reindex-Strategie
## Guardrails
## 10.1 Lokaler Re-Ingest
RetrieX schützt sich gegen strukturellen Drift.
- Compaction nur für document_id
- Neue Chunks append
- Danach vollständiger FAISS-Rebuild
Lokaler Ingest darf nicht weiterlaufen, wenn sich z. B. diese Strukturparameter geändert haben:
## 10.2 Global Reindex
- Embedding-Modell
- Embedding-Dimension
- Chunk-Größe
- Chunk-Overlap
- Scoring-Version
- Index-Strukturkonfiguration
- Alle aktiven Dokumente neu ingestieren
- index.ndjson komplett neu schreiben
- index_version++
- FAISS neu bauen
Dann ist ein **Global Reindex** erforderlich.
---
# 11. Rollenmodell
## Retrieval im aktuellen Stand
- SUPER_ADMIN
- KNOWLEDGE_ADMIN
- Wissensredaktion
- Frontend-User
Der aktive Retriever ist:
Dokumente sind immutable.
Chunks sind niemals manuell editierbar.
- `App\Knowledge\Retrieval\NdjsonHybridRetriever`
Wichtig:
„Hybrid“ bedeutet hier im verifizierten Code nicht einfach „Keyword + Vektor“, sondern eine orchestrierte Kombination aus mehreren Retrieval-Schritten.
### Aktuelle Retrieval-Pipeline
1. Query Cleaning
2. Query Enrichment
3. Intent-Erkennung
4. Katalog-/Listenrouting
5. Tag-Routing auf Kandidatendokumente
6. globale Vektorsuche
7. optionale gescopte Vektorsuche auf Kandidatendokumente
8. Fusion der Treffer
9. Selektion der finalen Chunk-IDs
10. Laden der finalen Chunk-Texte aus NDJSON
### Verwendete Bausteine
- `QueryCleaner`
- `QueryEnricher`
- `IntentLite`
- `SalesIntentLite`
- `CatalogIntentLite`
- `IntentRouteResolver`
- `TagRoutingService`
- `VectorSearchClient`
- `NdjsonChunkLookup`
### Besondere Logiken
- Listenfragen werden gesondert erkannt
- Kataloganfragen können direkt einen Katalogblock statt regulärer Chunks liefern
- globale und gescopte Treffer werden gefused
- Chunk-Selektion ist dokument- und abstandsbegrenzt
---
# 12. Guardrails
## Vektor-Layer
Verhindert:
### Komponenten
- Embedding-Modell-Drift
- Chunking-Parameter-Drift
- Index-Format-Drift
- Parallele Ingest-Jobs
- Live-Änderung aktiver Ingest-Profile
- `python/vector/vector_ingest.py`
- `python/vector/vector_search.py`
- `python/vector/vector_service.py`
### Betriebsmodus
Der produktive Zugriff erfolgt primär über den HTTP-basierten Vector-Service.
Service-URL im aktuellen Default:
- `http://127.0.0.1:8090`
### Aufgaben des Vector-Service
- Embedding-Modell laden
- FAISS-Hauptindex laden
- Tag-Index laden
- Suchanfragen für Chunks beantworten
- Suchanfragen für Tags beantworten
- Reloads auf neue Indexstände ermöglichen
### Wichtige Eigenschaft
Der Python-Service hält Modell und Indizes persistent im RAM, um wiederholte Suchanfragen deutlich schneller zu bedienen.
---
# 13. Wichtige Symfony Commands (validiert)
## Prompt-Aufbau
mto:agent:ingest:run
mto:agent:vector:control
mto:agent:vector:ingest
mto:agent:vector:search
`PromptBuilder` setzt den finalen LLM-Prompt im aktuellen Stand aus diesen Blöcken zusammen:
Namespace ist durchgehend:
1. **SYSTEM**
Aktiver System-Prompt aus der Datenbank
mto:agent:*
2. **CONVERSATION CONTEXT (authoritative)**
Gesprächshistorie des Nutzers
3. **LIVE SHOP RESULTS (authoritative for products)**
Shopware-Treffer, falls Commerce aktiv ist
4. **RETRIEVED KNOWLEDGE (supporting)**
Wissens-Chunks aus dem Retriever
5. **CONTENT FROM URL (supporting)**
Optional extrahierter Webinhalt
6. **USER QUESTION**
Aktuelle Nutzerfrage
Wichtig:
Für Produktfragen behandelt das System Shopdaten explizit als **führende Quelle**.
---
# 14. Systemgrenzen
## Commerce- und Shopware-Integration
Das System ist ausgelegt für:
RetrieX besitzt im aktuellen Stand eine optionale Shopware-Store-API-Integration.
- >200.000 Chunks
- Deterministische Reproduzierbarkeit
- Enterprise-Governance
- Drift-Sicherheit
- Skalierbare Erweiterung
### Aktivierung
Die Shop-Suche wird nur genutzt, wenn `CommerceIntentLite` eine passende Anfrage erkennt.
Mögliche Zustände:
- `none`
- `product_search`
- `advisory_product_search`
### Signale für Commerce
Der Intent-Detektor reagiert u. a. auf:
- Such- und Produktbegriffe
- SKU-/Zahlenmuster
- Preisangaben
- Größenangaben
- Farbangaben
- beratende Formulierungen wie „passt“, „besser“, „empfiehl“
### Aktueller Shop-Flow
1. Commerce-Intent erkennen
2. LLM erzeugt zunächst eine kurze Shop-Suchphrase
3. `ShopSearchService` ruft `CommerceQueryParser` auf
4. `ShopwareCriteriaBuilder` baut Suchkriterien
5. `StoreApiClient` ruft `/store-api/search` auf
6. Ergebnisse werden zu `ShopProductResult` gemappt
7. Ergebnisse fließen in den Prompt ein
### Wichtige Anmerkung
Der aktuelle Code kombiniert also:
- **heuristische Intent-Erkennung**
- **LLM-unterstützte Kurzsuchphrase**
- **deterministische Nachverarbeitung im Parser**
- **Store-API-Suche**
### Aktuelle Shop-Parameter
In `services.yaml` sind u. a. konfiguriert:
- `mto.commerce.enabled`
- `mto.commerce.max_shop_results`
- `mto.commerce.shop_timeout`
- `mto.commerce.store_api_base_url`
- `mto.commerce.sales_channel_access_key`
---
# 15. Nicht Bestandteil
## Antwort-Streaming
- Kein manuelles Chunk-Editing
- Kein Keyword-Retrieval
- Kein partieller FAISS-Rebuild
- Kein In-Place Index Update
Die Browserausgabe erfolgt per **Server-Sent Events**.
Immer vollständiger Rebuild.
### Eigenschaften des aktuellen SSE-Flows
- Output Buffer werden aktiv geleert
- Cookies werden vor Streamstart weitergereicht
- Chunks werden direkt als SSE `data:`-Zeilen übertragen
- Zeilenumbrüche bleiben erhalten
- am Ende wird ein `done`-Event gesendet
Das ist im aktuellen System die bevorzugte Streaming-Variante für den Browser.
---
# 16. Zusammenfassung
## Projektstruktur
Das System implementiert eine deterministische, governance-stabile RAG-Architektur mit:
Die zentralen Verzeichnisse im aktuellen Stand sind:
- NDJSON als Streaming-Single-Source
- Vollständigem FAISS-Rebuild
- Persistenter Vector-Service-Schicht
- Versionierter Dokumentverwaltung
- Strikter Guardrail-Logik
- `src/`
- `config/`
- `templates/`
- `migrations/`
- `python/`
- `public/`
- `var/`
- `bin/`
Diese README entspricht vollständig dem aktuellen Code-Stand der rag.zip.
### Wichtige Bereiche in `src/`
- `Agent/`
- `Commerce/`
- `Context/`
- `Controller/`
- `Index/`
- `Ingest/`
- `Intent/`
- `Knowledge/`
- `Routing/`
- `Shopware/`
- `Tag/`
- `Vector/`
---
## Wichtige Commands
Verifizierte relevante Commands im aktuellen Stand:
- `mto:agent:chat`
- `mto:agent:ingest:run`
- `mto:agent:ingest:version`
- `mto:agent:vector:control`
- `mto:agent:vector:rebuild`
- `mto:agent:vector:health`
- `mto:agent:tags:export`
- `mto:agent:tags:rebuild`
- `mto:agent:tags:job:run`
- `mto:agent:tag:health`
- `mto:agent:system:rebuild`
- `mto:agent:test:shop-search`
- `mto:agent:test-vector`
- `mto:agent:user:create`
### Besonders wichtig
#### Vector-Service steuern
```bash
bin/console mto:agent:vector:control --status
bin/console mto:agent:vector:control --install --start --reload