
我把Hello Agent整个系列的Task05做完的那个晚上脑子里只有一个念头以前我以为自己懂Agent现在才知道那只是背了概念没动手。Task05的任务量其实不大它真正逼我做的事是把一个能自己调用工具、自己决定下一步做什么的最小Agent从零跑起来然后看着它犯错、改错、再犯错。这篇笔记记录的就是这个从背概念到跑起来的过程里我想明白的东西以及那些文档不会告诉你的坑。如果你也在学Agent开发或者正准备把Agent放进真实业务这篇应该能帮你少走一段弯路。Task05放在整个学习路线里是个分水岭。前面的Task基本都在教怎么和大模型对话Prompt怎么写、RAG怎么查、Function Calling怎么定参数。到了Task05问题突然变成了怎么让模型自己决定对话的方向。这个转变看着小实际做起来完全是另一套思路。下面我按自己复盘问题的顺序把整个学习笔记整理出来。1. 先分清三件事Agent、RAG、Workflow以及一个容易被绕进去的harness学Task05之前我习惯把所有带模型调用的东西都叫Agent写个RAG聊天机器人叫Agent写个按固定顺序调用多个工具的程序也叫Agent。做Task05时才发现这么叫会让很多设计决策变得模糊。动手之前必须把几个概念切开。1.1 Agent到底是什么从调用模型到把模型当大脑官方文档和社区文章里有个说法我很认同Agentic System不是把决策提前写死而是让模型在执行中动态决定流程。传统程序像个流水线工人每个零件装在哪一步是提前定死的。RAG像是给这个工人加了台资料柜他查完资料继续按原流程走。Agent则更像是给工人一块实时看板让他自己决定下一步装哪个零件、要不要换工具、要不要先做个测试。同样是用大模型两句话的背后逻辑完全不同。前者是大模型作为程序里的一个函数把输入传进去拿输出后者是大模型作为决策循环里的核心每一步做什么由它自己判断。Task05要建立的第一个认知就是这个——Agent的本质是循环不是调用。这个循环长什么样其实就是经典的ReAct模式模型先想一步Thought再决定调什么工具Action拿到工具结果后继续观察Observation然后再想下一步直到它认为自己可以给出最终答案。整个过程像一个不停自问自答的工作坊模型既是师傅也是学徒。1.2 RAG和Agent不是竞品一个解决知识入口一个解决行动自主做Task05之前我对RAG和Agent的关系也是糊涂的总觉得它们应该二选一。后来用一个表格想明白了维度RAGAgent核心问题怎么让模型获得外部知识怎么让模型完成外部行动决策权检索和生成流程基本固定流程由模型动态决定典型组件向量库、Embedding、检索器规划循环、工具集、记忆失败表现答错、答得空泛做错步骤、卡在循环里和LLM的关系LLM是读材料的答题者LLM是做决策的操盘手实际项目里RAG和Agent经常一起出现Agent要回答问题但知识库里没有相关内容它需要先检索再回答。甚至可以说RAG是Agent的一个工具类型只是这个工具特别常用常被单独拿出来讲。学习的时候别把它们设定成对立面它们解决的是两个不同层面的问题。1.3 harness vs Agent到底在争什么最近社区里关于harness和Agent区别的讨论挺多我第一次看到也愣了以为是两种Agent实现方案。查了一圈才明白这俩根本不是同一个层面的东西。harness指的是Agent运行的外壳工具执行环境、沙箱隔离、代码解释器、生命周期管理、日志收集。它管的是Agent怎么被安全地跑起来。Agent更多指的是决策策略思考流程、工具选择逻辑、停止条件。它管的是Agent下一步做什么。打个比方harness是赛车的底盘和防护结构Agent是驾驶策略。你不能说底盘和驾驶策略哪个更重要因为它们根本不在同一个维度上。很多Agent平台出问题恰恰是策略设计得挺好但harness没兜住——比如代码执行没有沙箱隔离或者工具超时没有处理导致整个任务异常终止。顺带澄清一件小事Agent这个词在不同领域的含义差别很大。比如ROS2里有个micro-ROS agent那是负责设备通信的桥接组件和LLM Agent八竿子打不着。搜索学习资料时还可能遇到Agent Ransack这种老牌文件搜索工具关键词一搜全是干扰。学Agent第一步先把资料的语境分清。2. Agent的四件套Planning、Tools、Memory、ActionTask05前我一直以为Agent是调了Function Calling的聊天机器人做完才知道一个能稳定干活的Agent至少要有四个模块协同规划、工具、记忆、行动。这四块单独看都不难难的是让它们在一个循环里配合好。2.1 规划不是写一份计划书而是一个循环规划这个模块最容易被人误解成让模型写一份详细的步骤计划。如果只是写计划模型写完就完了根本不会去执行更不会根据执行结果调整计划。真正有用的规划是一个执行循环。当前Agent的规划方式大体分两类。一类是ReAct式走一步看一步每步都根据最新观察做决策。好处是灵活、能应对意外坏处是长任务容易走偏、token开销大。另一类是Plan-and-Execute式模型先把整个任务拆成阶段计划再逐段执行执行完一段回来评估计划要不要调整。好处是长任务主线清晰坏处是计划一旦写得不对后面容易带着错误认知一路跑到底。我自己的体会是Task05这个阶段先无脑用ReAct就好。因为ReAct的实现简单调试直观出了问题很容易定位是哪一步想错了还是工具错了。Plan-and-Execute适合任务边界已经比较清晰、只是执行链条长的场景等你有能力设计计划修正机制再上不迟。面试时这个问题出现频率也高ReAct和Plan-and-Execute有什么区别用上面这套思路答就行本质区别是决策频率和任务粒度的取舍。2.2 工具Agent的手脚Function Calling和MCP工具模块是Agent能做事的关键。没有工具Agent再聪明也只是个文本生成器。工具的本质很简单一个函数加上一段让模型理解什么时候该用、参数怎么填的描述。Function Calling的核心机制是你给模型一组JSON Schema格式的工具定义模型根据用户请求决定调用哪个工具、填入什么参数然后把调用意图以结构化数据返回给你。真正执行函数的是你的程序不是在模型内部执行。这个边界意识要建立起来——模型的输出永远只是调用请求不是执行结果。写工具描述时有个容易被低估的细节模型是通过描述来判断工具用途的描述写得含糊模型就会用错工具。我试过把一个创建任务的工具描述写成创建一条新任务记录模型在用户问帮我安排明天上午开会时反而去调了日程工具。把描述改成当用户需要新增待办事项时创建记录注意区分日程会议和待办事项之后选择准确率明显上升。工具接入这块MCP现在成了事实标准。它的核心思路是把工具、数据源、资源统一成标准协议让Agent平台和本地服务通过MCP Client/Server通信。相比早期每个框架自己定义一套工具协议MCP至少让一次接入、多处复用成为可能。另一股力量是Skills——把一套复杂的、多步骤的能力打包成可复用的技能包。二者不是替代关系MCP解决工具怎么连Skills解决复杂能力怎么打包复用。2.3 记忆短期、长期和工作记忆记忆模块被很多教程一笔带过但实际做Agent时它才是麻烦最多的部分。我把记忆拆成三层理解短期记忆就是当前对话的上下文窗口模型在这一次任务里记得的东西。很多人以为上下文窗口越大越好其实不然——窗口大了检索进去的噪声也多模型反而容易迷失重点而且成本直线上升。长期记忆是存在外部存储里的信息比如向量库、KV存储。它的作用不是让每轮对话都把所有历史倒进去而是按需取回。用户第二次来访时把身份、偏好、历史关键结论检索出来拼进这次对话的上下文里这才是合理的用法。工作记忆这个概念我特别想多说一句。Agent在执行复杂任务时中间状态进行到哪一步了、已经确认过什么信息、还有哪些不确定项是很容易丢失的。一个简单的做法是在系统提示里固定一个状态槽位通过工具调用来读写这个槽位。任务完成后归档到长期记忆任务中断时可以从工作记忆恢复。这个思路不需要复杂架构却能让Agent的连续性上一个台阶。核心原则只有一句话记忆不是全都记住而是需要时给得到、给得准。2.4 行动从决策到执行的最后一步最后把决策变成现实的动作就是行动模块。提到行动很多人第一反应是调一个API或者运行一段代码实际上行动模块的坑在安全边界上。执行Agent生成的代码必须放在沙箱里执行外部命令必须限制工作目录和网络权限。现在不少编码Agent工具偶尔会提示更新agent沙盒其实就是沙箱环境需要重建或配置变更本质是在提醒你代码执行是隔离的不等于直接在你电脑上裸奔。行动模块还有一个容易被忽视的组件叫Artifacts也就是Agent生成的产物报告、代码包、图表、配置文件。好的Agent会把每次行动的产出物结构化保存而不是只返回一段文字。这样用户看到的不只是我帮你做了X而是这是做X的成果可下载、可复用。3. Task05的实跑记录我手写了一个最小Agent理论拆完就该动手了。Task05的作业是实现一个能自主调用工具完成任务的Agent我一开始也想偷懒直接上LangGraph后来想了想还是决定先用原生Function Calling手写一遍循环。这个决定让我后面读框架文档时轻松了非常多。3.1 为什么不用LangGraph直接上先写循环再用框架现在Agent框架一抓一大把新手上来就用框架容易产生一个错觉以为Agent就是把几个Node连起来。实际上框架替你做的核心事情——状态传递、循环控制、工具结果回填——恰恰是理解Agent的关键。手写一遍最小循环你才能直观感受到模型返回的工具调用请求长什么样、多个工具连续调用时上下文怎么拼接、工具抛异常时循环该往哪走。这些体验用框架是学不到的。我的建议是学习阶段至少手搓一次循环生产阶段再考虑框架。3.2 最小Agent的完整代码我选的方案是OpenAI SDK的Function Calling加本地Python函数没有用任何Agent框架。代码如下import json from openai import OpenAI client OpenAI() # 1. 定义工具 def get_weather(city: str) - str: 模拟查询城市天气 return f{city}晴25℃湿度40% def calculator(expression: str) - str: 安全计算简单四则运算 allowed set(0123456789-*/(). ) if not all(c in allowed for c in expression): return Error: 非法字符 return str(eval(expression, {__builtins__: {}}, {})) 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: 数学表达式如 (35)*2} }, required: [expression] } } } ] def call_tool(name: str, args: dict) - str: if name get_weather: return get_weather(args[city]) elif name calculator: return calculator(args[expression]) return f未知工具: {name} SYSTEM_PROMPT 你是一个能调用工具的助手。 你可以查询天气、执行数学计算。 调用工具时如实根据工具返回值继续推理。 如果可以回答用户直接给出最终答复不要再调用工具。 def run_agent(user_input: str, max_steps: int 5): messages [{role: system, content: SYSTEM_PROMPT}, {role: user, content: user_input}] for step in range(max_steps): response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolsTOOLS, tool_choiceauto ) msg response.choices[0].message messages.append(msg) # 把模型消息可能带工具调用放入上下文 if not msg.tool_calls: return msg.content for tc in msg.tool_calls: result call_tool(tc.function.name, json.loads(tc.function.arguments)) messages.append({ role: tool, tool_call_id: tc.id, content: result }) return 已达到最大步骤限制任务未完成 print(run_agent(北京今天天气怎么样顺便算一下(35)*2等于多少。))这段代码看着短其实把Agent循环最核心的骨架都包含了System Prompt约束行为、工具Schema定义、循环把工具调用结果回填、终止条件。我后来用LangGraph重写同样功能代码量多了三倍但本质逻辑和这个循环一模一样——框架只是把这个循环抽象成了图。3.3 实测一次真实运行Agent是怎么一步步干活的跑上面的代码输入北京今天天气怎么样顺便算一下(35)*2等于多少模型实际执行过程是这样的第一步模型判断用户有两个需求返回两条工具调用请求get_weather(city北京)和calculator(expression(35)*2)。注意模型一次可以返回多个工具调用说明它理解了这两个需求相互独立。第二步程序依次执行两个工具把结果以roletool的消息回填给模型。第三步模型拿到两份工具结果后不再返回工具调用直接生成最终答复北京今天晴25℃至于(35)*2结果是16。这个流程看起来简单实际运行时有个细节特别容易出问题工具结果回填时必须携带tool_call_id而且必须放在模型那条带工具调用的消息之后顺序不能乱。顺序错了API直接报错id丢了模型会认为工具结果和调用请求对不上。这是手写Agent循环最容易踩的第一个坑。3.4 跑通过程中最常见的红色报错用LangChain或类似框架跑Agent时经常会看到一行红字Agent execution terminated due to error.。我第一次看到这行字以为是自己代码写错了排查了半天发现不是。这句报错的意思是Agent执行循环在某个环节抛了异常框架默认策略是终止任务而不是重试。触发它的原因通常是三类。一类是工具本身抛异常比如网络接口超时、文件不存在——这类需要在工具函数内部捕获并返回对模型友好的错误描述而不是让异常直接冒出去。另一类是上下文达到模型最大token工具返回太长把窗口挤爆——需要对工具结果做截断或摘要。还有一类是JSON解析失败模型偶尔会返回带格式错误的工具参数——稳妥做法是解析失败时让模型重新生成一次。我的处理方案是在工具函数层加重试和异常兜底在循环外层加总的步数限制防止模型陷入不断调用工具但始终不给出最终答案的死循环。这看起来都是小事但真实项目里Agent的稳定性恰恰是靠这些细节堆出来的。4. 从能跑到能扛并发、安全、记忆、评测四道坎把最小Agent跑通之后下一步自然是考虑怎么放进真实系统。这一下就撞上了四个现实问题任何一个处理不好都会翻车。4.1 并发一只Agent看着没多快为什么一上量就崩先说并发。第一次被人问AI Agent怎么扛并发时我一开始没当回事——不就是一个API调用吗限流不就行了后来压了一下才发现问题比想象中大。Agent慢的根本原因在于一次任务不是一次LLM调用而是多次。每做一步决策就要等一次模型推理一次完整任务往往要往返3到10次每次往返几百毫秒到几秒。外部工具调用还可能有额外耗时。这意味着一个Agent任务的最短时间不是一次API的延迟而是整个决策链的累积延迟。扛并发有套组合拳。第一所有Agent执行丢进异步任务队列不占用请求线程。第二给单次任务设置总超时和每步超时防止一个失败任务拖死整条队列。第三外部API做好限流与指数退避重试。第四把多个Agent实例复用同一个上下文缓存和记忆存储减少重复检索。我自己测试的结果是不做并发控制20路并发就能把延迟从2秒拉到15秒加了队列和超时控制后200路并发延迟基本稳定在3秒左右。import asyncio import random async def run_one_task(task): try: result await asyncio.wait_for(agent_execute(task), timeout30) return result except asyncio.TimeoutError: return timeout except Exception as e: return ferror: {e} async def main(tasks): sem asyncio.Semaphore(20) # 控制最大并发数 async def with_sem(t): async with sem: return await run_one_task(t) return await asyncio.gather(*[with_sem(t) for t in tasks])这段代码就是信号量限流超时兜底的典型套路生产环境再往上一套队列和监控就行。核心心法别指望Agent像普通接口一样快要做的不是让它更快而是让整个系统在慢的情况下不崩。4.2 安全给Agent的每分权限都要说明理由Agent安全是个老生常谈但真的会在生产里流血的话题。我的原则很简单给Agent的权限每一分都要列出理由。工具权限最小化是第一道防线。Agent能用什么工具、不能用什么工具决定了它能造成的破坏上限。给一个客服Agent配查询订单权限没问题如果顺手配了修改订单金额权限一旦提示词被恶意注入后果就是灾难性的。高风险操作必须加人工确认。删除文件、转账、发布内容、修改配置这类操作要么不让Agent执行要么执行前必须走一个确认接口。别嫌麻烦我见过太多Agent误删生产数据的案例问题几乎都不是模型坏而是权限给得太宽。代码执行一定要放进沙箱。Agent生成的代码是模型写的模型会犯错也会被诱导生成恶意代码。沙箱隔离网络、限制文件系统访问、控制资源配额这是编码类Agent的底线。还要防输出注入。模型生成的回复并不天然可信把它作为结构化数据解析时要做白名单校验。比如模型返回执行成功程序去比对真实退出码而不是直接相信这句话。安全设计不是给Agent加锁而是给整个系统加安全带。4.3 记忆不是把所有聊天记录塞给模型随着Agent承担的任务越来越复杂记忆问题会自己找上门。很多人的第一反应是把历史对话全塞进上下文不就完了。这个方案在小场景能跑一放大就废token暴涨、成本飙升、模型被历史中的噪声信息干扰。我现在的做法是分三层。第一层是工作记忆Agent任务执行过程中用专门的结构化槽位记录中间状态任务结束即归档。第二层是短期记忆只保留最近几轮对话关键信息超过窗口就做摘要压缩。第三层是长期记忆按照用户身份、业务事实、历史结论分门别类放到向量库或KV存储下次任务开始时按需检索。检索式记忆的核心不是存了什么而是取什么。判断标准是这条历史信息对当前决策有没有帮助没有帮助就不取。这个取舍做得越严格Agent的上下文越干净决策反而越准。Task05的笔记里这个点我写了两遍因为它是最反直觉但最影响效果的地方。4.4 评测没有标准答案的任务怎么判断好坏最后一个坎是评测。传统程序写个单元测试就行Agent任务经常是开放式——帮我写一份市场分析报告这种任务怎么断言好不好我的实践分四步。第一步建立任务用例集至少覆盖正常场景、边界场景、异常场景各几条。第二步定义量化指标任务成功率有没有完成任务、步骤效率调用了几次工具、多少次是无效的、成本token消耗、安全违规次数。第三步用自动化和人工组合打分可机检的用规则断言开放式的让评审模型打分关键用例人工复核。第四步每次修改Agent配置后全量重跑一遍用例集。这个流程看着朴素但坚持做下来效果极好。我踩过的坑是没有用例集就改提示词结果每改一次提示词都像开盲盒A场景好了B场景崩了。有了回归用例集Agent迭代才谈得上可掌控。5. 框架怎么选学完Task05之后我对主流Agent框架的认识手写一遍循环之后回头再看各个Agent框架感觉完全不一样了。之前是看文档看不懂它在说什么现在是看文档能猜到它内部大概怎么实现。这个阶段再做框架选型思路清晰很多。5.1 主流框架横向对比我把Task05前后接触到的框架放在一张表里对比维度选了入门难度、控制粒度、多Agent支持和最适合的场景框架入门难度控制粒度多Agent支持最适合场景裸SDK Function Calling较低最细需自己实现学习理解、小型工具链LangGraph中细显式状态图支持复杂状态流、需要显式控制AutoGen中中强多角色对话多Agent讨论、协作任务CrewAI低中强角色分工快速搭建角色化团队OpenAI Agents SDK低中支持OpenAI生态、快速原型扣子/Coze、Dify等低代码平台低弱视平台而定非代码背景快速验证产品Spring AI / Google ADK中中部分支持Java/服务端团队集成这个表只能当参考因为框架迭代速度太快各自的能力边界每天都在变。选型更重要的还是看团队背景和场景约束。5.2 我的选型建议按实际项目情况给的选型建议就三条。第一如果团队刚接触Agent、任务相对单一直接用裸SDK加Function Calling不要上框架。把核心循环、工具、记忆先跑明白再考虑框架带来的便利。第二如果任务是复杂状态流转比如多轮条件分支、需要人工介入的流程LangGraph这种显式状态图更合适。它能让你看清每个状态的转移条件出问题也好定位。第三如果目标是快速验证产品原型或者团队没有专职开发低代码平台是性价比最高的选择。它们的编排界面能拖拽出很多东西够用且快。一个我在面试和实际协作中反复强调的点不要为了多Agent而多Agent。多Agent的通信开销、角色冲突、成本翻倍都是真实代价。90%的场景一个Agent循环就能解决多Agent适合的是真需要不同角色视角碰撞的任务不是把一个Agent拆成三个显得高级。5.3 从代码到产品还缺什么跑通框架只是起点。把Agent放进真实产品还缺三块看起来不性感但决定成败的部分。可观测性Agent每一步的思考、工具调用参数、耗时、token消耗全部要有日志和追踪。否则线上出问题你根本不知道它为什么这么做。日志还可以拿来反哺评测集新发现的失败案例就是最好的回归用例。配置管理提示词、模型参数、工具列表、知识库版本这些都要纳入版本管理。我见过不止一次昨天还好好的今天突然全崩最后发现是有人改了提示词没通知任何人。Agent系统的脆弱性决定了配置变更必须可追溯。重试和降级模型调用会失败工具会超时网络会抖动。所有环节都要有重试策略和降级方案。核心心法是Agent永远不是百分百可靠的组件系统设计要默认它会出错然后给它一条体面的失败路径。6. Task05结束后我给自己定的三条纪律学习笔记的最后我不想写那种通过本Task我学会了的套话。记三条我给自己定的实际纪律算是这个阶段最想留下的东西。第一条先跑最小闭环再谈智能。任何Agent功能先让它在一个最简单、最窄的场景里完整跑通再逐步扩展。第一次跑通自动查天气并回答时那种它真的自己完成了的感觉比在PPT里画十个规划节点都值钱。先有循环再谈聪明。第二条把会出错放在设计第一位。我在Task05期间被一句话打醒Agent系统的失败不是异常是常态。设计时先问它会在哪里出错再问它怎么做好。工具异常、上下文超限、循环卡死、输出注入这些问题能提前想到的统统提前设计兜底不要上线后靠运气。第三条用用户委托而不是模型能力来定义Agent的边界。我一度想把Agent做得能力越强越好后来发现方向不对。Agent应该做的是用户托付给它做的事情而不是它能做的一切事情。边界清楚了权限才清晰安全才可控产品体验才不跑偏。这个原则后来帮我挡掉了至少三次让Agent全自动操作后台的危险需求。Task05学到这里后面还有Task06、Task07但我觉得自己已经摸到Agent的门槛了——不是会调用API那种门槛而是理解了一个Agent系统怎么思考、怎么出错、怎么防错的那种门槛。如果你正在学这一课希望你也能先手写一遍循环再上框架把这块地基打扎实。