from src.rag.qa_v1_1_73.agentic_controller import AgenticRetrievalController from src.rag.qa_v1_1_73.schemas import EvidenceItem, HypothesisEntity, RetrievalHypothesis QUESTION = "荔枝中有哪些基因家族已经被研究" class FakeLLM: available = True def __init__(self, *responses): self.responses = list(responses) self.calls = [] def complete_json(self, system, user, *, max_tokens=2500): self.calls.append((system, user, max_tokens)) response = self.responses.pop(0) if isinstance(response, Exception): raise response return response def test_controller_caps_replans_at_one(): controller = AgenticRetrievalController(FakeLLM(), max_replans=99) assert controller.max_replans == 1 def hypothesis(): return RetrievalHypothesis( question_type="survey", direct_question_entities=[ HypothesisEntity( mention="荔枝", canonical_candidate="Litchi chinensis", entity_type="species", origin="question", confidence=1.0, verification_status="verified_alias", matched_kb_entity="Litchi chinensis", use_as_graph_seed=True, ), HypothesisEntity( mention="基因家族", canonical_candidate="gene family", entity_type="gene_family", origin="question", confidence=1.0, verification_status="question_direct_unverified", use_as_graph_seed=True, ), ], relation_intents=["studied_gene_families", "identified"], answer_slots=["list of gene family names"], ) def agent_plan(**overrides): value = { "mode": "PLAN", "question_analysis": { "language": "zh", "intent": "列出荔枝中研究过的基因家族", "operation": "enumerate", "expected_answer_shape": "entity_list", "requires_graph": True, }, "anchors": [ { "mention": "荔枝", "canonical_name": "Litchi chinensis", "canonical_id": "", "entity_type": "species", "role": "start_anchor", "confidence": 1.0, "required_in_every_answer_path": True, } ], "answer_targets": [ { "description": "基因家族", "allowed_entity_types": ["gene_family"], "role": "answer_endpoint", } ], "relation_plan": { "relation_intents": ["identified", "studied_gene_families"], "allowed_relation_categories": [], "relation_name_hints": ["identified_in", "reported_in"], "allow_incoming": True, "allow_outgoing": True, "semantic_direction": "anchor_to_answer", "preserve_assertion_direction": True, }, "graph_plan": { "enabled": True, "max_hops": 1, "min_hops": 1, "anchor_only_start": True, "required_anchor_coverage": 1.0, "required_terminal_types": ["gene_family"], "permit_reverse_navigation": True, "exclude_unrelated_vector_start_seeds": True, }, "vector_plan": { "enabled": True, "purpose": "supporting_evidence", "queries": ["Litchi chinensis gene family"], "may_create_graph_start_seeds": False, }, "acceptance_gate": { "minimum_supported_candidates": 1, "require_anchor_in_each_path": True, "require_terminal_type_match": True, "require_traceable_provenance": True, "reject_cross_species_substitution": True, }, "reasoning_summary": "以荔枝为锚点枚举相连基因家族", } value.update(overrides) return value def test_plan_gives_agent_direction_seed_and_endpoint_control(): controller = AgenticRetrievalController(FakeLLM(agent_plan())) plan = controller.plan(QUESTION, hypothesis()) assert plan["anchors"] == [ { "mention": "荔枝", "canonical_name": "Litchi chinensis", "canonical_id": "", "entity_type": "species", "role": "start_anchor", "confidence": 1.0, "required_in_every_answer_path": True, } ] assert plan["answer_targets"][0]["allowed_entity_types"] == ["gene_family"] assert plan["relation_plan"]["allow_incoming"] is True assert plan["graph_plan"]["anchor_only_start"] is True assert plan["graph_plan"]["max_hops"] == 1 def test_agent_prompt_allows_structured_assertions_without_graph_for_definition_questions(): llm = FakeLLM(agent_plan()) controller = AgenticRetrievalController(llm) controller.plan(QUESTION, hypothesis()) system_prompt = llm.calls[0][0] assert "definition, naming, or structure" in system_prompt assert "source, relation, target" in system_prompt assert "graph path is optional" in system_prompt def test_plan_removes_invented_anchor_and_never_uses_generic_target_as_start(): unsafe = agent_plan( anchors=[ { "mention": "TCP", "canonical_name": "TCP gene family", "canonical_id": "cent_invented", "entity_type": "gene_family", "role": "start_anchor", "confidence": 1.0, "required_in_every_answer_path": True, }, { "mention": "基因家族", "canonical_name": "gene family", "canonical_id": "", "entity_type": "gene_family", "role": "start_anchor", "confidence": 1.0, "required_in_every_answer_path": True, }, ], graph_plan={"max_hops": 99}, ) controller = AgenticRetrievalController(FakeLLM(unsafe)) plan = controller.plan(QUESTION, hypothesis()) assert [item["canonical_name"] for item in plan["anchors"]] == ["Litchi chinensis"] assert plan["graph_plan"]["max_hops"] == 3 def test_plan_falls_back_deterministically_when_agent_json_fails(): controller = AgenticRetrievalController(FakeLLM(ValueError("bad json"))) plan = controller.plan(QUESTION, hypothesis()) assert plan["plan_source"] == "deterministic_fallback" assert plan["anchors"][0]["canonical_name"] == "Litchi chinensis" assert plan["answer_targets"][0]["allowed_entity_types"] == ["gene_family"] assert plan["relation_plan"]["allow_incoming"] is True assert plan["graph_plan"]["max_hops"] == 1 def litchi_evidence(): return EvidenceItem( evidence_id="E1", kb="V16/V15", source="SPL gene family", relation="18_homologues_identified", target="Litchi chinensis", evidence="Eighteen SPL homologues were identified.", paper_id="10.1080_14620316.2018.1543557", doi="10.1080/14620316.2018.1543557", raw={ "assertion_id": "cass_spl_litchi", "source_type": "gene_family", "target_type": "species", "anchor_name": "Litchi chinensis", "answer_entity": "SPL gene family", "answer_entity_type": "gene_family", }, ) def test_review_accepts_only_real_anchor_connected_ids(): llm = FakeLLM( { "mode": "REVIEW", "decision": "ACCEPT", "attempt": 0, "accepted_evidence_ids": ["E1", "E_INVENTED"], "accepted_chain_ids": ["AG1", "G_INVENTED"], "problems": [], "replan": {"required": False}, "final_handoff": {"allowed": True}, } ) controller = AgenticRetrievalController(llm) chains = [ { "chain_id": "AG1", "start": "Litchi chinensis", "end": "SPL gene family", "answer_entity": "SPL gene family", "answer_entity_type": "gene_family", "anchor_name": "Litchi chinensis", "doi": "10.1080/14620316.2018.1543557", "hops": [], } ] review = controller.review( QUESTION, agent_plan(), [litchi_evidence()], chains, attempt=0 ) assert review["decision"] == "ACCEPT" assert review["accepted_evidence_ids"] == ["E1"] assert review["accepted_chain_ids"] == ["AG1"] assert review["final_handoff"]["allowed"] is True def test_review_overrides_false_accept_when_anchor_is_absent(): llm = FakeLLM( { "mode": "REVIEW", "decision": "ACCEPT", "attempt": 0, "accepted_evidence_ids": ["E2"], "accepted_chain_ids": ["G2"], "problems": [], "replan": {"required": False}, "final_handoff": {"allowed": True}, } ) controller = AgenticRetrievalController(llm) unrelated = EvidenceItem( evidence_id="E2", kb="V16/V15", source="TCP gene family", relation="identified_in", target="strawberry", evidence="TCP evidence", doi="10.1/tcp", raw={"source_type": "gene_family", "target_type": "species"}, ) review = controller.review( QUESTION, agent_plan(), [unrelated], [{"chain_id": "G2", "start": "TCP gene family", "end": "MYB family"}], attempt=0, ) assert review["decision"] == "REPLAN" assert review["final_handoff"]["allowed"] is False def test_review_rejects_cross_species_count_comparison_even_when_llm_accepts_it(): llm = FakeLLM( { "mode": "REVIEW", "decision": "ACCEPT", "attempt": 0, "accepted_evidence_ids": ["E_COMPARE"], "accepted_chain_ids": [], "final_handoff": {"allowed": True}, } ) controller = AgenticRetrievalController(llm) comparison = EvidenceItem( evidence_id="E_COMPARE", kb="V16/V15", source="HcARF genes", relation="number_similar_to", target="Litchi chinensis", evidence="The number of HcARF genes was similar to that in Litchi chinensis.", pmid="34052884", raw={ "source_type": "gene_family", "target_type": "species", "anchor_name": "Litchi chinensis", "answer_entity_type": "gene_family", }, ) review = controller.review(QUESTION, agent_plan(), [comparison], [], attempt=0) assert review["decision"] == "REPLAN" assert review["accepted_evidence_ids"] == [] def test_review_abstains_after_one_failed_replan(): controller = AgenticRetrievalController(FakeLLM({"decision": "REPLAN"})) review = controller.review(QUESTION, agent_plan(), [], [], attempt=1) assert review["decision"] == "ABSTAIN" assert review["final_handoff"]["allowed"] is True assert review["accepted_evidence_ids"] == [] assert review["accepted_chain_ids"] == [] def test_review_can_accept_the_result_of_the_first_replan(): controller = AgenticRetrievalController( FakeLLM( { "decision": "ACCEPT", "accepted_evidence_ids": ["E1"], "accepted_chain_ids": [], "final_handoff": {"allowed": True}, } ) ) review = controller.review( QUESTION, agent_plan(), [litchi_evidence()], [], attempt=1 ) assert review["decision"] == "ACCEPT" assert review["accepted_evidence_ids"] == ["E1"] def test_review_treats_unknown_terminal_type_as_unconstrained(): controller = AgenticRetrievalController( FakeLLM( { "decision": "ACCEPT", "accepted_evidence_ids": ["E_R2R3"], "accepted_chain_ids": [], "final_handoff": {"allowed": True}, } ) ) evidence = EvidenceItem( evidence_id="E_R2R3", kb="V16/V15", source="R2R3-MYB proteins", relation="contain", target="R2 and R3 MYB repeats", evidence="R2R3-MYB proteins contain the R2 and R3 MYB repeats.", doi="10.1000/r2r3", raw={ "source_type": "protein", "target_type": "domain", "answer_entity_type": "domain", }, ) plan = { "anchors": [], "question_analysis": { "operation": "lookup", "expected_answer_shape": "fact", }, "graph_plan": {"required_terminal_types": ["unknown"]}, "acceptance_gate": { "require_anchor_in_each_path": False, "require_traceable_provenance": True, }, } review = controller.review( "R2R3-MYB蛋白为什么被称为R2R3-MYB?", plan, [evidence], [], attempt=0, ) assert review["decision"] == "ACCEPT" assert review["accepted_evidence_ids"] == ["E_R2R3"] def test_deterministic_handoff_keeps_structured_direct_evidence_but_enforces_anchor(): controller = AgenticRetrievalController(FakeLLM()) direct = EvidenceItem( evidence_id="E_R2R3", kb="V16/V15", source="R2R3-MYB proteins", relation="contain", target="R2 and R3 MYB repeats", evidence="R2R3-MYB proteins contain the R2 and R3 MYB repeats.", doi="10.1000/r2r3", raw={"answer_entity_type": "domain"}, ) unrelated = EvidenceItem( evidence_id="E_TCP", kb="V16/V15", source="TCP gene family", relation="identified_in", target="Arabidopsis thaliana", evidence="TCP genes were identified in Arabidopsis thaliana.", doi="10.1000/tcp", raw={"answer_entity_type": "gene_family"}, ) direct_items, _ = controller.deterministic_handoff( { "anchors": [], "graph_plan": {"required_terminal_types": ["unknown"]}, }, [direct, unrelated], [], ) strict_items, _ = controller.deterministic_handoff( { "anchors": [{"canonical_name": "Litchi chinensis"}], "graph_plan": {"required_terminal_types": ["gene_family"]}, }, [unrelated], [], ) assert [item.evidence_id for item in direct_items] == ["E_R2R3", "E_TCP"] assert strict_items == [] def hsp_hypothesis(): return RetrievalHypothesis( question_type="mechanism", direct_question_entities=[ HypothesisEntity( mention="HSP基因家族", canonical_candidate="HSP gene family", entity_type="gene_family", origin="question", confidence=0.9, verification_status="question_direct_unverified", ), HypothesisEntity( mention="植物", canonical_candidate="plants", entity_type="species", origin="question", confidence=0.9, verification_status="question_direct_unverified", ), HypothesisEntity( mention="高温", canonical_candidate="high temperature", entity_type="condition", origin="question", confidence=0.9, verification_status="question_direct_unverified", ), ], relation_intents=["heat stress response", "chaperone function"], answer_slots=["molecular mechanism", "protected target", "biological outcome"], ) def test_mechanism_plan_keeps_question_literal_anchors_and_rejects_wrong_gene_terminal(): unsafe = agent_plan( anchors=[ { "mention": "HSP基因家族", "canonical_name": "HSP gene family", "entity_type": "gene_family", }, { "mention": "植物", "canonical_name": "plants", "entity_type": "species", }, { "mention": "高温", "canonical_name": "high temperature", "entity_type": "condition", }, ], answer_targets=[ { "description": "wrong endpoint", "allowed_entity_types": ["gene_family"], } ], graph_plan={ "enabled": True, "max_hops": 3, "required_terminal_types": ["gene"], }, ) controller = AgenticRetrievalController(FakeLLM(unsafe)) plan = controller.plan("HSP基因家族如何帮助植物应对高温?", hsp_hypothesis()) assert [item["canonical_name"] for item in plan["anchors"]] == [ "HSP gene family", "high temperature", ] allowed = plan["answer_targets"][0]["allowed_entity_types"] assert "process" in allowed assert "protein" in allowed assert "gene" not in allowed assert "gene_family" not in allowed assert set(plan["graph_plan"]["required_terminal_types"]).issubset(set(allowed)) assert plan["graph_plan"]["max_hops"] == 2 assert plan["question_analysis"]["operation"] == "mechanism" def _hsp_evidence(evidence_id, relation, target, evidence): return EvidenceItem( evidence_id=evidence_id, kb="V16/V15", source="HSPs", relation=relation, target=target, evidence=evidence, paper_id="10.1000_hsp", doi="10.1000/hsp", raw={"source_type": "protein", "target_type": "process"}, ) def test_group_review_does_not_apply_hsp_specific_mechanism_vocabulary_gate(): llm = FakeLLM( { "groups": [ { "subquestion_id": "Q0", "status": "SATISFIED", "accepted_evidence_ids": [ "E_UP", "E_TOL", "E_PREVENT", "E_REFOLD", "E_INVENTED", ], "accepted_chain_ids": ["G1", "G_INVENTED"], "missing_answer_slots": [], "refinement": {}, } ] } ) controller = AgenticRetrievalController(llm) evidence = [ _hsp_evidence("E_UP", "upregulated_in", "heat stress", "HSPs were upregulated under heat stress."), _hsp_evidence("E_TOL", "enhance", "heat tolerance", "HSPs enhance heat tolerance."), _hsp_evidence( "E_PREVENT", "prevents", "protein aggregation", "HSPs act as molecular chaperones and prevent protein aggregation.", ), _hsp_evidence( "E_REFOLD", "assist_refolding", "heat-damaged proteins", "HSPs promote refolding of heat-damaged proteins.", ), ] chains = [ { "chain_id": "G1", "hops": [ { "source": "HSPs", "relation": "prevents", "target": "protein aggregation", "evidence": "HSPs prevent protein aggregation.", "doi": "10.1000/hsp", } ], } ] review = controller.review_subquestion_groups( "HSP基因家族如何帮助植物应对高温?", [ { "subquestion_id": "Q0", "question": "HSP基因家族如何帮助植物应对高温?", "answer_slot": "molecular mechanism", "target_entity_types": ["process", "protein", "trait"], "acceptance_requirements": { "requires_structured_assertion": True, "requires_paper_provenance": True, }, "evidence": evidence, "chains": chains, } ], attempt=1, ) group = review["groups"][0] assert group["status"] == "SATISFIED" assert group["accepted_evidence_ids"] == [ "E_UP", "E_TOL", "E_PREVENT", "E_REFOLD", ] assert group["accepted_chain_ids"] == ["G1"] assert "protein-homeostasis mechanisms" not in llm.calls[0][0] def test_group_review_recovers_valid_mechanism_ids_when_llm_describes_support_but_omits_ids(): llm = FakeLLM( { "groups": [ { "subquestion_id": "Q1", "status": "PARTIAL", "accepted_evidence_ids": [], "accepted_chain_ids": [], "missing_answer_slots": [], "review_reason": "Evidence directly states that HSPs prevent aggregation and refold damaged proteins, satisfying the answer slot.", "refinement": {}, } ] } ) controller = AgenticRetrievalController(llm) evidence = [ _hsp_evidence("E_UP", "upregulated_in", "heat stress", "HSPs were upregulated under heat stress."), _hsp_evidence( "E_MECH", "act_as", "molecular chaperones", "HSPs prevent protein aggregation and promote refolding of heat-damaged proteins.", ), ] review = controller.review_subquestion_groups( "HSP基因家族如何帮助植物应对高温?", [ { "subquestion_id": "Q1", "question": "HSP如何通过分子伴侣作用维持蛋白质稳态?", "answer_slot": "molecular mechanism", "acceptance_requirements": { "requires_structured_assertion": True, "requires_paper_provenance": True, }, "evidence": evidence, "chains": [], } ], attempt=1, ) group = review["groups"][0] assert group["accepted_evidence_ids"] == ["E_UP", "E_MECH"] assert group["status"] == "PARTIAL" assert group["selection_source"] == "deterministic_valid_evidence_recovery" def test_group_review_accepts_gst_mechanism_evidence_without_hsp_terms(): llm = FakeLLM( { "groups": [ { "subquestion_id": "Q1", "status": "PARTIAL", "accepted_evidence_ids": [], "accepted_chain_ids": [], "missing_answer_slots": [], "review_reason": ( "Evidence provides direct molecular actions including " "detoxification, peroxide scavenging, and redox regulation." ), "refinement": {}, } ] } ) controller = AgenticRetrievalController(llm) evidence = EvidenceItem( evidence_id="Q1.A1.S1", kb="V16/V15", source="GST gene family", relation="detoxifies", target="reactive electrophiles and peroxides", evidence=( "Plant GSTs catalyze glutathione conjugation and contribute to " "peroxide scavenging and cellular redox homeostasis under stress." ), pmid="28848585", raw={"source_type": "gene_family", "target_type": "biological_process"}, ) review = controller.review_subquestion_groups( "GST基因家族在植物胁迫响应中有什么作用?", [ { "subquestion_id": "Q1", "question": "GST通过哪些分子作用参与植物胁迫响应?", "answer_slot": "molecular mechanism", "anchors": [ {"mention": "GST基因家族", "canonical_name": "GST gene family"} ], "target_entity_types": ["biological_process"], "acceptance_requirements": { "requires_anchor_connection": True, "requires_structured_assertion": True, "requires_paper_provenance": True, }, "evidence": [evidence], "chains": [], } ], attempt=1, ) group = review["groups"][0] assert group["accepted_evidence_ids"] == ["Q1.A1.S1"] assert group["status"] == "PARTIAL" assert group["selection_source"] == "deterministic_valid_evidence_recovery" def test_group_review_recovers_valid_ids_without_matching_review_phrase(): llm = FakeLLM( { "groups": [ { "subquestion_id": "Q0", "status": "PARTIAL", "accepted_evidence_ids": [], "accepted_chain_ids": [], "missing_answer_slots": [], "review_reason": ( "Both direct and chain evidence support the overall role." ), "refinement": {}, } ] } ) controller = AgenticRetrievalController(llm) evidence = EvidenceItem( evidence_id="Q0.A1.S1", kb="V16/V15", source="GST gene family", relation="involved_in", target="plant stress response", evidence="GST genes participate in plant responses to abiotic stress.", pmid="28848585", raw={"source_type": "gene_family", "target_type": "biological_process"}, ) review = controller.review_subquestion_groups( "GST基因家族在植物胁迫响应中有什么作用?", [ { "subquestion_id": "Q0", "question": "GST基因家族在植物胁迫响应中有什么作用?", "answer_slot": "biological role", "anchors": [ {"mention": "GST基因家族", "canonical_name": "GST gene family"} ], "acceptance_requirements": { "requires_anchor_connection": True, "requires_structured_assertion": True, "requires_paper_provenance": True, }, "evidence": [evidence], "chains": [], } ], attempt=1, ) group = review["groups"][0] assert group["accepted_evidence_ids"] == ["Q0.A1.S1"] assert group["status"] == "PARTIAL" assert group["selection_source"] == "deterministic_valid_evidence_recovery" def test_group_review_creates_one_deterministic_refinement_when_no_evidence_or_ids(): llm = FakeLLM( { "groups": [ { "subquestion_id": "Q1", "status": "EMPTY", "accepted_evidence_ids": [], "accepted_chain_ids": [], "missing_answer_slots": [], "review_reason": "No usable evidence was selected.", "refinement": {}, } ] } ) controller = AgenticRetrievalController(llm) group = { "subquestion_id": "Q1", "question": "GST通过哪些分子作用参与植物胁迫响应?", "answer_slot": "molecular mechanism", "anchors": [ {"mention": "GST基因家族", "canonical_name": "GST gene family"} ], "relation_intents": ["detoxifies", "scavenges", "regulates"], "target_entity_types": ["biological_process"], "evidence": [], "chains": [], } first = controller.review_subquestion_groups( "GST基因家族在植物胁迫响应中有什么作用?", [group], attempt=1 )["groups"][0] second = controller.review_subquestion_groups( "GST基因家族在植物胁迫响应中有什么作用?", [group], attempt=2 )["groups"][0] assert first["refinement"]["vector_queries"] assert first["refinement"]["relation_name_hints"] == [ "detoxifies", "scavenges", "regulates", ] assert second["refinement"] == {} def test_group_review_accepts_hsp_subfamily_evidence_for_hsp_family_mechanism_anchor(): controller = AgenticRetrievalController(FakeLLM({"groups": []})) evidence = EvidenceItem( evidence_id="E_SHSP", kb="V16/V15", source="sHSP", relation="helps", target="damaged protein refolding", evidence="sHSPs prevent damaged protein aggregation and help damaged proteins refold.", pmcid="PMC9201338", raw={"source_type": "protein_family", "target_type": "biological_process"}, ) review = controller.review_subquestion_groups( "HSP基因家族如何帮助植物应对高温?", [ { "subquestion_id": "Q1", "question": "HSP通过哪些分子伴侣机制维持蛋白质稳态?", "answer_slot": "molecular mechanism", "anchors": [ { "mention": "HSP基因家族", "canonical_name": "HSP gene family", } ], "target_entity_types": ["biological_process"], "acceptance_requirements": { "requires_anchor_connection": True, "requires_structured_assertion": True, "requires_paper_provenance": True, }, "evidence": [evidence], "chains": [], } ], attempt=1, ) assert review["groups"][0]["accepted_evidence_ids"] == ["E_SHSP"] def test_group_review_rejects_cold_stress_mechanism_for_heat_stress_question(): controller = AgenticRetrievalController(FakeLLM({"groups": []})) cold = _hsp_evidence( "E_COLD", "upregulated_in", "cold environments", "HSPs protect plants from freezing-induced protein denaturation under cold stress.", ) heat = _hsp_evidence( "E_HEAT", "prevents", "protein denaturation", "Under heat stress, HSPs prevent protein denaturation and aggregation.", ) review = controller.review_subquestion_groups( "HSP基因家族如何帮助植物应对高温?", [ { "subquestion_id": "Q1", "question": "HSP应对高温的分子机制是什么?", "answer_slot": "molecular mechanism", "evidence": [cold, heat], "chains": [], } ], attempt=1, ) assert review["groups"][0]["accepted_evidence_ids"] == ["E_HEAT"] def test_group_review_sends_compact_evidence_not_full_raw_payload(): llm = FakeLLM({"groups": []}) controller = AgenticRetrievalController(llm) evidence = _hsp_evidence( "E_COMPACT", "prevents", "protein aggregation", "HSPs prevent protein aggregation.", ) evidence.raw = { "source_type": "protein", "target_type": "process", "large_internal_blob": "DO_NOT_SEND" * 1000, } controller.review_subquestion_groups( "HSP基因家族如何帮助植物应对高温?", [ { "subquestion_id": "Q0", "question": "HSP基因家族如何帮助植物应对高温?", "answer_slot": "mechanism", "evidence": [evidence], "chains": [], } ], attempt=1, ) user_payload = llm.calls[0][1] assert "E_COMPACT" in user_payload assert '"source_type": "protein"' in user_payload assert "DO_NOT_SEND" not in user_payload def test_group_review_rejects_comparison_only_evidence_for_species_enumeration(): llm = FakeLLM( { "groups": [ { "subquestion_id": "Q0", "status": "SATISFIED", "accepted_evidence_ids": ["E_COMPARE"], "accepted_chain_ids": [], "refinement": {}, } ] } ) controller = AgenticRetrievalController(llm) comparison = EvidenceItem( evidence_id="E_COMPARE", kb="V16/V15", source="HcARF genes", relation="number_similar_to", target="Litchi chinensis", evidence="The number of HcARF genes was similar to Litchi chinensis.", pmid="34052884", raw={"source_type": "gene_family", "target_type": "species"}, ) review = controller.review_subquestion_groups( "荔枝中有哪些基因家族已经被研究?", [ { "subquestion_id": "Q0", "question": "荔枝中有哪些基因家族已经被研究?", "answer_slot": "gene family list", "anchors": [ { "mention": "荔枝", "canonical_name": "Litchi chinensis", "entity_type": "species", } ], "target_entity_types": ["gene_family"], "acceptance_requirements": { "requires_anchor_connection": True, "requires_terminal_type_match": True, "requires_structured_assertion": True, "requires_paper_provenance": True, }, "evidence": [comparison], "chains": [], } ], attempt=1, ) assert review["groups"][0]["status"] == "PARTIAL" assert review["groups"][0]["accepted_evidence_ids"] == []