大模型Token计费配置:按调用量统计与配额限制落地
给自建的大模型API服务加上Token计费,核心是在请求入口做两件事:记录每次请求的Token消耗,并按用户或密钥累计用量、设置额度上限。
本文以常见的Nginx反向代理加Lua脚本方案为例,讲清楚从零配置到验证的完整流程,适合已经能跑通模型推理接口、需要对外提供按量计费能力的运维人员。
先确认你的服务入口和计费口径
动手前先明确三个信息,否则后面配置容易返工。
- 模型接口地址:例如推理服务监听在
127.0.0.1:8000,对外通过Nginx的/v1/chat/completions暴露。 - Token统计来源:优先使用模型返回体中的
usage字段(包含prompt_tokens、completion_tokens、total_tokens),不要在代理层自己估算,估算值和实际计费容易对不上。 - 计费维度:按API Key计费还是按用户ID计费。建议请求头中携带
X-Api-Key,代理层据此识别调用方。
检查当前服务是否正常返回usage字段,用curl发一个最小请求:
curl -s http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"your-model","messages":[{"role":"user","content":"hi"}]}'
返回JSON中应包含类似 "usage":{"prompt_tokens":5,"completion_tokens":3,"total_tokens":8} 的字段。
如果没有,需要先在推理服务侧开启用量统计,否则代理层无法拿到准确数字。
在Nginx中挂载Lua脚本统计Token
以下配置假设已安装OpenResty(自带Lua支持),版本以你实际环境为准。
编辑站点配置文件,在 server 块中加入:
location /v1/chat/completions {
access_by_lua_block {
local cjson = require "cjson"
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "redis connect failed: ", err)
return ngx.exit(500)
end
local api_key = ngx.req.get_headers()["X-Api-Key"]
if not api_key then
return ngx.exit(401)
end
local used = red:get("token_used:" .. api_key)
local quota = red:get("token_quota:" .. api_key)
if quota and quota ~= ngx.null and tonumber(used or 0) >= tonumber(quota) then
return ngx.exit(429)
end
red:set_keepalive(10000, 100)
}
body_filter_by_lua_block {
local cjson = require "cjson"
local resp = ngx.arg[1]
if not resp or resp == "" then return end
local ok, data = pcall(cjson.decode, resp)
if ok and data.usage and data.usage.total_tokens then
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
red:connect("127.0.0.1", 6379)
local api_key = ngx.req.get_headers()["X-Api-Key"]
if api_key then
red:incrby("token_used:" .. api_key, data.usage.total_tokens)
end
red:set_keepalive(10000, 100)
end
}
proxy_pass http://127.0.0.1:8000;
}
这段配置做了三件事:请求进来时检查Key是否存在、用量是否超配额;
响应返回时解析usage字段并把消耗累加到Redis。
配额值可以提前写入:
redis-cli set token_quota:test-key-001 100000
改完执行 nginx -t 检查语法,再 nginx -s reload 重载。
如果使用宝塔面板,对应路径是「网站」→「设置」→「配置文件」,保存后同样需要重载Nginx。
常见报错与避坑
- 返回500且日志出现redis connect failed:确认Redis在运行,
redis-cli ping应返回PONG;检查Lua脚本中的IP和端口是否与实际一致。 - 用量一直为0:多半是响应体不是JSON,或usage字段在流式输出中被拆成多段。流式接口需要额外处理SSE分块,建议先对非流式请求做统计,流式场景单独评估。
- body_filter_by_lua中拿不到完整响应:该阶段可能多次触发,务必只在能完整解析JSON时累加,避免重复计数。更稳妥的做法是在模型服务侧记录用量,代理层只做配额校验。
- 配额判断用
>=而不是>:否则恰好达到上限时还能再发一次请求。 - 不要在前端暴露Redis或管理接口,计费数据只允许服务端读写。
验证计费是否真正生效
配置完成后按顺序验证:
- 用带Key的请求调用一次,然后查Redis:
redis-cli get token_used:test-key-001,数值应等于返回体中的total_tokens。 - 把配额改成很小,例如
redis-cli set token_quota:test-key-001 10,再请求应返回429。 - 不带
X-Api-Key请求,应返回401。 - 查看Nginx错误日志
tail -f /var/log/nginx/error.log,确认没有Lua报错。
如果以上四步都符合预期,说明Token计费配置在代理层已经跑通。
后续对账时,建议定期把Redis累计值与模型服务自身日志做比对,发现偏差优先检查流式请求和异常中断的请求是否被漏记。
关于计费精度的补充
代理层统计依赖响应中的usage字段,适用于非流式和能完整返回JSON的场景。
如果你的业务大量使用流式输出,或者需要按输入输出分别定价,建议在模型服务内部埋点记录每次调用的prompt_tokens和completion_tokens,代理层只负责配额拦截和汇总展示。
两种方式可以并存,但要以服务端记录为准做最终账单。