DeepAgents 框架介绍与应用实战

一、DeepAgents 框架定位与核心价值

1.1 什么是 DeepAgents ?

DeepAgents(代码库名为 deepagents)是一个基于 LangChainLangGraph 构建的企业级高级智能体框架。它建立在 LangGraph(底层运行时)和 LangChain(工具/模型层)之上,是一个高阶的Agent Harness(智能体装备/套件)

  • 定位:它旨在简化长运行自主智能体 (Long-running Autonomous Agents) 的开发过程,通过内置的最佳实践和中间件,解决复杂任务中的规划、记忆、工具使用和环境交互问题。

  • 核心理念:如果说 LangChain 提供了积木,LangGraph 提供了地基,那么 DeepAgents 就是一套成品级的框架。它预设了最佳实践(规划、文件系统、子智能体),让你能快速构建类似 “OpenAI Deep Research” 或 “Claude Code” 的应用。

论文地址:https://arxiv.org/pdf/2510.21618

DeepAgents 开源地址:https://github.com/langchain-ai/deepagents

image

image

1.2 解决的核心问题

传统的 Agent 开发通常运行一个简单的循环:思考→调用工具→观察→重复。这种模式在处理多小时或多天的任务时,容易遇到以下"浅层陷阱"(Shallow Agent Problem)

  1. 规划能力缺失:原生 Agent 倾向于“走一步看一步”,缺乏全局视角的任务拆解,容易在多步任务中迷失方向。
  2. 遗忘与混乱:在执行超过 10-20 步的长任务时,由于 Context Window(上下文窗口)限制,传统 Agent 容易忘记初始目标或陷入死循环。
  3. 环境交互困难:文件系统操作、代码执行环境(沙箱)的配置和安全管理复杂。
  4. 上下文污染:所有工具返回结果都堆积在一个对话历史中,导致噪声过大。
  5. 协作编排复杂:多智能体(Multi-Agent)之间的任务分发和上下文隔离难以实现。

DeepAgents 通过引入"类人"的工作流解决了这些问题:先做计划(Plan),再执行,利用文件系统管理记忆,遇到复杂子任务时"外包"给子 Agent。将规划工具、文件系统访问、子代理和详细提示词等关键机制整合在一起,以支持复杂的深度任务 。

ChatGPT Image 2026年7月23日 09_50_13

import deepagents

# List all the functions
print(dir(deepagents))

输出:

image

1.3 应用场景

DeepAgents 不适合用来做简单的聊天机器人(Chatbot),它是为重任务设计的,它适用于任务需规划、上下文海量、需多专家协作、要求持久记忆的场景,将LangChain生态从单步响应提升至自主完成复杂项目的高度:

  • 深度调研 (Deep Research):自动进行多轮网页搜索、阅读文档、整理笔记并生成长篇报告(如分析某个行业的市场格局)。
  • 全栈代码生成 (Coding Assistant):类似 Claude Code,在沙箱环境中编写、运行、测试和修复代码,甚至重构整个代码库。
  • 复杂数据分析:自动连接数据库,生成 SQL,执行查询,将中间数据存为 CSV 文件(在虚拟文件系统中),最后生成图表。
  • 自动化运维 (DevOps Automation):操作文件系统、执行 Shell 命令、管理服务器状态。
  • 复杂工作流编排:需要多角色协作(如产品经理-程序员-测试员)的复杂业务流程。

当然这个描述相对来说比较抽象,因此我们这里对适用于DeepAgents的场景进行一个总结:

场景类型能力说明工作逻辑 / 技术特点代表性案例
深度调研与报告生成 支持长周期、多步骤、多来源信息整合的研究任务 • 自动生成研究计划 (Todo)
• 调用搜索工具获取资料
• 将关键信息写入文件系统 (长期记忆)
• 使用子代理 (Sub-Agents) 深入研究子课题
• 主代理统一规划、整合结果
• LangChain Deep Research 示例 (Tavily 搜索 + 多子代理拆分研究)
• OpenAI Deep Research (官方生产级深度调研助理)
自动编程与代码助理 理解代码、修改代码、生成新文件、执行工具链 • 代理可读写虚拟文件系统
• 自动分析源码输出 diff
• 人工审批 (Human-in-the-Loop) 保证安全写入
• 调用 Shell / 测试工具执行流程
• 可将项目规范写入 /memories 用作长期记忆
• LangChain DeepAgents CLI (终端自动编码)
• Anthropic Claude Code (深度自动重构与编程)
• Manus (多步骤代码智能体)
复杂流程自动化 (业务流程 Orchestration) 将多个步骤串联为可控流程,适合企业级自动化任务 • 任务分解 → 多步骤规划 → 调用不同工具
• 搜索、筛选、处理、生成等环节协作
• 使用文件系统存储中间数据 (如列表、分析结果等)
• 支持多工具、多子任务并行处理
• DeepAgents 求职助手 (职位搜索 → 筛选排序 → 求职信生成 → 打包结果)
• 企业场景如: 自动生成分析报告 / 客服知识库构建 / 数据采集 + 处理流

二、DeepAgents 与 LangChain 及 LangGraph 的区别

从技术定位看,LangChain 适用于需要自定义提示与工具的基础智能体搭建;LangGraph 更适合构建复杂的多智能体系统与工作流;而 DeepAgents 面向希望省去底层开发、直接采用深度自主机制的用户,可快速实现 AutoGPT 类应用。因此,DeepAgents 本质上是基于 LangChain 的深度模式封装——它并非替代 LangChain 或 LangGraph,而是将其常用抽象与运行时封装为开箱即用的组件,可视为一层“开发加速器”。

202512021438587

LangChain:提供 Prompt, Models, Tools 等积木。
LangGraph:提供 State, Nodes, Edges 等地基和连接逻辑。
DeepAgents:LangGraph 的一种“最佳实践实现”。它底层使用 LangGraph 来管理状态和循环,但向上提供了更高级的 API (`create_deep_agent`),隐藏了底层的图构建细节。

三、DeepAgents 核心功能介

安装deepagents,这里python使用3.12.13版本:

pip install deepagents

查看依赖

image

发现底层会自动安装langchain、langgraph相关组件:

image

测试:

import os

import dotenv
from langchain_deepseek import ChatDeepSeek

# 1.加载.env环境变量
dotenv.load_dotenv()

# 2.初始化模型
model = ChatDeepSeek(model="deepseek-v4-pro", api_base="https://api.deepseek.com",api_key=os.getenv('api_key'), temperature=0.7)
# 调用模型
print(model.invoke("农副产品批发行业市场研究分析?"))

image

3.1 核心入口:create_deep_agent()

这是整个框架的核心函数,它创建了一个功能完整的深度智能体。

默认配置

  • 使用 Claude Sonnet 4 或 GPT-4o 作为默认模型(推荐)。
  • 集成 7 个核心文件操作工具。
  • 提供待办事项管理功能。
  • 支持子代理调用。           

关键参数

  • model: 支持自定义语言模型。
  • tools: 自定义工具集。
  • system_prompt : 系统提示词
  • subagents: 子代理配置。
  • backend: 文件存储后端。
  • interrupt_on: 人机交互配置 (Human-in-the-Loop)。允许在特定节点暂停 Agent 执行,等待人工干预。这对于安全审核(删除文件)、成本控制(调用昂贵 API)和质量保证至关重要。

源码如下:

def create_deep_agent(
    model: str | BaseChatModel | None = None,
    tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
    *,
    system_prompt: str | None = None,
    middleware: Sequence[AgentMiddleware] = (),
    subagents: list[SubAgent | CompiledSubAgent] | None = None,
    response_format: ResponseFormat | None = None,
    context_schema: type[Any] | None = None,
    checkpointer: Checkpointer | None = None,
    store: BaseStore | None = None,
    backend: BackendProtocol | BackendFactory | None = None,
    interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,
    debug: bool = False,
    name: str | None = None,
    cache: BaseCache | None = None,
) -> CompiledStateGraph:

案例:实现网络搜索案例

pip install langchain-tavily

案例代码:

import os
import dotenv
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from langgraph.checkpoint.memory import InMemorySaver

dotenv.load_dotenv()

# 1.初始化Tavily搜索工具
tavily = TavilySearch(max_results=5)

# 2.准备提示词
prompt = """
你是一位资深的研究人员。你的工作是进行深入的研究,然后撰写一份精美的报告。
你可以通过互联网搜索引擎作为主要的信息收集工具。
## 可用工具
### `互联网搜索`
使用此功能针对给定的查询进行互联网搜索。您可以指定要返回的最大结果数量、主题以及是否包含原始内容。
### `写入本地文件`
使用此功能将研究报告保存到本地文件。当您完成研究并生成报告后,请使用此工具将完整的报告内容保存到文
件中。
- 文件路径建议使用 .md 格式(Markdown),例如 "research_report.md" 或 "./reports/报告名
称.md"
- 请确保报告内容完整、结构清晰,包含所有章节和引用来源
## 工作流程
在进行研究时:
1. 首先将研究任务分解为清晰的步骤
2. 使用互联网搜索来收集全面的信息
3. 将信息整合成一份结构清晰的报告
4. **重要**:完成报告后,务必使用 `写入本地文件` 工具将完整报告保存到本地文件
5. 务必引用你的资料来源
**注意**:请确保在完成研究后,将完整的报告内容保存到文件中,这样用户可以方便地查看和保存报告。
"""

# 3.初始化大模型
deepseek_v4_pro = init_chat_model(
    model="deepseek-v4-pro",
    model_provider='deepseek',
    base_url="https://api.deepseek.com",
    api_key=os.getenv('api_key'),
    temperature=0.7,
    max_tokens=10000,
)

# 4.创建agent
agent = create_deep_agent(
    model=deepseek_v4_pro, # 使用DeepSeek模型
    tools=[tavily],  # 使用Tavily搜索工具
    system_prompt=prompt,  # 使用自定义提示词
    checkpointer=InMemorySaver()  # 使用内存作为短期记忆
)
resp = agent.invoke(
    {"messages": [{"role": "user", "content": "请使用互联网搜索引擎进行搜索,并编写一份关于“如何实现一个企业智能知识库的”的详细报告。"}]},
    config={"configurable": {"thread_id": "1"}}
)

print(resp["messages"][-1].content)

image

3.2 create_deep_agent内部结构

源码参数说明:

image

安装美化代码库Rich

pip install rich

代码:

from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from create_demo_deep_agent.serarch_agent import agent

RICH_AVAILABLE = True

console = Console()


def print_agent_tools(agent):
    """
    打印 Agent 中加载的所有工具
    包括用户自定义工具、文件系统工具、系统工具等
    """
    # 获取 agent 的 nodes (LangGraph 的节点)
    if hasattr(agent, 'nodes') and 'tools' in agent.nodes:
        tools_node = agent.nodes['tools']

        # tools_node 是 PregelNode,真正的 ToolNode 在 bound 属性中
        if hasattr(tools_node, 'bound'):
            tool_node = tools_node.bound

            # 从 ToolNode 获取工具
            if hasattr(tool_node, 'tools_by_name'):
                tools = tool_node.tools_by_name

                # 分类工具
                user_tools = []
                filesystem_tools = []
                system_tools = []

                for tool_name, tool in tools.items():
                    tool_info = {
                        'name': tool_name,
                        'description': getattr(tool, 'description', '无描述')
                    }

                    # 分类
                    if tool_name in ['ls', 'read_file', 'write_file', 'edit_file', 'glob', 'grep', 'execute']:
                        filesystem_tools.append(tool_info)
                    elif tool_name in ['write_todos', 'task']:
                        system_tools.append(tool_info)
                    else:
                        user_tools.append(tool_info)

                # 打印加载工具的输出
                _print_tools_rich(user_tools, filesystem_tools, system_tools)

            else:
                print("无法获取工具列表 (tools_by_name 不存在)")
        else:
            print("无法获取工具列表 (bound 属性不存在)")
    else:
        print("无法获取工具列表 (nodes 结构不符合预期)")


def _print_tools_rich(user_tools, filesystem_tools, system_tools):
    """使用 Rich 库美化打印工具列表"""
    console.print()

    # 创建表格
    table = Table(title="Agent 加载的工具列表", show_header=True, header_style="bold magenta")
    table.add_column("类别", style="cyan", width=20)
    table.add_column("工具名称", style="green", width=20)
    table.add_column("描述", style="white", width=60)

    # 添加用户工具
    for i, tool in enumerate(user_tools):
        category = "用户工具" if i == 0 else ""
        desc = tool['description'][:80] + "..." if len(tool['description']) > 80 else tool['description']
        table.add_row(category, tool['name'], desc)

    # 添加文件系统工具
    for i, tool in enumerate(filesystem_tools):
        category = "文件系统工具" if i == 0 else ""
        desc = tool['description'][:80] + "..." if len(tool['description']) > 80 else tool['description']
        table.add_row(category, tool['name'], desc)

    # 添加系统工具
    for i, tool in enumerate(system_tools):
        category = "系统工具" if i == 0 else ""
        desc = tool['description'][:80] + "..." if len(tool['description']) > 80 else tool['description']
        table.add_row(category, tool['name'], desc)

    console.print(table)

    # 打印统计
    total = len(user_tools) + len(filesystem_tools) + len(system_tools)
    console.print(Panel(
        f"[bold green]共计 {total} 个工具[/bold green]\n\n"
        f"• 用户工具: {len(user_tools)} 个\n"
        f"• 文件系统工具: {len(filesystem_tools)} 个\n"
        f"• 系统工具: {len(system_tools)} 个",
        title="统计信息",
        border_style="green"
    ))
    console.print()


if __name__ == '__main__':
    print_agent_tools(agent)

image

image

这里可以看到,除了自己定义的工具(如 Tavily 搜索),DeepAgents 还默认添加了一些其他工具:

  • 文件系统中间件(FileSystemMiddleware): 用于读写、查询、执行文件系统中的文件。

  • 待办事项中间件(TodoListMiddleware): write_todos 用于写入待办事项,task 用于创建子agent来执行待办事项。

这些都是 DeepAgents 特有的功能,用于支持智能体在实际应用中的各种场景。那么,接下来我们来看看这些功能的具体应用。

Langgraph Studio 中可视化结构图:能看到

  • PatchToolCallsMiddleware用于 自动检测并修复“悬空”的工具调用的关键中间件,确保工具调用的完整性和正确性。
  • SummarizationMiddleware上下文压缩中间件,防止上下文过长

image四、四大核心内置工具与组件详解

DeepAgents 通过中间件 (Middleware) 的形式,为智能体注入了四项核心能力,构成了框架的四大支柱(Four Pillars)

ChatGPT Image 2026年7月23日 15_32_53

DeepAgents四大内置工具通过角色分离、状态贯通、成本优化的设计哲学,将长周期Agent的开发复杂度降低70%以上,同时通过LangGraph运行时保障生产级可靠性。其核心价值在于将原本需要手动编排的规划-存储-委托-执行流程,固化为中心化、可复用、可观测的中间件体系,标志着AI Agent从"脚本化"向"产品化"的关键演进。

维度系统提示词(System Prompt)规划工具(Planning Tool)文件系统(File System)子代理(Sub Agents)
角色定位 行为总导演:定义Agent的"世界观"与工具使用范式,确保三大中间件协同不偏离目标 任务架构师:将模糊需求转化为可执行、可追踪、可动态调整的结构化任务蓝图 上下文仓库:虚拟化存储引擎,解决长任务中的信息溢出与状态持久化难题 执行特派员:实现上下文隔离与专业分工,防止主Agent因深层递归导致状态混乱
核心功能 内置 Claude Code 风格指令,涵盖规划逻辑、文件操作规范、子代理调用协议,支持场景化自定义覆盖

write_todos:生成带优先级/依赖关系的 JSON 任务列表

read_todos:实时查询任务执行进度与状态

ls/glob:文件浏览与模式匹配

read/write/edit:CRUD 操作

grep:内容检索

execute:沙箱命令执行

task:动态生成同构或异构子Agent支持独立上下文窗口与工具集配置结果通过文件系统回传
技术实现 字符串模板,在 create_deep_agent 时注入;默认提示词约 2000 tokens,包含 ReAct 循环与三大中间件调用示例 TodoListMiddleware:拦截 LLM 输出中的 todo_list 字段,解析为 agent_state.todos 字典,状态变更触发持久化 FilesystemMiddleware:基于 LangGraph State 的 files 字段实现内存级虚拟文件系统,大工具结果(>2KB)自动放置 file 消息 SubAgentMiddleware:将 task 调用编译为独立的 StateGraph 子图,通过命名空间隔离状态,仅图通过 file 读取子图输出
状态管理 静态配置,单次会话内不可变;可通过 on_configurable_agent 实现热更新 动态状态机:每个 todo 包含 id / description / status / priority / dependencies 字段,执行后状态从 pending → completed,支持 update_todos 动态调整 持久化存储:默认存储在 LangGraph State,支持切换 StateBackend(内存/Redis/Postgres)实现跨会话文件共享 完全隔离:子Agent拥有独立的 messagesfiles 命名空间,异常不会污染父Agent状态;支持 max_iterations 限制防止无限递归
使用场景 垂直领域定制:金融研究/医疗诊断等需强化专业约束的场景② 多Agent协作:统一多个子Agent的行为规范③ 安全合规:注入数据脱敏、权限检查等硬性规则

长周期研究:自动拆解为文献检索→数据收集→分析→撰写的阶段性任务

故障恢复:崩溃后通过 read_todos 快速定位断点续跑

动态重规划:执行中发现信息不足时新增补充任务

大结果处理:搜索返回 100KB 内容自动落盘,避免上下文溢出

知识沉淀:中间分析结果写入文件供后续步骤复用

多Agent数据共享:父Agent与子Agent通过文件交换数据,无需序列化传递

高风险操作隔离:网页抓取、代码执行等易失败任务交由子Agent处理

专业化分工:主Agent负责总体编排,子Agent专注领域执行(如搜索、代码、分析)

资源优化:子Agent可使用轻量模型,降低整体 Token 成本

4.1 系统提示词 (System Prompts)

功能:定义 Agent 的“人设”、行为准则和核心目标。
机制:框架会自动将用户定义的 system_prompt 与内置的 BASE_AGENT_PROMPT 结合。
作用:确保 Agent 始终遵循指令,理解其可用的工具集,并保持一致的输出风格。
角色本质:系统提示词是DeepAgents三大中间件协同的"契约",其默认版本包含:

  • 规划指令:要求LLM在任务开始前必须调用write_todos,输出格式为JSON Schema;
  • 文件操作规范:明确write_file用于新内容,edit_file用于局部修改,避免覆盖冲突;
  • 子代理调用协议:规定task工具的参数结构及结果通过/subagent_results/ .md回传;
  • 安全底线:禁止直接执行删除、格式化等危险命令,必须通过execute沙箱;

1.文件系统中间件提示词(FileSystemMiddleWare)

文件路径:deepagents/middleware/filesystem.py

  • FileSystem_System_prompt 文件系统提示词

image

 

  • Execute_Tool_Descriptition 执行工具提示词

image

2.TodoList中间件提示词(TodoListMiddleware)

文件路径:deepagents/middleware/todo.py           

  • WRITE_TODOS_SYSTEM_PROMPT写执行任务提示词

image

3.子智能体中间件提示词(SubAgentMiddleware)

文件路径:deepagents/middleware/subagent.py

  • TASK_SYSTEM_PROMPT  Task执行器提示词

image

4.2 规划工具 (Planning System / Todo List)

组件:TodoListMiddleware
工具名:write_todos
功能:Agent 在行动前先生成 Markdown 格式的待办事项列表 (Todo List),并在执行过程中更新状态(完成/进行中)。
工作流:

  • Agent 接收复杂任务(简单短期的任务不会触发todolist)。
  • 调用 write_todos 将任务拆解为子步骤 (Pending)。
  • 每完成一步,更新状态为 (Completed)。
  • 自我反思:在每一步行动前,Agent 都会看到当前的 Todo List,从而避免迷失方向。这强制模型进行"思维链"的显性化管理。

类似编程工具中的待执行项:

image

import os
import dotenv
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from langgraph.checkpoint.memory import InMemorySaver

dotenv.load_dotenv()

# 1.初始化Tavily搜索工具
tavily = TavilySearch(max_results=5)

# 2.准备提示词
prompt = """
你是一位资深的研究人员。你的工作是进行深入的研究,然后撰写一份精美的报告。
你可以通过互联网搜索引擎作为主要的信息收集工具。
## 可用工具
### `互联网搜索`
使用此功能针对给定的查询进行互联网搜索。您可以指定要返回的最大结果数量、主题以及是否包含原始内容。
### `写入本地文件`
使用此功能将研究报告保存到本地文件。当您完成研究并生成报告后,请使用此工具将完整的报告内容保存到文
件中。
- 文件路径建议使用 .md 格式(Markdown),例如 "research_report.md" 或 "./reports/报告名
称.md"
- 请确保报告内容完整、结构清晰,包含所有章节和引用来源
## 工作流程
在进行研究时:
1. 首先将研究任务分解为清晰的步骤
2. 使用互联网搜索来收集全面的信息
3. 将信息整合成一份结构清晰的报告
4. **重要**:完成报告后,务必使用 `写入本地文件` 工具将完整报告保存到本地文件
5. 务必引用你的资料来源
**注意**:请确保在完成研究后,将完整的报告内容保存到文件中,这样用户可以方便地查看和保存报告。
"""

# 3.初始化大模型
deepseek_v4_pro = init_chat_model(
    model="deepseek-v4-pro",
    model_provider='deepseek',
    base_url="https://api.deepseek.com",
    api_key=os.getenv('api_key'),
    temperature=0.7,
    max_tokens=10000,
)

# 4.创建agent(供 langgraph.json / Studio 导入)
# 注意:langgraph dev / LangGraph API 会自行管理持久化,不能传自定义 checkpointer
# agent = create_deep_agent(
#     model=deepseek_v4_pro,  # 使用DeepSeek模型
#     tools=[tavily],  # 使用Tavily搜索工具
#     system_prompt=prompt,  # 使用自定义提示词
# )

# 本地脚本直接跑时,再挂内存 checkpointer
local_agent = create_deep_agent(
    model=deepseek_v4_pro,
    tools=[tavily],
    system_prompt=prompt,
    checkpointer=InMemorySaver(),
)


if __name__ == "__main__":
    # 本地脚本直接跑时,再挂内存 checkpointer
    local_agent = create_deep_agent(
        model=deepseek_v4_pro,
        tools=[tavily],
        system_prompt=prompt,
        checkpointer=InMemorySaver(),
    )
    resp = local_agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "请使用互联网搜索引擎进行搜索,并编写一份关于“如何实现一个企业智能知识库的”的详细报告。",
                }
            ]
        },
        config={"configurable": {"thread_id": "1"}},
    )
    print(resp["messages"][-1].content)

测试代码:

这段在做的事可以概括成一条流水线,每个 stream 步骤只看 messages 里最后一条,然后按消息类型分别打印:

步骤看什么打印什么
1 messages[-1] 本步新产生的那条消息
2 tool_calls 模型是否要调工具
3 有 content 且无 tool_calls 记为候选「最终回答」
4 content 绿色面板:AI 思考/正文(长文截断)
5 tool_calls 列表 蓝色面板:工具名 + 参数
6 消息有 .name(ToolMessage) 紫色面板:工具返回(截断)
步骤看什么打印什么
1 messages[-1] 本步新产生的那条消息
2 tool_calls 模型是否要调工具
3 有 content 且无 tool_calls 记为候选「最终回答」
4 content 绿色面板:AI 思考/正文(长文截断)
5 tool_calls 列表 蓝色面板:工具名 + 参数
6 消息有 .name(ToolMessage) 紫色面板:工具返回(截断)

对应一次典型运行顺序:

用户提问
  → AI(可能带 tool_calls)→ 蓝色「工具调用」
  → ToolMessage(工具结果)→ 紫色「工具响应」
  → AI 再思考 / 再调工具 …
  → 最后一次纯文本 AI → final_response

如下:

import json

from rich.console import Console
from rich.json import JSON
from rich.panel import Panel
from create_demo_deep_agent.serarch_agent import local_agent


RICH_AVAILABLE = True

console = Console()


def debug_agent(query: str, agent, save_to_file: str = None):
    """
    运行智能体并打印中间过程,使用Rich美化输出
    :param query: 用于查询
    :param save_to_file: 保存最终输出到文件(可选)
    :return: 最终的研究报告
    """
    console.print(Panel.fit(f"[bold cyan]查询:[/bold cyan] {query}", border_style="cyan"))

    step_num = 0
    final_response = None

    for chunk in agent.stream(
        {"messages": [{"role": "user", "content": query}]},
        config={"configurable": {"thread_id": "2"}},
        stream_mode="values",  # stream_mode="values" 时,每个 chunk 是完整 agent 状态字典
    ):
        console.print(f"\n[bold yellow]{'-' * 80}[/bold yellow]")
        console.print(f"[bold yellow]步骤 {step_num} [/bold yellow]")
        console.print(f"[bold yellow]{'-' * 80}[/bold yellow]")

        step_num += 1

        # stream_mode="values" 时,每个 chunk 通常是完整 agent 状态字典,例如:
        # {"messages": [HumanMessage, AIMessage, ToolMessage, ...], ...}
        if "messages" in chunk:
            # 取出截至当前步骤的全部消息列表(越往后越长,因为是累计状态)
            messages = chunk["messages"]
            if messages:
                # ---------- 1) 只关心「本步新增」的那条:取列表最后一条 ----------
                # HumanMessage: 用户输入
                # AIMessage:    模型回复(可能带 tool_calls,表示要调工具)
                # ToolMessage:  某个工具执行完后的返回结果
                last_message = messages[-1]

                # ---------- 2) 安全读取 tool_calls ----------
                # getattr(obj, "attr", default): 没有该属性时返回 default,避免 AttributeError
                # AIMessage 常有 tool_calls;Human/ToolMessage 通常没有
                # `or []`:若属性存在但值为 None / 空,统一当成空列表,后面 for 不会报错
                tool_calls = getattr(last_message, "tool_calls", None) or []

                # ---------- 3) 记录「可能的最终回答」----------
                # 条件:有正文 content,且没有待执行的工具调用
                # 含义:模型这轮是在「直接回答用户」,而不是「先去调工具」
                # 注意:中间过程也可能满足条件,所以用赋值覆盖;流结束后留下的就是最后一次纯文本回答
                if getattr(last_message, "content", None) and not tool_calls:
                    final_response = last_message.content

                # ---------- 4) 打印 AI 正文(思考/回答预览)----------
                # 只要当前这条消息带 content,就展示(AI 常见;Tool 也会有 content,会走到下面工具响应分支)
                if getattr(last_message, "content", None):
                    content = last_message.content
                    # 长文本且不是「正在发起工具调用」的 AI 回复:只预览前 300 字,避免刷屏
                    # (真正完整内容会在函数返回的 final_response / 主程序 print 里看到)
                    if len(content) > 300 and not tool_calls:
                        preview = content[:300] + "..."
                        console.print(
                            Panel(
                                # [dim]...[/dim] 是 Rich 标记:把提示文字显示成灰色弱化样式
                                f"{preview}\n\n[dim](内容较长,完整内容将在最后显示)[/dim]",
                                title="[bold green]AI思考[/bold green]",
                                border_style="green",
                            )
                        )
                    else:
                        # 短文本,或带 tool_calls 的 AI(content 可能为空/很短):完整打印
                        console.print(
                            Panel(content, title="[bold green]AI思考[/bold green]", border_style="green")
                        )

                # ---------- 5) 打印「模型决定调用哪些工具」----------
                # tool_calls 示例结构(dict 时)大致为:
                # [{"name": "tavily_search", "args": {"query": "..."}, "id": "call_xxx"}, ...]
                # 一条 AIMessage 里可以一次请求多个工具
                for tool_call in tool_calls:
                    # LangChain 里 tool_call 有时是 dict,有时是对象,两种都兼容
                    if isinstance(tool_call, dict):
                        # dict:用 .get,缺 key 时给默认值
                        name = tool_call.get("name", "unknown")
                        args = tool_call.get("args", {})
                    else:
                        # 对象:用 getattr 读属性
                        name = getattr(tool_call, "name", "unknown")
                        args = getattr(tool_call, "args", {})
                    # 整理成中文 key,方便在终端阅读
                    tool_info = {"工具名称": name, "参数": args}
                    console.print(
                        Panel(
                            # json.dumps → 普通 JSON 字符串;ensure_ascii=False 保留中文
                            # rich.json.JSON → 终端里带语法高亮的 JSON 展示
                            JSON(json.dumps(tool_info, ensure_ascii=False)),
                            title="[bold blue]工具调用[/bold blue]",
                            border_style="blue",
                        )
                    )

                # ---------- 6) 打印「工具执行结果」(ToolMessage)----------
                # ToolMessage 特有字段 name:被调用的工具名(如 tavily_search / write_file)
                # AIMessage/HumanMessage 一般没有有意义的 .name,这里用 name 是否存在来区分
                if getattr(last_message, "name", None):
                    # 工具返回可能很长(整页搜索结果),只展示前 500 字符
                    resp = str(last_message.content)[:500]
                    if len(str(last_message.content)) > 500:
                        # 提示被截断,并告诉原始总长度
                        resp += f"\n...(共{len(str(last_message.content))}字符)"
                    console.print(
                        Panel(
                            resp,
                            # 标题里带上工具名,一眼看出是哪个工具回的
                            title=f"[bold magenta]工具响应:{last_message.name}[/bold magenta]",
                            border_style="magenta",
                        )
                    )
    # for 流式循环结束:agent 跑完所有步骤(思考 → 调工具 → 再思考 → … → 最终回答)
    console.print("\n[bold green]任务完成![/bold green]\n")
    # 返回过程中不断覆盖得到的「最后一次无 tool_calls 的 AI 正文」
    return final_response

print("调用函数已创建")
if __name__ == '__main__':
    query = "详细调研关于如何使用deepagents框架实现一个爬取政策新闻,生成总结报告的助手,写一份结构化的总结报告。"

    resp = debug_agent(query, local_agent)
    print(resp)

输出如下:

D:\anaconda3\envs\deepagents\python.exe E:\code\InsightFlow\create_demo_deep_agent\demo_planning_sysytem_todo_list.py 
调用函数已创建
┌─────────────────────────────────────────────────────────────────────────────┐
│ 查询:                                                                      │
│ 详细调研关于如何使用deepagents框架实现一个爬取政策新闻,生成总结报告的助手  │
│ ,写一份结构化的总结报告。                                                  │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 0 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ 详细调研关于如何使用deepagents框架实现一个爬取政策新闻,生成总结报告的助手  │
│ ,写一份结构化的总结报告。                                                  │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 1 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ 详细调研关于如何使用deepagents框架实现一个爬取政策新闻,生成总结报告的助手  │
│ ,写一份结构化的总结报告。                                                  │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 2 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "write_todos",                                                │
│   "参数": {                                                                 │
│     "todos": [                                                              │
│       {                                                                     │
│         "content": "调研 deepagents 框架的基本概念、架构与核心特性",        │
│         "status": "in_progress"                                             │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研 deepagents 框架的安装与使用方法",                  │
│         "status": "pending"                                                 │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研如何使用 deepagents 实现网页爬取功能",              │
│         "status": "pending"                                                 │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研如何使用 deepagents 实现政策新闻摘要与报告生成",    │
│         "status": "pending"                                                 │
│       },                                                                    │
│       {                                                                     │
│         "content": "整合信息,撰写结构化总结报告",                          │
│         "status": "pending"                                                 │
│       },                                                                    │
│       {                                                                     │
│         "content": "将报告保存到本地文件",                                  │
│         "status": "pending"                                                 │
│       }                                                                     │
│     ]                                                                       │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 3 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ Updated todo list to [{'content': '调研 deepagents                          │
│ 框架的基本概念、架构与核心特性', 'status': 'in_progress'}, {'content':      │'调研 deepagents 框架的安装与使用方法', 'status': 'pending'}, {'content':   │
│ '调研如何使用 deepagents 实现网页爬取功能', 'status': 'pending'},           │
│ {'content': '调研如何使用 deepagents 实现政策新闻摘要与报告生成', 'status': │
│ 'pending'}, {'content': ...                                                 │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                            │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────── 工具响应:write_todos ───────────────────────────┐
│ Updated todo list to [{'content': '调研 deepagents                          │
│ 框架的基本概念、架构与核心特性', 'status': 'in_progress'}, {'content':      │'调研 deepagents 框架的安装与使用方法', 'status': 'pending'}, {'content':   │
│ '调研如何使用 deepagents 实现网页爬取功能', 'status': 'pending'},           │
│ {'content': '调研如何使用 deepagents 实现政策新闻摘要与报告生成', 'status': │
│ 'pending'}, {'content': '整合信息,撰写结构化总结报告', 'status':           │
│ 'pending'}, {'content': '将报告保存到本地文件', 'status': 'pending'}]       │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 4 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents framework 介绍 架构 使用方法",                     │
│     "search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents LangGraph agent framework tutorial",               │
│     "search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents web scraping crawling agent",                      │
│     "search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 5 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ {"query": "deepagents web scraping crawling agent", "follow_up_questions":  │
│ null, "answer": null, "images": [], "results": [{"url":                     │
│ "https://docs.langchain.com/oss/python/integrations/tools/hyperbrowser_web_ │
│ scraping_tools", "title": "Hyperbrowser web scraping integration - Docs by  │
│ LangChain", "conten...                                                      │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                            │
└─────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────── 工具响应:tavily_search ──────────────────────────┐
│ {"query": "deepagents web scraping crawling agent", "follow_up_questions":  │
│ null, "answer": null, "images": [], "results": [{"url":                     │
│ "https://docs.langchain.com/oss/python/integrations/tools/hyperbrowser_web_ │
│ scraping_tools", "title": "Hyperbrowser web scraping integration - Docs by  │
│ LangChain", "content": "> ## Documentation Index\n>\n> Fetch the complete   │
│ documentation index at:</llms.txt>\n>\n> Use this file to discover all      │
│ available pages before exploring further.\n\nInterrupt is coming to NYC an  │
│ ...(共11127字符)                                                            │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 6 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents Python create_deep_agent 代码示例 爬虫 报告生成",  │
│     "search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents GitHub repository documentation tools file system  │"search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "langchain deepagents 爬取新闻 政策 摘要报告 实现方案",        │
│     "search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 7 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ {"query": "langchain deepagents 爬取新闻 政策 摘要报告 实现方案",           │
│ "follow_up_questions": null, "answer": null, "images": [], "results":       │
│ [{"url":                                                                    │
│ "https://www.linkedin.com/posts/vivek-trivedy-433509134_at-langchain-we-hav │
│ e-a-ton-of-fun-building-activity-7438979196586168320-vMYM", "title":        │"LangChain Deepagents Lib...                                                │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                            │
└─────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────── 工具响应:tavily_search ──────────────────────────┐
│ {"query": "langchain deepagents 爬取新闻 政策 摘要报告 实现方案",           │
│ "follow_up_questions": null, "answer": null, "images": [], "results":       │
│ [{"url":                                                                    │
│ "https://www.linkedin.com/posts/vivek-trivedy-433509134_at-langchain-we-hav │
│ e-a-ton-of-fun-building-activity-7438979196586168320-vMYM", "title":        │"LangChain Deepagents Library for Efficient Agent Building | Vivek Trivedy  │
│ posted on the topic | LinkedIn", "content": "Is this the end of the \"Black │
│ Box\" AI Coding Agent? 🛠️💀\nIn case you missed it, Chorouk Malmoum just dr │
│ ...(共8801字符)                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 8 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents create_deep_agent tools tavily web_search subagent │"search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents Python middleware skills custom tool crawl news po │"search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 9 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ {"query": "deepagents Python middleware skills custom tool crawl news       │
│ policy summarization 2025", "follow_up_questions": null, "answer": null,    │"images": [], "results": [{"url":                                           │
│ "https://www.youtube.com/watch?v=AZ6257Ya_70", "title": "Rewriting Deep     │
│ Agents on top of LangChain 1.0", "content": "# Rewr...                      │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                            │
└─────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────── 工具响应:tavily_search ──────────────────────────┐
│ {"query": "deepagents Python middleware skills custom tool crawl news       │
│ policy summarization 2025", "follow_up_questions": null, "answer": null,    │"images": [], "results": [{"url":                                           │
│ "https://www.youtube.com/watch?v=AZ6257Ya_70", "title": "Rewriting Deep     │
│ Agents on top of LangChain 1.0", "content": "# Rewriting Deep Agents on top │
│ of LangChain 1.0\n## LangChain\n194000 subscribers\n457 likes\n\n###        │
│ Description\n19485 views\nPosted: 24 Sep 2025\nIn this video, we will walk  │
│ through how we rebuilt deepagen                                             │
│ ...(共11429字符)                                                            │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 10 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents Firecrawl BeautifulSoup web crawling tool integrat │"search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents policy news crawler agent subagent delegation plan │"search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 11 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ {"query": "deepagents policy news crawler agent subagent delegation planned │
│ task architecture", "follow_up_questions": null, "answer": null, "images":  │
│ [], "results": [{"url":                                                     │
│ "https://www.linkedin.com/posts/vivek-trivedy-433509134_at-langchain-we-hav │
│ e-a-ton-of-fun-building-activity-74389791965861683...                       │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                            │
└─────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────── 工具响应:tavily_search ──────────────────────────┐
│ {"query": "deepagents policy news crawler agent subagent delegation planned │
│ task architecture", "follow_up_questions": null, "answer": null, "images":  │
│ [], "results": [{"url":                                                     │
│ "https://www.linkedin.com/posts/vivek-trivedy-433509134_at-langchain-we-hav │
│ e-a-ton-of-fun-building-activity-7438979196586168320-vMYM", "title":        │"LangChain Deepagents Library for Efficient Agent Building | Vivek Trivedy  │
│ posted on the topic | LinkedIn", "content": "Is this the end of the \"Black │
│ Box\" AI Coding Agent? 🛠️💀\nIn                                             │
│ ...(共9918字符)                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 12 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "site:dev.to \"Building Advanced AI Agents with LangChain's De │"search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "tavily_search",                                              │
│   "参数": {                                                                 │
│     "query": "deepagents crawler news article extract content tool implemen │"search_depth": "advanced"                                              │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 13 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ {"query": "deepagents crawler news article extract content tool             │
│ implementation python code", "follow_up_questions": null, "answer": null,   │"images": [], "results": [{"url":                                           │
│ "https://stackoverflow.com/questions/30356069/extract-news-article-content- │from-stored-html-pages", "title": "python - Extract N...                    │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                            │
└─────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────── 工具响应:tavily_search ──────────────────────────┐
│ {"query": "deepagents crawler news article extract content tool             │
│ implementation python code", "follow_up_questions": null, "answer": null,   │"images": [], "results": [{"url":                                           │
│ "https://stackoverflow.com/questions/30356069/extract-news-article-content- │from-stored-html-pages", "title": "python - Extract News article content    │
│ from stored .html pages - Stack Overflow", "content": "Explore Stack        │
│ Internal\n\n# Extract News article content from stored .html pages\n\nAsk   │
│ Question\n\nAsked\n\nModified 4 years                                       │
│ ...(共9507字符)                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 14 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "write_todos",                                                │
│   "参数": {                                                                 │
│     "todos": [                                                              │
│       {                                                                     │
│         "content": "调研 deepagents 框架的基本概念、架构与核心特性",        │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研 deepagents 框架的安装与使用方法",                  │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研如何使用 deepagents 实现网页爬取功能",              │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研如何使用 deepagents 实现政策新闻摘要与报告生成",    │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "整合信息,撰写结构化总结报告",                          │
│         "status": "in_progress"                                             │
│       },                                                                    │
│       {                                                                     │
│         "content": "将报告保存到本地文件",                                  │
│         "status": "pending"                                                 │
│       }                                                                     │
│     ]                                                                       │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 15 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ Updated todo list to [{'content': '调研 deepagents                          │
│ 框架的基本概念、架构与核心特性', 'status': 'completed'}, {'content': '调研       │
│ deepagents 框架的安装与使用方法', 'status': 'completed'}, {'content':          │'调研如何使用 deepagents 实现网页爬取功能', 'status': 'completed'},             │
│ {'content': '调研如何使用 deepagents 实现政策新闻摘要与报告生成', 'status':       │
│ 'completed'}, {'conten...                                                   │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                                 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────── 工具响应:write_todos ───────────────────────────┐
│ Updated todo list to [{'content': '调研 deepagents                          │
│ 框架的基本概念、架构与核心特性', 'status': 'completed'}, {'content': '调研       │
│ deepagents 框架的安装与使用方法', 'status': 'completed'}, {'content':          │'调研如何使用 deepagents 实现网页爬取功能', 'status': 'completed'},             │
│ {'content': '调研如何使用 deepagents 实现政策新闻摘要与报告生成', 'status':       │
│ 'completed'}, {'content': '整合信息,撰写结构化总结报告', 'status':             │
│ 'in_progress'}, {'content': '将报告保存到本地文件', 'status': 'pending'}]      │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 16 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "write_file",                                                 │
│   "参数": {                                                                 │
│     "file_path": "/deepagents_policy_news_agent_report.md",                 │
│     "content": "# 基于 DeepAgents 框架构建政策新闻爬取与总结报告助手 —— 结  │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 17 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ Updated file /deepagents_policy_news_agent_report.md                        │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────── 工具响应:write_file ────────────────────────────┐
│ Updated file /deepagents_policy_news_agent_report.md                        │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 18 
-------------------------------------------------------------------------------
-
┌───────────────────────────────── 工具调用 ──────────────────────────────────┐
│ {                                                                           │
│   "工具名称": "write_todos",                                                │
│   "参数": {                                                                 │
│     "todos": [                                                              │
│       {                                                                     │
│         "content": "调研 deepagents 框架的基本概念、架构与核心特性",        │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研 deepagents 框架的安装与使用方法",                  │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研如何使用 deepagents 实现网页爬取功能",              │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "调研如何使用 deepagents 实现政策新闻摘要与报告生成",    │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "整合信息,撰写结构化总结报告",                          │
│         "status": "completed"                                               │
│       },                                                                    │
│       {                                                                     │
│         "content": "将报告保存到本地文件",                                  │
│         "status": "completed"                                               │
│       }                                                                     │
│     ]                                                                       │
│   }                                                                         │
│ }                                                                           │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 19 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ Updated todo list to [{'content': '调研 deepagents                          │
│ 框架的基本概念、架构与核心特性', 'status': 'completed'}, {'content': '调研  │
│ deepagents 框架的安装与使用方法', 'status': 'completed'}, {'content':       │'调研如何使用 deepagents 实现网页爬取功能', 'status': 'completed'},         │
│ {'content': '调研如何使用 deepagents 实现政策新闻摘要与报告生成', 'status': │
│ 'completed'}, {'conten...                                                   │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                            │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────── 工具响应:write_todos ───────────────────────────┐
│ Updated todo list to [{'content': '调研 deepagents                          │
│ 框架的基本概念、架构与核心特性', 'status': 'completed'}, {'content': '调研  │
│ deepagents 框架的安装与使用方法', 'status': 'completed'}, {'content':       │'调研如何使用 deepagents 实现网页爬取功能', 'status': 'completed'},         │
│ {'content': '调研如何使用 deepagents 实现政策新闻摘要与报告生成', 'status': │
│ 'completed'}, {'content': '整合信息,撰写结构化总结报告', 'status':         │
│ 'completed'}, {'content': '将报告保存到本地文件', 'status': 'completed'}]   │
└─────────────────────────────────────────────────────────────────────────────┘

-------------------------------------------------------------------------------
-
步骤 20 
-------------------------------------------------------------------------------
-
┌────────────────────────────────── AI思考 ───────────────────────────────────┐
│ 报告已完成并保存到                                                          │
│ `/deepagents_policy_news_agent_report.md`。以下是报告的核心内容摘要:       │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 📋 报告概览                                                              │
│                                                                             │
│ 该结构化报告共 **12 个章节**,涵盖以下核心内容:                            │
│                                                                             │
│ ### 🔑 关键发现                                                             │
│                                                                             │
│ **1. DeepAgents 是什么?**                                                  │
│ - LangChain 官方推出的"电池已装好"的 Agent 框架,基于 LangGraph 构建        │
│ - 灵感来自 Claude Code、Manus、Deep Research 等产品,将其共性能力抽象封装   │
│                                                                             │
│ **2. 四大核心支柱**                                                         │
│ - **规划工具**(`write_todos`):行动前先制定...                            │
│                                                                             │
│ (内容较长,完整内容将在最后显示)                                            │
└─────────────────────────────────────────────────────────────────────────────┘

任务完成!

报告已完成并保存到 `/deepagents_policy_news_agent_report.md`。以下是报告的核心内容摘要:

---

## 📋 报告概览

该结构化报告共 **12 个章节**,涵盖以下核心内容:

### 🔑 关键发现

**1. DeepAgents 是什么?**
- LangChain 官方推出的"电池已装好"的 Agent 框架,基于 LangGraph 构建
- 灵感来自 Claude Code、Manus、Deep Research 等产品,将其共性能力抽象封装

**2. 四大核心支柱**
- **规划工具**(`write_todos`):行动前先制定结构化 TODO 列表
- **虚拟文件系统**:`write_file`/`read_file`/`edit_file`/`ls`/`glob`/`grep` 全套工具,实现上下文管理和子代理协作
- **子代理机制**(`task` 工具):独立上下文窗口,爬取/分析/审校分工协作
- **系统提示词**:内置 + 用户自定义双层提示词设计

**3. 政策新闻助手的推荐架构**
```
主代理 → 爬虫子代理(搜索+爬取全文)
      → 分析子代理(提取要点+撰写初稿)
      → 审校子代理(检查准确性+反馈)
      → 主代理定稿 → final_report.md
```

**4. 报告包含完整可运行代码**,涵盖工具定义、子代理配置、主代理创建、流式输出、多轮对话支持等所有关键实现。

**5. 提供了调试方案**:LangGraph Studio 可视化调试、Deep Agents UI、LangSmith 追踪。

Process finished with exit code 0

4.3 子代理 (Sub-Agent Delegation)

组件:SubAgentMiddleware
工具名:task (delegate_task)
核心概念:

  • 任务隔离:每个子代理有独立的上下文窗口。
  • 并行执行:支持同时启动多个子代理。
  • 结果聚合:智能整合多个子代理的输出。

机制:

  • 当任务过于具体(如"爬取并分析这篇长论文")时,主 Agent 会生成一个隔离环境的子 Agent 去执行。
  • 子 Agent 启动时,只继承必要的环境配置,但拥有全新的、空白的消息历史。
  • 子 Agent 执行完毕后只返回一个总结性的结果。这保证了主 Agent 的时间线(Context)保持干净,极大地节省了 Token。

自动触发默认的 SubAgentMiddleware

import asyncio
import os

import dotenv
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model
from langchain_core.messages import BaseMessage, ToolMessage
from langchain_tavily import TavilySearch
from rich.console import Console
from rich.panel import Panel
from rich.tree import Tree

# 1.加载环境变量
dotenv.load_dotenv()

# 2.配置Rich Console
console = Console()


async def run_auto_subagent():
    """
    不显示传入subagents参数,自动触发默认的SubAgentMiddleware
    :return:
    """
    console.print(
        Panel.fit(
            "[bold magenta]DeepAgents自动subAgent中间件演示[/bold magenta]",
            border_style="magenta",
        )
    )
    console.print(
        "[dim]本演示验证:即使不传入subagents参数,Agent默认也会启动 'general-purpose' 子 Agent [/dim]"
    )

    # 1.初始化模型
    deepseek_v4_pro = init_chat_model(
        model="deepseek-v4-pro",
        model_provider="deepseek",
        base_url="https://api.deepseek.com",
        api_key=os.getenv("api_key"),
        temperature=0.7,
        max_tokens=10000,
    )

    # 2.初始化Tavily搜索工具
    tavily = TavilySearch(max_results=5)

    # 3.创建agent,但是不传入subagents参数
    # create_deep_agent默认会加载SubAgentMiddleware(), 这就意味着Agent会自动获取一个名为task的工具,可以调用 'general-purpose' 子 Agent
    console.print("[bold cyan]正在创建Agent(subagents=None)...[/bold cyan]")

    agent = create_deep_agent(
        model=deepseek_v4_pro,
        tools=[tavily],
        # subagents=[],  # 故意不传或者传入为空
        system_prompt="""你是一个能高效处理并发任务的智能助手,对于包含多个部分的复杂任务,
你必须使用 'task'工具来创建'general-purpose' 子Agent进行处理。
不要自己在主线程中串行执行所有操作。利用子Agent来隔离上下文并提高效率。""",
    )

    # 4.定义一个合适 并行/隔离 的任务
    task = """
    请同时调研一下四个完全不同的主题:并分别给出简单总结
    1. 请说明claude code的技术架构说明
    2. 请你帮我说明python的多线程机制
    3. python的性能为什么很差?
    4. open claude 小龙虾的技术实现架构说明
    """

    # 5.运行并可视化
    step = 0

    console.print("[dim]开始流式输出...[/dim]")

    try:
        # astream:异步流式跑图。每跑完「一个节点」就 yield 一个 chunk(默认类似 updates 模式)
        # 输入格式和 invoke 一样:{"messages": [用户消息, ...]}
        async for chunk in agent.astream({"messages": [{"role": "user", "content": task}]}):
            step += 1  # 每收到一个 chunk 算一步(含后面不打印的静默更新,所以 Step 可能跳号)

            # ---------- chunk 长什么样? ----------
            # 当前写法没传 stream_mode 时,astream 默认一般是 "updates"。若要显式写清楚:
            # chunk 是一个 dict,键 = LangGraph 节点名,值 = 该节点本次产出的状态增量。
            # 例子:
            #   {"model": {"messages": [AIMessage(...)]}}
            #   {"tools": {"messages": [ToolMessage(...), ToolMessage(...)]}}
            # 同一轮也可能只有一个键。
            # .items():把 dict 拆成 (key, value) 对,便于 for 解包。
            #   node_name → 如 "model" / "tools"
            #   node_data → 如 {"messages": [...]},也可能是 todos 等其它字段
            for node_name, node_data in chunk.items():
                # 个别更新可能是空的,跳过
                if node_data is None:
                    continue

                # 本演示只关心「带 messages 的更新」;
                # 若节点只改了 todos 等,没有 messages,整段 if 不进 → 终端上就像「少了 Step」
                if "messages" in node_data:
                    # 取出本节点新增/带回的消息列表(updates 模式下通常是本步新消息,不是全历史)
                    msgs = node_data["messages"]

                    # 防御:偶发单条消息而不是 list,先包成 list,后面统一 for 遍历
                    if not isinstance(msgs, list):
                        msgs = [msgs]

                    for msg in msgs:
                        # 0) 只要 LangChain 消息对象(HumanMessage / AIMessage / ToolMessage…)
                        #    其它奇怪类型直接跳过
                        if not isinstance(msg, BaseMessage):
                            continue

                        # 安全读 tool_calls:
                        # - AIMessage 可能带 tool_calls(模型决定调哪些工具)
                        # - HumanMessage / ToolMessage 通常没有该属性
                        # getattr(..., None) 避免 AttributeError;or [] 把 None/空统一成空列表
                        tool_calls = getattr(msg, "tool_calls", None) or []

                        # ---------- 分支 1:模型发起工具调用(常见于 Node=model)----------
                        # tool_calls 是一个列表,例如模型一次要调 4 个工具:
                        # [
                        #   {"name": "task", "args": {"subagent_type": "general-purpose", "description": "调研 Claude Code"}},
                        #   {"name": "task", "args": {"subagent_type": "general-purpose", "description": "调研 Python 多线程"}},
                        #   {"name": "write_todos", "args": {...}},
                        # ]
                        if tool_calls:
                            # Rich Tree:树形打印「本步调了哪些工具」
                            tree = Tree(
                                f"[bold yellow]Step {step}: 决策与调用 (Node: {node_name}) [/bold yellow]"
                            )

                            # 一条 AIMessage 里可以并行请求多个工具(本 demo 里常见一次 4 个 task)
                            # 取出工具名和参数(兼容两种格式)
                            # tc 是字典    tc["name"]、tc["args"](用 .get 防缺 key)
                            # tc 是对象    tc.name、tc.args(用 getattr 防缺属性)
                            for tc in tool_calls:
                                # LangChain 里 tool_call 有时是 dict,有时是对象,两种都兼容
                                if isinstance(tc, dict):
                                    tool_name = tc.get("name", "未知工具")  # 如 "task" / "write_todos"
                                    tool_args = tc.get("args", {})  # 调用参数字典
                                else:
                                    tool_name = getattr(tc, "name", "未知工具")
                                    tool_args = getattr(tc, "args", {}) or {}

                                if tool_name == "task":
                                    # task = SubAgentMiddleware 注入的「派生子 Agent」工具
                                    # 典型 args:subagent_type="general-purpose", description="调研xxx"
                                    # Step 7: 决策与调用 (Node: model)     ← 这是外面已建好的 tree 根
                                    # └── 成功触发 'task' 工具 (Sub-Agent)  ← branch = tree.add(...)
                                    #     ├── 子Agent类型: general-purpose ← branch.add(...)
                                    #     └── 任务指令: 调研 xxx           ← branch.add(...)
                                    branch = tree.add(
                                        "[bold red]成功触发 'task' 工具 (Sub-Agent)[/bold red]"
                                    )
                                    # tree.add 返回子节点,可再 .add 挂详情
                                    branch.add(
                                        f"[cyan]子Agent类型:[/cyan] {tool_args.get('subagent_type', '未知')}"
                                    )
                                    branch.add(
                                        f"[cyan]任务指令:[/cyan] {tool_args.get('description', '未知')}"
                                    )
                                else:
                                    # 其它工具(write_todos、tavily_search…)一律标成普通工具
                                    tree.add(f"[bold blue]普通工具调用:[/bold blue] {tool_name}")
                            console.print(tree)  # 把整棵树打到终端

                        # ---------- 分支 2:工具执行结果(常见于 Node=tools)----------
                        # ToolMessage:某个工具跑完后的返回;.name 是工具名,.content 是返回正文
                        # 判断当前消息是不是[工具回执],只有工具跑完后,图才会产生ToolMessage
                        elif isinstance(msg, ToolMessage):
                            content = str(msg.content)  # 统一转成字符串再展示
                            # task表示是子Agent的结果
                            if msg.name == "task":
                                # 子 Agent 整段任务做完后,只把「最终摘要/报告」回传给主 Agent
                                # (子 Agent 内部搜索过程默认不会全部冒泡到这里)
                                console.print(
                                    Panel(
                                        content,
                                        title=f"[bold magenta]Sub-Agent 完成任务 (Node: {node_name})[/bold magenta]",
                                        border_style="magenta",
                                    )
                                )
                            else:
                                # 非 task 工具(如 write_todos):内容可能很长,只预览前 100 字
                                preview = content[:100] + ("..." if len(content) > 100 else "")
                                console.print(f"[dim]Tool Output ({msg.name}): {preview}[/dim]")

                        # ---------- 分支 3:模型纯文本回复(无工具调用)----------
                        # 条件:有 content,且 tool_calls 为空 → 多半是对用户的最终/中间文字回答
                        elif getattr(msg, "content", None) and not tool_calls:
                            title = f"[bold green]Agent回复 (Node: {node_name})[/bold green]"
                            console.print(Panel(msg.content, title=title, border_style="green"))

    except Exception as e:
        console.print(f"[bold red]运行时错误:{e}[/bold red]")

    console.print("\n[bold magenta]演示结束[/bold magenta]")


if __name__ == "__main__":
    # 模块顶层不能直接 await,要用 asyncio.run 驱动协程
    asyncio.run(run_auto_subagent())

输出:

D:\anaconda3\envs\deepagents\python.exe E:\code\InsightFlow\create_demo_deep_agent\demo_sub_agent_middleware.py 
┌──────────────────────────────────┐
│ DeepAgents自动subAgent中间件演示 │
└──────────────────────────────────┘
本演示验证:即使不传入subagents参数,Agent默认也会启动 'general-purpose' 子 
Agent 
正在创建Agent(subagents=None)...
开始流式输出...
Step 3: 决策与调用 (Node: model) 
└── 普通工具调用: write_todos
Tool Output (write_todos): Updated todo list to [{'content': '调研 Claude Code 
技术架构', 'status': 'in_progress'}, {'content': '调研 P...
Step 7: 决策与调用 (Node: model) 
├── 成功触发 'task' 工具 (Sub-Agent)
│   ├── 子Agent类型: general-purpose
│   └── 任务指令: Claude Code 技术架构调研
├── 成功触发 'task' 工具 (Sub-Agent)
│   ├── 子Agent类型: general-purpose
│   └── 任务指令: Python 多线程机制调研
├── 成功触发 'task' 工具 (Sub-Agent)
│   ├── 子Agent类型: general-purpose
│   └── 任务指令: Python 性能差原因调研
└── 成功触发 'task' 工具 (Sub-Agent)
    ├── 子Agent类型: general-purpose
    └── 任务指令: Open Claude 小龙虾技术架构调研
┌───────────────────── Sub-Agent 完成任务 (Node: tools) ──────────────────────┐
│ # 🔍 Python 性能差原因深度调研报告                                          │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 一、核心架构层面(语言设计决定的天花板)                                 │
│                                                                             │
│ ### 1. 解释执行(Interpreted Execution)                                    │
│                                                                             │
│ 这是 Python 慢的**根本原因**。Python 代码的执行路径比 C/C++                 │
│ 等编译语言多了一个中间层:                                                  │
│                                                                             │
│ ```                                                                         │
│ Python:  源码 → 字节码(.pyc) → PVM解释器逐条解释 → CPU执行                  │
│ C/C++:   源码 → 直接编译为机器码 → CPU直接执行                              │
│ ```                                                                         │
│                                                                             │
│ 每次运行 Python 代码时,解释器都要做 **"读取字节码 → 解析 → 派发 → 执行"**  │
│ 的循环,这个 **dispatch loop** 本身消耗巨大。而 C 代码编译后直接就是 CPU    │
│ 指令,无需中间转换。                                                        │
│                                                                             │
│ ### 2. 动态类型(Dynamic Typing)                                           │
│                                                                             │
│ 这是 Python 最大的性能杀手之一:                                            │
│                                                                             │
│ | 对比维度 | 静态语言 (C/C++/Rust) | Python |                               │
│ |---------|----------------------|--------|                                 │
│ | 类型确定时机 | 编译期 | 运行期 |                                          │
│ | `a + b` 操作 | 编译成单条 CPU 加法指令 | 运行时查找                       │
│ `a.__add__`,检查类型,再执行 |                                             │
│ | 编译优化 | 可以做大量优化(内联、向量化) | 几乎无法做编译期优化 |        │
│                                                                             │
│ ```python                                                                   │
│ a = 1                                                                       │
│ b = 2                                                                       │
│ c = a + b  # 运行时要做:type(a) → int, type(b) → int, 查找 __add__, 调用,  │
│ 返回新对象                                                                  │
│ ```                                                                         │
│                                                                             │
│ 同一个 `+` 操作,Python 背后要经历:类型检查 → 方法查找 → 函数调用 →        │
│ 新对象分配,而 C 只是一条 CPU 指令。                                        │
│                                                                             │
│ > 一项微基准测试显示:在 Python 的执行时间中,约 **6%**                     │
│ 花在动态类型检查上(这还只是类型检查本身,不含连锁影响)。                  │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 二、内存模型层面                                                         │
│                                                                             │
│ ### 3. 万物皆对象(Everything is a PyObject)                               │
│                                                                             │
│ 在 C 中,一个 `int` 就是 4 或 8 字节的一块内存。而在 Python                 │
│ 中,即使是最简单的整数,也是一个复杂的结构体:                              │
│                                                                             │
│ ```                                                                         │
│ C int:       [4 bytes 存储值]                                               │
│ Python int:  [PyObject_HEAD (refcount + type指针)] + [实际数值]             │
│               ↑ 至少 28 字节 (64位系统)                                     │
│ ```                                                                         │
│                                                                             │
│ **内存开销对比**:                                                          │
│ - C 语言 `int`:4 字节                                                      │
│ - Python `int`:**28+ 字节**(包含引用计数、类型指针、值本身)              │
│                                                                             │
│ 这意味着:                                                                  │
│ - 同样的数据,Python 占用 5~7 倍内存                                        │
│ - CPU 缓存命中率大幅降低                                                    │
│ - 每次操作都要分配/释放堆内存                                               │
│                                                                             │
│ ### 4. 引用计数垃圾回收(Reference Counting GC)                            │
│                                                                             │
│ 每个 Python 对象的 `PyObject_HEAD` 中都包含一个 `refcount`                  │
│ 字段。**每一次**引用变化都会触发原子增减操作:                              │
│                                                                             │
│ ```python                                                                   │
│ a = obj      # obj->refcount += 1  (原子操作,有开销)                       │
│ b = a        # obj->refcount += 1                                           │del a        # obj->refcount -= 1                                           │
│ ```                                                                         │
│                                                                             │
│ 在一段密集型计算中,引用计数的原子操作可能占据 **10-30%**                   │
│ 的执行时间。这也是为什么 Gilectomy 项目(移除 GIL)在单线程场景下反而慢了约 │
│ 30% 的原因——原子引用计数操作太昂贵。                                        │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 三、并发层面                                                             │
│                                                                             │
│ ### 5. 全局解释器锁(GIL - Global Interpreter Lock)                        │
│                                                                             │
│ GIL 是 CPython 中最大的并发瓶颈:                                           │
│                                                                             │
│ > **GIL 是一把互斥锁,保证同一时刻只有一个线程在执行 Python 字节码。**      │
│                                                                             │
│ ```                                                                         │
│ 多核 CPU:  [Core 0: 执行] [Core 1: 等待GIL] [Core 2: 等待GIL] [Core 3:      │
│ 等待GIL]                                                                    │
│            ↑ 只有一个是真正在工作!                                         │
│ ```                                                                         │
│                                                                             │
│ **GIL 的影响**:                                                            │
│                                                                             │
│ | 任务类型 | 受 GIL 影响 | 说明 |                                           │
│ |---------|------------|------|                                             │
│ | CPU 密集型(计算、图像处理) | ❌ **严重影响** |                          │
│ 多线程反而可能比单线程更慢 |                                                │
│ | I/O 密集型(网络请求、文件读写) | ✅ 影响较小 | I/O 操作时会释放 GIL |   │
│                                                                             │
│ **绕开 GIL 的方式**:                                                       │
│ - `multiprocessing`:多进程(每个进程有自己独立的 GIL)                     │
│ - C 扩展:在 C 代码中可手动释放 GIL                                         │
│ - 替代实现:PyPy、Jython、IronPython 无 GIL                                 │
│ - Python 3.13+:PEP 703 引入了可选的 **GIL-free 模式**                      │
│                                                                             │
│ > ⚠️ 注意:GIL 只在 **CPython**(最主流实现)中存在,是 CPython             │
│ 为了简化内存管理(避免给每个对象加锁)所做的权衡。                          │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 四、运行时开销                                                           │
│                                                                             │
│ ### 6. 属性/方法查找开销                                                    │
│                                                                             │
│ Python 每个 `.` 操作符都意味着运行时的**字典查找**:                        │
│                                                                             │
│ ```python                                                                   │
│ result = []                                                                 │
│ for word in words:                                                          │
│     result.append(word.upper())  # 每次循环:查找 result.append → 查找      │
│ word.upper → 两次dict查找                                                   │
│ ```                                                                         │
│                                                                             │
│ 对于 100 万次循环,这就是 **200 万次字典查找**!优化方式是缓存方法引用:    │
│                                                                             │
│ ```python                                                                   │
│ append = result.append                                                      │
│ upper = str.upper                                                           │
│ for word in words:                                                          │
│     append(upper(word))  # 直接引用,无查找开销                             │
│ ```                                                                         │
│                                                                             │
│ ### 7. 函数调用开销巨大                                                     │
│                                                                             │
│ 每次 Python 函数调用都涉及:                                                │
│ - 创建栈帧(stack frame)                                                   │
│ - 参数打包与解包(打包成 tuple,被调用方再解析)                            │
│ - 递归深度检查                                                              │
│                                                                             │
│ 一项分析显示,在典型的 Python 代码中,**参数传递**占用了约 **31%**          │
│ 的执行时间,与"真正干活的时间"几乎持平。                                    │
│                                                                             │
│ ### 8. 没有 JIT 编译器(CPython)                                           │
│                                                                             │
│ | 语言/实现 | 执行方式 |                                                    │
│ |----------|---------|                                                      │
│ | **CPython** | 纯解释执行字节码 |                                          │
│ | **Java (JVM)** | JIT 编译热点代码为机器码 |                               │
│ | **JavaScript (V8)** | JIT 编译 + 内联缓存 |                               │
│ | **PyPy** | 跟踪 JIT(Tracing JIT),比 CPython 快 4-7 倍 |                │
│ | **Python 3.14 (PEP 744)** | 正在引入 Copy-and-Patch JIT |                 │
│                                                                             │
│ CPython 作为参考实现,选择了**简单性优先于性能**,一直未内置 JIT。          │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 五、开发者常见误区(可避免的性能坑)                                     │
│                                                                             │
│ | 问题 | 慢的做法 | 优化做法 |                                              │
│ |------|---------|---------|                                                │
│ | 字符串拼接 | `s += substr` 在循环中(每次创建新对象) | `''.join(list)` | │
│ | 查找操作 | 用 `list` 做成员检查 O(n) | 用 `set`/`dict` O(1) |             │
│ | 全局变量 | 频繁访问 `global` 变量 | 用局部变量缓存 |                      │
│ | 循环方式 | `range()` 返回完整列表(Python 2) | `xrange()` 或 Python 3 的 │
│ `range()` |                                                                 │
│ | 函数调用 | 循环内频繁调用小函数 | 内联或批量处理 |                        │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 六、总结:各因素影响权重                                                 │
│                                                                             │
│ 基于实际的微基准测试分析:                                                  │
│                                                                             │
│ ```                                                                         │
│ 参数传递/解析         ████████████████████████████  31%                     │
│ 真正计算工作          ████████████████████████████  31%                     │
│ 整数装箱拆箱          ████████████                  13%                     │
│ 解释器 dispatch 开销  ███████████                   11%                     │
│ 递归限制检查          ████████                      8%                      │
│ 动态类型检查          ██████                        6%                      │
│ ```                                                                         │
│                                                                             │
│ > *数据来源:kmod 的 Python 性能分析博客*                                   │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 七、关键结论                                                             │
│                                                                             │
│ 1. **Python 慢不是 bug,是设计取舍** —— 用性能换取开发效率、灵活性和可读性  │
│ 2.                                                                          │
│ **核心矛盾**:动态特性(动态类型、动态属性、运行时修改)从根本上限制了编译  │
│ 优化的可能性                                                                │
│ 3. **通用规律**:Python 比 C 慢 **10~100 倍**不等,但开发效率通常高出       │
│ **3~10 倍**                                                                 │
│ 4. **未来方向**:Python 3.13 的 GIL-free 模式 + Python 3.14 的 JIT          │
│ 编译器正在逐步改善局面                                                      │
│ 5. **实战策略**:CPU 密集型部分用 C 扩展 / Cython / Rust (PyO3)             │
│ 实现,Python 做胶水层                                                       │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────── Sub-Agent 完成任务 (Node: tools) ──────────────────────┐
│ 现在我已经收集了足够的信息,下面为你整理一份 Claude Code                    │
│ 技术架构的全面调研报告。                                                    │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ # Claude Code 技术架构调研报告                                              │
│                                                                             │
│ ## 一、概述                                                                 │
│                                                                             │
│ Claude Code 是 Anthropic 推出的**终端优先(Terminal-first)的自主编程 Agent │
│ 工具**,它不是一个简单的 CLI                                                │
│ 包装器,而是一个围绕"单线程主循环"构建的六层架构系统。其核心理念是 **"Less  │
│ Scaffolding, More                                                           │
│ Model"**——让模型自身承担推理和决策,而非依赖复杂的硬编码编排逻辑。          │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 二、技术栈                                                               │
│                                                                             │
│ | 层级 | 技术选型 | 选型理由 |                                              │
│ |------|----------|----------|                                              │
│ | **语言** | TypeScript | Claude 模型对 TS                                  │
│ 极其精通,处于模型的"分布内(on-distribution)" |                           │
│ | **终端 UI** | React + **Ink** | Ink 将 React                              │
│ 组件模型带到命令行,提供类浏览器组件化体验 |                                │
│ | **布局引擎** | Yoga(Meta 开源) | 基于 Flexbox                           │
│ 的约束布局,解决终端窗口尺寸适配问题 |                                      │
│ | **构建/打包** | Bun | 相比 Webpack/Vite 等构建速度更快 |                  │
│ | **分发** | npm | Node 生态最大包管理器,兼容性最好 |                      │
│ | **AI 模型** | Claude Sonnet(默认) | API 级调用 Anthropic 模型 |         │
│ | **自举率** | ~90% 代码由 Claude Code 自己编写 |                           │
│ 技术栈刻意选择模型擅长的领域 |                                              │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 三、核心架构:六层体系                                                   │
│                                                                             │
│ Claude Code 的系统架构分为**六个层次**,以单线程主循环(Master              │
│ Loop)为核心:                                                              │
│                                                                             │
│ ```                                                                         │
│ ┌──────────────────────────────────────────────┐                            │
│ │  Layer 1:  输入层 (Input Layer)               │                           │
│ │  Layer 2:  知识层 (Knowledge Layer)           │                           │
│ │  Layer 3:  执行层 (Execution Layer)           │                           │
│ │  Layer 4:  集成层 (Integration Layer)         │                           │
│ │  Layer 5:  多智能体层 (Multi-Agent Layer)     │                           │
│ │  Layer 6:  模型层 (Model Layer)               │                           │
│ │         ★ 主循环 (Master Loop) 贯穿始终        │                          │
│ └──────────────────────────────────────────────┘                            │
│ ```                                                                         │
│                                                                             │
│ ### 3.1 主循环(Master Loop / "nO")— 架构核心                              │
│                                                                             │
│ 这是整个系统的"心脏",代码逻辑极其简单,Anthropic 称之为 **"Dumb            │
│ Loop"(哑循环)**:                                                         │
│                                                                             │
│ ```                                                                         │
│ while (claude_response.has_tool_call):                                      │
│     result = execute_tool(tool_call)                                        │
│     claude_response = send_to_claude(result)                                │
│ return claude_response.text                                                 │
│ ```                                                                         │
│                                                                             │
│ **设计哲学:**                                                              │
│ - **没有** Intent Classifier(意图分类器)                                  │
│ - **没有** Task Router(任务路由器)                                        │
│ - **没有** RAG/Embedding Pipeline                                           │
│ - **没有** DAG Orchestrator                                                 │
│ - **没有** Planner/Executor Split                                           │
│                                                                             │
│ >                                                                           │
│ 模型自己决定何时调用工具、调用哪个工具、何时结束。所有智能都在模型权重中,  │
│ 循环只负责调度。                                                            │
│                                                                             │
│ ### 3.2 Layer 1 — 输入层(Input Layer)                                     │
│                                                                             │
│ - **会话管理**:管理多轮对话的生命周期                                      │
│ - **权限门控**:在请求到达模型前的权限检查                                  │
│ - **YAML 信任等级**:基于 YAML 配置的多级信任策略                           │
│ - **消息队列 "h2A"**:异步事件处理                                          │
│                                                                             │
│ ### 3.3 Layer 2 — 知识层(Knowledge Layer)                                 │
│                                                                             │
│ 这是 Claude Code "超越模型权重"的智能所在:                                 │
│                                                                             │
│ - **Skill Registry(技能注册表)**:可扩展的技能系统                        │
│ - **Task Graph(任务图)**:跟踪任务依赖和状态                              │
│ - **Cross-Session Memory(跨会话记忆)**:                                  │
│   - `CLAUDE.md`(~500 行限制)— 项目级持久记忆                              │
│   - `agent_memory.md` — 跨会话持久化                                        │
│ - **Context Compressor(上下文压缩器)**:                                  │
│   - **三层压缩体系**:92% 作为压缩触发阈值                                  │
│   - Tier 1:截断旧消息                                                      │
│   - Tier 2:用结构化摘要替换冗长输出                                        │
│   - Tier 3:LLM 驱动的上下文总结(9                                         │
│ 个维度:当前状态、目标意图、近期变更、关键决策、活跃工作、关键文件、经验教  │
│ 训、重要上下文、可选后续步骤)                                              │
│   - **关键约束**:压缩时禁止调用工具;绝不拆分 `tool_use` / `tool_result`   │
│ 配对                                                                        │
│                                                                             │
│ ### 3.4 Layer 3 — 执行层(Execution Layer)                                 │
│                                                                             │
│ **工具系统(Tool Arsenal):**                                              │
│                                                                             │
│ | 工具类别 | 工具 | 说明 |                                                  │
│ |----------|------|------|                                                  │
│ | **读取发现** | `View`(Read) | 文件读取,默认 ~2000 行 |                 │
│ | | `LS` | 目录列表 |                                                       │
│ | | `Glob` | 通配符文件搜索 |                                               │
│ | | `GrepTool` | 全正则搜索(类 ripgrep),**不使用向量数据库** |           │
│ | **编辑操作** | `Edit` | 精确字符串替换(old_string → new_string) |       │
│ | | `Write` | 创建新文件 |                                                  │
│ | **执行** | `Bash` | Shell 命令执行 |                                      │
│ | **规划** | `write_todos` | TODO 任务规划与跟踪 |                          │
│ | **子代理** | `Task` | 生成子代理处理独立任务 |                            │
│                                                                             │
│ **核心设计决策:不使用向量数据库/RAG**。原因:Claude                        │
│ 模型对代码结构的理解能力极强,可以直接生成复杂的正则表达式,无需维护搜索索  │
│ 引的运维开销。                                                              │
│                                                                             │
│ **编辑工具的独特设计:**                                                    │
│ - 使用 `old_string → new_string` 的精确替换,而非行号编辑                   │
│ - 避免行号漂移问题                                                          │
│                                                                             │
│ ### 3.5 Layer 4 — 集成层(Integration Layer)                               │
│                                                                             │
│ - **MCP Runtime**:连接外部 MCP 服务器(文件系统、Git、Jira、GitHub 等)    │
│ - **Hook                                                                    │
│ 系统**:在生命周期事件触发(工具执行前后、会话边界、权限请求、压缩等)      │
│ - **流式运行时**:处理并行执行和 token 流                                   │
│ - **Prompt Cache**:复用稳定前缀,成本降至约 10%                            │
│                                                                             │
│ ### 3.6 Layer 5 — 多智能体层(Multi-Agent Layer)                           │
│                                                                             │
│ 支持**两级并行**:                                                          │
│                                                                             │
│ | 级别 | 说明 |                                                             │
│ |------|------|                                                             │
│ | **Subagents(子代理)** | 独立上下文窗口,严格深度限制,不继承会话历史 |  │
│ | **Agent Teams(代理团队)** | 更高层级的协调与任务分发 |                  │
│                                                                             │
│ **子代理设计原则:**                                                        │
│ - **隔离上下文**:子代理拥有独立上下文窗口,不污染主会话                    │
│ - **深度限制**:防止无限递归代理生成                                        │
│ - **受约束的工具集**:子代理只能使用指定的工具子集                          │
│ - **权衡**:总 Token 消耗会增加(因代理间通信),但可显著减少主会话压缩次数 │
│                                                                             │
│ ### 3.7 Layer 6 — 模型层(Model Layer)                                     │
│                                                                             │
│ - **Claude Sonnet** 作为默认推理引擎                                        │
│ - 模型只产生 token 流和工具调用决策,**不直接执行任何操作**                 │
│ - 职责分离:                                                                │
│   - 模型 → 决定**做什么**                                                   │
│   - 权限层 → 决定**是否允许**                                               │
│   - 工具层 → 决定**如何执行**                                               │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 四、权限与安全模型                                                       │
│                                                                             │
│ 三级权限门控(Deny-First 设计):                                           │
│                                                                             │
│ | 级别 | 行为 |                                                             │
│ |------|------|                                                             │
│ | **Tier 1** | 默认拒绝,逐步授权 |                                         │
│ | **Tier 2** | 基于规则的自动允许(如安全文件路径) |                       │
│ | **Tier 3** | 完全信任模式(用户显式配置) |                               │
│                                                                             │
│ **架构级安全原则:** 将所有外部输入(MCP                                    │
│ 结果、文件内容、工具返回值)视为不可信。                                    │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 五、上下文管理与压缩                                                     │
│                                                                             │
│ ### 核心挑战                                                                │
│ 编码过程中大量文件读取、工具输出、日志很容易耗尽 200K 上下文窗口。          │
│                                                                             │
│ ### 三层压缩策略                                                            │
│                                                                             │
│ ```                                                                         │
│ Tier 1: 裁剪(Truncation)                                                  │
│   └─ 直接丢弃最旧的消息块                                                   │
│                                                                             │
│ Tier 2: 结构化替换                                                          │
│   └─ 用结构化摘要替换冗长的工具输出                                         │
│                                                                             │
│ Tier 3: LLM 摘要(触发阈值 ~92%)                                           │
│   └─ 调用 Claude Haiku 生成 9 维度结构化摘要                                │
│   └─ 关键:tools: [] 禁用工具调用,纯摘要模式                               │
│   └─ 绝不拆分 tool_use/tool_result 配对                                     │
│ ```                                                                         │
│                                                                             │
│ ### 子代理策略                                                              │
│ 将独立任务委托给子代理是**保持主会话上下文干净**的关键手段。子代理拥有独立  │
│ 上下文,完成后只返回结果摘要。                                              │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 六、扩展体系(Extension Stack)                                          │
│                                                                             │
│ Claude Code 提供六层扩展体系:                                              │
│                                                                             │
│ | 层级 | 特性 | 类型 |                                                      │
│ |------|------|------|                                                      │
│ | 1. CLAUDE.md | 项目记忆文件 | 确定性 |                                    │
│ | 2. Slash Commands | 自定义斜杠命令 | 确定性 |                             │
│ | 3. Skills | 可组合的技能模块 | 概率性 |                                   │
│ | 4. Hooks | 生命周期事件钩子 | 概率性 |                                    │
│ | 5. Subagents | 子代理委托 | 概率性 |                                      │
│ | 6. Agent Teams | 多代理协调 | 概率性 |                                    │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 七、关键设计哲学总结                                                     │
│                                                                             │
│ 1. **极简核心**:主循环简单到被称为"哑循环",智能完全来自模型               │
│ 2. **模型驱动**:每个新模型发布后,团队会**删除**模型不再需要的脚手架代码   │
│ 3. **可调试性优先**:扁平历史 + 单线程 >>                                   │
│ 多智能体并发(在没有强大可观测性工具的情况下)                              │
│ 4. **安全默认拒绝**:权限系统采用 deny-first 策略                           │
│ 5. **去复杂化**:不使用 RAG/向量数据库/意图分类器等常见 Agent 组件          │
│ 6. **自我迭代**:技术栈刻意选择模型擅长的 TypeScript,使 ~90% 代码由 Claude │
│ Code 自身编写                                                               │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 八、参考资料                                                             │
│                                                                             │
│ 1. [How Claude Code is built — Pragmatic                                    │
│ Engineer](https://newsletter.pragmaticengineer.com/p/how-claude-code-is-bui │
│ lt)                                                                         │
│ 2. [Claude Code Agent Architecture —                                        │
│ ZenML](https://www.zenml.io/llmops-database/claude-code-agent-architecture- │
│ single-threaded-master-loop-for-autonomous-coding)                          │
│ 3. [Claude Code's Architecture Explained Visually — Daily Dose of Data      │
│ Science](https://blog.dailydoseofds.com/p/claude-codes-architecture-explain │
│ ed)                                                                         │
│ 4. [System Architecture —                                                   │
│ DeepWiki](https://deepwiki.com/anthropics/claude-code/1.1-system-architectu │
│ re)                                                                         │
│ 5. [How Claude Code Works: Architecture & Internals —                       │
│ GitHub](https://github.com/FlorianBruniaux/claude-code-ultimate-guide/blob/ │
│ main/guide/core/architecture.md)                                            │
│ 6. [The System Design of Claude Code Agent Explained —                      │
│ Medium](https://medium.com/@milesk_33/the-system-design-of-claude-code-agen │
│ t-explained-318d17496534)                                                   │
│ 7. [Claude Code Official                                                    │
│ Docs](https://code.claude.com/docs/en/features-overview)                    │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────── Sub-Agent 完成任务 (Node: tools) ──────────────────────┐
│ # Python 多线程机制深度调研报告                                             │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 一、核心概念:GIL(全局解释器锁)                                        │
│                                                                             │
│ ### 1.1 什么是 GIL?                                                        │
│                                                                             │
│ **GIL(Global Interpreter Lock,全局解释器锁)** 是 CPython                 │
│ 解释器中的一个互斥锁(mutex),它确保**同一时刻只有一个线程在执行 Python    │
│ 字节码**。这意味着即使在多核 CPU 上,Python                                 │
│ 多线程程序也无法实现真正的并行执行。                                        │
│                                                                             │
│ ```                                                                         │
│ ┌──────────────────────────────────────────────────┐                        │
│ │                   CPython 进程                     │                      │
│ │  ┌─────────┐  ┌─────────┐  ┌─────────┐           │                        │
│ │  │ Thread1 │  │ Thread2 │  │ Thread3 │           │                        │
│ │  └────┬────┘  └────┬────┘  └────┬────┘           │                        │
│ │       │            │            │                 │                       │
│ │       └────────────┼────────────┘                 │                       │
│ │                    │                              │                       │
│ │              ┌─────▼──────┐                       │                       │
│ │              │    GIL     │  ← 同一时刻仅一个线程   │                     │
│ │              │  持有锁     │     能执行字节码       │                     │
│ │              └─────┬──────┘                       │                       │
│ │                    │                              │                       │
│ │              ┌─────▼──────┐                       │                       │
│ │              │  Python    │                       │                       │
│ │              │  解释器     │                       │                      │
│ │              └────────────┘                       │                       │
│ └──────────────────────────────────────────────────┘                        │
│ ```                                                                         │
│                                                                             │
│ ### 1.2 为什么需要 GIL?                                                    │
│                                                                             │
│ | 原因 | 说明 |                                                             │
│ |------|------|                                                             │
│ | **内存管理简化** | CPython 使用引用计数进行垃圾回收,GIL                  │
│ 避免了多线程并发修改引用计数的竞争条件 |                                    │
│ | **C 扩展兼容性** | 大量 C 扩展库假定单线程访问,GIL 保证了向后兼容 |      │
│ | **单线程性能** | 去掉 GIL                                                 │
│ 后需要细粒度锁,会显著拖慢单线程程序性能(历史实验显示下降约 30%) |        │
│                                                                             │
│ ### 1.3 GIL 的调度机制                                                      │
│                                                                             │
│ Python 使用 **"check and balance"** 机制:                                  │
│                                                                             │
│ ```python                                                                   │
│ # 伪代码:GIL 的工作方式                                                    │# 在 Python 3.2+ 中,GIL 使用固定的时间片(默认 5ms)                       │# 当线程的时间片耗尽或遇到 I/O 操作时,会释放 GIL                           │
│                                                                             │
│ while True:                                                                 │
│     acquire_gil()           # 获取 GIL                                      │
│     execute_bytecode()      # 执行字节码(最多 5ms 或直到 I/O)             │
│     release_gil()           # 释放 GIL(给其他线程机会)                    │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 二、`threading` 模块 — 核心 API                                          │
│                                                                             │
│ ### 2.1 创建线程的两种方式                                                  │
│                                                                             │
│ ```python                                                                   │
│ import threading                                                            │
│ import time                                                                 │
│                                                                             │
│ # 方式一:传入目标函数                                                      │def worker(name, delay):                                                    │
│     """工作线程函数"""                                                      │
│     for i in range(3):                                                      │
│         print(f"[{name}] iteration {i}")                                    │
│         time.sleep(delay)                                                   │
│                                                                             │
│ t = threading.Thread(target=worker, args=("Thread-1", 0.5), name="Worker")  │
│ t.start()   # 启动线程                                                      │
│ t.join()    # 等待线程结束                                                  │
│                                                                             │
│ # 方式二:继承 Thread 类                                                    │class MyThread(threading.Thread):                                           │
│     def __init__(self, name, delay):                                        │
│         super().__init__(name=name)                                         │
│         self.delay = delay                                                  │
│                                                                             │
│     def run(self):                                                          │
│         for i in range(3):                                                  │
│             print(f"[{self.name}] iteration {i}")                           │
│             time.sleep(self.delay)                                          │
│                                                                             │
│ t2 = MyThread("CustomThread", 0.3)                                          │
│ t2.start()                                                                  │
│ t2.join()                                                                   │
│ ```                                                                         │
│                                                                             │
│ ### 2.2 Thread 对象关键方法                                                 │
│                                                                             │
│ | 方法 | 说明 |                                                             │
│ |------|------|                                                             │
│ | `start()` | 启动线程(只能调用一次) |                                    │
│ | `join(timeout=None)` | 阻塞等待线程结束 |                                 │
│ | `is_alive()` | 线程是否还在运行 |                                         │
│ | `name` / `getName()` | 获取线程名称 |                                     │
│ | `daemon` | 设为守护线程(主线程退出时自动终止) |                         │
│ | `ident` | 线程唯一标识符 |                                                │
│ | `native_id` | 操作系统原生线程 ID |                                       │
│                                                                             │
│ ### 2.3 守护线程 vs 非守护线程                                              │
│                                                                             │
│ ```python                                                                   │
│ import threading                                                            │
│ import time                                                                 │
│                                                                             │
│ def background_task():                                                      │
│     while True:                                                             │
│         print("守护线程工作中...")                                          │
│         time.sleep(1)                                                       │
│                                                                             │
│ # daemon=True: 主线程结束时,该线程被强制终止                               │
│ t = threading.Thread(target=background_task, daemon=True)                   │
│ t.start()                                                                   │
│                                                                             │
│ time.sleep(3)                                                               │
│ print("主线程结束,守护线程自动终止")                                       │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 三、线程同步原语(Synchronization Primitives)                           │
│                                                                             │
│ ### 3.1 Lock(互斥锁)                                                      │
│                                                                             │
│ 最基本的同步原语,确保共享资源的互斥访问:                                  │
│                                                                             │
│ ```python                                                                   │
│ import threading                                                            │
│                                                                             │
│ counter = 0                                                                 │
│ lock = threading.Lock()                                                     │
│                                                                             │
│ def increment(n):                                                           │
│     global counter                                                          │
│     for _ in range(n):                                                      │
│         with lock:          # 上下文管理器自动 acquire/release              │
│             counter += 1                                                    │
│                                                                             │
│ # 无锁情况下的竞态条件示例                                                  │def unsafe_increment(n):                                                    │
│     global counter                                                          │
│     for _ in range(n):                                                      │
│         counter += 1  # 非原子操作:读-改-写,可能产生竞态                  │
│ ```                                                                         │
│                                                                             │
│ ### 3.2 RLock(可重入锁)                                                   │
│                                                                             │
│ 允许同一线程多次获取同一把锁(常用于递归场景):                            │
│                                                                             │
│ ```python                                                                   │
│ rlock = threading.RLock()                                                   │
│                                                                             │
│ def recursive_func(n):                                                      │
│     with rlock:                                                             │
│         if n > 0:                                                           │
│             recursive_func(n - 1)  # 同一线程可再次获取锁                   │# 否则普通 Lock 会死锁!                                            │
│ ```                                                                         │
│                                                                             │
│ ### 3.3 Semaphore(信号量)                                                 │
│                                                                             │
│ 控制同时访问资源的线程数量:                                                │
│                                                                             │
│ ```python                                                                   │
│ # 最多允许 3 个线程同时访问资源                                             │
│ semaphore = threading.Semaphore(3)                                          │
│                                                                             │
│ def limited_access():                                                       │
│     with semaphore:                                                         │
│         print(f"{threading.current_thread().name} 正在访问资源")            │
│         time.sleep(2)                                                       │
│     # 离开 with 块时自动释放                                                │
│                                                                             │
│ # BoundedSemaphore: 防止 release() 被调用超过 acquire() 次数                │
│ bounded_sem = threading.BoundedSemaphore(3)                                 │
│ ```                                                                         │
│                                                                             │
│ ### 3.4 Event(事件)                                                       │
│                                                                             │
│ 线程间通信的简单机制,一个线程等待信号,另一个线程发送信号:                │
│                                                                             │
│ ```python                                                                   │
│ event = threading.Event()                                                   │
│                                                                             │
│ def waiter():                                                               │
│     print("等待事件...")                                                    │
│     event.wait()        # 阻塞直到 event.set()                              │print("事件已触发!")                                                   │
│     event.clear()       # 重置事件                                          │
│     event.wait(timeout=5)  # 超时等待                                       │
│                                                                             │
│ def setter():                                                               │
│     time.sleep(2)                                                           │
│     event.set()         # 触发事件,唤醒所有等待线程                        │
│                                                                             │
│ # Event 方法:set(), clear(), wait(), is_set()                              │
│ ```                                                                         │
│                                                                             │
│ ### 3.5 Condition(条件变量)                                               │
│                                                                             │
│ 比 Event 更强大,支持 notify/wait 模式:                                    │
│                                                                             │
│ ```python                                                                   │
│ condition = threading.Condition()                                           │
│ items = []                                                                  │
│                                                                             │
│ def producer():                                                             │
│     for i in range(5):                                                      │
│         with condition:                                                     │
│             items.append(f"item-{i}")                                       │
│             print(f"生产: item-{i}")                                        │
│             condition.notify()      # 唤醒一个等待的消费者                  │# condition.notify_all()  # 唤醒所有等待的消费者                │
│                                                                             │
│ def consumer():                                                             │
│     while True:                                                             │
│         with condition:                                                     │
│             while not items:                                                │
│                 condition.wait()    # 等待生产者通知                        │
│             item = items.pop(0)                                             │
│             print(f"消费: {item}")                                          │
│ ```                                                                         │
│                                                                             │
│ ### 3.6 Barrier(屏障)                                                     │
│                                                                             │
│ 让多个线程互相等待,直到所有线程都到达屏障点:                              │
│                                                                             │
│ ```python                                                                   │
│ # 3 个线程都到达后才能继续                                                  │
│ barrier = threading.Barrier(3)                                              │
│                                                                             │
│ def worker():                                                               │
│     print(f"{threading.current_thread().name} 阶段1完成")                   │
│     barrier.wait()  # 等待所有线程                                          │print(f"{threading.current_thread().name} 阶段2开始")                   │
│ ```                                                                         │
│                                                                             │
│ ### 3.7 Timer(定时器)                                                     │
│                                                                             │
│ 延迟执行任务:                                                              │
│                                                                             │
│ ```python                                                                   │
│ def delayed_task():                                                         │
│     print("延迟执行的任务")                                                 │
│                                                                             │
│ # 5 秒后执行                                                                │
│ timer = threading.Timer(5.0, delayed_task)                                  │
│ timer.start()                                                               │
│ timer.cancel()  # 可以取消                                                  │
│ ```                                                                         │
│                                                                             │
│ ### 3.8 同步原语对比总结                                                    │
│                                                                             │
│ | 原语 | 用途 | 特点 |                                                      │
│ |------|------|------|                                                      │
│ | **Lock** | 互斥访问 | 不可重入,简单高效 |                                │
│ | **RLock** | 递归互斥 | 同一线程可多次获取 |                               │
│ | **Semaphore** | 资源池控制 | 限制并发数 N |                               │
│ | **Event** | 信号通知 | 一对多广播 |                                       │
│ | **Condition** | 生产者-消费者 | 支持 notify/wait |                        │
│ | **Barrier** | 阶段同步 | N 个线程相互等待 |                               │
│ | **Timer** | 延迟执行 | 定时触发 |                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 四、`concurrent.futures` — 高层接口                                      │
│                                                                             │
│ ### 4.1 ThreadPoolExecutor                                                  │
│                                                                             │
│ ```python                                                                   │
│ from concurrent.futures import ThreadPoolExecutor, as_completed             │
│ import time                                                                 │
│ import urllib.request                                                       │
│                                                                             │
│ def fetch_url(url):                                                         │
│     """模拟 I/O 密集型任务"""                                               │
│     start = time.time()                                                     │
│     with urllib.request.urlopen(url) as response:                           │
│         data = response.read()                                              │
│     elapsed = time.time() - start                                           │
│     return f"{url}: {len(data)} bytes in {elapsed:.2f}s"                    │
│                                                                             │
│ urls = [                                                                    │
│     "https://www.python.org",                                               │
│     "https://www.github.com",                                               │
│     "https://www.stackoverflow.com",                                        │
│ ]                                                                           │
│                                                                             │
│ # 使用线程池                                                                │
│ with ThreadPoolExecutor(max_workers=5) as executor:                         │
│     # 方式1: submit + as_completed(按完成顺序获取结果)                    │
│     futures = {executor.submit(fetch_url, url): url for url in urls}        │
│     for future in as_completed(futures):                                    │
│         print(future.result())                                              │
│                                                                             │
│     # 方式2: map(保持输入顺序)                                            │# results = executor.map(fetch_url, urls)                               │# for result in results:                                                │#     print(result)                                                     │
│ ```                                                                         │
│                                                                             │
│ ### 4.2 Future 对象方法                                                     │
│                                                                             │
│ | 方法 | 说明 |                                                             │
│ |------|------|                                                             │
│ | `result(timeout=None)` | 获取返回值(阻塞等待) |                         │
│ | `exception(timeout=None)` | 获取异常(如有) |                            │
│ | `done()` | 任务是否完成 |                                                 │
│ | `running()` | 是否正在执行 |                                              │
│ | `cancel()` | 尝试取消任务 |                                               │
│ | `cancelled()` | 是否被取消 |                                              │
│ | `add_done_callback(fn)` | 完成时回调 |                                    │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 五、I/O 密集型 vs CPU 密集型                                             │
│                                                                             │
│ ### 5.1 核心区别                                                            │
│                                                                             │
│ ```                                                                         │
│ ┌─────────────────────────────────────────────────────────────┐             │
│ │                       任务类型对比                            │           │
│ ├───────────────┬─────────────────────┬───────────────────────┤             │
│ │    场景       │     I/O 密集型       │     CPU 密集型         │           │
│ ├───────────────┼─────────────────────┼───────────────────────┤             │
│ │ 典型操作      │ 网络请求、文件读写    │ 数学计算、图像处理     │          │
│ │              │ 数据库查询、爬虫      │ 加密、模型训练         │           │
│ ├───────────────┼─────────────────────┼───────────────────────┤             │
│ │ GIL 影响      │ ★ 影响小             │ ★★★★★ 影响巨大        │            │
│ │              │ (I/O 等待时释放 GIL)  │ (计算时不释放 GIL)     │           │
│ ├───────────────┼─────────────────────┼───────────────────────┤             │
│ │ 推荐方案      │ threading / asyncio  │ multiprocessing       │            │
│ ├───────────────┼─────────────────────┼───────────────────────┤             │
│ │ 性能提升      │ 线性 ~ N 倍          │ 几乎无提升             │           │
│ │              │                     │ (甚至变慢)            │            │
│ └───────────────┴─────────────────────┴───────────────────────┘             │
│ ```                                                                         │
│                                                                             │
│ ### 5.2 性能对比实验                                                        │
│                                                                             │
│ ```python                                                                   │
│ import threading                                                            │
│ import time                                                                 │
│ import math                                                                 │
│                                                                             │
│ # === CPU 密集型任务 ===                                                    │def cpu_bound(n=10_000_000):                                                │
│     """纯计算,持续持有 GIL"""                                              │
│     result = 0                                                              │
│     for i in range(n):                                                      │
│         result += math.sqrt(i)                                              │
│     return result                                                           │
│                                                                             │
│ # === I/O 密集型任务 ===                                                    │def io_bound():                                                             │
│     """网络/磁盘 I/O,等待时释放 GIL"""                                     │
│     import urllib.request                                                   │
│     with urllib.request.urlopen("https://www.python.org") as r:             │
│         return len(r.read())                                                │
│                                                                             │
│ # 单线程 baseline                                                           │def run_sequential(task, times=4):                                          │
│     start = time.perf_counter()                                             │
│     for _ in range(times):                                                  │
│         task()                                                              │
│     return time.perf_counter() - start                                      │
│                                                                             │
│ # 多线程                                                                    │def run_threaded(task, times=4):                                            │
│     start = time.perf_counter()                                             │
│     threads =                                                               │
│     for t in threads:                                                       │
│         t.start()                                                           │
│     for t in threads:                                                       │
│         t.join()                                                            │
│     return time.perf_counter() - start                                      │
│                                                                             │
│ # 结果对比(典型数据):                                                    │# CPU密集型: sequential=10s, threaded=11s  (几乎无提升,甚至变慢)           │# IO密集型:  sequential=8s,  threaded=2s   (接近 4x 提升)                   │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 六、`multiprocessing` — 绕过 GIL 的替代方案                              │
│                                                                             │
│ ### 6.1 核心思想                                                            │
│                                                                             │
│ 每个进程拥有独立的 Python 解释器和 GIL,真正实现并行:                      │
│                                                                             │
│ ```python                                                                   │
│ from multiprocessing import Process, Pool, cpu_count                        │
│ import time                                                                 │
│                                                                             │
│ def cpu_intensive(n):                                                       │
│     """CPU 密集型计算"""                                                    │
│     result = 0                                                              │
│     for i in range(n):                                                      │
│         result += i ** 2                                                    │
│     return result                                                           │
│                                                                             │
│ # 方式1: Process(类似 Thread)                                             │
│ processes = []                                                              │
│ for i in range(cpu_count()):                                                │
│     p = Process(target=cpu_intensive, args=(5_000_000,))                    │
│     p.start()                                                               │
│     processes.append(p)                                                     │
│ for p in processes:                                                         │
│     p.join()                                                                │
│                                                                             │
│ # 方式2: Pool(推荐)                                                       │
│ with Pool(processes=cpu_count()) as pool:                                   │
│     results = pool.map(cpu_intensive, [5_000_000] * cpu_count())            │
│ ```                                                                         │
│                                                                             │
│ ### 6.2 进程间通信                                                          │
│                                                                             │
│ ```python                                                                   │
│ from multiprocessing import Queue, Pipe, Value, Array, Manager              │
│                                                                             │
│ # Queue: 进程安全队列                                                       │
│ q = Queue()                                                                 │
│ q.put("data")                                                               │
│ item = q.get()                                                              │
│                                                                             │
│ # Pipe: 双向管道                                                            │
│ parent_conn, child_conn = Pipe()                                            │
│ parent_conn.send("hello")                                                   │
│ msg = child_conn.recv()                                                     │
│                                                                             │
│ # Value / Array: 共享内存(C 类型)                                         │
│ counter = Value('i', 0)      # int 类型                                     │
│ arr = Array('d', [1.0, 2.0]) # double 类型                                  │
│                                                                             │
│ # Manager: 高级共享对象                                                     │
│ with Manager() as manager:                                                  │
│     shared_list = manager.list()                                            │
│     shared_dict = manager.dict()                                            │
│ ```                                                                         │
│                                                                             │
│ ### 6.3 threading vs multiprocessing 对比                                   │
│                                                                             │
│ | 维度 | threading | multiprocessing |                                      │
│ |------|-----------|-----------------|                                      │
│ | **并行能力** | 受 GIL 限制 | 真正的并行 |                                 │
│ | **内存开销** | 低(共享内存) | 高(独立内存空间) |                      │
│ | **启动速度** | 快(毫秒级) | 慢(秒级) |                                │
│ | **通信方式** | 共享变量 + 锁 | Queue/Pipe/共享内存 |                      │
│ | **适用场景** | I/O 密集型 | CPU 密集型 |                                  │
│ | **调试难度** | 中等 | 较高 |                                              │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 七、`queue.Queue` — 线程安全队列                                         │
│                                                                             │
│ ```python                                                                   │
│ from queue import Queue, LifoQueue, PriorityQueue                           │
│ import threading                                                            │
│                                                                             │
│ # FIFO 队列(默认)                                                         │
│ q = Queue(maxsize=10)    # 有界队列                                         │
│ q.put(item, block=True, timeout=5)  # 阻塞放入                              │
│ item = q.get(block=True, timeout=5) # 阻塞取出                              │
│ q.task_done()            # 标记任务完成                                     │
│ q.join()                 # 等待所有任务完成                                 │
│                                                                             │
│ # LIFO 队列(栈)                                                           │
│ stack = LifoQueue()                                                         │
│                                                                             │
│ # 优先级队列                                                                │
│ pq = PriorityQueue()                                                        │
│ pq.put((2, "low priority"))                                                 │
│ pq.put((1, "high priority"))  # 数字越小,优先级越高                        │
│ ```                                                                         │
│                                                                             │
│ ### 生产者-消费者模式                                                       │
│                                                                             │
│ ```python                                                                   │
│ import threading                                                            │
│ from queue import Queue                                                     │
│ import time                                                                 │
│ import random                                                               │
│                                                                             │
│ def producer(queue, n_items):                                               │
│     for i in range(n_items):                                                │
│         item = f"data-{i}"                                                  │
│         queue.put(item)                                                     │
│         print(f"生产: {item}")                                              │
│         time.sleep(random.uniform(0.1, 0.5))                                │
│     queue.put(None)  # 哨兵值,通知消费者停止                               │
│                                                                             │
│ def consumer(queue, name):                                                  │
│     while True:                                                             │
│         item = queue.get()                                                  │
│         if item is None:      # 收到哨兵                                    │
│             queue.put(None)   # 传递给下一个消费者                          │break                                                           │
│         print(f"[{name}] 消费: {item}")                                     │
│         time.sleep(random.uniform(0.2, 0.8))                                │
│                                                                             │
│ q = Queue(maxsize=5)                                                        │
│ prod = threading.Thread(target=producer, args=(q, 10))                      │
│ cons =                                                                      │
│                                                                             │
│ prod.start()                                                                │
│ for c in cons:                                                              │
│     c.start()                                                               │
│ prod.join()                                                                 │
│ for c in cons:                                                              │
│     c.join()                                                                │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 八、Python 3.13 Free-Threading(重大变革)                               │
│                                                                             │
│ ### 8.1 历史性突破                                                          │
│                                                                             │
│ Python 3.13(2024 年 10 月发布)引入了 **free-threaded CPython              │
│ 构建**,首次在官方版本中允许禁用 GIL。这是 PEP 703 的实现成果。             │
│                                                                             │
│ ### 8.2 如何启用                                                            │
│                                                                             │
│ ```bash                                                                     │
│ # 安装 free-threaded 版本(独立的可执行文件)                               │# 通常命名为 python3.13t 或 python3.13t.exe                                 │
│                                                                             │
│ # 检查 GIL 状态                                                             │
│ python3.13t -c "import sys; print(sys._is_gil_enabled())"  # False          │
│                                                                             │
│ # 临时启用 GIL                                                              │
│ PYTHON_GIL=1 python3.13t script.py                                          │
│ # 或                                                                        │
│ python3.13t -X gil=1 script.py                                              │
│ ```                                                                         │
│                                                                             │
│ ### 8.3 性能提升实测                                                        │
│                                                                             │
│ 来自社区 benchmark(CPU 密集型多线程任务,4 核环境):                      │
│                                                                             │
│ | 模式 | 执行时间 | 加速比 |                                                │
│ |------|---------|--------|                                                 │
│ | 单线程(GIL 启/禁) | ~7.5s | 1x |                                        │
│ | 多线程(GIL 启用) | ~7.5s | **1x**(无提升) |                           │
│ | 多线程(GIL 禁用) | ~1.4s | **~5.4x** |                                  │
│ | 多进程(GIL 启/禁) | ~1.5s | ~5x |                                       │
│                                                                             │
│ **关键发现**:GIL                                                           │
│ 禁用后,多线程性能从几乎无提升跃升至接近多进程水平,且内存开销更低。        │
│                                                                             │
│ ### 8.4 注意事项                                                            │
│                                                                             │
│ 1. **向后兼容**:普通 Python 3.13 构建仍默认启用 GIL,free-threaded         │
│ 是独立构建                                                                  │
│ 2. **C 扩展兼容性**:部分 C 扩展尚未适配无 GIL 环境,可能自动重新启用 GIL   │
│ 3. **生态过渡期**:NumPy、pandas 等核心库正在适配中                         │
│ 4. **不建议生产使用**:3.13 的 free-threaded 仍标记为实验性功能             │
│ 5. **长期目标**:PEP 703 计划在未来版本中将 free-threaded 作为默认模式      │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 九、协程(asyncio)— 另一种并发模型                                      │
│                                                                             │
│ 虽然不是多线程,但 asyncio 是 Python 解决 I/O 密集型并发的现代方案:        │
│                                                                             │
│ ```python                                                                   │
│ import asyncio                                                              │
│ import aiohttp                                                              │
│                                                                             │
│ async def fetch(session, url):                                              │
│     async with session.get(url) as response:                                │
│         return await response.text()                                        │
│                                                                             │
│ async def main():                                                           │
│     urls = ["https://python.org", "https://github.com"]                     │
│     async with aiohttp.ClientSession() as session:                          │
│         tasks =                                                             │
│         results = await asyncio.gather(*tasks)                              │
│     return results                                                          │
│                                                                             │
│ asyncio.run(main())                                                         │
│ ```                                                                         │
│                                                                             │
│ | 方案 | 并发模型 | 适用场景 | 内存开销 |                                   │
│ |------|---------|----------|----------|                                    │
│ | **threading** | 抢占式多线程 | I/O 密集型 | 低 |                          │
│ | **multiprocessing** | 多进程 | CPU 密集型 | 高 |                          │
│ | **asyncio** | 协作式协程 | 高并发 I/O | 极低 |                            │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 十、最佳实践与选型建议                                                   │
│                                                                             │
│ ### 10.1 决策流程图                                                         │
│                                                                             │
│ ```                                                                         │
│ 需要并发/并行?                                                             │
│ │                                                                           │
│ ├── I/O 密集型?                                                            │
│ │   ├── 需要极高并发(万级连接)? → asyncio                                │
│ │   ├── 使用已有同步库? → threading / ThreadPoolExecutor                   │
│ │   └── 简单并发请求? → ThreadPoolExecutor                                 │
│ │                                                                           │
│ ├── CPU 密集型?                                                            │
│ │   ├── Python 3.13t 可用? → threading(GIL 禁用)                         │
│ │   └── 否则 → multiprocessing / ProcessPoolExecutor                        │
│ │                                                                           │
│ └── 混合型?                                                                │
│     └── multiprocessing + threading 嵌套使用                                │
│ ```                                                                         │
│                                                                             │
│ ### 10.2 常见坑与解决方案                                                   │
│                                                                             │
│ | 问题 | 解决方案 |                                                         │
│ |------|----------|                                                         │
│ | **竞态条件** | 使用 Lock / RLock 保护共享数据 |                           │
│ | **死锁** | 统一加锁顺序;使用 `RLock`;设置超时                           │
│ `lock.acquire(timeout=...)` |                                               │
│ | **线程泄露** | 使用 `ThreadPoolExecutor` 自动管理生命周期 |               │
│ | **GIL 瓶颈** | CPU 密集用 multiprocessing;I/O 密集用 threading/asyncio | │
│ | **过度上下文切换** | 线程数 ≈ CPU 核心数 × (1 + 等待时间/计算时间) |      │
│ | **守护线程数据丢失** | 关键任务使用非守护线程 + `join()` |                │
│                                                                             │
│ ### 10.3 线程池大小经验公式                                                 │
│                                                                             │
│ ```python                                                                   │
│ import os                                                                   │
│                                                                             │
│ # I/O 密集型:可以开较多线程                                                │
│ io_workers = min(32, os.cpu_count() + 4)                                    │
│                                                                             │
│ # CPU 密集型(传统 GIL 模式):多线程无效,使用多进程                       │
│ cpu_workers = os.cpu_count()                                                │
│                                                                             │
│ # CPU 密集型(Free-Threaded 模式):                                        │
│ free_thread_workers = os.cpu_count()                                        │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 十一、总结                                                               │
│                                                                             │
│ | 维度 | 总结 |                                                             │
│ |------|------|                                                             │
│ | **GIL 本质** | CPython                                                    │
│ 的内存管理简化机制,限制同一时刻仅一个线程执行字节码 |                      │
│ | **threading 适用** | I/O 密集型任务(网络、磁盘、数据库),通过时间片和   │
│ I/O 释放 GIL |                                                              │
│ | **multiprocessing 适用** | CPU 密集型任务,每个进程独立 GIL,实现真正并行 │
│ |                                                                           │
│ | **同步原语** | Lock/RLock/Semaphore/Event/Condition/Barrier               │
│ 覆盖各类并发控制场景 |                                                      │
│ | **高层接口** | `concurrent.futures` 提供 ThreadPoolExecutor /             │
│ ProcessPoolExecutor |                                                       │
│ | **Python 3.13 变革** | Free-Threaded 构建允许禁用 GIL,CPU                │
│ 密集型多线程性能提升 5x+ |                                                  │
│ | **未来趋势** | Free-Threaded 是长期方向,但生态完全适配仍需数年 |         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ > **参考资料**:Python 官方文档 (docs.python.org)、PEP 703、Python 3.13     │
│ Release Notes、社区 benchmark 报告                                          │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────── Sub-Agent 完成任务 (Node: tools) ──────────────────────┐
│ ---                                                                         │
│                                                                             │
│ 以上就是 OpenClaw(小龙虾)技术架构的完整调研报告。核心要点总结:           │
│                                                                             │
│ 1. **六层架构**:渠道适配 → Gateway控制平面 → Agent运行时(ReAct) →          │
│ 模型抽象层 → 能力触角(Skills+沙箱) → 数据持久层                             │
│ 2. **Gateway 是心脏**:Node.js 进程,负责会话路由、Lane Queue               │
│ 序列化队列、定时任务                                                        │
│ 3. **ReAct 循环**:边推理边行动,不是预先规划、死板执行                     │
│ 4. **Skills 按需加载**:解决 context 膨胀和 token 浪费的关键设计            │
│ 5. **双源记忆**:Markdown 文件(长期)+ JSONL 会话日志(动态)+ SQLite      │
│ 向量搜索                                                                    │
│ 6. **Heartbeat 主动唤醒**:区别于所有其他 Agent 框架的最大创新              │
│ 7. **安全多层防御**:网络隔离、工具白名单、Docker 沙箱、硬拦截危险指令      │
│                                                                             │
│ 如果你对某个具体组件(如记忆系统、Skills 开发、Gateway                      │
│ 实现细节等)想深入了解,可以继续提问。                                      │
└─────────────────────────────────────────────────────────────────────────────┘
Step 14: 决策与调用 (Node: model) 
└── 普通工具调用: write_todos
Tool Output (write_todos): Updated todo list to [{'content': '调研 Claude Code 
技术架构', 'status': 'completed'}, {'content': '调研 Pyt...
Step 18: 决策与调用 (Node: model) 
└── 普通工具调用: write_todos
Tool Output (write_todos): Updated todo list to [{'content': '调研 Claude Code 
技术架构', 'status': 'completed'}, {'content': '调研 Pyt...
┌────────────────────────── Agent回复 (Node: model) ──────────────────────────┐
│ ---                                                                         │
│                                                                             │
│ # 四主题调研报告                                                            │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 1. Claude Code 技术架构                                                  │
│                                                                             │
│ **Claude Code** 是 Anthropic 推出的**终端优先(Terminal-first)的自主编程   │
│ Agent**,采用 TypeScript + React(Ink 终端 UI 库)构建,通过 npm 分发。     │
│                                                                             │
│ ### 核心架构:六层体系 + 单线程主循环                                       │
│                                                                             │
│ | 层级 | 职责 |                                                             │
│ |------|------|                                                             │
│ | Layer 1 输入层 | 会话管理、权限门控、消息队列 |                           │
│ | Layer 2 知识层 | Skill注册表、跨会话记忆(CLAUDE.md)、上下文压缩器 |     │
│ | Layer 3 执行层 | 工具系统(Read/Write/Edit/Bash/Grep/Glob/Task/todos) |  │
│ | Layer 4 集成层 | MCP运行时、Hook系统、Prompt Cache |                      │
│ | Layer 5 多Agent层 | 子代理隔离分发、任务并行 |                            │
│ | Layer 6 模型层 | Claude Sonnet API 推理 |                                 │
│                                                                             │
│ **核心设计哲学**:主循环是极简的 "Dumb Loop"——模型输出工具调用 → 执行 →     │
│ 结果返回模型 →                                                              │
│ 循环,直到模型认为任务完成。**不使用向量数据库/RAG、意图分类器等传统 Agent  │
│ 组件**,智能完全来自模型权重。约 90% 代码由 Claude Code 自身编写。          │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 2. Python 多线程机制                                                     │
│                                                                             │
│ ### GIL(全局解释器锁)                                                     │
│ CPython 中的互斥锁,**同一时刻只允许一个线程执行 Python                     │
│ 字节码**。存在原因:简化内存管理(引用计数安全)、保证 C 扩展兼容性。       │
│                                                                             │
│ ### threading 模块核心组件                                                  │
│                                                                             │
│ | 同步原语 | 用途 |                                                         │
│ |----------|------|                                                         │
│ | **Lock** | 互斥访问共享资源 |                                             │
│ | **RLock** | 允许同一线程多次获取 |                                        │
│ | **Semaphore** | 限制并发数 N |                                            │
│ | **Event** | 线程间一对多信号通知 |                                        │
│ | **Condition** | 生产者-消费者 notify/wait |                               │
│ | **Barrier** | N 个线程互相等待到齐 |                                      │
│                                                                             │
│ ### 关键结论                                                                │
│                                                                             │
│ - **I/O 密集型** → `threading` / `ThreadPoolExecutor`,GIL 影响小(I/O      │
│ 等待时释放),性能 ~N 倍提升                                                │
│ - **CPU 密集型** → `multiprocessing` / `ProcessPoolExecutor`,绕过          │
│ GIL,每进程独立解释器                                                       │
│ - **高并发 I/O**(万级连接)→ `asyncio` 协程,内存开销极低                  │
│ - **Python 3.13 突破**:引入 free-threaded 模式,可禁用 GIL,CPU            │
│ 密集型多线程性能提升 5x+                                                    │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 3. Python 性能差的原因                                                   │
│                                                                             │
│ ### 六大根因                                                                │
│                                                                             │
│ | 原因 | 影响 | 占比 |                                                      │
│ |------|------|------|                                                      │
│ | **解释执行** | 字节码 → 解释器 dispatch loop → CPU,多一层转换 | ~11% |   │
│ | **动态类型** | 运行时类型检查替代编译期优化,`a+b` 需查找 `__add__` | ~6% │
│ |                                                                           │
│ | **万物皆对象** | 一个 int 占 28+ 字节(C仅4字节),内存开销大5-7倍 | ~13% │
│ |                                                                           │
│ | **引用计数GC** | 每次赋值/传递都触发原子操作,CPU密集时占 10-30% | — |    │
│ | **GIL 锁** | 多核 CPU 密集型任务无法真正并行 | — |                        │
│ | **无 JIT(CPython)** | 纯解释执行,无热点编译优化 | — |                  │
│                                                                             │
│ ### 优化方向                                                                │
│ - **PyPy**:追踪 JIT,比 CPython 快 4-7 倍                                  │
│ - **Cython**:编译为 C 扩展                                                 │
│ - **Numba**:JIT 编译数值计算                                               │
│ - **Python 3.14**:正在引入 Copy-and-Patch JIT(PEP 744)                   │
│                                                                             │
│ > **核心矛盾**:Python 用性能换开发效率。比 C 慢 10-100 倍,但开发效率高    │
│ 3-10 倍。CPU 密集用 C/Rust 扩展,Python 做胶水层。                          │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 4. OpenClaw(小龙虾)技术架构                                            │
│                                                                             │
│ 调研发现的相关项目是 **OpenClaw**(小龙虾),一个开源的多渠道 AI Agent      │
│ 聊天机器人框架。                                                            │
│                                                                             │
│ ### 核心架构:六层体系                                                      │
│                                                                             │
│ | 层级 | 组件 |                                                             │
│ |------|------|                                                             │
│ | Channel Adapters | 多渠道适配(WhatsApp/TG/Discord/Web等) |        │
│ | Gateway 控制平面 | Node.js 进程,负责会话路由、Lane Queue                 │
│ 序列化队列、定时任务 |                                                      │
│ | Agent 运行时 | **ReAct 循环**(边推理边行动),非预规划执行 |             │
│ | 模型抽象层 | 统一模型调用接口 |                                           │
│ | 能力触角 | Skills 按需加载 + Docker 沙箱执行 |                            │
│ | 数据持久层 | Markdown 长期记忆 + JSONL 会话日志 + SQLite 向量搜索 |       │
│                                                                             │
│ ### 关键创新                                                                │
│                                                                             │
│ - **Heartbeat 主动唤醒**:区别于被动等待的 Agent 框架,可定时主动触发对话   │
│ - **Skills 按需加载**:解决 context 膨胀和 token 浪费                       │
│ - **双源记忆**:Markdown(长期)+ JSONL(动态)+ SQLite 向量检索            │
│ - **多层安全**:网络隔离 + 工具白名单 + Docker 沙箱 + 硬拦截危险指令        │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ 以上是四个主题的调研总结,如需深入了解某个方向的更多细节,可以进一步提问!  │
└─────────────────────────────────────────────────────────────────────────────┘

演示结束

Process finished with exit code 0

显示传入subAgent参数

安装 MCP 适配器(关键依赖)\MCP 服务器开发库(如需自定义工具)

pip install langchain-mcp-adapters mcp

检查 Node.js

node –-version

检查 npm/npx

npx –version

手动安装 MCP 服务器包

npm install -g @amap/amap-maps-mcp-server

案例:

import asyncio
import os

import dotenv
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model
from langchain_core.messages import BaseMessage, ToolMessage
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_tavily import TavilySearch
from rich.console import Console
from rich.panel import Panel
from rich.tree import Tree

# 1.加载环境变量
dotenv.load_dotenv()

# 2.配置Rich Console
console = Console()


# 3.配置 Context7 MCP
# Windows 上 stdio + npx 在并行子 Agent 里会反复拉起/关闭 Node 进程,
# 容易触发: Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)
# 因此默认用远程 HTTP;失败则回退 Tavily。
async def setup_mcp_tools():
    console.print("[dim]正在连接 Context7(streamable_http)...[/dim]")
    context7_headers = {}
    api_key = os.getenv("CONTEXT7_API_KEY")
    if api_key:
        context7_headers["CONTEXT7_API_KEY"] = api_key

    try:
        mcp_client = MultiServerMCPClient({
            "context7": {
                "transport": "streamable_http",
                "url": "https://mcp.context7.com/mcp",
                **({"headers": context7_headers} if context7_headers else {}),
            },
        })
        tools = await mcp_client.get_tools()
        console.print(f"[bold green]成功加载 {len(tools)} 个 MCP 工具(HTTP)[/bold green]")
        return mcp_client, tools
    except Exception as e:
        console.print(f"[bold yellow]Context7 HTTP 连接失败:{e}[/bold yellow]")
        return None, []


# 4.定义子Agent配置
# 注意:不要写 model="deepseek-v4-pro" 字符串 —— create_agent 会再 init_chat_model,
# 并要求环境变量 DEEPSEEK_API_KEY。应传入已配置好的模型实例,或省略 model 继承主 Agent。
def get_subagent_config(mcp_tools, model):
    # 每个子 Agent 用独立工具实例,避免并行时共享同一 MCP session 出问题
    community_search = TavilySearch(max_results=3)

    if mcp_tools:
        doc_tools = list(mcp_tools)
        docs_prompt = (
            "你是一名专门查询官方文档的技术专家。"
            "请优先使用 Context7 MCP 工具获取准确的技术细节。不要猜测。"
        )
        console.print("[dim]DocsResearcher 使用 Context7 MCP 工具[/dim]")
    else:
        doc_tools = [TavilySearch(max_results=5)]
        docs_prompt = (
            "你是一名专门查询官方文档的技术专家。"
            "请用搜索工具查找 docs.langchain.com / deepagents 官方文档,只依据检索结果作答,不要猜测。"
        )
        console.print("[dim]DocsResearcher 使用 Tavily(未启用 MCP)[/dim]")

    docs_researcher = {
        "name": "DocsResearcher",
        "description": "负责查阅官方文档和技术规范的专家Agent。",
        "system_prompt": docs_prompt,
        "tools": doc_tools,
        "model": model,
    }

    community_researcher = {
        "name": "CommunityResearcher",
        "description": "负责搜索社区博客、教程和最佳实践的专家Agent。",
        "system_prompt": "你是一名关注社区动态的开发者,请搜索博客、论坛和GitHub讨论。",
        "tools": [community_search],
        "model": model,
    }

    return [docs_researcher, community_researcher]


# 5.主逻辑
async def run_parallel_subagents():

    console.print(Panel.fit("[bold blue]DeepAgents并行子Agent演示[/bold blue]", border_style="blue"))

    # 先初始化模型(子 Agent 也要用同一套配置)
    deepseek_v4_pro = init_chat_model(
        model="deepseek-v4-pro",
        model_provider="deepseek",
        base_url="https://api.deepseek.com",
        api_key=os.getenv("api_key"),
        temperature=0.7,
        max_tokens=10000,
    )

    # 获取MCP工具
    mcp_client, mcp_tools = await setup_mcp_tools()

    # 获取子Agent配置(传入同一 model,避免 DEEPSEEK_API_KEY 校验失败)
    subagent_config = get_subagent_config(mcp_tools, deepseek_v4_pro)

    agent = create_deep_agent(
        model=deepseek_v4_pro,
        tools=[],
        subagents=subagent_config,
        system_prompt="""
        你是一名技术总监。你的任务是协调DocsResearcher和CommunityResearcher完成调研任务。
        请你根据用户需求,将任务拆解分发给这两个子Agent。
        调用 task 工具时,subagent_type 必须是 DocsResearcher 或 CommunityResearcher。
        如果任务允许,请务必并行调用它们以提高效率。
        最后汇总它们的报告。
        """,
    )

    task = (
        "请详细调研 'LangChain DeepAgents' 框架。"
        "我需要官方的技术架构说明(来自文档)以及社区最佳实践(来自博客、论坛、GitHub讨论)。请对比两者"
    )
    console.print(f"[bold green]任务指令:[/bold green] {task}\n")

    # 运行并可视化
    step = 0

    try:
        # astream:异步流式跑图。每跑完「一个节点」就 yield 一个 chunk(默认类似 updates 模式)
        # 输入格式和 invoke 一样:{"messages": [用户消息, ...]}
        async for chunk in agent.astream({"messages": [{"role": "user", "content": task}]}):
            step += 1  # 每收到一个 chunk 算一步(含后面不打印的静默更新,所以 Step 可能跳号)

            # ---------- chunk 长什么样? ----------
            # 当前写法没传 stream_mode 时,astream 默认一般是 "updates"。若要显式写清楚:
            # chunk 是一个 dict,键 = LangGraph 节点名,值 = 该节点本次产出的状态增量。
            # 例子:
            #   {"model": {"messages": [AIMessage(...)]}}
            #   {"tools": {"messages": [ToolMessage(...), ToolMessage(...)]}}
            # 同一轮也可能只有一个键。
            # .items():把 dict 拆成 (key, value) 对,便于 for 解包。
            #   node_name → 如 "model" / "tools"
            #   node_data → 如 {"messages": [...]},也可能是 todos 等其它字段
            for node_name, node_data in chunk.items():
                # 个别更新可能是空的,跳过
                if node_data is None:
                    continue

                # 本演示只关心「带 messages 的更新」;
                # 若节点只改了 todos 等,没有 messages,整段 if 不进 → 终端上就像「少了 Step」
                if "messages" in node_data:
                    # 取出本节点新增/带回的消息列表(updates 模式下通常是本步新消息,不是全历史)
                    msgs = node_data["messages"]

                    # 防御:偶发单条消息而不是 list,先包成 list,后面统一 for 遍历
                    if not isinstance(msgs, list):
                        msgs = [msgs]

                    for msg in msgs:
                        # 0) 只要 LangChain 消息对象(HumanMessage / AIMessage / ToolMessage…)
                        #    其它奇怪类型直接跳过
                        if not isinstance(msg, BaseMessage):
                            continue

                        # 安全读 tool_calls:
                        # - AIMessage 可能带 tool_calls(模型决定调哪些工具)
                        # - HumanMessage / ToolMessage 通常没有该属性
                        # getattr(..., None) 避免 AttributeError;or [] 把 None/空统一成空列表
                        tool_calls = getattr(msg, "tool_calls", None) or []

                        # ---------- 分支 1:模型发起工具调用(常见于 Node=model)----------
                        # tool_calls 是一个列表,例如模型一次要调 4 个工具:
                        # [
                        #   {"name": "task", "args": {"subagent_type": "general-purpose", "description": "调研 Claude Code"}},
                        #   {"name": "task", "args": {"subagent_type": "general-purpose", "description": "调研 Python 多线程"}},
                        #   {"name": "write_todos", "args": {...}},
                        # ]
                        if tool_calls:
                            # Rich Tree:树形打印「本步调了哪些工具」
                            tree = Tree(
                                f"[bold yellow]Step {step}: 决策与调用 (Node: {node_name}) [/bold yellow]"
                            )

                            # 一条 AIMessage 里可以并行请求多个工具(本 demo 里常见一次 4 个 task)
                            # 取出工具名和参数(兼容两种格式)
                            # tc 是字典    tc["name"]、tc["args"](用 .get 防缺 key)
                            # tc 是对象    tc.name、tc.args(用 getattr 防缺属性)
                            for tc in tool_calls:
                                # LangChain 里 tool_call 有时是 dict,有时是对象,两种都兼容
                                if isinstance(tc, dict):
                                    tool_name = tc.get("name", "未知工具")  # 如 "task" / "write_todos"
                                    tool_args = tc.get("args", {})  # 调用参数字典
                                else:
                                    tool_name = getattr(tc, "name", "未知工具")
                                    tool_args = getattr(tc, "args", {}) or {}

                                if tool_name == "task":
                                    # task = SubAgentMiddleware 注入的「派生子 Agent」工具
                                    # 典型 args:subagent_type="general-purpose", description="调研xxx"
                                    # Step 7: 决策与调用 (Node: model)     ← 这是外面已建好的 tree 根
                                    # └── 成功触发 'task' 工具 (Sub-Agent)  ← branch = tree.add(...)
                                    #     ├── 子Agent类型: general-purpose ← branch.add(...)
                                    #     └── 任务指令: 调研 xxx           ← branch.add(...)
                                    branch = tree.add(
                                        "[bold red]成功触发 'task' 工具 (Sub-Agent)[/bold red]"
                                    )
                                    # tree.add 返回子节点,可再 .add 挂详情
                                    branch.add(
                                        f"[cyan]子Agent类型:[/cyan] {tool_args.get('subagent_type', '未知')}"
                                    )
                                    branch.add(
                                        f"[cyan]任务指令:[/cyan] {tool_args.get('description', '未知')}"
                                    )
                                else:
                                    # 其它工具(write_todos、tavily_search…)一律标成普通工具
                                    tree.add(f"[bold blue]普通工具调用:[/bold blue] {tool_name}")
                            console.print(tree)  # 把整棵树打到终端

                        # ---------- 分支 2:工具执行结果(常见于 Node=tools)----------
                        # ToolMessage:某个工具跑完后的返回;.name 是工具名,.content 是返回正文
                        # 判断当前消息是不是[工具回执],只有工具跑完后,图才会产生ToolMessage
                        elif isinstance(msg, ToolMessage):
                            content = str(msg.content)  # 统一转成字符串再展示
                            # task表示是子Agent的结果
                            if msg.name == "task":
                                # 子 Agent 整段任务做完后,只把「最终摘要/报告」回传给主 Agent
                                # (子 Agent 内部搜索过程默认不会全部冒泡到这里)
                                console.print(
                                    Panel(
                                        content,
                                        title=f"[bold magenta]Sub-Agent 完成任务 (Node: {node_name})[/bold magenta]",
                                        border_style="magenta",
                                    )
                                )
                            else:
                                # 非 task 工具(如 write_todos):内容可能很长,只预览前 100 字
                                preview = content[:100] + ("..." if len(content) > 100 else "")
                                console.print(f"[dim]Tool Output ({msg.name}): {preview}[/dim]")

                        # ---------- 分支 3:模型纯文本回复(无工具调用)----------
                        # 条件:有 content,且 tool_calls 为空 → 多半是对用户的最终/中间文字回答
                        elif getattr(msg, "content", None) and not tool_calls:
                            title = f"[bold green]Agent回复 (Node: {node_name})[/bold green]"
                            console.print(Panel(msg.content, title=title, border_style="green"))

    except Exception as e:
        console.print(f"[bold red]运行时错误:{e}[/bold red]")

    console.print(f"[bold green]运行结束退出。[/bold green]")

if __name__ == '__main__':
    # 模块顶层不能直接 await,要用 asyncio.run 驱动协程
    asyncio.run(run_parallel_subagents())

输出:

D:\anaconda3\envs\deepagents\python.exe E:\code\InsightFlow\create_demo_deep_agent\demo_mulit_server.py 
┌───────────────────────────┐
│ DeepAgents并行子Agent演示 │
└───────────────────────────┘
正在连接 Context7(streamable_http)...
成功加载 2 个 MCP 工具(HTTP)
DocsResearcher 使用 Context7 MCP 工具
任务指令: 请详细调研 'LangChain DeepAgents' 
框架。我需要官方的技术架构说明(来自文档)以及社区最佳实践(来自博客、论坛、Git
Hub讨论)。请对比两者

Step 3: 决策与调用 (Node: model) 
└── 普通工具调用: write_todos
Tool Output (write_todos): Updated todo list to [{'content': '通过 
DocsResearcher 调研 LangChain DeepAgents 官方文档和技术架构', 'status': '...
Step 7: 决策与调用 (Node: model) 
├── 成功触发 'task' 工具 (Sub-Agent)
│   ├── 子Agent类型: DocsResearcher
│   └── 任务指令: 调研 LangChain DeepAgents 官方文档和架构
└── 成功触发 'task' 工具 (Sub-Agent)
    ├── 子Agent类型: CommunityResearcher
    └── 任务指令: 调研 DeepAgents 社区最佳实践
┌───────────────────── Sub-Agent 完成任务 (Node: tools) ──────────────────────┐
│ ---                                                                         │
│                                                                             │
│ # DeepAgents 社区最佳实践调研报告                                           │
│                                                                             │
│ > 综合整理了 GitHub Discussions、Reddit                                     │
│ (r/LangChain)、官方文档、技术博客及社区文章中的最佳实践。                   │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 一、项目定位与架构概况                                                   │
│                                                                             │
│ **DeepAgents** 是 LangChain 官方出品的 "batteries-included agent            │
│ harness"(开箱即用的 Agent 骨架),构建于 LangGraph 之上。它从 Claude       │
│ Code、Deep Research                                                         │
│ 等生产级系统中提炼出常见模式,内置了规划、文件系统、子代理委托、上下文管理  │
│ 和技能加载等能力。                                                          │
│                                                                             │
│ **三层架构:**                                                              │
│ ```                                                                         │
│ LangGraph (图运行时)  →  create_agent (轻量 harness)  →  DeepAgents (重度   │
│ harness)                                                                    │
│ ```                                                                         │
│                                                                             │
│ **核心原则:**                                                              │
│ - **Opinionated** — 默认调优为长时间、多步骤任务                            │
│ - **Extensible** — 可覆盖或替换任何组件,无需 fork                          │
│ - **Model-agnostic** — 任何支持 tool calling 的模型均可使用                 │
│ - **Production-ready** — 内置 streaming、persistence、checkpointing,可与   │
│ LangSmith 深度集成                                                          │
│                                                                             │
│ > 📦 **安装:** `pip install deepagents` 或 `uv add deepagents`             │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 二、使用场景选择(什么时候该用 DeepAgents)                              │
│                                                                             │
│ 根据社区和官方文档的一致共识:                                              │
│                                                                             │
│ | 场景 | 推荐方案 |                                                         │
│ |---|---|                                                                   │
│ | 多步骤任务需要规划 | ✅ **DeepAgents** |                                  │
│ | 超大上下文需要文件管理 | ✅ **DeepAgents** |                              │
│ | 需要专业子代理分工 | ✅ **DeepAgents** |                                  │
│ | 跨会话持久记忆 | ✅ **DeepAgents** |                                      │
│ | 简单单次工具调用 | ❌ `create_agent` 即可 |                               │
│ | 上下文可放入单次 prompt | ❌ `create_agent` 即可 |                        │
│ | 需要完全自定义图结构 | ❌ 直接用 LangGraph |                              │
│                                                                             │
│ > 💡 **社区建议:** "If starting in 2026, use deepagents (built on          │
│ LangGraph) and transition to raw LangGraph for finer control."
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 三、中间件 (Middleware) 最佳实践                                         │
│                                                                             │
│ ### 3.1 默认中间件栈及顺序                                                  │
│                                                                             │
│ ```                                                                         │
│ TodoListMiddleware → SkillsMiddleware → FilesystemMiddleware                │
│ → SubAgentMiddleware → SummarizationMiddleware                              │
│ ```                                                                         │
│                                                                             │
│ 顺序很重要!中间件按顺序执行,这是社区反复强调的。                          │
│                                                                             │
│ ### 3.2 覆盖中间件 —— 社区核心讨论                                          │
│                                                                             │
│ 来自 [GitHub Discussion                                                     │
│ #655](https://github.com/langchain-ai/deepagents/discussions/655)           │
│ 的激烈讨论:                                                                │
│                                                                             │
│ **关键机制:** 通过 `.name`                                                 │
│ 匹配来替换默认中间件实例,而非追加。即同名中间件会被你的自定义版本**替换**  │
│ 而非合并。                                                                  │
│                                                                             │
│ ```python                                                                   │
│ agent = create_deep_agent(                                                  │
│     model="claude-sonnet-4-5-20250929",                                     │
│     middleware=[                                                            │
│         SummarizationMiddleware(trigger=("tokens", 80000),                  │
│ keep=("messages", 20)),                                                     │
│     ],                                                                      │
│ )                                                                           │
│ ```                                                                         │
│                                                                             │
│ **⚠️ 社区踩坑:**                                                           │
│ - 覆盖 `FilesystemMiddleware` 时必须手动传入 `backend` 和 `permissions`     │
│ - `SubAgentMiddleware` 是**承重中间件**(load-bearing),不可移除           │
│ - `FilesystemMiddleware` 同样被多处依赖,移除会触发 `ValueError`            │
│ - 通用子代理**默认不继承**主代理的自定义中间件([Issue                      │
│ #2744](https://github.com/langchain-ai/deepagents/issues/2744))            │
│                                                                             │
│ **排除不需要的中间件(社区贡献方案):**                                    │
│ ```python                                                                   │
│ HarnessProfile(                                                             │
│     excluded_middleware=frozenset({                                         │
│         "TodoListMiddleware",                                               │
│         "FilesystemMiddleware",                                             │
│         "SummarizationMiddleware",                                          │
│     }),                                                                     │
│ )                                                                           │
│ ```                                                                         │
│                                                                             │
│ > 引用 Reddit 用户评论:「我尝试基于 deepagents                             │
│ 构建,但发现它相当受限,开源 LLM 对一些工具有困难。最后回归到常规           │
│ agent,只使用 deepagents 包中的部分中间件。」                               │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 四、系统提示词与指令设计                                                 │
│                                                                             │
│ ### 4.1 最佳实践                                                            │
│                                                                             │
│ 来自多个博客和官方文档的一致建议:                                          │
│                                                                             │
│ ```python                                                                   │
│ # ❌ 不好的做法                                                             │
│ system_prompt = "You are a helpful assistant."                              │
│                                                                             │
│ # ✅ 好的做法                                                               │
│ system_prompt = """                                                         │
│ You are a research analyst. When given a topic:                             │
│ 1. Use write_todos to plan your approach                                    │
│ 2. Delegate research to the general-purpose subagent via task()             │
│ 3. Read findings from the filesystem                                        │
│ 4. Synthesize into a structured report with clear sections                  │
│ 5. Save the final report to /reports/{topic}.md                             │
│ """                                                                         │
│ ```                                                                         │
│                                                                             │
│ - **具体胜过笼统**:告诉 agent 期望的输出格式、质量标准、操作步骤           │
│ - **自定义指令会被前置**到内置系统提示词之前                                │
│ - **不超过必要长度**:冗长的指令会挤占上下文窗口                            │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 五、工具 (Tools) 设计最佳实践                                            │
│                                                                             │
│ ### 5.1 文档字符串与类型提示                                                │
│                                                                             │
│ 这是社区最一致的建议之一:                                                  │
│                                                                             │
│ ```python                                                                   │
│ # ❌ 不好的工具设计                                                         │
│ @tool                                                                       │
│ def search(q: str) -> str:                                                  │
│     """search something"""                                                  │
│     return ...                                                              │
│                                                                             │
│ # ✅ 好的工具设计                                                           │
│ @tool                                                                       │
│ def web_search(query: str) -> str:                                          │
│     """Search the web for information on a given topic.                     │
│                                                                             │
│     Use this tool when you need up-to-date information or                   │
│     facts that may not be in your training data.                            │
│                                                                             │
│     Args:                                                                   │
│         query: A specific, detailed search query string.                    │
│                Use natural language and be as precise as possible.          │
│                                                                             │
│     Returns:                                                                │
│         Search results with titles, URLs, and snippets.                     │
│     """                                                                     │
│     return ...                                                              │
│ ```                                                                         │
│                                                                             │
│ **核心原则:**                                                              │
│ - LLM 通过 docstring 和参数描述决定何时/如何使用工具 —— **清晰文档 =        │
│ 更好工具调用**                                                              │
│ - 使用 `Literal` 类型、清晰的参数名(`query: str` 而非 `q: str`)           │
│ - 设计用于**深入研究**而非简单回答的工具                                    │
│                                                                             │
│ ### 5.2 安全模型                                                            │
│                                                                             │
│ > 来自 Flowtivity 博客:「Deep Agents 遵循 "trust the tools, not the model" │
│ 哲学。Agent                                                                 │
│ 可以做其工具允许的任何事情。你在工具层面而非期望模型自我约束来实施边界。」  │
│                                                                             │
│ - ✅ 对敏感操作添加 `HumanInTheLoopMiddleware`                              │
│ - ✅ 在工具级别做沙箱限制                                                   │
│ - ❌ 不要依赖 prompt 层面的护栏(脆弱且不可靠)                             │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 六、Skills(技能)最佳实践                                               │
│                                                                             │
│ ### 6.1 渐进式能力暴露                                                      │
│                                                                             │
│ Skills 是 DeepAgents 独有的模式:agent 启动时只看到 skill                   │
│ 的名称和描述(frontmatter),仅在需要时才加载完整内容。                     │
│                                                                             │
│ ```                                                                         │
│ .deepagents/skills/                                                         │
│ ├── research/SKILL.md                                                       │
│ ├── deploy/SKILL.md                                                         │
│ └── review-pr/SKILL.md                                                      │
│ ```                                                                         │
│                                                                             │
│ **社区推荐:**                                                              │
│                                                                             │
│ | 建议 | 原因 |                                                             │
│ |---|---|                                                                   │
│ | frontmatter 保持简洁 | 所有 frontmatter 在发现阶段注入系统提示词 |        │
│ | `SKILL.md` body 控制在 5000 token 以内 | body 只在激活时加载 |            │
│ | `description` 要具体且包含触发词 | 这是 agent 选择 skill 的唯一依据 |     │
│ | 每个子代理配置独立的 skills | 通用子代理会自动继承;自定义子代理不会 |    │
│                                                                             │
│ **description 示例:**                                                      │
│ ```                                                                         │
│ ✅ "Use when the user asks to research a topic, find information,           │or gather data from external sources. Handles web search,                │
│    source verification, and structured note-taking."
│                                                                             │
│ ❌ "Does research"                                                          │
│ ```                                                                         │
│                                                                             │
│ ### 6.2 Skills 创建流程(社区提炼)                                         │
│                                                                             │
│ 1. **Execute Task** — 手动让 agent 执行一次任务                             │
│ 2. **Reflect** — 让 agent 分析自己的执行轨迹                                │
│ 3. **Create Skill** — 用 `skill-creator` 固化流程为 skill                   │
│                                                                             │
│ > 来自 Reddit 用户:「我建议深入研究 skills.md,将每个子代理的 skills       │
│ 分开配置。这看起来很有前景,正在取得更好的结果,但通过 skills.md            │
│ 是一个逐步的迭代过程。」                                                    │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 七、子代理 (Sub-Agents) 最佳实践                                         │
│                                                                             │
│ ### 7.1 设计原则                                                            │
│                                                                             │
│ 来自 LangChain 官方博客 "Building Multi-Agent Applications with Deep        │
│ Agents":                                                                   │
│                                                                             │
│ **描述质量决定委托质量:**                                                  │
│                                                                             │
│ ```python                                                                   │
│ # ✅ 好的子代理描述                                                         │
│ subagents=[                                                                 │
│     {                                                                       │
│         "name": "financial-analyst",                                        │
│         "description": "Analyzes financial data and generates investment    │
│ insights                                                                    │
│                         with confidence scores and risk assessments",       │"system_prompt": "...",                                             │
│         "tools": ,                                                          │
│     },                                                                      │
│ ]                                                                           │
│                                                                             │
│ # ❌ 不好的描述                                                             │
│ subagents=[{"name": "finance", "description": "Does finance stuff"}]        │
│ ```                                                                         │
│                                                                             │
│ ### 7.2 分工模式                                                            │
│                                                                             │
│ 社区验证的有效模式:                                                        │
│                                                                             │
│ ```                                                                         │
│ Main Agent (Planner + Orchestrator)                                         │
│ ├── General-purpose Subagent (research, web search — 自动添加)              │
│ ├── Custom Subagent A (专业领域 X)                                          │
│ └── Custom Subagent B (专业领域 Y)                                          │
│ ```                                                                         │
│                                                                             │
│ > Reddit 用户实践:「我在构建生产级 deepagents,子代理工具通过 FastMCP      │
│ Gateway 调用。灵活性方面建议深入研究 skills.md。」                          │
│                                                                             │
│ **关键决策:**                                                              │
│ - 子代理不应承担不必要的中间件负担(GitHub #655 的讨论核心)                │
│ - 通用子代理自动继承主代理的 skills;自定义子代理需要显式配置               │
│ - 让子代理做单一职责的事情,避免"大而全"                                    │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 八、上下文与记忆管理                                                     │
│                                                                             │
│ ### 8.1 文件系统后端选择                                                    │
│                                                                             │
│ | 后端 | 用途 | 场景 |                                                      │
│ |---|---|---|                                                               │
│ | `StateBackend`(默认) | 单线程内临时文件 | 中间结果、草稿 |              │
│ | `StoreBackend` | 跨线程/跨会话持久化 | 长期记忆、用户偏好 |               │
│ | `FilesystemBackend` | 直接磁盘访问 | CLI 本地开发 |                       │
│ | `CompositeBackend` | 混合路由 | `/memories/` → Store, 其余 → State |      │
│                                                                             │
│ **社区推荐的多租户记忆模式:**                                              │
│                                                                             │
│ ```python                                                                   │
│ CompositeBackend(                                                           │
│     default=StateBackend(),                                                 │
│     routes={"/memories/": StoreBackend(store=store)},                       │
│ )                                                                           │
│ ```                                                                         │
│                                                                             │
│ Namespace 模式:按 `user_id` 隔离,每个用户拥有私有记忆空间。               │
│                                                                             │
│ ### 8.2 上下文窗口管理                                                      │
│                                                                             │
│ > "Context management remains a first-class engineering concern regardless  │
│ of window size, because attention quality degrades long before the hard     │
│ limit is reached." — abvijaykumar.medium.com                                │
│                                                                             │
│ **社区的实战教训:**                                                        │
│ - 自动上下文压缩 (`SummarizationMiddleware`)                                │
│ 的触发阈值和保留量需要根据任务调试                                          │
│ - 大型搜索结果自动卸载到文件系统,agent 通过 `read_file` 按需读取           │
│ - 使用 `thread_id` 保持跨调用的会话连续性                                   │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 九、评估与测试                                                           │
│                                                                             │
│ ### 9.1 LangChain 官方五大评估模式                                          │
│                                                                             │
│ 来自 [LangChain 博客 "Evaluating Deep Agents: Our                           │
│ Learnings"](https://www.langchain.com/blog/evaluating-deep-agents-our-learn │
│ ings):                                                                     │
│                                                                             │
│ | 模式 | 说明 |                                                             │
│ |---|---|                                                                   │
│ | **1. Bespoke test logic per datapoint** |                                 │
│ 每个测试用例有独立的断言逻辑,检查 agent 轨迹和状态 |                       │
│ | **2. Single-step evaluations** | 验证特定决策点(如是否正确调用了         │
│ `edit_file`) |                                                             │
│ | **3. Full agent turn testing** | 端到端测试完整行为 |                     │
│ | **4. Multi-turn conversations** | 模拟带条件逻辑的真实多轮交互 |          │
│ | **5. Environment setup** | 干净、可复现的测试环境 |                       │
│                                                                             │
│ **核心洞察:** Deep Agents 打破了传统 LLM 评估的假设 ——                     │
│ 不是每个测试用例都能用相同的应用逻辑运行和相同的评估器打分。                │
│                                                                             │
│ ### 9.2 实际应用示例                                                        │
│                                                                             │
│ ```python                                                                   │
│ @pytest.mark.langsmith                                                      │
│ def test_memory_update():                                                   │
│     result = agent.invoke({"messages": [{"role": "user", "content":         │
│ "..."}]})                                                                   │
│                                                                             │
│     # 检查轨迹中的特定工具调用                                              │assert any(call["name"] == "edit_file" and "/memories/" in              │
│ call["args"]["path"]                                                        │
│                for call in result["tool_calls"])                            │
│                                                                             │
│     # 检查最终回复包含记忆更新确认                                          │assert "updated" in result["messages"][-1].content.lower()              │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 十、生产部署                                                             │
│                                                                             │
│ ### 10.1 PII 与安全                                                         │
│                                                                             │
│ ```python                                                                   │
│ from langchain.agents.middleware import PIIMiddleware                       │
│                                                                             │
│ agent = create_deep_agent(                                                  │
│     model="google_genai:gemini-3.5-flash",                                  │
│     middleware=[                                                            │
│         PIIMiddleware("email", strategy="redact", apply_to_input=True),     │
│         PIIMiddleware("credit_card", strategy="mask", apply_to_input=True), │
│     ],                                                                      │
│ )                                                                           │
│ ```                                                                         │
│                                                                             │
│ 策略选项:`redact`、`mask`、`hash`、`block`                                 │
│                                                                             │
│ ### 10.2 人机协作 (Human-in-the-Loop)                                       │
│                                                                             │
│ ```python                                                                   │
│ agent = create_deep_agent(                                                  │
│     interrupt_on={"write_file": True, "execute": True},                     │
│     checkpointer=MemorySaver(),  # 必须!interrupt 需要 checkpointer        │
│ )                                                                           │
│ ```                                                                         │
│                                                                             │
│ ### 10.3 模型选择                                                           │
│                                                                             │
│ | 阶段 | 推荐 |                                                             │
│ |---|---|                                                                   │
│ | 原型开发 | Groq 等快速模型 |                                              │
│ | 生产环境 | GPT-4o、GPT-5、Claude Sonnet 等强模型 |                        │
│                                                                             │
│ > "Deep agents make many sequential decisions. More capable models          │
│ generally plan and execute better than smaller models."
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 十一、常见坑与社区经验教训                                               │
│                                                                             │
│ ### 11.1 社区最常踩的坑                                                     │
│                                                                             │
│ 1. **忘了传 `checkpointer`** — 使用 `interrupt_on` 时必须配合               │
│ checkpointer,否则报错                                                      │
│ 2. **覆盖中间件丢失配置** — 覆盖 `FilesystemMiddleware` 时忘记传入          │
│ `backend` 和 `permissions`                                                  │
│ 3. **子代理继承问题** — 自定义子代理不会自动继承主代理的 middleware 和      │
│ skills                                                                      │
│ 4. **开源模型兼容性** — 部分开源 LLM 对某些 built-in 工具有困难             │
│ 5. **跳过 `write_todos`** — 很多开发者初期跳过规划步骤,导致混乱执行        │
│ 6. **工具描述过于简略** — "Does finance stuff" 式的描述导致 agent           │
│ 无法正确路由                                                                │
│ 7. **线程 ID 不一致** — 每次调用使用不同 `thread_id` 导致记忆丢失           │
│                                                                             │
│ ### 11.2 Reddit 社区的真实反馈                                              │
│                                                                             │
│ > 👍 "Tracing/visibility as a first class concern is really nice — I can    │
│ see what's going on and fix/tweak the agent very easily."                   │
│                                                                             │
│ > 👍 "The abstraction handles context window shrinking and provides easy    │
│ ways to customize agents. It's been successful bringing people not so       │
│ familiar with LangGraph into building backend agents."
│                                                                             │
│ > 👎 "I found it really restrictive, and open source LLMs had trouble with  │
│ some of the tools."
│                                                                             │
│ > 💡 "If you want more customizability, also check out OpenHarness."        │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 十二、快速上手清单                                                       │
│                                                                             │
│ 按照社区推荐顺序:                                                          │
│                                                                             │
│ 1. **从简单开始** — 一个工具 + 简单 prompt,跑通后再加复杂度                │
│ 2. **用 `write_todos`** — 显式规划比让 agent "自己想办法" 效果好得多        │
│ 3. **写好工具 docstring** — LLM 通过它们决定调用策略                        │
│ 4. **启用 LangSmith** — 没有可观测性,调试几乎不可能                        │
│ 5. **选对模型** — 原型用快速模型,生产用强模型                              │
│ 6. **迭代 skills.md** — 逐步将成功的工作流程固化为 skills                   │
│ 7. **设计专用子代理** — 一个研究、一个审查、一个执行,分离关注点            │
│ 8. **配置记忆后端** — 短期用 StateBackend,长期用 StoreBackend,混合用      │
│ CompositeBackend                                                            │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 参考来源                                                                 │
│                                                                             │
│ - [DeepAgents GitHub](https://github.com/langchain-ai/deepagents)           │
│ - [官方文档 -                                                               │
│ Customization](https://docs.langchain.com/oss/python/deepagents/customizati │
│ on)                                                                         │
│ - [官方文档 -                                                               │
│ Skills](https://docs.langchain.com/oss/python/deepagents/skills)            │
│ - [官方文档 - Going to                                                      │
│ Production](https://docs.langchain.com/oss/python/deepagents/going-to-produ │
│ ction)                                                                      │
│ - [LangChain Blog - Evaluating Deep                                         │
│ Agents](https://www.langchain.com/blog/evaluating-deep-agents-our-learnings │
│ )                                                                           │
│ - [LangChain Blog - Building Multi-Agent                                    │
│ Applications](https://www.langchain.com/blog/building-multi-agent-applicati │
│ ons-with-deep-agents)                                                       │
│ - [Reddit r/LangChain - Anyone building on top of                           │
│ DeepAgents?](https://www.reddit.com/r/LangChain/comments/1s8t4tv/)          │
│ - [GitHub Discussions #655 - Overriding                                     │
│ Middleware](https://github.com/langchain-ai/deepagents/discussions/655)     │
│ - [GitHub Issue #2744 - Subagent Middleware                                 │
│ Inheritance](https://github.com/langchain-ai/deepagents/issues/2744)        │
│ - [Krish Naik - Building Deep Agents with                                   │
│ LangChain](https://krishcnaik.substack.com/p/building-deep-agents-with-lang │
│ chain)                                                                      │
│ - [Flowtivity - Deep Agents Framework                                       │
│ Review](https://flowtivity.ai/blog/langchain-deep-agents-framework-review)  │
│ - [Analytics Vidhya - Deep Agents                                           │
│ Tutorial](https://www.analyticsvidhya.com/blog/2025/11/langchains-deep-agen │
│ t-guide)                                                                    │
│ - [ZenML - Evaluation Patterns for Deep                                     │
│ Agents](https://www.zenml.io/llmops-database/evaluation-patterns-for-deep-a │
│ gents-in-production)                                                        │
│ - [DEV.to - Building Advanced AI Agents with                                │
│ DeepAgents](https://dev.to/samadhi_patil_294a4ff7fea/building-advanced-ai-a │
│ gents-with-langchains-deepagents-a-hands-on-guide-1bk4)                     │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────── Sub-Agent 完成任务 (Node: tools) ──────────────────────┐
│ ---                                                                         │
│                                                                             │
│ # LangChain DeepAgents 官方文档与架构调研报告                               │
│                                                                             │
│ ## 1. 概述                                                                  │
│                                                                             │
│ **DeepAgents** 是 LangChain 生态中的一个开源 Agent                          │
│ 框架,专门用于构建**处理复杂、长时间运行任务**的智能体。它基于              │
│ **LangGraph** 图编排引擎构建,提供了一套"电池包含"(batteries-included)的  │
│ Agent 脚手架。                                                              │
│                                                                             │
│ > 官方定义:*"Deep Agents is a framework for building agents designed for   │
│ complex, long-running tasks. It includes features like planning, subagents, │
│ a virtual filesystem, and long-term memory."*                               │
│                                                                             │
│ ### 核心仓库                                                                │
│                                                                             │
│ | 仓库 | 说明 |                                                             │
│ |------|------|                                                             │
│ | `deepagents` (Python 包) | 核心框架,安装后导入使用 |                     │
│ | `langchain-ai/deepagents-quickstarts` | 官方示例集(如 Deep Research      │
│ Agent) |                                                                   │
│ | `shkarupa-alex/deepagents-opensandbox` | 沙箱执行后端(社区) |           │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 2. 核心架构                                                              │
│                                                                             │
│ DeepAgents 的架构围绕以下几个核心概念构建:                                 │
│                                                                             │
│ ```                                                                         │
│ ┌─────────────────────────────────────────────────────┐                     │
│ │                  create_deep_agent()                │                     │
│ │                    (主入口函数)                       │                   │
│ ├─────────────────────────────────────────────────────┤                     │
│ │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │                      │
│ │  │  Model   │  │  Tools   │  │  System Prompt   │  │                      │
│ │  └──────────┘  └──────────┘  └──────────────────┘  │                      │
│ ├─────────────────────────────────────────────────────┤                     │
│ │                  Middleware 层                       │                    │
│ │  ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │                      │
│ │  │ Filesystem   │ │ SubAgent     │ │ Memory      │ │                      │
│ │  │ Middleware   │ │ Middleware   │ │ Middleware  │ │                      │
│ │  ├──────────────┤ ├──────────────┤ ├─────────────┤ │                      │
│ │  │ Summarization│ │ Planning     │ │ AsyncSubAg..│ │                      │
│ │  │ Middleware   │ │ (via LangCh) │ │ Middleware  │ │                      │
│ │  └──────────────┘ └──────────────┘ └─────────────┘ │                      │
│ ├─────────────────────────────────────────────────────┤                     │
│ │                   Backend 层                         │                    │
│ │  ┌──────────┐ ┌──────────┐ ┌────────────────────┐  │                      │
│ │  │ State    │ │ Store    │ │ CompositeBackend   │  │                      │
│ │  │ Backend  │ │ Backend  │ │ (路由混合)          │  │                     │
│ │  ├──────────┤ ├──────────┤ ├────────────────────┤  │                      │
│ │  │ Filesystem│ │ Sandbox  │ │ ProtocolBackend    │  │                     │
│ │  │ Backend  │ │ Backend  │ │ (自定义实现)        │  │                     │
│ │  └──────────┘ └──────────┘ └────────────────────┘  │                      │
│ ├─────────────────────────────────────────────────────┤                     │
│ │              LangGraph StateGraph                   │                     │
│ │        (编译为 CompiledStateGraph 运行)              │                    │
│ └─────────────────────────────────────────────────────┘                     │
│ ```                                                                         │
│                                                                             │
│ ### 2.1 主入口:`create_deep_agent()`                                       │
│                                                                             │
│ 这是构建 DeepAgent 的核心工厂函数,返回一个 `CompiledStateGraph`(LangGraph │
│ 编译后的图)。                                                              │
│                                                                             │
│ ```python                                                                   │
│ from deepagents import create_deep_agent                                    │
│                                                                             │
│ agent = create_deep_agent(                                                  │
│     model: str | BaseChatModel | None = None,        # LLM 模型             │
│     tools: Sequence[BaseTool | Callable | dict] = None, # 工具列表          │
│     *,                                                                      │
│     system_prompt: str | SystemMessage | None = None,   # 系统提示词        │
│     middleware: Sequence[AgentMiddleware] = (),          # 中间件列表       │
│     subagents: Sequence[SubAgent | CompiledSubAgent] = None,  # 子 Agent    │
│     skills: list | None = None,                    # 技能                   │
│     memory: list | None = None,                    # 记忆配置               │
│     permissions: list[FilesystemPermission] | None = None,  # 文件权限      │
│     backend: BackendProtocol | BackendFactory | None = None, # 后端存储     │
│     interrupt_on: dict = None, # 人工介入                                   │
│     response_format: ResponseFormat | None = None,      # 输出格式          │
│     state_schema: type[DeepAgentState] | None = None,   # 自定义状态        │
│     context_schema: type[ContextT] | None = None,       # 上下文Schema      │
│     checkpointer: Checkpointer | None = None,           # 检查点(持久化)  │
│     store: BaseStore | None = None,                     # 长期存储          │
│     debug: bool = False,                                                    │
│     name: str | None = None,                                                │
│     cache: BaseCache | None = None,                                         │
│ ) -> CompiledStateGraph                                                     │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 3. Middleware(中间件)体系                                              │
│                                                                             │
│ Middleware 是 DeepAgents 最核心的扩展机制。每种 Middleware 都实现了         │
│ `AgentMiddleware` 基类,可以 hook 到 Agent 生命周期的各个阶段:             │
│                                                                             │
│ | Hook 方法 | 触发时机 |                                                    │
│ |-----------|----------|                                                    │
│ | `before_agent()` / `abefore_agent()` | Agent 开始执行前 |                 │
│ | `before_model()` / `abefore_model()` | 每次模型调用前 |                   │
│ | `after_model()` / `aafter_model()` | 每次模型调用后 |                     │
│ | `wrap_model_call()` / `awrap_model_call()` | 拦截/包装模型调用 |          │
│ | `wrap_tool_call()` / `awrap_tool_call()` | 拦截/包装工具调用 |            │
│ | `after_agent()` / `aafter_agent()` | Agent 执行结束后 |                   │
│                                                                             │
│ ### 3.1 DeepAgents 内置 Middleware                                          │
│                                                                             │
│ | Middleware | 功能 |                                                       │
│ |------------|------|                                                       │
│ | **FilesystemMiddleware** | 提供虚拟文件系统工具:`ls`, `read_file`,       │
│ `write_file`, `edit_file`, `glob`, `grep`, `execute` |                      │
│ | **SubAgentMiddleware** | 子 Agent 委托管理——主 Agent 可将任务委派给子     │
│ Agent 并行执行 |                                                            │
│ | **AsyncSubAgentMiddleware** | 支持远程 LangGraph 部署作为子 Agent(通过   │
│ HTTP/ASGI) |                                                               │
│ | **MemoryMiddleware** | 长期记忆——从 `~/.deepagents/AGENTS.md`             │
│ 等文件加载/保存知识 |                                                       │
│ | **SummarizationMiddleware** | 自动摘要——当上下文超过阈值(如 token 数达   │
│ 85%)时自动压缩对话 |                                                       │
│ | **SummarizationToolMiddleware** | 提供手动触发摘要的工具 |                │
│ | **Planning Middleware** (via LangChain) | 规划与任务管理中间件 |          │
│ | **ToolRetryMiddleware** (via LangChain) | 工具调用失败自动重试 |          │
│                                                                             │
│ ### 3.2 FilesystemMiddleware 详解                                           │
│                                                                             │
│ 文件系统中件间提供了完整的虚拟文件系统,Agent 可以像在真实 OS               │
│ 中一样操作文件:                                                            │
│                                                                             │
│ ```python                                                                   │
│ # 工具列表                                                                  │
│ tools = [                                                                   │
│     self._create_ls_tool(),        # 列出目录                               │
│     self._create_read_file_tool(), # 读取文件                               │
│     self._create_write_file_tool(),# 写入文件                               │
│     self._create_edit_file_tool(), # 编辑文件                               │
│     self._create_glob_tool(),      # 通配符查找                             │
│     self._create_grep_tool(),      # 内容搜索                               │
│     self._create_execute_tool(),   # 执行命令 (需沙箱)                      │
│ ]                                                                           │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 4. Backend(后端存储)体系                                               │
│                                                                             │
│ Backend                                                                     │
│ 负责文件存储和状态持久化。选择取决于对**持久化**和**隔离性**的需求。        │
│                                                                             │
│ ### 4.1 内置 Backend 类型                                                   │
│                                                                             │
│ | Backend | 说明 |                                                          │
│ |---------|------|                                                          │
│ | **StateBackend** | 临时/内存存储(默认,不持久化) |                      │
│ | **StoreBackend** | 基于 LangGraph Store 的持久化存储 |                    │
│ | **FilesystemBackend** | 真实文件系统存储(需 sandbox 或 HIL 批准) |      │
│ | **CompositeBackend** | 路由混合后端——不同路径映射到不同后端 |             │
│ | **自定义 Backend** | 实现 `BackendProtocol` 接口 |                        │
│                                                                             │
│ ### 4.2 CompositeBackend 示例                                               │
│                                                                             │
│ ```python                                                                   │
│ from deepagents.backends import StateBackend, StoreBackend,                 │
│ CompositeBackend                                                            │
│                                                                             │
│ backend = CompositeBackend(                                                 │
│     default=StateBackend(),           # 默认临时存储                        │
│     routes={                                                                │
│         "/memories/": StoreBackend(),  # /memories/ 路径持久化              │
│     }                                                                       │
│ )                                                                           │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 5. SubAgent(子 Agent)体系                                              │
│                                                                             │
│ DeepAgents 支持**层级委托**——主 Agent 可以将子任务分派给专门的子 Agent。    │
│                                                                             │
│ ### 5.1 子 Agent 配置                                                       │
│                                                                             │
│ ```python                                                                   │
│ research_sub_agent = {                                                      │
│     "name": "research-agent",                                               │
│     "description": "Delegate research to the sub-agent researcher.",        │
│     "system_prompt": RESEARCHER_INSTRUCTIONS,                               │
│     "model": "openai:gpt-5.5",         # 可独立指定模型                     │"tools": , # 独立工具集                                                 │
│ }                                                                           │
│                                                                             │
│ agent = create_deep_agent(                                                  │
│     model=model,                                                            │
│     tools=[...],                                                            │
│     system_prompt=INSTRUCTIONS,                                             │
│     subagents=,     # 注入子 Agent                                          │
│ )                                                                           │
│ ```                                                                         │
│                                                                             │
│ ### 5.2 两种子 Agent 类型                                                   │
│                                                                             │
│ | 类型 | 说明 |                                                             │
│ |------|------|                                                             │
│ | **SubAgent** (本地) | 在同一进程中编译运行,共享状态 Schema |             │
│ | **AsyncSubAgent** (远程) | 通过 HTTP 调用远程 LangGraph 部署,支持        │
│ LangSmith 部署 |                                                            │
│                                                                             │
│ ```python                                                                   │
│ # 远程子 Agent                                                              │from deepagents.middleware.async_subagents import AsyncSubAgentMiddleware   │
│                                                                             │
│ middleware = AsyncSubAgentMiddleware(                                       │
│     async_subagents=[{                                                      │
│         "name": "researcher",                                               │
│         "description": "Remote research agent",                             │
│         "url": "https://my-deployment.langsmith.dev",                       │
│         "graph_id": "research_agent",                                       │
│     }]                                                                      │
│ )                                                                           │
│ ```                                                                         │
│                                                                             │
│ ### 5.3 并行执行                                                            │
│                                                                             │
│ DeepAgents 支持**并行执行**多个子 Agent——主 Agent 可在单次响应中发起多个    │
│ `task` 调用,子 Agent 独立返回结果。                                        │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 6. 状态管理                                                              │
│                                                                             │
│ ### 6.1 DeepAgentState                                                      │
│                                                                             │
│ DeepAgents 使用 `DeepAgentState`(TypedDict)作为 Agent 的运行时状态:      │
│                                                                             │
│ ```python                                                                   │
│ from deepagents.graph import DeepAgentState                                 │
│                                                                             │
│ # 自定义状态扩展                                                            │class MyState(DeepAgentState):                                              │
│     page_url: str                                                           │
│     file_urls: list                                                         │
│                                                                             │
│ agent = create_deep_agent(model=..., state_schema=MyState)                  │
│ ```                                                                         │
│                                                                             │
│ ### 6.2 持久化                                                              │
│                                                                             │
│ - **checkpointer**: LangGraph 的检查点机制,支持对话历史的持久化和恢复      │
│ - **store**: `BaseStore` 实例,用于长期键值存储                             │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 7. 部署                                                                  │
│                                                                             │
│ DeepAgents 作为 LangGraph 应用,通过 **LangGraph CLI** 部署:               │
│                                                                             │
│ ### 7.1 配置文件 `langgraph.json`                                           │
│                                                                             │
│ ```json                                                                     │
│ {                                                                           │
│   "dependencies": ["."],                                                    │
│   "graphs": {                                                               │
│     "research": "./agent.py:agent"                                          │
│   },                                                                        │
│   "env": ".env"                                                             │
│ }                                                                           │
│ ```                                                                         │
│                                                                             │
│ ### 7.2 开发服务器                                                          │
│                                                                             │
│ ```bash                                                                     │
│ langgraph dev          # 启动开发服务器 (默认 http://localhost:8123)        │
│ langgraph deploy       # 部署到生产环境                                     │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 8. 完整示例:Deep Research Agent                                         │
│                                                                             │
│ ```python                                                                   │
│ from datetime import datetime                                               │
│ from langchain.chat_models import init_chat_model                           │
│ from deepagents import create_deep_agent                                    │
│ from research_agent.tools import tavily_search, think_tool                  │
│                                                                             │
│ model = init_chat_model(                                                    │
│     model="anthropic:claude-sonnet-4-5-20250929",                           │
│     temperature=0.0                                                         │
│ )                                                                           │
│                                                                             │
│ research_sub_agent = {                                                      │
│     "name": "research-agent",                                               │
│     "description": "Delegate research to the sub-agent researcher.",        │
│     "system_prompt": RESEARCHER_INSTRUCTIONS,                               │
│     "tools": ,                                                              │
│ }                                                                           │
│                                                                             │
│ agent = create_deep_agent(                                                  │
│     model=model,                                                            │
│     tools=,                                                                 │
│     system_prompt=WORKFLOW_INSTRUCTIONS,                                    │
│     subagents=,                                                             │
│ )                                                                           │
│                                                                             │
│ # 调用 Agent                                                                │
│ result = agent.invoke({"messages": [{"role": "user", "content": "Research   │
│ AI trends 2024"}]})                                                         │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 9. 架构总结图                                                            │
│                                                                             │
│ ```                                                                         │
│                     ┌──────────────────┐                                    │
│                     │   User / Client  │                                    │
│                     └────────┬─────────┘                                    │
│                              │                                              │
│                     ┌────────▼─────────┐                                    │
│                     │  DeepAgentsApp   │  ← TUI/Web 应用层                  │
│                     │  (可选 UI 包装)   │                                   │
│                     └────────┬─────────┘                                    │
│                              │                                              │
│               ┌──────────────▼──────────────┐                               │
│               │     create_deep_agent()     │                               │
│               │   (CompiledStateGraph)      │                               │
│               │                             │                               │
│               │  ┌───────────────────────┐  │                               │
│               │  │   LangGraph Agent     │  │                               │
│               │  │   (ReAct / Tool Call) │  │                               │
│               │  └───────────────────────┘  │                               │
│               │                             │                               │
│               │  Middleware Pipeline:       │                               │
│               │  Filesystem → Memory →      │                               │
│               │  SubAgents → Summarization  │                               │
│               │                             │                               │
│               │  Backend Layer:             │                               │
│               │  State / Store / FS /       │                               │
│               │  Composite / Sandbox        │                               │
│               └─────────────────────────────┘                               │
│                              │                                              │
│                     ┌────────▼─────────┐                                    │
│                     │  LangGraph       │                                    │
│                     │  Platform        │                                    │
│                     │  (Checkpointer,  │                                    │
│                     │   Store, Server) │                                    │
│                     └──────────────────┘                                    │
│ ```                                                                         │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 10. 关键要点                                                             │
│                                                                             │
│ 1. **DeepAgents = LangGraph + Middleware + Backend**:本质是 LangGraph 的   │
│ Agent 预配置方案                                                            │
│ 2. **Middleware                                                             │
│ 是架构核心**:所有扩展能力(文件系统、子Agent、记忆、摘要)都通过           │
│ Middleware 注入                                                             │
│ 3. **Backend                                                                │
│ 解耦存储**:从临时内存到真实文件系统到远程沙箱,通过统一接口切换            │
│ 4. **子 Agent 支持层级委托和并行执行**:适合复杂多步骤任务                  │
│ 5. **与 LangSmith 深度集成**:远程子 Agent 可通过 LangSmith 部署调用        │
│ 6. **部署即标准 LangGraph 应用**:使用 `langgraph dev/deploy` 命令          │
└─────────────────────────────────────────────────────────────────────────────┘
Step 12: 决策与调用 (Node: model) 
└── 普通工具调用: write_todos
Tool Output (write_todos): Updated todo list to [{'content': '通过 
DocsResearcher 调研 LangChain DeepAgents 官方文档和技术架构', 'status': '...
Step 16: 决策与调用 (Node: model) 
└── 普通工具调用: write_file
Tool Output (write_file): Updated file /home/user/DeepAgents_对比报告.md
Step 20: 决策与调用 (Node: model) 
└── 普通工具调用: write_todos
Tool Output (write_todos): Updated todo list to [{'content': '通过 
DocsResearcher 调研 LangChain DeepAgents 官方文档和技术架构', 'status': '...
┌────────────────────────── Agent回复 (Node: model) ──────────────────────────┐
│ ---                                                                         │
│                                                                             │
│ 调研完成!完整报告已保存到                                                  │
│ `/home/user/DeepAgents_对比报告.md`。以下是核心发现的摘要:                 │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ## 📋 调研成果概览                                                          │
│                                                                             │
│ 两项独立调研(官方文档 + 社区实践)分别由 DocsResearcher 和                 │
│ CommunityResearcher 并行完成,然后进行了交叉对比分析。                      │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ### 🔷 官方架构核心                                                         │
│                                                                             │
│ DeepAgents 本质是 **LangGraph + Middleware + Backend** 的预配置方案:       │
│                                                                             │
│ ```                                                                         │
│ create_deep_agent() → Middleware Pipeline → Backend Layer → LangGraph       │
│ Runtime                                                                     │
│ ```                                                                         │
│                                                                             │
│ - **Middleware**                                                            │
│ 是所有扩展能力的核心(Filesystem、SubAgent、Memory、Summarization)         │
│ - **Backend** 解耦存储(State → Store → Filesystem → Composite 四层递进)   │
│ - **SubAgent** 支持层级委托和并行执行,可与 LangSmith 远程部署集成          │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ### 🔶 社区实践的关键发现                                                   │
│                                                                             │
│ 社区实践在三个层面与官方文档形成了有价值的互补:                            │
│                                                                             │
│ | 维度 | 官方说的 | 社区发现的 |                                            │
│ |------|----------|------------|                                            │
│ | **Middleware 替换** | 可自定义 |                                          │
│ 同名是**替换**而非合并,覆盖时极易丢失依赖参数 |                            │
│ | **SubAgent 继承** | 通用子 Agent 自动继承 | 自定义子 Agent                │
│ **不会**自动继承 middleware/skills(Issue #2744) |                         │
│ | **承重中间件** | 未特别标注 | `FilesystemMiddleware` 和                   │
│ `SubAgentMiddleware` 不可移除(会 ValueError) |                            │
│ | **模型兼容性** | "任何支持 tool calling 的模型" | 开源 LLM                │
│ 对内置工具有困难,生产必须用 Claude/GPT 等强模型 |                          │
│ | **评估** | 五大评估模式 | 每个测试用例可能需要独立断言逻辑,打破了传统    │
│ LLM 评估假设 |                                                              │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ### 🔴 四大"踩坑高发区"(社区血泪总结)                                     │
│                                                                             │
│ 1. **覆盖 Middleware 时忘记传 `backend` 和 `permissions`** → 运行时崩溃     │
│ 2. **使用 `interrupt_on` 却忘记传 `checkpointer`** → 直接报错               │
│ 3. **工具 docstring 过于简略**(如 `"Does finance stuff"`)→ Agent          │
│ 无法正确路由                                                                │
│ 4. **每次调用用不同的 `thread_id`** → 记忆完全丢失                          │
│                                                                             │
│ ---                                                                         │
│                                                                             │
│ ### 🟢 一句话结论                                                           │
│                                                                             │
│ > **DeepAgents 是 LangGraph                                                 │
│ 之上的"最佳实践固化层"**——官方文档提供清晰的架构蓝图,社区实践揭示蓝图落地  │
│ 时的真实边界条件和生存法则。两者结合使用,才能高效且安全地构建生产级 Agent  │
│ 系统。                                                                      │
│                                                                             │
│ 详细对比表、代码示例、社区原话引用和完整的参考来源请查看报告文件。          │
└─────────────────────────────────────────────────────────────────────────────┘
运行结束退出。

Process finished with exit code 0

五、文件系统集成 (Filesystem & Sandbox)

组件:FilesystemMiddleware & Backend
功能:这是 DeepAgent 的核心"外挂大脑"。Agent 不再将所有检索到的长文塞入上下文(Context),而是将其写入 Filesystem(如 /workspace),仅在需要时读取。这极大地扩展了 Agent 的"工作记忆"。
核心工具集

  • ls: 浏览目录结构。
  • read_file: 读取文件内容。关键封装:支持 offset(偏移量)和 limit(行数),强制 Agent 对大文件进行分页读取,避免一次性读爆上下文。
  • write_file: 创建新文件。
  • edit_file: 修改文件。支持精确的字符串替换和 replace_all 模式,并有防呆设计(要求先读后改)。
  • glob: 通配符模糊查找文件。
  • grep: 正则表达式搜索文件内容,像命令行一样在代码库中定位目标。
  • execute: 执行 Shell 命令(需 Sandbox 支持),用于运行代码、安装依赖等。

亮点功能:大结果自动转存

  • 当 Agent 调用工具产生的结果过长时,系统会自动拦截,将完整结果写入文件系统(如 /large_tool_results/{id}),并只给 Agent 返回一个摘要和文件路径。这完美解决了搜索结果或日志文件过长导致 Agent 崩溃的问题。

后端支持 (Backends)

  • FilesystemBackend: 直接操作本地磁盘。
  • DockerBackend: 在 Docker 容器中执行,提供隔离环境。
  • StoreBackend: 存储在数据库中,支持查询和检索。
  • E2BBackend: 使用 E2B 云端沙箱。
  • CompositeBackend: 混合模式(如:本地存文件,Docker 跑代码)

5.1.核心工具测试

import os

import dotenv
from deepagents.backends import FilesystemBackend
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent

# 1.初始化环境
dotenv.load_dotenv()

# 2.创建文件系统
# 使用本地文件系统后端,根目录设为./workspace
# 这样我们可以看到真实的文件创建,设置virtual_mode=True表示文件系统是虚拟的,创建的文件不会实际写入磁盘
backend = FilesystemBackend(root_dir="./workspace", virtual_mode=True)

# 3.模型初始化
deepseek_v4_pro = init_chat_model(
    model="deepseek-v4-pro",
    model_provider="deepseek",
    base_url="https://api.deepseek.com",
    api_key=os.getenv("api_key"),
    temperature=0.7,
    max_tokens=10000,
)

# 4.创建agent
agent = create_deep_agent(
    model=deepseek_v4_pro,
    backend=backend,
    system_prompt="""
        你是一个文件系统操作助手,请你根据用户指令使用相应的工具。
        """
)


# 5.运行并输出结果
def run_filesystem_backend(task_name, instruction):
    print(f"\n [测试:{task_name}]")
    print(f"指令:{instruction}")

    try:
        # 直接使用agent.invoke调用即可,简单一点
        result = agent.invoke({"messages": [{"role": "user", "content": instruction}]})
        print(f"🤖AI大模型回复结果:{result['messages'][-1].content}")

        # 如果有工具调用,则打印工具调用详情
        for msg in result["messages"]:
            if hasattr(msg, "tool_calls") and msg.tool_calls:
                for tool_call in msg.tool_calls:
                    print(f"🔧工具调用:{tool_call["name"]}, 参数:{tool_call["args"]}")
    except Exception as e:
        print(f"❌️ 错误:{e}")



# 1. write_file测试
# run_filesystem_backend("write_file", "请创建一个文件test.txt,并写入内容:Hello, World!")
# 输出: [测试:write_file]
# 指令:请创建一个文件test.txt,并写入内容:Hello, World!
# 🤖AI大模型回复结果:文件 `test.txt` 已创建成功,内容为 **Hello, World!**。


# 2.ls测试
# run_filesystem_backend("ls", "请列出当前目录下的所有文件")
# 输出: [测试:ls]
# 指令:请列出当前目录下的所有文件
# 🤖AI大模型回复结果:当前目录(根目录 `/`)下只有一个文件:
#
# - **`\test.txt`**
#
# 以上就是目前目录中的所有文件。如果你想查看该文件的内容或进行其他操作,请随时告诉我!
# 🔧工具调用:ls, 参数:{'path': '/'}


# 3.read_file测试
# run_filesystem_backend("read_file", "请读取文件test.txt的内容")
# 输出: [测试:read_file]
# 指令:请读取文件test.txt的内容
# 🤖AI大模型回复结果:文件 `test.txt` 的内容如下:
#
# ```
# Hello, World!
# ```
#
# 这是一个简单的文本文件,位于根目录 `/test.txt`,只有一行内容 "Hello, World!"。
# 🔧工具调用:glob, 参数:{'pattern': '**/test.txt'}
# 🔧工具调用:read_file, 参数:{'file_path': '/test.txt'}

# 4.edit_file测试
# run_filesystem_backend("edit_file", "请修改文件test.txt的内容为:Hello, DeepSeek!")
# 输出: [测试:edit_file]
# 指令:请修改文件test.txt的内容为:Hello, DeepSeek!
# 🤖AI大模型回复结果:已完成修改!`test.txt` 文件的内容已从 "Hello, World!" 更新为 "Hello, DeepSeek!"。
# 🔧工具调用:read_file, 参数:{'file_path': '/test.txt'}
# 🔧工具调用:edit_file, 参数:{'file_path': '/test.txt', 'old_string': 'Hello, World!', 'new_string': 'Hello, DeepSeek!'}

# 5.grep测试
# run_filesystem_backend("grep", "请在当前目录下搜索包含DeepSeek的文件")
# 输出: [测试:grep]
# 指令:请在当前目录下搜索包含DeepSeek的文件
# 🤖AI大模型回复结果:搜索完成!在当前目录下找到了 **1 个文件**包含 "DeepSeek":
#
# | 文件 |
# |------|
# | `/test.txt` |
#
# 需要我查看该文件的内容吗?
# 🔧工具调用:grep, 参数:{'pattern': 'DeepSeek', 'path': '/', 'output_mode': 'files_with_matches'}


# 6.glob测试
# run_filesystem_backend("glob", "请列出当前目录下所有的.txt文件")

# 输出: [测试:glob]
# 指令:请列出当前目录下所有的.txt文件
# 🤖AI大模型回复结果:当前目录下共找到 **1 个 `.txt` 文件**:
#
# - `/test.txt`
# 🔧工具调用:glob, 参数:{'pattern': '*.txt', 'path': '/'}

# 7.execute测试
# run_filesystem_backend("execute", "请执行命令:ls -l")
# 输出: [测试:execute]
# 指令:请执行命令:ls -l
# 🤖AI大模型回复结果:根目录 `/` 下当前有一个文件:
# 
# | 文件路径 |
# |----------|
# | `/test.txt` |
# 
# > 注意:当前 `ls` 工具不支持 `-l` 长格式参数,因此无法显示权限、大小、时间戳等详细信息。如需查看更多文件信息,可以告诉我具体路径,我可以帮你读取文件内容。
# 🔧工具调用:ls, 参数:{'path': '/'}

5.2.Backend 后端应用

1.默认模式(内存沙箱)

  • 内存沙箱,支持在内存中执行代码,防止代码注入攻击。代码执行结果会被存储在内存中,不会对本地环境造成影响。执行结束后,内存中的数据会被清除。
import os

import dotenv
from deepagents.backends import FilesystemBackend
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent

# 1.初始化环境
dotenv.load_dotenv()

# 2.创建文件系统
# 使用本地文件系统后端,根目录设为./workspace
# 这样我们可以看到真实的文件创建,设置virtual_mode=True表示文件系统是虚拟的,创建的文件不会实际写入磁盘
# backend = FilesystemBackend(root_dir="./workspace", virtual_mode=True)

# 3.模型初始化
deepseek_v4_pro = init_chat_model(
    model="deepseek-v4-pro",
    model_provider="deepseek",
    base_url="https://api.deepseek.com",
    api_key=os.getenv("api_key"),
    temperature=0.7,
    max_tokens=10000,
)

# 4.创建agent
agent = create_deep_agent(
    model=deepseek_v4_pro,
    # backend=backend,  # 这里注释掉,用于模拟不传
    system_prompt="""
        你是一个文件系统操作助手,请你根据用户指令使用相应的工具。
        """
)


# 5.运行并输出结果
def run_filesystem_backend(task_name, instruction):
    print(f"\n [测试:{task_name}]")
    print(f"指令:{instruction}")

    try:
        # 直接使用agent.invoke调用即可,简单一点
        result = agent.invoke({"messages": [{"role": "user", "content": instruction}]})
        print(f"🤖AI大模型回复结果:{result['messages'][-1].content}")

        # 如果有工具调用,则打印工具调用详情
        for msg in result["messages"]:
            if hasattr(msg, "tool_calls") and msg.tool_calls:
                for tool_call in msg.tool_calls:
                    print(f"🔧工具调用:{tool_call["name"]}, 参数:{tool_call["args"]}")
    except Exception as e:
        print(f"❌️ 错误:{e}")

# 1. write_file测试
# run_filesystem_backend("write_file", "请创建一个文件test.txt,并写入内容:Hello, World!")
# 输出:  [测试:write_file]
# 指令:请创建一个文件test.txt,并写入内容:Hello, World!
# 🤖AI大模型回复结果:文件 `test.txt` 已创建成功,内容为 **Hello, World!**。
# 🔧工具调用:write_file, 参数:{'file_path': '/test.txt', 'content': 'Hello, World!'}

# 2.read_file读取
# run_filesystem_backend("read_file", "请读取文件test.txt的内容")
# 输出:  [测试:read_file]
# 指令:请读取文件test.txt的内容
# 🤖AI大模型回复结果:文件 `test.txt` 在当前文件系统中不存在。我尝试了以下方式查找:
# 
# - 使用 glob 搜索 `**/test.txt` — 未找到任何匹配文件
# - 检查根目录 `/` — 目录为空
# - 直接读取 `/test.txt` — 文件不存在
# 
# 请问您能否提供文件的完整路径,或者确认该文件是否已经被创建?如果需要,我也可以帮您创建一个新的 `test.txt` 文件。
# 🔧工具调用:glob, 参数:{'pattern': '**/test.txt'}
# 🔧工具调用:ls, 参数:{'path': '/'}
# 🔧工具调用:read_file, 参数:{'file_path': '/test.txt'}

2.持久化模式(操作真实文件)

import os

import dotenv
from deepagents.backends import FilesystemBackend
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent

# 1.初始化环境
dotenv.load_dotenv()

# 2.创建文件系统
# 使用本地文件系统后端,根目录设为./workspace
# 这样我们可以看到真实的文件创建,设置virtual_mode=True表示文件系统是虚拟的,创建的文件不会实际写入磁盘
backend = FilesystemBackend(root_dir="./workspace", virtual_mode=True)

# 3.模型初始化
deepseek_v4_pro = init_chat_model(
    model="deepseek-v4-pro",
    model_provider="deepseek",
    base_url="https://api.deepseek.com",
    api_key=os.getenv("api_key"),
    temperature=0.7,
    max_tokens=10000,
)

# 4.创建agent
agent = create_deep_agent(
    model=deepseek_v4_pro,
    backend=backend,  # 指定FilesystemBackend利用本地磁盘持久化存储
    system_prompt="""
        你是一个文件系统操作助手,请你根据用户指令使用相应的工具。
        """
)


# 5.运行并输出结果
def run_filesystem_backend(task_name, instruction):
    print(f"\n [测试:{task_name}]")
    print(f"指令:{instruction}")

    try:
        # 直接使用agent.invoke调用即可,简单一点
        result = agent.invoke({"messages": [{"role": "user", "content": instruction}]})
        print(f"🤖AI大模型回复结果:{result['messages'][-1].content}")

        # 如果有工具调用,则打印工具调用详情
        for msg in result["messages"]:
            if hasattr(msg, "tool_calls") and msg.tool_calls:
                for tool_call in msg.tool_calls:
                    print(f"🔧工具调用:{tool_call["name"]}, 参数:{tool_call["args"]}")
    except Exception as e:
        print(f"❌️ 错误:{e}")

# 1. write_file测试
# run_filesystem_backend("write_file", "请创建一个文件test.txt,并写入内容:Hello, World!")
# 输出: [测试:write_file]
# 指令:请创建一个文件test.txt,并写入内容:Hello, World!
# 🤖AI大模型回复结果:文件 `/test.txt` 已创建成功,内容为 `Hello, World!`。
# 🔧工具调用:write_file, 参数:{'file_path': '/test.txt', 'content': 'Hello, World!'}

# 2.read_file读取
# run_filesystem_backend("read_file", "请读取文件test.txt的内容")
# 输出: [测试:read_file]
# 指令:请读取文件test.txt的内容
# 🤖AI大模型回复结果:文件 `test.txt` 的内容是:
# 
# ```
# Hello, World!
# ```
# 🔧工具调用:glob, 参数:{'pattern': '**/test.txt'}
# 🔧工具调用:read_file, 参数:{'file_path': '/test.txt'}

3.演示大文件读取分页

  • 在FilesystemBackend分页的参数是固定死的,默认每次读取500行,我们可以在系统提示词中指定每次读取的行数。
import os
import traceback

import dotenv
from deepagents.backends import FilesystemBackend
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent
from langchain_core.messages import HumanMessage, ToolMessage, BaseMessage

# 1.初始化环境
dotenv.load_dotenv()

# 2.创建文件系统
# 使用本地文件系统后端,根目录设为./workspace
# 这样我们可以看到真实的文件创建,设置virtual_mode=True表示文件系统是虚拟的,创建的文件不会实际写入磁盘
backend = FilesystemBackend(root_dir="./workspace", virtual_mode=True)

# 3.模型初始化
deepseek_v4_pro = init_chat_model(
    model="deepseek-v4-pro",
    model_provider="deepseek",
    base_url="https://api.deepseek.com",
    api_key=os.getenv("api_key"),
    temperature=0.7,
    max_tokens=10000,
)

system_prompt = """
        你是一个专业的系统管理员。
        你的任务是从日志文件中查找特定的错误代码。
        注意:日志文件可能非常大,为了避免上下文溢出,你必须使用 'read_file'工具的分页功能。
        每次读取请限制在300行以内(limit=300),并使用offset参数向后滚动。
        直到找到目标信息为止。
        """

# 4.创建agent
agent = create_deep_agent(
    model=deepseek_v4_pro,
    backend=backend,  # 指定FilesystemBackend利用本地磁盘持久化存储
    system_prompt=system_prompt
)


LARGE_FILE_NAME = "server_logs.txt"
# LARGE_FILE_PATH = WORK_DIR / LARGE_FILE_NAME
TARGET_SECRET = "RustFS"

# 准备用户指令
task = f"请从文件'/{LARGE_FILE_NAME}'中搜索包含'{TARGET_SECRET}'的行,并告诉我它的具体内容。"


step = 0

try:
    # agent.stream 默认多为 updates 模式:
    #   chunk: dict[str, Any]
    #   形如 {"model": {"messages": [...]}} 或 {"tools": {"messages": [...]}}
    for chunk in agent.stream({"messages": [HumanMessage(content=task)]}):
        # chunk.items() → Iterator[tuple[str, Any]]
        #   node_name: str   —— LangGraph 节点名,如 "model" / "tools"
        #   node_data: Any   —— 该节点本次状态增量(常为 dict,偶发 Overwrite 包装)
        for node_name, node_data in chunk.items():
            # 跳过空节点(None / {} / [] 等假值)
            if not node_data:
                continue

            # 处理 Overwrite 对象(langgraph 状态更新包装器)
            # Overwrite 有 .value 属性,真正的增量数据在 value 里
            # 类型示意:Overwrite(value={"messages": [...]}) → 取出 dict
            if hasattr(node_data, "value"):
                node_data = node_data.value  # node_data: Any → 期望变成 dict

            # 只处理字典形态的节点数据;其它类型(如纯标量)跳过
            if not isinstance(node_data, dict):
                continue
            # 至此 node_data: dict[str, Any],常见键: "messages", 也可能有 todos 等
            if "messages" in node_data:
                # msgs: list[BaseMessage] | Overwrite | BaseMessage(偶发单条)
                msgs = node_data["messages"]

                # messages 也可能被 Overwrite 包一层
                if hasattr(msgs, "value"):
                    msgs = msgs.value

                # 统一成 list,后面 for 遍历
                if not isinstance(msgs, list):
                    msgs = [msgs]
                # 至此 msgs: list[BaseMessage]

                for msg in msgs:
                    # msg: BaseMessage 子类
                    #   - AIMessage: type="ai",可能带 tool_calls
                    #   - ToolMessage: type="tool",.name=工具名,.content=工具返回
                    #   - HumanMessage: type="human"

                    # ---------- 1) 模型决定调工具:AIMessage + 非空 tool_calls ----------
                    # tool_calls: list[dict] | list[对象]
                    #   单条常见结构 dict:
                    #     {"name": str, "args": dict[str, Any], "id": str, ...}
                    if hasattr(msg, "tool_calls") and msg.tool_calls:
                        step += 1
                        print(
                            f"\n[Step {step}] Agent决定调用工具 "
                            f"(Node: {node_name!r} type={type(node_name).__name__})"
                        )
                        for tool_call in msg.tool_calls:
                            # 兼容 dict / 对象两种 tool_call
                            if isinstance(tool_call, dict):
                                name = tool_call.get("name", "unknown")  # name: str
                                args = tool_call.get("args", {})  # args: dict[str, Any]
                            else:
                                name = getattr(tool_call, "name", "unknown")
                                args = getattr(tool_call, "args", {}) or {}

                            print(f"    >>>工具:{name}  (type={type(name).__name__})")

                            if name == "read_file":
                                # FilesystemMiddleware 的分页读文件参数
                                offset = args.get("offset", 0)  # offset: int,从第几行开始
                                limit = args.get("limit", "Default")  # limit: int | str,最多读几行
                                # 不同版本参数名可能是 path 或 file_path
                                path_val = args.get("path") or args.get("file_path")  # path_val: str | None
                                print(
                                    f"    >>>参数:path={path_val!r} ({type(path_val).__name__}), "
                                    f"offset={offset!r} ({type(offset).__name__}), "
                                    f"limit={limit!r} ({type(limit).__name__})"
                                )
                                print(
                                    f"        (说明:正在读取从第{offset}行开始,最多读取{limit}行数据)"
                                )
                            else:
                                print(f"    >>>参数:{args}  (type={type(args).__name__})")

                    # ---------- 2) 工具执行结果:ToolMessage ----------
                    # msg.content: str | list(多数为 str,即工具返回正文)
                    elif isinstance(msg, ToolMessage):
                        content = msg.content
                        # 若 content 不是纯 str(极少见多模态块),先转成 str 再统计行数
                        content_str = content if isinstance(content, str) else str(content)
                        line_count = len(content_str.splitlines())  # line_count: int
                        # 检查本块工具输出里是否出现目标关键字
                        found_secret = TARGET_SECRET in content_str  # found_secret: bool

                        preview = content_str[:100].replace("\n", " ") + "..."  # preview: str
                        print(
                            f"\n[Tool Output] (Node: {node_name!r}, "
                            f"tool_name={msg.name!r} type={type(msg.name).__name__}, "
                            f"读取了 {line_count} 行, content_type={type(content).__name__})"
                        )
                        print(f"    内容预览:{preview}")
                        if found_secret:
                            print(
                                f"    成功:在此分块中发现了目标 Secret:"
                                f"{TARGET_SECRET!r} 已找到!"
                            )

                    # ---------- 3) 模型纯文本最终/中间回复:AIMessage,无 tool_calls ----------
                    # msg.type: str,AI 消息为 "ai"
                    # msg.content: str | list
                    elif isinstance(msg, BaseMessage) and msg.type == "ai" and msg.content:
                        print(
                            f"\n[Agent最终回复] (Node: {node_name!r}, "
                            f"msg.type={msg.type!r}, content_type={type(msg.content).__name__}):"
                        )
                        print(f"-" * 40)
                        print(msg.content)

except Exception as e:
    print(f"运行错误:{e}")
    traceback.print_exc()

输出:

D:\anaconda3\envs\deepagents\python.exe E:\code\InsightFlow\create_demo_deep_agent\demo_backend_file2.py 

[Step 1]Agent决定调用工具(Node: model)
    >>>工具:grep
>>>参数:{'pattern': 'RustFS', 'path': '/', 'output_mode': 'content'}

[Tool Output] (Node: tools 读取了1行数据)
    内容预览:No matches found...

[Step 2]Agent决定调用工具(Node: model)
    >>>工具:glob
>>>参数:{'pattern': '**/server_logs.txt'}

[Tool Output] (Node: tools 读取了1行数据)
    内容预览:['/\\server_logs.txt']...

[Step 3]Agent决定调用工具(Node: model)
    >>>工具:read_file
    >>>参数:path=/server_logs.txt, offset=0, limit=300
        (说明:正在读取从第0行开始,最多读取300行数据)

[Tool Output] (Node: tools 读取了87行数据)
    内容预览:     1    一、文件存储方案:RustFS 集群 + 备份服务器      2    1. RustFS 集成优势      3    S3 100% 兼容:与现有前端/后端通过 S3 API 无缝对接(bot...
    👌成功:在此分块中发现了目标Secret:RustFS已找到!

[Step 4]Agent决定调用工具(Node: model)
    >>>工具:grep
>>>参数:{'pattern': 'RustFS', 'path': '/server_logs.txt', 'output_mode': 'content'}

[Tool Output] (Node: tools 读取了1行数据)
    内容预览:No matches found...

[Step 5]Agent决定调用工具(Node: model)
    >>>工具:grep
>>>参数:{'output_mode': 'content', 'path': '/\\server_logs.txt', 'pattern': 'RustFS'}

[Tool Output] (Node: tools 读取了1行数据)
    内容预览:No matches found...

[Agent最终回复] (Node: model):
----------------------------------------
我已经通过读取文件获取了全部内容。以下是 `/server_logs.txt` 中所有包含 **RustFS** 的行及其内容:

---

| 行号 | 内容 |
|------|------|
| **1** | 一、文件存储方案:**RustFS** 集群 + 备份服务器 |
| **2** | 1. **RustFS** 集成优势 |
| **4~5** | S3 100% 兼容 + **Rust** 实现:高性能、内存安全,与您团队的 Rust 技术栈理念一致。 |
| **19** | **RustFS** 生产集群 (S3 兼容) |
| **29** | **RustFS** 独立实例 / 或 Rsync + 冷存储 |
| **32** | 生产集群:3 个 **RustFS** 节点组成分布式集群…**RustFS** 具体配置参考其文档… |
| **34** | 备份服务器:可部署另一台独立的 **RustFS** 服务(单节点即可)…或 **RustFS** 自身的复制机制… |
| **37** | 实时/准实时备份:利用 **RustFS** 的 Bucket Replication… |
| **43** | 注意:**RustFS** 的具体配置需参照其官方文档… |
| **46** | …仅存储层切换为 **RustFS**。核心交互如下: |
| **48** | 文件上传:FastAPI 接收文件流 → 调用 **RustFS** S3 API 存入生产集群… |
| **50** | 文件下载/预览:前端通过预签名 URL 直接访问 **RustFS** 生产集群。 |
| **74** | …**RustFS** 集群部署在 CPU 服务器上,不占用 GPU,对整体方案无影响。 |
| **77** | 搭建 **RustFS** 测试集群:使用 Docker Compose 快速启动 3 节点… |
| **85** | 如果您需要更详细的 **RustFS** 部署方案或需调整其他模块,请随时告知! |

---

### 总结

这个文件实际上是一份 **技术方案文档**(并非真正的服务器日志),核心内容围绕 **RustFS** 分布式存储集群的架构设计:

- **3 节点生产集群 + 1 节点备份服务器**的存储架构
- **S3 100% 兼容**,可与现有 boto3/aws-sdk 无缝对接
- **Rust 实现**,高性能、内存安全,Apache 2.0 协议
- 备份策略:支持 Bucket Replication(分钟级 RPO)或 rclone 定时同步
- 与 8×RTX 4090 GPU 方案配合,RustFS 部署在 CPU 服务器上,不占用 GPU 资源

如果你需要我进一步深挖某个具体细节,请随时告知!

Process finished with exit code 0

4.E2BBackend 使用E2B云沙箱

E2B (Environment To Be) 是一个 专为 AI 智能体设计的云端安全执行环境 。可以把它想象成一台 云端电脑 或 远程服务器 ,DeepAgents 将其作为“外挂大脑”和“执行手脚”。当 AI 需要写代码、运行脚本或操作文件时,它不会在你的本地机器上操作,而是连接到 E2B 的云端环境中进行。

核心特性

  1. 安全性与隔离性 (Security & Isolation) :

    • AI 生成的代码(可能包含错误或恶意逻辑)完全运行在云端沙箱中, 绝不会破坏你本地的电脑环境 。
    • 演示代码中,Agent 即使执行了 rm -rf / ,也只是删除了云端临时的沙箱,对宿主机毫发无损。
  2. 持久化会话 (Long-running Sessions) :

    • 沙箱可以保持运行状态。Agent 可以先创建一个文件(如演示中的 /home/user/hello.py ),然后在后续步骤中运行它。环境状态在会话期间是保持的。
  3. 标准 Linux 环境 :

    • 它提供标准的 Linux Shell。演示中 Agent 执行了 uname -a 和 python –version ,就像在真实的服务器上一样。

step1: 访问 https://e2b.dev 注册登陆,然后并获取 API_Key

image

  • 在左侧选择API Keys选项,创建API

image

  • 创建API Key后记得保存好,保存到.env文件中,作为:E2B_API_KEY

image

step2: 安装依赖

pip install e2b

e2b_backend.py

"""
E2B 沙箱后端示例:让 DeepAgents 的 execute / 读写文件跑在远程隔离环境里。

整体关系:
  create_deep_agent(backend=E2BBackend(...))
      → Agent 调 execute / write_file / read_file 等工具
      → 落到本类的 execute / upload_files / download_files
      → 再调用 e2b.Sandbox 云端 API
"""

from typing import Optional, List

# Sandbox: e2b 云端沙箱客户端类
# 类型: type[Sandbox]
# 实例化后大致能:
#   sandbox.commands.run(cmd) -> 有 stdout/stderr/exit_code
#   sandbox.files.write(path, content)
#   sandbox.files.read(path) -> str | bytes
#   sandbox.kill()
from e2b import Sandbox

# BaseSandbox: DeepAgents 抽象基类,子类至少要实现 execute(command) -> ExecuteResponse
# 其它 ls/read/write/grep 等默认会「拼 shell 命令再调 execute」
from deepagents.backends.sandbox import BaseSandbox

# 协议里约定的数据结构(dataclass):
#   ExecuteResponse(output: str, exit_code: int | None, truncated: bool)
#     例: ExecuteResponse(output="hello\n", exit_code=0, truncated=False)
#   FileUploadResponse(path: str, error: str | None)
#     例: FileUploadResponse(path="/tmp/a.txt", error=None)  # 成功
#     例: FileUploadResponse(path="/root/x", error="permission_denied")
#   FileDownloadResponse(path: str, content: bytes | None, error: str | None)
#     例: FileDownloadResponse(path="/tmp/a.txt", content=b"hi", error=None)
from deepagents.backends.protocol import (
    ExecuteResponse,
    FileDownloadResponse,
    FileUploadResponse,
)


class E2BBackend(BaseSandbox):
    """
    把 DeepAgents 的「沙箱后端协议」接到 E2B。

    继承 BaseSandbox 后:
      - 必须实现: execute(command: str) -> ExecuteResponse
      - 可选覆盖: upload_files / download_files(用 E2B 文件 API 更直接)
      - 自动获得: ls_info / read / write / edit / grep 等(内部调 execute 跑 shell)
    """

    def __init__(
        self,
        template: str = "base",
        api_key: Optional[str] = None,
        timeout: Optional[int] = None,
        metadata: Optional[dict[str, str]] = None,
    ) -> None:
        """
        创建一个远程 E2B 沙箱实例,并保存在 self.sandbox。

        参数类型与样例:
          template: str
            例: "base" —— E2B 预置模板名/ID
          api_key: str | None
            例: "e2b_xxxx" 或 None(None 时读环境变量 E2B_API_KEY)
          timeout: int | None
            例: 300 —— 沙箱最长存活秒数;None 用 SDK 默认
          metadata: dict[str, str] | None
            例: {"project": "insightflow", "env": "dev"}
        """
        # Sandbox.create(...): 云端启动一台隔离 Linux 环境
        # 返回类型: Sandbox(e2b 客户端对象)
        # 例: <Sandbox sandbox_id="iXXXX...">
        self.sandbox = Sandbox.create(
            template=template,  # str
            api_key=api_key,  # str | None
            timeout=timeout,  # int | None
            metadata=metadata,  # dict[str, str] | None
        )

    @property
    def id(self) -> str:
        """
        沙箱唯一 ID,供日志/追踪用。

        返回类型: str
        例: "i6wz5jfhsa1a2b3c4d5e"
        """
        # sandbox_id: str —— E2B 分配的实例 ID
        return self.sandbox.sandbox_id

    def execute(self, command: str) -> ExecuteResponse:
        """
        DeepAgents 核心抽象方法:在沙箱里执行一条 shell 命令。

        入参:
          command: str
            例: "ls -la /home"
            例: "python3 -c 'print(1+1)'"

        返回:
          ExecuteResponse(
            output: str,          # stdout+stderr 拼在一起,给 LLM 看
            exit_code: int|None,  # 0 成功,非 0 失败
            truncated: bool,      # 输出是否被截断
          )
          成功例:
            ExecuteResponse(output="2\\n", exit_code=0, truncated=False)
          失败例:
            ExecuteResponse(output="Error executing command: ...", exit_code=1, truncated=False)
        """
        try:
            # commands.run(command: str) -> CommandResult(e2b 对象)
            # stdout    str    标准输出:程序正常打印给用户看的内容    "hello\n"、"2\n"、ls 列出的文件名
            # stderr    str    标准错误:警告/报错信息(不一定代表进程失败)    ""(没有错误)、"Permission denied\n"
            # exit_code    int    退出码:进程结束状态    0 = 成功;非 0(如 1、127)= 失败
            # 常见字段:
            #   result.stdout: str     例: "hello\\n"
            #   result.stderr: str     例: "" 或 "warn...\\n"
            #   result.exit_code: int  例: 0
            result = self.sandbox.commands.run(command)

            # 协议要求把 stdout/stderr 合成一个字符串给模型
            combined_output: str = (result.stdout or "") + (result.stderr or "")
            # 例: combined_output == "file1\\nfile2\\n"

            return ExecuteResponse(
                output=combined_output,  # str
                exit_code=result.exit_code,  # int
                truncated=False,  # bool;这里不做截断检测,固定 False
            )
        except Exception as e:
            # 任何异常都转成「失败的 ExecuteResponse」,避免把异常直接抛给 Agent 图
            # str(e): str  例: "SandboxTimeoutError: ..."
            return ExecuteResponse(
                output=f"Error executing command: {str(e)}",  # str
                exit_code=1,  # int —— 约定非 0 表示失败
                truncated=False,  # bool
            )

    def upload_files(self, files: list[tuple[str, bytes]]) -> List[FileUploadResponse]:
        """
        批量上传文件到沙箱(覆盖 BaseSandbox 默认「用 shell 写文件」实现)。

        注意协议方法名是 upload_files(复数),不是 upload_file。

        入参:
          files: list[tuple[str, bytes]]
            每个元素 = (远程路径, 文件字节内容)
            例:
              [
                ("/home/user/a.txt", b"hello"),
                ("/tmp/data.json", b'{"x": 1}'),
              ]

        返回:
          list[FileUploadResponse]
            成功例: [FileUploadResponse(path="/home/user/a.txt", error=None)]
            失败例: [FileUploadResponse(path="/root/x", error="permission_denied")]
            error 允许值大致: None | "invalid_path" | "permission_denied" | ...
        """
        # responses: list[FileUploadResponse] —— 与 files 一一对应的结果列表
        responses: List[FileUploadResponse] = []

        # path: str      例: "/home/user/notes/a.txt"
        # content: bytes 例: b"hello world"
        for path, content in files:
            try:
                # 取父目录,便于 mkdir -p
                # rsplit("/", 1): 从右边按 / 切一次
                #   "/home/user/a.txt".rsplit("/", 1) -> ["/home/user", "a.txt"]
                #   再 [0] 得到父目录 "/home/user"
                # 注意: 不是 rstrip(rstrip 是删字符集合,语义完全不同)
                parent_dir: str = path.rsplit("/", 1)[0] if "/" in path else ""
                # parent_dir 例: "/home/user" 或 ""(文件就在根路径名时)

                if parent_dir:
                    # 在沙箱里创建目录;返回值这里忽略
                    # 命令例: "mkdir -p /home/user"
                    self.sandbox.commands.run(f"mkdir -p {parent_dir}")

                # files.write(path: str, content: str | bytes) -> None(写入远程文件)
                # 例: write("/home/user/a.txt", b"hello")
                self.sandbox.files.write(path, content)

                # 成功:error=None
                responses.append(FileUploadResponse(path=path, error=None))
            except Exception as e:
                # error_msg: str  例: "permission denied: /root"
                error_msg: str = str(e).lower()

                # error: str —— 协议约定的结构化错误码(给 LLM/调用方看)
                error: str = "invalid_path"
                if "permission" in error_msg:
                    error = "permission_denied"

                responses.append(FileUploadResponse(path=path, error=error))

        # 例: [FileUploadResponse(...), FileUploadResponse(...)]
        return responses

    def download_files(self, paths: list[str]) -> List[FileDownloadResponse]:
        """
        批量从沙箱下载文件。

        注意协议方法名是 download_files(复数)。

        入参:
          paths: list[str]
            例: ["/home/user/a.txt", "/tmp/out.log"]

        返回:
          list[FileDownloadResponse]
            成功例:
              FileDownloadResponse(
                path="/home/user/a.txt",
                content=b"hello",   # 必须是 bytes | None
                error=None,
              )
            失败例:
              FileDownloadResponse(
                path="/missing.txt",
                content=None,
                error="file_not_found",
              )
        """
        responses: List[FileDownloadResponse] = []

        # path: str  例: "/home/user/a.txt"
        for path in paths:
            try:
                # files.read(path: str) -> str | bytes(SDK 版本不同可能返回文本或字节)
                # 例: "hello\\n" 或 b"hello\\n"
                content = self.sandbox.files.read(path)

                # 协议要求 content: bytes | None,所以 str 要 encode
                if isinstance(content, str):
                    # content: str -> bytes
                    # 例: "你好".encode("utf-8") == b"\\xe4\\xbd\\xa0\\xe5\\xa5\\xbd"
                    content = content.encode("utf-8")
                # 至此 content: bytes

                responses.append(
                    FileDownloadResponse(
                        path=path,  # str
                        content=content,  # bytes
                        error=None,  # None 表示成功
                    )
                )
            except Exception as e:
                error_msg: str = str(e).lower()
                error: str = "invalid_path"
                if "not found" in error_msg:
                    error = "file_not_found"

                responses.append(
                    FileDownloadResponse(
                        path=path,  # str
                        content=None,  # 失败时没有内容
                        error=error,  # str 错误码
                    )
                )

        return responses

    def close(self) -> None:
        """
        销毁远程沙箱,释放云端资源。

        返回: None
        副作用: 远程实例被 kill,之后不能再 execute / 读写文件。
        """
        # kill(): None —— 终止沙箱生命周期
        self.sandbox.kill()


# ---------------------------------------------------------------------------
# 使用方式示意(本文件目前只定义后端类;真正跑 Agent 时类似下面):
#
#   backend = E2BBackend(api_key=os.getenv("E2B_API_KEY"), timeout=300)
#   # backend: E2BBackend
#   # backend.id -> str 例 "i6wz..."
#
#   agent = create_deep_agent(model=..., backend=backend)
#   # Agent 调 execute 工具时 -> backend.execute("python script.py")
#   #   -> ExecuteResponse(output=..., exit_code=0, truncated=False)
#
#   backend.close()  # 记得关,否则云端实例会一直占着直到超时
# ---------------------------------------------------------------------------
  • 将自定义好的沙箱后端类注册到 DeepAgents 
import asyncio
import os
import traceback

import dotenv
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model
from langchain_core.messages import BaseMessage, ToolMessage
from langchain_mcp_adapters.client import MultiServerMCPClient
from rich.console import Console
from create_demo_deep_agent.e2b_backend import E2BBackend

# 1.加载 .env 文件,设置环境变量
dotenv.load_dotenv()

# 2.初始化模型
deepseek_v4_pro = init_chat_model(
    model="deepseek-v4-pro",
    model_provider='deepseek',
    base_url="https://api.deepseek.com",
    api_key=os.getenv('api_key'),
    temperature=0.7,
    max_tokens=10000,
)

# 2.配置Rich Console
console = Console()


# 3.配置 Context7 MCP
async def setup_mcp_tools():
    console.print("[dim]正在连接 Context7(streamable_http)...[/dim]")

    try:
        mcp_client = MultiServerMCPClient(
            {
                "context7": {
                    "transport": "streamable_http",  # streamable_http模式
                    "url": "https://mcp.context7.com/mcp"
                }
            }
        )
        tools = await mcp_client.get_tools()
        console.print(f"[bold green]成功加载 {len(tools)} 个 MCP 工具(HTTP)[/bold green]")
        return mcp_client, tools
    except Exception as e:
        console.print(f"[bold yellow]Context7(streamable_http)连接失败: {e}[/red]")
        return None, []


async def run_chat_loop() -> None:
    print("\n" + "=" * 80)
    print("欢迎使用 DeepAgent E2BBackend (Sanbox) 演示!")
    print("\n" + "=" * 80)

    # 1.初始化mcp
    mcp_client, tools = await setup_mcp_tools()

    # 2.初始化E2B Backend
    print("正在初始化 E2B Backend 沙箱...")
    try:
        backend = E2BBackend(template="base")
        print(f"✅ 沙箱启动成功 (ID: {backend.id})")
    except Exception as e:
        print(f"❌️ 沙箱启动失败: {e}")
        return

    try:
        # 3.创建 DeepAgent
        agent = create_deep_agent(
            model=deepseek_v4_pro,
            tools=tools,  # 赋予MCP工具能力
            backend=backend,  # 使用 E2BBackend 实现E2B沙箱能力
            system_prompt="""
                你是一个拥有云端沙箱环境的高级技术助手。
                你的任务是演示如何在沙箱中进行操作.
                请执行以下步骤:
                1.使用 'execute_command' 运行 'uname -a' 和 'python --version' 来展示环境信息。
                2.创建一个Python脚本 '/home/user/hello.py', 内容是打印 'Hello from E2B Sandbox!'。
                3.运行这个Python脚本并显示输出。
                """,
        )

        # 4.执行任务
        task = "请你开始演示沙箱操作流程"
        step = 0

        # agent.astream:以「流式」方式跑 agent,每完成一小步就 yield 一个 chunk,
        # 而不是等全部跑完才一次性返回。这样可以实时打印中间过程。
        # 传入的 messages 就是用户本轮的输入。
        async for chunk in agent.astream({"messages": [{"role": "user", "content": task}]}):
            step += 1  # 仅用于日志编号,方便对照「第几步」

            # 每个 chunk 是一个 dict,形如:
            #   {"model": {"messages": [...]}}   ← 模型节点刚产出了回复/工具调用
            #   {"tools": {"messages": [...]}}   ← 工具节点刚执行完,返回了结果
            # node_name = "model" / "tools" 等;node_data = 该节点本轮的输出数据
            for node_name, node_data in chunk.items():
                if node_data is None:
                    continue

                # 只关心带 messages 的节点输出(agent 的对话与工具结果都在这里)
                if "messages" in node_data:
                    msgs = node_data["messages"]

                    # 有时是单条消息对象,有时是列表,统一成 list 方便遍历
                    if not isinstance(msgs, list):
                        msgs = [msgs]

                    for msg in msgs:
                        # ---- 情况 A:普通文本回复(AIMessage 等)----
                        # 打印模型对用户说的话;跳过「只有 tool_call、没有正文」的空壳消息
                        if isinstance(msg, BaseMessage) and msg.content:
                            is_tool_call_msg = getattr(msg, "tool_call", None)
                            if not is_tool_call_msg:
                                print(f"\n[Agent ({node_name})]")
                                print("-" * 40)
                                print(msg.content)
                                print("-" * 40)

                        # ---- 情况 B:模型决定调用工具 ----
                        # msg.tool_calls 里是本次要调的工具名和参数,例如 execute / write_file
                        if hasattr(msg, "tool_calls") and msg.tool_calls:
                            print(f"\n[Step {step}: 工具调用]")
                            for tool_call in msg.tool_calls:
                                args_str = str(tool_call["args"])
                                if len(args_str) > 500:
                                    args_str = args_str[:500] + "..."  # 参数过长时截断,避免刷屏
                                print(f"- {tool_call['name']}: ({args_str})")

                        # ---- 情况 C:工具执行结果 ----
                        # ToolMessage 是工具跑完后返回的内容(命令输出、文件内容等)
                        if isinstance(msg, ToolMessage):
                            content_preview = str(msg.content)
                            if len(content_preview) > 200:
                                content_preview = content_preview[:200] + "..."
                            print(f"[Tool Output ({msg.name})]: {content_preview}")
    except Exception as e:
        # 运行中任意一步出错都会进这里,打印错误和完整堆栈便于排查
        print(f"❌️ 运行时错误:{e}")
        traceback.print_exc()
    finally:
        # 无论成功还是失败,都要关掉沙箱,避免 E2B 资源一直占用计费
        print("\n正在关闭沙箱...")
        backend.close()
        print("✅ 沙箱已关闭")
        print("感谢使用 DeepAgent E2BBackend 演示!")

if __name__ == '__main__':
    # 入口:用 asyncio 驱动上面的异步聊天循环
    asyncio.run(run_chat_loop())

输出:

================================================================================
欢迎使用 DeepAgent E2BBackend (Sanbox) 演示!

================================================================================
正在连接 Context7(streamable_http)...
成功加载 2 个 MCP 工具(HTTP)
正在初始化 E2B Backend 沙箱...
✅ 沙箱启动成功 (ID: iwe40zufahl6jna8l1v68)

[Agent (model)]
----------------------------------------
好的!我来逐步演示沙箱的操作流程。

---

## 步骤 1:展示沙箱环境信息

首先,让我运行 `uname -a` 和 `python --version` 来查看系统环境:
----------------------------------------

[Step 3: 工具调用]
- execute: ({'command': 'uname -a && python --version'})

[Agent (tools)]
----------------------------------------
Linux e2b.local 6.1.158+ #1 SMP PREEMPT_DYNAMIC Fri Jul  3 14:02:15 UTC 2026 x86_64 GNU/Linux
Python 3.11.6

[Command succeeded with exit code 0]
----------------------------------------
[Tool Output (execute)]: Linux e2b.local 6.1.158+ #1 SMP PREEMPT_DYNAMIC Fri Jul  3 14:02:15 UTC 2026 x86_64 GNU/Linux
Python 3.11.6

[Command succeeded with exit code 0]

[Agent (model)]
----------------------------------------
环境信息已获取:

| 项目 | 详情 |
|------|------|
| **操作系统** | Linux (x86_64, GNU/Linux) |
| **内核版本** | 6.1.158+ |
| **Python 版本** | 3.11.6 |

---

## 步骤 2:创建 Python 脚本

现在创建 `/home/user/hello.py`:
----------------------------------------

[Step 7: 工具调用]
- write_file: ({'file_path': '/home/user/hello.py', 'content': "print('Hello from E2B Sandbox!')\n"})

[Agent (tools)]
----------------------------------------
Updated file /home/user/hello.py
----------------------------------------
[Tool Output (write_file)]: Updated file /home/user/hello.py

[Agent (model)]
----------------------------------------
脚本创建成功!让我确认一下文件内容:
----------------------------------------

[Step 11: 工具调用]
- read_file: ({'file_path': '/home/user/hello.py'})

[Agent (tools)]
----------------------------------------
     1    print('Hello from E2B Sandbox!')
----------------------------------------
[Tool Output (read_file)]:      1    print('Hello from E2B Sandbox!')

[Agent (model)]
----------------------------------------
---

## 步骤 3:运行 Python 脚本
----------------------------------------

[Step 15: 工具调用]
- execute: ({'command': 'python /home/user/hello.py'})

[Agent (tools)]
----------------------------------------
Hello from E2B Sandbox!

[Command succeeded with exit code 0]
----------------------------------------
[Tool Output (execute)]: Hello from E2B Sandbox!

[Command succeeded with exit code 0]

[Agent (model)]
----------------------------------------
---

## ✅ 演示完成

整个沙箱操作流程已全部完成,总结如下:

| 步骤 | 操作 | 结果 |
|------|------|------|
| **1. 查看环境** | `uname -a` + `python --version` | Linux x86_64, Python 3.11.6 |
| **2. 创建脚本** | 写入 `/home/user/hello.py` | 文件创建成功 |
| **3. 运行脚本** | `python /home/user/hello.py` | 输出 `Hello from E2B Sandbox!` |

这就是沙箱环境的完整操作流程:使用 `execute` 工具运行 shell 命令、使用 `write_file` 创建文件、使用 `read_file` 确认内容、最后再用 `execute` 执行脚本。整个过程流畅且直观! 🎉
----------------------------------------

正在关闭沙箱...
✅ 沙箱已关闭
感谢使用 DeepAgent E2BBackend 演示!

5.DockerBackend 使用Docker容器

DockerBackend 的核心作用是为 AI 智能体提供一个 安全沙箱(Sandbox) 。如果不使用 Docker,Agent 执行的每一条命令(如 rm -rf 、 pip install )都会直接发生在您的宿主机(Mac)上,这极其危险且环境不可控。

作用与优势:

  • 安全隔离 (Security & Isolation)

    • 作用 :Agent 的所有操作(文件读写、代码执行、系统命令)都被限制在 Docker 容器内部。

    • 优势 :即使 Agent 产生幻觉执行了恶意代码(如删除系统文件),也只会破坏容器, 宿主机也会毫发无损 。演示代码中的 auto_remove=True 确保任务结束后容器自动销毁,不留痕迹。

  • 环境一致性 (Reproducibility)

    • 作用 :代码指定了镜像 image=“python:3.11-slim” 。

    • 优势 :无论您的电脑安装的是 Python 3.9 还是 3.12,Agent 永远在一个干净、标准的 Python 3.11 环境中运行。这解决了“在我的机器上能跑”的经典依赖问题。

  • 生命周期管理 (Lifecycle Management)

    • 作用 : DockerBackend 自动处理容器的 启动 -> 连接 -> 执行 -> 销毁 全过程。

    • 优势 :开发者无需手动编写复杂的 Docker 命令,像使用本地对象一样简单地调用 backend.execute() 或 backend.write_file() 。

需要先安装一下docker依赖

pip install docker
  • 其次需要对Ubuntu Docker 远程访问 + 镜像加速(新机器部署清单)

适用场景:本机(Windows)通过 `tcp://服务器IP:2375` 连接 Ubuntu 上的 Docker,并用于 DeepAgents DockerBackend。
警告:`2375` 无 TLS,**仅限可信内网**。公网请用 SSH 或 TLS(2376),不要裸开 2375。

# --- 在新 Ubuntu 上执行 ---
sudo ufw allow 2375/tcp

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"],
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io",
    "https://docker.xuanyuan.me"
  ]
}
EOF

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/override.conf <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker
sudo ss -lntp | grep 2375
docker pull python:3.12-slim

客户端把 DOCKER_BASE_URL 改成新机器 IP 即可。

docker_backend.py

"""
Docker 沙箱后端:让 DeepAgents 的 execute / 读写文件跑在「本机 Docker 容器」里。

整体关系(和 E2BBackend 对称,只是执行环境从云端换成了本地容器):
  create_deep_agent(backend=DockerBackend(...))
      → Agent 调 execute / write_file / read_file 等工具
      → 落到本类的 execute / upload_files / download_files
      → 再调用 docker-py 操作本地容器

前置条件:
  1. 已安装 Docker Desktop / Docker Engine,且守护进程在运行
  2. 已安装 Python 包:pip install docker
"""

import io
import tarfile
import time

import docker
from docker.errors import NotFound, APIError
from deepagents.backends.protocol import (
    ExecuteResponse,
    FileDownloadResponse,
    FileUploadResponse,
)
from deepagents.backends.sandbox import BaseSandbox


class DockerBackend(BaseSandbox):
    """
    把 DeepAgents 的「沙箱后端协议」接到本机 Docker。

    继承 BaseSandbox 后:
      - 必须实现: execute(command: str) -> ExecuteResponse
      - 可选覆盖: upload_files / download_files(用 Docker API 传文件更高效)
      - 自动获得: ls_info / read / write / edit / grep 等(内部调 execute 跑 shell)
    """

    def __init__(
            self,
            image: str = "python:3.12-slim",
            auto_remove: bool = True,
            cpu_quota: int = 50000,  # 约 50% 单核 CPU(见下方注释)
            memory_limit: str = "512m",
            network_disabled: bool = False,
            working_dir: str = "/workspace",
            volumes: dict[str, dict[str, str]] | None = None,
            # ---------- 远程 Docker 连接(就在这里配)----------
            base_url: str | None = None,
            # 例: "ssh://user@192.168.1.100"
            # 例: "tcp://192.168.1.100:2376"   # 需 TLS 时再配 tls=...
            tls: bool | docker.tls.TLSConfig | None = None,
    ) -> None:
        """
        创建并启动一个长期存活的 Docker 容器,作为 Agent 的隔离执行环境。

        参数说明:

        image: str
          用哪张 Docker 镜像启动容器。镜像 = 操作系统 + 预装软件的「模板」。
          例: "python:3.12-slim"  —— 官方精简 Python 3.12 环境
          例: "ubuntu:22.04"      —— 完整 Ubuntu(体积更大)

        auto_remove: bool
          关闭沙箱时是否顺便删除容器。
          True  → close() 时 remove,磁盘不留残留(演示常用)
          False → close() 时只 stop,容器还在,方便事后 docker logs / inspect

        cpu_quota: int
          CPU 时间配额,单位是「微秒 / 每 100ms 周期」。
          Docker 默认周期 period=100000 微秒(0.1 秒),
          所以 cpu_quota=50000 表示每个周期最多用一半时间 ≈ 约 50% 单核。
          例: 100000 → 约 100% 单核;200000 → 约 2 核

        memory_limit: str
          容器可用内存上限,超出会被 OOM kill。
          例: "512m"、"1g"、"256m"

        network_disabled: bool
          是否禁用容器网络。
          True  → 容器不能上网、不能访问宿主机网络(更安全,但 pip install 会失败)
          False → 允许联网(默认,方便装包、调 API)

        working_dir: str
          容器内的默认工作目录;execute() 里跑命令时 cwd 就在这里。
          例: "/workspace"(注意要带前导 /)

        volumes: dict | None
          把宿主机目录挂进容器,格式与 docker run -v 对应:
          {
            "宿主机绝对路径": {
              "bind": "容器内路径",   # 挂载到哪里
              "mode": "rw" 或 "ro",  # 读写 / 只读
            }
          }
          例: {r"E:\\data": {"bind": "/data", "mode": "ro"}}
          注意:远程 Docker 时,路径是「服务器上的路径」,不是你本机路径。
          None 表示不挂任何卷。

        base_url: str | None
          【远程连接入口】Docker 守护进程地址。
          None  → 用 docker.from_env()(本机,或读环境变量 DOCKER_HOST)
          有值 → 直连该地址上的 Docker
            例: "ssh://ubuntu@10.0.0.8"
            例: "tcp://10.0.0.8:2376"

        tls: bool | TLSConfig | None
          仅 tcp:// 且开启 TLS 时需要;SSH 方式一般不用。
          例: True,或 docker.tls.TLSConfig(client_cert=..., ca_cert=..., verify=True)
        """

        if docker is None:
            raise ImportError("Docker not installed. Please install docker and try again.")

        # ---------- 连接 Docker(本机 or 远程)----------
        # 改这里 / 传 base_url,就能连服务器上的 Docker,而不改后面的跑容器逻辑。
        if base_url:
            # 远程:显式指定守护进程地址
            # 返回类型: docker.DockerClient
            self.client = docker.DockerClient(base_url=base_url, tls=tls)
        else:
            # 本机默认;若已设置环境变量 DOCKER_HOST=ssh://... 也会连远程
            self.client = docker.from_env()

        self.image = image
        self.auto_remove = auto_remove
        self.working_dir = working_dir
        self.volumes = volumes or {}
        self._container = None  # 稍后赋值为真正的容器对象

        try:
            # ---------- 1. 确保镜像存在 ----------
            # images.get:本地已有该镜像则直接用;没有会抛 NotFound
            try:
                self.client.images.get(self.image)
            except NotFound:
                print(f"Docker image {self.image} not found. Pulling...")
                # auth_config={}:公开镜像不走本机 credentials store
                # 避免 Windows 上报 docker-credential-desktop 不存在
                # (即便 Docker 在远程 Ubuntu,pull 仍可能读本机 ~/.docker/config.json)
                self.client.images.pull(self.image, auth_config={})

            # ---------- 2. 启动一个「空闲常驻」容器 ----------
            # 为什么用 tail -f /dev/null?
            #   python:3.12-slim 默认入口跑完就退出;容器一退出就没法再 exec。
            #   tail -f /dev/null 会永远阻塞,容器保持 Running,后续靠 exec_run 跑命令。
            #
            # detach=True:后台运行,不阻塞当前 Python 进程
            # auto_remove:容器退出后是否自动删掉(与 close() 里的逻辑配合)
            self._container = self.client.containers.run(
                self.image,
                command="tail -f /dev/null",  # 保活命令,让容器一直 Running
                detach=True,
                auto_remove=self.auto_remove,
                cpu_quota=cpu_quota,
                mem_limit=memory_limit,
                network_disabled=network_disabled,
                working_dir=working_dir,
                volumes=self.volumes,
            )

            # ---------- 3. 确保工作目录存在 ----------
            # -p:父目录不存在则一并创建;已存在也不报错
            self.execute(f"mkdir -p {working_dir}")
        except Exception as e:
            raise RuntimeError(f"Failed to create Docker container: {e}")

    @property
    def id(self) -> str:
        """
        沙箱唯一 ID(即 Docker 容器 ID),供日志/追踪用。

        返回类型: str
        例: "a1b2c3d4e5f6..."(完整 64 位 hex;docker ps 里常显示前 12 位)
        """
        return self._container.id if self._container else "unknown"

    def execute(self, command: str) -> ExecuteResponse:
        """
        在容器里执行一条 shell 命令,并返回输出 + 退出码。

        这是 Agent 最常用的能力:跑 uname、python、pip、写文件用的 echo 等,
        最终都会走到这里(或由 BaseSandbox 拼成 shell 再调这里)。

        参数:
          command: str  —— 完整 shell 命令字符串
            例: "python --version"
            例: "uname -a && ls /workspace"

        返回:
          ExecuteResponse(
            output: str,       # 合并后的 stdout/stderr 文本
            exit_code: int,    # 0 成功,非 0 失败
            truncated: bool,   # 输出是否被截断;本实现始终 False
          )
        """
        if not self._container:
            return ExecuteResponse(output="Container not running", exit_code=1, truncated=False)

        try:
            # exec_run:在「已运行」的容器里再开一个进程(类似 docker exec)
            #
            # cmd=["bash", "-c", command]:
            #   用 bash 解释整段 command,这样管道 |、&&、重定向 > 才能生效。
            #   若直接传字符串,有些特殊字符可能被错误拆分。
            #
            # workdir:本次命令的当前目录(覆盖容器默认 WORKDIR)
            # demux=False:stdout 和 stderr 混在一起返回(True 则会分开成两个流)
            #
            # 返回: (exit_code: int, output: bytes)
            exit_code, output = self._container.exec_run(
                cmd=["bash", "-c", command],
                workdir=self.working_dir,
                demux=False,
            )

            return ExecuteResponse(
                # decode:bytes → str;非法字节用 � 替换,避免解码崩溃
                output=output.decode("utf-8", errors="replace"),
                exit_code=exit_code,
                truncated=False,
            )
        except Exception as e:
            # Docker 守护进程 API 报错(容器状态异常、权限等)
            return ExecuteResponse(
                output=f"Error executing command: {str(e)}",
                exit_code=1,
                truncated=False,
            )

    def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
        """
        把若干文件写入容器文件系统。

        Docker 没有「直接写单个文件」的高层 API,官方推荐做法是:
          1. 在内存里打成 tar 包
          2. 用 put_archive 把 tar 解压进容器某个目录

        参数:
          files: list[tuple[path, content]]
            path: str     —— 容器内目标路径
            content: bytes —— 文件二进制内容
            例: [("/workspace/hello.py", b"print('hi')\\n")]

        返回:
          与 files 一一对应的 FileUploadResponse 列表
            成功: FileUploadResponse(path=..., error=None)
            失败: FileUploadResponse(path=..., error="permission_denied")
        """
        if not self._container:
            return [
                FileUploadResponse(path=file_path, error="permission_denied")
                for file_path, _ in files
            ]

        responses = []

        # ---------- 1. 在内存中组装 tar ----------
        # BytesIO:像文件一样读写的内存缓冲区,避免写临时磁盘文件
        tar_stream = io.BytesIO()

        with tarfile.open(fileobj=tar_stream, mode="w") as tar:
            for file_path, file_content in files:
                # 绝对路径:去掉前导 /,解压到容器根目录 /
                #   "/workspace/a.py" → arcname="workspace/a.py",最终落在 /workspace/a.py
                # 相对路径:保持原样,解压时相对 working_dir(见下方 dest_path 注释)
                #   注意:当前实现 put_archive 始终解压到 "/",
                #   相对路径场景下 dest_path 变量算了但未用于 put_archive,实际仍从根展开。
                if file_path.startswith("/"):
                    arcname = file_path.lstrip("/")
                    dest_path = "/"  # noqa: F841 —— 预留:若改为 put_archive(dest_path) 可用
                else:
                    arcname = file_path
                    dest_path = self.working_dir  # noqa: F841

                # TarInfo:描述 tar 里一个条目的元数据(文件名、大小、修改时间)
                info = tarfile.TarInfo(name=arcname)
                info.size = len(file_content)
                info.mtime = time.time()
                # addfile:把「元数据 + 文件内容流」写入 tar
                tar.addfile(tarinfo=info, fileobj=io.BytesIO(file_content))

                # 先记「准备成功」;真正 put_archive 失败时会整体改成 error
                responses.append(
                    FileUploadResponse(
                        path=file_path,
                        error=None,
                    )
                )

        # seek(0):把读指针拨回开头,否则 put_archive 读到的是空内容
        tar_stream.seek(0)

        # ---------- 2. 把 tar 解压进容器 ----------
        # put_archive(path="/", data=...):在容器的 / 下解压整个 tar
        # 因此 arcname="workspace/a.py" 会变成容器内 /workspace/a.py
        try:
            self._container.put_archive(path="/", data=tar_stream)
        except Exception:
            return [
                FileUploadResponse(path=file_path, error="permission_denied")
                for file_path, _ in files
            ]

        return responses

    def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
        """
        从容器里下载若干文件到本机内存(bytes)。

        与 upload 对称:Docker 用 get_archive 取出 tar,再从 tar 里抽出文件内容。

        参数:
          paths: list[str] —— 容器内文件路径列表
            例: ["/workspace/hello.py", "/tmp/out.txt"]

        返回:
          FileDownloadResponse 列表,与 paths 一一对应
            成功: content=文件字节, error=None
            失败: content=None, error 为下列之一:
              "file_not_found" / "is_directory" / "permission_denied" / "invalid_path"
        """
        if not self._container:
            return [
                FileDownloadResponse(path=file_path, error="permission_denied")
                for file_path in paths
            ]

        responses = []
        for path in paths:
            try:
                # get_archive:把容器内 path 打成 tar,以流的形式返回
                # bits: 可迭代的字节块生成器
                # stat: 该路径的元信息(大小等),这里未使用
                bits, stat = self._container.get_archive(path)

                # 把分块流拼成完整的 tar 字节流
                file_content = io.BytesIO()
                for chunk in bits:
                    file_content.write(chunk)
                file_content.seek(0)

                # 打开 tar,取出第一个成员(对单文件路径通常只有一项)
                with tarfile.open(fileobj=file_content, mode="r") as tar:
                    member = tar.next()
                    if member is None:
                        responses.append(
                            FileDownloadResponse(path=path, error="file_not_found")
                        )
                        continue

                    # 若 path 指向目录,协议要求报 is_directory,而不是返回一堆文件
                    if member.isdir():
                        responses.append(
                            FileDownloadResponse(path=path, error="is_directory")
                        )
                        continue

                    # extractfile:得到该成员的文件对象,再 read() 成 bytes
                    f = tar.extractfile(member)
                    if f:
                        content = f.read()
                        responses.append(
                            FileDownloadResponse(path=path, content=content, error=None)
                        )
                    else:
                        responses.append(
                            FileDownloadResponse(path=path, error="file_not_found")
                        )

            except NotFound:
                # 容器内路径不存在
                responses.append(
                    FileDownloadResponse(path=path, error="file_not_found")
                )
            except Exception as e:
                # 其它错误:尽量映射成协议规定的 error 字符串
                error_msg = str(e).lower()
                error = "invalid_path"
                if "permission" in error_msg:
                    error = "permission_denied"

                responses.append(
                    FileDownloadResponse(path=path, content=None, error=error)
                )
        return responses

    def close(self) -> None:
        """
        关闭沙箱,释放本机 Docker 资源。

        auto_remove=True  → force remove:强制删容器(即使还在跑)
        auto_remove=False → 只 stop:容器停止但保留,可用 docker start 再开
        """
        if self._container:
            try:
                if self.auto_remove:
                    self._container.remove(force=True)
                else:
                    self._container.stop()
            except Exception:
                # 容器可能已被手动删掉或守护进程异常,忽略清理错误
                pass
            self._container = None

docker_run_backend.py

"""
DeepAgent + 远程 Docker 沙箱演示入口。

流程概览:
  1. ping 远程 Docker API 是否通
  2. 用 DockerBackend 在远程机器上起一个容器
  3. 创建带 LLM 的 DeepAgent,让它在容器里执行演示任务
  4. 流式打印 Agent 输出,结束后关闭容器
"""

import asyncio  # 异步运行时:用来跑下面的 async def
import os  # 读环境变量(API Key、DOCKER_BASE_URL 等)
import traceback  # 出错时打印完整堆栈,方便排查

import docker  # docker-py:直接调 Docker API(这里用来 ping)
import dotenv  # 从 .env 文件加载环境变量
from langchain.chat_models import init_chat_model  # 统一入口初始化聊天模型
from create_demo_deep_agent.docker_backend import DockerBackend  # 自研:DeepAgents 的 Docker 后端
from langchain_core.messages import BaseMessage  # 消息基类(AIMessage / HumanMessage 等)
from deepagents import create_deep_agent  # 创建带沙箱能力的 Deep Agent

# 加载项目根目录(或当前目录)的 .env
# override=True:.env 里的值会覆盖系统里已有的同名环境变量
dotenv.load_dotenv(override=True)

# ========== 远程 Docker 地址(改这里)==========
# os.getenv(键, 默认值):优先读环境变量 DOCKER_BASE_URL;没有则用默认 tcp 地址
# 例: "tcp://192.168.102.129:2375"
# 例: "ssh://root@192.168.102.129"
DOCKER_BASE_URL = os.getenv("DOCKER_BASE_URL", "tcp://192.168.102.129:2375")


async def run_chat_loop():
    """异步主流程:连 Docker → 建 Agent → 流式跑任务 → 清理容器。"""

    print("\n" + "=" * 80)
    print("欢迎使用 DeepAgent DockerBackend 沙箱演示!")
    print(f"目标 Docker: {DOCKER_BASE_URL}")
    print("=" * 80)

    # ---------- 1. 连通性检查 ----------
    # 只做 ping,不创建容器;失败就提前 return,避免后面白跑
    try:
        # DockerClient(base_url=...):连指定守护进程
        # .ping():请求 /_ping,通了返回 True / "OK"
        docker.DockerClient(base_url=DOCKER_BASE_URL).ping()
        print("✅ 远程 Docker 连接成功")
    except Exception as e:
        print(f"❌️ 请检查 docker 环境配置,错误信息为:{e}")
        print(f"当前地址: {DOCKER_BASE_URL}")
        print("请确认:服务器 Docker 已启动,且已开放远程 API(或 SSH 可用)。")
        return  # 结束函数,不继续后面的逻辑

    # ---------- 2. 启动沙箱容器 ----------
    print("正在启动 docker 容器 (Image: python:3.12-slim)...")

    try:
        backend = DockerBackend(
            image="python:3.12-slim",  # 用哪张镜像起容器
            auto_remove=True,  # close() 时强制删掉容器,不留残留
            base_url=DOCKER_BASE_URL,  # 容器建在远程 Ubuntu,不是本机
        )
        # backend.id 是完整容器 ID(很长);[:12] 只打印前 12 位,和 docker ps 习惯一致
        print(f"容器已经启动成功! (ID: {backend.id[:12]})")
    except Exception as e:
        print(f"❌️ 启动 docker 容器失败,错误信息为:{e}")
        return

    try:
        # ---------- 3. 初始化大模型 ----------
        deepseek_v4_pro = init_chat_model(
            model="deepseek-v4-pro",  # 模型名
            model_provider="deepseek",  # 提供商,决定用哪套 SDK/协议
            base_url="https://api.deepseek.com",  # API 地址
            api_key=os.getenv("api_key"),  # 从 .env 读密钥
            temperature=0.7,  # 随机性:越高回答越发散
            max_tokens=10000,  # 单次回复最大 token 数
        )

        # ---------- 4. 创建 DeepAgent ----------
        # backend=backend:Agent 的 execute / 读写文件都会进上面的 Docker 容器
        agent = create_deep_agent(
            model=deepseek_v4_pro,
            backend=backend,
            system_prompt="""
            你是一个运行在 Docker 容器中的 AI 助手。
            你的任务是演示环境隔离性。

            请执行以下步骤:
            1. 运行 'cat /etc/os-release' 查看容器操作系统。
            2. 运行 'python --version' 确认 Python 环境。
            3. 创建文件 '/workspace/hello_docker.py',内容为打印 'Hello from Docker Container!'。
            4. 运行该脚本。
            """,
        )

        print("\n任务开始执行中...")

        # ---------- 5. 流式执行并打印输出 ----------
        # agent.astream(...):异步流式运行。
        #   输入是 LangGraph 状态字典,这里只塞本轮用户消息。
        #   每完成图中一个节点(如 model / tools),就 yield 一个 chunk。
        #
        # chunk 长什么样?(示意)
        #   {
        #     "model": {"messages": [AIMessage(...), ...]},   # 或包在 Overwrite 里
        #     # 有时是 "tools": {"messages": [ToolMessage(...), ...]}
        #   }
        #   也可能某个值为 None / 空,需要下面一串防护逻辑。
        async for chunk in agent.astream(
            {"messages": [{"role": "user", "content": "请开始执行任务"}]}
        ):
            # chunk.items():遍历本轮更新了哪些节点
            #   node_name: str,如 "model"、"tools"
            #   node_data: 该节点本次输出(可能是 dict,也可能被包成 Overwrite)
            # 1. 模型节点刚说完话 / 决定调工具
            # {
            #   "model": {
            #     "messages": [
            #       AIMessage(
            #         content="好的,我先查看系统信息...",
            #         tool_calls=[{"name": "execute", "args": {"command": "uname -a"}, "id": "..."}]
            #       )
            #     ]
            #   }
            # }
            # 2.工具节点执行完
            # {
            #   "tools": {
            #     "messages": [
            #       ToolMessage(
            #         content="Linux ...\nPython 3.12.x",
            #         name="execute",
            #         tool_call_id="..."
            #       )
            #     ]
            #   }
            # }
            # 3. 带 Overwrite 包装(你代码里要解包的情况)
            # {
            #   "model": Overwrite(value={"messages": [AIMessage(...)]})
            #   # 或
            #   "model": {"messages": Overwrite(value=[AIMessage(...)])}
            # }
            # 4.其它节点(可能没有 messages)
            # {"todos": [...]}           # 任务列表更新
            # {"something": None}        # 空更新 → 你代码里 continue 掉
            for node_name, node_data in chunk.items():

                # 没有数据就跳过(有的节点本轮可能是 None / 空)
                if not node_data:
                    continue

                # ----- 解包 Overwrite -----
                # LangGraph 更新状态时,有时用 Overwrite(value=真实数据)
                # 表示「用 value 整份覆盖该字段」,而不是和旧状态合并。
                # 所以打印前要先取出 .value,才拿到真正的 {"messages": [...]}。
                # hasattr(x, "value"):duck typing,有 value 属性就当 Overwrite 解包。
                if hasattr(node_data, "value"):
                    node_data = node_data.value

                # 解包后仍不是字典(异常形态),无法按 messages 解析,跳过
                if not isinstance(node_data, dict):
                    continue

                # 只关心带对话消息的输出(忽略其它状态字段)
                if "messages" in node_data:
                    messages = node_data["messages"]

                    # messages 本身也可能被 Overwrite 包一层,同样解包
                    if hasattr(messages, "value"):
                        messages = messages.value

                    # 只要列表里最后一条「有正文的消息」拿来打印
                    # (流式场景下列表可能累计了多轮,看最新一条通常就够做演示日志)
                    if isinstance(messages, list) and messages:
                        last_msg = messages[-1]  # 最新一条

                        # BaseMessage:LangChain 消息;.content 是文本正文
                        # 没有 content 的(例如纯 tool_calls 壳)就不打印,避免刷空行
                        if isinstance(last_msg, BaseMessage) and last_msg.content:
                            print(f"\n[Agent ({node_name})]")
                            print("-" * 40)
                            print(last_msg.content)
                            print("-" * 40)

    except Exception as e:
        # Agent 跑模型 / 调工具过程中出错
        print(f"❌️ 运行时错误,错误信息为:{e}")
        traceback.print_exc()  # 打印完整 traceback 到 stderr
    finally:
        # 无论成功失败都要关容器,避免远程服务器上残留占用资源
        print("\n任务执行完毕!正在清理容器...")
        backend.close()
        print("容器清理完成!演示结束")


# 脚本直接被 python 执行时才跑主流程;被 import 时不自动跑
if __name__ == "__main__":
    # asyncio.run:创建事件循环并执行协程 run_chat_loop()
    asyncio.run(run_chat_loop())

输出:

D:\anaconda3\envs\deepagents\python.exe E:\code\InsightFlow\create_demo_deep_agent\docker_run_backend.py 

================================================================================
欢迎使用 DeepAgent DockerBackend 沙箱演示!
目标 Docker: tcp://192.168.102.129:2375
================================================================================
✅ 远程 Docker 连接成功
正在启动 docker 容器 (Image: python:3.12-slim)...
容器已经启动成功! (ID: e062c93f824d)

任务开始执行中...

[Agent (PatchToolCallsMiddleware.before_agent)]
----------------------------------------
请开始执行任务
----------------------------------------

[Agent (tools)]
----------------------------------------
Python 3.12.13

[Command succeeded with exit code 0]
----------------------------------------

[Agent (tools)]
----------------------------------------
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
VERSION="13 (trixie)"
VERSION_CODENAME=trixie
DEBIAN_VERSION_FULL=13.6
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

[Command succeeded with exit code 0]
----------------------------------------

[Agent (tools)]
----------------------------------------
[]
----------------------------------------

[Agent (tools)]
----------------------------------------
Updated file /workspace/hello_docker.py
----------------------------------------

[Agent (tools)]
----------------------------------------
Hello from Docker Container!

[Command succeeded with exit code 0]
----------------------------------------

[Agent (model)]
----------------------------------------
所有 4 个步骤已全部完成!以下是环境隔离性演示的结果总结:

---

### 🔍 1. 容器操作系统

```
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
```

当前运行在 **Debian GNU/Linux 13 (Trixie)** 容器中。

---

### 🐍 2. Python 环境

```
Python 3.12.13
```

容器内预装了 **Python 3.12.13**---

### 📄 3. 创建脚本

已在 `/workspace/hello_docker.py` 创建文件,内容为:
```python
print('Hello from Docker Container!')
```

---

### 🚀 4. 运行脚本

```
Hello from Docker Container!
```

脚本成功执行,输出符合预期。

---

### ✅ 总结

这个演示清楚地展示了 **Docker 容器的环境隔离性**- 容器拥有自己独立的操作系统标识(Debian 13- Python 环境是容器内独立安装的版本(3.12.13- 文件系统 `/workspace` 是容器内的隔离工作空间
- 整个运行环境与宿主机完全隔离
----------------------------------------

任务执行完毕!正在清理容器...
容器清理完成!演示结束

Process finished with exit code 0

DockerBackend和E2BBackend对比:

特性DockerBackendE2BBackend
核心定位 本地轻量级容器化沙箱 云端安全沙箱环境 (SaaS)
部署位置 运行在本地机器 (Localhost) 运行在 E2B 云端集群 (Remote Cloud)
依赖环境 需要本地安装并运行 Docker Desktop/Daemon 仅需安装 e2b Python SDK,无需本地 Docker
资源消耗 消耗本地 CPU/内存资源 消耗 E2B 云端资源 (不占用本地算力)
启动速度 快 (本地镜像启动,毫秒-秒级) 较快 (云端冷启动约 1-3秒)
网络隔离 可配置 (支持完全离线 network_disable=True) 默认联网 (支持访问公网 API)
持久化 支持挂载本地卷 (Volumes) 实现数据持久化 临时环境 (会话结束即销毁),数据需手动导出
适用场景 • 本地开发/调试
• 数据隐私敏感 (不想数据上云)
• 离线环境使用
• 生产环境部署 (无需维护 Docker)
• 多租户隔离 (每个用户一个云沙箱)
• 本地资源受限设备
成本 免费 (使用自有硬件) 付费 (按使用时长/资源计费)
配置复杂度 中 (需管理镜像、卷挂载、Docker 进程) 低 (API Key 开箱即用)

6.StoreBackend 使用数据库存储

step1 : 这里我们使用postgresql数据库进行数据的存储,所以需要先系统安装postgresql在本地环境

  • brew install postgresql(mac系统)
  • sudo apt-get install postgresql(Linux系统)
  • https://blog.csdn.net/weixin_54787369/article/details/141348101(windows系统)

step2 : 安装完成之后,可以使用Docker来部署启动postgresql数据库,我这里使用的docker-compose.yml文件启动的,需要把password密码,user用户名,database数据库名称修改为自己的,使用docker-compose up -d启动数据库,如下所示:

#docker-compose.yml 文件:

services:
  postgres:
    image: postgres:15
    container_name: my-postgres
    environment:
      - POSTGRES_PASSWORD=123456
      - POSTGRES_USER=myuser
      - POSTGRES_DB=mydatabase
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/data/db
      - ./conf.d:/data/conf.d
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    restart: unless-stopped

volumes:
  pg_data:
    driver: local

docker直接安装:

docker run -d \
  --name my-postgres \
  -e POSTGRES_PASSWORD=123456 \
  -e POSTGRES_USER=myuser \
  -e POSTGRES_DB=mydb \
  -p 5432:5432 \
  -v postgres_data:/var/lib/postgresql \
  postgres

安装依赖:

pip install langgraph-checkpoint-postgres

案例代码:

# ========== 标准库 ==========
# asyncio:Python 异步编程库。本脚本主流程是 async 的(连 MCP、跑 agent.astream),所以要用它。
import asyncio
# os:读环境变量(如 POSTGRES_URI、api_key),避免把密钥硬编码进代码。
import os
# sys:判断当前操作系统(Windows / Linux),用来决定要不要改事件循环策略。
import sys
# traceback:异常时打印完整调用栈,方便排查。
import traceback
# uuid:生成唯一会话 ID(thread_id),让 LangGraph 能区分不同对话线程。
import uuid

# ========== 第三方 / 项目依赖 ==========
# dotenv:从 .env 文件加载环境变量(例如 api_key)。
import dotenv
# create_deep_agent:创建带「文件系统工具 + 规划能力」的 Deep Agent。
from deepagents import create_deep_agent
# StoreBackend:把 Agent 的 ls/read/write 等文件操作,映射到 LangGraph 的 Store(这里是 Postgres)。
from deepagents.backends import StoreBackend
# BaseMessage:所有对话消息的基类(Human / AI / Tool 消息都继承它)。
# ToolMessage:工具执行完后返回给模型的结果消息。
from langchain_core.messages import BaseMessage, ToolMessage
# init_chat_model:统一方式初始化大模型(DeepSeek / OpenAI 等),不用手写各家 SDK。
from langchain.chat_models import init_chat_model
# MultiServerMCPClient:连接一个或多个 MCP 服务器,把远程工具变成 LangChain tools。
from langchain_mcp_adapters.client import MultiServerMCPClient
# AsyncPostgresSaver:异步版「检查点存储器」。
#   作用:把对话状态(messages 等)持久化到 PostgreSQL。
#   为什么必须用 Async?因为后面用的是 astream / ainvoke(异步 API)。
#   若用同步 PostgresSaver,调用 aget_tuple 会直接 NotImplementedError。
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
# PostgresStore:把「长期记忆 / 虚拟文件系统」存在 PostgreSQL 的 store 表里。
from langgraph.store.postgres import PostgresStore
# pg_connect:同步方式连一次 Postgres,用来做启动前的连通性探测。
from psycopg import connect as pg_connect
# ConnectionPool:连接池。StoreBackend 的文件工具是同步的,所以这里用同步池。
from psycopg_pool import ConnectionPool
# Console:Rich 库的彩色终端输出。
from rich.console import Console

# ---------- Windows 事件循环兼容 ----------
# 背景:
#   - asyncio 在 Windows 上默认用 ProactorEventLoop
#   - psycopg 的「异步连接」不支持 ProactorEventLoop,只支持 SelectorEventLoop
#   - Linux / macOS 默认本来就是 SelectorEventLoop,所以不需要这行
# sys.platform == "win32":仅在 Windows 上改策略;Linux 下这段 if 不会进。
if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

# 从项目目录的 .env 加载环境变量。
# override=True:即使系统里已有同名变量,也用 .env 里的值覆盖。
dotenv.load_dotenv(override=True)

# 创建 Rich 控制台对象,后面用 console.print 打彩色日志。
console = Console()


async def setup_mcp_tools():
    """
    连接 Context7 MCP 服务,拉取可用工具列表。
    返回:(mcp_client, tools)
      - 成功:客户端对象 + 工具列表
      - 失败:None, [] (演示仍可继续,只是没有文档查询工具)
    """
    # [dim]...[/dim] 是 Rich 标记:灰色弱化显示。
    console.print("[dim]正在连接 Context7(streamable_http)...[/dim]")

    try:
        # 配置一个名为 "context7" 的 MCP 服务器。
        # transport="streamable_http":用 HTTP 流式协议通信(不是本地 stdio 子进程)。
        # url:Context7 官方 MCP 地址。
        mcp_client = MultiServerMCPClient({
            "context7": {
                "transport": "streamable_http",
                "url": "https://mcp.context7.com/mcp",
            },
        })
        # get_tools() 是异步的:网络请求拉取工具 schema,转成 LangChain Tool 对象。
        tools = await mcp_client.get_tools()
        console.print(f"[bold green]成功加载 {len(tools)} 个 MCP 工具(HTTP)[/bold green]")
        # 把 client 也返回:有的场景需要保持连接生命周期;本演示主要用 tools。
        return mcp_client, tools
    except Exception as e:
        # 连不上也不让整个程序崩溃,返回空工具列表,后面 Agent 只是少了文档查询能力。
        console.print(f"[bold yellow]Context7 HTTP 连接失败:{e}[/bold yellow]")
        return None, []


async def run_store_backend_demo():
    """
    演示主流程:
      1) 连 MCP 拿工具
      2) 连 PostgreSQL,准备 Checkpointer(会话状态)+ Store(虚拟文件)
      3) 创建 DeepAgent,backend=StoreBackend(文件写进 Postgres)
      4) 流式执行任务(查文档 → 写 md → 读回)
      5) 新建另一个 Agent,读同一文件,证明「跨重启」数据还在
    """

    # ---------- 数据库连接串 ----------
    # 格式:postgresql://用户名:密码@主机:端口/库名?参数
    # connect_timeout=10:连不上最多等 10 秒就报错,避免一直卡住。
    # os.getenv("POSTGRES_URI", 默认值):
    #   - 若环境变量 POSTGRES_URI 存在,优先用它(方便换库,不用改代码)
    #   - 否则用后面这个默认连接串
    DB_URI = os.getenv(
        "POSTGRES_URI",
        "postgresql://myuser:123456@192.168.163.240:5432/mydb?connect_timeout=10",
    )

    # =================================================================================================
    # Step 1. 初始化 MCP 工具
    # =================================================================================================
    # await:等待异步函数跑完。这里会真正发起 HTTP 请求去拉工具。
    mcp_client, mcp_tools = await setup_mcp_tools()

    # =================================================================================================
    # Step 2. 初始化 PostgreSQL:Checkpointer + Store
    # =================================================================================================
    # 打印日志时不要把密码打出来。
    # "user:pass@host/db".split("@")[-1] → "host/db"(只保留 @ 后面那截)
    safe_uri = DB_URI.split("@")[-1] if "@" in DB_URI else DB_URI
    print(f"\nStep 1: 连接数据库 ...@{safe_uri} 并初始化存储...")

    # 两个组件分工不同,别搞混:
    #   Checkpointer(AsyncPostgresSaver)→ 存「对话/图状态」(短期记忆,按 thread_id)
    #   Store(PostgresStore)           → 存「虚拟文件 / 长期记忆」(StoreBackend 用它)
    # 为什么一个异步、一个同步?
    #   - astream/ainvoke 走异步 API → 需要 AsyncPostgresSaver
    #   - StoreBackend 的文件工具在线程里跑同步读写 → 需要同步 ConnectionPool + PostgresStore
    try:
        # ----- 2.0 先探测:能不能连上库? -----
        # 单独先连一次,失败时错误更清晰;比直接卡在复杂组件初始化里好排查。
        print("  -> 探测 PostgreSQL 连通性...")
        # with ... as ...:用完自动关闭连接(即使中间报错也会关)。
        # autocommit=True:每条 SQL 立刻生效,不必手动 commit。
        with pg_connect(DB_URI, autocommit=True) as probe:
            # cursor:执行 SQL 的「游标」对象。
            with probe.cursor() as cur:
                # SELECT 1:最轻量的探活语句,成功说明鉴权、库名、网络都 OK。
                cur.execute("SELECT 1")
                # fetchone():取回一行结果(这里是 (1,)),不取也行,主要是确认能执行。
                cur.fetchone()
        print("  -> 探测成功,开始初始化 AsyncPostgresSaver / PostgresStore...")

        # ----- 2.1 异步 Checkpointer -----
        # async with:异步上下文管理器。进入时建立异步连接,退出时自动关闭。
        # from_conn_string(DB_URI):用连接串创建 AsyncPostgresSaver 实例。
        async with AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer:
            print("  -> AsyncPostgresSaver.setup()...")
            # setup():首次使用时创建 checkpoint 相关表(若不存在)。
            # 必须 await,因为它是异步方法。
            await checkpointer.setup()
            print("  -> Checkpointer 就绪")

            # ----- 2.2 同步连接池 + Store -----
            # ConnectionPool:维护一组可复用的数据库连接,避免每次读写都新建 TCP。
            with ConnectionPool(
                    conninfo=DB_URI,              # 连接串
                    kwargs={"autocommit": True},  # 池里每条连接的默认参数
                    timeout=10,                   # 从池里借连接最多等 10 秒
                    open=True,                    # 进入 with 时立刻打开池
            ) as pool:
                print("  -> ConnectionPool 就绪")

                # 把「连接池」交给 PostgresStore。
                # 之后 StoreBackend 读写文件,本质就是对 store 表做 JSON 存取。
                store = PostgresStore(pool)

                # ----- 2.3 表结构迁移(建 store 表等)-----
                # store.MIGRATIONS:一组建表/改表的 SQL 字符串列表。
                # 首次跑必须执行;重复执行一般也应是幂等的(视版本而定)。
                print("  -> 执行 Store 表迁移...")
                # pool.connection():从池里借一条连接;with 结束自动还回去。
                with pool.connection() as conn:
                    with conn.cursor() as cur:
                        # 逐条执行迁移 SQL。
                        for migration in store.MIGRATIONS:
                            cur.execute(migration)

                print("Step 2: 数据库表结构就绪")

                # =================================================================================================
                # Step 3. 创建 DeepAgent
                # =================================================================================================

                # 初始化大模型客户端(这里是 DeepSeek)。
                deepseek_v4_pro = init_chat_model(
                    model="deepseek-v4-pro",                 # 模型名称
                    model_provider="deepseek",              # 提供商(决定用哪套协议)
                    base_url="https://api.deepseek.com",    # API 地址
                    api_key=os.getenv("api_key"),           # 从环境变量/.env 读密钥
                    temperature=0.7,                        # 随机性:越高越发散
                    max_tokens=10000,                       # 单次回复最大 token
                )

                # backend 必须是「工厂函数」,不能直接传 StoreBackend()。
                # 原因:create_deep_agent 在运行时会把 LangGraph 的 runtime(rt)传进来,
                #       StoreBackend 需要这个 rt 才能拿到 store、配置命名空间等。
                # lambda rt: StoreBackend(rt) 等价于:
                #   def backend_factory(rt):
                #       return StoreBackend(rt)
                # 我现在不创建 Backend;等你框架那边有了 rt,把 rt 传给我,我再 StoreBackend(rt)。
                # Agent 执行到 write_file 时,框架大致会:
                #   - 造好当前的 rt(里面已经带上你的 store)
                #   - 调用 backend_factory(rt) → 得到 StoreBackend(rt)
                #   - 用这个 Backend 真正读写 Postgres
                backend_factory = lambda rt: StoreBackend(rt)
                print("Step 3: 正在初始化 DeepAgent (Backend: StoreBackend -> PostgresStore)...")

                # 组装 Agent:
                #   model        → 用哪个大模型思考
                #   tools        → 额外工具(这里是 Context7 MCP)
                #   backend      → 文件系统落在哪里(StoreBackend → Postgres)
                #   store        → 必须传入,backend 才能真正读写 PostgresStore
                #   checkpointer → 会话状态落在哪里(AsyncPostgresSaver → Postgres)
                #   system_prompt→ 系统提示词,约束 Agent 行为
                agent = create_deep_agent(
                    model=deepseek_v4_pro,
                    tools=mcp_tools,
                    backend=backend_factory,
                    store=store,                 # 关键:没有 store,StoreBackend 无处可写
                    checkpointer=checkpointer,   # 关键:没有它,astream 无法按 thread 恢复状态
                    system_prompt="""你是一个高级技术助手。
                    你的任务是使用 Context7 工具查询关于 'DeepAgents StoreBackend' 的用法。
                    查询后,创建一个总结文件 '/knowledge/store_backend_notes.md',并写入关键信息。
                    由于你使用的是 StoreBackend,这个文件将直接存储在 PostgreSQL 数据库中。
                    最后,请读取该文件以验证存储成功。"""
                )

                # =================================================================================================
                # Step 4. 流式执行任务
                # =================================================================================================
                # thread_id:会话线程 ID。同一个 ID = 同一条对话线(可恢复历史)。
                # uuid4():随机生成,避免和旧会话冲突。
                thread_id = str(uuid.uuid4())
                # LangGraph 约定:把 thread_id 放在 config["configurable"] 里。
                config = {"configurable": {"thread_id": thread_id}}

                task = "请查询 StoreBackend 的用法,并将总结写入 /knowledge/store_backend_notes.md,最后读取它验证。"

                print(f"Session Thread ID: {thread_id}")
                print("\n" + "=" * 80)
                print(f"Step 4: 开始执行任务: {task}")
                print("=" * 80)

                # step:自己数「第几次工具调用」,纯展示用。
                step = 0

                try:
                    # astream:异步流式跑图。每完成一个节点就 yield 一个 chunk。
                    #
                    # 输入:{"messages": [("user", task)]}
                    #   → 等价于塞进一条用户消息,开始本轮对话。
                    #
                    # stream_mode="updates"(默认也是 updates):
                    #   每个 chunk 是「本节点的增量」,形如:
                    #     {"model": {"messages": [AIMessage(...)]}}
                    #     {"tools": {"messages": [ToolMessage(...)]}}
                    #   顶层键 = 节点名(model / tools / ...),不是 "messages"!
                    #   所以不能写成 if "messages" in chunk(那样几乎永远打印不出来)。
                    #
                    # 对比 stream_mode="values":
                    #   每个 chunk 是「完整状态」,顶层才有 "messages" 全历史列表。
                    async for chunk in agent.astream(
                        {"messages": [("user", task)]},
                        config=config,
                        stream_mode="updates",
                    ):
                        # chunk.items():把 {"model": {...}, ...} 拆成 (节点名, 节点输出)
                        for node_name, node_data in chunk.items():
                            # 有的节点本轮可能是 None / 空,跳过。
                            if not node_data:
                                continue

                            # LangGraph 有时用 Overwrite(value=真实数据) 表示「整份覆盖」。
                            # 打印前要先取 .value,否则拿不到真正的 dict。
                            if hasattr(node_data, "value"):
                                node_data = node_data.value
                            # 只要带 messages 的更新;改 todos 等其它字段的先忽略。
                            if not isinstance(node_data, dict) or "messages" not in node_data:
                                continue

                            msgs = node_data["messages"]
                            # messages 本身也可能被 Overwrite 包一层。
                            if hasattr(msgs, "value"):
                                msgs = msgs.value
                            # 偶发单条消息而不是 list,统一成 list 方便 for 遍历。
                            if not isinstance(msgs, list):
                                msgs = [msgs]

                            for msg in msgs:
                                # 只处理标准 LangChain 消息对象。
                                if not isinstance(msg, BaseMessage):
                                    continue

                                # AIMessage 可能带 tool_calls(模型决定要调哪些工具)。
                                # HumanMessage / ToolMessage 通常没有该属性。
                                # getattr(..., None) 避免 AttributeError;or [] 把 None 统一成空列表。
                                tool_calls = getattr(msg, "tool_calls", None) or []

                                # ----- 情况 A:模型发起工具调用 -----
                                if tool_calls:
                                    step += 1
                                    print(f"\n[Step {step} | {node_name}] 工具调用:")
                                    for tc in tool_calls:
                                        # tc 是 dict:{"name": "...", "args": {...}, "id": "..."}
                                        print(f"  • 工具: {tc.get('name')}")
                                        print(f"  • 参数: {tc.get('args')}")

                                # ----- 情况 B:工具执行结果 -----
                                if isinstance(msg, ToolMessage):
                                    # content 有时不是 str(极少见),先转成字符串再切片预览。
                                    content = msg.content if isinstance(msg.content, str) else str(msg.content)
                                    # 太长只显示前 300 字,避免刷屏。
                                    preview = content[:300] + "..." if len(content) > 300 else content
                                    print(f"\n[Tool Output ({msg.name})]: {preview}")
                                # ----- 情况 C:模型纯文本回复(没有工具调用)-----
                                elif msg.content and not tool_calls:
                                    content = msg.content if isinstance(msg.content, str) else str(msg.content)
                                    print(f"\n[Agent | {node_name}]:\n{content}")

                except Exception as e:
                    # Step 4 跑挂了:打印错误后直接 return,不再做 Step 5(文件可能没写完)。
                    print(f"❌ 运行时错误: {e}")
                    traceback.print_exc()
                    return

                # =================================================================================================
                # Step 5. 验证持久化:模拟「程序重启」后再读同一文件
                # =================================================================================================
                print("\n" + "=" * 80)
                print("Step 5: 验证 StoreBackend (Postgres) 持久化")
                print("=" * 80)

                print("\n验证操作: 使用新 Agent 实例 (模拟重启) 读取同一文件...")

                # 关键点:新建一个 Agent 对象,但复用同一个 store / checkpointer。
                # 含义:内存里的旧 agent 对象不要了;只要数据库还在,文件就还在。
                verify_agent = create_deep_agent(
                    model=deepseek_v4_pro,
                    backend=backend_factory,
                    store=store,                 # 同一个 PostgresStore → 读到同一份「虚拟磁盘」
                    checkpointer=checkpointer,
                    system_prompt="验证助手"
                )

                # ainvoke:一次性跑完,返回最终完整状态(不像 astream 逐步 yield)。
                # config 用同一个 thread_id:会话线连续;这里主要验证的是 store 里的文件。
                verify_result = await verify_agent.ainvoke({
                    "messages": [("user", "请读取 /knowledge/store_backend_notes.md 的内容")]
                }, config=config)

                # verify_result 是状态 dict,其中 "messages" 是整段对话消息列表。
                # [-1]:取最后一条(通常是模型的最终回答)。
                last_msg = verify_result["messages"][-1]

                print("\n[数据库读取验证结果]:")
                print("-" * 20)
                print(last_msg.content)
                print("-" * 20)
                # Store 表里大致是:prefix=命名空间(默认 filesystem),key=文件路径,value 含 content。
                print("\n说明: 'prefix' 对应 StoreBackend 的 namespace (默认为 'filesystem'),'key' 对应文件绝对路径。")

                # 给用户一条可直接在 psql / 客户端里跑的 SQL,方便亲眼看到落库内容。
                sql_query = """
                            SELECT
                                key,
                                value->>'content' as content,
                                updated_at
                            FROM store
                            WHERE prefix = 'filesystem'
                            AND key = '/knowledge/store_backend_notes.md';
                                    """
                print("\n提示: 你可以使用以下 SQL 在数据库中直接查询此文件:")
                # strip():去掉字符串首尾空白,打印更干净。
                print(sql_query.strip())

                print("\n演示结束")

    except Exception as e:
        # 这里捕获的是「连库 / 建表 / 开池」阶段的失败(Step 2 之前或之中)。
        print(f"❌ 数据库连接或初始化失败: {e}")
        traceback.print_exc()
        print("排查建议:")
        print("  1. 本机能否访问: Test-NetConnection 192.168.163.240 -Port 5432")
        print("  2. 服务端 listen_addresses / pg_hba.conf 是否允许你的客户端 IP")
        print("  3. 防火墙/安全组是否放行 5432")
        print("  4. 可用环境变量覆盖连接串: set POSTGRES_URI=postgresql://user:pass@host:5432/db?connect_timeout=10")


# ---------- 程序入口 ----------
# 只有「直接运行本文件」时才会进这里;被别人 import 时不会自动跑演示。
if __name__ == "__main__":
    # 再设一次策略:有些 IDE/调试器会在 import 阶段就创建事件循环,
    # 文件顶部那次可能不够,入口处再保险一次(仅 Windows)。
    if sys.platform == "win32":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    # asyncio.run(...):创建事件循环,跑完异步主函数,然后关闭循环。
    # 这是启动 async def 的标准写法。
    asyncio.run(run_store_backend_demo())

输出:

image

  • 这里看一下Backend vs Checkpointer vs Store 这三者的区别
参数组件Backend (后端)Checkpointer (检查点)Store (存储)
核心定义 环境层 (Environment) 状态层 (State / Short-term Memory) 记忆层 (Memory / Long-term Memory)
负责什么? “外部世界”的交互能力。
即:文件存在哪?代码在哪跑?
“当前对话”的上下文。
即:刚才说了什么?现在运行到哪一步了?
“跨会话”的知识积累。
即:用户叫什么名字?上次任务学到了什么?
数据类型 非结构化文件 (.py, .md, .txt)
运行时环境 (Shell, Process)
BaseMessage 列表 (User/AI/Tool Message)
Graph 节点状态
结构化 JSON 数据 (Key-Value)
用户偏好、长期笔记
典型实现 DockerBackend (容器)
FilesystemBackend (磁盘)
E2BBackend (云沙箱)
MemorySaver (内存)
PostgresSaver (数据库)
SqliteSaver (本地DB)
InMemoryStore (内存)
PostgresStore (数据库)
生命周期 任务级
(任务结束容器可能销毁)
线程级 (Thread)
(换个 thread_id 就没了)
全局级 (Global)
(所有 thread 都能查到)
形象比喻 工作台 / 电脑 大脑的工作记忆
(只会死记硬背当前对话)
日记本 / 知识库
(记录永久信息)

7.CompositeBackend 使用混合模式

CompositeBackend是 Agent 文件操作的“智能路由器” 。在单一后端模式下,Agent 所有的文件操作(读、写、列出目录)都只能去往同一个地方(要么全是本地磁盘,要么全是 Docker 容器内)。而 CompositeBackend 允许你根据 文件路径前缀 ,将请求分发给不同的后端。

核心优势

  1. 性能与开销优化 (Performance) 这是最关键的技术优势。
    • DockerBackend 的局限 : 向 Docker 容器内读写文件(尤其是大文件)需要经过 Docker Daemon 的 API (如 put_archive / get_archive ),涉及网络通信和打包解包,开销较大。
    • CompositeBackend 的解法 : 对于数据文件( /data ),直接通过 FilesystemBackend 进行本地 I/O 操作, 完全绕过了 Docker API ,读写速度是操作系统原生的速度。
  2. 计算与存储分离 (Decoupling)
    • 计算是临时的 : 你的 processor.py 脚本可能只需要运行一次,运行环境(Python 依赖)可能很复杂且容易冲突。放在 Docker 里最合适。
    • 数据是永恒的 : 你的 raw_metrics.txt 和 health_report.txt 是业务资产。通过路由直接落盘到宿主机,即使 Docker 容器崩溃、被删除或重启, 数据毫发无损 且立即可在宿主机访问(如代码 Line 212-222 所示的验证步骤)。
  3. 给予 Agent “混合云” 的能力,Agent 可以像人类工程师一样工作:
    • “我在临时的沙箱里写代码测试(Docker)。”
    • “测试好了,我把结果保存到公司的共享网盘里(Filesystem/Mount)。”

Agent、Docker、Filesystem 都在同一台:

import asyncio
import shutil
import os
import time
from pathlib import Path
from dotenv import load_dotenv

# DeepAgents 导入
from deepagents import create_deep_agent
from deepagents.backends.composite import CompositeBackend
from deepagents.backends.filesystem import FilesystemBackend
from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_core.messages import BaseMessage, ToolMessage

# 导入 DockerBackend
try:
    from docker_backend import DockerBackend
except ImportError:
    try:
        from deepagents.backends.docker import DockerBackend
    except ImportError:
        DockerBackend = None

def print_header():
    print("\n" + "="*80)
    print("DeepAgents CompositeBackend 混合后端演示 (极简版)")
    print("架构:混合云原生模式 (Docker 执行 + 本地持久化)")
    print("="*80)

async def setup_mcp_tools():
    print("  → 正在连接 Context7 MCP 服务器...")
    try:
        client = MultiServerMCPClient({
            "context7": {
                "transport": "stdio",
                "command": "npx",
                "args": ["-y", "@upstash/context7-mcp@latest"],
            }
        })
        tools = await client.get_tools()
        print("  → MCP 工具加载成功")
        return client, tools
    except Exception as e:
        print(f"ERROR: MCP 连接失败: {e}")
        return None, []

async def run_composite_demo():
    load_dotenv(override=True)
    print_header()

    if DockerBackend is None:
        print("严重错误: 未找到 DockerBackend。请确保 docker_backend.py 存在。")
        return

    # Step 1
    print("\n" + "-"*40)
    print("步骤 1: 配置混合环境")
    print("-"*40)

    host_work_dir = Path("workspace/data_analysis_project").resolve()
    if host_work_dir.exists():
        shutil.rmtree(host_work_dir)
    host_work_dir.mkdir(parents=True, exist_ok=True)
    print(f"  • 宿主机持久层: {host_work_dir}")

    container_mount_path = "/data"
    docker_volumes = {
        str(host_work_dir): {'bind': container_mount_path, 'mode': 'rw'}
    }
    print(f"  • 容器挂载:      {host_work_dir} ↔ {container_mount_path}")

    # Step 2
    print("\n" + "-"*40)
    print("步骤 2: 初始化混合后端 (Composite Backend)")
    print("-"*40)

    fs_backend = FilesystemBackend(root_dir=host_work_dir, virtual_mode=True)

    print("  • 正在启动 Docker 容器 (python:3.11-slim)...")
    docker_backend = DockerBackend(
        image="python:3.11-slim",
        auto_remove=True,
        volumes=docker_volumes
    )

    routes = {
        container_mount_path: fs_backend
    }
    backend = CompositeBackend(default=docker_backend, routes=routes)

    print("\n[路由表配置]")
    print(f"1. 默认路由 (/): DockerBackend (临时执行)")
    print(f"2. 持久化路由 ({container_mount_path}/*): FilesystemBackend (宿主机存储)")

    # Step 3
    print("\n" + "-"*40)
    print("步骤 3: 部署 Agent")
    print("-"*40)

    mcp_client, mcp_tools = await setup_mcp_tools()

    system_prompt = f"""你是一名在混合环境中工作的高级数据工程师。

    环境地图:
    1. 执行层 (根目录 `/`):
       - 临时的 Docker 容器。
       - 用于创建脚本 (`.py`) 和运行命令。
       - 这里的文会在会话结束后消失。

    2. 存储层 (`{container_mount_path}`):
       - 从宿主机挂载的持久化存储。
       - 用于存放 输入 数据和 输出 报告。
       - 这里的文件会永久保存。

    你的任务:
    1. **摄入**: 创建一个文件 `{container_mount_path}/raw_metrics.txt`,内容为 "CPU: 45%, Mem: 60%"。
       (注意: 这使用了 'write_file' 工具,该工具通过路由直接写入宿主机文件系统)。

    2. **处理**: 创建一个 Python 脚本 `/processor.py` (在根目录),该脚本:
       - 读取 `{container_mount_path}/raw_metrics.txt`。
       - 计算 "健康分数" (模拟一下即可)。
       - 将报告写入 `{container_mount_path}/health_report.txt`。
       - 打印 "Analysis Complete"。

    3. **执行**: 使用 `python /processor.py` 运行脚本。
       (注意: 这在 Docker 内部运行。Docker 因为卷挂载能看到这些文件)。

    4. **验证**: 读取 `{container_mount_path}/health_report.txt` 并显示它。
    """

    agent = create_deep_agent(
        model=ChatOpenAI(model="gpt-4o", temperature=0),
        tools=mcp_tools,
        backend=backend,
        system_prompt=system_prompt
    )

    # Step 4
    print("\n" + "-"*40)
    print("步骤 4: 任务执行")
    print("-"*40)

    task_input = "开始工程流水线。"
    config = {"configurable": {"thread_id": "composite_demo_simple_v1"}}

    step_count = 0
    try:
        message_history_len = 0

        async for event in agent.astream({"messages": [("user", task_input)]}, config=config):
            if "messages" in event:
                current_messages = event["messages"]
                if len(current_messages) > message_history_len:
                    for i in range(message_history_len, len(current_messages)):
                        msg = current_messages[i]

                        # Agent Thinking
                        if isinstance(msg, BaseMessage) and msg.content and not getattr(msg, "tool_calls", None):
                            step_count += 1
                            print(f"\n[🧠 Agent 思考 (步骤 {step_count})]:\n{msg.content}")

                        # Tool Calls
                        if hasattr(msg, "tool_calls") and msg.tool_calls:
                            step_count += 1
                            for tc in msg.tool_calls:
                                tool_name = tc['name']
                                args = tc['args']

                                # Routing logic visualization
                                target = "Docker 容器 🐳"
                                path_arg = args.get('file_path') or args.get('path')
                                if path_arg and str(path_arg).startswith(container_mount_path):
                                    target = "宿主机文件系统 💾"

                                print(f"\n[🛠️ 工具执行 (步骤 {step_count})]:")
                                print(f"  • 工具: {tool_name}")

                                # Special handling for code content
                                if tool_name == "write_file" and path_arg and str(path_arg).endswith(".py"):
                                    code_content = args.get("content", "")
                                    # Print args without content first
                                    args_copy = args.copy()
                                    args_copy['content'] = "(代码内容如下...)"
                                    print(f"  • 参数: {args_copy}")
                                    print(f"  • 路由: → {target}")
                                    print(f"  • 📝 写入代码内容:\n")
                                    print("-" * 20)
                                    print(code_content)
                                    print("-" * 20)
                                else:
                                    print(f"  • 参数: {str(args)[:200] + '...' if len(str(args)) > 200 else args}")
                                    print(f"  • 路由: → {target}")

                        # Tool Outputs
                        if isinstance(msg, ToolMessage):
                            content = msg.content
                            if len(content) > 300:
                                content = content[:300] + "... [已截断]"
                            print(f"\n[↳ 输出]: {content}")

                    message_history_len = len(current_messages)

    except Exception as e:
        print(f"\n运行时错误: {e}")

    # Step 5
    print("\n" + "-"*40)
    print("步骤 5: 宿主机侧验证")
    print("-"*40)

    report_path = host_work_dir / "health_report.txt"
    raw_path = host_work_dir / "raw_metrics.txt"

    if raw_path.exists():
        print(f"✅ 原始数据已找到: {raw_path} (通过直接 FS 路由创建)")
    else:
        print(f"❌ 原始数据丢失: {raw_path}")

    if report_path.exists():
        content = report_path.read_text()
        print(f"\n🏆 持久化验证成功! 文件: {report_path}")
        print("内容:")
        print("-" * 20)
        print(content)
        print("-" * 20)
    else:
        print(f"❌ 报告丢失: {report_path}")

    # Step 6
    print("\n正在关闭基础设施...")
    if 'docker_backend' in locals() and hasattr(docker_backend, "close"):
        docker_backend.close()
        print("  • Docker 容器已终止")

    print("\n✨ 演示圆满完成!")

if __name__ == "__main__":
    try:
        # asyncio.run(run_composite_demo())
        await run_composite_demo()
    except RuntimeError as e:
        if "asyncio.run() cannot be called from a running event loop" in str(e):
            print("检测到正在运行的事件循环。请在单元格中使用 'await run_composite_demo()'。")
        else:
            raise e

Agent 在 Windows,Docker 在远程 Linux:

"""
================================================================================
【小白必读】这个文件到底在演示什么?
================================================================================

一句话:让 AI Agent 在 Docker 容器里写脚本、跑代码,同时把「重要数据」存到
        不会随容器删除而消失的目录里。

把它想成「厨房 + 保险箱」:
  - 厨房(Docker 容器):切菜、炒菜(写 .py、执行 python),用完可以拆掉厨房
  - 保险箱(挂载卷 /data):放原料和成品报告,厨房拆了,保险箱里的东西还在

Agent 眼里的路径长这样:

    /processor.py          ← 写在「厨房」里,容器删掉就没了(临时)
    /data/raw_metrics.txt  ← 写在「保险箱」里,容器删了还在(持久)
    /data/health_report.txt

两种运行模式(看你连的是本机 Docker 还是远程 Linux Docker):

  【本机 Docker】用 CompositeBackend「按路径分流」
      /        → DockerBackend      (跑命令、写临时脚本)
      /data/   → FilesystemBackend  (直接写本机文件夹;该文件夹同时挂进容器)

  【远程 Docker】不能用上面那套分流(本机 Windows 盘 ≠ 远程 Linux 盘)
      只用:单一 DockerBackend + 把远程目录挂到容器 /data
      /data/*       → 落在远程服务器硬盘(持久)
      /processor.py → 落在容器自己的磁盘(临时)

阅读建议:从上到下跟着「步骤1 → 步骤5」走,注释会解释「为什么要这样写」。
================================================================================
"""

# ---------------------------------------------------------------------------
# 导入区:把后面要用的「零件」拿进来
# ---------------------------------------------------------------------------
import asyncio   # Python 异步:可以一边等网络/模型,一边不卡死整个程序
import os        # 读环境变量(.env 里的 api_key、DOCKER_BASE_URL 等)
import shutil    # 删目录、复制文件等文件操作

import dotenv
from pathlib import Path  # 比字符串更安全的「路径」对象

# deepagents:创建「会用工具干活」的 Agent
from deepagents import create_deep_agent
# FilesystemBackend:把读写文件落到「真实磁盘文件夹」
from deepagents.backends.filesystem import FilesystemBackend
# CompositeBackend:按路径前缀,把请求分给不同 backend(像路由器)
from deepagents.backends.composite import CompositeBackend

# LangChain:初始化大模型、识别消息类型
from langchain.chat_models import init_chat_model
from langchain_core.messages import BaseMessage, ToolMessage
# MCP:Model Context Protocol,给 Agent 接外部工具(这里接 Context7 查文档)
from langchain_mcp_adapters.client import MultiServerMCPClient

# 我们自己写的后端:把 Agent 的「执行命令 / 读写文件」接到 Docker 容器里
from create_demo_deep_agent.docker_backend import DockerBackend
from rich.console import Console  # 彩色终端打印,好看一点

# 从项目里的 .env 文件加载密钥、Docker 地址等,避免写死在代码里
dotenv.load_dotenv()

# 全局彩色打印机(setup_mcp_tools 里会用到)
console = Console()


# ===========================================================================
# 函数 A:可选地连接「查文档」MCP 工具
# ===========================================================================
async def setup_mcp_tools():
    """
    尝试连接 Context7 MCP 服务,拿到「查库文档」这类工具。

    返回值:
      (mcp_client, tools)
      - 成功:客户端对象 + 工具列表
      - 失败:None, []  —— 演示照样能跑,只是 Agent 少了查文档能力

    小白提示:
      async / await = 异步函数。遇到网络请求时用 await「等一等」,
      不会把整个 Python 进程卡死。调用它时也要用 await。
    """
    # [dim]...[/dim] 是 Rich 的标记语法:灰色弱化显示
    console.print("[dim]正在连接 Context7(streamable_http)...[/dim]")

    try:
        # 配置一个名叫 "context7" 的 MCP 服务器
        # transport="streamable_http":走 HTTP 流式协议(不是本地起一个子进程)
        # url:Context7 官方提供的 MCP 地址
        mcp_client = MultiServerMCPClient({
            "context7": {
                "transport": "streamable_http",
                "url": "https://mcp.context7.com/mcp",
            },
        })
        # get_tools():发网络请求,把远端工具描述转成 LangChain 可用的 Tool
        tools = await mcp_client.get_tools()
        console.print(f"[bold green]成功加载 {len(tools)} 个 MCP 工具(HTTP)[/bold green]")
        # 有的场景需要一直握着 client;本演示主要用 tools
        return mcp_client, tools
    except Exception as e:
        # 连不上也不让整个程序崩溃 —— 返回空列表,后面照常创建 Agent
        console.print(f"[bold yellow]Context7 HTTP 连接失败:{e}[/bold yellow]")
        return None, []


# ===========================================================================
# 函数 B:演示主流程(核心!从头读到尾就能懂整份脚本)
# ===========================================================================
async def run_composite_demo():
    """
    整份演示按 5 步走:

      步骤1  决定「持久数据」挂在哪台机器的哪个目录
      步骤2  启动 Docker 容器,组装 backend(本机用 Composite,远程只用 Docker)
      步骤3  接上 MCP(可选)+ 写好 system_prompt(告诉 Agent 环境地图和任务)
      步骤4  创建 Agent,流式执行:写数据 → 写脚本 → 跑脚本 → 读报告
      步骤5  验证持久文件还在,然后关掉容器
    """

    # ==================================================================
    # 步骤1:配置混合环境 —— 「保险箱」放在哪?
    # ==================================================================
    print("\n" + "=" * 100)
    print("步骤1:配置混合环境")
    print("=" * 100)

    # ----- 1.1 连哪台 Docker? -----
    # 环境变量 DOCKER_BASE_URL 决定连本机还是远程。
    # 例: "tcp://192.168.102.129:2375" → 连局域网里一台 Ubuntu 上的 dockerd
    # 若你本机是 Windows Docker Desktop,常见是 npipe://... 这种管道地址
    DOCKER_BASE_URL = os.getenv("DOCKER_BASE_URL", "tcp://192.168.102.129:2375")

    # 判断是不是「远程 Docker」:
    #   - 有地址,且不是 Windows 本机管道 npipe:// → 当成远程
    #   - 远程时:卷路径必须是 Linux 路径,不能挂你本机的 E:\...
    is_remote_docker = bool(DOCKER_BASE_URL) and not DOCKER_BASE_URL.startswith("npipe://")

    # 容器里面,持久目录挂在哪个路径?后面 Agent 会往 /data/... 写东西
    container_mount_path = "/data"

    # ----- 1.2 本机工作目录(Windows 上的一个文件夹)-----
    # resolve():把相对路径变成绝对路径,避免「当前目录变了路径就错」
    local_work_dir = Path("workspace/data_analysis_project").resolve()
    # 每次演示先清空,保证从干净状态开始(避免上次残留文件干扰判断)
    if local_work_dir.exists():
        shutil.rmtree(local_work_dir)
    local_work_dir.mkdir(parents=True, exist_ok=True)

    # ----- 1.3 卷的「宿主机侧」路径(Docker 挂载左边那一头)-----
    # 【超级重要】Docker 挂载规则:
    #   volumes 左边的路径,必须是「跑 dockerd 那台机器」上真实存在的路径。
    #   - 本机 Windows Docker → 可以是 E:\code\InsightFlow\...
    #   - 远程 Linux Docker  → 必须是 /tmp/... 这种 Linux 路径
    #     (远程机器根本看不到你的 E 盘,硬挂会报 invalid volume specification)
    if is_remote_docker:
        # 远程服务器上的目录;可用环境变量 REMOTE_HOST_DATA_DIR 覆盖默认值
        volume_host_path = os.getenv(
            "REMOTE_HOST_DATA_DIR",
            "/tmp/deepagents_data_analysis",
        )
        print(f"· 模式:远程 Docker ({DOCKER_BASE_URL})")
        print(f"· 远程宿主机持久目录:{volume_host_path}")
        print(f"· 本机工作目录(仅本地镜像/日志,不参与卷挂载):{local_work_dir}")

        # 远程目录可能还不存在 → 用「一次性小容器」在服务器上 mkdir -p
        # 为什么 volumes 挂 /tmp?因为默认 volume_host_path 就在 /tmp 下面,
        # 把宿主机 /tmp 挂进容器 /tmp,容器里 mkdir 才能真正建到远程盘上。
        import docker as docker_sdk

        prep_client = docker_sdk.DockerClient(base_url=DOCKER_BASE_URL)
        try:
            prep_client.containers.run(
                "python:3.12-slim",                    # 用一个现成小镜像
                command=f"mkdir -p {volume_host_path}",  # 只干一件事:建目录
                volumes={"/tmp": {"bind": "/tmp", "mode": "rw"}},
                remove=True,  # 跑完立刻删掉这个临时容器,不留垃圾
            )
        finally:
            # 无论成功失败都关掉客户端连接,避免资源泄漏
            prep_client.close()
    else:
        # 本机模式:卷的左边 = 本机文件夹
        # 后面 FilesystemBackend 的 root_dir 也指向同一块盘 → 两边看到同一批文件
        volume_host_path = str(local_work_dir)
        print("· 模式:本机 Docker")
        print(f"· 宿主机持久层:{volume_host_path}")

    # Docker 卷的标准字典格式:
    #   { 宿主机路径: {"bind": 容器内路径, "mode": "读写权限"} }
    # 效果:容器里访问 /data  ≈  访问宿主机上的 volume_host_path
    docker_volumes = {
        volume_host_path: {"bind": container_mount_path, "mode": "rw"}
    }
    print(f"· 容器挂载:{volume_host_path} → {container_mount_path}")

    # ==================================================================
    # 步骤2:初始化混合后端 —— Agent 的「手」伸向哪里?
    # ==================================================================
    print("\n" + "=" * 100)
    print("步骤2:初始化混合后端")
    print("=" * 100)

    # ----- 2.1 启动一个常驻 Docker 容器当沙箱 -----
    # DockerBackend 内部大致会:必要时拉镜像 → 起容器 → 用类似
    #   tail -f /dev/null 的方式保活(容器一直开着,方便反复 exec)
    # 之后 Agent 调用 execute / write_file / read_file,都会进这个容器。
    print("  • 正在启动 Docker 容器 (python:3.12-slim)...")
    docker_backend = DockerBackend(
        image="python:3.12-slim",   # 容器用的镜像(带 python 即可跑脚本)
        auto_remove=True,           # 调用 close() 时自动删容器
        base_url=DOCKER_BASE_URL,   # 连本机还是远程 Docker
        volumes=docker_volumes,     # 把「保险箱」挂到容器 /data
    )

    # ----- 2.2 Composite 路由前缀必须带尾部斜杠 -----
    # 写成 "/data/",不要写成 "/data"。
    #
    # 原因(踩坑笔记):
    #   Composite 拼路径时用 prefix[:-1](去掉最后一个字符)
    #   "/data/"[:-1] → "/data"  再拼 "/a.txt" → "/data/a.txt"  ✓
    #   "/data"[:-1]  → "/dat"   再拼 "/a.txt" → "/dat/a.txt"   ✗ 怪路径
    route_prefix = container_mount_path.rstrip("/") + "/"  # → "/data/"

    if is_remote_docker:
        # ========== 远程模式:只用 DockerBackend,不要 Composite→Docker 分流 ==========
        #
        # 为什么?两个坑叠在一起:
        #
        # 坑① Composite 会「剥前缀」:
        #   Agent 写 "/data/raw_metrics.txt"
        #   → Composite 剥掉 "/data/"
        #   → 交给子 backend 的是 "/raw_metrics.txt"
        #   FilesystemBackend 需要这种「相对 root 的路径」→ 剥前缀是对的
        #   DockerBackend 需要容器内绝对路径 "/data/..." → 剥前缀就错了
        #     文件跑到容器根目录 /raw_metrics.txt,卷上的 /data 还是空的
        #     后面 python 读 /data/raw_metrics.txt → FileNotFoundError
        #
        # 坑② 磁盘根本不是同一块:
        #   本机 FilesystemBackend 写的是 Windows 盘
        #   远程容器挂的是 Linux 盘
        #   两边对不上,Composite(Filesystem+Docker) 在远程场景不适用
        #
        # 结论:远程只用「单一 DockerBackend + 卷」:
        #   /data/*        → 远程宿主机目录(持久)
        #   /processor.py  → 容器可写层(销毁即消失)
        backend = docker_backend
        print("\n[后端配置 · 远程 Docker]")
        print("1. 单一 DockerBackend + 卷挂载 /data(持久)")
        print("2. 未用 Composite→Docker 路由(避免路径前缀被剥掉写错位置)")
        print("3. 标准 Composite(Filesystem+Docker) 请在本机 Docker 下演示")
    else:
        # ========== 本机模式:标准 CompositeBackend 用法 ==========
        #
        # 数据流示意(写 /data/a.txt 时):
        #   Agent
        #     → Composite 看见前缀 /data/,交给 FilesystemBackend
        #     → 剥成 /a.txt,写到 本机 local_work_dir/a.txt
        #   同时:local_work_dir 又被挂进容器的 /data
        #     → 容器里 python 读 /data/a.txt,读到的是同一份文件
        #
        # CompositeBackend 两个关键参数:
        #   default:路径没命中任何路由时用谁
        #            (这里是 Docker → 负责跑命令、写 /processor.py)
        #   routes :{ 路径前缀: 后端 }
        #            (这里 /data/ → 本机文件系统)
        files_backend = FilesystemBackend(
            root_dir=local_work_dir,
            virtual_mode=True,  # 虚拟路径模式:Agent 仍用 /data/... 这种「看起来像容器」的路径
        )
        backend = CompositeBackend(
            default=docker_backend,
            routes={route_prefix: files_backend},
        )
        print("\n[路由表配置 · 本机 Composite]")
        print("1. 默认路由(/): DockerBackend (临时执行)")
        print(f"2. 持久化路由 {route_prefix}: FilesystemBackend → {local_work_dir}")

    # ==================================================================
    # 步骤3:准备 MCP 工具 + 系统提示词(告诉 Agent「世界长什么样」)
    # ==================================================================
    print("\n" + "=" * 100)
    print("步骤3:创建混合 Agent")
    print("=" * 100)

    # 可选工具;连不上就是 tools=[],不影响后面主任务
    mcp_client, tools = await setup_mcp_tools()

    # system_prompt = 给 Agent 的「说明书」
    # 必须把路径写清楚,否则模型可能把数据写到容器根目录,而不是 /data
    # f"""...""" 里的 {container_mount_path} 会替换成 "/data"
    system_prompt = f"""
        你是一名在混合环境中工作的高级数据工程师。
        环境地图:
        1.执行层(根目录 '/')
            - 临时的Docker容器
            - 用于创建脚本('.py')和运行命令
            - 这里的文件会在会话结束后消失
        2.存储层('{container_mount_path}'):
            - 挂载到 Docker 宿主机的持久化目录
            - 用于存放输入数据和输出报告
            - 这里的文件会保留下来
        你的任务:
        1. **摄入**: 创建一个文件 `{container_mount_path}/raw_metrics.txt`,内容为 "CPU: 45%, Mem: 60%"。

        2. **处理**: 创建一个 Python 脚本 `/processor.py` (在根目录),该脚本:
           - 读取 `{container_mount_path}/raw_metrics.txt`。
           - 计算 "健康分数" (模拟一下即可)。
           - 将报告写入 `{container_mount_path}/health_report.txt`。
           - 打印 "Analysis Complete"。

        3. **执行**: 使用 `python /processor.py` 运行脚本。
           (注意: 这在 Docker 内部运行。Docker 因为卷挂载能看到 /data 下的文件)。

        4. **验证**: 读取 `{container_mount_path}/health_report.txt` 并显示它。
        """

    # ==================================================================
    # 步骤4:创建 Agent 并流式执行任务
    # ==================================================================
    print("\n" + "=" * 100)
    print("步骤4:任务执行")
    print("=" * 100)

    # ----- 4.1 初始化大模型 -----
    # init_chat_model:LangChain 的统一入口,按名字接不同厂商的模型
    deepseek_v4_pro = init_chat_model(
        model="deepseek-v4-pro",
        model_provider="deepseek",
        base_url="https://api.deepseek.com",
        api_key=os.getenv("api_key"),  # 从 .env 读,不要把密钥提交进 git
        temperature=0.7,               # 随机性:0 更死板,越高越发散
        max_tokens=10000,              # 单次回复最长 token 数
    )

    # ----- 4.2 组装 Agent -----
    # create_deep_agent 四个核心零件:
    #   model         → 谁负责「思考 / 决定下一步」
    #   tools         → 额外工具(这里是 MCP 查文档,可为空)
    #   backend       → 文件读写、命令执行落到哪(上面组好的 docker 或 composite)
    #   system_prompt → 行为约束 + 环境说明
    agent = create_deep_agent(
        model=deepseek_v4_pro,
        tools=tools,
        backend=backend,
        system_prompt=system_prompt
    )

    # thread_id:会话线 ID。同一个 ID 可以接着上次对话;这里写死方便反复跑演示
    config = {"configurable": {"thread_id": "composite_simple_v1"}}

    step_count = 0  # 仅用于终端打印「第几步」,跟 Agent 内部状态无关

    try:
        # ----- 4.3 流式跑 Agent -----
        # astream:异步流式执行。每完成图里的一个节点(如 model / tools),
        #          就吐出一个 chunk,方便我们实时打印。
        #
        # stream_mode="updates" 时,chunk 长这样:
        #   {"model": {"messages": [...]}}
        #   {"tools": {"messages": [...]}}
        # 注意:顶层键是「节点名」,不是 "messages"!
        # 所以要用 for node_name, node_data in chunk.items() 来拆。
        async for chunk in agent.astream(
                {"messages": [{"role": "user", "content": "请完成任务"}]},
                config=config,
                stream_mode="updates",
        ):
            for node_name, node_data in chunk.items():
                # 空更新:跳过
                if not node_data:
                    continue

                # LangGraph 有时用 Overwrite(value=真实数据) 整份覆盖状态,
                # 这种对象没有普通 dict 的键,要先取出 .value
                if hasattr(node_data, "value"):
                    node_data = node_data.value

                # 我们只关心带 messages 的更新(todos 等其它字段先忽略)
                if not isinstance(node_data, dict) or "messages" not in node_data:
                    continue

                msgs = node_data["messages"]
                if hasattr(msgs, "value"):
                    msgs = msgs.value
                # 偶发只来一条消息,统一包成 list,后面 for 好好写
                if not isinstance(msgs, list):
                    msgs = [msgs]

                for msg in msgs:
                    # 不是标准消息对象就跳过(防御性写法)
                    if not isinstance(msg, BaseMessage):
                        continue

                    # AIMessage 可能带 tool_calls(「我要调用某某工具」);
                    # 没有就当成空列表,避免后面 None 报错
                    tool_calls = getattr(msg, "tool_calls", None) or []

                    # ----- 情况 A:模型纯文本思考 / 回答(没有工具调用)-----
                    if msg.content and not tool_calls and not isinstance(msg, ToolMessage):
                        step_count += 1
                        print(f"\n[🧠 Agent 思考 (步骤 {step_count} | {node_name})]:\n{msg.content}")

                    # ----- 情况 B:模型决定调用工具(write_file / execute 等)-----
                    if tool_calls:
                        step_count += 1
                        for tc in tool_calls:
                            tool_name = tc["name"]  # 例如 "write_file"、"execute"
                            args = tc["args"]       # 工具参数字典

                            # 下面的「路由」只是打印提示,帮你对照路径落在哪一层。
                            # 远程模式下并没有真正的 Composite 分流,
                            # 但路径以 /data 开头仍表示「持久卷」。
                            target = "Docker 容器 🐳"
                            path_arg = args.get("file_path") or args.get("path")
                            if path_arg and str(path_arg).startswith(container_mount_path):
                                target = "持久化卷 /data 💾"

                            print(f"\n[🛠️ 工具执行 (步骤 {step_count})]:")
                            print(f"  • 工具: {tool_name}")

                            # 写 .py 时:把代码单独漂亮打印出来,方便你检查脚本对不对
                            if tool_name == "write_file" and path_arg and str(path_arg).endswith(".py"):
                                code_content = args.get("content", "")
                                args_copy = dict(args)
                                # 参数摘要里不要塞整段代码,改成占位提示
                                args_copy["content"] = "(代码内容如下...)"
                                print(f"  • 参数: {args_copy}")
                                print(f"  • 路由: → {target}")
                                print("  • 📝 写入代码内容:\n")
                                print("-" * 20)
                                print(code_content)
                                print("-" * 20)
                            else:
                                args_str = str(args)
                                # 参数太长只预览前 200 字符,避免终端刷屏
                                print(f"  • 参数: {args_str[:200] + '...' if len(args_str) > 200 else args}")
                                print(f"  • 路由: → {target}")

                    # ----- 情况 C:工具执行完返回的结果(ToolMessage)-----
                    if isinstance(msg, ToolMessage):
                        content = msg.content if isinstance(msg.content, str) else str(msg.content)
                        if len(content) > 300:
                            content = content[:300] + "... [已截断]"
                        print(f"\n[↳ 输出]: {content}")

    except Exception as e:
        # 任何一步炸了:打印错误 + 完整堆栈,方便排查
        print(f"❌️ 混合 Agent 运行失败:{e}")
        import traceback
        traceback.print_exc()

    # ==================================================================
    # 步骤5:持久化验证 —— 容器可以没了,文件还在吗?
    # ==================================================================
    print("\n" + "=" * 100)
    print("步骤5:持久化验证")
    print("=" * 100)

    if is_remote_docker:
        # 远程:文件在 Linux 服务器硬盘上,Windows 的 Path.exists() 根本看不到。
        # 所以在关容器之前,用 docker exec(封装在 docker_backend.execute)去 cat 验证。
        print(f"· 在远程卷中检查文件(容器内路径 {container_mount_path}/...)")
        for name in ("raw_metrics.txt", "health_report.txt"):
            path_in_container = f"{container_mount_path}/{name}"
            result = docker_backend.execute(f"cat {path_in_container}")
            # exit_code == 0 表示命令成功;output 是标准输出文本
            if result.exit_code == 0:
                print(f"\n✅ 找到 {path_in_container}")
                print(f"  • 内容:\n{result.output}")
            else:
                print(f"\n❌ 缺少 {path_in_container}: {result.output}")
        print(f"\n提示: 容器删除后,文件仍在远程主机 {volume_host_path}/ 下,可 SSH 上去查看。")
    else:
        # 本机:FilesystemBackend 写的就是 local_work_dir,直接读磁盘即可
        report_path = local_work_dir / "health_report.txt"
        raw_path = local_work_dir / "raw_metrics.txt"

        if raw_path.exists():
            print(f"✅ 原始数据已经找到 💾 路径:{raw_path}")
            print(f"  • 内容:\n{raw_path.read_text()}")
        else:
            print(f"❌ 原始数据丢失:{raw_path}")

        if report_path.exists():
            print(f"\n✅ 持久化验证成功 🎉 文件:{report_path}")
            print("\n" + "=" * 100)
            print(f"  • 内容:\n{report_path.read_text()}")
            print("=" * 100)
        else:
            print(f"❌ 报告丢失:{report_path}")

    # ---------- 收尾:停掉并删除容器 ----------
    print("\n正在关闭基础设施...")
    # locals():当前函数里已创建的局部变量名集合
    # 防止步骤2半路失败、docker_backend 还没创建时,这里 NameError
    if "docker_backend" in locals() and hasattr(docker_backend, "close"):
        docker_backend.close()
        print("  • Docker 容器已终止")
        # 记住:
        #   /data 下的文件在「卷」上 → 容器没了,文件还在
        #   /processor.py 在容器自己的磁盘 → 会跟着容器一起消失

    print("\n✨ 演示圆满完成!")


# ===========================================================================
# 程序入口
# ===========================================================================
# 只有「直接运行本文件」时才启动演示:
#   python cim_posite_backend_demo.py  → 会跑
#   from xxx import run_composite_demo → 不会自动跑(方便别人当库引用)
if __name__ == "__main__":
    # asyncio.run:创建事件循环,跑完异步主函数后自动清理
    asyncio.run(run_composite_demo())

image

5.3.Human-in-the-loop(HITL) 人工干预

Human-in-the-loop (HITL):对于 write_file 或 delegate_task 等关键操作,利用 LangGraph 的中断机制加入人工审批。
异步运行:DeepAgent 的任务通常耗时较长,务必使用异步 Webhooks 接收结果。
监控与调试:强烈建议结合 LangSmith 使用。由于 DeepAgent 内部有复杂的子 Agent 递归调用,使用 LangSmith 的 Tracing 功能是排查问题的有效手段。
后端挂载:在生产环境中,建议将 VFS 挂载到云端存储(如 S3),以防止容器重启导致 Agent 的"记忆"丢失。
interrupt_on:这个参数其实是一个HITL的开关,就是把HITL的中间件插入到DeepAgent的执行流程中,当DeepAgent执行到需要人工审批的操作时,就会中断执行,等待人工审批。

  • 类型 : dict[str, bool | InterruptOnConfig]
  • 作用 : 映射“工具名称”到“中断配置”。
  • 示例 : interrupt_on={“write_file”: True} 表示当 Agent 试图调用 write_file 工具时,程序会暂停(Suspend),等待人工(Human)在 LangGraph 层面进行 Approve 、 Reject 或 Edit 操作后才能继续。
"""
一句话:让 AI Agent 干活时,在「调用搜索工具之前」先停下来,等你(管理员)点头同意,再继续执行。
你下任务 → Agent 思考 → 想调搜索工具 → 🛑 暂停问你
                                      ↓
                              你输入 y 批准
                                      ↓
                         真正搜索 → 写文件 → 完成
1.准备:模型 + Tavily 搜索 + Agent,并打开 interrupt_on={"tavily_search": True}
2.第一次 astream:跑任务,预计停在「要搜索」这一步
3.aget_state:看卡在哪、准备调什么工具、参数是什么
4.input():终端里问你同不同意
5.第二次 astream(Command(resume=...)):你批准后,从断点继续跑完

生活类比:
  - Agent 像一个助理
  - 助理想「上网搜索」时,必须先问你:「我可以搜吗?」
  - 你说「可以(y)」→ 助理继续搜,并写总结文件
  - 你说「不行」→ 本演示里就停住了(没有继续)

关键名词(后面注释会反复出现):
  - Agent:会自己决定「下一步用什么工具」的 AI 程序
  - 工具(tool):Agent 能调用的能力,这里是 Tavily 联网搜索
  - 中断(interrupt):执行到某一步先暂停,不立刻真正调用工具
  - HITL:Human In The Loop,人在回路里做审批
  - checkpointer:把对话/状态存起来,暂停后还能从断点恢复
  - thread_id:会话编号,同一编号 = 同一段对话历史
"""
import asyncio
import os
import dotenv
from deepagents.backends import FilesystemBackend
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents.middleware.human_in_the_loop import HITLResponse, ApproveDecision
from langgraph.types import Command

# 1. 初始化环境
dotenv.load_dotenv()


async def run_human_in_the_loop_demo():
    """
    示例1:基础中断功能 (封装版本),在工具调用中断,让用户确认是否继续执行
    流程概览(跟着这个走就不迷路):
      1) 创建模型 + 搜索工具 + Agent(并打开 interrupt_on)
      2) 第一次 astream 跑任务 → 预计会在搜索前暂停
      3) aget_state 看「卡在哪了」、要调什么工具
      4) input() 问你同不同意
      5) 同意后用 Command(resume=...) 第二次 astream 继续跑完
    :return:
    """
    print("\n" + "="*100)
    print("示例1:interrupt_on 使用")
    print("=" *100)
    print("\n基础中断功能,在工具调用中断,让用户确认是否继续执行\n")

    # 初始化模型
    deepseek_v4_pro = init_chat_model(
        model="deepseek-v4-pro",
        model_provider="deepseek",
        base_url="https://api.deepseek.com",
        api_key=os.getenv("api_key"),  # 从 .env 读,不要把密钥提交进 git
        temperature=0.7,  # 随机性:0 更死板,越高越发散
        max_tokens=10000,  # 单次回复最长 token 数
    )

    # 初始化搜索工具
    search_tool = TavilySearch(max_results=3)

    # 哪些工具调用前必须人工审批(后面统计 pending 时会用到)
    interrupt_on = {"tavily_search": True}

    # 创建agent
    agent = create_deep_agent(
        model=deepseek_v4_pro,
        tools=[search_tool],
        backend=FilesystemBackend(root_dir="/workspace", virtual_mode=True),  # root_dir:Agent 读写文件时相对这个目录, virtual_mode=True:给 Agent 看的是「虚拟路径」,更安全、路径更干净
        checkpointer=InMemorySaver(),  # 没有checkpointer中断后就【记不住卡在哪里】,无法resume
        interrupt_on=interrupt_on,  # 调用 tavily_search 前暂停,等人批准/拒绝/编辑
    )

    task = "搜索关于Google ADK框架的最新功能信息,创建一个总结文件"
    config = {"configurable": {"thread_id": "user_001"}}

    # 追踪已打印的消息数量,避免重复打印
    message_history_len = 0

    def print_new_messages(current_messages):
        """只打印相对上次新增的消息,避免流式输出刷屏。"""
        nonlocal message_history_len
        if len(current_messages) <= message_history_len:
            return
        for i in range(message_history_len, len(current_messages)):
            msg = current_messages[i]
            if msg.type == "ai":
                if hasattr(msg, "tool_calls") and msg.tool_calls:
                    # 可能一次发起多个工具调用,全部打印
                    for tc in msg.tool_calls:
                        print(f"🔧 [AI决定工具调用] {tc['name']}")
                        print(f" 参数: {tc['args']}")
                elif msg.content:
                    print(f"[AI | {agent.name}]:\n{msg.content}")
            elif msg.type == "tool":
                content = msg.content if isinstance(msg.content, str) else str(msg.content)
                if len(content) > 300:
                    print(f"\n[工具输出]:\n{content[:300]}...\n")
                else:
                    print(f"\n[工具输出]:\n{content}\n")
        message_history_len = len(current_messages)

    # ======================================================================
    # 第一次执行:预期会在调用搜索工具前停住
    # ======================================================================
    print("[第一次执行 - 预期中断]")

    async for chunk in agent.astream(
        {"messages": [{"role": "user", "content": task}]},
        config=config,
    ):
        if "messages" in chunk:
            print_new_messages(chunk["messages"])

    # ======================================================================
    # HITL 循环:模型可能一次挂起多个工具,也可能多次中断
    # 规则:decisions 条数必须 == 本次挂起的工具调用数
    # ======================================================================
    while True:
        # aget_state:异步读状态;get_state 是同步的,不能 await
        agent_state = await agent.aget_state(config)
        print(f"\n ⏸️ 执行状态:{agent_state.next}")

        # 没有未完成任务 → 流程结束(正常跑完,或未触发 interrupt)
        if not agent_state.tasks:
            print("\n[系统]:流程已结束(没有待处理的中断)。")
            if agent_state.values.get("messages"):
                last_message = agent_state.values["messages"][-1]
                if last_message.type == "ai" and last_message.content:
                    print(f"\n[最终回复]:\n{last_message.content}")
            break

        print(f"\n--- 📝 执行已暂停:(HITL Middleware) ---")
        print(f"下一步骤(Next): {agent_state.next}")

        last_message = agent_state.values["messages"][-1]
        if not (hasattr(last_message, "tool_calls") and last_message.tool_calls):
            print("[系统]:暂停了,但最后一条消息没有 tool_calls,无法审批。")
            break

        # 只统计「配置了 interrupt_on」的工具调用(其它工具不会挂起,也不需要 decision)
        pending_tool_calls = [
            tc for tc in last_message.tool_calls if tc["name"] in interrupt_on
        ]
        if not pending_tool_calls:
            print("[系统]:没有需要人工审批的工具调用。")
            break

        print("\n======================================人工介入==========================================")
        print(f"待审批数量: {len(pending_tool_calls)}(每个都要有一条 decision)")
        for idx, tc in enumerate(pending_tool_calls, start=1):
            print(f"\n[{idx}] 工具: {tc['name']}")
            print(f"    参数: {tc['args']}")

        approval = input("\n是否批准以上全部操作?(y/n):").strip().lower()

        if approval != "y":
            print("\n[系统]:操作被拒绝,演示结束。(本演示仅完整处理 'y')")
            break

        print("\n[系统]:已批准,继续执行...")

        # 关键:有几个挂起的工具,就要有几条 ApproveDecision,少一条就会报错
        hitl_response = HITLResponse(
            decisions=[ApproveDecision(type="approve") for _ in pending_tool_calls]
        )

        async for chunk in agent.astream(
            Command(resume=hitl_response),
            config=config,  # 必须同一 thread_id,才能接到断点
            stream_mode="values",
        ):
            if "messages" in chunk:
                print_new_messages(chunk["messages"])

        # 恢复后再看状态:若又触发中断,while 会再问一次;否则下一轮 break

if __name__ == '__main__':
    asyncio.run(run_human_in_the_loop_demo())

输出:

image六、总结

DeepAgents 采用 中间件模式 来增强 Agent 能力,这些中间件在 create_deep_agent 时自动装配:

  • FilesystemMiddleware :
    • 提供 ls , read_file , write_file , edit_file , glob , grep , execute 等标准工具。
    • 亮点功能 : 大结果自动转存 。当工具(如搜索或爬虫)返回内容过长时,自动截断并保存到文件系统,仅返回文件路径给 LLM,极大节省 Token 并防止 Crash。
  • TodoListMiddleware :
    • 拦截 LLM 输出的 todo_list ,将其解析为结构化状态。
    • 支持任务的增删改查(CRUD)和状态流转(Pending -> Completed)。
  • SubAgentMiddleware :
    • 自动创建 task 工具。
    • 支持 General Purpose Agent (通用分身)和 Custom Agent (专家分身)。
    • 实现父子 Agent 间通过文件系统交换数据,无需序列化传递大量文本。
  • HumanInTheLoopMiddleware :
    • 通过 interrupt_on 参数配置。
    • 支持在特定工具调用前(如 write_file , execute )暂停,等待人工审批、修改或拒绝。
  • PatchToolCallsMiddleware :
    • 自动检测并修复 LLM 产生的“悬空”工具调用(Dangling Tool Calls),增强稳定性。
posted @ 2026-07-23 11:00  酒剑仙*  阅读(217)  评论(0)    收藏  举报