)
最近在做一个小项目期间最多被问到的一个问题是都说 AI 能写代码到底能不能从零搭一个能上线的 API 服务这周我专门抽时间按这个思路走了一遍最终交付了一个“文本摘要服务”——对外暴露 REST API接收一段文本返回摘要和关键词。整条链路里从项目骨架、业务代码到测试用例绝大多数代码都是 AI 先生成、我再 Review 和调整同时服务本身也调用了大模型 API 完成文本理解。这篇文章就完整记录这次实战里我的技术选型、Prompt 编写、踩坑经历和上线经验给想用 AI 快速搭 API 服务的朋友一份可以直接照做的参考。1. 项目整体设计先定边界再让 AI 进场1.1 需求拆解这个 API 到底要做什么很多朋友一上来就让 AI“直接给我写个 API”结果 AI 吐出一大堆代码本地却怎么都跑不通。原因很简单没先定义边界。这次我给自己定的需求是极简的提供一个POST /v1/summarize接口接收{text, max_length}返回{summary, keywords}。提供一个GET /health用于健康检查方便后续部署后做探活。服务必须在本地一条命令启动代码量控制在几百行内不引入复杂框架。大模型 API 的 key 只能保存在服务端绝不能暴露给调用方。这种小范围需求非常适合做第一个 AI 辅助实战。目标太大会浪费大量时间在无关环节上目标太小又体现不出 AI 的优势。我强烈建议你在让 AI 动手之前先像这样把范围压到“小到不能再小”哪怕只是做一个返回固定 JSON 的接口也行。真正的业务逻辑可以在后续迭代里一点点加但工程骨架第一次就要立住。1.2 技术选型为什么选 FastAPI 而不是别的AI 生成代码比较擅长主流框架比如 Node.js 的 Express/Fastify、Python 的 FastAPI/Flask。我最终选了 FastAPI原因有三个FastAPI 自带的 Pydantic 可以很方便地做请求参数校验和响应模型定义省掉手写参数判断的脏活。它默认生成 OpenAPI 文档Swagger UI 直接挂在/docs联调时非常直观。Python 生态里调用大模型 SDK 比较顺手异步支持也能减少 IO 等待适合对接外部 API。我也在 prompt 里要求 AI 严格使用 FastAPI 的依赖注入方式写代码而不是像 Flask 那样全局变量满天飞。这个决定帮我省了后面大量重构时间。作为对比我也给经常纠结的朋友一个参考方向方案适合场景需要额外注意FastAPI快速迭代、需要自动文档、Python 生态需要理解异步与 PydanticFlask老项目、简单的 HTML 服务参数校验和文档要自己搭Express/FastifyNode.js 团队、前后端同语言大模型 SDK 不如 Python 丰富选型没有绝对标准但一旦选定就要把需求围绕这个框架描述清楚。AI 生成代码时最好给它一个确定的技术栈范围别让它自由发挥。1.3 AI 在方案里扮演什么角色这次我把 AI 当成“高级结对工程师”而不是替我做决策的工具。方案、边界、数据结构由我确定AI 负责把常见框架代码快速生成初稿我再逐行 Review、运行验证、修 bug。整个过程很像面试官给候选人布置任务最终代码质量还是要我把关。实践中你会发现LLM 对常见框架的代码范式非常熟悉比如FastAPI 的应用入口怎么写、CORS 中间件放哪里、环境变量怎么读取。但对你的业务细节基本不了解如果你不告诉它“我们要调用哪个大模型、超时多少秒、返回什么字段”它只会给出一堆通用内容和 TODO。所以我对所有项目成员的统一要求是需求描述写清楚AI 生成速度才真正快。2. 用 AI 生成从零开始的 FastAPI 骨架2.1 写提示词一份能拿到高质量代码的 Prompt 模板很多人说 AI 生成代码不够好用多半是 prompt 太随意。比如“帮我写一个 API”这个信息量太少。我这次用的模板可以收藏你是一名 Python/FastAPI 专家。请生成一个简单的 API 服务要求如下 1. 使用 fastapi、uvicorn、pydantic。 2. 提供 GET /health返回 {status: ok}。 3. 提供 POST /v1/summarize接收 {text: str, max_length: int 200}。 4. summarize 逻辑调用 OpenAI SDK 兼容的大模型接口base_url 和 api_key 从环境变量读取模型名为 deepseek-chat。 5. 请求校验text 不能少于 20 个字符max_length 限制在 20 到 500 之间。 6. 返回 {summary: str, keywords: list[str]}。 7. 加入 CORS 中间件、日志 logger。 8. 密钥禁止硬编码统一通过 dotenv 加载。 9. 给出项目文件结构、main.py、requirements.txt、.env.example。这个 prompt 基本等价于一份需求文档。AI 拿到后生成的代码绝大多数可以直接用。关键是包含几个要素角色定义、技术栈、接口定义、参数约束、安全要求、交付物清单。你也可以根据自己的语言习惯写成对话但信息密度一定要高。2.2 让 AI 产出项目骨架的实操过程我让 AI 生成的项目结构大致如下project/ ├── main.py ├── services/ │ └── llm_client.py ├── models.py ├── requirements.txt ├── .env.example └── tests/ └── test_main.py核心的main.py大概是这样的简写from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from dotenv import load_dotenv import os, httpx, logging load_dotenv() logger logging.getLogger(uvicorn) app FastAPI(titleText Summarize API, version1.0.0) class SummarizeRequest(BaseModel): text: str Field(..., min_length20, max_length8000) max_length: int Field(200, ge20, le500) class SummarizeResponse(BaseModel): summary: str keywords: list[str] app.get(/health) def health(): return {status: ok} async def call_llm_api(text: str, max_length: int): api_key os.getenv(LLM_API_KEY) base_url os.getenv(LLM_BASE_URL) if not api_key: logger.error(LLM_API_KEY not configured) raise HTTPException(status_code500, detailLLM_API_KEY not configured) async with httpx.AsyncClient(timeout30) as client: resp await client.post( f{base_url}/chat/completions, headers{Authorization: fBearer {api_key}}, json{ model: os.getenv(LLM_MODEL, deepseek-chat), messages: [ {role: system, content: 你是文本摘要助手输出 JSON 格式的 summary 和 keywords。}, {role: user, content: text} ], max_tokens: max_length, temperature: 0.3, }, ) resp.raise_for_status() data resp.json() content data[choices][0][message][content] # 在这里把模型输出的 JSON 解析成 Python 对象 return parse_summary(content)这段代码虽然是 AI 生成的但我做了几个关键调整。最重要的一点是我要求它把 timeout 显式加到网络请求里。默认情况下httpx不会等待无上限但外部模型接口慢起来能让人崩溃如果不设超时用户侧会一直挂起。另一点是模型返回的不一定是标准 JSON必须有容错处理后面测试时会说到。2.3 人工 ReviewAI 代码的三处必改项AI 展开代码后我做的第一件事不是运行而是按三条红线检查密钥管理搜索代码里有没有sk-开头或者os.environ[...]中写死敏感字符串。一旦发现硬编码立刻改成.env读取并确认.gitignore里忽略了.env。这个动作不能依赖 AI它默认不会帮你做安全审计。依赖版本AI 给的requirements.txt经常是fastapi、uvicorn这种不带版本号的写法和乱七八糟的过时包。我会快速改成固定版本至少锁住主版本号比如fastapi0.115.*。否则过几天依赖一升级接口行为就变了。异常处理AI 生成的代码多半只有成功路径对超时、模型服务不可用、返回格式异常这些场景处理很粗糙。这些只能靠有经验的开发者补上。如果项目里没有明确的全局异常处理器建议直接在 prompt 里要求“所有外部调用都进行异常捕获并返回统一的错误信息”。Review 完毕后再启动。只有一次跑通的代码才是真正的可交付代码。3. 数据库、模型路由与接口细节3.1 数据模型用 Pydantic 管好出入参FastAPI 最爽的部分就是用 Pydantic 定义请求和响应。这次需求里的SummarizeRequest和SummarizeResponse不仅能做参数校验还能作为 OpenAPI 文档的一部分展示给前端同事。要注意的是Field里的约束需要根据真实业务调校。我一开始直接把text限制为最短 10 字符但联调时发现很多短文本实际长度不到 10就放宽到了 20同时把max_length的最小值从 10 调到 20避免摘要比原文还短。这些细节 AI 是不知道的必须靠你从业务角度校准。3.2 记录调用日志与请求历史从 SQLite 开始虽然摘要服务不是数据密集型应用但为了后续查看调用量和排查问题我加了一张request_logs表记录每次调用的文本长度、耗时、状态和错误信息。本地先用 SQLite后续数据量上来再平滑切到 PostgreSQL。这里可以用 AI 生成 SQLAlchemy 模型from sqlalchemy import Column, Integer, String, DateTime, Text from sqlalchemy.sql import func class RequestLog(Base): __tablename__ request_logs id Column(Integer, primary_keyTrue) text_len Column(Integer, nullableFalse) duration_ms Column(Integer, nullableFalse) status Column(String(10), nullableFalse) error_msg Column(Text, nullableTrue) created_at Column(DateTime, server_defaultfunc.now())加日志表的好处是后面设定告警、看免费额度消耗、判断是否有人刷接口都有数据支撑。代码实现时我会在路由函数里用中间件或者依赖注入的方式统计耗时再写库。简单项目直接在函数里start time.time()也够用。3.3 模型路由同一个接口背后接不同大模型 API很多人的需求不止接一个模型比如同时接 DeepSeek、智谱、Kimi 等 API。这次我提前做了一个 Provider 抽象让同一个/v1/summarize接口可以随时切模型LLM_PROVIDERdeepseek LLM_API_KEYsk-xxx LLM_BASE_URLhttps://api.deepseek.com LLM_MODELdeepseek-chat业务代码里只需要读取当前 Provider 配置让 AI 生成一层适配器就行。实际反馈是各家 API 虽然都号称“OpenAI 兼容”但细节总有差异比如有些厂商的超时参数、响应格式、token 计费方式不同。所以不要天真地认为一套代码通吃所有厂商至少要留一个provider字段后续针对特定模型做参数微调。另外别把大模型 API 的能力直接暴露成“万能接口”。我见过不少团队把所有用户输入直接转发给大模型既不过滤也不限制长度轻则被刷走大量免费额度重则产生安全风险。服务端做一层校验和缓存很关键。4. 测试、联调与排坑实录4.1 本地跑通与 curl 验证环境准备这部分没什么捷径python -m venv venv source venv/bin/activate pip install -r requirements.txt uvicorn main:app --reload --port 8000启动后用浏览器打开http://localhost:8000/docsSwagger UI 里可以直接试接口。我更喜欢用 curl方便脚本化curl -X POST http://localhost:8000/v1/summarize \ -H Content-Type: application/json \ -d { text: 人工智能正在改变软件开发的流程从代码生成到测试用例编写AI 工具已经能承担大量重复劳动。, max_length: 100 }正常情况下会返回类似{ summary: AI 工具正在通过代码生成和测试自动化改变软件开发流程。, keywords: [人工智能, 软件开发, 代码生成] }这里有个小技巧联调前先把大模型 API 的超时调短一点比如 5 秒。否则真报错时你要等很久才能看到错误日志。我第一次就把 timeout 设成了 30 秒结果排查问题时每一步都像卡死一样。4.2 让 AI 先生成测试用例再补 Mock在 prompt 里要求 AI 生成测试用例是个很高效的做法。但必须强调“Mock 掉外部 API”否则每跑一次测试都会真实调用大模型接口既烧钱又慢。我的核心测试长这样from fastapi.testclient import TestClient from unittest.mock import AsyncMock, patch from main import app client TestClient(app) def test_health(): resp client.get(/health) assert resp.status_code 200 assert resp.json() {status: ok} patch(main.call_llm_api, new_callableAsyncMock) def test_summarize_success(mock_llm): mock_llm.return_value {summary: 测试摘要, keywords: [AI]} resp client.post(/v1/summarize, json{ text: 这是一段超过二十个字符的测试文本用于验证接口返回。, max_length: 100 }) assert resp.status_code 200 assert resp.json()[summary] 测试摘要Mock 的目的不是糊弄测试而是隔离外部依赖让测试结果稳定。真正的大模型 API 验证放到联调环境里做一两次就够了。测试用例生成出来后我还会追加几个边界用例过短文本、超过最大长度的文本、缺少字段的请求确保参数校验能拦住脏数据。4.3 常见报错速查表这次实战从开发到上线我整理了一张高频问题速查表很多都是热词里大家反复踩坑的内容。报错信息原因解决方案no api key for provider route deepseek-official服务启动时没有读取到环境变量里的 API Key检查.env文件是否被正确加载确认os.getenv(LLM_API_KEY)非空后重启服务api error: 400 this models maximum context length is ...输入文本太常超过模型上下文窗口限制在接口层增加文本长度校验或在调用模型前做截断/分段处理PermissionError: [Errno 13] Permission denied启动 Docker 或写目录时权限不够检查当前用户是否在docker组中或给运行目录配置正确的写入权限address already in use端口被其他进程占用换端口或关闭旧进程ModuleNotFoundError: pydantic依赖安装不完整重新执行pip install -r requirements.txt并锁定依赖版本返回内容不是合法 JSON大模型输出了一段 JSON 前后的解释性文字在 prompt 里要求严格 JSON 输出代码里做容错解析例如截取第一个大括号到最后一个大括号其中最坑的是“模型上下文长度超限”。大模型 API 的上下文窗口不是无限大的比如有些模型的 limit 是 1M tokens但绝大多数情况是用户传入了一篇超长文档。这时候接口层面的前置截断比模型层的兜底更有效率。你可以先用简单规则卡掉超过 8000 字符的文本然后再交给模型。4.4 避坑技巧CORS 和跨域如果这个 API 要提供给网页前端调用CORS 是必须处理的。AI 生成的代码有时会默认允许所有来源这在开发时方便但上线前要收敛。推荐按环境控制允许的域名列表示例from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_originsos.getenv(ALLOWED_ORIGINS, http://localhost:3000).split(,), allow_credentialsTrue, allow_methods[*], allow_headers[*], )顺便说一句很多人分不清 CORS 和服务器跨域的问题CORS 是浏览器拦截不是服务端拒绝。哪怕服务端没配 CORS用 curl 和 Postman 也照样能通。联调时如果前端报错先确认请求是否真的发出去了再看 CORS 配置。5. 部署上线与运行保障5.1 Docker 化从 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]构建并启动docker build -t text-summarize-api . docker run -d --name summarize-api --env-file .env -p 8000:8000 text-summarize-api这里有个易错点--env-file .env必须在docker run时显式指定否则容器里读不到环境变量接口就会报 key 未配置。另外.dockerignore里一定要排除.venv、.env、__pycache__否则镜像体积会很大而且可能把密钥打进去。如果你在配置 Docker 时遇到Permission denied while trying to connect to the docker API大概率是当前用户不在docker组里。临时方案是sudo执行或改 socket 权限长期方案是把用户加入 docker 组后重新登录sudo usermod -aG docker $USER5.2 用 systemd 管理进程而不是 CtrlC容器适合开发验证和交付但如果在虚拟机里长期跑我更习惯用 systemd 管理进程。把 venv 里的 uvicorn 作为服务进程开机自启、崩溃自动重启比手敲命令靠谱得多。写一个text-summarize-api.service[Unit] DescriptionText Summarize API Afternetwork.target [Service] Userwww-data WorkingDirectory/opt/app EnvironmentFile/opt/app/.env ExecStart/opt/app/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 Restartalways RestartSec5 [Install] WantedBymulti-user.target注意 systemd 的EnvironmentFile不支持export前缀.env里不能写export LLM_API_KEYxxx直接写LLM_API_KEYxxx即可。启动后用以下命令控制sudo systemctl daemon-reload sudo systemctl enable --now text-summarize-api journalctl -u text-summarize-api -f这种方案和 Docker 不冲突生产环境也可以用 Nginx 反向代理到容器的端口。选择哪种取决于团队运维习惯不必纠结。5.3 调用量、免费额度与缓存策略大模型 API 是按 token 计费的虽然大家都有一定免费额度但自己用着玩和上线给用户用完全是两码事。上线前我做了三件事加缓存相同text一天内不做重复调用。简单起见用内存字典加过期时间后续可以换 Redis。这样重复请求不会再增加调用量。加限流用 slowapi 或者自己在中间件里做按 IP 的请求频率限制避免被脚本刷爆。记录 token 用量从模型返回的usage字段里取出total_tokens写进日志。缓存那一段代码如下cache {} async def get_summary_cached(text: str, max_length: int): key f{text}_{max_length} if key in cache and cache[key][expire_at] time.time(): return cache[key][data] data await call_llm_api(text, max_length) cache[key] {data: data, expire_at: time.time() 86400} return data这样做的好处非常明显测试和前端联调时反复点同一个按钮不会反复消耗大模型 API 额度。以前我没加缓存光联调一天就跑掉了上万 token加了缓存之后本地测试基本零成本。5.4 安全加固上线前必做的几件事最后说安全因为 AI 生成的代码默认是不考虑安全粒度的。上线前至少完成这四项启用 HTTPSNginx 或网关层配置 TLS 证书不能用明文 HTTP 传数据。限制请求体大小FastAPI 里限制text最大长度前面已经做了 8000 字符。Nginx 层也可以再设client_max_body_size 512k。接口接入身份校验哪怕只是简单的 Bearer Token也能拦住绝大多数扫描流量。密钥隔离大模型 API key 只存在服务端调用方永远不接触。这些不是可选项。很多人觉得“只是一个内部 API”但暴露在公网后扫描器和爬虫随时都可能光顾。我在真实项目中见过因为没加请求体大小限制被一段 10MB 的请求直接打爆内存的例子代价远远大于提前加一行配置。跑完这个项目我最大的感受是AI 不是银弹但绝对是提效杠杆。真正有经验的开发者用 AI会把需求拆得更细、把约束写得清楚AI 生成的东西质量自然就高。说白了AI 写的是初稿做决定和背锅的还是你自己。这次我踩过最大的坑就是低估了环境变量配置的重要性服务在本地一切正常部署后因为忘记读取.env导致所有请求都返回 500。后来我在所有可能启动服务的方式里都验证了一遍才彻底解决。下一步我准备在这个小项目上加批量摘要任务、接入更多 Provider顺便把 Redis 缓存和限流做实。最后分享一个团队内部的小技巧AI 生成的代码合并前至少让另一个有经验的人 Review如果只是个人项目也务必在本地跑完pytest再上线。这样就算 AI 帮你把代码写得飞快你还是能守住最基本的工程质量底线。