From 38ae9d528fd7e2e1a215015b6b6db798c5784282 Mon Sep 17 00:00:00 2001 From: team 1 Date: Sun, 26 Apr 2026 11:00:13 +0200 Subject: [PATCH] move tokens to config --- PATCH_README_EVENTSOURCE_SHOP_STATUS_FIX.md | 42 ++++++++ RETRIEX_AGENT_CONFIG_FIX_README.md | 32 ++++++ config/retriex/agent.yaml | 65 +++++++++++- public/assets/styles/base.css | 2 +- src/Config/AgentRunnerConfig.php | 112 ++++++++++++++------ 5 files changed, 216 insertions(+), 37 deletions(-) create mode 100644 PATCH_README_EVENTSOURCE_SHOP_STATUS_FIX.md create mode 100644 RETRIEX_AGENT_CONFIG_FIX_README.md diff --git a/PATCH_README_EVENTSOURCE_SHOP_STATUS_FIX.md b/PATCH_README_EVENTSOURCE_SHOP_STATUS_FIX.md new file mode 100644 index 0000000..48cb81d --- /dev/null +++ b/PATCH_README_EVENTSOURCE_SHOP_STATUS_FIX.md @@ -0,0 +1,42 @@ +# RetrieX EventSource + Shop Status Fix + +Patch-only package. + +## Purpose + +This patch extends the previous stream robustness fix and addresses two issues: + +1. The chat now shows a visible reason when the Shopware shop/search API is unreachable or returns a system error. +2. Reference-probe failures no longer block the actual primary Shopware search request. + +## Files + +- `public/assets/js/base.js` + - Uses native `EventSource` instead of a manually parsed `fetch().body.getReader()` stream. + - Starts a short-lived stream job with `POST /ask-jobs`, then opens `GET /ask-sse/{jobId}`. + +- `src/Controller/AskSseController.php` + - Adds `POST /ask-jobs`. + - Adds `GET /ask-sse/{jobId}` for native EventSource streaming. + - Keeps the old `POST /ask-sse` endpoint as backwards compatibility. + - Stores short-lived stream jobs under `var/stream_jobs`. + +- `src/Commerce/ShopSearchService.php` + - Reference-probe Store API failures are logged but no longer stop the primary search. + - The failure state is reset after a failed reference probe before the primary search starts. + - Retry without commerce history is skipped only after a real primary Store API system failure. + +- `src/Agent/AgentRunner.php` + - If the primary shop search has a Store API system failure, the chat displays: + `Shopsystem aktuell nicht erreichbar oder fehlerhaft. Grund: ...` + - Repair search is skipped after primary Store API system failures. + +## After installation + +Run: + +```bash +bin/console cache:clear +``` + +If OPcache/PHP-FPM is active, reload PHP-FPM afterwards. diff --git a/RETRIEX_AGENT_CONFIG_FIX_README.md b/RETRIEX_AGENT_CONFIG_FIX_README.md new file mode 100644 index 0000000..55e0c76 --- /dev/null +++ b/RETRIEX_AGENT_CONFIG_FIX_README.md @@ -0,0 +1,32 @@ +# RetrieX Agent Config Centralization Fix + +This patch moves low-risk AgentRunner configuration values into `config/retriex/agent.yaml` while keeping the existing PHP fallback values in `AgentRunnerConfig`. + +## Scope + +Changed files: + +- `config/retriex/agent.yaml` +- `src/Config/AgentRunnerConfig.php` +- `RETRIEX_AGENT_CONFIG_FIX_README.md` + +## What was centralized + +- user-visible progress/status messages +- error messages +- source labels +- small HTML templates used by the agent stream/output +- Shopware query optimizer prompt text and rules + +## What was intentionally not changed + +- `PromptBuilderConfig` prompt wording/rules +- retrieval scoring logic +- shop matching logic +- vocabulary/intent/search-repair logic from the previous stable centralization step + +## Safety notes + +The YAML values mirror the existing PHP fallback values. `AgentRunnerConfig` still contains the old defaults as fallbacks, so missing or invalid YAML values should not break runtime behavior. + +After applying the patch, clear the Symfony cache and re-run the known 1.4.2 regression prompts. diff --git a/config/retriex/agent.yaml b/config/retriex/agent.yaml index d369e85..ee43f5d 100644 --- a/config/retriex/agent.yaml +++ b/config/retriex/agent.yaml @@ -1,8 +1,69 @@ -# Agent orchestration limits and user-visible source/progress labels. -# Values mirror the current 1.4.2 defaults. +# Agent orchestration limits, user-visible status/source labels and Shopware query prompt wording. +# Values mirror the current stable defaults; PHP fallbacks remain in AgentRunnerConfig. parameters: retriex.agent.config: commerce_history_budget_chars: 1000 product_search_knowledge_chunk_limit: 6 advisory_product_search_knowledge_chunk_limit: 9 optimized_shop_query_prefix_pattern: '/^(?:keywords?|suchquery|search\s*query|query)\s*:\s*/iu' + + messages: + empty_prompt: '❌ Empty prompt.' + analyze_request: 'Ich analysiere deine Anfrage...' + check_internet_sources: 'Ich prüfe auf Internetquellen...' + retrieve_knowledge: 'Ich hole relevante Daten aus meinem RAG-Wissen...' + optimize_search: 'Ich optimiere die Recherche...' + fetch_search_data_template: 'Ich rufe Recherchedaten ab (type: %s)' + analyze_all_information: 'Ich analysiere alle Informationen...' + thinking_while_streaming: 'Denke nach...' + no_llm_data_received: '❌ Es wurden keine Daten vom LLM empfangen.' + generic_internal_error: '❌ Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten.' + debug_internal_error_prefix: '❌ Interner Fehler: ' + + source_labels: + external_url: 'Externe URL' + rag_knowledge: 'RAG Wissen' + conversation_history: 'Chatverlauf' + shop_system: 'Shopsystem' + extended_shop_search: 'Erweiterte Shopsuche' + used_sources_prefix: 'Genutzte Quellen: ' + sources_prefix: 'Quellen: ' + + html: + source_badge_template: '%s' + error_template: | +
Hinweis
%s
+ think_template: | + %s + info_template: "\n\n%s\n" + debug_template: "\n\nDEBUG: %s\n" + + shop_prompt: + intro: 'Generate a short search query for Shopware 6 from the following user input text.' + output_format_block: |- + Output format: + Keyword1 Keyword2 Keyword3 + recent_conversation_context_label: 'RECENT CONVERSATION CONTEXT' + current_user_input_label: 'CURRENT USER INPUT' + rules: + - '- 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".' + conversation_context_rules: + - '- 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.' diff --git a/public/assets/styles/base.css b/public/assets/styles/base.css index 2850829..642da3c 100644 --- a/public/assets/styles/base.css +++ b/public/assets/styles/base.css @@ -378,7 +378,7 @@ span.think { } .think { - display: inline-block; + display: block; color: rgba(255, 255, 255, 0.72); background-image: linear-gradient( 100deg, diff --git a/src/Config/AgentRunnerConfig.php b/src/Config/AgentRunnerConfig.php index ed69c97..e8cfd40 100644 --- a/src/Config/AgentRunnerConfig.php +++ b/src/Config/AgentRunnerConfig.php @@ -36,136 +36,180 @@ final class AgentRunnerConfig public function getOptimizedShopQueryTrimCharacters(): string { - return " \t\n\r\0\x0B\"'`"; + return $this->getString('optimized_shop_query_trim_characters', " \t\n\r\0\x0B\"'`"); } private function getInt(string $key, int $default): int { - $value = $this->config[$key] ?? $default; + $value = $this->value($key, $default); return is_numeric($value) ? (int) $value : $default; } private function getString(string $key, string $default): string { - $value = $this->config[$key] ?? $default; + $value = $this->value($key, $default); return is_string($value) && $value !== '' ? $value : $default; } + /** + * @param string[] $default + * @return string[] + */ + private function getStringList(string $key, array $default): array + { + $value = $this->value($key, $default); + + if (!is_array($value)) { + return $default; + } + + $out = []; + + foreach ($value as $item) { + if (!is_scalar($item)) { + continue; + } + + $item = trim((string) $item); + + if ($item !== '') { + $out[] = $item; + } + } + + return $out !== [] ? $out : $default; + } + + private function value(string $key, mixed $default): mixed + { + $current = $this->config; + + foreach (explode('.', $key) as $segment) { + if (!is_array($current) || !array_key_exists($segment, $current)) { + return $default; + } + + $current = $current[$segment]; + } + + return $current; + } + public function getEmptyPromptMessage(): string { - return '❌ Empty prompt.'; + return $this->getString('messages.empty_prompt', '❌ Empty prompt.'); } public function getAnalyzeRequestMessage(): string { - return 'Ich analysiere deine Anfrage...'; + return $this->getString('messages.analyze_request', 'Ich analysiere deine Anfrage...'); } public function getCheckInternetSourcesMessage(): string { - return 'Ich prüfe auf Internetquellen...'; + return $this->getString('messages.check_internet_sources', 'Ich prüfe auf Internetquellen...'); } public function getRetrieveKnowledgeMessage(): string { - return 'Ich hole relevante Daten aus meinem RAG-Wissen...'; + return $this->getString('messages.retrieve_knowledge', 'Ich hole relevante Daten aus meinem RAG-Wissen...'); } public function getOptimizeSearchMessage(): string { - return 'Ich optimiere die Recherche...'; + return $this->getString('messages.optimize_search', 'Ich optimiere die Recherche...'); } public function getFetchSearchDataMessageTemplate(): string { - return 'Ich rufe Recherchedaten ab (type: %s)'; + return $this->getString('messages.fetch_search_data_template', 'Ich rufe Recherchedaten ab (type: %s)'); } public function getAnalyzeAllInformationMessage(): string { - return 'Ich analysiere alle Informationen...'; + return $this->getString('messages.analyze_all_information', 'Ich analysiere alle Informationen...'); } public function getThinkingWhileStreamingMessage(): string { - return 'Denke nach...'; + return $this->getString('messages.thinking_while_streaming', 'Denke nach...'); } public function getNoLlmDataReceivedMessage(): string { - return '❌ Es wurden keine Daten vom LLM empfangen.'; + return $this->getString('messages.no_llm_data_received', '❌ Es wurden keine Daten vom LLM empfangen.'); } public function getGenericInternalErrorMessage(): string { - return '❌ Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten.'; + return $this->getString('messages.generic_internal_error', '❌ Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten.'); } public function getDebugInternalErrorPrefix(): string { - return '❌ Interner Fehler: '; + return $this->getString('messages.debug_internal_error_prefix', '❌ Interner Fehler: '); } public function getExternalUrlSourceLabel(): string { - return 'Externe URL'; + return $this->getString('source_labels.external_url', 'Externe URL'); } public function getRagKnowledgeSourceLabel(): string { - return 'RAG Wissen'; + return $this->getString('source_labels.rag_knowledge', 'RAG Wissen'); } public function getConversationHistorySourceLabel(): string { - return 'Chatverlauf'; + return $this->getString('source_labels.conversation_history', 'Chatverlauf'); } public function getShopSystemSourceLabel(): string { - return 'Shopsystem'; + return $this->getString('source_labels.shop_system', 'Shopsystem'); } public function getExtendedShopSearchSourceLabel(): string { - return 'Erweiterte Shopsuche'; + return $this->getString('source_labels.extended_shop_search', 'Erweiterte Shopsuche'); } public function getUsedSourcesPrefix(): string { - return 'Genutzte Quellen: '; + return $this->getString('source_labels.used_sources_prefix', 'Genutzte Quellen: '); } public function getSourcesPrefix(): string { - return 'Quellen: '; + return $this->getString('source_labels.sources_prefix', 'Quellen: '); } public function getSourceBadgeHtmlTemplate(): string { - return '%s'; + return $this->getString('html.source_badge_template', '%s'); } public function getErrorHtmlTemplate(): string { - return '
Hinweis
%s
' . "\n"; + return $this->getString('html.error_template', '
Hinweis
%s
' . "\n"); } public function getThinkHtmlTemplate(): string { - return '%s' . "\n"; + return $this->getString('html.think_template', '%s' . "\n"); } public function getInfoHtmlTemplate(): string { - return "\n\n" . '%s' . "\n"; + return $this->getString('html.info_template', "\n\n" . '%s' . "\n"); } public function getDebugHtmlTemplate(): string { - return "\n\nDEBUG: %s\n"; + return $this->getString('html.debug_template', "\n\nDEBUG: %s\n"); } public function getShopPrompt(string $prompt, string $commerceHistoryContext = ''): string @@ -200,7 +244,7 @@ final class AgentRunnerConfig */ public function getShopPromptRules(): array { - return [ + return $this->getStringList('shop_prompt.rules', [ '- Output only the final search query.', '- Always convert relevant search terms to their singular form.', '- No introduction, no explanation, no quotation marks.', @@ -215,7 +259,7 @@ final class AgentRunnerConfig '- 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".', - ]; + ]); } /** @@ -223,34 +267,34 @@ final class AgentRunnerConfig */ public function getConversationContextRules(): array { - return [ + return $this->getStringList('shop_prompt.conversation_context_rules', [ '- 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.'; + return $this->getString('shop_prompt.intro', 'Generate a short search query for Shopware 6 from the following user input text.'); } public function getShopPromptOutputFormatBlock(): string { - return "Output format:\nKeyword1 Keyword2 Keyword3"; + return $this->getString('shop_prompt.output_format_block', "Output format:\nKeyword1 Keyword2 Keyword3"); } public function getRecentConversationContextLabel(): string { - return 'RECENT CONVERSATION CONTEXT'; + return $this->getString('shop_prompt.recent_conversation_context_label', 'RECENT CONVERSATION CONTEXT'); } public function getCurrentUserInputLabel(): string { - return 'CURRENT USER INPUT'; + return $this->getString('shop_prompt.current_user_input_label', 'CURRENT USER INPUT'); } private function buildRulesBlock(array $rules, string $headline = 'Rules:'): string