mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-10 17:35:57 +00:00
fix(agents): offload UploadsMiddleware uploads scan off the event loop (#3311)
UploadsMiddleware defines only the sync `before_agent` hook. LangChain wires a sync-only hook as `RunnableCallable(before_agent, None)`, and LangGraph's `ainvoke` runs it directly on the event loop when `afunc is None` — so the per-message uploads-directory scan (`exists`/`iterdir`/`stat` plus reading sibling `.md` outlines) blocks the asyncio event loop on every message that has an uploads directory. Add `abefore_agent` that offloads the scan to a worker thread via `run_in_executor`; it copies the current context, preserving the `user_id` contextvar read by `get_effective_user_id()`. Add a runtime anchor under `tests/blocking_io/` that drives the real `create_agent` graph via `ainvoke` under the strict Blockbuster gate, so a regression back onto the event loop fails CI. Update blocking-IO docs.
This commit is contained in:
@@ -7,6 +7,7 @@ from typing import NotRequired, override
|
||||
from langchain.agents import AgentState
|
||||
from langchain.agents.middleware import AgentMiddleware
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_core.runnables import run_in_executor
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
from deerflow.config.paths import Paths, get_paths
|
||||
@@ -293,3 +294,16 @@ class UploadsMiddleware(AgentMiddleware[UploadsMiddlewareState]):
|
||||
"uploaded_files": new_files,
|
||||
"messages": messages,
|
||||
}
|
||||
|
||||
@override
|
||||
async def abefore_agent(self, state: UploadsMiddlewareState, runtime: Runtime) -> dict | None:
|
||||
"""Async hook that offloads the synchronous uploads scan off the event loop.
|
||||
|
||||
``before_agent`` performs blocking filesystem IO (directory enumeration,
|
||||
``stat``, reading sibling ``.md`` outlines). When the graph runs async,
|
||||
langgraph would otherwise execute the sync hook directly on the event
|
||||
loop, so it is dispatched to a worker thread via ``run_in_executor``.
|
||||
``run_in_executor`` copies the current context, so the ``user_id``
|
||||
contextvar read by ``get_effective_user_id()`` is preserved.
|
||||
"""
|
||||
return await run_in_executor(None, self.before_agent, state, runtime)
|
||||
|
||||
Reference in New Issue
Block a user