diff --git a/RETRIEX_PATCH_9_0B_PROMPT_LABELS_SHOP_TEXTS_YAML_ONLY_README.md b/RETRIEX_PATCH_9_0B_PROMPT_LABELS_SHOP_TEXTS_YAML_ONLY_README.md new file mode 100644 index 0000000..d263317 --- /dev/null +++ b/RETRIEX_PATCH_9_0B_PROMPT_LABELS_SHOP_TEXTS_YAML_ONLY_README.md @@ -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 +``` diff --git a/src/Config/PromptBuilderConfig.php b/src/Config/PromptBuilderConfig.php index 14fa0c1..76d0993 100644 --- a/src/Config/PromptBuilderConfig.php +++ b/src/Config/PromptBuilderConfig.php @@ -263,6 +263,22 @@ final class PromptBuilderConfig 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[] */ @@ -291,6 +307,38 @@ final class PromptBuilderConfig 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[] */ @@ -331,17 +379,17 @@ final class PromptBuilderConfig public function getSystemSectionLabel(): string { - return $this->getString('sections.system_label', 'SYSTEM'); + return $this->getRequiredString('sections.system_label'); } public function getUserQuestionSectionLabel(): string { - return $this->getString('sections.user_question_label', 'USER QUESTION'); + return $this->getRequiredString('sections.user_question_label'); } 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 { - return $this->getStringList('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.', - ]); + return $this->getRequiredStringList('conversation_context.intro_lines'); } 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 { - 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 { - return $this->getStringList('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.', - ]); + return $this->getRequiredStringList('shop_results.header_lines'); } 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 { - return $this->getString('shop_results.record_header_template', '[%d] SHOP PRODUCT RECORD'); + return $this->getRequiredString('shop_results.record_header_template'); } 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 { - return $this->getStringList('shop_results.atomic_record_note_lines', [ - 'Record boundary: all fields below belong only to this exact shop product record.', - ]); + return $this->getRequiredStringList('shop_results.atomic_record_note_lines'); } 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 { - 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 { - return $this->getString('retrieved_knowledge.source_line', 'Source: Documents'); + return $this->getRequiredString('retrieved_knowledge.source_line'); } 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 { - return $this->getString('url_content.source_line', 'Source: URL'); + return $this->getRequiredString('url_content.source_line'); } 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 { - return $this->getString('shop_results.fields.manufacturer_label', 'Manufacturer'); + return $this->getRequiredString('shop_results.fields.manufacturer_label'); } 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 { - return $this->getString('shop_results.fields.availability_label', 'Available'); + return $this->getRequiredString('shop_results.fields.availability_label'); } 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 { - return $this->getString('shop_results.fields.availability_no_label', 'no'); + return $this->getRequiredString('shop_results.fields.availability_no_label'); } public function getShopHighlightPrefix(): string { - return $this->getString('shop_results.fields.highlight_prefix', '- '); + return $this->getRequiredString('shop_results.fields.highlight_prefix'); } 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 { - return $this->getString('shop_results.fields.product_image_label', 'Product image'); + return $this->getRequiredString('shop_results.fields.product_image_label'); } 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 { - return $this->getString('shop_results.fields.meta_information_label', 'Meta information'); + return $this->getRequiredString('shop_results.fields.meta_information_label'); } 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 { - 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 { - 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 { - return $this->getString( - '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.' - ); + return $this->getRequiredString('shop_results.fields.role_incompatible_commercial_suppression_note'); } /**