React RAG 与产品化(第 5-8 周)
目标:把“能回答”升级为“可追溯、可评估、可运营”的前端 AI 产品。
目录
学习目标
这一阶段你可以理解为“把 Demo 变成产品”的分水岭阶段。
前两个阶段你已经能让系统“回答问题”,但到了这里你要解决的是:用户为什么相信你、团队如何持续优化你、业务如何控制成本。
- 会做引用面板与证据展开
- 会展示成本与链路追踪字段
- 会用指标驱动迭代而不是“靠感觉”
前端必做模块
很多同学做 RAG 时只关注后端检索精度,但在真实产品里,用户最先看到的是前端。
如果你不把“证据、链路、成本”透明展示出来,用户只会看到一段答案,很难建立信任,也很难在出错时定位问题。
CitationPanel:引用编号、证据片段、原文定位TraceBadge:显示traceIdCostPanel:本次/今日 token 与模型信息
后端接口约定
这里的接口设计思路是“让前端一次拿齐可展示、可排障、可统计的数据”。
不要只返回 answer,否则前端只能做一个“文本框”,没法做产品化能力。
POST /rag/search:返回证据列表POST /rag/answer:返回答案 + 引用 + traceIdGET /metrics/me:返回当前用户基础用量
响应字段建议:
这份响应结构的目标是把“答案质量”和“系统成本”同时暴露给前端:
answer负责可读性evidence负责可信度与可追溯traceId负责排障定位usage负责成本管理
如果接口不返回这些字段,你很难做完整的产品化面板(尤其是运营和排障维度)。
{
"answer": "...",
"traceId": "trace_xxx",
"evidence": [{ "id": "S1", "title": "员工手册", "snippet": "..." }],
"usage": { "inputTokens": 1200, "outputTokens": 320 }
}
前端实现示例
下面两段代码分别解决“可信展示”和“成本可见”两个核心问题,建议放在同一页面中联动展示。
引用面板组件
这段组件代码的关键不是 UI 漂亮,而是“结构清晰、信息够用”:
用户至少要看到引用编号、来源标题、证据片段,最好还能看到相关度。这样用户会知道“这段答案不是编的,是有出处的”。
type Evidence = { id: string; title: string; snippet: string; score?: number };
export function CitationPanel({ items }: { items: Evidence[] }) {
if (!items.length) return <div>暂无证据</div>;
return (
<aside>
<h4>引用来源</h4>
<ul>
{items.map((it) => (
<li key={it.id}>
<strong>[{it.id}] {it.title}</strong>
<p>{it.snippet}</p>
{typeof it.score === "number" ? <small>相关度: {it.score.toFixed(3)}</small> : null}
</li>
))}
</ul>
</aside>
);
}
成本面板与指标聚合
对团队来说,成本面板不是“锦上添花”,而是上线必需。
你至少要告诉产品和运营:这次问答花了多少 token、折算多少钱、是否超预算。
这也是后续做“模型路由(大模型/小模型)”和“按场景控费”的基础。
type Usage = { inputTokens: number; outputTokens: number; pricePer1kInput: number; pricePer1kOutput: number };
export function calcCost(u: Usage) {
const inCost = (u.inputTokens / 1000) * u.pricePer1kInput;
const outCost = (u.outputTokens / 1000) * u.pricePer1kOutput;
return Number((inCost + outCost).toFixed(6));
}
export function CostPanel({ usage }: { usage: Usage }) {
const total = calcCost(usage);
return (
<section>
<h4>本次调用成本</h4>
<p>Input: {usage.inputTokens} tokens</p>
<p>Output: {usage.outputTokens} tokens</p>
<p>Total: ${total}</p>
</section>
);
}
后端接口实现示例
这段后端代码展示了一条标准 RAG 回答链路:
先检索并重排拿证据,再构建 Prompt 调模型,最后把答案和证据一起返回前端。
重点是“返回结构稳定”,这样前端组件就能长期复用,而不是每次都跟着接口临时改。
app.post("/rag/answer", async (req, res) => {
const traceId = crypto.randomUUID();
const { question } = req.body;
const evidence = await retrieveAndRerank(question, { topK: 10, topN: 4 });
const prompt = buildRagPrompt(question, evidence);
const { answer, usage } = await callModelWithUsage(prompt);
res.json({
answer,
traceId,
evidence: evidence.map((e, i) => ({
id: `S${i + 1}`,
title: e.meta.docTitle,
snippet: e.text.slice(0, 180),
score: e.score,
})),
usage,
});
});
时序图(端到端)
如果你把代码和图结合起来看,会更容易建立全链路理解:
谁先做什么、哪里最耗时、哪里最容易失败,一眼就能看出来。
面试时你也可以直接按这张图讲,表达会非常清楚。
评测与运营指标
指标不是“写在文档里好看”,它们直接决定你是否能持续迭代。
比如当引用准确率下降时,你就知道可能是切分策略或检索参数变差了;当成本上涨时,你就要看 topK、max_tokens 和模型选择。
- TopK 命中率
- 引用准确率
- TTFT / 平均响应时长
- 单次请求平均成本
验收标准
- 引用可展开,可复制,可定位
- 失败场景有可操作提示(重试/缩短问题)
- 至少有 1 个成本面板和 1 个质量指标面板