1. 客服知识为什么不能直接塞进 AI Agent
做过客服系统的人都有一个共同感受:知识库里的内容,人看着能用,机器一读就废。我见过太多团队把 Confluence 或者语雀里的客服文档直接导出成纯文本,切一切丢进向量库,然后指望 AI Agent 能对答如流。结果上线第一天,用户问“退款要多久到账”,Agent 回了一段“根据公司财务制度第三章第七条……”——用户当场关掉对话框。
问题的根子不在模型,在于知识的组织形态和消费形态不匹配。人类客服读文档时,脑子里会自动做三件事:定位到相关段落、提取关键结论、用口语重新组织。AI Agent 的 Runtime 如果没有把这套“阅读理解加转述”的流程固化下来,它就只能做关键词匹配,匹配到的还是原始文档的碎片。
所以这篇要聊的核心,就是把客服知识从“文档态”转成“问答卡片态”,并且让开源 AI Agent Runtime 能稳定地消费这些卡片。关键词里的开源、AI Agent、Runtime、客服知识、问答卡片,本质上是一条流水线上的五个环节:开源 Runtime 提供执行框架,AI Agent 负责调度,客服知识是原料,问答卡片是中间产物,最终输出的是可被检索、可被引用、可被追溯的结构化应答单元。
适合谁看?如果你正在用开源框架搭客服 Agent,或者你手里有一堆客服文档不知道怎么喂给模型,再或者你已经在做 RAG 但召回质量一直上不去,这篇的内容应该能帮你省掉至少两周的试错时间。我不讲空泛的架构图,只讲我实际跑通过的卡片设计、切分策略、Runtime 对接方式和踩过的坑。
2. 问答卡片到底长什么样才算合格
2.1 一张卡片的最小信息闭环
很多人以为问答卡片就是“问题加答案”两行字。我一开始也这么想,后来发现这种卡片在 Runtime 里根本没法用。原因很简单:Agent 需要知道这张卡片什么时候该被召回、召回后怎么用、用的时候边界在哪。只有问题和答案,等于给了一个没有标签的零件,装配的时候全靠猜。
我最终定下来的卡片结构包含六个字段,缺一不可:
| 字段名 | 作用 | 是否必填 |
|---|---|---|
| question | 标准问法,用于向量化和关键词匹配 | 必填 |
| aliases | 同义问法列表,覆盖用户口语变体 | 必填 |
| answer | 标准答案,口语化、可直接回复 | 必填 |
| conditions | 生效条件,比如地区、产品线、用户等级 | 选填但强烈建议 |
| source | 知识来源出处,用于追溯和人工复核 | 必填 |
| confidence_hint | 置信度提示,告诉 Runtime 这张卡片的可靠程度 | 选填 |
question和aliases的区别很关键。question是官方标准问法,比如“如何申请退款”;aliases是用户实际会打的字,比如“退款怎么弄”“我想退钱”“买错了能退吗”。Runtime 在做召回时,aliases的权重应该略低于question,但不能低太多,因为真实用户几乎不会用标准问法。
conditions这个字段是我踩坑之后才加上的。早期没有它的时候,一张“退款到账时间”的卡片会被所有用户召回,但实际业务里,不同支付渠道的到账时间完全不同。没有条件约束,Agent 就会给出一个“平均正确”但“具体错误”的答案,这比不回答还糟糕。
2.2 答案字段的写法直接决定 Agent 的回复质量
answer字段最容易写坏。我见过两种极端:一种是直接把文档原文粘进去,三百字带表格带脚注;另一种是只写一句“请联系客服”,等于没写。
合格的answer应该满足三个条件:口语化、自包含、可截断。
口语化是说读起来像人在说话,不是制度文件。比如“退款将在审核通过后 1-3 个工作日原路返回”就比“退款周期为审核通过后 1 至 3 个工作日,退回至原支付账户”更适合直接回复。
自包含是说这张卡片的答案不依赖其他卡片就能独立成立。如果答案里出现“详见上文”“参考相关规定”这种指代,Runtime 在单独召回这张卡片时就会断链。
可截断是说答案要分段,每段是一个完整语义单元。因为有些 Runtime 在拼接多个卡片时会做长度截断,如果答案是一整块,截断后可能只剩半句话。我通常会把答案拆成“结论段 + 补充段”,结论段控制在 50 字以内,补充段再展开细节。
2.3 卡片粒度:一张卡片只回答一个问题
这是最反直觉但最重要的一条。新手总想把相关的问题合并成一张大卡片,觉得这样“信息密度高”。实际恰恰相反,卡片粒度越细,召回精度越高。
举个例子。“退款”这个大主题下,至少有这些独立问题:退款条件是什么、退款怎么申请、退款多久到账、退款退到哪里、退款被拒怎么办。这五个问题应该做成五张卡片,而不是一张“退款说明”大卡片。
原因在于向量检索的语义空间。一张卡片如果包含五个问题的答案,它的向量会落在五个语义簇的中间地带,结果就是每个问题都召回它,但每个问题都只匹配到一部分。而五张独立卡片,每张的向量都精准落在自己的语义簇里,召回准确率会明显提升。
代价是卡片数量变多,维护成本上升。我的经验是,一个中等规模的客服场景,卡片数量在 300 到 800 张之间是比较健康的区间。低于 300 说明粒度太粗,高于 800 就要考虑是不是把非客服内容也塞进来了。
3. 从原始客服文档到卡片的完整加工链路
3.1 第一步不是切分,是标注文档类型
拿到一堆客服文档,绝大多数人的第一反应是直接切分。我早期也这么干,结果切出来的东西乱七八糟。后来我强制自己先做一步:给每份文档打类型标签。
客服文档大致分四类:流程类(怎么操作)、政策类(什么条件)、话术类(怎么回复)、FAQ 类(常见问答)。这四类的加工方式完全不同。
流程类文档适合抽成“步骤型卡片”,答案里带有序步骤。政策类文档适合抽成“条件型卡片”,conditions字段会比较多。话术类文档本身就是口语化的,加工成本最低,基本可以直接转卡片。FAQ 类文档最接近卡片形态,但往往粒度太粗,需要拆。
不打标签直接切分,等于把四种不同结构的东西混在一起处理,后面无论怎么调优都事倍功半。我现在的做法是,文档入库前先过一遍人工分类,分类完成后按类型走不同的加工流水线。
3.2 切分策略:按语义单元切,不按字数切
按字数切分是最省事也最坑的做法。500 字一刀切下去,经常把一句话切成两半,或者把两个不相关的问题切进同一块。
我用的切分策略是按语义单元切,具体规则如下:
- 遇到“问:”“Q:”“问题:”这类标记,作为新卡片的起点
- 遇到“答:”“A:”“回复:”这类标记,作为答案段的起点
- 遇到连续两个换行加标题格式,作为潜在的分割点
- 遇到“注意”“例外”“特殊情况”这类词,单独成段,不并入主答案
这套规则听起来简单,但实际跑下来,切分准确率能到 85% 以上。剩下的 15% 需要人工复核,主要集中在没有明确标记的文档里。
提示:切分完成后不要急着入库,先抽样 50 张卡片人工检查。如果这 50 张里有超过 5 张需要大改,说明切分规则有问题,先调规则再继续。
3.3 同义问法的批量生成方法
aliases字段如果全靠人工写,800 张卡片能写到崩溃。我的做法是半自动生成加人工筛选。
先用规则生成一批候选:把标准问法里的关键词做同义词替换,比如“申请”换成“办理”“操作”“弄”,“退款”换成“退钱”“返款”“退单”。再让模型基于标准问法生成 5 到 8 个口语变体。最后人工过一遍,删掉明显不合理的,保留 3 到 5 个高质量的。
这里有个经验:不要追求 aliases 数量多,要追求覆盖真实用户表达。我通常会从客服聊天记录里捞一批真实问法,和生成的 aliases 做比对,把真实问法里出现频率高的补进去。这样生成的 aliases 才有实战价值,而不是模型自嗨的产物。
3.4 卡片入库前的质量门禁
卡片写好之后,我设了三道门禁,过不了的不让入库:
第一道是完整性检查,六个必填字段缺一不可,answer字段不能少于 20 字,question不能超过 50 字。
第二道是重复度检查,用向量相似度算一遍,相似度超过 0.92 的卡片标记为疑似重复,人工确认是合并还是保留。
第三道是可回答性检查,把卡片单独拿出来,问自己“只看这张卡片,能不能回答对应的问题”。如果答案是否定的,说明卡片不自包含,打回重写。
这三道门禁跑下来,入库卡片的质量会稳定很多。我见过太多团队跳过门禁直接入库,结果线上召回一堆半成品卡片,Agent 的回复质量惨不忍睹。
4. 开源 Runtime 怎么消费这些卡片
4.1 Runtime 在整条链路里的角色定位
很多人把 Runtime 理解成“跑模型的地方”,这个理解太窄了。在客服 Agent 场景里,Runtime 的核心职责是编排:接收用户输入、决定要不要召回卡片、召回哪些卡片、怎么把卡片内容组织成回复、什么时候该转人工。
开源 Runtime 的好处是这套编排逻辑你可以完全掌控。商业方案往往把召回策略、重排逻辑、回复模板都封装成黑盒,你只能调几个参数。但客服场景的差异太大了,不同业务线的召回策略可能完全不同,黑盒方案根本不够用。
我选开源 Runtime 的判断标准有三条:支持自定义召回器、支持卡片级别的元数据过滤、支持回复内容的引用追溯。这三条缺一条,后面都会难受。
4.2 卡片索引的构建方式
卡片入库后,需要建两类索引:向量索引和关键词索引。
向量索引用于语义召回。我把question和aliases拼接后做 embedding,answer不参与 embedding,因为答案的语义会干扰问题的语义空间。这一点很多人搞反了,把整张卡片做 embedding,结果召回精度下降明显。
关键词索引用于精确匹配。用户输入里如果包含产品名、订单号、特定术语,关键词索引能快速定位到相关卡片。我通常用 BM25 做关键词召回,和向量召回做混合。
两类索引的召回结果需要融合。我用的融合策略是加权求和加去重:向量召回权重 0.7,关键词召回权重 0.3,同一张卡片被两路都召回时取最高分,不叠加。这个权重不是拍脑袋定的,是我在测试集上跑了一轮网格搜索选出来的。不同业务场景的最优权重可能不同,建议你也跑一轮。
4.3 召回之后的卡片重排逻辑
召回阶段通常会返回 10 到 20 张候选卡片,直接全塞给模型效果很差。需要做重排,把最相关的 3 到 5 张挑出来。
我的重排逻辑分三层:
第一层是条件过滤,把conditions字段和当前用户上下文不匹配的卡片直接剔除。比如用户是海外用户,国内专属的退款政策卡片就不该出现。
第二层是置信度排序,confidence_hint高的卡片优先。这个字段我通常根据卡片来源设定,官方政策文档转的卡片给高置信度,社区问答转的给中等,用户反馈整理的给低。
第三层是多样性控制,如果前几张卡片来自同一个知识来源,强制插入一张其他来源的卡片。这是为了防止 Agent 的回复被单一来源主导,导致视角偏颇。
重排之后,通常保留 3 张卡片进入回复生成阶段。超过 3 张,模型的注意力会被分散,回复质量反而下降。
4.4 回复生成时的卡片引用方式
卡片内容不能直接拼起来当回复。我见过最粗暴的做法是把三张卡片的answer用换行符连起来,结果回复读起来像三个人各说各话。
我的做法是让模型基于卡片内容重新组织语言,但在 prompt 里明确要求:结论必须来自卡片,不得自行发挥;如果卡片之间有冲突,优先采用置信度高的;如果卡片内容不足以回答问题,明确告知用户并建议转人工。
同时,回复里要保留引用追溯。Runtime 在返回回复的同时,附带一个sources字段,列出这次回复引用了哪几张卡片。这样人工客服在复核时能快速定位到原始知识,用户投诉时也能追溯到底哪张卡片出了问题。
5. 实测中暴露的五个典型问题
5.1 卡片召回对了但答案拼错了
这是最常见的问题。召回阶段返回了三张卡片,每张单独看都对,但拼在一起就矛盾了。比如一张卡片说“退款 1-3 个工作日到账”,另一张说“退款 7 个工作日内到账”,模型把两个都写进回复,用户直接懵了。
根因是卡片之间缺少优先级关系。我的解法是在卡片元数据里加一个priority字段,同一主题下的卡片按优先级排序,回复生成时只采用最高优先级的卡片,低优先级的作为补充但不作为结论。
5.2 用户问法超出 aliases 覆盖范围
用户的实际表达千奇百怪,aliases 永远覆盖不全。我遇到过用户问“钱什么时候回来”,而卡片里只有“退款到账时间”和“退款多久到账”两个 aliases,结果没召回。
解法是加一层查询改写。Runtime 在召回前,先用一个小模型把用户输入改写成标准问法,再拿改写后的问法去召回。改写模型不需要很大,我用的方案是在业务数据上微调了一个小模型,改写准确率能到 80% 左右,召回率提升明显。
5.3 条件字段写得太死导致召回为空
conditions字段如果写得太具体,比如限定到某个具体产品型号,那其他型号的用户就完全召回不到。我早期犯过这个错,把条件写得过细,结果覆盖率暴跌。
后来我改成分层条件:必选条件(比如产品线)和可选条件(比如具体型号)分开。必选条件不匹配直接剔除,可选条件不匹配只降权不剔除。这样既保证了相关性,又避免了召回为空。
5.4 卡片更新后索引没同步
卡片是活的,业务变了卡片就要改。但很多人改完卡片忘了重建索引,导致线上还是旧卡片在召回。
我的做法是把卡片更新和索引重建做成原子操作:卡片写入数据库的同时,触发索引更新任务,更新完成前旧索引继续服务,更新完成后原子切换。这样用户无感知,也不会出现新旧卡片混用的情况。
5.5 多轮对话里卡片被重复召回
用户第一轮问了退款,Agent 回了。第二轮用户追问“那要多久”,Agent 又召回了一遍退款卡片,回复里重复了上一轮的内容。
根因是 Runtime 没有维护对话级的已召回卡片集合。我的解法是在会话状态里记录已经召回过的卡片 ID,后续轮次召回时把这些卡片降权。如果用户明确追问同一主题,才允许再次召回,但回复时要避免重复表述。
6. 卡片体系的长期维护经验
6.1 建立卡片健康度看板
卡片上线不是终点。我维护了一套卡片健康度指标,每周看一次:
| 指标 | 含义 | 健康区间 |
|---|---|---|
| 召回率 | 被召回过的卡片占比 | 60%-80% |
| 命中率 | 召回后被采用的卡片占比 | 40%-60% |
| 空召回率 | 用户提问但无卡片召回的比例 | 低于 15% |
| 冲突率 | 同一次回复中卡片内容冲突的比例 | 低于 5% |
召回率太低说明卡片覆盖不够,太高说明卡片太泛。命中率太低说明召回精度有问题。空召回率高说明 aliases 覆盖不足或者卡片粒度太粗。冲突率高说明优先级管理没做好。
这套指标跑起来之后,卡片维护就从“凭感觉”变成了“看数据”。
6.2 卡片废弃比新增更重要
团队做知识库有个通病:只加不减。卡片越积越多,质量越来越差。我强制要求每季度做一次卡片审计,把连续三个月零召回的卡片标记为待废弃,人工确认后下线。
废弃卡片不是删除,是标记为deprecated状态,索引里排除,但数据库保留。这样万一以后需要追溯,还能查到。
6.3 人工反馈闭环怎么建
Agent 的回复质量最终要靠人工反馈来校准。我在 Runtime 里加了一个反馈接口,人工客服在复核 Agent 回复时可以标记“卡片选对了”“卡片选错了”“卡片内容有误”三种状态。
这些反馈数据每周汇总一次,选错的卡片进入重排逻辑调优,内容有误的卡片进入内容修订队列。这个闭环跑起来之后,卡片质量会持续提升,而不是上线即巅峰然后慢慢腐烂。
7. 一个可复现的最小实现路径
如果你现在就想动手,我建议按这个顺序来,不要跳步:
第一步,选 50 条真实客服问答,手工做成卡片,把六个字段填完整。这一步的目的是让你理解卡片结构,不要一上来就搞自动化。
第二步,用开源 Runtime 搭一个最小召回链路,只做向量召回,不做重排,看看这 50 张卡片能不能被正确召回。如果召回率低于 70%,先调卡片质量,不要急着加功能。
第三步,加入关键词召回和混合排序,观察召回率变化。这一步通常能把召回率提到 85% 以上。
第四步,加入条件过滤和优先级重排,观察回复质量变化。这一步主要解决“召回对了但回复错了”的问题。
第五步,接入真实用户流量,跑一周,收集空召回和错误召回案例,针对性补充卡片和 aliases。
这个路径我跑过三遍,每遍大概两周能到可用状态。跳过任何一步,后面都要回头补课。
注意:不要一开始就追求卡片数量。50 张高质量卡片的效果,远好于 500 张半成品卡片。卡片体系的质量下限由最差的那张卡片决定,而不是由最好的那张决定。
8. 关于开源方案选型的一点个人看法
开源 Runtime 的选择很多,我不推荐具体项目,因为不同团队的技术栈和运维能力差异太大。但我可以分享我的筛选逻辑。
首先看召回器是否可插拔。如果 Runtime 把召回逻辑写死了,你后面想换 embedding 模型或者加混合召回都会很痛苦。可插拔的召回器意味着你可以先用默认方案跑起来,后面再逐步替换。
其次看元数据过滤是否原生支持。卡片体系的核心优势就是元数据丰富,如果 Runtime 不支持按元数据过滤,那卡片里的conditions、priority、confidence_hint就全浪费了。
最后看社区活跃度和文档质量。开源项目最怕的是遇到问题没人答。我通常会看最近三个月的 issue 回复率和 PR 合并速度,这两个指标比 star 数更能反映项目的真实状态。
至于模型选择,客服场景不需要最大的模型。我实测下来,中等规模的模型配合好的卡片体系,效果比大模型配烂卡片好得多。卡片体系是杠杆,模型只是执行器。杠杆没搭好,执行器再强也撬不动。
这套东西我前后折腾了大半年,从最初的“文档直接切”到现在的“卡片加 Runtime 编排”,中间踩的坑基本都写在这了。如果你正在做类似的事情,希望这些经验能让你少走点弯路。卡片体系不是一蹴而就的,它更像是一个需要持续喂养和修剪的有机体,上线只是开始。