1. 大模型只会“说”不会“做”:Agent到底补上了哪一环
过去一年里,身边越来越多的人开始把大模型接入自己的业务系统。聊天机器人、文档问答、代码生成这些“输入一段文本,输出一段文本”的场景,基本已经被卷成了红海。但我自己的体感是:真正让大模型产生实际业务价值的,不是它能写出多漂亮的回答,而是它能不能替你把一件事办完。
举个例子。你让大模型“帮我查一下这个客户的订单状态,如果逾期了就发一封催款邮件”。纯语言模型能做到什么程度?它能给你一段话:“好的,我可以帮你写一封催款邮件,内容如下……”然后你复制、粘贴、打开邮箱、手动发送。这叫什么?这叫“建议助手”,所有的脏活累活还是你在干。
而AI智能体(Agent)要做的事情,是让模型自己去查数据库、判断逾期状态、调用邮件接口、把邮件发出去,然后在你的工作群里回一句:“客户A的订单已逾期3天,催款邮件已发送,抄送财务。”这一步之差,就是“语言模型”和“行动助手”的分水岭。
我最早接触Agent这个概念是在研究ReAct模式的论文时。那时候“Agent”还没有被炒得这么热,更多是一种把“推理”和“行动”交替执行的技术范式。后来各家框架层出不穷,概念包装越来越花哨,但拆开来看,核心其实一直没变:让大模型不仅会生成文本,还会在合适的时候选择调用外部工具,并根据工具返回的结果继续推理,直到完成一个明确的任务目标。
这篇文章我沉淀了一年多的实际开发经验,从原理到落地,从框架选型到稳定性工程,写给那些已经会写提示词、会调API、但还没系统搞过Agent的人。看完你会知道:一个真正的Agent系统由哪几部分构成、核心循环怎么写、工具调用怎么设计、任务规划怎么做,以及上线之后那些文档里不会写但一定会踩的坑。
2. 拆开Agent的骨架:推理、规划、记忆与工具调用的协作方式
很多人第一次看到Agent的Demo时觉得“好神奇”,但轮到自己写就无从下手。根本原因在于把Agent当成一个“模型”来理解,而不是一个“系统”。我倾向于用一句话概括Agent的本质:它是一个以LLM为大脑的闭环控制系统。
这个闭环里,至少要有四个部件协同工作。
第一是推理引擎。这是Agent的“大脑”,通常就是一个大模型,负责理解用户的自然语言目标,生成下一步动作的计划。推理质量直接决定了Agent的上限——你用一个小参数模型做复杂推理,再好的工具也白搭。
第二是规划机制。规划解决的是“先做什么、后做什么”的问题。最简单的规划是一次性生成一个完整步骤列表,然后逐步执行;进阶一点的是每一步执行完根据结果动态调整下一步(也就是Plan-and-Execute的变体)。很多失败的用户体验,都源于规划环节缺少动态修正。
第三是记忆系统。记忆分两层:短期记忆(当前任务上下文和工具返回结果)和长期记忆(跨任务的历史偏好、常用操作方式)。在实现上,短期记忆就是对话历史里塞入的工具输出;长期记忆则需要向量数据库做检索增强。没有记忆的Agent是“失忆的助理”,每句话都要重复交代。
第四是工具调用能力。这是Agent和普通聊天机器人最大的区别,也是标题里“语言模型”变成“行动助手”的关键一跃。工具的本质是一组“函数”,模型根据任务需要选择调用哪个函数、传什么参数,得到返回结果后再决定下一步。
这四个部件组合起来,就是一个典型的ReAct循环:
用户目标 -> 模型推理 -> 决定调用某个工具 -> 执行工具 -> 返回结果 ^ | |________________ 模型基于新信息再次推理 <______________|我在实际项目里最深的体会是:很多人把精力花在选模型、调提示词上,结果系统还是经常出错。因为问题往往不在“大脑”,而在工具接口定义和记忆管理的粗糙程度。工具接口定义得不清晰,模型就不知道该传什么参数;记忆管理得混乱,模型就会在长任务里逐渐“失忆”。
接下来我用一个真实的项目复盘来说明,这些抽象概念到底是怎么变成一行业代码的。
3. 从零跑通一个Agent:以自动化工单助手为例的完整开发过程
我最近做了一个内部用的“IT工单分类与处置助手”,功能不算复杂:员工提交一条文字描述(比如“会议室投影连不上”),Agent自动完成分类、查询设备归属、发送处理指引,并在符合条件的情况下直接创建维修工单。选择这个项目做例子,是因为它覆盖了Agent开发中最典型的三件事:工具规划、状态流转、外部系统交互。
3.1 明确任务边界:Agent不是什么都干
开工前我做的第一件事,不是写代码,而是给Agent划边界。这个边界写在系统提示词里,同时也写进了工具命名规范里:
- 只处理IT类工单,其他问题一律转人工;
- 分类判断置信度低时,不允许硬猜,必须向用户追问关键信息;
- 任何涉及账户权限变更的操作,一律不执行,只生成建议。
这条边界非常重要。没有边界的Agent会在“有用”和“闯祸”之间反复横跳。你当然希望它能干更多事,但代价是未知风险。我的建议是:第一版Agent宁可划窄一点,把核心闭环跑通,再逐步放开。
3.2 工具调用的接口设计:模型能“看懂”比“能调用”更重要
工具调用的接口定义质量,直接决定Agent的智能表现。同一个功能,接口设计得模糊和清晰,效果天差地别。
以“查询设备信息”为例。第一版我定义的工具参数是这样的:
tools = [ { "type": "function", "function": { "name": "query_device", "description": "查询设备信息", "parameters": { "type": "object", "properties": { "keyword": {"type": "string", "description": "设备关键词"} }, "required": ["keyword"] } } } ]看起来没问题对吧?实际测试时模型经常传一些模棱两可的关键词,比如“会议室那个东西”,返回一堆无关结果。后来我改成了这样:
{ "name": "query_device_by_name", "description": "根据设备完整名称或设备编号查询状态。设备编号格式:ITD-六位数字。用于工单处置前确认设备归属和保修状态。", "parameters": { "type": "object", "properties": { "device_id": {"type": "string", "description": "设备编号,如 ITD-001234"}, "device_name": {"type": "string", "description": "设备全称,如 EPSON 投影仪 CB-X05"} }, "required": [] } }差别在哪?后者给了模型更多“引导信息”:编号格式、使用场景、参数示例,甚至暗示了“如果不知道编号,就提供完整名称”。模型不是人,它不会“猜”你的意图,但它在选择参数时会参考description里的每一个措辞。所以接口文档写得越像“给新同事的交接文档”,模型的表现就越好。
3.3 核心循环代码:一个极简但完整可用的Agent框架
我第一版没有引入任何第三方Agent框架,直接用原生接口写了核心循环。原因很简单:先用最少的依赖把业务逻辑跑通,再考虑是否换框架。核心代码大致是这样:
# 这是一个极简的Agent执行循环,核心逻辑约50行 import json def agent_loop(user_request: str, tools: dict, max_rounds: int = 8): messages = [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_request}] for round_idx in range(max_rounds): response = llm_chat(messages, tools_schema) msg = response.choices[0].message # 判断模型是否请求调用工具 if msg.tool_calls: messages.append(msg) for tool_call in msg.tool_calls: tool_name = tool_call.function.name args = json.loads(tool_call.function.arguments) result = tools[tool_name](**args) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) continue # 无工具调用,说明Agent认为任务已完成 return msg.content return "已达最大轮数,任务未完成,请减少范围或补充信息"这个循环虽然简单,但包含了Agent运行的全部关键节点:模型决策是否调用工具、执行工具并回填结果、基于新信息再次推理。实际项目里我会在循环之外加日志、限流、超时控制和结果校验,但核心骨架就是这几十行。
调试时的技巧:每轮循环都打印一条形如[Round 2] 调用工具 query_device_by_name(device_name='EPSON投影仪') -> 返回 3 条结果的结构化日志。看日志是Agent开发中最常用的调试方式——你能直观看到模型在哪一步开始跑偏、哪一步传错了参数。
3.4 任务规划:从“一口气做完”到“分步走”
第一版我的Agent面对复杂请求时,容易一次性生成所有步骤然后按顺序执行。这种静态规划的毛病是:如果中间某一步结果和预期不符,后续步骤全错。后来我改成了两步走:先做一次粗略拆解,然后每执行完一步就让模型重新审视剩余计划。
在工单场景里,这个改动体现为:Agent先判断工单需要“查询设备”“核对保修期”“创建维修单”三步,执行完“查询设备”后如果发现该设备已过保,它会自动追加一个“生成自费维修报价说明”的步骤,而不是傻傻地继续创建维修单。动态规划的核心是给模型“每一步都重新做决定”的机会。
这里有个效率与智能的权衡:动态规划会让每一轮多一次推理调用,成本和时间都会上升,但对任务的完成率提升非常明显。简单任务我用静态规划,复杂任务用动态规划,这个开关通过系统提示词里的“任务复杂度判断”来控制。
3.5 记忆与多轮对话:为什么上下文管理直接决定成败
工单场景下,员工可能会十五分钟后回来追加一句“哦对了,那个投影仪在3楼不是2楼”。如果Agent没有合理的记忆机制,要么忘了前面的内容,要么把新旧信息搞混。
我的做法是把记忆分成两层:
- 会话记忆:直接塞进消息列表,保留用户最近5轮对话和全部工具输入输出。这里要特别小心工具输出过大——我遇到过工具返回一张巨大的表格,直接把上下文窗口塞爆的情况。解法是给工具输出做截断和摘要,只保留关键字段。
- 业务记忆:存放在向量库里,按员工ID检索。比如“该员工上次报修过同一台设备”“该员工偏向于电话沟通”这些特征,在每次会话开始时作为系统提示词的一部分注入。
说一句踩过的坑:长期记忆不是越多越好。曾经我在系统提示词里注入了该员工近半年的所有工单记录,结果模型被大量历史干扰,新任务的分分类反而变差了。后来调整为“只注入与本次问题关键词相关性超过阈值的3-5条记录”,分类准确率明显回升。
4. 上线之后才是真考验:稳定性、成本与智能边界的工程化处理
Demo能跑通不代表能用,这是Agent开发和普通后端开发最大的不同。普通接口是“输入合法参数就返回预期结果”,Agent是“输入合法意图也不保证结果正确”,这种不确定性对工程化提出了很多额外要求。
4.1 超时与重试:Agent的“蠕动”比慢更可怕
Agent任务通常是长耗时任务,一个复杂工单可能要调用7-8次工具,每次推理都要2-4秒,整体耗时可能超过30秒。用户体验上,不能只是干等。我加了两个机制:
一是流式状态推送。每完成一个步骤,就通过WebSocket向前端推送一条状态,比如“正在查询设备状态...”“正在核对保修期...”。用户虽然等得久,但知道系统在干活,心理体验完全不同。
二是超时熔断。某个工具超过5秒不返回就终止本轮调用,给模型返回一个“工具超时”的占位结果,让它决定是重试还是换方案。这个设计是向微服务的熔断机制学的——不要让单个慢工具拖垮整个Agent循环。
4.2 成本控制:一次任务烧掉几十万token的教训
Agent的token消耗远比普通对话要高。一个看似简单的任务,中间工具返回结果、模型重新推理、历史累积,几次循环下来可能消耗几千token。如果是复杂任务并且不加控制,一轮任务烧掉几万token很常见。
我的成本控制三板斧:
- 工具输出压缩:大段JSON先提取关键字段再回填给模型;
- 历史摘要:超过一定轮数,把前面轮次的消息做一轮摘要,替代原始内容;
- 预算配额:几乎所有的API都支持max_tokens参数,我还会在代码层面记录每个会话的累计消耗,超过阈值就强制结束任务并要求用户确认。
成本控制不只是一个财务问题,更是产品问题。我见过一个团队Demo阶段跑得很爽,上线后老板看到账单直接叫停项目。Agent能干事不等于可以无限干,每一轮思考都要花钱。
4.3 结果校验:让Agent学会“认错”
模型生成的结果有时看着合理,实际是幻觉。在工单场景里,最怕的是它“一本正经地瞎编一个设备编号”。
我做了三层校验:
- 格式校验:所有工具返回的JSON必须走schema校验,不合法就报错;
- 逻辑校验:比如Agent说“已创建工单”,但创建接口返回的状态码不是成功,就必须强制重试;
- 人工兜底:高风险操作(改权限、删数据)必须经过人的确认按钮。这不是技术限制,是产品底线。
这里想多说一句:在Agent的工程化中,“承认失败”的能力比“假装成功”的能力重要得多。系统中要有清晰的失败路径——告诉用户“我没法确认这个信息,请人工处理”,而不只是给一个可疑的结果。
4.4 评估体系:没有评测集,你都不知道Agent有没有变强
Agent开发最头疼的问题是“改动一处,不知道哪里又坏了”。因为模型行为不是确定性的,你改了一版系统提示词,很可能一个场景变好,另一个场景变差。
我建议从第一天就建设评测集。每个预期场景准备10条以上测试用例,覆盖正常流程、异常输入、边界情况。每一次改动后在评测集上回归一遍,这比任何代码评审都有效。自动化跑评测时,只要有一条用例的最终状态不对,就立即定位是哪一轮决策出问题。
这个工作前期投入大,但后期收益极高。我在工单项目里跑了上千条评测样本之后,才敢把Agent从“内测”转为“全员开放”。
5. 再往前走一步:多Agent协作与Agent能力的横向扩展
单Agent做成型之后,自然会遇到天花板:一个Agent什么都会一点的代价就是什么都不精。我在工单系统里就是一个Agent承担了分类、查询、创建工单、回复用户所有职责。随着场景增多,系统提示词越来越臃肿,工具数量越来越多,模型每次决策需要扫描的工具描述也越来越长,既费token又影响准确性。
解法是拆分多Agent:分类Agent只负责理解意图和做粗分类,处置Agent只负责执行具体工单操作,答复Agent只负责生成沟通话术。它们之间通过一个简单的“调度消息”衔接:分类Agent输出结构化结果(类别+关键实体),调度器根据结果路由给对应的处置Agent。
这个模式天然适合任务边界清晰的业务。各Agent的系统提示词精简,工具集也各自独立,模型要做的决策变得简单直接,准确率自然提升。
纵向能力的扩展方面,我比较看好的是给Agent加“技能包”。所谓技能包,就是把一组高度相关的工具封装成一个可复用的操作单元。比如“会议预定技能”内部门禁可以查会议室空闲时间表、预订会议室、通知参会成员,外部系统对接三个动作,对外只暴露两个入参(时间、人数)。这个设计很像RPA里的自动化组件,好处是Agent只需要学会“怎么用技能包”,而不必理解每个内部工具。技能包用久了,它甚至可以变成一套组织内部的API标准,新场景开发时直接复用。
但这部分我不建议初学者马上就去追。先把你手里那一个Agent做到“稳定好用不出错”,再考虑让它“长出更多手脚”。多Agent的调试难度是单Agent的数倍,因为错误会在Agent之间传播。我在工单系统里把单Agent跑到91%的任务完成率之后才开始尝试拆分。拆分之后单类任务准确率有提升,但整体调试链路复杂度也上了一个台阶。对一个刚入门的团队来说,先集中精力打好单Agent的底子,更稳妥。
最后说一点个人体会。Agent开发这件事,技术门槛其实没有想象中那么高——核心循环就几十行代码,难点在于你怎么定义好工具接口、怎么管理好上下文、怎么设计好评估集、怎么规划好失败路径。这些能力不是看书能看会的,而是要靠一个个真实业务的打磨。我在工单助手这个项目里最大的收获,不是代码写得多漂亮,而是学会了怎么“限制”一个Agent——限制它的边界、限制它的权限、限制它的想象力。真正可靠的智能体,不是什么都敢做,而是知道什么不该做。