1982 lines
66 KiB
PHP
1982 lines
66 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Config;
|
|
|
|
final class AgentRunnerConfig
|
|
{
|
|
/**
|
|
* @param array<string, mixed> $config
|
|
*/
|
|
public function __construct(
|
|
private readonly array $config = [],
|
|
private readonly ?DomainVocabularyConfig $vocabulary = null,
|
|
private readonly ?GenreConfig $genreConfig = null,
|
|
private readonly ?ChatMessagesConfig $chatMessages = 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->genreStringList('context_resolution.commercial_table_follow_up.history_anchor_patterns')
|
|
?: $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->genreStringList('context_resolution.commercial_table_follow_up.indicator_marker_patterns')
|
|
?: $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.indicator_marker_patterns');
|
|
}
|
|
|
|
public function getCommercialTableFollowUpQueryTemplateWithModel(): string
|
|
{
|
|
return $this->genreString('context_resolution.commercial_table_follow_up.query_template_with_model')
|
|
?: $this->getRequiredString('follow_up_context.commercial_table_follow_up.query_template_with_model');
|
|
}
|
|
|
|
public function getCommercialTableFollowUpQueryTemplateWithoutModel(): string
|
|
{
|
|
return $this->genreString('context_resolution.commercial_table_follow_up.query_template_without_model')
|
|
?: $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->getChatRequiredString('agent.input_normalization.heartbeat_message', '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'
|
|
);
|
|
}
|
|
|
|
/** @return string[] */
|
|
private function genreStringList(string $path): array
|
|
{
|
|
return $this->genreConfig?->getValueStringList($path) ?? [];
|
|
}
|
|
|
|
private function genreString(string $path): string
|
|
{
|
|
return $this->genreConfig?->getValueString($path) ?? '';
|
|
}
|
|
|
|
private function genreBool(string $path): ?bool
|
|
{
|
|
return $this->genreConfig?->getValueBool($path);
|
|
}
|
|
|
|
private function genreInt(string $path): ?int
|
|
{
|
|
return $this->genreConfig?->getValueInt($path);
|
|
}
|
|
|
|
/** @return array<int|string, mixed> */
|
|
private function genreArray(string $path): array
|
|
{
|
|
return $this->genreConfig?->getValueArray($path) ?? [];
|
|
}
|
|
|
|
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 getOptionalInt(string $key, int $default): int
|
|
{
|
|
$value = $this->optionalValue($key);
|
|
|
|
if ($value === null) {
|
|
return $default;
|
|
}
|
|
|
|
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 getChatRequiredString(string $chatKey, string $legacyKey): string
|
|
{
|
|
if ($this->chatMessages !== null) {
|
|
return $this->chatMessages->getString($chatKey);
|
|
}
|
|
|
|
return $this->getRequiredString($legacyKey);
|
|
}
|
|
|
|
private function getChatOnlyRequiredString(string $chatKey): string
|
|
{
|
|
if ($this->chatMessages === null) {
|
|
throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" is required.', $chatKey));
|
|
}
|
|
|
|
return $this->chatMessages->getString($chatKey);
|
|
}
|
|
|
|
private function getChatStringAllowEmpty(string $chatKey, string $legacyKey): string
|
|
{
|
|
if ($this->chatMessages !== null) {
|
|
return $this->chatMessages->getStringAllowEmpty($chatKey);
|
|
}
|
|
|
|
$value = $this->requiredValue($legacyKey);
|
|
if (is_string($value)) {
|
|
return $value;
|
|
}
|
|
|
|
throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a string.', $legacyKey));
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function getChatActionList(string $chatKey, string $legacyKey): array
|
|
{
|
|
if ($this->chatMessages !== null) {
|
|
return $this->chatMessages->getActionList($chatKey);
|
|
}
|
|
|
|
return $this->getRequiredActionList($legacyKey);
|
|
}
|
|
|
|
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<string, string[]>
|
|
*/
|
|
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<int, array<string, mixed>>
|
|
*/
|
|
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;
|
|
}
|
|
|
|
$action = [
|
|
'label' => $label,
|
|
'prompt' => $prompt,
|
|
];
|
|
|
|
foreach (['action_type', 'target_role', 'hide_when_answer_detail_score_at_least'] as $optionalKey) {
|
|
if (isset($item[$optionalKey]) && is_scalar($item[$optionalKey])) {
|
|
$optionalValue = trim((string) $item[$optionalKey]);
|
|
if ($optionalValue !== '') {
|
|
$action[$optionalKey] = $optionalValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (array_key_exists('requires_answer_anchor', $item) && (is_bool($item['requires_answer_anchor']) || is_scalar($item['requires_answer_anchor']))) {
|
|
$action['requires_answer_anchor'] = $item['requires_answer_anchor'];
|
|
}
|
|
|
|
if (isset($item['hide_when_answer_matches_any']) && is_array($item['hide_when_answer_matches_any'])) {
|
|
$patterns = [];
|
|
foreach ($item['hide_when_answer_matches_any'] as $pattern) {
|
|
if (!is_scalar($pattern)) {
|
|
continue;
|
|
}
|
|
|
|
$pattern = trim((string) $pattern);
|
|
if ($pattern !== '') {
|
|
$patterns[] = $pattern;
|
|
}
|
|
}
|
|
|
|
if ($patterns !== []) {
|
|
$action['hide_when_answer_matches_any'] = array_values(array_unique($patterns));
|
|
}
|
|
}
|
|
|
|
$out[] = $action;
|
|
}
|
|
|
|
if ($out === []) {
|
|
throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must contain at least one valid action definition.', $key));
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string[]>
|
|
*/
|
|
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<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;
|
|
}
|
|
|
|
|
|
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->getChatRequiredString('agent.messages.empty_prompt', 'messages.empty_prompt');
|
|
}
|
|
|
|
public function getAnalyzeRequestMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.analyze_request', 'messages.analyze_request');
|
|
}
|
|
|
|
public function getCheckInternetSourcesMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.check_internet_sources', 'messages.check_internet_sources');
|
|
}
|
|
|
|
public function getRetrieveKnowledgeMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.retrieve_knowledge', 'messages.retrieve_knowledge');
|
|
}
|
|
|
|
public function getOptimizeSearchMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.optimize_search', 'messages.optimize_search');
|
|
}
|
|
|
|
public function getNoConcreteShopQueryMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.no_concrete_shop_query', 'messages.no_concrete_shop_query');
|
|
}
|
|
|
|
public function getFetchSearchDataMessageTemplate(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.fetch_search_data_template', 'messages.fetch_search_data_template');
|
|
}
|
|
|
|
public function getAnalyzeAllInformationMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.analyze_all_information', 'messages.analyze_all_information');
|
|
}
|
|
|
|
public function getThinkingWhileStreamingMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.thinking_while_streaming', 'messages.thinking_while_streaming');
|
|
}
|
|
|
|
public function getNoLlmDataReceivedMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.no_llm_data_received', '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->getChatRequiredString('agent.final_answer_guard.truncation_message', '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->genreBool('shop_query_runtime.direct_answer.enabled')
|
|
?? $this->getRequiredBool('shop_runtime.direct_answer.enabled');
|
|
}
|
|
|
|
public function getDirectShopResultAnswerMaxResults(): int
|
|
{
|
|
return $this->genreInt('shop_query_runtime.direct_answer.max_results')
|
|
?? $this->getRequiredInt('shop_runtime.direct_answer.max_results');
|
|
}
|
|
|
|
public function getDirectShopResultAnswerIntro(): string
|
|
{
|
|
return $this->genreString('shop_query_runtime.direct_answer.intro')
|
|
?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.intro', 'shop_runtime.direct_answer.intro');
|
|
}
|
|
|
|
public function getDirectShopResultAnswerNoResultsMessage(): string
|
|
{
|
|
return $this->genreString('shop_query_runtime.direct_answer.no_results')
|
|
?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.no_results', 'shop_runtime.direct_answer.no_results');
|
|
}
|
|
|
|
public function getDirectShopResultAnswerSortedByLengthNote(): string
|
|
{
|
|
return $this->genreString('shop_query_runtime.direct_answer.sorted_by_length_note')
|
|
?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.sorted_by_length_note', 'shop_runtime.direct_answer.sorted_by_length_note');
|
|
}
|
|
|
|
public function getDirectShopResultAnswerMinLengthFilterNote(): string
|
|
{
|
|
return $this->genreString('shop_query_runtime.direct_answer.min_length_filter_note')
|
|
?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.min_length_filter_note', 'shop_runtime.direct_answer.min_length_filter_note');
|
|
}
|
|
|
|
public function getDirectShopResultAnswerMaxLengthFilterNote(): string
|
|
{
|
|
return $this->genreString('shop_query_runtime.direct_answer.max_length_filter_note')
|
|
?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.max_length_filter_note', 'shop_runtime.direct_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<string, string[]>
|
|
*/
|
|
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->getChatRequiredString('agent.no_llm_fallback.messages.shop_only', 'no_llm_fallback.messages.shop_only');
|
|
}
|
|
|
|
public function getNoLlmFallbackShopWithKnowledgeMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.messages.shop_with_knowledge', 'no_llm_fallback.messages.shop_with_knowledge');
|
|
}
|
|
|
|
public function getNoLlmFallbackAccessoryOnlyForMainDeviceMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.messages.accessory_only_for_main_device', 'no_llm_fallback.messages.accessory_only_for_main_device');
|
|
}
|
|
|
|
public function getNoLlmFallbackEscalationMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.messages.escalation', 'no_llm_fallback.messages.escalation');
|
|
}
|
|
|
|
public function getNoLlmFallbackKnowledgeOnlyMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.messages.knowledge_only', 'no_llm_fallback.messages.knowledge_only');
|
|
}
|
|
|
|
public function getNoLlmFallbackNoDataMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.messages.no_data', 'no_llm_fallback.messages.no_data');
|
|
}
|
|
|
|
|
|
public function getShopRepairCheckMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.shop_repair_check', 'messages.shop_repair_check');
|
|
}
|
|
|
|
public function getShopQueryOptimizationHeartbeatMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.shop_query_optimization_heartbeat', 'messages.shop_query_optimization_heartbeat');
|
|
}
|
|
|
|
public function getProductionUiStageLabel(string $key): string
|
|
{
|
|
return $this->getChatRequiredString('agent.production_ui.stage_labels.' . $key, 'production_ui.stage_labels.' . $key);
|
|
}
|
|
|
|
public function getProductionUiConfidenceLabel(string $key): string
|
|
{
|
|
return $this->getChatRequiredString('agent.production_ui.confidence_labels.' . $key, 'production_ui.confidence_labels.' . $key);
|
|
}
|
|
|
|
public function getProductionUiText(string $key): string
|
|
{
|
|
return $this->getChatRequiredString('agent.production_ui.text.' . $key, 'production_ui.text.' . $key);
|
|
}
|
|
|
|
public function getProductionUiTemplate(string $key): string
|
|
{
|
|
return $this->getChatRequiredString('agent.production_ui.templates.' . $key, 'production_ui.templates.' . $key);
|
|
}
|
|
|
|
public function getProductionUiShopResultsMaxCards(): int
|
|
{
|
|
return $this->getRequiredInt('production_ui.shop_results.max_cards');
|
|
}
|
|
|
|
public function isProductionUiFollowUpActionsEnabled(): bool
|
|
{
|
|
if ($this->chatMessages !== null) {
|
|
return $this->chatMessages->getBool('agent.production_ui.follow_up_actions.enabled');
|
|
}
|
|
|
|
return $this->getOptionalBool('production_ui.follow_up_actions.enabled', false);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function getProductionUiFollowUpActions(string $group): array
|
|
{
|
|
$legacyGroup = $group === 'shop_results' ? 'commerce' : $group;
|
|
|
|
return $this->getChatActionList('agent.production_ui.follow_up_actions.' . $group, 'production_ui.follow_up_actions.' . $legacyGroup);
|
|
}
|
|
|
|
public function getNoLlmProductField(string $key): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.product_fields.' . $key, 'no_llm_fallback.product_fields.' . $key);
|
|
}
|
|
|
|
public function getNoLlmFallbackNoShopResultsWithKnowledgeMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.messages.no_shop_results_with_knowledge', 'no_llm_fallback.messages.no_shop_results_with_knowledge');
|
|
}
|
|
|
|
public function getNoLlmFallbackNoShopResultsNoKnowledgeMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.messages.no_shop_results_no_knowledge', 'no_llm_fallback.messages.no_shop_results_no_knowledge');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getNoLlmMainDeviceRequestRoleKeywords(): array
|
|
{
|
|
$terms = $this->genreStringList('product_roles.no_llm_fallback_terms.main_device_request_keywords');
|
|
if ($terms !== []) {
|
|
return $terms;
|
|
}
|
|
|
|
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
|
|
{
|
|
$terms = $this->genreStringList('product_roles.no_llm_fallback_terms.accessory_product_keywords');
|
|
if ($terms !== []) {
|
|
return $terms;
|
|
}
|
|
|
|
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->getChatRequiredString('agent.no_llm_fallback.messages.shop_unavailable_with_knowledge', 'no_llm_fallback.messages.shop_unavailable_with_knowledge');
|
|
}
|
|
|
|
public function getNoLlmFallbackShopUnavailableNoKnowledgeMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.no_llm_fallback.messages.shop_unavailable_no_knowledge', 'no_llm_fallback.messages.shop_unavailable_no_knowledge');
|
|
}
|
|
|
|
public function getGenericInternalErrorMessage(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.generic_internal_error', 'messages.generic_internal_error');
|
|
}
|
|
|
|
public function getDebugInternalErrorPrefix(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.messages.debug_internal_error_prefix', 'messages.debug_internal_error_prefix');
|
|
}
|
|
|
|
public function getTechnicalErrorDetailTemplate(): string
|
|
{
|
|
return $this->getChatOnlyRequiredString('agent.messages.technical_error_detail_template');
|
|
}
|
|
|
|
public function getHistoryResponseSystemNoticeTemplate(): string
|
|
{
|
|
return $this->getChatOnlyRequiredString('agent.production_ui.templates.history_response_system_notice');
|
|
}
|
|
|
|
public function getExternalUrlSourceLabel(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.source_labels.external_url', 'source_labels.external_url');
|
|
}
|
|
|
|
public function getRagKnowledgeSourceLabel(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.source_labels.rag_knowledge', 'source_labels.rag_knowledge');
|
|
}
|
|
|
|
public function getConversationHistorySourceLabel(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.source_labels.conversation_history', 'source_labels.conversation_history');
|
|
}
|
|
|
|
public function getShopSystemSourceLabel(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.source_labels.shop_system', 'source_labels.shop_system');
|
|
}
|
|
|
|
public function getExtendedShopSearchSourceLabel(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.source_labels.extended_shop_search', 'source_labels.extended_shop_search');
|
|
}
|
|
|
|
public function getUsedSourcesPrefix(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.source_labels.used_sources_prefix', 'source_labels.used_sources_prefix');
|
|
}
|
|
|
|
public function getSourcesPrefix(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.source_labels.sources_prefix', 'source_labels.sources_prefix');
|
|
}
|
|
|
|
public function getSourceBadgeHtmlTemplate(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.html.source_badge_template', 'html.source_badge_template');
|
|
}
|
|
|
|
public function getErrorHtmlTemplate(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.html.error_template', 'html.error_template');
|
|
}
|
|
|
|
public function getThinkHtmlTemplate(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.html.think_template', 'html.think_template');
|
|
}
|
|
|
|
public function getInfoHtmlTemplate(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.html.info_template', 'html.info_template');
|
|
}
|
|
|
|
public function getDebugHtmlTemplate(): string
|
|
{
|
|
return $this->getChatRequiredString('agent.html.debug_template', '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->genreStringList('context_resolution.referential_terms.terms')
|
|
?: $this->getRequiredStringList('shop_runtime.context_resolution.context_usage.referential_terms');
|
|
}
|
|
|
|
public function isShopQueryCurrentInputPreservationEnabled(): bool
|
|
{
|
|
return $this->getOptionalBool('shop_runtime.query_cleanup.current_input_preservation.enabled', true);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryCurrentInputPreservationTerms(): array
|
|
{
|
|
return $this->getConfiguredStringListOrVocabularyView(
|
|
'shop_runtime.query_cleanup.current_input_preservation.terms',
|
|
'shop_runtime.query_cleanup.current_input_preservation.vocabulary_views.terms'
|
|
);
|
|
}
|
|
|
|
public function isShopQueryProductAttributeCleanupEnabled(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.attribute_cleanup.enabled');
|
|
}
|
|
|
|
public function getShopQueryProductAttributeCleanupMinTokens(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.attribute_cleanup.min_query_tokens_after_cleanup');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryProductAttributeCleanupProductTypeTerms(): array
|
|
{
|
|
return $this->getConfiguredStringListOrVocabularyView(
|
|
'shop_runtime.attribute_cleanup.product_type_terms',
|
|
'shop_runtime.attribute_cleanup.vocabulary_views.product_type_terms'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getDirectShopResultProductIdentityTerms(): array
|
|
{
|
|
return array_values(array_unique(array_merge(
|
|
$this->getShopQueryProductAttributeCleanupProductTypeTerms(),
|
|
$this->genreStringList('product_roles.accessory_product_terms.terms'),
|
|
$this->genreStringList('product_roles.requested_accessory_code_terms.terms')
|
|
)));
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryProductAttributeCleanupStopTerms(): array
|
|
{
|
|
return $this->getConfiguredStringListOrVocabularyView(
|
|
'shop_runtime.attribute_cleanup.stop_terms',
|
|
'shop_runtime.attribute_cleanup.vocabulary_views.stop_terms'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryProductAttributeCleanupComparativeConstraintPatterns(): array
|
|
{
|
|
return $this->genreStringList('product_attributes.direct_attribute_cleanup.comparative_constraint_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.attribute_cleanup.comparative_constraint_patterns');
|
|
}
|
|
|
|
public function isShopQueryStopwordCleanupEnabled(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.query_cleanup.stopword_cleanup.enabled');
|
|
}
|
|
|
|
public function getShopQueryStopwordCleanupMinTokens(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.query_cleanup.stopword_cleanup.min_query_tokens_after_cleanup');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryStopwordCleanupTerms(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.stopword_cleanup.terms')
|
|
?: $this->getRequiredStringList('shop_runtime.query_cleanup.stopword_cleanup.terms');
|
|
}
|
|
|
|
public function isShopQueryPositiveTokenFilterEnabled(): bool
|
|
{
|
|
return $this->genreBool('shop_query_runtime.positive_token_filter.enabled')
|
|
?? $this->getOptionalBool('shop_runtime.query_cleanup.positive_token_filter.enabled', false);
|
|
}
|
|
|
|
public function getShopQueryPositiveTokenFilterMinTokens(): int
|
|
{
|
|
return $this->genreInt('shop_query_runtime.positive_token_filter.min_query_tokens_after_filter')
|
|
?? $this->getOptionalInt('shop_runtime.query_cleanup.positive_token_filter.min_query_tokens_after_filter', 1);
|
|
}
|
|
|
|
public function shouldShopQueryPositiveTokenFilterIncludeCurrentInputPreservationTerms(): bool
|
|
{
|
|
return $this->genreBool('shop_query_runtime.positive_token_filter.include_current_input_preservation_terms')
|
|
?? $this->getOptionalBool('shop_runtime.query_cleanup.positive_token_filter.include_current_input_preservation_terms', true);
|
|
}
|
|
|
|
public function shouldShopQueryPositiveTokenFilterIncludeSemanticShopSearchTokens(): bool
|
|
{
|
|
return $this->genreBool('shop_query_runtime.positive_token_filter.include_semantic_shop_search_tokens')
|
|
?? $this->getOptionalBool('shop_runtime.query_cleanup.positive_token_filter.include_semantic_shop_search_tokens', true);
|
|
}
|
|
|
|
public function shouldShopQueryPositiveTokenFilterIncludeProductRoleTerms(): bool
|
|
{
|
|
return $this->genreBool('shop_query_runtime.positive_token_filter.include_product_role_terms')
|
|
?? $this->getOptionalBool('shop_runtime.query_cleanup.positive_token_filter.include_product_role_terms', true);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryPositiveTokenFilterAllowedTerms(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.positive_token_filter.allowed_terms')
|
|
?: $this->getOptionalStringList('shop_runtime.query_cleanup.positive_token_filter.allowed_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryPositiveTokenFilterBlockedTerms(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.positive_token_filter.blocked_terms')
|
|
?: $this->getOptionalStringList('shop_runtime.query_cleanup.positive_token_filter.blocked_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryPositiveTokenFilterCodePatterns(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.positive_token_filter.code_patterns')
|
|
?: $this->getOptionalStringList('shop_runtime.query_cleanup.positive_token_filter.code_patterns');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryPositiveTokenFilterAdjacentVariantPatterns(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.positive_token_filter.adjacent_variant_patterns')
|
|
?: $this->getOptionalStringList('shop_runtime.query_cleanup.positive_token_filter.adjacent_variant_patterns');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryPositiveTokenFilterAdjacentVariantTerms(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.positive_token_filter.adjacent_variant_terms')
|
|
?: $this->getOptionalStringList('shop_runtime.query_cleanup.positive_token_filter.adjacent_variant_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryPositiveTokenFilterSemanticShopSearchTokens(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.semantic_shop_search_tokens.terms');
|
|
}
|
|
|
|
public function isGenericDeviceQueryAnchorEnabled(): bool
|
|
{
|
|
return $this->genreBool('shop_query_runtime.generic_device_anchor.enabled') ?? false;
|
|
}
|
|
|
|
public function shouldGenericDeviceQueryAnchorRemoveGenericDeviceTerms(): bool
|
|
{
|
|
return $this->genreBool('shop_query_runtime.generic_device_anchor.remove_generic_device_terms') ?? false;
|
|
}
|
|
|
|
public function getGenericDeviceQueryAnchorTemplate(): string
|
|
{
|
|
return $this->genreString('shop_query_runtime.generic_device_anchor.template');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getGenericDeviceQueryAnchorTriggerTerms(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.generic_device_anchor.trigger_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getGenericDeviceQueryAnchorSuppressTerms(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.generic_device_anchor.suppress_if_terms');
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{anchor: string, match_terms: string[], template?: string}>
|
|
*/
|
|
public function getGenericDeviceQueryAnchorRules(): array
|
|
{
|
|
$rules = [];
|
|
|
|
foreach ($this->genreArray('shop_query_runtime.generic_device_anchor.anchor_rules') as $rule) {
|
|
if (!is_array($rule)) {
|
|
continue;
|
|
}
|
|
|
|
$anchor = $rule['anchor'] ?? '';
|
|
if (!is_scalar($anchor)) {
|
|
continue;
|
|
}
|
|
|
|
$anchor = trim((string) $anchor);
|
|
if ($anchor === '') {
|
|
continue;
|
|
}
|
|
|
|
$rawMatchTerms = $rule['match_terms'] ?? [];
|
|
if (!is_array($rawMatchTerms)) {
|
|
continue;
|
|
}
|
|
|
|
$matchTerms = [];
|
|
foreach ($rawMatchTerms as $term) {
|
|
if (!is_scalar($term)) {
|
|
continue;
|
|
}
|
|
|
|
$term = trim((string) $term);
|
|
if ($term !== '' && !in_array($term, $matchTerms, true)) {
|
|
$matchTerms[] = $term;
|
|
}
|
|
}
|
|
|
|
if ($matchTerms === []) {
|
|
continue;
|
|
}
|
|
|
|
$normalizedRule = [
|
|
'anchor' => $anchor,
|
|
'match_terms' => $matchTerms,
|
|
];
|
|
|
|
$template = $rule['template'] ?? null;
|
|
if (is_scalar($template)) {
|
|
$template = trim((string) $template);
|
|
if ($template !== '') {
|
|
$normalizedRule['template'] = $template;
|
|
}
|
|
}
|
|
|
|
$rules[] = $normalizedRule;
|
|
}
|
|
|
|
return $rules;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getGenericDeviceQueryAnchorPositiveFilterTerms(): array
|
|
{
|
|
$terms = [];
|
|
|
|
foreach ($this->getGenericDeviceQueryAnchorRules() as $rule) {
|
|
$terms[] = $rule['anchor'];
|
|
$terms = array_merge($terms, $rule['match_terms']);
|
|
}
|
|
|
|
return array_values(array_unique(array_filter(
|
|
array_map(static fn(string $term): string => trim($term), $terms),
|
|
static fn(string $term): bool => $term !== ''
|
|
)));
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryPositiveTokenFilterProductRoleTerms(): array
|
|
{
|
|
return array_values(array_unique(array_merge(
|
|
$this->genreStringList('product_roles.primary_product_terms.terms'),
|
|
$this->genreStringList('product_roles.accessory_product_terms.terms'),
|
|
$this->genreStringList('product_roles.shop_views.device_query_terms'),
|
|
$this->genreStringList('product_roles.shop_views.accessory_query_terms'),
|
|
$this->genreStringList('product_roles.shop_views.device_product_terms'),
|
|
$this->genreStringList('product_roles.shop_views.accessory_product_terms'),
|
|
$this->genreStringList('product_roles.shop_views.device_focus_terms'),
|
|
$this->genreStringList('product_roles.shop_views.accessory_focus_terms'),
|
|
$this->genreStringList('product_roles.no_llm_fallback_terms.main_device_request_keywords'),
|
|
$this->genreStringList('product_roles.no_llm_fallback_terms.accessory_product_keywords')
|
|
)));
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getRequestedAccessoryCodeTerms(): array
|
|
{
|
|
return $this->genreStringList('search_repair.requested_accessory_code_terms.terms')
|
|
?: $this->genreStringList('product_roles.requested_accessory_code_terms.terms');
|
|
}
|
|
|
|
public function isDirectShopResultGuardEnabled(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.result_identity.enabled');
|
|
}
|
|
|
|
public function shouldPreferDirectShopResultGuardPrimaryIdentityMatches(): bool
|
|
{
|
|
return $this->getOptionalBool('shop_runtime.result_identity.prefer_primary_identity_matches', true);
|
|
}
|
|
|
|
public function isDirectShopResultGuardCompoundPrefixMatchEnabled(): bool
|
|
{
|
|
return $this->getOptionalBool('shop_runtime.result_identity.compound_prefix_match.enabled', false);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getDirectShopResultGuardCompoundPrefixTerms(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.compound_prefix_match.terms')
|
|
?: $this->getOptionalStringList('shop_runtime.result_identity.compound_prefix_match.terms');
|
|
}
|
|
|
|
public function isDirectShopResultGuardPrimaryIdentityRepairEnabled(): bool
|
|
{
|
|
return $this->getOptionalBool('shop_runtime.result_identity.primary_identity_repair.enabled', true);
|
|
}
|
|
|
|
public function getDirectShopResultGuardPrimaryIdentityRepairMinQueryTokens(): int
|
|
{
|
|
return $this->getOptionalInt('shop_runtime.result_identity.primary_identity_repair.min_query_tokens_after_cleanup', 2);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getDirectShopResultGuardPrimaryIdentityRepairStopTerms(): array
|
|
{
|
|
return $this->genreStringList('shop_query_runtime.primary_identity_repair.stop_terms')
|
|
?: $this->getOptionalStringList('shop_runtime.result_identity.primary_identity_repair.stop_terms');
|
|
}
|
|
|
|
public function isShopResultLengthSortEnabled(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.answer_constraints.length_sort.enabled');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopResultLengthSortTriggerPatterns(): array
|
|
{
|
|
return $this->genreStringList('product_attributes.numeric_length_constraints.length_sort.trigger_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.answer_constraints.length_sort.trigger_patterns');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopResultLengthSortValuePatterns(): array
|
|
{
|
|
return $this->genreStringList('product_attributes.numeric_length_constraints.length_sort.value_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.answer_constraints.length_sort.value_patterns');
|
|
}
|
|
|
|
public function isShopResultLengthFilterEnabled(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.answer_constraints.length_filter.enabled');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopResultMinLengthFilterPatterns(): array
|
|
{
|
|
return $this->genreStringList('product_attributes.numeric_length_constraints.length_filter.min_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.answer_constraints.length_filter.min_patterns');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopResultMaxLengthFilterPatterns(): array
|
|
{
|
|
return $this->genreStringList('product_attributes.numeric_length_constraints.length_filter.max_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.answer_constraints.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<string, string[]>
|
|
*/
|
|
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_runtime.context_resolution.meta_query_guard.enabled');
|
|
}
|
|
|
|
public function getShopQueryContextFallbackCleanupProfile(): string
|
|
{
|
|
$value = $this->optionalValue('shop_runtime.context_resolution.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->genreStringList('context_resolution.meta_query_guard.meta_only_terms')
|
|
?: $this->getRequiredStringList('shop_runtime.context_resolution.meta_query_guard.meta_only_terms');
|
|
}
|
|
|
|
public function isShopQueryContextFallbackEnabled(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.context_resolution.meta_query_guard.context_fallback_enabled');
|
|
}
|
|
|
|
public function getShopQueryContextFallbackQuestionLimit(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.meta_query_guard.context_fallback_question_limit');
|
|
}
|
|
|
|
public function getShopQueryContextFallbackHistoryBudgetChars(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.meta_query_guard.context_fallback_history_budget_chars');
|
|
}
|
|
|
|
public function shouldUseFullHistoryForShopQueryContextFallback(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.context_resolution.meta_query_guard.context_fallback_use_full_history');
|
|
}
|
|
|
|
public function getShopQueryContextFallbackMaxTerms(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.meta_query_guard.context_fallback_max_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryContextFallbackFilterTerms(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.meta_query_guard.context_fallback_filter_terms')
|
|
?: $this->getRequiredStringList('shop_runtime.context_resolution.meta_query_guard.context_fallback_filter_terms');
|
|
}
|
|
|
|
public function isShopQueryContextAnchorEnrichmentEnabled(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.context_resolution.history_anchor_enrichment.enabled');
|
|
}
|
|
|
|
public function getShopQueryContextAnchorEnrichmentMaxQueryTerms(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.history_anchor_enrichment.max_query_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryContextAnchorEnrichmentTriggerTerms(): array
|
|
{
|
|
return $this->getConfiguredStringListOrVocabularyView(
|
|
'shop_runtime.context_resolution.history_anchor_enrichment.trigger_terms',
|
|
'shop_runtime.context_resolution.history_anchor_enrichment.vocabulary_views.trigger_terms'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryContextAnchorEnrichmentQueryTerms(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.history_anchor_enrichment.query_terms')
|
|
?: $this->getOptionalStringList('shop_runtime.context_resolution.history_anchor_enrichment.query_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryContextAnchorEnrichmentQueryNoiseTerms(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.history_anchor_enrichment.query_noise_terms')
|
|
?: $this->getOptionalStringList('shop_runtime.context_resolution.history_anchor_enrichment.query_noise_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryContextAnchorEnrichmentPatterns(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.history_anchor_enrichment.anchor_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.context_resolution.history_anchor_enrichment.anchor_patterns');
|
|
}
|
|
|
|
public function getShopQueryContextAnchorEnrichmentTemplate(): string
|
|
{
|
|
return $this->genreString('context_resolution.history_anchor_enrichment.template')
|
|
?: $this->getRequiredString('shop_runtime.context_resolution.history_anchor_enrichment.template');
|
|
}
|
|
|
|
|
|
public function isShopQueryProductListFollowUpEnabled(): bool
|
|
{
|
|
return $this->genreBool('context_resolution.product_list_followup.enabled') ?? false;
|
|
}
|
|
|
|
public function getShopQueryProductListFollowUpWeakQueryMaxTerms(): int
|
|
{
|
|
return $this->genreInt('context_resolution.product_list_followup.weak_query_max_terms') ?? 4;
|
|
}
|
|
|
|
public function getShopQueryProductListFollowUpWeakQueryMaxResidualTerms(): int
|
|
{
|
|
return $this->genreInt('context_resolution.product_list_followup.weak_query_max_residual_terms') ?? 0;
|
|
}
|
|
|
|
public function getShopQueryProductListFollowUpMaxAnchors(): int
|
|
{
|
|
return $this->genreInt('context_resolution.product_list_followup.max_anchors') ?? 4;
|
|
}
|
|
|
|
public function getShopQueryProductListFollowUpTemplate(): string
|
|
{
|
|
return $this->genreString('context_resolution.product_list_followup.template') ?: '{anchors}';
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryProductListFollowUpProductTerms(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.product_list_followup.product_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryProductListFollowUpShopTerms(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.product_list_followup.shop_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryProductListFollowUpNoiseTerms(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.product_list_followup.noise_terms');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryProductListFollowUpAnchorPatterns(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.product_list_followup.anchor_patterns');
|
|
}
|
|
public function isShopQueryRagAnchorEnrichmentEnabled(): bool
|
|
{
|
|
return $this->getRequiredBool('shop_runtime.context_resolution.rag_anchor_enrichment.enabled');
|
|
}
|
|
|
|
public function getShopQueryRagAnchorEnrichmentMinScore(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.rag_anchor_enrichment.min_score');
|
|
}
|
|
|
|
public function getShopQueryRagAnchorEnrichmentMaxQueryTerms(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.rag_anchor_enrichment.max_query_terms');
|
|
}
|
|
|
|
public function getShopQueryRagAnchorEnrichmentEarlyChunkBonusMax(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.rag_anchor_enrichment.early_chunk_bonus_max');
|
|
}
|
|
|
|
public function getShopQueryRagAnchorEnrichmentExactValueUnitScore(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.rag_anchor_enrichment.scores.exact_value_with_unit');
|
|
}
|
|
|
|
public function getShopQueryRagAnchorEnrichmentExactValueScore(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.rag_anchor_enrichment.scores.exact_value_only');
|
|
}
|
|
|
|
public function getShopQueryRagAnchorEnrichmentAnchorBonusScore(): int
|
|
{
|
|
return $this->getRequiredInt('shop_runtime.context_resolution.rag_anchor_enrichment.scores.anchor_bonus');
|
|
}
|
|
|
|
public function getShopQueryRagAnchorEnrichmentTemplate(): string
|
|
{
|
|
return $this->getRequiredString('shop_runtime.context_resolution.rag_anchor_enrichment.template');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryRagAnchorEnrichmentNumericFocusPatterns(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.rag_anchor_enrichment.numeric_focus_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.context_resolution.rag_anchor_enrichment.numeric_focus_patterns');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryRagAnchorEnrichmentProductTitlePatterns(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.rag_anchor_enrichment.product_title_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.context_resolution.rag_anchor_enrichment.product_title_patterns');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryRagAnchorEnrichmentAnchorBonusPatterns(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.rag_anchor_enrichment.anchor_bonus_patterns')
|
|
?: $this->getRequiredStringList('shop_runtime.context_resolution.rag_anchor_enrichment.anchor_bonus_patterns');
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getShopQueryRagAnchorEnrichmentSubjectTerms(): array
|
|
{
|
|
return $this->genreStringList('context_resolution.rag_anchor_enrichment.subject_terms')
|
|
?: $this->getRequiredStringList('shop_runtime.context_resolution.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);
|
|
}
|
|
}
|