
Agent-Reach 这个名字乍看容易让人联想到网络层的寻址协议但在我手里它其实是一套面向私域用户触达场景的智能体调度系统。简单说它把原本需要运营人工完成的客户筛分、话术匹配、消息推送、外呼跟进这些动作变成由多个 AI Agent 自动编排执行。这里说的 Agent不是某个大模型聊天机器人而是一个可以独立感知用户状态、决定下一步动作的流程单元。这套系统解决的核心问题非常具体当你的用户量起来之后靠人工去逐条跟进客户、判断什么时候发消息、发什么内容效率低到让人绝望而 Agent-Reach 正好能把这块脏活累活接过去。我自己当时接手这个项目背景是公司私域社群里的用户体量已经过了十万活动推送和售后回访全靠运营在后台手动操作一天下来每个人只能覆盖几百人还经常遗漏最佳触达时机。Agent-Reach 要做的就是让触达动作从“经验驱动”变成“数据驱动”适合想自己搭一套自动化触达系统的后端开发、AI 应用工程师以及被运营效率憋得头疼的业务负责人。这篇文章我不会讲太虚的概念直接拆我落地的这套架构、参数和排坑记录你也可以照着抄。1. 项目背景与整体设计思路1.1 为什么要把“触达”交给 Agent我最早接到需求时业务方的原话是“能不能让机器人帮我们给客户发消息而且要根据客户回复的不同内容给不同反馈”。这句话听起来简单真做起来会发现它其实包含了好几个层次。第一层是发消息这很简单微信群机器人或者短信接口都能做。第二层是判断客户状态比如他有没有回复、回复里是不是带有“不感兴趣”的负面情绪。第三层是动态决策客户说“现在没空”系统应该自动放到下午再次触达而不是机械地只回一句“好的”。普通定时任务只能解决第一层但 Agent-Reach 把第二层和第三层作为核心设计目标。我把整个触达流程拆成“感知 - 决策 - 执行 - 学习”的闭环。感知是指收集用户行为有没有点开消息、有没有点击链接、有没有在聊天里触发某个关键词。决策是让 Agent 根据感知到的信号决定下一步动作结束、继续、转人工、还是换一个渠道。执行就是真正去调用短信、企业微信、App 推送接口。学习则是把每次触达的结果记录成结构化数据供后续策略迭代。这个闭环跑起来之后运营人员不再需要盯着“谁还没回复”的表格只需要在 Agent-Reach 里定义好规则和话术剩下的事情由系统接管。这里有个关键认知Agent 不是越大越好不是非得用大模型去推理每一步。很多时候一个只有 if-else 的状态机加一些规则拦截效果就很好大模型只用在意图识别和话术生成这两个真正需要语义理解的地方。把任务拆细系统的稳定性和成本都会好很多。1.2 技术选型引擎、状态与模型接入落地到具体技术栈时我没有选择什么特别炫酷的框架核心就三板斧FastAPI 负责提供 API 和控制流PostgreSQL 存储用户画像与触达历史Redis 做分布式锁、频控计数器和任务队列。大模型接口单独抽象成一个 Provider 层这样后面换厂商或者做降级都比较方便。为什么自研状态机而不是直接用现成的工作流引擎我试过重一点的 workflow 框架发现它们在编排复杂的用户交互时有点“过度设计”。触达场景的分支条件看起来多但本质上是有限的用户回复了就进入对话分支没回复就进入超时分支拒绝就进入退订分支。用数据库里的一个枚举字段表示当前状态再配一个事件表记录迁移原因比画流程图的方案透明得多。出了问题我直接查某条触达记录的状态是什么、在哪个环节停住半小时就能定位。模型接入这块我用了通用大模型 API 来做意图识别和话术生成但在实际生产里不能把全部希望押在模型上。原因很简单模型可能抽风可能返回格式不对也可能延迟波动。我在 Provider 层加了超时熔断、JSON Schema 校验、还有兜底规则。如果模型连续三次返回无效结果系统自动降级到预设的固定话术不阻塞整个触达流程。这套混合方案跑下来稳定性比纯模型驱动高很多。2. 核心模块拆解与实现要点2.1 触达编排引擎状态机的巧妙设计Agent-Reach 的编排引擎核心是一张状态表我把每个用户的每次触达任务抽象成一条独立记录字段包括 task_id、user_id、channel、current_state、plan_time、retry_count、policy_id。状态集合一开始只有五个init、reaching、waiting_reply、completed、failed。后来加了 blocked专门处理被用户拉黑、退订或者误投诉的情况。状态迁移逻辑是init 到了预定的 plan_time由调度器把任务推进到 reaching此时系统调用渠道接口发送首条消息。发送成功后进入 waiting_reply等待用户回复。在 waiting_reply 状态下如果用户回复Agent 根据意图判断进入 completed 或者继续对话如果超过设定时间没有回复则进入重试或 completed 的终态。failed 状态是指发送接口报错、被渠道限流等异常情况这种任务不会直接丢弃而是进入重试队列最多重试三次。这个状态机的重点在于所有迁移都必须是幂等的。每个状态迁移事件都带一个唯一 event_id消费端如果收到重复事件直接丢弃。否则在 Redis 锁和异步任务并发的情况下同一个用户有可能被两个线程同时触达那就变成骚扰了。我在实现时每条触达记录都会执行一次 Lua 脚本做原子性检查确认当前状态等于预期状态后才允许更新到下一个状态。实测下来这个设计救了命因为它挡住了大量重复调用。2.2 AI 对话代理意图识别和话术生成状态机负责“什么时候做什么”AI 对话代理负责“该说什么、怎么判断对方说了什么”。我把对话代理分成两层先跑规则层再跑模型层。规则层是一些轻量级的正则和关键词匹配用来兜底。例如用户回复里出现“退订”“别再发了”“骚扰”这类词系统无需调用大模型直接触发宽松处理标记用户为退订任务进入 blocked 状态并把事件写入用户偏好表。这类拦截是刚性的不能被模型覆盖。模型层用来处理更模糊的意图。我把用户回复的文本与当前上下文拼接后发给大模型让它从预定义意图列表中选一个。预定义意图包括感兴趣、需要更多信息、现在不方便、拒绝、咨询售后、其他。大模型返回一个 JSON包含意图编码、置信度、建议动作。这里我不让模型自己发明新意图否则下游分支没法收敛。建议动作也被限制枚举值继续对话、延迟触达、转人工、结束、加入高优列表。话术生成上我没有用模型直接自由发挥而是采用“模板加变量”为主。给模型的是结构化的模板选项比如“首触达”“二次触达”“售后确认”这几个模板模型只需要负责根据用户画像和当前语义填补变量。这样能避免模型编造出不可控的话术。所有生成的话术都会先经过一个敏感词服务过滤掉承诺返现、夸大效果、包含竞品关键词等违规内容。这一步很重要尤其是面向 C 端用户合规问题不能靠事后补救。2.3 频控与合规策略不惹用户不踩红线做触达系统最怕什么不是故障是被用户投诉骚扰。频控如果做得不好再好的话术也没用。Agent-Reach 里的频控策略分三层全局频控、渠道频控、单用户频控。全局频控控制整个系统在一个时间窗口内发送的总量避免渠道接口被限流渠道频控是每个渠道单独的限制比如短信渠道一小时最多 800 条单用户频控则是同一用户一天最多 2 次触达且两次之间最小间隔 6 小时。这些参数不是拍脑袋定的而是从运营目标和用户体验之间折中出来的。以我们十万用户的场景为例活动转化周期是三天每天最多触达 2 次总共 6 次触达机会如果用户一直不回复第四天系统会自动把用户标签改写成“低意愿”后续策略不再主动触达。同时每条消息都会带上“回复TD退订”的明确提示用户一旦退订所有触达策略马上停止。这是合规底线测试环境可以随便调生产环境必须严格执行。我在实现频控时用的是 Redis 滑动窗口加 ZSetkey 是 user_id:channel:datescore 是时间戳每次触达前先统计窗口内次数超限就跳过。这部分还要防缓存击穿所以我在 Redis 前面加了一层本地缓存允许最多 1 秒的过期延迟。虽然增加了轻微的不一致概率但对频控来说少发一条远远好过多发一条。3. 关键参数配置与实操记录3.1 一个真实的触达流程配置样例下面给出一份我在项目里实际用过的简化配置。场景是电商大促前唤醒三个月内未复购用户目标是让他们点击活动链接。{ task_name: pre_promo_wakeup_202504, trigger: { type: cron, expression: 0 10 * * * }, target: { user_segment: last_purchase_before_90d, exclude: [blocked, blacklist, recent_7d_ordered] }, steps: [ { name: first_touch, channel: wecom, template_id: greeting_coupon, policy: { retry_on_fail: true, max_retry: 3 } }, { name: follow_up_if_no_reply, condition: no_reply_after_3h, channel: sms, template_id: promo_reminder } ], agent: { intent_model: gpt-4o-mini, fallback_intent: end, max_turns: 2 }, frequency_limit: { per_user_per_day: 2, interval_hours: 6, global_per_minute: 20 } }这份配置跑起来之后每天早上十点由定时器触发调度器先从 PostgreSQL 里捞出一批满足条件的用户然后逐条创建触达任务。首触达通过企业微信发送用户在三个小时内没有回复系统自动走第二步短信提醒。这里有个细节如果用户点了活动链接系统会在用户行为事件里捕获这个信号然后把后续的未回复跟进自动取消。也就是说跟进的触发条件不是单纯的“没回复”而是“没回复且没有点击链接”。这个逻辑我在实现时单独定义了一个行为事件订阅避免重复打扰已经转化的用户。3.2 频控参数是怎么算出来的频控参数看着是几个数字实际计算过程很有讲究。拿上面配置里的 global_per_minute20 为例这个值是由渠道接口能力、用户投诉容忍度、CPU 负载三方面综合出来的。我们的企业微信接口实测每分钟 100 条会触发限流报警为了留出 5 倍余量取 20 条。同时我们的客服团队每天能承接的会话量约 500 条如果触达后 20% 的用户会回复那么每分钟发出的 20 条消息中会产生 4 条回复一小时就是 240 条刚好在客服承接能力内。如果触发率再高就需要降低触达速率或者扩充客服团队。单用户每天 2 次是根据用户疲劳度实验得出的。我们试过把次数提到 3 次结果退订率从 0.3% 直接涨到 1.2%虽然成交率提升了一些但用户资产损失太大不划算。后来改成前两天每天 2 次第三天开始只做触达超过 7 天未复购的用户退订率才回落。两次触达间隔至少 6 小时主要是为了避免早晚各一次的机械感让系统看起来更像人。同样的逻辑也可以放到其他项目里不要迷信网上的“标准频控数值”必须根据你自己的用户反馈去调。3.3 日志追踪与效果评估做触达系统最怕黑盒如果不知道哪一步流失了优化就无从下手。Agent-Reach 里每一条触达链路都会输出一个 event log包含 event_id、task_id、user_id、channel、event_type、context、timestamp。event_type 包括 send_success、send_fail、delivered、read、reply、link_clicked、unsubscribe、timeout、converted。这些日志直接写入 ClickHouse供 BI 报表查询。我常用的评估漏斗是送达率delivered / send_success→ 打开率read / delivered→ 回复率reply / read→ 转化率converted / reply。举一个实际数据首触达送达率 98%打开率 34%回复率 18%最终转化率 5.6%。对比纯人工运营时代打开率提升了 12 个百分点转化率提升 2.1 倍。这里有一个很容易忽略的指标退订率。我会重点关注退订率有没有突然飙升只要超过 0.5%当天就会暂停触达任务排查话术或频控策略。日志系统还有一个功能叫“轨迹回放”给运营同事在后台输入一个 user_id就能看到这个用户最近七天的所有触达记录和当时策略决策原因排投诉的时候特别有用。4. 常见问题与排查技巧4.1 高频问题速查表我在开发 Agent-Reach 的过程中踩了不少坑有些问题在测试环境根本不会暴露一上生产就冒头。把它们整理成一张表方便你对号入座。问题现象根本原因解决方式用户收到多条相同消息调度器重复拉取任务消费端无幂等给任务增加唯一事件 ID在消费前加 Redis 分布式锁大模型返回的意图 JSON 解析失败模型输出不稳定回复里夹杂了其他文本在模型推理后加 JSON Schema 校验失败则降级到规则层每分钟发消息量超过渠道限制全局频控没有作用到异步任务每次发送前调用 Lua 脚本检查滑动窗口超限跳过用户回复之后系统没有继续对话等待回复状态的监听事件丢失增加定时扫描任务对 waiting_reply 超时未处理的事件做补偿部分用户收到消息后直接退订话术模板过于营销化或使用频率过高将用户退订事件写入黑名单并暂停所有未执行的触达任务定时任务在高峰时段堆积cron 触发瞬间创建大量任务数据库压力变大用 Redis 队列削峰调度器按固定速率从队列取任务创建这些问题的共性是系统越复杂越容易出现“极端路径没覆盖到”。我在测试环境里模拟了各种用户回复但还是漏掉了“用户在两个触达动作之间正好完成退订”这种并发场景。后来专门写了一个场景测试用脚本模拟用户在收到消息瞬间触发退订才把竞态问题修复。4.2 避坑心得这五件事让我少加很多次班第一所有外部接口都要设置超时和降级。短信接口偶尔会很慢大模型接口偶尔会超时。如果不在超时后快速降级整个任务队列会被一个堵塞的接口卡死。我们当时给短信渠道设的是 3 秒超时大模型接口是 5 秒。超时后走重试或直接跳到下一步绝不在原地等着。第二话术变量必须做长度校验。大模型生成的用户昵称、订单金额这些变量塞进模板后有概率出现几十个字的超长昵称直接把短信模板撑爆或者被渠道拒绝。我在渲染话术之前会遍历所有变量超过一定长度就截断或者替换为“用户”这个校验简单但特别有用。第三灰度发布比什么都重要。Agent-Reach 不可能一次把全部用户都切上线我们按照用户 ID 尾号分桶先是 1% 用户灰度跑了一周观察退订率和投诉量再把灰度比例调到 10%最后全量。灰度过程中发现某个话术模板在特定人群里退订率异常及时替换后才没有酿成大问题。第四活动触达必须准备好“召回走查”机制。所谓召回就是当运营临时反悔或者活动取消时能够把已经排好计划的任务全部取消。我们在任务表里增加了一个 active 字段取消活动时直接把某个策略下的所有未完成任务批量置为 inactive同时清理 Redis 中的待发送队列。没有这个机制取消活动后还是会不断有用户收到消息公司会直接被骂死。第五监控迟到日志。只看实时日志不够还要监控“应该发生但没发生”的事件。我写了一个定时任务每分钟检查 waiting_reply 超过半小时还没有事件的记录把这些“失联任务”捞出来补发事件或重新入队。这种幽灵任务用眼睛根本看不出来只能靠系统主动发现。5. 从 Agent-Reach 项目延续出来的思考我自己的体会是Agent-Reach 这个项目最让我满意的不是把消息发出去而是把触达这个动作从“混沌”变成了“可观测、可控制、可优化”。以前运营同事最怕的是用户投诉“你们到底有没有把我当人看”现在系统有了状态机、频控和退订机制这种风险被压到了最低。后续这个系统还可以继续扩展两个方向。一是接入更丰富的用户行为信号比如浏览了哪个商品详情页、加购了但没付款这些信号直接进入 Agent 的决策上下文触达的时机和内容就更精准。二是把多个 Agent 组合起来实现“多个决策单元并行协商”的效果比如一个 Agent 负责留存另一个负责转化它们在共享用户状态的基础上协同发力。最后再分享一个小技巧如果你也要做类似的触达系统先别急着写代码找个时间把业务方嘴里所有的“如果用户怎么怎么样”都列出来变成一张决策表。把这张表翻译成状态和事件的组合你会发现代码实现会顺畅很多也比从网上抄一套流程框架靠谱得多。好项目不是憋出来的是按照真实的业务逻辑一层层剥出来的。