refactor(backend): consolidate thread_id resolution into shared get_thread_id() utility (#2522)

Extract duplicated thread_id fallback logic from 11 files into a single
  deerflow.utils.runtime.get_thread_id() function with a documented 3-level
  cascade (runtime.context → runtime.config → get_config()).

  The module docstring also clarifies the __pregel_runtime injection pattern used in
  gateway mode.
This commit is contained in:
Willem Jiang
2026-04-26 10:52:37 +08:00
parent 9dc25987e0
commit a55de566b9
14 changed files with 185 additions and 84 deletions
@@ -3,33 +3,16 @@ from typing import Annotated
from langchain.tools import InjectedToolCallId, ToolRuntime, tool
from langchain_core.messages import ToolMessage
from langgraph.config import get_config
from langgraph.types import Command
from langgraph.typing import ContextT
from deerflow.agents.thread_state import ThreadState
from deerflow.config.paths import VIRTUAL_PATH_PREFIX, get_paths
from deerflow.utils.runtime import get_thread_id
OUTPUTS_VIRTUAL_PREFIX = f"{VIRTUAL_PATH_PREFIX}/outputs"
def _get_thread_id(runtime: ToolRuntime[ContextT, ThreadState]) -> str | None:
"""Resolve the current thread id from runtime context or RunnableConfig."""
thread_id = runtime.context.get("thread_id") if runtime.context else None
if thread_id:
return thread_id
runtime_config = getattr(runtime, "config", None) or {}
thread_id = runtime_config.get("configurable", {}).get("thread_id")
if thread_id:
return thread_id
try:
return get_config().get("configurable", {}).get("thread_id")
except RuntimeError:
return None
def _normalize_presented_filepath(
runtime: ToolRuntime[ContextT, ThreadState],
filepath: str,
@@ -51,7 +34,7 @@ def _normalize_presented_filepath(
if runtime.state is None:
raise ValueError("Thread runtime state is not available")
thread_id = _get_thread_id(runtime)
thread_id = get_thread_id(runtime)
if not thread_id:
raise ValueError("Thread ID is not available in runtime context or runtime config")
@@ -14,6 +14,7 @@ from deerflow.agents.thread_state import ThreadState
from deerflow.sandbox.security import LOCAL_BASH_SUBAGENT_DISABLED_MESSAGE, is_host_bash_allowed
from deerflow.subagents import SubagentExecutor, get_available_subagent_names, get_subagent_config
from deerflow.subagents.executor import SubagentStatus, cleanup_background_task, get_background_task_result, request_cancel_background_task
from deerflow.utils.runtime import get_thread_id
logger = logging.getLogger(__name__)
@@ -105,9 +106,7 @@ async def task_tool(
if runtime is not None:
sandbox_state = runtime.state.get("sandbox")
thread_data = runtime.state.get("thread_data")
thread_id = runtime.context.get("thread_id") if runtime.context else None
if thread_id is None:
thread_id = runtime.config.get("configurable", {}).get("thread_id")
thread_id = get_thread_id(runtime)
# Try to get parent model from configurable
metadata = runtime.config.get("metadata", {})