前端开发者做 RAG:别只收集点赞点踩,用 6 个字段把反馈变成优化闭环

作者:前端转 AI 深度实践者

【省流助手/核心观点】 :RAG 上线后,点赞点踩只是反馈入口,不是反馈闭环。真正可优化的反馈,至少要带上 traceIdquestionanswercitationsretrievalreason 这 6 类信息。前端开发者做知识库问答,不只是渲染模型答案,还要帮系统留下可复盘、可归因、可回流评测集的质量数据。


第 21 篇文章我们讲了 RAG 评测集:上线前用固定问题挡住明显回归。

但真实产品上线后,还会出现另一类问题:

评测集没覆盖,用户真的问到了。

用户的问题更口语化、更跳跃,也更贴近业务现场。

这时只靠离线评测不够,你还需要线上反馈。

很多团队会在答案下面放两个按钮:

text 复制代码
有帮助    没帮助

这当然比什么都不收集好。

但如果你只存了一个 likedislike,后面依然很难优化。

因为你不知道:

  • 用户为什么点踩?
  • 当时命中了哪些资料?
  • 引用来源是否正确?
  • 模型是合理拒答了,还是给出了无依据回答?
  • 这个问题应该改 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 个关键上下文:

  1. 用户当时问了什么。
  2. 系统当时答了什么。
  3. 当时引用了哪些 chunk。
  4. 检索和 Rerank 分数是多少。
  5. 用户觉得哪里不对。

最后这类数据通常会变成一个只能汇报、不能修问题的数字:

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>
  );
}

这个交互有两个好处:

  1. 用户只需要多点一下,不会被长表单劝退。
  2. 开发者能快速判断这条反馈更像哪一类问题。

注意,不要让反馈表单变得太重。用户不是测试工程师,他愿意告诉你"不对"已经很宝贵了。

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 答案页,可以按这个顺序补反馈闭环:

  1. 每次 RAG 请求返回 traceId
  2. 答案结构里保留 citationsconfidenceretrieval
  3. 前端提供"有帮助/没帮助 + 原因选择"。
  4. 反馈提交时带上问题、答案、引用、检索结果和 traceId。
  5. 后台把差评归因到 retrieval、rerank、generation、citation、document。
  6. 高价值差评转成 regression case,加入 RAG 评测集。

做到这一步,你的知识库问答就不再只是被动等待用户报错,而是开始具备持续进化能力。

结语

RAG 上线不是终点,而是质量迭代的起点。

离线评测集能帮你在发布前挡住回归。

线上反馈闭环能帮你发现真实用户正在遇到的问题。

前端开发者在这件事里非常关键。因为用户是否愿意反馈、反馈数据是否完整、问题能不能和 traceId 串起来,很多都发生在前端答案页。

不要只收集点赞点踩。

真正有价值的是把每一次"不好用",变成下一次可修复、可验证、可回归的优化样本。

能形成反馈闭环的 RAG,才会越用越准。

相关推荐
Narrastory2 小时前
Note:强化学习(五)
人工智能·深度学习·强化学习
行走的小派2 小时前
半导体爆发微观见证:全志A733+3TOPS NPU:Zero 3W能否撑起轻量边缘AI?
人工智能
码界索隆2 小时前
【腾讯位置服务开发者征文大赛】用 AI Agent + MCP 重构“周边去哪儿”决策链路:我的真实踩坑与MVP落地复盘
人工智能·typescript·node.js
peterfei2 小时前
一夜重构!我用 18000 行代码打造了完全自研的 AI TUI 终端
人工智能·开源·全栈
AI服务老曹2 小时前
突破芯片壁垒:基于 Docker 与异构计算架构的工业级 AI 视频管理平台深度解析
人工智能·docker·架构
byte轻骑兵2 小时前
【LE Audio】BASS精讲[4]: 控制点解析,广播接收指令交互全流程
人工智能·音视频·语音识别·le audio·低功耗音频
PHP武器库2 小时前
4天花10亿,glm-5v-turbo体验笔记
人工智能
star learning white2 小时前
线性代数3
人工智能·线性代数·机器学习
高工智能汽车2 小时前
中国首款5nm舱驾融合整车智能体芯片发布,地平线要做“物理AI时代的Wintel”
人工智能