patch 9.5

This commit is contained in:
team 1
2026-04-30 19:47:58 +02:00
parent 1788c70883
commit aa4aa009e4
2 changed files with 60 additions and 233 deletions

View File

@@ -0,0 +1,45 @@
# RetrieX Patch 9.0f - PromptBuilder Role Guard & Measurement Evidence YAML-only
## Scope
This patch continues the PromptBuilder YAML-only migration after Patch 9.0e.
It converts these remaining PromptBuilder truth/product-role related areas to required YAML configuration:
- `role_guard.main_device_request_keywords`
- `role_guard.main_device_product_keywords`
- `role_guard.accessory_product_keywords`
- `technical_product_keywords`
- `accessory_request_keywords`
- `sections.measurement_evidence_label`
- `measurement_evidence_guard.intro_rules`
- `measurement_evidence_guard.parameters`
- `technical_product_model_pattern`
## Intent
PHP no longer provides fallback truth for these PromptBuilder areas. If a required YAML value is missing or invalid, `PromptBuilderConfig` now throws a clear `InvalidArgumentException`.
## Not changed
This patch does not change:
- Retrieval logic
- Commerce logic
- Shop matching
- AgentRunner
- SSE / frontend
- Prompt wording content in YAML
## Notes
`measurement_evidence_guard.parameters` is now required to contain at least one valid parameter definition. Individual list fields inside a parameter are still normalized as before to preserve behavior.
## Recommended checks
```bash
php bin/console cache:clear
php bin/console mto:agent:config:validate
php bin/console mto:agent:config:audit-source --details
php bin/console mto:agent:regression:test
```

View File

@@ -6,151 +6,11 @@ namespace App\Config;
final class PromptBuilderConfig
{
private const MAIN_DEVICE_REQUEST_ROLE_KEYWORDS = [
'messanlage', 'messanlagen', 'anlage', 'anlagen', 'messgerät', 'messgeraet',
'messgeräte', 'messgeraete', 'analysegerät', 'analysegeraet', 'analysegeräte',
'analysegeraete', 'analysator', 'analysatoren', 'analyzer', 'gerät', 'geraet',
'geräte', 'geraete', 'system', 'systeme', 'monitor', 'monitore', 'controller',
'tester', 'pocket tester', 'pockettester', 'handmessgerät', 'handmessgeraet',
'überwachungsgerät', 'ueberwachungsgeraet', 'testomat', 'testoamt',
];
private const MAIN_DEVICE_PRODUCT_ROLE_KEYWORDS = [
'messanlage', 'messanlagen', 'messgerät', 'messgeraet', 'messgeräte', 'messgeraete',
'analysegerät', 'analysegeraet', 'analysegeräte', 'analysegeraete', 'analysator',
'analysatoren', 'analyzer', 'online-analysator', 'online analysator',
'online-analysegerät', 'online analysegeraet', 'gerät', 'geraet', 'geräte',
'geraete', 'system', 'systeme', 'monitor', 'monitore', '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',
];
private const ACCESSORY_PRODUCT_ROLE_KEYWORDS = [
'indikator', 'indikatoren', 'indicator', 'reagenz', 'reagenzien', 'reagent',
'zubehör', 'zubehor', 'ersatzteil', 'ersatzteile', 'kit', 'set',
'verbrauchsmaterial', 'consumable', 'nachfüll', 'nachfuell', 'refill',
'lösung', 'loesung', 'solution', 'teststreifen', 'test strip', 'filter',
'pumpenkopf', 'motorblock', 'service set', 'serviceset', '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-indikatoren', 'ph indikatoren',
];
private const TECHNICAL_PRODUCT_KEYWORDS = [
'technisch',
'technical',
'produkt',
'product',
'gerät',
'device',
'modell',
'model',
'messprinzip',
'measurement principle',
'schnittstelle',
'interface',
'relais',
'relay',
'indikator',
'indicator',
'grenzwert',
'threshold',
'messbereich',
'measurement range',
'gemessen',
'measured',
'minimaler',
'minimum',
'resthärte',
'resthaerte',
'°dh',
'dh',
'spannung',
'voltage',
'strom',
'current',
'druck',
'pressure',
'temperatur',
'temperature',
'schutzart',
'ip',
'fehlercode',
'error code',
'wasserhärte',
'hardness',
'testomat',
'chlor',
'chlormessung',
];
private const MEASUREMENT_EVIDENCE_PARAMETERS = [
[
'id' => 'ph',
'label' => 'pH / pH-Wert',
'request_terms' => ['ph', 'pH', 'pH-Wert', 'ph-wert', 'ph wert'],
'positive_terms' => ['pH', 'pH-Wert', 'ph-wert', 'ph wert'],
'positive_context_terms' => ['Messung', 'messen', 'misst', 'Messbereich', 'Messparameter', 'Messgröße', 'Messgroesse', 'Bestimmung', 'bestimmen', 'Analyse', 'analysiert', 'überwachen', 'ueberwachen', 'Indikator für', 'Indikator fuer', 'Reagenz für', 'Reagenz fuer', 'Sensor', 'Elektrode'],
'negative_context_terms' => ['Betriebsbereich', 'Betriebsumgebung', 'Einsatzbedingungen', 'störungsfrei', 'stoerungsfrei', 'pH-Wert bei', 'ph wert bei', 'ph-wert bei', 'bei 20 °C', 'bei 20 °c', 'bei 20°C', 'bei 20°c', 'Reagenzlösung hat', 'Loesung hat', 'Lösung hat'],
'non_equivalent_terms' => ['p-Wert', 'p Wert', 'm-Wert', 'minus m-Wert', 'Alkalität', 'Säurekapazität', 'mmol/l'],
'safe_no_evidence_answer_de' => 'Ich finde in den bereitgestellten Quellen keinen sicher belegten Testomat für pH-Messung.',
'safe_no_accessory_evidence_answer_de' => 'Ich finde in den bereitgestellten Quellen keinen sicher belegten pH-Indikator oder ein pH-Reagenz für Messgeräte.',
],
[
'id' => 'redox',
'label' => 'Redox / ORP',
'request_terms' => ['redox', 'orp', 'oxidations-reduktionspotential', 'oxidations reduktionspotential'],
'positive_terms' => ['Redox', 'ORP', 'Oxidations-Reduktionspotential', 'Oxidations Reduktionspotential'],
'positive_context_terms' => ['Messung', 'messen', 'misst', 'Messbereich', 'Messparameter', 'Messgröße', 'Messgroesse', 'Bestimmung', 'bestimmen', 'Analyse', 'analysiert', 'überwachen', 'ueberwachen', 'Indikator für', 'Indikator fuer', 'Reagenz für', 'Reagenz fuer', 'Sensor', 'Elektrode'],
'negative_context_terms' => ['Betriebsbereich', 'Betriebsumgebung', 'Einsatzbedingungen', 'störungsfrei', 'stoerungsfrei'],
'non_equivalent_terms' => [],
'safe_no_evidence_answer_de' => 'Ich finde in den bereitgestellten Quellen keinen sicher belegten Treffer für Redox-/ORP-Messung.',
'safe_no_accessory_evidence_answer_de' => 'Ich finde in den bereitgestellten Quellen keinen sicher belegten Redox-/ORP-Indikator oder ein Redox-/ORP-Reagenz für Messgeräte.',
],
[
'id' => 'free_chlorine',
'label' => 'freies Chlor',
'request_terms' => ['freies chlor', 'freiem chlor', 'freien chlor', 'free chlorine'],
'positive_terms' => ['freies Chlor', 'freiem Chlor', 'freien Chlor', 'free chlorine'],
'positive_context_terms' => ['Messung', 'messen', 'misst', 'Messbereich', 'Messparameter', 'Messgröße', 'Messgroesse', 'Bestimmung', 'bestimmen', 'Analyse', 'analysiert', 'überwachen', 'ueberwachen', 'Indikator für', 'Indikator fuer', 'Reagenz für', 'Reagenz fuer', 'Sensor', 'Elektrode'],
'negative_context_terms' => ['Betriebsbereich', 'Betriebsumgebung', 'Einsatzbedingungen', 'störungsfrei', 'stoerungsfrei'],
'non_equivalent_terms' => ['Chlor gesamt', 'Gesamtchlor', 'total chlorine'],
'safe_no_evidence_answer_de' => 'Ich finde in den bereitgestellten Quellen keinen sicher belegten Treffer für die Messung von freiem Chlor.',
'safe_no_accessory_evidence_answer_de' => 'Ich finde in den bereitgestellten Quellen keinen sicher belegten Indikator oder ein Reagenz für die Messung von freiem Chlor.',
],
];
private const ACCESSORY_REQUEST_KEYWORDS = [
'passend',
'passende',
'passendes',
'zubehör',
'zubehor',
'dazu',
'indikator',
'indikatoren',
'ph-indikator',
'ph indikator',
'ph-indikatoren',
'ph indikatoren',
'reagenz',
'kit',
'set',
'zusatz',
'ergänzung',
'ergaenzung',
];
/**
* @param array<string, mixed> $config
*/
public function __construct(
private readonly array $config = [],
private readonly ?DomainVocabularyConfig $vocabulary = null,
) {
}
@@ -214,19 +74,7 @@ final class PromptBuilderConfig
return $this->getRequiredInt('technical_product_keyword_match_threshold');
}
private function getInt(string $path, int $default): int
{
$value = $this->getValue($path, $default);
return is_numeric($value) ? (int) $value : $default;
}
private function getFloat(string $path, float $default): float
{
$value = $this->getValue($path, $default);
return is_numeric($value) ? (float) $value : $default;
}
private function getRequiredInt(string $path): int
{
@@ -250,18 +98,6 @@ final class PromptBuilderConfig
return (float) $value;
}
private function getString(string $path, string $default): string
{
$value = $this->getValue($path, $default);
if (!is_scalar($value)) {
return $default;
}
$value = (string) $value;
return $value !== '' ? $value : $default;
}
private function getRequiredString(string $path): string
{
@@ -279,33 +115,6 @@ final class PromptBuilderConfig
return $value;
}
/**
* @return string[]
*/
private function getStringList(string $path, array $default): array
{
$value = $this->getValue($path, $default);
if (!is_array($value)) {
return $default;
}
$out = [];
foreach ($value as $item) {
if (!is_scalar($item)) {
continue;
}
$item = trim((string) $item);
if ($item === '' || in_array($item, $out, true)) {
continue;
}
$out[] = $item;
}
return $out !== [] ? $out : $default;
}
/**
* @return string[]
@@ -371,28 +180,7 @@ final class PromptBuilderConfig
return $out;
}
/**
* @return string[]
*/
private function vocabularyView(string $path, array $fallback): array
{
return $this->vocabulary?->view($path, $fallback) ?? $fallback;
}
private function getValue(string $path, mixed $default): mixed
{
$current = $this->config;
foreach (explode('.', $path) as $segment) {
if (!is_array($current) || !array_key_exists($segment, $current)) {
return $default;
}
$current = $current[$segment];
}
return $current;
}
private function getOptionalValue(string $path): mixed
{
@@ -737,7 +525,7 @@ final class PromptBuilderConfig
*/
public function getMainDeviceRequestRoleKeywords(): array
{
return $this->getStringList('role_guard.main_device_request_keywords', self::MAIN_DEVICE_REQUEST_ROLE_KEYWORDS);
return $this->getRequiredStringList('role_guard.main_device_request_keywords');
}
/**
@@ -745,7 +533,7 @@ final class PromptBuilderConfig
*/
public function getMainDeviceProductRoleKeywords(): array
{
return $this->getStringList('role_guard.main_device_product_keywords', self::MAIN_DEVICE_PRODUCT_ROLE_KEYWORDS);
return $this->getRequiredStringList('role_guard.main_device_product_keywords');
}
/**
@@ -753,7 +541,7 @@ final class PromptBuilderConfig
*/
public function getAccessoryProductRoleKeywords(): array
{
return $this->getStringList('role_guard.accessory_product_keywords', self::ACCESSORY_PRODUCT_ROLE_KEYWORDS);
return $this->getRequiredStringList('role_guard.accessory_product_keywords');
}
/**
@@ -761,10 +549,7 @@ final class PromptBuilderConfig
*/
public function getTechnicalProductKeywords(): array
{
return $this->getStringList(
'technical_product_keywords',
$this->vocabularyView('prompt.technical_product_keywords', self::TECHNICAL_PRODUCT_KEYWORDS)
);
return $this->getRequiredStringList('technical_product_keywords');
}
/**
@@ -772,15 +557,12 @@ final class PromptBuilderConfig
*/
public function getAccessoryRequestKeywords(): array
{
return $this->getStringList(
'accessory_request_keywords',
$this->vocabularyView('prompt.accessory_request_keywords', self::ACCESSORY_REQUEST_KEYWORDS)
);
return $this->getRequiredStringList('accessory_request_keywords');
}
public function getMeasurementEvidenceSectionLabel(): string
{
return $this->getString('sections.measurement_evidence_label', 'MEASUREMENT PARAMETER EVIDENCE CHECK');
return $this->getRequiredString('sections.measurement_evidence_label');
}
/**
@@ -788,11 +570,7 @@ final class PromptBuilderConfig
*/
public function getMeasurementEvidenceIntroRules(): array
{
return $this->getStringList('measurement_evidence_guard.intro_rules', [
'- This block is generated from the current user question and is stricter than broad product-selection wording.',
'- For measurement-parameter questions, technical suitability requires explicit positive evidence for the requested parameter in the same source record.',
'- Similar water-treatment parameters, abbreviations, units, product families, search queries, or ranking positions are not enough.',
]);
return $this->getRequiredStringList('measurement_evidence_guard.intro_rules');
}
/**
@@ -800,10 +578,10 @@ final class PromptBuilderConfig
*/
public function getMeasurementEvidenceParameters(): array
{
$value = $this->getValue('measurement_evidence_guard.parameters', self::MEASUREMENT_EVIDENCE_PARAMETERS);
$value = $this->getRequiredValue('measurement_evidence_guard.parameters');
if (!is_array($value)) {
return self::MEASUREMENT_EVIDENCE_PARAMETERS;
throw new \InvalidArgumentException('RetrieX prompt config value "measurement_evidence_guard.parameters" must be a list of parameter definitions.');
}
$out = [];
@@ -837,7 +615,11 @@ final class PromptBuilderConfig
];
}
return $out !== [] ? $out : self::MEASUREMENT_EVIDENCE_PARAMETERS;
if ($out === []) {
throw new \InvalidArgumentException('RetrieX prompt config value "measurement_evidence_guard.parameters" must contain at least one valid parameter definition.');
}
return $out;
}
/**
@@ -866,6 +648,6 @@ final class PromptBuilderConfig
public function getTechnicalProductModelPattern(): string
{
return $this->getString('technical_product_model_pattern', '/\b[\p{L}]{2,}\s?\d{2,5}\b/u');
return $this->getRequiredString('technical_product_model_pattern');
}
}