站内讲过一遍确定性的自动化工作流 —— 触发器、条件、动作、流程状态,步骤是人写死的。把模型放进来之后多出来的问题只有一个,但它很大:下一步由谁决定。步骤一旦由模型选,工具定义、确认时机、终止条件、可观测这四件事全都要重做一遍。这篇讲的就是这部分增量,按 wecomapi 的接入形态展开。
三种形态,先想清楚停在哪一种
「企微Agent」这个词把三种差别很大的东西盖在了一起,先分开。
- 1固定流程加模型填空。步骤和顺序都写死,模型只负责在某一步生成文本或抽取字段。最可靠,也是被低估最多的一种。
- 2单步工具调用。模型看到一组工具,选一个、填好参数、拿到结果、组织成回答。只有一跳,失败面小,延迟可控。
- 3多步自主规划。模型自己决定调几次、按什么顺序、什么时候停。能力上限最高,可预测性最低。
判断标准不是任务复不复杂,是错误的可见性和可逆性。企业微信里的动作大多对外可见 —— 一条消息发出去客户就看到了,一张工单开出来就进了别人的队列。这种环境下,第三种形态的收益远小于它引入的不确定性。绝大多数企微场景应该停在第二种:让模型决定「查什么」,不让它决定「做几步」。
那第三种什么时候才值得?两个条件同时满足:步骤数确实无法枚举,且中间步骤全部是只读的、错了不会留下痕迹。排查类、汇总类任务是典型;成交、变更、退款类都不是。
工具定义就是模型能读到的全部文档
模型选错工具、填错参数,第一反应通常是换个更强的模型或者改提示词。多数情况下真正的原因在工具定义本身:它是模型能读到的全部文档,写得含糊,再强的模型也只能猜。
- 1按业务动作切,不按接口切。「查这个客户最近一笔订单的状态」是一个工具;拆成查客户、查订单列表、查订单详情三个,等于把编排责任推给模型,而这恰恰是最不该交出去的部分。
- 2参数要少,必填要真必填。一个有八个可选参数的工具,模型会填出八种组合,其中至少两种你没测过。
- 3能枚举就别用自由文本。状态、类型、时间范围一律给枚举,自由文本参数是幻觉最容易落地的地方。
- 4描述写给模型看,不是写给同事看。要写清「什么时候该用它」和「什么时候不该用它」,后半句经常被省略,而它比前半句更有效。
返回同样要设计。把业务系统的原始响应整段丢回给模型,是两个问题一起犯:上下文预算被吃掉,同时模型要在几十个字段里猜哪个是答案。返回应该是裁剪过、语义明确的少量字段,必要时直接给一句话结论。
错误返回尤其要讲究。给模型一个错误码,它只能编;给它一句「这个单号不存在,请向客户确认」,它知道下一步该做什么。工具错误要分两类写:能由模型自己纠正的(参数错、找不到对象),返回一句可行动的说明;不该由模型继续处理的(权限不足、下游不可用),直接终止这一轮并转人工,别让它反复重试。
写操作的确认,在 IM 里怎么做
读操作可以放心交给模型,写操作不行。这不是保守,是赔付金额的差别 —— 答错一次是尴尬,改错一次预约或多发一张券是要有人去平的。规矩很简单:所有对外可见、不可逆或涉及金额的动作,必须过一次人确认。
麻烦在于 IM 里没有通用的确认控件。一条消息发出去,用户回一句「可以」,你怎么知道这个「可以」对应哪个待确认动作?靠再调一次模型去理解,等于在最关键的一环又引入了不确定性。
做法是把待确认动作落成一个短时效的句柄:生成动作时先落库,拿到句柄和一段人类可读的动作描述,把描述连同一个明确的确认口令发给用户。用 wecomapi 的事件回调收到用户回复时,先按句柄匹配,匹配不上再进正常对话。句柄要带过期时间,也要带一次性标记 —— 过期的要求用户重新发起,已执行过的句柄再次命中直接忽略。
// 示意逻辑,函数与字段均为自拟;发送动作形如 https://manager.wecomapi.com/message/sendText
async function proposeWrite(sessionId, action) {
const h = await pending.create(sessionId, action, { ttl: "10m", once: true });
await send(sessionId, `${describe(action)}\n回复「确认 ${h.code}」执行,10 分钟内有效`);
}
async function onUserText(sessionId, text) {
const h = pending.matchCode(text); // 先按句柄匹配,匹配不上才进对话
if (!h) return chat(sessionId, text);
const act = await pending.claim(h); // claim 是原子的,重复确认拿不到第二次
if (!act) return send(sessionId, "这条确认已失效,请重新发起");
const r = await tools.run(act, { idempotencyKey: h.id }); // 幂等键用句柄,不用内容哈希
await send(sessionId, summarize(r));
}- 确认口令要带短码。只回「确认」两个字在群里会撞车,两个待确认动作同时挂着时你分不清
- 幂等键用句柄 ID,不要用请求内容的哈希。同一个动作被合法地发起两次,内容哈希是一样的
- 确认状态要能被坐席看到并手动作废。客户不回复的待确认动作会一直挂着,直到过期
多步任务的时间形态与中途变卦
单步问答是几秒,多步任务常常十几秒到几分钟,IM 的对话节奏撑不住这个。所以任务要从「一次请求」变成「一个有状态的东西」:发起时立刻回一条回执,中间补进度,结束时补结果。这三条消息走的是同一个发送动作,用 wecomapi 的消息接口按会话标识随时发起即可,不依赖发起时那次请求还活着。
真正新增的问题是中途变卦:任务正在跑,用户又说话了。三种处理各有代价 —— 排队(等这轮结束再处理,可能答的是过时问题)、打断重规划(取消当前任务用新输入重开,已花的调用作废)、忽略(只在结束后统一回应,体验最差)。按用户说了什么来分,不要一刀切。
- 1明确说「算了」「取消」:立刻中止,而且要真的能中止 —— 任务循环每一步开始前检查一次取消标记,不要只在开头查。
- 2补充条件:如果任务还没执行过任何写操作,取消重跑;已经写过就不要重跑,回一条说明并把新条件放进下一轮。
- 3问了个无关问题:排队,等当前任务结束。同一会话并发跑两个任务是回复错乱的头号来源。
「已经写过就不重跑」这条要写进代码,不能靠人记得。任务状态里维护一个「是否已产生外部影响」的标记,第一次写操作成功就置上,重规划前先查它。
预算与终止条件
自主规划的任务没有天然终点。模型可以在两个工具之间来回调用直到上下文塞满,也可以对着一个失败的调用一直重试下去。这两种情况都不会抛异常,只会在模型侧的账单和端到端延迟上体现,所以终止条件必须由外部强加,不能指望模型自己收手。
- 步数上限:单次任务最多调几次工具。触顶就停下来转人工,不要提高上限
- 时长上限:从任务开始算的墙钟时间。它和步数上限要同时有,因为单步也可能很慢
- 重复检测:同一个工具带着相同参数被连续调用两次以上,几乎总是模型在绕圈。检测到就中断,这一条能省下最多的钱
- 成本上限:按会话累计。超过阈值告警,而不是静默继续
触顶之后的动作也要定义清楚,默认不能是沉默。合理做法是把已经拿到的中间结果整理成一段说明发给用户,同时转人工并附上完整轨迹,让坐席从中间接手而不是从零开始。
这四条上限的初值不用纠结,先定得偏紧,看触顶率再放。反过来先松后紧的代价是真金白银,而且触顶率会掩盖在正常流量里,很久都看不出来。
轨迹才是这套东西的日志
确定性流程出问题时,看日志能还原发生了什么,因为路径是固定的。模型编排不行 —— 同样的输入下一次可能走出完全不同的路径,所以必须把这一次实际走过的路径完整记下来。这份记录就是轨迹,它是这类系统唯一有效的日志形态。
- 1每一步记:模型看到的输入摘要、它选了哪个工具、填了什么参数、工具返回了什么、耗时多少。
- 2记决策点而不只是结果。「它为什么没去查订单」这个问题,只有轨迹能回答。
- 3轨迹要能按任务标识一次拉出来,并且能给坐席看。转人工时把轨迹一起交过去,坐席才知道机器已经做过什么。
有了轨迹,评估的对象也该跟着变。单步问答评估最终答案就够,多步任务要评估路径:工具选对了没有、参数填对了没有、该停的时候停了没有。一个最终答案正确、但中间多开了一张工单的任务,按答案评是通过的,按轨迹评是事故。
本文讲的是模型参与编排之后多出来的取舍,示意代码中的函数与参数均为自拟。消息、事件的精确字段与端点以 wecomapi 线上接口文档为准。
常见问题
- 企微Agent 和站内讲的自动化工作流是一回事吗?
- 底座是同一套,差别在谁决定下一步。确定性工作流的步骤是人写死的,重试和恢复都有确定语义;Agent 的步骤由模型选,于是多出四件事:工具定义的质量直接影响成功率、写操作要有确认机制、必须外部强加终止条件、日志要记轨迹而不只是结果。触发器、实例状态、流程版本这些底层机制两者通用,站内讲自动化工作流那篇已经展开。
- 多步任务在企业微信里怎么给用户反馈进度?
- 拆成回执、进度、结果三条消息,别让用户对着空白等。回执在发起后立刻发出;进度按已完成的动作播报,不要按百分比 —— 步骤数事先不知道,百分比只能编。可用的消息类型与发送能力以 wecomapi 文档为准。
- 工具调用失败了,该让模型自己重试吗?
- 分两类。参数错、对象不存在这种模型能自己纠正的,返回一句可行动的说明让它改一次,但要限次数。权限不足、下游不可用、超时这类模型改不了的,直接终止这一轮并转人工 —— 让模型对着同一个错误反复重试,是这类系统最典型的烧钱方式,而且它不报错,只是慢慢变贵。
准备好动手了?
精确字段、鉴权与端点以线上文档为准;可在控制台创建密钥后联调。
