Agent 30 课程第3课,这个位置正好卡在“基础铺垫”和“实战上手”之间。第1课讲 Agent 是什么、能解决什么问题,第2课带大家把开发环境跑通、完成第一个能对话的 Agent。如果第3课继续讲概念,课堂节奏就拖了,学员会觉得无聊;如果直接上复杂项目,基础又不够。所以我把第3课定位成“Agent 的核心机制——规划与工具调用”,这也是整个系列里承上启下的一课。为什么偏偏选这个主题?因为规划能力决定了 Agent 是“聊天机器人”还是“智能体”,工具调用决定了 Agent 能不能真正干活。前面两课学员接触到的 Agent 本质上还是“你问我答”,你让它查天气,它只能说“抱歉,我无法访问实时数据”。从第3课开始,这种局面会被彻底打破。这一课学完,学员手里的 Agent 会第一次拥有“手脚”——能自己决定调用哪个工具、能根据工具返回结果调整下一步动作,甚至能在多步推理中保持目标不跑偏。如果你正在自学 Agent 开发,或者正准备设计一套 Agent 方向的课程,这节课的拆解思路、代码框架和避坑经验都可以直接抄作业。我会把课程设计的考虑、核心原理、实操项目、常见坑全部展开来讲,保证你在自己复现的时候不会卡在半路上。1. 课程定位与核心思路拆解1.1 前三课的教学地图:这节课承上启下我的 Agent 30 课程按“认知—搭建—机制—应用—优化”五条线推进。第1课解决“是什么”的问题,学员要能说清楚 Agent 和普通程序的差别:普通程序是写死的规则,Agent 是模型根据目标动态决策。第2课解决“跑起来”的问题,学员完成环境配置,调用大模型接口,做出了一个能多轮对话的 Demo。到这里,学员的成就感和困惑感是同时存在的——成就感来自终于见到 Agent 跑通了,困惑来自“这跟接了个 API 有什么区别?为什么它不叫 Agent?”第3课的教学目标就是把这份困惑转变成认知升级。我刻意避开了“搜索总结”“文档问答”这类常见案例,把重点放在两个抽象能力上:思考链(推理规划)和工具使用(外部动作)。这两个能力是所有复杂 Agent 应用的共同地基。为什么这么设计?因为后续课程要做的内容——比如让 Agent 操作数据库、自动写报告、处理多步骤办公任务——本质上都是“规划 + 工具调用”的排列组合。如果第3课不把这个地基打牢,后面每一节课都会变成“调框架、改提示词”的机械重复,学员根本不知道自己改的是什么。1.2 核心目标:让 Agent 从“会说话”变成“会做事”对学员来说,这一课最直观的体验变化是:Agent 终于可以访问外部数据、执行真实操作了。我反复强调一个课堂金句:“对话不是目的,完成任务才是。”第3课的一切内容都是围绕“完成任务”四个字展开。具体拆成三个可测量的学习目标:目标一:学员能说清楚 ReAct 循环(推理—行动—观察)的执行流程,能手动模拟一遍 Agent 决策过程。目标二:学员能写出符合格式的工具定义,让模型在需要时主动发起调用,而不是在对话里“假装”完成。目标三:学员能独立实现一个带两个以上工具的问答 Agent,并处理工具调用失败时的恢复流程。这三个目标对应 Bloom 教育目标分类里的“理解、应用、分析”三个层级,既不是纯理论背诵,也不是无脑敲代码,而是让学员真正具备“设计一个 Agent 行为”的思维能力。1.3 适合谁来学:不是零基础课,但门槛也不高严格来说,第3课不是零基础课程。学员需要满足三个前置条件:能写简单的 Python 函数、理解 HTTP 请求和 JSON 的基本概念、第2课的环境已经成功运行过。满足这三条的学员,学这节课会非常顺。如果你是完全没写过代码的小白,我建议先把 Python 基础语法过一遍,至少知道什么是字典、什么是列表、什么是函数调用。如果你已经有其他语言的编程经验,直接上手完全没问题,Agent 开发的核心不在语言特性,而在“如何设计模型的思考流程”。这节课的产出很明确:一个能用自然语言查询天气和计算数学表达式的 Agent。听上去很基础对吧?但真正动手做的时候你会发现,让模型“准确判断什么时候该查天气、什么时候该直接回答”这件事,比想象中要微妙得多。这个项目作为第3课的实战作业,难度适中,又能把课程核心知识点全部串起来。2. 核心机制原理解读:规划与工具调用的底层逻辑2.1 ReAct 循环:Agent 的“想—做—看”工作流第3课第一个必须讲透的概念是 ReAct,全称是“Reasoning + Acting”。这不是什么新潮发明,而是一种把推理过程和行动过程交替执行的程序框架。整个循环就三步,很好记:推理:模型根据当前状态,思考“现在要完成目标,我该做什么?”这个过程会输出一段文字,解释自己的思路。行动:模型从可用工具里选一个,并生成调用参数。观察:程序执行工具调用,把返回结果交给模型,模型再进入下一轮推理。这个过程会重复执行,直到模型判断目标已经完成,输出最终答案。我在课堂上会用一个生活化类比来解释:你指挥一个实习生去帮你找一份文件。你不会直接让他“把文件给我”就算完,而是引导他想:文件可能在哪儿?先去哪个柜子找?打开柜子发现没有,那下一步怎么办?Agent 的 ReAct 循环跟这个流程一模一样,只不过实习生换成了大模型,柜子换成了各种工具接口。这段话教学效果很不错,因为学员一下就理解了“为什么 Agent 需要循环”和“为什么不能只让模型回答一次”。模型的上下文里会保留每一轮推理、行动、观察的记录,所以它能记住自己已经做过什么、下一步该做什么。2.2 Function Calling:模型与工具之间的“契约机制”ReAct 循环里最关键的技术环节,是让模型“发起工具调用”。早期做法是让模型输出一段特殊格式的文本,比如“CALC: 1+1”,然后程序解析这段文本、匹配工具、执行、返回结果。这种方式能用,但太脆弱了,模型一旦格式输出得稍微不规范,整个链路就断了。现在主流大模型 API 基本都原生支持Function Calling(函数调用)。你可以把它理解成一份“能力说明书”:你先告诉模型:“我手头有两个工具,一个叫 get_weather,接收城市名参数;一个叫 calculator,接收数学表达式参数。”模型在推理时会判断当前任务是否需要调用工具。如果需要,它不会直接在文字里输出答案,而是返回一个结构化的调用请求,比如“我想调用 get_weather,参数是 {城市: 北京}”。你的程序收到这个请求后,执行真正常数调用,拿到结果,再把这个结果作为一条新的“观察消息”发回给模型。这样做的好处是稳定、可靠、可解析。模型不再需要“一边说人话一边混格式”,程序也不需要写复杂的正则去猜意图。这也是第3课我希望学员最先掌握的感觉:Agent 的本质不是魔法,而是一套设计严谨的协议交互。2.3 工具描述的艺术:为什么写不好 Description 模型就乱来Function Calling 的效果好不好,七成取决于工具定义写得好不好。很多初学者把工具描述写得很随意,结果模型频繁调用错误工具,或者该调用的时候不调用。这里有个关键认知:模型“理解”工具,靠的不是工具的函数名,而是工具名、描述、参数说明这三件套。我在课程里会展示一个反例:一个工具的 description 写的是“查询数据”,参数说明全是“随便填”。模型看到这种描述会非常困惑,它不知道这个工具是查数据库还是查天气,更不知道参数要填代码还是填自然语言。正确的写法是:工具名:用动宾结构,一眼看出功能,比如 get_real_weather。描述:说明工具的适用场景、数据来源、返回值格式,必要时写明“什么时候不要用”。参数说明:给每个字段写清楚类型、含义、取值范围、是否必填。工具描述本身就是一段提示词工程。你在写这段描述时,实际上是在引导模型做工具选型。描述写得越准确,模型选错工具的概率就越低。这一点要作为第3课的单独练习来抓,我会让学员互相审查工具描述,看谁的描述能让人(模拟模型)秒懂,谁的描述会产生歧义。2.4 记忆与上下文:为什么 Agent 需要“有限注意力”学员做到多轮对话时,会遇到一个很现实的问题:模型能记住的内容有限,一次对话塞不下无限的历史记录。当 Agent 执行了十几次工具调用之后,早期信息可能被挤出上下文窗口,导致它“忘记”最初的目标。这个困惑在第3课必须正面回答,不然学员会以为是自己代码写错了。我会给出三层记忆设计的思路:短期记忆:把最近几轮问答直接放进上下文,适合简单对话。滑动窗口:限制历史消息条数,超过就丢弃最旧的消息,适合大部分场景。摘要压缩:定期把早期对话“总结成几句话”,替换掉原始的长对话内容,适合长任务场景。第3课用到的滑动窗口方案最实用,实现也简单:维护一个消息队列,超过最大长度就弹出最老的消息。我明确告诉学员,这个方案不是最优解,但现阶段够用,等后续课程讲到长期记忆工具时再升级。3. 第3课实操项目设计:知识问答与工具调用双驱动3.1 项目需求说明:做一个“有常识也有工具”的 Agent综合前面所有考虑,我把课程的实战项目定为“多功能助手 Agent”。需求非常简单,只有两条:用户可以用自然语言提问,Agent 负责判断问题类型并选择回答策略。Agent 必须至少支持两类工具:一个实时天气查询工具,一个数学计算工具。此外,Agent 还需要具备不调用工具、直接回答常识问题的能力。这个需求正好覆盖第3课的全部知识点:规划(判断该不该调用工具)、工具定义(写清楚两个工具的参数)、循环执行(多步推理直到完成任务)、记忆处理(多轮对话不乱套)。我特意把“常识问题直接回答”作为一条显式需求,因为很多初学者会陷入一个误区:认为 Agent 每句话都必须调用工具。实际上,好的 Agent 应该会判断“什么时候不需要工具”。比如用户问“谁是某国现任总统”,这是时效性问题,应该查资料;用户问“一公斤等于多少克”,这种固定常识不应浪费一次工具调用。判断期间就是规划能力的体现。3.2 方案选型:工具函数自己写,不依赖第三方框架第3课我刻意不引入任何现成的 Agent 框架。市面上确实有一些好用的 Agent 开发框架,它们封装了 ReAct 循环、工具调用、记忆管理,开箱即用。但我坚持第3课全部手写,原因有三:原理优先:学员需要亲手实现一遍 ReAct 循环,才能真正理解框架在背后做了什么。直接上框架会导致“会用但不懂”。降低报错排查难度:框架层数多,报错信息复杂,初学者很难分清是自己写错了还是框架用法不对。手写版本总代码量不到两百行,出问题能一眼定位。轻量可控制:课程环境不要求安装额外依赖,只需要大模型 SDK 和 Python 标准库,网络环境简单,学员复现成功率更高。当然,我会在课程最后提一句:等学习者理解了底层原理,后续自己做复杂应用时再引入框架,效率会高很多。手写是为了理解,用框架是为了工程效率,两者不矛盾。3.3 核心代码结构:三个模块一个循环这个项目的代码组织按照“工具定义—调度逻辑—主循环”三个模块来拆:# 模块1: 工具实现(真实执行的部分) def get_weather(city: str) -> str: """模拟实时天气查询,真实项目中可替换为天气 API 调用""" # 这里用伪数据演示,实际开发时接入天气服务 return f"城市{city}当前天气:多云,气温 24 摄氏度,东南风 2 级" def calculator(expression: str) -> str: """执行数学表达式计算,仅支持四则运算""" # 注意: 实际开发中需要对表达式做安全校验,防止恶意代码执行 result = eval(expression) return f"{expression} = {result}"# 模块2: 工具注册表(把工具信息告诉模型的"说明书") tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询某个城市的实时天气信息,包括气温、天气现象、风力。当用户询问天气、温度、下雨概率等问题时使用。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,比如 北京、上海、广州" } }, "required": ["city"] } } }, { "type": "function", "function": { "name": "calculator", "description": "执行数学表达式计算,支持加(+)、减(-)、乘(*)、除(/)运算符。当用户提出数学计算需求时使用。", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "需要计算的数学表达式,例如 12 * 7 + 5" } }, "required": ["expression"] } } } ]主循环是核心教学点。为了让学员看清每一步,我把循环流程拆成六个清晰的动作:构造消息、请求模型、判断请求类型、执行工具、反馈结果、重复或终止。这六步展开来写,学员就不会觉得 ReAct 是个抽象概念。4. 实操过程与关键环节实现4.1 步骤一:搭建主循环骨架主循环是整个 Agent 的心脏。我给学员的模板代码是这样的:def run_agent(user_input: str) -> str: messages = [ {"role": "system", "content": "你是一个有帮助的助手。当你需要实时数据或精确计算结果时,请使用工具;否则可以直接回答。"}, {"role": "user", "content": user_input} ] max_iterations = 5 # 防止死循环的保险丝 for step in range(max_iterations): # 给模型传入消息历史 + 工具说明,让它决定下一步动作 response = llm.chat(messages=messages, tools=tools) if response.has_tool_call: # 模型决定调用工具,我们把它的推理过程也存进历史 messages.append(response.message) for tool_call in response.tool_calls: # 根据工具名分发到对应函数 if tool_call.name == "get_weather": result = get_weather(city=tool_call.arguments["city"]) elif tool_call.name == "calculator": result = calculator(expression=tool_call.arguments["expression"]) else: result = "未知工具" # 观察结果:工具返回后,作为一条新消息交给模型 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result }) else: # 模型没有调用工具,说明它准备直接回答,返回最终答案 return response.content return "抱歉,我没有在规定的步数内完成任务。"这个骨架我有意做了三个设计细节:max_iterations 保险丝:新手最常见的错误是 Agent 陷入死循环,反复调用同一个工具,永远给不出答案。加一个最大迭代次数能保证程序无论如何都能退出。工具调用结果以“tool”角色回传:这是必须的协议步骤。很多人漏掉这一步,导致模型拿到工具结果后不知道发生了什么,行为变得很奇怪。每一步都把完整消息追加进历史:让模型“记得”自己之前做了什么,是整个规划能力的基础。这一步省不掉,也替代不了。4.2 步骤二:跑通第一个“思考—行动—观察”闭环代码写好之后,我会让学员先跑三个测试用例,每跑一个都要手动记录模型输出的推理过程和工具调用情况。第一个测试用例是:“北京今天会下雨吗?”模型应该调用 get_weather 工具,拿到天气结果后再回答。第二个测试用例是:“计算 15 乘以 8 加 3 的结果。”模型应该调用 calculator 工具。第三个测试用例是:“中国的首都是哪里?”模型应该不调用任何工具,直接回答。这三个用例分别验证工具调用的三种状态:需要查询、需要计算、无需工具。学员跑完测试,对“模型如何决策”就有直观感受了。课堂上的实际反应往往会出乎意料。比如第二个测试,模型可能不调用 calculator,而是直接心算出“123”。这时我反而很高兴,因为这恰好是教学点:模型对自己的数学能力过于自信,但它的计算可靠性不如工具。我们需要在系统提示词里强调“涉及计算必须使用工具,不要自行心算”。这也是提示词工程的一环。4.3 步骤三:加入错误恢复流程工具不是万能的。天气服务可能超时,计算表达式可能是非法输入,模型可能选择了不存在的工具。这些情况必须纳入代码逻辑,否则 Agent 会卡在“调用失败—重试—再失败”的死循环里。我给项目加了一个简易容错机制:在工具执行函数里用 try-except 包裹代码,出错时返回一个明确的错误字符串,而不是让程序崩溃。同时,在系统提示词中加一句:如果工具返回错误信息,要告诉用户发生了什么,并主动询问是否需要换一种方式尝试。这一步对学员的影响很大。他们第一次意识到,Agent 工程的难点不只是把流程跑通,而是让流程在异常情况下还能优雅地降级。真实世界的接口不会像文档里写的那么听话,容错设计是工程师和爱好者的分水岭。4.4 步骤四:多轮对话与滑动窗口配置为了让 Agent 支持多轮对话,我给循环代码补上消息管理模块:设置最大历史消息数(比如 20 条),超过时删除最老的消息,始终保留系统提示词在开头。实现很简单,但效果明显。我在课堂上演示过一个连续 8 轮对话的场景:前 3 轮用户都在问北京的天气,第 5 轮问上海的天气,第 8 轮问“那上海明天比今天热吗”。Agent 需要结合前面某轮查询到的“今天上海温度”和工具返回的“明天上海温度”来做对比。如果消息历史被清空或管理不当,模型根本回答不了这种依赖上下文的问题。这个案例是第3课最精彩的一环。学员看着控制台里几十条消息来回传递,真正理解了什么叫“记忆让 Agent 具备连续性”。5. 常见问题与排查技巧实录5.1 模型就是不调用工具:原因和对策这是第3课出现频率最高的问题。模型面对“北京天气”这种问题,直接回答“我无法获取实时数据,请稍后再试”,就是不触发工具调用。排查步骤依次检查三个地方:工具描述是否写清楚触发条件:把“当用户询问天气、气温、降雨等信息时使用”写进 description,模型就很容易判断。系统提示词是否强调工具优先:加一句“你有能力查询实时数据,请在你需要信息时主动调用工具,不要拒绝任务”。模型版本是否支持工具调用:个别旧版本模型对 Function Calling 支持不完善,需要升级到支持工具调用的版本。我在课上会让学员记录一个统计:在所有该调用工具的场景中,模型正确调用工具的比例是多少。最低要求是 90%,低于这个数就要回头改描述或提示词。5.2 Agent 陷入循环,反复调用同一个工具最常见的循环发生在天气工具上:模型查完北京天气后,用户又问“那湿度呢?”因为天气工具返回结果里没有湿度字段,模型会再调用一次,又发现没有湿度,再次调用,循环不止。解决办法有三个层次。第一,工具返回结果要完整,一次把天气现象、温度、湿度、风力都返回,减少“查不到再查一次”的动机。第二,循环内加迭代次数上限,从机制上阻止无限循环。第三,在最开始构建回答时,引导 Agent 提前组织好问题:如果用户的追问信息不足,直接说明情况,而不是反复尝试。5.3 模型解析工具参数出错:字符串变数字、缺字段当模型生成“city=北京”而工具函数期望一个字符串时,有时 SDK 会解析错误,或者模型把多个参数塞进一个字段。现代 SDK 大多会自动处理类型转换,但参数缺失的问题仍会出现。我的建议是给工具函数加默认值或容错逻辑:比如 get_weather 的 city 参数如果为空,就用“北京”兜底,并在返回信息里注明“未指定城市,已默认使用北京”。这个兜底策略让程序不至于崩溃,也给用户一个明确反馈。课程里我会强调,工具函数的健壮性要比普通程序函数更高,因为你面对的输入可能来自“猜”而不是“准确输入”。5.4 常见问题速查表现象可能原因解决动作模型从不调用工具工具描述不清晰 / 提示词未强调重写描述,加入触发条件说明模型反复调用同一工具工具返回值不够完整补充返回字段,增加循环上限工具参数解析失败模型生成参数格式不对添加参数容错与默认值逻辑多轮对话后行为混乱历史消息被清空或过短增大滑动窗口,或引入摘要记忆工具执行报错导致程序退出没有捕获异常用 try-except 包裹工具执行体模型直接心算不调用计算器提示词未强调工具优先在系统提示词里明确“计算必须用工具”这张表我每期课程都会更新,学员反馈说它比讲义有用得多,实际遇到问题时翻一下就能解决。这里也建议正在自学的人,把这几种问题当成正常现象。如果真的完全没踩过这些坑,反而说明你可能没有真正深入到 Agent 的实现层。5.5 一个容易被忽略的细节:工具调用结果的格式很多人卡在一个很隐蔽的问题上:工具调用返回了正确结果,但模型看到结果后依然“无动于衷”,或者回答得驴唇不对马嘴。排查到最后,发现是消息角色用错了。工具返回的内容必须以“role: tool”传回,并且要带上对应的 tool_call_id,模型才能把结果和刚才的请求关联起来。这个细节在官方文档里写得很清楚,但实际教学时,几乎每期都有学员在调试时忽略掉。我因此特意总结了记忆口诀:“工具调用的完整四段——请求消息、结果角色、调用标识、内容回传”,少了哪一段都可能在链路里出现“断片”。这个小细节不进讲义,但在实战中价值极高。6. 课程作业设计与教学建议6.1 作业一:新增一个“单位换算”工具学员需要仿照 get_weather 的编写方式,为 Agent 新增一个单位换算工具,支持长度(米/英尺)、重量(千克/磅)等常用换算。要求写清楚参数描述、触发场景,并验证模型能在合理场景下调用它。这道题考察的是“工具描述的迁移能力”。会写天气工具不代表会写所有工具,学员必须理解“如何把工具能力表述成模型能理解的规则”。批改时会重点看工具描述的自我解释力,而不是单纯看代码跑没跑通。6.2 作业二:写一篇“工具评审报告”每个学员随机拿到另一位同学写的工具定义,以“模型第一视角”来评述:这些工具描述会不会造成误判?参数设计合理吗?有什么改进空间?这个作业妙在它是零代码的,却要求学员反向思考工具的消费方(模型)如何理解工具。批改作业时,我发现团队内互评出来的很多问题,比我这个导师指出来的还要尖锐。这说明工具描述的评审能力已经成为学员内部的一种“共同语言”了,这正是课程想要的氛围。6.3 教学节奏与建议课时分配第3课我安排的课时是 8 到 10 小时,分四次课完成。第一次课讲原理和代码骨架,第二次课集中调试和跑通主流程,第三次课做扩展工具练习,第四次课做小组互评和复盘。每两次课之间留一天让学员消化和踩坑。如果完全自学,我建议把节奏拉宽到一周:前三天看懂原理和代码,第四天自己动手写,第五天到第七天做扩展练习和总结。这个节奏不算快,但基础会非常扎实,尤其是“调试能力”,没有足够时间实打实练是出不来的。6.4 老学员常问的“接下来学什么”第3课学完后,学员往往意犹未尽。他们会问:“那 Agent 怎么连接数据库?怎么自动写文件?怎么调度多个步骤?”这些问题我会留到第5课和第6课解答。第4课讲“记忆增强的 Agent”,解决长任务和多轮上下文问题;第5课讲“数据库与文档操作工具实战”,让 Agent 真正开始操作业务数据;第6课讲“规划算法与任务分解”,把单步工具调用升级成多步骤的复杂任务编排。不过在进入那些内容之前,第3课的“规划 + 工具调用”这套底子必须熟练掌握。所有复杂的 Agent 最终都要回归到“决策—执行—观察”这个基本循环上来。基础打得越扎实,后面写复杂项目越顺手。我个人在实际教学中有一个很深的体会:第3课结束的时候,学员通常分化为两种状态。一种人觉得“原来 Agent 不过如此”,感觉核心机制就这几行代码;另一种人则开始兴奋,因为他看到“只要接上不同的工具,Agent 就能无限扩展能力”。这两种状态其实都是正面反馈,前者的心态帮他把原理固化,后者的心态驱动他继续探索。我作为课程设计者,最希望看到的,是学员同时拥有这两种感觉——既看穿了机制的本质,又不失去对应用空间的想象力。写到这里,如果你正准备把 Agent 课程推进到第3课,或者正在自学这一块,还有一个小建议:先不要急着接入复杂的工具或者框架。花一个下午,用最简单的手写循环,把天气工具和计算器工具跑通。相信我,这个看似朴素的练习带来的理解深度,会远远超过直接调用那些一键封装好的库。等你亲手把那个循环彻底跑通,再抬起头看任何 Agent 框架,都会觉得那是透明的水晶盒子,而不是摸不透的黑箱。
Agent 30 课程第3课,这个位置正好卡在“基础铺垫”和“实战上手”之间。第1课讲 Agent 是什么、能解决什么问题,第2课带大家把开发环境跑通、完成第一个能对话的 Agent。如果第3课继续讲概念,课堂节奏就拖了,学员会觉得无聊;如果直接上复杂项目,基础又不够。所以我把第3课定位成“Agent 的核心机制——规划与工具调用”,这也是整个系列里承上启下的一课。为什么偏偏选这个主题?因为规划能力决定了 Agent 是“聊天机器人”还是“智能体”,工具调用决定了 Agent 能不能真正干活。前面两课学员接触到的 Agent 本质上还是“你问我答”,你让它查天气,它只能说“抱歉,我无法访问实时数据”。从第3课开始,这种局面会被彻底打破。这一课学完,学员手里的 Agent 会第一次拥有“手脚”——能自己决定调用哪个工具、能根据工具返回结果调整下一步动作,甚至能在多步推理中保持目标不跑偏。如果你正在自学 Agent 开发,或者正准备设计一套 Agent 方向的课程,这节课的拆解思路、代码框架和避坑经验都可以直接抄作业。我会把课程设计的考虑、核心原理、实操项目、常见坑全部展开来讲,保证你在自己复现的时候不会卡在半路上。1. 课程定位与核心思路拆解1.1 前三课的教学地图:这节课承上启下我的 Agent 30 课程按“认知—搭建—机制—应用—优化”五条线推进。第1课解决“是什么”的问题,学员要能说清楚 Agent 和普通程序的差别:普通程序是写死的规则,Agent 是模型根据目标动态决策。第2课解决“跑起来”的问题,学员完成环境配置,调用大模型接口,做出了一个能多轮对话的 Demo。到这里,学员的成就感和困惑感是同时存在的——成就感来自终于见到 Agent 跑通了,困惑来自“这跟接了个 API 有什么区别?为什么它不叫 Agent?”第3课的教学目标就是把这份困惑转变成认知升级。我刻意避开了“搜索总结”“文档问答”这类常见案例,把重点放在两个抽象能力上:思考链(推理规划)和工具使用(外部动作)。这两个能力是所有复杂 Agent 应用的共同地基。为什么这么设计?因为后续课程要做的内容——比如让 Agent 操作数据库、自动写报告、处理多步骤办公任务——本质上都是“规划 + 工具调用”的排列组合。如果第3课不把这个地基打牢,后面每一节课都会变成“调框架、改提示词”的机械重复,学员根本不知道自己改的是什么。1.2 核心目标:让 Agent 从“会说话”变成“会做事”对学员来说,这一课最直观的体验变化是:Agent 终于可以访问外部数据、执行真实操作了。我反复强调一个课堂金句:“对话不是目的,完成任务才是。”第3课的一切内容都是围绕“完成任务”四个字展开。具体拆成三个可测量的学习目标:目标一:学员能说清楚 ReAct 循环(推理—行动—观察)的执行流程,能手动模拟一遍 Agent 决策过程。目标二:学员能写出符合格式的工具定义,让模型在需要时主动发起调用,而不是在对话里“假装”完成。目标三:学员能独立实现一个带两个以上工具的问答 Agent,并处理工具调用失败时的恢复流程。这三个目标对应 Bloom 教育目标分类里的“理解、应用、分析”三个层级,既不是纯理论背诵,也不是无脑敲代码,而是让学员真正具备“设计一个 Agent 行为”的思维能力。1.3 适合谁来学:不是零基础课,但门槛也不高严格来说,第3课不是零基础课程。学员需要满足三个前置条件:能写简单的 Python 函数、理解 HTTP 请求和 JSON 的基本概念、第2课的环境已经成功运行过。满足这三条的学员,学这节课会非常顺。如果你是完全没写过代码的小白,我建议先把 Python 基础语法过一遍,至少知道什么是字典、什么是列表、什么是函数调用。如果你已经有其他语言的编程经验,直接上手完全没问题,Agent 开发的核心不在语言特性,而在“如何设计模型的思考流程”。这节课的产出很明确:一个能用自然语言查询天气和计算数学表达式的 Agent。听上去很基础对吧?但真正动手做的时候你会发现,让模型“准确判断什么时候该查天气、什么时候该直接回答”这件事,比想象中要微妙得多。这个项目作为第3课的实战作业,难度适中,又能把课程核心知识点全部串起来。2. 核心机制原理解读:规划与工具调用的底层逻辑2.1 ReAct 循环:Agent 的“想—做—看”工作流第3课第一个必须讲透的概念是 ReAct,全称是“Reasoning + Acting”。这不是什么新潮发明,而是一种把推理过程和行动过程交替执行的程序框架。整个循环就三步,很好记:推理:模型根据当前状态,思考“现在要完成目标,我该做什么?”这个过程会输出一段文字,解释自己的思路。行动:模型从可用工具里选一个,并生成调用参数。观察:程序执行工具调用,把返回结果交给模型,模型再进入下一轮推理。这个过程会重复执行,直到模型判断目标已经完成,输出最终答案。我在课堂上会用一个生活化类比来解释:你指挥一个实习生去帮你找一份文件。你不会直接让他“把文件给我”就算完,而是引导他想:文件可能在哪儿?先去哪个柜子找?打开柜子发现没有,那下一步怎么办?Agent 的 ReAct 循环跟这个流程一模一样,只不过实习生换成了大模型,柜子换成了各种工具接口。这段话教学效果很不错,因为学员一下就理解了“为什么 Agent 需要循环”和“为什么不能只让模型回答一次”。模型的上下文里会保留每一轮推理、行动、观察的记录,所以它能记住自己已经做过什么、下一步该做什么。2.2 Function Calling:模型与工具之间的“契约机制”ReAct 循环里最关键的技术环节,是让模型“发起工具调用”。早期做法是让模型输出一段特殊格式的文本,比如“CALC: 1+1”,然后程序解析这段文本、匹配工具、执行、返回结果。这种方式能用,但太脆弱了,模型一旦格式输出得稍微不规范,整个链路就断了。现在主流大模型 API 基本都原生支持Function Calling(函数调用)。你可以把它理解成一份“能力说明书”:你先告诉模型:“我手头有两个工具,一个叫 get_weather,接收城市名参数;一个叫 calculator,接收数学表达式参数。”模型在推理时会判断当前任务是否需要调用工具。如果需要,它不会直接在文字里输出答案,而是返回一个结构化的调用请求,比如“我想调用 get_weather,参数是 {城市: 北京}”。你的程序收到这个请求后,执行真正常数调用,拿到结果,再把这个结果作为一条新的“观察消息”发回给模型。这样做的好处是稳定、可靠、可解析。模型不再需要“一边说人话一边混格式”,程序也不需要写复杂的正则去猜意图。这也是第3课我希望学员最先掌握的感觉:Agent 的本质不是魔法,而是一套设计严谨的协议交互。2.3 工具描述的艺术:为什么写不好 Description 模型就乱来Function Calling 的效果好不好,七成取决于工具定义写得好不好。很多初学者把工具描述写得很随意,结果模型频繁调用错误工具,或者该调用的时候不调用。这里有个关键认知:模型“理解”工具,靠的不是工具的函数名,而是工具名、描述、参数说明这三件套。我在课程里会展示一个反例:一个工具的 description 写的是“查询数据”,参数说明全是“随便填”。模型看到这种描述会非常困惑,它不知道这个工具是查数据库还是查天气,更不知道参数要填代码还是填自然语言。正确的写法是:工具名:用动宾结构,一眼看出功能,比如 get_real_weather。描述:说明工具的适用场景、数据来源、返回值格式,必要时写明“什么时候不要用”。参数说明:给每个字段写清楚类型、含义、取值范围、是否必填。工具描述本身就是一段提示词工程。你在写这段描述时,实际上是在引导模型做工具选型。描述写得越准确,模型选错工具的概率就越低。这一点要作为第3课的单独练习来抓,我会让学员互相审查工具描述,看谁的描述能让人(模拟模型)秒懂,谁的描述会产生歧义。2.4 记忆与上下文:为什么 Agent 需要“有限注意力”学员做到多轮对话时,会遇到一个很现实的问题:模型能记住的内容有限,一次对话塞不下无限的历史记录。当 Agent 执行了十几次工具调用之后,早期信息可能被挤出上下文窗口,导致它“忘记”最初的目标。这个困惑在第3课必须正面回答,不然学员会以为是自己代码写错了。我会给出三层记忆设计的思路:短期记忆:把最近几轮问答直接放进上下文,适合简单对话。滑动窗口:限制历史消息条数,超过就丢弃最旧的消息,适合大部分场景。摘要压缩:定期把早期对话“总结成几句话”,替换掉原始的长对话内容,适合长任务场景。第3课用到的滑动窗口方案最实用,实现也简单:维护一个消息队列,超过最大长度就弹出最老的消息。我明确告诉学员,这个方案不是最优解,但现阶段够用,等后续课程讲到长期记忆工具时再升级。3. 第3课实操项目设计:知识问答与工具调用双驱动3.1 项目需求说明:做一个“有常识也有工具”的 Agent综合前面所有考虑,我把课程的实战项目定为“多功能助手 Agent”。需求非常简单,只有两条:用户可以用自然语言提问,Agent 负责判断问题类型并选择回答策略。Agent 必须至少支持两类工具:一个实时天气查询工具,一个数学计算工具。此外,Agent 还需要具备不调用工具、直接回答常识问题的能力。这个需求正好覆盖第3课的全部知识点:规划(判断该不该调用工具)、工具定义(写清楚两个工具的参数)、循环执行(多步推理直到完成任务)、记忆处理(多轮对话不乱套)。我特意把“常识问题直接回答”作为一条显式需求,因为很多初学者会陷入一个误区:认为 Agent 每句话都必须调用工具。实际上,好的 Agent 应该会判断“什么时候不需要工具”。比如用户问“谁是某国现任总统”,这是时效性问题,应该查资料;用户问“一公斤等于多少克”,这种固定常识不应浪费一次工具调用。判断期间就是规划能力的体现。3.2 方案选型:工具函数自己写,不依赖第三方框架第3课我刻意不引入任何现成的 Agent 框架。市面上确实有一些好用的 Agent 开发框架,它们封装了 ReAct 循环、工具调用、记忆管理,开箱即用。但我坚持第3课全部手写,原因有三:原理优先:学员需要亲手实现一遍 ReAct 循环,才能真正理解框架在背后做了什么。直接上框架会导致“会用但不懂”。降低报错排查难度:框架层数多,报错信息复杂,初学者很难分清是自己写错了还是框架用法不对。手写版本总代码量不到两百行,出问题能一眼定位。轻量可控制:课程环境不要求安装额外依赖,只需要大模型 SDK 和 Python 标准库,网络环境简单,学员复现成功率更高。当然,我会在课程最后提一句:等学习者理解了底层原理,后续自己做复杂应用时再引入框架,效率会高很多。手写是为了理解,用框架是为了工程效率,两者不矛盾。3.3 核心代码结构:三个模块一个循环这个项目的代码组织按照“工具定义—调度逻辑—主循环”三个模块来拆:# 模块1: 工具实现(真实执行的部分) def get_weather(city: str) -> str: """模拟实时天气查询,真实项目中可替换为天气 API 调用""" # 这里用伪数据演示,实际开发时接入天气服务 return f"城市{city}当前天气:多云,气温 24 摄氏度,东南风 2 级" def calculator(expression: str) -> str: """执行数学表达式计算,仅支持四则运算""" # 注意: 实际开发中需要对表达式做安全校验,防止恶意代码执行 result = eval(expression) return f"{expression} = {result}"# 模块2: 工具注册表(把工具信息告诉模型的"说明书") tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询某个城市的实时天气信息,包括气温、天气现象、风力。当用户询问天气、温度、下雨概率等问题时使用。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,比如 北京、上海、广州" } }, "required": ["city"] } } }, { "type": "function", "function": { "name": "calculator", "description": "执行数学表达式计算,支持加(+)、减(-)、乘(*)、除(/)运算符。当用户提出数学计算需求时使用。", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "需要计算的数学表达式,例如 12 * 7 + 5" } }, "required": ["expression"] } } } ]主循环是核心教学点。为了让学员看清每一步,我把循环流程拆成六个清晰的动作:构造消息、请求模型、判断请求类型、执行工具、反馈结果、重复或终止。这六步展开来写,学员就不会觉得 ReAct 是个抽象概念。4. 实操过程与关键环节实现4.1 步骤一:搭建主循环骨架主循环是整个 Agent 的心脏。我给学员的模板代码是这样的:def run_agent(user_input: str) -> str: messages = [ {"role": "system", "content": "你是一个有帮助的助手。当你需要实时数据或精确计算结果时,请使用工具;否则可以直接回答。"}, {"role": "user", "content": user_input} ] max_iterations = 5 # 防止死循环的保险丝 for step in range(max_iterations): # 给模型传入消息历史 + 工具说明,让它决定下一步动作 response = llm.chat(messages=messages, tools=tools) if response.has_tool_call: # 模型决定调用工具,我们把它的推理过程也存进历史 messages.append(response.message) for tool_call in response.tool_calls: # 根据工具名分发到对应函数 if tool_call.name == "get_weather": result = get_weather(city=tool_call.arguments["city"]) elif tool_call.name == "calculator": result = calculator(expression=tool_call.arguments["expression"]) else: result = "未知工具" # 观察结果:工具返回后,作为一条新消息交给模型 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result }) else: # 模型没有调用工具,说明它准备直接回答,返回最终答案 return response.content return "抱歉,我没有在规定的步数内完成任务。"这个骨架我有意做了三个设计细节:max_iterations 保险丝:新手最常见的错误是 Agent 陷入死循环,反复调用同一个工具,永远给不出答案。加一个最大迭代次数能保证程序无论如何都能退出。工具调用结果以“tool”角色回传:这是必须的协议步骤。很多人漏掉这一步,导致模型拿到工具结果后不知道发生了什么,行为变得很奇怪。每一步都把完整消息追加进历史:让模型“记得”自己之前做了什么,是整个规划能力的基础。这一步省不掉,也替代不了。4.2 步骤二:跑通第一个“思考—行动—观察”闭环代码写好之后,我会让学员先跑三个测试用例,每跑一个都要手动记录模型输出的推理过程和工具调用情况。第一个测试用例是:“北京今天会下雨吗?”模型应该调用 get_weather 工具,拿到天气结果后再回答。第二个测试用例是:“计算 15 乘以 8 加 3 的结果。”模型应该调用 calculator 工具。第三个测试用例是:“中国的首都是哪里?”模型应该不调用任何工具,直接回答。这三个用例分别验证工具调用的三种状态:需要查询、需要计算、无需工具。学员跑完测试,对“模型如何决策”就有直观感受了。课堂上的实际反应往往会出乎意料。比如第二个测试,模型可能不调用 calculator,而是直接心算出“123”。这时我反而很高兴,因为这恰好是教学点:模型对自己的数学能力过于自信,但它的计算可靠性不如工具。我们需要在系统提示词里强调“涉及计算必须使用工具,不要自行心算”。这也是提示词工程的一环。4.3 步骤三:加入错误恢复流程工具不是万能的。天气服务可能超时,计算表达式可能是非法输入,模型可能选择了不存在的工具。这些情况必须纳入代码逻辑,否则 Agent 会卡在“调用失败—重试—再失败”的死循环里。我给项目加了一个简易容错机制:在工具执行函数里用 try-except 包裹代码,出错时返回一个明确的错误字符串,而不是让程序崩溃。同时,在系统提示词中加一句:如果工具返回错误信息,要告诉用户发生了什么,并主动询问是否需要换一种方式尝试。这一步对学员的影响很大。他们第一次意识到,Agent 工程的难点不只是把流程跑通,而是让流程在异常情况下还能优雅地降级。真实世界的接口不会像文档里写的那么听话,容错设计是工程师和爱好者的分水岭。4.4 步骤四:多轮对话与滑动窗口配置为了让 Agent 支持多轮对话,我给循环代码补上消息管理模块:设置最大历史消息数(比如 20 条),超过时删除最老的消息,始终保留系统提示词在开头。实现很简单,但效果明显。我在课堂上演示过一个连续 8 轮对话的场景:前 3 轮用户都在问北京的天气,第 5 轮问上海的天气,第 8 轮问“那上海明天比今天热吗”。Agent 需要结合前面某轮查询到的“今天上海温度”和工具返回的“明天上海温度”来做对比。如果消息历史被清空或管理不当,模型根本回答不了这种依赖上下文的问题。这个案例是第3课最精彩的一环。学员看着控制台里几十条消息来回传递,真正理解了什么叫“记忆让 Agent 具备连续性”。5. 常见问题与排查技巧实录5.1 模型就是不调用工具:原因和对策这是第3课出现频率最高的问题。模型面对“北京天气”这种问题,直接回答“我无法获取实时数据,请稍后再试”,就是不触发工具调用。排查步骤依次检查三个地方:工具描述是否写清楚触发条件:把“当用户询问天气、气温、降雨等信息时使用”写进 description,模型就很容易判断。系统提示词是否强调工具优先:加一句“你有能力查询实时数据,请在你需要信息时主动调用工具,不要拒绝任务”。模型版本是否支持工具调用:个别旧版本模型对 Function Calling 支持不完善,需要升级到支持工具调用的版本。我在课上会让学员记录一个统计:在所有该调用工具的场景中,模型正确调用工具的比例是多少。最低要求是 90%,低于这个数就要回头改描述或提示词。5.2 Agent 陷入循环,反复调用同一个工具最常见的循环发生在天气工具上:模型查完北京天气后,用户又问“那湿度呢?”因为天气工具返回结果里没有湿度字段,模型会再调用一次,又发现没有湿度,再次调用,循环不止。解决办法有三个层次。第一,工具返回结果要完整,一次把天气现象、温度、湿度、风力都返回,减少“查不到再查一次”的动机。第二,循环内加迭代次数上限,从机制上阻止无限循环。第三,在最开始构建回答时,引导 Agent 提前组织好问题:如果用户的追问信息不足,直接说明情况,而不是反复尝试。5.3 模型解析工具参数出错:字符串变数字、缺字段当模型生成“city=北京”而工具函数期望一个字符串时,有时 SDK 会解析错误,或者模型把多个参数塞进一个字段。现代 SDK 大多会自动处理类型转换,但参数缺失的问题仍会出现。我的建议是给工具函数加默认值或容错逻辑:比如 get_weather 的 city 参数如果为空,就用“北京”兜底,并在返回信息里注明“未指定城市,已默认使用北京”。这个兜底策略让程序不至于崩溃,也给用户一个明确反馈。课程里我会强调,工具函数的健壮性要比普通程序函数更高,因为你面对的输入可能来自“猜”而不是“准确输入”。5.4 常见问题速查表现象可能原因解决动作模型从不调用工具工具描述不清晰 / 提示词未强调重写描述,加入触发条件说明模型反复调用同一工具工具返回值不够完整补充返回字段,增加循环上限工具参数解析失败模型生成参数格式不对添加参数容错与默认值逻辑多轮对话后行为混乱历史消息被清空或过短增大滑动窗口,或引入摘要记忆工具执行报错导致程序退出没有捕获异常用 try-except 包裹工具执行体模型直接心算不调用计算器提示词未强调工具优先在系统提示词里明确“计算必须用工具”这张表我每期课程都会更新,学员反馈说它比讲义有用得多,实际遇到问题时翻一下就能解决。这里也建议正在自学的人,把这几种问题当成正常现象。如果真的完全没踩过这些坑,反而说明你可能没有真正深入到 Agent 的实现层。5.5 一个容易被忽略的细节:工具调用结果的格式很多人卡在一个很隐蔽的问题上:工具调用返回了正确结果,但模型看到结果后依然“无动于衷”,或者回答得驴唇不对马嘴。排查到最后,发现是消息角色用错了。工具返回的内容必须以“role: tool”传回,并且要带上对应的 tool_call_id,模型才能把结果和刚才的请求关联起来。这个细节在官方文档里写得很清楚,但实际教学时,几乎每期都有学员在调试时忽略掉。我因此特意总结了记忆口诀:“工具调用的完整四段——请求消息、结果角色、调用标识、内容回传”,少了哪一段都可能在链路里出现“断片”。这个小细节不进讲义,但在实战中价值极高。6. 课程作业设计与教学建议6.1 作业一:新增一个“单位换算”工具学员需要仿照 get_weather 的编写方式,为 Agent 新增一个单位换算工具,支持长度(米/英尺)、重量(千克/磅)等常用换算。要求写清楚参数描述、触发场景,并验证模型能在合理场景下调用它。这道题考察的是“工具描述的迁移能力”。会写天气工具不代表会写所有工具,学员必须理解“如何把工具能力表述成模型能理解的规则”。批改时会重点看工具描述的自我解释力,而不是单纯看代码跑没跑通。6.2 作业二:写一篇“工具评审报告”每个学员随机拿到另一位同学写的工具定义,以“模型第一视角”来评述:这些工具描述会不会造成误判?参数设计合理吗?有什么改进空间?这个作业妙在它是零代码的,却要求学员反向思考工具的消费方(模型)如何理解工具。批改作业时,我发现团队内互评出来的很多问题,比我这个导师指出来的还要尖锐。这说明工具描述的评审能力已经成为学员内部的一种“共同语言”了,这正是课程想要的氛围。6.3 教学节奏与建议课时分配第3课我安排的课时是 8 到 10 小时,分四次课完成。第一次课讲原理和代码骨架,第二次课集中调试和跑通主流程,第三次课做扩展工具练习,第四次课做小组互评和复盘。每两次课之间留一天让学员消化和踩坑。如果完全自学,我建议把节奏拉宽到一周:前三天看懂原理和代码,第四天自己动手写,第五天到第七天做扩展练习和总结。这个节奏不算快,但基础会非常扎实,尤其是“调试能力”,没有足够时间实打实练是出不来的。6.4 老学员常问的“接下来学什么”第3课学完后,学员往往意犹未尽。他们会问:“那 Agent 怎么连接数据库?怎么自动写文件?怎么调度多个步骤?”这些问题我会留到第5课和第6课解答。第4课讲“记忆增强的 Agent”,解决长任务和多轮上下文问题;第5课讲“数据库与文档操作工具实战”,让 Agent 真正开始操作业务数据;第6课讲“规划算法与任务分解”,把单步工具调用升级成多步骤的复杂任务编排。不过在进入那些内容之前,第3课的“规划 + 工具调用”这套底子必须熟练掌握。所有复杂的 Agent 最终都要回归到“决策—执行—观察”这个基本循环上来。基础打得越扎实,后面写复杂项目越顺手。我个人在实际教学中有一个很深的体会:第3课结束的时候,学员通常分化为两种状态。一种人觉得“原来 Agent 不过如此”,感觉核心机制就这几行代码;另一种人则开始兴奋,因为他看到“只要接上不同的工具,Agent 就能无限扩展能力”。这两种状态其实都是正面反馈,前者的心态帮他把原理固化,后者的心态驱动他继续探索。我作为课程设计者,最希望看到的,是学员同时拥有这两种感觉——既看穿了机制的本质,又不失去对应用空间的想象力。写到这里,如果你正准备把 Agent 课程推进到第3课,或者正在自学这一块,还有一个小建议:先不要急着接入复杂的工具或者框架。花一个下午,用最简单的手写循环,把天气工具和计算器工具跑通。相信我,这个看似朴素的练习带来的理解深度,会远远超过直接调用那些一键封装好的库。等你亲手把那个循环彻底跑通,再抬起头看任何 Agent 框架,都会觉得那是透明的水晶盒子,而不是摸不透的黑箱。