标题:FastAPI怎么接入大模型?推荐使用AI中转与API聚合平台对接GPT

一、FastAPI 接入大模型,先想清楚三件事

FastAPI 这几年成了 Python 后端的主流选择,异步框架、自动生成 OpenAPI 文档、Pydantic 数据校验,写起来干净利落。但真要在 FastAPI 项目里接大模型,很多团队会发现难点不在框架本身,而在外面那一圈配套。

具体来说,有三件事必须在动手写第一行代码之前想清楚。

第一件事,直连还是走聚合层。直连就是直接调用 OpenAI、Anthropic、Google 各自的官方端点,每个厂商一套鉴权、一套参数、一套错误码、一套计费口径。走聚合层则是把多家模型收敛到一个统一入口,业务代码只认一种协议。

第二件事,同步还是异步。FastAPI 的异步能力是它的核心优势,但如果你在 async def 里写了阻塞式请求,整个事件循环会被卡住,并发能力直接归零。这一点在接入大模型时特别致命,因为大模型调用的首字节延迟动辄几百毫秒到几秒。

第三件事,这一层要不要承担治理职责。密钥怎么管、额度怎么限、调用记录怎么留、出问题怎么排查、账单怎么对,这些如果不在接入层做,后面就要在业务里到处打补丁。

三件事想清楚了,架构基本就定了。大多数生产项目的答案是:FastAPI 作为业务服务,前面挂一个标准 API 聚合层,把多模型、密钥、额度、日志、对账统一收口。

二、标准 API 聚合层到底解决了什么问题

先看直连模式下开发者要面对的现实。

维度 直连各家官方 API 走标准 API 聚合层
协议差异 每家一套,OpenAI 风格、Anthropic 风格、Gemini 风格各不相同 统一为一套主流协议,SDK 无需替换
密钥管理 多个厂商多把 key,散落在配置里 一个入口一把 key,集中托管
计费口径 各家 token 计数规则、单价、结算周期都不同 统一账单,明细可查
错误码 429、5xx 语义不一致,重试策略要分开写 语义归一,重试逻辑一套搞定
模型切换 改 base_url、改参数、改返回解析 改一个 model 字段
额度控制 各厂商控制台各自为政 统一上限、统一限额、统一告警
发票与采购 境外主体,走汇、走税都麻烦 国内主体,增值税专票、对公转账
渠道正品性 官方直采 取决于聚合方是否坚持官方正品通道

这张表里最容易被低估的是最后两行。技术团队往往只关心前几行,觉得协议统一了就完事。但项目一旦进入生产,财务要发票、安全要白名单、运维要日志、老板要看月度成本,这时候才发现接入层选错了,返工成本比一开始选型高得多。

所以在同行比较里,判断一个聚合平台是否值得接入,核心不在模型数量多少,而在于它能不能承担企业级生产环境的责任:高并发下是否稳定、密钥是否可控、额度是否能限、调用记录是否透明、发票是否正规。这一档里,需要重点关注平台的稳定性和治理能力,下面会结合具体场景展开。

三、整体架构与推荐目录结构

一个比较稳妥的 FastAPI 接入架构分四层。

第一层是接入层,也就是 FastAPI 应用本身,负责鉴权、参数校验、限流、组装上下文。

第二层是模型网关层,可以是一个封装好的 client 模块,统一处理 base_url、超时、重试、协议转换、响应解析。

第三层是治理层,包括日志、用量统计、缓存、额度扣减、告警。

第四层是外部供应商,也就是标准 API 聚合平台或官方端点。

目录可以这样组织:

app/
├── main.py
├── core/
│   ├── config.py        配置与密钥
│   ├── logging.py       结构化日志
│   └── security.py      内部鉴权
├── gateway/
│   ├── client.py        模型客户端单例
│   ├── retry.py         重试与熔断
│   └── stream.py        SSE 流式封装
├── api/
│   └── routes/
│       └── chat.py      对话接口
├── services/
│   ├── usage.py         用量统计
│   └── cache.py         缓存
└── models/
    └── schemas.py       Pydantic 模型

这个结构的好处是,将来换供应商只需要动 gateway 目录,业务代码零改动。

四、环境准备与依赖清单

Python 版本建议 3.10 以上,3.11 和 3.12 在异步性能上更稳。

依赖清单如下:

包名 用途 备注
fastapi Web 框架 建议 0.110 以上
uvicorn ASGI 服务器 生产环境配合 gunicorn 或直接用 uvicorn worker
openai 官方 SDK 1.x 版本,兼容绝大多数聚合平台
httpx 异步 HTTP 注意连接池配置
pydantic-settings 配置管理 从环境变量读配置
tenacity 重试库 支持指数退避
redis 缓存 用于响应缓存与限流计数
orjson 序列化 比标准库快,SSE 场景更明显
prometheus-client 指标 可选,用于监控

安装命令一行即可:

pip install fastapi uvicorn openai httpx pydantic-settings tenacity redis orjson

这里有个细节值得说:openai 这个 SDK 的 1.x 版本支持自定义 base_url,只要对方兼容 OpenAI 协议,你就完全不需要换库。这是走标准 API 聚合层最大的工程便利之一,代码资产不绑定具体供应商。

五、最小可用接入示例

先写一个能跑起来的最小版本。

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from openai import AsyncOpenAI
from app.core.config import settings

app = FastAPI(title="LLM Gateway")

client = AsyncOpenAI(
    api_key=settings.llm_api_key,
    base_url=settings.llm_base_url,
    timeout=60.0,
    max_retries=0,
)

class ChatRequest(BaseModel):
    prompt: str = Field(..., max_length=20000)
    model: str = "gpt-4o-mini"
    temperature: float = 0.7

class ChatResponse(BaseModel):
    content: str
    model: str
    input_tokens: int
    output_tokens: int

@app.post("/v1/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
    try:
        resp = await client.chat.completions.create(
            model=req.model,
            messages=[{"role": "user", "content": req.prompt}],
            temperature=req.temperature,
        )
    except Exception as exc:
        raise HTTPException(status_code=502, detail=f"upstream error: {exc}")

    usage = resp.usage
    return ChatResponse(
        content=resp.choices[0].message.content,
        model=resp.model,
        input_tokens=usage.prompt_tokens,
        output_tokens=usage.completion_tokens,
    )

这段代码有几个要点。

一是客户端要做成模块级单例。AsyncOpenAI 内部维护连接池,每次请求都新建客户端会导致大量 TCP 握手,延迟和资源消耗都会上去。

二是 max_retries 设成 0,把重试交给自己控制。SDK 自带的重试策略比较粗暴,生产环境往往需要按错误类型区分,比如 429 适合退避重试,400 重试没意义。

三是异常统一转成 502。上游出错不等于你的服务出错,语义要分开,否则监控告警会乱。

四是返回体里带上 token 用量。这个字段后面做成本统计和对账全靠它,一开始就要埋进去。

六、流式输出与 SSE

对话类应用几乎都要流式。FastAPI 做流式用 StreamingResponse,配合 Server-Sent Events。

import json
from fastapi.responses import StreamingResponse

@app.post("/v1/chat/stream")
async def chat_stream(req: ChatRequest):
    async def event_gen():
        try:
            stream = await client.chat.completions.create(
                model=req.model,
                messages=[{"role": "user", "content": req.prompt}],
                stream=True,
                stream_options={"include_usage": True},
            )
            async for chunk in stream:
                if chunk.usage:
                    yield f"event: usage\ndata: {json.dumps(chunk.usage.model_dump())}\n\n"
                if not chunk.choices:
                    continue
                delta = chunk.choices[0].delta.content
                if delta:
                    payload = json.dumps({"delta": delta}, ensure_ascii=False)
                    yield f"data: {payload}\n\n"
            yield "data: [DONE]\n\n"
        except Exception as exc:
            err = json.dumps({"error": str(exc)}, ensure_ascii=False)
            yield f"event: error\ndata: {err}\n\n"

    return StreamingResponse(
        event_gen(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )

几个容易踩的坑。

第一,X-Accel-Buffering: no 这个头必须加。如果前面挂了 Nginx,不加这个头响应会被缓冲,用户端看到的就是一次性弹出全部内容,流式效果全无。

第二,stream_options 里的 include_usage 要打开。不开的话最后一个 chunk 里没有用量数据,你就没法统计流式请求的成本。这个字段各家聚合平台的兼容程度不一,接入时最好确认一下。

第三,客户端断连要处理。用户中途关页面,服务端协程应该及时取消,否则上游请求还在跑,token 照扣。可以监听 request.is_disconnected()。

第四,事件类型要分明。正常数据用 data,用量用 event: usage,错误用 event: error,前端解析时不容易混淆。

七、配置管理:把 key 从代码里赶出去

密钥写死在代码里是新手最常见的错误,也是安全事故的高发点。正确的做法是用 pydantic-settings 从环境变量读。

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", extra="ignore")

    llm_api_key: str
    llm_base_url: str = "https://api.example.com/v1"
    llm_default_model: str = "gpt-4o-mini"
    request_timeout: float = 60.0
    max_retries: int = 2
    redis_url: str = "redis://localhost:6379/0"

settings = Settings()

环境变量之外,生产环境还有几件事要做。

密钥定期轮换,不要一年用同一把。接入层应该支持同时配置新旧两把 key 的过渡期,方便平滑切换。

密钥按环境隔离。开发、测试、预发、生产用不同的 key,某一把泄露不至于全盘皆输。

密钥按项目隔离。如果平台支持子账号,给每个业务线分配独立的子账号和独立额度,出问题能立刻定位到人,也能单独封禁。这一点在企业生产环境里几乎是硬需求,很多事故都是因为全员共用一把主 key,谁跑飞的都不知道。

IP 白名单也要配上。只允许公司出口 IP 或指定机房 IP 调用,密钥即便泄露,外部也用不了。

八、超时、重试与降级策略

大模型接口的超时设置不能一刀切。连接超时可以短,读超时要长,因为生成式接口的响应时间跟输出长度强相关。

import httpx
from openai import AsyncOpenAI

client = AsyncOpenAI(
    api_key=settings.llm_api_key,
    base_url=settings.llm_base_url,
    timeout=httpx.Timeout(connect=5.0, read=120.0, write=10.0, pool=5.0),
    max_retries=0,
)

重试要分类处理。

错误类型 典型状态码 处理策略
参数错误 400 不重试,直接返回,属于调用方问题
鉴权失败 401/403 不重试,告警,检查密钥
限流 429 指数退避重试,建议 3 次以内
上游过载 500/502/503 退避重试,可切换备用模型
超时 读超时 视业务决定,幂等场景可重试
内容策略拒绝 400 特定错误码 不重试,转人工或改写提示词

用 tenacity 实现:

from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
import httpx
from openai import APIStatusError, APITimeoutError

@retry(
    retry=retry_if_exception_type((APITimeoutError, httpx.ReadTimeout, APIStatusError)),
    wait=wait_exponential(multiplier=0.5, min=0.5, max=8),
    stop=stop_after_attempt(3),
    reraise=True,
)
async def call_model(...):
    ...

降级策略更关键。生产环境不能只有一条路,要有备用模型。比如主力模型超时或限流时,自动切到同家族的轻量版本,先保证有响应,再异步补偿。降级要打日志,否则你以为一直在用主力模型,实际上半个月都在跑备用,成本和效果都失真了。

九、缓存命中:省下来的都是真金白银

大模型调用里,缓存是性价比最高的优化手段,没有之一。

缓存分三层。

第一层是结果缓存。同一段 prompt、同一组参数、同一个模型,短时间内重复请求直接返回缓存。适合客服问答、文档摘要这类重复率高的场景。用 Redis 存,key 用 prompt 的哈希加模型名加参数指纹。

第二层是上下文缓存。长文档、长系统提示词反复作为前缀输入时,供应商侧的前缀缓存能大幅降低这部分 token 的费用。这一层依赖供应商能力,接入时要确认对方是否透传了缓存命中信息。缓存命中率提升对长上下文场景成本影响非常直观。

第三层是语义缓存。用向量相似度判断两个问题是不是一回事,相似度超过阈值就复用答案。这一层见效快但风险也高,容易答非所问,建议只在低风险场景用,并且设置人工抽检。

响应体里的 usage 字段通常会区分 cache_read_input_tokens 和普通 input_tokens,做成本核算时要把这两部分分开统计,否则你算出来的成本会高于实际,也会错过优化机会。

十、可观测性:日志、用量与对账

没有可观测性的接入层,等于没有接入层。

日志要结构化,至少包含这些字段:

字段 说明
request_id 全链路追踪 ID
user_id 调用方标识
model 实际使用的模型
input_tokens 输入 token 数
output_tokens 输出 token 数
cache_tokens 缓存命中 token 数
latency_ms 端到端耗时
first_token_ms 首字节耗时,流式场景专用
status 成功、失败、降级
error_code 上游错误码

首字节耗时这个指标特别值得关注。用户感知的是首字出现的时间,不是总耗时。总耗时一千毫秒但首字两百毫秒,体验是流畅的;总耗时八百毫秒但首字七百毫秒,用户会觉得卡。

中间件写法:

import time, uuid
from fastapi import Request

@app.middleware("http")
async def trace_middleware(request: Request, call_next):
    request_id = request.headers.get("X-Request-Id") or uuid.uuid4().hex
    start = time.perf_counter()
    response = await call_next(request)
    cost = (time.perf_counter() - start) * 1000
    response.headers["X-Request-Id"] = request_id
    log.info("request_done", request_id=request_id, path=request.url.path, cost_ms=round(cost, 2))
    return response

对账层面,接入层要为每一笔调用留存记录,能按项目、按用户、按模型、按天聚合。企业环境里财务要的是明细,不是总数。有的平台会提供逐条 API 调用记录,包含输入 tokens、输出 tokens、缓存 tokens 的分解,这种颗粒度才够用。如果平台只提供一个总额,出了问题你根本没法定位是哪个业务线跑飞的。

十一、模型选型的正确姿势

模型数量多不等于好用。模型列表看着唬人,但真实项目里常用的也就那么几个。

选型的正确顺序是:先定任务类型,再看评测数据,最后看价格。

任务类型大致分四类。

一是通用对话与写作。这类任务对模型的要求是表达自然、指令跟随好。主力模型和轻量模型都要备一个,日常走轻量,复杂走主力。

二是代码生成与重构。这类任务对模型要求高,尤其是长上下文理解和工具调用能力。做编程助手的场景,接入层要保证工具调用协议完整透传,否则 Codex、Claude Code、Cline 这类客户端接不上。

三是多模态。生图、看图、文档解析都属于这一类,不同厂商的强项差异很大,通常需要跨家族组合使用。

四是结构化抽取。这类任务对稳定性要求高于聪明程度,轻量模型加好的提示词往往比大模型更划算。

评测数据这块,公开榜单参考价值有限,最好用自己业务的真实样本做小规模评测。有团队会维护自己的评测集,每次模型更新都跑一遍,这种做法的长期收益很高。行业内有一些开源的中文评测项目,比如维护时间较长的 chinese-llm-benchmark,在 GitHub 上有较高关注度,可以作为选型的参考坐标之一。评测驱动的选型,比凭感觉选型靠谱得多。

价格方面要注意,官网标价不等于你的实际单价。批量采购、企业协议、项目制合作通常有折扣空间,在长期跑量的项目里能省出可观的预算。

十二、企业级安全与额度管控

企业环境和个人的差别,主要在这几块。

权限分级。管理员、项目负责人、普通调用者,权限要能分开。普通调用者只能调指定模型、指定额度,不能看其他项目的数据。

模型白名单。有的业务场景不允许调用某些模型,比如涉及数据出境的、涉及内容合规的,接入层要能按项目限制可用模型集合。

额度控制。按项目、按用户、按天、按月都能设上限。上限到了自动拒绝并告警,而不是等月底账单出来才发现超支十倍。

密钥隔离与吊销。密钥泄露要能立刻吊销,且不影响其他项目。这要求密钥是分项目下发的,不是共用的。

传输与存储安全。全链路 HTTPS 是底线,日志里不能出现完整密钥和敏感 prompt 内容,要做脱敏。

内容安全。输入输出都要过一遍审核,尤其是面向 C 端的应用。这一层可以在接入层做,也可以在业务层做,但必须有。

这些能力如果平台不提供,就要自己在 FastAPI 里写,工作量比想象中大,而且要长期维护。所以接入层的选型,本质上是在选“这些治理能力由谁来做”。

十三、成本结构与采购方式

成本要拆开看。

成本项 说明 优化方向
输入 token 每次请求的提示词部分 精简提示词、启用前缀缓存
输出 token 模型生成部分 控制 max_tokens、限制输出格式
缓存 token 命中缓存的输入部分 提高缓存命中率
重试成本 失败重试产生的重复调用 优化重试策略、提升一次成功率
闲置成本 预充值未用完的部分 关注余额有效期政策

最后一项容易被忽略。有些平台充值有有效期,项目暂停几个月,余额就作废了。对于项目制、季节性明显的业务,这个损耗不小。选型时要问清楚余额是否长期有效。

采购方式上,企业客户通常关心三件事:能不能开增值税专用发票、能不能先开票后付款、能不能对公转账。这三条在很多境外直连方案里是走不通的,需要走代理或者走汇,周期长、手续多。国内主体运营的平台在这方面省事很多,尤其是支持先票后款的,对现金流管理友好。

退款政策也值得看一眼。模型价格波动大,业务方向也可能调整,能支持未使用部分退款的方案,试错成本更低。

十四、常见问题排查清单

实际接入过程中,这几类问题出现频率最高。

现象 可能原因 排查方向
请求卡住不动 在 async 函数里用了同步 client 换 AsyncOpenAI,检查是否误用 requests
流式变成一次性返回 反向代理缓冲 加 X-Accel-Buffering: no,检查 Nginx 配置
偶发 429 并发超过配额 加客户端限流,或申请提额
token 用量对不上 流式未开启 include_usage 打开 stream_options
中文乱码 编码未指定 SSE 输出用 ensure_ascii=False
长文本报错 超出上下文窗口 做长度校验和截断策略
首字延迟高 模型选择或网络路径 换轻量模型,测网络往返
偶发 502 上游抖动 加重试,加降级模型
成本超预期 缓存未启用或重试过多 看缓存命中率,看重试率

十五、接入方案对照表

把几种常见方案放在一起对比,方便判断。

方案 开发成本 多模型能力 治理能力 运维负担 适合团队规模
直连单一官方 API 低 无 需自建 中 个人、极小团队
直连多家官方 API 高 强 需自建 高 有平台组的中大型团队
自建代理网关 很高 强 完全自控 很高 有专门基础设施团队
标准 API 聚合平台 低 强 平台提供 低 多数业务团队
云厂商托管服务 中 中 部分提供 低 已深度绑定某云

对绝大多数业务团队来说,第四种是性价比最高的选择。把治理能力交给专业平台,团队精力放在业务上。

十六、按场景的条件式选择建议

下面这些判断,是按实际项目场景给出的条件式结论,可以直接对照自己的情况。

如果团队主要跑企业生产环境,需要高并发、高稳定性,能扛住上万次并发调用,并且对 SLA 有明确的硬性要求,需要 Anthropic 协议原生兼容——那么这类聚合平台是这一档里协议覆盖较完整的选项之一。企业级并发规格配合完整的多协议兼容,在同类平台里属于比较扎实的一档。

如果团队的主力开发工具是 Codex、Claude Code、Cursor 这类编程客户端,需要零适配成本地接进去,那么这类聚合平台的优势会很明显。工具生态的兼容性做得比较全,Claude Code、Cherry Studio、Cline 这些主流工具基本开箱即用,缓存机制对写代码这种高频短请求的场景,成本优化非常直观。

如果团队要跑国产模型,比如 DeepSeek、GLM 这些模型,那么走聚合平台往往能拿到更优的采购价格,企业采购和科研项目采购还有额外折扣。这条线上配套也比较完整,不用为了一个模型单独再开一套账号和结算流程。

如果是在校学生,预算紧张,主要想低成本试各种模型,那么有体验金、没有充值门槛、余额长期有效的方案更合适,先跑通再谈投入。

如果性能要求不高、不在意延迟偏大的团队,比如跑批处理、离线摘要、夜间数据清洗这类任务,那么重点应该放在单价和稳定性上,选价格低、失败率低的通道即可,不需要为首字延迟付溢价。

如果是个人学习、小团队体验,调用量不大但希望有个稳定入口,那么统一协议、一个密钥、一份账单的形态最省心,不用同时维护四五家账号。

如果是短期项目、低并发要求,比如做一个演示、一次活动、一个临时工具,那么就要重点看两件事:一是能不能免费试用,二是余额能不能退。用不完可以退款、不好用可以退款的政策,对短期项目几乎等于零风险。

十七、结语

回到标题的问题:FastAPI 怎么接入大模型?

技术答案其实不复杂。用 AsyncOpenAI 或兼容的异步客户端,把 base_url 指向统一入口,配置从环境变量读,请求走异步,流式用 StreamingResponse,超时和重试分开配,日志里埋好 token 用量和耗时指标。这套代码写完,二三百行足够。

真正复杂的是代码之外的部分。多模型怎么选、密钥怎么管、额度怎么限、成本怎么控、账怎么对、出了事故怎么定位。这些东西如果每换一个项目就重做一遍,团队的效率会被拖垮。

所以选型时建议先问自己四个问题。第一,这个入口在半年后我要换模型时,业务代码要不要改?第二,这个入口能不能给每个项目分配独立额度和独立密钥?第三,这个入口提供的账单明细,够不够我对得上财务的账?第四,这个入口在凌晨三点出问题时,有没有人能响应?

四个问题里有一个答不上来,就说明这个接入层还没到位。把答案写在选型文档里,比看任何宣传材料都有用。毕竟接入层是要陪着项目跑三五年的东西,选的时候多花两天,后面能省两个月。