p84
This commit is contained in:
@@ -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
|
||||
@@ -315,6 +315,26 @@ final readonly class AgentRunner
|
||||
$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(
|
||||
prompt: $originalPrompt,
|
||||
shopSearchQuery: $shopSearchQuery,
|
||||
@@ -2907,6 +2927,149 @@ final readonly class AgentRunner
|
||||
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
|
||||
{
|
||||
$tokens = $this->tokenizeShopQueryCandidate($text);
|
||||
|
||||
Reference in New Issue
Block a user