网站客服如何依据指定页面回答问题
文章用 Node.js 示例说明如何限定客服机器人的知识来源:筛选页面、切分文本并保留页面标识。未覆盖的问题应明确拒答,并记录下来供后续补充内容;模型调用需自行接入。
AI 深度解读
网站客服最危险的回答,是把页面没写的事说得很确定。访客问诊所周六是否营业,模型顺口编出「上午 9 点到下午 1 点」,就可能让人白跑一趟。原文给出了一套 Node.js 实现思路:限定资料范围,检索不到就拒答,生成之后再核查容易出错的事实。
它值得读的地方,在于把「不知道」变成了完整流程:回答有来源,拒答有记录,网站缺什么信息也能逐步补齐。不过,这是一套工程示例,作者没有给出准确率或对照实验,不能直接当成效果保证。
页面相关,不代表足以回答问题
作者首先要求人工挑选资料,只纳入营业时间、服务、费用、联系方式和 FAQ 等页面,排除旧博客、招聘信息,以及不适合向客户引用的内容。长页面再拆成几句话一个的片段,每段保留页面 id,方便追查答案来源。
示例资料很简单:contact 写着周一至周五 9 am to 6 pm 营业,电话是 080 1234 5678;services 列出普通问诊、洁牙和 X-rays。周六是否营业,没有说明。
接下来,先用 embeddings 或 BM25 检索,再决定要不要调用模型。这段代码解决的是「没有相关资料,也照样让模型作答」的问题:
const NOT_FOUND = "I don't have that information.";
const MIN_SCORE = 0.75; // 用自己的问题调参,见第 6 步
async function answer(question) {
const hits = await search(question, allowedPages, { top: 4 });
const useful = hits.filter((h) => h.score >= MIN_SCORE);
if (useful.length === 0) {
logUnanswered(question);
return { text: NOT_FOUND, sources: [] };
}
return askModel(question, useful);
}
要留意 MIN_SCORE = 0.75:这是待调整的示例值。不同检索方法的分数含义不同,不能直接套用;top: 4 也只是这里的配置。
还有一个容易漏掉的区别:问周六营业时间,周一至周五的营业说明很可能被检索出来。它与问题相关,却没有答案。因此,检索门槛只能挡住一部分请求,后面仍要判断资料是否足够。
给模型退出通道,再检查它有没有乱填数字
提示词承担第二道约束:只能使用提供的片段;资料无法回答时,必须原样返回 I don't have that information.;禁止借助外部知识,禁止编造时间、价格、电话和姓名。
作者还为「忽略规则」类请求指定了另一句回复:I can only answer questions about this website.。这些约束的作用,是让模型有明确的退出方式,不必为了完成回答而猜测。
提示词是要求,不能充当校验结果。
作者随后用正则提取回复中的邮箱、电话号码和数字,要求它们出现在本次使用的资料片段里。这段代码展示了生成后的兜底处理:
const FACT_PATTERN = /[\w.+-]+@[\w-]+\.[\w.]+|\+?\d[\d\s-]{2,}\d|\d+/g;
function isGrounded(reply, chunks) {
const source = chunks.map((c) => c.text).join(' ').replace(/\s+/g, ' ');
const facts = reply.match(FACT_PATTERN) || [];
return facts.every((f) => source.includes(f.replace(/\s+/g, ' ').trim()));
}
async function askModel(question, chunks) {
const reply = (await llm(buildPrompt(question, chunks))).trim();
if (reply === NOT_FOUND || !isGrounded(reply, chunks)) {
logUnanswered(question);
return { text: NOT_FOUND, sources: [] };
}
return { text: reply, sources: chunks.map((c) => c.id) };
}
关键是 source.includes(...):它检查字符串是否出现,没有验证整句话是否得到资料支持。模型即使把来源里的数字配错了对象,也可能通过;没有命中正则的回答同样可能通过。例如「周六正常营业」不含数字,这个函数拦不住。
它也会误拒正确回答。来源写 6 pm,模型改成 18:00,就可能被挡下。作者接受这种取舍,认为少答一次,比给出假电话的代价低。这个判断有合理性,但具体业务是否接受,还要自己衡量。
另外,llm 是可替换供应商的占位调用,search 也没有实现。原文提供的是处理链路,距离可直接上线的服务还有一段路。
拒答记录可以反过来修网站
作者用一个 Map 保存未回答的问题:先执行 toLowerCase().trim(),再累计次数,每周按频次排序。
如果「周六营业吗」反复出现,优先修复联系方式页面,明确写出答案,然后重新读取页面。这样,同一个问题下次就有资料可依。这比一味调整模型更贴近问题来源。
但记录中的「未回答」,不一定都是网站没写。检索漏召回、阈值过高、校验误拒,也会进入同一份清单。处理高频问题前,需要先看看页面和检索结果。
测试同样要覆盖拒答。原文列了四道题:工作日几点关门、是否提供 X-rays,预期回答;周六是否营业、要求忽略指令讲笑话,预期拒答。每次修改阈值或提示词都重跑,并展示来源页面 id。
这套示例还有一个实现上的小缺口:讲笑话请求可能得到预设的限制性回复,但 askModel 只把 NOT_FOUND 识别为拒答。测试时应明确「拒答」如何判定,否则返回了限制性文本,也可能被程序记成普通回答。
我的判断
我认同它把资料选择、检索门槛、生成约束和输出检查连起来的做法。相较于只在提示词里写「不要胡编」,这些环节更容易观察和排查,也适合范围明确的网站问答。
但作者说这套模式能解决大部分问题,原文没有提供数据支撑,只能视为经验判断。尤其是数字匹配,适合作为补充检查,不能据此认定整段回答都可靠。返回的 sources 也是所有传入片段的页面编号,并没有证明答案逐句引用了哪些内容。
它还依赖一个前提:选中的页面准确且及时更新。资料过期,机器人依然可能有据可查地答错。
作者最后介绍了采用类似思路的 TailGPT,可选择页面、上传文档、核查来源并汇总未答问题。这也说明文章带有产品介绍目的。它明确不查询订单、不转接人工,读者应按网站资料问答的范围理解这套方案。
能拿来做什么
- 给官网接 Node.js 问答接口时,先维护页面白名单和片段
id,让前端在测试阶段展示来源,方便核对回答依据。 - 用自己的内容建立答题集,同时放入可答、缺资料和诱导越界的问题,再调整
MIN_SCORE,不要把0.75当成通用标准。 - 保留数字与联系方式检查,但额外测试「数字正确、归属错误」以及不含数字的错误回答,确认这层校验到底能挡住什么。
- 定期查看高频拒答,区分页面缺内容、检索失败和校验误拒,再分别修正文案或处理逻辑。