patch 7.2

This commit is contained in:
team 1
2026-04-30 15:47:20 +02:00
parent 04fb092193
commit 1731d10746

View File

@@ -87,6 +87,29 @@ final class AgentRunnerConfig
throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be numeric.', $key)); throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be numeric.', $key));
} }
private function getRequiredBool(string $key): bool
{
$value = $this->requiredValue($key);
if (is_bool($value)) {
return $value;
}
if (is_scalar($value)) {
$normalized = strtolower(trim((string) $value));
if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) {
return true;
}
if (in_array($normalized, ['0', 'false', 'no', 'off'], true)) {
return false;
}
}
throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be boolean.', $key));
}
private function getRequiredString(string $key): string private function getRequiredString(string $key): string
{ {
$value = $this->requiredValue($key); $value = $this->requiredValue($key);
@@ -177,6 +200,39 @@ final class AgentRunnerConfig
return $out; return $out;
} }
/**
* @return array<string, string>
*/
private function getOptionalStringMap(string $key): array
{
$value = $this->optionalValue($key);
if ($value === null) {
return [];
}
if (!is_array($value)) {
throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a string map.', $key));
}
$out = [];
foreach ($value as $mapKey => $mapValue) {
if (!is_scalar($mapKey) || !is_scalar($mapValue)) {
continue;
}
$mapKey = trim((string) $mapKey);
$mapValue = trim((string) $mapValue);
if ($mapKey !== '' && $mapValue !== '') {
$out[$mapKey] = $mapValue;
}
}
return $out;
}
/** /**
* @param string[] $default * @param string[] $default
* @return string[] * @return string[]
@@ -236,6 +292,21 @@ final class AgentRunnerConfig
return $current; return $current;
} }
private function optionalValue(string $key): mixed
{
$current = $this->config;
foreach (explode('.', $key) as $segment) {
if (!is_array($current) || !array_key_exists($segment, $current)) {
return null;
}
$current = $current[$segment];
}
return $current;
}
public function getEmptyPromptMessage(): string public function getEmptyPromptMessage(): string
{ {
return $this->getRequiredString('messages.empty_prompt'); return $this->getRequiredString('messages.empty_prompt');
@@ -474,25 +545,7 @@ final class AgentRunnerConfig
*/ */
public function getShopPromptRules(): array public function getShopPromptRules(): array
{ {
return $this->getStringList('shop_prompt.rules', [ return $this->getRequiredStringList('shop_prompt.rules');
'- Output only the final search query.',
'- Always convert relevant search terms to their singular form.',
'- No introduction, no explanation, no quotation marks.',
'- Use only shop-relevant search terms from the user input for a shop search.',
'- Maximum 6 search terms, preferably fewer.',
'- Remove filler words, polite phrases, and irrelevant words.',
'- Preserve product names, brands, model numbers, and compound terms exactly if they are relevant.',
'- Preserve the language of the CURRENT USER INPUT for generic product/search terms; do not translate German search terms into English.',
'- For German user input, output German shop terms, for example "freies Chlor Messung" instead of "free chlorine measurement".',
'- Preserve domain terms from the current user input or resolved context in their original language.',
'- Numbers that belong to a product name or model must be preserved (e.g. Indikator 300, Testomat 808, Testomat 2000).',
'- Separate terms using spaces only.',
'- If a relevant product name is present, it must be placed at the beginning of the final search query.',
'- Try to always identify all products mentioned in the user input text, even in long prompts.',
'- Look for terms such as Testomat, Horiba, Tritromat, or words like indicator/Indikator.',
'- If the current user input is vague or referential, use the recent conversation context only as support.',
'- Do not output words that only describe conversation flow, such as "same", "again", "also", or "like above".',
]);
} }
/** /**
@@ -500,39 +553,32 @@ final class AgentRunnerConfig
*/ */
public function getConversationContextRules(): array public function getConversationContextRules(): array
{ {
return $this->getStringList('shop_prompt.conversation_context_rules', [ return $this->getRequiredStringList('shop_prompt.conversation_context_rules');
'- The current user input has highest priority.',
'- Use the recent conversation context only to resolve omitted references.',
'- Use it only for product carry-over, brand carry-over, model carry-over, or variant follow-ups.',
'- Do not revive older products unless the current user input clearly refers to them.',
'- If the current input starts a new topic, ignore older product context.',
'- Prefer the most recent product reference over older ones.',
]);
} }
public function getShopPromptIntro(): string public function getShopPromptIntro(): string
{ {
return $this->getString('shop_prompt.intro', 'Generate a short search query for Shopware 6 from the following user input text.'); return $this->getRequiredString('shop_prompt.intro');
} }
public function getShopPromptOutputFormatBlock(): string public function getShopPromptOutputFormatBlock(): string
{ {
return $this->getString('shop_prompt.output_format_block', "Output format:\nKeyword1 Keyword2 Keyword3"); return $this->getRequiredString('shop_prompt.output_format_block');
} }
public function getRecentConversationContextLabel(): string public function getRecentConversationContextLabel(): string
{ {
return $this->getString('shop_prompt.recent_conversation_context_label', 'RECENT CONVERSATION CONTEXT'); return $this->getRequiredString('shop_prompt.recent_conversation_context_label');
} }
public function getCurrentUserInputLabel(): string public function getCurrentUserInputLabel(): string
{ {
return $this->getString('shop_prompt.current_user_input_label', 'CURRENT USER INPUT'); return $this->getRequiredString('shop_prompt.current_user_input_label');
} }
public function isShopQueryLanguagePreservationEnabled(): bool public function isShopQueryLanguagePreservationEnabled(): bool
{ {
return $this->getBool('shop_prompt.language_preservation.enabled', true); return $this->getRequiredBool('shop_prompt.language_preservation.enabled');
} }
/** /**
@@ -540,20 +586,10 @@ final class AgentRunnerConfig
*/ */
public function getShopQueryLanguageMarkers(): array public function getShopQueryLanguageMarkers(): array
{ {
$default = [ $value = $this->requiredValue('shop_prompt.language_preservation.language_markers');
'de' => [
' ä ', ' ö ', ' ü ', ' ß ',
' der ', ' die ', ' das ', ' ein ', ' eine ', ' einer ', ' einen ',
' welchem ', ' welchen ', ' welche ', ' welcher ',
' kann ', ' nutzen ', ' zur ', ' für ', ' fuer ',
' messung ', ' indikator ', ' reagenz ', ' chlor ',
],
];
$value = $this->value('shop_prompt.language_preservation.language_markers', $default);
if (!is_array($value)) { if (!is_array($value)) {
return $default; throw new \InvalidArgumentException('RetrieX agent config key "shop_prompt.language_preservation.language_markers" must be a map of marker lists.');
} }
$out = []; $out = [];
@@ -582,15 +618,16 @@ final class AgentRunnerConfig
} }
} }
return $out !== [] ? $out : $default; if ($out === []) {
throw new \InvalidArgumentException('RetrieX agent config key "shop_prompt.language_preservation.language_markers" must contain at least one valid marker list.');
}
return $out;
} }
/**
* @return array<string, string>
*/
public function isShopQueryMetaGuardEnabled(): bool public function isShopQueryMetaGuardEnabled(): bool
{ {
return $this->getBool('shop_prompt.meta_query_guard.enabled', true); return $this->getRequiredBool('shop_prompt.meta_query_guard.enabled');
} }
/** /**
@@ -598,59 +635,32 @@ final class AgentRunnerConfig
*/ */
public function getShopQueryMetaOnlyTerms(): array public function getShopQueryMetaOnlyTerms(): array
{ {
return $this->getStringList('shop_prompt.meta_query_guard.meta_only_terms', [ return $this->getRequiredStringList('shop_prompt.meta_query_guard.meta_only_terms');
'shop',
'shopsuche',
'shop-suche',
'suche',
'suchen',
'such',
'finde',
'find',
'zeige',
'zeig',
'bitte',
'mal',
'im',
'in',
'nach',
'danach',
'dazu',
'damit',
'dafür',
'dafuer',
'hierzu',
'den',
'die',
'das',
'der',
'dem',
]);
} }
public function isShopQueryContextFallbackEnabled(): bool public function isShopQueryContextFallbackEnabled(): bool
{ {
return $this->getBool('shop_prompt.meta_query_guard.context_fallback_enabled', true); return $this->getRequiredBool('shop_prompt.meta_query_guard.context_fallback_enabled');
} }
public function getShopQueryContextFallbackQuestionLimit(): int public function getShopQueryContextFallbackQuestionLimit(): int
{ {
return $this->getInt('shop_prompt.meta_query_guard.context_fallback_question_limit', 12); return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_question_limit');
} }
public function getShopQueryContextFallbackHistoryBudgetChars(): int public function getShopQueryContextFallbackHistoryBudgetChars(): int
{ {
return $this->getInt('shop_prompt.meta_query_guard.context_fallback_history_budget_chars', 20000); return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_history_budget_chars');
} }
public function shouldUseFullHistoryForShopQueryContextFallback(): bool public function shouldUseFullHistoryForShopQueryContextFallback(): bool
{ {
return $this->getBool('shop_prompt.meta_query_guard.context_fallback_use_full_history', true); return $this->getRequiredBool('shop_prompt.meta_query_guard.context_fallback_use_full_history');
} }
public function getShopQueryContextFallbackMaxTerms(): int public function getShopQueryContextFallbackMaxTerms(): int
{ {
return $this->getInt('shop_prompt.meta_query_guard.context_fallback_max_terms', 6); return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_max_terms');
} }
/** /**
@@ -658,50 +668,17 @@ final class AgentRunnerConfig
*/ */
public function getShopQueryContextFallbackFilterTerms(): array public function getShopQueryContextFallbackFilterTerms(): array
{ {
return $this->getStringList('shop_prompt.meta_query_guard.context_fallback_filter_terms', [ return $this->getRequiredStringList('shop_prompt.meta_query_guard.context_fallback_filter_terms');
'mit',
'welche',
'welcher',
'welches',
'welchem',
'welchen',
'ist',
'sind',
'gut',
'geeignet',
'was',
'wie',
'wo',
'kann',
'koennen',
'können',
'konnte',
'könnte',
'ich',
'wir',
'man',
'nutzen',
'benutzen',
'verwenden',
'verwende',
'nehmen',
'zur',
'zum',
'für',
'fuer',
'messen',
'gemessen',
]);
} }
public function isShopQueryContextAnchorEnrichmentEnabled(): bool public function isShopQueryContextAnchorEnrichmentEnabled(): bool
{ {
return $this->getBool('shop_prompt.context_anchor_enrichment.enabled', true); return $this->getRequiredBool('shop_prompt.context_anchor_enrichment.enabled');
} }
public function getShopQueryContextAnchorEnrichmentMaxQueryTerms(): int public function getShopQueryContextAnchorEnrichmentMaxQueryTerms(): int
{ {
return $this->getInt('shop_prompt.context_anchor_enrichment.max_query_terms', 2); return $this->getRequiredInt('shop_prompt.context_anchor_enrichment.max_query_terms');
} }
/** /**
@@ -709,17 +686,7 @@ final class AgentRunnerConfig
*/ */
public function getShopQueryContextAnchorEnrichmentTriggerTerms(): array public function getShopQueryContextAnchorEnrichmentTriggerTerms(): array
{ {
return $this->getStringList('shop_prompt.context_anchor_enrichment.trigger_terms', [ return $this->getRequiredStringList('shop_prompt.context_anchor_enrichment.trigger_terms');
'indikator',
'indikatortyp',
'indicator',
'reagenz',
'reagenzsatz',
'reagent',
'zubehör',
'zubehor',
'accessory',
]);
} }
/** /**
@@ -727,54 +694,21 @@ final class AgentRunnerConfig
*/ */
public function getShopQueryContextAnchorEnrichmentPatterns(): array public function getShopQueryContextAnchorEnrichmentPatterns(): array
{ {
return $this->getStringList('shop_prompt.context_anchor_enrichment.anchor_patterns', [ return $this->getRequiredStringList('shop_prompt.context_anchor_enrichment.anchor_patterns');
'/\b(?:indikator(?:typ)?|indicator(?:\s+type)?|reagenz(?:satz|typ)?|reagent(?:\s+set|\s+type)?|typ|type)\s+[A-Za-zÄÖÜäöüß]{0,8}\s*\d{1,5}(?:\s*[A-ZÄÖÜ]{1,4})?(?:\s*%)?\b/iu',
]);
} }
public function getShopQueryContextAnchorEnrichmentTemplate(): string public function getShopQueryContextAnchorEnrichmentTemplate(): string
{ {
return $this->getString('shop_prompt.context_anchor_enrichment.template', '{anchor} {query}'); return $this->getRequiredString('shop_prompt.context_anchor_enrichment.template');
} }
public function getShopQueryTranslationReplacements(string $language): array public function getShopQueryTranslationReplacements(string $language): array
{ {
$default = [ $value = $this->getOptionalStringMap('shop_prompt.language_preservation.translation_replacements.' . $language);
'de' => [
'free chlorine' => 'freies chlor',
'free chlor' => 'freies chlor',
'total chlorine' => 'gesamtchlor',
'chlorine measurement' => 'chlor messung',
'water hardness' => 'wasserhärte',
'measurement' => 'messung',
'measuring' => 'messung',
'chlorine' => 'chlor',
'indicator' => 'indikator',
'indicators' => 'indikatoren',
'reagent' => 'reagenz',
'reagents' => 'reagenzien',
'accessory' => 'zubehör',
'accessories' => 'zubehör',
],
];
$value = $this->value(
'shop_prompt.language_preservation.translation_replacements.' . $language,
$default[$language] ?? []
);
if (!is_array($value)) {
return $default[$language] ?? [];
}
$out = []; $out = [];
foreach ($value as $source => $target) { foreach ($value as $source => $target) {
if (!is_scalar($source) || !is_scalar($target)) { $source = strtolower(trim($source));
continue; $target = trim($target);
}
$source = strtolower(trim((string) $source));
$target = trim((string) $target);
if ($source !== '' && $target !== '') { if ($source !== '' && $target !== '') {
$out[$source] = $target; $out[$source] = $target;
@@ -783,7 +717,7 @@ final class AgentRunnerConfig
uksort($out, static fn(string $a, string $b): int => strlen($b) <=> strlen($a)); uksort($out, static fn(string $a, string $b): int => strlen($b) <=> strlen($a));
return $out !== [] ? $out : ($default[$language] ?? []); return $out;
} }
private function buildRulesBlock(array $rules, string $headline = 'Rules:'): string private function buildRulesBlock(array $rules, string $headline = 'Rules:'): string