你在本地把 FastAPI 服务跑起来,刚调/chat就碰到KeyError,进程直接退出。问题通常不在代码逻辑,而在环境变量没有进入启动服务的那个终端。读完这篇教程,你能拿到一个可直接复制的封装骨架,包含流式输出、错误透传和三个高频排错检查点。
场景与前置条件
这篇教程不是从零解释什么是 FastAPI,也不做平台对比。它解决一个具体问题:你手上有一段能调通大模型 API 的脚本,想把它封装成稳定的 HTTP 服务,同时不希望每次请求都重复初始化客户端,或者把密钥写死在仓库里。
开始前需要准备:Python 3.10 及以上;一个可用 API Key,前缀是sk_live_;模型调用名从模型广场详情页复制,示例用minimax-m2.5。如果你已经有 FastAPI 项目,但没有独立环境变量管理,建议先看下一节。本文只覆盖 OpenAI 兼容对话端点,响应和错误结构都按这个端点来写。
环境准备
先建目录并安装三个依赖:fastapi、uvicorn、openai。openai是调用客户端,uvicorn负责把 FastAPI 服务跑起来。示例接的是千木的 OpenAI 兼容端点,接入参数只改两处:Base URL 和 API Key。
密钥不要硬编码进 Python 文件。原因很直接:一旦提交到 Git,泄露很难撤销。更稳妥的做法是写入当前 shell 的环境变量,代码通过os.environ读取。Linux/macOS 执行下面命令:
exportSILVAMUX_API_KEY='sk_live_你的真实密钥'如果服务从别的终端启动,这个变量不会自动存在。Windows 用户可以用set或$env:设置同名变量。验证时用printenv查看,但密钥别打进日志。
最小可跑示例
下面是一个完整脚本,保存成main.py后可以直接启动。它封装了一个/chat路由,接收提示词和模型名,返回文本。
importosfromfastapiimportFastAPIfrompydanticimportBaseModel,FieldfromopenaiimportOpenAI app=FastAPI(title='llm-gateway')client=OpenAI(api_key=os.environ['SILVAMUX_API_KEY'],base_url='https://www.silvamux.com/api/v1',)classChatBody(BaseModel):prompt:str=Field(...,description='用户输入')model:str='minimax-m2.5'max_tokens:int=512@app.post('/chat')defchat(body:ChatBody):resp=client.chat.completions.create(model=body.model,messages=[{'role':'user','content':body.prompt}],max_tokens=body.max_tokens,)return{'text':resp.choices[0].message.content,'model':body.model}第 4 行的 Base URL 是关键。域名、/api/v1路径都不能改,省掉任一部分都会请求失败。第 5 行从环境变量读取密钥,避免密钥出现在源码和 Git 历史里。
model的默认值是minimax-m2.5。它必须来自模型广场的调用名,不建议根据展示名自己拼接版本。填成展示名或其他 alias 可能返回 404。max_tokens这里给默认值 512,否则大模型可能输出过长,拖慢接口响应。
启动命令:
uvicorn main:app--reload--port8000测试请求:
curl-XPOST http://127.0.0.1:8000/chat-H'Content-Type: application/json'-d'{"prompt":"用一句话解释 FastAPI","model":"minimax-m2.5"}'返回里text就是大模型内容。走到这里,你就把一个 API 调用封装成了一个 FastAPI 服务。
逐步扩展
最小示例只能等完整返回。下一步加两个实用能力:流式输出和错误透传。流式场景里,大模型 API 会返回 SSE 事件,客户端逐段渲染。这里要用stream=True,并且不能只等data: [DONE]结束。
importjsonfromfastapi.responsesimportStreamingResponse@app.post('/chat/stream')defchat_stream(body:ChatBody):defgenerate():stream=client.chat.completions.create(model=body.model,messages=[{'role':'user','content':body.prompt}],max_tokens=body.max_tokens,stream=True,)forchunkinstream:ifnotchunk.choices:continuedelta=chunk.choices[0].deltaifdeltaanddelta.content:payload={'text':delta.content}yield'data: '+json.dumps(payload,ensure_ascii=False)+chr(10)+chr(10)yield'data: [DONE]'+chr(10)+chr(10)returnStreamingResponse(generate(),media_type='text/event-stream')if not chunk.choices这行是在兼容末端用量 chunk,它可能没有 choices 但有 usage。客户端应该以流结束为准,而不是只依赖data: [DONE]。这是实际很容易踩的坑。StreamingResponse的media_type必须是text/event-stream,否则浏览器不会按 SSE 解析。
第二个扩展是错误透传。平台返回的错误结构里带error.message、error.type和error.code字段,type的值通常是gateway_error。模型侧 4xx/5xx 会脱敏后透传。FastAPI 服务不应该把这些错误吞成 500。可以在路由里捕获openai.APIStatusError,把状态码原样抛给调用方。
fromfastapiimportHTTPExceptionfromopenaiimportAPIStatusError@app.post('/chat')defchat(body:ChatBody):try:resp=client.chat.completions.create(model=body.model,messages=[{'role':'user','content':body.prompt}],max_tokens=body.max_tokens,)exceptAPIStatusErrorasexc:raiseHTTPException(status_code=exc.status_code,detail=exc.bodyifexc.bodyelsestr(exc),)return{'text':resp.choices[0].message.content}这里exc.status_code保持上游状态码,exc.body保留脱敏后的错误信息。不要手动把 429、401 翻译成 500,调用方需要知道应该改参数还是检查 key。
排错
ModuleNotFoundError: No module named ‘fastapi’
报错文本:
ModuleNotFoundError: No module named 'fastapi'直接原因:当前解释器没有安装 FastAPI,或者 IDE 选错虚拟环境。
具体修法:在启动服务的同一环境执行pip install fastapi uvicorn openai。如果用了 venv,先激活再装。
KeyError:启动时缺少环境变量
报错文本:
KeyError: 'SILVAMUX_API_KEY'直接原因:进程启动时没有读到这个环境变量。常见于变量在另一个终端导出,或者用nohup/systemd 启动时环境被清空。
具体修法:在真正运行uvicorn的终端里重新执行上文的环境变量导出命令,或者把它写进 systemd 的Environment=配置。
openai.AuthenticationError: Error code: 401
报错文本:
openai.AuthenticationError: Error code: 401 - {'error': {'message': '...', 'type': 'gateway_error', 'code': 'ERROR_CODE'}}直接原因:API Key 错误、未生效或复制时多了空格。
具体修法:检查环境变量里的 key 是否以sk_live_开头,确认它属于当前绑定的项目。401 不要重试,直接查认证。
openai.NotFoundError: Error code: 404
报错文本:
openai.NotFoundError: Error code: 404 - {'error': {'message': '...', 'type': 'gateway_error', 'code': 'ERROR_CODE'}}直接原因:模型调用名不对,或者请求路径少了/api/v1。
具体修法:先对着模型广场详情页复制 alias,示例用minimax-m2.5;再检查 Base URL 是否与最小示例完全一致,尤其带www和/api/v1。
常见问题
为什么我的 /chat 请求要等很久才返回?
如果max_tokens设得太大,大模型是可生成很久。默认 512 已经足够多数摘要和问答。确认不是把stream=True忘了,因为非流式会一次等完所有 token 再返回,客户端看起来就是一直转圈。
FastAPI 的 def 和 async def 端点有什么区别?
这里用def而不是async def。FastAPI 会把同步端点丢进线程池,OpenAI SDK 的阻塞调用不会卡住事件循环。若改成async def又直接调用同步client.chat.completions.create,请求期间会阻塞整个服务。
能不能把 Base URL 换成不带 www 的文档页地址?
不能。文档页地址和调用地址不是一回事。代码里要保留www和/api/v1,否则会连接失败或 404。文档页只用于查模型调用名和错误处理说明。
以上示例在千木SilvaMux的 OpenAI 兼容端点上验证,模型调用名为minimax-m2.5。参数变更或完整模型调用名可查 开发者文档。