From e54d7ecbe7ea07b0b8c80926ab7ef56cd86c50db Mon Sep 17 00:00:00 2001 From: team 1 Date: Sun, 3 May 2026 08:27:45 +0200 Subject: [PATCH] patch 20h --- ...OTAL_COMMERCE_ROUTING_STATUS_FIX_README.md | 152 ++++++++ src/Agent/AgentRunner.php | 335 +++++++----------- 2 files changed, 284 insertions(+), 203 deletions(-) create mode 100644 RETRIEX_PATCH_20H_TOTAL_COMMERCE_ROUTING_STATUS_FIX_README.md diff --git a/RETRIEX_PATCH_20H_TOTAL_COMMERCE_ROUTING_STATUS_FIX_README.md b/RETRIEX_PATCH_20H_TOTAL_COMMERCE_ROUTING_STATUS_FIX_README.md new file mode 100644 index 0000000..fe8e043 --- /dev/null +++ b/RETRIEX_PATCH_20H_TOTAL_COMMERCE_ROUTING_STATUS_FIX_README.md @@ -0,0 +1,152 @@ +# RetrieX Patch 20h – Total Commerce Routing & Shop Status Fix + +## Ziel + +Dieser Patch ersetzt die fragmentierten p20d/p20e/p20f/p20g-Nachbesserungen durch einen kumulativen Fix gegen den aktuellen `rag-inprogress.zip`-Stand. + +Er adressiert zwei zusammenhängende Fehlerklassen: + +1. Explizite oder referenzielle Commerce-/Shop-Anfragen dürfen nicht im RAG-only-Pfad landen. +2. Sobald ein Shop-/Commerce-Routing erkannt ist, darf die Statuskarte während des Ablaufs nicht weiter `Shop-Treffer: nicht angefragt` anzeigen. + +## Behobene Fälle + +### Expliziter Shop-Intent + +Beispiel: + +```text +shop testomat 808 +``` + +Erwartung: + +- Commerce-Intent wird abgesichert. +- Shop-Pfad wird betreten. +- Status springt nach der Intent-Erkennung auf `Shop-Treffer: wird geladen`. + +### Tippfehler-Normalisierung bleibt erhalten + +Beispiel: + +```text +was kpstet der indikator +``` + +Erwartung: + +- LLM-/Fuzzy-Normalisierung bleibt erhalten. +- Die Anfrage kann intern als Preis-/Shop-Follow-up geroutet werden. +- Es wird keine konkrete Tippfehlerliste eingeführt. + +### Referenzieller Tabellen-/Preis-Follow-up + +Beispiel: + +```text +welche grenzwerte kann der testomat 808 messen + +die tabelle mit preisen +``` + +Erwartung: + +- `die tabelle mit preisen` wird als kommerzieller Tabellen-Follow-up in den Shop-Pfad gehoben. +- Die Shop-Query wird aus dem Verlauf robuster abgeleitet, z. B. `Testomat 808 indikator`. +- Die History wird newest-first durchsucht und nicht nur der letzte Turn betrachtet. + +### Status-/Meta-Konsistenz + +Sobald Commerce-Intent erkannt ist, wird `shopCountMode` zentral berechnet: + +- vor Shop-Suche: `loading` → `Shop-Treffer: wird geladen` +- nach Shop-Suche: `count` → echte Trefferzahl +- Shop-Systemfehler: `unavailable` → `Shop-Treffer: nicht verfügbar` +- Commerce erkannt, aber keine konkrete Suchquery lösbar: `not_resolved` → `Shop-Treffer: keine Suchquery` +- nur Nicht-Commerce bleibt `not_requested` + +Damit gibt es im Shop-/Commerce-Ablauf keine verstreuten harten `not_requested`-Zwischenstatus mehr. + +## Geänderte Datei + +- `src/Agent/AgentRunner.php` + +## Technische Änderungen + +- Initialisierung von `$commerceIntent` und `$shopSearchSkippedBecauseNoQuery` vor dem Try-Block. +- Absicherung gegen Normalisierungsverluste: falls der normalisierte Routing-Prompt keinen Commerce-Intent ergibt, der Originalprompt aber schon, wird der Original-Commerce-Intent übernommen. +- Frühes Meta-Event `Shop-Routing erkannt` bei Commerce-Intent. +- RAG-Zwischenmeta nutzt bei Commerce-Intent `loading` statt `not_requested`. +- `not_requested` wird nicht mehr an mehreren Stellen hart gesetzt, sondern über `resolveShopCountModeForMeta()` zentral berechnet. +- Referenzielle Tabellen-Follow-ups werden ohne vorherige harte History-Anchor-Bedingung in den Shop-Pfad gehoben. +- Tabellen-/Preis-Follow-up-Query sucht History-Kontext robuster über Commerce-Kontext, erweiterten Verlauf und optional Full-History. +- History-Turns werden newest-first geprüft. +- Wenn ein Indikator-/Tabellenkontext ohne Modell gefunden wird, bleibt ein Fallback erhalten; ein später gefundener Modellanker hat Vorrang. + +## Lokale Prüfungen + +Durchgeführt in der Container-Umgebung: + +```bash +php -l src/Agent/AgentRunner.php +``` + +Ergebnis: OK. + +Zusätzliche statische Checks: + +- kein hartes `shopCountMode: 'not_requested'` mehr im AgentRunner +- `not_requested` nur noch zentral in `resolveShopCountModeForMeta()` +- `loading`, `not_resolved`, `unavailable` und `count` werden zentral unterschieden +- keine konkreten Tippfehlerlisten wie `kpstet` / `ksotet` + +Nicht vollständig ausführbar in dieser Umgebung: + +```bash +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 +``` + +Grund: Die ZIP enthält keine installierten Composer-/Runtime-Dependencies; die lokale Umgebung hat außerdem nicht alle benötigten PHP-Extensions. + +## Pflichtprüfung nach Einspielen + +```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 +``` + +Falls OPcache/PHP-FPM aktiv ist, danach PHP-FPM bzw. Container neu laden. + +## Manuelle Regressionstests + +```text +shop testomat 808 +``` + +Erwartung: Shop wird angefragt; nach Shop-Routing nicht mehr `Shop-Treffer: nicht angefragt`. + +```text +was kpstet der indikator +``` + +Erwartung: Shop-/Preis-Follow-up greift weiterhin über Normalisierung/Fuzzy-Routing. + +```text +welche grenzwerte kann der testomat 808 messen + +die tabelle mit preisen +``` + +Erwartung: Shop-Suche wird ausgelöst; Query sinngemäß `Testomat 808 indikator`. + +```text +die tabelle mit shop preisen +``` + +Erwartung: Shop-Suche wird ausgelöst, sofern Verlaufskontext vorhanden ist; sonst `Shop-Treffer: keine Suchquery` statt irreführend `nicht angefragt`. diff --git a/src/Agent/AgentRunner.php b/src/Agent/AgentRunner.php index a124c5b..10fdd8f 100644 --- a/src/Agent/AgentRunner.php +++ b/src/Agent/AgentRunner.php @@ -58,15 +58,15 @@ final readonly class AgentRunner $sources = []; $optimizedShopQuery = ''; $shopSearchQuery = ''; - $forcedShopSearchQuery = ''; $commerceHistoryContext = ''; $attemptedShopRepair = false; $usedShopRepair = false; $shopRepairQueries = []; $shopSearchAttempted = false; - $explicitShopRoutingForced = false; $primaryShopSearchHadSystemFailure = false; $historyNotices = []; + $commerceIntent = CommerceIntentLite::NONE; + $shopSearchSkippedBecauseNoQuery = false; $this->agentLogger->info('Agent run started', [ 'userId' => $userId, @@ -83,7 +83,12 @@ final readonly class AgentRunner stageLabel: 'Antwort wird vorbereitet', ragCount: null, shopCount: null, - shopCountMode: 'not_requested', + shopCountMode: $this->resolveShopCountModeForMeta( + commerceIntent: $commerceIntent, + shopSearchAttempted: $shopSearchAttempted, + shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure, + shopSearchSkippedBecauseNoQuery: $shopSearchSkippedBecauseNoQuery + ), sourceLabels: $sources, confidenceLabel: 'Beleglage wird geprüft' ), @@ -109,54 +114,44 @@ final readonly class AgentRunner $this->addSource($sources, $this->agentRunnerConfig->getExternalUrlSourceLabel()); } - $originalPromptHasExplicitShopSignal = $this->containsExplicitShopRoutingSignal($originalPrompt); - $routingPromptHasExplicitShopSignal = $this->containsExplicitShopRoutingSignal($routingPrompt); - - if ( - $originalPromptHasExplicitShopSignal - && !$this->isCommercialTableFollowUpPrompt($routingPrompt) - ) { - // Explicit user routing terms such as "shop" must never be lost - // through LLM normalization before the commerce gate is evaluated. - $routingPrompt = $originalPrompt; - $routingPromptHasExplicitShopSignal = true; - } - - $forcedShopSearchQuery = $this->resolveForcedCommercialFollowUpShopQuery( + $commerceIntent = $this->detectCommerceIntentForRouting( $routingPrompt, $userId, $requestContextHint ); + $originalCommerceIntent = $this->detectCommerceIntentForRouting( + $originalPrompt, + $userId, + $requestContextHint + ); - $explicitShopRoutingForced = $forcedShopSearchQuery === '' - && ($originalPromptHasExplicitShopSignal || $routingPromptHasExplicitShopSignal); - - $commerceIntent = ($forcedShopSearchQuery !== '' || $explicitShopRoutingForced) - ? CommerceIntentLite::PRODUCT_SEARCH - : $this->detectCommerceIntentForRouting( - $routingPrompt, - $userId, - $requestContextHint - ); - - if ($forcedShopSearchQuery !== '') { - $this->agentLogger->info('Forced commercial follow-up into shop routing', [ + if (!$this->isCommerceIntent($commerceIntent) && $this->isCommerceIntent($originalCommerceIntent)) { + $this->agentLogger->info('Promoted normalized routing back to explicit original commerce intent', [ 'userId' => $userId, - 'prompt' => $prompt, + 'originalPrompt' => $originalPrompt, 'routingPrompt' => $routingPrompt, - 'forcedShopSearchQuery' => $forcedShopSearchQuery, - 'hasRequestContextHint' => trim($requestContextHint) !== '', + 'originalCommerceIntent' => $originalCommerceIntent, ]); + $commerceIntent = $originalCommerceIntent; } - if ($explicitShopRoutingForced) { - $this->agentLogger->info('Forced explicit shop signal into commerce routing', [ - 'userId' => $userId, - 'prompt' => $prompt, - 'routingPrompt' => $routingPrompt, - 'originalPromptHasExplicitShopSignal' => $originalPromptHasExplicitShopSignal, - 'routingPromptHasExplicitShopSignal' => $routingPromptHasExplicitShopSignal, - ]); + if ($this->isCommerceIntent($commerceIntent)) { + yield $this->systemMsg( + $this->buildProductionUiMetaMessage( + stageLabel: 'Shop-Routing erkannt', + ragCount: null, + shopCount: null, + shopCountMode: $this->resolveShopCountModeForMeta( + commerceIntent: $commerceIntent, + shopSearchAttempted: $shopSearchAttempted, + shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure, + shopSearchSkippedBecauseNoQuery: $shopSearchSkippedBecauseNoQuery + ), + sourceLabels: $sources, + confidenceLabel: 'Shopdaten werden geprüft' + ), + 'meta' + ); } yield $this->systemMsg($this->agentRunnerConfig->getRetrieveKnowledgeMessage(), 'think'); @@ -179,7 +174,12 @@ final readonly class AgentRunner stageLabel: 'RAG-Wissen wurde durchsucht', ragCount: count($knowledgeChunks), shopCount: null, - shopCountMode: 'not_requested', + shopCountMode: $this->resolveShopCountModeForMeta( + commerceIntent: $commerceIntent, + shopSearchAttempted: $shopSearchAttempted, + shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure, + shopSearchSkippedBecauseNoQuery: $shopSearchSkippedBecauseNoQuery + ), sourceLabels: $sources, confidenceLabel: $this->resolveRagEvidenceConfidenceLabel($knowledgeEvidenceState) ), @@ -217,23 +217,18 @@ final readonly class AgentRunner $this->addSource($sources, $this->agentRunnerConfig->getConversationHistorySourceLabel()); } - if ($forcedShopSearchQuery !== '') { - $optimizedShopQuery = ''; - $shopSearchQuery = $forcedShopSearchQuery; - } else { - $optimizedShopQuery = yield from $this->buildOptimizedShopQuery( - $routingPrompt, - $userId, - $commerceHistoryContext - ); + $optimizedShopQuery = yield from $this->buildOptimizedShopQuery( + $routingPrompt, + $userId, + $commerceHistoryContext + ); - $shopSearchQuery = $this->resolveShopSearchQuery( - prompt: $routingPrompt, - optimizedShopQuery: $optimizedShopQuery, - commerceHistoryContext: $commerceHistoryContext, - userId: $userId - ); - } + $shopSearchQuery = $this->resolveShopSearchQuery( + prompt: $routingPrompt, + optimizedShopQuery: $optimizedShopQuery, + commerceHistoryContext: $commerceHistoryContext, + userId: $userId + ); if ($shopSearchQuery === '') { $this->agentLogger->info('Commerce search skipped because no concrete shop query could be resolved', [ @@ -247,6 +242,7 @@ final readonly class AgentRunner 'hasRequestContextHint' => trim($requestContextHint) !== '', ]); + $shopSearchSkippedBecauseNoQuery = true; $noConcreteShopQueryMessage = $this->agentRunnerConfig->getNoConcreteShopQueryMessage(); yield $this->systemMsg( @@ -254,7 +250,12 @@ final readonly class AgentRunner stageLabel: 'Mehr Kontext nötig', ragCount: count($knowledgeChunks), shopCount: null, - shopCountMode: 'not_requested', + shopCountMode: $this->resolveShopCountModeForMeta( + commerceIntent: $commerceIntent, + shopSearchAttempted: $shopSearchAttempted, + shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure, + shopSearchSkippedBecauseNoQuery: $shopSearchSkippedBecauseNoQuery + ), sourceLabels: $sources, confidenceLabel: 'mehr Kontext nötig', completed: true @@ -483,7 +484,12 @@ final readonly class AgentRunner stageLabel: 'Antwort wird generiert', ragCount: count($knowledgeChunks), shopCount: $primaryShopSearchHadSystemFailure ? null : ($shopSearchAttempted ? count($shopResults) : null), - shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : ($shopSearchAttempted ? 'count' : 'not_requested'), + shopCountMode: $this->resolveShopCountModeForMeta( + commerceIntent: $commerceIntent, + shopSearchAttempted: $shopSearchAttempted, + shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure, + shopSearchSkippedBecauseNoQuery: $shopSearchSkippedBecauseNoQuery + ), sourceLabels: $sources, confidenceLabel: $this->resolveProductionUiConfidenceLabel( hasKnowledge: $this->isDirectKnowledgeEvidence($knowledgeEvidenceState), @@ -523,7 +529,12 @@ final readonly class AgentRunner stageLabel: 'Abgeschlossen', ragCount: count($knowledgeChunks), shopCount: $primaryShopSearchHadSystemFailure ? null : ($shopSearchAttempted ? count($shopResults) : null), - shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : ($shopSearchAttempted ? 'count' : 'not_requested'), + shopCountMode: $this->resolveShopCountModeForMeta( + commerceIntent: $commerceIntent, + shopSearchAttempted: $shopSearchAttempted, + shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure, + shopSearchSkippedBecauseNoQuery: $shopSearchSkippedBecauseNoQuery + ), sourceLabels: $sources, confidenceLabel: $this->resolveProductionUiConfidenceLabel( hasKnowledge: $this->isDirectKnowledgeEvidence($knowledgeEvidenceState), @@ -598,7 +609,12 @@ final readonly class AgentRunner stageLabel: 'Antwort wurde unterbrochen', ragCount: count($knowledgeChunks), shopCount: $primaryShopSearchHadSystemFailure ? null : ($shopSearchAttempted ? count($shopResults) : null), - shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : ($shopSearchAttempted ? 'count' : 'not_requested'), + shopCountMode: $this->resolveShopCountModeForMeta( + commerceIntent: $commerceIntent, + shopSearchAttempted: $shopSearchAttempted, + shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure, + shopSearchSkippedBecauseNoQuery: $shopSearchSkippedBecauseNoQuery + ), sourceLabels: $sources, confidenceLabel: 'nicht abgeschlossen', completed: true @@ -988,76 +1004,6 @@ final readonly class AgentRunner return (string) ($commerceMeta['intent'] ?? CommerceIntentLite::NONE); } - private function containsExplicitShopRoutingSignal(string $prompt): bool - { - $normalized = $this->normalizeFollowUpText($prompt); - - if ($normalized === '') { - return false; - } - - foreach ($this->agentRunnerConfig->getFollowUpExplicitCommercialSignalTerms() as $signal) { - $signal = $this->normalizeFollowUpText($signal); - - if ($signal === '') { - continue; - } - - $pattern = '/(?isCommercialTableFollowUpPrompt($prompt)) { - return ''; - } - - $commerceHistoryContext = $this->buildCommerceHistoryContext($userId, $requestContextHint); - $query = $this->resolveCommercialTableFollowUpShopQuery($commerceHistoryContext, $userId); - - if ($query !== '' && !$this->isMetaOnlyShopQuery($query)) { - return $query; - } - - $contextQuery = $this->extractContextualShopSearchQuery($commerceHistoryContext); - if ($contextQuery !== '' && !$this->isMetaOnlyShopQuery($contextQuery)) { - return $contextQuery; - } - - $extendedHistoryBudget = $this->agentRunnerConfig->getShopQueryContextFallbackHistoryBudgetChars(); - if ($extendedHistoryBudget > mb_strlen($commerceHistoryContext, 'UTF-8')) { - $extendedHistory = $this->contextService->buildUserContextWithinBudget($userId, $extendedHistoryBudget); - $contextQuery = $this->extractContextualShopSearchQuery($extendedHistory); - - if ($contextQuery !== '' && !$this->isMetaOnlyShopQuery($contextQuery)) { - return $contextQuery; - } - } - - if ($this->agentRunnerConfig->shouldUseFullHistoryForShopQueryContextFallback()) { - $fullHistory = $this->contextService->buildUserContext($userId, true); - $contextQuery = $this->extractContextualShopSearchQuery($fullHistory); - - if ($contextQuery !== '' && !$this->isMetaOnlyShopQuery($contextQuery)) { - return $contextQuery; - } - } - - // Last-resort fallback for explicit commercial table follow-ups. - // This keeps the request in the shop path instead of falling back to RAG-only. - return trim($this->agentRunnerConfig->getCommercialTableFollowUpQueryTemplateWithoutModel()); - } - private function detectCommerceIntentForRouting( string $prompt, string $userId, @@ -1073,12 +1019,9 @@ final readonly class AgentRunner return $commerceIntent; } - $commerceHistoryContext = $this->buildCommerceHistoryContext($userId, $requestContextHint); - $this->agentLogger->info('Promoted commercial table follow-up to shop intent', [ 'userId' => $userId, 'prompt' => $prompt, - 'hasHistoryAnchor' => $this->commercialTableFollowUpHistoryHasAnchor($commerceHistoryContext), 'hasRequestContextHint' => trim($requestContextHint) !== '', ]); @@ -1281,7 +1224,7 @@ final readonly class AgentRunner $parts = preg_split($this->agentRunnerConfig->getFollowUpHistoryTurnSplitPattern(), $history); if ($parts === false || $parts === []) { - return [$history]; + return []; } $turns = array_values(array_filter( @@ -1443,13 +1386,12 @@ final readonly class AgentRunner string $userId ): string { if ($this->isCommercialTableFollowUpPrompt($prompt)) { - $commercialTableContextQuery = $this->resolveCommercialTableFollowUpShopQuery( - $commerceHistoryContext, - $userId - ); + foreach ($this->buildCommercialTableFollowUpContextCandidates($commerceHistoryContext, $userId) as $contextCandidate) { + $commercialTableContextQuery = $this->extractCommercialTableFollowUpShopQuery($contextCandidate); - if ($commercialTableContextQuery !== '' && !$this->isMetaOnlyShopQuery($commercialTableContextQuery)) { - return $commercialTableContextQuery; + if ($commercialTableContextQuery !== '' && !$this->isMetaOnlyShopQuery($commercialTableContextQuery)) { + return $commercialTableContextQuery; + } } } @@ -1490,32 +1432,34 @@ final readonly class AgentRunner return ''; } - private function resolveCommercialTableFollowUpShopQuery(string $commerceHistoryContext, string $userId): string + /** + * @return string[] + */ + private function buildCommercialTableFollowUpContextCandidates(string $commerceHistoryContext, string $userId): array { - $query = $this->extractCommercialTableFollowUpShopQuery($commerceHistoryContext); + $candidates = []; - if ($query !== '') { - return $query; + $commerceHistoryContext = trim($commerceHistoryContext); + if ($commerceHistoryContext !== '') { + $candidates[] = $commerceHistoryContext; } $extendedHistoryBudget = $this->agentRunnerConfig->getShopQueryContextFallbackHistoryBudgetChars(); - if ($extendedHistoryBudget > mb_strlen($commerceHistoryContext, 'UTF-8')) { - $extendedHistory = $this->contextService->buildUserContextWithinBudget($userId, $extendedHistoryBudget); - $query = $this->extractCommercialTableFollowUpShopQuery($extendedHistory); - - if ($query !== '') { - return $query; + $extendedHistory = trim($this->contextService->buildUserContextWithinBudget($userId, $extendedHistoryBudget)); + if ($extendedHistory !== '') { + $candidates[] = $extendedHistory; } } if ($this->agentRunnerConfig->shouldUseFullHistoryForShopQueryContextFallback()) { - return $this->extractCommercialTableFollowUpShopQuery( - $this->contextService->buildUserContext($userId, true) - ); + $fullHistory = trim($this->contextService->buildUserContext($userId, true)); + if ($fullHistory !== '') { + $candidates[] = $fullHistory; + } } - return ''; + return array_values(array_unique($candidates)); } private function extractCommercialTableFollowUpShopQuery(string $commerceHistoryContext): string @@ -1524,7 +1468,7 @@ final readonly class AgentRunner return ''; } - $hasIndicatorContext = false; + $fallbackWithoutModel = ''; foreach ($this->extractHistoryTurnsNewestFirst($commerceHistoryContext) as $turn) { if (!$this->matchesAnyConfiguredPattern( @@ -1534,7 +1478,6 @@ final readonly class AgentRunner continue; } - $hasIndicatorContext = true; $model = $this->extractFirstTestomatModelAnchor($turn); if ($model !== '') { @@ -1546,13 +1489,11 @@ final readonly class AgentRunner return trim((string) preg_replace('/\s+/u', ' ', $query)); } + + $fallbackWithoutModel = trim($this->agentRunnerConfig->getCommercialTableFollowUpQueryTemplateWithoutModel()); } - if ($hasIndicatorContext) { - return trim($this->agentRunnerConfig->getCommercialTableFollowUpQueryTemplateWithoutModel()); - } - - return ''; + return $fallbackWithoutModel; } private function isCommercialTableFollowUpPrompt(string $prompt): bool @@ -1561,47 +1502,9 @@ final readonly class AgentRunner return false; } - $normalized = $this->normalizeFollowUpText($prompt); - - if ($this->matchesAnyConfiguredPattern( - $normalized, - $this->agentRunnerConfig->getCommercialTableFollowUpPromptPatterns() - )) { - return true; - } - - $tokens = $this->tokenizeMetaGuardText($normalized); - if ($tokens === []) { - return false; - } - - $hasTableReference = count(array_intersect( - $tokens, - $this->agentRunnerConfig->getCommercialTableFollowUpTableTerms() - )) > 0; - - if (!$hasTableReference) { - return false; - } - - foreach ($this->agentRunnerConfig->getCommercialTableFollowUpCommercialTerms() as $term) { - if (in_array($this->normalizeFollowUpText($term), $tokens, true)) { - return true; - } - } - - return false; - } - - private function commercialTableFollowUpHistoryHasAnchor(string $commerceHistoryContext): bool - { - if (trim($commerceHistoryContext) === '') { - return false; - } - return $this->matchesAnyConfiguredPattern( - $commerceHistoryContext, - $this->agentRunnerConfig->getCommercialTableFollowUpHistoryAnchorPatterns() + $this->normalizeFollowUpText($prompt), + $this->agentRunnerConfig->getCommercialTableFollowUpPromptPatterns() ); } @@ -2632,6 +2535,31 @@ final readonly class AgentRunner return trim($value); } + private function resolveShopCountModeForMeta( + string $commerceIntent, + bool $shopSearchAttempted, + bool $shopSearchHadSystemFailure, + bool $shopSearchSkippedBecauseNoQuery = false + ): string { + if ($shopSearchHadSystemFailure) { + return 'unavailable'; + } + + if ($shopSearchAttempted) { + return 'count'; + } + + if ($shopSearchSkippedBecauseNoQuery) { + return 'not_resolved'; + } + + if ($this->isCommerceIntent($commerceIntent)) { + return 'loading'; + } + + return 'not_requested'; + } + /** * @param string[] $sourceLabels */ @@ -2652,6 +2580,7 @@ final readonly class AgentRunner 'count' => 'Shop-Treffer: ' . max(0, (int) $shopCount), 'loading' => 'Shop-Treffer: wird geladen', 'unavailable' => 'Shop-Treffer: nicht verfügbar', + 'not_resolved' => 'Shop-Treffer: keine Suchquery', default => 'Shop-Treffer: nicht angefragt', }; $statusLabel = $completed ? 'Status: abgeschlossen' : 'Status: läuft';