p42
This commit is contained in:
@@ -1651,12 +1651,122 @@ final readonly class AgentRunner
|
||||
}
|
||||
|
||||
$guardedQuery = $this->guardStandaloneOptimizedShopQuery($prompt, $shopSearchQuery);
|
||||
$query = $guardedQuery !== $shopSearchQuery
|
||||
? $this->preserveCurrentInputShopQueryTerms($prompt, $guardedQuery)
|
||||
: $this->preserveCurrentInputShopQueryTerms($prompt, $shopSearchQuery);
|
||||
|
||||
if ($guardedQuery !== $shopSearchQuery) {
|
||||
return $this->preserveCurrentInputShopQueryTerms($prompt, $guardedQuery);
|
||||
return $this->cleanupDirectProductAttributeShopQuery($prompt, $query);
|
||||
}
|
||||
|
||||
private function cleanupDirectProductAttributeShopQuery(string $prompt, string $shopSearchQuery): string
|
||||
{
|
||||
$shopSearchQuery = trim($shopSearchQuery);
|
||||
|
||||
if (
|
||||
$shopSearchQuery === ''
|
||||
|| !$this->agentRunnerConfig->isShopQueryProductAttributeCleanupEnabled()
|
||||
) {
|
||||
return $shopSearchQuery;
|
||||
}
|
||||
|
||||
return $this->preserveCurrentInputShopQueryTerms($prompt, $shopSearchQuery);
|
||||
$combined = trim($prompt . ' ' . $shopSearchQuery);
|
||||
if (!$this->containsAnyShopQueryTerm($combined, $this->agentRunnerConfig->getShopQueryProductAttributeCleanupProductTypeTerms())) {
|
||||
return $shopSearchQuery;
|
||||
}
|
||||
|
||||
$constraintTokens = $this->extractConfiguredShopQueryConstraintTokens(
|
||||
$combined,
|
||||
$this->agentRunnerConfig->getShopQueryProductAttributeCleanupComparativeConstraintPatterns()
|
||||
);
|
||||
|
||||
if ($constraintTokens === []) {
|
||||
return $shopSearchQuery;
|
||||
}
|
||||
|
||||
$removeTokens = array_fill_keys($constraintTokens, true);
|
||||
foreach ($this->agentRunnerConfig->getShopQueryProductAttributeCleanupStopTerms() as $term) {
|
||||
foreach ($this->tokenizeShopQueryCandidate($term) as $token) {
|
||||
$removeTokens[$token] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$kept = [];
|
||||
foreach ($this->tokenizeShopQueryCandidate($shopSearchQuery) as $token) {
|
||||
if (isset($removeTokens[$token]) || isset($kept[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$kept[$token] = $token;
|
||||
}
|
||||
|
||||
if (count($kept) < max(1, $this->agentRunnerConfig->getShopQueryProductAttributeCleanupMinTokens())) {
|
||||
return $shopSearchQuery;
|
||||
}
|
||||
|
||||
$cleaned = implode(' ', array_values($kept));
|
||||
|
||||
return $cleaned !== '' ? $cleaned : $shopSearchQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $terms
|
||||
*/
|
||||
private function containsAnyShopQueryTerm(string $text, array $terms): bool
|
||||
{
|
||||
$tokens = array_fill_keys($this->tokenizeShopQueryCandidate($text), true);
|
||||
|
||||
if ($tokens === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($terms as $term) {
|
||||
$termTokens = $this->tokenizeShopQueryCandidate($term);
|
||||
if ($termTokens === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches = true;
|
||||
foreach ($termTokens as $termToken) {
|
||||
if (!isset($tokens[$termToken])) {
|
||||
$matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $patterns
|
||||
* @return string[]
|
||||
*/
|
||||
private function extractConfiguredShopQueryConstraintTokens(string $text, array $patterns): array
|
||||
{
|
||||
$tokens = [];
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (@preg_match_all($pattern, $text, $matches, PREG_SET_ORDER) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$value = $match['value'] ?? ($match[1] ?? '');
|
||||
if (!is_scalar($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->tokenizeShopQueryCandidate((string) $value) as $token) {
|
||||
$tokens[$token] = $token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($tokens);
|
||||
}
|
||||
|
||||
private function preserveCurrentInputShopQueryTerms(string $prompt, string $shopSearchQuery): string
|
||||
|
||||
@@ -222,6 +222,9 @@ final readonly class PromptBuilder
|
||||
$isDetailed = count($limitedShopResults) <= $this->config->getDetailedShopResultsMaxCount();
|
||||
$requestedRole = $requestedProductRole ?? $this->resolveRequestedProductRole($prompt);
|
||||
$measurementGuard = $this->resolveRequestedMeasurementGuard($prompt);
|
||||
if ($measurementGuard !== null && $this->shouldSkipMeasurementEvidenceForAccessoryLookup($prompt, $requestedRole)) {
|
||||
$measurementGuard = null;
|
||||
}
|
||||
$lines = [];
|
||||
|
||||
foreach ($limitedShopResults as $i => $product) {
|
||||
@@ -779,13 +782,17 @@ final readonly class PromptBuilder
|
||||
return '';
|
||||
}
|
||||
|
||||
$resolvedRequestedRole = $requestedRole ?? $this->resolveRequestedProductRole($prompt);
|
||||
if ($this->shouldSkipMeasurementEvidenceForAccessoryLookup($prompt, $resolvedRequestedRole)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$positiveTerms = $this->extractMeasurementGuardStringList($guard, 'positive_terms');
|
||||
$positiveContextTerms = $this->extractMeasurementGuardStringList($guard, 'positive_context_terms');
|
||||
$negativeContextTerms = $this->extractMeasurementGuardStringList($guard, 'negative_context_terms');
|
||||
$nonEquivalentTerms = $this->extractMeasurementGuardStringList($guard, 'non_equivalent_terms');
|
||||
$label = $this->normalizeBlockText((string) ($guard['label'] ?? $this->config->getMeasurementEvidenceRuleTemplate('default_requested_parameter_label')));
|
||||
$strictNoEvidence = (bool) ($guard['strict_no_evidence'] ?? true);
|
||||
$resolvedRequestedRole = $requestedRole ?? $this->resolveRequestedProductRole($prompt);
|
||||
$safeNoEvidenceAnswer = $this->normalizeBlockText((string) (
|
||||
$resolvedRequestedRole === 'accessory_or_consumable'
|
||||
? ($guard['safe_no_accessory_evidence_answer_de'] ?? $guard['safe_no_evidence_answer_de'] ?? '')
|
||||
@@ -869,6 +876,21 @@ final readonly class PromptBuilder
|
||||
}
|
||||
|
||||
|
||||
private function shouldSkipMeasurementEvidenceForAccessoryLookup(string $prompt, string $requestedRole): bool
|
||||
{
|
||||
if ($requestedRole !== 'accessory_or_consumable') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$normalizedPrompt = $this->normalizeForMeasurementMatching($prompt);
|
||||
|
||||
if ($this->containsAnyPromptKeyword($normalizedPrompt, $this->config->getMeasurementEvidenceAccessoryLookupGuardTerms())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->containsAnyPromptKeyword($normalizedPrompt, $this->config->getMeasurementEvidenceAccessoryLookupPassthroughTerms());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $values
|
||||
*/
|
||||
|
||||
@@ -208,6 +208,16 @@ final readonly class SearchRepairService
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
$requestedAccessoryCodes === []
|
||||
&& $this->isDirectProductAttributeLookup($prompt . ' ' . $primaryQuery)
|
||||
) {
|
||||
return $this->normalizeRepairQueries(
|
||||
$this->buildDirectProductAttributeRepairQueries($prompt, $primaryQuery),
|
||||
$primaryQuery
|
||||
);
|
||||
}
|
||||
|
||||
$topPrimaryName = $primaryShopResults[0]->name ?? '';
|
||||
$topPrimaryProductNumber = $primaryShopResults[0]->productNumber ?? null;
|
||||
$topPrimaryPhrase = trim($topPrimaryName . ' ' . ($topPrimaryProductNumber ?? ''));
|
||||
@@ -259,6 +269,125 @@ final readonly class SearchRepairService
|
||||
return $this->normalizeRepairQueries($queries, $primaryQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function buildDirectProductAttributeRepairQueries(string $prompt, string $primaryQuery): array
|
||||
{
|
||||
$queries = [];
|
||||
|
||||
foreach ([$primaryQuery, $prompt] as $source) {
|
||||
$query = $this->cleanupDirectProductAttributeRepairQuery($source);
|
||||
if ($query !== '') {
|
||||
$queries[] = $query;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($queries));
|
||||
}
|
||||
|
||||
private function cleanupDirectProductAttributeRepairQuery(string $source): string
|
||||
{
|
||||
$source = trim($source);
|
||||
|
||||
if ($source === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$constraintTokens = $this->extractDirectProductAttributeConstraintTokens($source);
|
||||
if ($constraintTokens === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$removeTokens = array_fill_keys($constraintTokens, true);
|
||||
foreach ($this->config->getDirectProductAttributeLookupStopTerms() as $term) {
|
||||
foreach ($this->tokenize($term) as $token) {
|
||||
$removeTokens[$token] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$kept = [];
|
||||
foreach ($this->tokenize($source) as $token) {
|
||||
if (isset($removeTokens[$token]) || isset($kept[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$kept[$token] = $token;
|
||||
}
|
||||
|
||||
if (count($kept) < $this->config->getDirectProductAttributeLookupMinTokens()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$query = implode(' ', array_values($kept));
|
||||
|
||||
return $this->containsDirectProductTypeTerm($query) ? $query : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function extractDirectProductAttributeConstraintTokens(string $text): array
|
||||
{
|
||||
$tokens = [];
|
||||
|
||||
foreach ($this->config->getDirectProductAttributeLookupComparativeConstraintPatterns() as $pattern) {
|
||||
if (@preg_match_all($pattern, $text, $matches, PREG_SET_ORDER) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$value = $match['value'] ?? ($match[1] ?? '');
|
||||
if (!is_scalar($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->tokenize((string) $value) as $token) {
|
||||
$tokens[$token] = $token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($tokens);
|
||||
}
|
||||
|
||||
private function isDirectProductAttributeLookup(string $text): bool
|
||||
{
|
||||
return $this->config->isDirectProductAttributeLookupRepairEnabled()
|
||||
&& $this->containsDirectProductTypeTerm($text)
|
||||
&& $this->extractDirectProductAttributeConstraintTokens($text) !== [];
|
||||
}
|
||||
|
||||
private function containsDirectProductTypeTerm(string $text): bool
|
||||
{
|
||||
$tokens = array_fill_keys($this->tokenize($text), true);
|
||||
|
||||
if ($tokens === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->config->getDirectProductAttributeLookupProductTypeTerms() as $term) {
|
||||
$termTokens = $this->tokenize($term);
|
||||
if ($termTokens === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches = true;
|
||||
foreach ($termTokens as $termToken) {
|
||||
if (!isset($tokens[$termToken])) {
|
||||
$matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $queries
|
||||
* @return string[]
|
||||
|
||||
@@ -918,6 +918,40 @@ final class AgentRunnerConfig
|
||||
return $this->getOptionalStringList('shop_prompt.current_input_preservation.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->getRequiredStringList('shop_prompt.product_attribute_query_cleanup.product_type_terms');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getShopQueryProductAttributeCleanupStopTerms(): array
|
||||
{
|
||||
return $this->getRequiredStringList('shop_prompt.product_attribute_query_cleanup.stop_terms');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getShopQueryProductAttributeCleanupComparativeConstraintPatterns(): array
|
||||
{
|
||||
return $this->getRequiredStringList('shop_prompt.product_attribute_query_cleanup.comparative_constraint_patterns');
|
||||
}
|
||||
|
||||
public function getShopPromptIntro(): string
|
||||
{
|
||||
return $this->getRequiredString('shop_prompt.intro');
|
||||
|
||||
@@ -670,6 +670,22 @@ final class PromptBuilderConfig
|
||||
return $this->getRequiredString('measurement_evidence_guard.generic_safe_no_accessory_evidence_answer_template_de');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMeasurementEvidenceAccessoryLookupGuardTerms(): array
|
||||
{
|
||||
return $this->getRequiredStringList('measurement_evidence_guard.accessory_lookup_guard_terms');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMeasurementEvidenceAccessoryLookupPassthroughTerms(): array
|
||||
{
|
||||
return $this->getRequiredStringList('measurement_evidence_guard.accessory_lookup_passthrough_terms');
|
||||
}
|
||||
|
||||
public function getMeasurementEvidenceRuleTemplate(string $key): string
|
||||
{
|
||||
return $this->getRequiredString('measurement_evidence_guard.rule_templates.' . $key);
|
||||
|
||||
@@ -845,6 +845,13 @@ final readonly class RetriexEffectiveConfigProvider
|
||||
'min_primary_results_without_repair' => $this->searchRepairConfig->getMinPrimaryResultsWithoutRepair(),
|
||||
'strict_requested_accessory_code_repair' => $this->searchRepairConfig->shouldRestrictRequestedAccessoryCodeRepair(),
|
||||
'prefer_prompt_anchored_model_for_requested_accessory_code' => $this->searchRepairConfig->shouldPreferPromptAnchoredModelForRequestedAccessoryCode(),
|
||||
'direct_product_attribute_lookup' => [
|
||||
'enabled' => $this->searchRepairConfig->isDirectProductAttributeLookupRepairEnabled(),
|
||||
'min_query_tokens_after_cleanup' => $this->searchRepairConfig->getDirectProductAttributeLookupMinTokens(),
|
||||
'product_type_terms' => $this->searchRepairConfig->getDirectProductAttributeLookupProductTypeTerms(),
|
||||
'stop_terms' => $this->searchRepairConfig->getDirectProductAttributeLookupStopTerms(),
|
||||
'comparative_constraint_patterns' => $this->searchRepairConfig->getDirectProductAttributeLookupComparativeConstraintPatterns(),
|
||||
],
|
||||
'requested_accessory_code_fallback_query_templates' => $this->searchRepairConfig->getRequestedAccessoryCodeFallbackQueryTemplates(),
|
||||
'requested_accessory_code_fallback_terms' => $this->searchRepairConfig->getRequestedAccessoryCodeFallbackTerms(),
|
||||
'requested_accessory_code_context_prefix_terms' => $this->searchRepairConfig->getRequestedAccessoryCodeContextPrefixTerms(),
|
||||
|
||||
@@ -50,6 +50,37 @@ final class SearchRepairConfig
|
||||
return $this->requiredBool('prefer_prompt_anchored_model_for_requested_accessory_code');
|
||||
}
|
||||
|
||||
public function isDirectProductAttributeLookupRepairEnabled(): bool
|
||||
{
|
||||
return $this->requiredBool('direct_product_attribute_lookup.enabled');
|
||||
}
|
||||
|
||||
public function getDirectProductAttributeLookupMinTokens(): int
|
||||
{
|
||||
return $this->requiredPositiveInt('direct_product_attribute_lookup.min_query_tokens_after_cleanup');
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getDirectProductAttributeLookupProductTypeTerms(): array
|
||||
{
|
||||
return $this->configOrVocabularyStringList(
|
||||
'direct_product_attribute_lookup.product_type_terms',
|
||||
'search_repair.direct_product_type_terms'
|
||||
);
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getDirectProductAttributeLookupStopTerms(): array
|
||||
{
|
||||
return $this->requiredStringList('direct_product_attribute_lookup.stop_terms');
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getDirectProductAttributeLookupComparativeConstraintPatterns(): array
|
||||
{
|
||||
return $this->requiredStringList('direct_product_attribute_lookup.comparative_constraint_patterns');
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getRequestedAccessoryCodeFallbackQueryTemplates(): array
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user