最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
PHP 如何封装一个中间件实现不同 AI 服务商计费统计的统一管理
时间:2026-07-10 10:16:56 编辑:袖梨 来源:一聚教程网
中间件应仅拦截 /api/chat、/api/image/generate 等真实调用 AI 服务的路由,排除 /health、/auth/login 等非计费路径及 OPTIONS 预检请求;优先通过路由名判断而非 URL 路径;计费统计宜置于 API 网关后、业务服务前;需为不同服务商实现统一 BillableResponse 接口以适配 token 提取逻辑。
中间件要拦截哪些请求路径才合理
只对真正调用 AI 接口的路由做计费统计,比如 /api/chat、/api/image/generate 这类明确由前端发起、后端转发给第三方 AI 服务的请求。别把健康检查 /health、登录 /auth/login 或静态资源也包进去——这些不产生费用,统计反而污染数据。
常见错误是直接在全局中间件里无差别记录所有请求,结果发现账单对不上,因为日志里混进了大量非计费调用。
- 用 Laravel 的
$request->route()->getName()或 ThinkPHP 的Route::current()->getName()判断路由名更可靠,比靠 URL 路径匹配容错性高 - 如果用了 API 网关层(如 Kong),计费统计应放在网关后、业务服务前,避免重复或遗漏
- 务必排除 OPTIONS 预检请求——它们不触发实际 AI 调用,但会被某些框架自动捕获
如何从请求和响应中提取计费关键字段
不同服务商返回结构差异大:openai 在响应头带 x-ratelimit-remaining 和 x-ratelimit-reset,但计费核心得看响应体里的 usage;anthropic 返回 content 字段不带 token 数,得靠 usage.input_tokens 和 output_tokens;qwen(通义千问)则把 usage.total_tokens 放在顶层。
不能写死解析逻辑,否则换一个服务商就得改中间件代码。
立即学习“PHP免费学习笔记(深入)”;
- 定义统一抽象接口
BillableResponse,要求各服务商适配器实现getInputTokens()、getOutputTokens()、</li><li>中间件里不做 JSON 解析,只把原始响应体传给对应适配器——避免中间件承担格式转换职责</li><li>若响应失败(HTTP 4xx/5xx),仍需记录失败事件:有些服务商按“调用次数”收费(如早期 Azure OpenAI 某些 SKU),不是只算成功请求</li></ul><H3>计费数据写入时为什么不能直接 INSERT</H3><p>高并发下多个请求几乎同时完成,直接往数据库 <code>INSERT
计费记录,容易因主键冲突或唯一索引报错(比如同一秒内多个请求用了相同 trace_id)。更糟的是,如果中间件里还顺手更新用户余额表,事务锁表会拖慢整个 API 链路。计费是事后审计行为,不是业务强依赖流程。
- 用 Redis 的
LPUSH把原始计费事件(含 timestamp、model、tokens、cost、request_id)暂存为队列,格式用 JSON 字符串 - 单独起一个 Laravel 的
artisan schedule:run或 Swoole Worker 定时消费队列,批量写入 MySQL 或 ClickHouse - 避免在中间件里调用
sleep()或重试逻辑——超时会传染到前端,必须保证中间件执行时间
怎么验证中间件没漏记也没多记
最有效的办法是拿真实流量跑 A/B 对照:一边走老逻辑(SDK 内部手动埋点),一边走新中间件,对比两套数据的
total_tokens总和与请求量。偏差超过 0.5% 就得查。容易被忽略的是异步调用场景——比如用户上传文件后,后端用
dispatch(new AiImageJob())异步生成图,这种请求不在 HTTP 生命周期内,中间件根本捕获不到。- 对异步任务,要在 Job 的
handle()开头显式调用BillingRecorder::recordAsync(...),传入 job_id 替代 request_id - 在中间件里加个开关配置
billing.enabled,上线初期先设为 false,用日志输出模拟计费数据,确认字段齐全再开写入 - 每条计费记录必须带
source字段(值为middleware或job),方便后续归因
- 用 Redis 的