fix shop research intent

This commit is contained in:
team 1
2026-04-27 16:11:25 +02:00
parent 75c376c7a8
commit 8c40153745
7 changed files with 157 additions and 4 deletions

View File

@@ -0,0 +1,64 @@
# RetrieX shop advisory intent fix
## Problem
The prompt
```text
welcher pockettester ist für Redox messung gut
```
was classified as a purely technical RAG question because the factual guard matched
question wording such as `welcher` plus technical wording such as `Messung`.
As a result no Shopware search was executed. The later follow-up
```text
suche im shop
```
then had to recover a shop query from history/client context, which remained fragile.
## Fix
This patch keeps the factual RAG guard for technical questions, but lets clear product
advisory prompts pass into commerce detection when both conditions are true:
1. The prompt contains an advisory term such as `gut`, `geeignet`, `empfehl...`.
2. The prompt contains a product/device class signal such as `pockettester`,
`messgerät`, `handmessgerät`, etc.
This means:
- `Was ist der niedrigste Grenzwert ... Testomat ... Wasserhärte?` stays RAG-only.
- `mit welchem indikator wird der wert gemessen` stays RAG-only.
- `was kostet der indikator` stays commerce/product search.
- `welcher pockettester ist für Redox messung gut` now triggers commerce search directly.
## Changed files
```text
src/Intent/CommerceIntentLite.php
src/Config/CommerceIntentConfig.php
config/retriex/intent.yaml
config/retriex/agent.yaml
```
## Validation performed
```bash
php -n -l src/Intent/CommerceIntentLite.php
php -n -l src/Config/CommerceIntentConfig.php
```
Additional local intent check:
```text
Was ist der niedrigste Grenzwert ... => none
mit welchem indikator ... => none
was kostet der indikator => product_search
welcher pockettester ist für Redox messung gut => product_search
suche im shop => product_search
suche im shop nach redox messung => product_search
welcher Grenzwert kann mit dem Testomat 808 gemessen werden => none
welches Messgerät ist für Redox Messung geeignet => product_search
```

View File

@@ -61,7 +61,7 @@ parameters:
- '- Separate terms using spaces only.'
- '- If a relevant product name is present, it must be placed at the beginning of the final search query.'
- '- Try to always identify all products mentioned in the user input text, even in long prompts.'
- '- Look for terms such as Testomat, Horiba, Tritromat, or words like indicator/Indikator.'
- '- Look for terms such as Testomat, Horiba, Tritromat, Pockettester, Redox, ORP, or words like indicator/Indikator.'
- '- If the current user input is vague or referential, use the recent conversation context only as support.'
- '- Do not output words that only describe conversation flow, such as "same", "again", "also", or "like above".'
conversation_context_rules:

View File

@@ -21,6 +21,10 @@ parameters:
- analysegeraet
- messgerät
- messgeraet
- pockettester
- pocket tester
- handmessgerät
- handmessgeraet
- analysator
- analyzer
- puffer
@@ -40,6 +44,7 @@ parameters:
- eignet
- besser
- besten
- gut
- gut für
- gut fuer
- passend für

View File

@@ -393,7 +393,7 @@ span.think {
background-position: 220% 0;
-webkit-background-clip: text;
background-clip: text;
animation: thinkScan 2.2s linear infinite;
animation: thinkScan 1.4s linear infinite;
}
@keyframes thinkScan {

Binary file not shown.

View File

@@ -25,6 +25,10 @@ final class CommerceIntentConfig
'analysegeraet',
'messgerät',
'messgeraet',
'pockettester',
'pocket tester',
'handmessgerät',
'handmessgeraet',
'analysator',
'analyzer',
'puffer',
@@ -46,6 +50,7 @@ final class CommerceIntentConfig
'eignet',
'besser',
'besten',
'gut',
'gut für',
'gut fuer',
'passend für',

View File

@@ -40,7 +40,11 @@ final class CommerceIntentLite
);
}
if ($this->isTechnicalFactualKnowledgeQuery($prompt) && !$this->hasExplicitCommerceIntent($prompt)) {
if (
$this->isTechnicalFactualKnowledgeQuery($prompt)
&& !$this->hasExplicitCommerceIntent($prompt)
&& !$this->hasAdvisoryProductSelectionIntent($prompt)
) {
return $this->buildDetectionResult(
intent: self::NONE,
score: 0,
@@ -95,6 +99,81 @@ final class CommerceIntentLite
return $this->matchesAnyPattern($prompt, $this->config->getExplicitCommerceIntentPatterns());
}
/**
* Product advisory prompts can contain technical words such as "Messung".
*
* They must not be swallowed by the factual RAG guard when the user is
* clearly asking for a suitable shop product or device class.
*/
private function hasAdvisoryProductSelectionIntent(string $prompt): bool
{
if (!$this->containsConfiguredAdvisorySignal($prompt)) {
return false;
}
if (preg_match($this->config->getModelLikeProductPattern(), $prompt) === 1) {
return true;
}
foreach ($this->config->getStrongSignalsList() as $signal) {
$signal = mb_strtolower(trim($signal));
if ($signal === '' || $this->isNonProductCommerceSignal($signal)) {
continue;
}
if (str_contains($prompt, $signal)) {
return true;
}
}
return false;
}
private function containsConfiguredAdvisorySignal(string $prompt): bool
{
foreach ($this->config->getAdvisorySignals() as $signal) {
if ($this->containsConfiguredPhraseOrToken($prompt, $signal)) {
return true;
}
}
return false;
}
private function containsConfiguredPhraseOrToken(string $prompt, string $signal): bool
{
$signal = mb_strtolower(trim($signal));
if ($signal === '') {
return false;
}
if (preg_match('/\s/u', $signal) === 1) {
return str_contains($prompt, $signal);
}
$pattern = '/(?<![\p{L}\p{N}])' . preg_quote($signal, '/') . '(?![\p{L}\p{N}])/u';
return preg_match($pattern, $prompt) === 1;
}
private function isNonProductCommerceSignal(string $signal): bool
{
return in_array($signal, [
'shop',
'alle',
'kunde',
'online',
'kaufen',
'kostet',
'suche',
'such',
'finde',
'finden',
], true);
}
/**
* Detects factual technical knowledge questions that must stay in RAG retrieval.
*
@@ -255,7 +334,7 @@ final class CommerceIntentLite
private function applyAdvisorySignals(string $prompt, int $score, array $signals): array
{
foreach ($this->config->getAdvisorySignals() as $signal) {
if (str_contains($prompt, mb_strtolower($signal))) {
if ($this->containsConfiguredPhraseOrToken($prompt, $signal)) {
$score += $this->config->getAdvisorySignalScore();
$signals[] = $this->config->getAdvisorySignalPrefix() . $signal;
}