DeepAgent开发学习心得笔记

DeepAgents 学习笔记:从入门到上手,手把手搞懂多智能体框架

一、先搞懂:DeepAgents 到底是什么

先搞清楚"它解决什么问题",再学"怎么写代码",否则会很晕。

1.1 AI 的三次进化

  • LLM(大模型):只会"回答问题",一次性输出,黑盒。
  • AI Agent:能"调用工具、落地执行",比如联网搜索、写文件。
  • Agentic AI(深度代理):有"协作意识、可驾驭复杂工作流",会规划 → 执行 → 反馈 → 迭代,还能把任务派给多个子代理。

DeepAgents 就是帮你快速造"第三种智能体"的开箱即用框架 ,它建立在 langchain + langgraph 之上。

1.2 LangChain 全家桶一句话区分

角色 类比
langchain-core 最底层抽象(接口/类型) 砖块和水泥
langgraph 有状态的图编排引擎 施工队和脚手架
langchain Agent 高阶 API 毛坯房框架
deepagents 多智能体套件(规划/文件系统/子代理) 精装全配豪宅

📌 记忆口诀:单 Agent 用 langchain;要精细控制流程、持久化用 langgraph;要"长期运行 + 自主规划 + 子代理"直接用 deepagents。


二、5 分钟跑通第一个 Agent(Hello World)

目标:造一个会联网搜索、用中文回答的 Agent。

2.1 最小可运行示例

python 复制代码
from deepagents import create_deep_agent
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from tavily import TavilyClient
import os
load_dotenv()

tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))

def internet_search(query: str, max_results: int = 5,
                    topic: Literal["general", "news", "finance"] = "general",
                    include_raw_content: bool = False):
    return tavily_client.search(query=query, max_results=max_results,
                                topic=topic, include_raw_content=include_raw_content)

llm = ChatOpenAI(model_name="qwen3.7-max",
                 api_key=os.getenv("QWEN_API_KEY"),
                 base_url=os.getenv("QWEN_BASE_URL"))

agent = create_deep_agent(model=llm,
                          tools=[internet_search],
                          system_prompt="你是一个助手,需要使用网络搜索工具来获取信息。请使用中文回答问题")

resp = agent.invoke({"messages": {"role": "human", "content": "你好,我想知道北京的天气"}})
print(resp)

2.2 怎么拿到"最终那段话"

invoke 返回的是一个字典(LangGraph 的 state),所有对话都存在 resp["messages"] 里。最后一条就是 Agent 的终稿:

python 复制代码
# 安全通用的写法
text = resp["messages"][-1].content
print(text)
  • result["messages"] → 全流程对话列表
  • [-1] → 抓最后一条(Agent 整理后的回复)
  • .content → 取纯文本(它是 AIMessage 对象,不是 dict,所以用点号)

配 Key 提醒 :本项目用通义千问(QWEN_API_KEY / QWEN_BASE_URL),也支持 OpenAI、DeepSeek、Anthropic。Key 写在 .env,用 load_dotenv() 加载。


三、流式输出与异步执行

想看"打字机效果"或做高并发,就要会 stream / async

3.1 同步流式 agent.stream()

📄 源码:quickstart/01_helloworld_stream.py

python 复制代码
resp = agent.stream({"messages": {"role": "human", "content": "你好,我想知道北京的天气"}})
for chunk in resp:
    print(chunk)

每个 chunk 是一个小字典,关键看它的 key 是 "model" 还是 "tools"

python 复制代码
for chunk in stream:
    for key, value in chunk.items():
        if not value or "messages" not in value:
            continue
        last_msg = value["messages"][-1]
        if key == "model":
            if last_msg.tool_calls:                       # 模型决定调工具
                for tc in last_msg.tool_calls:
                    print(f"调用工具 {tc['name']} 参数 {tc['args']}")
            elif last_msg.content:
                print(f"结果: {last_msg.content}")        # 模型给最终回答
        elif key == "tools":
            print(f"工具返回: {last_msg.content}")         # 工具执行结果

3.2 异步 agent.ainvoke() / agent.astream()

📄 源码:quickstart/01_helloworld_stream_async.py

python 复制代码
import asyncio

async def ainvoke_agent(message: str):
    resp = await agent.ainvoke({"messages": {"role": "human", "content": message}})
    content = resp["messages"][-1].content
    print(content)

async def batch_invoke():
    # 用 asyncio.gather 并发跑三个请求,总耗时≈最慢的那个
    await asyncio.gather(
        ainvoke_agent("北京天气"),
        ainvoke_agent("股市行情"),
        ainvoke_agent("转会信息"),
    )

if __name__ == "__main__":
    asyncio.run(batch_invoke())

📌 astream() 返回异步迭代器 ,必须用 async for 遍历。高并发接口(FastAPI)、批量任务、GUI 程序里用异步才不会卡住主线程。


四、四大核心能力

DeepAgents「开箱即用」的真正底气,都在这里。

  1. 智能规划与任务分解(write_todos) :框架内置 write_todos 工具,Agent 会自动把复杂任务拆成待办清单并实时追踪 / 调整。你不用写代码,System Prompt 描述清楚目标即可。
  2. 高效上下文管理(文件系统工具) :内置 ls / read_file / write_file / edit_file。大文本先存文件、用时再读,避免把上下文窗口撑爆------相当于给 Agent 配了个"文件柜"。
  3. 子代理生成(task 工具):主代理把任务派给专业子代理,主代理只收最终结果,环境保持干净。详见下一节。
  4. 长期记忆:通过文件 / KV 存储 / 组合存储把信息落盘,跨会话也不"失忆"。详见第九节 Backends。

五、SubAgents 子代理(分而治之)

核心思想:一个主代理当"项目经理",把活派给专业子代理。 好处是上下文隔离(主代理不被中间结果淹没)+ 专业能力分工。

5.1 方式一:字典定义(最常用)

python 复制代码
weather_agent = {
    "name": "weather_agent",
    "description": "一个天气助手,需要使用工具来获取信息。请使用中文回答问题",
    "tools": [get_weather],
    "system_prompt": "你是一个天气助手,需要使用工具来获取信息。请使用中文回答问题"
}
math_agent     = {"name": "math_agent",     "description": "一个数学助手",     "tools": [], "system_prompt": "你是一个数学助手"}
translate_agent = {"name": "translate_agent", "description": "一个翻译助手",     "tools": [], "system_prompt": "你是一个翻译助手"}

main_agent = create_deep_agent(name="main_agent", model=llm,
                               subagents=[weather_agent, math_agent, translate_agent],
                               system_prompt="你是一个智能助手,可以将任务进行拆解,调用不同的子助手来完成任务。")

重点description 不是装饰!主代理靠这段描述判断该把任务派给谁。写得越具体、越"以行动为导向",分发越准。

5.2 方式二:CompiledSubAgent(兼容 langchain / langgraph)

python 复制代码
from deepagents import create_deep_agent, CompiledSubAgent
from langchain.agents import create_agent

# 先用 langchain 造一个普通 agent
generic_agent = create_agent(name="weather_agent", model=llm, tools=[get_weather],
                             system_prompt="你是一个天气助手,需要使用工具来获取信息。请使用中文回答问题")

# 把它「编译」成 deepagents 的子代理
weather_sub_agent = CompiledSubAgent(name="weather_agent",
                                     description="一个天气助手,需要使用工具来获取信息。请使用中文回答问题",
                                     runnable=generic_agent)

main_agent = create_deep_agent(name="main_agent", model=llm,
                               subagents=[weather_sub_agent],
                               system_prompt="你是一个智能助手,可以将任务进行拆解,调用不同的子助手来完成任务。")

同样可以把一个 LangGraph 的 StateGraph 编译进去(见 subagent/01_subagent_3.py):

python 复制代码
from langgraph.graph import add_messages, StateGraph
from langgraph.constants import START, END

class MyState(TypedDict):
    messages: Annotated[list, add_messages]

def node_demo(state):
    return {"messages": [AIMessage(f"处理后的结果:{state['messages'][-1].content}")]}

def build_graph():
    g = StateGraph(MyState)
    g.add_node("demo", node_demo)
    g.add_edge(START, "demo")
    g.add_edge("demo", END)
    return g.compile()

demo_sub_agent = CompiledSubAgent(name="demo_agent", description="一个演示助手", runnable=build_graph())

六、结构化输出(response_format)

想让子代理吐出"可解析的 JSON"而不是自由文本?用 Pydantic 模型约束它。

6.1 定义结构 + 配置 response_format

📄 源码:subagent/01_subagent_4.py / 01_subagent_5.py

python 复制代码
from pydantic import BaseModel, Field

class WeatherResponse(BaseModel):
    location: str = Field(title="Location", description="城市")
    temperature: int = Field(title="Temperature", description="温度")

weather_sub_agent = {
    "name": "weather_sub_agent",
    "description": "一个天气助手...",
    "tools": [get_weather],
    "system_prompt": "你是一个天气助手...",
    "response_format": WeatherResponse,   # 约束子代理输出结构
}

6.2 ⚠️ 实测坑:qwen 思考模式下不一定返回 JSON

01_subagent_5.py 里专门写了兜底解析 :如果模型返回的是 JSON 字符串,就手动 json.loads 再交给 Pydantic 校验。

python 复制代码
import json
from pydantic import parse_obj_as

last = result["messages"][-1]
if isinstance(last.content, str) and last.content.strip().startswith("{"):
    data = json.loads(last.content)
    weather_response = parse_obj_as(WeatherResponse, data)
    print(weather_response.location, weather_response.temperature)
  • JSON 模式ChatOpenAI(..., json_mode=True),再自己从文本里抠 JSON(见 01_subagent_4_json_mode.pyextract_json_from_text)。
  • Anthropic :用 ChatAnthropic + response_format 更干净,没有 tool_choice 的小毛病(见 01_subagent_4_anthropic.py)。

七、嵌套子代理(CEO → CTO → CODER)

真实项目会分层:老板派活给总监,总监派给工程师。

7.1 ❌ 字典形式"嵌套 subagents"不生效

📄 源码:subagent/01_subagent_6_ebedding.py

python 复制代码
cto_agent = {
    "name": "CTO",
    "system_prompt": "...必须找 CODER...",
    "tools": [],
    "subagents": [coder_agent]   # ← 字典形式下,底层根本不识别这个字段!
}

📌 结论 :字典子代理目前不支持 嵌套。想套娃,必须用 CompiledSubAgent 一层层包。

7.2 ✅ CompiledSubAgent 嵌套(正确姿势)

📄 源码:subagent/01_subagent_6_ebedding_2.py

python 复制代码
# 最底层:CODER
coder_agent = create_deep_agent(model=llm, tools=[], subagents=[],
                                name="CODER",
                                system_prompt="你是 CODER,高级 Python 工程师,只负责直接写代码。")
coder_subagent = CompiledSubAgent(name="coder",
                                  description="负责编写 Python 代码...",
                                  runnable=coder_agent)

# 中间层:CTO(挂上 coder)
cto_agent = create_deep_agent(model=llm, subagents=[coder_subagent], name="CTO",
                              system_prompt="你是 CTO,不能亲自写代码,必须调用 coder 子代理。")
cto_subagent = CompiledSubAgent(name="cto", description="...", runnable=cto_agent)

# 顶层:CEO(挂上 cto)
ceo_agent = create_deep_agent(model=llm, subagents=[cto_subagent], name="CEO",
                              system_prompt="你是CEO,严禁直接写代码,委派给 CTO。")

📌 实践建议 :虽然支持无限嵌套,但层级过深会难调试、延迟高,一般 2~3 层 足够。


八、HITL 人工审批(Human-in-the-Loop)

删库、删文件这种高危操作,得让人先点"同意"再执行。

8.1 配置 interrupt_on + 检查点

📄 源码:hitl/01_hitl_1.py

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver

main_agent = create_deep_agent(model=llm,
    tools=[delete_table, delete_file, query_table],
    system_prompt="回答使用中文,调用对应的工具实现对应的功能!",
    interrupt_on={
        "delete_table": True,                                   # 需要审批
        "query_table": False,                                  # 不需要审批
        "delete_file": {"allowed_decisions": ["approve", "reject"]}  # 只允许同意/拒绝
    },
    checkpointer=InMemorySaver())   # 必须!中断时保存状态,恢复时接着跑

8.2 两段式执行:先中断,人决策,再恢复

python 复制代码
config = {"configurable": {"thread_id": "user_session_123456"}}

# 第 1 次:触发中断,不会真执行
result = main_agent.invoke({"messages": [{"role": "human",
    "content": "先查询product表,再删除user表,最后删除zhaoweifeng.txt文件"}]}, config=config)

if result["__interrupt__"]:
    decisions = []
    for action in result["__interrupt__"][0].value["action_requests"]:
        if action["name"] == "delete_table":
            decisions.append({"type": "reject"})      # 拒绝删表
        elif action["name"] == "delete_file":
            decisions.append({"type": "approve"})     # 同意删文件
    # 第 2 次:带着决策 + 相同 thread_id 恢复
    result2 = main_agent.invoke(Command(resume={"decisions": decisions}), config=config)
    print(result2["messages"][-1].content)

⚠️ 注意hitl/01_hitl_1.py 只写了"第 1 次 invoke + 打印",没有 处理 __interrupt__Command(resume) 的恢复逻辑(是个半成品)。完整两段式写法见上面代码。


九、Backends 后端存储(虚拟文件系统)

Agent 调文件工具时,文件最终存哪?由 Backend 决定。只有显式写文件才会落盘,思考过程只留内存。

四种后端

后端 存哪 场景
StateBackend(默认) 内存 临时文件,会话结束即销毁
FilesystemBackend 本地硬盘 本地开发、想直接看生成文件
StoreBackend KV 数据库 生产、跨 Agent 共享记忆(Redis/Postgres)
CompositeBackend 混合 生产最佳实践:临时文件本地 + 重要记忆入库
python 复制代码
from deepagents.backends import FilesystemBackend, StoreBackend, CompositeBackend
from langgraph.store.memory import InMemoryStore
from pathlib import Path

workspace_dir = Path("./agent_workspace").resolve()
workspace_dir.mkdir(parents=True, exist_ok=True)
fs_backend = FilesystemBackend(root_dir=workspace_dir, virtual_mode=True)  # 限制在工作区内

# 重要记忆走 Store
store = InMemoryStore()
store_backend = StoreBackend(namespace=lambda rt: ("deepagents_test",))

# /store/ 前缀 → 数据库;其余 → 本地
composite = CompositeBackend(default=fs_backend, routes={"/store/": store_backend})

agent = create_deep_agent(model=llm, store=store, backend=composite, tools=[],
                          system_prompt="普通文件存本地;/store/ 下的文件存记忆库。")

📌 开启 virtual_mode=True 把 Agent 锁在工作目录,防止它乱读系统文件。


十、Permissions 文件权限控制

用声明式规则做路径级黑白名单,限制 Agent 的读/写。生效于内置文件工具(ls/read_file/write_file/edit_file...)。

python 复制代码
from deepagents import FilesystemPermission

agent = create_deep_agent(model=llm, backend=file_backend, permissions=[
    # 规则从上往下匹配,命中第一条即生效;具体路径写在前面
    FilesystemPermission(operations=["read", "write"], paths=["/allow_dir/**"], mode="allow"),
    FilesystemPermission(operations=["read", "write"], paths=["/**"],        mode="deny"),
])

十一、Agent Skills 技能

给 Agent 装"可插拔能力包",核心是一个 SKILL.md。启动只读元数据(轻量),任务匹配时才加载详细指令(渐进式披露,省上下文)。

python 复制代码
file_backend = FilesystemBackend(root_dir=Path(__file__).parent.resolve(), virtual_mode=True)

main_agent = create_deep_agent(model=llm, backend=file_backend,
                               skills=["skills"],   # 指向虚拟路径下的技能文件夹
                               system_prompt="你是一个智能助手,可以使用 SKILL 技能!")

标准目录:skills/code-reviewer/SKILL.mdSKILL.md 分 YAML 头(name/description/trigger)和 Markdown 正文(操作步骤)。


十二、避坑指南:我在真实项目里踩过的 5 个坑

下面是我在读这份项目代码时发现的真实不一致------初学者最容易在这里卡住,建议重点看。

坑 1:stream_parser_demo 导入了不存在的函数

📄 examples/stream_parser_demo.pyutils/json_stream_parser.py

stream_parser_demo.py 里写:

python 复制代码
from utils.json_stream_parser import process_stream, extract_text_content, extract_final_result

utils/json_stream_parser.py 里实际只定义了:

python 复制代码
parse_agent_chunk(...)          # 解析单个 chunk
process_streaming_response(...) # 处理整个流
extract_text_from_chunks(...)   # 抽取文本
extract_final_result(...)       # 抽取最终结果

直接运行会报 ImportError。 修正方法二选一:① 把 demo 的 import 改成 process_streaming_responseextract_text_from_chunks;② 或把工具模块里的函数改名成 demo 期望的名字。教训:跑之前先确认函数名对得上。

坑 2:HITL 示例是"半成品"

hitl/01_hitl_1.py 只有第一次 invoke 并打印,没写 __interrupt__ 判断和 Command(resume) 恢复。光看这个文件会以为"中断就结束了"。完整两段式写法见第八节。

坑 3:取结果用 .content 还是 ["content"]

result["messages"][-1]AIMessage 对象 ,取文本用 .content。但 01_subagent_4_anthropic.py 里有 if hasattr(result, "messages") 这种写法------result 本身是 dict,统一用 result["messages"][-1].content 最稳。

坑 4:response_format 不是万能的

qwen 在 thinking 模式下可能吐普通文本而非 JSON。务必像 01_subagent_5.py 那样加手动解析兜底,别裸用 response_format

坑 5:字典子代理不能嵌套

想在子代理里再挂子代理?字典形式的 "subagents" 字段不生效,必须走 CompiledSubAgent(详见第七节对比)。


十三、学习路线图 & 自测清单

学完上面 12 节,用下面这份清单自测(建议先默背,再回头翻):

  • create_deep_agent(model=, tools=, system_prompt=) 的四个核心参数能默写出吗?
  • invoke / stream / ainvoke / astream 分别返回什么?怎么取最终文本?
  • 子代理两种配置方式(字典 vs CompiledSubAgent)的区别与适用场景?
  • 为什么 description 对子代理分发至关重要?
  • response_format 怎么用?qwen 下为什么要兜底解析?
  • 嵌套子代理为什么必须用 CompiledSubAgent?
  • HITL 的 interrupt_on + checkpointer + Command(resume) 三段式?
  • 四种 Backend 各自存哪、什么时候用?
  • FilesystemPermission 的规则匹配顺序(具体在前、宽泛在后)?
  • Skills 的渐进式披露是什么意思?

推荐学习顺序:第二节 HelloWorld → 第三节流式异步 → 第四节四大能力(建立全局观)→ 第五节子代理 → 第七节嵌套 → 第八节 HITL → 第九~十一节存储/权限/技能 → 第十二节回头看踩坑。


结语

一句话总结 :DeepAgents = "会规划 + 会用文件柜 + 会招小弟(子代理)+ 记得住事"的智能体管家。写代码时记住:主代理管调度,子代理管专业,高危操作加 HITL,文件落地选对 Backend。

如果你刚入门多智能体,建议先把第二节到第五节跑通,再回头啃 HITL 和嵌套。踩坑那节最好在你动手改项目代码前先扫一遍,能省不少 debug 时间。

参考资料

  • 项目源码:quickstart / subagent / utils / hitl / examples
  • 《DeepAgent复习.pdf》《1.deepAgents开发笔记.pdf》

📌 转载请注明出处。如果这篇笔记对你有帮助,点赞 + 收藏 + 关注 三连就是对我最大的鼓励~有问题欢迎在评论区交流,一起把多智能体玩明白。

相关推荐
用户7783366132111 小时前
serpbase + Cloudflare R2 边缘持久化实战
前端·人工智能
东方小月1 小时前
从零开发一个 Coding Agent(三):EventStream 事件流通道设计与实现
前端·人工智能·后端
中微极客2 小时前
降维算法75倍加速:从PCA到稀疏字典学习的工程实践
人工智能·学习·算法
代码青铜2 小时前
三步给 Codex 接上一个真正的后端:无需写代码,让 AI 自动搭建完整应用
人工智能
文心快码BaiduComate3 小时前
从“提示词工程”到“技能工程”:Comate 创建Agent Skills 实战
人工智能
星栈3 小时前
MCP 从 stdio 迁到 SSE,踩了 5 个传输层坑
人工智能·后端·架构
林泽毅3 小时前
PyTRIO快速入门(二):Datum构建
人工智能·算法·产品
金斗潼关3 小时前
使用MLP神经网络模型预测质数
人工智能·深度学习·神经网络
吴佳浩3 小时前
一文讲透AI算力单位:TFLOPS、PFLOPS、TOPS、稀疏算力,到底怎么算、怎么比?
人工智能·ai编程·gpu
XS0301063 小时前
SpringMVC核心知识点实操笔记
笔记