专注AI 实践 × 3D 开发

LangChain+FastAPI从零构建RAG问答Agent

最近我从空目录开始,搭了一个能真正跑起来的 RAG 问答 Agent。 Vue 3 做聊天页面,FastAPI 接住请求,LangChain 和 LangGraph 处理模型调用与流程,Chroma、PostgreSQL、Redis 和腾讯云 COS 各管一摊。

FIG. 01 手写框架,AI 提效,人工 Review

做到后面,登录鉴权、联网判断、知识库检索、连续对话和 SSE 流式输出都加了进来。我的习惯是先手写骨架,把边界和主链路跑通; 重复代码再交给 AI 提速。生成结果不会直接合并,我会逐项 Review。

01框架自己搭关键链路亲自跑通

02AI 帮忙提速处理重复编码工作

03人工 Review代码合并前必须看过

02 · PROJECT OVERVIEW

一个问题,背后是一整套系统

这是一个移动端金融知识问答 H5。用户登录后可以接着上一轮继续问。 普通问题先查私有 PDF;碰到股价、政策、新闻这类有时效性的内容, 再走联网搜索。答案通过 SSE 一段段回到聊天窗口。

前端Vue 3 · TypeScript · Vite聊天界面与流式渲染
APIFastAPI鉴权、路由与生命周期
AgentLangChain · LangGraph模型能力与流程编排
RAGChroma · Embeddings向量化与证据召回
数据PostgreSQL · Redis · COS用户、缓存与文件
部署Docker Compose · Nginx编排、检查与代理
FIG. 02 一个问题如何穿过整个系统
03 · SERVICE ARCHITECTURE

FastAPI:Agent 后端必须有一层可靠的服务

项目还小,两层就够了。Controller 负责路由、鉴权、数据校验和请求处理; Service 写业务逻辑,通过 ORM 访问数据库。Repository 以后真有需要再加, 现在硬塞一层,只会让找代码变慢。

01ControllerRoute · Validate · Request · Response
02ServiceBusiness · ORM · Agent · Storage
FIG. 03 简单项目,两层已经足够清晰

lifespan 与存储生命周期

应用启动时连接 Redis、PostgreSQL 和 COS,退出时按顺序释放。 每个请求拿到自己的 PostgreSQL Session:成功就提交,报错就回滚, 用完关闭。业务代码只管业务,不必到处复制连接和清理逻辑。

FIG. 04 启动时连接,关闭时完整释放

统一异常、拆分路由、SSE 输出

参数错了、业务失败了,或者遇到没预料到的异常,接口都返回同一种结构。 用户、状态、会话各自放在独立路由里。流式接口只转发 LangGraph 的 answer 消息,最后补一个 [DONE]

07 · UNDER THE HOOD

有了 AI 编程,为什么还要学技术细节?

骨架跑通以后,登录接口、类型定义、测试样例这些重复工作,交给 AI 确实省时间。但生成的代码不会直接进入主分支,业务逻辑、异常路径和数据边界, 还得自己过一遍。

AI 很快就能给出一份“看起来能跑”的实现,却不知道这个项目真正的边界: 会话应该怎样隔离,接口需要兼容什么,哪条权限规则绝不能放松。 它看到的是当前提示词和有限上下文,程序员面对的是整个系统和真实用户。

FIG. 13 没有人工 Review,AI 编程很容易越跑越偏

人工 Review 也不只是检查缩进和命名。它要确认代码会不会串用户、丢数据、 越过权限边界,服务重启后能不能恢复。问题真的发生时,还得有人顺着日志、 请求、状态和数据一路定位根因,而不是让 AI 一轮轮碰运气。

会话为什么不能串用户?检索结果不相关怎么办?SSE 断开如何恢复?实时问题该查网络吗?多实例如何共享状态?权限校验漏了怎么办?数据库异常如何回滚?容器启动时依赖就绪吗?

这个项目里,至少有四个坑需要人来判断

01 会话隔离thread_id 如果只按会话生成,没有绑定当前用户, 两个人可能读到同一份对话状态。代码可以正常运行,结果却是严重的数据越权。

02 检索质量 模型回答不对,未必是 Prompt 写差了。PDF 分块过碎、Embedding 不合适或 Top K 设置失衡,都可能让模型拿到一堆无关证据。

03 流式边界 一次网络读取不等于一个完整 SSE 事件。前端如果直接解析当前分片, 中文、JSON 或 [DONE] 都可能被截成两半。

04 运行状态MemorySaver 本地调试很好用,放到多实例环境就会丢失共享状态。 容器显示“已启动”,也不代表 PostgreSQL 和 Redis 已经可以接请求。

FIG. 11 AI 显示通过,不代表代码真的可以上线

AI 像一台干活很快的施工机器,复制、改写、补样板代码都很拿手。 但房间怎么分、承重墙在哪,它替你做不了主。HTTP、异步、数据库、 鉴权、向量检索和状态管理这些细节,最后都落在三件事上:判断、验收和排错。

01判断决定系统应该怎么拆
02验收Review 自动生成的实现
03排错快速定位问题在哪一层

不懂细节时,调试往往只剩一句:“还是不对,再改一下。” 懂一点底层,你会先去看流式响应有没有拆坏、检索召回的数据是否相关, 再决定该改代码、检索参数还是 Prompt。同一个 AI,用法一下就拉开了差距。

这也是现阶段程序员仍然难被替代的部分。敲代码只占工作的一部分, 剩下的是理解模糊需求、权衡成本与风险、为上线结果负责,并在故障发生后把系统救回来。 AI 能放大编码速度,但谁来判断“什么代码应该上线”,答案目前仍然是人。

FIG. 08 自动生成以后,真正的工作是验证
EXTRA · PLATFORM OR FRAMEWORK

有了 Coze、Dify,为什么还要学习 LangChain?

Coze、Dify 很适合把想法迅速做出来。界面可视化,知识库和插件也准备好了, 非程序员同样能参与配置。很多标准场景,用它们更省事。

如果需求只是上传资料、设置提示词、发布机器人,低代码平台通常就是更好的选择。 一旦检索规则、用户状态或业务系统需要深度定制,就会碰到平台的边界。 这时 LangChain 的价值才出来:每个环节都能用代码接管。

FIG. 10 标准场景快速上线,定制场景自由组合
COZE / DIFY像点外卖

  • 验证想法速度快
  • 常用能力开箱即用
  • 可视化配置门槛低
  • 适合标准化场景
LANGCHAIN像自己下厨

  • 检索策略可自定义
  • 状态与用户体系打通
  • 模型和向量库可替换
  • 适合深度业务集成

Coze、Dify 像点外卖:打开菜单,选好套餐,下单没多久就能吃。 LangChain 更像自己下厨,模型、检索、记忆和工具都是手边的食材, 先放什么、放多少、怎么出锅都能改。只是想快速吃顿饭,就别急着装修厨房; 客户开始要求“少盐、免辣、不要香菜,还得分桌上菜”时, 能自己掌勺就很重要了。

05 · LANGCHAIN & RAG

RAG 到底解决了什么问题?

模型没看过我们的私有资料,也可能把不知道的内容说得很像真的。 把整本 PDF 塞进 Prompt 又贵又笨,上下文窗口也未必装得下。

FIG. 06 不把整本资料塞给模型,只取与问题有关的证据
01清洗资料并切成适合检索的小块
02用 Embedding 把文本变成向量
03根据问题召回最相关的内容
04让模型结合证据生成回答
05保留文件名和页码,方便核查

当前项目使用 PyPDFLoader 读取 PDF,清理多余空白并跳过过短页面, 再用 RecursiveCharacterTextSplitter 切块。每块默认 700 个字符,重叠 80 个字符,尽量在段落和中文标点处分割。

用户提问时,系统从 Chroma 召回最相关的 3 个文本块,并过滤低相关内容。 模型收到问题时,还会一起拿到三份候选证据。回答里保留资料编号, 用户至少能顺着出处回去核对,不必只凭一句“听起来挺对”。

04 · FROM QUESTION TO ANSWER

LangGraph:把 Agent 的脑回路拆开

FIG. 05 状态沿节点依次流转
Vue 3FastAPILangGraphChroma / WebSSE

LangGraph:把脑回路拆成节点

当前 Agent 的主流程是 decide_search → retrieve → answerdecide_search 判断要不要查实时信息, retrieve 找回本地资料,answer 拿着联网结果或知识库上下文组织回答。

以后要加问题改写、多路召回、结果重排、敏感内容检查或人工确认, 就继续加节点。否则这些分支迟早会挤进同一个函数,最后只剩一大团 if...else

FastAPI:把能力包装成服务

FastAPI 负责 JWT 鉴权、应用生命周期、普通聊天接口和 SSE 流式接口。服务启动时初始化 Redis、PostgreSQL 和 COS,结束时统一释放连接。 流式接口只转发 LangGraph 中 answer 节点产生的消息片段。

Vue 3:接住后端吐出的每一个字

前端通过 ReadableStream 持续读取 SSE 数据,将网络片段写入 buffer,再按事件边界解析。第一段文字到了就创建助手消息,后面的内容继续往上追加, 读到 [DONE] 才算收完。

FIG. 09 网络片段不等于完整 SSE 事件
06 · SHIP IT SAFELY

Docker 部署:按正确顺序启动

01服务健康检查PostgreSQL 使用 pg_isready,Redis 使用 ping,避免 API 抢跑。
02自动数据库迁移容器先执行 alembic upgrade head,再启动 Uvicorn。
03环境配置隔离本地使用 localhost,容器通过 Compose 服务名互相通信。
04Nginx 统一入口提供前端静态资源,并将 /api 转发到 FastAPI。

阿里云 ECS:用 Docker Compose 部署整套服务

这套服务通过 docker-compose.yaml 部署在阿里云 ECS。 服务器访问 Docker Hub 偶尔会超时,所以远程镜像统一切到了国内镜像源: Python、Node、Nginx、PostgreSQL 和 Redis 都通过 DaoCloud 镜像地址拉取;APT、APK、PyPI 和 npm 也分别配置了国内软件源。 这一步不改变镜像内容,主要是避免部署卡在 pull 或依赖下载阶段。

FIG. 07 一条 Compose 命令背后,还有镜像、检查与启动顺序
08 · SUMMARY

最后,做个总结

这个项目并不复杂,但一条完整的问答链路已经齐了:FastAPI 管接口和资源生命周期,LangGraph 管状态与流程,LangChain 把模型、检索和工具接起来,Docker 负责让整套服务按顺序启动。

FIG. 12 AI 负责提速,人负责把整条链路接对

先把框架写明白。 Controller 和 Service 各做什么,数据怎样流动,状态放在哪里,这些边界需要先定下来。

再让 AI 处理重复工作。 类型定义、样板接口和测试代码可以提速,但业务逻辑、异常路径和数据安全仍要人工 Review。

工具按场景选。 标准需求用 Coze、Dify 更快;需要定制检索、状态和业务流程时,再用 LangChain 接管细节。

真正值得带走的,是对整条链路的判断力。知道问题出在接口、状态、检索还是模型, 下一次换框架、换模型,仍然知道该从哪里下手。