mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-10 17:35:57 +00:00
feat(subagents): extend deferred MCP tool loading to subagents (#3432)
* feat(subagents): extend deferred MCP tool loading to subagents (#3341) Subagents now reuse the lead agent's deferred-tool path: when tool_search.enabled, MCP tool schemas are withheld from the model and surfaced by name in <available-deferred-tools>, fetched on demand via the generated tool_search helper. DeferredToolFilterMiddleware deterministically rewrites request.tools to hide the deferred schemas (the prompt section is discovery only, not enforcement). Consolidates the assembly into deerflow.tools.builtins.tool_search, now the single home for both assemble_deferred_tools (centralized fail-closed guard, replacing the lead-only private _assemble_deferred) and the relocated get_deferred_tools_prompt_section. Shared by every build path: lead agent, embedded client, and subagent executor. tool_search is appended after the subagent's name-level tool policy and is treated as infrastructure: its catalog is built from the already policy-filtered list, so it can never surface a tool the policy denied. Follow-up to #3370. Fixes #3341. * test(subagents): assert the real middleware builder emits a working deferred filter (#3341) The existing recipe test hand-constructs DeferredToolFilterMiddleware, so it cannot catch a regression in how build_subagent_runtime_middlewares (the call executor._create_agent actually makes) wires the deferred setup into the filter. Add a test that sources the filter from the real builder given a real setup and runs it through a graph: a wrong catalog hash would silently stop promotion, a dropped filter would stop hiding — both now caught. Running the full real middleware stack is intentionally avoided (the other runtime middlewares need sandbox/thread infra to execute, which would make the test flaky); their attachment + ordering before Safety stays locked in test_tool_error_handling_middleware.py. * test(subagents): keep executor tests config-free in CI * chore: trigger ci * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -253,3 +253,45 @@ def test_subagent_runtime_middlewares_skip_view_image_for_text_model(monkeypatch
|
||||
middlewares = build_subagent_runtime_middlewares(app_config=app_config, model_name="test-model")
|
||||
|
||||
assert not any(isinstance(middleware, ViewImageMiddleware) for middleware in middlewares)
|
||||
|
||||
|
||||
def test_subagent_runtime_middlewares_attach_deferred_filter_when_setup_has_names(monkeypatch):
|
||||
"""A subagent built with deferred MCP tools gets DeferredToolFilterMiddleware, positioned before SafetyFinishReasonMiddleware (mirrors the lead ordering)."""
|
||||
from langchain_core.tools import tool as as_tool
|
||||
|
||||
from deerflow.agents.middlewares.deferred_tool_filter_middleware import DeferredToolFilterMiddleware
|
||||
from deerflow.agents.middlewares.safety_finish_reason_middleware import SafetyFinishReasonMiddleware
|
||||
from deerflow.tools.builtins.tool_search import build_deferred_tool_setup
|
||||
from deerflow.tools.mcp_metadata import tag_mcp_tool
|
||||
|
||||
app_config = _make_app_config()
|
||||
_stub_runtime_middleware_imports(monkeypatch)
|
||||
|
||||
@as_tool
|
||||
def mcp_thing(x: str) -> str:
|
||||
"deferred mcp tool"
|
||||
return x
|
||||
|
||||
setup = build_deferred_tool_setup([tag_mcp_tool(mcp_thing)], enabled=True)
|
||||
assert setup.deferred_names # sanity: populated setup
|
||||
|
||||
middlewares = build_subagent_runtime_middlewares(app_config=app_config, deferred_setup=setup)
|
||||
|
||||
filters = [m for m in middlewares if isinstance(m, DeferredToolFilterMiddleware)]
|
||||
assert len(filters) == 1
|
||||
filter_idx = next(i for i, m in enumerate(middlewares) if isinstance(m, DeferredToolFilterMiddleware))
|
||||
safety_idx = next(i for i, m in enumerate(middlewares) if isinstance(m, SafetyFinishReasonMiddleware))
|
||||
assert filter_idx < safety_idx
|
||||
|
||||
|
||||
def test_subagent_runtime_middlewares_skip_deferred_filter_without_names(monkeypatch):
|
||||
"""No deferred setup (disabled / no MCP tool) -> no DeferredToolFilterMiddleware."""
|
||||
from deerflow.agents.middlewares.deferred_tool_filter_middleware import DeferredToolFilterMiddleware
|
||||
from deerflow.tools.builtins.tool_search import DeferredToolSetup
|
||||
|
||||
app_config = _make_app_config()
|
||||
_stub_runtime_middleware_imports(monkeypatch)
|
||||
|
||||
for setup in (None, DeferredToolSetup(None, frozenset(), None)):
|
||||
middlewares = build_subagent_runtime_middlewares(app_config=app_config, deferred_setup=setup)
|
||||
assert not any(isinstance(m, DeferredToolFilterMiddleware) for m in middlewares)
|
||||
|
||||
Reference in New Issue
Block a user