让 AI 学会你的私有知识

为什么需要 RAG

大模型有三个天然缺陷:

1.知识截止日期:GPT-4o 可能不知道上周发生的事 2.不懂你的私有数据:公司内部文档、你的个人笔记 3.爱瞎编(幻觉):遇到不知道的会一本正经胡说

**RAG (Retrieval-Augmented Generation)**的思路非常朴素:

回答问题前,先去知识库里一下相关资料,把资料和问题一起交给 LLM,让它基于资料回答。

这就像你参加开卷考试——知识在书里,你只负责查和组织答案。

RAG 的五步流程

1. 准备阶段(离线一次性做)
   知识库文档 → 切分 chunks → 生成 embedding → 存向量库

2. 查询阶段(每次用户提问)
   用户问题 → 生成 embedding → 向量库里查相似文档 → 拼 Prompt → 调 LLM → 答案

画成图:

[用户问题] ─embed─► [向量] ─search─► [Top-K 相关文档]


                         ┌──────────────────────┐
                         │  Prompt:             │
                         │  基于下面资料回答:    │
                         │  {相关文档}           │
                         │                      │
                         │  问题: {用户问题}     │
                         └──────────────────────┘


                             [LLM] → 答案

手写一个最小 RAG

不用任何框架,几十行代码就够。

// rag.js
import OpenAI from 'openai'
import fs from 'node:fs/promises'

const client = new OpenAI()

async function embed(text) {
  const r = await client.embeddings.create({
    model: 'text-embedding-3-small',
    input: text,
  })
  return r.data[0].embedding
}

function cosineSim(a, b) {
  let dot = 0, na = 0, nb = 0
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i]
    na += a[i] * a[i]
    nb += b[i] * b[i]
  }
  return dot / (Math.sqrt(na) * Math.sqrt(nb))
}

// 1. 切分:把长文档切成小块
function splitText(text, chunkSize = 500, overlap = 50) {
  const chunks = []
  for (let i = 0; i < text.length; i += chunkSize - overlap) {
    chunks.push(text.slice(i, i + chunkSize))
  }
  return chunks
}

// 2. 索引:所有 chunk 预计算 embedding
const doc = await fs.readFile('./my-notes.md', 'utf-8')
const chunks = splitText(doc)
const index = await Promise.all(
  chunks.map(async (text) => ({ text, vec: await embed(text) }))
)

// 3. 查询:根据问题找 Top-K 相关片段
async function retrieve(question, k = 3) {
  const qVec = await embed(question)
  return index
    .map((item) => ({ ...item, score: cosineSim(qVec, item.vec) }))
    .sort((a, b) => b.score - a.score)
    .slice(0, k)
}

// 4. 生成:拼 Prompt 交给 LLM
async function ask(question) {
  const relevant = await retrieve(question, 3)
  const context = relevant.map((r, i) => `[资料${i + 1}]\n${r.text}`).join('\n\n')

  const res = await client.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {
        role: 'system',
        content: '你是一个严谨的助手。只根据用户提供的资料回答,不知道就说不知道,不要编造。',
      },
      {
        role: 'user',
        content: `资料:\n${context}\n\n问题:${question}`,
      },
    ],
  })
  return res.choices[0].message.content
}

console.log(await ask('这份笔记里提到的核心观点是什么?'))

这就是 RAG 的全部。真的就这么简单。

切分(Chunking)的讲究

切分策略直接决定 RAG 的召回质量。常见方案:

1. 固定长度切分(上面的例子)

  • 优点:简单
  • 缺点:容易把一个完整的句子切开

2. 按段落/标题切分

// 按 markdown 的 ## 二级标题切分
const chunks = text.split(/\n(?=##\s)/)

3. 语义切分

用 LLM 或专门模型识别语义边界。LangChain 有 SemanticChunker

4. 带元信息的切分

每个 chunk 附加文档名、章节号、页码,方便最后给用户"出处"。

{
  text: '这是正文...',
  metadata: {
    source: 'design-system.md',
    section: '颜色规范',
    page: 3,
  }
}

检索质量的调优

chunk_size:太小丢上下文,太大召回不精准。一般 300~800 字符起步。

overlap:chunk 之间要有一定重叠(常见 10-20%),避免关键信息正好卡在边界上。

top_k:取多少条。太少可能漏,太多 Token 超标。通常 3~10。

重排序(Rerank):先用向量粗查 20 条,再用一个 Cross-Encoder 模型精排,取前 5 条给 LLM。显著提升质量。推荐 Cohere Rerank 或 BGE-Reranker。

混合检索:向量 + 关键词(BM25)结果合并。对人名、专有名词这种向量不擅长的场景很有用。

RAG 的典型失败模式

1. 召回不到相关内容

  • Embedding 模型对你的领域数据不匹配 → 换模型或微调
  • 用户问题太短/模糊 → 用 LLM 先改写问题(Query Rewriting)

2. 召回了但 LLM 没用上

  • Prompt 里资料放太靠后 → 模型"迷失在中间"(Lost in the Middle)
  • 解决:把最相关的资料放最前或最后

3. LLM 还是在编造

  • System Prompt 强化:"只用提供的资料,没依据就说不知道"
  • 让模型引用资料编号[资料1][资料3]

需要什么框架吗?

原型阶段手写就好。上生产再考虑:

-LangChain / LangChain.js:生态最全,但抽象有点重 -LlamaIndex / LlamaIndex.TS:专门做 RAG,封装更好 -Vercel AI SDK:前端友好,和 Next.js 无缝

第 9 篇会用 Vercel AI SDK 做一个带 RAG 的聊天界面。

生产级 RAG 的架构图

不给你画复杂图了,直接贴第 4 篇提到的向量库方案:

[前端] → [后端 API]

           ├─► [用户问题 → Embedding]

           ├─► [pgvector: 向量 + 关键词混合查询]

           ├─► [Rerank 模型精排]

           └─► [LLM: 生成答案 + 引用]

动手作业

基于你的博客(或者任何一堆 Markdown 文件)做一个问答机器人:

  1. 扫描所有 .md,按二级标题切分
  2. 全部 embed 存 JSON 数组(几千条内不用数据库)
  3. CLI 问答,每次回答都带上"参考资料"

进阶:把 JSON 换成 SQLite + sqlite-vss 或 pgvector。

参考资料

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:RAG:让 AI 回答你的专属数据

本文链接:https://www.sshipanoo.com/blog/ai/ai-for-frontend/05-RAG入门/

本文最后一次更新为 天前,文章中的某些内容可能已过时!