知识点思维导图
29 个知识节点
生产工程(06) - AI 应用接口设计
读完后,你应能完成以下任务:
- 绘制“生产工程(06) - AI 应用接口设计 / 门卫的顺序:requestId → 鉴权 → 限流 → 业务”的关键对象与数据流,解释“因为非法请求根本不该消耗你的限流配额,也不该进入任何后续逻辑——越早挡掉越好。”,并用源码位置、日志或 Trace 标注证据。
- 为“生产工程(06) - AI 应用接口设计 / ① requestId:把散落的日志串成一条链”设计正常与异常输入,验证“这是排查 AI 应用坏 case 的命脉。”,输出首个偏差位置与回归测试结果。
- 实现“生产工程(06) - AI 应用接口设计 / ③ 限流:滑动窗口挡住高频请求”的最小代码或配置,检验“限流防止单个用户(或攻击者)把后端刷爆、把模型费用刷上天。”,输出命令、结果与 Diff,并说明不适用边界。
一、AI 应用接口设计的真实应用场景
你的 AI 问答接口上线了,前面几篇的能力都串起来了。然后出三件事:
- 用户反馈「有时候回答很奇怪」,你想查那一次到底发生了啥——但日志里几千条混在一起,根本定位不到是哪一次请求。
- 有人拿到你的接口地址,写脚本一秒钏几十次,模型调用费蹭蹭涨。
- 接口没鉴权,谁都能调,你的模型额度在给陌生人花钱。
这三件事,对应 AI 接口必须有的三道门卫:requestId(能追踪)、限流(防刷爆)、鉴权(挡非法)。业务逻辑写得再好,少了这三样就不能上生产。
二、门卫的顺序:requestId → 鉴权 → 限流 → 业务
一个请求进来,要依次过这几关,顺序有讲究:
请求进来
↓
① 分配 requestId —— 第一件事,后面所有日志都带上它
↓
② 鉴权(401) —— 非法身份直接挡掉,不浪费后续任何资源
↓
③ 限流(429) —— 合法用户也不能无限刷
↓
④ 业务逻辑(含参数校验 400)
↓
返回(带 requestId)
为什么鉴权在限流前?因为非法请求根本不该消耗你的限流配额,也不该进入任何后续逻辑——越早挡掉越好。
2.1 ① requestId:把散落的日志串成一条链
每个请求一进来就生成一个唯一 id,前端、后端、模型调用的日志全带上它。出问题时,拿这个 id 一搜,整条链路的日志就拼齐了:
这是排查 AI 应用坏 case 的命脉。AI 输出不稳定,没有 requestId 串联,复现和定位基本靠猜。
2.2 ② 鉴权:只认后端白名单
鉴权必须在后端做,且只信你自己的白名单,绝不信任前端传来的任何身份声明:
2.3 ③ 限流:滑动窗口挡住高频请求
限流防止单个用户(或攻击者)把后端刷爆、把模型费用刷上天。常用滑动窗口:记录每个 key 最近的请求时间,窗口内超过上限就拒绝:
三、用对状态码,前端才好处理
不同失败要返回不同状态码,前端据此决定怎么应对:
| 场景 | 状态码 | 前端该做什么 |
|---|---|---|
| 没登录 / Key 无效 | 401 | 跳登录,重试无意义 |
| 触发限流 | 429 | 提示「请求太频繁」,等一会再试 |
| 参数错(缺字段) | 400 | 提示用户改输入,重试无意义 |
| 服务端故障 | 500 | 可重试 |
| 正常 | 200 | 展示结果 |
把 401、429、400 全返成 500,前端就分不清「该重试」还是「该改请求」,体验和稳定性都垮。
四、统一响应结构
所有接口返回同一套结构,前端一套解析逻辑通吃:
request_id 永远带上(出错时前端能上报)、code 标明结果、成功放 data、失败放 error。
五、工程上真正会踩的坑
- 限流状态存内存。单机没问题,多实例部署时每个实例各算各的,实际放行量是配置的好几倍。多实例要用 Redis 做共享限流。
- 鉴权信任前端传的 userId。前端传
{userId: "admin"}你就当真,等于没鉴权。身份只能从后端校验过的 token 推出来。 - 错误码乱用。全返 200 然后在 body 里写
success: false,或者啥错都返 500。前端没法按状态码分流处理。 - 日志记了明文敏感数据。requestId 日志里把用户手机号、完整 prompt 原样打出来,违规也有泄露风险。日志要脱敏。
- 把模型原始响应直接透传前端。模型返回的格式可能变、可能含内部信息。后端要包成自己稳定的结构再返回,别让前端直接吃模型原文。
六、一句话面试答法
AI 接口除了业务逻辑还要设计什么? 三道门卫加一套规范。门卫按顺序:每个请求先分配 requestId 串联全链路日志,这是排查 AI 坏 case 的命脉;再鉴权,只认后端白名单不信前端传的身份,挡掉非法请求返 401;再限流,用滑动窗口防止刷爆后端和模型费用,超了返 429。规范上:状态码要用对,401/429/400/500 让前端能区分该重试还是该改请求;响应结构统一成 request_id + code + data/error;日志脱敏;模型响应包成自己的结构再返回,不直接透传。多实例部署限流状态要放 Redis 共享。
七、动手实践:19 AI 应用接口设计
一个 AI 接口除了业务逻辑,还得有三层「门卫」:requestId 追踪、鉴权头校验、限流。这里用一组中间件函数模拟请求依次过关,离线可跑。
7.1 在线运行
零依赖,纯标准库。
7.2 预期输出
=== 场景 1:没带 Authorization(鉴权失败 401)===
匿名请求
-> {'request_id': 'req_61ae6ab9', 'code': 401, 'error': '缺少或格式错误的 Authorization 头'}
=== 场景 2:错误的 API Key(鉴权失败 401)===
乱填 key
-> {'request_id': 'req_3eb67923', 'code': 401, 'error': '无效的 API Key'}
=== 场景 3:合法用户连续请求,第 4 次触发限流(429)===
alice 第 1 次请求
-> {'request_id': 'req_f2f79080', 'code': 200, 'data': {'answer': '(回答给 alice)你的问题「问题1」已收到。'}}
alice 第 2 次请求
-> {'request_id': 'req_c8f04d45', 'code': 200, 'data': {'answer': '(回答给 alice)你的问题「问题2」已收到。'}}
alice 第 3 次请求
-> {'request_id': 'req_48cf4f23', 'code': 200, 'data': {'answer': '(回答给 alice)你的问题「问题3」已收到。'}}
alice 第 4 次请求
-> {'request_id': 'req_7af3b55d', 'code': 429, 'error': '触发限流,请 10.0s 后重试'}
=== 场景 4:合法但参数为空(400)===
bob 空消息
-> {'request_id': 'req_7307f7b8', 'code': 400, 'error': 'message 不能为空'}
要点:每个请求都有 requestId;先鉴权再限流再业务;不同失败对应不同 code。
每个响应都带 request_id(每次运行的随机值不同);alice 连发 3 次正常、第 4 次被限流返回 429;不同失败对应不同状态码(401/429/400),前端据此区别处理。
7.3 代码↔概念对应
| 概念 | 在 main.py 哪里 |
|---|---|
| 每个请求分配 requestId | gen_request_id |
| 鉴权:校验 Authorization 头 + Key 白名单 | check_auth |
| 限流:滑动窗口算法 | check_rate_limit |
| 门卫顺序:requestId → 鉴权 → 限流 → 业务 | handle_request |
| 统一响应结构(request_id + code + data/error) | handle_request 的返回值 |
7.4 动手改
- 把
MAX_REQUESTS改成 5,看 alice 要发到第 6 次才被限流。 - 把鉴权和限流的顺序对调,思考为什么应该先鉴权(非法请求不该消耗限流配额,也不该进任何后续逻辑)。
- 真实项目里
request_history存内存只适合单机,多实例部署要换成 Redis;API_KEYS白名单也应存数据库。
7.5 可运行源码:AI 应用接口设计
main.py
八、总结
- 门卫的顺序:requestId → 鉴权 → 限流 → 业务:因为非法请求根本不该消耗你的限流配额,也不该进入任何后续逻辑——越早挡掉越好。
- 用对状态码,前端才好处理:把 401、429、400 全返成 500,前端就分不清「该重试」还是「该改请求」,体验和稳定性都垮。
- 统一响应结构:request_id 永远带上(出错时前端能上报)、code 标明结果、成功放 data、失败放 error。
- 工程上真正会踩的坑:单机没问题,多实例部署时每个实例各算各的,实际放行量是配置的好几倍。
- 一句话面试答法:门卫按顺序:每个请求先分配 requestId 串联全链路日志,这是排查 AI 坏 case 的命脉;
- ① requestId:把散落的日志串成一条链:这是排查 AI 应用坏 case 的命脉。
学完自测
选择所有正确答案;提交后逐项核对判断依据。