mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-24 17:36:47 +08:00
129 lines
5.1 KiB
Python
129 lines
5.1 KiB
Python
"""Pipeline — unified tool execution dispatcher."""
|
|
|
|
import time
|
|
import logging
|
|
from typing import Any
|
|
|
|
from rag.advanced_rag.harness.types import ToolResult
|
|
from rag.advanced_rag.harness.tools.registry import TOOL_REGISTRY
|
|
|
|
_LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class Pipeline:
|
|
"""Unified tool execution layer.
|
|
|
|
- execute(tool_name, **kwargs): dispatch to registered tool, normalize result
|
|
- available_tools(mode_tools): return LLM-visible tool definitions (compilation-filtered)
|
|
- get_chunks(evidence_ids): retrieve raw chunks for sufficiency cross-check
|
|
- trace: execution history for auditing
|
|
"""
|
|
|
|
def __init__(self, rag_tools, compilation_map: dict[str, set[str]] | None = None):
|
|
self.tools = rag_tools
|
|
self.compilation_map = compilation_map or {}
|
|
self.trace: list[dict] = []
|
|
|
|
async def execute(self, tool_name: str, **kwargs) -> ToolResult:
|
|
"""Execute a registered tool by name."""
|
|
tool = TOOL_REGISTRY.get(tool_name)
|
|
if not tool:
|
|
return ToolResult(chunks=[], metadata={}, error=f"Unknown tool: {tool_name}")
|
|
|
|
fn = tool.get("fn")
|
|
if not fn:
|
|
return ToolResult(chunks=[], metadata={}, error=f"Tool {tool_name} has no executor")
|
|
|
|
start = time.time()
|
|
try:
|
|
raw = await fn(self.tools, **kwargs)
|
|
elapsed = time.time() - start
|
|
self.trace.append({"tool": tool_name, "args": kwargs, "elapsed": elapsed, "success": True})
|
|
result = self._normalize(raw)
|
|
# Feed the shared citation pool: agent searches go through the
|
|
# pipeline, so without this their evidence never reaches kbinfos and
|
|
# the final answer has nothing to cite.
|
|
self._merge_into_kbinfos(result)
|
|
return result
|
|
except Exception as e:
|
|
elapsed = time.time() - start
|
|
_LOG.exception("Pipeline.execute(%s) failed", tool_name)
|
|
self.trace.append({"tool": tool_name, "args": kwargs, "elapsed": elapsed, "success": False, "error": str(e)})
|
|
return ToolResult(chunks=[], metadata={}, error=str(e))
|
|
|
|
def available_tools(self, mode_tools: list[str]) -> list[dict]:
|
|
"""Return LLM-visible tool definitions, filtered by compilation availability."""
|
|
names = filter_available_tools(mode_tools, self.compilation_map)
|
|
defs = []
|
|
for name in names:
|
|
tool = TOOL_REGISTRY.get(name)
|
|
if tool and tool.get("function_schema"):
|
|
defs.append(tool["function_schema"])
|
|
return defs
|
|
|
|
def get_chunks(self, evidence_ids: list[int]) -> dict[int, dict]:
|
|
"""Retrieve raw chunks by ID from current kbinfos."""
|
|
result = {}
|
|
chunks = self.tools.kbinfos.get("chunks", [])
|
|
for eid in evidence_ids:
|
|
if 0 <= eid < len(chunks):
|
|
result[eid] = chunks[eid]
|
|
return result
|
|
|
|
def get_trace(self) -> list[dict]:
|
|
return list(self.trace)
|
|
|
|
# ── Private ──
|
|
|
|
def _merge_into_kbinfos(self, result: ToolResult) -> None:
|
|
"""Merge a tool result's chunks/doc_aggs into ``tools.kbinfos``, deduped."""
|
|
if not result or not result.chunks:
|
|
return
|
|
kb = self.tools.kbinfos
|
|
seen = {c.get("chunk_id") or c.get("id") or id(c) for c in kb.get("chunks", [])}
|
|
for c in result.chunks:
|
|
k = c.get("chunk_id") or c.get("id") or id(c)
|
|
if k in seen:
|
|
continue
|
|
seen.add(k)
|
|
kb.setdefault("chunks", []).append(c)
|
|
aggs = result.metadata.get("aggs") if isinstance(result.metadata, dict) else None
|
|
if aggs:
|
|
dseen = {d.get("doc_id") for d in kb.get("doc_aggs", [])}
|
|
for d in aggs:
|
|
if d.get("doc_id") in dseen:
|
|
continue
|
|
dseen.add(d.get("doc_id"))
|
|
kb.setdefault("doc_aggs", []).append(d)
|
|
|
|
@staticmethod
|
|
def _normalize(raw: Any) -> ToolResult:
|
|
if isinstance(raw, ToolResult):
|
|
return raw
|
|
if isinstance(raw, dict):
|
|
return ToolResult(
|
|
chunks=raw.get("chunks", []),
|
|
metadata={"aggs": raw.get("doc_aggs", []), "answer": raw.get("answer", "")},
|
|
)
|
|
if isinstance(raw, list):
|
|
return ToolResult(chunks=raw, metadata={})
|
|
return ToolResult(chunks=[], metadata={"raw": str(raw)})
|
|
|
|
|
|
def filter_available_tools(tool_names: list[str], compilation_map: dict[str, set[str]]) -> list[str]:
|
|
"""Filter tool list by compilation artifact availability."""
|
|
available = []
|
|
for name in tool_names:
|
|
tool = TOOL_REGISTRY.get(name)
|
|
if not tool:
|
|
continue
|
|
if tool.get("requires_compilation"):
|
|
comp_type = tool.get("compilation_type")
|
|
# ``compilation_type`` may name one artifact or several (a tool that
|
|
# reads either one is available when ANY of them is compiled).
|
|
wanted = {comp_type} if isinstance(comp_type, str) else set(comp_type or ())
|
|
if wanted and not any(wanted & comps for comps in compilation_map.values()):
|
|
continue
|
|
available.append(name)
|
|
return available
|