From bd62248c8d2a2fa545937560ab612aa695002f9f Mon Sep 17 00:00:00 2001 From: team 1 Date: Sat, 9 May 2026 11:43:13 +0200 Subject: [PATCH] p65 --- config/retriex/agent.yaml | 192 +-------------- config/retriex/chat-messages.yaml | 187 ++++++++++++++ config/services.yaml | 1 + ..._AGENT_CHAT_MESSAGES_UNIFICATION_README.md | 63 +++++ src/Config/AgentRunnerConfig.php | 134 ++++++---- src/Config/ChatMessagesConfig.php | 229 ++++++++++++++++++ 6 files changed, 567 insertions(+), 239 deletions(-) create mode 100644 patch_history/RETRIEX_PATCH_65_AGENT_CHAT_MESSAGES_UNIFICATION_README.md diff --git a/config/retriex/agent.yaml b/config/retriex/agent.yaml index 93d1895..51f167a 100644 --- a/config/retriex/agent.yaml +++ b/config/retriex/agent.yaml @@ -1,5 +1,5 @@ -# Agent orchestration limits, user-visible status/source labels and Shopware query prompt wording. -# Values mirror the current stable defaults; PHP fallbacks remain in AgentRunnerConfig. +# Agent orchestration limits, routing rules and Shopware query prompt wording. +# User-visible chat messages live in config/retriex/chat-messages.yaml. parameters: retriex.agent.config: commerce_history_budget_chars: 1000 @@ -14,7 +14,6 @@ parameters: max_output_chars: 700 max_added_tokens: 2 max_length_ratio_percent: 150 - heartbeat_message: 'Ich optimiere die Anfrage…' output_prefix_pattern: '/^(?:normalisiert|korrigiert|corrected|normalized)\s*:\s*/iu' placeholder_outputs: - 'normalized user input' @@ -133,30 +132,11 @@ parameters: product_model_pattern: '/\bTestomat(?:®)?\s+(?:\d{3,4}(?:\s+[A-Z]{2,8})?|EVO(?:\s+[A-Z]{2,6})?|ECO(?:[-\s]?(?:PLUS|C))?|DUO(?:\s+\d{3,4})?|LAB(?:\s+[A-Z]{2,6})?)\b/iu' measurement_value_pattern: '/\b\d+(?:[,.]\d+)?\s*°\s*dH\b/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...' - no_concrete_shop_query: 'Ich kann die Shop-Suche noch nicht belastbar auflösen. Bitte nenne das Produkt, den Messparameter oder das Zubehör etwas konkreter.' - 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.' - shop_repair_check: 'Erweiterte Shopsuche wird geprüft…' - shop_query_optimization_heartbeat: 'Shop-Suchanfrage wird optimiert…' - generic_internal_error: '❌ Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten.' - debug_internal_error_prefix: '❌ Interner Fehler: ' - final_answer_guard: enabled: true # Prevents runaway final answers caused by repeated LLM output. This is a # runtime safety guard and does not change retrieval, ranking or product matching. max_output_chars: 12000 - truncation_message: |2- - - Hinweis: Die Antwort wurde gekürzt, weil eine wiederholte oder zu lange Ausgabe erkannt wurde. Bitte starten Sie bei Bedarf eine präzisere Nachfrage. repeated_line: enabled: true min_output_chars: 1200 @@ -298,13 +278,6 @@ parameters: direct_answer: enabled: true max_results: 10 - intro: '' - no_results: '' - sorted_by_length_note: '' - min_length_filter_note: '' - max_length_filter_note: '' - - rag_evidence_guard: cleanup_profile: rag_evidence # Legacy/domain override list. Generic German stopwords are provided by @@ -352,170 +325,9 @@ parameters: no_llm_fallback: max_shop_results: 5 - messages: - shop_only: 'Ich finde dazu im RAG-Wissen keine belastbare Fachinformation. Aus den Shopdaten ergeben sich folgende Treffer; technische Eignung bitte prüfen:' - shop_with_knowledge: 'Es liegen RAG-/Kontexttreffer und Shopdaten vor. Ohne LLM leite ich daraus keine technische Eignung ab. Die Shopdaten zeigen folgende Treffer; technische Eignung bitte prüfen:' - accessory_only_for_main_device: 'Die Shop-Treffer wirken wie Zubehör/Verbrauchsmaterial und nicht wie eine angefragte Messanlage oder ein Hauptgerät. Ich werte sie deshalb nicht als passende Hauptlösung.' - escalation: 'Für eine verbindliche Produktauswahl sollte der konkrete Anwendungsfall durch Vertrieb oder Support geprüft werden.' - knowledge_only: 'Ich habe Treffer im RAG-Wissen gefunden, aber ohne LLM kann ich daraus keine belastbare fachliche Antwort synthetisieren. Ich gebe deshalb keine sichere Produktaussage aus. Bitte aktiviere das LLM oder konkretisiere die Frage für eine gezielte Prüfung.' - no_data: 'Ich finde dazu keine belastbaren Daten in den vorliegenden Quellen. Bitte nenne Produkt, Messparameter, Zubehör oder Anwendungsfall genauer.' - no_shop_results_with_knowledge: 'Ich finde RAG-/Kontexttreffer, aber keine passenden Shop-Treffer zur aktuellen Suchanfrage. Das ist keine Aussage, dass es das Produkt nicht gibt. Ohne LLM gebe ich keine technische Negativaussage aus; bitte prüfe den Suchbegriff oder den Anwendungsfall gezielter.' - no_shop_results_no_knowledge: 'Ich finde weder belastbares RAG-Wissen noch passende Shop-Treffer zur aktuellen Suchanfrage. Das ist keine sichere Negativaussage. Bitte nenne Produkt, Messparameter oder Zubehör konkreter.' - shop_unavailable_with_knowledge: 'Live-Shopdaten konnten nicht geladen werden. Ich kann keine Aussage zu aktueller Verfügbarkeit, Preis oder Shop-Portfolio treffen. Wenn das RAG-Wissen einen direkten Fachbeleg enthält, wird die fachliche Antwort davon getrennt betrachtet.' - shop_unavailable_no_knowledge: 'Live-Shopdaten konnten nicht geladen werden und die RAG-Treffer enthalten keinen direkten Fachbeleg zur Anfrage. Ich kann daraus keine verlässliche Produkt-, Verfügbarkeits- oder Portfolioaussage ableiten.' - product_fields: - unreadable_results_message: '- Es wurden Shop-Treffer übergeben, aber keine lesbaren Produktdaten gefunden.' - unnamed_product: 'Unbenanntes Shop-Produkt' - product_number_template: 'Art.-Nr. {value}' - manufacturer_template: 'Hersteller: {value}' - price_template: 'Preis: {value}' - availability_template: 'Verfügbar: {value}' - availability_yes: 'ja' - availability_no: 'nein' - url_template: 'URL: {value}' - incompatible_role_note: 'Hinweis: Zubehör/Verbrauchsartikel; nicht als Messanlage/Gerät bestätigt' - line_template: '{index}. {parts}' - separator: ' | ' - unavailable_reason_template: '{message} Ursache: {reason}' - - - production_ui: - stage_labels: - preparing_answer: 'Antwort wird vorbereitet' - shop_routing_detected: 'Shop-Routing erkannt' - rag_searched: 'RAG-Wissen wurde durchsucht' - shop_search_preparing: 'Shop-Suche wird vorbereitet' - more_context_needed: 'Mehr Kontext nötig' - shop_search_running: 'Shop wird durchsucht' - shop_unavailable: 'Shopdaten nicht verfügbar' - shop_completed: 'Shop-Suche abgeschlossen' - answer_generating: 'Antwort wird generiert' - completed: 'Abgeschlossen' - interrupted: 'Antwort wurde unterbrochen' - confidence_labels: - checking_evidence: 'Beleglage wird geprüft' - checking_shop_data: 'Shopdaten werden geprüft' - more_context_needed: 'mehr Kontext nötig' - interrupted: 'nicht abgeschlossen' - direct: 'fachlich belegt' - aggregate_missing: 'geprüfte Quellen, keine passende Zählinformation' - weak: 'RAG-Näherungstreffer, kein direkter Fachbeleg' - default: 'noch keine belastbaren Treffer' - direct_shop_check: 'fachlich belegt; Shopdaten werden geprüft' - aggregate_missing_shop_check: 'geprüfte Quellen ohne Zählinformation; Shopdaten werden geprüft' - weak_shop_check: 'RAG-Näherungstreffer; Shopdaten werden geprüft' - default_shop_check: 'Shopdaten werden geprüft' - aggregate_missing_shop_unavailable: 'geprüfte Quellen ohne Zählinformation; Shopdaten nicht verfügbar' - aggregate_missing_no_count: 'geprüfte Quellen, keine passende Zählinformation' - shop_unavailable_with_knowledge: 'fachlich belegt; Shopdaten nicht verfügbar' - shop_unavailable: 'Shopdaten nicht verfügbar' - rag_and_shop: 'RAG + Shopdaten' - shop_only: 'nur Shopdaten' - rag_no_shop_hits: 'RAG-Wissen, keine Shop-Treffer' - no_reliable_data: 'keine belastbaren Daten' - no_reliable_hits: 'noch keine belastbaren Treffer' - text: - live_shop_source_plain_label: 'Live-Shopdaten' - run_status_eyebrow: 'RetrieX-Status' - evidence_prefix: 'Beleglage: ' - data_basis_label: 'Datenbasis' - data_basis_empty_completed: 'keine belastbare Datenbasis' - data_basis_empty_running: 'wird geprüft' - rag_hits_checking: 'RAG-Treffer: wird geprüft' - shop_hits_loading: 'Shop-Treffer: wird geladen' - shop_hits_unavailable: 'Shop-Treffer: nicht verfügbar' - shop_hits_no_query: 'Shop-Treffer: keine Suchquery' - shop_hits_not_requested: 'Shop-Treffer: nicht angefragt' - status_completed: 'Status: abgeschlossen' - status_running: 'Status: läuft' - shop_results_eyebrow: 'Shop-Ergebnisse' - shop_results_title: 'Shop-Ergebnisse' - evaluated_query_label: 'Ausgewertete Suchquery' - unnamed_product: 'Unbenanntes Produkt' - field_not_provided: 'nicht übermittelt' - product_number_label: 'Artikelnummer' - price_label: 'Preis' - availability_label: 'Verfügbarkeit' - manufacturer_label: 'Hersteller' - relevance_label: 'Relevanz' - availability_yes: 'verfügbar' - availability_no: 'nicht verfügbar' - availability_unknown: 'Shopstatus nicht übermittelt' - followup_eyebrow: 'Folgeaktionen' - followup_title: 'Was möchtest du als Nächstes tun?' - shop_meta_fallback_query: 'keine Suchquery ermittelt' - shop_meta_query_mode_optimized: 'optimiert' - shop_meta_query_mode_direct: 'direkt' - shop_meta_default_intent: 'commerce' - shop_meta_title_unavailable: 'Shopdaten nicht verfügbar' - shop_meta_title_completed: 'Shop-Suche abgeschlossen' - shop_meta_title_running: 'Shop-Suche wird ausgeführt' - shop_meta_status_completed: 'Status: abgeschlossen' - shop_meta_status_running: 'Status: läuft' - shop_meta_result_unavailable: 'Shoptreffer: nicht verfügbar' - shop_meta_result_loading: 'Shoptreffer: wird geladen' - shop_meta_repair_used: 'Erweiterte Suche: genutzt' - shop_meta_repair_checked: 'Erweiterte Suche: geprüft' - shop_meta_eyebrow: 'Shop-Suche' - shop_meta_query_label: 'Gesendete Suchquery' - shop_meta_query_prefix: 'Query: ' - shop_meta_intent_prefix: 'Intent: ' - shop_unavailable_default_reason: 'Keine Detailmeldung vom Shopware-Server.' - shop_unavailable_title: 'Shopdaten konnten nicht geladen werden' - shop_unavailable_text_prefix: 'RetrieX antwortet ohne Live-Shopdaten weiter. Ursache: ' - no_llm_history_default: 'Es wurden keine Daten vom LLM empfangen.' - history_notice_default_title: 'Systemhinweis' - history_notice_shop_unavailable_title: 'Shopdaten konnten nicht geladen werden' - history_notice_answer_incomplete_title: 'Antwort konnte nicht abgeschlossen werden' - templates: - rag_hits_count: 'RAG-Treffer: {count}' - shop_hits_count: 'Shop-Treffer: {count}' - shop_results_summary: '{count} Shop-Treffer ausgewertet' - shop_results_top_displayed_suffix: ' · Top {max} angezeigt' - shop_results_repair_suffix: ' · erweiterte Shopsuche genutzt' - relevance_matched_queries: 'Gefunden über: {queries}' - relevance_highlight: 'Passender Shop-Hinweis: {highlight}' - relevance_match_source: 'Trefferquelle: {source}' - relevance_query: 'Passend zur Suchquery: {query}' - relevance_default: 'Aus den Live-Shopdaten übernommen' - shop_meta_result_count: 'Shoptreffer: {count}' - history_notice_without_detail: 'Systemhinweis: {title}.' - history_notice_with_detail: 'Systemhinweis: {title}. Ursache: {detail}' shop_results: max_cards: 5 - follow_up_actions: - commerce: - - label: 'Im Shop suchen' - prompt: 'Suche die aktuelle Produktauswahl im Shop.' - - label: 'Nur Zubehör anzeigen' - prompt: 'Zeige aus der aktuellen Produktauswahl nur Zubehör.' - - label: 'Nur Geräte anzeigen' - prompt: 'Zeige aus der aktuellen Produktauswahl nur Geräte.' - - label: 'Preis anzeigen' - prompt: 'Zeige mir die Preise der aktuell relevanten Produkte.' - knowledge: - - label: 'Technische Details anzeigen' - prompt: 'Zeige technische Details zur aktuellen Antwort.' - - 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: |- diff --git a/config/retriex/chat-messages.yaml b/config/retriex/chat-messages.yaml index 2776e08..1871e89 100644 --- a/config/retriex/chat-messages.yaml +++ b/config/retriex/chat-messages.yaml @@ -73,3 +73,190 @@ parameters: - 'keine konkrete shop-suchanfrage erkannt' - 'shop-suche noch nicht belastbar auflösen' - 'shop-suche noch nicht belastbar aufloesen' + + agent: + input_normalization: + heartbeat_message: Ich optimiere die Anfrage… + 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... + no_concrete_shop_query: Ich kann die Shop-Suche noch nicht belastbar auflösen. Bitte nenne das Produkt, den Messparameter oder das Zubehör etwas konkreter. + 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. + shop_repair_check: Erweiterte Shopsuche wird geprüft… + shop_query_optimization_heartbeat: Shop-Suchanfrage wird optimiert… + generic_internal_error: ❌ Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten. + debug_internal_error_prefix: '❌ Interner Fehler: ' + final_answer_guard: + truncation_message: |2- + + Hinweis: Die Antwort wurde gekürzt, weil eine wiederholte oder zu lange Ausgabe erkannt wurde. Bitte starten Sie bei Bedarf eine präzisere Nachfrage. + shop_runtime: + direct_answer: + intro: '' + no_results: '' + sorted_by_length_note: '' + min_length_filter_note: '' + max_length_filter_note: '' + no_llm_fallback: + messages: + shop_only: 'Ich finde dazu im RAG-Wissen keine belastbare Fachinformation. Aus den Shopdaten ergeben sich folgende Treffer; technische Eignung bitte prüfen:' + shop_with_knowledge: 'Es liegen RAG-/Kontexttreffer und Shopdaten vor. Ohne LLM leite ich daraus keine technische Eignung ab. Die Shopdaten zeigen folgende Treffer; technische Eignung bitte prüfen:' + accessory_only_for_main_device: Die Shop-Treffer wirken wie Zubehör/Verbrauchsmaterial und nicht wie eine angefragte Messanlage oder ein Hauptgerät. Ich werte sie deshalb nicht als passende Hauptlösung. + escalation: Für eine verbindliche Produktauswahl sollte der konkrete Anwendungsfall durch Vertrieb oder Support geprüft werden. + knowledge_only: Ich habe Treffer im RAG-Wissen gefunden, aber ohne LLM kann ich daraus keine belastbare fachliche Antwort synthetisieren. Ich gebe deshalb keine sichere Produktaussage aus. Bitte aktiviere das LLM oder konkretisiere die Frage für eine gezielte Prüfung. + no_data: Ich finde dazu keine belastbaren Daten in den vorliegenden Quellen. Bitte nenne Produkt, Messparameter, Zubehör oder Anwendungsfall genauer. + no_shop_results_with_knowledge: Ich finde RAG-/Kontexttreffer, aber keine passenden Shop-Treffer zur aktuellen Suchanfrage. Das ist keine Aussage, dass es das Produkt nicht gibt. Ohne LLM gebe ich keine technische Negativaussage aus; bitte prüfe den Suchbegriff oder den Anwendungsfall gezielter. + no_shop_results_no_knowledge: Ich finde weder belastbares RAG-Wissen noch passende Shop-Treffer zur aktuellen Suchanfrage. Das ist keine sichere Negativaussage. Bitte nenne Produkt, Messparameter oder Zubehör konkreter. + shop_unavailable_with_knowledge: Live-Shopdaten konnten nicht geladen werden. Ich kann keine Aussage zu aktueller Verfügbarkeit, Preis oder Shop-Portfolio treffen. Wenn das RAG-Wissen einen direkten Fachbeleg enthält, wird die fachliche Antwort davon getrennt betrachtet. + shop_unavailable_no_knowledge: Live-Shopdaten konnten nicht geladen werden und die RAG-Treffer enthalten keinen direkten Fachbeleg zur Anfrage. Ich kann daraus keine verlässliche Produkt-, Verfügbarkeits- oder Portfolioaussage ableiten. + product_fields: + unreadable_results_message: '- Es wurden Shop-Treffer übergeben, aber keine lesbaren Produktdaten gefunden.' + unnamed_product: Unbenanntes Shop-Produkt + product_number_template: Art.-Nr. {value} + manufacturer_template: 'Hersteller: {value}' + price_template: 'Preis: {value}' + availability_template: 'Verfügbar: {value}' + availability_yes: ja + availability_no: nein + url_template: 'URL: {value}' + incompatible_role_note: 'Hinweis: Zubehör/Verbrauchsartikel; nicht als Messanlage/Gerät bestätigt' + line_template: '{index}. {parts}' + separator: ' | ' + unavailable_reason_template: '{message} Ursache: {reason}' + production_ui: + stage_labels: + preparing_answer: Antwort wird vorbereitet + shop_routing_detected: Shop-Routing erkannt + rag_searched: RAG-Wissen wurde durchsucht + shop_search_preparing: Shop-Suche wird vorbereitet + more_context_needed: Mehr Kontext nötig + shop_search_running: Shop wird durchsucht + shop_unavailable: Shopdaten nicht verfügbar + shop_completed: Shop-Suche abgeschlossen + answer_generating: Antwort wird generiert + completed: Abgeschlossen + interrupted: Antwort wurde unterbrochen + confidence_labels: + checking_evidence: Beleglage wird geprüft + checking_shop_data: Shopdaten werden geprüft + more_context_needed: mehr Kontext nötig + interrupted: nicht abgeschlossen + direct: fachlich belegt + aggregate_missing: geprüfte Quellen, keine passende Zählinformation + weak: RAG-Näherungstreffer, kein direkter Fachbeleg + default: noch keine belastbaren Treffer + direct_shop_check: fachlich belegt; Shopdaten werden geprüft + aggregate_missing_shop_check: geprüfte Quellen ohne Zählinformation; Shopdaten werden geprüft + weak_shop_check: RAG-Näherungstreffer; Shopdaten werden geprüft + default_shop_check: Shopdaten werden geprüft + aggregate_missing_shop_unavailable: geprüfte Quellen ohne Zählinformation; Shopdaten nicht verfügbar + aggregate_missing_no_count: geprüfte Quellen, keine passende Zählinformation + shop_unavailable_with_knowledge: fachlich belegt; Shopdaten nicht verfügbar + shop_unavailable: Shopdaten nicht verfügbar + rag_and_shop: RAG + Shopdaten + shop_only: nur Shopdaten + rag_no_shop_hits: RAG-Wissen, keine Shop-Treffer + no_reliable_data: keine belastbaren Daten + no_reliable_hits: noch keine belastbaren Treffer + text: + live_shop_source_plain_label: Live-Shopdaten + run_status_eyebrow: RetrieX-Status + evidence_prefix: 'Beleglage: ' + data_basis_label: Datenbasis + data_basis_empty_completed: keine belastbare Datenbasis + data_basis_empty_running: wird geprüft + rag_hits_checking: 'RAG-Treffer: wird geprüft' + shop_hits_loading: 'Shop-Treffer: wird geladen' + shop_hits_unavailable: 'Shop-Treffer: nicht verfügbar' + shop_hits_no_query: 'Shop-Treffer: keine Suchquery' + shop_hits_not_requested: 'Shop-Treffer: nicht angefragt' + status_completed: 'Status: abgeschlossen' + status_running: 'Status: läuft' + shop_results_eyebrow: Shop-Ergebnisse + shop_results_title: Shop-Ergebnisse + evaluated_query_label: Ausgewertete Suchquery + unnamed_product: Unbenanntes Produkt + field_not_provided: nicht übermittelt + product_number_label: Artikelnummer + price_label: Preis + availability_label: Verfügbarkeit + manufacturer_label: Hersteller + relevance_label: Relevanz + availability_yes: verfügbar + availability_no: nicht verfügbar + availability_unknown: Shopstatus nicht übermittelt + followup_eyebrow: Folgeaktionen + followup_title: Was möchtest du als Nächstes tun? + shop_meta_fallback_query: keine Suchquery ermittelt + shop_meta_query_mode_optimized: optimiert + shop_meta_query_mode_direct: direkt + shop_meta_default_intent: commerce + shop_meta_title_unavailable: Shopdaten nicht verfügbar + shop_meta_title_completed: Shop-Suche abgeschlossen + shop_meta_title_running: Shop-Suche wird ausgeführt + shop_meta_status_completed: 'Status: abgeschlossen' + shop_meta_status_running: 'Status: läuft' + shop_meta_result_unavailable: 'Shoptreffer: nicht verfügbar' + shop_meta_result_loading: 'Shoptreffer: wird geladen' + shop_meta_repair_used: 'Erweiterte Suche: genutzt' + shop_meta_repair_checked: 'Erweiterte Suche: geprüft' + shop_meta_eyebrow: Shop-Suche + shop_meta_query_label: Gesendete Suchquery + shop_meta_query_prefix: 'Query: ' + shop_meta_intent_prefix: 'Intent: ' + shop_unavailable_default_reason: Keine Detailmeldung vom Shopware-Server. + shop_unavailable_title: Shopdaten konnten nicht geladen werden + shop_unavailable_text_prefix: 'RetrieX antwortet ohne Live-Shopdaten weiter. Ursache: ' + no_llm_history_default: Es wurden keine Daten vom LLM empfangen. + history_notice_default_title: Systemhinweis + history_notice_shop_unavailable_title: Shopdaten konnten nicht geladen werden + history_notice_answer_incomplete_title: Antwort konnte nicht abgeschlossen werden + templates: + rag_hits_count: 'RAG-Treffer: {count}' + shop_hits_count: 'Shop-Treffer: {count}' + shop_results_summary: '{count} Shop-Treffer ausgewertet' + shop_results_top_displayed_suffix: ' · Top {max} angezeigt' + shop_results_repair_suffix: ' · erweiterte Shopsuche genutzt' + relevance_matched_queries: 'Gefunden über: {queries}' + relevance_highlight: 'Passender Shop-Hinweis: {highlight}' + relevance_match_source: 'Trefferquelle: {source}' + relevance_query: 'Passend zur Suchquery: {query}' + relevance_default: Aus den Live-Shopdaten übernommen + shop_meta_result_count: 'Shoptreffer: {count}' + history_notice_without_detail: 'Systemhinweis: {title}.' + history_notice_with_detail: 'Systemhinweis: {title}. Ursache: {detail}' + follow_up_actions: + commerce: + - label: Im Shop suchen + prompt: Suche die aktuelle Produktauswahl im Shop. + - label: Nur Zubehör anzeigen + prompt: Zeige aus der aktuellen Produktauswahl nur Zubehör. + - label: Nur Geräte anzeigen + prompt: Zeige aus der aktuellen Produktauswahl nur Geräte. + - label: Preis anzeigen + prompt: Zeige mir die Preise der aktuell relevanten Produkte. + knowledge: + - label: Technische Details anzeigen + prompt: Zeige technische Details zur aktuellen Antwort. + 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" diff --git a/config/services.yaml b/config/services.yaml index 384addf..56551dd 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -146,6 +146,7 @@ services: $config: '%retriex.agent.config%' $vocabulary: '@App\Config\DomainVocabularyConfig' $genreConfig: '@App\Config\GenreConfig' + $chatMessages: '@App\Config\ChatMessagesConfig' App\Config\ChatMessagesConfig: arguments: diff --git a/patch_history/RETRIEX_PATCH_65_AGENT_CHAT_MESSAGES_UNIFICATION_README.md b/patch_history/RETRIEX_PATCH_65_AGENT_CHAT_MESSAGES_UNIFICATION_README.md new file mode 100644 index 0000000..578c2a3 --- /dev/null +++ b/patch_history/RETRIEX_PATCH_65_AGENT_CHAT_MESSAGES_UNIFICATION_README.md @@ -0,0 +1,63 @@ +# RetrieX Patch p65 - Agent Chat Messages Unification + +## Ziel + +p65 fuehrt die p63/p64 Chat-Messages-Linie weiter und verschiebt chat-sichtbare Agent-/Production-UI-Texte aus `config/retriex/agent.yaml` nach `config/retriex/chat-messages.yaml`. + +Der Patch aendert keine Retrieval-, Ranking-, Scoring-, Intent- oder Shopware-Logik. Er aendert nur die Konfigurationsquelle fuer nutzerlesbare Agent-/UI-Meldungen. + +## Geaenderte Dateien + +- `config/retriex/chat-messages.yaml` + - neuer Bereich `agent` + - uebernimmt Agent-Statusmeldungen, No-LLM-Fallback-Texte, Product-Field-Texte, Production-UI-Texte, Source-Labels, HTML-Templates und den Final-Answer-Guard-Hinweis +- `config/retriex/agent.yaml` + - entfernt die verschobenen chat-sichtbaren Textbloecke + - behaelt technische Orchestration, Limits, Guards, Routing-, Prompt- und Runtime-Konfiguration +- `src/Config/ChatMessagesConfig.php` + - bietet generische String-, Allow-Empty-String- und Action-List-Accessor fuer Agent-Chattexte + - validiert die neu verschobenen Agent-Chattexte +- `src/Config/AgentRunnerConfig.php` + - liest chat-sichtbare Agent-Texte bevorzugt aus `ChatMessagesConfig` + - behaelt Legacy-Fallbacks auf die alten `agent.yaml`-Pfade fuer Rueckwaertskompatibilitaet +- `config/services.yaml` + - injiziert `ChatMessagesConfig` in `AgentRunnerConfig` + +## Bewusst nicht geaendert + +- Keine Aenderung an `AgentRunner.php` +- Keine Aenderung an Prompt-, Retrieval-, Scoring-, Ranking- oder Shop-Matching-Logik +- Keine Aenderung an technischen Protokollwerten, Status-Enums, CSS-Klassen oder internen Keys +- Genre-sensitive Direct-Answer-Konfiguration in `genre.yaml` bleibt unberuehrt; p65 verschiebt nur die Agent-Fallback-Texte aus `agent.yaml` + +## Lokale Checks + +Ausfuehrbar und gruen im Patch-Arbeitsverzeichnis: + +```bash +php -l src/Config/AgentRunnerConfig.php +php -l src/Config/ChatMessagesConfig.php +php -l src/Config/RetriexEffectiveConfigProvider.php +php -l src/Config/ConfigSourceAuditProvider.php +php -l src/Config/GenreSourceOfTruthGuard.php +python3 - <<'PY' +import yaml +for f in ['config/retriex/agent.yaml','config/retriex/chat-messages.yaml','config/services.yaml']: + yaml.safe_load(open(f)) + print('yaml ok', f) +PY +``` + +Zusatzcheck: Die verschobenen Werte aus `agent.yaml` wurden gegen `chat-messages.yaml` verglichen und semantisch identisch erhalten. `ChatMessagesConfig::validate()` sowie wichtige `AgentRunnerConfig`-Getter wurden per kleinem PHP-Smoke-Test erfolgreich geprueft. + +## Nicht lokal ausfuehrbar + +Die Console-Checks koennen im entpackten ZIP ohne `vendor/` nicht lokal laufen: + +```bash +php bin/console mto:agent:config:validate +php bin/console mto:agent:regression:test +php bin/console mto:agent:config:audit-source --details +``` + +Bitte diese Checks nach dem Einspielen in der Zielumgebung mit installierten Dependencies ausfuehren. diff --git a/src/Config/AgentRunnerConfig.php b/src/Config/AgentRunnerConfig.php index c19349d..b6bc405 100644 --- a/src/Config/AgentRunnerConfig.php +++ b/src/Config/AgentRunnerConfig.php @@ -13,6 +13,7 @@ final class AgentRunnerConfig private readonly array $config = [], private readonly ?DomainVocabularyConfig $vocabulary = null, private readonly ?GenreConfig $genreConfig = null, + private readonly ?ChatMessagesConfig $chatMessages = null, ) { } @@ -186,7 +187,7 @@ final class AgentRunnerConfig public function getInputNormalizationHeartbeatMessage(): string { - return $this->getRequiredString('input_normalization.heartbeat_message'); + return $this->getChatRequiredString('agent.input_normalization.heartbeat_message', 'input_normalization.heartbeat_message'); } public function getInputNormalizationOutputPrefixPattern(): string @@ -376,6 +377,41 @@ final class AgentRunnerConfig throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a non-empty string.', $key)); } + private function getChatRequiredString(string $chatKey, string $legacyKey): string + { + if ($this->chatMessages !== null) { + return $this->chatMessages->getString($chatKey); + } + + return $this->getRequiredString($legacyKey); + } + + private function getChatStringAllowEmpty(string $chatKey, string $legacyKey): string + { + if ($this->chatMessages !== null) { + return $this->chatMessages->getStringAllowEmpty($chatKey); + } + + $value = $this->requiredValue($legacyKey); + if (is_string($value)) { + return $value; + } + + throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a string.', $legacyKey)); + } + + /** + * @return array + */ + private function getChatActionList(string $chatKey, string $legacyKey): array + { + if ($this->chatMessages !== null) { + return $this->chatMessages->getActionList($chatKey); + } + + return $this->getRequiredActionList($legacyKey); + } + private function getOptionalBool(string $key, bool $default): bool { $value = $this->optionalValue($key); @@ -677,52 +713,52 @@ final class AgentRunnerConfig public function getEmptyPromptMessage(): string { - return $this->getRequiredString('messages.empty_prompt'); + return $this->getChatRequiredString('agent.messages.empty_prompt', 'messages.empty_prompt'); } public function getAnalyzeRequestMessage(): string { - return $this->getRequiredString('messages.analyze_request'); + return $this->getChatRequiredString('agent.messages.analyze_request', 'messages.analyze_request'); } public function getCheckInternetSourcesMessage(): string { - return $this->getRequiredString('messages.check_internet_sources'); + return $this->getChatRequiredString('agent.messages.check_internet_sources', 'messages.check_internet_sources'); } public function getRetrieveKnowledgeMessage(): string { - return $this->getRequiredString('messages.retrieve_knowledge'); + return $this->getChatRequiredString('agent.messages.retrieve_knowledge', 'messages.retrieve_knowledge'); } public function getOptimizeSearchMessage(): string { - return $this->getRequiredString('messages.optimize_search'); + return $this->getChatRequiredString('agent.messages.optimize_search', 'messages.optimize_search'); } public function getNoConcreteShopQueryMessage(): string { - return $this->getRequiredString('messages.no_concrete_shop_query'); + return $this->getChatRequiredString('agent.messages.no_concrete_shop_query', 'messages.no_concrete_shop_query'); } public function getFetchSearchDataMessageTemplate(): string { - return $this->getRequiredString('messages.fetch_search_data_template'); + return $this->getChatRequiredString('agent.messages.fetch_search_data_template', 'messages.fetch_search_data_template'); } public function getAnalyzeAllInformationMessage(): string { - return $this->getRequiredString('messages.analyze_all_information'); + return $this->getChatRequiredString('agent.messages.analyze_all_information', 'messages.analyze_all_information'); } public function getThinkingWhileStreamingMessage(): string { - return $this->getRequiredString('messages.thinking_while_streaming'); + return $this->getChatRequiredString('agent.messages.thinking_while_streaming', 'messages.thinking_while_streaming'); } public function getNoLlmDataReceivedMessage(): string { - return $this->getRequiredString('messages.no_llm_data_received'); + return $this->getChatRequiredString('agent.messages.no_llm_data_received', 'messages.no_llm_data_received'); } public function isFinalAnswerGuardEnabled(): bool @@ -737,7 +773,7 @@ final class AgentRunnerConfig public function getFinalAnswerGuardTruncationMessage(): string { - return $this->getRequiredString('final_answer_guard.truncation_message'); + return $this->getChatRequiredString('agent.final_answer_guard.truncation_message', 'final_answer_guard.truncation_message'); } public function isFinalAnswerRepeatedLineGuardEnabled(): bool @@ -788,31 +824,31 @@ final class AgentRunnerConfig public function getDirectShopResultAnswerIntro(): string { return $this->genreString('shop_query_runtime.direct_answer.intro') - ?: $this->getRequiredString('shop_runtime.direct_answer.intro'); + ?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.intro', 'shop_runtime.direct_answer.intro'); } public function getDirectShopResultAnswerNoResultsMessage(): string { return $this->genreString('shop_query_runtime.direct_answer.no_results') - ?: $this->getRequiredString('shop_runtime.direct_answer.no_results'); + ?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.no_results', 'shop_runtime.direct_answer.no_results'); } public function getDirectShopResultAnswerSortedByLengthNote(): string { return $this->genreString('shop_query_runtime.direct_answer.sorted_by_length_note') - ?: $this->getRequiredString('shop_runtime.direct_answer.sorted_by_length_note'); + ?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.sorted_by_length_note', 'shop_runtime.direct_answer.sorted_by_length_note'); } public function getDirectShopResultAnswerMinLengthFilterNote(): string { return $this->genreString('shop_query_runtime.direct_answer.min_length_filter_note') - ?: $this->getRequiredString('shop_runtime.direct_answer.min_length_filter_note'); + ?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.min_length_filter_note', 'shop_runtime.direct_answer.min_length_filter_note'); } public function getDirectShopResultAnswerMaxLengthFilterNote(): string { return $this->genreString('shop_query_runtime.direct_answer.max_length_filter_note') - ?: $this->getRequiredString('shop_runtime.direct_answer.max_length_filter_note'); + ?: $this->getChatStringAllowEmpty('agent.shop_runtime.direct_answer.max_length_filter_note', 'shop_runtime.direct_answer.max_length_filter_note'); } public function getNoLlmFallbackMaxShopResults(): int @@ -870,63 +906,63 @@ final class AgentRunnerConfig public function getNoLlmFallbackShopOnlyMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.shop_only'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.shop_only', 'no_llm_fallback.messages.shop_only'); } public function getNoLlmFallbackShopWithKnowledgeMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.shop_with_knowledge'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.shop_with_knowledge', 'no_llm_fallback.messages.shop_with_knowledge'); } public function getNoLlmFallbackAccessoryOnlyForMainDeviceMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.accessory_only_for_main_device'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.accessory_only_for_main_device', 'no_llm_fallback.messages.accessory_only_for_main_device'); } public function getNoLlmFallbackEscalationMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.escalation'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.escalation', 'no_llm_fallback.messages.escalation'); } public function getNoLlmFallbackKnowledgeOnlyMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.knowledge_only'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.knowledge_only', 'no_llm_fallback.messages.knowledge_only'); } public function getNoLlmFallbackNoDataMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.no_data'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.no_data', 'no_llm_fallback.messages.no_data'); } public function getShopRepairCheckMessage(): string { - return $this->getRequiredString('messages.shop_repair_check'); + return $this->getChatRequiredString('agent.messages.shop_repair_check', 'messages.shop_repair_check'); } public function getShopQueryOptimizationHeartbeatMessage(): string { - return $this->getRequiredString('messages.shop_query_optimization_heartbeat'); + return $this->getChatRequiredString('agent.messages.shop_query_optimization_heartbeat', 'messages.shop_query_optimization_heartbeat'); } public function getProductionUiStageLabel(string $key): string { - return $this->getRequiredString('production_ui.stage_labels.' . $key); + return $this->getChatRequiredString('agent.production_ui.stage_labels.' . $key, 'production_ui.stage_labels.' . $key); } public function getProductionUiConfidenceLabel(string $key): string { - return $this->getRequiredString('production_ui.confidence_labels.' . $key); + return $this->getChatRequiredString('agent.production_ui.confidence_labels.' . $key, 'production_ui.confidence_labels.' . $key); } public function getProductionUiText(string $key): string { - return $this->getRequiredString('production_ui.text.' . $key); + return $this->getChatRequiredString('agent.production_ui.text.' . $key, 'production_ui.text.' . $key); } public function getProductionUiTemplate(string $key): string { - return $this->getRequiredString('production_ui.templates.' . $key); + return $this->getChatRequiredString('agent.production_ui.templates.' . $key, 'production_ui.templates.' . $key); } public function getProductionUiShopResultsMaxCards(): int @@ -939,22 +975,22 @@ final class AgentRunnerConfig */ public function getProductionUiFollowUpActions(string $group): array { - return $this->getRequiredActionList('production_ui.follow_up_actions.' . $group); + return $this->getChatActionList('agent.production_ui.follow_up_actions.' . $group, 'production_ui.follow_up_actions.' . $group); } public function getNoLlmProductField(string $key): string { - return $this->getRequiredString('no_llm_fallback.product_fields.' . $key); + return $this->getChatRequiredString('agent.no_llm_fallback.product_fields.' . $key, 'no_llm_fallback.product_fields.' . $key); } public function getNoLlmFallbackNoShopResultsWithKnowledgeMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.no_shop_results_with_knowledge'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.no_shop_results_with_knowledge', 'no_llm_fallback.messages.no_shop_results_with_knowledge'); } public function getNoLlmFallbackNoShopResultsNoKnowledgeMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.no_shop_results_no_knowledge'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.no_shop_results_no_knowledge', 'no_llm_fallback.messages.no_shop_results_no_knowledge'); } /** @@ -991,82 +1027,82 @@ final class AgentRunnerConfig public function getNoLlmFallbackShopUnavailableWithKnowledgeMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.shop_unavailable_with_knowledge'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.shop_unavailable_with_knowledge', 'no_llm_fallback.messages.shop_unavailable_with_knowledge'); } public function getNoLlmFallbackShopUnavailableNoKnowledgeMessage(): string { - return $this->getRequiredString('no_llm_fallback.messages.shop_unavailable_no_knowledge'); + return $this->getChatRequiredString('agent.no_llm_fallback.messages.shop_unavailable_no_knowledge', 'no_llm_fallback.messages.shop_unavailable_no_knowledge'); } public function getGenericInternalErrorMessage(): string { - return $this->getRequiredString('messages.generic_internal_error'); + return $this->getChatRequiredString('agent.messages.generic_internal_error', 'messages.generic_internal_error'); } public function getDebugInternalErrorPrefix(): string { - return $this->getRequiredString('messages.debug_internal_error_prefix'); + return $this->getChatRequiredString('agent.messages.debug_internal_error_prefix', 'messages.debug_internal_error_prefix'); } public function getExternalUrlSourceLabel(): string { - return $this->getRequiredString('source_labels.external_url'); + return $this->getChatRequiredString('agent.source_labels.external_url', 'source_labels.external_url'); } public function getRagKnowledgeSourceLabel(): string { - return $this->getRequiredString('source_labels.rag_knowledge'); + return $this->getChatRequiredString('agent.source_labels.rag_knowledge', 'source_labels.rag_knowledge'); } public function getConversationHistorySourceLabel(): string { - return $this->getRequiredString('source_labels.conversation_history'); + return $this->getChatRequiredString('agent.source_labels.conversation_history', 'source_labels.conversation_history'); } public function getShopSystemSourceLabel(): string { - return $this->getRequiredString('source_labels.shop_system'); + return $this->getChatRequiredString('agent.source_labels.shop_system', 'source_labels.shop_system'); } public function getExtendedShopSearchSourceLabel(): string { - return $this->getRequiredString('source_labels.extended_shop_search'); + return $this->getChatRequiredString('agent.source_labels.extended_shop_search', 'source_labels.extended_shop_search'); } public function getUsedSourcesPrefix(): string { - return $this->getRequiredString('source_labels.used_sources_prefix'); + return $this->getChatRequiredString('agent.source_labels.used_sources_prefix', 'source_labels.used_sources_prefix'); } public function getSourcesPrefix(): string { - return $this->getRequiredString('source_labels.sources_prefix'); + return $this->getChatRequiredString('agent.source_labels.sources_prefix', 'source_labels.sources_prefix'); } public function getSourceBadgeHtmlTemplate(): string { - return $this->getRequiredString('html.source_badge_template'); + return $this->getChatRequiredString('agent.html.source_badge_template', 'html.source_badge_template'); } public function getErrorHtmlTemplate(): string { - return $this->getRequiredString('html.error_template'); + return $this->getChatRequiredString('agent.html.error_template', 'html.error_template'); } public function getThinkHtmlTemplate(): string { - return $this->getRequiredString('html.think_template'); + return $this->getChatRequiredString('agent.html.think_template', 'html.think_template'); } public function getInfoHtmlTemplate(): string { - return $this->getRequiredString('html.info_template'); + return $this->getChatRequiredString('agent.html.info_template', 'html.info_template'); } public function getDebugHtmlTemplate(): string { - return $this->getRequiredString('html.debug_template'); + return $this->getChatRequiredString('agent.html.debug_template', 'html.debug_template'); } public function getShopPrompt(string $prompt, string $commerceHistoryContext = ''): string { diff --git a/src/Config/ChatMessagesConfig.php b/src/Config/ChatMessagesConfig.php index bc453a2..cb45520 100644 --- a/src/Config/ChatMessagesConfig.php +++ b/src/Config/ChatMessagesConfig.php @@ -156,6 +156,30 @@ final class ChatMessagesConfig throw new \InvalidArgumentException('RetrieX chat messages config key "frontend" must be an array.'); } + public function getString(string $path): string + { + return $this->string($path); + } + + public function getStringAllowEmpty(string $path): string + { + $value = $this->value($path); + + if (is_string($value)) { + return $value; + } + + throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must be a string.', $path)); + } + + /** + * @return array + */ + public function getActionList(string $path): array + { + return $this->actionList($path); + } + /** * @return array */ @@ -187,6 +211,14 @@ final class ChatMessagesConfig } } + foreach ($this->requiredActionListPaths() as $path) { + try { + $this->actionList($path); + } catch (\InvalidArgumentException $e) { + $errors[] = $e->getMessage(); + } + } + return [ 'status' => $errors === [] ? 'OK' : 'ERROR', 'errors' => $errors, @@ -251,6 +283,165 @@ final class ChatMessagesConfig 'frontend.stream.missing_retry', 'frontend.stream.stale_retry', 'frontend.stream.connection_interrupted_retry', + 'agent.input_normalization.heartbeat_message', + 'agent.messages.empty_prompt', + 'agent.messages.analyze_request', + 'agent.messages.check_internet_sources', + 'agent.messages.retrieve_knowledge', + 'agent.messages.optimize_search', + 'agent.messages.no_concrete_shop_query', + 'agent.messages.fetch_search_data_template', + 'agent.messages.analyze_all_information', + 'agent.messages.thinking_while_streaming', + 'agent.messages.no_llm_data_received', + 'agent.messages.shop_repair_check', + 'agent.messages.shop_query_optimization_heartbeat', + 'agent.messages.generic_internal_error', + 'agent.messages.debug_internal_error_prefix', + 'agent.final_answer_guard.truncation_message', + 'agent.no_llm_fallback.messages.shop_only', + 'agent.no_llm_fallback.messages.shop_with_knowledge', + 'agent.no_llm_fallback.messages.accessory_only_for_main_device', + 'agent.no_llm_fallback.messages.escalation', + 'agent.no_llm_fallback.messages.knowledge_only', + 'agent.no_llm_fallback.messages.no_data', + 'agent.no_llm_fallback.messages.no_shop_results_with_knowledge', + 'agent.no_llm_fallback.messages.no_shop_results_no_knowledge', + 'agent.no_llm_fallback.messages.shop_unavailable_with_knowledge', + 'agent.no_llm_fallback.messages.shop_unavailable_no_knowledge', + 'agent.no_llm_fallback.product_fields.unreadable_results_message', + 'agent.no_llm_fallback.product_fields.unnamed_product', + 'agent.no_llm_fallback.product_fields.product_number_template', + 'agent.no_llm_fallback.product_fields.manufacturer_template', + 'agent.no_llm_fallback.product_fields.price_template', + 'agent.no_llm_fallback.product_fields.availability_template', + 'agent.no_llm_fallback.product_fields.availability_yes', + 'agent.no_llm_fallback.product_fields.availability_no', + 'agent.no_llm_fallback.product_fields.url_template', + 'agent.no_llm_fallback.product_fields.incompatible_role_note', + 'agent.no_llm_fallback.product_fields.line_template', + 'agent.no_llm_fallback.product_fields.separator', + 'agent.no_llm_fallback.product_fields.unavailable_reason_template', + 'agent.production_ui.stage_labels.preparing_answer', + 'agent.production_ui.stage_labels.shop_routing_detected', + 'agent.production_ui.stage_labels.rag_searched', + 'agent.production_ui.stage_labels.shop_search_preparing', + 'agent.production_ui.stage_labels.more_context_needed', + 'agent.production_ui.stage_labels.shop_search_running', + 'agent.production_ui.stage_labels.shop_unavailable', + 'agent.production_ui.stage_labels.shop_completed', + 'agent.production_ui.stage_labels.answer_generating', + 'agent.production_ui.stage_labels.completed', + 'agent.production_ui.stage_labels.interrupted', + 'agent.production_ui.confidence_labels.checking_evidence', + 'agent.production_ui.confidence_labels.checking_shop_data', + 'agent.production_ui.confidence_labels.more_context_needed', + 'agent.production_ui.confidence_labels.interrupted', + 'agent.production_ui.confidence_labels.direct', + 'agent.production_ui.confidence_labels.aggregate_missing', + 'agent.production_ui.confidence_labels.weak', + 'agent.production_ui.confidence_labels.default', + 'agent.production_ui.confidence_labels.direct_shop_check', + 'agent.production_ui.confidence_labels.aggregate_missing_shop_check', + 'agent.production_ui.confidence_labels.weak_shop_check', + 'agent.production_ui.confidence_labels.default_shop_check', + 'agent.production_ui.confidence_labels.aggregate_missing_shop_unavailable', + 'agent.production_ui.confidence_labels.aggregate_missing_no_count', + 'agent.production_ui.confidence_labels.shop_unavailable_with_knowledge', + 'agent.production_ui.confidence_labels.shop_unavailable', + 'agent.production_ui.confidence_labels.rag_and_shop', + 'agent.production_ui.confidence_labels.shop_only', + 'agent.production_ui.confidence_labels.rag_no_shop_hits', + 'agent.production_ui.confidence_labels.no_reliable_data', + 'agent.production_ui.confidence_labels.no_reliable_hits', + 'agent.production_ui.text.live_shop_source_plain_label', + 'agent.production_ui.text.run_status_eyebrow', + 'agent.production_ui.text.evidence_prefix', + 'agent.production_ui.text.data_basis_label', + 'agent.production_ui.text.data_basis_empty_completed', + 'agent.production_ui.text.data_basis_empty_running', + 'agent.production_ui.text.rag_hits_checking', + 'agent.production_ui.text.shop_hits_loading', + 'agent.production_ui.text.shop_hits_unavailable', + 'agent.production_ui.text.shop_hits_no_query', + 'agent.production_ui.text.shop_hits_not_requested', + 'agent.production_ui.text.status_completed', + 'agent.production_ui.text.status_running', + 'agent.production_ui.text.shop_results_eyebrow', + 'agent.production_ui.text.shop_results_title', + 'agent.production_ui.text.evaluated_query_label', + 'agent.production_ui.text.unnamed_product', + 'agent.production_ui.text.field_not_provided', + 'agent.production_ui.text.product_number_label', + 'agent.production_ui.text.price_label', + 'agent.production_ui.text.availability_label', + 'agent.production_ui.text.manufacturer_label', + 'agent.production_ui.text.relevance_label', + 'agent.production_ui.text.availability_yes', + 'agent.production_ui.text.availability_no', + 'agent.production_ui.text.availability_unknown', + 'agent.production_ui.text.followup_eyebrow', + 'agent.production_ui.text.followup_title', + 'agent.production_ui.text.shop_meta_fallback_query', + 'agent.production_ui.text.shop_meta_query_mode_optimized', + 'agent.production_ui.text.shop_meta_query_mode_direct', + 'agent.production_ui.text.shop_meta_default_intent', + 'agent.production_ui.text.shop_meta_title_unavailable', + 'agent.production_ui.text.shop_meta_title_completed', + 'agent.production_ui.text.shop_meta_title_running', + 'agent.production_ui.text.shop_meta_status_completed', + 'agent.production_ui.text.shop_meta_status_running', + 'agent.production_ui.text.shop_meta_result_unavailable', + 'agent.production_ui.text.shop_meta_result_loading', + 'agent.production_ui.text.shop_meta_repair_used', + 'agent.production_ui.text.shop_meta_repair_checked', + 'agent.production_ui.text.shop_meta_eyebrow', + 'agent.production_ui.text.shop_meta_query_label', + 'agent.production_ui.text.shop_meta_query_prefix', + 'agent.production_ui.text.shop_meta_intent_prefix', + 'agent.production_ui.text.shop_unavailable_default_reason', + 'agent.production_ui.text.shop_unavailable_title', + 'agent.production_ui.text.shop_unavailable_text_prefix', + 'agent.production_ui.text.no_llm_history_default', + 'agent.production_ui.text.history_notice_default_title', + 'agent.production_ui.text.history_notice_shop_unavailable_title', + 'agent.production_ui.text.history_notice_answer_incomplete_title', + 'agent.production_ui.templates.rag_hits_count', + 'agent.production_ui.templates.shop_hits_count', + 'agent.production_ui.templates.shop_results_summary', + 'agent.production_ui.templates.shop_results_top_displayed_suffix', + 'agent.production_ui.templates.shop_results_repair_suffix', + 'agent.production_ui.templates.relevance_matched_queries', + 'agent.production_ui.templates.relevance_highlight', + 'agent.production_ui.templates.relevance_match_source', + 'agent.production_ui.templates.relevance_query', + 'agent.production_ui.templates.relevance_default', + 'agent.production_ui.templates.shop_meta_result_count', + 'agent.production_ui.templates.history_notice_without_detail', + 'agent.production_ui.templates.history_notice_with_detail', + 'agent.source_labels.external_url', + 'agent.source_labels.rag_knowledge', + 'agent.source_labels.conversation_history', + 'agent.source_labels.shop_system', + 'agent.source_labels.extended_shop_search', + 'agent.source_labels.used_sources_prefix', + 'agent.source_labels.sources_prefix', + 'agent.html.source_badge_template', + 'agent.html.error_template', + 'agent.html.think_template', + 'agent.html.info_template', + 'agent.html.debug_template', + ]; + } + + /** + * @return list + */ + private function requiredActionListPaths(): array + { + return [ + 'agent.production_ui.follow_up_actions.commerce', + 'agent.production_ui.follow_up_actions.knowledge', ]; } @@ -296,6 +487,44 @@ final class ChatMessagesConfig return ''; } + /** + * @return array + */ + private function actionList(string $path): array + { + $value = $this->value($path); + + if (!is_array($value)) { + throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must be a list of action definitions.', $path)); + } + + $out = []; + + foreach ($value as $item) { + if (!is_array($item)) { + continue; + } + + $label = isset($item['label']) && is_scalar($item['label']) ? trim((string) $item['label']) : ''; + $prompt = isset($item['prompt']) && is_scalar($item['prompt']) ? trim((string) $item['prompt']) : ''; + + if ($label === '' || $prompt === '') { + continue; + } + + $out[] = [ + 'label' => $label, + 'prompt' => $prompt, + ]; + } + + if ($out === []) { + throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must contain at least one valid action definition.', $path)); + } + + return $out; + } + /** * @return list */