开场:第二课,我们开始动手
如果你已经看完了第一课,脑子里对AI Agent应该有了一个基本画面——它不是一个单跑的模型,而是一个能自己规划、调用工具、迭代执行、最终交付结果的“数字员工”。但第一课看完,大概率你还是会有一个困惑:原理我都懂了,ReAct循环也看过图解了,可真让我从零搭一个能用的Agent,我还是不知道第一步该干什么。
这一课就把这个问题解决掉。
我会从四个层面往下拆:先讲清楚Agent在运行时的核心机制——这一层决定你写的Agent是“能演示”还是“能干活”;再做一次框架选型,把LangGraph、Spring AI这些主流方案的底层差异摊开看;接着聊MCP这个绕不过去的工具标准化协议;最后用一个真实的知识库问答Agent案例,把整个过程串一遍。这套内容对应到面试官的提问逻辑里,基本就是“你做过Agent吗”和“你做的是玩具还是生产级Agent”的区别。
适合的读者:已经看完第一课、对提示词工程和大模型基本调用有概念的人。如果你连Agent是什么都不知道,建议回到第一课补基础。
1. Agent运行机制深度拆解:决定你写的是玩具还是工具
很多人写Agent,第一步就是去调LangChain或LangGraph的封装API,跑通了一个demo就觉得自己会了。但一旦遇到真实业务需求,立刻翻车——要么工具调用老出错,要么多轮迭代下来上下文乱成一团,要么Agent自己绕进死循环出不来。这些问题,表面上看是代码问题,根子上是对运行机制理解不到位。
1.1 从ReAct到真正的循环控制
第一课讲过,ReAct的核心是“思考-行动-观察”的循环。但第一课限于篇幅,没有展开一个关键问题:这个循环在生产环境里到底是怎么被控制的?
标准ReAct循环在代码层面的本质,其实是一个while循环:
- 模型根据当前对话上下文生成下一步动作(调用工具 or 直接回答)。
- 如果是工具调用,解析出工具名和参数,执行工具。
- 把工具返回结果拼接到上下文中,回到第1步。
- 如果模型判断任务完成(输出最终答案),循环终止。
这个逻辑看起来简单,但实际开发中你会遇到一个很要命的问题:模型判断“任务完成”的时机对不对?我见过太多Agent在工具返回一个错误结果时,直接说“好的,任务已完成”然后把错误结果交给用户。原因就是那套提示词里只写了“完成任务后输出答案”,没有定义“什么叫任务真正完成”。
生产级Agent必须在提示词里显式约定循环终止条件。我自己的做法是在系统提示词里写死两条规则:一是只有当所有子任务都被标记为“成功”或“无需执行”时才允许输出最终答案;二是如果某个工具调用连续失败两次,必须切换策略或请求用户输入,禁止第三次重试同一个操作。这两条规则,在demo里看不出来差别,但放到可靠运行场景里,就是天壤之别。
1.2 记忆系统:Agent的“临时手写板”和“长期笔记本”
另一个被严重低估的是记忆机制。Agent开发里记忆分两种:短期工作记忆和长期存储。
短期工作记忆,就是当前对话的上下文窗口。工具返回的数据、中间推理过程,都堆在这里。这个区域的容量是有限的——上下文窗口就这么大,塞满了就溢出。我在开发中经常看到新手犯的错:每轮工具调用都把完整返回结果塞进上下文,五轮之后上下文就爆了。
处理办法是“摘要压缩”。当上下文接近阈值时,用一个轻量模型把前面的对话浓缩成摘要,替代原始消息。这个技术在AutoGPT、BabyAGI那批早期项目里就已经是标配了,但很多人做Agent时完全没有这个意识。
长期记忆则要靠外部存储,通常是向量数据库。它解决的问题是:Agent下次接到类似任务时,能不能回忆起上次的执行经验。这块我放到后面的知识库案例里一起讲,因为单独聊太抽象,配合具体场景比较容易理解。
1.3 规划能力:线性执行与动态规划的取舍
ReAct框架天然是线性的——走一步看一步,每一步都重新推理。这种方式的优点是灵活性高,即时反馈;缺点也很明显:复杂任务可能要走十几步,每一步都在消耗token,而且一旦中途走偏,纠偏成本极高。
生产环境里,我更喜欢Plan-and-Execute模式:先让Agent基于任务目标生成一份执行计划,再逐个执行计划中的步骤。这种模式把“规划”和“执行”解耦,好处有两个——一是规划阶段可以一次性把任务拆透,执行阶段每步都很聚焦;二是如果某个步骤失败了,Agent可以只重试那一步,不用推倒重来。
我曾经用一个多步骤数据处理任务做过对比:线性ReAct模式跑了17轮,其中有5轮是在无关的推理上打转;Plan-and-Execute模式规划阶段用了2轮生成计划,后面8轮执行完所有步骤,总共10轮搞定。省下来的不只是token费用,更是时间和出错的概率。
| 维度 | 线性ReAct | Plan-and-Execute |
|---|---|---|
| 执行方式 | 边想边做 | 先规划后执行 |
| 灵活性 | 高,可随时调整 | 中,计划先行但可修改 |
| 任务复杂度 | 适合简单任务 | 适合多步骤复杂任务 |
| Token消耗 | 通常更高 | 更可控 |
| 典型应用 | 工具调用、问答 | 业务流程、数据处理 |
真实项目里,两种模式不是二选一。LangGraph里完全可以用规划节点来做顶层拆分,每个子任务内部再走ReAct循环。这种“大规划+小循环”的组合,是我个人最推荐的Agent架构基线。
2. 框架选型:LangGraph、Spring AI与多Agent体系的对比
聊完原理就该动真格的了。市面上Agent开发框架五花八门,新手最容易被“哪个火选哪个”带偏。我先给出我的选型结论,再展开分析:单Agent场景,LangGraph是当前最合适的选择;Java技术栈团队,重点看Spring AI;复杂业务需要多角色协作的,可以研究AutoGen或LangGraph的多Agent子图能力。
2.1 主流框架横向对比
| 框架 | 语言 | 核心优势 | 适用场景 | 学习曲线 |
|---|---|---|---|---|
| LangGraph | Python/JS | 图状态机精确控制流程,可生产化 | 复杂工作流、有状态Agent | 中高 |
| LangChain | Python/JS | 生态成熟,组件丰富 | 快速原型验证 | 低 |
| AutoGen | Python | 多Agent对话编排能力强 | 多角色协作、群体讨论 | 中 |
| Spring AI | Java | 与Spring生态无缝集成,企业级 | Java技术栈的AI应用 | 中 |
| Semantic Kernel | C#/Python/Java | 微软系,企业级支持好 | 微软生态用户 | 中 |
这个表格只代表框架的“方向性差异”。实际选型时要考虑的不只是功能,还有团队的存量技术栈。很多企业级项目里,最合适的不是“功能最强的框架”,而是“离现有系统最近的框架”。
2.2 LangGraph为什么值得优先学
LangGraph的核心思想是把Agent的运行流程定义成一张图。图的节点是处理步骤(可以是模型调用、工具执行、条件判断),边是转换逻辑,状态是贯穿整个图的共享数据。这个设计的好处是:流程的每个环节都可视化、可控、可调试。
我自己学LangGraph的路径,是从三个核心概念入手的,也建议你按照这个顺序来:
State(状态):整个图的“共享内存”。所有节点都能读写State,节点之间的数据传递就靠它。定义State时要提前想清楚哪些字段需要跨节点传递,哪些字段用完即弃。
Node(节点):图里的基本执行单元。一个节点可以是一个Python函数,负责完成一件事,比如“调用OpenAI API生成回复”或“执行数据库查询”。
Edge(边):节点之间的连接关系。除了普通的前后顺序,LangGraph还支持条件边——根据State里某个字段的值,决定下一步走哪个节点。
举个最直观的例子:如果我要写一个“先检索知识库,再生成回答”的Agent,用LangGraph就是三个节点——一个负责把用户问题向量化并检索,一个负责组装提示词调用模型,一个负责格式化输出。节点连接以后,运行过程完全透明,哪一步出了问题,直接看图上状态就知道。
LangGraph还有一个杀手级功能:状态检查点。它能把Agent每一步执行后的State快照存下来(支持存到SQLite或PostgreSQL),一旦中途崩溃,可以从最近一个快照恢复继续跑。这个能力在生产环境有多重要,做过的都懂——不是“锦上添花”,是“没有它就别谈上线”。
2.3 Multi-Agent:更高级但更复杂的架构
热词里反复出现“multi agent”,我也简单展开一下。多Agent不是“用多个Agent分别干活”那么简单,核心挑战在Agent之间的通信协议和任务仲裁机制。
比如Spring AI里的Multi-Agent支持,核心思路是通过一个编排层统一调度多个专职Agent(一个负责写代码,一个负责查文档,一个负责测试)。编排层要处理的核心问题包括:任务该分配给谁、各Agent的输出怎么汇总、Agent之间意见冲突时听谁的。
多Agent架构能显著提升单一任务的上限,但同时也会引入额外的工作量:每个Agent的上下文是独立的还共享的?工具访问权限怎么隔离?全链路怎么追踪?我的建议是:刚学Agent,先老老实实把单Agent做到极致,再碰多Agent。很多人一上来就盲目追multi-agent,结果连单Agent的工具调用稳定性都没解决,多Agent只会把问题放大。
3. MCP协议:让Agent长出手脚的关键一环
Agent如果只能对话,那它只是一个聊天机器人。Agent之所以是Agent,在于它能调用外部工具——查数据库、调API、操作浏览器。而工具接入方式的设计,经历了一个明显的演进,这是今天我特别想讲清楚的一笔账。
3.1 为什么需要MCP而不是Function Calling
最初大家都是用Function Calling。开发者把工具定义用JSON Schema描述出来,塞给模型,模型根据用户意图选择调用哪个函数。这套方案的痛点在于:每个工具都要为每个Agent单独做适配。你给Agent A接了一个搜索工具,下次Agent B要用,还得重新接一遍。接入方和被接入方完全耦合,改一个字段,两边都要动。
MCP(Model Context Protocol)就是为了解决工具接入的标准化问题而生的。你可以把MCP理解成工具界的USB-C接口——所有工具都实现一套统一协议,Agent通过同一个协议去发现、调用所有工具,不需要为每个工具单独写对接逻辑。行业里把MCP称为“AI Agent的硬件接口标准”,这个比喻我觉得挺贴切。
3.2 MCP架构拆解与实战示例
MCP的架构分三层:
- MCP Server:工具的实际提供方。把现有能力(比如数据库查询、搜索、文件操作)包装成一个MCP Server,暴露标准化的工具接口。
- MCP Client:Agent侧的连接器。负责与MCP Server建立通信,把Server提供的工具列表拉取给模型。
- 传输协议:默认基于JSON-RPC 2.0,支持stdio和SSE两种传输方式。
举个例子,假设我要让Agent能查本地SQLite数据库。不用MCP的话,我得写一个query_database函数,格式化参数列表,写进系统的提示词里。用MCP的话,我可以用一个现成的SQLite MCP Server:
# server.py - 基于官方mcp库定义SQLite查询工具 from mcp.server import Server from mcp.server.stdio import stdio_server import sqlite3, json app = Server("sqlite-server") @app.tool() async def query_sqlite(query: str, db_path: str) -> str: """在指定SQLite数据库上执行查询,返回带格式的结果。""" conn = sqlite3.connect(db_path) try: cur = conn.cursor() cur.execute(query) cols = [d[0] for d in cur.description] rows = cur.fetchall() return json.dumps({"columns": cols, "rows": rows}, ensure_ascii=False, default=str) except Exception as e: return f"查询失败: {e}" finally: conn.close() if __name__ == "__main__": import asyncio asyncio.run(stdio_server(app))Agent侧接入时,只需要在LangGraph的配置里告诉它“这个Agent挂了一个SQLite工具”,剩下的动作——发现工具、生成调用请求、解析返回值——全走MCP标准协议,不用写一行定制对接代码。
3.3 一处制作、处处复用的工程意义
对个人开发者来说,MCP初期可能感觉有点“多此一举”。但对团队和企业,收益极其明显。我参与的一个项目里,团队把内部业务系统封了十几个MCP Server(订单查询、库存盘点、物流追踪),然后所有Agent统一接入这十几个Server。新Agent上线当天就能用所有现有工具,不用重新做对接。
MCP里还有一个“Agent网关”的用法值得提一句:把企业内部的所有MCP Server统一在一个网关注册,Agent通过网关统一鉴权、统一路由。这样既解决了工具标准化的问题,又解决了工具访问权限控制的问题。这块现在各家云厂商都有产品在做,大家在选型时可以多关注一下。
4. 生产级执行全流程:从任务输入到成果交付的完整链路
很多教程讲到框架和工具就停了,好像写完一个能跑的Agent就是终点。但真正从“能跑”到“能交付”,中间隔着一条很长的路。行业内有人总结过一套方法论:三阶段、六泳道、三十个核心节点,我觉得这个框架很能说明问题,这里给你完整拆一遍。
4.1 三阶段:规划-执行-交付
| 阶段 | 核心目标 | 关键产出 |
|---|---|---|
| 规划阶段 | 理解任务、制定方案 | 任务拆解清单、执行计划、所需资源列表 |
| 执行阶段 | 按计划完成任务 | 中间产物、工具调用记录、结果数据 |
| 交付阶段 | 验证结果、输出最终成果 | 格式化答案、结果验证报告、后续行动建议 |
三个阶段不是简单的时间顺序,而是每阶段都有质量门禁。阶段之间过不去门槛,就回退到前一个阶段重新处理。
4.2 六泳道:贯穿全程的关注维度
三阶段是把时间轴分成了三段,六泳道则是从横向维度去看整个执行过程中需要并行关注的事情:
- 需求理解泳道:持续校准“用户到底要什么”,防止做偏。
- 任务规划泳道:负责把大目标拆解成可执行的小任务,并安排顺序。
- 工具调度泳道:负责选择、调用、切换工具,管理调用过程中的异常。
- 上下文管理泳道:控制上下文的增长、摘要、记忆存取。
- 推理决策泳道:负责每一步的模型推理和质量判断。
- 结果验证泳道:验证工具返回结果是否符合预期,存在疑问时及时修正。
这六个通道,几乎覆盖了我在开发Agent过程中踩过的所有坑。比如上下文管理,学习阶段基本没人会想这一层,但真到生产环境,我自己就遇到过上下文继续膨胀、费用突然飙升、以及运行速度下降好几个级别的现象。加了上下文摘要机制之后,才算稳定下来。
那“30个核心节点”是什么?其实就是把三阶段六泳道进一步细化成30个具体动作,比如“接收用户输入并解析目标”“制定执行方案”“初始化长期记忆”“选择首个工具”“执行工具调用”“验证工具返回数据”“判断是否需切换策略”“生成最终答案”“主动提出后续建议”等等。不需要把这30个节点背下来,重点是理解一个道理:生产级Agent的执行不是“模型自由发挥”,而是“有流程、有检查点、有兜底方案”的工程化过程。
4.3 工程启示:从“模型逻辑”到“产品逻辑”
整套三阶段六泳道的方法论,本质上是在把Agent从“模型驱动”转成“流程驱动”。模型仍然负责推理和决策,但不再负责整个执行过程的“自由落体”。每一阶段有目标,每一泳道有规则,整个系统才是可控的。
这个思路在面试里也特别好用。当面试官问你“如何保证Agent的输出质量”,你回答“我通过结果验证泳道,对工具返回数据做格式和逻辑的双重校验,不通过则触发修正流程”,和回答“我让模型自己判断对错”,说服力完全是两个级别。
5. 实操演示:用LangGraph+MCP搭建一个知识库问答Agent
理论部分差不多了,我给一个我自己做过的具体项目:一个基于私有知识库的问答Agent。这类Agent是目前企业里用得最多、也最适合用来练手的场景。它既涉及RAG(检索增强生成),又涉及工具调用,逻辑链条完整,而且每个环节都能独立调优。
5.1 需求与架构设计
需求很简单:用户向Agent提问,Agent从企业知识库(一堆PDF/Word文档)中检索相关内容,基于检索结果回答。不能胡编,必须引出处。
基于这个需求,架构分成四个模块:
- 文档处理模块:把文档切块、向量化,写入向量数据库。
- 检索模块:用户提问后,从向量数据库检索top-k相关片段。
- 模型问答模块:把问题+检索结果拼进提示词,调用大模型生成回答。
- 质量控制模块:判断检索片段是否真的与问题相关,不相关则换一种检索策略重试。
5.2 核心代码实现(LangGraph版本)
这是我的LangGraph实现,节点定义就是上面说的四个模块:
from langgraph.graph import StateGraph, END from typing import TypedDict, List class AgentState(TypedDict): question: str context: List[str] answer: str def retrieve_node(state: AgentState): """检索节点:从向量库拉取相关片段""" docs = vector_db.search(state["question"], top_k=5) # 问题:top_k检索经常混入不相关内容,通过重排序模型过滤掉 reranked = reranker.rerank(state["question"], docs, top_n=3) return {"context": reranked} def qa_node(state: AgentState): """生成节点:基于检索结果生成答案""" ctx = "\n\n".join(state["context"]) answer = llm.invoke(f"""基于以下资料回答问题。资料中未提及的信息,明确回答"资料中未找到相关内容"。 资料:{ctx} 问题:{state["question"]}""") return {"answer": answer} def guard_node(state: AgentState): """质量门禁:检查检索片段和回答的关联度""" if any(len(c) < 50 for c in state["context"]): return {"context": state["context"], "answer": "检索不足,请换一种问法"} return state g = StateGraph(AgentState) g.add_node("retrieve", retrieve_node) g.add_node("qa", qa_node) g.add_node("guard", guard_node) g.add_edge("retrieve", "qa") g.add_edge("qa", "guard") g.add_edge("guard", END) app = g.compile()这段代码跑起来不难,但真正要优化到“能上线”的水平,我踩过几个大坑,这里一起说清楚。
5.3 关键参数选择与调优心得
第一是检索的top_k设置。我一开始用top_k=5,结果回答质量不稳定。后来加了重排序模型(reranker),先把候选从10篇里筛出来,再精排到3篇。这个步骤让回答准确率提升非常明显,而且token消耗反而降了。强烈建议凡是做RAG的Agent,都加一层重排序。
第二是回答的幻觉控制。模型经常会把资料里没有的内容“推理”进去,表现得一本正经。我的缓解办法是在提示词里强制要求“资料中未提及的信息,明确说明未找到”,并且让模型在回答末尾列出引用了哪几篇文档。加了这两条之后,实际测试中幻觉类问题大幅减少,虽然不能完全消除,但已经达到了业务可接受的程度。对这个领域的从业者来说,有一个基本认知特别重要:幻觉不是靠提示词能根除的,只能通过反复验证和系统设计来逼近零幻觉,这才是Agent落地的正确心态。
第三是切片策略。我一开始每500字切一片,但很多知识条目恰好被拦腰截断,导致检索到的内容不完整。后来改成“按段落边界切,再结合语义合并”,并且上下各保留50字作为重叠区,检索质量好了一截。
第四是模型选择。知识库问答其实不一定要用最强的模型。一个中等参数规模的模型,配合好的检索结果和提示词,效果已经足够好,token成本却低了一半还多。给生产环境选模型时,先跑一套评测集,用“答案相关性+引用正确率”这两个指标量化对比,别凭感觉拍板。
5.4 接入MCP工具后的能力跃迁
基础版本跑通后,我给这个Agent加了一个MCP工具:一个可以查询资料更新时间的内部系统。
之后Agent的行为逻辑多了一条分支:用户问“XX政策现在还是不是最新版”时,Agent先去MCP工具查询资料的“最后更新时间”,再决定直接用知识库回答,还是告诉用户“现有资料可能已过期,请确认后再使用”。
这个改动让Agent的业务价值明显上升。原因在于,纯粹的知识库是一个静态快照,但业务场景里的信息是动态的。Agent能主动调用外部系统验证信息时效,才算真正从“资料查询机”进化成了“能辅助判断的工作助手”。这类“Agent主动发起工具调用”的场景,建议你在做项目时多设计几个。
6. 常见问题与排查技巧实录
这部分是我自己实践过程中踩过的坑,很多是翻官方文档翻不到的,直接整理成速查清单,你遇到类似问题时可以对照着排查。
| 问题现象 | 根因分析 | 排查思路与解决方式 |
|---|---|---|
| Agent频繁调用同一个工具3-5次,且参数不变 | 模型陷入了“重复尝试”循环 | 在系统提示词中约定“同一工具相同参数最多执行2次,仍失败必须切换策略”;也可以设置调用频率限制 |
| 工具调用返回的参数总是解析失败 | 工具返回的JSON结构不稳定 | 不要直接让模型解析,自己写解析逻辑,前后加文字时用正则提取JSON,再做异常兜底 |
| 多轮对话后上下文爆满 | 所有历史消息都保留在上下文里 | 加“摘要节点”,接近阈值后用轻量模型压缩;长对话只保留摘要+最近两轮原文 |
| Agent回答时引用不存在的内容 | 检索相关度不够/提示词约束不严 | 加入重排序模型;提示词强制“未检索到必须说明”;添加引用源字段,让输出可追溯 |
| 某个工具的调用老是超时 | 工具在MCP Server中初始化开销大 | 对MCP Server做连接池复用;把高频工具的连接改为全局常驻,避免每次从头建连 |
| 多个Agent实例共享同一工具时互相干扰 | 工具状态/配置被多实例共享 | 将工具的上文无关性设计成纯函数式调用,杜绝全局变量;涉及状态的操作加实例级隔离 |
| Agent跑得很慢 | 单轮推理时间太长,或多次调用长上下文模型 | 精简上下文:过滤无关工具返回、使用摘要替换完整历史;对分支操作并行化执行 |
对新手来说,第七个问题尤其值得警惕。很多Agent项目一开始“跑通了”,一压真实数据就卡顿,通常就是在早期阶段忽略了上下文瘦身。我们把这个理念放在相对靠前的位置来规划,整个系统的稳定性会好很多。
还有一条独门经验:给Agent加一层“输入校验”。我遇到过用户输入信息不全,Agent反复补问,体验很不好。后来给Agent加了一个前置校验节点,先判断用户信息是否完整,不完整就一次把需要的字段全部列出来追问,而不是一轮轮挤牙膏。这个改动让整个交互体验顺滑了很多,算是我自己很满意的一个优化。
最后的几句经验之谈
写到这里,第二课的核心内容就讲完了。回到我们开头说的那个问题——为什么很多人学完Agent开发,做出来的东西还是停留在“能演示”的层面?我的答案很简单:因为只学了搭建,没学工程。
Agent开发能力的真正分水岭,不在于你会不会调LangGraph的API,也不在于你能不能跑通一个MCP工具,而在于你有没有建立起一套“让Agent在未知环境里稳定交付结果”的系统思维。三阶段六泳道是这种思维的方法论,LangGraph和MCP是这种思维的落地工具,而无数次问题排查才是训练这种思维的最佳实践。
这一课的知识密度不小,建议你至少完整读两遍,然后挑一个小场景动起手来。下一课,我会重点讲Agent的评测体系和监控运维——毕竟在生产环境里,怎么衡量Agent“好”或者“不好”,怎么在出了问题之后快速定位和修复,才是决定它能走多远的东西。到时候见。