前端如何使用AI Agent 实现RAG知识库检索搜索

19 阅读3分钟

用 AI SDK 从 0 实现一个 PDF RAG 知识库(demo)

在做 AI 知识库 / ChatPDF / 企业文档问答 时,最核心的技术就是 RAG(Retrieval Augmented Generation)

本文通过一个 完整可运行的 Node.js 示例,一步一步实现:

  • 解析 PDF
  • 文本切分
  • 生成向量(Embedding)
  • 向量检索
  • 使用 AI SDK 进行回答

最终实现一个 简单版 ChatPDF


一、最终实现效果

流程如下:

PDF
 ↓
解析文本
 ↓
chunk切分
 ↓
embedding
 ↓
vector store
 ↓
用户提问
 ↓
向量检索
 ↓
LLM回答

用户提问:

Node.js 的 event loop 是什么?

系统会:

1️⃣ 从 PDF 中找到最相关段落
2️⃣ 将段落提供给 LLM
3️⃣ 生成答案


二、创建项目

创建项目目录:

rag-demo

初始化 Node 项目:

npm init -y

安装依赖:

npm install ai @ai-sdk/openai pdfjs-dist 

依赖说明:

依赖作用
aiAI SDK
@ai-sdk/openaiLLM Provider
pdfjs-dist解析 PDF

三、项目结构

项目结构如下:

rag-demo
│
│─ test.pdf
├─ ingest.js
├─ embedding.js
├─ rag.js
├─ vectorStore.json
└─ package.json

说明:

文件作用
ingest.js文档入库
embedding.jsembedding API
rag.js问答
vectorStore.json向量数据

四、解析 PDF

首先我们需要把 PDF 转成文本

创建文件:

ingest.js

代码:

import fs from "fs";
import * as pdfjs from "pdfjs-dist/legacy/build/pdf.mjs";

async function extractTextFromPDF(path) {
  const data = new Uint8Array(fs.readFileSync(path));

  const pdf = await pdfjs.getDocument({ data }).promise;

  let text = "";

  for (let i = 1; i <= pdf.numPages; i++) {
    const page = await pdf.getPage(i);

    const content = await page.getTextContent();

    const pageText = content.items.map(item => item.str).join(" ");

    text += pageText + "\n";
  }

  return text;
}

const text = await extractTextFromPDF("./test.pdf");

console.log(text);

运行:

node ingest.js

输出:

PDF完整文本

五、文本切分(Chunk)

在 RAG 中,不能把整篇文档做 embedding。

原因:

整篇文档 → 一个向量

检索效果会很差。

正确方式是:

文档
 ↓
chunk1
chunk2
chunk3

添加 chunk 函数:

function chunkText(text, size = 500) {
  const chunks = [];

  for (let i = 0; i < text.length; i += size) {
    chunks.push(text.slice(i, i + size));
  }

  return chunks;
}

示例:

chunk1: Node.js 简介
chunk2: Node.js Event Loop
chunk3: Node.js Stream

六、生成 Embedding

向量是 RAG 的核心。

这里要用到embeddings模型,推荐 ai.gitee.com/serverless-…

image.png 示例

 const openai = createOpenAI({
    apiKey: 'your key',
    baseURL: "https://ai.gitee.com/v1"
  });

for (const chunk of chunks) {
  const embedding = await embed({
    model: openai.embedding("Qwen3-Embedding-8B"),
    value: chunk
  });

  vectorStore.push({
    text: chunk,
    embedding: embedding.embedding
  });
}
}

七、文档入库(Embedding Pipeline)

修改 ingest.js

import fs from "fs";
import * as pdfjs from "pdfjs-dist/legacy/build/pdf.mjs";
import { embed } from "ai";
import { createOpenAI } from "@ai-sdk/openai";

async function extractTextFromPDF(path) {
  const data = new Uint8Array(fs.readFileSync(path));
  const pdf = await pdfjs.getDocument({ data }).promise;
  let text = "";

  for (let i = 1; i <= pdf.numPages; i++) {
    const page = await pdf.getPage(i);
    const content = await page.getTextContent();

    const pageText = content.items.map(item => item.str).join(" ");

    text += pageText + "\n";
  }

  return text;
}

function chunkText(text, size = 500) {
  const chunks = [];

  for (let i = 0; i < text.length; i += size) {
    chunks.push(text.slice(i, i + size));
  }

  return chunks;
}

const text = await extractTextFromPDF("./test.pdf");

const chunks = chunkText(text);

const vectorStore = [];

  const openai = createOpenAI({
    apiKey: 'your key',
    baseURL: "https://ai.gitee.com/v1"
  });

for (const chunk of chunks) {
  const embedding = await embed({
    model: openai.embedding("Qwen3-Embedding-8B"),
    value: chunk
  });

  vectorStore.push({
    text: chunk,
    embedding: embedding.embedding
  });
}

fs.writeFileSync("vectorStore.json", JSON.stringify(vectorStore, null, 2));

console.log("Embedding完成");

运行:

node ingest.js

生成:

vectorStore.json

数据示例:

{
 "text": "Node.js 是一个基于 V8 的 JavaScript runtime",
 "embedding": [0.123, -0.234, ...]
}

image.png


八、实现向量检索

创建文件:

rag.js

首先实现余弦相似度:

function cosineSimilarity(a, b) {
  let dot = 0;
  let normA = 0;
  let normB = 0;

  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }

  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}

然后实现搜索:

async function search(query) {
  const embedding = await embed({
    model: openai.embedding("Qwen3-Embedding-8B"),
    value: query
  });

  const queryVector = embedding.embedding;

  const scored = vectorStore.map(doc => ({
    text: doc.text,
    score: cosineSimilarity(queryVector, doc.embedding)
  }));

  scored.sort((a, b) => b.score - a.score);

  return scored.slice(0, 3);
}


九、结合 AI SDK 生成回答

完整 rag.js

import fs from "fs";
import { embed, streamText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";

const vectorStore = JSON.parse(fs.readFileSync("./vectorStore.json"));

const openai = createOpenAI({
  apiKey: 'your key',
  baseURL: "https://ai.gitee.com/v1"
});

function cosineSimilarity(a, b) {
  let dot = 0;
  let normA = 0;
  let normB = 0;

  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }

  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}

async function search(query) {
  const embedding = await embed({
    model: openai.embedding("Qwen3-Embedding-8B"),
    value: query
  });

  const queryVector = embedding.embedding;

  const scored = vectorStore.map(doc => ({
    text: doc.text,
    score: cosineSimilarity(queryVector, doc.embedding)
  }));

  scored.sort((a, b) => b.score - a.score);

  return scored.slice(0, 3);
}

async function ask(question) {
  const docs = await search(question);

  const context = docs.map(d => d.text).join("\n");

  const result = await streamText({
    model: openai.chat("Qwen3-8B"),
    system: `Answer using this context:\n${context}`,
    prompt: question
  });

  for await (const chunk of result.textStream) {
    process.stdout.write(chunk);
  }
}

ask("这个PDF主要讲什么?");

十、运行 RAG

先执行:

node ingest.js

生成向量库。

再执行:

node rag.js

提问:

这个 PDF 讲什么?

image.png

系统会:

问题
 ↓
embedding
 ↓
vector search
 ↓
Top3 文档
 ↓
LLM
 ↓
回答

十一、完整架构总结

最终系统流程:

PDF
 ↓
pdfjs 解析
 ↓
chunk
 ↓
embedding
 ↓
vector store
 ↓
query embedding
 ↓
vector search
 ↓
top K context
 ↓
LLM回答

总结

本文实现了一个 完整的 PDF RAG 系统

  • PDF 解析
  • 文档切分
  • Embedding
  • 向量检索
  • AI SDK 问答

理解这一套流程,就掌握了 AI 知识库系统的核心技术