last update before full new 1.5.0
This commit is contained in:
@@ -21,6 +21,90 @@ parameters:
|
|||||||
generic_internal_error: '❌ Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten.'
|
generic_internal_error: '❌ Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten.'
|
||||||
debug_internal_error_prefix: '❌ Interner Fehler: '
|
debug_internal_error_prefix: '❌ Interner Fehler: '
|
||||||
|
|
||||||
|
rag_evidence_guard:
|
||||||
|
stop_terms:
|
||||||
|
- suche
|
||||||
|
- suchen
|
||||||
|
- finde
|
||||||
|
- finden
|
||||||
|
- zeige
|
||||||
|
- einen
|
||||||
|
- eine
|
||||||
|
- einem
|
||||||
|
- einer
|
||||||
|
- der
|
||||||
|
- die
|
||||||
|
- das
|
||||||
|
- den
|
||||||
|
- dem
|
||||||
|
- des
|
||||||
|
- für
|
||||||
|
- fuer
|
||||||
|
- mit
|
||||||
|
- ohne
|
||||||
|
- und
|
||||||
|
- oder
|
||||||
|
- kann
|
||||||
|
- können
|
||||||
|
- koennen
|
||||||
|
- messen
|
||||||
|
- messung
|
||||||
|
- tester
|
||||||
|
- testgerät
|
||||||
|
- testgeraet
|
||||||
|
- gerät
|
||||||
|
- geraet
|
||||||
|
- messgerät
|
||||||
|
- messgeraet
|
||||||
|
- produkt
|
||||||
|
- produkte
|
||||||
|
- artikel
|
||||||
|
- shop
|
||||||
|
synonyms:
|
||||||
|
salinität:
|
||||||
|
- salinität
|
||||||
|
- salinitaet
|
||||||
|
- salinity
|
||||||
|
- salzgehalt
|
||||||
|
- tds
|
||||||
|
- leitfähigkeit
|
||||||
|
- leitfaehigkeit
|
||||||
|
salinitaet:
|
||||||
|
- salinität
|
||||||
|
- salinitaet
|
||||||
|
- salinity
|
||||||
|
- salzgehalt
|
||||||
|
- tds
|
||||||
|
- leitfähigkeit
|
||||||
|
- leitfaehigkeit
|
||||||
|
salinity:
|
||||||
|
- salinität
|
||||||
|
- salinitaet
|
||||||
|
- salinity
|
||||||
|
- salzgehalt
|
||||||
|
- tds
|
||||||
|
- leitfähigkeit
|
||||||
|
- leitfaehigkeit
|
||||||
|
redox:
|
||||||
|
- redox
|
||||||
|
- orp
|
||||||
|
- oxidations-reduktionspotential
|
||||||
|
- oxidations reduktionspotential
|
||||||
|
orp:
|
||||||
|
- redox
|
||||||
|
- orp
|
||||||
|
- oxidations-reduktionspotential
|
||||||
|
- oxidations reduktionspotential
|
||||||
|
ph:
|
||||||
|
- ph
|
||||||
|
- ph-wert
|
||||||
|
- ph wert
|
||||||
|
chlor:
|
||||||
|
- chlor
|
||||||
|
- freies chlor
|
||||||
|
- gesamtchlor
|
||||||
|
- chlorine
|
||||||
|
|
||||||
no_llm_fallback:
|
no_llm_fallback:
|
||||||
max_shop_results: 5
|
max_shop_results: 5
|
||||||
messages:
|
messages:
|
||||||
@@ -32,8 +116,8 @@ parameters:
|
|||||||
no_data: 'Ich finde dazu keine belastbaren Daten in den vorliegenden Quellen. Bitte nenne Produkt, Messparameter, Zubehör oder Anwendungsfall genauer.'
|
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_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.'
|
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. Ohne Shop-Check treffe ich keine Aussage zu aktueller Verfügbarkeit, Preis oder Shop-Portfolio. Vorhandenes RAG-Wissen darf nur als fachlicher Kontext verstanden werden.'
|
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 ich finde kein belastbares RAG-Wissen. Ich kann daraus keine verlässliche Produkt- oder Verfügbarkeitsaussage ableiten.'
|
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_roles:
|
product_roles:
|
||||||
main_device_request_keywords:
|
main_device_request_keywords:
|
||||||
- anlage
|
- anlage
|
||||||
|
|||||||
@@ -93,13 +93,6 @@ parameters:
|
|||||||
- monitor
|
- monitor
|
||||||
- controller
|
- controller
|
||||||
- testomat
|
- testomat
|
||||||
- tester
|
|
||||||
- pocket tester
|
|
||||||
- pockettester
|
|
||||||
- handmessgerät
|
|
||||||
- handmessgeraet
|
|
||||||
- überwachungsgerät
|
|
||||||
- ueberwachungsgeraet
|
|
||||||
- testoamt
|
- testoamt
|
||||||
main_device_product_keywords:
|
main_device_product_keywords:
|
||||||
- messanlage
|
- messanlage
|
||||||
@@ -117,24 +110,6 @@ parameters:
|
|||||||
- system
|
- system
|
||||||
- monitor
|
- monitor
|
||||||
- controller
|
- controller
|
||||||
- tester
|
|
||||||
- pocket tester
|
|
||||||
- pockettester
|
|
||||||
- handmessgerät
|
|
||||||
- handmessgeraet
|
|
||||||
- labor messgerät
|
|
||||||
- labor-messgerät
|
|
||||||
- labor messgeraet
|
|
||||||
- labor-messgeraet
|
|
||||||
- kombimessgerät
|
|
||||||
- kombi-messgerät
|
|
||||||
- kombimessgeraet
|
|
||||||
- kombi-messgeraet
|
|
||||||
- überwachungsgerät
|
|
||||||
- ueberwachungsgeraet
|
|
||||||
- messumformer
|
|
||||||
- transmitter
|
|
||||||
- regler
|
|
||||||
- testomat
|
- testomat
|
||||||
accessory_product_keywords:
|
accessory_product_keywords:
|
||||||
- indikator
|
- indikator
|
||||||
@@ -164,23 +139,6 @@ parameters:
|
|||||||
- service set
|
- service set
|
||||||
- serviceset
|
- serviceset
|
||||||
- service-set
|
- service-set
|
||||||
- elektrode
|
|
||||||
- elektroden
|
|
||||||
- electrode
|
|
||||||
- electrodes
|
|
||||||
- glasschaft-elektrode
|
|
||||||
- kunststoffschaft-elektrode
|
|
||||||
- sensor
|
|
||||||
- sensoren
|
|
||||||
- sensors
|
|
||||||
- sonde
|
|
||||||
- sonden
|
|
||||||
- probe
|
|
||||||
- probes
|
|
||||||
- messsonde
|
|
||||||
- elektrolyt
|
|
||||||
- kabel
|
|
||||||
- adapter
|
|
||||||
- ph-indikator
|
- ph-indikator
|
||||||
- ph indikator
|
- ph indikator
|
||||||
- ph-indikatoren
|
- ph-indikatoren
|
||||||
@@ -354,7 +312,6 @@ parameters:
|
|||||||
- '- Prefer transparent uncertainty over a confident but unsupported answer.'
|
- '- Prefer transparent uncertainty over a confident but unsupported answer.'
|
||||||
- '- Never present missing or weak evidence as proof that a product, value, accessory, or suitability does not exist.'
|
- '- Never present missing or weak evidence as proof that a product, value, accessory, or suitability does not exist.'
|
||||||
- '- A negative answer is allowed only when the provided sources explicitly support that negative finding for the asked scope.'
|
- '- A negative answer is allowed only when the provided sources explicitly support that negative finding for the asked scope.'
|
||||||
- '- If the sources merely do not prove suitability, answer as missing evidence instead of as a definitive exclusion. Avoid words such as "ausschließlich", "keines", or "nicht geeignet" unless directly grounded.'
|
|
||||||
- '- If several products, parameters, or accessories could match, ask one focused clarification question instead of guessing.'
|
- '- If several products, parameters, or accessories could match, ask one focused clarification question instead of guessing.'
|
||||||
- '- For risky or binding product selection, state that sales or support should verify the application before a final selection.'
|
- '- For risky or binding product selection, state that sales or support should verify the application before a final selection.'
|
||||||
without_shop_check_rules:
|
without_shop_check_rules:
|
||||||
@@ -376,8 +333,16 @@ parameters:
|
|||||||
- '- State that no reliable information was found in the provided RAG knowledge, URL content, or shop results.'
|
- '- State that no reliable information was found in the provided RAG knowledge, URL content, or shop results.'
|
||||||
- '- Do not answer with "gibt es nicht". Use narrow wording such as "Ich finde dazu keine belastbaren Daten in den vorliegenden Quellen."'
|
- '- Do not answer with "gibt es nicht". Use narrow wording such as "Ich finde dazu keine belastbaren Daten in den vorliegenden Quellen."'
|
||||||
- '- Ask one focused clarification question if a parameter, product family, accessory type, or application context would make the search answerable.'
|
- '- Ask one focused clarification question if a parameter, product family, accessory type, or application context would make the search answerable.'
|
||||||
|
semantische_rag_treffer_kein_direkter_fachbeleg:
|
||||||
|
- '- Retrieved RAG records are semantic nearest-neighbor hits only; they are not a direct factual match for the essential user term or configured synonym.'
|
||||||
|
- '- Do not present these RAG hits as fachlich belegt. Say narrowly that the RAG knowledge does not contain a direct Fachbeleg for the requested term.'
|
||||||
|
- '- You may mention that semantic neighbor hits were found only if it helps explain uncertainty, but do not infer suitability or product existence from them.'
|
||||||
|
semantische_rag_treffer_kein_direkter_fachbeleg_shopdaten_nicht_verfuegbar:
|
||||||
|
- '- Live shop data could not be loaded and retrieved RAG records are semantic nearest-neighbor hits without a direct Fachbeleg for the essential user term.'
|
||||||
|
- '- State both facts separately: shop unavailable; no direct RAG Fachbeleg. Do not make a portfolio-wide negative claim.'
|
||||||
|
- '- Do not answer with availability, price, shop portfolio, or a technical recommendation.'
|
||||||
shopdaten_nicht_verfuegbar:
|
shopdaten_nicht_verfuegbar:
|
||||||
- '- State that live shop data could not be loaded and answer only from retrieved knowledge or URL content if available.'
|
- '- State that live shop data could not be loaded. If retrieved knowledge or URL content contains a direct Fachbeleg, still answer the factual part from that source and clearly separate it from missing shop data.'
|
||||||
- '- Do not draw negative conclusions about current product availability, price, or shop portfolio while the shop is unavailable.'
|
- '- Do not draw negative conclusions about current product availability, price, or shop portfolio while the shop is unavailable.'
|
||||||
|
|
||||||
response_format:
|
response_format:
|
||||||
@@ -392,9 +357,6 @@ parameters:
|
|||||||
- '- Do not generate external alternative lists, vendor suggestions, or purchase recommendations unless they are explicitly present in the provided sources.'
|
- '- Do not generate external alternative lists, vendor suggestions, or purchase recommendations unless they are explicitly present in the provided sources.'
|
||||||
- '- Do not combine technical identity from one source with commercial fields from a different product.'
|
- '- Do not combine technical identity from one source with commercial fields from a different product.'
|
||||||
- '- Product number, price, availability, and URL must belong to the same explicitly grounded product.'
|
- '- Product number, price, availability, and URL must belong to the same explicitly grounded product.'
|
||||||
- '- Avoid absolute negative wording such as "ausschließlich", "keines", "nicht geeignet", "gibt es nicht", or portfolio-wide negations unless the provided sources explicitly support that exact scope.'
|
|
||||||
- '- Prefer narrow evidence wording, for example "in den vorliegenden Quellen nicht sicher belegt", "in den aktuellen Treffern nicht belegt", or "die gezeigten Treffer belegen keine Eignung".'
|
|
||||||
- '- When mentioning options outside the provided sources, do not recommend specific external products, vendors, or purchases. Phrase only the required category neutrally and state that such evidence is not present in the provided sources.'
|
|
||||||
with_shop_rules:
|
with_shop_rules:
|
||||||
- '- If a product is identified, prefer this structure per product: product name, product number, price, availability, URL, then only the most relevant technical
|
- '- If a product is identified, prefer this structure per product: product name, product number, price, availability, URL, then only the most relevant technical
|
||||||
facts.'
|
facts.'
|
||||||
@@ -416,7 +378,6 @@ parameters:
|
|||||||
- '- For direct follow-up questions about an indicator, value, threshold, or device, answer the resolved mapping first before any table or explanation.'
|
- '- For direct follow-up questions about an indicator, value, threshold, or device, answer the resolved mapping first before any table or explanation.'
|
||||||
- '- If the sources only support a negative finding, output only that negative finding and do not add speculative alternatives.'
|
- '- If the sources only support a negative finding, output only that negative finding and do not add speculative alternatives.'
|
||||||
- '- For product-selection answers, keep the answer minimal: suitable product if explicitly supported, exact evidence, current shop fields if same product identity is clear. Do not add sections for Vorteile, Einsatzbereiche, Messprinzip, or Hinweise unless directly asked and explicitly sourced.'
|
- '- For product-selection answers, keep the answer minimal: suitable product if explicitly supported, exact evidence, current shop fields if same product identity is clear. Do not add sections for Vorteile, Einsatzbereiche, Messprinzip, or Hinweise unless directly asked and explicitly sourced.'
|
||||||
- '- If no suitable product is grounded, do not pivot to alternative product categories as a recommendation. You may only say neutrally what kind of explicitly designated product or accessory would be needed, and that it is not evidenced in the current sources.'
|
|
||||||
accessory_rules:
|
accessory_rules:
|
||||||
- '- If the user asks for a matching accessory, separate the answer into: main device and matching accessory.'
|
- '- If the user asks for a matching accessory, separate the answer into: main device and matching accessory.'
|
||||||
- '- The main device must come first. The accessory must not replace the main device.'
|
- '- The main device must come first. The accessory must not replace the main device.'
|
||||||
@@ -447,8 +408,6 @@ parameters:
|
|||||||
the same fact is present in the current retrieved sources.'
|
the same fact is present in the current retrieved sources.'
|
||||||
- '- Never mention external manufacturers, external brands, or external products unless they are explicitly present in the provided sources.'
|
- '- Never mention external manufacturers, external brands, or external products unless they are explicitly present in the provided sources.'
|
||||||
- '- If the sources do not identify a suitable product, do not invent one.'
|
- '- If the sources do not identify a suitable product, do not invent one.'
|
||||||
- '- Do not turn absence of evidence into a broad portfolio statement. Use scoped wording tied to the provided sources and current search results.'
|
|
||||||
- '- Strong negative terms such as "ausschließlich", "keines", "nicht geeignet", or "gibt es nicht" require explicit source support for the full stated scope.'
|
|
||||||
with_shop_rules:
|
with_shop_rules:
|
||||||
- '- Use shop data as highest priority for current commercial fields: price, availability, URL, current shop-visible naming, and explicitly shop-visible product suitability for product-selection questions.'
|
- '- Use shop data as highest priority for current commercial fields: price, availability, URL, current shop-visible naming, and explicitly shop-visible product suitability for product-selection questions.'
|
||||||
- '- Use retrieved knowledge as highest priority for technical matching, thresholds, measurement principles, and technical explanation when it contains a matching product or fact.'
|
- '- Use retrieved knowledge as highest priority for technical matching, thresholds, measurement principles, and technical explanation when it contains a matching product or fact.'
|
||||||
@@ -470,8 +429,6 @@ parameters:
|
|||||||
- '- Shop product names are authoritative for their own shop URL, product number, price, availability, image, description, and metadata.'
|
- '- Shop product names are authoritative for their own shop URL, product number, price, availability, image, description, and metadata.'
|
||||||
- '- Do not rewrite a shop record heading with a similar device name from retrieved knowledge. If identities differ or are uncertain, separate the RAG device from the shop hit.'
|
- '- Do not rewrite a shop record heading with a similar device name from retrieved knowledge. If identities differ or are uncertain, separate the RAG device from the shop hit.'
|
||||||
- '- If the user asks for a main device, measuring device, analyzer, system, or measuring installation, do not present an accessory, indicator, reagent, kit, set, consumable, or service item as the requested main solution.'
|
- '- If the user asks for a main device, measuring device, analyzer, system, or measuring installation, do not present an accessory, indicator, reagent, kit, set, consumable, or service item as the requested main solution.'
|
||||||
- '- If the user asks for a main device, measuring device, analyzer, system, or measuring installation, prioritize complete measuring devices/testers/analyzers. Do not mix electrodes, probes, sensors, cables, adapters, reagents, indicators, or spare parts into the main product list.'
|
|
||||||
- '- Components such as electrodes, probes, sensors, transmitters, controllers, or measuring converters may be mentioned only in a separate section such as "Komponenten/Zubehör (kein vollständiges Messgerät)" when this distinction is clear from the shop record.'
|
|
||||||
- '- If the user asks for an accessory, indicator, reagent, consumable, kit, or solution with a specific measurement parameter, do not replace the requested parameter with another parameter. A hardness indicator is not a valid answer to a pH-indicator request unless the same source explicitly states pH measurement, pH determination, pH measuring range, or an equivalent parameter-specific purpose.'
|
- '- If the user asks for an accessory, indicator, reagent, consumable, kit, or solution with a specific measurement parameter, do not replace the requested parameter with another parameter. A hardness indicator is not a valid answer to a pH-indicator request unless the same source explicitly states pH measurement, pH determination, pH measuring range, or an equivalent parameter-specific purpose.'
|
||||||
- '- Mentions of operating conditions, allowed sample pH, reagent-solution pH, storage pH, or pH values at a temperature are not measurement-parameter evidence by themselves.'
|
- '- Mentions of operating conditions, allowed sample pH, reagent-solution pH, storage pH, or pH values at a temperature are not measurement-parameter evidence by themselves.'
|
||||||
- '- If the only shop hit is role-incompatible with the requested product role, state that no matching main-device shop hit is available in the provided shop data; mention the incompatible hit only as a separate accessory/consumable hit if useful.'
|
- '- If the only shop hit is role-incompatible with the requested product role, state that no matching main-device shop hit is available in the provided shop data; mention the incompatible hit only as a separate accessory/consumable hit if useful.'
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ final readonly class AgentRunner
|
|||||||
$shopResults = [];
|
$shopResults = [];
|
||||||
$primaryShopResults = [];
|
$primaryShopResults = [];
|
||||||
$knowledgeChunks = [];
|
$knowledgeChunks = [];
|
||||||
|
$knowledgeEvidenceState = 'none';
|
||||||
$sources = [];
|
$sources = [];
|
||||||
$optimizedShopQuery = '';
|
$optimizedShopQuery = '';
|
||||||
$shopSearchQuery = '';
|
$shopSearchQuery = '';
|
||||||
@@ -105,6 +106,7 @@ final readonly class AgentRunner
|
|||||||
$usedFollowUpRetrievalContext = $knowledgeRetrievalPrompt !== $prompt;
|
$usedFollowUpRetrievalContext = $knowledgeRetrievalPrompt !== $prompt;
|
||||||
|
|
||||||
$knowledgeChunks = $this->retriever->retrieve($knowledgeRetrievalPrompt);
|
$knowledgeChunks = $this->retriever->retrieve($knowledgeRetrievalPrompt);
|
||||||
|
$knowledgeEvidenceState = $this->resolveKnowledgeEvidenceState($prompt, $knowledgeChunks, $urlContent);
|
||||||
if ($knowledgeChunks !== []) {
|
if ($knowledgeChunks !== []) {
|
||||||
$this->addSource($sources, $this->agentRunnerConfig->getRagKnowledgeSourceLabel());
|
$this->addSource($sources, $this->agentRunnerConfig->getRagKnowledgeSourceLabel());
|
||||||
}
|
}
|
||||||
@@ -116,7 +118,7 @@ final readonly class AgentRunner
|
|||||||
shopCount: null,
|
shopCount: null,
|
||||||
shopCountMode: 'not_requested',
|
shopCountMode: 'not_requested',
|
||||||
sourceLabels: $sources,
|
sourceLabels: $sources,
|
||||||
confidenceLabel: $knowledgeChunks !== [] ? 'fachlich belegt' : 'noch keine belastbaren Treffer'
|
confidenceLabel: $this->resolveRagEvidenceConfidenceLabel($knowledgeEvidenceState)
|
||||||
),
|
),
|
||||||
'meta'
|
'meta'
|
||||||
);
|
);
|
||||||
@@ -138,7 +140,7 @@ final readonly class AgentRunner
|
|||||||
shopCount: null,
|
shopCount: null,
|
||||||
shopCountMode: 'loading',
|
shopCountMode: 'loading',
|
||||||
sourceLabels: $sources,
|
sourceLabels: $sources,
|
||||||
confidenceLabel: $knowledgeChunks !== [] ? 'fachlich belegt; Shopdaten werden geprüft' : 'Shopdaten werden geprüft'
|
confidenceLabel: $this->resolveRagEvidenceShopCheckConfidenceLabel($knowledgeEvidenceState)
|
||||||
),
|
),
|
||||||
'meta'
|
'meta'
|
||||||
);
|
);
|
||||||
@@ -241,7 +243,7 @@ final readonly class AgentRunner
|
|||||||
shopCount: null,
|
shopCount: null,
|
||||||
shopCountMode: 'loading',
|
shopCountMode: 'loading',
|
||||||
sourceLabels: $sources,
|
sourceLabels: $sources,
|
||||||
confidenceLabel: $knowledgeChunks !== [] ? 'fachlich belegt; Shopdaten werden geprüft' : 'Shopdaten werden geprüft'
|
confidenceLabel: $this->resolveRagEvidenceShopCheckConfidenceLabel($knowledgeEvidenceState)
|
||||||
),
|
),
|
||||||
'meta'
|
'meta'
|
||||||
);
|
);
|
||||||
@@ -274,6 +276,17 @@ final readonly class AgentRunner
|
|||||||
$shopUnavailableMessage,
|
$shopUnavailableMessage,
|
||||||
'err'
|
'err'
|
||||||
);
|
);
|
||||||
|
yield $this->systemMsg(
|
||||||
|
$this->buildShopSearchMetaMessage(
|
||||||
|
query: $shopSearchDisplayQuery !== '' ? $shopSearchDisplayQuery : $shopSearchQuery,
|
||||||
|
commerceIntent: $commerceIntent,
|
||||||
|
usedOptimizedQuery: $shopSearchUsedOptimizedQuery,
|
||||||
|
originalQuery: $shopSearchQuery,
|
||||||
|
completed: true,
|
||||||
|
unavailable: true
|
||||||
|
),
|
||||||
|
'meta'
|
||||||
|
);
|
||||||
$historyNotices[] = $this->buildHistoryNotice(
|
$historyNotices[] = $this->buildHistoryNotice(
|
||||||
'Shopdaten konnten nicht geladen werden',
|
'Shopdaten konnten nicht geladen werden',
|
||||||
$primaryShopSearchFailureReason
|
$primaryShopSearchFailureReason
|
||||||
@@ -337,7 +350,7 @@ final readonly class AgentRunner
|
|||||||
shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : 'count',
|
shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : 'count',
|
||||||
sourceLabels: $sources,
|
sourceLabels: $sources,
|
||||||
confidenceLabel: $this->resolveProductionUiConfidenceLabel(
|
confidenceLabel: $this->resolveProductionUiConfidenceLabel(
|
||||||
hasKnowledge: $knowledgeChunks !== [] || trim($urlContent) !== '',
|
hasKnowledge: $this->isDirectKnowledgeEvidence($knowledgeEvidenceState),
|
||||||
isCommerceIntent: true,
|
isCommerceIntent: true,
|
||||||
shopSearchAttempted: $shopSearchAttempted,
|
shopSearchAttempted: $shopSearchAttempted,
|
||||||
hasShopResults: $shopResults !== [],
|
hasShopResults: $shopResults !== [],
|
||||||
@@ -363,7 +376,8 @@ final readonly class AgentRunner
|
|||||||
fullContext: $forceFullContext,
|
fullContext: $forceFullContext,
|
||||||
swagFullOutPut: $optimizedShopQuery,
|
swagFullOutPut: $optimizedShopQuery,
|
||||||
commerceSearchAttempted: $shopSearchAttempted,
|
commerceSearchAttempted: $shopSearchAttempted,
|
||||||
shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure
|
shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure,
|
||||||
|
knowledgeEvidenceState: $knowledgeEvidenceState
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($this->debug && $this->logPrompt) {
|
if ($this->debug && $this->logPrompt) {
|
||||||
@@ -401,7 +415,7 @@ final readonly class AgentRunner
|
|||||||
shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : ($shopSearchAttempted ? 'count' : 'not_requested'),
|
shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : ($shopSearchAttempted ? 'count' : 'not_requested'),
|
||||||
sourceLabels: $sources,
|
sourceLabels: $sources,
|
||||||
confidenceLabel: $this->resolveProductionUiConfidenceLabel(
|
confidenceLabel: $this->resolveProductionUiConfidenceLabel(
|
||||||
hasKnowledge: $knowledgeChunks !== [] || trim($urlContent) !== '',
|
hasKnowledge: $this->isDirectKnowledgeEvidence($knowledgeEvidenceState),
|
||||||
isCommerceIntent: $this->isCommerceIntent($commerceIntent),
|
isCommerceIntent: $this->isCommerceIntent($commerceIntent),
|
||||||
shopSearchAttempted: $shopSearchAttempted,
|
shopSearchAttempted: $shopSearchAttempted,
|
||||||
hasShopResults: $shopResults !== [],
|
hasShopResults: $shopResults !== [],
|
||||||
@@ -426,7 +440,8 @@ final readonly class AgentRunner
|
|||||||
commerceIntent: $commerceIntent,
|
commerceIntent: $commerceIntent,
|
||||||
shopSearchAttempted: $shopSearchAttempted,
|
shopSearchAttempted: $shopSearchAttempted,
|
||||||
shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure,
|
shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure,
|
||||||
shopSearchFailureReason: $primaryShopSearchFailureReason ?? null
|
shopSearchFailureReason: $primaryShopSearchFailureReason ?? null,
|
||||||
|
knowledgeEvidenceState: $knowledgeEvidenceState
|
||||||
);
|
);
|
||||||
|
|
||||||
$fullOutput = yield from $this->streamFinalAnswer($finalPrompt, $noLlmFallbackAnswer);
|
$fullOutput = yield from $this->streamFinalAnswer($finalPrompt, $noLlmFallbackAnswer);
|
||||||
@@ -439,7 +454,7 @@ final readonly class AgentRunner
|
|||||||
shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : ($shopSearchAttempted ? 'count' : 'not_requested'),
|
shopCountMode: $primaryShopSearchHadSystemFailure ? 'unavailable' : ($shopSearchAttempted ? 'count' : 'not_requested'),
|
||||||
sourceLabels: $sources,
|
sourceLabels: $sources,
|
||||||
confidenceLabel: $this->resolveProductionUiConfidenceLabel(
|
confidenceLabel: $this->resolveProductionUiConfidenceLabel(
|
||||||
hasKnowledge: $knowledgeChunks !== [] || trim($urlContent) !== '',
|
hasKnowledge: $this->isDirectKnowledgeEvidence($knowledgeEvidenceState),
|
||||||
isCommerceIntent: $this->isCommerceIntent($commerceIntent),
|
isCommerceIntent: $this->isCommerceIntent($commerceIntent),
|
||||||
shopSearchAttempted: $shopSearchAttempted,
|
shopSearchAttempted: $shopSearchAttempted,
|
||||||
hasShopResults: $shopResults !== [],
|
hasShopResults: $shopResults !== [],
|
||||||
@@ -1413,9 +1428,10 @@ final readonly class AgentRunner
|
|||||||
string $commerceIntent,
|
string $commerceIntent,
|
||||||
bool $shopSearchAttempted,
|
bool $shopSearchAttempted,
|
||||||
bool $shopSearchHadSystemFailure,
|
bool $shopSearchHadSystemFailure,
|
||||||
?string $shopSearchFailureReason
|
?string $shopSearchFailureReason,
|
||||||
|
string $knowledgeEvidenceState = 'unknown'
|
||||||
): string {
|
): string {
|
||||||
$hasKnowledge = $knowledgeChunks !== [] || trim($urlContent) !== '';
|
$hasKnowledge = $this->isDirectKnowledgeEvidence($knowledgeEvidenceState) || ($knowledgeEvidenceState === 'unknown' && ($knowledgeChunks !== [] || trim($urlContent) !== ''));
|
||||||
$hasShopResults = $shopResults !== [];
|
$hasShopResults = $shopResults !== [];
|
||||||
$isCommerceIntent = $this->isCommerceIntent($commerceIntent);
|
$isCommerceIntent = $this->isCommerceIntent($commerceIntent);
|
||||||
|
|
||||||
@@ -1636,6 +1652,172 @@ final readonly class AgentRunner
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distinguish semantic nearest-neighbor retrieval hits from direct factual evidence.
|
||||||
|
*
|
||||||
|
* Vector retrieval can return useful context even when the essential user term is not
|
||||||
|
* present. Those hits should stay visible as RAG hits, but they must not be counted as
|
||||||
|
* "fachlich belegt" unless at least one salient request term or configured synonym
|
||||||
|
* appears in the retrieved knowledge or user-provided URL content.
|
||||||
|
*
|
||||||
|
* @param string[] $knowledgeChunks
|
||||||
|
*/
|
||||||
|
private function resolveKnowledgeEvidenceState(string $prompt, array $knowledgeChunks, string $urlContent): string
|
||||||
|
{
|
||||||
|
if ($knowledgeChunks === [] && trim($urlContent) === '') {
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trim($urlContent) !== '') {
|
||||||
|
return 'direct';
|
||||||
|
}
|
||||||
|
|
||||||
|
$needles = $this->buildRagEvidenceNeedles($prompt);
|
||||||
|
|
||||||
|
if ($needles === []) {
|
||||||
|
// No meaningful term could be extracted. Preserve the previous behavior for
|
||||||
|
// very short follow-ups instead of hiding potentially valid context.
|
||||||
|
return 'direct';
|
||||||
|
}
|
||||||
|
|
||||||
|
$haystack = $this->normalizeRagEvidenceText(implode("\n\n", array_map('strval', $knowledgeChunks)));
|
||||||
|
|
||||||
|
foreach ($needles as $needleGroup) {
|
||||||
|
foreach ($needleGroup as $needle) {
|
||||||
|
if ($this->containsRagEvidenceTerm($haystack, $needle)) {
|
||||||
|
return 'direct';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'weak';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isDirectKnowledgeEvidence(string $knowledgeEvidenceState): bool
|
||||||
|
{
|
||||||
|
return $knowledgeEvidenceState === 'direct';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveRagEvidenceConfidenceLabel(string $knowledgeEvidenceState): string
|
||||||
|
{
|
||||||
|
return match ($knowledgeEvidenceState) {
|
||||||
|
'direct' => 'fachlich belegt',
|
||||||
|
'weak' => 'RAG-Näherungstreffer, kein direkter Fachbeleg',
|
||||||
|
default => 'noch keine belastbaren Treffer',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveRagEvidenceShopCheckConfidenceLabel(string $knowledgeEvidenceState): string
|
||||||
|
{
|
||||||
|
return match ($knowledgeEvidenceState) {
|
||||||
|
'direct' => 'fachlich belegt; Shopdaten werden geprüft',
|
||||||
|
'weak' => 'RAG-Näherungstreffer; Shopdaten werden geprüft',
|
||||||
|
default => 'Shopdaten werden geprüft',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string[]>
|
||||||
|
*/
|
||||||
|
private function buildRagEvidenceNeedles(string $prompt): array
|
||||||
|
{
|
||||||
|
$normalizedPrompt = $this->normalizeRagEvidenceText($prompt);
|
||||||
|
$stopTerms = [];
|
||||||
|
|
||||||
|
foreach ($this->agentRunnerConfig->getRagEvidenceStopTerms() as $term) {
|
||||||
|
$term = $this->normalizeRagEvidenceText($term);
|
||||||
|
if ($term !== '') {
|
||||||
|
$stopTerms[$term] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match_all('/[\p{L}\p{N}][\p{L}\p{N}\-]{1,}/u', $normalizedPrompt, $matches);
|
||||||
|
$tokens = $matches[0] ?? [];
|
||||||
|
$groups = [];
|
||||||
|
$synonyms = $this->normalizedRagEvidenceSynonyms();
|
||||||
|
|
||||||
|
foreach ($tokens as $token) {
|
||||||
|
$token = trim((string) $token);
|
||||||
|
|
||||||
|
if ($token === '' || isset($stopTerms[$token]) || mb_strlen($token, 'UTF-8') < 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$group = $synonyms[$token] ?? [$token];
|
||||||
|
$group = array_values(array_unique(array_filter(array_map(
|
||||||
|
fn (string $item): string => $this->normalizeRagEvidenceText($item),
|
||||||
|
$group
|
||||||
|
))));
|
||||||
|
|
||||||
|
if ($group !== []) {
|
||||||
|
$groups[] = $group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string[]>
|
||||||
|
*/
|
||||||
|
private function normalizedRagEvidenceSynonyms(): array
|
||||||
|
{
|
||||||
|
$out = [];
|
||||||
|
|
||||||
|
foreach ($this->agentRunnerConfig->getRagEvidenceSynonyms() as $key => $items) {
|
||||||
|
$key = $this->normalizeRagEvidenceText((string) $key);
|
||||||
|
if ($key === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$terms = [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$item = $this->normalizeRagEvidenceText((string) $item);
|
||||||
|
if ($item !== '' && !in_array($item, $terms, true)) {
|
||||||
|
$terms[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($key, $terms, true)) {
|
||||||
|
$terms[] = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
$out[$key] = $terms;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function containsRagEvidenceTerm(string $haystack, string $needle): bool
|
||||||
|
{
|
||||||
|
$needle = $this->normalizeRagEvidenceText($needle);
|
||||||
|
|
||||||
|
if ($haystack === '' || $needle === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/(?<![\p{L}\p{N}])' . preg_quote($needle, '/') . '(?![\p{L}\p{N}])/u';
|
||||||
|
|
||||||
|
return preg_match($pattern, $haystack) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeRagEvidenceText(string $value): string
|
||||||
|
{
|
||||||
|
$value = html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||||
|
$value = mb_strtolower($value, 'UTF-8');
|
||||||
|
$value = str_replace(['‐', '‑', '‒', '–', '—'], '-', $value);
|
||||||
|
$value = strtr($value, [
|
||||||
|
'ä' => 'ae',
|
||||||
|
'ö' => 'oe',
|
||||||
|
'ü' => 'ue',
|
||||||
|
'ß' => 'ss',
|
||||||
|
]);
|
||||||
|
$value = preg_replace('/\s+/u', ' ', $value) ?? $value;
|
||||||
|
|
||||||
|
return trim($value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $sources
|
* @param string[] $sources
|
||||||
*/
|
*/
|
||||||
@@ -1814,10 +1996,6 @@ final readonly class AgentRunner
|
|||||||
private function formatProductionUiSourceLabels(array $sourceLabels): array
|
private function formatProductionUiSourceLabels(array $sourceLabels): array
|
||||||
{
|
{
|
||||||
$labels = [];
|
$labels = [];
|
||||||
$seen = [];
|
|
||||||
$shopSystemKey = $this->canonicalProductionUiSourceLabelKey(
|
|
||||||
$this->plainTextFromHtml($this->agentRunnerConfig->getShopSystemSourceLabel())
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($sourceLabels as $label) {
|
foreach ($sourceLabels as $label) {
|
||||||
// Source labels are stored as badge HTML for the legacy "Genutzte Quellen" line.
|
// Source labels are stored as badge HTML for the legacy "Genutzte Quellen" line.
|
||||||
@@ -1829,35 +2007,18 @@ final readonly class AgentRunner
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$key = $this->canonicalProductionUiSourceLabelKey($label);
|
if ($label === $this->plainTextFromHtml($this->agentRunnerConfig->getShopSystemSourceLabel())) {
|
||||||
|
|
||||||
if ($key === $shopSystemKey || $key === 'liveshopdaten') {
|
|
||||||
$label = 'Live-Shopdaten';
|
$label = 'Live-Shopdaten';
|
||||||
$key = 'liveshopdaten';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($seen[$key])) {
|
if (!in_array($label, $labels, true)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$seen[$key] = true;
|
|
||||||
$labels[] = $label;
|
$labels[] = $label;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $labels;
|
return $labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function canonicalProductionUiSourceLabelKey(string $label): string
|
|
||||||
{
|
|
||||||
$label = html_entity_decode(strip_tags($label), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
||||||
$label = str_replace(["\xc2\xa0", '‐', '‑', '‒', '–', '—'], [' ', '-', '-', '-', '-', '-'], $label);
|
|
||||||
$label = preg_replace('/\s+/u', ' ', $label) ?? $label;
|
|
||||||
$label = mb_strtolower(trim($label), 'UTF-8');
|
|
||||||
$label = preg_replace('/[^\p{L}\p{N}]+/u', '', $label) ?? $label;
|
|
||||||
|
|
||||||
return $label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ShopProductResult[] $shopResults
|
* @param ShopProductResult[] $shopResults
|
||||||
*/
|
*/
|
||||||
@@ -2037,7 +2198,8 @@ final readonly class AgentRunner
|
|||||||
?int $resultCount = null,
|
?int $resultCount = null,
|
||||||
bool $completed = false,
|
bool $completed = false,
|
||||||
bool $attemptedRepair = false,
|
bool $attemptedRepair = false,
|
||||||
bool $usedRepair = false
|
bool $usedRepair = false,
|
||||||
|
bool $unavailable = false
|
||||||
): string {
|
): string {
|
||||||
$query = $this->normalizeOneLine($query);
|
$query = $this->normalizeOneLine($query);
|
||||||
$originalQuery = $this->normalizeOneLine($originalQuery);
|
$originalQuery = $this->normalizeOneLine($originalQuery);
|
||||||
@@ -2048,11 +2210,13 @@ final readonly class AgentRunner
|
|||||||
|
|
||||||
$queryModeLabel = $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';
|
$title = $unavailable ? 'Shopdaten nicht verfügbar' : ($completed ? 'Shop-Suche abgeschlossen' : 'Shop-Suche wird ausgeführt');
|
||||||
$statusLabel = $completed ? 'Status: abgeschlossen' : 'Status: läuft';
|
$statusLabel = $completed ? 'Status: abgeschlossen' : 'Status: läuft';
|
||||||
$resultLabel = $resultCount === null
|
$resultLabel = $unavailable
|
||||||
|
? 'Shoptreffer: nicht verfügbar'
|
||||||
|
: ($resultCount === null
|
||||||
? 'Shoptreffer: wird geladen'
|
? 'Shoptreffer: wird geladen'
|
||||||
: 'Shoptreffer: ' . max(0, $resultCount);
|
: 'Shoptreffer: ' . max(0, $resultCount));
|
||||||
$state = $completed ? 'completed' : 'running';
|
$state = $completed ? 'completed' : 'running';
|
||||||
$resultCountAttribute = $resultCount === null ? '' : (string) max(0, $resultCount);
|
$resultCountAttribute = $resultCount === null ? '' : (string) max(0, $resultCount);
|
||||||
$repairLabel = '';
|
$repairLabel = '';
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ final readonly class PromptBuilder
|
|||||||
?bool $fullContext = false,
|
?bool $fullContext = false,
|
||||||
?string $swagFullOutPut = '',
|
?string $swagFullOutPut = '',
|
||||||
bool $commerceSearchAttempted = false,
|
bool $commerceSearchAttempted = false,
|
||||||
bool $shopSearchHadSystemFailure = false
|
bool $shopSearchHadSystemFailure = false,
|
||||||
|
string $knowledgeEvidenceState = 'unknown'
|
||||||
): string {
|
): string {
|
||||||
$prompt = $this->normalizeBlockText($prompt);
|
$prompt = $this->normalizeBlockText($prompt);
|
||||||
$urlContent = $this->normalizeBlockText($urlContent);
|
$urlContent = $this->normalizeBlockText($urlContent);
|
||||||
@@ -57,7 +58,8 @@ final readonly class PromptBuilder
|
|||||||
hasKnowledge: $hasKnowledge,
|
hasKnowledge: $hasKnowledge,
|
||||||
hasShopResults: $hasShopResults,
|
hasShopResults: $hasShopResults,
|
||||||
commerceSearchAttempted: $commerceSearchAttempted,
|
commerceSearchAttempted: $commerceSearchAttempted,
|
||||||
shopSearchHadSystemFailure: $shopSearchHadSystemFailure
|
shopSearchHadSystemFailure: $shopSearchHadSystemFailure,
|
||||||
|
knowledgeEvidenceState: $knowledgeEvidenceState
|
||||||
);
|
);
|
||||||
|
|
||||||
$systemBlock = $this->buildSystemBlock();
|
$systemBlock = $this->buildSystemBlock();
|
||||||
@@ -275,21 +277,31 @@ final readonly class PromptBuilder
|
|||||||
bool $hasKnowledge,
|
bool $hasKnowledge,
|
||||||
bool $hasShopResults,
|
bool $hasShopResults,
|
||||||
bool $commerceSearchAttempted,
|
bool $commerceSearchAttempted,
|
||||||
bool $shopSearchHadSystemFailure
|
bool $shopSearchHadSystemFailure,
|
||||||
|
string $knowledgeEvidenceState = 'unknown'
|
||||||
): string {
|
): string {
|
||||||
if ($shopSearchHadSystemFailure && !$hasKnowledge) {
|
$hasDirectKnowledgeEvidence = $knowledgeEvidenceState === 'direct' || $knowledgeEvidenceState === 'unknown' && $hasKnowledge;
|
||||||
return 'shopdaten_nicht_verfuegbar';
|
$hasWeakKnowledgeEvidence = $knowledgeEvidenceState === 'weak';
|
||||||
|
|
||||||
|
if ($shopSearchHadSystemFailure && !$hasDirectKnowledgeEvidence) {
|
||||||
|
return $hasWeakKnowledgeEvidence
|
||||||
|
? 'semantische_rag_treffer_kein_direkter_fachbeleg_shopdaten_nicht_verfuegbar'
|
||||||
|
: 'shopdaten_nicht_verfuegbar';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hasKnowledge && !$hasShopResults) {
|
if ($hasWeakKnowledgeEvidence && !$hasShopResults) {
|
||||||
|
return 'semantische_rag_treffer_kein_direkter_fachbeleg';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hasDirectKnowledgeEvidence && !$hasShopResults) {
|
||||||
return 'sicher_beantwortbar';
|
return 'sicher_beantwortbar';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hasKnowledge && $hasShopResults) {
|
if ($hasDirectKnowledgeEvidence && $hasShopResults) {
|
||||||
return 'wahrscheinlich_beantwortbar';
|
return 'wahrscheinlich_beantwortbar';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$hasKnowledge && $hasShopResults) {
|
if (!$hasDirectKnowledgeEvidence && $hasShopResults) {
|
||||||
return 'nur_shop_treffer_kein_belastbares_fachwissen';
|
return 'nur_shop_treffer_kein_belastbares_fachwissen';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,7 +552,6 @@ final readonly class PromptBuilder
|
|||||||
: $this->config->getShopAvailabilityNoLabel());
|
: $this->config->getShopAvailabilityNoLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$suppressCommercialFields) {
|
|
||||||
foreach ($product->highlights as $highlight) {
|
foreach ($product->highlights as $highlight) {
|
||||||
$highlight = $this->normalizeBlockText((string) $highlight);
|
$highlight = $this->normalizeBlockText((string) $highlight);
|
||||||
|
|
||||||
@@ -548,7 +559,6 @@ final readonly class PromptBuilder
|
|||||||
$entryParts[] = $this->config->getShopHighlightPrefix() . $highlight;
|
$entryParts[] = $this->config->getShopHighlightPrefix() . $highlight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!$suppressCommercialFields && $product->url) {
|
if (!$suppressCommercialFields && $product->url) {
|
||||||
$entryParts[] = $this->config->getShopUrlLabel() . ': '
|
$entryParts[] = $this->config->getShopUrlLabel() . ': '
|
||||||
|
|||||||
@@ -25,6 +25,56 @@ final class AgentRunnerConfig
|
|||||||
'pockettester',
|
'pockettester',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private const RAG_EVIDENCE_STOP_TERMS = [
|
||||||
|
'suche',
|
||||||
|
'suchen',
|
||||||
|
'finde',
|
||||||
|
'finden',
|
||||||
|
'zeige',
|
||||||
|
'einen',
|
||||||
|
'eine',
|
||||||
|
'einem',
|
||||||
|
'einer',
|
||||||
|
'der',
|
||||||
|
'die',
|
||||||
|
'das',
|
||||||
|
'den',
|
||||||
|
'dem',
|
||||||
|
'des',
|
||||||
|
'für',
|
||||||
|
'fuer',
|
||||||
|
'mit',
|
||||||
|
'ohne',
|
||||||
|
'und',
|
||||||
|
'oder',
|
||||||
|
'kann',
|
||||||
|
'können',
|
||||||
|
'koennen',
|
||||||
|
'messen',
|
||||||
|
'messung',
|
||||||
|
'tester',
|
||||||
|
'testgerät',
|
||||||
|
'testgeraet',
|
||||||
|
'gerät',
|
||||||
|
'geraet',
|
||||||
|
'messgerät',
|
||||||
|
'messgeraet',
|
||||||
|
'produkt',
|
||||||
|
'produkte',
|
||||||
|
'artikel',
|
||||||
|
'shop',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const RAG_EVIDENCE_SYNONYMS = [
|
||||||
|
'salinität' => ['salinität', 'salinitaet', 'salinity', 'salzgehalt', 'tds', 'leitfähigkeit', 'leitfaehigkeit'],
|
||||||
|
'salinitaet' => ['salinität', 'salinitaet', 'salinity', 'salzgehalt', 'tds', 'leitfähigkeit', 'leitfaehigkeit'],
|
||||||
|
'salinity' => ['salinität', 'salinitaet', 'salinity', 'salzgehalt', 'tds', 'leitfähigkeit', 'leitfaehigkeit'],
|
||||||
|
'redox' => ['redox', 'orp', 'oxidations-reduktionspotential', 'oxidations reduktionspotential'],
|
||||||
|
'orp' => ['redox', 'orp', 'oxidations-reduktionspotential', 'oxidations reduktionspotential'],
|
||||||
|
'ph' => ['ph', 'ph-wert', 'ph wert'],
|
||||||
|
'chlor' => ['chlor', 'freies chlor', 'gesamtchlor', 'chlorine'],
|
||||||
|
];
|
||||||
|
|
||||||
private const NO_LLM_ACCESSORY_PRODUCT_ROLE_KEYWORDS = [
|
private const NO_LLM_ACCESSORY_PRODUCT_ROLE_KEYWORDS = [
|
||||||
'indikator',
|
'indikator',
|
||||||
'indicator',
|
'indicator',
|
||||||
@@ -228,6 +278,57 @@ final class AgentRunnerConfig
|
|||||||
return $this->getInt('no_llm_fallback.max_shop_results', 5);
|
return $this->getInt('no_llm_fallback.max_shop_results', 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getRagEvidenceStopTerms(): array
|
||||||
|
{
|
||||||
|
return $this->getStringList('rag_evidence_guard.stop_terms', self::RAG_EVIDENCE_STOP_TERMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string[]>
|
||||||
|
*/
|
||||||
|
public function getRagEvidenceSynonyms(): array
|
||||||
|
{
|
||||||
|
$value = $this->value('rag_evidence_guard.synonyms', self::RAG_EVIDENCE_SYNONYMS);
|
||||||
|
|
||||||
|
if (!is_array($value)) {
|
||||||
|
return self::RAG_EVIDENCE_SYNONYMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
|
||||||
|
foreach ($value as $key => $items) {
|
||||||
|
if (!is_scalar($key) || !is_array($items)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = trim((string) $key);
|
||||||
|
if ($key === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$terms = [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (!is_scalar($item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = trim((string) $item);
|
||||||
|
if ($item !== '' && !in_array($item, $terms, true)) {
|
||||||
|
$terms[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($terms !== []) {
|
||||||
|
$out[$key] = $terms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out !== [] ? $out : self::RAG_EVIDENCE_SYNONYMS;
|
||||||
|
}
|
||||||
|
|
||||||
public function getNoLlmFallbackShopOnlyMessage(): string
|
public function getNoLlmFallbackShopOnlyMessage(): string
|
||||||
{
|
{
|
||||||
return $this->getString(
|
return $this->getString(
|
||||||
|
|||||||
Reference in New Issue
Block a user