前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9915
  • 谷歌 ADK 配方库贯通 Agent 开发部署
  • 为多轮 Agent 建立持续评估流水线
  • ctxtrim 压缩日志,节省编程上下文
  • 技能触发不准,先改好 description
  • AI 生成页面上线前,补验 GA4 事件链路
  • 让客服机器人把处理结果反馈到检索排序
  • Muse Spark Open 发布,主打编程与 Agent
  • FLUX 3 Klein 开放权重,8GB 显存可运行
  • Gemini 4 Argon 整合机器人控制与仿真
  • 英伟达开放多模态模型,面向企业智能体
  • Vercel 扩展后端与 Agent 部署能力
  • 英伟达发布面向智能体的多模态开放模型
  • 英伟达扩充智能体模型,覆盖云端与本地
  • 日合并超三万次,Anthropic 重构版本控制
  • CLiF 将智能体运行记录转为训练评测资产
  • GitHub 代码扫描智能体修复正式可用
  • Webhook 驱动 Agent 的可靠性要点
  • 用 TypeScript 为 Agent 补齐应用连接器
  • 177 项科研技能补齐智能体领域知识
  • 发现 AI 擅自决策:简单审查胜过复杂流水线
  • WhatsApp 智能体上线后的工程难题
  • Netflix 为编程智能体接入 IDE 代码分析引擎
  • PentAGI 用多智能体执行渗透测试
  • MCP 排错日志里的凭据泄露隐患
  • Jev新版加入图像输入与26.2万上下文
  • 微软用知识编译降低智能体延迟与耗量
  • Grok语音模型升级,接口与价格保持不变
  • 把 ClickUp 项目记录变成 Agent 可查询记忆
  • Matrix-Game 4.0 开源单卡实时生成游戏
  • Agent 接入外部服务前先检查失败恢复能力
  • 多模型网关踩坑:推理预算吃光了回答
  • Step 5 Preview 开放权重,可部署百万上下文
  • AI 工具写操作防重:执行前先抢占
  • 给 n8n 的模型输出加一道契约检查
  • 自托管 AI 工具漏洞公告可能漏过扫描
  • 本地运行 Gemma 4:推测解码与草稿模型
  • AI 代码上线前先排查这些安全漏洞
  • Copilot更新HydraFusion并新增Max
  • MCP工具数量实测:准确率之外更要看成本
  • Apigee 支持在自有集群运行 MCP 工具
  • 编程 Agent 源码研究:架构比功能清单耐用
  • 把 GPU 内核调优变成夜间并行搜索
  • AI 生成的 Supabase 权限代码可能允许提权
  • Agent 成本控制要先预留并发调用预算
  • 全 AI 编码项目如何靠质量关卡持续交付
  • AI反编译加速老游戏移植,也带来风险
  • 用 AgentCore 构建可适应新格式的文档处理
  • 用 Issue 标签驱动 AI 分析与提 PR
  • AI 辅助六天串起 Chrome 漏洞利用链
  • Claude Code 改造电商站的操作与复盘
  • Agent 平台缺少身份配置却开放管理权限
  • 已加载 51 / 9915
8.0
热点
AI SCORE
技术实践2026-10-15 20:12

WhatsApp 智能体上线后的工程难题

dev.to · AI#Agent#客服自动化#TypeScript
Editor brief · 编辑速览

文章结合 WhatsApp 客服场景,讨论越界提问、人工接管、消息突发、旧会话恢复及模型故障等生产问题。其关键做法是将是否回复的分类判断与答案生成分开,并用强制工具调用返回枚举结果。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

WhatsApp 上的 AI Agent,演示起来总是很漂亮。你贴上一段业务介绍,问一句“你们几点开门?”,它就会用一句友好的话回答。然后,真正的客户来了。他们会问竞品的问题,发送语音消息,用全大写输入“HUMAN!!”,在五秒内连发三条消息,还会回复三天前的对话。模型服务商也会有状态不佳的时候。还有客户会要求机器人忽略自己的指令。

这篇文章会介绍,为了让 AI Agent 在 WAPGROWAI 的 WhatsApp 收件箱里稳定运行,我们围绕模型构建了哪些东西,以及我们认为每个团队在启用 Agent 之前都应该想清楚的问题。文中的 TypeScript 代码片段是这些实现模式的小型简化版本,每段都针对它所描述的场景运行验证过。

1. 在模型回答之前,先判断问题是否属于业务范围

最重要的设计决策是:负责生成回答的模型,不应该同时负责决定要不要回答。

每条收到的消息,首先都会交给一次独立、低成本的分类调用,返回三个标签之一:回答、拒绝,或者转交人工。标签通过带有 enum 的强制工具调用返回,temperature 设为 0,因此结果绝不会是需要你自行解析的自由文本:

// scope.ts
export type Scope = "in_scope" | "out_of_scope" | "wants_human";

// The model must call this tool; free text is never used as the label.
export const LABEL_TOOL = {
  type: "function",
  function: {
    name: "label_message",
    parameters: {
      type: "object",
      properties: { scope: { type: "string", enum: ["in_scope", "out_of_scope", "wants_human"] } },
      required: ["scope"],
    },
  },
};

type CallTool = (system: string, user: string, tool: typeof LABEL_TOOL) => Promise<{ scope?: string }>;

export const classifyScope = async (message: string, businessName: string, callTool: CallTool): Promise<Scope> => {
  const system =
    `Label one customer message for ${businessName}. ` +
    "wants_human: they ask for a person or are clearly frustrated. " +
    "out_of_scope: unrelated to this business. When unsure, choose in_scope.";
  try {
    const { scope } = await callTool(system, message.slice(0, 2000), LABEL_TOOL);
    return scope === "out_of_scope" || scope === "wants_human" ? scope : "in_scope";
  } catch {
    return "in_scope"; // fail open: a classifier outage shouldn't silence the agent
  }
};

export const HANDOFF_LINE = "I'll pass you to someone from our team — one moment.";
export const refusalLine = (businessName: string) =>
  `Sorry, I can only help with questions about ${businessName}. What can I help you with?`;

这里有两个选择,是我们有意做出的权衡:

拒绝和转人工的回复使用固定字符串,不由模型生成。偏离业务范围或带有敌意的消息,根本不会进入负责生成回答的模型,也就没有机会诱导它。当“忽略你的指令,然后……”得到的回复,是你自己写好的一句话时,prompt injection 能钻的空子就少得多。

分类器出错时,默认放行。如果调用出错,就把消息视为业务范围内的问题,交给回答模型;回答模型自身的 prompt 仍会限制它只处理与业务有关的问题。另一种选择是分类器稍有故障就保持沉默,我们认为这对客户体验更糟。如果你选择另一种做法,也应该是主动决策,而不是意外形成的行为。

分类器只看当前这条消息。这样既快又便宜,也正因如此,我们要求它在不确定时选择 in_scope:如果没有上下文,单独一个“是”字没有任何意义。

2. 以一份文档为依据,而且尽量精简

WAPGROWAI 的 Agent 根据一份由商家编写或上传的业务资料回答问题:卖什么、营业时间、配送和退货规则、愿意公开报价的价格,以及机器人绝不能作出的承诺。每次调用时,整份资料都会放进 system prompt,并附上规则:事实、价格和营业时间必须来自这份资料,Agent 不能下单、收款或预约。

我们选择不为此使用 retrieval,也就是不做分块、embeddings 和向量存储。小商家的业务资料通常只有几页,而且资料长度上限是 200,000 个字符。整份发送,可以避免索引过期,也能避免检索步骤悄无声息地漏掉最关键的那一段。代价是每次调用都要消耗这些 token;如果你的知识库很大,就需要留意这项权衡。

我们还要求模型使用客户的语言,最多用三个短句回答。在 WhatsApp 上,一大堵文字往往不如简短的回答。

3. 历史记录来自你的消息存储,而不是模型的记忆

模型不会记住对话。每次调用时,你都要根据已经存储的消息重新构建上下文,而其中的细节很重要:

// history.ts
type StoredMessage = { direction: "in" | "out"; type: string; text?: string };
type Turn = { role: "user" | "assistant"; content: string };

const PLACEHOLDER: Record<string, string> = {
  audio: "[sent a voice message]",
  image: "[sent an image]",
  video: "[sent a video]",
  document: "[sent a document]",
  template: "[sent a template message]",
};

// Everything the business sent — AI, chatbot or a human agent — is an "assistant" turn.
export const buildHistory = (messages: StoredMessage[], maxTurns = 16, maxChars = 2000): Turn[] =>
  messages
    .map((m): Turn | null => {
      const content = m.text?.trim() || PLACEHOLDER[m.type];
      if (!content) return null;
      return { role: m.direction === "in" ? "user" : "assistant", content: content.slice(0, maxChars) };
    })
    .filter((t): t is Turn => t !== null)
    .slice(-maxTurns);

商家发出的所有消息,都算作 assistant 轮次,包括人工客服输入的消息。如果一个小时前有人回答过某个问题,AI 就必须看到那个回答,否则它可能给出相互矛盾的说法。

媒体消息用占位文本表示。模型看不到语音或照片,但应该知道客户发过这些内容,因此历史记录里会放入 "[sent a voice message]",而不是直接留空。

限制轮次数和字符数。旧上下文会消耗 token,却很少有帮助;限定窗口大小,可以让成本和延迟都更可预测。

4. 同一段对话,一次只生成一条回复

客户常常会连续发消息:“你好”“你们送货到浦那吗”“运费多少”。如果每条消息都发起一次独立的模型调用,这些调用会以随机顺序完成,客户就会收到三条内容重叠的回答。

// lock.ts
const inFlight = new Set<string>();

// One reply per conversation at a time. A message that arrives mid-reply is skipped,
// not queued: the reply being generated already reads the latest history.
export const withConversationLock = async <T>(conversationId: string, job: () => Promise<T>): Promise<T | null> => {
  if (inFlight.has(conversationId)) return null;
  inFlight.add(conversationId);
  try {
    return await job();
  } finally {
    inFlight.delete(conversationId);
  }
};

生成回复期间收到的消息会跳过,不会排队——正在生成的回复会读取历史记录,所以通常能覆盖这些消息。这是进程内的锁;如果你运行多个实例,就需要通过共享资源提供同样的保证,例如数据库中的一行记录,或者一个 Redis key。生成期间,WAPGROWAI 还会显示 WhatsApp 的“正在输入”提示,这样几秒钟的思考时间就不会显得毫无回应。

5. 为服务商状态不佳做好准备

Agent 调用 Cloudflare Workers AI 上兼容 OpenAI 的 endpoint,并使用 NVIDIA 托管的模型作为后备。要让故障切换真正有用,关键在于区别对待不同错误:

// failover.ts
export type ErrorClass = "transient" | "quota" | "fatal";
export type Provider = { name: string; call: () => Promise<string>; classify: (error: unknown) => ErrorClass };

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

// Transient: retry here, then move on. Quota: move on now. Fatal (a bad request): stop,
// because another provider would reject it too.
export const callWithFailover = async (providers: Provider[], attempts = 2, delayMs = 400): Promise<string> => {
  let lastError: unknown;
  for (const provider of providers) {
    for (let attempt = 1; attempt <= attempts; attempt++) {
      try {
        return await provider.call();
      } catch (error) {
        lastError = error;
        const kind = provider.classify(error);
        if (kind === "fatal") throw error;
        if (kind === "quota") break;
        if (attempt < attempts) await sleep(delayMs * attempt);
      }
    }
  }
  throw lastError;
};

临时错误,例如超时、容量不足:先在同一服务商上做短暂重试,再切换到下一个。

配额耗尽或模型不可用:重试没有意义,立即切换。

致命错误,也就是请求本身被拒绝:停止调用。把格式错误的请求再发给第二家服务商,只会让失败次数翻倍。

每种任务也都有自己的预算:分类器的 temperature 设为 0,超时时间较短;回答调用可以多给一些余量;生成聊天机器人流程这类长任务,则会给到几分钟。

6. 让输出适配 WhatsApp

模型输出 Markdown。WhatsApp 不渲染标题,而且用单个星号表示粗体。发送前先转换格式,并在句子边界处限制长度:

// whatsapp-format.ts
const MAX_REPLY_CHARS = 900;

// Models write Markdown; WhatsApp uses *bold* and has no headings.
export const toWhatsApp = (reply: string): string => {
  let text = reply
    .replace(/\*\*(.+?)\*\*/g, "*$1*")
    .replace(/__(.+?)__/g, "*$1*")
    .replace(/^#{1,6}\s+/gm, "")
    .trim();
  if (text.length > MAX_REPLY_CHARS) {
    const cut = text.slice(0, MAX_REPLY_CHARS);
    const lastStop = Math.max(cut.lastIndexOf(". "), cut.lastIndexOf("? "), cut.lastIndexOf("! "));
    text = lastStop > 0 ? cut.slice(0, lastStop + 1) : cut;
  }
  return text;
};

还有一种值得添加的过滤:某些推理模型偶尔会返回思维链,而不是回答。检测到这种模式时,应该丢弃内容,别把它发给客户。

7. 决定谁先处理:规则、聊天机器人,还是 AI

一个收件箱通常会同时运行多种自动化。如果没有明确的顺序,关键词规则和 AI Agent 就可能同时回复同一条消息。WAPGROWAI 对每条入站消息的处理顺序是:

优先处理客户对商家所发按钮和表单的回复,例如订单结账表单或模板按钮。

接着执行启动聊天机器人的规则——关键词始终可以把客户带入结构化流程。

已经运行中的聊天机器人继续执行。

只有在这些步骤之后,如果当前对话启用了 AI Agent,才由它回答。

AI Agent 接管一段对话期间,其他规则不会对这段对话中的消息作出响应。

确定性流程优先于 AI,AI 则优先于其余所有处理。不管你选择什么顺序,都要写下来——客户收到两条回复时,别人首先就会问你这个问题。

上线前需要想清楚的问题

这些问题没有唯一正确答案,但每一个都需要经过明确决策:

语音和图片。是转录、回复请客户发送文字,还是转交人工?一个只支持文本的 Agent,收到语音后什么都不说,看起来就像坏了。

模型彻底失败时怎么办。发送一句简短的“我们会稍后回复您”,转交人工,还是保持沉默,等待人工接手?

谁来接手转人工的对话。聊天会分配给某个人吗?有人会收到通知吗?还是进入共享队列等待处理?没人看到的转人工,比根本没有 AI 更糟。

什么会结束 AI 模式。客户要求人工、某条规则、某个按钮——还有,队友回复消息或分配聊天时,是否应该自动结束 AI 模式?

24 小时窗口。回复客户没有问题,但对于已经沉寂好几天的聊天,AI 编写的问候消息会被 WhatsApp 拒绝;主动发送消息需要使用模板。

成本和上限。每条 AI 回复同时也是一条 WhatsApp 服务消息,而 Meta 现在会对每个号码每月超过 1,000 条的部分收费。要提前决定达到月度上限后怎么办。

数据。客户消息会发送给你的模型服务商。你需要清楚数据去了哪里,并在隐私政策里说明。

先分类,使用独立调用,并为“不回答”和“请转人工”准备固定回复。

让 Agent 以一份精简文档为依据,并告诉它绝不能作出哪些承诺。

从自己的存储中重建历史记录,包括人工说过的话。

同一段对话,一次只生成一条回复。

根据错误类型切换服务商,不要盲目切换。

适配 WhatsApp 的格式,并过滤泄露的推理内容。

把自动化的执行顺序写下来。

模型是容易的那部分。生产环境里真正要做的,是决定它什么时候开口、能说什么,以及不该开口时该怎么办的所有配套工作。

我参与开发 WAPGROWAI,这个 Agent 就运行在它的团队收件箱里。如果想从非技术角度了解 AI Agent 应该和不应该为商家做什么,可以阅读 Medium 上的配套文章。本系列的其他文章还涉及 webhooks、24 小时窗口、模板与营销活动,以及共存模式。

如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
发现 AI 擅自决策:简单审查胜过复杂流水线
下一篇
Netflix 为编程智能体接入 IDE 代码分析引擎