From 0fb18e368c21d64bff48c4fc144aedac7a6743d8 Mon Sep 17 00:00:00 2001 From: AochenShen99 Date: Tue, 9 Jun 2026 11:56:28 +0800 Subject: [PATCH] refactor(lead-agent): make build_middlewares public to drop the last cross-module private import (#3458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `client.py` imported the private `_build_middlewares` from `agent.py` across a module boundary and called it as public API. Because the `_` name signals "module-private, no external callers", any future rename or signature change silently breaks the embedded `DeerFlowClient` path — and the test suite even monkeypatched `deerflow.client._build_middlewares`, baking the leak in. `DeerFlowClient` is a lead-agent variant that genuinely needs the lead agent's full middleware composition, so make the dependency honest: promote the helper to a documented public entry point `build_middlewares` and update every in-repo caller. Found during #3341 review; #3341 already removed one such leak (`_assemble_deferred` -> public `assemble_deferred_tools`) and left this one out of scope on purpose. - agent.py: rename def + both internal call sites; expand the docstring into a public-entry-point contract and document the previously-undocumented model_name / app_config / deferred_setup params - client.py: import + call site now use the public name (removes the last cross-module private import) - scripts/tool-error-degradation-detection.sh: update its import + call site - tests (5 files): update monkeypatch/patch targets and direct calls - docs (backend/CLAUDE.md, plan_mode_usage.md, middlewares.mdx): sync the live references that describe the symbol as current API Pure mechanical rename, no behavior change. Historical design docs (rfc, superpowers spec) intentionally keep the old name as point-in-time records. Closes #3431 --- backend/CLAUDE.md | 4 ++-- backend/docs/plan_mode_usage.md | 8 ++++---- .../deerflow/agents/lead_agent/agent.py | 17 +++++++++++++---- backend/packages/harness/deerflow/client.py | 4 ++-- backend/tests/test_checkpointer.py | 4 ++-- backend/tests/test_client.py | 14 +++++++------- backend/tests/test_client_e2e.py | 4 ++-- .../tests/test_lead_agent_model_resolution.py | 18 +++++++++--------- backend/tests/test_lead_agent_skills.py | 8 ++++---- .../src/content/en/harness/middlewares.mdx | 2 +- scripts/tool-error-degradation-detection.sh | 4 ++-- 11 files changed, 48 insertions(+), 39 deletions(-) diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index 903d86e28..29a776217 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -192,7 +192,7 @@ from deerflow.config import get_app_config ### Middleware Chain -Lead-agent middlewares are assembled in strict append order across `packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py` (`build_lead_runtime_middlewares`) and `packages/harness/deerflow/agents/lead_agent/agent.py` (`_build_middlewares`): +Lead-agent middlewares are assembled in strict append order across `packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py` (`build_lead_runtime_middlewares`) and `packages/harness/deerflow/agents/lead_agent/agent.py` (`build_middlewares`): 1. **ThreadDataMiddleware** - Creates per-thread directories under the user's isolation scope (`backend/.deer-flow/users/{user_id}/threads/{thread_id}/user-data/{workspace,uploads,outputs}`); resolves `user_id` via `get_effective_user_id()` (falls back to `"default"` in no-auth mode); Web UI thread deletion now follows LangGraph thread removal with Gateway cleanup of the local thread directory 2. **UploadsMiddleware** - Tracks and injects newly uploaded files into conversation @@ -493,7 +493,7 @@ Both can be modified at runtime via Gateway API endpoints or `DeerFlowClient` me - `"messages-tuple"` — per-chunk update: for AI text this is a **delta** (concat per `id` to rebuild the full message); tool calls and tool results are emitted once each - `"custom"` — forwarded from `StreamWriter` - `"end"` — stream finished (carries cumulative `usage` counted once per message id) -- Agent created lazily via `create_agent()` + `_build_middlewares()`, same as `make_lead_agent` +- Agent created lazily via `create_agent()` + `build_middlewares()`, same as `make_lead_agent` - Supports `checkpointer` parameter for state persistence across turns - `reset_agent()` forces agent recreation (e.g. after memory or skill changes) - See [docs/STREAMING.md](docs/STREAMING.md) for the full design: why Gateway and DeerFlowClient are parallel paths, LangGraph's `stream_mode` semantics, the per-id dedup invariants, and regression testing strategy diff --git a/backend/docs/plan_mode_usage.md b/backend/docs/plan_mode_usage.md index 369f1bd4d..8caf30c93 100644 --- a/backend/docs/plan_mode_usage.md +++ b/backend/docs/plan_mode_usage.md @@ -127,8 +127,8 @@ complex_agent = create_agent_for_task("high") ## How It Works 1. When `make_lead_agent(config)` is called, it extracts `is_plan_mode` from `config.configurable` -2. The config is passed to `_build_middlewares(config)` -3. `_build_middlewares()` reads `is_plan_mode` and calls `_create_todo_list_middleware(is_plan_mode)` +2. The config is passed to `build_middlewares(config)` +3. `build_middlewares()` reads `is_plan_mode` and calls `_create_todo_list_middleware(is_plan_mode)` 4. If `is_plan_mode=True`, a `TodoListMiddleware` instance is created and added to the middleware chain 5. The middleware automatically adds a `write_todos` tool to the agent's toolset 6. The agent can use this tool to manage tasks during execution @@ -141,7 +141,7 @@ make_lead_agent(config) │ ├─> Extracts: is_plan_mode = config.configurable.get("is_plan_mode", False) │ - └─> _build_middlewares(config) + └─> build_middlewares(config) │ ├─> ThreadDataMiddleware ├─> SandboxMiddleware @@ -156,7 +156,7 @@ make_lead_agent(config) ### Agent Module - **Location**: `packages/harness/deerflow/agents/lead_agent/agent.py` - **Function**: `_create_todo_list_middleware(is_plan_mode: bool)` - Creates TodoListMiddleware if plan mode is enabled -- **Function**: `_build_middlewares(config: RunnableConfig)` - Builds middleware chain based on runtime config +- **Function**: `build_middlewares(config: RunnableConfig)` - Builds middleware chain based on runtime config - **Function**: `make_lead_agent(config: RunnableConfig)` - Creates agent with appropriate middlewares ### Runtime Configuration diff --git a/backend/packages/harness/deerflow/agents/lead_agent/agent.py b/backend/packages/harness/deerflow/agents/lead_agent/agent.py index 39110424c..2d87799c7 100644 --- a/backend/packages/harness/deerflow/agents/lead_agent/agent.py +++ b/backend/packages/harness/deerflow/agents/lead_agent/agent.py @@ -265,7 +265,7 @@ Being proactive with task management demonstrates thoroughness and ensures all r # ViewImageMiddleware should be before ClarificationMiddleware to inject image details before LLM # ToolErrorHandlingMiddleware should be before ClarificationMiddleware to convert tool exceptions to ToolMessages # ClarificationMiddleware should be last to intercept clarification requests after model calls -def _build_middlewares( +def build_middlewares( config: RunnableConfig, model_name: str | None, agent_name: str | None = None, @@ -274,12 +274,21 @@ def _build_middlewares( app_config: AppConfig | None = None, deferred_setup=None, ): - """Build middleware chain based on runtime configuration. + """Build the lead-agent middleware chain based on runtime configuration. + + Public entry point for the lead agent's full middleware composition. Used by + ``make_lead_agent`` and by the embedded ``DeerFlowClient`` (a lead-agent variant + that needs the identical chain). Keep this name stable: it is imported across a + module boundary, so renames/signature changes ripple into ``client.py``. Args: config: Runtime configuration containing configurable options like is_plan_mode. + model_name: Resolved runtime model name; gates vision-only middleware. agent_name: If provided, MemoryMiddleware will use per-agent memory storage. custom_middlewares: Optional list of custom middlewares to inject into the chain. + app_config: Explicit AppConfig; falls back to ``get_app_config()`` when omitted. + deferred_setup: Optional deferred-MCP-tool setup that attaches + ``DeferredToolFilterMiddleware`` when ``tool_search`` is enabled. Returns: List of middleware instances. @@ -472,7 +481,7 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig): return create_agent( model=create_chat_model(name=model_name, thinking_enabled=thinking_enabled, app_config=resolved_app_config, attach_tracing=False), tools=final_tools, - middleware=_build_middlewares(config, model_name=model_name, app_config=resolved_app_config, deferred_setup=setup), + middleware=build_middlewares(config, model_name=model_name, app_config=resolved_app_config, deferred_setup=setup), system_prompt=apply_prompt_template( subagent_enabled=subagent_enabled, max_concurrent_subagents=max_concurrent_subagents, @@ -493,7 +502,7 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig): return create_agent( model=create_chat_model(name=model_name, thinking_enabled=thinking_enabled, reasoning_effort=reasoning_effort, app_config=resolved_app_config, attach_tracing=False), tools=final_tools, - middleware=_build_middlewares(config, model_name=model_name, agent_name=agent_name, app_config=resolved_app_config, deferred_setup=setup), + middleware=build_middlewares(config, model_name=model_name, agent_name=agent_name, app_config=resolved_app_config, deferred_setup=setup), system_prompt=apply_prompt_template( subagent_enabled=subagent_enabled, max_concurrent_subagents=max_concurrent_subagents, diff --git a/backend/packages/harness/deerflow/client.py b/backend/packages/harness/deerflow/client.py index b9c09a5c6..567338350 100644 --- a/backend/packages/harness/deerflow/client.py +++ b/backend/packages/harness/deerflow/client.py @@ -33,7 +33,7 @@ from langchain.agents.middleware import AgentMiddleware from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage from langchain_core.runnables import RunnableConfig -from deerflow.agents.lead_agent.agent import _build_middlewares +from deerflow.agents.lead_agent.agent import build_middlewares from deerflow.agents.lead_agent.prompt import apply_prompt_template from deerflow.agents.thread_state import ThreadState from deerflow.config.agents_config import AGENT_NAME_PATTERN @@ -247,7 +247,7 @@ class DeerFlowClient: # Attaching them again on the model would emit duplicate spans. "model": create_chat_model(name=model_name, thinking_enabled=thinking_enabled, attach_tracing=False), "tools": final_tools, - "middleware": _build_middlewares(config, model_name=model_name, agent_name=self._agent_name, custom_middlewares=self._middlewares, deferred_setup=deferred_setup), + "middleware": build_middlewares(config, model_name=model_name, agent_name=self._agent_name, custom_middlewares=self._middlewares, deferred_setup=deferred_setup), "system_prompt": apply_prompt_template( subagent_enabled=subagent_enabled, max_concurrent_subagents=max_concurrent_subagents, diff --git a/backend/tests/test_checkpointer.py b/backend/tests/test_checkpointer.py index e86be8644..a9ac227ce 100644 --- a/backend/tests/test_checkpointer.py +++ b/backend/tests/test_checkpointer.py @@ -747,7 +747,7 @@ class TestClientCheckpointerFallback: patch("deerflow.client.get_app_config", return_value=config_mock), patch("deerflow.client.create_agent", side_effect=fake_create_agent), patch("deerflow.client.create_chat_model", return_value=MagicMock()), - patch("deerflow.client._build_middlewares", return_value=[]), + patch("deerflow.client.build_middlewares", return_value=[]), patch("deerflow.client.apply_prompt_template", return_value=""), patch("deerflow.client.DeerFlowClient._get_tools", return_value=[]), ): @@ -781,7 +781,7 @@ class TestClientCheckpointerFallback: patch("deerflow.client.get_app_config", return_value=config_mock), patch("deerflow.client.create_agent", side_effect=fake_create_agent), patch("deerflow.client.create_chat_model", return_value=MagicMock()), - patch("deerflow.client._build_middlewares", return_value=[]), + patch("deerflow.client.build_middlewares", return_value=[]), patch("deerflow.client.apply_prompt_template", return_value=""), patch("deerflow.client.DeerFlowClient._get_tools", return_value=[]), ): diff --git a/backend/tests/test_client.py b/backend/tests/test_client.py index d9483900c..6c15c04e7 100644 --- a/backend/tests/test_client.py +++ b/backend/tests/test_client.py @@ -910,7 +910,7 @@ class TestEnsureAgent: with ( patch("deerflow.client.create_chat_model"), patch("deerflow.client.create_agent", return_value=mock_agent), - patch("deerflow.client._build_middlewares", return_value=[]) as mock_build_middlewares, + patch("deerflow.client.build_middlewares", return_value=[]) as mock_build_middlewares, patch("deerflow.client.apply_prompt_template", return_value="prompt") as mock_apply_prompt, patch.object(client, "_get_tools", return_value=[]), patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()), @@ -935,7 +935,7 @@ class TestEnsureAgent: with ( patch("deerflow.client.create_chat_model"), patch("deerflow.client.create_agent", return_value=mock_agent) as mock_create_agent, - patch("deerflow.client._build_middlewares", return_value=[]), + patch("deerflow.client.build_middlewares", return_value=[]), patch("deerflow.client.apply_prompt_template", return_value="prompt"), patch.object(client, "_get_tools", return_value=[]), patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=mock_checkpointer), @@ -960,7 +960,7 @@ class TestEnsureAgent: with ( patch("deerflow.client.create_chat_model"), patch("deerflow.client.create_agent", return_value=mock_agent) as mock_create_agent, - patch("deerflow.client._build_middlewares", side_effect=fake_build_middlewares), + patch("deerflow.client.build_middlewares", side_effect=fake_build_middlewares), patch("deerflow.client.apply_prompt_template", return_value="prompt"), patch.object(client, "_get_tools", return_value=[]), patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()), @@ -979,7 +979,7 @@ class TestEnsureAgent: with ( patch("deerflow.client.create_chat_model"), patch("deerflow.client.create_agent", return_value=mock_agent) as mock_create_agent, - patch("deerflow.client._build_middlewares", return_value=[]), + patch("deerflow.client.build_middlewares", return_value=[]), patch("deerflow.client.apply_prompt_template", return_value="prompt"), patch.object(client, "_get_tools", return_value=[]), patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=None), @@ -1957,7 +1957,7 @@ class TestScenarioAgentRecreation: with ( patch("deerflow.client.create_chat_model"), patch("deerflow.client.create_agent", side_effect=fake_create_agent), - patch("deerflow.client._build_middlewares", return_value=[]), + patch("deerflow.client.build_middlewares", return_value=[]), patch("deerflow.client.apply_prompt_template", return_value="prompt"), patch.object(client, "_get_tools", return_value=[]), patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()), @@ -1985,7 +1985,7 @@ class TestScenarioAgentRecreation: with ( patch("deerflow.client.create_chat_model"), patch("deerflow.client.create_agent", side_effect=fake_create_agent), - patch("deerflow.client._build_middlewares", return_value=[]), + patch("deerflow.client.build_middlewares", return_value=[]), patch("deerflow.client.apply_prompt_template", return_value="prompt"), patch.object(client, "_get_tools", return_value=[]), patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()), @@ -2010,7 +2010,7 @@ class TestScenarioAgentRecreation: with ( patch("deerflow.client.create_chat_model"), patch("deerflow.client.create_agent", side_effect=fake_create_agent), - patch("deerflow.client._build_middlewares", return_value=[]), + patch("deerflow.client.build_middlewares", return_value=[]), patch("deerflow.client.apply_prompt_template", return_value="prompt"), patch.object(client, "_get_tools", return_value=[]), patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()), diff --git a/backend/tests/test_client_e2e.py b/backend/tests/test_client_e2e.py index 4b6a62ea9..5c2b6cd9b 100644 --- a/backend/tests/test_client_e2e.py +++ b/backend/tests/test_client_e2e.py @@ -144,14 +144,14 @@ def e2e_env(tmp_path, monkeypatch): # non-determinism and cost to E2E tests (title generation is already # disabled via TitleConfig above, but the middleware still participates # in the chain and can interfere with event ordering). - from deerflow.agents.lead_agent.agent import _build_middlewares as _original_build_middlewares + from deerflow.agents.lead_agent.agent import build_middlewares as _original_build_middlewares from deerflow.agents.middlewares.title_middleware import TitleMiddleware def _sync_safe_build_middlewares(*args, **kwargs): mws = _original_build_middlewares(*args, **kwargs) return [m for m in mws if not isinstance(m, TitleMiddleware)] - monkeypatch.setattr("deerflow.client._build_middlewares", _sync_safe_build_middlewares) + monkeypatch.setattr("deerflow.client.build_middlewares", _sync_safe_build_middlewares) return {"tmp_path": tmp_path} diff --git a/backend/tests/test_lead_agent_model_resolution.py b/backend/tests/test_lead_agent_model_resolution.py index 0522b9ae1..2ced54408 100644 --- a/backend/tests/test_lead_agent_model_resolution.py +++ b/backend/tests/test_lead_agent_model_resolution.py @@ -56,7 +56,7 @@ def test_make_lead_agent_attaches_tracing_callbacks_at_graph_root(monkeypatch): monkeypatch.setattr(lead_agent_module, "get_app_config", lambda: app_config) monkeypatch.setattr(tools_module, "get_available_tools", lambda **kwargs: []) - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) sentinel_handler = object() monkeypatch.setattr(lead_agent_module, "build_tracing_callbacks", lambda: [sentinel_handler]) @@ -94,7 +94,7 @@ def test_internal_make_lead_agent_uses_explicit_app_config(monkeypatch): monkeypatch.setattr(lead_agent_module, "get_app_config", _raise_get_app_config) monkeypatch.setattr(tools_module, "get_available_tools", lambda **kwargs: []) - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) captured: dict[str, object] = {} @@ -128,7 +128,7 @@ def test_make_lead_agent_uses_runtime_app_config_from_context_without_global_rea monkeypatch.setattr(lead_agent_module, "get_app_config", _raise_get_app_config) monkeypatch.setattr(tools_module, "get_available_tools", lambda **kwargs: []) - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) captured: dict[str, object] = {} @@ -207,7 +207,7 @@ def test_make_lead_agent_disables_thinking_when_model_does_not_support_it(monkey monkeypatch.setattr(lead_agent_module, "get_app_config", lambda: app_config) monkeypatch.setattr(tools_module, "get_available_tools", lambda **kwargs: []) - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) captured: dict[str, object] = {} @@ -251,7 +251,7 @@ def test_make_lead_agent_reads_runtime_options_from_context(monkeypatch): get_available_tools = MagicMock(return_value=[]) monkeypatch.setattr(lead_agent_module, "get_app_config", lambda: app_config) monkeypatch.setattr(tools_module, "get_available_tools", get_available_tools) - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda config, model_name, agent_name=None, **kwargs: []) captured: dict[str, object] = {} @@ -328,7 +328,7 @@ def test_build_middlewares_uses_resolved_model_name_for_vision(monkeypatch): monkeypatch.setattr(lead_agent_module, "_create_summarization_middleware", lambda **kwargs: None) monkeypatch.setattr(lead_agent_module, "_create_todo_list_middleware", lambda is_plan_mode: None) - middlewares = lead_agent_module._build_middlewares( + middlewares = lead_agent_module.build_middlewares( {"configurable": {"model_name": "stale-model", "is_plan_mode": False, "subagent_enabled": False}}, model_name="vision-model", custom_middlewares=[MagicMock()], @@ -374,7 +374,7 @@ def test_build_middlewares_passes_explicit_app_config_to_shared_factory(monkeypa lambda agent_name=None, *, memory_config: captured.setdefault("memory_config", memory_config) or "memory-middleware", ) - middlewares = lead_agent_module._build_middlewares( + middlewares = lead_agent_module.build_middlewares( {"configurable": {"is_plan_mode": False, "subagent_enabled": False}}, model_name="safe-model", app_config=app_config, @@ -407,7 +407,7 @@ def test_build_middlewares_uses_loop_detection_config(monkeypatch): monkeypatch.setattr(lead_agent_module, "_create_summarization_middleware", lambda *, app_config=None: None) monkeypatch.setattr(lead_agent_module, "_create_todo_list_middleware", lambda is_plan_mode: None) - middlewares = lead_agent_module._build_middlewares( + middlewares = lead_agent_module.build_middlewares( {"configurable": {"is_plan_mode": False, "subagent_enabled": False}}, model_name="safe-model", app_config=app_config, @@ -433,7 +433,7 @@ def test_build_middlewares_omits_loop_detection_when_disabled(monkeypatch): monkeypatch.setattr(lead_agent_module, "_create_summarization_middleware", lambda *, app_config=None: None) monkeypatch.setattr(lead_agent_module, "_create_todo_list_middleware", lambda is_plan_mode: None) - middlewares = lead_agent_module._build_middlewares( + middlewares = lead_agent_module.build_middlewares( {"configurable": {"is_plan_mode": False, "subagent_enabled": False}}, model_name="safe-model", app_config=app_config, diff --git a/backend/tests/test_lead_agent_skills.py b/backend/tests/test_lead_agent_skills.py index 8bece986d..2f625857f 100644 --- a/backend/tests/test_lead_agent_skills.py +++ b/backend/tests/test_lead_agent_skills.py @@ -139,7 +139,7 @@ def test_make_lead_agent_empty_skills_passed_correctly(monkeypatch): monkeypatch.setattr(lead_agent_module, "create_chat_model", lambda **kwargs: "model") monkeypatch.setattr("deerflow.tools.get_available_tools", lambda **kwargs: []) monkeypatch.setattr(lead_agent_module, "_load_enabled_skills_for_tool_policy", lambda available_skills, *, app_config: []) - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda *args, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda *args, **kwargs: []) monkeypatch.setattr(lead_agent_module, "create_agent", lambda **kwargs: kwargs) class MockModelConfig: @@ -180,7 +180,7 @@ def test_make_lead_agent_filters_tools_from_available_skills(monkeypatch): monkeypatch.setattr(lead_agent_module, "_resolve_model_name", lambda x=None, **kwargs: "default-model") monkeypatch.setattr(lead_agent_module, "create_chat_model", lambda **kwargs: "model") - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda *args, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda *args, **kwargs: []) monkeypatch.setattr(lead_agent_module, "apply_prompt_template", lambda **kwargs: "mock_prompt") monkeypatch.setattr(lead_agent_module, "create_agent", lambda **kwargs: kwargs) monkeypatch.setattr(lead_agent_module, "load_agent_config", lambda x: AgentConfig(name="test", skills=["restricted", "legacy"])) @@ -203,7 +203,7 @@ def test_make_lead_agent_all_legacy_skills_preserve_all_tools(monkeypatch): monkeypatch.setattr(lead_agent_module, "_resolve_model_name", lambda x=None, **kwargs: "default-model") monkeypatch.setattr(lead_agent_module, "create_chat_model", lambda **kwargs: "model") - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda *args, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda *args, **kwargs: []) monkeypatch.setattr(lead_agent_module, "apply_prompt_template", lambda **kwargs: "mock_prompt") monkeypatch.setattr(lead_agent_module, "create_agent", lambda **kwargs: kwargs) monkeypatch.setattr(lead_agent_module, "load_agent_config", lambda x: AgentConfig(name="test", skills=None)) @@ -227,7 +227,7 @@ def test_make_lead_agent_enforces_allowed_tools_when_skill_cache_is_cold(monkeyp monkeypatch.setattr(lead_agent_module, "_resolve_model_name", lambda x=None, **kwargs: "default-model") monkeypatch.setattr(lead_agent_module, "create_chat_model", lambda **kwargs: "model") - monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda *args, **kwargs: []) + monkeypatch.setattr(lead_agent_module, "build_middlewares", lambda *args, **kwargs: []) monkeypatch.setattr(lead_agent_module, "apply_prompt_template", lambda **kwargs: "mock_prompt") monkeypatch.setattr(lead_agent_module, "create_agent", lambda **kwargs: kwargs) monkeypatch.setattr(lead_agent_module, "load_agent_config", lambda x: AgentConfig(name="test", skills=["restricted"])) diff --git a/frontend/src/content/en/harness/middlewares.mdx b/frontend/src/content/en/harness/middlewares.mdx index b185696a7..1af35018b 100644 --- a/frontend/src/content/en/harness/middlewares.mdx +++ b/frontend/src/content/en/harness/middlewares.mdx @@ -218,4 +218,4 @@ class MyMiddleware(AgentMiddleware): return state, config ``` -Custom middlewares are passed to `make_lead_agent` via the `custom_middlewares` parameter in `_build_middlewares`. They are injected immediately before `ClarificationMiddleware` at the end of the chain. +Custom middlewares are passed to `make_lead_agent` via the `custom_middlewares` parameter in `build_middlewares`. They are injected immediately before `ClarificationMiddleware` at the end of the chain. diff --git a/scripts/tool-error-degradation-detection.sh b/scripts/tool-error-degradation-detection.sh index 14b2da88b..2873beab9 100755 --- a/scripts/tool-error-degradation-detection.sh +++ b/scripts/tool-error-degradation-detection.sh @@ -35,7 +35,7 @@ from requests.exceptions import SSLError from langchain.agents.middleware import AgentMiddleware from langchain_core.messages import ToolMessage -from deerflow.agents.lead_agent.agent import _build_middlewares +from deerflow.agents.lead_agent.agent import build_middlewares from deerflow.config import get_app_config from deerflow.sandbox.middleware import SandboxMiddleware @@ -188,7 +188,7 @@ if not model_name: print("[FAIL] No model configured; cannot evaluate lead middleware chain.") raise SystemExit(8) -lead_middlewares = _build_middlewares({"configurable": {}}, model_name=model_name) +lead_middlewares = build_middlewares({"configurable": {}}, model_name=model_name) sub_middlewares = _build_sub_middlewares() print("[STEP 3] Simulate two sequential tool calls and check whether conversation flow aborts.")