174 lines
5.2 KiB
Markdown
174 lines
5.2 KiB
Markdown
# RetrieX Patch 20 - LLM-assisted Input Normalization before Routing
|
|
|
|
## Ziel
|
|
|
|
Patch 20 ersetzt den p19-Symptom-Fix fuer einzelne Preis-Tippfehler (`kpstet`, `ksotet`) durch eine generische, LLM-gestuetzte Eingabe-Normalisierung vor der Intent-/Commerce-/Retrieval-Erkennung.
|
|
|
|
Der Ausloeser war der Flow:
|
|
|
|
1. `Was ist der niedrigste Grenzwert fuer die Wasserhaerte, welcher mit einem Testomaten ueberwacht werden kann?`
|
|
2. `mit welchem indikator wird der wert gemessen`
|
|
3. `was kpstet der indikator`
|
|
|
|
p19 konnte diesen konkreten Tippfehler per YAML-Liste auffangen, skaliert aber nicht. p20 normalisiert die Nutzereingabe vorher, z. B. `was kpstet der indikator` -> `was kostet der indikator`, ohne Produktkontext fachlich aufzufuellen.
|
|
|
|
## Architektur
|
|
|
|
Der Normalisierungsschritt sitzt in `AgentRunner::run()` direkt nach `analyze_request` und vor:
|
|
|
|
- URL-/Quellenpruefung
|
|
- CommerceIntentLite-Erkennung
|
|
- Knowledge-Retrieval-Prompt-Bau
|
|
- Shop-Query-Optimierung
|
|
- finalem PromptBuilder-Aufruf
|
|
|
|
Das LLM ist ueber den bereits vorhandenen `final class OllamaClient` angebunden und nutzt analog zur Shop-Query-Optimierung:
|
|
|
|
```php
|
|
foreach ($this->ollamaClient->stream($normalizationPrompt) as $token) {
|
|
...
|
|
}
|
|
```
|
|
|
|
Die Originalfrage bleibt erhalten und wird weiterhin in die Conversation History geschrieben. Die normalisierte Frage wird nur als effektive Eingabe fuer Routing, Intent, Retrieval, Shop-Optimierung und Antwortgenerierung genutzt.
|
|
|
|
## Guardrails
|
|
|
|
Die Normalisierung darf nur offensichtliche Tippfehler korrigieren. Sie darf nicht fachlich interpretieren.
|
|
|
|
Konkrete Schutzmechanismen:
|
|
|
|
- YAML-konfigurierbarer Normalizer-Prompt in `config/retriex/agent.yaml`
|
|
- keine fachliche Kontextauflösung im Normalizer
|
|
- keine Produktnamen, Modellnummern, Messwerte, Artikelnummern oder Einsatzbereiche hinzufuegen
|
|
- vage Referenzen wie `der indikator` bleiben vage und werden erst spaeter ueber bestehende Kontextlogik aufgeloest
|
|
- URL-/Code-aehnliche Eingaben werden uebersprungen
|
|
- maximale Eingabe-/Ausgabelaenge
|
|
- maximale Laengenvergroesserung
|
|
- maximale Token-Zunahme
|
|
- neue Zahlen in der normalisierten Eingabe werden verworfen
|
|
- bei Fehlern, leerer Ausgabe oder unsicherem Ergebnis faellt RetrieX auf die Originalfrage zurueck
|
|
|
|
## Geaenderte Dateien
|
|
|
|
- `src/Agent/AgentRunner.php`
|
|
- `src/Config/AgentRunnerConfig.php`
|
|
- `src/Config/RetriexEffectiveConfigProvider.php`
|
|
- `config/retriex/agent.yaml`
|
|
- `config/retriex/intent.yaml`
|
|
- `config/retriex/commerce.yaml`
|
|
|
|
## Entfernt aus p19-Symptomlisten
|
|
|
|
Die expliziten Tippfehler `kpstet` und `ksotet` wurden entfernt aus:
|
|
|
|
- `intent.yaml` strong/non-product/price/explicit-commerce Listen
|
|
- `commerce.yaml` stopword- und correction-Listen
|
|
|
|
Damit ist p20 nicht mehr auf diese konkreten Fehlerlisten angewiesen.
|
|
|
|
## Erwartetes Verhalten
|
|
|
|
Eingabe:
|
|
|
|
```text
|
|
was kpstet der indikator
|
|
```
|
|
|
|
Interne Normalisierung:
|
|
|
|
```text
|
|
was kostet der indikator
|
|
```
|
|
|
|
Danach sollte der bestehende Commerce-/Shop-Follow-up-Flow greifen:
|
|
|
|
- Commerce Intent wird erkannt
|
|
- Shop-Suche wird angefragt
|
|
- referenzieller Kontext `Indikatortyp 300` kann durch bestehende Shop-Query-Context-Anchor-Logik ergaenzt werden
|
|
|
|
## Lokal ausgefuehrte Pruefungen
|
|
|
|
Im Container ausgefuehrt:
|
|
|
|
```bash
|
|
php -l src/Agent/AgentRunner.php
|
|
php -l src/Config/AgentRunnerConfig.php
|
|
php -l src/Config/RetriexEffectiveConfigProvider.php
|
|
python3 - <<'PY'
|
|
import yaml
|
|
from pathlib import Path
|
|
for rel in ['config/retriex/agent.yaml','config/retriex/intent.yaml','config/retriex/commerce.yaml']:
|
|
with (Path('.') / rel).open() as f:
|
|
yaml.safe_load(f)
|
|
PY
|
|
php -r '$patterns=["/^(?:normalisiert|korrigiert|corrected|normalized)\\s*:\\s*/iu","/https?:\\/\\//iu","/\\bwww\\./iu","/```/u"]; foreach($patterns as $p){ if(@preg_match($p, "was kpstet der indikator")===false){ exit(1); } } echo "OK\n";'
|
|
grep -R "kpstet\|ksotet" -n config src || true
|
|
```
|
|
|
|
Ergebnis:
|
|
|
|
- PHP-Syntax: OK
|
|
- YAML-Parse: OK
|
|
- Regex-Smoke-Test: OK
|
|
- `kpstet` / `ksotet`: nicht mehr in `config` oder `src`
|
|
|
|
## Nicht lokal ausfuehrbar
|
|
|
|
Die Symfony-/Composer-basierten Pflichtchecks konnten im Container nicht ausgefuehrt werden, weil im ZIP keine installierten Vendor-Dependencies enthalten sind.
|
|
|
|
Bitte nach dem Einspielen ausfuehren:
|
|
|
|
```bash
|
|
bin/console mto:agent:config:validate
|
|
bin/console mto:agent:regression:test
|
|
bin/console mto:agent:config:audit-source --details
|
|
bin/console mto:agent:config:audit-patterns --details
|
|
```
|
|
|
|
## Empfohlene Regressionstests
|
|
|
|
1. Stabiler v1.5.1-Flow:
|
|
|
|
```text
|
|
Was ist der niedrigste Grenzwert fuer die Wasserhaerte, welcher mit einem Testomaten ueberwacht werden kann?
|
|
mit welchem indikator wird der wert gemessen
|
|
was kostet der indikator
|
|
```
|
|
|
|
2. Tippfehler-Flow:
|
|
|
|
```text
|
|
Was ist der niedrigste Grenzwert fuer die Wasserhaerte, welcher mit einem Testomaten ueberwacht werden kann?
|
|
mit welchem indikator wird der wert gemessen
|
|
was kpstet der indikator
|
|
```
|
|
|
|
Erwartung fuer beide Preisfragen:
|
|
|
|
- Shop-Suche wird angefragt
|
|
- Shop-Treffer werden genutzt
|
|
- keine Rueckkehr in RAG-only mit Testomat-2000-Indikatoren
|
|
|
|
3. Guardrail-Test:
|
|
|
|
```text
|
|
was kpstet der indikator 300
|
|
```
|
|
|
|
Erwartung:
|
|
|
|
- Normalisierung darf `300` erhalten
|
|
- keine neue Modellnummer / Artikelnummer hinzufuegen
|
|
|
|
4. URL-Skip-Test:
|
|
|
|
```text
|
|
pruefe https://example.com/test?x=kpstet
|
|
```
|
|
|
|
Erwartung:
|
|
|
|
- Normalisierung wird uebersprungen
|
|
- URL bleibt unveraendert
|