fix p46
This commit is contained in:
@@ -218,6 +218,8 @@ parameters:
|
|||||||
intro: 'Aus den Shopdaten ergeben sich folgende passende Treffer:'
|
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.'
|
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.'
|
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:
|
no_llm_fallback:
|
||||||
max_shop_results: 5
|
max_shop_results: 5
|
||||||
@@ -507,6 +509,17 @@ parameters:
|
|||||||
value_patterns:
|
value_patterns:
|
||||||
- '/(?P<value>\d+(?:[,.]\d+)?)\s*(?:m|meter|metern)\b/iu'
|
- '/(?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:
|
context_usage:
|
||||||
referential_terms:
|
referential_terms:
|
||||||
- der
|
- der
|
||||||
|
|||||||
@@ -3006,6 +3006,15 @@ final readonly class AgentRunner
|
|||||||
return $shopResults;
|
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 {
|
usort($decorated, static function (array $a, array $b): int {
|
||||||
if ($a['length'] === null && $b['length'] === null) {
|
if ($a['length'] === null && $b['length'] === null) {
|
||||||
return $a['index'] <=> $b['index'];
|
return $a['index'] <=> $b['index'];
|
||||||
@@ -3041,6 +3050,112 @@ final readonly class AgentRunner
|
|||||||
return false;
|
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
|
private function extractShopProductLengthMeters(ShopProductResult $product): ?float
|
||||||
{
|
{
|
||||||
$text = trim(implode(' ', array_filter([
|
$text = trim(implode(' ', array_filter([
|
||||||
@@ -3153,13 +3268,26 @@ final readonly class AgentRunner
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($shopResults === []) {
|
$answerShopResults = $shopResults;
|
||||||
|
$lengthFilter = $this->resolveShopResultLengthFilter($prompt, $shopSearchQuery);
|
||||||
|
if ($lengthFilter !== null) {
|
||||||
|
$answerShopResults = $this->filterDirectShopAnswerResultsByLength($answerShopResults, $lengthFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($answerShopResults === []) {
|
||||||
return $this->agentRunnerConfig->getDirectShopResultAnswerNoResultsMessage();
|
return $this->agentRunnerConfig->getDirectShopResultAnswerNoResultsMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
$lines = [$this->agentRunnerConfig->getDirectShopResultAnswerIntro()];
|
$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());
|
$note = trim($this->agentRunnerConfig->getDirectShopResultAnswerSortedByLengthNote());
|
||||||
if ($note !== '') {
|
if ($note !== '') {
|
||||||
$lines[] = $note;
|
$lines[] = $note;
|
||||||
@@ -3167,7 +3295,7 @@ final readonly class AgentRunner
|
|||||||
}
|
}
|
||||||
|
|
||||||
$lines[] = '';
|
$lines[] = '';
|
||||||
foreach ($this->buildDirectShopProductLines($shopResults, 'accessory_or_consumable') as $line) {
|
foreach ($this->buildDirectShopProductLines($answerShopResults, 'accessory_or_consumable') as $line) {
|
||||||
$lines[] = $line;
|
$lines[] = $line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -757,6 +757,16 @@ final class AgentRunnerConfig
|
|||||||
return $this->getRequiredString('direct_shop_result_answer.sorted_by_length_note');
|
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
|
public function getNoLlmFallbackMaxShopResults(): int
|
||||||
{
|
{
|
||||||
return $this->getRequiredInt('no_llm_fallback.max_shop_results');
|
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');
|
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
|
public function getShopPromptIntro(): string
|
||||||
{
|
{
|
||||||
return $this->getRequiredString('shop_prompt.intro');
|
return $this->getRequiredString('shop_prompt.intro');
|
||||||
|
|||||||
Reference in New Issue
Block a user