From 433ce2046f720038c7a1287dd3a9183af496f4f9 Mon Sep 17 00:00:00 2001 From: team 1 Date: Wed, 6 May 2026 11:02:48 +0200 Subject: [PATCH] fix p46 --- config/retriex/agent.yaml | 13 +++ src/Agent/AgentRunner.php | 134 ++++++++++++++++++++++++++++++- src/Config/AgentRunnerConfig.php | 31 +++++++ 3 files changed, 175 insertions(+), 3 deletions(-) diff --git a/config/retriex/agent.yaml b/config/retriex/agent.yaml index d0be2ee..0f68a8d 100644 --- a/config/retriex/agent.yaml +++ b/config/retriex/agent.yaml @@ -218,6 +218,8 @@ parameters: intro: 'Aus den Shopdaten ergeben sich folgende passende Treffer:' no_results: 'Ich finde in den Shopdaten keine passenden Treffer für die angefragte Produktsuche. Ich liste deshalb keine fachfremden Ersatzprodukte auf.' sorted_by_length_note: 'Sortierung: aufsteigend nach erkannter Kabellänge.' + min_length_filter_note: 'Filter: nur Treffer ab {value} m.' + max_length_filter_note: 'Filter: nur Treffer bis {value} m.' no_llm_fallback: max_shop_results: 5 @@ -507,6 +509,17 @@ parameters: value_patterns: - '/(?P\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu' + length_filter: + enabled: true + min_patterns: + - '/\b(?:ab|mindestens|minimum|min\.?|>=|größer\s+gleich|groesser\s+gleich)\s*(?P\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu' + - '/\b(?:länger|laenger|größer|groesser|mehr)\s+(?:als\s+)?(?P\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu' + - '/(?P\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\s*(?:oder\s+)?(?:länger|laenger|mehr)\b/iu' + max_patterns: + - '/\b(?:bis|maximal|maximum|max\.?|höchstens|hoechstens|<=|kleiner\s+gleich)\s*(?P\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu' + - '/\b(?:kürzer|kuerzer|kleiner|weniger)\s+(?:als\s+)?(?P\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu' + - '/(?P\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\s*(?:oder\s+)?(?:kürzer|kuerzer|weniger)\b/iu' + context_usage: referential_terms: - der diff --git a/src/Agent/AgentRunner.php b/src/Agent/AgentRunner.php index 5dd8827..83df611 100644 --- a/src/Agent/AgentRunner.php +++ b/src/Agent/AgentRunner.php @@ -3006,6 +3006,15 @@ final readonly class AgentRunner return $shopResults; } + return $this->sortDecoratedShopProductsByLength($decorated); + } + + /** + * @param array $decorated + * @return ShopProductResult[] + */ + private function sortDecoratedShopProductsByLength(array $decorated): array + { usort($decorated, static function (array $a, array $b): int { if ($a['length'] === null && $b['length'] === null) { return $a['index'] <=> $b['index']; @@ -3041,6 +3050,112 @@ final readonly class AgentRunner return false; } + /** + * @return array{type: string, value: float}|null + */ + private function resolveShopResultLengthFilter(string $prompt, string $shopSearchQuery): ?array + { + if (!$this->agentRunnerConfig->isShopResultLengthFilterEnabled()) { + return null; + } + + $text = trim($prompt . ' ' . $shopSearchQuery); + if ($text === '') { + return null; + } + + foreach ($this->agentRunnerConfig->getShopResultMinLengthFilterPatterns() as $pattern) { + $value = $this->extractShopLengthFilterPatternValue($pattern, $text); + if ($value !== null) { + return ['type' => 'min', 'value' => $value]; + } + } + + foreach ($this->agentRunnerConfig->getShopResultMaxLengthFilterPatterns() as $pattern) { + $value = $this->extractShopLengthFilterPatternValue($pattern, $text); + if ($value !== null) { + return ['type' => 'max', 'value' => $value]; + } + } + + return null; + } + + private function extractShopLengthFilterPatternValue(string $pattern, string $text): ?float + { + if (@preg_match($pattern, $text, $matches) !== 1) { + return null; + } + + $value = $matches['value'] ?? ($matches[1] ?? null); + if (!is_scalar($value)) { + return null; + } + + $normalized = str_replace(',', '.', (string) $value); + if (!is_numeric($normalized)) { + return null; + } + + return (float) $normalized; + } + + /** + * @param ShopProductResult[] $shopResults + * @param array{type: string, value: float} $lengthFilter + * @return ShopProductResult[] + */ + private function filterDirectShopAnswerResultsByLength(array $shopResults, array $lengthFilter): array + { + $decorated = []; + + foreach (array_values($shopResults) as $index => $product) { + if (!$product instanceof ShopProductResult) { + continue; + } + + $length = $this->extractShopProductLengthMeters($product); + if ($length === null) { + continue; + } + + if (($lengthFilter['type'] === 'min' && $length < $lengthFilter['value']) + || ($lengthFilter['type'] === 'max' && $length > $lengthFilter['value']) + ) { + continue; + } + + $decorated[] = [ + 'index' => $index, + 'length' => $length, + 'product' => $product, + ]; + } + + return $this->sortDecoratedShopProductsByLength($decorated); + } + + /** + * @param array{type: string, value: float} $lengthFilter + */ + private function buildDirectShopAnswerLengthFilterNote(array $lengthFilter): string + { + $template = $lengthFilter['type'] === 'max' + ? $this->agentRunnerConfig->getDirectShopResultAnswerMaxLengthFilterNote() + : $this->agentRunnerConfig->getDirectShopResultAnswerMinLengthFilterNote(); + + return str_replace('{value}', $this->formatShopLengthFilterValue($lengthFilter['value']), $template); + } + + private function formatShopLengthFilterValue(float $value): string + { + if (abs($value - round($value)) < 0.00001) { + return (string) (int) round($value); + } + + return rtrim(rtrim(str_replace('.', ',', sprintf('%.2F', $value)), '0'), ','); + } + private function extractShopProductLengthMeters(ShopProductResult $product): ?float { $text = trim(implode(' ', array_filter([ @@ -3153,13 +3268,26 @@ final readonly class AgentRunner return ''; } - if ($shopResults === []) { + $answerShopResults = $shopResults; + $lengthFilter = $this->resolveShopResultLengthFilter($prompt, $shopSearchQuery); + if ($lengthFilter !== null) { + $answerShopResults = $this->filterDirectShopAnswerResultsByLength($answerShopResults, $lengthFilter); + } + + if ($answerShopResults === []) { return $this->agentRunnerConfig->getDirectShopResultAnswerNoResultsMessage(); } $lines = [$this->agentRunnerConfig->getDirectShopResultAnswerIntro()]; - if ($this->isShopResultLengthSortRequested($prompt . ' ' . $shopSearchQuery)) { + if ($lengthFilter !== null) { + $note = trim($this->buildDirectShopAnswerLengthFilterNote($lengthFilter)); + if ($note !== '') { + $lines[] = $note; + } + } + + if ($lengthFilter !== null || $this->isShopResultLengthSortRequested($prompt . ' ' . $shopSearchQuery)) { $note = trim($this->agentRunnerConfig->getDirectShopResultAnswerSortedByLengthNote()); if ($note !== '') { $lines[] = $note; @@ -3167,7 +3295,7 @@ final readonly class AgentRunner } $lines[] = ''; - foreach ($this->buildDirectShopProductLines($shopResults, 'accessory_or_consumable') as $line) { + foreach ($this->buildDirectShopProductLines($answerShopResults, 'accessory_or_consumable') as $line) { $lines[] = $line; } diff --git a/src/Config/AgentRunnerConfig.php b/src/Config/AgentRunnerConfig.php index 1e97553..df8f757 100644 --- a/src/Config/AgentRunnerConfig.php +++ b/src/Config/AgentRunnerConfig.php @@ -757,6 +757,16 @@ final class AgentRunnerConfig return $this->getRequiredString('direct_shop_result_answer.sorted_by_length_note'); } + public function getDirectShopResultAnswerMinLengthFilterNote(): string + { + return $this->getRequiredString('direct_shop_result_answer.min_length_filter_note'); + } + + public function getDirectShopResultAnswerMaxLengthFilterNote(): string + { + return $this->getRequiredString('direct_shop_result_answer.max_length_filter_note'); + } + public function getNoLlmFallbackMaxShopResults(): int { return $this->getRequiredInt('no_llm_fallback.max_shop_results'); @@ -1151,6 +1161,27 @@ final class AgentRunnerConfig return $this->getRequiredStringList('shop_prompt.length_sort.value_patterns'); } + public function isShopResultLengthFilterEnabled(): bool + { + return $this->getRequiredBool('shop_prompt.length_filter.enabled'); + } + + /** + * @return string[] + */ + public function getShopResultMinLengthFilterPatterns(): array + { + return $this->getRequiredStringList('shop_prompt.length_filter.min_patterns'); + } + + /** + * @return string[] + */ + public function getShopResultMaxLengthFilterPatterns(): array + { + return $this->getRequiredStringList('shop_prompt.length_filter.max_patterns'); + } + public function getShopPromptIntro(): string { return $this->getRequiredString('shop_prompt.intro');