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「开箱即用」的真正底气,都在这里。
- 智能规划与任务分解(write_todos) :框架内置
write_todos工具,Agent 会自动把复杂任务拆成待办清单并实时追踪 / 调整。你不用写代码,System Prompt 描述清楚目标即可。 - 高效上下文管理(文件系统工具) :内置
ls / read_file / write_file / edit_file。大文本先存文件、用时再读,避免把上下文窗口撑爆------相当于给 Agent 配了个"文件柜"。 - 子代理生成(task 工具):主代理把任务派给专业子代理,主代理只收最终结果,环境保持干净。详见下一节。
- 长期记忆:通过文件 / 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.py的extract_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.md,SKILL.md 分 YAML 头(name/description/trigger)和 Markdown 正文(操作步骤)。
十二、避坑指南:我在真实项目里踩过的 5 个坑
下面是我在读这份项目代码时发现的真实不一致------初学者最容易在这里卡住,建议重点看。
坑 1:stream_parser_demo 导入了不存在的函数
📄
examples/stream_parser_demo.py↔utils/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_response和extract_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》
📌 转载请注明出处。如果这篇笔记对你有帮助,点赞 + 收藏 + 关注 三连就是对我最大的鼓励~有问题欢迎在评论区交流,一起把多智能体玩明白。