p43B
This commit is contained in:
@@ -0,0 +1,40 @@
|
|||||||
|
# RetrieX Patch 43B - Product Role Resolver Consolidation
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Dieser Patch reduziert doppelte PHP-Rollenlogik fuer Produktrollen, ohne fachliche YAML-Werte, Ranking-Gewichte oder Prompt-Regeln zu aendern.
|
||||||
|
|
||||||
|
## Inhalt
|
||||||
|
|
||||||
|
Neu:
|
||||||
|
|
||||||
|
- `src/Commerce/ProductRoleResolver.php`
|
||||||
|
|
||||||
|
Geaendert:
|
||||||
|
|
||||||
|
- `src/Commerce/ShopSearchService.php`
|
||||||
|
- `src/Agent/PromptBuilder.php`
|
||||||
|
|
||||||
|
Die bisher lokal duplizierte Logik fuer angefragte Produktrolle, Produktrolle und Rollenkompatibilitaet wird ueber den zentralen Resolver gefuehrt.
|
||||||
|
|
||||||
|
## Bewusst nicht geaendert
|
||||||
|
|
||||||
|
- Keine YAML-Werte geaendert
|
||||||
|
- Keine neuen Token-/Stringlisten im PHP-Core
|
||||||
|
- Keine Scoring-Gewichte geaendert
|
||||||
|
- Keine Search-Repair-Logik geaendert
|
||||||
|
- Kein Adminbereich
|
||||||
|
- Keine fachliche Runtime-Strategie geaendert
|
||||||
|
|
||||||
|
## Erwartete Wirkung
|
||||||
|
|
||||||
|
Der Effekt soll gleich bleiben. Der Patch bereitet die weitere Reduktion und generischere Rollenbehandlung vor, indem die mehrfach vorhandene Rollenlogik zentralisiert wird.
|
||||||
|
|
||||||
|
## Empfohlene Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Agent;
|
namespace App\Agent;
|
||||||
|
|
||||||
use App\Commerce\Dto\ShopProductResult;
|
use App\Commerce\Dto\ShopProductResult;
|
||||||
|
use App\Commerce\ProductRoleResolver;
|
||||||
use App\Config\LanguageCleanupConfig;
|
use App\Config\LanguageCleanupConfig;
|
||||||
use App\Config\PromptBuilderConfig;
|
use App\Config\PromptBuilderConfig;
|
||||||
use App\Context\ContextService;
|
use App\Context\ContextService;
|
||||||
@@ -20,6 +21,7 @@ final readonly class PromptBuilder
|
|||||||
private SystemPromptRepository $systemPromptRepository,
|
private SystemPromptRepository $systemPromptRepository,
|
||||||
private ModelGenerationConfigProvider $modelGenerationConfigProvider,
|
private ModelGenerationConfigProvider $modelGenerationConfigProvider,
|
||||||
private PromptBuilderConfig $config,
|
private PromptBuilderConfig $config,
|
||||||
|
private ProductRoleResolver $productRoleResolver,
|
||||||
private LanguageCleanupConfig $languageCleanupConfig,
|
private LanguageCleanupConfig $languageCleanupConfig,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -1258,112 +1260,40 @@ final readonly class PromptBuilder
|
|||||||
|
|
||||||
private function resolveRequestedProductRole(string $prompt): string
|
private function resolveRequestedProductRole(string $prompt): string
|
||||||
{
|
{
|
||||||
$normalized = mb_strtolower($this->normalizeBlockText($prompt), 'UTF-8');
|
return $this->productRoleResolver->resolveRequestedRole(
|
||||||
$hasAccessoryIntent = $this->containsAnyPromptKeyword($normalized, $this->config->getAccessoryProductRoleKeywords());
|
prompt: $prompt,
|
||||||
$hasMainDeviceIntent = $this->containsAnyPromptKeyword($normalized, $this->config->getMainDeviceRequestRoleKeywords());
|
accessoryIntentKeywords: $this->config->getAccessoryProductRoleKeywords(),
|
||||||
|
mainDeviceIntentKeywords: $this->config->getMainDeviceRequestRoleKeywords(),
|
||||||
if ($hasAccessoryIntent && !$this->hasDirectMainDeviceRequest($normalized)) {
|
directMainDeviceRequestPatterns: $this->config->getDirectMainDeviceRequestPatterns(),
|
||||||
return 'accessory_or_consumable';
|
normalize: fn(string $value): string => $this->normalizeBlockText($value)
|
||||||
}
|
);
|
||||||
|
|
||||||
if ($hasMainDeviceIntent) {
|
|
||||||
return 'main_device';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($hasAccessoryIntent) {
|
|
||||||
return 'accessory_or_consumable';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function hasDirectMainDeviceRequest(string $normalizedPrompt): bool
|
|
||||||
{
|
|
||||||
foreach ($this->config->getDirectMainDeviceRequestPatterns() as $pattern) {
|
|
||||||
if (preg_match($pattern, $normalizedPrompt) === 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveShopProductRole(ShopProductResult $product): string
|
private function resolveShopProductRole(ShopProductResult $product): string
|
||||||
{
|
{
|
||||||
$primaryRole = $this->resolveShopPrimaryProductRole($product);
|
return $this->productRoleResolver->resolveProductRole(
|
||||||
|
product: $product,
|
||||||
if ($primaryRole !== 'unknown') {
|
accessoryKeywords: $this->config->getAccessoryProductRoleKeywords(),
|
||||||
return $primaryRole;
|
deviceKeywords: $this->config->getMainDeviceProductRoleKeywords(),
|
||||||
}
|
normalize: fn(string $value): string => $this->normalizeBlockText($value),
|
||||||
|
detectAmbiguousPrimaryRole: false
|
||||||
$corpus = mb_strtolower(implode(' ', array_filter([
|
);
|
||||||
$product->name,
|
|
||||||
$product->productNumber,
|
|
||||||
$product->manufacturer,
|
|
||||||
implode(' ', $product->highlights),
|
|
||||||
$product->description,
|
|
||||||
$product->customFields,
|
|
||||||
$product->url,
|
|
||||||
])), 'UTF-8');
|
|
||||||
|
|
||||||
$isAccessory = $this->containsAnyPromptKeyword($corpus, $this->config->getAccessoryProductRoleKeywords());
|
|
||||||
$isMainDevice = $this->containsAnyPromptKeyword($corpus, $this->config->getMainDeviceProductRoleKeywords());
|
|
||||||
|
|
||||||
if ($isAccessory) {
|
|
||||||
return 'accessory_or_consumable';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isMainDevice) {
|
|
||||||
return 'main_device';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'unknown';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveShopPrimaryProductRole(ShopProductResult $product): string
|
private function resolveShopPrimaryProductRole(ShopProductResult $product): string
|
||||||
{
|
{
|
||||||
$primaryText = mb_strtolower(implode(' ', array_filter([
|
return $this->productRoleResolver->resolvePrimaryProductRole(
|
||||||
$product->name,
|
product: $product,
|
||||||
$product->url,
|
accessoryKeywords: $this->config->getAccessoryProductRoleKeywords(),
|
||||||
])), 'UTF-8');
|
deviceKeywords: $this->config->getMainDeviceProductRoleKeywords(),
|
||||||
|
normalize: fn(string $value): string => $this->normalizeBlockText($value),
|
||||||
if ($this->normalizeBlockText($primaryText) === '') {
|
detectAmbiguousPrimaryRole: false
|
||||||
return 'unknown';
|
);
|
||||||
}
|
|
||||||
|
|
||||||
$isAccessory = $this->containsAnyPromptKeyword($primaryText, $this->config->getAccessoryProductRoleKeywords());
|
|
||||||
$isMainDevice = $this->containsAnyPromptKeyword($primaryText, $this->config->getMainDeviceProductRoleKeywords());
|
|
||||||
|
|
||||||
if ($isAccessory) {
|
|
||||||
return 'accessory_or_consumable';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isMainDevice) {
|
|
||||||
return 'main_device';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'unknown';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveShopRoleCompatibility(string $requestedRole, string $inferredRole): string
|
private function resolveShopRoleCompatibility(string $requestedRole, string $inferredRole): string
|
||||||
{
|
{
|
||||||
if ($requestedRole === 'unknown' || $inferredRole === 'unknown') {
|
return $this->productRoleResolver->resolveCompatibility($requestedRole, $inferredRole);
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($requestedRole === 'main_device' && $inferredRole === 'accessory_or_consumable') {
|
|
||||||
return 'incompatible_accessory_for_main_device_request';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($requestedRole === 'accessory_or_consumable' && $inferredRole === 'main_device') {
|
|
||||||
return 'incompatible_main_device_for_accessory_request';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($inferredRole === 'ambiguous_mixed_role') {
|
|
||||||
return 'ambiguous_keep_separate';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'compatible';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ final class ShopSearchService
|
|||||||
private readonly ShopwareCriteriaBuilder $criteriaBuilder,
|
private readonly ShopwareCriteriaBuilder $criteriaBuilder,
|
||||||
private readonly StoreApiClient $storeApiClient,
|
private readonly StoreApiClient $storeApiClient,
|
||||||
private readonly ShopServiceConfig $shopConfig,
|
private readonly ShopServiceConfig $shopConfig,
|
||||||
|
private readonly ProductRoleResolver $productRoleResolver,
|
||||||
private readonly LoggerInterface $logger,
|
private readonly LoggerInterface $logger,
|
||||||
private readonly bool $enabled = true,
|
private readonly bool $enabled = true,
|
||||||
private readonly int $maxResults = 25,
|
private readonly int $maxResults = 25,
|
||||||
@@ -1330,80 +1331,24 @@ final class ShopSearchService
|
|||||||
|
|
||||||
private function isAccessoryLikeProduct(ShopProductResult $product): bool
|
private function isAccessoryLikeProduct(ShopProductResult $product): bool
|
||||||
{
|
{
|
||||||
$primaryRole = $this->resolvePrimaryShopProductRole($product);
|
return $this->productRoleResolver->isAccessoryLikeProduct(
|
||||||
|
product: $product,
|
||||||
if ($primaryRole === 'accessory_or_consumable') {
|
accessoryKeywords: $this->shopConfig->getAccessoryProductKeywords(),
|
||||||
return true;
|
deviceKeywords: $this->shopConfig->getDeviceProductKeywords(),
|
||||||
}
|
normalize: fn(string $value): string => $this->normalizeForMatching($value)
|
||||||
|
|
||||||
if ($primaryRole === 'main_device') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->containsAnyShopKeyword(
|
|
||||||
$this->buildNormalizedProductCorpus($product),
|
|
||||||
$this->shopConfig->getAccessoryProductKeywords()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isDeviceLikeProduct(ShopProductResult $product): bool
|
private function isDeviceLikeProduct(ShopProductResult $product): bool
|
||||||
{
|
{
|
||||||
$primaryRole = $this->resolvePrimaryShopProductRole($product);
|
return $this->productRoleResolver->isDeviceLikeProduct(
|
||||||
|
product: $product,
|
||||||
if ($primaryRole === 'main_device') {
|
accessoryKeywords: $this->shopConfig->getAccessoryProductKeywords(),
|
||||||
return true;
|
deviceKeywords: $this->shopConfig->getDeviceProductKeywords(),
|
||||||
}
|
normalize: fn(string $value): string => $this->normalizeForMatching($value)
|
||||||
|
|
||||||
if ($primaryRole === 'accessory_or_consumable') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->containsAnyShopKeyword(
|
|
||||||
$this->buildNormalizedProductCorpus($product),
|
|
||||||
$this->shopConfig->getDeviceProductKeywords()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolvePrimaryShopProductRole(ShopProductResult $product): string
|
|
||||||
{
|
|
||||||
$primaryText = $this->buildNormalizedPrimaryProductIdentity($product);
|
|
||||||
|
|
||||||
if ($primaryText === '') {
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
$isAccessoryLike = $this->containsAnyShopKeyword(
|
|
||||||
$primaryText,
|
|
||||||
$this->shopConfig->getAccessoryProductKeywords()
|
|
||||||
);
|
|
||||||
$isDeviceLike = $this->containsAnyShopKeyword(
|
|
||||||
$primaryText,
|
|
||||||
$this->shopConfig->getDeviceProductKeywords()
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($isAccessoryLike && !$isDeviceLike) {
|
|
||||||
return 'accessory_or_consumable';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isDeviceLike && !$isAccessoryLike) {
|
|
||||||
return 'main_device';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isAccessoryLike && $isDeviceLike) {
|
|
||||||
return 'ambiguous_mixed_role';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildNormalizedPrimaryProductIdentity(ShopProductResult $product): string
|
|
||||||
{
|
|
||||||
return $this->normalizeForMatching(implode(' ', array_filter([
|
|
||||||
$product->name,
|
|
||||||
$product->url,
|
|
||||||
])));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $keywords
|
* @param string[] $keywords
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user