move intent an config value into config files

This commit is contained in:
team2
2026-04-23 21:49:54 +02:00
parent 87417febf4
commit fce44e971d
17 changed files with 1937 additions and 1133 deletions

View File

@@ -22,157 +22,75 @@ final class CommerceIntentLite
*/
public function detect(string $originalPrompt): array
{
$p = mb_strtolower(trim($originalPrompt));
$prompt = mb_strtolower(trim($originalPrompt));
if ($p === '') {
return [
'intent' => self::NONE,
'score' => 0,
'signals' => [],
];
if ($prompt === '') {
return $this->buildDetectionResult(
intent: self::NONE,
score: 0,
signals: []
);
}
// Block support / diagnostic questions from entering the commerce flow
// unless the prompt also contains very explicit purchase / shop intent.
if ($this->isSupportOrDiagnosticQuery($p) && !$this->hasExplicitCommerceIntent($p)) {
return [
'intent' => self::NONE,
'score' => 0,
'signals' => ['support_or_diagnostic'],
];
if ($this->isSupportOrDiagnosticQuery($prompt) && !$this->hasExplicitCommerceIntent($prompt)) {
return $this->buildDetectionResult(
intent: self::NONE,
score: 0,
signals: [$this->config->getSupportOrDiagnosticSignalLabel()]
);
}
$score = 0;
$signals = [];
$strongSignals = $this->config->getStrongSignalsList();
foreach ($strongSignals as $signal) {
if (str_contains($p, mb_strtolower($signal))) {
$score += 3;
$signals[] = $signal;
}
}
// Treat long numeric identifiers as stronger product-number-like signals.
// This avoids over-triggering commerce purely because a model name contains
// a short number such as "808" in support questions.
if (preg_match('/\b\d{4,10}\b/u', $p) === 1) {
$score += 2;
$signals[] = 'sku';
}
$pricePattern = $this->config->getPricePattern();
if (preg_match('/\b\d+(?:[.,]\d+)?\s*(' . $pricePattern . ')\b/u', $p) === 1) {
$score += 2;
$signals[] = 'price';
}
$sizePattern = $this->config->getSizePattern();
if (preg_match('/\b(' . $sizePattern . ')\s*[a-z0-9.-]+\b/u', $p) === 1) {
$score += 2;
$signals[] = 'size';
}
$sizeTokenPattern = $this->config->getSizeTokenPattern();
if (preg_match('/\b(' . $sizeTokenPattern . ')\b/u', $p) === 1) {
$score += 1;
$signals[] = 'size_token';
}
$colorPattern = $this->config->getColorPattern();
if (preg_match('/\b(' . $colorPattern . ')\b/u', $p) === 1) {
$score += 1;
$signals[] = 'color';
}
$advisorySignals = $this->config->getAdvisorySignals();
foreach ($advisorySignals as $signal) {
if (str_contains($p, mb_strtolower($signal))) {
$score += 1;
$signals[] = 'advisory:' . $signal;
}
}
[$score, $signals] = $this->applyStrongSignals($prompt, $score, $signals);
[$score, $signals] = $this->applySkuSignal($prompt, $score, $signals);
[$score, $signals] = $this->applyPriceSignal($prompt, $score, $signals);
[$score, $signals] = $this->applySizeSignal($prompt, $score, $signals);
[$score, $signals] = $this->applySizeTokenSignal($prompt, $score, $signals);
[$score, $signals] = $this->applyColorSignal($prompt, $score, $signals);
[$score, $signals] = $this->applyAdvisorySignals($prompt, $score, $signals);
$signals = array_values(array_unique($signals));
if ($score >= 3) {
return [
'intent' => self::PRODUCT_SEARCH,
'score' => $score,
'signals' => $signals,
];
if ($score >= $this->config->getProductSearchMinScore()) {
return $this->buildDetectionResult(
intent: self::PRODUCT_SEARCH,
score: $score,
signals: $signals
);
}
if ($score >= 2) {
return [
'intent' => self::ADVISORY_PRODUCT_SEARCH,
'score' => $score,
'signals' => $signals,
];
if ($score >= $this->config->getAdvisoryProductSearchMinScore()) {
return $this->buildDetectionResult(
intent: self::ADVISORY_PRODUCT_SEARCH,
score: $score,
signals: $signals
);
}
return [
'intent' => self::NONE,
'score' => $score,
'signals' => $signals,
];
return $this->buildDetectionResult(
intent: self::NONE,
score: $score,
signals: $signals
);
}
private function isSupportOrDiagnosticQuery(string $prompt): bool
{
$patterns = [
'/\bfehler\b/u',
'/\bfehlercode\b/u',
'/\berror\b/u',
'/\bstörung\b/u',
'/\bstoerung\b/u',
'/\balarm\b/u',
'/\bstörungsmeldung\b/u',
'/\bstoerungsmeldung\b/u',
'/\bmeldung\b/u',
'/\bwarnung\b/u',
'/\bwarncode\b/u',
'/\bcode\b/u',
'/\bwas bedeutet\b/u',
'/\bwarum\b/u',
'/\bblinkt\b/u',
'/\bzeigt\b/u',
'/\bzeigt an\b/u',
'/\bursache\b/u',
'/\bdiagnose\b/u',
'/\bservicefall\b/u',
'/\bproblem\b/u',
'/\bstörung beheben\b/u',
'/\bstoerung beheben\b/u',
'/\be\d{1,3}\b/u',
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $prompt) === 1) {
return true;
}
}
return false;
return $this->matchesAnyPattern($prompt, $this->config->getSupportDiagnosticPatterns());
}
private function hasExplicitCommerceIntent(string $prompt): bool
{
$patterns = [
'/\bshop\b/u',
'/\bpreis\b/u',
'/\bkosten\b/u',
'/\bkostet\b/u',
'/\bkaufen\b/u',
'/\bbestellen\b/u',
'/\bprodukt\b/u',
'/\bartikel\b/u',
'/\bsku\b/u',
'/\bonline\b/u',
];
return $this->matchesAnyPattern($prompt, $this->config->getExplicitCommerceIntentPatterns());
}
/**
* @param string[] $patterns
*/
private function matchesAnyPattern(string $prompt, array $patterns): bool
{
foreach ($patterns as $pattern) {
if (preg_match($pattern, $prompt) === 1) {
return true;
@@ -181,4 +99,119 @@ final class CommerceIntentLite
return false;
}
/**
* @param string[] $signals
* @return array{0:int,1:string[]}
*/
private function applyStrongSignals(string $prompt, int $score, array $signals): array
{
foreach ($this->config->getStrongSignalsList() as $signal) {
if (str_contains($prompt, mb_strtolower($signal))) {
$score += $this->config->getStrongSignalScore();
$signals[] = $signal;
}
}
return [$score, $signals];
}
/**
* @param string[] $signals
* @return array{0:int,1:string[]}
*/
private function applySkuSignal(string $prompt, int $score, array $signals): array
{
if (preg_match($this->config->getSkuLikePattern(), $prompt) === 1) {
$score += $this->config->getSkuSignalScore();
$signals[] = $this->config->getSkuSignalLabel();
}
return [$score, $signals];
}
/**
* @param string[] $signals
* @return array{0:int,1:string[]}
*/
private function applyPriceSignal(string $prompt, int $score, array $signals): array
{
if (preg_match($this->config->getPriceValuePattern(), $prompt) === 1) {
$score += $this->config->getPriceSignalScore();
$signals[] = $this->config->getPriceSignalLabel();
}
return [$score, $signals];
}
/**
* @param string[] $signals
* @return array{0:int,1:string[]}
*/
private function applySizeSignal(string $prompt, int $score, array $signals): array
{
if (preg_match($this->config->getSizeValuePattern(), $prompt) === 1) {
$score += $this->config->getSizeSignalScore();
$signals[] = $this->config->getSizeSignalLabel();
}
return [$score, $signals];
}
/**
* @param string[] $signals
* @return array{0:int,1:string[]}
*/
private function applySizeTokenSignal(string $prompt, int $score, array $signals): array
{
if (preg_match($this->config->getSizeTokenValuePattern(), $prompt) === 1) {
$score += $this->config->getSizeTokenSignalScore();
$signals[] = $this->config->getSizeTokenSignalLabel();
}
return [$score, $signals];
}
/**
* @param string[] $signals
* @return array{0:int,1:string[]}
*/
private function applyColorSignal(string $prompt, int $score, array $signals): array
{
if (preg_match($this->config->getColorValuePattern(), $prompt) === 1) {
$score += $this->config->getColorSignalScore();
$signals[] = $this->config->getColorSignalLabel();
}
return [$score, $signals];
}
/**
* @param string[] $signals
* @return array{0:int,1:string[]}
*/
private function applyAdvisorySignals(string $prompt, int $score, array $signals): array
{
foreach ($this->config->getAdvisorySignals() as $signal) {
if (str_contains($prompt, mb_strtolower($signal))) {
$score += $this->config->getAdvisorySignalScore();
$signals[] = $this->config->getAdvisorySignalPrefix() . $signal;
}
}
return [$score, $signals];
}
/**
* @param string[] $signals
* @return array{intent:string, score:int, signals:string[]}
*/
private function buildDetectionResult(string $intent, int $score, array $signals): array
{
return [
'intent' => $intent,
'score' => $score,
'signals' => $signals,
];
}
}