Files
MtoRagSystem/RETRIEX-EVAL-CASE-HOWTO.md
team 1 6dced1c4df p101
2026-05-12 10:56:50 +02:00

731 lines
12 KiB
Markdown

# RetrieX How-to: Neue Eval-Cases korrekt erstellen
Dieses How-to beschreibt, wie neue Regressionstests für die RetrieX Eval-Suite über den Admin-Bereich angelegt werden.
Ziel ist, neue rote oder fachlich wichtige Fälle dauerhaft abzusichern, ohne direkt Core-Logik, Retrieval-Regeln oder Shopquery-Heuristiken zu verändern.
## Einstieg
Admin-Pfad:
```text
/admin/evals/
```
Im Bereich **„Eval-Case erstellen“** können neue Cases für folgende Typen angelegt werden:
```text
retrieval
shop_query
followup
answer_guard
```
Nach dem Speichern wird der Case in die passende Datei geschrieben:
```text
tests/evals/cases/retrieval.ndjson
tests/evals/cases/shop_query.ndjson
tests/evals/cases/followup.ndjson
tests/evals/cases/answer_guard.ndjson
```
---
## Grundregel
Ein guter Eval-Case prüft genau **einen klaren Sachverhalt**.
Gut:
```json
{
"expected_query": "testomat 808",
"must_not_include_terms": [
"indikator",
"300"
]
}
```
Weniger gut:
```json
{
"expected_query": "testomat 808",
"must_include_terms": [
"testomat",
"808",
"gerät",
"preis",
"wasserhärte"
],
"must_not_include_terms": [
"indikator",
"300",
"testomat 2000",
"chlor",
"versand"
]
}
```
Je kleiner und eindeutiger der Case ist, desto besser eignet er sich als Regressionstest.
---
# Felder im Admin
## 1. Eval-Typ
Wähle den Typ passend zum Ziel des Tests.
```text
retrieval → prüft, ob die richtigen RAG-Dokumente/Chunks gefunden werden
shop_query → prüft, welche Shopquery aus einem direkten Prompt entsteht
followup → prüft, welche Shopquery aus Prompt + Chatverlauf entsteht
answer_guard → prüft No-Answer-, Nicht-Halluzinations- oder Evidenzfälle
```
Faustregel:
```text
Wird das richtige Dokument gefunden? → retrieval
Wird die richtige Shopquery erzeugt? → shop_query
Versteht RetrieX die Folgefrage im Verlauf? → followup
Erfindet RetrieX nichts bei schwacher Evidenz? → answer_guard
```
---
## 2. Neue Case-ID
Die Case-ID muss eindeutig sein und darf nur folgende Zeichen enthalten:
```text
Buchstaben
Zahlen
_
-
```
Gute Beispiele:
```text
retrieval_semantic_chlor_clt_001
shop_query_indicator_300_exact_002
followup_main_device_price_002
answer_guard_unknown_medium_001
```
Nicht verwenden:
```text
Test 1
shop query indikator 300
gerät/frage/neue-version
```
Empfohlenes Schema:
```text
<typ>_<thema>_<ziel>_<nummer>
```
Beispiel:
```text
followup_testomat808_device_price_001
```
---
## 3. Prompt
Hier kommt exakt der Nutzerprompt hinein, der getestet werden soll.
Beispiele:
```text
welches geraet ist fuer chlorueberwachung gedacht
```
```text
was kostet der indikator
```
```text
und was kostet das gerät selber
```
```text
welcher testomat misst drachenblut
```
Der Prompt sollte möglichst so eingetragen werden, wie er real im Chat vorkommt. Tippfehler dürfen bewusst enthalten sein, wenn genau dieses Verhalten abgesichert werden soll.
---
## 4. Assert-JSON
Das Assert-JSON beschreibt, was der Test prüfen soll.
Das Feld muss immer ein gültiges JSON-Objekt sein:
```json
{
}
```
Wichtig:
- Keine Kommentare im JSON
- Keine trailing commas
- Doppelte Anführungszeichen verwenden
- Das Feld muss ein Objekt `{ ... }` sein, kein Array
---
# Eval-Typen und Beispiele
## A) Retrieval-Case
Retrieval-Cases prüfen, ob die richtigen RAG-Dokumente oder Chunks gefunden werden.
### Minimaler positiver Retrieval-Case
```json
{
"min_results": 1
}
```
### Retrieval-Case mit erwarteter Dokument-ID
```json
{
"min_results": 1,
"must_include_one_of_document_ids": [
"DOKUMENT-ID-HIER"
]
}
```
### Retrieval-Case mit mehreren möglichen Ziel-Dokumenten
```json
{
"min_results": 1,
"must_include_one_of_document_ids": [
"DOKUMENT-ID-1",
"DOKUMENT-ID-2"
]
}
```
### Retrieval-Case mit Pflichtbegriffen
```json
{
"min_results": 1,
"must_include_any_terms": [
"lieferung",
"versand"
]
}
```
### Retrieval-Case mit verbotenen Dokumenten
```json
{
"min_results": 1,
"must_not_include_document_ids": [
"FALSCHE-DOKUMENT-ID"
]
}
```
### Retrieval-Case für No-Result / Unsinn
```json
{
"max_results": 0
}
```
### Empfohlene Retrieval-Struktur
```json
{
"min_results": 1,
"must_include_one_of_document_ids": [
"DOKUMENT-ID-HIER"
],
"must_include_any_terms": [
"wichtiger fachbegriff",
"produktname"
]
}
```
---
## B) Shopquery-Case
Shopquery-Cases prüfen, welche Shopquery aus einem direkten Prompt entsteht.
### Exakte Shopquery
Prompt:
```text
was kostet der Testomat 808 Indikator 300
```
Assert-JSON:
```json
{
"expected_query": "testomat 808 300 indikator"
}
```
### Shopquery mit Pflicht- und Verbotsbegriffen
```json
{
"must_include_terms": [
"testomat",
"808",
"300",
"indikator"
],
"must_not_include_terms": [
"300 s",
"301",
"302",
"303"
]
}
```
### Query darf nicht auf Noise fallen
```json
{
"must_not_equal_query": "information"
}
```
### Multi-Produkt- oder Link-Follow-up mit Einzelqueries
```json
{
"expected_individual_queries": [
"testomat 2000 self clean",
"testomat 2000 cal",
"testomat 808"
],
"expected_individual_queries_exact": true
}
```
### Empfehlung für Shopquery-Cases
Nicht jeden Case sofort zu streng mit `expected_query` absichern. Bei noch variabler Query-Bildung ist oft besser:
```json
{
"must_include_terms": [
"testomat",
"808",
"sio2"
],
"must_not_include_terms": [
"gerät",
"möchte",
"messen"
]
}
```
`expected_query` nur verwenden, wenn die Query bereits stabil und bewusst exakt sein soll.
---
## C) Follow-up-Case
Follow-up-Cases prüfen, ob RetrieX den Verlauf korrekt nutzt.
Bei `followup` ist **History-JSON praktisch Pflicht**, weil sonst kein echter Verlauf getestet wird.
### Beispiel: Indikatorpreis nach Verlauf
Prompt:
```text
was kostet der indikator
```
History-JSON:
```json
[
{
"prompt": "Was ist der niedrigste Grenzwert für die Wasserhärte, welcher mit einem Testomaten überwacht werden kann?",
"answer": "Der niedrigste Grenzwert für die Wasserhärte beträgt 0,02 °dH. Dieser Wert wird vom Testomat 808 gemessen."
},
{
"prompt": "mit welchem indikator",
"answer": "Der niedrigste messbare Grenzwert für Wasserhärte mit dem Testomat 808 wird mit dem Indikatortyp 300 erreicht."
}
]
```
Assert-JSON:
```json
{
"expected_query": "testomat 808 300 indikator",
"must_include_terms": [
"testomat",
"808",
"300",
"indikator"
],
"must_not_include_terms": [
"300 s",
"301",
"302",
"303",
"testomat 2000"
]
}
```
### Beispiel: Wechsel vom Indikator zurück zum Hauptgerät
Prompt:
```text
und was kostet das gerät selber
```
History-JSON:
```json
[
{
"prompt": "was kostet der indikator",
"answer": "Shop-Suche abgeschlossen. Gesendete Suchquery: testomat 808 300 indikator. Testomat® 808 Indikator 300 500 ml, Produkt-Nummer 141001. Testomat® 808 Indikator 300 2 x 100 ml, Produkt-Nummer 140001. Der zugehörige Testomat ist Testomat 808."
}
]
```
Assert-JSON:
```json
{
"expected_query": "testomat 808",
"must_include_terms": [
"testomat",
"808"
],
"must_not_include_terms": [
"indikator",
"300",
"141001",
"140001"
]
}
```
### Empfehlung für Follow-up-Cases
Die History sollte genau die Informationen enthalten, die der echte Chat vorher hatte.
Nicht zu wenig:
```text
Nur "Indikator 300" ohne Geräteanker kann zu unklar sein.
```
Nicht zu viel:
```text
Ein kompletter langer Chatverlauf kann den Case unnötig instabil machen.
```
Gut ist ein kurzer, fachlich relevanter Auszug.
---
## D) Answer-Guard-Case
Answer-Guard-Cases prüfen, dass RetrieX bei Unsinn, schwacher Evidenz oder falschen Zuordnungen nichts erfindet.
### Unsinn soll keine Treffer liefern
Prompt:
```text
dsgfsdgfsdgf
```
Assert-JSON:
```json
{
"max_results": 0
}
```
### Erfundenes Medium soll nicht als echtes Produkt beantwortet werden
Prompt:
```text
welcher testomat misst drachenblut
```
Assert-JSON:
```json
{
"must_not_include_terms": [
"drachenblut"
]
}
```
### Falsches Dokument darf nicht gezogen werden
```json
{
"min_results": 1,
"must_not_include_document_ids": [
"FALSCHE-DOKUMENT-ID"
]
}
```
### Empfehlung für Answer-Guard-Cases
Bei Answer-Guard-Cases möglichst nicht auf einzelne Wörter im kompletten Retrieval-Text überreagieren. Besser sind:
```text
Dokument-IDs
klare Produktnamen
klare verbotene Zielbegriffe
max_results bei Unsinn
```
Ein Wort irgendwo im Retrieval-Kontext ist nicht automatisch ein fachlicher Fehler.
---
# Optionales Feld: History-JSON
History-JSON wird vor allem für `followup` verwendet.
Format:
```json
[
{
"prompt": "vorherige Nutzerfrage",
"answer": "vorherige Antwort oder relevanter Auszug"
}
]
```
Mehrere Turns:
```json
[
{
"prompt": "erste Frage",
"answer": "erste Antwort"
},
{
"prompt": "zweite Frage",
"answer": "zweite Antwort"
}
]
```
Wichtig:
```text
History-JSON ist ein Array [...]
Assert-JSON ist ein Objekt {...}
```
---
# Optionales Feld: Request Context Hint
Dieses Feld kann meistens leer bleiben.
Es ist nur sinnvoll, wenn ein Case zusätzlichen Kontext simulieren soll, der nicht sauber über History abbildbar ist.
Beispiel:
```text
Sichtbare Shop-Ergebnisse enthalten Testomat 808 und Testomat 808 Indikator 300.
Der Nutzer fragt nach dem Gerät selber.
```
Empfehlung:
```text
Für normale Regressionen lieber History-JSON verwenden.
Request Context Hint nur für Spezialfälle nutzen.
```
---
# Vollständiges Beispiel: Follow-up-Gerätepreis
## Eval-Typ
```text
followup
```
## Neue Case-ID
```text
followup_testomat808_main_device_price_002
```
## Prompt
```text
und was kostet das gerät selber
```
## Assert-JSON
```json
{
"expected_query": "testomat 808",
"must_include_terms": [
"testomat",
"808"
],
"must_not_include_terms": [
"indikator",
"300",
"141001",
"140001"
]
}
```
## History-JSON
```json
[
{
"prompt": "was kostet der indikator",
"answer": "Shop-Suche abgeschlossen. Gesendete Suchquery: testomat 808 300 indikator. Testomat® 808 Indikator 300 500 ml, Produkt-Nummer 141001. Testomat® 808 Indikator 300 2 x 100 ml, Produkt-Nummer 140001. Der zugehörige Testomat ist Testomat 808."
}
]
```
## Request Context Hint
Leer lassen.
---
# Nach dem Speichern prüfen
Nach dem Speichern sollte der passende Eval-Typ ausgeführt werden.
Im Admin:
```text
/admin/evals/
```
Oder per CLI:
```bash
php bin/console mto:agent:config:validate
php bin/console mto:agent:eval:run retrieval
php bin/console mto:agent:eval:run shop_query
php bin/console mto:agent:eval:run followup
php bin/console mto:agent:eval:run answer_guard
```
Für einen einzelnen Typ:
```bash
php bin/console mto:agent:eval:run followup
```
---
# Praktische Checkliste
Vor dem Speichern prüfen:
```text
[ ] Eval-Typ passt zum Ziel
[ ] Case-ID ist eindeutig
[ ] Case-ID enthält nur Buchstaben, Zahlen, _ oder -
[ ] Prompt ist realistisch und exakt
[ ] Assert-JSON ist gültiges JSON-Objekt
[ ] History-JSON ist bei Follow-up-Cases vorhanden
[ ] History-JSON ist gültiges JSON-Array
[ ] Der Case prüft nur einen klaren Sachverhalt
[ ] Assertions sind nicht unnötig streng
[ ] Nach dem Speichern läuft der passende Eval-Typ grün
```
---
# Wann ein neuer Eval-Case angelegt werden sollte
Ein neuer Case ist sinnvoll, wenn:
```text
ein realer Prompt rot war
ein wichtiger grüner Flow dauerhaft abgesichert werden soll
ein Tippfehler-/Noise-Fall stabil bleiben soll
eine Produktidentität nicht verloren gehen darf
eine falsche Dokumentzuordnung verhindert werden soll
eine No-Answer-Situation nicht halluzinieren darf
```
Kein neuer Case ist nötig, wenn:
```text
nur die Formulierung einer Antwort leicht anders war
der Prompt fachlich nicht relevant ist
die Erwartung nicht eindeutig definiert werden kann
der Case mehrere unabhängige Dinge gleichzeitig prüfen würde
```
---
# Leitlinie
Ab RetrieX v1.6.2 gilt:
```text
Keine neue Genauigkeitslogik ohne konkreten roten oder fachlich wichtigen Eval-Fall.
```
Daher sollten neue Optimierungen möglichst immer so ablaufen:
```text
1. Prompt testen
2. Verhalten bewerten
3. Wenn wichtig: Eval-Case anlegen
4. Eval grün bekommen
5. Erst danach Logik, YAML oder Parameter ändern
```