40岁了,最近再学bun。
你是不是也想转 AI Agent 开发,却被满屏 Python/PyTorch/LangChain 劝退?听过 RAG,却觉得那是算法工程师才懂的"黑魔法"?手里只会 JS/TS,完全不知道从哪下手?
你不是一个人。这篇文章用一段能直接跑的 TS 代码,把 RAG 讲清楚。你看完就会明白:不是只有 Python 才能玩 AI,JS 一样能干。

javascript
// ① 引入 RAG 三件套:管道、向量库、切块器
import { DocumentPipeline, InMemoryVectorStore, RecursiveTextChunker } from "@kognitivedev/rag";
// ② AI SDK 适配器,把百炼 embedding 包成统一接口
import { AISDKEmbeddingProvider } from "@kognitivedev/adapter-ai-sdk";
// ③ 用 OpenAI 兼容格式连接百炼
import { createOpenAI } from "@ai-sdk/openai";
const qwen = createOpenAI({
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", // 百炼兼容端点
apiKey: "sk-你的百炼API-KEY",
});
// ④ 组装 RAG 流水线
const agent = new DocumentPipeline({
chunker: new RecursiveTextChunker(), // 文档怎么切
embedder: new AISDKEmbeddingProvider({
model: qwen.embedding("text-embedding-v4") // 用什么模型转向量
}),
vectorStore: new InMemoryVectorStore(), // 向量存在哪
});
// ⑤ 喂文档、建索引
await agent.ingest([{ content: "公司规定:离职需提前30天申请。" }]);
// ⑥ 提问并检索最相似的块
const results = await agent.search("怎么离职?");
console.log(results[0].content); // 公司规定:离职需提前30天申请。
sql
bun add @kognitivedev/rag @ai-sdk/openai ai @kognitivedev/adapter-ai-sdk
API KEY 从这里申请:阿里云百炼控制台
阿里注册送100万Token,正好用来测试。
上面这 8 行代码,已经能跑通一个最小的 RAG 流程。下面解释它到底干了什么。
一、核心概念(一句话搞懂)
- • RAG:先翻书,再回答。让 AI 基于你的文档说话。
- • Agent:能自主决策、调用工具的智能体。RAG 是它的"检索工具"。
- • Embedding:文本→向量(一串数字),让计算机能算"相似度"。
- • Vector Store:存向量的数据库,负责快速找最像的那几段。
二、RAG 干了什么?(代码透视)
ini
// 你的文档
const doc = "公司规定:离职需提前30天申请。";
// ① 切块(Chunking)------ 把长文切成小段
const chunks = ["公司规定:离职需提前30天申请。"];
// ② 向量化(Embedding)------ 文字→数字指纹
const vector = [0.123, -0.456, 0.789, /* ... */]; // 1024维或更多
// ③ 存库(Store)------ 入库备查
await store.add({ content: chunks[0], vector });
// ④ 检索(Search)------ 问题也转向量,找最像的
const question = "怎么离职?";
const qVector = [0.111, -0.222, 0.333, /* ... */];
const similar = await store.find(qVector, { topK: 1 });
// → "公司规定:离职需提前30天申请。"
三句话总结:文档切块→转向量→存库。问题来了→转向量→找最像的块→拿去给 AI。
三、怎么用?
javascript
import { DocumentPipeline, InMemoryVectorStore, RecursiveTextChunker } from "@kognitivedev/rag";
import { AISDKEmbeddingProvider } from "@kognitivedev/adapter-ai-sdk";
// 三个积木,随便换
const pipeline = new DocumentPipeline({
chunker: new RecursiveTextChunker(), // 怎么切
embedder: new AISDKEmbeddingProvider({ // 怎么转向量
model: qwen.embedding("text-embedding-v4")
}),
vectorStore: new InMemoryVectorStore(), // 存哪
});
await pipeline.ingest([{ content: "你的文档" }]); // 自动完成 切→转→存
const results = await pipeline.search("你的问题"); // 自动完成 转→找→返回
Chunker 不是只能递归切。长文档用 MarkdownChunker,按标题切;按 Token 预算切用 TokenChunker;HTML、JSON、句子都有对应实现。
php
import { MarkdownChunker, TokenChunker, SentenceChunker } from "@kognitivedev/rag";
// 按 Markdown 标题切,适合文档站点
const mdChunks = new MarkdownChunker().chunk({
content: "# 入职\n\n办工卡。\n\n# 离职\n\n提前30天申请。",
});
// 按 Token 预算切,适合超长文本
const tokenChunks = new TokenChunker({ tokensPerChunk: 512, overlap: 50 }).chunk({
content: "A".repeat(10000),
});
// 按句子边界切,适合法律文书
const sentenceChunks = new SentenceChunker({ maxChunkSize: 200 }).chunk({
content: "第一条 合同期限一年。第二条 提前30天通知。",
});
换数据库(内存→Redis):
arduino
// 开发
vectorStore: new InMemoryVectorStore()
// 生产(换这一行就行)
vectorStore: new RedisVectorStore(redisClient)
换模型(百炼→OpenAI):
css
import { openai } from "@ai-sdk/openai";
// 只改这一行
embedder: new AISDKEmbeddingProvider({
model: openai.embedding("text-embedding-3-small")
})
四、批量入库记得带 metadata
真实文档不会只有一条。ingest 时带上 metadata,后面可以按部门、标签、版本号过滤。
less
const result = await pipeline.ingest([
{ content: "研发部:代码评审每周五下午。", metadata: { dept: "研发部" } },
{ content: "人力资源部:年假按工龄计算。", metadata: { dept: "人力资源部" } },
{ content: "人力资源部:离职需提前30天申请。", metadata: { dept: "人力资源部" } },
]);
console.log(result);
// { documentsProcessed: 3, chunksCreated: 3, vectorsStored: 3 }
metadata 会跟着向量一起存,检索结果里也能取到,方便做来源标注和权限控制。
五、搜索参数要会调
search 不是只能传字符串。topK、minScore 决定召回质量,生产级向量库(Redis、PgVector、Qdrant)还支持 filter 按 metadata 过滤。
ini
const results = await pipeline.search("年假多少天?", {
topK: 5, // 返回前 5 个结果
minScore: 0.5, // 相似度低于 0.5 的不要
});
console.log(results.map(r => ({
content: r.content,
score: r.score,
})));
minScore 过低会召回垃圾内容,过高可能漏答案。先用默认值跑通,再按数据特点微调。
六、检索完让大模型说人话
RAG 只负责找资料,生成答案还得靠大模型。下面把检索结果拼进 prompt,交给百炼的 qwen-plus:
javascript
import { generateText } from "ai";
const docs = await pipeline.search("怎么离职?", { topK: 3 });
const context = docs.map(d => d.content).join("\n\n");
const { text } = await generateText({
model: qwen.chat("qwen-plus"),
prompt: `根据以下资料回答问题:\n\n${context}\n\n问题:怎么离职?`,
});
console.log(text);
实际项目里,建议把 prompt 模板化,并加上"如果资料里没有,就说不知道"的约束,避免 hallucination。
七、进阶用法(让 Agent 自己决定是否检索)
ini
// 把 RAG 包装成一个"工具",Agent 可以自主决定何时调用
const retrievalTool = pipeline.asTool();
// Agent 看到问题后,自己判断:要不要翻书?
const response = await agent.chat({
messages: [...],
tools: [retrievalTool], // 给 Agent 配个"书库"
});
八、三个关键认知
arduino
// 认知1:RAG ≠ 微调
// 微调 = 让 AI 背新知识(贵,慢,要数据)
// RAG = 让 AI 考试时翻书(便宜,快,实时更新)
// 认知2:RAG = 检索 + 生成
// 检索 = 找资料(解决问题"记不住")
// 生成 = 说人话(解决问题"说不清")
// 认知3:RAG 是 Agent 的"外部大脑"
// Agent 负责决策,RAG 负责提供素材
九、模型从哪来?(云端 vs 本地)
php
// 云端(不会下载模型)
const embedder = new AISDKEmbeddingProvider({
model: qwen.embedding("text-embedding-v4")
});
// → 发请求到阿里云百炼,云端算完返回结果
// 本地(会自动下载模型)
import { embed } from "ollama";
await embed({ model: "nomic-embed-text", input: "文档" });
// → 首次运行下载几百 MB 到 ~/.ollama/models
云端适合快速验证和生产,本地适合数据不出内网的场景。新手建议先走云端,跑通了再考虑本地化。
十、两个常见坑
坑 1:向量维度对不上。 text-embedding-v4 默认 1024 维,text-embedding-v3 默认也是 1024 维,但 OpenAI 的 text-embedding-3-small 是 1536 维。不同 embedding 模型混用时,向量库必须清空重建,否则检索会报错。
坑 2:文档太短反而搜不到。 如果切的 chunk 只有几个字,embedding 的语义特征很弱,搜"年假"可能匹配到"年假",但搜"带薪休假"就漏了。建议 chunk 至少包含一个完整句子或一段完整语义。
十一、术语速查
| 术语 | 读音(拼音) | 一句话解释 |
|---|---|---|
| Agent | ài zhēn tè | 能自主决策的智能体 |
| RAG | ruì gé | 先检索再生成 |
| Embedding | ēn bèi dīng | 文本→向量数字 |
| Vector | wéi kè tè | 一串数字表示语义 |
| Vector Store | wéi kè tè sī duō | 向量数据库 |
| Chunk | chāng kè | 切出来的文本块 |
| Prompt | pǔ ràng pǔ tè | 给 AI 的指令 |
| ingest | yīn jié sī tè | 摄入文档建索引 |
| search | sè chí | 检索相似内容 |
| Top-K | tāo pǔ kāi | 返回前 K 个结果 |
总结
-
- RAG = 先翻书再回答
-
- Agent = 自主决策的智能体,RAG 是它的工具
我在准备下一篇:AI 只会"知道"还不够,得让它"能做"------理解 MCP, 关注我哦。