This commit is contained in:
team 1
2026-05-06 11:02:48 +02:00
parent 4832c2e287
commit 433ce2046f
3 changed files with 175 additions and 3 deletions

View File

@@ -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<value>\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<value>\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu'
- '/\b(?:länger|laenger|größer|groesser|mehr)\s+(?:als\s+)?(?P<value>\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu'
- '/(?P<value>\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<value>\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu'
- '/\b(?:kürzer|kuerzer|kleiner|weniger)\s+(?:als\s+)?(?P<value>\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu'
- '/(?P<value>\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\s*(?:oder\s+)?(?:kürzer|kuerzer|weniger)\b/iu'
context_usage:
referential_terms:
- der

View File

@@ -3006,6 +3006,15 @@ final readonly class AgentRunner
return $shopResults;
}
return $this->sortDecoratedShopProductsByLength($decorated);
}
/**
* @param array<int, array{index: int, length: float|null, product: mixed}> $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;
}

View File

@@ -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');