Notes

RAG ①:Embeddings 与向量数据库入门(Node.js 优先)

摘要:RAG(Retrieval-Augmented Generation)的第一步不是“让模型更强”,而是把你的文档变成一种可检索的索引。Embeddings 负责把文本映射到向量;向量数据库负责存储与相似度搜索。


1. Embeddings:把文本变成向量

Embedding 模型会把一段文本编码成一个高维向量(例如 1536/3072 维)。语义越接近,向量越接近。

你可以把它理解成:

  • 传统搜索:靠关键词匹配
  • 向量搜索:靠“语义距离”

典型用途:

  • 文档/FAQ 检索
  • 相似问答
  • 聚类、去重

2. 相似度指标(你需要知道的最少知识)

常见三种:

  • Cosine(余弦相似度):最常用
  • IP(Inner Product):有时等价于 cosine(取决于向量是否归一化)
  • L2(欧氏距离)

工程上你主要关注:

  • 你选的向量库支持哪种 metric
  • embedding 模型输出是否已归一化(normalized)

3. 向量数据库选型:Milvus / Chroma / pgvector

roadmap 里提到三类:

  • Milvus:生产级,能力全(适合大规模、团队协作)
  • Chroma:轻量,本地开发友好(适合 demo/小项目)
  • pgvector:把向量能力放进 PostgreSQL(适合已有 PG 体系的团队)

本文先不绑定具体向量库,先把“embedding + upsert + query”的抽象概念讲清楚。


4. Node.js:计算 Embedding(OpenAI 兼容)

下面示例以 OpenAI 风格 SDK 为例(其它兼容平台结构类似)。

你只需要替换 baseURL(如果不是 OpenAI)和 apiKey

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  // baseURL: "https://xxx/v1" // 如果你用的是兼容 OpenAI 的第三方平台
});

export async function embed(text) {
  const resp = await client.embeddings.create({
    model: "text-embedding-3-small",
    input: text,
  });

  return resp.data[0].embedding; // number[]
}

// demo
const v = await embed("RAG 是什么?");
console.log(v.length);

工程建议:

  • 对输入做去噪(去掉多余空白、HTML 标签等)
  • 对同一段文本做缓存(embedding 成本可观)

5. 一套最小的向量检索接口(建议你先抽象出来)

无论你用 Milvus/Chroma/pgvector,最终你都需要这三个能力:

  • upsert(chunks[]):写入/更新向量
  • query(vector, topK):按向量相似度查 topK
  • delete(filter):删除(可选)

你可以先定义一个最小接口:

// 伪代码/接口定义
export interface VectorStore {
  upsert(items: Array<{ id: string; vector: number[]; text: string; metadata?: any }>): Promise<void>;
  query(vector: number[], topK: number): Promise<Array<{ id: string; score: number; text: string; metadata?: any }>>;
}

有了它,你的 RAG 逻辑就不会被某个向量库绑定死。


6. 最小 RAG(只差一步):把检索结果拼回 Prompt

在你有了 query() 之后,RAG 的第一版就能跑:

  1. 用户问题 -> embedding
  2. 去向量库 query topK
  3. 把 topK 文本片段作为“证据”拼到 prompt
  4. 让 LLM 基于证据回答,并要求引用证据

拼 prompt 的示例:

你是一个文档问答助手。请只根据 EVIDENCE 回答,不要编造。

EVIDENCE:
- (doc1#chunk3) ...
- (doc2#chunk7) ...

QUESTION:
{userQuestion}

7. 下一篇写什么

下一篇进入 RAG 的“ETL”部分:

  • 文档加载(Markdown/HTML/PDF)
  • 切分(Recursive / MarkdownHeader)
  • 为什么不能随便按长度切

并且会继续保持 Node.js 优先(示例会用 JS/TS)。

cd ..