十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Advisor对话拦截:用规则引擎为AI客服筑起安全防线

Advisor对话拦截:用规则引擎为AI客服筑起安全防线 做AI客服、把大模型接进业务系统的人对这样一个场景应该不陌生用户发来一句话你拿不准该不该放行。放了吧后面可能跟着投诉和合规压力不放吧又容易误伤正常用户。Advisor对话拦截就是用来解决这种“该不该放行”的中间层它不负责对话本身只负责在对话进入业务逻辑之前做一次快速判定。今天这篇就聊聊Advisor怎么用、怎么自定义以及在真实项目里哪些地方最容易翻车。1. 为什么对话里非要加一道“拦截层”1.1 拦截和过滤不是一回事很多团队最初清理对话内容用的是关键词过滤消息里有黑名单词就删掉或者拒绝。这个方案在静态场景下够用但对话系统里不够用因为对话是一个上下文过程同样一句话在不同语境下意思完全不同。“这块不适合你”可能是客服在劝退也可能是用户收到的自动回复里的一句。关键词过滤只能看到“字面命中了没有”看不到“这句话该不该在这个会话里出现”。Advisor的思路不一样。它不叫filter叫intercept意思就是它像一个站在消息队列旁边的人每条消息进来先过一遍规则链链条上的每个规则可以给出自己的判断放行、拦截、转人工、改写。判断的结果不只是“命中了没”还会带上规则名、置信度、需要保留的证据比如命中的关键词、正则分组、上下文快照。这些信息在后续审计和误杀复盘里非常有用。1.2 Advisor在整个对话链路上的位置从部署位置看Advisor一般放在两个地方。输入侧用户消息进入对话引擎之前先做一轮拦截。这时候拦截的目标是“不让违规内容进入模型”。输出侧模型生成内容返回给用户之前再做一轮拦截。这时候拦截的目标是“不让模型说出越界内容”。Advisor最理想的位置是同时覆盖这两个出入口形成一条“输入检查 - 对话引擎 - 输出检查”的管道。模型能力越强越不能把安全边界完全压在模型本身的指令遵循上。模型是一个概率系统它今天能守住规则明天换了参数、换了版本表现可能就不一样了。外层拿Advisor兜一道比反复去调prompt要稳定得多。1.3 什么时候你才真正需要Advisor如果只是做Demo或者内部工具其实不需要自己写几个if判断就行。但凡是线上业务用户量一大问题就会冒出来规则散落在代码各处改一条要发版本客服反馈误杀但查不到是哪条规则杀的恶意用户用各种变体绕过关键词模型输出突然说错话却找不到兜底机制。这些信号凑齐的时候就该把拦截逻辑抽成一个独立组件了。Advisor的核心价值在于三件事规则可配置、判定可观测、动作可编排。这三个特性是我认为它区别于临时if-else方案的关键。规则可配置意味着改策略不用发版判定可观测意味着每次拦截都有据可查动作可编排意味着拦截不只是“拒绝”一种结果。后面会逐步拆开讲。2. 拆开看Advisor一次拦截的完整生命周期2.1 从输入到决策的五步流程Advisor的一次拦截并不是简单跑一条正则而是一个有明确顺序的流程。拿输入侧拦截举例消息进入Advisor生成一个带会话上下文的事件对象。按优先级排序的规则链开始执行。每条规则自行判断返回决策对象。决策被写入审计日志。最终动作决定了消息是放行、拦截还是进入人工队列。这个流程看着简单但每一步都有坑。比如第2步如果规则链里同时有“关键词拦截”和“意图识别拦截”顺序不同结果可能完全不同。一个用户说“我要投诉你们客服态度差”可能先被关键词规则命中“投诉”而进了高危队列但这句话在语义上完全没有风险反而是一条需要优先处理的客诉。相同的一句话规则顺序不一样用户的体验就完全不一样。2.2 决策对象到底长什么样Advisor的决策对象是整套机制的核心数据结构。我建议你在设计阶段就把字段定义清楚不要只留一个布尔值。参考结构如下dataclass class Decision: action: str # allow / block / review / rewrite rule_name: str # 命中哪条规则 confidence: float # 置信度 evidence: dict # 证据比如命中的词、正则匹配片段、上下文摘要 meta: dict # 扩展字段比如是否需要转人工、是否触顶频率action是最终结果rule_name拿来定位问题evidence拿来复盘误杀。很多团队只关心action忽略后两个字段结果出了线上问题连证据都捞不到。我的建议是从第一天就把evidence记录下来哪怕是内部测试阶段。一次误杀经常会牵出规则设计的问题没有证据就只能靠猜。2.3 三种常见动作block / review / rewriteblock是最常见的动作直接拒绝当前消息可以配合提示文案给用户看。review是不直接拒绝把消息转给人工或者标记为待观察适合置信度不高但规则又想覆盖的场景。rewrite是改造消息内容把敏感部分替换掉比如把手机号脱敏后再传给模型或者把“加V”这种变体词统一替换成“加微信”再做语义分析。动作不是一个选择题而是一个组合。比如一条消息可以同时被标记为review rewrite内容进入人工队列看一眼脱敏后的版本继续给模型处理。Advisor的动作设计给了这种灵活性这也是我推荐大家在自定义时不要只做布尔返回值的原因。只返回“拦不拦”后续想加“转人工”“脱敏”“降级”这些动作时整个框架都得重写。3. 开箱即用把Advisor接进你的对话服务3.1 最小接入Demo假设你有一个Python写的对话服务接入Advisor只需要在收到用户消息时加一层调用。以异步客户端的方式接入大概是这个样子from advisor import AdvisorClient, ChatContext client AdvisorClient(config_pathadvisor.yaml) async def handle_message(user_id: str, text: str, history: list[dict]): ctx ChatContext(user_iduser_id, historyhistory, scenariocustomer_service) decision await client.intercept(session_iduser_id, user_messagetext, contextctx) if decision.action block: return 抱歉这条消息无法发送。 if decision.action review: await push_to_review_queue(user_id, text, decision) if decision.action rewrite: text decision.meta[rewritten_text] return await call_dialog_engine(user_id, text, history)注意这里是async调用。Advisor内部可能要串行执行规则链如果不做成异步遇到远程语义模型时很容易把主链路卡死。在实际生产中我建议把Advisor的调用放在独立线程池或者独立进程里通过RPC暴露给业务侧不要在业务进程里直接同步调。后面第6章会专门聊这个坑。3.2 配置文件怎么写Advisor的规则一般通过YAML或JSON配置管理。一个最小配置如下advisor: chain: - name: block_business_keywords priority: 10 matcher: keyword keywords: [加微信, 转账, 代开发票] action: block level: hard - name: review_url priority: 20 matcher: regex pattern: (https?://|www\\.)\\S action: review reason: 检测到外部链接需要人工确认 - name: allow_thanks priority: 1 matcher: intent intents: [thanks, bye] action: allow stop_chain: truepriority数字越小越先执行。allow_thanks这条是白名单式兜底优先级最高先于关键词规则执行避免用户说“谢谢你帮我加微信备注辛苦了”的时候被“加微信”命中。stop_chain为true时命中后直接终止规则链不再走后面的黑名单规则。3.3 第一个规则跑起来后的验证动作规则加好后别急着上全量先打日志看命中情况。我会在一个影子环境里跑两天命中的样本全部落到审计表里然后人工抽样看准确率。对于review动作还要看进入人工队列的占比是否在可接受范围内一般建议初期控制在总消息量的1%以下。跑稳定了再把规则从shadow模式切到enforced模式。这里有一个容易被忽略的点关键词规则不是加上就完了你要给每条规则配一个owner。谁提的需求谁负责后续维护谁对这条规则的误杀率负责。规则一旦超过三个月没有人维护命中量再高也该进入审视名单。4. 自定义规则拦截器设计是核心功课4.1 规则链的编排逻辑Advisor自定义能力的核心就是规则链。每一条规则本质上是一个“判定函数”输入是一个会话事件输出是一个决策对象。规则之间可以串联、可以短路也可以用优先级控制执行顺序。好的规则链设计应该遵循几个原则轻量先行、高置信度优先、白名单高于黑名单。轻量规则比如关键词匹配、正则、长度检测放在前面几乎不花时间重量级规则比如语义模型、向量检索放在后面前面没有拦截再走。黑名单不要放在白名单之前避免把正常表达误伤。很多人一上来就画一个复杂的规则流程图其实Advisor的规则链本质就是一个有序数组关键是控制好每个规则的作用范围和退出条件。4.2 常见拦截器分类按用途拦截器可以分成几类类型典型用途优点缺点关键词命中业务黑名单词快、可控容易绕过需要维护变体正则抓手机号、链接、卡号等模式灵活写坏了会大面积误杀意图识别判断“骂人”“导流”等意图覆盖面广需要标注样本向量相似度和已有违规样本做语义比对能防变体延迟高需要维护样本库频率/行为连续发相同消息、短时高频防刷需要和业务场景结合实际项目里很少只用一个类型。比如拦截“导流”类广告可以先用关键词命中“加微信”这种强特征再用一个相似度模型兜底拦截“VXxxx”这类变体。关键词负责快和准模型负责广和变。跑一段时间后你会发现关键词词表在缩小因为大部分变体都被模型接住了。4.3 自定义拦截器代码骨架如果框架本身不满足需求Advisor也允许注册自定义拦截器。以Python为例from advisor import register_interceptor, Interceptor, Decision register_interceptor(behavior_frequency) class BehaviorFrequencyInterceptor(Interceptor): def __init__(self, max_count: int 5, window_seconds: int 60): self.max_count max_count self.window_seconds window_seconds async def evaluate(self, event, context) - Decision: key ffreq:{context.session_id} current await context.cache.incr(key, expireself.window_seconds) if current self.max_count: return Decision( actionblock, rule_nameself.name, confidence1.0, evidence{count: current, limit: self.max_count} ) return Decision(actionallow, rule_nameself.name, confidence0.0)这里的关键是每个拦截器都要返回Decision而不是True/False。这样上层的动作编排才能统一处理要不要暂停规则链、要不要记日志、要不要触发人工。我见过很多自定义实现只返回布尔值结果后面想加“转人工”动作时整个框架都得改。4.4 上下文与豁免机制拦截器不能只看当前这一句话还要会看上下文。比如用户说“我不是这个意思”单看这句话没有任何问题但如果前面的消息触发了review这句话可能是对人工审核结果的质疑。Advisor会在事件对象里带上会话上下文拦截器可以取到最近几轮消息。同时用户分层和场景参数也要考虑进来。同一个词在儿童内容场景里和金融客服场景里风险等级完全不同。可以在配置里设置场景变量在规则条件里加上when.scene finance这种限定。用户等级也是一样VIP用户、内部测试账号、游客账号可以走不同规则集。比如内测用户发一条“开发票”应该放行因为这是他们在测试开票流程但普通用户发同样的内容就要走合规评审。5. 拦下来之后怎么办动作编排与样本回流5.1 拦截不只是拒绝我见过不少团队把“拦截”等同于“拒绝回复”这是最大的误解。拦截动作真正的作用是给对话流程一个干预点干预方式可以非常多直接拒绝并给用户一个反馈文案转人工让客服介入处理静默弃掉当前消息不让模型看到但也不打断用户脱敏后再放行比如把身份证号、手机号替换成占位符再传模型降级回答换成通用话术而不是模型的实时输出。这些动作可以通过规则链的action组合实现。以脱敏为例可以先过一个PII识别拦截器命中后不是直接block而是生成rewrite版本的消息然后放行。这样用户和模型都不知道发生过脱敏体验上几乎没有感知。等业务方对合规要求变严时再决定是否把rewrite升级成review。5.2 审计日志与样本回流Advisor的审计日志是这类组件里我最看重的能力。每次拦截至少记录session_id、user_id、消息原文、规则名、决策、置信度、证据、处理时间。日志的作用有两个。一是定位线上问题。某天客服反馈“用户明明没违规为什么被限制了”没有证据基本查不了。二是作为规则迭代时的训练语料。被review但人工确认无风险的消息是绝佳的负样本可以拿去优化语义模型被block但用户反复用不同说法表达同样意思的消息说明规则被绕过了需要补充变体。我通常把审计日志直接落到ClickHouse这类列式存储里按天分表保留90天。每天用脚本统计命中Top规则、误杀Top规则然后周会过一遍。这个周会不用很长但一定要固定开否则规则就慢慢腐烂了。5.3 三个关键指标自定义做多了Advisor的效果评估也要数据化。我常用的指标有三个指标计算方式含义拦截率命中拦截消息数 / 总消息数规则覆盖面误杀率人工复核后无风险 / 拦截总数规则是否合理平均判定耗时总耗时 / 请求数性能是否达标拦截率不是越高越好。有些团队把拦截率做到了5%以上表面看拦截很多查误杀率一定很高。合理的做法是盯相邻两个指标拦截率保持稳定误杀率持续下降说明规则在变准而不是变严。如果两个指标一起涨说明新规则大概率太激进了。6. 我在实际项目里踩过的几个坑6.1 正则写得太宽把所有链接都拦了有一版配置里我想拦截“导流到站外的链接”写了个正则可匹配URL。结果上线后发现用户发“官网里写着 www.example.com 大家可以去看看”也被拦了因为规则没有区分站内域名和站外域名。正常业务里大量用户会提到网址全拦掉体验非常差。后来改成先做域名白名单判断再走URL规则才把误杀降下来。这件事给我的教训是正则规则的pattern一定要能反推出“目的是什么”并且在证据里带上匹配片段。上线前用至少1000条线上历史消息做回归测试看看有多少正常消息会命中。正则这东西匹配范围宽一点误杀面就是指数级放大。6.2 规则顺序没控制好白名单失效在白名单机制还没做之前我给“关键词拦截”设了优先级10意图识别设了20。结果用户说“不是转账是转给我朋友看看”先被关键词“转账”命中直接block。用户很困惑客服也查不到原因因为拦截时根本没有记录当前意图。后面我调整了策略所有allow/negative类规则的优先级必须高于block类并且allow规则一旦命中就短路。优先级设计要跟着业务目标走而不是跟着规则添加时间走。新加规则的时候默认放最后不要觉得“加一条规则”是小事它可能改变前面所有规则的执行效果。6.3 模型输出拦截与流式输出的矛盾Advisor放在输出侧的时候遇到的最大问题是和流式输出打架。LLM的token是一个一个蹦出来的等整句生成完再拦截用户在界面上要等很久要边生成边拦截又面临“前半句正常后半句越界”的情况。我的方案是对输出内容做分片缓存维护一个待判定窗口每生成一段就送去Advisor做一次轻量判定。如果发现风险立即终止生成流用预设兜底话术替换剩余部分。这个方案不是万无一失但实测下来用户感知到的卡顿基本控制在几百毫秒内比整句再拦要好得多。轻量判定只跑关键词和正则不跑语义模型否则延迟依然很高。6.4 同步调用把业务进程拖死有一版实现里Advisor的语义相似度拦截器要调用远程embedding服务。因为图省事直接在进程里同步调用结果压测时QPS一高线程全部卡在等待响应上业务主流程跟着雪崩。后面改成异步并发处理语义模型在独立服务里跑Advisor只负责汇总结果性能才算稳住。这类问题不好复现往往在流量高峰才暴露。建议从设计初期就把Advisor定位成“可能慢的服务”所有调用都走超时和降级。比如语义模型超时300ms就跳过该规则不让单条规则的故障拖垮整个对话。网络调用永远要假设对方会挂这是线上系统的基本素养。7. 让自定义规则稳定落地的三个经验7.1 规则要能灰度不要一键全量Advisor的规则不能像改普通配置一样直接推到生产。我给每个规则都配上灰度维度可以按用户ID哈希的百分比放量也可以按场景维度开启。先让5%的流量走新规则观察24小时确认拦截率和误杀率都在预期范围再逐步放开。灰度期间拦截动作统一降级为shadow也就是只记录决策、不真正执行。这样即使规则写错了也不会伤到用户。等规则验证过再切到enforced。很多事故不是规则本身写错了而是没有给它一个观察期。7.2 永远留一条人工放行通道不管规则多完善误杀都无法完全避免。所以Advisor接入的业务都要留一个“申诉-放行”通道。用户可以反馈“我没违规”客服在后台看到消息原文和触发规则后手动放行。手动放行的样本不仅要回写给用户还要回流到误杀样本库里作为优化规则的依据。我见过很多团队忽略这个通道结果规则一旦误杀用户投诉无门最后只能靠开发临时改配置。这个体验很差而且会让业务方对拦截组件失去信任。拦截组件一旦失去业务信任大家就会绕开它规则就会变成摆设。7.3 定期给规则做减法自定义规则是会膨胀的。业务每次提一个新风险点就往规则链里加一条半年下来可能积累了几百条规则。规则太多不只是性能问题更严重的是规则互相覆盖、互相打架排查起来非常痛苦。我的习惯是每个月做一次规则盘点命中量为零的规则直接下线命中率高但证据字段几乎相同的规则合并被新规则完全覆盖的旧规则删除。同时把每个规则的“最后命中时间”统计出来超过30天没命中的提醒负责人确认是否还要保留。这些事听起来琐碎但按我个人经验规则库的整洁程度直接决定Advisor能不能长期用下去。一次认真做好的规则梳理比新加十次规则更能降低误杀率。拦截系统从来不是“加得越多越安全”而是“该拦的拦得住不该拦的放得过去”。
返回列表