From 510e1197c6261f9d97c12ee115bc7d38a4ca33a1 Mon Sep 17 00:00:00 2001 From: Kevin Hu Date: Wed, 5 Aug 2026 14:00:45 +0800 Subject: [PATCH] Fix: graph explore bug. (#17829) ### Summary Fix: graph explore bug. --- .../harness/orchestrator/agentic.py | 28 +++++++++++++++++++ rag/advanced_rag/harness/types.py | 10 +++++++ rag/llm/chat_model.py | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/rag/advanced_rag/harness/orchestrator/agentic.py b/rag/advanced_rag/harness/orchestrator/agentic.py index 212cbc110c..f857435629 100644 --- a/rag/advanced_rag/harness/orchestrator/agentic.py +++ b/rag/advanced_rag/harness/orchestrator/agentic.py @@ -26,6 +26,30 @@ def _snip(text: str, limit: int = 160) -> str: return text if len(text) <= limit else text[: limit - 3] + "..." +def _discovered_entity(tools) -> str | None: + """Pick a salient discovered name from the gathered evidence. + + Prefers an explicit entity/keyword tag on a chunk, falling back to a source + document name. Used only to gate ``graph_explore`` eligibility (its context + check needs ``context.last_entity``), so a coarse signal is enough. + """ + chunks = (getattr(tools, "kbinfos", {}) or {}).get("chunks", []) or [] + for c in chunks: + for key in ("entities_kwd", "important_kwd"): + val = c.get(key) + if isinstance(val, list) and val: + first = str(val[0]).strip() + if first: + return first + if isinstance(val, str) and val.strip(): + return val.strip().split()[0] + for c in chunks: + name = str(c.get("docnm_kwd") or "").strip() + if name: + return name + return None + + async def agentic_research(state: dict, tools) -> dict: """Two-level loop for high/ultra modes.""" question = state.get("question", "") @@ -93,6 +117,10 @@ async def agentic_research(state: dict, tools) -> dict: ) _LOG.info('[Agentic research] Found a new angle worth researching: "%s"', dc) + # ── Step A.5: note a discovered entity so graph_explore becomes eligible + # in the next round (its context gate requires context.last_entity). ── + ctx.note_entity(_discovered_entity(tools)) + # ── Step B: Sufficiency Check ── all_chunks = {i: c for i, c in enumerate(tools.kbinfos.get("chunks", []))} agent_results_list = [c.agent_result for c in ctx.claims if c.agent_result] diff --git a/rag/advanced_rag/harness/types.py b/rag/advanced_rag/harness/types.py index 564e9f9fe7..80e5d47715 100644 --- a/rag/advanced_rag/harness/types.py +++ b/rag/advanced_rag/harness/types.py @@ -153,6 +153,16 @@ class OrchestratorContext: def last_entity(self) -> str | None: return self._last_entity + def note_entity(self, name: str | None) -> None: + """Record the most recently discovered entity/document name. + + Gates ``graph_explore`` in ``tool_fits_context``: the tool is only + offered once research has surfaced something to expand from. Ignores + empty values so a fruitless round can't clear a prior discovery. + """ + if isinstance(name, str) and name.strip(): + self._last_entity = name.strip() + @property def current_claim(self) -> str | None: unverified = [c for c in self.claims if not c.is_verified] diff --git a/rag/llm/chat_model.py b/rag/llm/chat_model.py index 69e431d68a..d34d679d1a 100644 --- a/rag/llm/chat_model.py +++ b/rag/llm/chat_model.py @@ -1999,7 +1999,7 @@ class LiteLLMBase(ABC): content = json.dumps(result, ensure_ascii=False) else: content = str(result) - hist.append({"role": "tool", "tool_call_id": tc.id, "content": content}) + hist.append({"role": "tool", "tool_call_id": tc.id, "content": content.replace("", "")}) return hist def bind_tools(self, toolcall_session=None, tools=None):