用 FastAPI 搭建一个 RAG 后端需要哪些接口?
码海寻道 · 大模型、智能体与 RAG 工程组件系列第 41 篇

一个 RAG 后端不只是一个 /chat 接口。要支持文件上传、异步解析、知识库管理、问答、引用、任务状态和权限控制,应该先设计清楚资源和接口边界,再接入模型与向量数据库。
一、先按资源拆接口
text
认证与用户
├── /api/v1/auth
知识库
├── /api/v1/knowledge-bases
文档
├── /api/v1/documents
任务
├── /api/v1/jobs
问答
├── /api/v1/chat
└── /api/v1/conversations
资源型 API 负责管理事实和状态,Agent、RAG 和大模型属于服务内部的执行链路,不应把所有业务都塞进一个路由函数。
二、推荐的核心接口
接口设计先固定资源关系,再决定具体路径。生产环境建议使用 /api/v1 这类版本前缀,并为每次请求生成 request_id 或沿用上游 trace_id。接口版本、错误码和响应字段一旦被前端使用,就应通过兼容期和变更记录管理,不能只靠修改路由代码。
知识库
http
POST /api/v1/knowledge-bases
GET /api/v1/knowledge-bases
GET /api/v1/knowledge-bases/{kb_id}
PATCH /api/v1/knowledge-bases/{kb_id}
DELETE /api/v1/knowledge-bases/{kb_id}
文档
http
POST /api/v1/knowledge-bases/{kb_id}/documents
GET /api/v1/knowledge-bases/{kb_id}/documents
GET /api/v1/documents/{document_id}
DELETE /api/v1/documents/{document_id}
POST /api/v1/documents/{document_id}/reindex
任务
http
GET /api/v1/jobs/{job_id}
POST /api/v1/jobs/{job_id}/cancel
POST /api/v1/jobs/{job_id}/retry
对话
http
POST /api/v1/chat/completions
GET /api/v1/conversations/{conversation_id}
DELETE /api/v1/conversations/{conversation_id}
三、FastAPI 路由层应该做什么?
路由层负责:
- 解析请求和响应模型;
- 鉴权和租户上下文;
- 参数校验;
- 调用应用服务;
- 返回统一错误和状态码。
不建议在路由函数中直接写 Embedding、Milvus 和复杂事务逻辑:
python
@router.post("/chat/completions")
async def chat(request: ChatRequest, user: CurrentUser = Depends(get_user)):
scope = await permission_service.scope_for(user)
return await rag_service.answer(request, scope)
业务逻辑放在 rag_service,路由更容易测试和替换。
四、请求和响应模型
python
from pydantic import BaseModel, Field
class ChatRequest(BaseModel):
knowledge_base_id: str
message: str = Field(min_length=1, max_length=8000)
conversation_id: str | None = None
top_k: int = Field(default=5, ge=1, le=20)
stream: bool = False
class Citation(BaseModel):
document_id: str
title: str
page: int | None = None
snippet: str
class ChatResponse(BaseModel):
answer: str
citations: list[Citation]
trace_id: str
请求模型还应限制字符串长度、分页上限、top_k、模型名称和过滤条件。不要把向量过滤表达式、tenant_id 或权限字段直接交给客户端;这些字段由认证上下文和服务端策略生成。对于创建文档、提交任务和发送消息的接口,支持 Idempotency-Key,避免网络重试造成重复任务。
客户端传来的 top_k 仍需受到服务端上限约束,不能让用户任意扩大召回和模型上下文成本。
五、文件上传不要同步解析
接口只完成:
- 校验文件和用户权限;
- 保存原始文件到对象存储;
- 写入 PostgreSQL 文档记录;
- 创建任务并发布消息;
- 返回
202 Accepted和job_id。
python
@router.post("/documents", status_code=202)
async def upload_document(
file: UploadFile,
kb_id: str,
user: CurrentUser = Depends(get_user),
):
document = await document_service.create_upload(kb_id, file, user)
return {"document_id": document.id, "job_id": document.job_id}
解析、OCR、切分和索引由异步 Worker 完成,前端通过任务接口查看进度。
上传接口的成功只表示"文件已接收并创建任务",不表示文档已经可检索。响应中应返回 document_id、upload_id、job_id、当前状态和查询地址;任务状态由数据库维护,Worker 通过版本号或乐观锁更新,避免旧任务把新版本覆盖回去。
六、统一错误格式
json
{
"error": {
"code": "DOCUMENT_NOT_READY",
"message": "文档仍在解析中",
"trace_id": "trace-001",
"details": null
}
}
错误码供前端和监控使用,展示文案可以由前端根据语言环境转换。不要把数据库堆栈、密钥和内部路径直接返回给用户。
错误还应区分客户端可修正、权限拒绝、依赖暂时不可用和服务端未知异常。对 429、503 等暂时性错误返回 retry_after;对流式接口已经发送部分内容后的失败,则通过事件发送最终 error 或 cancelled 状态,并把运行结果持久化。
七、RAG 问答接口内部流程
text
请求进入
↓ 鉴权与租户范围
↓ 加载会话上下文
↓ 查询改写
↓ Embedding
↓ Milvus / pgvector 检索
↓ Reranker 与去重
↓ Prompt 组装
↓ 大模型生成
↓ 引用回查与审计
每一步应有 trace_id 和耗时记录,便于区分是检索慢、模型慢还是数据库回查慢。
八、同步响应还是流式响应?
短回答可以返回 JSON;长回答和聊天体验可以提供 SSE。建议保持同一个业务接口的请求语义,使用 stream=true 选择响应方式,或提供独立的 /chat/stream 接口。
流式接口仍要保存最终消息和引用,不能因为客户端断开就丢失服务端事实。
接口层还要明确取消语义:客户端断开、主动点击停止和服务端超时不是同一种状态。取消请求应沿 run_id 传播到模型、检索、工具和异步任务;无法立即中断的下游调用,要标记为 cancelling 并由后台完成收尾。
九、接口安全清单
- 所有资源接口都校验用户和租户范围;
- 文件上传检查大小、类型、哈希和恶意内容;
- 问答接口限制消息长度、Top K 和模型预算;
- 数据库和向量过滤由服务端生成;
- 写操作具备幂等键;
- 长任务返回 job_id,不阻塞请求;
- 错误响应不泄露内部信息;
- 记录 trace_id、审计和调用成本。
结语
FastAPI RAG 后端应该围绕知识库、文档、任务和对话设计资源接口,把路由、业务服务、异步 Worker 和模型链路分层。接口先稳定,内部组件才有替换和扩展空间。
下一篇将转向前端,比较 Vue、React 和 Next.js 如何设计 AI 应用交互。
参考资料
本文为"码海寻道"原创技术文章。FastAPI、Pydantic 和相关 SDK 会随版本变化,正式项目请以目标版本文档为准。