
做 AI Agent 这一年多我发现不管是一千行代码还是几万行代码的系统本质都是在跑同一个东西一个不断循环的“感知 → 思考 → 行动 → 观察”过程。很多人一上来就研究 LangGraph、Spring AI、Rust 并发把架构搭得特别大结果连最基本的循环都跑不稳。这篇文章就聊清楚两件事AI Agent 的最小循环是什么以及怎么从最小循环走向可靠系统。这适合两类人看一是刚入门 Agent被各种概念绕晕想搞明白底层逻辑的朋友二是已经在做项目但总遇到死循环、超时、并发一高就崩想从工程角度夯实基础的开发者。我会把核心原理、最小代码实现、工程化手段和常见坑位全部拆开讲顺便穿插一些框架选型经验。1. 先理解 AI Agent 的最小循环1.1 Agent 到底是个什么东西Agent 不是科幻电影里那种全能机器人。在当下的技术语境里它就是一个“大模型 工具 环境”的组合体通过循环决策去完成一个用户目标。大模型负责思考工具负责执行环境是工具所作用的外部世界比如数据库、网页、文件系统、某个 API。举一个生活化的类比做菜。你是一个 Agent头脑里的烹饪知识是大模型菜刀、锅、灶台是工具食材是环境。你接到“做一盘番茄炒蛋”的指令后会先看冰箱里有什么感知然后决定先切番茄还是先打蛋思考接着动手操作行动看菜的状态再决定下一步观察。这个循环反复进行直到出锅。AI Agent 的最小循环就是把这个过程交给代码和大模型来跑。拆开看每个 Agent 系统都在做四件事接收信息、生成决策、执行动作、把执行结果放回上下文。理解了这个后面所有复杂架构都只是在这个最小循环上做工程强化。1.2 最小循环的四个环节我用工程语言重新描述一遍感知把用户请求、历史对话、环境观察到的内容组装成上下文交给模型。思考模型基于上下文输出下一步动作可能是调用工具也可能是直接回答。行动如果模型决定调用工具程序就去执行真实函数查天气、写文件、查数据库等。观察把工具执行结果作为新的消息写回上下文再回到感知环节。这四个环节里“思考”看起来像黑盒实际上模型的输出会被约束成一个结构化的工具调用指令比如“调用函数 get_weather参数是 city北京”。程序解析这个指令执行对应的 Python 函数再把返回值塞回消息列表模型看到工具结果后继续决策。在这个循环里上下文就是 Agent 的“短期记忆”每轮都会追加新的消息。如果工具结果很大或循环次数太多上下文会被撑爆这也是后面要说到的上下文管理问题的起源。1.3 最小实现几十行代码跑通一个 Agent别急着上框架先自己手写一个最小循环这是理解 Agent 最有效的方式。我用 Python 加 OpenAI SDK 做了个极简版换成其他模型接口逻辑也一样from openai import OpenAI client OpenAI() tools [ { type: function, function: { name: get_weather, description: 获取指定城市的天气, parameters: { type: object, properties: { city: {type: string, description: 城市名} }, required: [city] } } } ] def get_weather(city: str) - str: # 这里替换成真实天气 API return f{city}今天晴天气温25度 messages [{role: user, content: 北京今天天气怎么样需要带伞吗}] for _ in range(10): # 设置最大循环次数防止死循环 response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, ) msg response.choices[0].message messages.append(msg) if not msg.tool_calls: print(msg.content) break for tool_call in msg.tool_calls: if tool_call.function.name get_weather: import json args json.loads(tool_call.function.arguments) result get_weather(args[city]) messages.append({ role: tool, tool_call_id: tool_call.id, content: result })这段代码已经构成一个完整的 Agent 闭环。注意几个关键点模型返回的 tool_calls 包含函数名和参数参数是 JSON 字符串必须解析后才能执行工具执行结果要以 role 为 “tool” 的消息追加并且带上了 tool_call_id 做关联循环必须有上限否则模型一直在调用工具就崩了。这个最小实现能跑通之后你会直观感受到 Agent 的核心难点模型输出的不确定性。它可能不调工具直接回答错误答案可能连续调用工具十几次可能把参数拼错。可靠系统要解决的就是这些问题。2. 从最小循环到可用系统绕不开的准备最小循环只是骨架真实项目里还要处理工具定义、记忆、规划等基础设施。这一节讲三件绕不开的事。2.1 工具调用与函数定义的边界工具定义是把外部能力暴露给模型的关键。很多新手把函数写得很大结果模型经常用错。我踩过几次坑之后总结出几个原则一个工具只做一件事参数要少且语义清晰。比如不要写一个“全能数据查询器”而是拆成 get_user_info、get_order_list。description 要写清楚“什么时候用”最好带上“如果用户没提订单不要调用订单查询”这种约束。参数类型要严格能枚举就枚举模型填错的概率会降低很多。工具返回值要简洁只返回必要信息大段日志直接扔进上下文会浪费 token还会干扰模型判断。工具定义还涉及权限边界。Agent 能调用的工具就是它的“上限”。如果你的 Agent 需要访问内部系统一定要做细粒度的权限控制比用户命令能触达的工具列表更小。不要把所有数据库读写权限直接暴露给模型这跟给员工发一张无限额度的公司卡没区别。2.2 记忆与上下文管理最小循环里所有历史消息都会进入上下文但真实业务场景下对话不会只有三轮。我见过一个客服 Agent聊到第十轮系统提示词加完整的商品列表加历史记录一次请求直接烧掉 3 万 token响应也变慢。上下文管理的常见手段有三种截断只保留最近 N 轮对话并且手动压缩系统提示词。摘要每几轮对话后让模型把前面的内容总结成一段摘要塞回上下文。向量检索把长期信息存入向量数据库需要时用相似度检索出相关片段再注入上下文。这三种各有优劣。截断最省事但会丢失关键信息摘要能保住重点但摘要本身可能出错向量检索适合知识库问答但实现复杂度高。我通常的做法是组合使用核心上下文始终保留中间轮次做摘要外部知识通过 RAG 检索动态加入。还要注意多轮对话里的“状态”。Agent 的工具调用完成后如果后续决策还依赖之前的中间结果一定要把关键结果显式写入上下文而不是指望模型自己记住。模型对细节的记忆能力远没有想象中那么强。2.3 规划与任务拆解的工程化很多人把“规划”理解成让模型写一个 todo list 再逐项执行。这在简单任务里可行但在复杂场景里单个模型输出的 todo list 往往质量不稳定。更工程化的做法是让“规划”显式化。有两种主流思路ReAct 风格不强制显式规划让模型在循环中动态决定下一步。Plan-and-Execute 风格先让模型拆解出任务清单然后逐个执行执行完再检查是否符合目标不符合就重新规划。Plan-and-Execute 看起来高级但实际落地要小心。如果任务清单本身拆错了会出现做了半天无用功的情况。我一般会加一道“规划校验”的步骤让模型先告诉自己“这个任务的关键依赖是什么哪些可以并行哪些必须串行”再进行工具调度。另外任务拆解要区分“动态拆解”和“静态模板”。很多业务场景其实有固定流程比如订单售后流程确认身份 → 查单 → 判断责任 → 给出方案这种场景直接用状态机比让模型自由发挥可靠得多。Agent 的自由度应该被约束在实际需要的范围内。3. 把 Agent 做成可靠系统工程实践才是分水岭最小循环像赛车引擎可以直接上赛道但不能保证不爆缸。可靠系统要做的是给引擎加冷却、加安全笼。这一节我从五个工程维度拆解。3.1 超时、重试与失败处理大模型调用不是本地函数它可能因为网络问题、服务端过载而返回错误也可能一直挂在那里。不设超时的生产系统就是定时炸弹。我给所有外部调用设三级超时连接超时、读取超时、整体请求超时。以 OpenAI SDK 为例client OpenAI( timeouthttpx.Timeout(connect5.0, read30.0, write10.0, pool5.0) )除了网络超时还有工具执行本身的超时。比如一个工具要调用第三方 API对方挂了整个 Agent 就会被卡住。工具执行建议用asyncio.wait_for或线程池的Future设置超时超时后给模型返回“工具执行超时请稍后重试”而不是让循环挂死。重试也不是盲目重试。模型调用失败要区分是限流还是服务端错误。限流要等待后重试参数错误重试一万次也没用。我常用的策略是5xx 和连接错误做指数退避重试4xx 直接抛出并终止当前请求。失败处理的关键是做兜底话术。当所有重试都失败时Agent 不应该硬着头皮编答案而应明确告诉用户“我现在无法获取天气信息请稍后再试”。这会让产品体验有很大提升。3.2 并发与资源控制Agent 怎么扛并发“ai agent 怎么扛并发”是被问最多的一个问题。这里先说结论Agent 的并发瓶颈通常不在代码而在模型 API 的速率限制和上下文大小。你把 Agent 内部循环拆得再细只要每个请求还是同步阻塞地调模型并发一高必然完蛋。扛并发要从几层入手异步化Agent 的循环可以使用asyncio改造让模型调用和工具调用都变成异步非阻塞。FastAPI 天生支持异步配合AsyncOpenAI客户端可以很好地服务高并发请求。并发上限控制不要无限制地创建协程要使用信号量或连接池限制同时进行的 Agent 任务数。我之前线上出现过几百个并发请求同时打到模型 API 导致限流的情况加了asyncio.Semaphore(20)后稳定很多。请求合并有些模型 API 支持 batch可以把多个短请求合并成一个批量请求。但不是所有场景都适用因为 Agent 循环依赖上一步的结果只有真正独立的请求才能合并。状态隔离每个 Agent 会话的状态要独立存储不能放在全局变量里。用 Redis 按 session_id 存储上下文多个 worker 实例才能水平扩展。还有一点容易被忽略工具执行的并发度。Agent 里可能同时有多个工具返回如果你的工具函数是阻塞的比如同步的数据库查询会挡住事件循环。高阶做法是把工具注册成异步函数或者丢给线程池执行。3.3 可观测性与日志体系Agent 系统比普通 Web 接口难排查因为内部的决策链路不可见。模型为什么这么回答、为什么调用了这个工具、工具返回了什么不做追踪根本无从谈起。可观测性要做到三个层面日志按 session_id 和 trace_id 记录每一轮的输入输出。重点记录四件事模型请求的消息数量、模型返回的内容或工具调用、工具执行结果、循环次数。指标统计任务成功率、平均完成时间、平均循环次数、token 消耗量、工具调用失败率。这些数据能直接反映 Agent 质量。链路追踪用 OpenTelemetry 的 Span 把一次 Agent 任务的所有环节串联起来。推荐把“规划→工具调用→模型再决策”的整体作为一个 trace每个内部调用作为一个 span。我还会专门给 Agent 加一个“思考日志”把模型每次返回的原始响应包括 tool_calls 完整 JSON落盘方便出问题时复现。很多人只看最终答案但 Agent 的问题往往出现在中间步骤。3.4 状态持久化与恢复扛住宕机是可靠系统的底线。Agent 任务执行到一半服务重启了怎么办上下文还在吗很多开发者会在 Redis 里存上下文列表但 Redis 本身也可能丢数据。生产环境我会做两级存储实时状态写 Redis用于多实例访问和快速恢复。审计备份每完成一个关键节点比如一次工具调用把上下文同步写一份到数据库或对象存储。恢复流程是任务中断后从备份恢复上下文重新创建 LLM 客户端从上次最后一个未完成的动作继续执行。这里有一个坑恢复到“未调用工具”的状态还是“已调用但未观察结果”的状态我建议恢复到“已调用但未观察结果”避免工具副作用重复执行。比如“发送邮件”这种非幂等操作重复执行会出大问题。所以工具设计时就要考虑幂等性或者至少让 Agent 记录执行结果。状态持久化不只是技术问题更是业务安全边界问题。4. 技术选型框架不是银弹取舍是关键工具链现在非常繁荣有 LangChain/LangGraph、Spring AI、Rust 方案也有扣子这类低代码平台。选型本质是在评估风险你对生态的依赖程度有多高4.1 LangGraph / LangChain / FastAPI 怎么配合LangChain 把场景抽象得很全面但正因为抽象太多踩坑成本也高。LangGraph 解决了“有状态图”的问题适合做复杂多跳 Agent节点之间的状态管理比较清晰。FastAPI 是服务层的好选择异步支持好类型提示完整写起接口来非常舒服。我目前的实践组合是业务服务用 FastAPI 提供 HTTP 接口Agent 内部用 LangGraph 构建有状态的工作流工具层直接用普通 Python 函数注册。LangGraph 的节点可以定义条件边这种表达力很适合 Agent 循环。但如果你只需要一个简单的“调工具→再决策”循环用 LangGraph 反而重了。4.2 Spring AI 与 Java 生态企业级 Java 团队往往绕不开 Spring AI。它是把 AI 能力注入 Spring 生态的一套框架好处是能够复用现有 Spring Boot 的依赖注入、事务管理、监控体系。如果你的核心业务在 Java 里用 Spring AI 能够减少语言切换的鸿沟。但是 Spring AI 的迭代速度很快API 变动比较频繁社区沉淀也还不够。我接触下来适合“在 Java 服务里嵌入一个 Agent 模块”的场景但如果是做积木式的复杂 Agent 产品Java 生态的灵活度会低一些。用之前先评估你的团队是否真的需要 Java 生态的稳定性而不是因为“公司用 Java”就直接选。4.3 Rust 的语言级优势有人问“基于 Rust 语言做 AI Agent”这个问题更多是冲着并发和性能去的。Rust 的内存安全和高并发能力确实让 Agent 服务可以承载更高密度的并发同时保持极低的资源占用。理论上一千个 Agent 任务的并发Rust 的资源和稳定性都要比 Python 好。但实际的痛点在于生态和迭代速度。Rust 的 LLM 框架还在早期工具链不够丰富上手门槛高写业务代码效率低。我的建议是除非你的 Agent 服务已经到了需要极致压榨并发性能的阶段否则没必要用 Rust 从零写。比较务实的路线是主服务用 Python/Java把高频或计算密集的子模块用 Rust 做成 sidecar 或独立服务。4.4 低代码平台扣子 Coze 这类工具能干嘛扣子这类低代码平台确实能快速搭出 demo很多人都拿它做一些小红书自动发消息、简单客服问答之类的 Agent。它的优势是内置了知识库、插件、工作流编排不需要写代码就能做出一套可用的 Agent。但低代码平台的边界也很明显复杂状态管理、私有化部署、高并发定制化逻辑很难施展。我之前见过团队用扣子搭了一个业务流后期想接入自己的鉴权和数据库发现非常别扭。它适合做原型验证、快速给非技术同事做自动化工具真正上生产系统还是得回到代码。用的时候心里要清楚低代码是“能做”不代表“能所有场景都做得好”。5. 可靠系统的评估没有度量就没有优化Agent 系统的质量是不能靠感觉判断的。模型换一个版本可能对话质量提升但工具调用准确率下降不跑评估根本发现不了。5.1 离线评估集怎么建准备一批覆盖典型场景的测试用例每个用例包含用户输入、期望走的步骤、期望的最终答案或答案应该包含的关键信息。数量不用太多50-200 条就能发现大部分问题。评估指标分三类任务成功率Agent 是否正确完成了目标。工具调用准确率该调用哪个工具就调用哪个工具不能乱调。效率指标平均循环次数、平均 token 消耗。跑评估时可以用一个“评估模型”当裁判让 gpt-4o 或本地模型打分。但裁判模型也有偏差所以最好同时保留人工抽检。5.2 线上回归与 A/B离线评估只能守住下限上线后还要做 real traffic 监控。最有效的办法是灰度先让 5% 流量走新 Agent 版本对比旧版本的任务完成率和用户满意度。每次改 prompt、换模型、调整工具参数都要跑一轮灰度回归。我还有一个习惯把线上失败的样本自动收集到“bad case 库”每周挑几个典型的更新到离线评估集里。这样评估集会越来越贴合真实场景不会变成自嗨的玩具。5.3 人在回路中的兜底没有任何 Agent 能保证 100% 正确所以可靠系统必须考虑人介入的机制。当 Agent 的自信心低或连续两次工具调用失败时应该主动转人工。最低成本的做法是设置一个“bump 条件”循环超过 N 次就停止自动执行把当前上下文和候选方案一并交给人工客服处理。人在回路不只是兜底它也是收集反馈的机制。人工客服看到 Agent 的中间日志后修正一次这个修正结果可以成为优化工具调用时效的训练数据。这条飞轮转起来Agent 的质量才会持续提升。6. 高频问题与实战排查6.1 Agent 陷入了死循环这是最常见的问题。模型在“调用工具→查看结果→再调用工具”之间反复横跳可能因为上下文信息不足也可能因为模型本身的毛病。排查思路先在循环里打点记录每次工具调用的 name 和参数。看是不是同一组工具反复调用。检查工具结果是否有用。很多死循环是因为工具返回信息不够模型拿不到答案只能一直重试。设置最大循环次数比如 5 次达到后让模型总结当前已获得的成果并给用户一个“当前信息不足”的回答。你可能会看到“Agent 只调用一次工具就结束了”这种反向问题。排查下来往往是工具定义写得不好模型认为一次调用就够了或者系统提示词里缺少“你可以多次调用工具直到完成任务”的引导。这种需要反复调整 prompt同时看评估集改进是否有效。6.2 工具调用结果解析失败模型返回的 tool_calls.arguments 是 JSON 字符串但偶尔会有非法 JSON比如多了一个逗号、键没带双引号。不要慌最直接的处理是捕获解析异常把错误信息发给模型让它重新生成参数。这相当于一次自动修复try: args json.loads(tool_call.function.arguments) except json.JSONDecodeError: messages.append({ role: user, content: f你刚才的工具参数 {tool_call.function.arguments} 不是合法 JSON请重新生成 }) continue另外要保证工具函数的入参校验。模型可能传了 schema 里没有的字段或者字段类型不对。不要直接抛异常把异常信息包装成“工具执行失败失败原因是 XXX请检查参数”返回给模型它通常能自我纠错。6.3 并发一高就嗝屁如果你已经用了异步但并发还是起不来重点排查两件事一是工具函数有没有用阻塞式同步 I/O二是模型 API 的限流参数。很多云厂商的并发限制是每个 key 的 QPM每分钟请求数而不是并发数。异步跑起来后可能瞬间打满配额。正确的做法是引入令牌桶或简单的滑动窗口限流。如果用一个共享的 Redis 计数器控制“当前正在跑的 Agent 任务数”超过阈值就直接返回 429 让客户端稍后重试。不要为了并发把所有请求都怼出去。6.4 模型输出不稳定同一个 prompt模型这次答对那次答错这很正常。应对思路把 temperature 调到 0 或 0.1在实际项目中我用 0 配合重试效果挺明显在系统提示词里加入“必须遵守步骤”的显式约束为关键场景准备 few-shot 示例让模型模仿示例的输出格式。更进阶的手段是用“结构化输出”功能强制模型返回符合 JSON Schema 的结果。无论是工具调用的参数还是最终回复都限制成固定结构能很大程度避免乱写的现象。写在最后我的一点实操体会做 Agent 这一年多我踩过最大的坑不是模型能力不够而是系统设计时把“可能性”当成了“确定性”。最小循环跑通只是第一步后面每一步——超时怎么兜、并发怎么控、状态怎么存、坏案例怎么回收——才是真正决定 Agent 能不能上生产的因素。另外我建议所有读者都手写一次最小循环哪怕是几行代码。当你亲手看到模型返回的 tool_calls、亲手把工具结果塞回上下文、亲手调试死循环你会对 Agent 的整个机理有非常直观的理解。之后再上 LangGraph、Spring AI 这些框架你会发现它们只是帮你把循环的某几个环节固化下来核心逻辑和你手写的那几行一样。最后分享一个小技巧Agent 遇到反复纠缠不清的问题时不一定要继续在循环里打转先给用户一个过渡性回答把上下文记录下来异步继续处理或转人工。业务上让用户等三秒拿到一个“处理中”的状态远好过让用户盯着 AI 转圈三十秒后得到一个错误结果。