$config */ public function __construct( private readonly array $config = [], private readonly ?DomainVocabularyConfig $vocabulary = null, ) { } public function getCommerceHistoryBudgetChars(): int { return $this->getRequiredInt('commerce_history_budget_chars'); } public function getProductSearchKnowledgeChunkLimit(): int { return $this->getRequiredInt('product_search_knowledge_chunk_limit'); } public function getAdvisoryProductSearchKnowledgeChunkLimit(): int { return $this->getRequiredInt('advisory_product_search_knowledge_chunk_limit'); } public function getOptimizedShopQueryPrefixPattern(): string { return $this->getRequiredString('optimized_shop_query_prefix_pattern'); } public function getOptimizedShopQueryTrimCharacters(): string { return $this->getRequiredString('optimized_shop_query_trim_characters'); } /** * @return string[] */ public function getFollowUpStrongReferencePatterns(): array { return $this->getRequiredStringList('follow_up_context.strong_reference_patterns'); } /** * @return string[] */ public function getFollowUpExplicitCommercialSignalTerms(): array { return $this->getRequiredStringList('follow_up_context.explicit_commercial_signal_terms'); } public function isCommercialTableFollowUpEnabled(): bool { return $this->getRequiredBool('follow_up_context.commercial_table_follow_up.enabled'); } /** * @return string[] */ public function getCommercialTableFollowUpPromptPatterns(): array { return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.prompt_patterns'); } /** * @return string[] */ public function getCommercialTableFollowUpHistoryAnchorPatterns(): array { return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.history_anchor_patterns'); } /** * @return string[] */ public function getCommercialTableFollowUpTableTerms(): array { return $this->getConfiguredStringListOrVocabularyView( 'follow_up_context.commercial_table_follow_up.table_terms', 'follow_up_context.commercial_table_follow_up.vocabulary_views.table_terms' ); } /** * @return string[] */ public function getCommercialTableFollowUpCommercialTerms(): array { return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.commercial_terms'); } /** * @return string[] */ public function getCommercialTableFollowUpIndicatorMarkerPatterns(): array { return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.indicator_marker_patterns'); } public function getCommercialTableFollowUpQueryTemplateWithModel(): string { return $this->getRequiredString('follow_up_context.commercial_table_follow_up.query_template_with_model'); } public function getCommercialTableFollowUpQueryTemplateWithoutModel(): string { return $this->getRequiredString('follow_up_context.commercial_table_follow_up.query_template_without_model'); } public function getFollowUpHistoryQuestionPattern(): string { return $this->getRequiredString('follow_up_context.history_question_pattern'); } public function getFollowUpHistoryTurnSplitPattern(): string { return $this->getRequiredString('follow_up_context.history_turn_split_pattern'); } public function getFollowUpHistoryQuestionStripPattern(): string { return $this->getRequiredString('follow_up_context.history_question_strip_pattern'); } public function getFollowUpReferenceAnchorProductModelPattern(): string { return $this->getRequiredString('follow_up_context.reference_anchor.product_model_pattern'); } public function getFollowUpReferenceAnchorMeasurementValuePattern(): string { return $this->getRequiredString('follow_up_context.reference_anchor.measurement_value_pattern'); } public function getFollowUpContextPreviousUserQuestionTemplate(): string { return $this->getRequiredString('follow_up_context.context_labels.previous_user_question_template'); } public function getFollowUpContextPreviousReferenceAnchorsTemplate(): string { return $this->getRequiredString('follow_up_context.context_labels.previous_reference_anchors_template'); } public function getFollowUpContextCurrentQuestionTemplate(): string { return $this->getRequiredString('follow_up_context.context_labels.current_follow_up_question_template'); } public function isInputNormalizationEnabled(): bool { return $this->getRequiredBool('input_normalization.enabled'); } public function getInputNormalizationMaxInputChars(): int { return $this->getRequiredInt('input_normalization.max_input_chars'); } public function getInputNormalizationMaxOutputChars(): int { return $this->getRequiredInt('input_normalization.max_output_chars'); } public function getInputNormalizationMaxAddedTokens(): int { return $this->getRequiredInt('input_normalization.max_added_tokens'); } public function getInputNormalizationMaxLengthRatioPercent(): int { return $this->getRequiredInt('input_normalization.max_length_ratio_percent'); } public function getInputNormalizationHeartbeatMessage(): string { return $this->getRequiredString('input_normalization.heartbeat_message'); } public function getInputNormalizationOutputPrefixPattern(): string { return $this->getRequiredString('input_normalization.output_prefix_pattern'); } /** * @return string[] */ public function getInputNormalizationPlaceholderOutputs(): array { return $this->getRequiredStringList('input_normalization.placeholder_outputs'); } /** * @return string[] */ public function getInputNormalizationSkipPatterns(): array { return $this->getRequiredStringList('input_normalization.skip_patterns'); } public function getInputNormalizationPrompt(string $prompt): string { return $this->implodePromptBlocks([ $this->getInputNormalizationIntro(), $this->buildRulesBlock($this->getInputNormalizationRules()), $this->getInputNormalizationOutputFormatBlock(), $this->getInputNormalizationCurrentUserInputLabel() . ':', trim($prompt), ]); } /** * @return string[] */ public function getInputNormalizationRules(): array { return $this->getRequiredStringList('input_normalization.prompt.rules'); } public function getInputNormalizationIntro(): string { return $this->getRequiredString('input_normalization.prompt.intro'); } public function getInputNormalizationOutputFormatBlock(): string { return $this->getRequiredString('input_normalization.prompt.output_format_block'); } public function getInputNormalizationCurrentUserInputLabel(): string { return $this->getRequiredString('input_normalization.prompt.current_user_input_label'); } public function isInputNormalizationFuzzyRoutingEnabled(): bool { return $this->getRequiredBool('input_normalization.fuzzy_routing.enabled'); } public function getInputNormalizationFuzzyRoutingMinTokenLength(): int { return $this->getRequiredInt('input_normalization.fuzzy_routing.min_token_length'); } public function getInputNormalizationFuzzyRoutingMediumTokenLength(): int { return $this->getRequiredInt('input_normalization.fuzzy_routing.medium_token_length'); } public function getInputNormalizationFuzzyRoutingLongTokenLength(): int { return $this->getRequiredInt('input_normalization.fuzzy_routing.long_token_length'); } public function getInputNormalizationFuzzyRoutingMaxDistanceShort(): int { return $this->getRequiredInt('input_normalization.fuzzy_routing.max_distance_short'); } public function getInputNormalizationFuzzyRoutingMaxDistanceMedium(): int { return $this->getRequiredInt('input_normalization.fuzzy_routing.max_distance_medium'); } public function getInputNormalizationFuzzyRoutingMaxDistanceLong(): int { return $this->getRequiredInt('input_normalization.fuzzy_routing.max_distance_long'); } public function getInputNormalizationFuzzyRoutingMinSimilarityPercent(): int { return $this->getRequiredInt('input_normalization.fuzzy_routing.min_similarity_percent'); } /** * @return string[] */ public function getInputNormalizationFuzzyRoutingTerms(): array { return $this->getConfiguredStringListOrVocabularyView( 'input_normalization.fuzzy_routing.terms', 'input_normalization.fuzzy_routing.vocabulary_views.terms' ); } private function getRequiredInt(string $key): int { $value = $this->requiredValue($key); if (is_numeric($value)) { return (int) $value; } 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 { $value = $this->requiredValue($key); if (is_string($value) && $value !== '') { return $value; } throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a non-empty string.', $key)); } private function getOptionalBool(string $key, bool $default): bool { $value = $this->optionalValue($key); if ($value === null) { return $default; } 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)); } /** * @return string[] */ private function getOptionalStringList(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 list.', $key)); } $out = []; foreach ($value as $item) { if (!is_scalar($item)) { continue; } $item = trim((string) $item); if ($item !== '') { $out[] = $item; } } return array_values(array_unique($out)); } /** * @return string[] */ private function getRequiredStringList(string $key): array { $value = $this->requiredValue($key); if (!is_array($value)) { throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a list.', $key)); } $out = []; foreach ($value as $item) { if (!is_scalar($item)) { continue; } $item = trim((string) $item); if ($item !== '') { $out[] = $item; } } if ($out === []) { throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must contain at least one non-empty value.', $key)); } return $out; } /** * @return string[] */ private function getConfiguredStringListOrVocabularyView(string $configPath, string $viewPathConfigPath): array { if ($this->optionalValue($configPath) !== null) { return $this->getRequiredStringList($configPath); } if ($this->vocabulary === null) { throw new \InvalidArgumentException(sprintf( 'RetrieX agent config path "%s" is missing and no vocabulary resolver is available.', $configPath )); } $viewPath = $this->getRequiredString($viewPathConfigPath); $terms = $this->vocabulary->view($viewPath, []); if ($terms === []) { throw new \InvalidArgumentException(sprintf( 'RetrieX agent vocabulary view "%s" resolved to an empty list.', $viewPath )); } return $terms; } /** * @return array */ private function getConfiguredStringListMapOrVocabularyMap(string $configPath, string $mapPathConfigPath): array { if ($this->optionalValue($configPath) !== null) { return $this->getRequiredStringListMap($configPath); } if ($this->vocabulary === null) { throw new \InvalidArgumentException(sprintf( 'RetrieX agent config path "%s" is missing and no vocabulary resolver is available.', $configPath )); } $mapPath = $this->getRequiredString($mapPathConfigPath); $terms = $this->vocabulary->map($mapPath, []); if ($terms === []) { throw new \InvalidArgumentException(sprintf( 'RetrieX agent vocabulary map "%s" resolved to an empty map.', $mapPath )); } return $terms; } /** * @return array */ private function getRequiredActionList(string $key): array { $value = $this->requiredValue($key); if (!is_array($value)) { throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a list of action definitions.', $key)); } $out = []; foreach ($value as $item) { if (!is_array($item)) { continue; } $label = isset($item['label']) && is_scalar($item['label']) ? trim((string) $item['label']) : ''; $prompt = isset($item['prompt']) && is_scalar($item['prompt']) ? trim((string) $item['prompt']) : ''; if ($label === '' || $prompt === '') { continue; } $out[] = [ 'label' => $label, 'prompt' => $prompt, ]; } if ($out === []) { throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must contain at least one valid action definition.', $key)); } return $out; } /** * @return array */ private function getRequiredStringListMap(string $key): array { $value = $this->requiredValue($key); if (!is_array($value)) { throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a map of string lists.', $key)); } $out = []; foreach ($value as $mapKey => $items) { if (!is_scalar($mapKey) || !is_array($items)) { continue; } $mapKey = trim((string) $mapKey); if ($mapKey === '') { continue; } $terms = []; foreach ($items as $item) { if (!is_scalar($item)) { continue; } $item = trim((string) $item); if ($item !== '' && !in_array($item, $terms, true)) { $terms[] = $item; } } if ($terms !== []) { $out[$mapKey] = $terms; } } if ($out === []) { throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must contain at least one valid entry.', $key)); } return $out; } /** * @return array */ 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; } private function requiredValue(string $key): mixed { $current = $this->config; foreach (explode('.', $key) as $segment) { if (!is_array($current) || !array_key_exists($segment, $current)) { throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" is required.', $key)); } $current = $current[$segment]; } 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 { return $this->getRequiredString('messages.empty_prompt'); } public function getAnalyzeRequestMessage(): string { return $this->getRequiredString('messages.analyze_request'); } public function getCheckInternetSourcesMessage(): string { return $this->getRequiredString('messages.check_internet_sources'); } public function getRetrieveKnowledgeMessage(): string { return $this->getRequiredString('messages.retrieve_knowledge'); } public function getOptimizeSearchMessage(): string { return $this->getRequiredString('messages.optimize_search'); } public function getNoConcreteShopQueryMessage(): string { return $this->getRequiredString('messages.no_concrete_shop_query'); } public function getFetchSearchDataMessageTemplate(): string { return $this->getRequiredString('messages.fetch_search_data_template'); } public function getAnalyzeAllInformationMessage(): string { return $this->getRequiredString('messages.analyze_all_information'); } public function getThinkingWhileStreamingMessage(): string { return $this->getRequiredString('messages.thinking_while_streaming'); } public function getNoLlmDataReceivedMessage(): string { return $this->getRequiredString('messages.no_llm_data_received'); } public function isFinalAnswerGuardEnabled(): bool { return $this->getRequiredBool('final_answer_guard.enabled'); } public function getFinalAnswerGuardMaxOutputChars(): int { return $this->getRequiredInt('final_answer_guard.max_output_chars'); } public function getFinalAnswerGuardTruncationMessage(): string { return $this->getRequiredString('final_answer_guard.truncation_message'); } public function isFinalAnswerRepeatedLineGuardEnabled(): bool { return $this->getRequiredBool('final_answer_guard.repeated_line.enabled'); } public function getFinalAnswerRepeatedLineMinOutputChars(): int { return $this->getRequiredInt('final_answer_guard.repeated_line.min_output_chars'); } public function getFinalAnswerRepeatedLineMinLineChars(): int { return $this->getRequiredInt('final_answer_guard.repeated_line.min_line_chars'); } public function getFinalAnswerRepeatedLineMaxRepetitions(): int { return $this->getRequiredInt('final_answer_guard.repeated_line.max_line_repetitions'); } public function getFinalAnswerRepeatedLineTrailingWindowLines(): int { return $this->getRequiredInt('final_answer_guard.repeated_line.trailing_window_lines'); } /** * @return string[] */ public function getFinalAnswerRepeatedLineIgnorePatterns(): array { return $this->getRequiredStringList('final_answer_guard.repeated_line.ignore_patterns'); } public function isDirectShopResultAnswerEnabled(): bool { return $this->getRequiredBool('direct_shop_result_answer.enabled'); } public function getDirectShopResultAnswerMaxResults(): int { return $this->getRequiredInt('direct_shop_result_answer.max_results'); } public function getDirectShopResultAnswerIntro(): string { return $this->getRequiredString('direct_shop_result_answer.intro'); } public function getDirectShopResultAnswerNoResultsMessage(): string { return $this->getRequiredString('direct_shop_result_answer.no_results'); } public function getDirectShopResultAnswerSortedByLengthNote(): string { 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'); } public function getRagEvidenceCleanupProfile(): string { return $this->getRequiredString('rag_evidence_guard.cleanup_profile'); } /** * @return string[] */ public function getRagEvidenceStopTerms(): array { return $this->getRequiredStringList('rag_evidence_guard.stop_terms'); } /** * @return array */ public function getRagEvidenceSynonyms(): array { return $this->getConfiguredStringListMapOrVocabularyMap( 'rag_evidence_guard.synonyms', 'rag_evidence_guard.vocabulary_maps.synonyms' ); } /** * @return string[] */ public function getRagEvidenceAggregateQueryPatterns(): array { return $this->getRequiredStringList('rag_evidence_guard.aggregate_query_patterns'); } /** * @return string[] */ public function getRagEvidenceAggregateEvidenceTerms(): array { return $this->getRequiredStringList('rag_evidence_guard.aggregate_evidence_terms'); } /** * @return string[] */ public function getRagEvidenceAggregateAnswerEvidencePatterns(): array { return $this->getRequiredStringList('rag_evidence_guard.aggregate_answer_evidence_patterns'); } public function getNoLlmFallbackShopOnlyMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.shop_only'); } public function getNoLlmFallbackShopWithKnowledgeMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.shop_with_knowledge'); } public function getNoLlmFallbackAccessoryOnlyForMainDeviceMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.accessory_only_for_main_device'); } public function getNoLlmFallbackEscalationMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.escalation'); } public function getNoLlmFallbackKnowledgeOnlyMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.knowledge_only'); } public function getNoLlmFallbackNoDataMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.no_data'); } public function getShopRepairCheckMessage(): string { return $this->getRequiredString('messages.shop_repair_check'); } public function getShopQueryOptimizationHeartbeatMessage(): string { return $this->getRequiredString('messages.shop_query_optimization_heartbeat'); } public function getProductionUiStageLabel(string $key): string { return $this->getRequiredString('production_ui.stage_labels.' . $key); } public function getProductionUiConfidenceLabel(string $key): string { return $this->getRequiredString('production_ui.confidence_labels.' . $key); } public function getProductionUiText(string $key): string { return $this->getRequiredString('production_ui.text.' . $key); } public function getProductionUiTemplate(string $key): string { return $this->getRequiredString('production_ui.templates.' . $key); } public function getProductionUiShopResultsMaxCards(): int { return $this->getRequiredInt('production_ui.shop_results.max_cards'); } /** * @return array */ public function getProductionUiFollowUpActions(string $group): array { return $this->getRequiredActionList('production_ui.follow_up_actions.' . $group); } public function getNoLlmProductField(string $key): string { return $this->getRequiredString('no_llm_fallback.product_fields.' . $key); } public function getNoLlmFallbackNoShopResultsWithKnowledgeMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.no_shop_results_with_knowledge'); } public function getNoLlmFallbackNoShopResultsNoKnowledgeMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.no_shop_results_no_knowledge'); } /** * @return string[] */ public function getNoLlmMainDeviceRequestRoleKeywords(): array { return $this->getConfiguredStringListOrVocabularyView( 'no_llm_fallback.product_roles.main_device_request_keywords', 'no_llm_fallback.product_roles.vocabulary_views.main_device_request_keywords' ); } /** * @return string[] */ public function getNoLlmAccessoryProductRoleKeywords(): array { return $this->getConfiguredStringListOrVocabularyView( 'no_llm_fallback.product_roles.accessory_product_keywords', 'no_llm_fallback.product_roles.vocabulary_views.accessory_product_keywords' ); } public function getNoLlmFallbackShopUnavailableWithKnowledgeMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.shop_unavailable_with_knowledge'); } public function getNoLlmFallbackShopUnavailableNoKnowledgeMessage(): string { return $this->getRequiredString('no_llm_fallback.messages.shop_unavailable_no_knowledge'); } public function getGenericInternalErrorMessage(): string { return $this->getRequiredString('messages.generic_internal_error'); } public function getDebugInternalErrorPrefix(): string { return $this->getRequiredString('messages.debug_internal_error_prefix'); } public function getExternalUrlSourceLabel(): string { return $this->getRequiredString('source_labels.external_url'); } public function getRagKnowledgeSourceLabel(): string { return $this->getRequiredString('source_labels.rag_knowledge'); } public function getConversationHistorySourceLabel(): string { return $this->getRequiredString('source_labels.conversation_history'); } public function getShopSystemSourceLabel(): string { return $this->getRequiredString('source_labels.shop_system'); } public function getExtendedShopSearchSourceLabel(): string { return $this->getRequiredString('source_labels.extended_shop_search'); } public function getUsedSourcesPrefix(): string { return $this->getRequiredString('source_labels.used_sources_prefix'); } public function getSourcesPrefix(): string { return $this->getRequiredString('source_labels.sources_prefix'); } public function getSourceBadgeHtmlTemplate(): string { return $this->getRequiredString('html.source_badge_template'); } public function getErrorHtmlTemplate(): string { return $this->getRequiredString('html.error_template'); } public function getThinkHtmlTemplate(): string { return $this->getRequiredString('html.think_template'); } public function getInfoHtmlTemplate(): string { return $this->getRequiredString('html.info_template'); } public function getDebugHtmlTemplate(): string { return $this->getRequiredString('html.debug_template'); } public function getShopPrompt(string $prompt, string $commerceHistoryContext = ''): string { $historyBlock = ''; if (trim($commerceHistoryContext) !== '') { $historyBlock = $this->buildHistoryBlock($commerceHistoryContext); } return $this->implodePromptBlocks([ $this->getShopPromptIntro(), $this->buildRulesBlock($this->getShopPromptRules()), $this->getShopPromptOutputFormatBlock(), $historyBlock, $this->getCurrentUserInputLabel() . ':', trim($prompt), ]); } private function buildHistoryBlock(string $commerceHistoryContext): string { return $this->implodePromptBlocks([ $this->getRecentConversationContextLabel() . ':', trim($commerceHistoryContext), $this->buildRulesBlock($this->getConversationContextRules(), 'Additional rules for conversation context:'), ]); } /** * @return string[] */ public function getShopPromptRules(): array { return $this->getRequiredStringList('shop_prompt.rules'); } /** * @return string[] */ public function getConversationContextRules(): array { return $this->getRequiredStringList('shop_prompt.conversation_context_rules'); } /** * @return string[] */ public function getShopQueryContextUsageReferentialTerms(): array { return $this->getRequiredStringList('shop_prompt.context_usage.referential_terms'); } public function isShopQueryCurrentInputPreservationEnabled(): bool { return $this->getOptionalBool('shop_prompt.current_input_preservation.enabled', true); } /** * @return string[] */ public function getShopQueryCurrentInputPreservationTerms(): array { return $this->getConfiguredStringListOrVocabularyView( 'shop_prompt.current_input_preservation.terms', 'shop_prompt.current_input_preservation.vocabulary_views.terms' ); } public function isShopQueryProductAttributeCleanupEnabled(): bool { return $this->getRequiredBool('shop_prompt.product_attribute_query_cleanup.enabled'); } public function getShopQueryProductAttributeCleanupMinTokens(): int { return $this->getRequiredInt('shop_prompt.product_attribute_query_cleanup.min_query_tokens_after_cleanup'); } /** * @return string[] */ public function getShopQueryProductAttributeCleanupProductTypeTerms(): array { return $this->getConfiguredStringListOrVocabularyView( 'shop_prompt.product_attribute_query_cleanup.product_type_terms', 'shop_prompt.product_attribute_query_cleanup.vocabulary_views.product_type_terms' ); } /** * @return string[] */ public function getShopQueryProductAttributeCleanupStopTerms(): array { return $this->getConfiguredStringListOrVocabularyView( 'shop_prompt.product_attribute_query_cleanup.stop_terms', 'shop_prompt.product_attribute_query_cleanup.vocabulary_views.stop_terms' ); } /** * @return string[] */ public function getShopQueryProductAttributeCleanupComparativeConstraintPatterns(): array { return $this->getRequiredStringList('shop_prompt.product_attribute_query_cleanup.comparative_constraint_patterns'); } public function isShopQueryStopwordCleanupEnabled(): bool { return $this->getRequiredBool('shop_prompt.query_stopword_cleanup.enabled'); } public function getShopQueryStopwordCleanupMinTokens(): int { return $this->getRequiredInt('shop_prompt.query_stopword_cleanup.min_query_tokens_after_cleanup'); } /** * @return string[] */ public function getShopQueryStopwordCleanupTerms(): array { return $this->getRequiredStringList('shop_prompt.query_stopword_cleanup.terms'); } public function isDirectShopResultGuardEnabled(): bool { return $this->getRequiredBool('shop_prompt.direct_result_guard.enabled'); } public function isShopResultLengthSortEnabled(): bool { return $this->getRequiredBool('shop_prompt.length_sort.enabled'); } /** * @return string[] */ public function getShopResultLengthSortTriggerPatterns(): array { return $this->getRequiredStringList('shop_prompt.length_sort.trigger_patterns'); } /** * @return string[] */ public function getShopResultLengthSortValuePatterns(): array { 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'); } public function getShopPromptOutputFormatBlock(): string { return $this->getRequiredString('shop_prompt.output_format_block'); } public function getRecentConversationContextLabel(): string { return $this->getRequiredString('shop_prompt.recent_conversation_context_label'); } public function getCurrentUserInputLabel(): string { return $this->getRequiredString('shop_prompt.current_user_input_label'); } public function isShopQueryLanguagePreservationEnabled(): bool { return $this->getRequiredBool('shop_prompt.language_preservation.enabled'); } /** * @return array */ public function getShopQueryLanguageMarkers(): array { $value = $this->requiredValue('shop_prompt.language_preservation.language_markers'); if (!is_array($value)) { throw new \InvalidArgumentException('RetrieX agent config key "shop_prompt.language_preservation.language_markers" must be a map of marker lists.'); } $out = []; foreach ($value as $language => $markers) { if (!is_string($language) || !is_array($markers)) { continue; } $cleanMarkers = []; foreach ($markers as $marker) { if (!is_scalar($marker)) { continue; } $marker = strtolower((string) $marker); if ($marker !== '') { $cleanMarkers[] = $marker; } } if ($cleanMarkers !== []) { $out[$language] = array_values(array_unique($cleanMarkers)); } } 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; } public function isShopQueryMetaGuardEnabled(): bool { return $this->getRequiredBool('shop_prompt.meta_query_guard.enabled'); } public function getShopQueryContextFallbackCleanupProfile(): string { $value = $this->optionalValue('shop_prompt.meta_query_guard.cleanup_profile'); if (is_string($value) && trim($value) !== '') { return trim($value); } return 'shop_context_fallback'; } /** * @return string[] */ public function getShopQueryMetaOnlyTerms(): array { return $this->getRequiredStringList('shop_prompt.meta_query_guard.meta_only_terms'); } public function isShopQueryContextFallbackEnabled(): bool { return $this->getRequiredBool('shop_prompt.meta_query_guard.context_fallback_enabled'); } public function getShopQueryContextFallbackQuestionLimit(): int { return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_question_limit'); } public function getShopQueryContextFallbackHistoryBudgetChars(): int { return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_history_budget_chars'); } public function shouldUseFullHistoryForShopQueryContextFallback(): bool { return $this->getRequiredBool('shop_prompt.meta_query_guard.context_fallback_use_full_history'); } public function getShopQueryContextFallbackMaxTerms(): int { return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_max_terms'); } /** * @return string[] */ public function getShopQueryContextFallbackFilterTerms(): array { return $this->getRequiredStringList('shop_prompt.meta_query_guard.context_fallback_filter_terms'); } public function isShopQueryContextAnchorEnrichmentEnabled(): bool { return $this->getRequiredBool('shop_prompt.context_anchor_enrichment.enabled'); } public function getShopQueryContextAnchorEnrichmentMaxQueryTerms(): int { return $this->getRequiredInt('shop_prompt.context_anchor_enrichment.max_query_terms'); } /** * @return string[] */ public function getShopQueryContextAnchorEnrichmentTriggerTerms(): array { return $this->getConfiguredStringListOrVocabularyView( 'shop_prompt.context_anchor_enrichment.trigger_terms', 'shop_prompt.context_anchor_enrichment.vocabulary_views.trigger_terms' ); } /** * @return string[] */ public function getShopQueryContextAnchorEnrichmentPatterns(): array { return $this->getRequiredStringList('shop_prompt.context_anchor_enrichment.anchor_patterns'); } public function getShopQueryContextAnchorEnrichmentTemplate(): string { return $this->getRequiredString('shop_prompt.context_anchor_enrichment.template'); } public function isShopQueryRagAnchorEnrichmentEnabled(): bool { return $this->getRequiredBool('shop_prompt.rag_anchor_enrichment.enabled'); } public function getShopQueryRagAnchorEnrichmentMinScore(): int { return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.min_score'); } public function getShopQueryRagAnchorEnrichmentMaxQueryTerms(): int { return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.max_query_terms'); } public function getShopQueryRagAnchorEnrichmentEarlyChunkBonusMax(): int { return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.early_chunk_bonus_max'); } public function getShopQueryRagAnchorEnrichmentExactValueUnitScore(): int { return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.scores.exact_value_with_unit'); } public function getShopQueryRagAnchorEnrichmentExactValueScore(): int { return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.scores.exact_value_only'); } public function getShopQueryRagAnchorEnrichmentAnchorBonusScore(): int { return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.scores.anchor_bonus'); } public function getShopQueryRagAnchorEnrichmentTemplate(): string { return $this->getRequiredString('shop_prompt.rag_anchor_enrichment.template'); } /** * @return string[] */ public function getShopQueryRagAnchorEnrichmentNumericFocusPatterns(): array { return $this->getRequiredStringList('shop_prompt.rag_anchor_enrichment.numeric_focus_patterns'); } /** * @return string[] */ public function getShopQueryRagAnchorEnrichmentProductTitlePatterns(): array { return $this->getRequiredStringList('shop_prompt.rag_anchor_enrichment.product_title_patterns'); } /** * @return string[] */ public function getShopQueryRagAnchorEnrichmentAnchorBonusPatterns(): array { return $this->getRequiredStringList('shop_prompt.rag_anchor_enrichment.anchor_bonus_patterns'); } /** * @return string[] */ public function getShopQueryRagAnchorEnrichmentSubjectTerms(): array { return $this->getRequiredStringList('shop_prompt.rag_anchor_enrichment.subject_terms'); } public function getShopQueryTranslationReplacements(string $language): array { $value = $this->getOptionalStringMap('shop_prompt.language_preservation.translation_replacements.' . $language); $out = []; foreach ($value as $source => $target) { $source = strtolower(trim($source)); $target = trim($target); if ($source !== '' && $target !== '') { $out[$source] = $target; } } uksort($out, static fn(string $a, string $b): int => strlen($b) <=> strlen($a)); return $out; } private function buildRulesBlock(array $rules, string $headline = 'Rules:'): string { return $headline . "\n" . implode("\n", $rules); } /** * @param string[] $blocks */ private function implodePromptBlocks(array $blocks): string { $normalized = array_values(array_filter( array_map( static fn(string $block): string => trim($block), $blocks ), static fn(string $block): bool => $block !== '' )); return implode("\n\n", $normalized); } }