「我们已经有问答机器人了,现在想升级成 AI 助手」—— 这句话背后往往是一次被低估的重写。问答机器人是一个无状态的函数:消息进去,回复出来,处理完就忘。助手是一个有状态、有在办事项、会主动开口、并且被授权代表某个人做事的常驻角色。换更强的模型不会让前者变成后者,中间隔着四条要自己搭的工程线。这篇讲这四条线各是什么、边界画在哪,以及按 wecomapi 的接入方式怎么落。
四条线,决定你在做哪一种东西
判断很快:把需求文档里的动词圈出来。出现「记住」「帮我」「下次」「到点提醒我」「盯着」这几个词里的任何一个,做的就是助手,不是机器人。每多出现一个,工程量上一个台阶,而这些台阶和模型能力基本无关。
- 状态:机器人一问一答就结束;助手有在办的事,事情跨越多条消息、多天、甚至多个人。
- 记忆:机器人的上下文活在一次请求里;助手要区分这次会话、这个客户、这家企业三种不同寿命的记忆。
- 主动性:机器人只被用户消息触发;助手会被时间、业务事件、外部系统触发,主动开口。
- 授权:机器人只说话;助手会做事,于是必须回答「它代表谁做的」和「做错了算谁的」。
这四条里,团队通常只准备了第一条的一半(把历史消息塞进上下文),然后发现助手记不住事、该提醒的时候不吭声、或者在不该动手的地方动了手。这不是提示词问题,是缺组件。
状态:会话不是状态,任务才是
最常见的做法是把「这件事办到哪一步了」写进上下文,让模型自己记。它在两三轮内看着能用,一到多轮长对话就开始漏 —— 上下文被截断,进度也跟着没了;用户中途插一句别的,模型就把原来那件事丢了。
正确的形状是把「事」建成一个独立实体,和会话解耦:一个任务有自己的标识、目标、待填的槽位、状态机和超时时间。消息只是任务的输入之一,模型只负责从消息里抽取槽位、判断该走哪个转移,进度由你的系统记着。上下文是给模型看的,任务状态是给系统看的,两者不能是同一份数据。
- 同一个人可以同时有多个在办任务,所以要有「当前指的是哪件事」的消歧,别默认只有一件。
- 任务要有超时和结束条件。没有超时的助手会攒下一堆永远处于「进行中」的事,然后在某天全部一起提醒。
- 任务状态的每次转移都留一条记录:由哪条消息、哪次模型调用、哪条规则触发。事后复盘只有对话记录是不够的。
- 待办不要放进提示词。放进去意味着每轮都在为同一份内容付费,而且模型会「顺手」把它改掉。
// 示意逻辑,函数与字段均为自拟;消息与事件的精确字段以 wecomapi 文档为准
async function onMessage(msg) {
const tasks = await taskStore.openBy(msg.sessionId); // 在办的事,可能不止一件
const target = tasks.length > 1 ? await disambiguate(msg, tasks) : tasks[0];
const ctx = {
turn: await memory.recentTurns(msg.sessionId, 8), // 短期:进 prompt
profile: await memory.profile(msg.customerId), // 长期:结构化,按需注入
task: target && target.publicView(), // 只给模型看该看的槽位
};
const out = await llm.extract(ctx, msg.text); // 抽槽位 + 判意图,不让它管进度
if (target) await taskStore.transit(target.id, out, { cause: msg.id });
else if (out.intent) await taskStore.create(msg, out);
await reply(msg, render(out));
}这个拆分还有一个附带收益:任务层不关心输入从哪来,wecomapi 的事件回调送进来的消息、定时器和业务系统的回传,走的是同一个入口。更直接的好处是可测试:任务状态机可以脱离模型单测,模型抽槽位的准确率可以脱离业务单测。混在一起的实现只能靠人工对话来验,改一句提示词就得重跑一遍所有场景。
记忆分三层,只有一层进模型
「让助手记住客户」通常被实现成「把更多历史聊天塞回上下文」。这条路的终点是成本随聊天时长线性上涨,而准确率随之下降 —— 早期的无关内容会持续干扰当前判断。
- 1短期:本次会话最近若干轮。直接进 prompt,按预算截断,寿命是一次对话。
- 2长期:关于这个客户的结论性事实 —— 所在行业、对接人、偏好、已知的禁忌。它必须是结构化的字段,不是一段自由文本,因为要能被查询、被修改、被删除。用到时按需注入,不是每轮都带。
- 3组织级:产品资料、话术、政策。这一层是检索来的,和具体客户无关,命中才注入。
长期记忆的写入要有闸门。让模型自己判断「这句话值得记住」,会记进大量临时信息和它自己的猜测,几周之后档案里全是错的「事实」,而且没人知道是哪一轮写进去的。稳妥做法是只有明确的确认动作(用户明说、人工确认、业务系统回传)才写入长期层,每条记忆带上来源和时间,并且在界面上可查看、可删除 —— 这一条同时是合规要求。
组织级记忆的身份过滤是老问题,站内讲四种架构那篇已经展开过。这里只补助手特有的一条:长期记忆层同样要过滤,而且更难。组织级资料至少分得清内外,长期记忆是助手自己写下的结论,里面混着内部同事的说法和坐席的临场判断,写的时候没有人想过它以后会在对客场景被引用。可见范围要在写入那一刻就记成字段,等到检索时再补是补不齐的。
主动性:谁在决定这次开口
助手和机器人在链路上最大的结构差异在这里:机器人的输出全部由入站消息触发,助手有一条完全独立的出站链路 —— 定时器到了、任务超时了、业务系统回传了结果、某个条件满足了。这条链路要单独设计,它不在回调那条路上。
出站链路的三件事:触发源要能追溯到具体任务,不允许存在「不知道为什么发出去」的消息;同一件事只提醒一次,去重键用任务标识加提醒轮次,不要用时间;发送结果要有确认与重试,主动消息失败时没有用户会来告诉你。用 wecomapi 的消息接口下发之后,把返回和后续状态都记到任务上,助手才知道自己说过什么。
- 静默时段按接收者的时区算,不按服务器时区算。深夜的一条「友情提醒」造成的损失,远大于它想挽回的那点时效。
- 给每个任务设提醒预算,比如最多两次。超过还没推进的,转给人处理,不要让助手自己一直催。
- 任何主动消息都要能被关掉,且关闭状态要落在客户档案上而不是某段代码的开关里。
授权:助手代表谁说话
这是最被低估的一条,也是唯一一条做错了会产生真实赔付的。问答机器人只是回答问题,说错了顶多尴尬;助手一旦以某个身份对外发言或动手,责任归属立刻变成一个必须回答的问题。
- 1对内助手:只服务内部成员,答错的代价可控,可以放手让它直接答、直接查。绝大多数团队的第一版应该落在这里。
- 2对外辅助:面向客户的场景,先做「助手生成建议、成员点发送」。它保留了效率收益,把责任留在人那一侧,且能顺便攒下一批人工修改样本用来改进。
- 3对外代发:让助手直接以某个身份对客户说话。上这一档之前先明确:哪些话术允许自动发、哪些必须人工、出错之后由谁在多长时间内介入。
- 4写操作单独授权:查订单和改订单不是一回事。读可以放开,写一律要人确认,哪怕只是点一下。
留痕要做到能回答「它当时凭什么这么做」:哪条消息触发、命中了哪条规则、检索到了什么、模型返回了什么、最终发了什么。只存对话记录是不够的,事故复盘时缺的永远是中间那几步。
验收:换指标,不然改不动
问答机器人的验收看回答准确率,助手看这个数会得出错误结论 —— 每句话都答得挺对,事情一件也没办成,是很常见的状态。
- 任务完成率:发起的事里有多少真正到达了结束态。这是唯一的主指标。
- 平均干预次数:一件事需要人插手几次。它比完成率更早反映问题。
- 无效轮次占比:没有推进任何槽位的对话轮数。高说明消歧或提问设计有问题。
- 跑偏率:做了没被要求的事的比例。这个数要盯死,它直接对应授权那一节的风险。
还有一条不是指标但同样重要:助手要有明确的退出条件。判断不了、缺信息、超出授权,就把事交回给人,并把已经掌握的信息一起交过去。会承认自己办不了的助手,比什么都答得上来的助手可靠得多。
本文讲的是助手与问答机器人的结构差异和工程取舍,示意代码里的函数与字段均为自拟。消息、事件与客户相关接口的精确字段与端点以 wecomapi 线上接口文档为准。
常见问题
- 已经有问答机器人了,要不要推倒重来?
- 不用。保留「一条消息进来产出一条回复」的那个函数,在它外面加任务层和记忆层,原来的问答逻辑退化成任务里的一个分支。真正需要新写的是出站链路和授权留痕,这两块本来就不在问答机器人里,不算重写。
- 做助手需要多强的模型?
- 通常不是模型强弱决定成败。任务状态机、槽位定义、记忆写入闸门这些工程件齐了,中等模型也能稳定跑;这些件缺了,最强的模型也会在第五轮忘掉用户三轮前说过的话,因为那句话根本没被存下来。先把状态和记忆做出来,再谈换模型。
- 助手能不能直接替员工给客户发消息?
- 技术上没有障碍,用 wecomapi 的消息接口下发即可,但建议第一版先做成「生成建议、成员点发送」。这样效率收益能拿到大半,责任仍然落在人身上,同时你会得到一批人工修改记录 —— 它是改进助手最有价值的数据,比任何评测集都贴近真实场景。
准备好动手了?
精确字段、鉴权与端点以线上文档为准;可在控制台创建密钥后联调。
