p86a-e
This commit is contained in:
@@ -105,6 +105,7 @@ parameters:
|
|||||||
- configuration_values.context_resolution.commercial_table_follow_up
|
- configuration_values.context_resolution.commercial_table_follow_up
|
||||||
- configuration_values.context_resolution.referential_terms
|
- configuration_values.context_resolution.referential_terms
|
||||||
- configuration_values.context_resolution.history_anchor_enrichment
|
- configuration_values.context_resolution.history_anchor_enrichment
|
||||||
|
- configuration_values.context_resolution.product_list_followup
|
||||||
- configuration_values.context_resolution.meta_query_guard
|
- configuration_values.context_resolution.meta_query_guard
|
||||||
- configuration_values.context_resolution.rag_anchor_enrichment
|
- configuration_values.context_resolution.rag_anchor_enrichment
|
||||||
review_path_groups:
|
review_path_groups:
|
||||||
@@ -1106,6 +1107,92 @@ parameters:
|
|||||||
- /\b(?:indikator(?:typ)?|indicator(?:\s+type)?|reagenz(?:satz|typ)?|reagent(?:\s+set|\s+type)?|typ|type)\s+[A-Za-zÄÖÜäöüß]{0,8}\s*\d{1,5}(?:\s*[A-ZÄÖÜ]{1,4})?(?:\s*%)?\b/iu
|
- /\b(?:indikator(?:typ)?|indicator(?:\s+type)?|reagenz(?:satz|typ)?|reagent(?:\s+set|\s+type)?|typ|type)\s+[A-Za-zÄÖÜäöüß]{0,8}\s*\d{1,5}(?:\s*[A-ZÄÖÜ]{1,4})?(?:\s*%)?\b/iu
|
||||||
template: '{anchor} {query}'
|
template: '{anchor} {query}'
|
||||||
max_query_terms: 2
|
max_query_terms: 2
|
||||||
|
product_list_followup:
|
||||||
|
origin: genre_native
|
||||||
|
enabled: true
|
||||||
|
# Handles referential follow-ups such as "links/preise zu den
|
||||||
|
# produkten aus dem shop". It only rewrites weak meta queries and
|
||||||
|
# uses product/model anchors from the latest assistant answer.
|
||||||
|
weak_query_max_terms: 4
|
||||||
|
weak_query_max_residual_terms: 0
|
||||||
|
max_anchors: 4
|
||||||
|
template: '{anchors}'
|
||||||
|
product_terms:
|
||||||
|
- produkt
|
||||||
|
- produkte
|
||||||
|
- produkten
|
||||||
|
- produkteintrag
|
||||||
|
- produkteinträge
|
||||||
|
- produkteintraege
|
||||||
|
- artikel
|
||||||
|
- gerät
|
||||||
|
- geraet
|
||||||
|
- geräte
|
||||||
|
- geraete
|
||||||
|
- modell
|
||||||
|
- modelle
|
||||||
|
shop_terms:
|
||||||
|
- shop
|
||||||
|
- shopdaten
|
||||||
|
- shop-daten
|
||||||
|
- link
|
||||||
|
- links
|
||||||
|
- url
|
||||||
|
- urls
|
||||||
|
- produktlink
|
||||||
|
- produktlinks
|
||||||
|
- preis
|
||||||
|
- preise
|
||||||
|
- kosten
|
||||||
|
- kostet
|
||||||
|
noise_terms:
|
||||||
|
- gebe
|
||||||
|
- gib
|
||||||
|
- zeige
|
||||||
|
- zeig
|
||||||
|
- nenne
|
||||||
|
- mir
|
||||||
|
- bitte
|
||||||
|
- link
|
||||||
|
- links
|
||||||
|
- url
|
||||||
|
- urls
|
||||||
|
- produktlink
|
||||||
|
- produktlinks
|
||||||
|
- shop
|
||||||
|
- shopdaten
|
||||||
|
- shop-daten
|
||||||
|
- preis
|
||||||
|
- preise
|
||||||
|
- kosten
|
||||||
|
- kostet
|
||||||
|
- produkt
|
||||||
|
- produkte
|
||||||
|
- produkten
|
||||||
|
- artikel
|
||||||
|
- gerät
|
||||||
|
- geraet
|
||||||
|
- geräte
|
||||||
|
- geraete
|
||||||
|
- modell
|
||||||
|
- modelle
|
||||||
|
- zu
|
||||||
|
- zum
|
||||||
|
- zur
|
||||||
|
- aus
|
||||||
|
- den
|
||||||
|
- der
|
||||||
|
- die
|
||||||
|
- das
|
||||||
|
- dem
|
||||||
|
- diese
|
||||||
|
- diesen
|
||||||
|
- dieser
|
||||||
|
- dazu
|
||||||
|
- davon
|
||||||
|
anchor_patterns:
|
||||||
|
- '/(?:^|\R)[^\S\r\n]*(?:\d+[.)][^\S\r\n]*)?(?P<anchor>Testomat(?:®)?[^\S\r\n]+\d{3,4}(?:[^\S\r\n]+[A-Za-z0-9-]{1,12}){0,2})\b/iu'
|
||||||
|
- '/\b(?P<anchor>Testomat(?:®)?[^\S\r\n]+(?:\d{3,4}(?:[^\S\r\n]+(?=[A-Z0-9-]*[A-Z])[A-Z0-9-]{2,12}){0,2}|EVO(?:[^\S\r\n]+[A-Z]{2,8})?|ECO(?:[- ]?(?:PLUS|C))?|DUO(?:[^\S\r\n]+\d{3,4})?|LAB(?:[^\S\r\n]+[A-Z-]{1,8}){1,2}))\b/iu'
|
||||||
meta_query_guard:
|
meta_query_guard:
|
||||||
origin: genre_native
|
origin: genre_native
|
||||||
meta_only_terms:
|
meta_only_terms:
|
||||||
@@ -1336,6 +1423,8 @@ parameters:
|
|||||||
- evo
|
- evo
|
||||||
- eco
|
- eco
|
||||||
- plus
|
- plus
|
||||||
|
- self
|
||||||
|
- clean
|
||||||
- c
|
- c
|
||||||
- duo
|
- duo
|
||||||
adjacent_variant_patterns:
|
adjacent_variant_patterns:
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# RetrieX Patch p86b - Referential Product Links RAG Fallback
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Referenzielle Shop-Follow-ups wie:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gebe mir links zu den produkten aus dem shop
|
||||||
|
```
|
||||||
|
|
||||||
|
duerfen nicht als schwache Meta-Query wie `links zu aus` an Shopware gehen, wenn im vorherigen Kontext konkrete Produkte/Modelle genannt wurden.
|
||||||
|
|
||||||
|
## Problem in p86
|
||||||
|
|
||||||
|
p86 ersetzte schwache Produktlisten-Follow-up-Queries nur, wenn Produktanker im Commerce-History-Kontext gefunden wurden. In manchen Runs war dieser Kontext fuer diesen Follow-up-Pfad leer, gekuerzt oder nicht ausreichend nutzbar, obwohl die aktuelle RAG-Retrieval-Stufe passende Produktkontexte geladen hatte. Dadurch blieb die Query unveraendert bei `links zu aus`.
|
||||||
|
|
||||||
|
## Aenderung
|
||||||
|
|
||||||
|
`guardReferentialProductListShopQueryWithHistoryAnchors()` erhaelt nun zusaetzlich die aktuellen RAG-`knowledgeChunks`.
|
||||||
|
|
||||||
|
Reihenfolge:
|
||||||
|
|
||||||
|
1. Produkt-/Modellanker aus dem aktuellen Commerce-History-Kontext extrahieren.
|
||||||
|
2. Falls dort keine Anker gefunden werden: Produkt-/Modellanker aus den aktuellen RAG-Knowledge-Chunks extrahieren.
|
||||||
|
3. Nur bei referenziellen Produktlisten-Shop-Follow-ups und nur bei schwachen/noisy Shopqueries ersetzen.
|
||||||
|
4. Produktlisten-Anker-Patterns werden auf einzelne Zeilen begrenzt, damit `Testomat 808` nicht versehentlich mit dem naechsten Satz/Wort zusammengezogen wird.
|
||||||
|
|
||||||
|
Damit bleibt der Fix generisch:
|
||||||
|
|
||||||
|
- keine Sonderlogik fuer medizinische Geraete
|
||||||
|
- keine festen Produktnamen im PHP-Core
|
||||||
|
- keine neue Ranking-/Retrieval-/Shop-Matching-Logik
|
||||||
|
- keine automatische Shop-Ausloesung durch `geraet`
|
||||||
|
|
||||||
|
## Erwartetes Verhalten
|
||||||
|
|
||||||
|
```text
|
||||||
|
geraet zur messung Prozesswasser in medizinischen Geraeten
|
||||||
|
-> RAG-Antwort nennt z. B. Testomat 2000 self clean, Testomat 2000 CAL, Testomat 808
|
||||||
|
|
||||||
|
gebe mir links zu den produkten aus dem shop
|
||||||
|
-> Shopquery wird aus Produktankern gebildet, nicht aus `links zu aus`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Geaenderte Dateien
|
||||||
|
|
||||||
|
- `src/Agent/AgentRunner.php`
|
||||||
|
- `config/retriex/genre.yaml`
|
||||||
|
|
||||||
|
## Lokale Checks
|
||||||
|
|
||||||
|
- `php -l src/Agent/AgentRunner.php`
|
||||||
|
- `php -l src/Config/AgentRunnerConfig.php`
|
||||||
|
- `php -l src/Config/RetriexEffectiveConfigProvider.php`
|
||||||
|
- YAML parse
|
||||||
|
- p86b referential product-link fallback smoke
|
||||||
|
|
||||||
|
Symfony-Console-Checks muessen in der Zielumgebung mit vorhandenem `vendor/` ausgefuehrt werden.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# RETRIEX Patch 86C - Referential Product Links Empty-History Fallback
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Behebt den Fall, dass referenzielle Shop-Follow-ups wie
|
||||||
|
|
||||||
|
```text
|
||||||
|
gebe mir links zu den produkten aus dem shop
|
||||||
|
```
|
||||||
|
|
||||||
|
weiterhin als schwache Meta-Query wie `links zu aus` an Shopware gesendet werden, wenn der vorherige fachliche Antwortkontext nicht im Commerce-History-Kontext vorhanden ist.
|
||||||
|
|
||||||
|
## Ursache
|
||||||
|
|
||||||
|
p86/p86b enthielten bereits die generische Produktlisten-Follow-up-Logik inklusive RAG-Fallback. Die Guard-Methode wurde jedoch zu früh verlassen, sobald der Commerce-History-Kontext leer war:
|
||||||
|
|
||||||
|
```php
|
||||||
|
trim($commerceHistoryContext) === ''
|
||||||
|
```
|
||||||
|
|
||||||
|
Damit konnte der RAG-Fallback nicht greifen, obwohl aktuelle Knowledge-Chunks passende Produkt-/Modellanker enthielten.
|
||||||
|
|
||||||
|
## Änderung
|
||||||
|
|
||||||
|
- `src/Agent/AgentRunner.php`
|
||||||
|
- Entfernt den frühen Return bei leerem Commerce-History-Kontext in `guardReferentialProductListShopQueryWithHistoryAnchors()`.
|
||||||
|
- Nutzt History-Anker nur, wenn History vorhanden ist.
|
||||||
|
- Fällt sonst auf Produktanker aus aktuellen Knowledge-Chunks zurück.
|
||||||
|
- Log-Meldung präzisiert: History **oder RAG** Product Anchors.
|
||||||
|
|
||||||
|
## Erwartetes Verhalten
|
||||||
|
|
||||||
|
Vorher:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Follow-up: gebe mir links zu den produkten aus dem shop
|
||||||
|
Gesendete Suchquery: links zu aus
|
||||||
|
```
|
||||||
|
|
||||||
|
Nachher, wenn die vorherige/fachliche RAG-Antwort Produktanker wie Testomat-Modelle enthält:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Follow-up: gebe mir links zu den produkten aus dem shop
|
||||||
|
Gesendete Suchquery: testomat 2000 self clean testomat 2000 cal testomat 808
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Logik bleibt generisch:
|
||||||
|
|
||||||
|
- keine Sonderlogik für medizinische Geräte
|
||||||
|
- keine feste Produktliste im Core
|
||||||
|
- kein pauschales `gerät => testomat`
|
||||||
|
- nur bei bereits aktivem Shop-/Commerce-Follow-up
|
||||||
|
- nur bei schwacher/noisy Meta-Query
|
||||||
|
|
||||||
|
## Lokale Checks
|
||||||
|
|
||||||
|
```text
|
||||||
|
php -l src/Agent/AgentRunner.php
|
||||||
|
php -l src/Config/AgentRunnerConfig.php
|
||||||
|
php -l src/Config/RetriexEffectiveConfigProvider.php
|
||||||
|
YAML parse OK
|
||||||
|
p86c smoke OK
|
||||||
|
```
|
||||||
|
|
||||||
|
Symfony-Console-Checks müssen in einer Umgebung mit installiertem `vendor/` ausgeführt werden.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# RETRIEX Patch 86D - Referential Product Links Deep Context Anchors
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Behebt den weiterhin fehlerhaften Flow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gerät zur messung Prozesswasser in medizinischen Geräten
|
||||||
|
→ gebe mir links zu den produkten aus dem shop
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Shopquery durfte nicht als schwache Meta-/Noise-Query an Shopware gehen:
|
||||||
|
|
||||||
|
```text
|
||||||
|
links zu aus
|
||||||
|
```
|
||||||
|
|
||||||
|
sondern muss bei einem referenziellen Produktlisten-Follow-up die zuvor genannten Produkt-/Modellanker aus dem Verlauf verwenden.
|
||||||
|
|
||||||
|
## Tiefenanalyse
|
||||||
|
|
||||||
|
p86/p86b/p86c lagen am richtigen Themenbereich, aber der Hebel war noch nicht robust genug:
|
||||||
|
|
||||||
|
1. Die finale Shopquery entsteht in diesem Flow über den Standalone-/Fallback-Pfad und wird durch Stopword-/Positive-Filterung zu `links zu aus` reduziert.
|
||||||
|
2. Der Produktlisten-Guard sitzt zwar vor der Shop-Suche, war aber zu abhängig vom direkt übergebenen `commerceHistoryContext`.
|
||||||
|
3. In der aktuellen Basis war außerdem noch der frühe Return bei leerem `commerceHistoryContext` vorhanden, wodurch der RAG-/History-Fallback nicht zuverlässig erreicht wurde.
|
||||||
|
4. Selbst bei History-Treffern konnten Produktanker wie `Testomat 2000 CAL-Version bietet` zu breit extrahiert werden, weil die vorhandenen Patterns mit `iu` arbeiten und dadurch Großbuchstabenklassen case-insensitiv wirken.
|
||||||
|
|
||||||
|
## Änderung
|
||||||
|
|
||||||
|
- `src/Agent/AgentRunner.php`
|
||||||
|
- Der Produktlisten-Follow-up-Guard erhält jetzt zusätzlich die `userId`.
|
||||||
|
- Der Guard verlässt die Methode nicht mehr nur wegen leerem `commerceHistoryContext`.
|
||||||
|
- Er durchsucht mehrere Kontextkandidaten:
|
||||||
|
- direkt übergebenen Commerce-History-Kontext
|
||||||
|
- erweiterten Verlauf innerhalb des bestehenden Context-Fallback-Budgets
|
||||||
|
- Full-History-Kontext, wenn in der bestehenden Config erlaubt
|
||||||
|
- danach weiterhin aktuelle Knowledge-Chunks als Fallback
|
||||||
|
- Produktlisten-Anker werden kanonisiert:
|
||||||
|
- `Testomat 2000 self clean` bleibt erhalten.
|
||||||
|
- `Testomat 2000 CAL-Version bietet` wird zu `testomat 2000 cal` gekürzt.
|
||||||
|
- `Testomat 808-Version` wird zu `testomat 808` gekürzt.
|
||||||
|
- Die Kanonisierung nutzt die bereits YAML-gepflegte `adjacent_variant_terms`-Liste aus `genre.yaml`; keine neue harte Produktliste im PHP-Core.
|
||||||
|
|
||||||
|
## Erwartetes Verhalten
|
||||||
|
|
||||||
|
```text
|
||||||
|
Follow-up: gebe mir links zu den produkten aus dem shop
|
||||||
|
Vorher: links zu aus
|
||||||
|
Nachher: testomat 2000 self clean testomat 2000 cal testomat 808
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Logik bleibt generisch:
|
||||||
|
|
||||||
|
- keine Sonderlogik für medizinische Geräte
|
||||||
|
- keine feste Produktliste im Core
|
||||||
|
- kein pauschales `gerät => testomat`
|
||||||
|
- nur bei bereits aktivem Shop-/Commerce-Follow-up
|
||||||
|
- nur bei schwacher/noisy Meta-Query
|
||||||
|
- Produktanker kommen aus Verlauf/RAG und werden gegen vorhandene YAML-Variantentokens normalisiert
|
||||||
|
|
||||||
|
## Lokale Checks
|
||||||
|
|
||||||
|
```text
|
||||||
|
php -l src/Agent/AgentRunner.php
|
||||||
|
YAML parse OK
|
||||||
|
p86d anchor canonicalization smoke OK
|
||||||
|
```
|
||||||
|
|
||||||
|
Symfony-Console-Checks müssen in einer Umgebung mit installiertem `vendor/` ausgeführt werden.
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# RetrieX Patch p86e - Referential Product Links Early Context Resolution
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Referential shop follow-ups such as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gebe mir links zu den produkten aus dem shop
|
||||||
|
```
|
||||||
|
|
||||||
|
could still be converted into a weak/noisy Shopware query such as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
links zu aus
|
||||||
|
```
|
||||||
|
|
||||||
|
In the affected flow, the previous RAG answer listed concrete products such as `Testomat® 2000 self clean`, `Testomat® 2000 CAL` and `Testomat® 808`, but the shop-query resolver treated the current follow-up as a standalone/current-input query before the product-list history anchor guard could reliably replace it.
|
||||||
|
|
||||||
|
## Root cause
|
||||||
|
|
||||||
|
The prompt is referential and commerce-related, but it is not covered by the older `meta-only` fallback path. Therefore `resolveShopSearchQuery()` could return the current cleaned prompt before consulting product anchors from recent history or the frontend context hint.
|
||||||
|
|
||||||
|
The later p86 guard existed, but this flow showed that relying only on a late correction after standalone cleanup was not robust enough.
|
||||||
|
|
||||||
|
## Change
|
||||||
|
|
||||||
|
p86e moves product-list follow-up resolution into the early shop-query resolution path:
|
||||||
|
|
||||||
|
- product-list follow-up prompts now explicitly require commerce history/context usage;
|
||||||
|
- they are no longer isolated as standalone shop queries;
|
||||||
|
- deterministic standalone query generation is disabled for this prompt class;
|
||||||
|
- before returning optimized/current prompt fallback queries, `resolveShopSearchQuery()` tries to replace weak product-list meta queries with product/model anchors from history/context/RAG fallback;
|
||||||
|
- product-list meta prompts whose tokens are all configured noise terms are treated as weak even when the raw prompt is longer than the normal weak-query term limit.
|
||||||
|
|
||||||
|
Expected behavior:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gerät zur messung Prozesswasser in medizinischen Geräten
|
||||||
|
-> RAG answer lists products
|
||||||
|
|
||||||
|
gebe mir links zu den produkten aus dem shop
|
||||||
|
-> testomat 2000 self clean testomat 2000 cal testomat 808
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- No hardcoded medical-device rule.
|
||||||
|
- No fixed product list in PHP.
|
||||||
|
- No `gerät => testomat` shortcut.
|
||||||
|
- Active only for referential product-list shop follow-ups.
|
||||||
|
- Still requires weak/noisy shop query detection before replacing the query.
|
||||||
|
- No changes to retrieval, ranking, scoring or Shopware matching.
|
||||||
|
|
||||||
|
## Local checks
|
||||||
|
|
||||||
|
```text
|
||||||
|
php -l src/Agent/AgentRunner.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional reflection smoke test verified that the product-list guard and the early resolver return:
|
||||||
|
|
||||||
|
```text
|
||||||
|
testomat 2000 self clean testomat 2000 cal testomat 808
|
||||||
|
```
|
||||||
|
|
||||||
|
for the failing product-link follow-up when the previous turn contains the listed Testomat products.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# RetrieX Patch p86 - Referential Product List Shop Anchors
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Referenzielle Shop-Follow-ups wie:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gebe mir links zu den produkten aus dem shop
|
||||||
|
```
|
||||||
|
|
||||||
|
durften nicht mehr zu schwachen Meta-/Noise-Queries wie `links zu aus` werden, wenn die vorherige Antwort bereits konkrete Produkte oder Gerätemodelle genannt hat.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Der bestehende History-/Shopquery-Repair war auf Zubehör-/Indikator-Referenzen und einzelne Hauptgeräte-Follow-ups fokussiert. Eine generische Nachfrage nach Links/Preisen zu "den Produkten" wurde zwar als Shop-Intent geroutet, verlor aber die zuvor genannten Produktanker und sendete eine nutzlose Query wie `links zu aus` an die Shopsuche.
|
||||||
|
|
||||||
|
## Lösung
|
||||||
|
|
||||||
|
p86 ergänzt einen generischen Product-List-Follow-up-Guard:
|
||||||
|
|
||||||
|
- erkennt referenzielle Produktlisten-Follow-ups über YAML-gepflegte `product_terms` + `shop_terms`
|
||||||
|
- greift nur bei schwachen Shopqueries, deren Tokens vollständig aus YAML-gepflegten `noise_terms` bestehen
|
||||||
|
- extrahiert Produkt-/Modellanker aus der neuesten Assistant-Antwort über YAML-gepflegte `anchor_patterns`
|
||||||
|
- ersetzt die schwache Query durch die extrahierten Produktanker
|
||||||
|
- bleibt auf Shopquery-Reparatur beschränkt und erzeugt keinen neuen Shop-Intent
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Vorherige Antwort nennt:
|
||||||
|
- Testomat 2000 self clean
|
||||||
|
- Testomat 2000 CAL
|
||||||
|
- Testomat 808
|
||||||
|
|
||||||
|
Follow-up:
|
||||||
|
gebe mir links zu den produkten aus dem shop
|
||||||
|
|
||||||
|
Vor p86:
|
||||||
|
links zu aus
|
||||||
|
|
||||||
|
Nach p86:
|
||||||
|
testomat 2000 self clean testomat 2000 cal testomat 808
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- Keine harte Branchenlogik fuer "medizinische Geräte".
|
||||||
|
- Keine Änderung an Retrieval, Ranking, Scoring oder Shop-Matching.
|
||||||
|
- Kein pauschales `produkt => testomat` oder `gerät => testomat`.
|
||||||
|
- Der Guard greift nur, wenn bereits Commerce-/Shop-Intent aktiv ist.
|
||||||
|
- Bereits konkrete Shopqueries mit Produkt-/Modellanker werden nicht überschrieben.
|
||||||
|
|
||||||
|
## Dateien
|
||||||
|
|
||||||
|
- `config/retriex/genre.yaml`
|
||||||
|
- `src/Agent/AgentRunner.php`
|
||||||
|
- `src/Config/AgentRunnerConfig.php`
|
||||||
|
- `src/Config/RetriexEffectiveConfigProvider.php`
|
||||||
|
|
||||||
|
## Lokale Checks
|
||||||
|
|
||||||
|
```text
|
||||||
|
php -l src/Agent/AgentRunner.php
|
||||||
|
php -l src/Config/AgentRunnerConfig.php
|
||||||
|
php -l src/Config/RetriexEffectiveConfigProvider.php
|
||||||
|
YAML parse OK
|
||||||
|
p86 smoke OK: referential product-list prompt + weak query + product-anchor extraction
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Symfony-Console-Checks muessen in der Zielumgebung mit installiertem `vendor/` ausgefuehrt werden.
|
||||||
@@ -272,7 +272,8 @@ final readonly class AgentRunner
|
|||||||
optimizedShopQuery: $optimizedShopQuery,
|
optimizedShopQuery: $optimizedShopQuery,
|
||||||
commerceHistoryContext: $shopQueryHistoryContext,
|
commerceHistoryContext: $shopQueryHistoryContext,
|
||||||
userId: $userId,
|
userId: $userId,
|
||||||
currentPromptFallback: $routingPrompt
|
currentPromptFallback: $routingPrompt,
|
||||||
|
knowledgeChunks: $knowledgeChunks
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +336,28 @@ final readonly class AgentRunner
|
|||||||
$optimizedShopQuery = '';
|
$optimizedShopQuery = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$productListAnchoredShopSearchQuery = $this->guardReferentialProductListShopQueryWithHistoryAnchors(
|
||||||
|
prompt: $originalPrompt,
|
||||||
|
shopSearchQuery: $shopSearchQuery,
|
||||||
|
commerceHistoryContext: $commerceHistoryContext,
|
||||||
|
userId: $userId,
|
||||||
|
knowledgeChunks: $knowledgeChunks
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($productListAnchoredShopSearchQuery !== $shopSearchQuery) {
|
||||||
|
$this->agentLogger->info('Enriched referential product-list shop query with history or RAG product anchors', [
|
||||||
|
'userId' => $userId,
|
||||||
|
'prompt' => $prompt,
|
||||||
|
'routingPrompt' => $routingPrompt,
|
||||||
|
'optimizedShopQuery' => $optimizedShopQuery,
|
||||||
|
'shopSearchQuery' => $shopSearchQuery,
|
||||||
|
'productListAnchoredShopSearchQuery' => $productListAnchoredShopSearchQuery,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$shopSearchQuery = $productListAnchoredShopSearchQuery;
|
||||||
|
$optimizedShopQuery = '';
|
||||||
|
}
|
||||||
|
|
||||||
$ragAnchoredShopSearchQuery = $this->enrichShopSearchQueryWithRagAnchor(
|
$ragAnchoredShopSearchQuery = $this->enrichShopSearchQueryWithRagAnchor(
|
||||||
prompt: $originalPrompt,
|
prompt: $originalPrompt,
|
||||||
shopSearchQuery: $shopSearchQuery,
|
shopSearchQuery: $shopSearchQuery,
|
||||||
@@ -1516,6 +1539,26 @@ final readonly class AgentRunner
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function buildProductListFollowUpWeakQueryCandidates(
|
||||||
|
string $prompt,
|
||||||
|
string $optimizedShopQuery,
|
||||||
|
string $currentPromptFallback
|
||||||
|
): array {
|
||||||
|
$candidates = [];
|
||||||
|
|
||||||
|
foreach ([$optimizedShopQuery, $currentPromptFallback, $prompt] as $candidate) {
|
||||||
|
$candidate = trim($candidate);
|
||||||
|
if ($candidate !== '' && !in_array($candidate, $candidates, true)) {
|
||||||
|
$candidates[] = $candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $candidates;
|
||||||
|
}
|
||||||
|
|
||||||
private function shouldUseCommerceHistoryForShopQuery(string $prompt): bool
|
private function shouldUseCommerceHistoryForShopQuery(string $prompt): bool
|
||||||
{
|
{
|
||||||
$prompt = trim($prompt);
|
$prompt = trim($prompt);
|
||||||
@@ -1528,6 +1571,10 @@ final readonly class AgentRunner
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->isReferentialProductListShopFollowUpPrompt($prompt)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->isMetaOnlyShopQuery($prompt)) {
|
if ($this->isMetaOnlyShopQuery($prompt)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1579,7 +1626,11 @@ final readonly class AgentRunner
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isCommercialTableFollowUpPrompt($prompt) || $this->isMetaOnlyShopQuery($prompt)) {
|
if (
|
||||||
|
$this->isCommercialTableFollowUpPrompt($prompt)
|
||||||
|
|| $this->isReferentialProductListShopFollowUpPrompt($prompt)
|
||||||
|
|| $this->isMetaOnlyShopQuery($prompt)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1632,6 +1683,10 @@ final readonly class AgentRunner
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->isReferentialProductListShopFollowUpPrompt($prompt)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->isMetaOnlyShopQuery($prompt)) {
|
if ($this->isMetaOnlyShopQuery($prompt)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2547,12 +2602,16 @@ final readonly class AgentRunner
|
|||||||
return $overlap === 0 && !$this->isMetaOnlyShopQuery($prompt);
|
return $overlap === 0 && !$this->isMetaOnlyShopQuery($prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $knowledgeChunks
|
||||||
|
*/
|
||||||
private function resolveShopSearchQuery(
|
private function resolveShopSearchQuery(
|
||||||
string $prompt,
|
string $prompt,
|
||||||
string $optimizedShopQuery,
|
string $optimizedShopQuery,
|
||||||
string $commerceHistoryContext,
|
string $commerceHistoryContext,
|
||||||
string $userId,
|
string $userId,
|
||||||
string $currentPromptFallback = ''
|
string $currentPromptFallback = '',
|
||||||
|
array $knowledgeChunks = []
|
||||||
): string {
|
): string {
|
||||||
if ($this->isCommercialTableFollowUpPrompt($prompt)) {
|
if ($this->isCommercialTableFollowUpPrompt($prompt)) {
|
||||||
foreach ($this->buildCommercialTableFollowUpContextCandidates($commerceHistoryContext, $userId) as $contextCandidate) {
|
foreach ($this->buildCommercialTableFollowUpContextCandidates($commerceHistoryContext, $userId) as $contextCandidate) {
|
||||||
@@ -2564,11 +2623,25 @@ final readonly class AgentRunner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$currentPromptFallback = trim($currentPromptFallback);
|
||||||
|
foreach ($this->buildProductListFollowUpWeakQueryCandidates($prompt, $optimizedShopQuery, $currentPromptFallback) as $productListFallbackQuery) {
|
||||||
|
$productListContextQuery = $this->guardReferentialProductListShopQueryWithHistoryAnchors(
|
||||||
|
prompt: $prompt,
|
||||||
|
shopSearchQuery: $productListFallbackQuery,
|
||||||
|
commerceHistoryContext: $commerceHistoryContext,
|
||||||
|
userId: $userId,
|
||||||
|
knowledgeChunks: $knowledgeChunks
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($productListContextQuery !== $productListFallbackQuery) {
|
||||||
|
return $productListContextQuery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($optimizedShopQuery !== '' && !$this->isMetaOnlyShopQuery($optimizedShopQuery)) {
|
if ($optimizedShopQuery !== '' && !$this->isMetaOnlyShopQuery($optimizedShopQuery)) {
|
||||||
return $this->guardStandaloneOptimizedShopQuery($prompt, $optimizedShopQuery);
|
return $this->guardStandaloneOptimizedShopQuery($prompt, $optimizedShopQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
$currentPromptFallback = trim($currentPromptFallback);
|
|
||||||
if ($currentPromptFallback !== '' && !$this->isMetaOnlyShopQuery($currentPromptFallback)) {
|
if ($currentPromptFallback !== '' && !$this->isMetaOnlyShopQuery($currentPromptFallback)) {
|
||||||
return $currentPromptFallback;
|
return $currentPromptFallback;
|
||||||
}
|
}
|
||||||
@@ -3062,6 +3135,261 @@ final readonly class AgentRunner
|
|||||||
return $enriched !== '' ? $enriched : $shopSearchQuery;
|
return $enriched !== '' ? $enriched : $shopSearchQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $knowledgeChunks
|
||||||
|
*/
|
||||||
|
private function guardReferentialProductListShopQueryWithHistoryAnchors(
|
||||||
|
string $prompt,
|
||||||
|
string $shopSearchQuery,
|
||||||
|
string $commerceHistoryContext,
|
||||||
|
string $userId,
|
||||||
|
array $knowledgeChunks = []
|
||||||
|
): string {
|
||||||
|
$shopSearchQuery = trim($shopSearchQuery);
|
||||||
|
|
||||||
|
if (
|
||||||
|
$shopSearchQuery === ''
|
||||||
|
|| !$this->agentRunnerConfig->isShopQueryProductListFollowUpEnabled()
|
||||||
|
|| !$this->isReferentialProductListShopFollowUpPrompt($prompt)
|
||||||
|
|| !$this->isWeakProductListFollowUpShopQuery($shopSearchQuery)
|
||||||
|
) {
|
||||||
|
return $shopSearchQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
$anchors = [];
|
||||||
|
foreach ($this->buildProductListFollowUpAnchorContextCandidates($commerceHistoryContext, $userId) as $contextCandidate) {
|
||||||
|
$anchors = $this->extractLatestHistoryProductListAnchors($contextCandidate);
|
||||||
|
if ($anchors !== []) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($anchors === []) {
|
||||||
|
$anchors = $this->extractProductListAnchorsFromKnowledgeChunks($knowledgeChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($anchors === []) {
|
||||||
|
return $shopSearchQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = $this->agentRunnerConfig->getShopQueryProductListFollowUpTemplate();
|
||||||
|
$rendered = $this->renderAgentTemplate($template, [
|
||||||
|
'anchors' => implode(' ', $anchors),
|
||||||
|
'query' => $shopSearchQuery,
|
||||||
|
]);
|
||||||
|
$rendered = preg_replace('/\s+/u', ' ', $rendered) ?? $rendered;
|
||||||
|
$rendered = trim($rendered);
|
||||||
|
|
||||||
|
return $rendered !== '' ? $rendered : $shopSearchQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function buildProductListFollowUpAnchorContextCandidates(string $commerceHistoryContext, string $userId): array
|
||||||
|
{
|
||||||
|
$candidates = [];
|
||||||
|
|
||||||
|
$commerceHistoryContext = trim($commerceHistoryContext);
|
||||||
|
if ($commerceHistoryContext !== '') {
|
||||||
|
$candidates[] = $commerceHistoryContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extendedHistoryBudget = $this->agentRunnerConfig->getShopQueryContextFallbackHistoryBudgetChars();
|
||||||
|
if ($extendedHistoryBudget > mb_strlen($commerceHistoryContext, 'UTF-8')) {
|
||||||
|
$extendedHistory = trim($this->contextService->buildUserContextWithinBudget($userId, $extendedHistoryBudget));
|
||||||
|
if ($extendedHistory !== '') {
|
||||||
|
$candidates[] = $extendedHistory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->agentRunnerConfig->shouldUseFullHistoryForShopQueryContextFallback()) {
|
||||||
|
$fullHistory = trim($this->contextService->buildUserContext($userId, true));
|
||||||
|
if ($fullHistory !== '') {
|
||||||
|
$candidates[] = $fullHistory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($candidates));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isReferentialProductListShopFollowUpPrompt(string $prompt): bool
|
||||||
|
{
|
||||||
|
$tokens = $this->tokenizeShopQueryCandidate($prompt);
|
||||||
|
if ($tokens === []) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokenSet = array_fill_keys($tokens, true);
|
||||||
|
$productTokens = $this->buildShopQueryTokenSet(
|
||||||
|
$this->agentRunnerConfig->getShopQueryProductListFollowUpProductTerms()
|
||||||
|
);
|
||||||
|
$shopTokens = $this->buildShopQueryTokenSet(
|
||||||
|
$this->agentRunnerConfig->getShopQueryProductListFollowUpShopTerms()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->tokenSetIntersects($tokenSet, $productTokens)
|
||||||
|
&& $this->tokenSetIntersects($tokenSet, $shopTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isWeakProductListFollowUpShopQuery(string $shopSearchQuery): bool
|
||||||
|
{
|
||||||
|
if ($this->referenceAnchorExtractor->extractFirstProductModelAnchor($shopSearchQuery) !== '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens = $this->tokenizeShopQueryCandidate($shopSearchQuery);
|
||||||
|
if ($tokens === []) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$noiseTokens = $this->buildShopQueryTokenSet(
|
||||||
|
$this->agentRunnerConfig->getShopQueryProductListFollowUpNoiseTerms()
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($noiseTokens === []) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$residualTokens = [];
|
||||||
|
foreach ($tokens as $token) {
|
||||||
|
if (isset($noiseTokens[$token])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$residualTokens[$token] = $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($residualTokens) === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($tokens) > max(1, $this->agentRunnerConfig->getShopQueryProductListFollowUpWeakQueryMaxTerms())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($residualTokens) <= max(0, $this->agentRunnerConfig->getShopQueryProductListFollowUpWeakQueryMaxResidualTerms());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function extractLatestHistoryProductListAnchors(string $commerceHistoryContext): array
|
||||||
|
{
|
||||||
|
$maxAnchors = max(1, $this->agentRunnerConfig->getShopQueryProductListFollowUpMaxAnchors());
|
||||||
|
|
||||||
|
foreach ($this->extractHistoryTurnsNewestFirst($commerceHistoryContext) as $turn) {
|
||||||
|
$answer = preg_replace($this->agentRunnerConfig->getFollowUpHistoryQuestionStripPattern(), '', $turn, 1) ?? $turn;
|
||||||
|
$anchors = $this->extractProductListAnchorsFromText($answer, $maxAnchors);
|
||||||
|
|
||||||
|
if ($anchors !== []) {
|
||||||
|
return $anchors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $knowledgeChunks
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function extractProductListAnchorsFromKnowledgeChunks(array $knowledgeChunks): array
|
||||||
|
{
|
||||||
|
if ($knowledgeChunks === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxAnchors = max(1, $this->agentRunnerConfig->getShopQueryProductListFollowUpMaxAnchors());
|
||||||
|
$text = trim(implode("\n\n", array_map('strval', $knowledgeChunks)));
|
||||||
|
|
||||||
|
if ($text === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->extractProductListAnchorsFromText($text, $maxAnchors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function extractProductListAnchorsFromText(string $text, int $maxAnchors): array
|
||||||
|
{
|
||||||
|
$anchors = [];
|
||||||
|
$seen = [];
|
||||||
|
|
||||||
|
foreach ($this->agentRunnerConfig->getShopQueryProductListFollowUpAnchorPatterns() as $pattern) {
|
||||||
|
if (@preg_match_all($pattern, $text, $matches, PREG_SET_ORDER) === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$candidate = '';
|
||||||
|
if (isset($match['anchor']) && is_string($match['anchor'])) {
|
||||||
|
$candidate = $match['anchor'];
|
||||||
|
} elseif (isset($match[1]) && is_string($match[1])) {
|
||||||
|
$candidate = $match[1];
|
||||||
|
} elseif (isset($match[0]) && is_string($match[0])) {
|
||||||
|
$candidate = $match[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidate = $this->normalizeShopQueryAnchor($candidate);
|
||||||
|
$candidate = $this->canonicalizeProductListAnchor($candidate);
|
||||||
|
if ($candidate === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = implode(' ', $this->tokenizeShopQueryCandidate($candidate));
|
||||||
|
if ($key === '' || isset($seen[$key])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$seen[$key] = true;
|
||||||
|
$anchors[] = $candidate;
|
||||||
|
|
||||||
|
if (count($anchors) >= $maxAnchors) {
|
||||||
|
return $anchors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $anchors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canonicalizeProductListAnchor(string $anchor): string
|
||||||
|
{
|
||||||
|
$tokens = $this->tokenizeShopQueryCandidate($anchor);
|
||||||
|
if ($tokens === []) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($tokens[0] ?? '') !== 'testomat') {
|
||||||
|
return trim((string) preg_replace('/\s+/u', ' ', $anchor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($tokens[1])) {
|
||||||
|
return 'testomat';
|
||||||
|
}
|
||||||
|
|
||||||
|
$canonical = ['testomat', $tokens[1]];
|
||||||
|
$variantTerms = $this->buildShopQueryTokenSet(
|
||||||
|
$this->agentRunnerConfig->getShopQueryPositiveTokenFilterAdjacentVariantTerms()
|
||||||
|
);
|
||||||
|
|
||||||
|
for ($i = 2, $count = count($tokens); $i < $count; $i++) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
|
||||||
|
if (isset($variantTerms[$token]) || preg_match('/\d/u', $token) === 1) {
|
||||||
|
$canonical[] = $token;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim(implode(' ', $canonical));
|
||||||
|
}
|
||||||
|
|
||||||
private function guardMainDeviceReferentialShopQueryWithHistoryModelAnchor(
|
private function guardMainDeviceReferentialShopQueryWithHistoryModelAnchor(
|
||||||
string $prompt,
|
string $prompt,
|
||||||
string $shopSearchQuery,
|
string $shopSearchQuery,
|
||||||
|
|||||||
@@ -1805,6 +1805,64 @@ final class AgentRunnerConfig
|
|||||||
return $this->genreString('context_resolution.history_anchor_enrichment.template')
|
return $this->genreString('context_resolution.history_anchor_enrichment.template')
|
||||||
?: $this->getRequiredString('shop_runtime.context_resolution.history_anchor_enrichment.template');
|
?: $this->getRequiredString('shop_runtime.context_resolution.history_anchor_enrichment.template');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function isShopQueryProductListFollowUpEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->genreBool('context_resolution.product_list_followup.enabled') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShopQueryProductListFollowUpWeakQueryMaxTerms(): int
|
||||||
|
{
|
||||||
|
return $this->genreInt('context_resolution.product_list_followup.weak_query_max_terms') ?? 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShopQueryProductListFollowUpWeakQueryMaxResidualTerms(): int
|
||||||
|
{
|
||||||
|
return $this->genreInt('context_resolution.product_list_followup.weak_query_max_residual_terms') ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShopQueryProductListFollowUpMaxAnchors(): int
|
||||||
|
{
|
||||||
|
return $this->genreInt('context_resolution.product_list_followup.max_anchors') ?? 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShopQueryProductListFollowUpTemplate(): string
|
||||||
|
{
|
||||||
|
return $this->genreString('context_resolution.product_list_followup.template') ?: '{anchors}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getShopQueryProductListFollowUpProductTerms(): array
|
||||||
|
{
|
||||||
|
return $this->genreStringList('context_resolution.product_list_followup.product_terms');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getShopQueryProductListFollowUpShopTerms(): array
|
||||||
|
{
|
||||||
|
return $this->genreStringList('context_resolution.product_list_followup.shop_terms');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getShopQueryProductListFollowUpNoiseTerms(): array
|
||||||
|
{
|
||||||
|
return $this->genreStringList('context_resolution.product_list_followup.noise_terms');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getShopQueryProductListFollowUpAnchorPatterns(): array
|
||||||
|
{
|
||||||
|
return $this->genreStringList('context_resolution.product_list_followup.anchor_patterns');
|
||||||
|
}
|
||||||
public function isShopQueryRagAnchorEnrichmentEnabled(): bool
|
public function isShopQueryRagAnchorEnrichmentEnabled(): bool
|
||||||
{
|
{
|
||||||
return $this->getRequiredBool('shop_runtime.context_resolution.rag_anchor_enrichment.enabled');
|
return $this->getRequiredBool('shop_runtime.context_resolution.rag_anchor_enrichment.enabled');
|
||||||
|
|||||||
@@ -1342,6 +1342,37 @@ final readonly class RetriexEffectiveConfigProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$contextResolution = is_array($configurationValues['context_resolution'] ?? null)
|
||||||
|
? $configurationValues['context_resolution']
|
||||||
|
: [];
|
||||||
|
$productListFollowUp = is_array($contextResolution['product_list_followup'] ?? null)
|
||||||
|
? $contextResolution['product_list_followup']
|
||||||
|
: [];
|
||||||
|
if ($productListFollowUp !== []) {
|
||||||
|
if (array_key_exists('enabled', $productListFollowUp) && !is_bool($productListFollowUp['enabled'])) {
|
||||||
|
$errors[] = 'genre.configuration_values.context_resolution.product_list_followup.enabled must be boolean.';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
'weak_query_max_terms',
|
||||||
|
'weak_query_max_residual_terms',
|
||||||
|
'max_anchors',
|
||||||
|
] as $intKey) {
|
||||||
|
if (array_key_exists($intKey, $productListFollowUp) && (($this->asInt($productListFollowUp[$intKey]) ?? -1) < 0)) {
|
||||||
|
$errors[] = sprintf('genre.configuration_values.context_resolution.product_list_followup.%s must be numeric and non-negative.', $intKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('template', $productListFollowUp) && (!is_string($productListFollowUp['template']) || trim($productListFollowUp['template']) === '')) {
|
||||||
|
$errors[] = 'genre.configuration_values.context_resolution.product_list_followup.template must be a non-empty string.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validateStringList($this->toList($productListFollowUp['product_terms'] ?? []), 'genre.configuration_values.context_resolution.product_list_followup.product_terms', $errors, $warnings);
|
||||||
|
$this->validateStringList($this->toList($productListFollowUp['shop_terms'] ?? []), 'genre.configuration_values.context_resolution.product_list_followup.shop_terms', $errors, $warnings);
|
||||||
|
$this->validateStringList($this->toList($productListFollowUp['noise_terms'] ?? []), 'genre.configuration_values.context_resolution.product_list_followup.noise_terms', $errors, $warnings);
|
||||||
|
$this->validateRegexPatternList($productListFollowUp['anchor_patterns'] ?? [], 'genre.configuration_values.context_resolution.product_list_followup.anchor_patterns', $errors);
|
||||||
|
}
|
||||||
|
|
||||||
$shopQueryRuntime = is_array($configurationValues['shop_query_runtime'] ?? null)
|
$shopQueryRuntime = is_array($configurationValues['shop_query_runtime'] ?? null)
|
||||||
? $configurationValues['shop_query_runtime']
|
? $configurationValues['shop_query_runtime']
|
||||||
: [];
|
: [];
|
||||||
|
|||||||
Reference in New Issue
Block a user