1. 从客服质检的痛点说起为什么我选择动手做这个 Demo电商客服这个岗位表面看是“打字回复”实际背后是一整套高压力的情绪劳动。我身边做运营的朋友经常吐槽大促期间一个客服同时挂十几个会话客户上来就是“你们是不是骗子”“再不发货我就投诉”客服稍微回慢一点情绪就炸了。而质检环节更头疼——传统质检靠人工抽检一个质检员一天最多听几十通录音或翻几百条聊天记录覆盖率低得可怜等发现问题时客户早就流失了。我这次动手做的就是一个电商客服质检 Demo核心目标很明确把客服和客户的对话实时过一遍自动完成三件事——意图识别客户到底想干嘛、情绪监控双方情绪有没有失控苗头、危险话术拦截客服有没有说出违规承诺或激化矛盾的词。整套东西我用Jev来搭配合TypeSafe的思路做结构化输出最终跑通了一个能演示、能扩展的原型。为什么选 Jev因为这类质检场景对“输出结构稳定性”要求极高。你想想如果模型返回的是一段自由文本我还得再写正则去抠字段那工程上就是灾难。Jev 在结构化生成和类型约束上的表现让我可以把“意图”“情绪分”“风险等级”直接定义成 schema模型按格式吐结果下游代码拿到的就是干净的对象。这对做 Demo 来说省掉了大量胶水代码。这篇文章适合谁看如果你是做电商中台、客服系统、或者对 LLM 落地质检感兴趣的后端/算法同学这篇可以直接抄作业。我会把整体设计、Jev 的接入方式、意图分类体系、情绪打分逻辑、危险话术规则引擎以及我踩过的坑全部摊开讲。哪怕你之前没接触过 Jev跟着走一遍也能理解这套质检链路是怎么转起来的。2. 整体架构设计质检链路是怎么串起来的2.1 为什么是“规则 模型”双轨而不是纯模型一开始我也想过纯模型方案把对话丢给 Jev让它直接输出“是否违规、情绪如何”。但实测下来纯模型在危险话术拦截上有个致命问题——漏报。比如客服说“这个我私下给你补”模型可能觉得语气挺友好判定为正常。但业务上这是典型的“私下承诺”属于高危。规则引擎的好处是确定性只要命中关键词或句式立刻拦截不跟你讲概率。所以我的架构是双轨并行模型轨Jev 负责意图识别和情绪监控输出结构化结果。规则轨本地维护一份危险话术库用 AC 自动机做多模式匹配毫秒级命中。两轨结果最后在一个决策融合层汇合输出最终的质检标签。这样做的好处是模型负责“理解语义”规则负责“守住底线”各司其职。2.2 数据流全景从一条消息到一张质检单整个链路我拆成五步消息接入模拟客服系统推送的对话流每条消息带session_id、role客服/客户、content、timestamp。预处理清洗文本去表情、去多余空格、按会话窗口聚合最近 N 轮对话。模型推理把聚合后的对话喂给 Jev拿到意图标签、情绪分值、风险提示。规则匹配同一份文本过危险话术库命中则打上规则标签。融合输出合并模型和规则结果生成质检单写入结果表。这里有个设计细节为什么要按会话窗口聚合而不是单条消息分析因为意图和情绪都是上下文相关的。客户单独说一句“好的”你根本判断不出情绪但结合前一轮客服说“退款要等 7 天”这个“好的”可能就是不满的隐忍。所以我固定取最近 6 轮对话作为分析窗口实测这个长度在客服场景里比较平衡既够上下文又不会让 token 爆掉。2.3 TypeSafe 思路在质检里的价值TypeSafe 这个词听起来玄落到质检 Demo 里其实很实在让模型的输出必须符合预定义的类型结构。我定义了一个质检结果类型包含intent: 枚举比如咨询物流、申请退款、投诉服务、闲聊。emotion_score: 0 到 1 的浮点数越高越负面。risk_level: 枚举low、medium、high。risk_reason: 字符串说明为什么判风险。Jev 在生成时被约束成只能输出这个结构。这样一来下游的融合层拿到的就是强类型对象不用做任何解析容错。我试过用普通模型输出 JSON十次里有两三次会多带解释文字或者字段名拼错而 Jev 的约束生成把这类问题压到了几乎为零。3. Jev 接入实操从环境准备到第一次跑通3.1 环境准备与依赖安装我是在 Windows 上做的开发Jev 的本地部署对 Windows 支持还算友好。整体步骤不复杂但有几个点容易卡住。首先确认你的机器有 Python 3.10 以上我用的 3.11。然后建虚拟环境这一步别省因为 Jev 的依赖里有些版本和系统全局包会冲突python -m venv venv venv\Scripts\activate接着安装核心依赖。我这里列的是我实际跑通的版本组合你可以直接抄pip install jev-sdk0.4.2 pip install pydantic2.6.1 pip install pyahocorasick2.0.0pyahocorasick是做危险话术多模式匹配用的后面会讲。pydantic用来定义 TypeSafe 的 schema和 Jev 配合很顺。注意Jev 的 SDK 版本更新比较快如果你装完发现接口对不上先看官方文档的版本说明别硬套我这套版本号。我写这篇时用的是 0.4.2接口是稳定的。3.2 申请密钥与初始化客户端Jev 需要密钥才能调用。申请流程这里不展开按官方指引走就行。拿到密钥后我建议放到环境变量里别硬编码set JEV_API_KEYyour_key_here初始化客户端import os from jev import JevClient client JevClient(api_keyos.environ[JEV_API_KEY])这里有个小坑Windows 下环境变量设置后当前终端不生效得重开一个终端。我第一次弄的时候折腾了十分钟以为是密钥错了其实是终端没刷新。3.3 定义质检输出的 TypeSafe Schema这是整个 Demo 的核心。我用 Pydantic 定义结构Jev 会按这个结构约束生成from pydantic import BaseModel, Field from enum import Enum class Intent(str, Enum): LOGISTICS 咨询物流 REFUND 申请退款 COMPLAINT 投诉服务 CHITCHAT 闲聊 OTHER 其他 class RiskLevel(str, Enum): LOW low MEDIUM medium HIGH high class QualityResult(BaseModel): intent: Intent emotion_score: float Field(ge0.0, le1.0) risk_level: RiskLevel risk_reason: str Field(max_length100)定义好之后调用时把 schema 传进去Jev 就会按这个格式返回。我实测下来字段类型和枚举约束都能被严格遵守emotion_score也不会跑出 0 到 1 的范围。3.4 第一次调用与结果验证先拿一段模拟对话试水dialog 客户我上周买的鞋子到现在还没发货你们到底怎么回事 客服您好我帮您查一下请稍等。 客户等什么等都等一周了再不发货我就退款投诉 result client.generate( promptf分析以下客服对话输出意图、情绪分、风险等级和原因\n{dialog}, schemaQualityResult, ) print(result.intent) # 投诉服务 print(result.emotion_score) # 0.85 print(result.risk_level) # high第一次跑通看到结构化输出的时候确实挺爽。意图识别准确情绪分也合理风险等级因为客户提到“投诉”被判为 high。这一步验证了模型轨是通的。4. 意图识别体系怎么让模型分得准4.1 意图分类的粒度设计意图分类最怕两件事太粗和太细。太粗比如只分“咨询”和“投诉”那质检报告没价值太细分几十个类模型容易混维护也累。我最后定了五个大类覆盖电商客服 90% 以上的场景意图标签典型触发语质检关注点咨询物流发货、快递、到哪了响应时效申请退款退款、退货、不想要了流程合规投诉服务投诉、差评、曝光情绪安抚闲聊谢谢、好的、在吗无需干预其他无法归类的人工复核这个粒度是我反复调过的。一开始我加了“咨询价格”“咨询尺码”这些后来发现模型经常和“咨询物流”混因为客户一句话里可能同时问价格和发货。合并成大类后准确率明显上来了。4.2 用 Few-shot 示例提升分类稳定性Jev 支持在 prompt 里给示例。我发现给每个意图配 2 到 3 个典型例子分类稳定性提升很明显。比如few_shot 示例1 客户我的订单什么时候发货 意图咨询物流 示例2 客户这个我不要了给我退钱。 意图申请退款 示例3 客户你们客服态度太差了我要投诉 意图投诉服务 把这些示例拼进 prompt模型对边界情况的判断会稳很多。我对比过不加示例时“投诉服务”和“申请退款”偶尔会混加了之后基本不混了。4.3 意图识别的边界情况处理实际对话里有一类难缠的情况客户一句话包含多个意图。比如“你们发货这么慢我要退款还要投诉你们”。这时候模型只能输出一个意图怎么办我的处理策略是按优先级取最高的。优先级顺序是 投诉服务 申请退款 咨询物流 闲聊。因为投诉是最高风险质检必须优先捕捉。这个优先级我写在 prompt 里明确告诉模型让它遇到多意图时按这个规则选。另外还有一类“意图漂移”客户一开始咨询物流聊着聊着变成投诉。我的做法是每个会话窗口独立分析不继承上一轮意图。这样虽然会丢失一些连续性但避免了错误传播。实测下来独立分析反而更稳。5. 情绪监控给对话装一个“情绪温度计”5.1 情绪分值的定义与计算逻辑情绪监控我用的是一维分值0 表示非常平静1 表示极度愤怒。为什么不用“正面/中性/负面”三分类因为质检需要的是趋势不是快照。分值能让我看到情绪是往上走还是往下走。Jev 在 prompt 里被要求输出这个分值。我给的指令是根据客户和客服的对话评估客户当前的情绪激烈程度。0 表示完全平静1 表示极度愤怒。只输出数值不要解释。实测下来模型给分和人工标注的相关性挺高。我抽了 50 条对话做人工对比偏差超过 0.2 的只有 4 条主要出现在反讽语境比如“你们可真厉害啊”模型有时给 0.3人工觉得应该是 0.7。这类反讽是情绪识别的老大难后面我会讲怎么补。5.2 情绪趋势判断单点分值不够用单点情绪分只能看当下但质检真正关心的是情绪有没有失控趋势。所以我加了一个简单的趋势判断取最近 3 个会话窗口的情绪分做线性拟合斜率大于阈值就标记为“情绪上升”。def emotion_trend(scores): if len(scores) 3: return insufficient_data # 简单斜率计算 n len(scores) x_mean (n - 1) / 2 y_mean sum(scores) / n numerator sum((i - x_mean) * (s - y_mean) for i, s in enumerate(scores)) denominator sum((i - x_mean) ** 2 for i in range(n)) slope numerator / denominator if slope 0.15: return rising elif slope -0.15: return falling return stable这个斜率阈值 0.15 是我调出来的。太低会频繁误报太高又抓不住苗头。0.15 对应的是“三个窗口内情绪分涨了 0.3 左右”这个幅度在客服场景里已经值得预警了。5.3 客服侧情绪也要盯很多人做质检只盯客户情绪但客服情绪同样重要。客服如果情绪崩了回复会变得机械甚至带刺反而激化矛盾。所以我在 prompt 里让 Jev 同时输出客服情绪分虽然 schema 里我只留了客户情绪分做主字段但客服情绪分我存在了扩展字段里用于内部预警。实测发现一个规律客服情绪分先于客户情绪分下降。也就是说客服开始烦躁的时候客户往往还没到爆发点。这个信号如果用好可以在客户爆发前就介入比如换一个客服接手。这个发现是我在跑了几百条模拟对话后总结出来的挺有价值。6. 危险话术拦截规则引擎怎么守住底线6.1 危险话术的分类与词库构建危险话术我分了四类违规承诺私下补偿、保证退款、承诺时效。激化矛盾你爱买不买、随便你投诉。推卸责任这不是我们的问题、你自己没看清。敏感信息引导私下交易、索要个人联系方式。词库我是手工整理的加上从历史质检记录里挖的高频违规句。整理下来大概 200 多条模式。这里要说明词库不是越多越好太多会误伤正常对话。我控制在 200 条左右覆盖主要风险点。6.2 AC 自动机实现毫秒级匹配200 多条模式如果用 for 循环逐条匹配一条对话要跑 200 次太慢。我用 AC 自动机做多模式匹配一次扫描就能找出所有命中import ahocorasick def build_automaton(patterns): A ahocorasick.Automaton() for idx, pattern in enumerate(patterns): A.add_word(pattern, (idx, pattern)) A.make_automaton() return A automaton build_automaton(danger_patterns) def match_danger(text): hits [] for end_index, (idx, pattern) in automaton.iter(text): hits.append(pattern) return hits实测一条 500 字的对话匹配耗时在 1 毫秒以内完全不影响整体链路。6.3 规则与模型的融合决策规则命中后怎么和模型结果融合我的策略是规则命中high风险词直接覆盖模型结果判high。规则命中medium风险词如果模型也判medium或以上维持否则提升到medium。规则未命中以模型结果为准。这样做的逻辑是规则是底线模型是补充。规则说高危那就是高危不给模型翻案的机会。规则没说话才让模型发挥语义理解的优势。7. 实操全流程从零跑通一个质检会话7.1 模拟数据构造为了演示我构造了一段典型对话session { session_id: test_001, messages: [ {role: customer, content: 我上周买的鞋子还没发货}, {role: agent, content: 您好我帮您查一下}, {role: customer, content: 查什么查都一周了}, {role: agent, content: 这个我私下给您补个优惠券吧}, {role: customer, content: 我不要优惠券我要投诉你们}, ] }这段对话里客户意图是投诉情绪在上升客服说了“私下补优惠券”属于违规承诺。正好能测全链路。7.2 完整质检流程代码def quality_check(session): # 1. 聚合对话 dialog \n.join( f{客户 if m[role]customer else 客服}{m[content]} for m in session[messages] ) # 2. 模型推理 model_result client.generate( promptbuild_prompt(dialog), schemaQualityResult, ) # 3. 规则匹配 rule_hits match_danger(dialog) # 4. 融合决策 final_risk model_result.risk_level if any(p in HIGH_RISK_PATTERNS for p in rule_hits): final_risk RiskLevel.HIGH elif rule_hits and final_risk RiskLevel.LOW: final_risk RiskLevel.MEDIUM return { session_id: session[session_id], intent: model_result.intent, emotion_score: model_result.emotion_score, risk_level: final_risk, risk_reason: model_result.risk_reason, rule_hits: rule_hits, }跑这段代码输出大概是{ session_id: test_001, intent: 投诉服务, emotion_score: 0.88, risk_level: high, risk_reason: 客户明确表示要投诉情绪激烈, rule_hits: [私下给您补] }意图、情绪、风险、规则命中全齐了。这个结果直接可以写进质检单。7.3 结果落库与可视化Demo 里我用 SQLite 存结果方便演示import sqlite3 conn sqlite3.connect(qc_results.db) conn.execute( CREATE TABLE IF NOT EXISTS qc ( session_id TEXT, intent TEXT, emotion_score REAL, risk_level TEXT, risk_reason TEXT, rule_hits TEXT ) )可视化我没做太重就用简单的统计每天高危会话数、意图分布、平均情绪分。这些指标对运营来说已经够用了。8. 常见问题与排查技巧实录8.1 模型输出不稳定怎么办现象同样的对话跑两次意图标签不一样。排查先看 temperature 设置。Jev 默认温度可能偏高我把它调到 0.1 后稳定性明显提升。另外检查 prompt 里有没有歧义表述比如“分析对话”这种模糊指令改成“按以下规则分类”会稳很多。解决温度降到 0.1prompt 里明确分类规则加 few-shot 示例。8.2 情绪分和人工判断偏差大现象反讽语句情绪分偏低。排查反讽是语义理解的难点模型容易按字面理解。解决我在 prompt 里加了一句“注意识别反讽、阴阳怪气等表达这类应判为高负面情绪”。加了之后反讽场景的偏差小了很多。另外可以维护一个反讽句式库命中后直接给情绪分加 0.3。8.3 规则误伤正常对话现象客服说“我帮您申请退款”命中了“退款”相关规则被判风险。排查规则词库太宽泛“退款”本身不是危险词危险的是“保证退款”“一定退款”。解决把规则从单词改成短语模式比如“保证退款”“肯定能退”。同时给规则加角色限定只匹配客服说的话客户说“退款”不触发。8.4 常见问题速查表问题可能原因解决方向意图分类混淆分类粒度过细合并相似意图情绪分偏低反讽未识别加反讽提示或句式库规则误报词库太宽泛改短语匹配加角色限定输出格式错乱未用 schema 约束启用 TypeSafe 结构响应慢对话窗口太长限制最近 6 轮8.5 几个我踩过的坑第一个坑Windows 下路径分隔符问题。我一开始把词库文件路径写成data/danger.txt在 Windows 上跑报错。改成os.path.join拼接就好了。第二个坑Jev 的 schema 里枚举值中文。我一开始担心中文枚举会出问题实测没问题Jev 能正确输出中文枚举值。但如果你下游要做数据库存储建议还是映射成英文避免编码问题。第三个坑会话窗口取太长导致 token 超限。我一开始取最近 20 轮结果长对话直接爆 token。后来改成 6 轮并且对每条消息做长度截断超过 200 字就截断。这个策略在客服场景够用因为客服对话通常短平快。9. 后续可以怎么扩展这套 Demo 跑通后我脑子里已经有好几个扩展方向。第一个是实时拦截现在我是事后分析如果改成流式处理客服刚打出危险话术就弹窗提醒价值更大。第二个是质检报告自动生成把每天的质检结果汇总成报告自动发给运营主管。第三个是意图识别的持续优化把人工复核的结果回流做 few-shot 示例的动态更新让模型越用越准。Jev 在这套链路里的角色很清晰负责语义理解和结构化输出。它的 TypeSafe 特性让我省了很多解析代码这是我最满意的地方。如果你也在做类似的质检或对话分析项目建议先把 schema 定义清楚再往上搭逻辑这样后面改起来不会乱。最后分享一个小技巧情绪分的阈值不要拍脑袋定。我是拿了一批历史对话人工标了情绪等级然后看模型分值分布找到区分“平静”和“愤怒”的分界点。我最后定的是 0.6超过就算负面。这个值因业务而异你得用自己的数据去校准。