从一句 Hello World 到完整 RAG 系统:一个 AI 知识库的架构选型实录

1 阅读10分钟

每个技术决策都在回答一个问题:为什么选它,而不是选另一个?


项目地址

项目地址:xuxchao/knowledge-quiz: AI 知识问答系统

本项目会分很多期更新,后续还有:

欢迎点赞收藏进群唠嗑(联系方式文章底部)

效果演示

第一篇文章,里面有。这里就不重复了

一、第一步永远是从 Hello World 开始

1.1 最简单的 AI 对话

用 LangChain 调用大模型,核心就这几行:

import { ChatOpenAI } from '@langchain/openai';

const model = new ChatOpenAI({
  apiKey: process.env.QWEN_API_KEY,
  configuration: {
    baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  },
  model: 'qwen-plus',
  temperature: 0.7,
  streaming: true,
});

const stream = await model.stream('hello world');
for await (const chunk of stream) {
  process.stdout.write(chunk.content as string);
}

注意 baseURL 指向阿里云通义千问,但用的是 ChatOpenAI 类——这就是 OpenAI 兼容协议的威力。但真正的项目要管理对话历史、做文件解析、接检索系统、追踪调用链路,这时候选什么框架就关键了。

1.2 为什么要选择 langchain 呢?

我们作为业务方肯定是想支持的大模型语言越多越好,所以我们要知道主流的大模型协议有哪些。

大模型通信协议分两层,决定了你的代码能不能在不同模型间无缝切换。

模型通信协议(应用怎么调模型):

协议提出方地位
OpenAI Chat Completions APIOpenAI事实标准。通义千问、DeepSeek、Moonshot、智谱 GLM、百度文心、字节豆包全部提供兼容接口
Anthropic Messages APIAnthropicClaude 原生,格式与 OpenAI 不同
Google Gemini APIGoogleGemini 原生

OpenAI 协议能成为事实标准,靠的是先发优势:OpenAI 第一个跑通大模型 API,后来者想抢开发者,最快的方式就是兼容它的接口格式。改一个 baseURL 就能切换模型厂商,业务代码一行不动。

工具集成协议(AI 怎么连外部工具和数据):

协议提出方现状
Function CallingOpenAI广泛实现,但各厂商格式有差异
MCP (Model Context Protocol)Anthropic 2024.11,已捐赠 Linux 基金会97M+ 月 SDK 下载,1 万+ 公开服务器,Claude/ChatGPT/Gemini/Copilot/Cursor 全部原生支持

MCP 解决的是"N×M 集成问题":5 个模型接 10 个系统原本要写 50 套对接代码,现在每个系统写一个 MCP Server,所有支持 MCP 的模型都能用。基于 JSON-RPC 2.0,有 Python/TypeScript/C#/Java 四种官方 SDK。

框架选型指标:支持 OpenAI 协议 = 通吃绝大部分模型;支持 MCP = Agent 能接海量工具。

1.3 同类型框架横评

这里目前最火的就是 Vercel AI SDKlangchain 了。

Vercel AI SDK 是纯 ts 框架,并且支持前后端,非复杂项目和学习成本都比较低,我这个项目用它其实完全足够,但我的野心不止于此,后面还想做多 agent,所以没考虑他,而是用了 langchain

另外 LangChain 和 Vercel AI SDK(ai 包)不是竞争而是互补:LangChain 管后端模型调用和检索,AI SDK 管前后端流式通信。@ai-sdk/langchain 桥接包负责格式转换:

LangChain AIMessageChunk → @ai-sdk/langchain 转换 → AI SDK UI Message Stream → HTTP SSE → @ai-sdk/vue useChat

二、前端:Vue + Vite + UnoCSS + @ai-sdk/vue

2.1 为什么是 Vue

两个页面——AI 对话页和文档管理页,选 Vue 3 就一个理由:我本身就是前端开发工程师,对他十分熟悉。Composition API + <script setup> + Pinia + UnoCSS,技术栈标准到没有展开的必要。

2.2 流式输出为什么用 @ai-sdk/vue

流式输出技术上不难——后端 SSE 推数据,前端 fetch + ReadableStream 接收。难的是前后端协调各种卡片,各种状态如何处理:

@ai-sdk/vueuseChat 把这些全封装了:

const { messages, input, handleSubmit, status, error } = useChat({
  api: '/api/conversations/chat',
});
// messages 响应式自动更新,status 管理就绪/流式中/出错

更关键的是,AI SDK 的 UI Message Stream 协议是事实标准。 Vercel v0、ChatGPT Clone 模板、大量 AI 应用都在用。选普及度高的协议,前端组件生态和调试工具都能直接用。项目早期考虑过手写 SSE,但自己维护通信协议要处理重连、错误恢复、状态同步,全是重复劳动。

三、数据库:PostgreSQL + pgvector

3.1 为什么不用 MySQL

PostgreSQL 原生支持向量类型(pgvector),MySQL 不支持。

RAG 系统需要把文档分块转成向量存储和检索。用 MySQL 就得额外引入向量库(Milvus/Pinecone/Weaviate),带来三个问题:

  • 数据一致性:业务数据在 MySQL,向量在 Milvus,一次写入操作两个库。写入成功但向量库失败怎么办?这种跨库一致性是 RAG 最常见的 bug 来源。
  • 幽灵引用:用户删了文档,MySQL 记录删了但向量库的向量还在,AI 照样检索到已删除内容——比报错更危险。
  • 运维成本:多一个组件多一份部署、监控、备份。

PostgreSQL + pgvector 让业务数据和向量在同一个事务里,要么都成功要么都回滚,删除时一条 SQL 连带清理。

3.2 为什么不用专门向量数据库

维度专门向量库(Milvus/Pinecone/Qdrant)pgvector
性能百万级以上优势明显(HNSW/IVF/PQ 专用索引)万级数据毫秒级,够用
代码多一套 SDK + 连接管理 + 索引维护向量操作就是 SQL:ORDER BY embedding <=> $1 LIMIT 30
部署Milvus 依赖 etcd+MinIO,Pinecone 是 SaaSCREATE EXTENSION IF NOT EXISTS vector; 一行搞定

个人知识库文档量百到千级,分块数万级,pgvector 完全够用。什么时候该换? 向量到百万级、查询延迟明显上升、需要多向量联合查询时再迁。而且从 pgvector 迁出只需导出向量数据,业务数据不动;反过来从 Milvus 迁回 PostgreSQL 就麻烦多了。

四、知识库:文件处理和检索

4.1 第三方上传选择

  1. 云服务:阿里云,腾讯云 等等,优势,使用方便,维护简单,就是需要花钱
  2. 开源框架:主要有 rustfs 和 MinIO 这俩种。MinIO 为了维护商业版开源版做了功能阉割

这里有个点就是第三方云服务的对象存储包括 Rustfs 和 MinIO 都支持 S3 存储协议接口。因此都能够很方便的给第三方提供存储服务,例如本项目中用到的 langfuse 默认用的就是 MinIO,这里可以无缝换成 Rustfs

因为是个人知识库项目,大概率是本地运行。所以花钱的不考虑,Rustfs 功能没有阉割成了最好的选择

更加精细的不同文档类型进行解析上传,如何切片处理放到第三篇说明

五、检索:混合检索

5.1 关键词 + 向量,为什么需要两路

Elasticsearch 做关键词查询,pgvector 做语义查询,两路并行各取 Top 30。 因为两者擅长的场景完全不同:

用户问题文档原文关键词搜索向量搜索
"离职流程""离职流程"✅ 精准命中✅ 也能命中
"怎么辞职""离职流程"❌ 无匹配✅ 语义匹配
"HNSW""HNSW 索引原理"✅ 精准命中⚠️ 可能被泛化稀释
"容器自动重启""Pod 自愈机制"❌ 无匹配✅ 语义匹配
"ERR_TIMEOUT""ERR_TIMEOUT 错误处理"✅ 精准命中⚠️ 错误码不是语义实体

一句话总结:关键词搜索抓精确匹配(专有名词、错误码),向量搜索抓语义泛化(不同措辞、口语化提问)。两路各有盲区,合在一起才完整。

5.2 为什么需要 Graph

用户的问题如果是结构化的语义查询和关键词查询都不行的。给你举几个例子体会一下:

  1. 找出「我不认识,但我朋友认识的人」
  2. 小说 X 出现了哪些地点

这里用到了 neo4j 来处理,他主要是有极易上手,开源并且带有可视化页面。缺点就是数据量大了不行,对于我这个个人知识库来说刚刚好。

像其他的还有内存型的:graphology,ngraph,graphlib 不符合我要求。还有一些例如 nebula-nodejs,gremlin,他们上手难度高,不过上限也高。我并不想把精力浪费到这上面来

image.png

更加精细的RAG 策略调优放到第三篇说明

六、可观测性:Langfuse

RAG 调用链路很长:提问 → 向量化 → 双路检索 → 融合排序 → 重排序 → 上下文拼接 → LLM 生成 → 流式返回。中间任何一步出问题,表现都是"AI 回答不好",没链路追踪只能靠猜。

为什么选 Langfuse 而不是 LangSmith? LangSmith 是 LangChain 官方平台,集成度最高,但没有开源自托管版本——只能用 SaaS,数据传到他们服务器。知识库文档和用户对话含敏感数据,不可接受。

Langfuse 开源,Docker Compose 自托管,数据在本地 PostgreSQL + ClickHouse。@langfuse/langchain 包让 LangChain 回调自动上报,不改业务代码:

const model = new ChatOpenAI({
  model: 'qwen-plus',
  callbacks: langfuseService.getLangChainCallbacks(), // 自动追踪
});

用户反馈"回答不对"时,打开 Langfuse 面板就能看到完整链路:召回了哪些分块、各自分数、最终 Prompt 上下文、模型输出,问题出在哪一步一目了然。

image.png


七、记忆系统:Mem0

Mem0 做跨会话语义记忆——让 AI 记住用户偏好和长期目标。但对个人知识库来说没有实际意义:用户量和会话频率都不高,记不记住偏好影响不大。引入它纯粹是个人想了解这套方案怎么工作——怎么从对话提取记忆、怎么做语义检索、怎么管理记忆生命周期。

如果只做能用的知识库问答,PostgreSQL 存会话历史 + Prompt 拼最近几轮对话就够了。Mem0 更适合用户量大、交互频繁的 C 端产品。这节不展开。


写在最后

层级选择核心理由
LLM 框架LangChainOpenAI 协议通吃国产模型,Loader/Splitter/Graph 开箱即用
流式通信Vercel AI SDK事实标准协议,useChat 封装状态管理
前端Vue 3 + Vite + UnoCSS熟悉,够用
数据库PostgreSQL + pgvector业务数据和向量同库同事务,万级性能足够
文件处理LangChain Loaders + 视觉模型 + ASR全格式覆盖,统一转 Markdown 是未来方向
检索ES 关键词 + pgvector 向量双路互补,覆盖精确匹配和语义泛化
可观测性Langfuse开源自托管,数据不外传
记忆Mem0了解技术方案为主,当前项目非必须
Graphneo4j处理结构性问题

技术选型有一个判断原则:如果你说不清楚为什么不选另一个,那你还没想清楚为什么选这个。

这是系列第二篇。第一篇讲项目复盘,这篇讲架构选型,第三篇深入 RAG 进阶优化——RRF 融合、重排序、邻居扩展、图谱检索、检索质量评估。


交流沟通

如果你想跟我跟更多的人讨论 AI ,全栈相关的知识欢迎加我入群沟通, 记得备注 AI交流 让我知道你的来意

微信二维码.jpg

另外找一份 ai 相关的全栈工作,地点不限,欢迎有意向的来沟通