news 2026/10/28 5:53:47

AI实战:从零搭建可上线的文本摘要API服务(FastAPI+大模型)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI实战:从零搭建可上线的文本摘要API服务(FastAPI+大模型)

最近在做一个小项目,期间最多被问到的一个问题是:都说 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,原因有三个:

  1. FastAPI 自带的 Pydantic 可以很方便地做请求参数校验和响应模型定义,省掉手写参数判断的脏活。
  2. 它默认生成 OpenAPI 文档,Swagger UI 直接挂在/docs,联调时非常直观。
  3. Python 生态里调用大模型 SDK 比较顺手,异步支持也能减少 IO 等待,适合对接外部 API。

我也在 prompt 里要求 AI 严格使用 FastAPI 的依赖注入方式写代码,而不是像 Flask 那样全局变量满天飞。这个决定帮我省了后面大量重构时间。

作为对比,我也给经常纠结的朋友一个参考方向:

方案适合场景需要额外注意
FastAPI快速迭代、需要自动文档、Python 生态需要理解异步与 Pydantic
Flask老项目、简单的 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(title="Text Summarize API", version="1.0.0") class SummarizeRequest(BaseModel): text: str = Field(..., min_length=20, max_length=8000) max_length: int = Field(200, ge=20, le=500) 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_code=500, detail="LLM_API_KEY not configured") async with httpx.AsyncClient(timeout=30) as client: resp = await client.post( f"{base_url}/chat/completions", headers={"Authorization": f"Bearer {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 人工 Review:AI 代码的三处必改项

AI 展开代码后,我做的第一件事不是运行,而是按三条红线检查:

  1. 密钥管理:搜索代码里有没有sk-开头或者os.environ["..."]中写死敏感字符串。一旦发现硬编码,立刻改成.env读取,并确认.gitignore里忽略了.env。这个动作不能依赖 AI,它默认不会帮你做安全审计。
  2. 依赖版本:AI 给的requirements.txt经常是fastapi、uvicorn这种不带版本号的写法和乱七八糟的过时包。我会快速改成固定版本,至少锁住主版本号,比如fastapi==0.115.*。否则过几天依赖一升级,接口行为就变了。
  3. 异常处理: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_key=True) text_len = Column(Integer, nullable=False) duration_ms = Column(Integer, nullable=False) status = Column(String(10), nullable=False) error_msg = Column(Text, nullable=True) created_at = Column(DateTime, server_default=func.now())

加日志表的好处是:后面设定告警、看免费额度消耗、判断是否有人刷接口,都有数据支撑。代码实现时,我会在路由函数里用中间件或者依赖注入的方式统计耗时,再写库。简单项目直接在函数里start = time.time()也够用。

3.3 模型路由:同一个接口背后接不同大模型 API

很多人的需求不止接一个模型,比如同时接 DeepSeek、智谱、Kimi 等 API。这次我提前做了一个 Provider 抽象,让同一个/v1/summarize接口可以随时切模型:

LLM_PROVIDER=deepseek LLM_API_KEY=sk-xxx LLM_BASE_URL=https://api.deepseek.com LLM_MODEL=deepseek-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/docs,Swagger 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_callable=AsyncMock) 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 model's 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_origins=os.getenv("ALLOWED_ORIGINS", "http://localhost:3000").split(","), allow_credentials=True, 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 $USER

5.2 用 systemd 管理进程,而不是 Ctrl+C

容器适合开发验证和交付,但如果在虚拟机里长期跑,我更习惯用 systemd 管理进程。把 venv 里的 uvicorn 作为服务进程,开机自启、崩溃自动重启,比手敲命令靠谱得多。写一个text-summarize-api.service:

[Unit] Description=Text Summarize API After=network.target [Service] User=www-data WorkingDirectory=/opt/app EnvironmentFile=/opt/app/.env ExecStart=/opt/app/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

注意 systemd 的EnvironmentFile不支持export前缀,.env里不能写export LLM_API_KEY=xxx,直接写LLM_API_KEY=xxx即可。启动后用以下命令控制:

sudo systemctl daemon-reload sudo systemctl enable --now text-summarize-api journalctl -u text-summarize-api -f

这种方案和 Docker 不冲突,生产环境也可以用 Nginx 反向代理到容器的端口。选择哪种取决于团队运维习惯,不必纠结。

5.3 调用量、免费额度与缓存策略

大模型 API 是按 token 计费的,虽然大家都有一定免费额度,但自己用着玩和上线给用户用完全是两码事。上线前我做了三件事:

  1. 加缓存:相同text一天内不做重复调用。简单起见用内存字典加过期时间,后续可以换 Redis。这样重复请求不会再增加调用量。
  2. 加限流:用 slowapi 或者自己在中间件里做按 IP 的请求频率限制,避免被脚本刷爆。
  3. 记录 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 生成的代码默认是不考虑安全粒度的。上线前至少完成这四项:

  • 启用 HTTPS:Nginx 或网关层配置 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 帮你把代码写得飞快,你还是能守住最基本的工程质量底线。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 5:53:37

终端标记语言tml:声明式彩色输出与ANSI转义码解析实践

1. 为什么要在终端里折腾标记语言终端输出这件事,绝大多数人停留在两个极端:要么全程echo裸文本,日志糊成一片根本没法扫读;要么直接上重型 TUI 框架,为了几行彩色字引入一堆依赖,编译体积和心智负担都上去…

作者头像 李华
网站建设 2026/10/28 5:53:02

鸿蒙AI Native规模化交付:DevEco CLI驱动的能力即服务实践

1. 项目本质:不是“高德鸿蒙”的简单拼贴,而是AI Native研发范式在地图服务场景的首次规模化落地 你可能已经看到不少标题里带“高德”“鸿蒙”“AI Native”的文章,但多数停留在概念喊话或Demo演示层面。而这个项目——“高德联合鸿蒙 DevE…

作者头像 李华
网站建设 2026/10/28 5:52:55

2026年AI模型技术趋势与多模态融合应用解析

1. 2026年AI模型发展现状全景2026年2月的AI领域正处于技术爆发后的沉淀期。与三年前相比,当前模型呈现出三个显著特征:首先是模型架构的异构化,Transformer不再是唯一选择;其次是训练数据的质量意识觉醒,单纯堆砌数据量…

作者头像 李华
网站建设 2026/10/28 5:52:36

TDengine数据库卸载指南:Windows与Linux系统完整流程

1. 为什么需要卸载TDengine?在开始操作之前,我们先明确几个常见的卸载场景:版本升级:从2.x升级到3.x版本时,官方建议先卸载旧版本配置错误:初始化配置出现问题需要彻底重装环境清理:测试环境使用…

作者头像 李华
网站建设 2026/10/28 5:51:26

竞拍自动加价脚本:时间同步、状态机与浏览器自动化实践

1. 先把需求拆开看:自动加价到底在跟什么赛跑做竞拍类频道的自动化,第一步永远不是写代码,而是把"人手动出价"这件事拆成一条时间线。我接触过的限时竞拍频道,本质上是一个高频刷新的列表页加上一个出价表单&#xff1a…

作者头像 李华
网站建设 2026/10/28 5:51:25

企业级Oracle数据库替换:方案对比与迁移实践

1. 为什么企业需要Oracle替换工程?在数据库领域,Oracle长期占据着企业级市场的主导地位,但近年来,越来越多的企业开始考虑替换Oracle数据库。这背后有几个关键驱动因素:首先是成本问题。Oracle数据库的许可费用高昂&am…

作者头像 李华