手搓 FastAPI + Redis + 流式 RAG 实录

提示:学吧学吧,一学一个不吱声~

先说好哈,本文是玩的,用到的部分技术是轻量级或实验性的,只是提供一个架构思路。


前言

AI 开发不仅仅是"调个大模型 API"。

带着问题:怎么保证高并发?怎么解决大模型没记忆?怎么解决幻觉?怎么提升用户体验?

基于 FastAPI、Redis 和 DeepSeek,从零手搓一个带多轮记忆 与 RAG(检索增强生成) 的流式后端服务

一、怎么不直接调用API?

1、无状态:模型本身没有记忆,不传历史记录根本不知道上一句聊了什么

2、高延迟:生成一段回复可能需要10秒,要全部生成后再返回,反正我早把页面关了

3、幻觉:问自己公司报销制度,他可能一本正经从网上编一个回复你,你受得了?
不好意思,FastAPI(异步流式底座) + Redis(记忆管理) + RAG(私有知识约束)就可以解决。

二、环境准备(Docker 跑起 Redis)

1.多轮对话,保存历史记录。启动 Redis(基于 Docker)

先安装Docker。装好后,在终端执行一行命令拉取并启动 Redis:

复制代码
docker run -d --name my-redis -p 6379:6379 redis:latest

配合 uvicorn app:app --reload 启动,自带 Swagger UI,体验感十足。

2.安装依赖

复制代码
pip install fastapi uvicorn openai redis httpx

3.接入大模型与redis

搭建基础的通信管道,因为要实现高并发流式输出,使用异步客户端 AsyncOpenAI

复制代码
import json
import httpx
import redis
from fastapi import FastAPI
from pydantic import BaseModel
from openai import AsyncOpenAI
from fastapi.responses import StreamingResponse

app = FastAPI()

# 1. 连接本地 Docker 里的 Redis
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)

# 2. 初始化异步大模型客户端
client = AsyncOpenAI(
    api_key="你的DeepSeek_API_KEY", 
    base_url="https://api.deepseek.com",
    timeout=httpx.Timeout(300.0, connect=10.0),
)

# 3. 定义前端传参的格式(相当于 Java 里的 DTO)
class ChatRequest(BaseModel):
    user_id: str
    message: str

4.实现多轮对话

大模型的本质是"无状态"的。所谓记忆,就是每次请求时,你把之前的聊天记录拼成一个列表发给它。接着写

复制代码
# 4. 核心生成器:处理记忆和流式输出
async def chat_stream_generator(request: ChatRequest):
    # 4.1 从 Redis 读取该用户的历史记录
    redis_key = f"chat_history:{request.user_id}"
    try:
        history_str = r.get(redis_key)
        messages = json.loads(history_str) if history_str else []
    except Exception as e:
        print(f"Redis 读取失败: {e}")
        messages = [] # 容错降级

    # 4.2 将用户的新问题加入列表
    messages.append({"role": "user", "content": request.message})

    # 4.3 异步请求大模型,开启 stream=True
    stream = await client.chat.completions.create(
        model="deepseek-chat", # 或 deepseek-v4-pro
        messages=messages,
        stream=True,
    )

    full_reply = ""
    # 4.4 边接收,边吐给前端(打字机效果)
    async for chunk in stream:
        if chunk.choices and chunk.choices[0].delta.content:
            content = chunk.choices[0].delta.content
            full_reply += content
            yield content

    # 4.5 对话结束,把 AI 的回答也加入列表,存回 Redis
    messages.append({"role": "assistant", "content": full_reply})
    try:
        r.set(redis_key, json.dumps(messages, ensure_ascii=False), ex=3600) # 1小时过期
    except Exception as e:
        print(f"Redis 写入失败: {e}")

# 5. 暴露接口
@app.post("/chat")
async def chat_endpoint(request: ChatRequest):
    return StreamingResponse(chat_stream_generator(request), media_type="text/plain")

启动 uvicorn app:app --reload,你问它"我叫张三",再问"我叫什么?",它就能记住你了。)

5.加入RAG

模型虽然聪明,但不知道我公司的报销制度。我们要实现:上传文档 -> 切分文本 -> 组装 Prompt -> 让模型严格根据文档回答

为了演示和学习方便,用内存变量暂存数据(生产环境需用 Milvus 或 Chroma 向量库)。

补充代码:

复制代码
# 6. 模拟全局知识库
KNOWLEDGE_BASE = {
    "chunks": []
}

# 7. 定义上传文档的 DTO
class DocRequest(BaseModel):
    doc_text: str

# 8. 文档入库接口(核心是文本切分)
@app.post("/upload_doc")
def upload_doc(request: DocRequest):
    # 这里为了简单,我们直接按换行符分割文档(实际生产中需用正则或 LangChain 切分)
    raw_chunks = [chunk.strip() for chunk in request.doc_text.split("\n") if chunk.strip()]
    KNOWLEDGE_BASE["chunks"] = raw_chunks
    return {"msg": f"成功入库!共切分了 {len(raw_chunks)} 个知识块。"}

改造刚才的对话接口,加入 RAG 检索与 Prompt 约束:

补充: chat_stream_generator 函数,在 messages.append({"role": "user", ...}) 之前插入以下内容。

复制代码
    # ==== 插入 RAG 逻辑 ====
    context_text = ""
    # 如果知识库里有内容(为了跑通,先暴力全量读取)
    if KNOWLEDGE_BASE["chunks"]:
        context_text = "\n".join(KNOWLEDGE_BASE["chunks"])

    # 如果有参考信息,就强制塞给模型,让它扮演"闭卷考试"的角色
    if context_text:
        system_prompt = (
            f"你是一个智能助手。请严格根据以下参考信息回答用户问题。"
            f"如果参考信息中没有答案,请直接说不知道,不要编造。\n\n【参考信息】\n{context_text}"
        )
        messages.append({"role": "system", "content": system_prompt})
    # =======================

6.效果验收

1、打开 http://127.0.0.1:8000/docs(Swagger UI)

2、先调用 /upload_doc,输入一段你们公司的制度:

"公司最新的报销制度如下:\n1. 员工每个月的打车报销额度为 500 元。\n2. 报销单必须在次月的 5 号前提交。"

3、再调用 /chat,问它:"咱们公司打车能报销多少钱?"

4、顺便验证下流式输出,也就是上面说到的提升用户体验,UI好像不支持,最好用黑窗去验证。

5、如果你问它:"公司报销单用什么颜色的笔填?"(文档里没写),它会老老实实回答:"不知道。"


总结

一定要用 AsyncOpenAI :如果用同步的 OpenAI,流式输出会把整个服务阻塞住,一个人请求,其他人都得排队。

Redis 悬挂问题:如果 Docker 挂了,Python 连不上 Redis,代码会直接抛 500

RAG 的检索不是"真检索":我只是为了跑通,用的是"全量塞入"。一旦文档超过几千字,就会超出模型上下文限制,除非你的token不花钱~)。

下一篇我将引入 Milvus 向量数据库 + BGE Embedding 模型,实现真正的语义检索。虽然我也不知道什么时候做,方向是这个方向一点点的完善。

最后的最后附上几张实验图吧

相关推荐
像风一样自由20203 小时前
2.如何用FastAPI把一个Python算法封装成HTTP服务
python·算法·fastapi
狗凯之家源码网5 小时前
iOS C2 V2 与 Python FastAPI 全链路远控系统深度评测
python·ios·fastapi
触底反弹19 小时前
🚀 FastAPI 五大重难点深度剖析:从 500 报错到彻底搞懂 Depends、Pydantic 与 SQLAlchemy
python·fastapi
奔跑的熊猫1 天前
VSCode + FastAPI +AI Agent+langchain第一个程序
langchain·fastapi
全栈陈序员1 天前
【Python】基础语法入门(三十一)—— API 开发:用 FastAPI 构建 RESTful 接口
python·restful·fastapi
一枕槐安4872 天前
应届生 RAG 项目包装:FastAPI 检索服务接口设计指南
fastapi·web服务·rag·pydantic·接口封装
又蓝3 天前
【元宝搜索开发者征文大赛】用 FastAPI + Vue3 做了个 AI 炒股工具,接了六个分析师和腾讯元宝搜索
人工智能·python·ai·fastapi
外收内放3 天前
FastAPI猜字谜实战(8):收官
开发语言·python·学习·fastapi
hhb_6184 天前
FastAPI接口开发与性能优化实战
性能优化·fastapi