智能体项目复盘 · deepagents 篇:1 主 3 子架构、HITL、RAGFlow 接入,以及 16 个真实踩坑

智能体项目复盘 · deepagents 篇:1 主 3 子架构、HITL、RAGFlow 接入,以及 16 个真实踩坑


💡 前面几篇把大模型的基础盘、RAG 调参、低代码平台和私有化部署都过了一遍,但那些内容有个共同上限:一个 Agent 干所有事。

这一篇讲的是另一条路------多智能体。课件是尚硅谷大模型智能体课程的 04-多智能体项目_deepagents,5 天时间从 create_deep_agent() 一行代码,一路做到"主智能体 + 3 个子智能体 + FastAPI + WebSocket + Vue 前端 + MySQL 查询 + RAGFlow 知识库"的完整深度搜索项目。

但真正值得写的不是它能跑起来,而是中间那 16 个坑:为什么并发必须用 ContextVar 而不是全局变量、为什么流式解析要先过滤中间件节点、为什么教案里把 RAGFlow 说成"字节开源"、为什么子代理套子代理这条路走不通。这些内容才是一个项目从 Demo 到能上线的距离。


1. 先把三层关系对齐:LangChain、LangGraph、DeepAgents

这是看这类课程最容易迷路的第一步:三个东西都叫 agent 框架,但它们不在同一层。

课件给的分层说法我认为是全篇最清晰的一句:

层 项目 角色 类比 管什么
Framework LangChain 提供"做动作"的积木 Spring 模型适配、Prompt 模板、工具、@tool
Runtime LangGraph 提供"跑流程"的引擎 SpringMVC 容器 状态图、节点、边、checkpointer、Store
Harness DeepAgents 负责组织与调度 SpringBoot Starter 规划、子代理、虚拟文件系统、长期记忆、HITL
text 复制代码
你的业务代码 --> DeepAgents(Harness) --> LangChain(Framework) + LangGraph(Runtime) --> 大模型 API

简单理解:LangChain 负责"会做事",LangGraph 负责"按流程做事",DeepAgents 负责"把复杂的事拆给一队人做"。

课件用的版本锁得很死,这套组合是配套演进的,跨版本很容易炸:

依赖 版本
deepagents 0.4.3
langchain 1.2.10
langgraph 1.0.9
langgraph-checkpoint 4.0.0
langgraph-prebuilt 1.0.8
langchain-openai 1.1.10
fastapi / uvicorn 0.129.2 / 0.41.0
ragflow-sdk 0.24.0
前端 Vue 3.5.24 + TS 5.9 + Vite(被 override 成 rolldown-vite@7.2.5)
Q:为什么不直接用 LangGraph 手搓一个?

因为 DeepAgents 帮你内置了四件你迟早要自己写的事。课件把它总结为四大核心能力:

text 复制代码
① write_todos     --> 规划与任务分解(配套 read/update/delete_todos)
② 文件系统工具集   --> ls / read_file / write_file / edit_file,用来把上下文卸载到文件,防溢出
③ task 工具        --> 生成子代理 + 上下文隔离
④ LangGraph Store --> 跨 thread 的长期记忆

其中 ② 是最容易被低估的一条。长任务真正的瓶颈不是模型不聪明,是上下文塞不下------把中间结果写进文件,只把路径留在对话里,是这套框架最实用的设计。


2. 多智能体:什么时候该上,什么时候不该上

课件在这一节写得非常克制,这很难得------大部分教程会直接开始教你建 5 个 agent。

先给对比:

维度 单体 Agent 多智能体
上下文 全部堆在一个窗口,注意力被稀释 每个子代理只看自己该看的,上下文隔离
提示词 越长越互相打架 每个角色一份短提示词
拆解方式 靠模型自己排序 分而治之
成本 线性 Token 指数级失控
可调试性 尚可 非确定性叠加,调试噩梦

课件因此给了两条很硬的规则:

三条入场铁律:① 问题极度开放;② 存在领域冲突(不同角色对同一问题的最优解不同);③ 需要多方向并行。

一句工程铁律:不建全链路追踪,绝不上线。

第二条我自己踩过,深表认同------多智能体的 bug 长得不像 bug,它长得像"模型今天心情不好"。

架构模式上,业界三条路也顺便理清了:

text 复制代码
层级式 Orchestrator-Workers:主代理派活、收结果   --> DeepAgents 走这条
网  络式 Network         :任意两两通信           --> AutoGen 走这条
混  合式                 :角色分工 + 局部协商     --> CrewAI 走这条
Q:所以 deepagents 到底是不是"多智能体框架"?

更准确的说法是:它是一个层级式(指挥官模式)的多智能体脚手架 。子代理在框架里被统一暴露成一个叫 task 的工具,主代理发出的调用长这样:

json 复制代码
{"name": "task", "args": {"subagent_type": "网络搜索助手", "description": "..."}}

也就是说,"调用子代理"这件事被降维成了"调用一个工具"。这是 DeepAgents 最重要的一个设计取舍:简单、可组合,但也正是这个设计决定了它的一个硬限制------见下一节。


3. 核心 API:一个 create_deep_agent 就够了

课件里 90% 的示例都从这一个函数出发。把它的参数认清,框架就通了。

python 复制代码
from deepagents import create_deep_agent

deep_agent = create_deep_agent(
    model=llm,
    tools=[internet_search],
    subagents=[],
    system_prompt="""你是一个专家级的研究员!...""",
)

实际用到的全部参数(来自课件的《DeepAgents 使用总结》):

参数 作用 备注
model 模型实例 init_chat_model(model=..., model_provider="openai", temperature=0.1)
system_prompt 人设与规则 项目里全部外置到 prompts.yml
tools 主代理自己的工具
subagents 子代理列表 见下
checkpointer 状态存储 HITL 必需,示例用 InMemorySaver()
interrupt_on 人工介入白名单
backend 文件/记忆的落点 缺省走 LangGraph state(虚拟 FS)
store 跨 thread 长期记忆
middleware 中间件 限流、自定义工具拦截
skills Agent Skills 目录 必须先配 FilesystemBackend

子代理两种写法:

python 复制代码
# 写法 1:字典(项目里三个子代理都是这个,每个只有 8 行)
weather_agent = {
    "name": "weather_helper",
    "description": "...",
    "system_prompt": "...",
    "tools": [],
}

# 写法 2:兼容层------把 LangGraph 图 / LangChain agent 包装进来
from deepagents import CompiledSubAgent
sub_agent = CompiledSubAgent(name="graph_agent", description="处理所有的业务逻辑!!", runnable=compile_graph)

写法 2 有个强制约束:图的 State 必须包含 messages,且带 add_messages reducer,否则消息合并直接报错。

Q:流式输出怎么解析?

这是课件标注"重点"的部分,也是最容易被劝退的部分。astream() 的 chunk 只有四种语义:

text 复制代码
A 模型决定调工具  -->  B 工具返回结果  -->  C 模型决定调子代理(name=='task')  -->  D 最终回复

而节点名只有 model 和 tools 两个 key。但要先加一道过滤,否则会中途崩掉:

python 复制代码
if not state or "messages" not in state:
    continue   # 否则会被 {'TodoListMiddleware.after_model': None} 之类崩掉

框架内置的 TodoListMiddleware、PatchToolCallsMiddleware 会往流里塞自己的节点,不认识的 key 一律跳过是多智能体流式解析的通用保命符。

Q:HITL(人工确认)怎么接?

三个硬条件,缺一个就静默失败:

条件 说明
必须配 checkpointer 状态要能挂起
必须传 thread_id 且二次调用线程 id 必须完全一致
二次 invoke 只能传 Command(resume={"decisions": [...]}) 不能再把 query 传进去

decisions 支持 approve / reject / edit(edit 要带 edited_action: {name, args}),并且数组顺序必须与被拦截的顺序一一对应 。课件注释很坦白:InMemorySaver 仅用于测试,生产建议换 Redis checkpointer。

Q:子代理能再套子代理吗?

不能。课件专门写了 CEO→CTO→Coder 的三层委派实验,结论是当前版本不支持子代理再生成子代理 ,字典式 subagent 硬写 subagents 键也没用(原注释:"没有这个属性,硬写的!dict 底层不识别")。多层级只能靠提示词强行约束。

这一条直接决定了架构上限:你能做的是两层(1 主 N 子),不是树。


4. 项目全貌:一个"深度搜索"产品的完整链路

前面是零件,这里是怎么装起来。项目定位:企业知识 + 业务数据库 + 联网搜索三源合一的深度研究报告助手。

text 复制代码
Vue 前端(5173)  --HTTP POST /api/task {query, thread_id}-->  FastAPI(8000)  --立即返回 started-->  前端
     │                                                              │
     └────────────── WS /ws/{thread_id} 长连接 ◄────────────────────┘
                                                                    │
                                          asyncio.create_task(run_deep_agent)
                                                                    │
                          主智能体(项目经理人设:只规划/委派/汇总,不自己查数据)
                            ├── task --> network_search_agent   (Tavily)
                            ├── task --> database_query_agent   (MySQL 三工具)
                            └── task --> knowledge_base_agent   (RAGFlow)
                                                                    │
                          monitor 单例 + contextvars 取当前 thread_id --> 定向 send_json 回对应 socket

接口层一共 5 个,设计得很朴素但都是必要的:

接口 方法 作用
/api/task POST 提交任务,立即返回 {status:"started", thread_id},内部 asyncio.create_task
/api/upload POST multipart 上传到 updated/session_{thread_id}/
/api/download?path= GET 下载产物,带路径穿越校验
/api/files?path= GET 递归列文件,按 mtime 倒序
/ws/{thread_id} WS 事件回传,收 ping 回 pong,前端断线 3 秒自动重连
Q:为什么"提交任务"和"拿结果"要分成两条通道?

因为一个深度搜索任务要跑几分钟。HTTP 请求只能立刻返回"已受理",过程事件(tool_start / assistant_call / task_result)靠 WebSocket 推。这里有个很值得记住的实现细节:

并发隔离只能靠 ContextVar。 全局变量会串台(A 会话的文件写进 B 会话的目录),而 threading.local 在 asyncio 单线程模型下完全失效------因为从头到尾就一个线程。

api/context.py 整个文件就是这段论证。同理,ConnectionManager.loop 要延迟到 @app.on_event("startup") 里才 set_loop():同 loop 用 create_task,跨线程/跨 loop 必须 asyncio.run_coroutine_threadsafe,否则报"协程在错误的循环中运行"。

数据侧:MySQL + RAGFlow
项 选型 细节
业务库 MySQL 8 pharma_db drugs 10 条 / inventory 每药 3 批次 / sales_records,外键 ON DELETE CASCADE
访问方式 裸 mysql.connector,不是 ORM list_sql_tables / get_table_data / execute_sql_query
返回格式 CSV 文本 首行列头 + 逗号分隔
RAG RAGFlow (ragflow-sdk 0.24.0) 检索与生成全在服务端完成,本项目不做切块、不做向量检索
Embedding text-embedding-v3@Tongyi-Qianwen 建库时显式指定
向量存储 Elasticsearch 8.11.3(可选 Infinity) 部署时需手工把 es 镜像标签改成这个版本号,否则触发长时间重拉

两个反直觉但很实用的决定:

一是 SQL 结果转 CSV 再喂给模型。 模型对 CSV 的字段对齐理解比 JSON 稳,尤其在列数多的时候------JSON 里字段名重复占 token,模型反而更容易把值和列对错位。

二是 RAG 完全外包。 整个项目里没有一行 chunk/embedding 代码,ragflow_tools.py 只有两个 @tool:get_assistant_list() 拼出"助手名称;功能介绍;关联知识库",create_ask_delete(chat_name, question) 走 create_session → session.ask(stream=True) → delete_sessions。Agent 只负责"选哪个助手 + 问什么问题"。

这带来一个直接后果,课件在 prompts.yml 里把它写成了硬规则:知识库名和 description 必须写准,因为主代理完全靠 description 决定调用哪个助手;同时子代理被要求"保留检索的全部信息,不需要进行概括性总结",防止子代理自作主张压缩上下文。


5. 16 个坑清单

这一节是我认为这份课件最值钱的地方,全部来自代码注释和运行报错,不是编的。

# 坑 后果 / 解法
1 流式解析没过滤中间件节点 {'TodoListMiddleware.after_model': None} 直接崩,加 continue 守卫
2 HITL 二次调用又传了 query 必须只传 Command(resume=...),且 thread_id 完全一致
3 字典 subagent 里写 subagents 键 底层不识别,静默失效,多层委派走不通
4 LangGraph 图的 State 少了 messages+add_messages 合并报错
5 在错误的 event loop 上跑 WS 协程 用 run_coroutine_threadsafe,loop 延迟到 startup 设置
6 用全局变量 / threading.local 存会话 id 串台 / 完全失效,只能用 ContextVar
7 模型自己编路径(/workspace、/mnt/data、/home/user) path_utils.py 一张 12 行场景表兜底清洗;Windows 无盘符要当相对路径;session_abc/session_abc/ 要拍平
8 os.getenv("tavily_api_key") 大小写不匹配 取到 None,day01 示例真有这个 bug,day04/05 才改对
9 rawflow/rag_config.py 的 .env 路径算错一层 分支永不命中,全靠 load_dotenv() 回退掩盖
10 execute_sql_query 无只读约束,表名用 f-string 拼 模型可执行任意 SQL。生产必须上只读账号 + 语句白名单
11 PDF 转换走 win32com + 本机装了 Word Linux/Mac 直接不可用;requirements 里的 xhtml2pdf/weasyprint 是没接线的备胎
12 前端 http://localhost:8000 与 http://127.0.0.1:8000 混用;/outputs/... 是死链(后端 app.mount 已注释) 真实下载走 /api/download
13 提示词自相矛盾:一处要求"必须把工作目录告诉子代理",另一处说"不需要把文件地址告诉子智能体" 模型行为不可预测;人设写成"空调公司"但数据是医药库,角色-数据错配直接影响子代理选择
14 read_file_content 的 instruction 参数收下后完全未用 典型"为提示词而写的假参数",装饰器签名与函数体脱节
15 SKILL 放太多 原注释:"不是越多越好,7-10,多了模型也不会调用(摆烂),千万不要放重复功能的 SKILL(选择困难)";且文件夹名必须等于 SKILL.md 的 name
16 限流中间件没配 ModelCallLimitMiddleware(thread_limit, run_limit, exit_behavior="error"/"end") + @wrap_tool_call 自定义中间件是控制 Token 失控的唯一实用手段

第 10 条值得单独强调:给 Agent 一个能执行任意 SQL 的工具,等价于把数据库读写权限交给了提示词注入。 这是所有 text-to-SQL 项目的通病,课件原样留着当教学,生产环境绝对不能照搬。


6. 顺手做个事实核查:教案里有几处是错的

这一节不在课程大纲里,是我核对时发现的。写出来是因为如果你照抄教案,会在面试或者文档里翻车。

教案说法 实际情况
"RAGFlow 是字节跳动开源的 RAG 框架" 不对。RAGFlow 是 InfiniFlow 项目的开源产品
把 Infinity 解释成"字节开源的高分辨率图像生成模型" 完全串了。在 RAGFlow 语境里,Infinity 是它自带的可选向量/文档引擎(与 Elasticsearch 二选一)
把 Valkey 说成"缓存、消息队列" Valkey 是 Redis 协议的分叉 KV 存储,在 RAGFlow 里做队列/缓存,但不是消息队列产品
模型名 qwen3-32b、qwen-max、qwen2.5-14b-instruct 混用 都是走 DashScope 的 OpenAI 兼容模式 https://dashscope.aliyuncs.com/compatible-mode/v1,model_provider="openai"

另外,课程代码的 .env 与教案 md 里是明文 API Key (DashScope、Tavily、RAGFlow 三套)。本文已全部脱敏成 ‹REDACTED-API-KEY›。这里顺带说一句:课程资料是会被反复下载和转发的,明文密钥写进 .env 示例就等于已经泄露了 ,真实项目里这类文件必须进 .gitignore,用 .env.example 占位。


最后总结

  • DeepAgents 的定位要认准 :它不是新的 Agent 框架,是 LangChain 之上、LangGraph 之上的 Harness 层。它的价值是"组织",不是"能力"。
  • 一句话理解 :模型负责思考,LangChain 负责做事,LangGraph 负责跑流程,DeepAgents 负责把活分给一队人。
  • 多智能体是成本换来的确定性:上下文隔离、职责单一,代价是 Token 指数级失控和非确定性调试。三条入场铁律(问题足够开放 / 存在领域冲突 / 需要并行)不满足就别上。
  • 架构上限是两层 :当前版本子代理不能再生成子代理。想搭"树",得自己在 task 工具之上重做。
  • 最实用的三个内置能力 :虚拟文件系统(上下文卸载)、interrupt_on(HITL)、CompositeBackend(短期 state + 长期 Store + 本地文件混用)。
  • 工程真正的难点不在 Agent,在周边 :event loop 归属、ContextVar 并发隔离、模型编路径的清洗表、WS 通道设计------这三样占了本项目一半以上的 bug。
  • 笔者(后端 & 架构)的判断是:这套代码可以直接当"多智能体入门 Demo"看,但不能当"生产模板"抄。 具体不抄三条:任意 SQL 工具、InMemorySaver、Windows-only 的 Word 转 PDF。真要上生产,先补的是全链路追踪和只读数据通道,而不是再加两个子代理。

下一篇讲一个完整的落地项目------掌柜智库,看 RAG 在真实业务系统里怎么切文档、怎么设计那 24 个 .prompt 文件。


参考资料 & 致谢

1 DeepAgents - GitHub

2 LangChain 官方文档

3 LangGraph 官方文档

4 RAGFlow - GitHub(InfiniFlow)

5 阿里云百炼 DashScope 兼容模式 - 官方文档

6 Tavily API - 官网

7 Vue 3 - 官网

8 FastAPI - 官网

9\] \[尚硅谷大模型智能体线上速成班 V2.0 · Day02《多智能体项目 deepagents》课件与源码

注:文中所有 API Key 已脱敏;版本号、端口、表结构等配置均来自课程交付代码,属 2026 年课程快照,实际依赖请以你本地 pip freeze 为准。

相关推荐
信息安全失业大专人员4 小时前
每日了解一个智能体-ClaudeCode
智能体·claudecode
一级新生7 小时前
LLM _ Agent _ Skill _ 专家 的关系与选型笔记
java·开发语言·人工智能·笔记·大模型·智能体·skill
信息安全失业大专人员1 天前
每日了解一个智能体-WorkBuddy
开发语言·ai·c#·智能体
虎鲸不是鱼1 天前
【虎鲸养殖手册 B007】命令执行的安全细节:白名单不是防弹衣
人工智能·安全·agent·智能体·自进化agent
小易老师AI实战1 天前
LangChain Deep Agents:新一代增强型通用智能体框架
langchain·大模型开发·deepagents·ai 智能体·agent 开发
郝学胜-神的一滴1 天前
AI 编程智能体 20:技术栈选型与架构落地全攻略
人工智能·python·架构·langchain·1024程序员节·智能体
AAwangzong2 天前
AI时代RPA技术解析:如何构建兼顾流程执行与智能决策的自动化平台?
人工智能·经验分享·rpa·智能体
小易老师AI实战2 天前
用中间件把任务规划嵌进智能体循环
中间件·状态管理·智能体·工具调用·langgraph·任务规划
随遇而安zx3 天前
Codex CLI 介绍和使用
ai·codex·智能体