fix p52
This commit is contained in:
@@ -513,6 +513,33 @@ parameters:
|
||||
terms:
|
||||
- puffer
|
||||
- kalibrierpuffer
|
||||
primary_identity_repair:
|
||||
enabled: true
|
||||
min_query_tokens_after_cleanup: 2
|
||||
# Only used for a retry query when the direct-result guard would
|
||||
# otherwise suppress all shop results. Keep product words and context
|
||||
# such as brand/pH/Redox, but remove target-device wording that can
|
||||
# push Shopware ranking toward devices instead of the requested
|
||||
# accessory/consumable.
|
||||
stop_terms:
|
||||
- messgerät
|
||||
- messgeraet
|
||||
- messgeräte
|
||||
- messgeraete
|
||||
- messgeräten
|
||||
- messgeraeten
|
||||
- gerät
|
||||
- geraet
|
||||
- geräte
|
||||
- geraete
|
||||
- geräten
|
||||
- geraeten
|
||||
- handmessgerät
|
||||
- handmessgeraet
|
||||
- handmessgeräte
|
||||
- handmessgeraete
|
||||
- messkoffer
|
||||
- koffer
|
||||
|
||||
length_sort:
|
||||
enabled: true
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# RetrieX Patch p52 - Direct Product Primary Identity Repair
|
||||
|
||||
## Ziel
|
||||
|
||||
Korrigiert die p51-Regression bei direkten Shop-Produktlisten, bei der echte Store-API-Treffer intern auf `Shop-Treffer: 0` fallen konnten.
|
||||
|
||||
Betroffener Fall:
|
||||
|
||||
```text
|
||||
welche neomeris puffer gibt es für Kalibrierung von pH-Messgeräten
|
||||
```
|
||||
|
||||
Die angezeigte Shopquery bleibt korrekt und soll nicht wieder mit Geräte-/Darstellungslogik vermischt werden:
|
||||
|
||||
```text
|
||||
neomeris puffer kalibrierung ph messgeräten
|
||||
```
|
||||
|
||||
Wenn die Store API mit dieser Suche Treffer liefert, darf RetrieX diese nicht intern durch den direkten Produkt-Guard verlieren.
|
||||
|
||||
## Ursache
|
||||
|
||||
p51 hat den Direct-Product-Guard bewusst verschärft: Bei direkten Produktlisten sollten nur noch Produkte ausgegeben werden, deren primäre Identität, also Produktname/URL, zur angefragten Produktart passt. Das verhindert Geräte/Koffer als Ersatztreffer für Verbrauchsmaterialien.
|
||||
|
||||
Der Guard war aber zu hart, wenn die vorliegende Shop-Trefferliste bereits durch Shopware-Ranking/Repair/Guardrails in Richtung Geräte verschoben war. Dann konnte der Guard alle Treffer entfernen und der Status fiel auf:
|
||||
|
||||
```text
|
||||
Shop-Treffer: 0
|
||||
```
|
||||
|
||||
obwohl die Store API für die Suchquery echte Produktkandidaten liefert.
|
||||
|
||||
## Änderung
|
||||
|
||||
p52 ergänzt einen kleinen, defensiven Retry nur für diesen Fall:
|
||||
|
||||
1. Die normale Shopquery bleibt unverändert.
|
||||
2. Der p51-Primary-Identity-Guard läuft weiterhin.
|
||||
3. Nur wenn der Guard aus einer nicht-leeren Shopliste **alles** entfernt, wird eine bereinigte Repair-Query gebaut.
|
||||
4. Diese Repair-Query entfernt nur konfigurierte Zielgeräte-Wörter wie `Messgerät`, `Gerät`, `Koffer` und behält Produktart/Brand/Kontext wie `neomeris`, `puffer`, `ph`.
|
||||
5. Die Repair-Ergebnisse laufen erneut durch denselben Primary-Identity-Guard.
|
||||
6. Wenn danach weiterhin nichts passt, bleibt die saubere Keine-Treffer-Antwort erhalten; es werden keine Geräte als Ersatz ausgegeben.
|
||||
|
||||
Beispiel Repair-Query:
|
||||
|
||||
```text
|
||||
neomeris puffer kalibrierung ph
|
||||
```
|
||||
|
||||
statt die Anfrage als Geräte-/Koffer-Liste zu behandeln.
|
||||
|
||||
## Wichtig
|
||||
|
||||
- Keine Änderung an der angezeigten primären Shopquery.
|
||||
- Keine Änderung an Shopware-Kriterien.
|
||||
- Kein Neomeris-/pH-/Redox-Sonderfall im PHP-Core.
|
||||
- Die entfernten Zielgeräte-Wörter sind YAML-konfiguriert unter `shop_prompt.direct_result_guard.primary_identity_repair.stop_terms`.
|
||||
- p51 bleibt erhalten: Geräte/Koffer werden nicht als Pufferliste ausgegeben.
|
||||
- p48, p45/p46 und der Artikelnummer-Fix p47 bleiben unverändert.
|
||||
|
||||
## Geänderte Dateien
|
||||
|
||||
```text
|
||||
config/retriex/agent.yaml
|
||||
src/Agent/AgentRunner.php
|
||||
src/Config/AgentRunnerConfig.php
|
||||
patch_history/RETRIEX_PATCH_52_DIRECT_PRODUCT_PRIMARY_IDENTITY_REPAIR_README.md
|
||||
```
|
||||
|
||||
## Erwartetes Verhalten
|
||||
|
||||
```text
|
||||
welche neomeris puffer gibt es für Kalibrierung von pH-Messgeräten
|
||||
```
|
||||
|
||||
soll wieder echte Pufferprodukte ausgeben, sofern sie von der Store API geliefert werden, zum Beispiel:
|
||||
|
||||
```text
|
||||
Neomeris pH-Pufferlösung pH 4.01
|
||||
Neomeris pH-Pufferlösung pH 7.00
|
||||
Neomeris pH-Pufferlösung pH 9.21
|
||||
Neomeris pH-Pufferlösung pH 10.01
|
||||
Neomeris Redox-Pufferlösung 200 mV / 475 mV / 650 mV
|
||||
```
|
||||
|
||||
Es sollen nicht mehr nur Geräte/Koffer erscheinen, und es soll nicht fälschlich `Shop-Treffer: 0` entstehen, wenn die Store API passende Pufferprodukte liefert.
|
||||
|
||||
## Lokale Checks
|
||||
|
||||
Ausgeführt:
|
||||
|
||||
```bash
|
||||
php -l src/Agent/AgentRunner.php
|
||||
php -l src/Config/AgentRunnerConfig.php
|
||||
python3 - <<'PY'
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
for f in ['config/retriex/agent.yaml', 'config/retriex/vocabulary.yaml']:
|
||||
yaml.safe_load(Path(f).read_text())
|
||||
print('YAML ok')
|
||||
PY
|
||||
```
|
||||
|
||||
`bin/console` wurde lokal nicht ausgeführt, weil `vendor/` im ZIP nicht enthalten ist.
|
||||
|
||||
## Nach dem Einspielen prüfen
|
||||
|
||||
```bash
|
||||
bin/console cache:clear
|
||||
bin/console mto:agent:config:validate
|
||||
bin/console mto:agent:regression:test
|
||||
bin/console mto:agent:config:audit-source --details
|
||||
bin/console mto:agent:config:audit-patterns --details
|
||||
```
|
||||
@@ -36,7 +36,7 @@ a {
|
||||
text-decoration: none;
|
||||
}
|
||||
li {
|
||||
margin-bottom: .5rem;
|
||||
margin-bottom: .125rem;
|
||||
}
|
||||
a:hover {
|
||||
color: #FFF;
|
||||
|
||||
@@ -486,12 +486,29 @@ final readonly class AgentRunner
|
||||
}
|
||||
}
|
||||
|
||||
$shopResults = $repairPayload['results'];
|
||||
$shopResults = $this->guardDirectProductShopResults($prompt, $shopSearchQuery, $shopResults);
|
||||
$unguardedShopResults = $repairPayload['results'];
|
||||
$shopResults = $this->guardDirectProductShopResults($prompt, $shopSearchQuery, $unguardedShopResults);
|
||||
|
||||
$directIdentityRepairPayload = $this->repairEmptyDirectProductPrimaryIdentityResults(
|
||||
prompt: $prompt,
|
||||
userId: $userId,
|
||||
commerceIntent: $commerceIntent,
|
||||
shopSearchQuery: $shopSearchQuery,
|
||||
unguardedShopResults: $unguardedShopResults,
|
||||
guardedShopResults: $shopResults
|
||||
);
|
||||
|
||||
if ($directIdentityRepairPayload['results'] !== null) {
|
||||
$shopResults = $directIdentityRepairPayload['results'];
|
||||
}
|
||||
|
||||
$shopResults = $this->sortShopResultsForLengthRequest($prompt, $shopSearchQuery, $shopResults);
|
||||
$attemptedShopRepair = $repairPayload['attemptedRepair'];
|
||||
$usedShopRepair = $repairPayload['usedRepair'];
|
||||
$shopRepairQueries = $repairPayload['repairQueries'];
|
||||
$attemptedShopRepair = $repairPayload['attemptedRepair'] || $directIdentityRepairPayload['attemptedRepair'];
|
||||
$usedShopRepair = $repairPayload['usedRepair'] || $directIdentityRepairPayload['usedRepair'];
|
||||
$shopRepairQueries = array_values(array_unique(array_merge(
|
||||
$repairPayload['repairQueries'],
|
||||
$directIdentityRepairPayload['repairQueries']
|
||||
)));
|
||||
|
||||
if (!$primaryShopSearchHadSystemFailure) {
|
||||
yield $this->systemMsg(
|
||||
@@ -3081,6 +3098,157 @@ final readonly class AgentRunner
|
||||
return array_values(array_merge($primaryMatches, $corpusMatches));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShopProductResult[] $unguardedShopResults
|
||||
* @param ShopProductResult[] $guardedShopResults
|
||||
* @return array{results: array|null, attemptedRepair: bool, usedRepair: bool, repairQueries: string[]}
|
||||
*/
|
||||
private function repairEmptyDirectProductPrimaryIdentityResults(
|
||||
string $prompt,
|
||||
string $userId,
|
||||
string $commerceIntent,
|
||||
string $shopSearchQuery,
|
||||
array $unguardedShopResults,
|
||||
array $guardedShopResults
|
||||
): array {
|
||||
$emptyResult = [
|
||||
'results' => null,
|
||||
'attemptedRepair' => false,
|
||||
'usedRepair' => false,
|
||||
'repairQueries' => [],
|
||||
];
|
||||
|
||||
if (
|
||||
$guardedShopResults !== []
|
||||
|| $unguardedShopResults === []
|
||||
|| !$this->agentRunnerConfig->isDirectShopResultGuardPrimaryIdentityRepairEnabled()
|
||||
) {
|
||||
return $emptyResult;
|
||||
}
|
||||
|
||||
$requestedTerms = $this->extractRequestedDirectProductTerms($prompt, $shopSearchQuery);
|
||||
if ($requestedTerms === []) {
|
||||
return $emptyResult;
|
||||
}
|
||||
|
||||
$repairQuery = $this->buildDirectProductPrimaryIdentityRepairQuery(
|
||||
shopSearchQuery: $shopSearchQuery,
|
||||
requestedTerms: $requestedTerms
|
||||
);
|
||||
|
||||
if ($repairQuery === '' || $this->normalizeShopQueryForComparison($repairQuery) === $this->normalizeShopQueryForComparison($shopSearchQuery)) {
|
||||
return $emptyResult;
|
||||
}
|
||||
|
||||
$this->agentLogger->info('Direct product primary identity guard retrying with cleaned repair query', [
|
||||
'userId' => $userId,
|
||||
'commerceIntent' => $commerceIntent,
|
||||
'prompt' => $prompt,
|
||||
'shopSearchQuery' => $shopSearchQuery,
|
||||
'repairQuery' => $repairQuery,
|
||||
'unguardedShopResultsCount' => count($unguardedShopResults),
|
||||
'requestedTerms' => $requestedTerms,
|
||||
]);
|
||||
|
||||
$repairResults = $this->searchShop(
|
||||
$repairQuery,
|
||||
$commerceIntent,
|
||||
$userId,
|
||||
''
|
||||
);
|
||||
|
||||
if ($this->shopSearchService->hadLastSearchSystemFailure()) {
|
||||
return [
|
||||
'results' => null,
|
||||
'attemptedRepair' => true,
|
||||
'usedRepair' => false,
|
||||
'repairQueries' => [$repairQuery],
|
||||
];
|
||||
}
|
||||
|
||||
$guardedRepairResults = $this->guardDirectProductShopResults($prompt, $repairQuery, $repairResults);
|
||||
|
||||
if ($guardedRepairResults === []) {
|
||||
$this->agentLogger->info('Direct product primary identity repair finished without matching products', [
|
||||
'userId' => $userId,
|
||||
'commerceIntent' => $commerceIntent,
|
||||
'prompt' => $prompt,
|
||||
'shopSearchQuery' => $shopSearchQuery,
|
||||
'repairQuery' => $repairQuery,
|
||||
'repairResultsCount' => count($repairResults),
|
||||
]);
|
||||
|
||||
return [
|
||||
'results' => null,
|
||||
'attemptedRepair' => true,
|
||||
'usedRepair' => false,
|
||||
'repairQueries' => [$repairQuery],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'results' => $guardedRepairResults,
|
||||
'attemptedRepair' => true,
|
||||
'usedRepair' => true,
|
||||
'repairQueries' => [$repairQuery],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $requestedTerms
|
||||
*/
|
||||
private function buildDirectProductPrimaryIdentityRepairQuery(string $shopSearchQuery, array $requestedTerms): string
|
||||
{
|
||||
$tokens = $this->tokenizeShopQueryCandidate($shopSearchQuery);
|
||||
if ($tokens === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$stopTokens = [];
|
||||
foreach ($this->agentRunnerConfig->getDirectShopResultGuardPrimaryIdentityRepairStopTerms() as $term) {
|
||||
foreach ($this->tokenizeShopQueryCandidate($term) as $token) {
|
||||
$stopTokens[$token] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$requestedTokens = [];
|
||||
foreach ($requestedTerms as $term) {
|
||||
foreach ($this->tokenizeShopQueryCandidate($term) as $token) {
|
||||
$requestedTokens[$token] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$kept = [];
|
||||
foreach ($tokens as $token) {
|
||||
if (isset($stopTokens[$token]) && !isset($requestedTokens[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($kept[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$kept[$token] = $token;
|
||||
}
|
||||
|
||||
foreach (array_keys($requestedTokens) as $requestedToken) {
|
||||
if (!isset($kept[$requestedToken])) {
|
||||
$kept[$requestedToken] = $requestedToken;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($kept) < max(1, $this->agentRunnerConfig->getDirectShopResultGuardPrimaryIdentityRepairMinQueryTokens())) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return trim(implode(' ', array_values($kept)));
|
||||
}
|
||||
|
||||
private function normalizeShopQueryForComparison(string $query): string
|
||||
{
|
||||
return trim(implode(' ', $this->tokenizeShopQueryCandidate($query)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShopProductResult[] $shopResults
|
||||
* @return ShopProductResult[]
|
||||
|
||||
@@ -301,6 +301,21 @@ final class AgentRunnerConfig
|
||||
throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be numeric.', $key));
|
||||
}
|
||||
|
||||
private function getOptionalInt(string $key, int $default): int
|
||||
{
|
||||
$value = $this->optionalValue($key);
|
||||
|
||||
if ($value === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be numeric.', $key));
|
||||
}
|
||||
|
||||
private function getRequiredBool(string $key): bool
|
||||
{
|
||||
$value = $this->requiredValue($key);
|
||||
@@ -1158,6 +1173,24 @@ final class AgentRunnerConfig
|
||||
return $this->getOptionalStringList('shop_prompt.direct_result_guard.compound_prefix_match.terms');
|
||||
}
|
||||
|
||||
public function isDirectShopResultGuardPrimaryIdentityRepairEnabled(): bool
|
||||
{
|
||||
return $this->getOptionalBool('shop_prompt.direct_result_guard.primary_identity_repair.enabled', true);
|
||||
}
|
||||
|
||||
public function getDirectShopResultGuardPrimaryIdentityRepairMinQueryTokens(): int
|
||||
{
|
||||
return $this->getOptionalInt('shop_prompt.direct_result_guard.primary_identity_repair.min_query_tokens_after_cleanup', 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getDirectShopResultGuardPrimaryIdentityRepairStopTerms(): array
|
||||
{
|
||||
return $this->getOptionalStringList('shop_prompt.direct_result_guard.primary_identity_repair.stop_terms');
|
||||
}
|
||||
|
||||
public function isShopResultLengthSortEnabled(): bool
|
||||
{
|
||||
return $this->getRequiredBool('shop_prompt.length_sort.enabled');
|
||||
|
||||
Reference in New Issue
Block a user