DeepSeek-R1-Distill-Qwen-1.5B实操手册:API封装为FastAPI服务供其他系统调用
1. 为什么要把Streamlit聊天应用改造成FastAPI服务?
你已经跑通了那个清爽好用的Streamlit本地对话界面——输入问题,气泡弹出思考链+答案,侧边栏一点就清空显存,整个过程丝滑又安心。但很快你会发现:Streamlit是给“人”用的,不是给“系统”用的。
比如,你想把DeepSeek-R1-Distill-Qwen-1.5B的能力嵌入到公司内部的知识库搜索页里,用户搜“报销流程”,后端自动调用模型生成结构化解读;或者集成进客服工单系统,当坐席遇到复杂技术问题时,一键触发模型推理并返回参考话术;再比如,想用Python脚本批量测试不同提示词对数学题求解效果的影响……这些场景,都绕不开一个核心需求:稳定、无状态、可编程调用的HTTP接口。
而Streamlit本质是个前端驱动的交互式应用框架,它不暴露标准REST API,无法被curl、requests或Postman直接调用,也不支持并发请求管理、身份认证、请求限流等生产级能力。这时候,把它“抽离逻辑、封装接口、交由FastAPI托管”,就成了从“玩具”走向“工具”的关键一步。
本手册不讲大道理,只做一件事:手把手带你把已有的Streamlit项目中那套成熟可用的推理逻辑,完整剥离出来,封装成一个轻量、健壮、开箱即用的FastAPI服务。全程基于你已部署好的/root/ds_1.5b模型路径,零模型重下载、零参数重调优、零环境重配置——你只需要改代码、加路由、启服务。
1.1 你将获得什么(不是概念,是具体能力)
- 一个独立运行的FastAPI服务,监听
http://localhost:8000,提供标准/v1/chat/completions兼容接口 - 完全复用原Streamlit项目的全部推理能力:原生聊天模板拼接、思维链格式化、
temperature=0.6/top_p=0.95精准采样、max_new_tokens=2048长推理支持 - 支持标准OpenAI-style JSON请求体(含
messages,model,stream等字段),下游系统无需改造即可接入 - 自动处理GPU/CPU设备识别、显存清理、无梯度推理,和原来一样省资源
- 内置健康检查端点
/health和模型信息端点/models,方便监控与集成 - 提供完整可运行代码、启动命令、请求示例,复制粘贴就能跑
没有抽象架构图,没有理论推导,只有你能立刻执行、立刻验证、立刻集成的实操路径。
2. 核心改造思路:三步剥离,四层封装
我们不做“重写”,只做“迁移”。整个过程围绕一个原则:保留所有已验证的推理逻辑,只替换掉Streamlit的UI层和交互层,用FastAPI的标准组件重新组织。
2.1 第一步:剥离模型加载与推理核心(inference.py)
原Streamlit代码里,模型加载、分词器初始化、推理函数通常混在st.cache_resource装饰器下,和UI逻辑耦合。我们要做的,是把它们“提纯”出来,变成一个干净、无依赖、可独立测试的模块。
新建inference.py,内容如下:
# inference.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline from typing import List, Dict, Optional # 全局模型与分词器实例(单例,避免重复加载) _model = None _tokenizer = None def load_model_and_tokenizer(model_path: str = "/root/ds_1.5b") -> tuple: """加载模型与分词器,自动适配设备与精度""" global _model, _tokenizer if _model is not None and _tokenizer is not None: return _model, _tokenizer print(f" Loading model from {model_path}...") _tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) _model = AutoModelForCausalLM.from_pretrained( model_path, device_map="auto", torch_dtype="auto", trust_remote_code=True ) print(" Model loaded successfully.") return _model, _tokenizer def format_thinking_output(text: str) -> str: """将模型原始输出中的<|think|>...</think>标签转为结构化格式""" # 原Streamlit中使用的相同逻辑 import re pattern = r"<\|think\|>(.*?)<\|answer\|>" match = re.search(pattern, text, re.DOTALL) if match: thinking = match.group(1).strip() answer = text.split("<|answer|>")[-1].strip() return f"「思考过程」\n{thinking}\n\n「最终回答」\n{answer}" return text.strip() def generate_response( messages: List[Dict[str, str]], max_new_tokens: int = 2048, temperature: float = 0.6, top_p: float = 0.95, do_sample: bool = True ) -> str: """执行一次完整推理:模板拼接 → 模型生成 → 格式化输出""" model, tokenizer = load_model_and_tokenizer() # 1. 使用官方聊天模板拼接上下文 input_text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) # 2. 编码输入 inputs = tokenizer(input_text, return_tensors="pt").to(model.device) # 3. 无梯度推理(节省显存) with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=max_new_tokens, temperature=temperature, top_p=top_p, do_sample=do_sample, pad_token_id=tokenizer.eos_token_id, eos_token_id=tokenizer.eos_token_id ) # 4. 解码并去除输入部分 full_output = tokenizer.decode(outputs[0], skip_special_tokens=False) response = full_output[len(input_text):].strip() # 5. 格式化思维链输出 return format_thinking_output(response)这个文件就是你的新“引擎”。它完全复用了原项目的所有关键逻辑:apply_chat_template、device_map="auto"、torch.no_grad()、format_thinking_output——只是去掉了所有st.前缀和UI相关代码。你可以单独运行它测试:
# test_inference.py from inference import generate_response msgs = [ {"role": "system", "content": "你是一个严谨的数学助手"}, {"role": "user", "content": "解方程:2x + 3 = 7"} ] print(generate_response(msgs))2.2 第二步:定义FastAPI路由与请求模型(main.py)
现在,把引擎装进FastAPI的“车身”。新建main.py:
# main.py from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel from typing import List, Dict, Optional, Any from inference import generate_response import uvicorn import json app = FastAPI( title="DeepSeek-R1-Distill-Qwen-1.5B API", description="基于DeepSeek-R1-Distill-Qwen-1.5B蒸馏模型的轻量级本地推理服务", version="1.0.0" ) class ChatMessage(BaseModel): role: str content: str class ChatCompletionRequest(BaseModel): model: str = "deepseek-r1-distill-qwen-1.5b" messages: List[ChatMessage] max_tokens: Optional[int] = 2048 temperature: Optional[float] = 0.6 top_p: Optional[float] = 0.95 stream: Optional[bool] = False # 本版本暂不支持流式,保持兼容 class ChatCompletionResponse(BaseModel): id: str object: str = "chat.completion" created: int model: str choices: List[Dict[str, Any]] usage: Dict[str, int] @app.get("/health") def health_check(): """健康检查端点""" return {"status": "healthy", "model_loaded": True} @app.get("/models") def list_models(): """列出可用模型信息""" return { "data": [{ "id": "deepseek-r1-distill-qwen-1.5b", "object": "model", "owned_by": "local", "permission": [] }] } @app.post("/v1/chat/completions", response_model=ChatCompletionResponse) def chat_completions(request: ChatCompletionRequest): """标准OpenAI-style聊天补全接口""" try: # 调用核心推理函数 response_text = generate_response( messages=[m.dict() for m in request.messages], max_new_tokens=request.max_tokens, temperature=request.temperature, top_p=request.top_p ) # 构造标准响应(简化版,满足基本集成需求) import time import uuid return { "id": f"chatcmpl-{uuid.uuid4().hex}", "object": "chat.completion", "created": int(time.time()), "model": request.model, "choices": [{ "index": 0, "message": { "role": "assistant", "content": response_text }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 0, # 实际可扩展为真实统计 "completion_tokens": 0, "total_tokens": 0 } } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"推理失败: {str(e)}" ) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000, workers=1)注意几个关键点:
- 请求体
ChatCompletionRequest严格遵循OpenAI API规范,messages字段支持system/user/assistant角色; generate_response调用方式与你在Streamlit中完全一致,参数一一对应;/v1/chat/completions返回结构也尽量贴近OpenAI,下游系统(如LangChain、LlamaIndex)可直接复用已有客户端;- 错误处理明确,异常直接转为标准HTTP错误码。
2.3 第三步:启动服务并验证(终端操作)
确保你已安装必要依赖:
pip install fastapi uvicorn transformers torch sentencepiece然后,在项目根目录(与main.py同级)执行:
uvicorn main:app --host 0.0.0.0 --port 8000 --reload注意:
--reload仅用于开发调试,生产环境请去掉,并使用--workers 1(因模型加载耗显存,多进程需谨慎)。
服务启动后,你会看到类似日志:
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) INFO: Started reloader process [12345] INFO: Started server process [12346] INFO: Waiting for application startup. Loading model from /root/ds_1.5b... Model loaded successfully. INFO: Application startup complete.此时,服务已就绪。打开新终端,用curl测试:
curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1-distill-qwen-1.5b", "messages": [ {"role": "system", "content": "你是一个代码助手"}, {"role": "user", "content": "用Python写一个快速排序函数"} ] }' | python -m json.tool你会看到结构清晰的JSON响应,其中choices[0].message.content就是带「思考过程」+「最终回答」的完整结果——和你在Streamlit界面上看到的一模一样。
3. 进阶实用技巧:让服务更稳、更快、更好集成
光能跑通还不够。在真实系统中,你需要应对并发、监控、日志、安全等现实问题。以下是几条经过验证的轻量级增强建议,全部基于现有代码微调,无需引入复杂中间件。
3.1 显存自动回收:避免长时间运行后OOM
原Streamlit的「🧹 清空」按钮在FastAPI里没了,但我们可以用atexit钩子,在服务退出时主动释放显存:
在main.py顶部添加:
import atexit import torch def cleanup_gpu(): if torch.cuda.is_available(): torch.cuda.empty_cache() print(" GPU cache cleared on exit") atexit.register(cleanup_gpu)更进一步,如果你希望在每次请求后都轻量清理(适合低频但长时服务),可在chat_completions函数末尾加一行:
torch.cuda.empty_cache() # 放在return之前3.2 请求日志记录:排查问题有据可依
在chat_completions函数开头加入简单日志:
import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @app.post("/v1/chat/completions", response_model=ChatCompletionResponse) def chat_completions(request: ChatCompletionRequest): logger.info(f"Received request for model {request.model}, user message: {request.messages[-1]['content'][:50]}...") # ...后续逻辑日志会输出到控制台,清晰显示谁在什么时候问了什么,极大缩短排障时间。
3.3 简单请求限流:防止单个客户端拖垮服务
对于内部系统调用,一般不需要复杂限流。用slowapi库加一行即可:
pip install slowapi在main.py中:
from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter @app.post("/v1/chat/completions", response_model=ChatCompletionResponse) @limiter.limit("5/minute") # 每分钟最多5次 def chat_completions(request: ChatCompletionRequest): # ...原有逻辑超过限制会自动返回429 Too Many Requests,无需额外处理。
3.4 Docker一键封装(可选,但强烈推荐)
把服务打包成Docker镜像,彻底解决环境一致性问题。新建Dockerfile:
FROM nvidia/cuda:12.1.1-base-ubuntu22.04 RUN apt-get update && apt-get install -y python3-pip python3-dev && rm -rf /var/lib/apt/lists/* COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt COPY . /app WORKDIR /app CMD ["uvicorn", "main:app", "--host", "0.0.0.0:8000", "--port", "8000"]requirements.txt内容:
fastapi==0.110.0 uvicorn==0.29.0 transformers==4.40.0 torch==2.2.0+cu121 sentencepiece==0.2.0构建并运行:
docker build -t ds15b-api . docker run -p 8000:8000 --gpus all -v /root/ds_1.5b:/root/ds_1.5b ds15b-api模型路径通过卷映射挂载,安全又灵活。
4. 与其他系统的集成示例:三行代码接入
服务跑起来后,怎么用?下面给出最常用的三种集成方式,每种都只需3-5行核心代码。
4.1 Python requests调用(最常用)
import requests url = "http://localhost:8000/v1/chat/completions" headers = {"Content-Type": "application/json"} data = { "model": "deepseek-r1-distill-qwen-1.5b", "messages": [ {"role": "user", "content": "解释下Transformer的自注意力机制"} ] } response = requests.post(url, headers=headers, json=data) result = response.json() print(result["choices"][0]["message"]["content"])4.2 LangChain快速接入(已有LangChain项目)
from langchain.llms import OpenAI from langchain.chat_models import ChatOpenAI # 复用OpenAI客户端,只需改base_url llm = ChatOpenAI( openai_api_base="http://localhost:8000/v1", openai_api_key="not-needed", # 本服务无需key model_name="deepseek-r1-distill-qwen-1.5b" ) result = llm.invoke("用一句话总结量子计算的核心思想") print(result.content)4.3 前端JavaScript调用(嵌入网页)
async function askDeepSeek(question) { const response = await fetch('http://localhost:8000/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'deepseek-r1-distill-qwen-1.5b', messages: [{ role: 'user', content: question }] }) }); const data = await response.json(); return data.choices[0].message.content; } // 调用示例 askDeepSeek("如何给初学者解释递归?").then(console.log);所有方式,都复用你已验证的同一套推理逻辑。你投入在Streamlit上的所有调优工作,此刻全部生效。
5. 总结:从界面到接口,能力真正流动起来
你刚刚完成的,不只是一个技术动作,而是一次能力升级:
- 从“演示”到“可用”:Streamlit界面是展示,FastAPI服务是交付;
- 从“单点”到“网络”:一个模型,不再只服务一个浏览器标签页,而是成为整个系统可调用的智能节点;
- 从“实验”到“生产”:健康检查、日志、限流、Docker封装——每一步都在拉近与真实业务的距离。
整个过程没有魔改模型,没有重写推理,没有新增依赖。你只是把已有的、可靠的、经过验证的代码,用更合适的方式组织起来。这正是工程实践的精髓:不追求炫技,只关注价值落地。
现在,你的DeepSeek-R1-Distill-Qwen-1.5B,已经准备好为知识库、客服系统、自动化脚本、内部工具……提供稳定、私有、高效的文本推理能力。下一步,就是把它接入你真正需要的地方。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。