AI辅助编程实战:用FastAPI和DeepSeek从零搭建可部署的API服务
做一个小项目,最烦的不是功能设计,而是搭环境和写脚手架。我自己平时写点小工具,最常碰到的就是这种尴尬:需求想得很清楚,但一打开编辑器,从初始化项目到调通第一个接口,半天时间就没了。后来开始尝试用 AI 辅助写代码,发现只要思路对,AI 完全能胜任“从零到一”这段最枯燥的活儿。
这篇是这个实战系列的第一篇,核心是用 AI 从零搭一个 API 服务出来。我会把完整过程拆开讲清楚,包括怎么选型、怎么写提示词、怎么让 AI 生成可运行的代码,以及那些文档里不会写的报错排查思路。希望给正准备把手头小项目落地,或者想试试 AI 辅助编程的朋友,提供一个可以直接照着走的路子。
1. 为什么“从零搭 API 服务”最适合作为 AI 编程的第一个实战
先说一个我自己的判断:API 服务这类项目,是 AI 编程目前最擅长、也最适合拿来练手的场景之一。原因并不复杂——它结构清晰、套路固定、边界明确。
1.1 脚手架类代码:AI 的舒适区
一个基础 API 服务,本质上是几件事的组合:定义接口路由、处理请求参数、调用业务逻辑、返回响应结果。这套结构在 FastAPI、Flask、Express 这些框架里都已经高度模式化。模式化的意思是,同样一段“健康检查”接口代码,一百个人写出来大差不差;同样一个“请求体校验”的写法,官方文档写得明明白白。
AI 大模型在训练时见过大量这类代码,所以让它生成一个 FastAPI 项目的启动文件和基础路由,准确率非常高,几乎不会出现“幻觉式”的乱编接口。这和让它写复杂业务算法是两个完全不同的难度级别——后者需要真正的逻辑推理,而前者更像是“高精度模板匹配”。
1.2 需求明确,验收标准清晰
API 服务好不好用,判断标准非常客观:启动不报错、请求能通、参数校验正确、错误信息合理。没有那种“我感觉这个界面差点意思”的主观评价,也不需要反复调 UI 细节。这种特性对 AI 辅助开发特别友好,因为你可以快速验证 AI 的输出是否正确,错了也知道错在哪里。
我第一次尝试让 AI 写完整项目时,选的就是一个“AI 文本摘要 API”。当时的需求描述很简单:提供一个 POST 接口,接收一段文本和一个最大长度参数,返回文本摘要。这个需求本身没有任何模糊地带,AI 生成完代码,我启动服务,用 curl 一测就知道行不行。整个过程下来,我最大的感受是:只要需求描述得足够清晰,AI 产出的第一版代码往往就能跑通大半。
1.3 对新手和熟手都有价值
新手可以用它来学习项目结构——AI 生成的代码本身就是很好的学习材料,读一遍就知道一个规范的服务长什么样;熟手可以用它来节省时间——那些每次写新项目都要重复一遍的“开机仪式”,直接交给 AI 就好。
给第一次尝试 AI 编程的朋友一个建议:不要一上来就让 AI 给你生成一个完完整整的“大型系统”,十有八九会跑不起来。从一个最小可用的 API 服务开始,先跑通,再迭代,这个节奏最舒服。
2. 技术选型:FastAPI 和 DeepSeek 的组合逻辑
既然目标是搭一个能调大模型接口的 API 服务,那技术选型就是第一步要考虑清楚的事。我这次的选型组合是:Python + FastAPI + DeepSeek API。下面说说每个选择的思路。
2.1 为什么是 FastAPI 而不是 Flask 或 Node.js
Python 生态在 AI 和大模型调用这个领域,优势实在太明显了。几乎所有大模型服务商的 SDK 都优先支持 Python,各种数据处理工具、向量数据库的客户端也都是 Python 优先。做 AI 相关的小项目,选 Python 基本等于选择了最大的“兼容性保险”。
框架方面,我在 Flask 和 FastAPI 之间犹豫过一段时间。Flask 的好处是极简,上手成本低,但缺点也很明显——它太“自由”了,参数校验、API 文档、数据序列化这些事都得自己搭。FastAPI 则是另一套思路:基于 Python 类型注解自动做请求校验,自动生成交互式文档,自带异步支持,不额外配置。
打个比方,Flask 像是一张白纸,你可以随意作画,但笔墨纸砚都得自己准备;FastAPI 像是一套精装画板,纸张、画框、展架都是现成的,而且质量稳定。对一个要快速落地的 AI 项目来说,FastAPI 这种“把常规事情都顺手做好”的框架,能省下大量模板代码的时间。
而且 FastAPI 文档自动生成这个特性,在联调阶段特别好用。前端同事直接打开/docs路由就能看到接口定义,还能在线测试,省掉了写接口文档的时间。
2.2 为什么选 DeepSeek API 而不是其他大模型
选 DeepSeek,首先是性价比考量。作为一个自用的小项目,我需要的是“可以放心调、成本可控”的模型接口。DeepSeek 的定价在同类里属于非常便宜的那档,注册还会送一些免费额度,对个人开发者测试和学习来说很够用。
另一个关键因素是它兼容 OpenAI 的接口格式。这意味着我不需要引入一个专属 SDK,直接使用openai库,只要把base_url指到 DeepSeek 的地址就能跑通。这个兼容性设计让代码更通用——以后想切换别的模型,改一个配置就行,业务代码完全不用动。
至于为什么不选纯本地模型,原因很简单:本地部署需要显卡和显存,而且模型体积动辄几个 GB 起步。我的目标是快速搭出一个 API 服务,而不是先解决推理资源的问题。
2.3 整体服务结构设计
我设计的服务功能很直接:接收用户发送的文本,调用 DeepSeek 模型生成回复,再把回复返回给调用方。整个链路如下图所示,但从项目结构上说就三层:
客户端(curl / 前端页面 / 其他服务) -> FastAPI 服务(路由 + 参数校验) -> DeepSeek APIFastAPI 服务在这条链路上,实际上扮演的是“中转站”和“守门员”的角色。说它是中转站,因为大模型的真正调用发生在我们服务内部;说它是守门员,因为它对外暴露接口时不直接透传所有东西,而是先做参数检查、再做必要的数据加工,最后才把请求转发给大模型。
这一点设计对真实场景很重要。比如你不想让调用方直接接触你的 API Key,你就可以在 FastAPI 这一层做鉴权;你不想让调用方乱传参数,你就可以在 FastAPI 这一层做校验。这些都属于“API 外壳”的职责。
2.4 为什么不自己做模型部署
用 API 而不是自建模型服务,最大的原因还是“聚焦”。你做一个 API 服务,核心价值在于把业务逻辑和外部接口组织好,而不是从零训练一个模型再部署上去。模型部署牵扯推理优化、负载均衡、GPU 资源管理,那是一整套工程体系,对个人小项目来说完全没必要。
用现成 API 的好处是,你花几块钱就能获得一个推理能力还不错的模型,然后把省下来的精力和成本投到真正的业务逻辑里。这就像你开餐厅不必自己种菜一样——菜农把菜种好,你负责把菜做成美味佳肴,分工协作才是高效的。
3. 从零开工:写好提示词,让 AI 生成第一版代码
选型定了,接下来就是实际操作。这一节我会把提示词怎么写的逻辑拆开讲,然后把 AI 生成的代码一步步分析清楚。很多人用 AI 写代码觉得“不靠谱”,多半问题出在提问方式上——你问得模糊,它自然回答得含糊。
3.1 提示词的三个层次:需求、约束、验收标准
写提示词和写需求文档是一个道理。最高效的提示词,必须包含三层信息。
第一层是需求描述:你要做一个什么功能,输入是什么,输出是什么。这一层要具体,比如你说“做一个聊天接口”,AI 能理解但可能会做得很随意;但你说“做一个 POST 接口,接收一个 message 字符串参数,调用 DeepSeek 模型,返回模型的回复内容”,AI 就知道该怎么做。
第二层是约束条件:用什么语言、什么框架、什么 SDK,API Key 怎么读,参数怎么校验。这些约束决定了代码的形态和可维护性。你不说,AI 可能就会用全局变量存 API Key,或者把密钥硬编码在代码里。
第三层是验收标准:服务要能启动、接口要有文档、错误要有处理。告诉 AI 你后面会怎么验证它生成的代码,它就会尽可能把边界情况考虑到。
我当时给的提示词大概是这样的:
请用 Python FastAPI 框架写一个 API 服务。 功能要求: 1. 提供 GET /health 健康检查接口,返回 {"status": "ok"} 2. 提供 POST /chat 接口,接收 JSON 请求体,字段包括 message(字符串,必填)和 max_tokens(整数,可选,默认 2048) 3. 在 /chat 接口中调用 DeepSeek 的 deepseek-chat 模型,将 message 作为用户消息传入 4. 模型 API Key 从环境变量 DEEPSEEK_API_KEY 读取,不要硬编码 5. 使用 openai 库,base_url 设置为 https://api.deepseek.com/v1 6. 返回格式为 JSON,包含 reply 字段,值是模型的回复内容 7. 启动方式为 uvicorn main:app --reload 请直接输出完整代码。这个提示词的效果非常好,AI 差不多一次性就给出了可运行的代码。我发现关键在于:每一个约束都在回答一个“AI 默认会怎么选”的问题。比如我说“API Key 从环境变量读取”,AI 就不会把密钥硬编码进去;我说“使用 openai 库”,它就不会自作主张去用别的 SDK。
3.2 核心代码拆解:AI 生成的 FastAPI 服务长什么样
AI 生成的代码,主体结构是这样的(把关键部分注释一下):
import os from fastapi import FastAPI from pydantic import BaseModel from openai import OpenAI app = FastAPI(title="AI API Service") client = OpenAI( api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com/v1" ) class ChatRequest(BaseModel): message: str max_tokens: int = 2048 @app.get("/health") def health_check(): return {"status": "ok"} @app.post("/chat") def chat(request: ChatRequest): try: response = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "user", "content": request.message} ], max_tokens=request.max_tokens ) return {"reply": response.choices[0].message.content} except Exception as e: return {"error": str(e)}这段代码的每一部分,我都检查了一遍,它实际做得比我要求的还细。
app = FastAPI(title="AI API Service")—— 轻量不过度,title 让/docs文档更友好。- 模型客户端的初始化放在模块顶层,而不是每次请求都新建一次。这样不会重复建立连接,性能会好很多。这是一个细节上的加分项。
ChatRequest用了 Pydantic 的 BaseModel 来定义请求结构,message是必需的字符串,max_tokens有默认值 2048。校验逻辑完全自动,FastAPI 会在请求进来时自动检查类型。/chat接口内部做了异常捕获,把模型的报错信息透传给调用方,这在调试阶段非常有用——你可以直接看到是参数问题还是模型端的错误。
3.3 环境准备和启动运行的完整步骤
代码有了,接下来是让服务跑起来。假设你的电脑上已经装好了 Python 3.9 以上版本,下面这几步可以照抄。
第一步,创建项目目录和虚拟环境:
mkdir ai-api-service cd ai-api-service python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate第二步,安装依赖:
pip install fastapi "uvicorn[standard]" openai python-dotenv第三步,创建.env文件存放 API Key,内容如下:
DEEPSEEK_API_KEY=sk-你的密钥第四步,让 Python 代码能读取.env里的内容,在main.py开头加两行:
from dotenv import load_dotenv load_dotenv()load_dotenv()的作用是把.env文件里的键值对加载到环境变量中,这样os.getenv("DEEPSEEK_API_KEY")就能读到了。
第五步,启动服务:
uvicorn main:app --reload --port 8000看到Application startup complete的日志,就说明服务跑起来了。--reload参数让代码修改后自动重载,调试阶段非常方便。
3.4 用 curl 快速验证接口
服务启动后,打开一个新的终端窗口,先用健康检查接口确认服务状态:
curl http://localhost:8000/health预期返回:
{"status":"ok"}然后验证核心的对话接口:
curl -X POST http://localhost:8000/chat \ -H "Content-Type: application/json" \ -d '{"message": "用一句话介绍一下 FastAPI"}'如果一切正常,你会收到大模型生成的回复,就放在reply字段里。做到这一步,一个“用 AI 搭出来的、能调 AI 模型的 API 服务”就已经完整跑通了。
关于接口测试,这里有个小技巧:FastAPI 自带的
/docs页面就是最好的调试工具。浏览器打开http://localhost:8000/docs,能看到接口文档,点开接口还能直接填参数发起请求,不需要记忆任何 curl 命令。
4. 开发过程中躲不开的坑:从报错到解决的完整链路
前面说到 AI 一次性给出了可运行代码,但实际开发过程的真实情况是:跑通第一版之后,各种问题才陆续浮出来。这一节我会还原三个最有代表性的报错场景,每一个都是我实际踩过的,排查思路比最终答案更重要。
4.1 报错一:No API key for provider route "deepseek-official"
这个报错是我第一次用 DeepSeek API 时遇见的,当时用的一个工具链里配置了多个模型供应商,但 DeepSeek 的 key 没配上。错误信息直译过来是:“在 deepseek-official 这条 provider 路线上没有找到 API key”。
排查思路:先分清这个报错是从哪里来的。如果报错信息里带了provider route这种字眼,通常不是编程语言的报错,而是某个开发框架或中间件在做模型路由。这种情况下,核心问题一般出在配置没传对地方,而不是 key 本身没生成。
解决方案:在 FastAPI 服务里面,我直接看OpenAI客户端能不能拿到环境变量。写一段最小验证代码:
import os from openai import OpenAI api_key = os.getenv("DEEPSEEK_API_KEY") print("key exists:", bool(api_key)) client = OpenAI( api_key=api_key, base_url="https://api.deepseek.com/v1" )如果key exists: False,说明.env文件没被加载,回到代码检查load_dotenv()是否在OpenAI客户端初始化之前调用——顺序很重要。如果key exists: True但还是报错,就把base_url检查一下,确认没有拼写错误。
4.2 报错二:api error: 400 this model's maximum context length is 1048576 tokens...
这个报错是在我调大模型接口时遇到的。400是 HTTP 状态码,表示请求参数有问题;后面的信息说明模型的最大上下文长度是 1048576 个 token,但当前请求超过了这个限制。
核心原因:上下文过长。我在服务里做了多轮对话功能,每轮都把历史消息全部塞进请求里,结果很多次调用后,历史消息累积得越来越大,最终超过了模型的上下文窗口。
解决方案:做上下文长度控制,不能让“历史消息”无限膨胀。我加了一个简单的截断逻辑,只保留最近几轮对话,而不是全部:
def trim_messages(messages, max_tokens=6000): # 粗略按字符估算 token,保留最后若干条消息 while sum(len(m["content"]) for m in messages) > max_tokens * 3: messages.pop(0) return messages这是一个粗糙但有效的方案。len(m["content"])只是字符数,不是 token 数,但作为快速截断的近似判断足够了。严格的 token 计数需要引入分词器,小项目不必做到这种程度。
经验分享:以后凡是涉及“多轮对话”的功能,一定要把“上下文管理”纳入设计,而不是等到报错了才处理。方案通常有几种:只保留最近 N 轮对话、把历史内容做摘要压缩之后再拼进请求、或者设定总 token 预算,超了就裁剪。这个预算值要考虑到你用的模型最大上下文长度,以及你自己的成本控制——全量塞进去不仅可能超限,费用也会随之增加。
4.3 报错三:Permission denied while trying to connect to the Docker daemon socket
这个报错是我的 API 服务容器化部署时踩的。当时我用 Docker 启动容器,想通过挂载 Docker socket 的方式让容器内调用宿主机的 Docker,结果权限直接被拒绝了。
原因剖析:Docker daemon socket(默认在/var/run/docker.sock)属于docker用户组,普通用户或容器内的非 root 用户没有权限访问。很多教程里让你直接把 socket 挂进容器,这确实是 Docker-in-Docker 的一种优雅做法,但前提是用户权限要够。
解决方案:最直接的办法是宿主机上没有添加docker用户组,那就把自己加进组里:
sudo usermod -aG docker $USER注意,改完用户组之后,要么退出重新登录,要么执行newgrp docker让当前会话生效。如果项目环境要求更严格的隔离,那建议换方案:不要去直接控制宿主机 Docker,而是把“需要执行的 Docker 操作”封装到 API 自身的能力中,或者改用 Docker SDK 配合正确的认证方式。
小菜但管用的提示:socket 路径权限可以用ls -l /var/run/docker.sock查看,如果你看到srw-rw----后面跟着的root docker,那就说明这个 socket 只对 root 和 docker 组的用户开放,普通用户不在组里就必然报 Permission denied。
4.4 排错思路的共同点
把三个报错放在一起看,其实都能用同一套思路解决:先看清楚报错信息在说哪一层的问题,再对症下药。
拿第一个报错来说,provider route这个词已经指出了问题方向——不是代码语法问题,是配置问题;第二个报错里maximum context length指出了模型上下文限制,答案是裁剪内容;第三个报错里permission denied指出了操作系统权限层面,答案是权限调整。
我觉得很多初学者在面对报错时最容易犯的错,是“不看报错信息,直接猜答案”。实际排错的第一步永远是仔细读一遍完整报错,找到关键词,再决定下一步动作。AI 写代码也一样,报错给了 AI,它往往能快速指出问题所在,但前提是你要把完整报错贴给它,而不是只贴一句“我的服务挂了”。
5. 从本地跑通到小范围部署:容器化和优化
本地跑通接口之后,紧接着的问题是“怎么让别人也能用上”。对个人小项目来说,Docker 化是成本最低、最干净的部署方式。这一节讲 Docker 部署和几个代码层面的优化点。
5.1 用 Dockerfile 把服务打包成镜像
先写 Dockerfile,内容很简单:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]逐行说下设计意图。基于python:3.11-slim镜像,体积小、依赖少,适合轻量 API 服务。WORKDIR /app设定容器内的工作目录。复制requirements.txt并先安装依赖,是为了利用 Docker 的缓存机制——以后代码改了但依赖没变的话,重新构建会快很多,不用每次都重装依赖。最后用uvicorn启动,注意--host 0.0.0.0,这个必须写,不然容器外部访问不到服务。
构建命令:
docker build -t ai-api-service .运行命令:
docker run -d --name ai-api -p 8000:8000 --env-file .env ai-api-service--env-file .env会把本地的环境变量导入到容器里,API Key 不用写在镜像里,安全性更高。
5.2 用 docker-compose 管理服务
如果后续还加了别的组件,比如 Redis、MySQL、或者别的服务,docker-compose.yml会是更好的管理方式。基础版本:
services: api: build: . ports: - "8000:8000" env_file: - .env之后加依赖服务,只需要在services下继续追加配置就行,一个docker compose up -d就能把整套服务全拉起来,比手敲一串docker run省心太多。
5.3 代码层优化:日志、错误码和限流
部署只是开始,API 服务的健壮性还差几块拼图。
第一,加日志。每次请求都打印一下时间、来源 IP、请求参数和模型响应耗时。不要小看这一步,别人调用你的服务出了问题,第一件事就是让你翻日志。我在chat接口里加一个简易日志:
import time @app.post("/chat") def chat(request: ChatRequest): start_time = time.time() try: response = client.chat.completions.create(...) elapsed = time.time() - start_time print(f"[{time.strftime('%H:%M:%S')}] message_len={len(request.message)} elapsed={elapsed:.2f}s") return {"reply": response.choices[0].message.content} except Exception as e: print(f"ERROR: {e}") return {"error": "模型调用失败,请稍后重试"}第二,错误信息别裸奔。模型调用失败的原因可能有很多:余额不足、网络波动、参数超限等。对外返回统一格式的错误提示,别把底层的异常堆栈直接抛给调用方。之前提到的return {"error": str(e)},调试时方便,但做成正式服务时最好改成更安全的风格。
第三,加上简单的限流。个人小项目可以不引入复杂的限流中间件,但至少做一个“每个 IP 每分钟最多请求 N 次”的内存计数器。否则别人一个脚本就把你的 API 额度刷爆了。等到真有做大的需求,再考虑换成熟方案不迟。
扩展的思路也不止我上面写的这些。你可以在外面再接一层别的 API,把你这个服务当作一个通用接口网关;可以加鉴权,只在携带有效 token 时放行;也可以把“多轮对话”做进去,让 /chat 接口可以按会话 ID 记录上下文。每一点展开,都能写成一篇新的实战笔记。这套从零到一搭 API 服务的流程,本身就是一个很好的行动框架,顺着往下走,你的项目就能一点点长出真正的业务能力。