add shop result counter into response

This commit is contained in:
team2
2026-04-27 21:09:38 +02:00
parent 547f2a0177
commit 316a5b5cc2
3 changed files with 94 additions and 25 deletions

View File

@@ -3,7 +3,7 @@
parameters: parameters:
retriex.commerce.enabled: true retriex.commerce.enabled: true
retriex.commerce.max_shop_results: '%env(SHOPWARE_STORE_API_MAX_RESULT)%' retriex.commerce.max_shop_results: '%env(SHOPWARE_STORE_API_MAX_RESULT)%'
retriex.commerce.shop_timeout: 3 retriex.commerce.shop_timeout: 15
retriex.commerce.store_api_base_url: '%env(SHOPWARE_STORE_API_BASE_URL)%' retriex.commerce.store_api_base_url: '%env(SHOPWARE_STORE_API_BASE_URL)%'
retriex.commerce.sales_channel_access_key: '%env(SHOPWARE_SALES_CHANNEL_ACCESS_KEY)%' retriex.commerce.sales_channel_access_key: '%env(SHOPWARE_SALES_CHANNEL_ACCESS_KEY)%'

View File

@@ -414,9 +414,33 @@ document.addEventListener('DOMContentLoaded', () => {
removeThinkSpansOnly(container); removeThinkSpansOnly(container);
} }
function deduplicateRetriexMetaCards(container) {
if (!container) {
return;
}
const cards = Array.from(container.querySelectorAll('.retriex-meta-card[data-retriex-meta-id]'));
const latestCardById = new Map();
cards.forEach((card) => {
latestCardById.set(card.getAttribute('data-retriex-meta-id'), card);
});
cards.forEach((card) => {
const cardId = card.getAttribute('data-retriex-meta-id');
if (latestCardById.get(cardId) !== card) {
card.remove();
}
});
cleanupEmptyBlocks(container);
}
function renderBubbleContent(bubble, raw) { function renderBubbleContent(bubble, raw) {
bubble.innerHTML = renderMarkdown(raw); bubble.innerHTML = renderMarkdown(raw);
cleanupThinkSpans(bubble); cleanupThinkSpans(bubble);
deduplicateRetriexMetaCards(bubble);
enhanceChatLinks(bubble); enhanceChatLinks(bubble);
scrollChatToBottom(); scrollChatToBottom();
} }
@@ -495,6 +519,7 @@ document.addEventListener('DOMContentLoaded', () => {
clearScheduledRender(); clearScheduledRender();
bubble.innerHTML = renderMarkdown(raw); bubble.innerHTML = renderMarkdown(raw);
removeThinkSpansOnly(bubble); removeThinkSpansOnly(bubble);
deduplicateRetriexMetaCards(bubble);
bubble.classList.remove('loader'); bubble.classList.remove('loader');
enhanceChatLinks(bubble); enhanceChatLinks(bubble);
scrollChatToBottom(); scrollChatToBottom();

View File

@@ -53,6 +53,8 @@ final readonly class AgentRunner
$sources = []; $sources = [];
$optimizedShopQuery = ''; $optimizedShopQuery = '';
$shopSearchQuery = ''; $shopSearchQuery = '';
$shopSearchDisplayQuery = '';
$shopSearchUsedOptimizedQuery = false;
$commerceIntent = CommerceIntentLite::NONE; $commerceIntent = CommerceIntentLite::NONE;
$knowledgeRetrievalPrompt = $prompt; $knowledgeRetrievalPrompt = $prompt;
$usedFollowUpRetrievalContext = false; $usedFollowUpRetrievalContext = false;
@@ -127,7 +129,6 @@ final readonly class AgentRunner
commerceHistoryContext: $commerceHistoryContext, commerceHistoryContext: $commerceHistoryContext,
userId: $userId userId: $userId
); );
$usedResolvedOptimizedShopQuery = $optimizedShopQuery !== '' && $shopSearchQuery === $optimizedShopQuery;
if ($shopSearchQuery === '') { if ($shopSearchQuery === '') {
$this->agentLogger->info('Commerce search skipped because no concrete shop query could be resolved', [ $this->agentLogger->info('Commerce search skipped because no concrete shop query could be resolved', [
@@ -154,11 +155,16 @@ final readonly class AgentRunner
$commerceHistoryContext $commerceHistoryContext
); );
$shopSearchDisplayQuery = $shopQueryPreview->searchText !== ''
? $shopQueryPreview->searchText
: $shopSearchQuery;
$shopSearchUsedOptimizedQuery = $optimizedShopQuery !== '';
yield $this->systemMsg( yield $this->systemMsg(
$this->buildShopSearchMetaMessage( $this->buildShopSearchMetaMessage(
query: $shopQueryPreview->searchText !== '' ? $shopQueryPreview->searchText : $shopSearchQuery, query: $shopSearchDisplayQuery,
commerceIntent: $commerceIntent, commerceIntent: $commerceIntent,
usedOptimizedQuery: $usedResolvedOptimizedShopQuery, usedOptimizedQuery: $shopSearchUsedOptimizedQuery,
originalQuery: $shopSearchQuery originalQuery: $shopSearchQuery
), ),
'meta' 'meta'
@@ -167,7 +173,7 @@ final readonly class AgentRunner
$this->agentLogger->info('Commerce search prepared', [ $this->agentLogger->info('Commerce search prepared', [
'userId' => $userId, 'userId' => $userId,
'commerceIntent' => $commerceIntent, 'commerceIntent' => $commerceIntent,
'usedOptimizedShopQuery' => $usedResolvedOptimizedShopQuery, 'usedOptimizedShopQuery' => $optimizedShopQuery !== '',
'optimizedShopQuery' => $optimizedShopQuery, 'optimizedShopQuery' => $optimizedShopQuery,
'shopSearchQuery' => $shopSearchQuery, 'shopSearchQuery' => $shopSearchQuery,
'hasCommerceHistoryContext' => $commerceHistoryContext !== '', 'hasCommerceHistoryContext' => $commerceHistoryContext !== '',
@@ -232,6 +238,22 @@ final readonly class AgentRunner
$usedShopRepair = $repairPayload['usedRepair']; $usedShopRepair = $repairPayload['usedRepair'];
$shopRepairQueries = $repairPayload['repairQueries']; $shopRepairQueries = $repairPayload['repairQueries'];
if ($shopSearchQuery !== '' && !$primaryShopSearchHadSystemFailure) {
yield $this->systemMsg(
$this->buildShopSearchMetaMessage(
query: $shopSearchDisplayQuery !== '' ? $shopSearchDisplayQuery : $shopSearchQuery,
commerceIntent: $commerceIntent,
usedOptimizedQuery: $shopSearchUsedOptimizedQuery,
originalQuery: $shopSearchQuery,
resultCount: count($shopResults),
completed: true,
attemptedRepair: $attemptedShopRepair,
usedRepair: $usedShopRepair
),
'meta'
);
}
if ($shopResults !== []) { if ($shopResults !== []) {
$this->addSource($sources, $this->agentRunnerConfig->getShopSystemSourceLabel()); $this->addSource($sources, $this->agentRunnerConfig->getShopSystemSourceLabel());
} }
@@ -483,7 +505,7 @@ final readonly class AgentRunner
return []; return [];
} }
if (preg_match_all('/^Question:\s*(.+)$/mi', $history, $matches) < 1) { if (preg_match_all('/^Question:\s*(.+)$/mi', $history, $matches) !== 1) {
return []; return [];
} }
@@ -724,20 +746,11 @@ final readonly class AgentRunner
string $commerceHistoryContext, string $commerceHistoryContext,
string $userId string $userId
): string { ): string {
$promptIsMetaOnly = $this->isMetaOnlyShopQuery($prompt); if ($optimizedShopQuery !== '' && !$this->isMetaOnlyShopQuery($optimizedShopQuery)) {
/**
* A pure meta command such as "suche im shop" has no own product
* semantics. In that case the LLM optimizer must not be trusted as the
* primary source because it can copy instruction terms from the query
* prompt itself (for example "Shopware 6"). Resolve meta commands
* deterministically from the recent conversation instead.
*/
if (!$promptIsMetaOnly && $optimizedShopQuery !== '' && !$this->isMetaOnlyShopQuery($optimizedShopQuery)) {
return $optimizedShopQuery; return $optimizedShopQuery;
} }
if (!$promptIsMetaOnly) { if (!$this->isMetaOnlyShopQuery($prompt)) {
return $prompt; return $prompt;
} }
@@ -847,7 +860,7 @@ final readonly class AgentRunner
$value = mb_strtolower(trim($value), 'UTF-8'); $value = mb_strtolower(trim($value), 'UTF-8');
$value = str_replace(['-', '/', '_'], ' ', $value); $value = str_replace(['-', '/', '_'], ' ', $value);
if (preg_match_all('/\d+(?:[,.]\d+)?|[\p{L}\p{N}]+/u', $value, $matches) < 1) { if (preg_match_all('/\d+(?:[,.]\d+)?|[\p{L}\p{N}]+/u', $value, $matches) !== 1) {
return []; return [];
} }
@@ -1294,7 +1307,11 @@ final readonly class AgentRunner
string $query, string $query,
string $commerceIntent, string $commerceIntent,
bool $usedOptimizedQuery, bool $usedOptimizedQuery,
string $originalQuery string $originalQuery,
?int $resultCount = null,
bool $completed = false,
bool $attemptedRepair = false,
bool $usedRepair = false
): string { ): string {
$query = $this->normalizeOneLine($query); $query = $this->normalizeOneLine($query);
$originalQuery = $this->normalizeOneLine($originalQuery); $originalQuery = $this->normalizeOneLine($originalQuery);
@@ -1303,20 +1320,47 @@ final readonly class AgentRunner
$query = $originalQuery !== '' ? $originalQuery : 'keine Suchquery ermittelt'; $query = $originalQuery !== '' ? $originalQuery : 'keine Suchquery ermittelt';
} }
$badge = $usedOptimizedQuery ? 'optimiert' : 'direkt'; $queryModeLabel = $usedOptimizedQuery ? 'optimiert' : 'direkt';
$intentLabel = $commerceIntent !== '' ? $commerceIntent : 'commerce'; $intentLabel = $commerceIntent !== '' ? $commerceIntent : 'commerce';
$title = $completed ? 'Shop-Suche abgeschlossen' : 'Shop-Suche wird ausgeführt';
$statusLabel = $completed ? 'Status: abgeschlossen' : 'Status: läuft';
$resultLabel = $resultCount === null
? 'Shoptreffer: wird geladen'
: 'Shoptreffer: ' . max(0, $resultCount);
$state = $completed ? 'completed' : 'running';
$resultCountAttribute = $resultCount === null ? '' : (string) max(0, $resultCount);
$repairLabel = '';
return '<div class="retriex-meta-card retriex-shop-meta">' if ($usedRepair) {
$repairLabel = 'Erweiterte Suche: genutzt';
} elseif ($attemptedRepair) {
$repairLabel = 'Erweiterte Suche: geprüft';
}
$html = '<div class="retriex-meta-card retriex-shop-meta" data-retriex-meta-id="shop-search" data-retriex-meta-state="'
. htmlspecialchars($state, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. '" data-retriex-shop-result-count="'
. htmlspecialchars($resultCountAttribute, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. '">'
. '<div class="retriex-meta-card__eyebrow">Live-Shopdaten</div>' . '<div class="retriex-meta-card__eyebrow">Live-Shopdaten</div>'
. '<div class="retriex-meta-card__title">Shop-Suche wird ausgeführt</div>' . '<div class="retriex-meta-card__title">' . htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</div>'
. '<div class="retriex-meta-card__body">' . '<div class="retriex-meta-card__body">'
. '<span class="retriex-meta-pill">' . htmlspecialchars($badge, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>' . '<span class="retriex-meta-pill retriex-meta-pill--result">' . htmlspecialchars($resultLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>'
. '<span class="retriex-meta-pill">Intent: ' . htmlspecialchars($intentLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>' . '<span class="retriex-meta-pill">' . htmlspecialchars($statusLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>'
. '</div>' . '<span class="retriex-meta-pill">Query: ' . htmlspecialchars($queryModeLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>'
. '<span class="retriex-meta-pill">Intent: ' . htmlspecialchars($intentLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>';
if ($repairLabel !== '') {
$html .= '<span class="retriex-meta-pill">' . htmlspecialchars($repairLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>';
}
$html .= '</div>'
. '<div class="retriex-meta-query"><span>Gesendete Suchquery</span><code>' . '<div class="retriex-meta-query"><span>Gesendete Suchquery</span><code>'
. htmlspecialchars($query, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . htmlspecialchars($query, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. '</code></div>' . '</code></div>'
. '</div>'; . '</div>';
return $html;
} }
private function buildShopUnavailableMessage(?string $reason): string private function buildShopUnavailableMessage(?string $reason): string