Guard unbound entity names in case interpretation

This commit is contained in:
Nico committed 2026-10-15 09:41:34 +02:00
1 parent 69e4f3b349
commit 3bf7b5bba7
12 files changed
+241 -18

No files matched your search

+12 -4
View File
@@ -20,7 +20,7 @@ PHASES = {
"hypotheses": (State.GENERATING_HYPOTHESES, "analysis/hypotheses", "hypotheses-rejected", "hypothesis-error", "hypothesis_context", "hypothesis-context"),
}
MODEL_CODES = frozenset({"LLM_OUTPUT_SCHEMA", "LLM_RESPONSE", "LLM_INCOMPLETE_OUTPUT",
"INTERPRETATION_SCHEMA", "INTERPRETATION_SOURCE_UNKNOWN", "INTERPRETATION_SOURCE_KIND",
"INTERPRETATION_UNGROUNDED_ENTITY_LABEL", "INTERPRETATION_SCHEMA", "INTERPRETATION_SOURCE_UNKNOWN", "INTERPRETATION_SOURCE_KIND",
"INTERPRETATION_EXPECTATION_UNKNOWN", "INTERPRETATION_CONTRADICTION_SOURCES",
"INTERPRETATION_GAP_SOURCES", "INTERPRETATION_ENTITY", "INTERPRETATION_RESOLVED_ENTITY_OMITTED",
"INTERPRETATION_UNRESOLVED_ENTITY_OMITTED", "INTERPRETATION_ENTITY_PROVENANCE", "INTERPRETATION_DESCRIPTION_SOURCES",
@@ -159,14 +159,22 @@ def feedback(reader, phase, failure):
if len(available) <= 32:
issue["allowed_source_ids"] = available
issues.append(issue)
if error.get('prompt_version') in {'case_interpretation_resolved_v2','case_interpretation_resolved_v3'}:
if error.get('prompt_version') in {'case_interpretation_resolved_v2','case_interpretation_resolved_v3','case_interpretation_resolved_v4'}:
from .contracts import entity_citation_issues
issues.extend(entity_citation_issues(payload,context))
if error.get('prompt_version') == 'case_interpretation_resolved_v4':
from .quality import unnamed_entity_reasons
reasons = unnamed_entity_reasons(context)
for index, entity in enumerate(payload.get('relevant_entities',[])):
sid = entity.get('entity_id')
if sid in reasons and entity.get('reason') != reasons[sid]:
issues.append({'field':f'relevant_entities/{index}/reason',
'constraint':'use_neutral_identity_without_name_binding', 'required_reason':reasons[sid]})
for sid, source in context['sources'].items():
if (source['kind']=='data_quality' and source['case_pointer'].startswith('/entity_resolution/')
and not any(sid in s.get('source_ids',[]) for s in payload.get('unknowns',[]) if isinstance(s,dict))):
issues.append({'field':'unknowns','source_id':sid,'constraint':'retain_scoped_lookup_uncertainty'})
if error.get('prompt_version') == 'case_interpretation_resolved_v3':
if error.get('prompt_version') in {'case_interpretation_resolved_v3','case_interpretation_resolved_v4'}:
from .quality import description_sources
allowed = description_sources(context)
for index, statement in enumerate(payload.get('described_behavior',[])):
@@ -213,7 +221,7 @@ def feedback(reader, phase, failure):
if not set(proposal.get("interpretation_ids", [])) & focus:
issues.append({"field": "hypotheses/0/interpretation_ids", "constraint": "primary_focus", "allowed_source_ids": sorted(focus)[:32]})
instruction = "Return a complete replacement JSON object conforming to the supplied schema and source rules. Do not invent facts or sources. Use empty arrays/null for unsupported claims. Distinct hypotheses must retain valid grounding and count constraints."
if phase == 'interpretation' and error.get('prompt_version') in {'case_interpretation_resolved_v2','case_interpretation_resolved_v3'}:
if phase == 'interpretation' and error.get('prompt_version') in {'case_interpretation_resolved_v2','case_interpretation_resolved_v3','case_interpretation_resolved_v4'}:
instruction += " Keep tester observations in observed_behavior with observation sources; move technical assertions to confirmed_technical_facts with fact sources. Retain selected entity_id values and cite their original mentions plus entity or matching capture evidence. Keep lookup uncertainties scoped to their queries."
return {"code":error["code"],"issues":issues[:32],"instruction":instruction}