做数据分析 Copilot 最危险的诱惑,就是让大语言模型直接数数和算比例。本文作者分享了一个智能客服分析助手的实战经验:建立“数字必须由计算得出,绝不生成”的铁律,用 DuckDB 和规则分流实现 125ms 响应与零调用成本。

客服业务主管抛出了一个常见问题:上个月导致负面情绪的核心问题是什么?
最省事的架构设计,莫过于将所有对话记录统统塞给大语言模型(LLM),然后直接输出结果。这种回答读起来通常行文流畅、无懈可击。但它很可能是错的——直到某天汇报 PPT 里的虚假数据被领导当场戳穿,团队才会意识到问题严重性。
上线过足够多数据产品的人都会达成一个共识:看板里给出一个错误数字,比根本没有看板更危险,因为业务人员会据此做出真实决策。
因此,我构建了一个完全反向运作的系统。
这套面向客服对话的智能分析助手基于 LangGraph 编排分析流程,在包含 4.1 万条消息的 3000 场对话上执行预注册的 DuckDB 分析查询,召回支撑结论的真实对话切片,做出解释,并在呈现前对自身答案进行严苛审计。
大模型在整套链路中完全是可选的。产品核心的四个业务问题,实现了 0 次模型调用,在普通笔记本 CPU 上的中位数响应时间仅为 125 毫秒。
在敲下第一行代码前,我立下了一条原则,由它来决定后续所有的工程妥协:
数字必须由程序计算得出,绝不可由模型生成。
写下这条规则,是因为我知道自己之后一定会动摇。很多时候模型“差不多能数对”,直接让它生成往往能省下大量联调规则的精力。

立下铁律后,核心问题变成了:如果大模型连基础事实都不能提供,那到底什么时候才值得调用它? 我在这个问题上耗费的时间,超过了任何单个具体功能。

整个工作流分为四个阶段,系统界面的“决策回放”(Decision Replay)面板允许逐节点回溯:

架构中有三项核心选型至关重要:
在触发模型前,一套低成本、高精度的级联检查机制会先行判断问题类型:
部分典型问题的分流策略:
回到最开始的问题:上个月导致负面情绪的前 5 大原因是什么?
“上个月”本身就是个常见的时区陷阱。测试数据集跨越 2025 年 8 月至 12 月初,因此“上个月”必须动态锚定为数据集覆盖的最近完整自然月(11月),而非当前系统日历时间。
2025 年 11 月,682 场对话中有 240 场 被划分为负向情绪。

一个合格的数据分析师在出具汇报时会把这些统计学限制交代得一清二楚。现在的 Copilot 同样会主动讲明这些盲区。
这些意外挑战并没写在初始需求文档里,却占用了绝大部分开发精力。
业务定义:当客户发言的平均情绪分值 ≤ -0.25 时,判定为负向对话。对话 C0010797 包含 8 轮客户发言:
-0.6, -0.6, -0.6, -0.6, +0.8, -0.6, +0.8, -0.6
纸面算术上,平均值恰好是 -0.25。但在计算机浮点数累加时:
let sum = 0;
for (const s of [-0.6, -0.6, -0.6, -0.6, 0.8, -0.6, 0.8, -0.6]) sum += s;
sum / 8; // 结果为 -0.24999999999999994
sum / 8 <= -0.25; // 返回 false:被错误标记为“非负向”
我在手工做校验对比时发现了一个 0.5 的分值偏差。修复方案是比较前统一做 9 位精度四舍五入。这个改动立刻让业务优先级分值从 52.9 变动至 53.4,11 月的负向会话总数从 239 纠正回 240。
更讽刺的是,我最初写的校验脚本也是用浮点数算的,因而完全复现了同一个 Bug。两个共享了底层假设的校验器,本质上是一个校验器。 最终真相验证器改用了分数精准运算。
在现成的 3000 场对话数据里,消息的时间戳字段与真实的交谈先后顺序完全对不上。按时间戳排序会得到“先发感谢再做提问”的荒谬对话。最终系统确立以 Turn Index(轮次递增序号)为唯一绝对依据,并明确向使用者披露时间戳失真。
第二个问题是识别“哪些客服人员需要接受业务辅导培训”。但整个数据集包含 3000 场对话,却有多达 2987 个不同的客服工号,平均每个客服只有 1 到 2 场会话记录。
基于一两通对话就对一线员工的业务水平盖棺定论,在管理上是不负责任的。 系统选择拒绝直接输出坐席淘汰排名,而是提供更有建设性的聚合大盘:
原始数据是一套合成对话集,每条消息末尾附带了混淆字符。在剥离这些干扰字符后,全量 41,965 条记录其实是由 426 种 固定文本模板排列组合而成的。
与其花大笔 Token 让大模型给四万多条消息分别打标,我直接用确定性规则对这 426 个模板进行了清晰的手工映射。同时在前端暴露标注纠偏入口,并明确提示该判定规则尚未经受大规模双盲测试。
开发中途,云端商业模型的 Key 突然提示 401 API_KEY_SERVICE_BLOCKED。这反而成为了整个项目最好的架构催化剂——它逼我思考:这套系统到底哪一步是非要 LLM 不可的?
我彻底把模型做成了可选插拔组件,引入约 1.1GB 体积的开源小模型 Qwen2.5-1.5B-Instruct,借助 llama.cpp 直接在本地 CPU 上运行。常驻预热状态下处理一次简单意图识别仅需 1 秒左右,彻底摆脱对外网 API 的依赖。
最初我让 1.5B 参数小模型执行意图归类,在固定的保留验证集上准确率仅有惨淡的 19.2%。通过将 Prompt 改写为基于闭合枚举集合,并挂载 JSON Schema 强约束语法树后,准确率拉升至 58.9%。

但在小模型上死磕 Prompt 收益极低。最终的破局点是让模型退出主干流程,改用级联置信度机制:
小模型最终只承担了 6.9% 的边缘长尾意图解析。其余超过九成的问题,纯代码规则跑得又快又准。
“证据挑战器”作为一个对抗性节点,在每次生成解释文本后强行介入:
在我人工为报表编写的注释中,曾顺手写过某类目排名垫底是“因为平局决胜规则”。挑战器直接将“因为”(because of)暴力替换成“与…相关”(associated with),让句子变得生硬。但这证明它切实发挥了作用——在数据分析里,不能把主观因果推断当作事实交付。
在做前端可视化联调时,我注入了 120 场带特殊标签的测试对话。结果它们被默认加载到了分析集中,导致原本 3000 场的全量数据变成了 3120 场。将测试数据改为手动显式加载后,底层倒排词典发生了变动,直接影响了召回率指标。在报告中必须如实呈现变动后的指标波动,而不是为了数据好看强行保留旧指标。
系统对 18,639 个文本分块同时建立了 MiniLM 语义向量与 LSA 关键词索引,并通过倒数排名融合(RRF)进行加权合并。

从测试表现看,混合检索确实优于纯向量或纯文本匹配,但收益幅度并不夸张。向业务方坦承“混合检索仅带来了微弱但可靠的边际收益”,远比用挑选过的数据吹嘘大幅提升更有价值。
所有返回的答案都会在界面右上角标出路由决策流、命中的意图类型以及实际 Token 消耗:
评估一个分析型系统时,同样要看它拒绝做什么:
如果推倒重来,有三件事是一定要保留的:
免费获取企业 AI 成熟度诊断报告,发现转型机会
关注公众号

扫码关注,获取最新 AI 资讯
3 步完成企业诊断,获取专属转型建议
已有 200+ 企业完成诊断