知识问答助手

一个全栈RAG应用,用于上传内部知识文档,生成嵌入向量,通过向量搜索检索相关内容,并通过Gemini驱动的聊天体验回答问题。

  • by ThanoojKhan
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 ThanoojKhan/MCP-RAG

使用说明

# AI知识助理(https://mcp-rag-frontend.vercel.app/)

生产级全栈RAG应用程序,用于上传内部知识文档,用嵌入对其进行索引,用PostgreSQL检索相关块 pgvector,并通过后端工具调用的Gemini聊天体验回答问题。

代码库是围绕干净的架构原则组织的:

  • 控制器->服务->存储库流
  • 人工智能问题孤立 backend/src/ai
  • 存储库中隔离的数据库访问
  • 集中验证和错误处理
  • 使用TanStack Query进行最小但面向生产的前端状态管理

应用程序做什么

  1. 用户上传 .txt 或 .md 文件
  2. 后端存储原始文档内容
  3. 文档被分割成更小的文本段
  4. 为每个块生成Gemini嵌入
  5. 块存储在PostgreSQL中 VECTOR(1536) 嵌入柱
  6. 用户在聊天界面中提问
  7. 后端嵌入问题
  8. PostgreSQL使用向量相似性搜索返回最相似的块
  9. Gemini根据检索到的数据块生成答案
  10. 该模型还可以调用后端工具,如搜索或列出文档

技术栈

前端

  • 反应
  • 维特
  • 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

存储顶级文档元数据和原始源内容。

柱:

  • id
  • title
  • status
  • raw_content
  • created_at

document_chunks

使用嵌入存储块级可搜索文本。

柱:

  • id
  • document_id
  • content
  • embedding VECTOR(1536)

文档状态模型

每个文档都有两种状态之一:

ready

  • 存在嵌入
  • 块存在
  • RAG可以搜索文档

pending_embeddings

  • 原始内容存在
  • 块嵌入尚不可用
  • 文档尚未可搜索
  • 可以手动或自动重试

上传工作流

主要实施于 文档.服务.ts.

步骤

  1. 验证文件扩展名/MIME类型
  2. 拒绝二进制内容
  3. 读取原始文本内容
  4. 如果存在以下情况,则阻止重复 ready 具有相同文件名的文档已存在
  5. 如果匹配 pending_embeddings 文档存在,替换其原始内容并重用该记录
  6. 将文档另存为 pending_embeddings
  7. 尝试生成Gemini嵌入
  8. 如果嵌入成功:
    • 创建块行
    • 将文档标记为 ready
  9. 如果嵌入失败是因为提供程序不可用:
    • 将文档保存在 pending_embeddings
    • 向用户界面显示友好警告

重试架构

手动重试

用户可以从索引库UI触发重试。

端点:

  • POST /api/documents/:id/retry

自动重试

后台重试循环在后端进程内运行,并定期尝试处理挂起的文档。

配置人:

  • EMBEDDING_RETRY_INTERVAL_MS

RAG请求流

实施范围:

  • chat.service.ts
  • rag.service.ts

步骤

  1. 验证输入的问题
  2. 为问题生成双子座嵌入
  3. 在PostgreSQL中运行向量相似性搜索
  4. 从检索到的块构建一个固定的提示
  5. 允许模型决定是否应调用后端工具
  6. 在服务器端执行任何工具调用
  7. 通过SSE生成最终答案并将其流式传输到前端

MCP样式工具调用

注册地点:

  • chat.tools.ts

当前工具:

  • search_documents
  • get_document_by_id
  • list_documents

工具执行行为:

  • 模型提出函数调用
  • 后端使用Zod验证参数
  • 后端执行工具功能
  • 工具输出被添加回提示流中

API表面

健康

GET /health

返回应用程序和数据库就绪状态。

上传文档

POST /api/documents/upload

多部分表单数据:

  • file

列出文件

GET /api/documents?page=1&pageSize=6

退货:

  • items
  • page
  • pageSize
  • totalItems
  • totalPages

按id获取文档

GET /api/documents/:id

重试文档的嵌入

POST /api/documents/:id/retry

聊天

POST /api/chat

请求:

{
  "question": "How does JWT refresh token work?"
}

答复:

  • 通过服务器发送的事件流式传输

已包含前端用户体验改进

索引库

  • 后端分页
  • 控制高度和滚动区域
  • 待处理文档的重试按钮
  • 使用缓存的先前数据进行稳定的页面转换

聊天

  • 固定高度视口
  • 可滚动对话区
  • 流式令牌显示
  • 错误感知回退消息

查询缓存

文档分页使用TanStack查询:

  • placeholderData: keepPreviousData
  • staleTime: 30000
  • gcTime: 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 文档尚未嵌入,或者问题在索引块中没有强匹配。

建议的后续改进

  1. 为上传、重试和聊天流添加自动测试
  2. 为生产重试编排添加后台作业基础架构
  3. 添加身份验证和文档所有权
  4. 在库中添加文档搜索/筛选UI
  5. 为生产添加容器或传统节点部署目标