patch 9.1

This commit is contained in:
team 1
2026-04-30 18:51:47 +02:00
parent 6c2c595a1c
commit d6e73ef5a2
2 changed files with 125 additions and 56 deletions

View File

@@ -0,0 +1,46 @@
# RetrieX Patch 9.0b - PromptBuilder Labels and Shop Result Texts YAML-only
## Scope
This patch builds on Patch 9.0a.
It only removes PHP fallback defaults from small PromptBuilder text/label getters:
- section labels
- conversation context intro lines
- shop search source line
- live shop result header lines
- shop product record templates
- shop result field labels
- retrieved knowledge / URL content labels and source lines
## Not changed
This patch does not change:
- output priority rule texts
- fallback escalation rules
- response format rules
- language rules
- fact grounding rules
- measurement evidence guard
- role guard keyword lists
- retrieval
- commerce parser
- shop matching
- AgentRunner
- SSE / frontend
## Expected audit effect
PromptBuilderConfig should lose the fallback accessor entries for the migrated labels/templates/lists.
Remaining PromptBuilder fallback accessors are expected for the larger rule groups that will be handled in later 9.x patches.
## Test commands
```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

@@ -263,6 +263,22 @@ final class PromptBuilderConfig
return $value !== '' ? $value : $default; return $value !== '' ? $value : $default;
} }
private function getRequiredString(string $path): string
{
$value = $this->getRequiredValue($path);
if (!is_scalar($value)) {
throw new \InvalidArgumentException(sprintf('RetrieX prompt config value "%s" must be a scalar string.', $path));
}
$value = (string) $value;
if (trim($value) === '') {
throw new \InvalidArgumentException(sprintf('RetrieX prompt config value "%s" must not be empty.', $path));
}
return $value;
}
/** /**
* @return string[] * @return string[]
*/ */
@@ -291,6 +307,38 @@ final class PromptBuilderConfig
return $out !== [] ? $out : $default; return $out !== [] ? $out : $default;
} }
/**
* @return string[]
*/
private function getRequiredStringList(string $path): array
{
$value = $this->getRequiredValue($path);
if (!is_array($value)) {
throw new \InvalidArgumentException(sprintf('RetrieX prompt config value "%s" must be a string list.', $path));
}
$out = [];
foreach ($value as $item) {
if (!is_scalar($item)) {
continue;
}
$item = trim((string) $item);
if ($item === '' || in_array($item, $out, true)) {
continue;
}
$out[] = $item;
}
if ($out === []) {
throw new \InvalidArgumentException(sprintf('RetrieX prompt config value "%s" must contain at least one string.', $path));
}
return $out;
}
/** /**
* @return string[] * @return string[]
*/ */
@@ -331,17 +379,17 @@ final class PromptBuilderConfig
public function getSystemSectionLabel(): string public function getSystemSectionLabel(): string
{ {
return $this->getString('sections.system_label', 'SYSTEM'); return $this->getRequiredString('sections.system_label');
} }
public function getUserQuestionSectionLabel(): string public function getUserQuestionSectionLabel(): string
{ {
return $this->getString('sections.user_question_label', 'USER QUESTION'); return $this->getRequiredString('sections.user_question_label');
} }
public function getConversationContextSectionLabel(): string public function getConversationContextSectionLabel(): string
{ {
return $this->getString('sections.conversation_context_label', 'CONVERSATION CONTEXT (contextual only)'); return $this->getRequiredString('sections.conversation_context_label');
} }
/** /**
@@ -349,23 +397,17 @@ final class PromptBuilderConfig
*/ */
public function getConversationContextIntroLines(): array public function getConversationContextIntroLines(): array
{ {
return $this->getStringList('conversation_context.intro_lines', [ return $this->getRequiredStringList('conversation_context.intro_lines');
'The following messages are previous turns of this conversation.',
'Use them only to resolve references, follow-up questions, and user intent.',
'Previous assistant answers are not a factual source for technical values, product compatibility, indicators, ranges, prices, or availability.',
'All factual claims must come from retrieved factual knowledge, user-provided URL content, or live shop data.',
'Conversation context must not override retrieved factual knowledge or live shop data.',
]);
} }
public function getShopSearchQuerySectionLabel(): string public function getShopSearchQuerySectionLabel(): string
{ {
return $this->getString('sections.shop_search_query_label', 'SHOP SEARCH QUERY'); return $this->getRequiredString('sections.shop_search_query_label');
} }
public function getShopSearchQuerySourceLine(): string public function getShopSearchQuerySourceLine(): string
{ {
return $this->getString('shop_search.source_line', 'Source: Shop Search'); return $this->getRequiredString('shop_search.source_line');
} }
/** /**
@@ -373,36 +415,22 @@ final class PromptBuilderConfig
*/ */
public function getLiveShopResultsHeaderLines(): array public function getLiveShopResultsHeaderLines(): array
{ {
return $this->getStringList('shop_results.header_lines', [ return $this->getRequiredStringList('shop_results.header_lines');
'LIVE SHOP RESULTS (authoritative for current commercial details):',
'Use these results as the primary source for current price, availability, URL, current shop-visible product naming, and explicitly shop-visible product suitability when the user asks which product/device can measure or monitor something.',
'If retrieved documents conflict with shop data on price, availability, URL, current naming, or explicitly shop-visible product suitability, prefer the shop data for those fields.',
'If retrieved documents do not identify a matching product, but a live shop result explicitly names the requested measurement parameter or application, do not conclude that no matching product exists.',
'Output real URL values exactly as provided in the shop results. Do not replace them with placeholders, link labels, or product names.',
'Do not infer undocumented technical specifications from shop data.',
'Commercial fields from shop data may only be assigned to a product if the shop item clearly matches the same product identity.',
'Do not merge a device identified in retrieved knowledge with price, URL, product number, or availability from a different shop item such as a reagent, accessory, kit, consumable, or service item.',
'If a shop result has no price field, do not state a price for it.',
'Never interpret a missing price or a zero price as free, kostenlos, gratis, or available for 0.00 EUR.',
'Treat every SHOP PRODUCT RECORD as atomic: exact product name, product number, price, availability, URL, image, description, and metadata must stay together.',
'When outputting a shop item, use the exact shop product name from that same SHOP PRODUCT RECORD as the heading. Never use a retrieved-knowledge device name as the heading for a different shop URL or product number.',
'If a technical device from retrieved knowledge and a shop record are not clearly the same exact product identity, separate Fachliche Einordnung from Shop-Treffer instead of merging them.',
]);
} }
public function getLiveShopResultsOverflowNoticeTemplate(): string public function getLiveShopResultsOverflowNoticeTemplate(): string
{ {
return $this->getString('shop_results.overflow_notice_template', 'Only the top %d ranked shop results are shown here out of %d total results.'); return $this->getRequiredString('shop_results.overflow_notice_template');
} }
public function getShopRecordHeaderTemplate(): string public function getShopRecordHeaderTemplate(): string
{ {
return $this->getString('shop_results.record_header_template', '[%d] SHOP PRODUCT RECORD'); return $this->getRequiredString('shop_results.record_header_template');
} }
public function getShopExactProductNameLabel(): string public function getShopExactProductNameLabel(): string
{ {
return $this->getString('shop_results.exact_product_name_label', 'Exact shop product name'); return $this->getRequiredString('shop_results.exact_product_name_label');
} }
/** /**
@@ -410,14 +438,12 @@ final class PromptBuilderConfig
*/ */
public function getShopAtomicRecordNoteLines(): array public function getShopAtomicRecordNoteLines(): array
{ {
return $this->getStringList('shop_results.atomic_record_note_lines', [ return $this->getRequiredStringList('shop_results.atomic_record_note_lines');
'Record boundary: all fields below belong only to this exact shop product record.',
]);
} }
public function getOutputPrioritySectionLabel(): string public function getOutputPrioritySectionLabel(): string
{ {
return $this->getString('sections.output_priority_label', 'OUTPUT PRIORITY'); return $this->getRequiredString('sections.output_priority_label');
} }
/** /**
@@ -700,100 +726,97 @@ final class PromptBuilderConfig
public function getRetrievedKnowledgeSectionLabel(): string public function getRetrievedKnowledgeSectionLabel(): string
{ {
return $this->getString('sections.retrieved_knowledge_label', 'RETRIEVED KNOWLEDGE (primary for technical matching and factual explanation)'); return $this->getRequiredString('sections.retrieved_knowledge_label');
} }
public function getRetrievedKnowledgeSourceLine(): string public function getRetrievedKnowledgeSourceLine(): string
{ {
return $this->getString('retrieved_knowledge.source_line', 'Source: Documents'); return $this->getRequiredString('retrieved_knowledge.source_line');
} }
public function getUrlContentSectionLabel(): string public function getUrlContentSectionLabel(): string
{ {
return $this->getString('sections.url_content_label', 'CONTENT FROM URL (authoritative if user-provided)'); return $this->getRequiredString('sections.url_content_label');
} }
public function getUrlContentSourceLine(): string public function getUrlContentSourceLine(): string
{ {
return $this->getString('url_content.source_line', 'Source: URL'); return $this->getRequiredString('url_content.source_line');
} }
public function getShopProductNumberLabel(): string public function getShopProductNumberLabel(): string
{ {
return $this->getString('shop_results.fields.product_number_label', 'Product number'); return $this->getRequiredString('shop_results.fields.product_number_label');
} }
public function getShopManufacturerLabel(): string public function getShopManufacturerLabel(): string
{ {
return $this->getString('shop_results.fields.manufacturer_label', 'Manufacturer'); return $this->getRequiredString('shop_results.fields.manufacturer_label');
} }
public function getShopPriceLabel(): string public function getShopPriceLabel(): string
{ {
return $this->getString('shop_results.fields.price_label', 'Price'); return $this->getRequiredString('shop_results.fields.price_label');
} }
public function getShopAvailabilityLabel(): string public function getShopAvailabilityLabel(): string
{ {
return $this->getString('shop_results.fields.availability_label', 'Available'); return $this->getRequiredString('shop_results.fields.availability_label');
} }
public function getShopAvailabilityYesLabel(): string public function getShopAvailabilityYesLabel(): string
{ {
return $this->getString('shop_results.fields.availability_yes_label', 'yes'); return $this->getRequiredString('shop_results.fields.availability_yes_label');
} }
public function getShopAvailabilityNoLabel(): string public function getShopAvailabilityNoLabel(): string
{ {
return $this->getString('shop_results.fields.availability_no_label', 'no'); return $this->getRequiredString('shop_results.fields.availability_no_label');
} }
public function getShopHighlightPrefix(): string public function getShopHighlightPrefix(): string
{ {
return $this->getString('shop_results.fields.highlight_prefix', '- '); return $this->getRequiredString('shop_results.fields.highlight_prefix');
} }
public function getShopUrlLabel(): string public function getShopUrlLabel(): string
{ {
return $this->getString('shop_results.fields.url_label', 'URL'); return $this->getRequiredString('shop_results.fields.url_label');
} }
public function getShopProductImageLabel(): string public function getShopProductImageLabel(): string
{ {
return $this->getString('shop_results.fields.product_image_label', 'Product image'); return $this->getRequiredString('shop_results.fields.product_image_label');
} }
public function getShopDescriptionLabel(): string public function getShopDescriptionLabel(): string
{ {
return $this->getString('shop_results.fields.description_label', 'Description'); return $this->getRequiredString('shop_results.fields.description_label');
} }
public function getShopMetaInformationLabel(): string public function getShopMetaInformationLabel(): string
{ {
return $this->getString('shop_results.fields.meta_information_label', 'Meta information'); return $this->getRequiredString('shop_results.fields.meta_information_label');
} }
public function getShopRequestedRoleLabel(): string public function getShopRequestedRoleLabel(): string
{ {
return $this->getString('shop_results.fields.requested_role_label', 'Requested product role'); return $this->getRequiredString('shop_results.fields.requested_role_label');
} }
public function getShopInferredRoleLabel(): string public function getShopInferredRoleLabel(): string
{ {
return $this->getString('shop_results.fields.inferred_role_label', 'Inferred shop product role'); return $this->getRequiredString('shop_results.fields.inferred_role_label');
} }
public function getShopRoleCompatibilityLabel(): string public function getShopRoleCompatibilityLabel(): string
{ {
return $this->getString('shop_results.fields.role_compatibility_label', 'Role compatibility with request'); return $this->getRequiredString('shop_results.fields.role_compatibility_label');
} }
public function getShopRoleIncompatibleCommercialSuppressionNote(): string public function getShopRoleIncompatibleCommercialSuppressionNote(): string
{ {
return $this->getString( return $this->getRequiredString('shop_results.fields.role_incompatible_commercial_suppression_note');
'shop_results.fields.role_incompatible_commercial_suppression_note',
'Commercial fields suppressed: this shop record is not a matching main-device result for the requested product role.'
);
} }
/** /**