p101
This commit is contained in:
731
RETRIEX-EVAL-CASE-HOWTO.md
Normal file
731
RETRIEX-EVAL-CASE-HOWTO.md
Normal file
@@ -0,0 +1,731 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user