一、概述:先理解本质
1.1需求一句话
用公司自研的类 Coze 平台,搭一个微信小程序问答机器人:30 条 QA 资料,只输出匹配到的答案原文(大模型不参与、不润色),没匹配到就返回固定话术。
1.2核心判断(第一性原理)
这是检索/匹配问题,不是生成问题。
- 30 条 QA 的规模,根本不需要大模型"理解并回答"
- 最优解:让平台节点链路只做"捞答案",大模型全程不参与
- 好处:机制上杜绝润色、编造、答非所问;行为完全可预期、可复现、可排查
1.3三条硬约束
| 约束 | 含义 |
|---|---|
| 只输出匹配 answer | 答案逐字一致,不允许大模型改写 |
| 无匹配 → 固定话术 | 「抱歉,暂时没有找到您问题的答案,请咨询客服」 |
| 暂不脱离平台 | 刚接手求稳,所有实现都在平台节点内完成 |
1.4平台能力前提(已确认)
- ✅ 代码节点
- ✅ 条件分支
- ✅ 知识库节点可检索后直接输出命中原文(可完全绕开大模型)
二、方案总览:双路径架构
2.1两条路径
| 路径 | 触发方式 | 实现机制 | 精确度 |
|---|---|---|---|
| 路径一:点选(主) | 用户点选问题卡片 | 代码节点字典查表 {question_id → answer} |
100% 精确 |
| 路径二:自由输入(兜底) | 用户输入文字 | 知识库语义检索 top1 + 相似度阈值 | 阈值内可靠 |
2.2为什么点选和自由输入分开走
- 点选是"确定性问题":用户点的问题就是标准问题,直接用字典映射,不需要任何语义理解
- 自由输入是"不确定问题":用户说法千变万化,需要语义检索 + 阈值兜底
- 分开走:点选路径零风险、零延迟;自由输入路径可独立调优,互不影响
2.3全流程时序
三、平台节点配置步骤(照做)
以下节点名称为通用叫法,实际以你平台为准(类 Coze 平台大同小异)。
0
准备
- 拿到 30 条 QA 的最终版(问题 + 答案,逐字确认)
- 给每条 QA 编唯一 ID:
Q01~Q30 - 确认平台是否支持"知识库条目结构化字段"(question/answer 字段)
1
建知识库
| 配置项 | 值 | 说明 |
|---|---|---|
| 条目数 | 30 | 一条 QA = 一个条目,不切片 |
| 条目结构 | {Q: 标准问题, A: 答案原文} | 向量化只对 Q 字段做 |
| 匹配方向 | 只匹配 Q | A 是负载,不参与匹配(防串味) |
| 标签 | 不加 | 30 条量级不需要预筛 |
| 相似度输出 | 开启返回 score | 条件分支需要用到 |
平台不支持结构化字段时:条目里只存 Q 做检索,A 通过"引用字段/关联字段"或代码节点映射取出。
2
搭节点链路
开始节点(接收 question_id + free_text)
└─ 条件分支 1:question_id 非空?
├─ 是 → 代码节点 A:字典查表 {Q01: answer1, ...}
│ └─ 输出 answer 原文(防御:查不到 → 固定话术)
└─ 否(自由输入)→ 知识库节点:语义检索 top1
└─ 条件分支 2:score ≥ 0.75?
├─ 是 → 输出命中条目的 A 原文
└─ 否 → 输出固定话术
3
配置代码节点 A(点选路径)
- 维护 30 条映射:
{"Q01": "答案1原文", "Q02": "答案2原文", ...} - 输入:
question_id - 输出:
answer(对应答案原文);未命中返回固定话术(防御分支) - 答案文本必须与知识库 A 字段逐字一致(同源校验)
4
配置阈值分支
- 阈值建议 0.75 起步(见第五节调优)
- score ≥ 阈值 → 输出命中条目 A 原文(只取 top1,禁止拼接多条)
- score < 阈值 → 固定话术
5
固定话术节点
- 文案:「抱歉,暂时没有找到您问题的答案,请咨询客服」
- 实现:条件分支"未命中"分支直接输出该常量(或代码节点返回)
6
大模型节点
- 全程不使用
- 若平台强制要求必经大模型节点:配置为"透传"——指令为空/原样输出上游输入,
temperature=0
7
发布 API 供小程序调用
- 发布为 API(平台一般自动生成接口)
- 小程序侧:点选问题卡片 → 传
question_id;输入框提交 → 传free_text - 联调确认:鉴权、超时、返回结构(
{answer: "..."})
四、知识库设计详解
4.1匹配 Q 不匹配 A(最重要的一条)
检索只对 Q(问题文本)做语义匹配,A(答案)是"负载",随命中条目输出,不参与匹配。
为什么?A 里的词会和用户问法撞车,造成错误命中:
例:Q₁="如何申请退款?" A₁="退款需联系客服,电话 400-xxx"
用户问"客服电话是多少" → 若 A 参与匹配,A₁ 里"客服/电话"高分命中 → 答非所问
只匹配 Q 就杜绝这类串味
4.2不切片
- 一条 QA = 一个条目,30 条 = 30 个条目
- 切片只适用于长文档(说明文档、规章制度);QA 答案是一段完整的话,切片会切断答案完整性
4.3不加标签
- 标签预筛适合几百上千条的场景;30 条直接全量 top1 匹配足够准,加标签是纯维护负担
4.4同义问法扩写策略(迭代路径)
| 阶段 | 做法 |
|---|---|
| 第一版(现在) | 裸跑,不扩写。没有历史问法数据,盲扩会引入噪音 |
| 上线 1-2 周后 | 采集"未命中 query"日志(平台对话日志/调试记录),用真实未命中问法做第一轮扩写 |
| 扩写方式 | 每条 Q 挂 2-3 个别名,30 条 → ~100 检索条目;答案不变 |
| 机制 | 扩写后检索机制不变(top1 + 阈值),只是候选空间更贴近真实问法 |
为什么不能凭空编别名:扩写 Q 的目的是"覆盖用户真实会问的话",不是"让条目变多"。凭空写的别名用户大概率不这么说,浪费 embedding 空间还引入噪音。
五、阈值调优(上线前必做)
5.1测试集设计
| 类型 | 数量 | 内容 | 期望 |
|---|---|---|---|
| 正样本 | 30+ | 每条标准 Q + 1-2 个合理改写问法("退款怎么弄""如何申请退款") | 命中对应 QA |
| 负样本 | 10-15 | 业务无关输入("今天天气""你们老板是谁") | 返回固定话术 |
5.2阈值扫描
- 用测试集扫 0.70 / 0.75 / 0.80 / 0.85
- 选:误命中为 0 且命中率最高的阈值
- 注意:阈值越高越保守(漏命中多但不会答错);越低越激进(命中多但可能答错)——按"宁可答不上来也不能答错"的原则选
5.3上线后持续验证
- 每周复盘一次"未命中 query":哪些其实有答案 → 扩写 Q;哪些确实没有 → 保持固定话术
- 关注误命中:用户收到错误答案比收到固定话术伤害更大
六、验收清单(照此逐条测)
| # | 用例 | 期望结果 |
|---|---|---|
| T1 | 点选问题 1 | 返回问题 1 的答案原文,逐字一致 |
| T2 | 点选问题 30 | 返回问题 30 的答案原文 |
| T3 | 自由输入与某 Q 高度相似 | 返回该 Q 对应 A 原文 |
| T4 | 自由输入与所有 Q 都不相似 | 返回固定话术 |
| T5 | 任意输入 | 返回内容不含润色痕迹(与 A 原文或固定话术逐字一致) |
| T6 | 知识库 A 字段 vs 点选路径 answer | 同源一致 |
| T7 | 输入含"客服/电话"等 A 中常见词但语义不对应 | 不误命中含该词答案的 QA |
| T8 | 多结果检索场景 | 只输出 top1,不拼接 |
| T9 | 点选传无效 question_id(防御) | 返回固定话术 |
七、常见坑(对抗性预演)
| 坑 | 后果 | 规避 |
|---|---|---|
| A 参与语义匹配 | 答非所问(客服/电话串味) | 知识库只匹配 Q |
| 答案被大模型润色 | 违反硬需求 | 全程不用大模型节点;必须用时透传 |
| 多结果拼接输出 | 答案混乱 | 只取 top1 |
| 阈值太低 | 误命中,答错问题 | 0.75 起步,测试集调优 |
| 盲目扩写别名 | 检索噪音 | 只用真实未命中问法扩写 |
| 点选路径与知识库答案不一致 | 同问题两个答案 | 同源维护,验收 T6 |
八、后续迭代路线
- 第 1 周:裸跑上线,验证点选路径 100% 精确、自由输入路径阈值表现
- 第 2-3 周:采集未命中日志,第一轮扩写(真实问法)
- 第 4 周起:每周复盘命中率,持续扩写 + 阈值微调
- 规模变大时(>100 条 QA):考虑加标签分类预筛、多轮意图分支
- 脱离平台的远期:迁移到自建服务(30 条 QA 用代码直接做精确匹配 + 向量检索,逻辑完全一致,只是节点换成代码)