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 = '';
|
$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);
|
||||||
|
|||||||
Reference in New Issue
Block a user