一、概述:先理解本质

1.1需求一句话

用公司自研的类 Coze 平台,搭一个微信小程序问答机器人:30 条 QA 资料,只输出匹配到的答案原文(大模型不参与、不润色),没匹配到就返回固定话术。

1.2核心判断(第一性原理)

这是检索/匹配问题,不是生成问题。

1.3三条硬约束

约束含义
只输出匹配 answer答案逐字一致,不允许大模型改写
无匹配 → 固定话术「抱歉,暂时没有找到您问题的答案,请咨询客服」
暂不脱离平台刚接手求稳,所有实现都在平台节点内完成

1.4平台能力前提(已确认)

二、方案总览:双路径架构

微信小程序问答机器人 — 双路径流程(方案 A)用户进入小程序问答页提问方式?列表点选自由输入携带 question_id条件分支 1:question_id 非空 → 代码节点 A代码节点 A:字典查表 {qid → answer}命中?否 · 防御输出 answer 原文输出固定话术携带 free_text知识库节点:语义检索 top1(只匹配 Q 字段,A 不参与匹配)条件分支 3:score ≥ 0.75?相似度 ≥ 阈值?输出命中条目 A 原文输出固定话术「抱歉,暂时没有找到您问题的答案,请咨询客服」

2.1两条路径

路径触发方式实现机制精确度
路径一:点选(主) 用户点选问题卡片 代码节点字典查表 {question_id → answer} 100% 精确
路径二:自由输入(兜底) 用户输入文字 知识库语义检索 top1 + 相似度阈值 阈值内可靠

2.2为什么点选和自由输入分开走

2.3全流程时序

微信小程序问答机器人 — 时序图(方案 A)用户用户微信小程序微信小程序类Coze平台类Coze平台知识库知识库代码节点代码节点路径一:列表点选(主路径,100% 精确)点选问题卡片POST 问答接口 { question_id: "Q03" }调用代码节点 A(字典查表)answer 原文{ answer: "……原文……" }展示答案路径二:自由输入(兜底路径,语义检索 + 阈值)输入文字「怎么申请退款?」POST 问答接口 { free_text: "怎么申请退款?" }语义检索(只匹配 Q 字段,top1){ q: "如何申请退款", a: "……原文……", score: 0.82 }条件分支:score ≥ 0.75?{ answer: "……原文……" } (命中)展示答案路径三:未命中 → 固定话术输入文字「今天天气如何?」POST 问答接口 { free_text: "今天天气如何?" }语义检索{ score: 0.41 } (低于阈值)条件分支:score < 0.75 → 固定话术{ answer: "抱歉,暂时没有找到您问题的答案,请咨询客服" }展示固定话术

三、平台节点配置步骤(照做)

以下节点名称为通用叫法,实际以你平台为准(类 Coze 平台大同小异)。

0

准备

  • 拿到 30 条 QA 的最终版(问题 + 答案,逐字确认)
  • 给每条 QA 编唯一 ID:Q01 ~ Q30
  • 确认平台是否支持"知识库条目结构化字段"(question/answer 字段)
1

建知识库

配置项说明
条目数30一条 QA = 一个条目,不切片
条目结构{Q: 标准问题, A: 答案原文}向量化只对 Q 字段做
匹配方向只匹配 QA 是负载,不参与匹配(防串味)
标签不加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不切片

4.3不加标签

4.4同义问法扩写策略(迭代路径)

阶段做法
第一版(现在)裸跑,不扩写。没有历史问法数据,盲扩会引入噪音
上线 1-2 周后采集"未命中 query"日志(平台对话日志/调试记录),用真实未命中问法做第一轮扩写
扩写方式每条 Q 挂 2-3 个别名,30 条 → ~100 检索条目;答案不变
机制扩写后检索机制不变(top1 + 阈值),只是候选空间更贴近真实问法

为什么不能凭空编别名:扩写 Q 的目的是"覆盖用户真实会问的话",不是"让条目变多"。凭空写的别名用户大概率不这么说,浪费 embedding 空间还引入噪音。

五、阈值调优(上线前必做)

5.1测试集设计

类型数量内容期望
正样本 30+ 每条标准 Q + 1-2 个合理改写问法("退款怎么弄""如何申请退款") 命中对应 QA
负样本 10-15 业务无关输入("今天天气""你们老板是谁") 返回固定话术

5.2阈值扫描

5.3上线后持续验证

六、验收清单(照此逐条测)

#用例期望结果
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. 第 1 周:裸跑上线,验证点选路径 100% 精确、自由输入路径阈值表现
  2. 第 2-3 周:采集未命中日志,第一轮扩写(真实问法)
  3. 第 4 周起:每周复盘命中率,持续扩写 + 阈值微调
  4. 规模变大时(>100 条 QA):考虑加标签分类预筛、多轮意图分支
  5. 脱离平台的远期:迁移到自建服务(30 条 QA 用代码直接做精确匹配 + 向量检索,逻辑完全一致,只是节点换成代码)