作者:前端转 AI 深度实践者
【省流助手/核心观点】 :RAG 上线后,点赞点踩只是反馈入口,不是反馈闭环。真正可优化的反馈,至少要带上
traceId、question、answer、citations、retrieval、reason这 6 类信息。前端开发者做知识库问答,不只是渲染模型答案,还要帮系统留下可复盘、可归因、可回流评测集的质量数据。
第 21 篇文章我们讲了 RAG 评测集:上线前用固定问题挡住明显回归。
但真实产品上线后,还会出现另一类问题:
评测集没覆盖,用户真的问到了。
用户的问题更口语化、更跳跃,也更贴近业务现场。
这时只靠离线评测不够,你还需要线上反馈。
很多团队会在答案下面放两个按钮:
text
有帮助 没帮助
这当然比什么都不收集好。
但如果你只存了一个 like 或 dislike,后面依然很难优化。
因为你不知道:
- 用户为什么点踩?
- 当时命中了哪些资料?
- 引用来源是否正确?
- 模型是合理拒答了,还是给出了无依据回答?
- 这个问题应该改 Prompt,还是该补文档?
这篇文章讲的不是"怎么加一个反馈按钮",而是 RAG 反馈闭环:从用户点踩,到问题归因,再到修复和回流评测集。
1. 痛点:点踩不是答案,只是排查入口
用户点了"没帮助",只说明一个结果:
这次回答没有满足用户。
但它没有告诉你原因。
一次 RAG 答错,可能来自很多层:
- 查询理解失败:用户问法太口语,查询改写没处理好。
- 文档切块失败:关键上下文被切断。
- 检索失败:正确资料没有被召回。
- Rerank 失败:正确资料被排到后面。
- 生成失败:模型看到了资料,但理解错了。
- 引用失败:来源看起来有,但和答案不对应。
- 文档失败:资料本身过期或缺失。
如果你的反馈数据只有这一条:
json
{ "messageId": "m_001", "feedback": "bad" }
它几乎无法指导 RAG 优化。
你最多知道"有人不满意",但不知道该修检索、排序、生成、引用,还是文档。
2. 错误做法:只存点赞点踩,不存上下文
很多前端同学第一次做反馈,会这样写:
ts
async function submitFeedback(messageId: string, liked: boolean) {
await fetch("/api/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messageId,
liked
})
});
}
这段代码能完成"收集反馈",但数据太薄。
等你想复盘时,会发现它缺少 5 个关键上下文:
- 用户当时问了什么。
- 系统当时答了什么。
- 当时引用了哪些 chunk。
- 检索和 Rerank 分数是多少。
- 用户觉得哪里不对。
最后这类数据通常会变成一个只能汇报、不能修问题的数字:
text
满意率:76%
满意率能看趋势,但它不能告诉你下一步该改哪里。
3. 正确做法:把反馈事件设计成可复盘的数据结构
更有用的反馈,不应该只记录"喜欢/不喜欢",而要记录这次 RAG 回答的质量上下文。
ts
type FeedbackReason =
| "wrong_answer"
| "missing_answer"
| "bad_citation"
| "outdated_doc"
| "not_relevant"
| "too_vague";
type RagFeedbackEvent = {
traceId: string;
messageId: string;
question: string;
answer: string;
feedback: "helpful" | "unhelpful";
reason?: FeedbackReason;
comment?: string;
confidence: "high" | "medium" | "low";
citations: Array<{
chunkId: string;
title: string;
quote: string;
}>;
retrieval: Array<{
chunkId: string;
score: number;
rerankScore?: number;
}>;
createdAt: string;
};
这里最关键的是 6 类字段:
traceId:把前端反馈、后端日志、模型调用和检索记录串起来。question:复盘用户真实问法。answer:复盘系统当时给出的答案。citations:判断引用来源是否支撑答案。retrieval:判断问题出在召回、排序,还是生成。reason:让用户低成本告诉你"不对在哪里"。
提交反馈时,前端应该把这些上下文一起带上:
ts
async function submitRagFeedback(event: RagFeedbackEvent) {
await fetch("/api/rag-feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(event)
});
}
这样一条差评,才有机会变成后续优化线索。
4. 前端交互:不要只问"有用吗",还要问"哪里不对"
用户点了"没帮助"以后,不要直接结束。
给一个轻量的原因选择,反馈质量会高很多。
ts
type RagMessage = {
id: string;
question: string;
answer: string;
confidence: "high" | "medium" | "low";
citations: RagFeedbackEvent["citations"];
retrieval: RagFeedbackEvent["retrieval"];
};
const feedbackReasons = [
{ value: "wrong_answer", label: "答案不对" },
{ value: "missing_answer", label: "没有回答到重点" },
{ value: "bad_citation", label: "引用来源不对" },
{ value: "outdated_doc", label: "资料可能过期" },
{ value: "too_vague", label: "回答太笼统" }
] satisfies Array<{ value: FeedbackReason; label: string }>;
页面组件可以这样组织:
tsx
function RagFeedbackPanel({
traceId,
message,
onSubmit
}: {
traceId: string;
message: RagMessage;
onSubmit: (event: RagFeedbackEvent) => Promise<void>;
}) {
function buildBaseEvent() {
return {
traceId,
messageId: message.id,
question: message.question,
answer: message.answer,
confidence: message.confidence,
citations: message.citations,
retrieval: message.retrieval,
createdAt: new Date().toISOString()
};
}
return (
<div>
<button
type="button"
onClick={() =>
onSubmit({
...buildBaseEvent(),
feedback: "helpful"
})
}
>
有帮助
</button>
{feedbackReasons.map((item) => (
<button
key={item.value}
type="button"
onClick={() =>
onSubmit({
...buildBaseEvent(),
feedback: "unhelpful",
reason: item.value
})
}
>
{item.label}
</button>
))}
</div>
);
}
这个交互有两个好处:
- 用户只需要多点一下,不会被长表单劝退。
- 开发者能快速判断这条反馈更像哪一类问题。
注意,不要让反馈表单变得太重。用户不是测试工程师,他愿意告诉你"不对"已经很宝贵了。
5. 反馈收上来以后,要做问题归因
收集反馈只是第一步。
如果所有差评都堆在数据库里,系统不会自动变好。
你需要把反馈转成可处理的质量 issue。
ts
type RagIssueType =
| "retrieval_miss"
| "rerank_error"
| "generation_error"
| "citation_error"
| "document_gap"
| "document_outdated"
| "unclear_question";
type RagQualityIssue = {
id: string;
feedbackId: string;
traceId: string;
question: string;
issueType: RagIssueType;
priority: "p0" | "p1" | "p2";
expectedChunkIds?: string[];
note?: string;
};
一个简单的初筛规则可以这样写:
ts
function classifyFeedback(event: RagFeedbackEvent): RagIssueType {
if (event.reason === "bad_citation") {
return "citation_error";
}
if (event.reason === "outdated_doc") {
return "document_outdated";
}
if (event.reason === "not_relevant") {
return "retrieval_miss";
}
const topRetrieval = Math.max(
...event.retrieval.map((item) => item.rerankScore ?? item.score),
0
);
if (topRetrieval < 0.45) {
return "retrieval_miss";
}
if (event.citations.length === 0 && event.confidence !== "low") {
return "citation_error";
}
if (event.reason === "missing_answer") {
return "document_gap";
}
return "generation_error";
}
这套规则不需要一开始就非常准确。
它的价值是把"用户说不好用"拆成可处理的工程问题。
6. 把高价值差评回流到评测集
第 21 篇讲过,RAG 评测集是上线前防回归的工具。
而线上反馈,是评测集最重要的来源之一。
一个反馈能不能变成评测用例,可以用这几个条件判断:
- 问题高频,或者来自关键业务流程。
- 答案错误有明确业务风险。
- 能找到正确文档,或者能确认文档缺失。
- 不是纯主观偏好,比如"语气不够热情"。
可以把差评转成这样的 regression case:
ts
type RagEvalCase = {
id: string;
question: string;
expectedAnswer: string;
expectedChunkIds: string[];
shouldRefuse: boolean;
tags: string[];
};
function feedbackToEvalCase(
issue: RagQualityIssue,
expectedAnswer: string,
expectedChunkIds: string[]
): RagEvalCase {
return {
id: `regression-${issue.id}`,
question: issue.question,
expectedAnswer,
expectedChunkIds,
shouldRefuse: issue.issueType === "document_gap",
tags: ["regression", issue.issueType]
};
}
这样一次线上差评,就不只是"修掉一个 bug",而是变成后续每次上线都会跑到的保护网。
这才是完整的反馈闭环:
text
用户反馈 -> 问题归因 -> 修复链路 -> 加入评测集 -> 发布前回归
7. 生产环境避坑指南
1. 不要让用户写长反馈
反馈入口越复杂,填写率越低。
建议默认提供 4 到 6 个原因选项,再加一个可选补充框。
对大多数用户来说,点一下"引用不对"比写一段话容易得多。
2. 反馈必须绑定 traceId
没有 traceId 的反馈,很难和后端日志、检索结果、模型调用记录串起来。
前端拿到 RAG 答案时,就应该保存这次请求的 traceId:
ts
type RagResponse = {
traceId: string;
message: RagMessage;
};
后面用户点踩,必须把同一个 traceId 带回去。
3. 不要把满意率当唯一指标
满意率能看趋势,但不能指导修复。
更建议拆成这些指标:
- 点踩率
- 低置信回答占比
- 无引用回答占比
- bad citation 占比
- document_gap 占比
- regression case 增长数量
这些指标更接近 RAG 优化的真实动作。
4. 差评不一定都是模型问题
很多差评其实是文档问题。
比如资料过期、政策没写、字段描述不完整。
这类问题靠改 Prompt 解决不了,应该回到知识库维护流程。
5. 反馈数据要做隐私处理
用户问题里可能包含手机号、订单号、姓名、内部项目名。
记录反馈前,至少要考虑脱敏和访问权限。
ts
function maskSensitiveText(text: string) {
return text
.replace(/\b1[3-9]\d{9}\b/g, "[PHONE]")
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}\b/gi, "[EMAIL]");
}
RAG 反馈数据是质量资产,但也可能是敏感数据。不要为了排查方便,把所有原始内容无限期暴露给所有人。
8. 常见误区
误区 1:有点赞点踩就算有反馈闭环
不算。点赞点踩只是反馈入口。
没有上下文、没有归因、没有回流评测集,就很难推动系统变好。
误区 2:点踩多说明模型不行
不一定。可能是文档缺失、检索没召回、排序错了、引用错了,也可能是前端没有展示足够上下文。
误区 3:用户反馈越详细越好
对开发者来说当然越详细越好。
但对用户来说,越详细越不愿意填。更好的方式是让用户轻量选择原因,系统自动补齐技术上下文。
误区 4:只处理 P0 差评就够了
P0 要先处理,但大量 P1/P2 反馈能暴露趋势。
例如连续出现"引用不对",往往说明引用生成或校验链路需要整体优化。
9. 给前端开发者的落地清单
如果你正在做 RAG 答案页,可以按这个顺序补反馈闭环:
- 每次 RAG 请求返回
traceId。 - 答案结构里保留
citations、confidence、retrieval。 - 前端提供"有帮助/没帮助 + 原因选择"。
- 反馈提交时带上问题、答案、引用、检索结果和 traceId。
- 后台把差评归因到 retrieval、rerank、generation、citation、document。
- 高价值差评转成 regression case,加入 RAG 评测集。
做到这一步,你的知识库问答就不再只是被动等待用户报错,而是开始具备持续进化能力。
结语
RAG 上线不是终点,而是质量迭代的起点。
离线评测集能帮你在发布前挡住回归。
线上反馈闭环能帮你发现真实用户正在遇到的问题。
前端开发者在这件事里非常关键。因为用户是否愿意反馈、反馈数据是否完整、问题能不能和 traceId 串起来,很多都发生在前端答案页。
不要只收集点赞点踩。
真正有价值的是把每一次"不好用",变成下一次可修复、可验证、可回归的优化样本。
能形成反馈闭环的 RAG,才会越用越准。