This commit is contained in:
team 1
2026-05-10 11:17:24 +02:00
parent fad07ce734
commit ee7930ce16
2 changed files with 207 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
# RetrieX Patch p84 - Main Device Referential Price Anchor
## Ziel
Referenzielle Gerätepreis-Nachfragen nach einem Zubehör-/Indikator-Flow sollen den zuletzt belegten Hauptgeräteanker behalten.
Beispiel:
1. `zu welchem gerät gehört der indikator 300`
2. `Preis anzeigen`
3. `und was kostet das gerät selber`
Die dritte Anfrage darf nicht mehr auf die generische Shopquery `gerät` reduziert werden, sondern soll den zuletzt belegten Gerätemodellanker verwenden, z. B. `testomat 808`.
## Änderung
- Ergänzt in `AgentRunner` einen engen Guard für referenzielle Hauptgeräte-Shopqueries.
- Der Guard greift nur, wenn:
- die aktuelle Query noch keinen Modellanker enthält,
- die aktuelle Frage einen Hauptgerätebezug enthält,
- die aktuelle Frage keinen Zubehör-/Indikator-/Reagenzbezug enthält,
- die bereinigte Query nur generische Geräte-/Preis-/Referenz-/Stopword-Tokens enthält,
- im Verlauf ein Produktmodellanker vorhanden ist.
- Bei Treffer wird die generische Query durch den neuesten Modellanker aus dem Verlauf ersetzt.
## Regressionsschutz
Die bestehende Zubehör-/Indikator-Ankerlogik bleibt unverändert und läuft weiterhin vor diesem neuen Guard. Der neue Guard blockiert sich selbst, wenn die aktuelle Frage Zubehör-/Indikator-/Reagenz-Tokens enthält. Dadurch sollen Flows wie `was kostet der indikator` weiterhin über die bestehende Zubehörlogik laufen.
## Nicht geändert
- Kein Retrieval-Scoring.
- Kein Shop-Ranking.
- Kein Shop-Matching.
- Keine PromptBuilder-Änderung.
- Keine neuen harten Produkt- oder Fachlisten im PHP-Core; die Entscheidung nutzt bestehende YAML-konfigurierbare Tokenlisten.
## Lokale Checks
- `php -l src/Agent/AgentRunner.php`
- YAML-Parsing der RetrieX-Konfiguration
- Logische Guard-Simulation:
- `gerät` + Verlauf mit `Testomat 808 Indikator 300` + Prompt `und was kostet das gerät selber` -> `testomat 808`
- `indikator`-/`zubehör`-Prompts bleiben vom neuen Hauptgeräte-Guard ausgeschlossen

View File

@@ -315,6 +315,26 @@ final readonly class AgentRunner
$optimizedShopQuery = ''; $optimizedShopQuery = '';
} }
$mainDeviceAnchoredShopSearchQuery = $this->guardMainDeviceReferentialShopQueryWithHistoryModelAnchor(
prompt: $originalPrompt,
shopSearchQuery: $shopSearchQuery,
commerceHistoryContext: $shopQueryHistoryContext
);
if ($mainDeviceAnchoredShopSearchQuery !== $shopSearchQuery) {
$this->agentLogger->info('Enriched referential main-device shop query with history model anchor', [
'userId' => $userId,
'prompt' => $prompt,
'routingPrompt' => $routingPrompt,
'optimizedShopQuery' => $optimizedShopQuery,
'shopSearchQuery' => $shopSearchQuery,
'mainDeviceAnchoredShopSearchQuery' => $mainDeviceAnchoredShopSearchQuery,
]);
$shopSearchQuery = $mainDeviceAnchoredShopSearchQuery;
$optimizedShopQuery = '';
}
$ragAnchoredShopSearchQuery = $this->enrichShopSearchQueryWithRagAnchor( $ragAnchoredShopSearchQuery = $this->enrichShopSearchQueryWithRagAnchor(
prompt: $originalPrompt, prompt: $originalPrompt,
shopSearchQuery: $shopSearchQuery, shopSearchQuery: $shopSearchQuery,
@@ -2907,6 +2927,149 @@ final readonly class AgentRunner
return $enriched !== '' ? $enriched : $shopSearchQuery; return $enriched !== '' ? $enriched : $shopSearchQuery;
} }
private function guardMainDeviceReferentialShopQueryWithHistoryModelAnchor(
string $prompt,
string $shopSearchQuery,
string $commerceHistoryContext
): string {
$shopSearchQuery = trim($shopSearchQuery);
if (
$shopSearchQuery === ''
|| trim($commerceHistoryContext) === ''
|| $this->referenceAnchorExtractor->extractFirstProductModelAnchor($prompt) !== ''
|| $this->referenceAnchorExtractor->extractFirstProductModelAnchor($shopSearchQuery) !== ''
) {
return $shopSearchQuery;
}
if (!$this->isMainDeviceReferentialShopQueryPrompt($prompt)) {
return $shopSearchQuery;
}
if (!$this->isGenericMainDeviceReferentialShopQuery($shopSearchQuery)) {
return $shopSearchQuery;
}
$modelAnchor = $this->normalizeShopQueryAnchor(
$this->extractLatestHistoryProductModelAnchor($commerceHistoryContext)
);
if ($modelAnchor === '') {
return $shopSearchQuery;
}
return $this->queryAlreadyContainsAllAnchorTokens($shopSearchQuery, $modelAnchor)
? $shopSearchQuery
: $modelAnchor;
}
private function isMainDeviceReferentialShopQueryPrompt(string $prompt): bool
{
$tokens = $this->tokenizeShopQueryCandidate($prompt);
if ($tokens === []) {
return false;
}
$tokenSet = array_fill_keys($tokens, true);
$mainDeviceTokens = $this->buildShopQueryTokenSet(
$this->agentRunnerConfig->getNoLlmMainDeviceRequestRoleKeywords()
);
if (!$this->tokenSetIntersects($tokenSet, $mainDeviceTokens)) {
return false;
}
$accessoryTokens = $this->buildShopQueryTokenSet($this->mergeUniqueStrings(
$this->agentRunnerConfig->getNoLlmAccessoryProductRoleKeywords(),
$this->agentRunnerConfig->getRequestedAccessoryCodeTerms()
));
if ($this->tokenSetIntersects($tokenSet, $accessoryTokens)) {
return false;
}
$referenceTokens = $this->buildShopQueryTokenSet($this->mergeUniqueStrings(
$this->agentRunnerConfig->getShopQueryContextUsageReferentialTerms(),
$this->agentRunnerConfig->getShopQueryMetaOnlyTerms()
));
return $this->tokenSetIntersects($tokenSet, $referenceTokens);
}
private function isGenericMainDeviceReferentialShopQuery(string $shopSearchQuery): bool
{
$tokens = $this->tokenizeShopQueryCandidate($shopSearchQuery);
if ($tokens === []) {
return false;
}
foreach ($tokens as $token) {
if (preg_match('/\d/u', $token) === 1) {
return false;
}
}
$genericTerms = $this->mergeUniqueStrings(
$this->agentRunnerConfig->getNoLlmMainDeviceRequestRoleKeywords(),
$this->agentRunnerConfig->getShopQueryContextUsageReferentialTerms()
);
$genericTerms = $this->mergeUniqueStrings($genericTerms, $this->agentRunnerConfig->getShopQueryMetaOnlyTerms());
$genericTerms = $this->mergeUniqueStrings($genericTerms, $this->agentRunnerConfig->getShopQueryContextFallbackFilterTerms());
$genericTerms = $this->mergeUniqueStrings($genericTerms, $this->agentRunnerConfig->getShopQueryStopwordCleanupTerms());
$genericTokens = $this->buildShopQueryTokenSet($genericTerms);
if ($genericTokens === []) {
return false;
}
$hasMainDeviceToken = false;
$mainDeviceTokens = $this->buildShopQueryTokenSet(
$this->agentRunnerConfig->getNoLlmMainDeviceRequestRoleKeywords()
);
foreach ($tokens as $token) {
if (!isset($genericTokens[$token])) {
return false;
}
if (isset($mainDeviceTokens[$token])) {
$hasMainDeviceToken = true;
}
}
return $hasMainDeviceToken;
}
private function extractLatestHistoryProductModelAnchor(string $commerceHistoryContext): string
{
foreach ($this->extractHistoryTurnsNewestFirst($commerceHistoryContext) as $turn) {
$modelAnchor = $this->referenceAnchorExtractor->extractFirstProductModelAnchor($turn);
if ($modelAnchor !== '') {
return $modelAnchor;
}
}
return '';
}
/**
* @param array<string, true> $left
* @param array<string, true> $right
*/
private function tokenSetIntersects(array $left, array $right): bool
{
foreach ($left as $token => $_) {
if (isset($right[$token])) {
return true;
}
}
return false;
}
private function extractReferentialShopQueryTriggerTerms(string $text): string private function extractReferentialShopQueryTriggerTerms(string $text): string
{ {
$tokens = $this->tokenizeShopQueryCandidate($text); $tokens = $this->tokenizeShopQueryCandidate($text);