首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >AI 原生超级全栈:从 62% 召回率到 89% 的可评估 RAG 系统

AI 原生超级全栈:从 62% 召回率到 89% 的可评估 RAG 系统

原创
作者头像
IT大佬 jzit-top
发布于 2026-10-02 10:09:50
发布于 2026-10-02 10:09:50
30
举报

我们给一家 SaaS 做企业知识助手,第一版纯向量检索(text-embedding-3-large + pgvector),上线两周后数据很难看:引用命中率 62%,忠实度 0.71,用户反馈“答非所问”占 31%。问题不在模型,而在检索和评估。这篇文章讲我们怎么把召回率做到 89%、忠实度 0.93、单次成本从 $0.042 降到 $0.011。

1. 混合检索 + RRF:向量不够,BM25 来补

纯向量对专有名词、型号、错误码几乎无效。我们并行跑向量和 BM25,用 RRF(k=60)融合,再用 bge-reranker-v2-m3 精排。

代码语言:javascript
复制
// apps/api/src/rag/hybrid-retrieve.ts
export async function hybridRetrieve(
  db: Pool, query: string, embedding: number[],
  userId: string, tenantId: string, topK = 6,
): Promise<Chunk[]> {
  const [vec, bm25] = await Promise.all([
    db.query(
      `SELECT id, doc_id, text, 1 - (embedding <=> $1) AS score
       FROM chunks
       WHERE tenant_id = $2 AND visibility @> ARRAY[$3]::text[]
       ORDER BY embedding <=> $1 LIMIT 30`,
      [embedding, tenantId, userId],
    ),
    db.query(
      `SELECT id, doc_id, text,
              ts_rank_cd(tsv, plainto_tsquery('simple', $1)) AS score
       FROM chunks
       WHERE tenant_id = $2 AND tsv @@ plainto_tsquery('simple', $1)
       ORDER BY score DESC LIMIT 30`,
      [query, tenantId],
    ),
  ]);

  const rrf = new Map<string, { chunk: any; score: number }>();
  const add = (rows: any[], w: number) =>
    rows.forEach((r, i) => {
      const cur = rrf.get(r.id) ?? { chunk: r, score: 0 };
      cur.score += w / (60 + i + 1);
      rrf.set(r.id, cur);
    });
  add(vec.rows, 1.0);
  add(bm25.rows, 0.7);

  const merged = [...rrf.values()]
    .sort((a, b) => b.score - a.score)
    .slice(0, 20)
    .map((x) => x.chunk);

  return (await rerank(query, merged)).slice(0, topK);
}

RRF 权重 1.0 / 0.7 是网格搜索出来的:BM25 权重再高会引入噪声,再低则型号类查询召回下降。

2. 评估门禁:没有指标的 RAG 就是玄学

我们建了 200 条评估集,覆盖事实问答、型号查询、拒答、多轮。CI 中跑四类指标,不达标直接阻断。

代码语言:javascript
复制
// evals/rag-gate.test.ts
const TH = {
  citationHitRate: 0.85,
  faithfulness: 0.90,
  refusalAccuracy: 0.95,
  p95FirstTokenMs: 1200,
};

it('meets RAG thresholds', async () => {
  const r = await runEvalSet('evals/datasets/qa-200.jsonl');
  expect(r.citationHitRate).toBeGreaterThanOrEqual(TH.citationHitRate);
  expect(r.faithfulness).toBeGreaterThanOrEqual(TH.faithfulness);
  expect(r.refusalAccuracy).toBeGreaterThanOrEqual(TH.refusalAccuracy);
  expect(r.p95FirstTokenMs).toBeLessThanOrEqual(TH.p95FirstTokenMs);
});

忠实度用 LLM-as-judge:把回答拆成原子断言,逐条判断是否被引用支持,比例即忠实度。拒答准确率指知识库无答案时是否说“不知道”,这条比召回更重要——错误回答的代价远高于拒答。

3. 流式 + 降级链:成本和体验的平衡

首 token 延迟我们用 SSE,模型走三级降级:GPT-4.1 → GPT-4.1-mini → 纯检索摘要。用户预算超限自动降级。

代码语言:javascript
复制
async function pickModel(userId: string) {
  const spent = Number(await redis.get(`cost:${userId}:${today()}`) ?? 0);
  if (spent > 0.5) return { model: 'gpt-4.1-mini', tier: 'standard' };
  if (spent > 1.5) return { model: null, tier: 'retrieval-only' };
  return { model: 'gpt-4.1', tier: 'premium' };
}

降级时仍返回引用片段,用户至少能看到来源。上线后 P95 首 token 从 2.3s 降到 0.9s,单次成本从 $0.042 降到 $0.011。

4. 工具调用必须沙箱化

AI 原生全栈最容易出事的是工具。我们的原则:白名单 + 参数 schema + 用户权限二次校验。

代码语言:javascript
复制
const TOOLS = {
  query_order: {
    schema: z.object({ orderId: z.string().regex(/^[A-Z0-9]{8,20}$/) }),
    run: async (args, ctx) => {
      const order = await db.order.findUnique({ where: { id: args.orderId } });
      if (order?.userId !== ctx.userId) throw new Error('FORBIDDEN');
      return order;
    },
  },
};

模型永远不直接碰数据库,只调用注册过的工具;权限校验在工具内部,不在提示词里。

5. 权衡:为什么不上 LangChain、不微调

LangChain 抽象层太厚,调试检索问题时链路不透明,我们最终用 200 行原生代码替代。微调成本高、迭代慢,而混合检索 + reranker 的收益更直接。真正难的不是模型,是评估集、权限隔离、成本控制、降级策略——这些才是 AI 原生全栈工程师的核心竞争力。

最终指标:召回率 89%,忠实度 0.93,拒答准确率 0.97,P95 首 token 0.9s,单次成本 $0.011。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 1. 混合检索 + RRF:向量不够,BM25 来补
  • 2. 评估门禁:没有指标的 RAG 就是玄学
  • 3. 流式 + 降级链:成本和体验的平衡
  • 4. 工具调用必须沙箱化
  • 5. 权衡:为什么不上 LangChain、不微调
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档