fix 4 optimize systemMsg and model default config values

This commit is contained in:
team 1
2026-04-24 19:52:35 +02:00
parent 2fd7977693
commit 9312562dd8
6 changed files with 313 additions and 9 deletions

View File

@@ -205,13 +205,8 @@ final readonly class CommerceQueryParser
) ?? $text;
}
if ($brand !== null && $brand !== '' && !$this->isBrandPartOfModelPhrase($prompt, $brand)) {
$text = preg_replace(
$this->config->buildExactTokenRemovalPattern($brand),
' ',
$text
) ?? $text;
}
// Keep known brand terms in the shop search text because the Store API
// request does not add a separate manufacturer filter.
if ($priceMin !== null || $priceMax !== null) {
foreach ($this->config->getPriceRemovalPatterns($this->intentConfig) as $pattern) {

View File

@@ -188,6 +188,14 @@ final readonly class SearchRepairService
$modelCandidates = $this->extractModelCandidates($combinedText);
$accessoryCandidates = $this->extractAccessoryCandidates($combinedText);
$requestedAccessoryCodes = $this->extractRequestedAccessoryCodes($prompt . "\n" . $primaryQuery);
if ($requestedAccessoryCodes !== []) {
$accessoryCandidates = $this->filterAccessoryCandidatesByRequestedCodes(
accessoryCandidates: $accessoryCandidates,
requestedCodes: $requestedAccessoryCodes
);
}
$topPrimaryName = $primaryShopResults[0]->name ?? '';
$topPrimaryProductNumber = $primaryShopResults[0]->productNumber ?? null;
@@ -195,6 +203,18 @@ final readonly class SearchRepairService
$queries = [];
$queries = array_merge(
$queries,
$this->buildFocusedModelAccessoryQueries(
prompt: $prompt,
primaryQuery: $primaryQuery,
knowledgeText: $knowledgeText,
modelCandidates: $modelCandidates,
accessoryCandidates: $accessoryCandidates,
requestedAccessoryCodes: $requestedAccessoryCodes
)
);
if ($topPrimaryPhrase !== '' && $this->containsModelLikePhrase($topPrimaryPhrase)) {
$queries[] = $topPrimaryPhrase;
} elseif ($topPrimaryName !== '' && $this->containsModelLikePhrase($topPrimaryName)) {
@@ -293,6 +313,7 @@ final readonly class SearchRepairService
foreach ($matches[1] ?? [] as $candidate) {
$candidate = $this->sanitizeQuery($candidate);
$candidate = $this->reduceToSpecificModelCandidate($candidate);
if ($candidate === '') {
continue;
@@ -377,6 +398,245 @@ final readonly class SearchRepairService
return $score;
}
/**
* @param string[] $modelCandidates
* @param string[] $accessoryCandidates
* @param string[] $requestedAccessoryCodes
* @return string[]
*/
private function buildFocusedModelAccessoryQueries(
string $prompt,
string $primaryQuery,
string $knowledgeText,
array $modelCandidates,
array $accessoryCandidates,
array $requestedAccessoryCodes
): array {
if ($requestedAccessoryCodes === []) {
return [];
}
$queries = [];
$models = $this->filterModelCandidatesByRequestedAccessoryCodes(
prompt: $prompt . "\n" . $primaryQuery,
knowledgeText: $knowledgeText,
modelCandidates: $modelCandidates,
requestedCodes: $requestedAccessoryCodes
);
if ($models === []) {
return [];
}
$accessories = $accessoryCandidates;
if ($accessories === []) {
foreach ($requestedAccessoryCodes as $code) {
$accessories[] = 'Indikator ' . $code;
}
}
foreach ($models as $model) {
foreach ($accessories as $accessory) {
if (!$this->candidateMatchesRequestedAccessoryCodes($accessory, $requestedAccessoryCodes)) {
continue;
}
$queries[] = trim($model . ' ' . $accessory);
}
}
return array_values(array_unique(array_filter(
array_map(fn(string $query): string => $this->sanitizeQuery($query), $queries),
static fn(string $query): bool => $query !== ''
)));
}
/**
* @return string[]
*/
private function extractRequestedAccessoryCodes(string $text): array
{
$codes = [];
if (preg_match_all('/\b(?:indikator|indicator|reagenz|reagent)\s*([A-Za-z]{0,3}\s*\d{1,5}[A-Za-z0-9\-]*)\b/iu', $text, $matches) !== false) {
foreach ($matches[1] ?? [] as $code) {
$normalized = $this->normalizeAccessoryCode((string) $code);
if ($normalized !== '') {
$codes[$normalized] = $normalized;
}
}
}
return array_values($codes);
}
/**
* @param string[] $accessoryCandidates
* @param string[] $requestedCodes
* @return string[]
*/
private function filterAccessoryCandidatesByRequestedCodes(array $accessoryCandidates, array $requestedCodes): array
{
return array_values(array_filter(
$accessoryCandidates,
fn(string $candidate): bool => $this->candidateMatchesRequestedAccessoryCodes($candidate, $requestedCodes)
));
}
/**
* @param string[] $modelCandidates
* @param string[] $requestedCodes
* @return string[]
*/
private function filterModelCandidatesByRequestedAccessoryCodes(
string $prompt,
string $knowledgeText,
array $modelCandidates,
array $requestedCodes
): array {
$models = [];
$normalizedPrompt = $this->normalizeForRepairMatching($prompt);
foreach ($modelCandidates as $candidate) {
$candidate = $this->reduceToSpecificModelCandidate($candidate);
if ($candidate === '') {
continue;
}
$normalizedCandidate = $this->normalizeForRepairMatching($candidate);
$isPromptAnchored = $normalizedCandidate !== '' && str_contains($normalizedPrompt, $normalizedCandidate);
foreach ($requestedCodes as $code) {
if ($isPromptAnchored || $this->modelAppearsNearAccessoryCode($knowledgeText, $candidate, $code)) {
$models[$candidate] = $candidate;
break;
}
}
}
return array_values($models);
}
private function candidateMatchesRequestedAccessoryCodes(string $candidate, array $requestedCodes): bool
{
$normalizedCandidate = $this->normalizeForRepairMatching($candidate);
$compactCandidate = preg_replace('/\s+/u', '', $normalizedCandidate) ?? $normalizedCandidate;
foreach ($requestedCodes as $code) {
$normalizedCode = $this->normalizeAccessoryCode($code);
if ($normalizedCode === '') {
continue;
}
$pattern = '/\b' . preg_quote($normalizedCode, '/') . '\b/u';
if (preg_match($pattern, $normalizedCandidate) === 1 || preg_match($pattern, $compactCandidate) === 1) {
return true;
}
}
return false;
}
private function modelAppearsNearAccessoryCode(string $knowledgeText, string $model, string $code): bool
{
$normalizedText = $this->normalizeForRepairMatching($knowledgeText);
$normalizedModel = $this->normalizeForRepairMatching($model);
$normalizedCode = $this->normalizeAccessoryCode($code);
if ($normalizedText === '' || $normalizedModel === '' || $normalizedCode === '') {
return false;
}
$modelPositions = $this->findNeedlePositions($normalizedText, $normalizedModel);
if ($modelPositions === []) {
return false;
}
$codeNeedles = [
'indikator ' . $normalizedCode,
'indicator ' . $normalizedCode,
'indikatortyp ' . $normalizedCode,
$normalizedCode,
];
foreach ($codeNeedles as $needle) {
foreach ($this->findNeedlePositions($normalizedText, $needle) as $codePos) {
foreach ($modelPositions as $modelPos) {
if (abs($codePos - $modelPos) <= 1600) {
return true;
}
}
}
}
return false;
}
/**
* @return int[]
*/
private function findNeedlePositions(string $haystack, string $needle): array
{
if ($haystack === '' || $needle === '') {
return [];
}
$positions = [];
$offset = 0;
while (($position = mb_strpos($haystack, $needle, $offset, 'UTF-8')) !== false) {
$positions[] = $position;
$offset = $position + max(1, strlen($needle));
}
return $positions;
}
private function reduceToSpecificModelCandidate(string $candidate): string
{
$candidate = $this->sanitizeQuery($candidate);
if ($candidate === '') {
return '';
}
$patterns = [
'/\b(Testomat(?:®)?\s+(?:\d{3,4}|EVO(?:\s+[A-ZÄÖÜ]{1,8})?|ECO(?:[-\s]?(?:PLUS|C))?|DUO(?:\s+\d{3,4})?|LAB(?:\s+[A-ZÄÖÜ]{1,8})?))\b/iu',
'/\b(Horiba\s+LAQUA\s+[A-Z0-9\-]+)\b/iu',
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $candidate, $matches) === 1) {
return $this->sanitizeQuery((string) ($matches[1] ?? ''));
}
}
if (preg_match('/\b(?:indikator|indicator|reagenz|reagent|verfuegbarkeit|verfügbarkeit|shop)\b/iu', $candidate) === 1) {
return '';
}
return $candidate;
}
private function normalizeAccessoryCode(string $code): string
{
$code = $this->normalizeForRepairMatching($code);
$code = preg_replace('/\s+/u', '', $code) ?? $code;
return trim($code);
}
private function normalizeForRepairMatching(string $value): string
{
$value = mb_strtolower(trim($value), 'UTF-8');
$value = str_replace('®', '', $value);
$value = preg_replace('/[^\p{L}\p{N}]+/u', ' ', $value) ?? $value;
$value = preg_replace($this->config->getWhitespaceCollapsePattern(), ' ', $value) ?? $value;
return trim($value);
}
private function asksForBundleOrAccessory(string $prompt): bool
{
return preg_match($this->config->getAccessoryOrBundlePattern(), $prompt) === 1;