做完半年 AI Agent 应用,我踩过的 5 个坑,全是"看起来解决了"的那种

去年年底我从纯 Java 后端转到 AI 应用这条线上,一开始以为最难的是一堆新概念:RAG、MCP、ReAct、Memory......文档看一圈,感觉都能讲两句。

真正上手做项目才发现,最难的不是"不懂概念",而是每个坑我在踩之前都以为自己已经解决了。

这篇不复述概念,只写五个真实踩过的坑,以及踩完之后我理解的正确做法。

坑一:max_steps = 10 让我以为死循环问题解决了

这是我最早的"自信"。给 ReAct 循环设个最大步数,超了就终止,日志里再也不会无限增长了,挺完美。

然后线上出现一个现象:某个深度研究任务,正常应该跑 30 步左右出结果,现在跑到第 10 步就被我杀掉了,用户拿到的是半成品。而另一个真正在打转的任务,因为每步"看起来都在做事",一直撑到第 10 步才被杀,白烧了 10 步的 Token。

我犯的错是把"动作重复"当成了循环的判据。

但这两件事不是一回事:

  • 搜索 Agent 正在分页拉数据,每次工具名都一样、参数只差一个 page,这是有进展的;
  • 接口第一次返回限流,系统按错误类型退避重试,调用参数完全相同 ,这是合理容错;
  • 反过来,Agent 每次都换一个搜索词,参数全不一样,但一条新证据都没拿到 ,这是实打实的空转。

所以单看"做了什么"根本判不准,得同时看"状态变了什么"。

我后来改成四个维度一起看:

维度 看什么 判据
动作 生成动作指纹 :子任务 + Agent 名 + 工具名 + 标准化后的参数,取哈希 指纹在窗口内反复出现 → 预警
结果 文本比摘要语义相似度,结构化数据比关键字段 输出高度相似 → 加重嫌疑
状态 已完成步骤有没有增加、待办有没有缩小、有没有新证据 状态没推进才是硬证据
路径 把调用链画成有向图,看 A → B → A 有环 ≠ 有问题,要回到 A 时看状态变没变

参数标准化这步千万别省。参数键顺序变一下、搜索词多一个空格,如果不做标准化,同一件事会被算成全新动作,指纹就废了。

max_steps 最后我保留了,但定位变了 ------它不是循环检测,它是保险丝。防止问题无限放大的兜底,不是诊断手段。而且简单查询和深度研究不能共用一组阈值,得按任务类型分开校准。

坑二:把 MCP 当成了 Function Calling 的替代品

这个坑是我在准备技术分享时被自己问住的。

我当时的理解:MCP 出来了,Function Calling 就过时了,以后工具调用都走 MCP。

然后我问自己:那 MCP 底层靠什么驱动工具的?我答不上来。

真相是 MCP 底层依然靠 Function Calling。

MCP Client 连上 Server 之后,会调 list_tools 把所有工具定义拉下来,转换成模型原生的 Function Calling 格式 再传给模型。模型输出的还是 tool_calls,Client 再把它路由到对应的 Server 执行。

从模型视角看,它完全感知不到 MCP 的存在------它以为自己只是在做普通的 Function Calling。MCP 所有的"魔法"都发生在宿主程序那一层:工具发现、schema 转换、请求路由、结果回传。

所以这两个东西根本不在一个层次:

  • Function Calling 管"一次调用长什么样"------像 HTTP 协议,解决传输格式;
  • MCP 管"一堆工具怎么被组织、发现、跨项目复用"------像 REST 规范加服务注册发现。

有个推论挺关键:如果模型本身不支持 Function Calling,MCP 就完全用不了,因为这层"翻译"失效了。这也解释了为什么有些推理模型跑不了 MCP。

那 MCP 到底解决了什么?我算过一笔账:团队里 5 个应用,每个接 8 个工具,就是 40 份工具对接代码。GitHub API 改个字段,你要在 5 个地方同步改,漏一个就深夜报警;想从 Claude 迁到 GPT-4,40 份里的调用格式全要重适配。

MCP 的思路是把工具做成独立运行的标准化服务,谁要用就来连。工具只实现一次,Claude Desktop、Cursor、你自己写的 Agent 都能用。

实际配一个确实简单,Claude Desktop 里加几行配置就能自动发现工具,一行对接代码都不用写:

复制代码

但"配置简单"不等于"这层抽象没有代价" ------坑三就跟着来了。

坑三:工具失败一律 return "调用失败"

我一开始的代码长这样:工具调用抛异常,catch 住,统一返回一句"调用失败"给模型。

看起来挺健壮,实际上是我给 Agent 挖的最大一个坑。

因为模型拿到"调用失败"这四个字,它不知道这次失败值不值得重试 。它最容易的选择是:把上一个动作原样再做一遍。

于是超时、限流、参数错、权限拒绝------四种完全不同性质的失败,在模型眼里长成一个样,全都被当成"再试一次说不定就好了"。

正确做法是按失败性质分流:

失败类型 处理
超时 / 限流 有界重试 + 退避,这是临时性的
参数错误 / 权限拒绝 直接修正参数或问用户,重试一万次也没用
一直没有进展 禁用当前失败路径,换参数或换工具,必要时从检查点重新规划
预算耗尽 / 风险越界 停止自动执行,把已完成结果、卡点、可选方案交给人工

我改成把错误类型、是否可重试、建议动作都结构化返回之后,Agent 的无效重试少了一大截。

坑四:有副作用的工具,我一个都没加幂等键

这个坑最贵,因为它不只是多花 Token。

当时场景是 Agent 帮忙发通知邮件。由于模型对"这件事还没做完"的判断出错,同一封邮件被发了两遍。

问题出在两处叠加:

  1. 工具明明执行成功了,但我没有把"已完成"和"新证据"写回任务状态,下一轮模型仍然以为这事没做;
  1. 邮件发送、扣款、建群这类有副作用的工具,我没有加幂等键。

后果就是:重复调用会再一次改变外部系统。这比多烧几千 Token 严重得多------用户收到两封邮件只是尴尬,如果是扣款工具呢?

我现在的规矩:所有有副作用的工具必须带幂等键,且执行结果必须显式写回状态。

坑五:以为用了 LangChain4j 的注解,模型切换就是免费的

我一开始对 LangChain4j 的理解是"LangChain 的 Java 版",觉得它就是把 Python 那套翻译成 Java。后来发现这个定位本身就错了------它不是官方移植版,API、内部实现、发布节奏都独立于 Python LangChain。

它真正有价值的地方是顺着 Java 的习惯设计:ChatModel、EmbeddingModel、EmbeddingStore 做统一抽象,高层用 AI Services------声明一个 Java 接口,运行时给你代理实现,很像我熟悉的 Spring Data JPA 或 Retrofit。

复制代码

写完接口就能调,Prompt 拼装、消息转换、输出反序列化都不用管了。

但我踩的坑是把"减少胶水代码"理解成了"消灭供应商差异"。

抽象只能覆盖交集 。某个模型支不支持工具调用、原生 JSON Schema、多模态输入、思考内容、特殊采样参数------这些都要单独查能力矩阵。切换供应商之后,Prompt 效果、Token 计算、限流策略、异常类型、评测基线,全都得重新验一遍。

还有一个容易忽略的点:ChatMemory 保留的是模型当前需要的上下文 ,它不等于完整聊天记录。复杂的长事务还得交给业务系统或者专门的编排层。

另外选型时要看模块成熟度------官方到现在仍有带 beta 后缀的模块,Guardrails 和可观测性部分标的是实验性,上生产前得自己权衡。

最后几条我真正沉淀下来的判断

  1. "看起来解决了"比"没解决"危险 。max_steps、统一错误、注解一把梭,都给了我虚假的安全感,掩盖了真正的问题。
  1. 观测状态比观测动作重要。Agent 系统的核心不是"它调了什么工具",而是"任务往成功标准推进了没有"。
  1. 有副作用的工具是另一个风险量级。Token 是钱的问题,副作用是事故的问题,处理逻辑要分开设计。
  1. 框架减少的是重复代码,不减少工程责任。权限、数据隔离、评测、运维,框架不替你背。

这半年踩的坑基本都在这些方向上。后面打算把 Agent 的错误处理协议和评测集搭建再系统地整理一篇,如果对你有用,可以先关注一波,发了会第一时间推。

相关推荐
用户EasyAdminBlazor1 小时前
Blazor Admin 关联表怎么处理?EasyAdminBlazor Navigate、Include、Join 实战
后端
一粒麦仔1 小时前
llama.cpp / Ollama / LM Studio:本地 LLM 推理栈的硬核拆解
人工智能·后端·架构
用户204937554951 小时前
Rust推理库编译失败排查:从Cargo到输入法集成
后端·程序员
yunwei371 小时前
eBPF 开发者教程: 简单的 XDP 负载均衡器
linux·后端·性能优化
考虑考虑1 小时前
SQL中的 CASE WHEN
数据库·后端·sql
IT_陈寒1 小时前
Python的列表推导式差点让我加班到凌晨
前端·人工智能·后端
掘金者阿豪1 小时前
MySQL 迁移金仓,SQL 能正常执行,为什么查询结果却不一样?
后端
机器之心1 小时前
突发!GPT-6 Sol与Claude Opus 5.5同日开打,谁是「性价比之王」
前端·人工智能·后端
用户34232323763171 小时前
Ternary-Bonsai-2-27B(PTQ1_0) 在 RTX 4090 上的部署与调优实录
后端