ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

开源项目Agent-Reach:用智能体实现主动触达的自动化工作流

开源项目Agent-Reach:用智能体实现主动触达的自动化工作流 上个月和几个做增长的朋友吃饭大家不约而同说到同一个问题大模型套壳的聊天机器人已经做腻了但真正在业务里跑起来发现它们全都一个毛病——只会等人来问不会主动找人。CRM里躺了几千条线索市场部发过一轮邮件回复率低得可怜销售一个个手动跟进又忙不过来。我回来之后花了几周时间把之前积累的智能体Agent开发经验重新整合了一轮做了一个开源项目 Agent-Reach。简单说它是让 AI 智能体从“被动响应”走向“主动触达”的一套工作流框架能自动完成线索筛选、内容生成、多渠道触达、策略调度和效果回灌。这篇文章把我从设计初衷到落地踩坑的完整过程写下来适合正在做智能体应用、增长运营、售前自动化的朋友参考。1. 为什么我会做 Agent-Reach聊天机器人解决不了“主动触达”的问题1.1 我的亲身经历AI 挺聪明但不会“主动”过去小半年我给几个团队做过智能体咨询和落地。大家最开始的期望都很一致让 AI 把重复性的客服问答接走。也确实接走了FAQ 问答、工单分类、知识库检索这些场景都跑得不错。可业务方慢慢发现一个尴尬的地方这套系统只有在用户主动发起对话的时候才有价值而大部分潜在用户根本不会发起对话。我印象很深的一个案例某 SaaS 公司办了一场线上发布会现场留资了三百多条线索。按原来的流程这些线索要分配给销售去挨个打电话、加微信、发资料。销售忙不过来最多就是发一轮群发邮件然后听天由命。市场部想的是能不能让 AI 先替我们把第一轮触达做了于是我开始尝试把大模型接入到触达流程里。最开始很简单写一个 Prompt让它给每条线索生成一封个性化的邮件。效果比群发模板好不少但还不够——因为真正决定触达成功率的不只是文案还有时机、频率、渠道选择和后续跟进。那段经历让我意识到市面上的“AI 营销工具”和“聊天机器人”之间缺了一层东西一个能自主判断“该不该触达、什么时候触达、用什么渠道触达、说了之后怎么办”的智能体大脑。Agent-Reach 就是冲着这个缺口去的。1.2 Agent-Reach 不等于“群发工具”核心差异点很多人第一次听说 Agent-Reach会下意识把它理解成“更智能的群发器”。这个误会我解释过很多遍。传统群发工具的核心是“把同一段话发给所有人”最多做点变量替换比如把姓名和公司名插进去而 Agent-Reach 的核心是“为每一条线索做独立决策”。我把差异点整理成了一张对比表对比维度传统群发工具Agent-Reach内容生成固定模板字段拼接基于线索上下文动态生成每条内容都不同触达时机定时批量发送结合线索活跃时间、渠道状态和业务规则动态决策渠道选择单一渠道多渠道适配自动选择优先级和互斥关系后续动作发送即结束根据用户回复、退订、打开等事件自动调整下一步停止机制几乎无当线索明确拒绝或评分过低时自动停止人工监督靠事后统计内置高风险人工审批和全程审计日志换句话说Agent-Reach 不是替代销售而是做“智能助理”该做的事把每条线索变成一个有状态、有上下文、有后续动作的独立任务AI 智能体在这个任务里做判断、写内容、发消息、看反馈再决定继续还是退出。这也是为什么我在设计的时候拼命往“决策质量”上花功夫而不是追求“发得越多越好”。2. 项目整体架构五个模块怎么串成一条自动触达流水线Agent-Reach 的整体结构我在设计时其实参考了广告投放系统的逻辑人群定向、创意生成、竞价投放、数据回流本质上是一条闭环流水线。触达这件事也是一样的思路。2.1 数据接入层一手线索从哪来触达的前提是得有数据。Agent-Reach 的数据接入层支持三类来源第一种是业务系统的主动推送比如线上活动报名、产品试用申请、白皮书下载这些用户本来就留了联系方式属于清楚授权的场景第二种是 CRM 里已有的历史客户数据通过 Webhook 或者定时同步接进来第三种是公域场景的补充信息比如用户在官网的行为轨迹、在社区里的公开提问这部分只作为摘要参考不会用来做未经授权的触达。这里要特别强调一个原则数据来源必须是合规授权的。所以我在接入层留了一个 source_type 字段标明每条线索的来源和授权状态。任何来源不明、授权不清晰的线索Agent-Reach 默认不会进入触达流程。这个底线从一开始就是硬编码在系统里的。2.2 意图理解与价值评分LLM 如何判断“该不该触达”数据进来之后第一步不是急着发消息而是做意图理解。我会把一条线索的所有信息打包给大模型让它输出三个东西一句话摘要、用户核心需求、触达价值评分0 到 100并且必须附上评分的理由。价值评分不是让 LLM 凭感觉打分而是基于一组明确的参考维度比如线索是否有明确的业务需求表达比如主动询问过价格用户身份是否匹配产品目标人群比如岗位、行业、公司规模渠道可触达性邮箱是否有效、企业微信是否已添加近期互动信号是否打开过邮件、访问过官网特定页面评分的作用是排序。Agent-Reach 不会对几千条线索一视同仁而是优先处理前 20%的高潜力线索。这比把预算平均撒出去有效得多。2.3 触达渠道适配层一套语义多种出口意图判断做完之后真正发消息的活交给渠道适配层。这一层我设计成了插件式结构邮件、企业微信、飞书、钉钉、短信、Webhook每个渠道都是一个独立插件实现同一套接口。上层智能体不用关心具体渠道的 API 差异只需要说“我要给这个人发一条触达消息内容是什么期望用哪个渠道”。这样设计带来的直接好处是接新渠道的成本被压到很低。我后来加飞书插件的时候只写了不到两百行代码就接完了因为核心逻辑都在统一的 Channel 接口里。2.4 策略调度引擎什么时候说、说几次、怎么退有了渠道有了内容还差一个“调度大脑”。策略调度引擎负责回答三个问题现在是不是合适的时间这条线索已经触达过几次下一步该继续还是转换策略它靠一套规则和状态机来保证智能体不过度打扰用户。举个例子如果某条线索在上午十点被触达过且没有回复引擎不会在下午两点再发一遍而是等到第二天再用另一个渠道跟进。规则看似简单但正是这些“笨规则”保证了整个系统的分寸感。LLM 擅长生成内容和理解意图但在“控制次数、遵守时间窗”这种事情上经常掉链子所以调度必须交给确定性规则。2.5 反馈回灌闭环数据怎么反哺下一轮触达触达发出去不是结束。Agent-Reach 会持续收集三类反馈事件用户行为事件打开邮件、点击链接、访问落地页、显性回复用户直接回邮件或消息、渠道元事件退订、投诉、被标记为垃圾。这些反馈会回流到意图理解模块更新线索评分和标签。比如一个用户打开邮件但没回复系统会把它标记为“中度兴趣”隔一天换一个渠道再触达一次如果用户直接回复“暂不需要”那么这条线索的评分会被调低触达流程会自动停止并且给销售留一条备注用户已明确拒绝建议三个月后再联系。整个流程里每一轮的决策都在上一轮反馈的基础上做这也让 Agent-Reach 区别于一次性群发。3. 关键模块实战渠道适配层的设计细节含代码示例很多人都关心 Agent-Reach 是怎么做到“一套语义多种出口”的。我把这块单独拿出来讲因为它是整个项目里最具复用价值的部分。渠道适配的核心是抽象出一组所有渠道都通用的接口同时把每个渠道的差异细节藏在实现内部。3.1 Channel 接口设计我用 Python 写了一个 Channel 协议核心就三个方法发送、判断可发送、解析回复。from dataclasses import dataclass, field from typing import Protocol, Optional dataclass class TouchMessage: channel: str to_user: str subject: str body: str template_vars: dict field(default_factorydict) metadata: dict field(default_factorydict) allow_follow_up: bool True dataclass class ReplyEvent: channel: str user_id: str content: str intent_hint: Optional[str] None raw: dict field(default_factorydict) class Channel(Protocol): channel_type: str def send(self, msg: TouchMessage) - dict: 发送一条触达消息返回渠道侧的消息ID等元信息。 ... def can_send(self, user_profile: dict) - tuple[bool, str]: 判断该渠道对当前用户是否可用返回可发送标识和原因。 ... def parse_reply(self, raw: dict) - ReplyEvent: 将渠道回调的原始数据解析成统一的回复事件。 ...为什么这么设计我踩过不少坑。最早我写了一套针对邮件的触达逻辑后来接企业微信时发现如果发送逻辑和渠道 API 耦合在一起新渠道接入时会连带改掉很多上层代码。用 Channel 协议隔离之后上层只管构造 TouchMessage调用 send()然后从返回的字典里拿消息 ID 存进日志。至于邮件是走 SMTP 还是 API企业微信是发应用消息还是群机器人消息都是各个实现自己的事。3.2 用注册表管理多渠道在 Agent-Reach 里我没有用复杂的依赖注入框架而是用一个简单的注册表来管理渠道实例。CHANNELS: dict[str, Channel] {} def register_channel(channel_cls: type) - type: instance channel_cls() if CHANNELS.get(instance.channel_type): raise ValueError(fduplicate channel: {instance.channel_type}) CHANNELS[instance.channel_type] instance return channel_cls def get_channel(channel_type: str) - Channel: channel CHANNELS.get(channel_type) if not channel: raise KeyError(funsupported channel: {channel_type}) return channel注册表的好处是扩展方便。新渠道写好后一行装饰器就能装上register_channel class EmailChannel: channel_type email ...上层在选择渠道时可以遍历所有注册的渠道结合用户的 profile 和触达历史做优先级排序def pick_channel(candidates: list[str], user_profile: dict) - Optional[str]: for channel_name in candidates: ch get_channel(channel_name) ok, reason ch.can_send(user_profile) if ok: return channel_name # 把不可用的原因记进日志方便观测 logger.info(channel unreachable, channelchannel_name, reasonreason) return None这里有一个细节候选渠道的顺序不是写死的而是由策略引擎根据用户最近的互动渠道动态调整。如果用户最近对邮件很活跃那邮件就排在前面如果用户是企业微信好友关系且长时间没互动企业微信的优先级会更高。目标是用用户最舒服的渠道去沟通而不是用渠道去绑架用户。3.3 渠道专项配置与合规检查每个渠道都有自己的隐性规则。邮件发太频繁会导致域名信誉下降企业微信对主动添加好友的频率有严格限制飞书机器人只能在一定范围内主动发起会话。这些差异我全部放到渠道实现里由 can_send() 方法负责把关。class EmailChannel: channel_type email DAILY_SEND_LIMIT 30 MIN_INTERVAL_SECONDS 3600 def can_send(self, user_profile: dict) - tuple[bool, str]: if not user_profile.get(email): return False, missing email if not user_profile.get(email_verified): return False, email not verified # 检查全局频控这里从共享存储里读取数据 if recent_send_count(self.channel_type, user_profile[email]) self.DAILY_SEND_LIMIT: return False, daily limit reached return True, ok合规检查不是一个横幅功能而是硬编码在发送管道里的。任何触达消息在真正发出去之前还会经过一层统一的合规检查比如消息体中是否包含退订链接、是否包含强承诺性的字眼、是否冒充真人。这层检查不是 LLM 来做的而是用正则和关键词规则库来做保证 100% 拦截。4. 触达策略引擎用规则保证下限用 LLM 拉高上限4.1 为什么不能全交给 LLM我在早期原型里尝试过让 LLM 直接决定“要不要给这个人发消息”。结果很不稳定。有一次 LLM 把一条已经明确退订的用户又生成了跟进内容差点发出去还有一次同一个用户在十分钟内被两个不同的任务触达因为 LLM 在单次对话里看不到全局上下文。这种问题靠调 Prompt 很难根除因为 LLM 天生对数字、时间和计数不敏感。所以 Agent-Reach 的策略引擎采用了“规则骨架 LLM 血肉”的方式规则只负责做状态判断和边界控制LLM 负责在规则允许的范围内生成个性化的内容和动作建议。4.2 策略引擎的输入输出策略引擎的输入是一个结构化对象包含当前线索的基本信息、历史触达记录、实时渠道状态和用户反馈事件。输出是一个明确的决策动作通常是以下五类中的一类touch_now现在就触达wait_until等到某个时间再触达switch_channel换一个渠道触达stop_and_notify停止触达并通知人工跟进wait_for_human_approval内容或场景需要人工审批后再触达这个设计让智能体的行为变得可解释、可审计。每一条触达记录都能追溯到决策理由。4.3 一个可落地的决策流程示例我给出一个简化版的决策流程用 Python 伪代码描述def decide(profile, history, channel_states): score profile.get(lead_score, 0) if not profile.get(consent_granted): return stop_and_notify, no consent if history.has_explicit_rejection(): return stop_and_notify, user rejected if score 40: return wait_until, candidate score too low, try later next_channels rank_channels(profile, history) for channel in next_channels: ch get_channel(channel) ok, reason ch.can_send(profile) if not ok: continue if history.contacted_within_24h(channel): return wait_until, recently contacted on this channel # 到达这一步候选渠道和目标人都是合适的 if profile.get(risk_level) high: return wait_for_human_approval, high-value lead requires review return touch_now, channel return wait_until, no channel available right now这个流程的核心思想是宁可错过一条紧急消息也不要越界骚扰用户。“规则骨架”的值不在于聪明而在于稳定。4.4 LLM 在策略中扮演的角色策略引擎决定“要不要发”LLM 决定“发什么”。当决策动作是 touch_now 时Agent-Reach 会把线索的所有上下文打包交给一个内容生成 Prompt要求输出主题、正文和明确的 CTA行动号召。Prompt 里会嵌入三条硬性约束必须从原始线索中提取事实不得凭空捏造用户信息必须明确说明这是 AI 助理代为运营的触达必须在结尾提供退订或拒绝的选项内容生成完之后还会做一轮“事实一致性校验”把生成文本中的实体公司名、称呼、产品名与结构化字段做比对。这一步能拦截大部分幻觉问题后面踩坑篇里我会细讲。5. 真实运行三个月我踩过的坑幻觉内容、渠道限流、误触达5.1 幻觉坑Agent 把客户公司名写错了项目上线第一周邮件渠道出了个很丢人的事故。某个线索来自官网试用申请用户填的公司名是“云衡智能”但 Agent 根据官网摘要总结的时候把公司名写成了“衡云智能”还在邮件正文里写了一句“最近看到贵司在渠道数字化方面动作频繁”。用户直接回复了一句“你们到底有没有看过我们信息” 后续自然没戏了。这个问题的根因在于生成 Prompt 只要求 LLM “基于背景资料写一封触达邮件”但 LLM 在生成时会把相近的公司名“记忆”重组成一个更像自然语言的版本。排查看日志时发现上下文中明明有正确的字段生成结果却错了。我加了两层保护。第一层是在生成前把所有事实性字段单独抽出来作为“不可变事实”注入 Prompt并明确要求“只能原样使用这些字段禁止改写”第二层是生成后跑一次实体对齐提取生成文本里的公司名、人名、产品名和结构化字段做字符串匹配匹配失败就触发重新生成最多重试三次。这个改动之后类似的事故基本没再出现过。5.2 多个渠道重复触达引发的反感第二个坑是渠道重复。有个线索同时存在于邮件列表和企业微信好友里Agent 先发了邮件又因为策略编排问题在当天下午又发了一条企业微信消息。用户直接在微信里回复“你们别再找我了邮件我已经看到。”排查链路是这样的我最初把每个渠道的发送记录分别存储没有做跨渠道的全局频率控制。策略引擎判断企业微信渠道时只看了企业微信通道有没有触达过没看邮件通道。修复方式也不复杂在触达记录表里增加一个全局事件字段按 user_key对应用户邮箱或手机号做唯一的全局频控。同时增加一条规则24 小时内最多触达一个用户一次如果已经触达过无论哪个渠道都等待。这个教训让我明白做多渠道不是让渠道各自为政而是要让系统以“用户”为粒度记住所有交互而不是以渠道为粒度。5.3 渠道限流和静默拉黑的教训邮件发送量上来之后我遇到了一个所有做邮件触达的人都会遇到的坑域名信誉下降。一开始每天发两百封到了第三周很多邮件直接进了垃圾箱打开率从 40% 掉到 11%。原因很简单同一个发件域名发送量激增而且退订率过高服务商判定为低质量邮件源。修复措施有三步。第一步是降低每日发送峰值把单日上限从 200 封砍到 50 封再慢慢涨模拟合理的增长曲线。第二步是启动“慢启动”机制新域名或者低信誉域名每天增量不超过 20%。第三步是更严格地清洗收件人列表凡是超过 30 天未打开邮件的订阅者自动退出日常触达池只保留高活跃度用户。这三步做完之后邮件送达率回到了 95% 以上。所谓“触达”前提是能送达否则内容写再好也是白搭。5.4 身份边界问题AI 该不该装成真人还有一类问题不是技术上的而是体验和合规上的Agent 要不要在触达时伪装成真人我见过一些自动触达工具会刻意用“我这边是 XX 科技的 XX”这种话术营造一种“有真人销售跟进”的错觉。短期可能有效果但一旦客户追问细节、要求语音沟通就会穿帮而且会让用户对整个品牌产生反感。Agent-Reach 的默认策略是开诚布公在每一条触达消息里明确标注“AI 助理为你整理的信息”或“由智能体协助发送”。用户如果回复明确要求转人工系统会自动打上 high_priority 标签并通知销售负责人。这个策略可能会降低一点点回复率但换来的信任感和合规性是值得的。6. 效果数据与优化记录从触达率 17% 到 38% 我做了什么跑到现在Agent-Reach 处理了近万条线索触达结果经历了三次比较大的迭代。我复盘了一份数据记录核心指标的变化。指标第一轮第二轮第三轮有效触达率17%29%38%首轮回复率4%9%14%退订率8%5%2%单线索触达平均成本高中低6.1 第一轮无脑按分数排序发送第一轮实现其实很简陋就是把线索按价值评分从高到低排序然后自动发邮件。发送时段固定在工作日上午十点。文案由 LLM 生成但是 Prompt 约束比较少写了一问比较长的“公司介绍 产品优势 邀请体验”。结果触达率只有 17%大多数邮件虽然打开率高但没人回复。复盘发现两个问题一是邮件正文指向不清晰用户不知道要回什么、为什么要回二是发送时机太固定没有考虑用户的活跃时间。这个阶段暴露的问题是大多数 AI 触达工具的通病——AI 只是换了一种方式写文案但整个策略还是老一套“群发 撞运气”模式。6.2 第二轮加入个性化开头和明确 CTA第二轮我做了两个重要改动。第一是在 Prompt 里加入“从用户最近在官网的访问记录中找一条真实信号作为开场第一句话”。比如用户看过“价格页面”开场就提“看到你在关注定价请问目前主要考虑哪类方案”用户下载过白皮书就围绕白皮书里提到的场景展开。第二是给每封邮件加了一个明确的 CTA 按钮而且只放一个不是三个让用户面对的选择变少。这两改让首轮回复率从 4% 提升到 9%。这说明用户不是反感被触达而是反感“和自己无关的触达”。6.3 第三轮时间窗渠道互斥人审抽检第三轮主要优化的是节奏和边界。我把时间窗从“统一上午十点”改成了“按用户历史活跃时间分布调度”比如线索来自科技公司平均活跃时间是上午十点到十一点那就选十点半如果线索在深夜访问过官网就说明可能是夜猫子触达时间改到晚上八点以后。同时加入渠道互斥规则和全局频控。还做了一件看起来会拖慢速度但实际很有用的事对高价值线索和文案中带有强承诺性词语的触达强制人工审批。虽然每 100 条里只有 3-4 条需要人工看一眼但这一眼拦住了一些可能导致品牌风险的表述。第三轮的有效触达率稳定在 38%退订率降到了 2%。7. 后续想让它走得更远Agent-Reach 的开放方向目前的 Agent-Reach 还是一个偏个人的开源项目代码结构上我尽量保持简洁核心模块都在一个仓库里可运行。接下来我想做三件事。第一把渠道适配层进一步模块化让社区可以自由贡献新的渠道插件。第二把策略引擎的规则部分做成可视化配置界面这样不懂代码的运营也能调整触达频率、时间窗和审批规则。第三沉淀一套脱敏后的触达回放数据集方便大家做触达策略的离线仿真测试。写这篇文章的时候我又把几个核心模块重构了一次。说实话Agent-Reach 看起来是个技术项目但做到后面我越来越觉得它更像一个“分寸感”工程。智能体并不是越主动越好而是在对的时间、对的情境下用对的方式说对的话。让 AI 帮我们触达用户前提是永远把用户的体验和选择权放在第一位。这才是 Agent-Reach 最想解决的问题。
返回列表