$config */ public function __construct( private readonly array $config = [], ) { } public function getCommerceHistoryBudgetChars(): int { return $this->getInt('commerce_history_budget_chars', 1000); } public function getProductSearchKnowledgeChunkLimit(): int { return $this->getInt('product_search_knowledge_chunk_limit', 6); } public function getAdvisoryProductSearchKnowledgeChunkLimit(): int { return $this->getInt('advisory_product_search_knowledge_chunk_limit', 9); } public function getOptimizedShopQueryPrefixPattern(): string { return $this->getString('optimized_shop_query_prefix_pattern', '/^(?:keywords?|suchquery|search\\s*query|query)\\s*:\\s*/iu'); } public function getOptimizedShopQueryTrimCharacters(): string { return " \t\n\r\0\x0B\"'`"; } private function getInt(string $key, int $default): int { $value = $this->config[$key] ?? $default; return is_numeric($value) ? (int) $value : $default; } private function getString(string $key, string $default): string { $value = $this->config[$key] ?? $default; return is_string($value) && $value !== '' ? $value : $default; } public function getEmptyPromptMessage(): string { return '❌ Empty prompt.'; } public function getAnalyzeRequestMessage(): string { return 'Ich analysiere deine Anfrage...'; } public function getCheckInternetSourcesMessage(): string { return 'Ich prüfe auf Internetquellen...'; } public function getRetrieveKnowledgeMessage(): string { return 'Ich hole relevante Daten aus meinem RAG-Wissen...'; } public function getOptimizeSearchMessage(): string { return 'Ich optimiere die Recherche...'; } public function getFetchSearchDataMessageTemplate(): string { return 'Ich rufe Recherchedaten ab (type: %s)'; } public function getAnalyzeAllInformationMessage(): string { return 'Ich analysiere alle Informationen...'; } public function getThinkingWhileStreamingMessage(): string { return 'Denke nach...'; } public function getNoLlmDataReceivedMessage(): string { return '❌ Es wurden keine Daten vom LLM empfangen.'; } public function getGenericInternalErrorMessage(): string { return '❌ Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten.'; } public function getDebugInternalErrorPrefix(): string { return '❌ Interner Fehler: '; } public function getExternalUrlSourceLabel(): string { return 'Externe URL'; } public function getRagKnowledgeSourceLabel(): string { return 'RAG Wissen'; } public function getConversationHistorySourceLabel(): string { return 'Chatverlauf'; } public function getShopSystemSourceLabel(): string { return 'Shopsystem'; } public function getExtendedShopSearchSourceLabel(): string { return 'Erweiterte Shopsuche'; } public function getUsedSourcesPrefix(): string { return 'Genutzte Quellen: '; } public function getSourcesPrefix(): string { return 'Quellen: '; } public function getSourceBadgeHtmlTemplate(): string { return '%s'; } public function getErrorHtmlTemplate(): string { return '
Hinweis
%s
' . "\n"; } public function getThinkHtmlTemplate(): string { return '%s' . "\n"; } public function getInfoHtmlTemplate(): string { return "\n\n" . '%s' . "\n"; } public function getDebugHtmlTemplate(): string { return "\n\nDEBUG: %s\n"; } public function getShopPrompt(string $prompt, string $commerceHistoryContext = ''): string { $historyBlock = ''; if (trim($commerceHistoryContext) !== '') { $historyBlock = $this->buildHistoryBlock($commerceHistoryContext); } return $this->implodePromptBlocks([ $this->getShopPromptIntro(), $this->buildRulesBlock($this->getShopPromptRules()), $this->getShopPromptOutputFormatBlock(), $historyBlock, $this->getCurrentUserInputLabel() . ':', trim($prompt), ]); } private function buildHistoryBlock(string $commerceHistoryContext): string { return $this->implodePromptBlocks([ $this->getRecentConversationContextLabel() . ':', trim($commerceHistoryContext), $this->buildRulesBlock($this->getConversationContextRules(), 'Additional rules for conversation context:'), ]); } /** * @return string[] */ public function getShopPromptRules(): array { return [ '- Output only the final search query.', '- Always convert relevant search terms to their singular form.', '- No introduction, no explanation, no quotation marks.', '- Use only shop-relevant search terms from the user input for a shop search.', '- Maximum 6 search terms, preferably fewer.', '- Remove filler words, polite phrases, and irrelevant words.', '- Preserve product names, brands, model numbers, and compound terms exactly if they are relevant.', '- Numbers that belong to a product name or model must be preserved (e.g. Indikator 300, Testomat 808, Testomat 2000).', '- Separate terms using spaces only.', '- If a relevant product name is present, it must be placed at the beginning of the final search query.', '- Try to always identify all products mentioned in the user input text, even in long prompts.', '- Look for terms such as Testomat, Horiba, Tritromat, or words like indicator.', '- If the current user input is vague or referential, use the recent conversation context only as support.', '- Do not output words that only describe conversation flow, such as "same", "again", "also", or "like above".', ]; } /** * @return string[] */ public function getConversationContextRules(): array { return [ '- The current user input has highest priority.', '- Use the recent conversation context only to resolve omitted references.', '- Use it only for product carry-over, brand carry-over, model carry-over, or variant follow-ups.', '- Do not revive older products unless the current user input clearly refers to them.', '- If the current input starts a new topic, ignore older product context.', '- Prefer the most recent product reference over older ones.', ]; } public function getShopPromptIntro(): string { return 'Generate a short search query for Shopware 6 from the following user input text.'; } public function getShopPromptOutputFormatBlock(): string { return "Output format:\nKeyword1 Keyword2 Keyword3"; } public function getRecentConversationContextLabel(): string { return 'RECENT CONVERSATION CONTEXT'; } public function getCurrentUserInputLabel(): string { return 'CURRENT USER INPUT'; } private function buildRulesBlock(array $rules, string $headline = 'Rules:'): string { return $headline . "\n" . implode("\n", $rules); } /** * @param string[] $blocks */ private function implodePromptBlocks(array $blocks): string { $normalized = array_values(array_filter( array_map( static fn(string $block): string => trim($block), $blocks ), static fn(string $block): bool => $block !== '' )); return implode("\n\n", $normalized); } }