This commit is contained in:
team 1
2026-05-05 14:17:54 +02:00
parent 913616a3df
commit 10a3a09a63
5 changed files with 469 additions and 120 deletions

View File

@@ -30,6 +30,7 @@ final readonly class AgentRunner
private RetrieverInterface $retriever,
private ShopSearchService $shopSearchService,
private SearchRepairService $searchRepairService,
private ReferenceAnchorExtractor $referenceAnchorExtractor,
private CommerceIntentLite $commerceIntentLite,
private OllamaClient $ollamaClient,
private LoggerInterface $agentLogger,
@@ -1135,7 +1136,7 @@ final readonly class AgentRunner
$history = $this->contextService->buildUserContextWithinBudget($userId, 3000);
$previousQuestions = $this->extractRecentUserQuestions($history, 2);
$referenceAnchors = $this->extractLatestAssistantReferenceAnchors($history);
$referenceAnchors = $this->referenceAnchorExtractor->extractLatestAssistantReferenceAnchors($history);
if ($previousQuestions === [] && $referenceAnchors === []) {
return $prompt;
@@ -1232,74 +1233,6 @@ final readonly class AgentRunner
return array_slice($questions, -$limit);
}
/**
* Extracts stable reference anchors from the latest assistant answer.
*
* These anchors are only used to resolve follow-up references such as
* "der Wert" or "welcher Indikator". They are not factual evidence for
* the final answer. To avoid propagating wrong earlier answers, only the
* first explicit product-model reference and the first explicit measurement value
* are kept. Indicator names, reagent codes, prices, URLs and product
* numbers are intentionally ignored here.
*
* @return string[]
*/
private function extractLatestAssistantReferenceAnchors(string $history): array
{
$turn = $this->extractLatestHistoryTurn($history);
if ($turn === '') {
return [];
}
$answer = preg_replace($this->agentRunnerConfig->getFollowUpHistoryQuestionStripPattern(), '', $turn, 1) ?? '';
$answer = trim($answer);
if ($answer === '') {
return [];
}
$anchors = [];
$model = $this->extractFirstProductModelAnchor($answer);
if ($model !== '') {
$anchors[] = $model;
}
$hardnessValue = $this->extractFirstMeasurementValueAnchor($answer);
if ($hardnessValue !== '') {
$anchors[] = $hardnessValue;
}
return array_values(array_unique($anchors));
}
private function extractLatestHistoryTurn(string $history): string
{
$history = trim($history);
if ($history === '') {
return '';
}
$parts = preg_split($this->agentRunnerConfig->getFollowUpHistoryTurnSplitPattern(), $history);
if ($parts === false || $parts === []) {
return '';
}
$turns = array_values(array_filter(
array_map(static fn(string $part): string => trim($part), $parts),
static fn(string $part): bool => $part !== ''
));
if ($turns === []) {
return '';
}
return (string) end($turns);
}
/**
* @return string[]
*/
@@ -1325,29 +1258,6 @@ final readonly class AgentRunner
return array_reverse($turns);
}
private function extractFirstProductModelAnchor(string $text): string
{
if (preg_match($this->agentRunnerConfig->getFollowUpReferenceAnchorProductModelPattern(), $text, $matches) !== 1) {
return '';
}
$value = $this->sanitizeHistoryQuestion(($matches[0] ?? ''));
$value = preg_replace('/\s+/u', ' ', $value) ?? $value;
return trim(str_replace('®', '', $value));
}
private function extractFirstMeasurementValueAnchor(string $text): string
{
if (preg_match($this->agentRunnerConfig->getFollowUpReferenceAnchorMeasurementValuePattern(), $text, $matches) !== 1) {
return '';
}
$value = preg_replace('/\s+/u', ' ', ($matches[0] ?? '')) ?? '';
return trim($value);
}
private function sanitizeHistoryQuestion(string $question): string
{
$question = trim((string) preg_replace('/\s+/u', ' ', $question));
@@ -1500,7 +1410,7 @@ final readonly class AgentRunner
return true;
}
if ($this->extractFirstProductModelAnchor($prompt) !== '') {
if ($this->referenceAnchorExtractor->extractFirstProductModelAnchor($prompt) !== '') {
return false;
}
@@ -1564,7 +1474,7 @@ final readonly class AgentRunner
private function hasStandaloneConcreteShopSubject(string $prompt): bool
{
if ($this->extractFirstProductModelAnchor($prompt) !== '') {
if ($this->referenceAnchorExtractor->extractFirstProductModelAnchor($prompt) !== '') {
return true;
}
@@ -1622,7 +1532,7 @@ final readonly class AgentRunner
return $prompt;
}
if ($this->extractFirstProductModelAnchor($prompt) === '') {
if ($this->referenceAnchorExtractor->extractFirstProductModelAnchor($prompt) === '') {
return $optimizedShopQuery;
}
@@ -2249,7 +2159,7 @@ final readonly class AgentRunner
continue;
}
$model = $this->extractFirstProductModelAnchor($turn);
$model = $this->referenceAnchorExtractor->extractFirstProductModelAnchor($turn);
if ($model !== '') {
$query = str_replace(
@@ -2334,7 +2244,7 @@ final readonly class AgentRunner
}
}
$modelAnchor = $this->extractFirstProductModelAnchor($turn);
$modelAnchor = $this->referenceAnchorExtractor->extractFirstProductModelAnchor($turn);
if ($modelAnchor !== '' && !$this->isMetaOnlyShopQuery($modelAnchor)) {
return mb_strtolower($modelAnchor, 'UTF-8');