这两年做AI应用开发,最常被朋友问的一句话是:“我把GPT接到业务里做了个Agent,你帮我看看效果怎么样?”点开一看,绝大多数其实是套了一层提示词的聊天机器人。这不是咬文嚼字,聊天机器人和智能体(Agent)的架构、故障模式、迭代方式完全不同,搞混了后面会吃大亏。这篇文章就用最直白的方式,把一个大模型智能体的简易流程讲清楚:一个任务进来之后,Agent内部到底经历了什么,你应该怎么设计工具、怎么选框架、怎么避开最常见的坑。目标是让有基础编程能力,或者天天泡在AI工具里的人,看完能搭出第一个真正干活的Agent。
1. 先泼一盆冷水:聊天机器人不等于智能体
1.1 用“会不会主动做事”来判断
很多人把Agent当作“升级版聊天机器人”:接上大模型API,写一段系统提示词,再套一个好看的网页,就对外叫Agent。这种理解最大的问题,是把“响应”当成了“行动”。
聊天机器人的工作模式是:用户输入 → 大模型生成文本 → 展示。整个过程只有一次模型调用,模型没有渠道去获取任何外部信息,只能依赖自己的内置知识。而Agent的工作模式是循环式的:用户给一个目标 → 模型判断需要什么信息 → 调用工具获取信息 → 基于反馈继续推理 → 直到目标达成。
所以,“会不会根据用户的目标主动调用外部工具”,是我判断一个系统到底是不是Agent最直接的试金石。这不是说聊天机器人不好,很多客服场景它反而是最合适的选择;而是说,如果你想解决的是“需要实时数据、需要操作外部系统”的问题,那就必须用Agent架构。为了更直观,我列个对比:
| 对比项 | 聊天机器人 | 智能体(Agent) |
|---|---|---|
| 核心模式 | 一问一答 | 任务循环 |
| 模型调用次数 | 通常一次 | 多次,直到任务完成 |
| 外部工具 | 不调用 | 主动调用 |
| 典型问题 | 知识截止、编造数据 | 循环失控、工具出错 |
1.2 大模型的能力边界决定了Agent的必然性
大模型隐含的假设是“知识都在参数里”,但这一点在实际业务中会带来三个硬伤。
第一,知识有截止日期。训练完毕的那一刻,它对世界的认知就冻结了,最新的事件、行情、政策它都不知道。第二,它没有实时交互能力。问“现在几点”“今天上海天气如何”“这个订单发货没”,它回答不了,因为参数里没有这些状态。第三,它会一本正经地编造数据。让它猜一个具体数字,它宁可编一个看起来合理的数字,也不愿意承认自己不知道。也就是说,大模型真正擅长的是“理解意图、拆解步骤、组织表达”,不擅长的是“精确地读取和修改现实世界状态”。
Agent恰好补上了这块短板:把模型当作大脑,把工具当作手脚,让大脑指挥手脚去获取事实、完成任务。看懂了这个前提,你就能明白为什么现在所有主流Agent方案都在围绕“工具调用”做文章——工具是模型和现实世界之间唯一的通道。
1.3 一个Agent应该具备的四个特征
如果让我给Agent下个定义,至少要包含这四件事,缺一个都不完整:
- 目标驱动。输入是一个“任务”,比如“把这份Excel里的低库存商品整理成预警清单并发给我”,而不是一句简单的问候。
- 自主规划。由模型决定先做什么、再做什么,步骤不是程序员预先写死的。
- 工具调用。能调用至少一个外部函数、API或服务,去获取信息或产生影响。
- 反馈修正。拿到工具结果后,发现自己原来的计划有问题,能调整计划继续执行。
这四条是递进的。如果只做到了“任务输入+预设工作流”,那算半个Agent;如果只有对话没有工具,那就还停留在聊天机器人阶段。理解了这些基础,下面看看Agent内部到底是怎么跑的。
2. 一个Agent跑通任务的完整循环:从提问到交付中间发生了什么
2.1 用一次“出差安排”看懂循环
我在给团队讲Agent原理时最喜欢用一个例子:让Agent安排明天去上海的出差行程。这个任务够日常,又能把整个循环的复杂度体现出来。
如果是聊天机器人,它会直接生成一篇“建议你去外滩、陆家嘴……”的小作文;而Agent的流程完全不同。模型先解析任务:地点上海、时间明天、要求交通便利。接着它意识到自己并不知道明天的天气,也不知道各地点间的通行时间,于是安排第一步行动——调用天气工具查询上海明天天气。拿到“明天下雨”的结果之后,它调整计划,把原本排在室外的景点砍掉,换成室内场馆。然后第二步调用地图时间估算工具,计算两个室内地点之间坐地铁要多久。最后,把所有信息组织成一份带时间表的行程。
这个过程中,模型被调用了很多次,每一次调用都基于上一次工具返回的新信息。这就是Agent和普通API调用的本质区别——它在循环里工作,而不是一锤子买卖。这也是为什么很多人第一次看Agent日志会觉得“啰嗦”:因为系统真的像人一样,查一次资料,思考一会儿,再查一次,再思考。
2.2 循环里的四个阶段:感知、推理、行动、观察
一个Agent循环可以拆成四段:感知、推理、行动、观察。
- 感知(Observation):把用户输入、工具返回结果、历史消息这些信息,整理成模型当前能读到的上下文。
- 推理(Reasoning):模型根据当前上下文判断“还缺什么信息”“下一步该干什么”。在ReAct风格的实现里,这一步通常表现为模型输出Thought(想法)和Action(动作)。
- 行动(Action):执行一次具体动作,比如调用天气API、执行一段Python代码、查询数据库。动作的类型由Agent可用的工具集决定。
- 观察(Observation):把动作的执行结果,比如API返回的JSON,再放回上下文里。
观察结束后,模型开始下一轮推理。如果它觉得还缺信息,就继续行动;如果它觉得信息够了,就输出最终答案。这个“推理—行动—观察”的循环会一直重复,直到任务完成或到达终止条件。
2.3 没有终止条件的循环,就是失控循环
Agent循环听起来简单,但有一个潜在问题:模型是概率性的,它有可能永远觉得“还不够”。比如让它收集某一年的行业数据,它会一次一次去查,查完觉得还可以更全,于是再查一次,直到token耗尽或者预算打爆。
所以一个工程上可用的Agent,必须有明确的终止信号:
- 完成信号。模型决定不再调用工具,输出最终回复,这是最理想的终点。
- 最大步数。不管完成没有,循环到N轮(常见3到10轮)就强制结束。
- 超时与预算。整个任务达到一定时长或token数就截断,避免失控。
这三个条件在实际系统里通常同时存在。后面的最小Demo就实现了“完成信号+最大步数”两个,先让你直观感受一下。
3. 不依赖框架,手写一个最小可运行的Agent Demo
3.1 环境准备:一个兼容接口就够了
很多人以为写Agent一定要上LangChain这种重框架,其实不然。Agent的核心循环,用代码实现起来很短,几百行以内就能写完。我建议所有想深入的人,先手写一遍最小循环,把手感建立起来再谈框架。
准备条件很简单:
- Python 3.8以上;
- 安装openai库,因为大多数大模型服务都提供OpenAI兼容接口,无论云端还是本地部署都能用;
- 一个可用的API Key和base_url。
我之所以用OpenAI兼容方式来写,是因为这样你后面换模型时,只需要改model和base_url,核心逻辑完全不用动。这对“不绑定某个供应商”非常重要。
3.2 定义工具:天气查询函数与它的Schema
先做一个最简单的工具函数:查询天气。我用内置字典模拟数据,真实项目里替换成HTTP调用即可。
def get_weather(city: str) -> dict: weather_table = { "北京": {"temperature": 18, "condition": "晴", "wind": "3级"}, "上海": {"temperature": 22, "condition": "多云", "wind": "2级"}, } data = weather_table.get(city) if not data: return {"error": f"暂不支持查询城市 {city}"} return {"city": city, **data}但光有Python函数不够,模型并不知道这个函数的存在,必须给它一份JSON Schema。这就是Function Calling的“说明书”:
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市当前天气情况,包括温度、天气状况和风力。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,例如 北京、上海" } }, "required": ["city"] } } } ]这里有个容易被忽略的点:description写得越清楚,模型选错参数的概率越低。很多人随便写一句“查询天气”,模型就经常把“北京明天”整个塞进city字段。把参数含义和示例写清楚,是工具设计的第一课。
3.3 核心循环:用代码实现感知—推理—行动—观察
工具定义好了,接下来实现Agent主循环。流程是:
- 把用户问题放入messages;
- 调用模型,带上tools参数;
- 判断返回结果里有没有tool_calls;
- 有,就解析函数名和参数,执行本地函数,把结果以role=tool的message放回messages,继续循环;
- 没有,说明模型准备直接输出,取出content,结束。
代码如下:
import json import openai client = openai.OpenAI( api_key="你的API_KEY", base_url="http://你的服务地址/v1", ) def run_agent(user_input: str, max_steps: int = 5) -> str: messages = [{"role": "user", "content": user_input}] for step in range(max_steps): resp = client.chat.completions.create( model="gpt-4o-mini", # 换成你实际用的模型 messages=messages, tools=tools, ) message = resp.choices[0].message messages.append(message) if not message.tool_calls: return message.content for tool_call in message.tool_calls: fn_name = tool_call.function.name args = json.loads(tool_call.function.arguments) print(f"[Step {step+1}] 调用工具: {fn_name}({args})") if fn_name == "get_weather": result = get_weather(args["city"]) else: result = {"error": f"未知工具 {fn_name}"} messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False), }) return "已达最大步数,任务未完成,请调整提示词或工具。"这段代码就是一个完整的Agent骨架。整个过程中,messages的维护是核心:每一步模型的历史消息必须完整保留,尤其不能漏掉tool角色返回的那一条。一旦漏掉,模型就不知道刚才调用的工具结果是什么,然后开始胡编。
3.4 跑一个例子,看Agent的中间轨迹
用“北京明天适合跑步吗”来跑,你会看到一个典型轨迹:
- Step 1:模型输出tool_calls,调用get_weather({"city": "北京"});
- 工具返回 {"city": "北京", "temperature": 18, "condition": "晴", "wind": "3级"};
- Step 2:模型不再调用工具,输出最终结论:“北京明天是晴天,温度18度,风力3级,适合跑步,建议穿一件薄外套。”
但注意,这个Demo有一个明显缺陷:它查的是“当前天气”而不是“明天天气”,因为工具本身没做时间区分。这就引出一个重要结论:工具能力决定了Agent能力的上限。模型再聪明,也只能在工具给的数据范围内做判断。真实系统里,你的工具必须覆盖任务所需的真实数据源。
4. 用现成平台搭建智能体:Dify这类工具到底帮你省了什么
4.1 为什么已经有手写Demo,还要用框架
上面的Demo能跑通,但它距离“可生产的Agent”还有很长的路。你会立刻遇到几个问题:多个用户并发时,上下文状态怎么隔离;工具调用日志在哪里看;运营同事想调整提示词,难道要改代码重新部署吗;某个模型服务不稳定,想一键切换怎么办。
这些就是Agent框架和平台存在的价值。它们把对话状态管理、工具注册、模型供应商适配、日志追踪这些公共能力提前做好了。你要做的只是填写业务相关的提示词和工具。
所以我的判断是:如果你是产品经理或业务方,直接用可视化平台搭;如果你是开发,也建议先用平台快速验证业务,再决定要不要自研。先跑通再优化,永远比一上来就写框架稳。
4.2 在Dify上搭建一个Agent的完整步骤
Dify是现在比较主流的开源智能体平台,既支持私有化部署,也有官方云端版本。在Dify里搭一个Agent,完整流程大概是这样的:
- 登录后创建“Agent”类型应用,这里注意别选成“Chatbot”或“Workflow”,类型选错后面的交互逻辑完全不一样。
- 在“模型”配置里选择模型供应商,填写API Key,选好具体模型。
- 编写系统提示词。这一步最重要,建议显式写明:你是谁、你能调用哪些工具、什么情况必须调用工具、输出格式是什么。
- 在“工具”区域添加工具。Dify自带了一批内置工具,比如搜索、天气、计算器;也支持自己导入OpenAPI Schema,把你公司的接口暴露给Agent。
- 打开“模型自行调用工具”的开关。这是Agent类型应用和其他应用最大的区别,不开这个开关,模型就不会主动用工具。
- 在调试界面输入一个任务,点运行,看每一步的输入输出,特别是工具返回的原始内容和模型基于它生成的推理。
- 满意后发布,Dify会生成API接口,你可以把它接到自己的前端、公众号、企业微信机器人等地方。
很多人卡在第4步。自己写的内部接口要以OpenAPI格式导入,建议先用一个简单的GET接口试通,再上POST和复杂鉴权,一次全上很容易排查不出问题。
4.3 主流框架怎么选:LangGraph、Dify、Coze、AutoGen对比
我按自己的理解做了一张对比,方便你对号入座。
| 框架/平台 | 形态 | 适合谁 | 核心优势 | 主要成本 |
|---|---|---|---|---|
| LangGraph | Python代码库 | 开发者为主 | 对状态的精细控制,适合复杂图结构Agent | 学习曲线陡,所有事都要写代码 |
| Dify | 可视化平台,可私有化 | 产品/运营为主,开发配合 | 搭得快,日志清晰,能对接多种模型 | 高度自定义场景需要写插件 |
| Coze | 云端平台 | 想快速接内容生态的个人/团队 | 插件市场丰富,发布渠道多,不用运维 | 平台绑定较深,扩展受平台限制 |
| AutoGen | Python代码库 | 研究多智能体协作 | 支持多Agent对话和任务编排 | 生产稳定性需要自己做更多工作 |
这里不做“谁更好”的结论,只看匹配度。先想清楚业务逻辑的复杂度、团队里谁会维护、对数据私有化的要求,再选型。
4.4 关于选型的个人建议
如果让我给一个通用建议,大多数业务型项目从Dify这类可视化平台起步是最保险的,因为它的可观测性天然比代码好。你的运营同事也能直接看到Agent每一步在干什么,这在debug阶段价值巨大。
当业务长到平台满足不了,比如需要非常精细的状态机、复杂的反思循环,再迁移到LangGraph这类代码框架。到了最后你会发现,自己写核心循环不难,难的是周边生态,比如监控、评估、权限控制。这些成熟平台已经替你解决了不少。
5. 让Agent在真实场景撑得住:工具设计、记忆与防呆
5.1 工具调用的两种主流实现:Function Calling与ReAct
很多人以为工具调用只有一种方式,其实区别挺大。我分一下:
- Function Calling:模型经过专门训练,会直接输出一个结构化的JSON,包括函数名和参数。代码拿到后直接执行,不需要去解析自然语言。好处是稳定、解析成本低,坏处是要求模型本身支持这个能力。
- ReAct:模型不输出结构化JSON,而是按提示词要求输出Thought、Action、Action Input这样的固定格式,代码用正则或字符串匹配去解析。好处是任何模型都能用,坏处是格式不稳定,一旦模型哪天不按套路出牌,解析就崩了。
实际项目里我会优先用Function Calling,因为它把“意图到参数”的转换变成了模型的原生能力。但如果用的是本地部署的开源模型,且该模型不支持Function Calling,ReAct是可行的兜底方案。
| 对比项 | Function Calling | ReAct |
|---|---|---|
| 依赖模型原生支持 | 需要 | 不需要 |
| 输出稳定性 | 高 | 一般,受提示词影响大 |
| 解析成本 | 低,直接JSON解析 | 高,需要解析文本格式 |
| 工具数量多了之后 | 能处理较多工具 | 容易混乱 |
5.2 工具返回格式,决定了Agent的下限
Agent在做工具调用时,最怕的不是失败,而是返回结果“没法看”。
想象一个场景:工具返回了一大段HTML页面,或者一个几千行的查询结果。模型需要从中提取有用信息,噪声一大,推理质量立刻下降,token开销也会指数上涨。所以你设计的每一个工具,返回时都应该做“面向模型的摘要”。
我的习惯是:外部API拿到数据后,先过滤掉模型用不到的字段,只保留必要的、JSON格式干净的字段,再返回给模型。如果数据量实在大,就先让一段脚本或一个小模型提炼成要点,再塞回上下文。工具返回做得干净,Agent的成功率会肉眼可见地提升。
5.3 记忆:短期上下文和长期存储要分开设计
Agent的“记忆”是个容易被神话的概念,实际分两层。
短期上下文就是当前任务的对话历史,包括用户输入、工具调用、中间推理等。它的容量受限于模型的上下文窗口。超出窗口怎么办?要么对早期的过程做摘要,要么只保留最近几轮,不能无脑塞。
长期记忆则要落到外部存储。比如把用户偏好、历史订单、历史决策写成结构化记录或向量,下次任务开始时按用户ID检索相关记录放进来。这里的核心是“检索”而不是“全量塞入”。把几年的历史全部塞给模型,只会让模型被无关信息淹没,看起来参数很足,实际效果很差。
5.4 防呆:权限、超时、预算、人工审批缺一不可
Agent的“自主”是一把双刃剑。一旦它真的能调用工具,就一定会犯错。我在生产环境里要求的底线配置是这四样:
- 权限最小化。每个工具只给当前任务需要的最小权限,绝不让Agent拥有一把“删库”级别的刀。
- 超时与最大步数。任务必须有硬性极限,绝不允许无限制循环。
- 预算上限。把token和费用预算写进系统,超了就停机。很多失控事故都是从“多跑了几十个循环”开始的。
- 人工审批。凡是会产生真实世界影响的动作(发邮件、下单、改数据库),一律不直接执行,而是返回一个确认请求,等人在界面上点了确认再执行。
这四条优先级甚至高于模型本身的聪明程度。宁可用一个笨但可靠的Agent,也不要一个聪明但会乱动手的Agent。
6. 从Demo到生产,我踩过的坑和给新手的建议
6.1 模型在循环里原地打转怎么办
我见过最多的故障,是Agent一直重复调用同一个工具,或者陷入“调用A工具→返回结果→还想调用A工具”的循环。表面上看是模型傻,实际上通常是两个问题:一是工具返回的信息不满足模型下一步需要,它只能重试;二是提示词里没有告诉模型“什么时候该停”。
解决思路有三个:把任务的完成条件写清楚;给工具增加去重和缓存;给最大步数设一个严格限制。有团队把最大步数设到50,一个简单查询任务跑了40多步,费用直接爆掉。生产环境我一般默认5步,特殊情况再放宽到10。
6.2 Token消耗比想象中快得多
新手容易低估工具调用对token的消耗。一次普通对话可能只要几百token,一个Agent任务却可能在内部跑四五个工具调用,每个工具返回几百上千token,几个来回下来就是几千token,复杂度一高,上万也不奇怪。
所以做Agent项目,成本预估要按“任务数×每任务平均token数”来算,而不是按“对话轮次”来算。优化手段包括:精简工具返回字段、给工具结果加缓存、控制历史消息长度、能用小模型做的步骤(比如摘要)不要用大模型。
6.3 忽略评估,等于闭着眼睛改流程
Agent没有标准答案,同一个输入,模型每次输出可能都不一样,这是它和传统接口最大的差别。正因如此,回归测试更重要。我习惯的做法:先攒20到30条来自真实业务的典型case,用相同输入跑一遍,记录任务成功率、平均步数、平均耗时、平均token、失败原因这几个指标。
改动提示词、换模型、加工具之后,都用同一份case重跑。如果没有这套评估,你根本不知道这次改动是变好了还是变坏了,只能靠感觉。靠感觉的Agent项目,通常活不长。
6.4 一张表排查最常见的故障
| 现象 | 常见原因 | 处理建议 |
|---|---|---|
| Agent反复调用同一个工具 | 工具结果不满足下一步需要 | 精简返回字段、增加缓存和去重 |
| 模型完全不调用工具 | 提示词没说清、平台开关没打开 | 在系统提示词里写明“必须先查工具”,检查开关 |
| 最终回答和工具结果无关 | 工具结果没被放回messages | 检查tool消息及tool_call_id是否正确 |
| 任务经常超时 | 最大步数过高或单步耗时过长 | 限制步数、把慢工具改成异步 |
| 长时间不输出 | 模型连续输出text但没最终结果 | 校验是否误判了结束条件 |
这张表不是标准答案,而是我排障时的一个习惯:先看日志,再猜模型,不要一上来就怪“模型太笨”。大多数问题都出在工程侧,不在模型侧。
6.5 我的学习与落地路径建议
如果你现在刚接触Agent,我的建议路径是:先用现成平台(Dify这类)搭一个真实业务场景,完整走一遍从提示词到工具的流程,把Agent循环的体感建立起来;然后对照本文第3章的代码,自己画一遍调用过程,理解状态是怎么流转的;接着尝试给Agent加一个新工具,比如查订单、播报库存;等你觉得平台或者框架限制你了,再深入LangGraph或者自己写核心循环。
我在带团队时反复强调一句话:不要先研究框架,先研究循环。框架会更新,但“感知—推理—行动—观察”这套循环思路不会变。
说个我自己的体会。刚开始做Agent时,我很喜欢把工具数量堆得很多,觉得功能越全越厉害。后来在真实项目里踩了几次坑才发现,工具越少越好,少而精才能真正可控。一个Agent能用好三五把工具,已经能解决绝大多数业务问题。把工具定义清楚、返回格式整理干净,比一味追求模型聪明更重要。