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

12 KiB

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:

/admin/evals/

Im Bereich „Eval-Case erstellen“ können neue Cases für folgende Typen angelegt werden:

retrieval
shop_query
followup
answer_guard

Nach dem Speichern wird der Case in die passende Datei geschrieben:

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:

{
  "expected_query": "testomat 808",
  "must_not_include_terms": [
    "indikator",
    "300"
  ]
}

Weniger gut:

{
  "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.

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:

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:

Buchstaben
Zahlen
_
-

Gute Beispiele:

retrieval_semantic_chlor_clt_001
shop_query_indicator_300_exact_002
followup_main_device_price_002
answer_guard_unknown_medium_001

Nicht verwenden:

Test 1
shop query indikator 300
gerät/frage/neue-version

Empfohlenes Schema:

<typ>_<thema>_<ziel>_<nummer>

Beispiel:

followup_testomat808_device_price_001

3. Prompt

Hier kommt exakt der Nutzerprompt hinein, der getestet werden soll.

Beispiele:

welches geraet ist fuer chlorueberwachung gedacht
was kostet der indikator
und was kostet das gerät selber
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:

{
}

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

{
  "min_results": 1
}

Retrieval-Case mit erwarteter Dokument-ID

{
  "min_results": 1,
  "must_include_one_of_document_ids": [
    "DOKUMENT-ID-HIER"
  ]
}

Retrieval-Case mit mehreren möglichen Ziel-Dokumenten

{
  "min_results": 1,
  "must_include_one_of_document_ids": [
    "DOKUMENT-ID-1",
    "DOKUMENT-ID-2"
  ]
}

Retrieval-Case mit Pflichtbegriffen

{
  "min_results": 1,
  "must_include_any_terms": [
    "lieferung",
    "versand"
  ]
}

Retrieval-Case mit verbotenen Dokumenten

{
  "min_results": 1,
  "must_not_include_document_ids": [
    "FALSCHE-DOKUMENT-ID"
  ]
}

Retrieval-Case für No-Result / Unsinn

{
  "max_results": 0
}

Empfohlene Retrieval-Struktur

{
  "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:

was kostet der Testomat 808 Indikator 300

Assert-JSON:

{
  "expected_query": "testomat 808 300 indikator"
}

Shopquery mit Pflicht- und Verbotsbegriffen

{
  "must_include_terms": [
    "testomat",
    "808",
    "300",
    "indikator"
  ],
  "must_not_include_terms": [
    "300 s",
    "301",
    "302",
    "303"
  ]
}

Query darf nicht auf Noise fallen

{
  "must_not_equal_query": "information"
}
{
  "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:

{
  "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:

was kostet der indikator

History-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:

{
  "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:

und was kostet das gerät selber

History-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:

{
  "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:

Nur "Indikator 300" ohne Geräteanker kann zu unklar sein.

Nicht zu viel:

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:

dsgfsdgfsdgf

Assert-JSON:

{
  "max_results": 0
}

Erfundenes Medium soll nicht als echtes Produkt beantwortet werden

Prompt:

welcher testomat misst drachenblut

Assert-JSON:

{
  "must_not_include_terms": [
    "drachenblut"
  ]
}

Falsches Dokument darf nicht gezogen werden

{
  "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:

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:

[
  {
    "prompt": "vorherige Nutzerfrage",
    "answer": "vorherige Antwort oder relevanter Auszug"
  }
]

Mehrere Turns:

[
  {
    "prompt": "erste Frage",
    "answer": "erste Antwort"
  },
  {
    "prompt": "zweite Frage",
    "answer": "zweite Antwort"
  }
]

Wichtig:

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:

Sichtbare Shop-Ergebnisse enthalten Testomat 808 und Testomat 808 Indikator 300.
Der Nutzer fragt nach dem Gerät selber.

Empfehlung:

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

followup

Neue Case-ID

followup_testomat808_main_device_price_002

Prompt

und was kostet das gerät selber

Assert-JSON

{
  "expected_query": "testomat 808",
  "must_include_terms": [
    "testomat",
    "808"
  ],
  "must_not_include_terms": [
    "indikator",
    "300",
    "141001",
    "140001"
  ]
}

History-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:

/admin/evals/

Oder per CLI:

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:

php bin/console mto:agent:eval:run followup

Praktische Checkliste

Vor dem Speichern prüfen:

[ ] 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:

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:

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:

Keine neue Genauigkeitslogik ohne konkreten roten oder fachlich wichtigen Eval-Fall.

Daher sollten neue Optimierungen möglichst immer so ablaufen:

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