基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-02 15:19

客服智能体上线即出错:演示遗漏了什么

dev.to · AI#Agent#生产可靠性#工具调用
Editor brief · 编辑速览

作者复盘电商客服智能体上线后出现的错误退款、重复调用工具和回复语言异常。文章指出,演示中的干净输入与受控交互掩盖了真实用户场景下的外围工程问题。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

一个电商客服 Agent,演示时两步就能查完订单,上线后不到一小时,却退错了订单、连续调用同一个工具 11 次,还用客户没使用过的语言回复。作者的判断是:问题出在模型周围缺失的工程约束。

这篇复盘值得读,是因为它把「Agent 能完成任务」拆成了更实际的问题:输入有歧义怎么办,工具参数错了怎么办,后端超时怎么办,以及什么时候必须停下来找人。对正在接入工具调用的开发者,这些比一段顺滑的演示更接近交付要求。

演示为什么恰好避开了所有麻烦

这个 Agent 负责电商后台工单,技术栈很简单:支持 function calling 的托管 LLM、Python 编排循环,以及几个 REST 工具。它能读取消息,通过内部 API 查询订单、起草回复,也能调用工具发起小额退款。

演示输入是「Where is my order ORD-10293847?」,订单号明确、任务单一,两步完成。作者后来发现,自己无意中提供了四个有利条件:输入干净,用户配合,后端健康,而且是否成功由自己事后判断。

真实工单几分钟就打破了这些条件。邮件里混着签名、历史引用和三个订单号;有人转发葡萄牙语消息;还有人只写「和上次一样的问题」。Agent 把这些内容都当作可信指令,选中了历史邮件中的第一个订单号。

作者的修复是把输入整理放到模型调用之前:剥离引用回复、检测语言、提取候选订单号,并标记歧义。出现多个候选订单时,先追问,不再猜测。作者称,这项改动消除了大部分退错订单事件,但没有给出具体比例。

这里真正有用的做法,是先判断请求是否足够明确,再决定是否允许执行。订单号提取出来了,并不代表已经知道用户要处理哪一笔订单。

JSON 能解析,离允许退款还差几道检查

function calling 能提供结构化输出,却不能保证参数符合业务事实。作者遇到过格式正确但实际不存在的订单号,也遇到过超过订单总额的退款金额。

模型输出应当按不可信输入处理。

下面这段代码同时检查参数结构和订单事实,解决「看起来像合法退款」却不能执行的问题:

from pydantic import BaseModel, Field, ValidationError

class RefundArgs(BaseModel):
    order_id: str = Field(pattern=r"^ORD-\d{8}$")
    amount_cents: int = Field(gt=0, le=50_000)
    reason: str = Field(min_length=5)

def run_tool(name: str, raw_args: str) -> dict:
    if name == "issue_refund":
        try:
            args = RefundArgs.model_validate_json(raw_args)
        except ValidationError as e:
            return {"error": "invalid_arguments", "details": e.errors()}
        order = orders_api.get(args.order_id)
        if order is None:
            return {"error": "order_not_found"}
        if args.amount_cents > order.total_cents:
            return {"error": "amount_exceeds_order_total"}
        return refunds_api.create(args)

要看清 orders_api.get(args.order_id) 之后的检查:正则只能约束 ORD- 后面是 8 位数字,无法证明订单存在。amount_cents 必须大于 0、最多为 50_000,退款原因至少 5 个字符,但这些也不能替代订单总额校验。

校验失败会作为工具结果返回给模型,让它下一步修正。作者说模型通常能改对,没有提供成功率。因此,这是一种纠错机会,不能作为最终保障。

通过校验之后,还要回答另一个问题:这次操作是否允许自动执行?作者把工具分成只读和修改状态两类,将 issue_refund、cancel_order、update_address 放入 RISKY_TOOLS。未通过 is_approved(call) 的调用进入 review_queue.add,返回 pending_human_review 和工单标识,而不直接执行。

后来,他们才对低风险、低金额退款放宽限制。原文没有交代具体放行阈值,也没有描述完整审批流程,不能直接照搬成一套退款策略。

Agent 必须知道何时停,还得留下执行轨迹

订单 API 超时后,Agent 连续发起了 11 次相同调用,消耗 token,最终触发限流。单纯让模型「继续尝试」,没有明确的停止条件。

作者用步数预算和重复调用检测来终止失控执行:

MAX_STEPS = 8

def run_agent(messages):
    seen_calls = set()
    for step in range(MAX_STEPS):
        resp = llm.chat(messages, tools=TOOLS)
        if not resp.tool_calls:
            return resp.content
        for call in resp.tool_calls:
            signature = (call.name, call.arguments)
            if signature in seen_calls:
                return escalate("repeated_tool_call", messages)
            seen_calls.add(signature)
            messages.append(run_tool_message(call))
    return escalate("step_budget_exceeded", messages)

关键是 signature = (call.name, call.arguments):同名工具、同样参数再次出现,就触发 repeated_tool_call;循环达到 MAX_STEPS = 8,则触发 step_budget_exceeded。升级处理时应附带完整上下文,交给人工接续。

这段代码也有边界。8 步限制的是外层循环次数,一次响应仍可能包含多个工具调用;参数直接参与比较,也不等于比较了规范化后的业务含义。作者还建议限制 token、时间和金额预算,但没有给出对应实现。

停得下来,还需要查得明白。第一次退错款时,作者只有 print 和最终回复,无法还原中间经过。后来每一步通过 trace.emit 记录结构化事件,包括 run_id、step、tool、args_hash、latency_ms、tokens_in、tokens_out 和 outcome。

执行轨迹(trace)的价值,是把一次运行中的步骤串起来,回答「哪个工具带着什么参数执行,耗时多少,返回什么结果」。作者称,改造后一次异常可以在五分钟内调查清楚,也能统计工具失败和 token 消耗的分布。这是他们团队的经验,原文没有给出排查耗时的对照数据。

作者认为最有价值的改动,则是把失败对话存为回归用例,每次修改后重放 evals/real_failures.jsonl,通过 case["check"](result) 判断结果是否符合预期。演示只证明某条路径能走通;回归集才能帮助发现,一次提示词修改有没有破坏之前修好的行为。

我的判断

这篇复盘最有价值的地方,是把正确性检查、执行权限和停止条件放回了应用代码。相比继续堆提示词,这些约束更容易检查,也更容易稳定执行。模型即使没有遵守要求,工具边界仍能拒绝错误操作。

但作者开头断言「模型没有问题」,证据并不充分。文中没有模型对照实验,也没有更换模型前后的结果。能支持的结论是:他们发现并修补了多处工程缺口,不能据此排除模型选择的影响。

几项修复也需要结合业务调整。剥离历史引用可能丢掉上下文,「和上次一样」可能需要补查记录;相同参数再次查询,有时是合理的状态刷新;API 超时后的只读重试,也可能适合交给有上限的重试逻辑。作者的重复调用检测适合作为保守起点,未必适合所有流程。

退款代码还没有展示订单归属、操作者权限、累计已退款金额和幂等控制。它验证了「订单存在、金额不过大」,却没有完整证明「这位用户有权退这笔款」。文章没有说明这些能力是否由后端提供,读者需要自行确认。

回归评测同样如此。原文没有展开 case["check"] 如何实现;如果只检查最终回复,仍可能漏掉过程中的错误退款。对能修改状态的 Agent,评测必须关心实际副作用。作者给出了方向,但还不是可直接投产的完整方案。

能拿来做什么

  • 在接入工单或客服 Agent 前,先收集真实输入。作者建议即使只有 20 条杂乱样本,也比精修演示脚本更有价值;优先覆盖多个订单号、转发邮件、信息不足和用户中途改意图的情况。
  • 在 Node 或 Python 的工具执行层统一校验参数,并调用后端核验订单事实与权限。把 invalid_arguments、order_not_found、amount_exceeds_order_total 这类错误作为明确结果返回,同时确保失败调用不会产生退款副作用。
  • 给编排循环设置步骤、时间、token 和费用上限,并设计人工接续入口。可以参考 MAX_STEPS = 8 的写法,但具体阈值应由业务耗时和任务复杂度决定。
  • 从第一批试用开始保存执行轨迹和失败用例。每次调整提示词、工具定义或执行代码后重放回归集,同时检查回复、工具调用和实际状态变化。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
让 Cursor 稳定调用 MCP 的排障思路
下一篇
通过兼容网关自建 E2B 代码沙箱

资讯速览

dev.to · AI · 2026-11-02 15:19

AI 评分 8.0 / 10

相关标签

#Agent#生产可靠性#工具调用

继续阅读

  • Kornia 为 PyTorch 补齐可微视觉算子
  • 让 Cursor 稳定调用 MCP 的排障思路
  • 通过兼容网关自建 E2B 代码沙箱
  • 让 MCP 错误信息支持智能体自主恢复
  • AI重复造工具函数,可能绕过关键业务规则