前端开发者做 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,才会越用越准。

相关推荐
Hilaku11 分钟前
从搜索排名到 AI 回答? 先聊一聊 AI 可见度工具 BuildSOM !
前端·javascript·程序员
zzmgc413 分钟前
纯静态 + Web Worker + 虚拟滚动:我是怎么让浏览器吃下 10MB JSON 不卡的
前端·架构
辰同学ovo14 分钟前
用 Chrome DevTools MCP 给 AI 写的页面做“质检“
前端·人工智能·chrome devtools
乌托邦21 分钟前
uni-mini-ci:让 uniapp 小程序构建后自动预览和上传
前端·vue.js·uni-app
爱吃大芒果25 分钟前
从零搭建完整 HarmonyOS 应用实战教程
华为·typescript·harmonyos
果汁华28 分钟前
Agent 与 Skill 的使用边界
人工智能
天上路人29 分钟前
采用AI 神经网络降噪技术降噪模组A-59F如何区分“人声”与“环境噪声”?
人工智能·语音识别
啵啵肠29 分钟前
给 AI Agent 一把求职 CLI:推荐一个面向 BOSS 直聘工作流的开源项目 boss-agent-cli
人工智能·github
豹哥学前端31 分钟前
前端工程化实战:从包管理到 Vite 配置,一套下来全明白
前端·javascript·vite
小新同学^O^35 分钟前
简单学习 --> 模型微调
开发语言·人工智能·python·模型微淘