diff --git a/RETRIEX_PATCH_20P_HISTORY_PRODUCT_ANCHOR_FOLLOWUP_README.md b/RETRIEX_PATCH_20P_HISTORY_PRODUCT_ANCHOR_FOLLOWUP_README.md new file mode 100644 index 0000000..a1e5afe --- /dev/null +++ b/RETRIEX_PATCH_20P_HISTORY_PRODUCT_ANCHOR_FOLLOWUP_README.md @@ -0,0 +1,55 @@ +# RetrieX Patch p20p - History Product Anchor Follow-up Fix + +## Ziel + +Behebt den Fall, dass kurze Shop-Follow-ups wie `suche im shop` den Produkt-/Modellanker aus dem vorherigen fachlichen Turn nicht mehr sicher übernehmen. + +Beispiel: + +1. `welche grenzwerte kann der testomat 2000 thcl messen` +2. `suche im shop` + +Vorher konnte die Shop-Suche mit `keine Suchquery` enden, obwohl der vorherige Turn einen klaren Produktanker enthält. + +## Ursache + +Die bisherige Shop-Follow-up-Auflösung nutzte primär extrahierte `Question:`-Zeilen. Wenn diese Extraktion nicht greift oder der stabile Produktanker nur in der Antwort bzw. im gesamten letzten Turn steht, wurde keine belastbare Shopquery erzeugt. + +Außerdem erkannte der Testomat-Modellanker bisher numerische Testomat-Modelle ohne nachgestellten Varianten-/Parameter-Suffix. Dadurch wurde `Testomat 2000 THCL` nicht als kompletter Modellanker erfasst. + +## Änderung + +- `AgentRunner::extractContextualShopSearchQuery()` wurde gehärtet. +- Wenn aus den letzten Nutzerfragen keine Query entsteht, werden die neuesten History-Turns selbst geprüft. +- Pro Turn wird zuerst die Nutzerfrage ausgewertet. +- Danach wird ein expliziter Testomat-Modellanker aus dem gesamten Turn extrahiert. +- Das Modellanker-Pattern erlaubt nun numerische Testomat-Modelle mit Suffix, z. B. `Testomat 2000 THCL`. + +## Dateien + +- `src/Agent/AgentRunner.php` +- `config/retriex/agent.yaml` + +## Erwartung + +`suche im shop` nach einem technischen Turn zu `Testomat 2000 THCL` soll eine Shopquery wie `testomat 2000 thcl` ableiten, statt mit `keine Suchquery` abzubrechen. + +Der Fix ist generisch für History-Produktanker und enthält keine harte 808- oder THCL-Sonderquery. + +## Prüfungen lokal + +- `php -l src/Agent/AgentRunner.php`: OK +- `config/retriex/agent.yaml` mit Python YAML Parser: OK +- Regex-Smoke-Test für `Testomat 2000 THCL`, `Testomat 808`, `Testomat EVO TH`: OK + +## Nach dem Einspielen prüfen + +```bash +bin/console cache:clear +bin/console mto:agent:config:validate +bin/console mto:agent:regression:test +bin/console mto:agent:config:audit-source --details +bin/console mto:agent:config:audit-patterns --details +``` + +PHP-FPM/OPcache danach neu laden, falls aktiv. diff --git a/config/retriex/agent.yaml b/config/retriex/agent.yaml index cd44c58..e193ffc 100644 --- a/config/retriex/agent.yaml +++ b/config/retriex/agent.yaml @@ -189,7 +189,7 @@ parameters: history_turn_split_pattern: '/(?=^Question:\s)/m' history_question_strip_pattern: '/^Question:\s*.*(?:\R|$)/u' reference_anchor: - testomat_model_pattern: '/\bTestomat(?:®)?\s+(?:\d{3,4}|EVO(?:\s+[A-Z]{2,6})?|ECO(?:[-\s]?(?:PLUS|C))?|DUO(?:\s+\d{3,4})?|LAB(?:\s+[A-Z]{2,6})?)\b/iu' + testomat_model_pattern: '/\bTestomat(?:®)?\s+(?:\d{3,4}(?:\s+[A-Z]{2,8})?|EVO(?:\s+[A-Z]{2,6})?|ECO(?:[-\s]?(?:PLUS|C))?|DUO(?:\s+\d{3,4})?|LAB(?:\s+[A-Z]{2,6})?)\b/iu' hardness_value_pattern: '/\b\d+(?:[,.]\d+)?\s*°\s*dH\b/iu' messages: diff --git a/config/retriex/commerce.yaml b/config/retriex/commerce.yaml index cd185d0..86c5762 100644 --- a/config/retriex/commerce.yaml +++ b/config/retriex/commerce.yaml @@ -56,11 +56,14 @@ parameters: - davon - stattdessen - bitte + - preiswerte - gern + - eine - größer - würde - ich - gerne + - mein - größer - zeige - zeig @@ -80,6 +83,7 @@ parameters: - welches - welchen - sind + - zur - ist - geeignet - geeigent diff --git a/retriex_work/config/retriex/commerce.yaml b/retriex_work/config/retriex/commerce.yaml index 757ba6a..f591a7d 100644 --- a/retriex_work/config/retriex/commerce.yaml +++ b/retriex_work/config/retriex/commerce.yaml @@ -58,6 +58,10 @@ parameters: - stattdessen - bitte - gern + - preiswerte + - zur + - mein + - eine - würde - ich - gerne diff --git a/src/Agent/AgentRunner.php b/src/Agent/AgentRunner.php index ed70bc0..2a07b8c 100644 --- a/src/Agent/AgentRunner.php +++ b/src/Agent/AgentRunner.php @@ -1847,14 +1847,57 @@ final readonly class AgentRunner $contextQuery = $this->buildContextFallbackShopQuery($question); - if ($contextQuery !== '' && !$this->isMetaOnlyShopQuery($contextQuery)) { + if ($this->isUsableContextualShopQuery($contextQuery)) { return $contextQuery; } } + return $this->extractContextualShopSearchQueryFromHistoryTurns($commerceHistoryContext); + } + + private function extractContextualShopSearchQueryFromHistoryTurns(string $commerceHistoryContext): string + { + foreach ($this->extractHistoryTurnsNewestFirst($commerceHistoryContext) as $turn) { + $question = $this->extractQuestionFromHistoryTurn($turn); + + if ($question !== '' && !$this->isMetaOnlyShopQuery($question)) { + $contextQuery = $this->buildContextFallbackShopQuery($question); + + if ($this->isUsableContextualShopQuery($contextQuery)) { + return $contextQuery; + } + } + + $modelAnchor = $this->extractFirstTestomatModelAnchor($turn); + + if ($modelAnchor !== '' && !$this->isMetaOnlyShopQuery($modelAnchor)) { + return mb_strtolower($modelAnchor, 'UTF-8'); + } + } + return ''; } + private function extractQuestionFromHistoryTurn(string $turn): string + { + if (preg_match($this->agentRunnerConfig->getFollowUpHistoryQuestionPattern(), $turn, $matches) !== 1) { + return ''; + } + + return $this->sanitizeHistoryQuestion((string) ($matches[1] ?? '')); + } + + private function isUsableContextualShopQuery(string $query): bool + { + $query = trim($query); + + if ($query === '' || $this->isMetaOnlyShopQuery($query)) { + return false; + } + + return $this->tokenizeShopQueryCandidate($query) !== []; + } + private function buildContextFallbackShopQuery(string $question): string { $tokens = $this->tokenizeShopQueryCandidate($question);