From 0174c8d1b1bae40395415e1788a585c83ef44fd7 Mon Sep 17 00:00:00 2001 From: team 1 Date: Sun, 10 May 2026 12:04:40 +0200 Subject: [PATCH] p85 --- src/Agent/AgentRunner.php | 135 +++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-) diff --git a/src/Agent/AgentRunner.php b/src/Agent/AgentRunner.php index ca6db10..55cf32d 100644 --- a/src/Agent/AgentRunner.php +++ b/src/Agent/AgentRunner.php @@ -355,6 +355,21 @@ final readonly class AgentRunner $optimizedShopQuery = ''; } + $deviceAnchoredShopSearchQuery = $this->enrichGenericDeviceShopQueryWithConfiguredAnchor($shopSearchQuery); + if ($deviceAnchoredShopSearchQuery !== $shopSearchQuery) { + $this->agentLogger->info('Enriched generic device shop query with configured product-family anchor', [ + 'userId' => $userId, + 'prompt' => $prompt, + 'routingPrompt' => $routingPrompt, + 'optimizedShopQuery' => $optimizedShopQuery, + 'shopSearchQuery' => $shopSearchQuery, + 'deviceAnchoredShopSearchQuery' => $deviceAnchoredShopSearchQuery, + ]); + + $shopSearchQuery = $deviceAnchoredShopSearchQuery; + $optimizedShopQuery = ''; + } + $positiveFilteredShopSearchQuery = $this->filterShopQueryToPositiveTokens($shopSearchQuery); if ($positiveFilteredShopSearchQuery !== $shopSearchQuery) { $this->agentLogger->info('Filtered final shop search query to positive product tokens', [ @@ -1718,6 +1733,99 @@ final readonly class AgentRunner return $cleaned !== '' ? $cleaned : $shopSearchQuery; } + private function enrichGenericDeviceShopQueryWithConfiguredAnchor(string $shopSearchQuery): string + { + $shopSearchQuery = trim($shopSearchQuery); + + if ( + $shopSearchQuery === '' + || !$this->agentRunnerConfig->isGenericDeviceQueryAnchorEnabled() + ) { + return $shopSearchQuery; + } + + $tokens = $this->tokenizeShopQueryCandidate($shopSearchQuery); + if ($tokens === []) { + return $shopSearchQuery; + } + + $tokenSet = array_fill_keys($tokens, true); + $genericDeviceTokens = $this->buildShopQueryTokenSet( + $this->agentRunnerConfig->getGenericDeviceQueryAnchorTriggerTerms() + ); + + if (!$this->tokenSetIntersects($tokenSet, $genericDeviceTokens)) { + return $shopSearchQuery; + } + + $suppressTokens = $this->buildShopQueryTokenSet( + $this->agentRunnerConfig->getGenericDeviceQueryAnchorSuppressTerms() + ); + + if ($this->tokenSetIntersects($tokenSet, $suppressTokens)) { + return $shopSearchQuery; + } + + foreach ($this->agentRunnerConfig->getGenericDeviceQueryAnchorRules() as $rule) { + $anchor = $rule['anchor']; + if ($anchor === '' || !$this->containsAnyShopQueryTerm($shopSearchQuery, $rule['match_terms'])) { + continue; + } + + if ($this->queryAlreadyContainsAllAnchorTokens($shopSearchQuery, $anchor)) { + return $shopSearchQuery; + } + + $query = $this->agentRunnerConfig->shouldGenericDeviceQueryAnchorRemoveGenericDeviceTerms() + ? $this->removeConfiguredGenericDeviceShopQueryTerms( + $shopSearchQuery, + $this->agentRunnerConfig->getGenericDeviceQueryAnchorTriggerTerms() + ) + : $shopSearchQuery; + + $template = $this->agentRunnerConfig->getGenericDeviceQueryAnchorTemplate(); + if ($template === '') { + return $shopSearchQuery; + } + + $enriched = $this->renderAgentTemplate($template, [ + 'anchor' => $anchor, + 'query' => $query, + ]); + $enriched = preg_replace('/\s+/u', ' ', $enriched) ?? $enriched; + $enriched = trim($enriched); + + return $enriched !== '' ? $enriched : $shopSearchQuery; + } + + return $shopSearchQuery; + } + + /** + * @param string[] $genericDeviceTerms + */ + private function removeConfiguredGenericDeviceShopQueryTerms(string $shopSearchQuery, array $genericDeviceTerms): string + { + $removeTokens = $this->buildShopQueryTokenSet($genericDeviceTerms); + + if ($removeTokens === []) { + return $shopSearchQuery; + } + + $kept = []; + foreach ($this->tokenizeShopQueryCandidate($shopSearchQuery) as $token) { + if (isset($removeTokens[$token]) || isset($kept[$token])) { + continue; + } + + $kept[$token] = $token; + } + + $cleaned = implode(' ', array_values($kept)); + + return $cleaned !== '' ? $cleaned : $shopSearchQuery; + } + private function filterShopQueryToPositiveTokens(string $shopSearchQuery): string { $shopSearchQuery = trim($shopSearchQuery); @@ -1797,11 +1905,15 @@ final readonly class AgentRunner array $variantPatterns, array $variantTokens ): bool { - if (!isset($variantTokens[$token]) && !$this->matchesAnyConfiguredShopQueryCodePattern($token, $variantPatterns)) { + $isExplicitVariantToken = isset($variantTokens[$token]); + $isPatternVariantToken = $this->matchesAnyConfiguredShopQueryCodePattern($token, $variantPatterns); + + if (!$isExplicitVariantToken && !$isPatternVariantToken) { return false; } $hasAdjacentNumericContext = false; + $hasAdjacentExplicitVariantContext = false; $nearbyKeptContextCount = 0; for ($offset = -2; $offset <= 2; ++$offset) { @@ -1822,9 +1934,23 @@ final readonly class AgentRunner if (abs($offset) === 1 && preg_match('/\d/u', $nearbyToken) === 1) { $hasAdjacentNumericContext = true; } + + if (abs($offset) === 1 && isset($variantTokens[$nearbyToken])) { + $hasAdjacentExplicitVariantContext = true; + } } - return $hasAdjacentNumericContext && $nearbyKeptContextCount >= 2; + if ($hasAdjacentNumericContext && $nearbyKeptContextCount >= 2) { + return true; + } + + // Preserve compact all-alpha model/acronym chains such as + // "Testomat LAB CL" without allowing arbitrary descriptive words to + // pass the positive token filter. The non-numeric path therefore uses + // only explicitly configured neighbouring variant terms from YAML. + return $isExplicitVariantToken + && $hasAdjacentExplicitVariantContext + && $nearbyKeptContextCount >= 1; } /** @@ -1855,6 +1981,11 @@ final readonly class AgentRunner ); } + $terms = $this->mergeUniqueStrings( + $terms, + $this->agentRunnerConfig->getGenericDeviceQueryAnchorPositiveFilterTerms() + ); + $tokens = []; foreach ($terms as $term) { foreach ($this->tokenizeShopQueryCandidate($term) as $token) {