用 LangChain 做的项目,你写过测试吗?
我做过三个 LangChain 项目,没一个有正经测试。不是不想写,是不知道怎么写。chain 是黑盒,中间状态看不到;LLM 输出不确定,同一个输入每次结果不一样;依赖外部 API,mock 成本比写业务代码还高。
结果就是:上线后每次改 prompt 都心惊胆战,每次升级依赖都怕 regression,每次用户报 bug 都只能手动复现。
直到用 DeepAgents 做到 Day 11 的综合项目,我决定在 Day 12 把这件事补上。发现 DeepAgents 的测试比 LangChain 好写很多——因为 Agent 的每一步都是可观测的 tool call,不是 chain 的黑盒中间态。
这是 Day 12 的笔记,也是整个 12 天系列的收尾。把测试、优化、踩坑汇总和学习路线回顾一次讲完。
阅读提示
- 适合谁看:用 LangChain/LangGraph 做过项目,但没有测试、不敢重构
- 看完能做什么:给 DeepAgents Agent 写单元测试和集成测试,知道优化从哪下手
- 带走什么判断:12 天学完后,什么场景该用 DeepAgents、什么场景 LangGraph 更合适
- 全文约 15 分钟
一、全局地图:Day 12 在哪一层
12 天计划走到终点:
- 第一阶段(Day 1-3):框架认知——是什么、怎么装、源码长什么样
- 第二阶段(Day 4-8):核心模块——Agent、Tool、Memory、Workflow、Multi-Agent
- 第三阶段(Day 9-10):工程化能力——容错与可观测性、RAG 集成
- Day 11:综合项目——把所有模块拼成 MVP
- Day 12(本篇):测试、优化、总结
图 1|12 天学习路线总览
Day 12 是收尾,但不是可选的。**没有测试的项目不是"完成了",是"还没出事"**。
二、为什么 LangChain 的测试这么难写
先说清楚痛点,不是为了吐槽,而是为了理解 DeepAgents 在测试这件事上做了什么不一样的事。
LangChain 测试的三大障碍(来自我自己的项目经验):
障碍 1:chain 是黑盒
chain = RetrievalQA.from_chain_type(llm=llm, retriever=retriever)result = chain.invoke("退货政策是什么")你只能看到result,看不到中间的 retrieval 结果、prompt 拼接、LLM 原始输出。要测中间态,要么加 callback,要么拆 chain,成本很高。
障碍 2:LLM 输出不确定
同一个输入跑 10 次,可能得到 10 个不同表述的回答。用assertEqual测不了,用assertIn又太松。
障碍 3:依赖链太长
从DocumentLoader到Embeddings到VectorStore到LLM,每个都是外部依赖。全 mock 工作量大,不 mock 又不稳定。
DeepAgents 在这三个问题上的设计选择,让测试变得可行:
- Agent 的每一步都是tool call,tool call 有明确的输入输出,可以直接断言
- Agent 的推理过程可以通过state完整观测
- Tool 是普通函数,mock 成本低
三、DeepAgents 的测试策略:三层结构
图 2|DeepAgents 知识图谱
我总结了一个三层测试结构,从内到外:
L1:Tool 单元测试(最便宜、最先写)
每个 Tool 就是一个普通函数,输入字符串、输出字符串。测试方式跟普通 Python 函数完全一样。
代码 1— Tool 单元测试
import pytestfrom unittest.mock import MagicMock, patch# 假设这是你的 RAG Tooldef search_docs(query: str, retriever) -> str: """搜索内部文档库。""" docs = retriever.invoke(query) if not docs: return "没有找到相关文档。" results = [] for i, doc in enumerate(docs): source = doc.metadata.get("source", "unknown") results.append(f"[{i+1}] ({source})\n{doc.page_content}") return "\n\n---\n\n".join(results)# 测试def test_search_docs_returns_results(): mock_retriever = MagicMock() mock_doc = MagicMock() mock_doc.page_content = "退货政策:7天无理由退货" mock_doc.metadata = {"source": "policy.md"} mock_retriever.invoke.return_value = [mock_doc] result = search_docs("退货", mock_retriever) assert "退货政策" in result assert "policy.md" in result mock_retriever.invoke.assert_called_once_with("退货")def test_search_docs_no_results(): mock_retriever = MagicMock() mock_retriever.invoke.return_value = [] result = search_docs("不存在的内容", mock_retriever) assert "没有找到相关文档" in result关键点:retriever 是注入的依赖,不是全局变量。这样 mock 成本极低——只需要一个MagicMock。
L2:Agent 集成测试(中等成本)
测 Agent 的推理链路:给一个输入,检查它是否调了正确的 tool、传了正确的参数。
代码 2— Agent 集成测试
from deepagents import create_deep_agentfrom unittest.mock import MagicMock, patchdef test_agent_calls_search_docs_for_knowledge_question(): # 准备 mock tool mock_search = MagicMock(return_value="[1] (policy.md)\n退货政策:7天无理由退货") mock_search.__doc__ = "搜索内部文档库" agent = create_deep_agent( model="openai:gpt-4o", tools=[mock_search], system_prompt="你是知识库助手,先检索再回答。", ) # 运行 result = agent.invoke( {"messages": [{"role": "user", "content": "退货政策是什么?"}]} ) # 断言:search_docs 被调用了 mock_search.assert_called() # 断言:最终回答包含关键信息 final_message = result["messages"][-1].content assert "退货" in final_message or "7天" in final_message注意:这里 mock 的是 tool 函数本身,不是 LLM。LLM 仍然用真实的——因为你要测的就是"LLM 是否正确地调用了 tool"。如果 mock LLM,就失去了集成测试的意义。
L3:端到端测试(最贵、最后写)
从用户输入到最终输出,全链路测试。包含真实的 LLM 调用、真实的向量检索。
代码 3— 端到端测试
@pytest.mark.e2edef test_e2e_rag_agent(): """端到端测试:需要真实 API key 和向量索引。""" # 使用真实的组件 from langchain_community.vectorstores import FAISS from langchain_openai import OpenAIEmbeddings embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = FAISS.load_local("./test_index", embeddings, allow_dangerous_deserialization=True) retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) def search_docs(query: str) -> str: docs = retriever.invoke(query) return "\n".join(d.page_content for d in docs) agent = create_deep_agent( model="openai:gpt-4o", tools=[search_docs], system_prompt="你是知识库助手。先检索再回答。", ) result = agent.invoke( {"messages": [{"role": "user", "content": "如何配置 CI/CD?"}]} ) # 断言:回答不是空的,且包含技术内容 answer = result["messages"][-1].content assert len(answer) > 50 assert any(kw in answer.lower() for kw in ["ci", "cd", "pipeline", "部署", "自动化"])关键点:用@pytest.mark.e2e标记,CI 里可以单独跑或跳过。端到端测试不适合每次 commit 都跑,适合 nightly 或发版前跑。
四、性能优化:从哪下手
Agent 的性能瓶颈通常不在单个 tool 的执行速度,而在LLM 调用次数和上下文长度。
优化 1:减少不必要的 LLM 调用
Agent 的 ReAct 循环每一步都调一次 LLM。如果你的任务是确定性流程(不需要 LLM 判断),用Workflow(Day 7)替代 Agent。
| 场景 | 用 Agent | 用 Workflow |
|---|---|---|
| 用户意图不确定 | ✅ | ❌ |
| 执行路径固定 | ❌ | ✅ |
| 需要动态决策 | ✅ | ❌ |
| 纯顺序执行 | ❌ | ✅ |
经验判断:如果你的 prompt 里写了"如果……则……否则……",大概率应该用 Workflow 的条件路由,而不是让 Agent 每次都推理一遍。
优化 2:控制上下文长度
Agent 的每轮对话都会把历史消息发给 LLM。对话轮数越多,token 消耗越大,延迟越高。
# 不好:每轮都带全部历史agent = create_deep_agent(model="openai:gpt-4o", tools=[...])# 好:限制历史窗口agent = create_deep_agent( model="openai:gpt-4o", tools=[...], max_history_messages=20, # 只保留最近 20 条)优化 3:Tool 结果精简
RAG Tool 返回的文档片段不要太多。top_k=4通常够了,top_k=10会浪费大量 token。
# 不好:返回太多retriever = vectorstore.as_retriever(search_kwargs={"k": 10})# 好:精简返回retriever = vectorstore.as_retriever(search_kwargs={"k": 3})# 更好:只返回关键字段def search_docs(query: str) -> str: docs = retriever.invoke(query) # 只返回内容,不返回 metadata,节省 token return "\n---\n".join(d.page_content[:500] for d in docs)优化 4:并行 Tool 调用
如果 Agent 需要调用多个独立的 tool,可以让它们并行执行。DeepAgents 支持 tool 的并行调用(前提是 tool 之间没有依赖)。
五、踩坑汇总:12 天踩过的所有坑
我把整个学习过程中踩过的坑按模块整理,方便你按模块排查。
Agent 模块(Day 4)
| 坑 | 现象 | 解法 |
|---|---|---|
| Agent 不调工具 | 直接用 LLM 知识回答 | system_prompt 明确要求"必须使用工具" |
| Agent 死循环 | 反复调同一个 tool 不停 | 检查 tool 返回值是否让 Agent 认为"任务未完成" |
| Agent 返回格式不对 | 想要 JSON 返回纯文本 | 用response_format参数约束输出格式 |
Tool 模块(Day 5)
| 坑 | 现象 | 解法 |
|---|---|---|
| Tool 描述不准确 | LLM 选错 tool | 优化 tool 的 docstring,写清楚"什么时候用" |
| Tool 参数校验缺失 | LLM 传了错误类型参数 | 用 Pydantic 做参数 schema,或者在函数里加类型检查 |
| Tool 报错吞掉了 | Agent 静默失败 | 在 tool 里显式 raise,不要 return “错误” 字符串 |
Memory 模块(Day 6)
| 坑 | 现象 | 解法 |
|---|---|---|
| 多用户串记忆 | A 的偏好被 B 看到 | namespace 用lambda rt: (user_id,) |
| 记忆没生效 | Agent 不知道用户偏好 | 检查memory=参数和 backend route 路径前缀一致 |
| InMemoryStore 重启丢失 | 测试通过,生产丢了 | 生产用PostgresStore |
Workflow 模块(Day 7)
| 坑 | 现象 | 解法 |
|---|---|---|
| 条件路由死循环 | 循环判断不停 | 检查条件边的终止条件 |
| 并行节点结果丢失 | 只拿到一个分支的结果 | 检查 state reducer 是否正确合并 |
Multi-Agent 模块(Day 8)
| 坑 | 现象 | 解法 |
|---|---|---|
| Orchestrator 自己全干了 | 不调度给 Worker | prompt 强调"你只负责调度" |
| Worker 之间上下文丢失 | 后一个 Agent 没收到前一个的结果 | prompt 要求把前序结果放进 context |
| SubGraph 思维残留 | 想给 Worker 加 add_edge | 放下画图思维,信任 LLM 调度 |
RAG 模块(Day 10)
| 坑 | 现象 | 解法 |
|---|---|---|
| 表格被切碎 | 检索到半张表 | 用MarkdownHeaderTextSplitter按标题切分 |
| 检索结果重复 | top_k 个 chunk 都来自同一篇文档 | 用 MMR 检索策略 |
| Embedding 不一致 | 检索精度断崖下降 | 建索引和查询用同一个模型 |
| Agent 每次都检索 | 闲聊也调 search_docs | prompt 区分"需要检索"和"不需要检索" |
六、12 天学习路线回顾
12 天走下来,我们经历了四个阶段:
第一阶段:框架认知(Day 1-3)
- Day 1:搞清楚 DeepAgents 是什么、解决什么问题、跟 LangChain/LangGraph 的关系
- Day 2:环境搭建、跑通 QuickStart
- Day 3:源码结构、模块划分
第二阶段:核心模块(Day 4-8)
- Day 4:Agent 定义——
create_deep_agent背后就是 LangGraph 的 StateGraph - Day 5:Tool 注册——函数即工具,docstring 即描述
- Day 6:Memory 管理——文件即记忆,backend 路由
- Day 7:Workflow 编排——条件路由、并行执行
- Day 8:Multi-Agent 协作——Orchestrator + Workers,消息传递
第三阶段:工程化能力(Day 9-10)
- Day 9:容错与可观测性——错误处理、重试、Tracing
- Day 10:RAG 集成——RAG-as-Tool,文档问答
第四阶段:实战综合(Day 11-12)
- Day 11:综合项目——把所有模块拼成 MVP
- Day 12:测试、优化、总结
一句话总结这 12 天的核心收获
DeepAgents 不是 LangChain 的替代品,是 LangGraph 的上层封装。 它解决的不是"能不能做"的问题,是"能不能少写样板代码、能不能更好调试"的问题。
七、DeepAgents vs LangChain/LangGraph:迁移决策框架
学完 12 天,该不该从 LangChain/LangGraph 迁移到 DeepAgents?我做了一个决策框架:
适合迁移的场景
- 你的项目大量使用 ReAct Agent 模式(Day 4 的样板代码省得最多)
- 你需要 Agent 自主管理记忆(Day 6 的 filesystem memory 是独有优势)
- 你的调试成本很高(tool call 日志比 chain 黑盒好调试太多)
- 你要做多 Agent 协作(sub_agents 比 SubGraph 嵌套简单)
不适合迁移的场景
- 你的项目主要是 RAG chain,不需要 Agent → LangChain 的 RetrievalQA 更简单直接
- 你需要精细控制图结构(复杂的条件路由、循环、分支)→ LangGraph 显式图更灵活
- 你的团队已经深度使用 LangChain 生态(大量自定义 callback、集成)→ 迁移成本高
- 你用的不是 OpenAI/Anthropic 模型 → DeepAgents 的模型支持范围可能不如 LangChain 广
如果只做一件事
先用 DeepAgents 重写你最简单的一个 Agent 场景(比如一个 tool 的 ReAct Agent),对比一下代码量和调试体验。如果觉得好,再逐步迁移其他模块。
八、给读者的下一步建议
决策帮助
- 如果你是刚入门:按 12 天计划再走一遍,每天 1.5-2 小时,两周能走完
- 如果你已经在用 LangChain:先迁移一个最简单的 Agent 场景,对比代码量和调试体验
- 如果你在做生产系统:重点关注测试(本篇的三层结构)和可观测性(Day 9)
- 如果你在做技术选型:DeepAgents 适合 Agent-heavy 项目,LangGraph 适合 Graph-heavy 项目
九、系列总结
12 天前,我从 LangChain 的样板代码和 chain 黑盒里跳出来,开始看 DeepAgents。
12 天后,我的判断是:
DeepAgents 最大的价值不是"新功能",而是"减少样板代码 + 提升可观测性"。 它把 LangGraph 的图编程模型包装成了"Agent 即函数"的接口,降低了心智负担,但没有牺牲灵活性。
它不是万能的。复杂的图结构、精细的流程控制、非主流模型支持,这些场景 LangGraph 仍然更合适。
但对于大多数 Agent 项目——尤其是"Agent + 几个 Tool + 一点 Memory + 一个 RAG"这种最常见的组合——DeepAgents 的开发体验确实好很多。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~