mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-26 02:13:29 +08:00
### Summary Agentic search <img width="1149" height="1575" alt="image" src="https://github.com/user-attachments/assets/bce9a3e7-0517-4fb2-80a2-5d2a81a4da78" /> --------- Co-authored-by: Yingfeng Zhang <yingfeng.zhang@gmail.com>
113 lines
3.9 KiB
Python
113 lines
3.9 KiB
Python
"""Medium mode: decompose → parallel search → sufficiency check."""
|
|
|
|
import asyncio
|
|
import logging
|
|
|
|
from rag.advanced_rag.harness.types import ClaimTarget, AgentResult, OrchestratorContext
|
|
from rag.advanced_rag.harness.config import get_mode
|
|
from rag.advanced_rag.harness.sufficiency import (
|
|
cross_check_claim,
|
|
compute_fusion_score,
|
|
route_sufficiency_verdict,
|
|
)
|
|
from rag.advanced_rag.harness.tools.search import hybrid_search
|
|
|
|
_LOG = logging.getLogger(__name__)
|
|
|
|
|
|
async def decompose_and_search(state: dict, tools) -> dict:
|
|
"""Decompose → parallel search → merge → sufficiency check → iterate."""
|
|
question = state.get("question", "")
|
|
keywords = state.get("keywords", "")
|
|
claims_raw = state.get("claims", [])
|
|
mode_label = state.get("route", {}).thinking_mode if state.get("route") else "medium"
|
|
mode = get_mode(mode_label)
|
|
|
|
claims = [ClaimTarget(**c) if isinstance(c, dict) else c for c in claims_raw]
|
|
ctx = OrchestratorContext(question=question, claims=claims, mode=mode_label)
|
|
|
|
for cycle in range(mode.max_orchestrator_cycles):
|
|
ctx.iteration = cycle
|
|
unverified = [c for c in ctx.claims if not c.is_verified]
|
|
if not unverified:
|
|
break
|
|
|
|
# Parallel search on unverified claims
|
|
tasks = []
|
|
for c in unverified:
|
|
tasks.append(hybrid_search(tools, query=c.description, keywords=keywords))
|
|
results = await asyncio.gather(*tasks)
|
|
|
|
for c, result in zip(unverified, results):
|
|
if result.get("chunks"):
|
|
c.is_verified = True
|
|
c.confidence = 0.8
|
|
c.agent_result = AgentResult(
|
|
claim_id=c.claim_id,
|
|
report=_summarize(result),
|
|
is_verified=True,
|
|
confidence=0.8,
|
|
evidence_ids=list(range(len(result.get("chunks", [])))),
|
|
)
|
|
_merge_kbinfos(tools, result)
|
|
else:
|
|
c.agent_result = AgentResult(
|
|
claim_id=c.claim_id,
|
|
report="",
|
|
is_verified=False,
|
|
confidence=0.0,
|
|
)
|
|
|
|
all_chunks = {i: c for i, c in enumerate(tools.kbinfos.get("chunks", []))}
|
|
agent_results = [c.agent_result for c in ctx.claims if c.agent_result]
|
|
cross_results = [cross_check_claim(r, all_chunks) for r in agent_results]
|
|
|
|
verdict = compute_fusion_score(agent_results, cross_results, mode)
|
|
|
|
action, should_continue = route_sufficiency_verdict(
|
|
verdict,
|
|
mode_label,
|
|
cycle,
|
|
mode.max_orchestrator_cycles,
|
|
)
|
|
|
|
if action in ("ANSWER", "ANSWER_PARTIAL"):
|
|
return {
|
|
"verdict": verdict.__dict__,
|
|
"partial_answer": action == "ANSWER_PARTIAL",
|
|
"kbinfos": tools.kbinfos,
|
|
}
|
|
if action == "ABSTAIN":
|
|
tools.kbinfos["chunks"] = []
|
|
return {"verdict": verdict.__dict__, "abstain": True}
|
|
|
|
return {"kbinfos": tools.kbinfos}
|
|
|
|
|
|
def _merge_kbinfos(tools, result: dict):
|
|
if not result or not result.get("chunks"):
|
|
return
|
|
seen = {_chunk_key(c) for c in tools.kbinfos.get("chunks", [])}
|
|
for c in result.get("chunks", []):
|
|
k = _chunk_key(c)
|
|
if k in seen:
|
|
continue
|
|
seen.add(k)
|
|
tools.kbinfos.setdefault("chunks", []).append(c)
|
|
dseen = {d.get("doc_id") for d in tools.kbinfos.get("doc_aggs", [])}
|
|
for d in result.get("doc_aggs", []):
|
|
if d.get("doc_id") in dseen:
|
|
continue
|
|
dseen.add(d.get("doc_id"))
|
|
tools.kbinfos.setdefault("doc_aggs", []).append(d)
|
|
|
|
|
|
def _chunk_key(ck: dict) -> str:
|
|
return ck.get("chunk_id") or ck.get("id") or str(id(ck))
|
|
|
|
|
|
def _summarize(result: dict) -> str:
|
|
chunks = result.get("chunks", [])
|
|
texts = [c.get("content_with_weight", "")[:200] for c in chunks[:3]]
|
|
return " | ".join(texts)
|