作者多年交付文档密集型数字服务的经验总结,讲解如何构建provider无关的自托管RAG平台,涵盖LangChain集成、生产级注意事项。
问题所在:文档无法自行回答问题
我合作过的每一个组织——公共部门、受监管的企业,或其他——都存在一个同样的隐性瓶颈:信息被锁在 PDF、DOCX 文件和扫描报告里,没有人有时间完整阅读。一个社工需要从 40 页的政策文件中找到某个条款。一名分析师需要从合规报告第 22 页找出埋在里面的一条数字。文档技术上"存在",也可以"获取",但实际上根本无法检索。
我从事大型文档密集型数字服务的交付工作多年,在各个部门和行业中不断看到这种模式:团队要么为一款闭源的文档 AI 产品付费——定价模式模糊、数据驻留存疑——要么就靠人工搜索勉强对付。
所以我构建了 AI-DocumentIntelligence——一个开源、可自托管、提供商无关的 RAG(检索增强生成)平台,用于文档上传、处理和自然语言问答。我的目标不是再套一层 API 包装,而是构建一个我愿意放心推荐给任何处理敏感文档的交付团队使用的东西:透明、可切换、可审计。
解决方案:上传、分块、嵌入、提问
应用核心做四件事:
摄取 PDF、DOCX 或 TXT 文档
将文档拆分为语义上有意义的块
使用 pgvector 将这些块嵌入 PostgreSQL
使用 OpenAI 或 Anthropic Claude 回答关于文档内容的自然语言问题,并引用来源块——通过单个环境变量切换
最后一点比听起来更重要。大多数教程级的 RAG 项目硬编码了单一的 LLM 提供商。在真实采购场景中——任何受监管的部门或企业——这是不可接受的——你需要能够为了成本、合规或可用性原因切换提供商,而不需要重写代码。所以 LLM_PROVIDER 是一个配置标志,而不是代码深处埋着的架构决策。
我刻意将技术栈保持在一个"无聊"但最好的意义上——这里没有任何东西需要团队去学习新的部署范式。如果你的组织已经能运行 Node.js 服务和 PostgreSQL 数据库,你就能运行这个。
┌────────────┐ ┌──────────────────┐ ┌────────────────────┐
│ React UI │ ───► │ Express API │ ───► │ Document Processor │
│ (upload+chat)│ │ (TypeScript) │ │ (chunking / split) │
└────────────┘ └──────────────────┘ └────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌────────────────────┐
│ LangChain Layer │ ◄──► │ PostgreSQL + pgvector│
│ (OpenAI / Claude) │ │ (embeddings store) │
└──────────────────┘ └────────────────────┘
分块和嵌入管道在每次上传时运行一次。查询时间是标准的检索后生成流程:嵌入问题、对 pgvector 做相似性搜索、将 top-k 块连同 grounded prompt 传给选定的 LLM,返回答案以及该文档的聊天历史。
没什么花哨的东西。这是刻意的——价值不在于一个巧妙的新架构,而在于把那些"无聊"的部分(提供商抽象、分块质量、凭证缺失的错误处理)真正做对。
代码走读(有价值的部分,不是整个仓库)
切换 LLM 提供商只需要读配置,不需要在三个文件深处埋 if-else:
// backend/src/llm/provider.ts (simplified)
const provider = process.env.LLM_PROVIDER; // "openai" | "anthropic"
export function getChatModel() {
if (provider === "anthropic") {
return new ChatAnthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
model: process.env.ANTHROPIC_MODEL,
});
}
return new ChatOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
}
分块使用 LangChain 的递归分割器,针对文档问答而非原始摄取做了调优:
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 150,
});
const chunks = await splitter.splitDocuments(rawDocs);
重叠部分比大多数教程承认的更重要——太少,答案会在块边界被截成两半;太多,你又为同一句话付了三次嵌入和搜索的费用。在 1000 字符的块上用 150 重叠,是在混合 PDF/DOCX 测试集上表现最均衡的选择。
检索和生成保持分离,所以任一半部分都可以独立测试——我可以在不消耗 API 额度做生成的情况下验证检索是否返回了正确的块,这在迭代未付费 OpenAI 套餐时非常关键。
从第一天起就保持 LLM 提供商可切换,这立即得到了回报——我在接入付费 API 之前就已经用本地嵌入路径构建和测试了大部分检索逻辑,将迭代成本保持在接近零。
将分块/嵌入与查询路径分离使调试变得可控。当某个结果看起来不对时,我可以在不到一分钟内定位到是检索问题还是生成问题,而不是靠猜。
整个技术栈(后端 + 前端 + Postgres)的 Docker Compose 意味着我可以将仓库交给别人,让他们在几分钟内跑起来,而不是花一个下午做依赖考古。
真正的难点:
凭证受限的开发。在没有付费 OpenAI/Claude 套餐的情况下构建 RAG 系统是一个真实的约束,不是脚注。我最终是在接入付费生成调用之前,先用本地免费嵌入模型验证了检索管道——这个模式我现在推荐给任何在预算内原型化 RAG 的人,无论属于哪个领域。
块边界调优虽然不光鲜,但决定性最高。再多的 prompt 工程也救不了一个糟糕的分块。我早期低估了这一点,而它正是整个项目中杠杆效应最大的修复。
"在我机器上能跑"和"别人执行 docker compose up 能跑"是两个不同的标准。要达到后者——清晰的 .env.example、明确的 DB_NAME 预期、有文档记录的端口冲突回退方案——比 RAG 逻辑本身花的时间还长,而这恰恰是决定别人是否能真正使用这个项目的关键。
这在更大的图景中处于什么位置
我正在围绕一小部分开源 AI 工程项目构建这个——面向公民服务的政策问答 RAG 系统、一个 agentic PR-review 机器人、一个语音转敏捷用户故事的生成器——所有项目都反映了同一条主线:将 LLM 编排(LangChain/LangGraph)应用于我在大规模数字交付中遇到的真实工作流问题,而不是玩具级演示。如果"生产交付经验 + 开源 AI 工程应用"这个组合对任何在评估这些工作的人有用,那这正是我发布的意义所在。
仓库是开源的,只需几条命令即可本地开发:
git clone https://github.com/Srameshgitnow/AI-DocumentIntelligence.git
cd AI-DocumentIntelligence
docker compose up --build
完整的安装说明、环境变量和故障排查在 README 中。
如果你试用了、遇到了 bug,或者对提供商抽象方案有想法,我非常希望能听到你的意见——issues 和 PR 都开放。如果这个项目对你有用,给仓库点个 ⭐ 对帮助其他人发现它大有帮助:
👉 github.com/Srameshgitnow/AI-DocumentIntelligence
我是一名全栈/AI 工程师(React、Node.js、LangChain/LangGraph),有大规模数字服务交付背景。我写关于应用 AI 工程和开源工具的文章——关注这个系列的后续文章。