项目源码:https://github.com/goliter/ai-chat
AI-Chat是一个基于Next.js和React开发的现代化大语言模型的知识库问答系统。该平台提供了简易的对话界面,支持上传文件进行知识库的构建,让用户在与大语言模型进行问答时给与大模型知识库内的相关内容。
1、环境要求
Node.js >= 18.0.0
npm >= 9.0.0
2、安装依赖
npm install
3、启动开发服务器
npm run dev
4、构建生产版本
npm run build
npm start
5、构建环境
.env
DATABASE_URL="******"
.env.local
OPENAI_API_KEY=*********
DATABASE_URL="********"
AUTH_SECRET="*******" # Added by `npx auth`. Read more: https://cli.authjs.dev
ai-chat/
├── app/
│ ├── (auth)/ # 认证相关路由
│ │ ├── login/
│ │ └── register/
│ ├── (chat)/ # 聊天功能路由
│ │ ├── api/ # 聊天相关API
│ │ └── home/ # 聊天界面
│ ├── home/ # 主功能模块
│ │ ├── chat/ # 聊天记录管理
│ │ └── knowledge/ # 知识库管理
│ └── layout.tsx # 全局布局
├── components/ # 组件库
├── lib/ # 工具函数库
│ ├── db.ts # 数据库操作
│ └── zod.ts # Zod 模式定义
├── prisma/ # 数据库配置
│ └── schema.prisma # Prisma 模型定义
├── public/ # 静态资源
├── ai/ # AI核心模块
│ ├── index.ts # AI服务入口
│ └── ragMiddleware.ts # RAG中间件
RAG模型的工作流程可以总结为以下几个步骤:
相应代码(界面路由/home/knowledge/new, api路由/app/(chat)/api/knowledge)
// 核心功能模块:
1. 认证模块:使用 next-auth 进行会话验证
2. 文件处理流水线:
- 文件存储(PDF/DOCX/TXT/MD)
- 内容解析(PDFReader/mammoth)
- 文本分块(LangChain 的 RecursiveSplitter)
- 向量化(OpenAI Embedding API)
3. 任务管理:
- 进度跟踪(progressStore)
- 异步处理(processFilesAsync)
- 错误收集与重试机制
// 关键设计特点:
- 采用生产者-消费者模式:API 接收请求后立即返回 taskId,后台异步处理文件
- 使用 Map 实现内存级进度存储(适合单实例部署)
- 支持多文件并行处理(Promise.allSettled)
- 模块化设计(parseFileContent 单独解耦)
// 核心交互流程:
1. 身份验证拦截 → 2. 表单提交 → 3. 文件管理 → 4. sse流式更新进度 → 5. 结果反馈
// 状态管理设计:
- 使用复合状态对象管理进度(percentage + currentStep + errors)
- 加载状态与按钮禁用联动
- 文件列表的增删操作(immutable 模式)
- 错误边界处理(网络错误 + 业务逻辑错误)
// 用户体验优化:
- 拖放文件上传支持
- 实时进度可视化(动态进度条)
- 大文件体积自动转换显示(MB 单位)
- 错误分类展示(全局错误 + 文件级错误)
后端:
Promise.allSettled
保证单个文件失败不影响整体流程前端:
/lib/db
export async function createChunkRecordRaw(data: {
knowledgeBaseId: string;
content: string;
embedding: number[];
}) {
try {
const id = uuidv4();
await prisma.$executeRaw`
INSERT INTO "KnowledgeChunk"
("id", "knowledgeBaseId", "content", "embedding", "createdAt")
VALUES (${id}, ${data.knowledgeBaseId}, ${data.content}, ${data.embedding}::vector, NOW())
`;
return { id, ...data };
} catch (error) {
console.error("创建知识块记录失败:", error);
throw new Error("无法创建知识块记录");
}
}
export async function searchRelevantChunks(
knowledgeBaseIds: string[],
queryEmbedding: number[],
limit: number
) {
return prisma.$queryRaw`
SELECT
kc.id,
kc.content,
kc."knowledgeBaseId",
kc.embedding <=> ${queryEmbedding}::vector as similarity
FROM "KnowledgeChunk" kc
WHERE kc."knowledgeBaseId" IN (${Prisma.join(knowledgeBaseIds)})
ORDER BY similarity ASC
LIMIT ${limit}
`;
}
使用postgres的pgvector插件实现向量的储存,并通过比较余弦相似度来查找知识库中最相关的chunk用以实现检索增强
kc.embedding <=> ${queryEmbedding}::vector as similarity
<=> 用来比较余弦距离,余弦距离=1-余弦相似度
余弦相似度越大,余弦距离越小,两个向量越接近
取前5个最相关的chunk
使用了AI SDK的语言模型中间件
详情见https://sdk.vercel.ai/docs/ai-sdk-core/middleware
大致流程:
用户问题
→ 分类模型
→ 假设答案生成
→ 向量嵌入
→ 知识库检索
→ 增强提示
→ 最终生成
// index.ts 核心配置
export const customModel = wrapLanguageModel({
model: openai("gpt-4o-mini"), // 基础模型
middleware: ragMiddleware, // RAG 增强中间件
});
请求拦截:所有通过 customModel
的请求都会先经过 ragMiddleware
处理
智能消息分类:
// 消息类型判断
const { object: classification } = await generateObject({
model: openai("gpt-4o-mini", { structuredOutputs: true }),
output: "enum",
enum: ["question", "statement", "other"],
system: "classify the user message...",
prompt: lastUserMessageContent,
});
知识检索增强:
// 检索流程
const hypotheticalAnswer = await generateText(...); // 生成假设答案
const hypotheticalAnswerEmbedding = await embed(...); // 生成嵌入向量
const chunks = await searchRelevantChunks(...); // 向量数据库检索
上下文增强:
// 注入知识到提示词
messages.push({
role: "user",
content: [
...originalContent,
{ type: "text", text: "Relevant information..." },
...chunks.map(chunk => ({ type: "text", text: chunk.content }))
]
});