使用说明
# AI知识助理(https://mcp-rag-frontend.vercel.app/)
生产级全栈RAG应用程序,用于上传内部知识文档,用嵌入对其进行索引,用PostgreSQL检索相关块 pgvector,并通过后端工具调用的Gemini聊天体验回答问题。
代码库是围绕干净的架构原则组织的:
- 控制器->服务->存储库流
- 人工智能问题孤立
backend/src/ai - 存储库中隔离的数据库访问
- 集中验证和错误处理
- 使用TanStack Query进行最小但面向生产的前端状态管理
应用程序做什么
- 用户上传
.txt或.md文件 - 后端存储原始文档内容
- 文档被分割成更小的文本段
- 为每个块生成Gemini嵌入
- 块存储在PostgreSQL中
VECTOR(1536)嵌入柱 - 用户在聊天界面中提问
- 后端嵌入问题
- PostgreSQL使用向量相似性搜索返回最相似的块
- Gemini根据检索到的数据块生成答案
- 该模型还可以调用后端工具,如搜索或列出文档
技术栈
前端
- 反应
- 维特
- TypeScript
- Tailwind CSS
- TanStack查询
- 阿西奥斯
后端
- Node.js
- 快速
- TypeScript
- 黄道带
- 皮诺
- Dotenv。
数据库
- PostgreSQL
pgvector
人工智能
- Gemini API通过
@google/genai
托管注意事项
- 标准长时间运行的Node.js后端
- 共享PostgreSQL连接池
- 后台重试工作程序在后端进程内运行
Monorepo布局
.
+- backend/
¦ +- sql/
¦ ¦ +- init.sql
¦ +- src/
¦ +- ai/
¦ +- config/
¦ +- middleware/
¦ +- modules/
¦ ¦ +- chat/
¦ ¦ +- documents/
¦ +- repositories/
¦ +- types/
¦ +- utils/
+- frontend/
¦ +- src/
¦ +- components/
¦ +- hooks/
¦ +- pages/
¦ +- services/
¦ +- types/
+- .env.example
+- README.md
核心架构
后端层
1.控制器
仅限控制器:
- 解析请求输入
- 呼叫服务
- 发送规范化响应
文件夹:
- chat.controller.ts
- documents.controller.ts
2.服务
服务包含业务逻辑:
- 文档上传工作流
- 防止重复
- 重试编排
- RAG请求编排
文件夹:
- chat.service.ts
- 文档.服务.ts
3.仓库
存储库是唯一直接与PostgreSQL对话的层。
文件:
- document.repository.ts
4.AI模块
AI模块是特定于提供商的,目前已在Gemini中实现。
文件夹:
- gemini.client.ts
- embedding.service.ts
- rag.service.ts
- tool.register.ts
5.中间件和实用程序
这些提供了跨领域行为:
- 日志记录
- 集中错误翻译
- 异步路由包装
- 键入的应用程序错误
文件夹:
- error.middleware.ts
- requestLogger.middleware.ts
- apiError.ts
- asyncHandler.ts
前端架构
页面
- 助理Page.tsx
- 主成分层
- 通知/烤面包状态
- 英雄、上传、聊天和索引库的布局
组件
- ChatWindow.tsx
- 流媒体聊天用户界面
- 长对话视口控制
- 自动滚动到最新消息
- 文档上传.tsx
- 上传表面
- 文档列表.tsx
- 分页索引文档轨道
- 重试挂起文档的操作
服务和挂钩
- 文件
- chat.ts
- 使用Documents.ts
TanStack查询用于:
- 分页文档提取
- 跨页面转换的缓存重用
- 上传和重试后无效
数据模型
documents
存储顶级文档元数据和原始源内容。
柱:
idtitlestatusraw_contentcreated_at
document_chunks
使用嵌入存储块级可搜索文本。
柱:
iddocument_idcontentembedding VECTOR(1536)
文档状态模型
每个文档都有两种状态之一:
ready
- 存在嵌入
- 块存在
- RAG可以搜索文档
pending_embeddings
- 原始内容存在
- 块嵌入尚不可用
- 文档尚未可搜索
- 可以手动或自动重试
上传工作流
主要实施于 文档.服务.ts.
步骤
- 验证文件扩展名/MIME类型
- 拒绝二进制内容
- 读取原始文本内容
- 如果存在以下情况,则阻止重复
ready具有相同文件名的文档已存在 - 如果匹配
pending_embeddings文档存在,替换其原始内容并重用该记录 - 将文档另存为
pending_embeddings - 尝试生成Gemini嵌入
- 如果嵌入成功:
- 创建块行
- 将文档标记为
ready
- 如果嵌入失败是因为提供程序不可用:
- 将文档保存在
pending_embeddings - 向用户界面显示友好警告
- 将文档保存在
重试架构
手动重试
用户可以从索引库UI触发重试。
端点:
POST /api/documents/:id/retry
自动重试
后台重试循环在后端进程内运行,并定期尝试处理挂起的文档。
配置人:
EMBEDDING_RETRY_INTERVAL_MS
RAG请求流
实施范围:
- chat.service.ts
- rag.service.ts
步骤
- 验证输入的问题
- 为问题生成双子座嵌入
- 在PostgreSQL中运行向量相似性搜索
- 从检索到的块构建一个固定的提示
- 允许模型决定是否应调用后端工具
- 在服务器端执行任何工具调用
- 通过SSE生成最终答案并将其流式传输到前端
MCP样式工具调用
注册地点:
- chat.tools.ts
当前工具:
search_documentsget_document_by_idlist_documents
工具执行行为:
- 模型提出函数调用
- 后端使用Zod验证参数
- 后端执行工具功能
- 工具输出被添加回提示流中
API表面
健康
GET /health
返回应用程序和数据库就绪状态。
上传文档
POST /api/documents/upload
多部分表单数据:
file
列出文件
GET /api/documents?page=1&pageSize=6
退货:
itemspagepageSizetotalItemstotalPages
按id获取文档
GET /api/documents/:id
重试文档的嵌入
POST /api/documents/:id/retry
聊天
POST /api/chat
请求:
{
"question": "How does JWT refresh token work?"
}
答复:
- 通过服务器发送的事件流式传输
已包含前端用户体验改进
索引库
- 后端分页
- 控制高度和滚动区域
- 待处理文档的重试按钮
- 使用缓存的先前数据进行稳定的页面转换
聊天
- 固定高度视口
- 可滚动对话区
- 流式令牌显示
- 错误感知回退消息
查询缓存
文档分页使用TanStack查询:
placeholderData: keepPreviousDatastaleTime: 30000gcTime: 5 minutes
这提高了感知的响应能力,同时减少了不必要的文档引用。
环境变量
复制 /.env.示例 到 .env.
# Backend
PORT=4000
NODE_ENV=development
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/ai_knowledge_assistant
GEMINI_API_KEY=
GEMINI_CHAT_MODEL=gemini-2.5-flash
GEMINI_EMBEDDING_MODEL=gemini-embedding-001
GEMINI_EMBEDDING_DIMENSION=1536
FRONTEND_ORIGIN=http://localhost:5173
LOG_LEVEL=info
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100
MAX_UPLOAD_SIZE_MB=2
EMBEDDING_RETRY_INTERVAL_MS=60000
# Frontend
VITE_API_URL=http://localhost:4000/api
PostgreSQL和 pgvector
本地
使用PostgreSQL 15+ pgvector 安装。
托管提供商
对于Aiven等提供商,通常需要SSL。
例子:
DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/ai_knowledge_assistant?sslmode=require
数据库引导行为
本地/长时间运行的后端
启动时,后端尝试:
- 创造
vector扩展 - 创建表
- 添加缺失的列
- 创建索引
- 验证模式准备就绪
本地运行
从repo根目录安装依赖项:
npm install
启动后端:
npm run dev --workspace backend
启动前端:
npm run dev --workspace frontend
构建一切:
npm run build
响应形状
成功
{
"success": true,
"data": {}
}
错误
{
"success": false,
"error": {
"message": "",
"code": ""
}
}
错误处理策略
后端将常见故障转换为结构化API错误,包括:
- 验证失败
- 文件上传失败
- 重复的文件名
- 缺少架构
- AI提供商故障
- 旧的待处理文档缺少不可恢复的内容
这使控制器保持精简,并防止原始基础设施错误泄露到响应中。
日志策略
Pino在后端用于:
- 传入请求
- 启动事件
- 工具执行日志
- 重试工作日志
- 错误事件
开发日志打印得很好,便于阅读。
常见操作场景
1.上传成功,但状态为 pending_embeddings
在嵌入生成过程中,AI提供者不可用。系统存储了原始文档,并将允许重试。
2.重试表示需要重新上传
该文档是在引入原始内容存储之前创建的。现在,重新上传同一文件将替换旧的待处理记录。
3.阻止重复上传
A. ready 具有相同文件名的文档已存在。
4.聊天表示未检索到上下文
通常意味着没有 ready 文档尚未嵌入,或者问题在索引块中没有强匹配。
建议的后续改进
- 为上传、重试和聊天流添加自动测试
- 为生产重试编排添加后台作业基础架构
- 添加身份验证和文档所有权
- 在库中添加文档搜索/筛选UI
- 为生产添加容器或传统节点部署目标