1. 从最小循环说起:AI Agent 到底在循环什么
很多人第一次接触 AI Agent,脑子里浮现的是科幻电影里那种能自己思考、自己行动的智能体。但真上手搭一个之后你会发现,剥掉所有花哨的概念,Agent 的核心就是一个循环——感知、决策、执行、再感知。这个循环在业界通常叫 Agent Loop,是所有 Agent 框架最底层的东西。
我最早接触这个概念的时候,也觉得不就是让大模型反复调用工具嘛,有什么难的。但实际搭过几个项目之后才明白,最小循环能跑通只是起点,真正难的是让这个循环在真实场景里稳定、可控、不失控。这篇文章就围绕这个核心,把 AI Agent 从最小可运行循环到可靠系统的路径拆开讲清楚。
先明确一下这篇文章适合谁看。如果你已经用过大模型 API,知道什么是 Prompt,也听说过 Function Calling,但还没真正搭过一个能自己调工具、自己判断下一步的 Agent,那这篇内容就是给你准备的。如果你已经搭过简单的 Agent,但发现它经常跑偏、死循环、或者在生产环境里根本不敢用,那这篇也能帮你理清从玩具到系统的差距在哪。
核心关键词先摆出来:AI Agent、Agent Loop、Function Calling、Prompt、Workflow。这几个词基本构成了 Agent 开发的主干。Agent Loop 是骨架,Function Calling 是手脚,Prompt 是大脑的指令,Workflow 是当 Agent 不够可靠时的兜底方案。下面逐个展开。
2. 最小循环的拆解:一个 Agent 最少需要什么
2.1 最小循环的四个组成部分
一个能跑的最小 Agent 循环,拆开来看就四块东西:
- 输入接收:用户给一句话,或者系统给一个事件触发
- 模型推理:把当前状态和可用工具列表塞给大模型,让它决定下一步
- 工具执行:模型说调用哪个函数、传什么参数,程序去实际执行
- 结果回灌:把执行结果塞回对话历史,再次调用模型,直到模型认为任务完成
这四步循环往复,就是 Agent Loop 的全部。听起来简单,但每一步都有坑。
我拿一个实际例子来说明。假设你要做一个能查天气、能发邮件的 Agent。用户说“帮我查一下明天北京的天气,如果下雨就提醒我带伞”。最小循环是这样跑的:
第一轮,模型看到用户输入和可用工具列表(查天气、发邮件),决定调用查天气工具,参数是城市=北京、日期=明天。程序执行查天气,拿到结果“明天北京有雨”。第二轮,把天气结果塞回去,模型看到有雨,决定调用发邮件工具,参数是收件人=用户、内容=记得带伞。程序执行发邮件。第三轮,模型看到邮件发送成功,输出“已提醒你带伞”。
这就是最小循环。没有记忆、没有规划、没有反思,就是单纯的“想-做-看-再想”。
2.2 为什么 Function Calling 是关键转折点
在 Function Calling 出现之前,让模型调用工具是一件很痛苦的事。你得在 Prompt 里写一大堆格式说明,让模型输出特定格式的文本,然后自己写解析器去提取。模型稍微不听话,格式就崩了。
Function Calling 的本质是把“模型输出结构化指令”这件事变成了模型的原生能力。你不再需要教模型怎么输出 JSON,只需要告诉它有哪些函数可用、每个函数需要什么参数,模型会自己决定调不调、调哪个、传什么。
这里有个关键细节很多人会忽略:Function Calling 的可靠性取决于你对函数描述的精确程度。函数名、参数名、参数类型、参数描述,每一个都会影响模型的选择。我踩过的坑是,两个功能相近的函数,如果描述写得模糊,模型会随机选,甚至两个都调。后来我把每个函数的描述都写成“什么时候该用这个函数、什么时候不该用”,准确率立刻上来了。
2.3 Prompt 在循环中的角色变化
在传统对话里,Prompt 就是用户那句话。但在 Agent 循环里,Prompt 是分层的:
- 系统 Prompt:定义 Agent 的身份、能力边界、行为准则
- 工具描述:告诉模型有哪些工具可用
- 对话历史:之前每一轮的输入、模型输出、工具执行结果
- 当前指令:用户最新的一句话
这四层拼在一起,才是模型每一轮真正看到的 Prompt。很多人搭 Agent 时只关注用户说了什么,忽略了系统 Prompt 和工具描述的质量,结果就是 Agent 行为不可控。
我的经验是,系统 Prompt 里一定要写清楚三件事:你是谁、你能做什么、你不能做什么。尤其是“不能做什么”,这是防止 Agent 跑偏的第一道防线。比如你做一个客服 Agent,就要明确写“不要回答与产品无关的问题、不要承诺任何退款金额、遇到不确定的问题必须转人工”。
3. 从循环到系统:可靠性的四个支柱
最小循环能跑通之后,你会发现它在真实场景里根本不够用。模型会忘记之前说过的话、会重复调用同一个工具、会在该停止的时候继续跑、会在不该调工具的时候瞎调。要让 Agent 变得可靠,需要加上四个支柱。
3.1 状态管理:让 Agent 记住自己在哪
最小循环里,对话历史就是全部状态。但对话一长,模型就会丢失早期信息,而且 token 成本会爆炸。状态管理要解决的就是:什么该记、什么该忘、什么该压缩。
常见的做法是分层记忆。短期记忆放当前对话的最近几轮,长期记忆放关键事实和用户偏好,工作记忆放当前任务的中间结果。我一般会用一个大模型做“记忆压缩”,把超过一定轮数的对话总结成一段摘要,替换掉原始对话。这样既保留了关键信息,又控制了 token 消耗。
这里有个实操细节:压缩的时机很重要。太早压缩会丢失细节,太晚压缩 token 已经爆了。我的经验是,当对话历史超过模型上下文窗口的 60% 时开始压缩,留出 40% 给后续交互。
3.2 工具治理:不是工具越多越好
新手搭 Agent 容易犯的错是拼命加工具,觉得工具越多能力越强。实际上工具越多,模型选择越困难,出错概率越高。我做过一个测试,同一个任务,给模型 5 个工具时准确率 92%,给 20 个工具时掉到 67%。
工具治理的核心是分组和路由。把工具按场景分组,先用一个轻量模型或规则判断当前该用哪组工具,再把那组工具的描述塞给主模型。这样每次模型看到的工具数量可控,选择准确率就上来了。
另一个关键是工具的幂等性。Agent 可能会因为各种原因重复调用同一个工具,如果你的工具不是幂等的,就会产生重复下单、重复发邮件这种事故。我的做法是给每个工具有一个唯一的调用 ID,执行前先检查这个 ID 是否已经执行过。
3.3 循环终止:什么时候该停
最小循环最大的风险是死循环。模型可能因为工具返回结果不符合预期,反复调用同一个工具。我见过最夸张的一次,Agent 在一个查数据库的工具上循环了 47 次,因为每次返回都是空,模型觉得再查一次可能有结果。
终止条件要设多层:
- 最大轮数限制:硬性上限,比如 10 轮,到了就强制停止
- 重复检测:连续两次调用同一个工具且参数相同,直接中断
- 无进展检测:如果连续几轮的状态没有实质变化,判定为卡住
- 显式完成信号:模型输出特定的完成标记,或者调用一个“任务完成”的工具
这几层要同时存在,任何一层触发都终止。不要指望模型自己知道什么时候该停,它不知道。
3.4 错误处理:Agent 出错时怎么办
Agent 出错是常态,关键是怎么处理。错误分几类:模型输出格式错误、工具执行失败、工具返回结果不符合预期、模型判断错误。
我的处理原则是:能自动恢复的自动恢复,不能的降级到人工。格式错误可以让模型重试一次,工具执行失败可以重试或换备用工具,结果不符合预期可以让模型重新规划。但如果连续几次都失败,就要停下来,把当前状态和错误信息交给人工处理。
这里有个容易被忽略的点:错误信息要回灌给模型。很多人工具执行失败后直接抛异常终止,其实更好的做法是把错误信息作为工具结果塞回去,让模型自己决定是重试、换工具还是放弃。模型看到“数据库连接超时”和看到“查询结果为空”,反应是完全不同的。
4. Workflow 与 Agent 的边界:什么时候不该用 Agent
4.1 Workflow 的本质是确定性编排
Workflow 编排这几年很火,但很多人把它和 Agent 混为一谈。两者的核心区别是:Workflow 的路径是预先定义的,Agent 的路径是运行时决定的。
Workflow 就像流水线,每一步做什么、下一步去哪,都是写死的。Agent 就像自由职业者,接到任务后自己决定怎么做。Workflow 的优点是可控、可预测、好调试;缺点是灵活性差,遇到没预设的情况就卡住。Agent 的优点是灵活;缺点是行为不可预测,调试困难。
我见过很多团队一上来就用 Agent 做所有事,结果发现大部分场景其实用 Workflow 就够了,而且更稳定。比如一个审批流程,步骤是固定的:提交、初审、复审、通过。这种场景用 Workflow 编排,每一步调一次模型做判断,比让 Agent 自由发挥可靠得多。
4.2 混合架构:Agent 做决策,Workflow 做执行
实际项目里,最稳的架构往往是混合的。用 Agent 做高层决策,用 Workflow 做底层执行。比如一个客服系统,Agent 负责理解用户意图、决定走哪条处理路径,具体的退款流程、查询流程用 Workflow 编排。这样既有 Agent 的灵活性,又有 Workflow 的可靠性。
判断标准很简单:如果这个任务的步骤是确定的,用 Workflow;如果步骤取决于运行时信息,用 Agent。大部分业务场景其实是前者,或者两者的混合。
4.3 从 Agent 到 Workflow 的降级策略
一个实用的技巧是:先用 Agent 跑,观察它的行为模式,如果发现某个路径反复出现,就把它固化成 Workflow。这叫“Agent 行为固化”。我做过一个项目,Agent 处理用户咨询时,80% 的情况都走同样的三步:识别问题类型、查知识库、生成回复。后来我把这三步固化成 Workflow,只在识别问题类型那一步用模型,整体稳定性和响应速度都大幅提升。
这种渐进式的策略比一上来就设计完美架构要务实得多。先让 Agent 跑起来,收集真实数据,再根据数据优化架构。
5. 实操:搭一个带工具调用的最小 Agent
5.1 环境准备与依赖选择
我用 Python 来演示,因为生态最成熟。核心依赖就两个:一个大模型 SDK,一个 HTTP 客户端。模型选支持 Function Calling 的就行,现在主流模型基本都支持。
pip install openai httpx如果你用的是其他模型,把 SDK 换掉就行,逻辑是一样的。关键是模型要支持 Function Calling,这是前提。
5.2 定义工具与函数描述
先定义两个工具:查天气和发提醒。
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市指定日期的天气。当用户询问天气相关问题时使用此工具。", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称,如北京"}, "date": {"type": "string", "description": "日期,格式 YYYY-MM-DD"} }, "required": ["city", "date"] } } }, { "type": "function", "function": { "name": "send_reminder", "description": "给用户发送提醒。当需要通知用户某件事时使用此工具。", "parameters": { "type": "object", "properties": { "content": {"type": "string", "description": "提醒内容"} }, "required": ["content"] } } } ]注意每个 description 都写了“什么时候用”,这是提高模型选择准确率的关键。
5.3 实现 Agent Loop 主逻辑
import json from openai import OpenAI client = OpenAI() def run_agent(user_input, max_turns=10): messages = [ {"role": "system", "content": "你是一个助手,可以查天气和发提醒。完成任务后直接回复用户。"}, {"role": "user", "content": user_input} ] for turn in range(max_turns): response = client.chat.completions.create( model="gpt-4o", messages=messages, tools=tools, tool_choice="auto" ) msg = response.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for tool_call in msg.tool_calls: name = tool_call.function.name args = json.loads(tool_call.function.arguments) if name == "get_weather": result = f"{args['city']}{args['date']}有雨" elif name == "send_reminder": result = f"已发送提醒:{args['content']}" else: result = "未知工具" messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result }) return "达到最大轮数,任务未完成"这段代码就是最小循环的完整实现。跑起来之后你会发现,简单的任务它能处理,但稍微复杂一点就开始出问题。
5.4 加上终止条件和错误处理
def run_agent_safe(user_input, max_turns=10): messages = [ {"role": "system", "content": "你是一个助手,可以查天气和发提醒。完成任务后直接回复用户。"}, {"role": "user", "content": user_input} ] last_tool_signature = None repeat_count = 0 for turn in range(max_turns): try: response = client.chat.completions.create( model="gpt-4o", messages=messages, tools=tools, tool_choice="auto" ) except Exception as e: return f"模型调用失败:{e}" msg = response.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for tool_call in msg.tool_calls: signature = f"{tool_call.function.name}:{tool_call.function.arguments}" if signature == last_tool_signature: repeat_count += 1 if repeat_count >= 2: return "检测到重复调用,任务终止" else: repeat_count = 0 last_tool_signature = signature try: name = tool_call.function.name args = json.loads(tool_call.function.arguments) result = execute_tool(name, args) except Exception as e: result = f"工具执行失败:{e}" messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result }) return "达到最大轮数,任务未完成"加上重复检测和异常捕获之后,Agent 的稳定性明显提升。但你会发现,它还是会在某些情况下卡住,比如工具一直返回空结果。
6. 常见问题与排查技巧实录
6.1 模型不调用工具怎么办
这是最常见的问题。模型直接回复用户,而不是调用工具。原因通常有三个:工具描述不够清晰、系统 Prompt 没有引导、模型本身能力不足。
排查顺序:先看工具描述,是不是写得太抽象;再看系统 Prompt,有没有明确说“需要查信息时必须调用工具”;最后换一个 Function Calling 能力更强的模型试试。我遇到的大部分情况都是工具描述的问题,把 description 写具体之后就好了。
6.2 模型调用错误的工具
两个工具功能相近时,模型容易选错。解决办法是在 description 里写清楚边界。比如“查天气”和“查历史天气”,要明确写“查未来天气用前者,查过去天气用后者”。另外,参数名也要有区分度,不要两个工具都有叫“query”的参数。
6.3 循环停不下来
前面讲过,加最大轮数、重复检测、无进展检测。这里补充一个技巧:在系统 Prompt 里明确写“如果连续两次得到相同结果,停止并告知用户”。模型有时候会听这个指令,能减少一部分死循环。
6.4 工具返回结果太长导致 token 爆炸
工具返回的结果不要原样塞回去,先做截断或摘要。比如数据库查询返回 100 条记录,只取前 10 条,或者让模型先总结再塞回去。我的做法是给每个工具设一个返回长度上限,超过就截断并加一句“结果已截断”。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 模型不调工具 | 工具描述模糊 | 补充使用场景说明 |
| 调错工具 | 工具边界不清 | 明确各工具适用条件 |
| 死循环 | 无终止条件 | 加轮数上限和重复检测 |
| token 爆炸 | 历史太长 | 加记忆压缩和结果截断 |
| 结果不符合预期 | 工具返回格式差 | 统一返回结构,加错误信息 |
| 响应太慢 | 工具串行执行 | 无依赖的工具并行调用 |
7. 从能跑到好用:几个实战经验
7.1 日志要记全
Agent 的调试比普通程序难,因为它的行为不确定。每一轮的输入、模型输出、工具调用、工具结果,都要记下来。我一般会把整个 messages 数组在每一轮结束后落盘,出问题时直接回放。没有日志的 Agent 调试就是盲人摸象。
7.2 先做窄场景,再做宽场景
不要一上来就做一个什么都能干的通用 Agent。先选一个具体场景,比如“查订单状态”,把这条链路跑通、跑稳,再逐步扩展。窄场景的好处是边界清晰,容易判断对错,也容易收集反馈。
7.3 人工兜底不是失败
很多人觉得 Agent 处理不了转人工是失败,其实不是。一个可靠的系统不是 100% 自动,而是知道什么时候该找人。我的做法是设一个置信度阈值,模型对当前判断不确定时,直接转人工,不要硬撑。用户宁愿多等一会儿找人工,也不愿意被一个瞎猜的 Agent 来回折腾。
7.4 版本管理要跟上
Agent 的行为会随着 Prompt 和工具的调整而变化。每次改动都要记录版本,最好能 A/B 测试。我见过一个团队改了系统 Prompt 里一个词,导致 Agent 的行为完全变了,但因为没记录,排查了两天才找到原因。
7.5 成本要算清楚
Agent 的 token 消耗比普通对话高得多,因为每一轮都要把完整历史塞进去。一个 10 轮的 Agent 任务,token 消耗可能是单轮对话的 20 倍以上。做之前先算一下成本,如果单次任务成本超过人工成本,就要重新考虑方案了。
8. 关于并发和性能的一点补充
AI Agent 怎么扛并发是最近被问得很多的问题。核心矛盾在于:Agent 的每一轮都要调模型,模型调用是慢操作,而且有速率限制。并发一上来,要么被限流,要么响应时间爆炸。
我的处理思路是分层:接入层做队列和限流,执行层做异步和并行。用户请求进来先入队,按优先级和速率限制逐个处理。Agent 内部,如果多个工具调用之间没有依赖关系,就并行执行,减少总耗时。
另一个技巧是缓存。很多 Agent 任务其实是重复的,比如“查订单状态”这种,同样的输入可以直接返回缓存结果。缓存命中率高的场景,并发能力能提升一个数量级。
但要注意,缓存的前提是任务幂等。如果 Agent 的任务有副作用(比如发邮件、下单),就不能简单缓存,要做更细粒度的判断。
9. 写在最后的一点个人体会
搭 Agent 这件事,我最大的体会是:不要被概念带跑,要盯着问题本身。Agent、Workflow、Function Calling 这些都是工具,用哪个取决于你要解决什么问题。一个稳定的 Workflow 加几个模型调用,往往比一个花哨的 Agent 更管用。
另外,Agent 的可靠性不是靠一个技巧实现的,是靠一层层的防护堆出来的。最大轮数、重复检测、错误回灌、人工兜底,每一层都只能挡住一部分问题,但叠在一起,系统就稳了。这跟做后端系统是一个道理,没有银弹,只有工程。
最后分享一个我常用的调试方法:把 Agent 的每一轮对话单独拿出来看,问自己“如果我是模型,看到这些信息,我会怎么选”。大部分时候,Agent 的错误决策都是因为信息不足或信息误导。把信息补全、把误导去掉,Agent 的表现就会好很多。