# 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 ___ ``` 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 ```