p61 new
This commit is contained in:
@@ -332,6 +332,21 @@ final readonly class AgentRunner
|
||||
$optimizedShopQuery = '';
|
||||
}
|
||||
|
||||
$positiveFilteredShopSearchQuery = $this->filterShopQueryToPositiveTokens($shopSearchQuery);
|
||||
if ($positiveFilteredShopSearchQuery !== $shopSearchQuery) {
|
||||
$this->agentLogger->info('Filtered final shop search query to positive product tokens', [
|
||||
'userId' => $userId,
|
||||
'prompt' => $prompt,
|
||||
'routingPrompt' => $routingPrompt,
|
||||
'optimizedShopQuery' => $optimizedShopQuery,
|
||||
'shopSearchQuery' => $shopSearchQuery,
|
||||
'positiveFilteredShopSearchQuery' => $positiveFilteredShopSearchQuery,
|
||||
]);
|
||||
|
||||
$shopSearchQuery = $positiveFilteredShopSearchQuery;
|
||||
$optimizedShopQuery = '';
|
||||
}
|
||||
|
||||
if ($shopSearchQuery === '') {
|
||||
$this->agentLogger->info('Commerce search skipped because no concrete shop query could be resolved', [
|
||||
'userId' => $userId,
|
||||
@@ -502,7 +517,6 @@ final readonly class AgentRunner
|
||||
$shopResults = $directIdentityRepairPayload['results'];
|
||||
}
|
||||
|
||||
$shopResults = $this->guardShopResultsByReferencedProductAnchor($shopSearchQuery, $shopResults);
|
||||
$shopResults = $this->sortShopResultsForLengthRequest($prompt, $shopSearchQuery, $shopResults);
|
||||
$attemptedShopRepair = $repairPayload['attemptedRepair'] || $directIdentityRepairPayload['attemptedRepair'];
|
||||
$usedShopRepair = $repairPayload['usedRepair'] || $directIdentityRepairPayload['usedRepair'];
|
||||
@@ -1664,6 +1678,118 @@ final readonly class AgentRunner
|
||||
return $cleaned !== '' ? $cleaned : $shopSearchQuery;
|
||||
}
|
||||
|
||||
private function filterShopQueryToPositiveTokens(string $shopSearchQuery): string
|
||||
{
|
||||
$shopSearchQuery = trim($shopSearchQuery);
|
||||
|
||||
if (
|
||||
$shopSearchQuery === ''
|
||||
|| !$this->agentRunnerConfig->isShopQueryPositiveTokenFilterEnabled()
|
||||
) {
|
||||
return $shopSearchQuery;
|
||||
}
|
||||
|
||||
$tokens = $this->tokenizeShopQueryCandidate($shopSearchQuery);
|
||||
if ($tokens === []) {
|
||||
return $shopSearchQuery;
|
||||
}
|
||||
|
||||
$allowedTokens = $this->buildPositiveShopQueryAllowedTokenSet();
|
||||
$blockedTokens = $this->buildPositiveShopQueryBlockedTokenSet();
|
||||
$codePatterns = $this->agentRunnerConfig->getShopQueryPositiveTokenFilterCodePatterns();
|
||||
|
||||
if ($allowedTokens === [] && $codePatterns === []) {
|
||||
return $shopSearchQuery;
|
||||
}
|
||||
|
||||
$kept = [];
|
||||
foreach ($tokens as $token) {
|
||||
if (isset($blockedTokens[$token]) || isset($kept[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($allowedTokens[$token]) || $this->matchesAnyConfiguredShopQueryCodePattern($token, $codePatterns)) {
|
||||
$kept[$token] = $token;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($kept) < max(1, $this->agentRunnerConfig->getShopQueryPositiveTokenFilterMinTokens())) {
|
||||
return $shopSearchQuery;
|
||||
}
|
||||
|
||||
$filtered = implode(' ', array_values($kept));
|
||||
|
||||
return $filtered !== '' ? $filtered : $shopSearchQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, true>
|
||||
*/
|
||||
private function buildPositiveShopQueryAllowedTokenSet(): array
|
||||
{
|
||||
$terms = $this->agentRunnerConfig->getShopQueryPositiveTokenFilterAllowedTerms();
|
||||
|
||||
if ($this->agentRunnerConfig->shouldShopQueryPositiveTokenFilterIncludeCurrentInputPreservationTerms()) {
|
||||
$terms = $this->mergeUniqueStrings(
|
||||
$terms,
|
||||
$this->agentRunnerConfig->getShopQueryCurrentInputPreservationTerms()
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->agentRunnerConfig->shouldShopQueryPositiveTokenFilterIncludeSemanticShopSearchTokens()) {
|
||||
$terms = $this->mergeUniqueStrings(
|
||||
$terms,
|
||||
$this->agentRunnerConfig->getShopQueryPositiveTokenFilterSemanticShopSearchTokens()
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->agentRunnerConfig->shouldShopQueryPositiveTokenFilterIncludeProductRoleTerms()) {
|
||||
$terms = $this->mergeUniqueStrings(
|
||||
$terms,
|
||||
$this->agentRunnerConfig->getShopQueryPositiveTokenFilterProductRoleTerms()
|
||||
);
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
foreach ($terms as $term) {
|
||||
foreach ($this->tokenizeShopQueryCandidate($term) as $token) {
|
||||
$tokens[$token] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, true>
|
||||
*/
|
||||
private function buildPositiveShopQueryBlockedTokenSet(): array
|
||||
{
|
||||
$tokens = [];
|
||||
|
||||
foreach ($this->agentRunnerConfig->getShopQueryPositiveTokenFilterBlockedTerms() as $term) {
|
||||
foreach ($this->tokenizeShopQueryCandidate($term) as $token) {
|
||||
$tokens[$token] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $patterns
|
||||
*/
|
||||
private function matchesAnyConfiguredShopQueryCodePattern(string $token, array $patterns): bool
|
||||
{
|
||||
foreach ($patterns as $pattern) {
|
||||
if (@preg_match($pattern, $token) === 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function cleanupDirectProductAttributeShopQuery(string $prompt, string $shopSearchQuery): string
|
||||
{
|
||||
$shopSearchQuery = trim($shopSearchQuery);
|
||||
@@ -2673,40 +2799,20 @@ final readonly class AgentRunner
|
||||
return '';
|
||||
}
|
||||
|
||||
$triggerTokens = $this->buildShopQueryTokenSet(
|
||||
$this->agentRunnerConfig->getShopQueryContextAnchorEnrichmentTriggerTerms()
|
||||
);
|
||||
$triggerTokens = [];
|
||||
foreach ($this->agentRunnerConfig->getShopQueryContextAnchorEnrichmentTriggerTerms() as $term) {
|
||||
foreach ($this->tokenizeShopQueryCandidate($term) as $termToken) {
|
||||
$triggerTokens[$termToken] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($triggerTokens === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$hasTrigger = false;
|
||||
foreach ($tokens as $token) {
|
||||
if (isset($triggerTokens[$token])) {
|
||||
$hasTrigger = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasTrigger) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$queryTokens = $this->buildShopQueryTokenSet(
|
||||
$this->agentRunnerConfig->getShopQueryContextAnchorEnrichmentQueryTerms()
|
||||
);
|
||||
if ($queryTokens === []) {
|
||||
$queryTokens = $triggerTokens;
|
||||
}
|
||||
|
||||
$noiseTokens = $this->buildShopQueryTokenSet(
|
||||
$this->agentRunnerConfig->getShopQueryContextAnchorEnrichmentQueryNoiseTerms()
|
||||
);
|
||||
|
||||
$out = [];
|
||||
foreach ($tokens as $token) {
|
||||
if (!isset($queryTokens[$token]) || isset($noiseTokens[$token]) || isset($out[$token])) {
|
||||
if (!isset($triggerTokens[$token]) || isset($out[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2716,23 +2822,6 @@ final readonly class AgentRunner
|
||||
return implode(' ', array_values($out));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $terms
|
||||
* @return array<string, true>
|
||||
*/
|
||||
private function buildShopQueryTokenSet(array $terms): array
|
||||
{
|
||||
$tokens = [];
|
||||
|
||||
foreach ($terms as $term) {
|
||||
foreach ($this->tokenizeShopQueryCandidate($term) as $termToken) {
|
||||
$tokens[$termToken] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function enrichReferentialShopQueryFromHistory(
|
||||
string $query,
|
||||
string $sourcePrompt,
|
||||
@@ -2801,33 +2890,11 @@ final readonly class AgentRunner
|
||||
}
|
||||
|
||||
private function extractLatestConfiguredShopQueryContextAnchor(string $commerceHistoryContext): string
|
||||
{
|
||||
foreach ($this->extractHistoryTurnsNewestFirst($commerceHistoryContext) as $turn) {
|
||||
if (!$this->containsConfiguredShopQueryAnchorTrigger($turn)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$modelAnchor = $this->referenceAnchorExtractor->extractFirstProductModelAnchor($turn);
|
||||
$turnAnchor = $this->extractLatestConfiguredShopQueryPatternAnchor($turn);
|
||||
|
||||
if ($modelAnchor !== '') {
|
||||
return $this->buildModelQualifiedShopQueryAnchor($modelAnchor, $turnAnchor);
|
||||
}
|
||||
|
||||
if ($turnAnchor !== '') {
|
||||
return $turnAnchor;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->extractLatestConfiguredShopQueryPatternAnchor($commerceHistoryContext);
|
||||
}
|
||||
|
||||
private function extractLatestConfiguredShopQueryPatternAnchor(string $text): string
|
||||
{
|
||||
$latest = '';
|
||||
|
||||
foreach ($this->agentRunnerConfig->getShopQueryContextAnchorEnrichmentPatterns() as $pattern) {
|
||||
if (@preg_match_all($pattern, $text, $matches, PREG_SET_ORDER) === false) {
|
||||
if (@preg_match_all($pattern, $commerceHistoryContext, $matches, PREG_SET_ORDER) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2842,51 +2909,6 @@ final readonly class AgentRunner
|
||||
return $latest;
|
||||
}
|
||||
|
||||
private function buildModelQualifiedShopQueryAnchor(string $modelAnchor, string $detailAnchor): string
|
||||
{
|
||||
$modelAnchor = trim($modelAnchor);
|
||||
if ($modelAnchor === '') {
|
||||
return trim($detailAnchor);
|
||||
}
|
||||
|
||||
$detailTokens = $this->extractShopQueryDetailAnchorTokens($detailAnchor, $modelAnchor);
|
||||
if ($detailTokens === []) {
|
||||
return $modelAnchor;
|
||||
}
|
||||
|
||||
return trim($modelAnchor . ' ' . implode(' ', $detailTokens));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function extractShopQueryDetailAnchorTokens(string $detailAnchor, string $modelAnchor): array
|
||||
{
|
||||
$tokens = $this->tokenizeShopQueryCandidate($detailAnchor);
|
||||
if ($tokens === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$modelTokens = array_fill_keys($this->tokenizeShopQueryCandidate($modelAnchor), true);
|
||||
$queryTokens = $this->buildShopQueryTokenSet(
|
||||
$this->agentRunnerConfig->getShopQueryContextAnchorEnrichmentQueryTerms()
|
||||
);
|
||||
$noiseTokens = $this->buildShopQueryTokenSet(
|
||||
$this->agentRunnerConfig->getShopQueryContextAnchorEnrichmentQueryNoiseTerms()
|
||||
);
|
||||
|
||||
$out = [];
|
||||
foreach ($tokens as $token) {
|
||||
if (isset($modelTokens[$token]) || isset($queryTokens[$token]) || isset($noiseTokens[$token]) || isset($out[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$out[$token] = $token;
|
||||
}
|
||||
|
||||
return array_values($out);
|
||||
}
|
||||
|
||||
private function normalizeShopQueryAnchor(string $anchor): string
|
||||
{
|
||||
$anchor = str_replace('®', '', $anchor);
|
||||
@@ -3354,48 +3376,6 @@ final readonly class AgentRunner
|
||||
return trim(implode(' ', $this->tokenizeShopQueryCandidate($query)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShopProductResult[] $shopResults
|
||||
* @return ShopProductResult[]
|
||||
*/
|
||||
private function guardShopResultsByReferencedProductAnchor(string $shopSearchQuery, array $shopResults): array
|
||||
{
|
||||
if ($shopResults === []) {
|
||||
return $shopResults;
|
||||
}
|
||||
|
||||
$anchor = $this->referenceAnchorExtractor->extractFirstProductModelAnchor($shopSearchQuery);
|
||||
if ($anchor === '') {
|
||||
return $shopResults;
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
foreach ($shopResults as $product) {
|
||||
if (!$product instanceof ShopProductResult) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->shopProductMatchesReferencedProductAnchor($product, $anchor)) {
|
||||
$filtered[] = $product;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
private function shopProductMatchesReferencedProductAnchor(ShopProductResult $product, string $anchor): bool
|
||||
{
|
||||
$productText = trim(implode(' ', array_filter([
|
||||
$product->name,
|
||||
$product->description,
|
||||
implode(' ', $product->highlights),
|
||||
$product->customFields,
|
||||
$product->url,
|
||||
])));
|
||||
|
||||
return $this->containsAllShopQueryTokens($productText, $anchor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShopProductResult[] $shopResults
|
||||
* @return ShopProductResult[]
|
||||
|
||||
Reference in New Issue
Block a user