id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week
id
title
type
domain
stack
tags
level
prerequisites
est_hours
related
week
python.fastapi.scenario.streaming-chat-api
大模型对话 API:鉴权、统一错误、SSE 流式、客户端断开即停止
scenario
python
intermediate
python.fastapi.streaming-testing
2
python.fastapi.interview
devops.nginx.scenario.ai-gateway-sse-canary
大模型对话 API:鉴权、统一错误、SSE 流式、客户端断开即停止
业务背景与问题
公司要把大模型能力封装成内部统一的对话服务,给客服系统、OA、知识库等多个业务调用,而不是每个业务各自直连模型服务商。要求:
各业务系统用自己的 API Key 调用,按租户统计用量;
支持流式(打字机效果)和非流式两种模式;
错误格式统一,调用方能程序化处理;
出了问题能用请求 ID 串起调用方和服务端的日志;
未来可以切换模型服务商而不影响调用方。
约束
第一版只有两三个人开发,要求简单可靠。
模型调用按 token 计费,用户关闭页面后不应继续生成。
模型 API Key 只保存在这个服务里,不下发给业务系统。
方案设计
分层 :路由层(校验、鉴权、响应格式)→ 服务层(组装提示词、计量)→ 模型客户端(LLMClient 协议,可替换服务商)。
鉴权 : current_tenant 依赖,API Key → 租户;后续计量、限流都按租户。
统一错误 : HTTPException、RequestValidationError、兜底异常都转成 {code, message, request_id};兜底异常只记日志不返回细节。
请求 ID :中间件读取或生成 X-Request-ID,写进响应头和日志。
流式 : SSE;中途出错发送 event: error;客户端断开立即停止。
可测试 :模型客户端通过依赖注入,测试中替换为脚本化的假实现。
关键代码
完整代码和测试见 examples/chat-api :
测试覆盖:未带 API Key 返回 401 统一格式;参数错误返回 422 并列出 messages.0.role 等字段;非流式返回完整内容并透传请求 ID;流式事件顺序正确并以 [DONE] 结束;模型中途失败时发送 error 事件;内部异常不泄露连接串。
取舍
选择
优点
缺点
SSE(本方案)
基于 HTTP,简单,代理和网关支持好
单向;EventSource 不支持 POST,前端要用 fetch 解析
WebSocket
双向,适合语音、实时协作
连接管理和负载均衡更复杂
统一对话服务(本方案)
密钥集中、可计量、可切换服务商
多一跳;需要保证高可用
业务直连模型服务商
少一跳
密钥分散、无法统一计量和治理
直接使用 LangChain 等框架封装
功能多
对一个转发 + 治理的服务来说偏重,版本变化快
易错点
流式中途异常想改状态码:响应头已发出,只能发错误事件。
不检查断开:页面关了模型还在生成。
统一错误处理器漏掉 RequestValidationError: 422 的格式和其他错误不一致。
兜底异常返回 str(e):可能包含连接串、内部地址。
Nginx 没关缓冲:流式变整段(见 AI 应用网关场景 )。
只在内存里计量:多 worker / 多实例数据不准,要写数据库或 Redis。
面试怎么讲
“我做了一个内部统一的对话服务,FastAPI 实现。鉴权用依赖注入把 API Key 映射到租户,便于计量和限流;所有错误转成统一的 code/message/request_id 格式,内部异常只记日志;流式用 SSE,中途出错发 error 事件,客户端断开就停止生成以节省 token;模型客户端抽象成协议,测试里用假实现替换,覆盖了鉴权、校验、流式和异常场景。”