每个技术决策都在回答一个问题:为什么选它,而不是选另一个?
项目地址
项目地址:xuxchao/knowledge-quiz: AI 知识问答系统
本项目会分很多期更新,后续还有:
- 从0到1落地AI知识问答系统(一):AI结对协作实战技巧
从0到1落地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 API | OpenAI | 事实标准。通义千问、DeepSeek、Moonshot、智谱 GLM、百度文心、字节豆包全部提供兼容接口 |
| Anthropic Messages API | Anthropic | Claude 原生,格式与 OpenAI 不同 |
| Google Gemini API | Gemini 原生 |
OpenAI 协议能成为事实标准,靠的是先发优势:OpenAI 第一个跑通大模型 API,后来者想抢开发者,最快的方式就是兼容它的接口格式。改一个 baseURL 就能切换模型厂商,业务代码一行不动。
工具集成协议(AI 怎么连外部工具和数据):
| 协议 | 提出方 | 现状 |
|---|---|---|
| Function Calling | OpenAI | 广泛实现,但各厂商格式有差异 |
| 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 SDK 和 langchain 了。
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/vue 的 useChat 把这些全封装了:
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 是 SaaS | CREATE EXTENSION IF NOT EXISTS vector; 一行搞定 |
个人知识库文档量百到千级,分块数万级,pgvector 完全够用。什么时候该换? 向量到百万级、查询延迟明显上升、需要多向量联合查询时再迁。而且从 pgvector 迁出只需导出向量数据,业务数据不动;反过来从 Milvus 迁回 PostgreSQL 就麻烦多了。
四、知识库:文件处理和检索
4.1 第三方上传选择
- 云服务:阿里云,腾讯云 等等,优势,使用方便,维护简单,就是需要花钱
- 开源框架:主要有 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
用户的问题如果是结构化的语义查询和关键词查询都不行的。给你举几个例子体会一下:
- 找出「我不认识,但我朋友认识的人」
- 小说 X 出现了哪些地点
这里用到了 neo4j 来处理,他主要是有极易上手,开源并且带有可视化页面。缺点就是数据量大了不行,对于我这个个人知识库来说刚刚好。
像其他的还有内存型的:graphology,ngraph,graphlib 不符合我要求。还有一些例如 nebula-nodejs,gremlin,他们上手难度高,不过上限也高。我并不想把精力浪费到这上面来
更加精细的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 上下文、模型输出,问题出在哪一步一目了然。
七、记忆系统:Mem0
Mem0 做跨会话语义记忆——让 AI 记住用户偏好和长期目标。但对个人知识库来说没有实际意义:用户量和会话频率都不高,记不记住偏好影响不大。引入它纯粹是个人想了解这套方案怎么工作——怎么从对话提取记忆、怎么做语义检索、怎么管理记忆生命周期。
如果只做能用的知识库问答,PostgreSQL 存会话历史 + Prompt 拼最近几轮对话就够了。Mem0 更适合用户量大、交互频繁的 C 端产品。这节不展开。
写在最后
| 层级 | 选择 | 核心理由 |
|---|---|---|
| LLM 框架 | LangChain | OpenAI 协议通吃国产模型,Loader/Splitter/Graph 开箱即用 |
| 流式通信 | Vercel AI SDK | 事实标准协议,useChat 封装状态管理 |
| 前端 | Vue 3 + Vite + UnoCSS | 熟悉,够用 |
| 数据库 | PostgreSQL + pgvector | 业务数据和向量同库同事务,万级性能足够 |
| 文件处理 | LangChain Loaders + 视觉模型 + ASR | 全格式覆盖,统一转 Markdown 是未来方向 |
| 检索 | ES 关键词 + pgvector 向量 | 双路互补,覆盖精确匹配和语义泛化 |
| 可观测性 | Langfuse | 开源自托管,数据不外传 |
| 记忆 | Mem0 | 了解技术方案为主,当前项目非必须 |
| Graph | neo4j | 处理结构性问题 |
技术选型有一个判断原则:如果你说不清楚为什么不选另一个,那你还没想清楚为什么选这个。
这是系列第二篇。第一篇讲项目复盘,这篇讲架构选型,第三篇深入 RAG 进阶优化——RRF 融合、重排序、邻居扩展、图谱检索、检索质量评估。
交流沟通
如果你想跟我跟更多的人讨论 AI ,全栈相关的知识欢迎加我入群沟通, 记得备注 AI交流 让我知道你的来意
另外找一份 ai 相关的全栈工作,地点不限,欢迎有意向的来沟通