1. 客服知识为什么需要“问答卡片”这种形态
做过客服系统的人都有一个共同感受:知识库里的文档写得再全,一线客服在跟用户对话的那几分钟里,根本没时间翻。用户问“我这个订单为什么还没发货”,客服需要的不是一篇三千字的售后政策说明,而是一张能直接命中问题、给出标准回复口径的卡片。这就是问答卡片存在的意义。
我最早接触这个需求,是在给一个电商团队做 AI Agent 客服助手的时候。当时他们的知识库是一堆 Word 文档和飞书文档,里面有产品参数、退换货规则、物流时效说明、活动细则,内容很全,但结构极差。AI Agent 接进去之后,回答质量惨不忍睹——要么答非所问,要么把三段不相关的政策拼在一起,要么直接说“我无法回答这个问题”。后来我们花了大概两周时间,把那些文档拆成了结构化的问答卡片,Agent 的命中率和回答准确率才有了质的提升。
所以这篇文章要聊的,就是开源 AI Agent Runtime 场景下,客服知识怎么整理成问答卡片这件事。它不是单纯的知识管理问题,也不是单纯的 AI 工程问题,而是两者的交叉地带。你既要想清楚“知识怎么拆”,也要想清楚“Runtime 怎么用这些卡片”。
适合读这篇内容的人大概有三类:一是正在从零搭建 AI Agent 客服系统的开发者,二是手里有一堆客服文档但不知道怎么喂给 AI 的运营或产品同学,三是已经在用开源 Runtime 但效果不理想的工程师。我会尽量把原理讲透,同时给出可以直接抄的操作步骤。
2. 先搞清楚 Runtime 到底在问答卡片里扮演什么角色
2.1 Runtime 不是知识库,它是“调度器”
很多人一开始会把 Runtime 和知识库混为一谈,觉得把文档丢进去,Runtime 就能自动回答问题了。这个理解偏差会导致后面所有设计都跑偏。
Runtime 的本质是一个执行调度层。它负责的是:接收用户输入、决定要不要检索知识、检索到什么、怎么组织上下文、调用哪个模型、怎么生成最终回复。它不负责“知识长什么样”,那是知识库和问答卡片的事。
打个比方:Runtime 像是餐厅的前厅经理,问答卡片像是后厨备好的半成品菜。前厅经理负责接单、传菜、协调出餐顺序,但菜本身得后厨提前备好。你不能指望前厅经理现场去种菜。
在开源 AI Agent Runtime 里,这个调度逻辑通常体现在几个环节:
- 意图识别:判断用户这句话是在问物流、问退款、还是问产品功能
- 知识检索:根据意图去问答卡片库里捞对应的卡片
- 上下文组装:把捞到的卡片内容拼成模型能理解的 prompt
- 回复生成:让模型基于卡片内容生成自然语言回复
问答卡片的质量,直接决定了第三步和第四步的上限。卡片拆得烂,Runtime 再强也救不回来。
2.2 问答卡片和普通文档的本质区别
普通客服文档是给人读的,问答卡片是给 Runtime 检索和模型消费的。这个区别决定了它们的结构完全不同。
普通文档可以有大段叙述、可以有前后文依赖、可以靠标题层级来组织信息。但问答卡片不行,它必须是自包含的——每一张卡片单独拿出来,都要能完整回答一个问题,不依赖其他卡片。
我见过一个典型的反面案例:某团队把退换货政策拆成了“退货条件”“退货流程”“退款时效”三张卡片,但“退货条件”那张卡片里写的是“满足上述条件的用户可申请退货”,这个“上述条件”在卡片里根本找不到,因为它在上一个文档段落里。Runtime 检索到这张卡片,模型看到“上述条件”四个字直接懵了。
所以问答卡片的第一原则是:一张卡片 = 一个独立问题 + 一个完整答案 + 必要的边界条件。
2.3 卡片粒度怎么定:太粗和太细都是坑
粒度是问答卡片设计里最难拿捏的部分。我踩过的坑基本都在这。
粒度太粗:一张卡片覆盖了退换货的所有情况,答案写了八百字。Runtime 检索到这张卡片,模型要么截断,要么把不相关的部分也塞进回复里。用户问“七天无理由怎么退”,模型回了一大段包括质量问题退货、换货、维修的内容,体验很差。
粒度太细:把“七天无理由退货”拆成“七天从哪天算起”“无理由包括哪些理由”“退货要不要包装”三张卡片。用户问一句“我想退货”,Runtime 检索到三张卡片,模型不知道该用哪张,或者把三张拼在一起,回复变得啰嗦。
我的经验是:以用户的一个完整意图为粒度。用户问“七天无理由怎么退”,这是一个完整意图,对应一张卡片。用户问“退货要多久到账”,这是另一个完整意图,对应另一张卡片。判断标准很简单——如果两个问题用户不会在同一句话里问出来,就拆成两张卡片。
3. 问答卡片的结构设计:字段怎么定
3.1 最小可用字段集
一张能用的问答卡片,至少需要这几个字段:
| 字段名 | 作用 | 是否必填 |
|---|---|---|
| card_id | 卡片唯一标识,用于检索和追踪 | 必填 |
| question | 标准问题,用于意图匹配 | 必填 |
| answer | 标准答案,用于生成回复 | 必填 |
| category | 分类标签,用于粗筛 | 建议填 |
| keywords | 关键词列表,用于召回 | 建议填 |
| conditions | 适用条件/边界,用于精确匹配 | 选填 |
| source | 知识来源,用于追溯 | 选填 |
这个字段集是我试过好几版之后稳定下来的。card_id 用 UUID 或者业务编号都行,关键是唯一。question 不一定要跟用户原话一模一样,但要是用户可能问的标准表述。answer 是核心,后面会专门讲怎么写。
3.2 question 字段怎么写才能被检索到
question 字段的作用是让 Runtime 能把用户输入匹配到这张卡片。匹配方式通常有两种:向量相似度匹配和关键词匹配。开源 Runtime 里两种都很常见,有的还会做混合检索。
如果是向量匹配,question 的表述要尽量覆盖用户可能的问法。比如“七天无理由退货”这张卡片,question 可以写成“七天无理由退货怎么操作”,但用户可能问“我想退货”“买了不想要了能退吗”“七天包退是什么意思”。这时候有两个做法:
一是把 question 写成多个变体的数组,Runtime 检索时取最高相似度。二是把变体放到 keywords 字段里,做关键词召回兜底。
我一般两个都做。question 主字段写最标准的那个问法,keywords 里塞五到十个变体。实测下来,混合检索的召回率比单用向量高不少,尤其是用户用了口语化表达的时候。
3.3 answer 字段的写法:给模型看的,不是给人读的
这是最容易被忽视的一点。answer 字段的内容,最终是要被塞进 prompt 让模型消费的。所以它的写法要符合模型的“阅读习惯”。
第一,结论前置。模型在生成回复时,倾向于优先使用上下文里靠前的内容。所以 answer 的第一句就要给出核心结论。比如“七天无理由退货的时效是签收后七天内,从签收次日零点开始计算。”而不是先铺垫“根据我们的售后政策规定……”
第二,分点但不啰嗦。模型对结构化内容的处理能力比大段文字强。可以用短句分点,但每点不要太长。我一般控制在每点不超过两行。
第三,明确边界。如果这个答案有例外情况,一定要写清楚。比如“定制类商品不支持七天无理由退货”,这句话必须出现在 answer 里,否则模型可能会给出错误承诺。
第四,避免指代。不要出现“如上所述”“参见前文”“该政策”这种指代,每张卡片都要自包含。
3.4 conditions 字段:处理“看情况”的问题
客服场景里大量问题是“看情况”的。退货时效看商品类型,运费承担看退货原因,活动优惠看用户等级。这些“看情况”的部分,如果全塞进 answer 里,卡片会变得很臃肿。
我的做法是单独设一个 conditions 字段,用结构化的方式写清楚适用条件。比如:
{ "conditions": [ {"field": "商品类型", "operator": "in", "value": ["普通商品"], "result": "支持七天无理由"}, {"field": "商品类型", "operator": "in", "value": ["定制商品", "生鲜"], "result": "不支持七天无理由"} ] }Runtime 在检索到这张卡片后,可以结合用户订单信息做二次过滤,把不相关的条件剔除,只把命中的那部分塞进 prompt。这样模型拿到的上下文更干净,回复也更准确。
4. 从原始文档到问答卡片的完整拆解流程
4.1 第一步:文档清洗和去重
原始客服文档通常有几个问题:格式混乱、内容重复、版本不一致。直接拆卡片会把这些毛病带进去。
我一般会先做一轮清洗:
- 统一格式:把 Word、PDF、飞书文档都转成纯文本或 Markdown,去掉页眉页脚、水印、无关的排版符号
- 合并重复:同一个政策在不同文档里出现多次的,合并成一份,以最新版本为准
- 标注来源:每段内容标注它来自哪个文档、哪个版本,方便后续追溯
- 剔除无效内容:内部通知、会议纪要、临时公告这些不属于客服知识的内容,先拿出去
这一步看起来简单,但实际很耗时间。我做过一个项目,原始文档有四十多份,清洗完只剩十二份是真正有用的客服知识。如果不做这一步,后面拆出来的卡片会有一大半是噪音。
4.2 第二步:按用户意图聚类
清洗完之后,不要急着按文档结构拆卡片。文档结构是写文档的人的逻辑,不是用户的逻辑。
正确的做法是按用户意图聚类。具体操作是:把清洗后的内容通读一遍,然后问自己“用户会在什么场景下问这个问题”,把属于同一个场景的内容归到一起。
比如退换货政策,文档里可能分散在“售后服务”“物流说明”“活动规则”三个章节。但用户问“我要退货”的时候,他关心的是:能不能退、怎么退、运费谁出、多久到账。这四个问题应该聚成一个意图簇,然后再拆成四张卡片。
我一般会用一张表格来做这个聚类:
| 意图簇 | 包含的原始内容 | 来源文档 |
|---|---|---|
| 退货申请 | 退货条件、退货入口、申请流程 | 售后服务v2.1 |
| 退货运费 | 运费承担规则、运费险说明 | 售后服务v2.1、活动规则v1.3 |
| 退款时效 | 退款到账时间、支付方式差异 | 物流说明v3.0 |
这张表做完,卡片的骨架就出来了。
4.3 第三步:逐张卡片填充字段
聚类完成后,就是逐张卡片填字段。这个过程我建议用表格工具或者 JSON 文件来做,不要直接在文档里写,方便后续导入 Runtime。
填字段的顺序建议是:先写 question,再写 answer,然后补 keywords 和 conditions,最后填 category 和 source。
写 answer 的时候有个技巧:先写一版给用户看的自然语言答案,再改写成给模型看的结构化答案。第一版帮你理清逻辑,第二版才是最终入库的版本。
4.4 第四步:卡片质量校验
卡片写完不能直接用,必须校验。我一般会做三轮:
第一轮,自包含校验。随机抽十张卡片,单独拿出来看,能不能不依赖其他卡片就回答清楚一个问题。如果有指代、有缺失、有歧义,打回去重写。
第二轮,冲突校验。检查不同卡片之间有没有矛盾。比如一张卡片说“退货包运费”,另一张说“退货运费自理”,这种冲突会让模型精神分裂。发现冲突要回到原始文档确认哪个是最新政策。
第三轮,覆盖度校验。拿真实的用户问题日志,随机抽一百条,看有多少条能被现有卡片覆盖。覆盖率低于百分之八十,说明卡片拆得不够全,要补。
这三轮做完,卡片库才算能上线。
5. 卡片入库 Runtime 后的检索与生成调优
5.1 检索策略:向量加关键词的混合召回
卡片入库之后,Runtime 怎么把它们捞出来,是决定效果的关键。我试过纯向量、纯关键词、混合三种方案,最后稳定用的是混合召回。
纯向量的问题是,用户问“七天无理由”,向量可能匹配到“七天发货”“无理由换货”这些语义相近但意图不同的卡片。纯关键词的问题是,用户换个说法就匹配不到了。
混合召回的做法是:先用关键词做一轮粗筛,把包含核心词的卡片捞出来,再用向量做精排,取相似度最高的前几张。这样既保证了召回率,又保证了准确率。
在开源 Runtime 里,这个逻辑通常可以通过配置检索器的 pipeline 来实现。有的 Runtime 支持自定义检索器,那就更灵活,可以按业务特点调权重。
5.2 上下文组装:给模型喂几张卡片合适
检索到卡片之后,塞几张进 prompt,也是个需要调的参数。
塞太少,模型信息不够,回答不完整。塞太多,模型注意力被分散,容易把不相关的卡片内容也编进回复里。
我的经验值是三到五张。具体看卡片的内容长度,如果每张卡片答案都很短,可以塞五张;如果卡片答案比较长,塞三张就够了。
另外,塞进去的顺序也有讲究。相似度最高的卡片放最前面,因为模型对靠前的内容更敏感。如果卡片之间有逻辑顺序,比如“退货条件”在“退货流程”前面,那就按逻辑顺序排。
5.3 生成约束:怎么让模型别乱编
即使卡片内容是对的,模型也可能生成出卡片里没有的信息。这在客服场景里是致命的,因为可能给出错误承诺。
我一般会在 prompt 里加几条硬约束:
- 只使用提供的卡片内容回答,不要添加卡片之外的信息
- 如果卡片内容不足以回答问题,明确告知用户“这个问题我需要帮您转接人工”
- 不要对卡片内容做推断和延伸
这几条约束写进 system prompt 里,能挡掉大部分幻觉。但也不是万能的,模型有时候还是会“自作聪明”。所以上线前一定要做一轮对抗测试,专门问一些卡片里没有的问题,看模型会不会乱答。
5.4 效果评估:怎么知道卡片拆得好不好
卡片拆得好不好,不能靠感觉,要有指标。
我一般看四个指标:
| 指标 | 含义 | 目标值 |
|---|---|---|
| 召回率 | 用户问题能检索到相关卡片的比例 | 大于85% |
| 准确率 | 检索到的卡片确实相关的比例 | 大于90% |
| 回答采纳率 | 模型生成的回复被客服直接采用的比例 | 大于70% |
| 转人工率 | 模型无法回答转人工的比例 | 小于15% |
这四个指标里,召回率和准确率反映卡片库的质量,回答采纳率和转人工率反映整体效果。如果召回率低,说明卡片不够全或者 question 写得不好;如果准确率高但采纳率低,说明 answer 写得不好,模型没法直接用。
6. 实操中踩过的坑和排查技巧
6.1 卡片答案太长导致模型截断
这是最常见的坑。一张卡片的 answer 写了五百字,Runtime 检索到三张,prompt 直接超长,模型要么截断要么忽略后面的内容。
排查方法:统计所有卡片 answer 的长度分布,超过两百字的卡片重点检查,看能不能拆成两张。
解决思路:answer 控制在两百字以内,超出的部分要么拆卡片,要么放到 conditions 里做条件分支。
6.2 同义问题匹配不到
用户问“我不想买了能退吗”,卡片里写的是“七天无理由退货”,向量相似度不够,匹配不到。
排查方法:拿用户问题日志跑一遍检索,把没匹配到的问题捞出来,人工看应该匹配哪张卡片。
解决思路:把这些问法加到对应卡片的 keywords 里。如果量很大,可以考虑用模型做一轮问法扩展,自动生成变体。
6.3 多张卡片内容冲突
两张卡片对同一个问题给出了不同答案,模型随机选一个,回复不稳定。
排查方法:用聚类算法把语义相近的卡片找出来,人工检查有没有冲突。
解决思路:回到原始文档确认最新政策,把旧卡片下线或者标注失效。
6.4 模型忽略卡片内容自己编
卡片里明明写了“不支持退货”,模型回复“可以为您办理退货”。
排查方法:做对抗测试,专门问卡片里有明确否定答案的问题,看模型是否遵守。
解决思路:加强 prompt 约束,把关键结论用更醒目的方式标注,比如加粗或者用特殊符号包裹。如果还是不行,考虑在生成后加一层校验,检查回复里有没有出现卡片里没有的关键承诺。
6.5 卡片更新后 Runtime 没生效
改了卡片内容,但 Runtime 检索到的还是旧内容。
排查方法:检查 Runtime 的缓存机制和索引更新策略。
解决思路:大部分开源 Runtime 都有索引重建的接口,卡片更新后要触发重建。有的还支持增量更新,但增量更新有时候会有延迟,重要更新建议直接全量重建。
7. 一个完整的卡片示例和它的设计思路
说了这么多理论,来看一张实际的卡片长什么样。
{ "card_id": "return_007", "question": "七天无理由退货的时效怎么算", "answer": "七天无理由退货的时效从签收次日零点开始计算,共七天。例如1月1日签收,1月2日零点开始计算,1月8日二十四点截止。超过时效后无法申请七天无理由退货,但质量问题退货不受此限制。", "keywords": ["七天无理由", "退货时效", "退货时间", "几天内退货", "签收后多久"], "conditions": [ {"field": "商品类型", "operator": "in", "value": ["普通商品"], "result": "适用"}, {"field": "商品类型", "operator": "in", "value": ["定制商品", "生鲜", "虚拟商品"], "result": "不适用"} ], "category": "售后服务-退货", "source": "售后服务政策v2.1-第三章" }这张卡片的设计思路是这样的:
question 写的是最标准的问法,但 keywords 里塞了五个变体,覆盖了用户可能的口语化表达。answer 第一句直接给结论,第二句用例子说明,第三句补充例外情况。conditions 把商品类型的差异单独拎出来,Runtime 可以结合订单信息做过滤。source 标注了来源,方便后续追溯和更新。
这张卡片 answer 长度控制在一百字出头,塞三张进 prompt 也不会超长。conditions 结构化之后,Runtime 可以做精确匹配,不会把定制商品的政策错误地用在普通商品上。
8. 卡片库的长期维护:别让它烂掉
卡片库不是建完就完事的,它会随着业务变化而腐烂。政策改了、活动结束了、产品下架了,对应的卡片如果不同步更新,就会变成错误知识的来源。
我的做法是建立一套维护机制:
定期审计:每个月抽一天,把卡片库过一遍,检查有没有过期内容。重点看活动相关的卡片,活动结束就要下线。
变更联动:业务侧的政策文档更新时,要同步触发卡片更新。这个最好做成流程,而不是靠人记得。
效果监控:持续监控前面说的四个指标,指标下滑就说明卡片库有问题,要及时排查。
用户反馈闭环:客服在使用过程中发现卡片有问题,要有一个方便的反馈入口,反馈要能直接触达卡片维护人。
这套机制跑起来之后,卡片库的维护成本会低很多。最怕的是建完就不管,半年后回头看,一半卡片都是错的。
9. 关于开源 Runtime 选型的一点个人看法
最后聊几句 Runtime 选型。现在开源 AI Agent Runtime 的选择不少,有偏重工作流编排的,有偏重对话管理的,有偏重知识检索的。
我的建议是,如果你的核心场景是客服问答,优先选知识检索能力强、支持自定义检索器、支持结构化知识入库的 Runtime。工作流编排能力反而是次要的,因为客服问答的流程相对固定,不需要太复杂的编排。
另外要看 Runtime 的卡片管理能力。有的 Runtime 自带知识库管理界面,可以直接导入结构化卡片;有的需要自己写接口对接。如果卡片量大,自带管理界面的会省很多事。
还有一点是社区活跃度。开源项目最怕的是停更,遇到问题没人管。选之前看看最近的提交记录和 issue 响应速度,这个比功能列表更能反映项目的真实状态。
我在实际使用中的体会是,Runtime 本身的能力差距其实没有想象中那么大,真正拉开效果差距的是卡片库的质量。同样的 Runtime,卡片拆得好,效果能到八十分;卡片拆得烂,效果可能只有四十分。所以与其花时间纠结选哪个 Runtime,不如先把卡片拆好。