1. 从最小循环说起:AI Agent 到底在“循环”什么
很多人第一次接触 AI Agent,脑子里浮现的是科幻电影里那种能自己思考、自己行动的智能体。但真到动手搭建的时候,你会发现它最核心的东西其实特别朴素——就是一个循环。这个循环在业内通常叫Agent Loop,翻译过来就是“智能体循环”。你把它理解成一个“感知-决策-执行-再感知”的闭环就行,跟人做事的逻辑没本质区别。
我刚开始接触这块的时候,也走过弯路。那时候觉得 Agent 嘛,肯定得有个复杂的架构,得有一堆模块互相调用。结果搭出来一个东西,跑两下就乱了,要么死循环,要么该调工具的时候不调,要么调完了不知道怎么把结果接回去。后来才明白,问题不在架构复杂不复杂,而在于那个最基础的循环有没有跑通。最小循环跑不通,后面加再多东西都是空中楼阁。
那这个最小循环到底长什么样?用最直白的话说:你给 Agent 一个目标,它把目标加上当前上下文发给模型,模型返回一个动作指令,Agent 执行这个动作,把执行结果塞回上下文,然后再发给模型,如此反复,直到模型说“我完成了”或者达到某个终止条件。就这么简单。但就是这简简单单的几步,里面藏着大量细节,每一个细节没处理好,整个系统就会变得不可靠。
为什么我要强调“最小循环”?因为现在网上很多教程一上来就讲多 Agent 协作、讲复杂的 workflow 编排、讲各种花哨的框架,新手跟着搭,搭完发现根本不知道哪里出了问题。如果你先把最小循环吃透,知道每一步在干什么、数据怎么流转、哪里容易出错,后面再往上叠东西,心里就有底了。这就像学编程先学写“Hello World”,学做菜先学炒鸡蛋,基础打牢了,后面才走得稳。
这篇文章我会从最小循环开始拆,一步步讲到怎么把它变成一个可靠系统。中间会涉及 Function Calling、Prompt 设计、Workflow 编排、并发处理、错误恢复这些实际搭建中绕不开的话题。不管你是刚入门想搞清楚 Agent 到底怎么回事,还是已经搭过一些东西但总觉得不够稳,应该都能从里面找到对你有用的东西。
2. 最小循环的四个核心环节拆解
2.1 目标输入与上下文组装
任何 Agent 的起点都是“目标”。这个目标可以是一句话,比如“帮我查一下明天北京的天气”,也可以是一个更复杂的任务描述。但目标本身不是直接扔给模型的,你需要把它组装成模型能理解的上下文。这个组装过程,就是Prompt的构建。
很多人觉得 Prompt 就是写一段话让模型干活,其实在 Agent 场景下,Prompt 的构建要复杂得多。它至少包含几个部分:系统指令(告诉模型它是什么角色、有什么能力、遵守什么规则)、用户目标(当前要完成的任务)、历史交互记录(之前发生了什么)、可用工具列表(模型能调用哪些函数)。这几部分拼在一起,才是一个完整的 Agent Prompt。
我见过不少新手在这里踩坑。最常见的问题是系统指令写得太模糊,比如“你是一个有用的助手”,这种话等于没说。模型不知道边界在哪,就容易乱来。好的系统指令应该明确告诉模型:你的职责是什么、你可以调用哪些工具、调用工具时应该遵循什么格式、遇到不确定的情况应该怎么办。这些都要写清楚,不能含糊。
还有一个坑是历史记录的处理。Agent 循环跑起来之后,每一轮都会产生新的对话记录。如果你把所有历史都塞进 Prompt,token 消耗会飞快增长,而且模型容易被无关信息干扰。所以你需要一个策略来决定哪些历史保留、哪些丢弃。常见的做法是保留最近 N 轮,或者只保留与当前任务相关的部分。这个策略的选择会直接影响 Agent 的表现和成本。
2.2 模型推理与动作决策
上下文组装好之后,发给模型,模型开始推理。这一步的核心是模型要根据当前状态决定下一步做什么。在 Agent 场景下,模型的输出通常有两种类型:一种是直接给出最终答案,另一种是请求调用某个工具。这个“请求调用工具”的能力,就是Function Calling。
Function Calling 是 Agent 能“干活”的关键。没有它,模型只能动嘴皮子,不能真正执行操作。有了它,模型可以调用搜索、计算、读写文件、发请求等各种函数。但这里有个关键点:模型本身并不执行函数,它只是输出一个“我要调用某个函数,参数是什么”的指令。真正执行函数的是你的代码。这个区分很重要,很多新手会搞混,以为模型能直接操作外部系统,其实不是的。
模型决定调用哪个函数、传什么参数,这个过程依赖几个东西。一是工具的描述是否清晰。你给模型一个函数,得告诉它这个函数是干什么的、参数是什么意思、什么情况下该用。描述写得不好,模型就可能该调的时候不调,或者调错函数。二是模型的推理能力。不同模型在这方面的表现差异很大,有些模型能很好地理解什么时候该调工具,有些则容易犯迷糊。三是 Prompt 的引导。你可以在系统指令里给一些示例,告诉模型在什么情况下应该怎么做,这能显著提升调用的准确率。
2.3 工具执行与结果回传
模型输出动作指令后,轮到你的代码干活了。你需要解析模型的输出,提取出函数名和参数,然后执行对应的函数,拿到结果,再把结果格式化后塞回上下文。这一步看起来简单,但实际搭建时问题不少。
首先是解析的健壮性。模型输出的格式可能不完全符合你的预期,比如 JSON 里多了个逗号、字段名拼错了、该用字符串的地方用了数字。如果你直接按理想格式解析,很容易崩。所以解析逻辑要写得宽容一些,能处理常见的格式偏差。其次是执行的异常处理。工具执行可能失败,比如网络超时、文件不存在、参数不合法。这些异常不能直接让整个循环崩掉,你得捕获它们,把错误信息作为结果回传给模型,让模型知道“刚才那个操作失败了,原因是什么”,然后它可能会换个方式重试,或者告诉你它做不了。
结果回传的格式也很重要。你回传的内容要清晰告诉模型:这个结果是哪个函数的、成功还是失败、具体内容是什么。如果回传格式混乱,模型可能理解不了,导致后续决策出错。我一般会把结果包装成一个结构化的消息,包含函数名、状态码、结果内容这几个字段,这样模型看起来清楚,后续排查也方便。
2.4 循环终止与结果输出
循环不能无限跑下去,得有终止条件。常见的终止条件有几个:模型明确表示任务完成、达到了最大循环次数、执行时间超过限制、出现了不可恢复的错误。这几个条件通常要同时设置,任何一个满足就退出循环。
最大循环次数这个参数很关键。设得太小,复杂任务可能没跑完就被截断了;设得太大,万一模型陷入死循环,会白白消耗大量 token 和时间。我的经验是,根据任务复杂度来定,简单任务 5 到 10 轮够了,复杂任务可以放到 20 到 30 轮。但不管多少,一定要设一个上限,不能让它无限跑。
循环退出后,你需要从上下文里提取最终结果返回给用户。这个结果可能是模型最后一条消息,也可能是你根据整个执行过程汇总出来的。如果是后者,你需要在循环过程中记录关键信息,最后统一整理。这一步的体验直接影响用户对 Agent 的感知,所以别随便扔一条原始消息就完事,该格式化的格式化,该总结的总结。
3. 从能跑到可靠:四个必须解决的工程问题
3.1 Prompt 设计的稳定性问题
最小循环跑通之后,你会发现一个现象:同样的输入,有时候 Agent 表现很好,有时候就胡言乱语。这很大程度上是 Prompt 稳定性问题。Prompt 稍微改几个字,模型的行为可能就完全不一样。这种不确定性在 demo 阶段还能忍,到了实际使用场景就没法接受了。
提升 Prompt 稳定性,我总结了几条实用经验。第一,系统指令要具体、结构化。别写一大段散文,用分点的方式把规则列清楚。模型对结构化指令的遵循度明显更高。第二,关键规则要重复强调。比如“调用工具时必须使用指定格式”这种要求,在系统指令开头说一遍,在工具描述里再说一遍,在示例里再体现一遍。重要的事情说三遍,对模型也适用。第三,给示例。Few-shot 示例对稳定模型行为非常有效,尤其是对于格式要求严格的场景,给一两个正确示例,模型出错的概率会大幅下降。
还有一个容易被忽视的点是 Prompt 的版本管理。你调好一版 Prompt,跑得挺好,过两天想优化一下,改了几个字,结果效果反而变差了。这时候你想回滚,发现已经不记得之前那版长什么样了。所以从项目一开始,就要把 Prompt 当成代码来管理,每次修改都记录版本和变更内容,方便对比和回滚。
3.2 Function Calling 的边界处理
Function Calling 让 Agent 有了干活的能力,但也带来了新的问题。模型可能会调用不存在的函数、传错误的参数、在不该调用的时候调用。这些边界情况如果不处理,系统就会变得很脆弱。
处理这些问题,首先要做好参数校验。模型传过来的参数不能直接信,得检查类型对不对、必填项有没有、值在不在合理范围内。校验不通过就返回一个清晰的错误信息给模型,告诉它哪里错了、应该怎么改。模型收到错误信息后,通常会调整参数重试。其次要限制可调用的函数范围。不是所有函数都应该暴露给模型,只暴露当前任务需要的那些。暴露得越多,模型选错的概率越大。最后要设置调用频率限制。有些函数调用成本高或者有副作用,不能让模型无限制地调。设置一个每轮最多调用次数,超过就强制终止或者让模型换个方式。
3.3 上下文管理与 Token 控制
Agent 循环跑起来之后,上下文会不断增长。每一轮的工具调用结果、模型的思考过程、用户的输入,都会累积到上下文里。如果不加控制,很快就会超出模型的上下文窗口,或者 token 成本高到无法接受。
上下文管理的核心是“什么该留、什么该丢”。我的做法是分层处理:系统指令和工具描述始终保留,这是基础;最近几轮的完整对话保留,保证模型能理解当前状态;更早的历史只保留摘要或者关键信息,丢弃细节。具体保留几轮,要看任务特点。需要多步推理的任务,保留的轮次多一些;简单任务可以少一些。
Token 控制还有一个实用技巧是压缩工具返回结果。有些工具返回的内容很长,比如搜索返回一大堆网页摘要,全塞进上下文很浪费。你可以在回传之前做一层过滤或摘要,只把最相关的部分给模型。这样既节省 token,又减少干扰信息,模型的表现反而更好。
3.4 错误恢复与重试策略
Agent 运行过程中出错是常态,不是异常。网络会抖、工具会挂、模型会抽风。一个可靠的系统不是不犯错,而是犯了错能恢复。所以错误恢复机制是必须的。
错误分几类,处理方式不一样。第一类是临时性错误,比如网络超时、服务暂时不可用。这类错误重试通常能解决,但要注意重试次数和退避策略,别一上来就疯狂重试把对方打挂。第二类是参数错误,模型传的参数不对。这类错误不用重试,直接把错误信息回传给模型,让它自己修正。第三类是逻辑错误,比如模型陷入死循环、反复调用同一个函数。这类错误需要外部干预,比如达到循环上限后强制终止,或者检测到重复模式后改变策略。
重试策略的设计要考虑几个参数:最大重试次数、重试间隔、退避方式。我的经验是,临时性错误重试 2 到 3 次,间隔用指数退避,第一次等 1 秒,第二次等 2 秒,第三次等 4 秒。这样既能应对短暂抖动,又不会在对方持续不可用时浪费太多时间。
4. 实操搭建:一个可运行的最小 Agent 循环
4.1 环境准备与依赖选择
动手搭建之前,先把环境理清楚。语言方面,Python 是这块最主流的选择,生态最全,各种库都有。如果你对性能有要求,Rust 和 Go 也有相应的方案,但生态成熟度差一些。框架方面,LangChain 和 LangGraph 是目前用得比较多的,前者偏重链式调用,后者偏重图结构编排。如果你只是想理解原理,我建议先不用框架,用最基础的 HTTP 请求直接调模型 API,把循环逻辑自己写一遍。这样你能清楚看到每一步的数据流转,后面再用框架就知其然也知其所以然。
依赖方面,你需要一个能调模型 API 的客户端库、一个 HTTP 请求库(如果工具需要发请求)、一个 JSON 处理库。这些基本都是标配,没什么特别的。模型选择上,建议选支持 Function Calling 的模型,不然 Agent 没法调工具,就只能聊天了。
4.2 核心循环的代码实现
下面是一个最小循环的伪代码结构,我用 Python 风格写,方便理解:
def agent_loop(goal, max_turns=10): context = build_initial_context(goal) for turn in range(max_turns): response = call_model(context) if response.is_final_answer: return response.content if response.is_tool_call: tool_name = response.tool_name tool_args = response.tool_args try: result = execute_tool(tool_name, tool_args) context.append(tool_result_message(tool_name, result)) except Exception as e: context.append(tool_error_message(tool_name, str(e))) return "达到最大循环次数,任务未完成"这段代码很简短,但包含了最小循环的全部要素:组装上下文、调模型、判断输出类型、执行工具、回传结果、循环控制。实际项目中,每个环节都会复杂很多,但骨架就是这个。
4.3 工具函数的定义与注册
工具函数是 Agent 的手和脚。定义工具时,有几个要点。函数名要清晰,让人一看就知道是干什么的。参数要明确类型和含义,别用模糊的名字。函数描述要写清楚什么情况下该用这个工具,这对模型判断是否调用很关键。
注册工具时,你需要把工具的描述转换成模型能理解的格式。不同模型的格式要求不一样,但基本都包含函数名、描述、参数 schema 这几部分。参数 schema 通常用 JSON Schema 来描述,定义每个参数的类型、是否必填、取值范围等。这个 schema 写得越清楚,模型调用时出错越少。
4.4 运行测试与效果验证
搭好之后,先跑几个简单用例验证。比如让 Agent 查天气、做计算、搜索信息。观察它的行为:该调工具的时候调了没有、参数传对了没有、结果处理正确了没有、最终答案合理不合理。如果发现问题,逐个环节排查。
测试用例要覆盖正常情况和边界情况。正常情况验证基本功能,边界情况验证健壮性。比如传一个空目标、传一个超长目标、让 Agent 调用一个不存在的工具、让工具执行时抛异常。这些情况都跑一遍,看看系统会不会崩、错误处理得怎么样。
5. 进阶话题:并发、编排与系统可靠性
5.1 并发场景下的 Agent 处理
单个 Agent 跑通了,接下来要考虑并发。实际使用中,可能同时有多个用户请求,或者一个任务需要多个 Agent 并行处理。这时候问题就来了:多个 Agent 同时调模型 API,会不会触发限流?多个 Agent 同时读写共享资源,会不会冲突?一个 Agent 卡住了,会不会影响其他 Agent?
处理并发,首先要做的是限流。模型 API 通常有 QPS 限制,你得控制并发请求数,别一股脑全发出去。可以用信号量或者队列来控制。其次是资源隔离。每个 Agent 的上下文、状态、工具调用记录都要独立管理,别混在一起。最后是超时控制。每个 Agent 的执行时间要有上限,超时就终止,别让它一直占着资源。
5.2 Workflow 编排与 Agent 协作
复杂任务往往需要多个步骤、多个 Agent 协作完成。这时候就需要 Workflow 编排。编排的核心是定义清楚:有哪些步骤、步骤之间的依赖关系是什么、数据怎么流转、出错怎么处理。
简单的编排可以用串行方式,一步接一步。复杂的可以用 DAG(有向无环图)来描述依赖关系,让没有依赖的步骤并行执行。LangGraph 这类框架就是干这个的,它让你用图的方式定义 Agent 的执行流程,支持条件分支、循环、并行等结构。
编排时要注意的是状态管理。多个步骤之间传递的数据要清晰定义,别让下游步骤去猜上游输出了什么。每个步骤的输入输出都应该是明确的、结构化的,这样出问题时容易定位,也方便单独测试每个步骤。
5.3 监控、日志与可观测性
Agent 系统跑起来之后,你得知道它运行得怎么样。这就需要监控和日志。要记录的关键信息包括:每次循环的输入输出、工具调用的名称和参数、执行耗时、token 消耗、错误信息。这些数据不仅能帮你排查问题,还能帮你优化成本和性能。
日志要结构化,方便后续查询和分析。别用 print 随便打,用结构化的日志库,把关键字段都带上。监控方面,至少要关注几个指标:请求成功率、平均执行轮数、平均耗时、token 消耗趋势。这些指标异常时能及时发现问题。
5.4 从 Demo 到生产的关键检查项
Demo 能跑和生产可靠之间,有一道鸿沟。跨过这道鸿沟,需要检查很多东西。下面这个表格列了我认为最关键的检查项:
| 检查项 | Demo 阶段 | 生产要求 |
|---|---|---|
| 错误处理 | 基本没有 | 分类处理,有重试和降级 |
| 超时控制 | 无 | 每步都有超时,整体有上限 |
| 并发处理 | 单请求 | 限流、隔离、队列 |
| 日志监控 | 结构化日志,关键指标监控 | |
| Prompt 管理 | 硬编码 | 版本化,可回滚 |
| 成本控制 | 不关注 | token 预算,超限告警 |
| 安全过滤 | 无 | 输入输出都有过滤 |
这张表里的每一项,都是从实际踩坑中总结出来的。Demo 阶段可以怎么快怎么来,但上生产之前,这些都得补齐。少一项,系统就可能在某天给你惊喜。
6. 常见问题与排查技巧实录
6.1 Agent 陷入死循环怎么办
死循环是 Agent 最常见的问题之一。表现是 Agent 反复调用同一个工具,或者反复输出类似的内容,就是不肯结束。原因通常有几个:模型没理解任务已经完成、工具返回的结果让模型困惑、终止条件没设置好。
排查时先看日志,确认循环是在哪一步卡住的。如果是模型反复调同一个工具,检查工具返回的结果是不是有问题,比如返回了错误信息但模型没理解。如果是模型反复输出类似内容,检查 Prompt 里有没有让它“确认完成”的机制。解决方式包括:设置最大循环次数强制终止、在 Prompt 里明确告诉模型什么情况下应该结束、优化工具返回格式让模型更容易理解。
6.2 工具调用参数错误的排查思路
模型传错参数很常见。排查时先把模型输出的原始参数打出来看,确认是模型传错了还是你解析错了。如果是模型传错,检查工具描述和参数 schema 是不是不够清晰。如果是解析错,检查解析逻辑是不是太严格。
解决方式:在工具描述里给参数示例、在系统指令里强调参数格式、在解析时做宽容处理(比如字符串和数字互转、忽略多余字段)。如果某个参数经常传错,考虑简化参数设计,或者把复杂参数拆成几个简单参数。
6.3 Token 消耗过快的优化方法
Token 消耗快通常是因为上下文太长。优化方向有几个:压缩历史记录、精简工具返回、优化 Prompt 长度。压缩历史可以用摘要代替原文,精简工具返回可以只保留关键字段,优化 Prompt 可以去掉冗余描述。
还有一个容易被忽视的点是模型选择。不同模型的 token 计价不一样,有些模型便宜但能力弱,有些模型贵但能力强。根据任务复杂度选择合适的模型,简单任务用便宜模型,复杂任务用强模型,能省不少成本。
6.4 模型输出格式不稳定的应对
模型输出格式不稳定,解析就容易出错。应对方式:在 Prompt 里明确格式要求、给格式示例、在解析时做容错。如果格式要求很严格,可以考虑用结构化输出功能(如果模型支持),或者用输出解析器来强制格式。
还有一个技巧是在 Prompt 里让模型“先思考再输出”。让模型在输出最终结果之前,先输出一段思考过程,这样最终结果的格式往往更稳定。这个技巧在需要复杂推理的场景下特别有效。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方式 |
|---|---|---|---|
| 死循环 | 终止条件缺失 | 看循环次数和重复模式 | 设最大轮数,优化 Prompt |
| 不调工具 | 工具描述不清 | 看模型输出和工具描述 | 优化描述,加示例 |
| 参数错误 | schema 不清晰 | 看原始参数和 schema | 简化参数,加校验 |
| Token 暴涨 | 上下文太长 | 看历史记录和工具返回 | 压缩历史,精简返回 |
| 格式错乱 | Prompt 不明确 | 看 Prompt 和输出 | 明确格式,加示例 |
| 执行超时 | 工具太慢 | 看各步耗时 | 设超时,优化工具 |
这张表我放在手边随时查,遇到问题先对号入座,能省不少排查时间。
7. 我踩过的坑和几条实在建议
搭建 Agent 系统这段时间,踩的坑不少,有些现在想起来还觉得冤。说几个印象深的,希望能帮你少走弯路。
第一个坑是过早引入框架。刚开始觉得 LangChain 功能全,直接上,结果发现框架封装太厚,出了问题根本不知道是哪一层的事。后来退回去用最基础的方式重写了一遍,才把每个环节搞清楚。所以我的建议是,先手写一遍最小循环,理解透了再用框架。框架是提效工具,不是理解替代品。
第二个坑是忽视 Prompt 版本管理。有次调好一版 Prompt,效果很好,后来想微调一下,改了几个词,结果效果断崖式下跌。想回滚发现没存旧版,只能凭记忆重写,折腾了好久。从那以后,所有 Prompt 都放版本控制里,每次修改都记 commit message,再也没出现过这种问题。
第三个坑是没设 token 预算。有次跑一个批量任务,没注意 token 消耗,跑完一看账单,心在滴血。后来给每个任务都设了 token 上限,超了就终止并告警,成本一下子可控了。
几条实在建议:一是从最小循环开始,别一上来就搞复杂架构;二是 Prompt 当代码管理,版本化、可回滚;三是错误处理从第一天就做,别等出事了再补;四是设成本上限,别让账单给你惊喜;五是日志打全,出问题时你会感谢自己。
这个领域变化很快,新模型、新框架、新方法层出不穷。但底层的东西变化没那么快,循环、工具调用、上下文管理、错误处理,这些核心概念搞清楚了,上层怎么变你都能跟上。我自己的学习路径就是先把最小循环跑通,然后一个个解决实际遇到的问题,每解决一个,对系统的理解就深一层。这个过程没有捷径,但每一步都算数。