Accept traced entity capture citations and improve correction feedback

This commit is contained in:
Nico committed 2026-10-15 01:01:02 +02:00
1 parent 4c73b00494
commit 12bc84c02c
12 files changed
+2020 -11

No files matched your search

+53 -2
View File
@@ -55,6 +55,7 @@ class Model:
self.invalid_assessment = 0
self.unavailable_stage = None
self.omit_resolved = 0
self.linked_capture = False
async def generate(self, config, request, *, max_response_bytes):
self.requests.append(copy.deepcopy(request))
@@ -83,7 +84,12 @@ class Model:
data = json.loads(request["messages"][1]["content"])["input"]
selected = [(k,v) for k,v in data["sources"].items() if v["kind"] == "entity" and v["case_pointer"].startswith("/entity_resolution/")]
for k,v in selected:
document["relevant_entities"].append({"entity_id": k, "reason": "Contextually matched affected switch", "source_ids": [k,"O0001"]})
evidence = k
if self.linked_capture:
evidence = next(f for f,s in data['sources'].items() if s['kind']=='fact'
and s['data'].get('name')=='entity_name_lookup'
and s['data']['provenance']['source_ref']==v['data']['provenance']['source_ref'])
document["relevant_entities"].append({"entity_id": k, "reason": "Contextually matched affected switch", "source_ids": [evidence,"O0001"]})
if self.omit_resolved:
self.omit_resolved -= 1
document["relevant_entities"] = payload()["relevant_entities"]
@@ -118,7 +124,7 @@ def test_resolves_unrecorded_subject_preserves_case_and_portable_replay(built_ca
model = Model(); service = service_for(store)
result = run(store, model, service)
assert result.status == "COMPLETED" and PackageValidator().validate(result.package).valid
assert [r["prompt_version"] for r in model.requests] == ["entity_resolution_plan_v2","entity_resolution_assessment_v2","case_interpretation_resolved_v1"]
assert [r["prompt_version"] for r in model.requests] == ["entity_resolution_plan_v2","entity_resolution_assessment_v2","case_interpretation_resolved_v2"]
assert len(service.database.calls) == 1
assert service.database.calls[0]["filters"] == [{"column":"name","operator":"contains","value":"switch"}]
with PackageReader(result.package) as reader:
@@ -254,10 +260,55 @@ def test_final_interpretation_cannot_discard_resolved_identity(built_case):
assert result.status=='REJECTED' and result.code=='INTERPRETATION_RESOLVED_ENTITY_OMITTED'
result=run(store,model,service,correction_config=CorrectionConfig())
assert result.status=='COMPLETED' and len(model.requests)==4
feedback=json.loads(model.requests[-1]['messages'][-1]['content'])['validation_feedback']
assert any(i.get('constraint')=='retain_resolved_entity' and i['entity_id']=='N0002' for i in feedback['issues'])
assert len(service.database.calls)==1
assert run(store,model).reused and len(model.requests)==4
def test_matching_database_capture_is_valid_without_redundant_entity_citation(built_case):
store=built_case[0];model=Model();model.linked_capture=True;service=service_for(store)
result=run(store,model,service)
assert result.status=='COMPLETED' and len(model.requests)==3
assert run(store,model).reused
with PackageReader(result.package) as r:
doc=json.loads(r.read_bytes(result.interpretation_ref))
assert 'N0002' not in doc['interpretation']['relevant_entities'][-1]['source_ids']
def test_provenance_feedback_identifies_missing_mention_and_wrong_capture(built_case):
from viano_bug_analysis.analysis.contracts import entity_citation_issues,validate_interpretation
store=built_case[0];model=Model();service=service_for(store);result=run(store,model,service)
with PackageReader(result.package) as r:
doc=json.loads(r.read_bytes(result.interpretation_ref));context=json.loads(r.read_bytes(doc['context_ref']))
item=doc['interpretation']['relevant_entities'][-1]
item['source_ids']=['F0001']
issues=entity_citation_issues(doc['interpretation'],context)
assert {i['constraint'] for i in issues}=={'cite_original_mention','cite_entity_or_matching_capture'}
assert next(i for i in issues if i['constraint']=='cite_original_mention')['allowed_source_ids']==['O0001']
assert 'N0002' in next(i for i in issues if i['constraint']=='cite_entity_or_matching_capture')['allowed_source_ids']
with pytest.raises(InterpretationError,match='INTERPRETATION_ENTITY_PROVENANCE'):
validate_interpretation(doc['interpretation'],context)
# Even a lookup-shaped fact cannot substitute an unrelated capture.
context['sources']['F0001']['data'].update(name='entity_name_lookup',provenance={'source_ref':'different/capture.json'})
item['source_ids']=['O0001','F0001']
assert [i['constraint'] for i in entity_citation_issues(doc['interpretation'],context)]==['cite_entity_or_matching_capture']
def test_real_gemini_reply_is_accepted_with_matching_capture_and_legacy_rule_is_preserved():
from pathlib import Path
from viano_bug_analysis.analysis.contracts import validate_interpretation
fixture=json.loads((Path(__file__).parent/'fixtures/interpretation/real-capture-citations.json').read_text(encoding='utf-8'))
validate_interpretation(fixture['response'],fixture['context'])
entity=next(e for e in fixture['response']['relevant_entities'] if e['entity_id']=='N0005')
assert 'N0005' not in entity['source_ids'] and 'F0027' in entity['source_ids']
assert fixture['context']['sources']['N0005']['data']['id']=='23487'
old=copy.deepcopy(fixture['context'])
del old['sources']['N0005']['data']['citation_policy']
with pytest.raises(InterpretationError,match='INTERPRETATION_RESOLVED_ENTITY_OMITTED'):
validate_interpretation(fixture['response'],old)
@pytest.mark.parametrize('settings', [{'enabled':True,'catalogs':[]},{**SETTINGS,'max_searches':True},{**SETTINGS,'unexpected':5}])
def test_invalid_operator_configuration(settings):
with pytest.raises(InterpretationError,match='ENTITY_RESOLUTION_CONFIG'):policy(settings)