Skip to content

feat(server): 新增 /v1/stats 按模型请求统计端点 - #80

Closed
SunshineR04 wants to merge 1 commit into
Sliverkiss:masterfrom
SunshineR04:feat/v1-stats
Closed

SunshineR04 wants to merge 1 commit into
Sliverkiss:masterfrom
SunshineR04:feat/v1-stats

Conversation

@SunshineR04

@SunshineR04 SunshineR04 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

本段由 AI 治理机器人自动维护,请勿手工删除上面这行锚点注释。

  • 要点

要点:新增按模型聚合的请求统计端点 /v1/stats 及 /v1/stats/reset,提供持久化的可编程用量与性能指标读取入口。
要做的事:

  • 新增 GET /v1/stats 与 POST /v1/stats/reset 两个路由及处理函数,按模型返回请求数、延迟、吞吐、token、缓存、信用等聚合统计,并附 total 汇总行。
  • 新增 internal/metrics 包,实现只依赖标准库的持久化累加器,每 20 次记录落盘一次并在退出时 flush,默认写入 ./data/metrics.json。
  • 在 internal/server/logging.go 的 chatStat.done() 采集点挂载 collector,覆盖所有请求出口(成功/失败/轮转/panic 恢复),并扩展现有解析逻辑以兼容不同 usage 字段风格。
  • 在 cmd/server/config.go 增加 server.metrics_enabled(默认 true)与 server.metrics_file(默认 ./data/metrics.json)配置,main.go 装配 collector,支持关闭或纯内存模式。
  • 确保改动零回归:go test 失败集合与未改动基线一致,并完成端到端验证,包括真实流式/非流式请求、重启后数据保留、关闭开关及缺失字段处理等边界情况。
  • 关联时间:2026-09-14T13:49:14.665Z

关联 canonical issue

Closes #81


动机

当前网关的可观测性只有 /status(账号池计数:可用/冷却/禁用、在途、粘性)与每请求一行的 stdout 日志。缺的是按模型聚合的性能视图:哪个模型首字慢、吞吐低、缓存命中率差、每次扣多少积分。

这类信息排障时很关键 —— 例如缓存命中率直接决定实际扣费(本机实测命中时输入 token 价格约为未命中的一半),
但只看两端的 agent 无法判断"这个号是不是走了缓存"、"换模型后是变快还是变慢"。

issue #26「查不到用量」也反映了同类诉求:用量与性能缺少可编程的读取入口。

方案

新增 GET /v1/statsPOST /v1/stats/reset,与既有 /v1/models 同组、同一把 api_key。

返回按模型分组的聚合(另含一行 total 汇总):

  • 请求数 / 成功 / 失败 / 流式占比
  • 平均首字延迟(TTFB)、平均耗时、生成吞吐(tok/s,已剔除首字等待)
  • prompt / completion / total token
  • 缓存命中、未命中、写入 token 及命中率
  • 上游返回的 credit 累计与每请求均值
  • 每模型最后活跃时间

GET /v1/stats 响应示例(本机脱敏实测):

{
  "enabled": true,
  "since": "2026-09-14T20:02:29+08:00",
  "now":   "2026-09-14T21:05:00+08:00",
  "uptime_sec": 3760,
  "total": {
    "model": "(all)", "requests": 87, "success": 87, "failed": 0, "streaming": 87,
    "avg_ttfb_ms": 4317.5, "avg_latency_ms": 5915.2, "tokens_per_sec": 244.0,
    "prompt_tokens": 25430343, "completion_tokens": 0, "total_tokens": 25430343,
    "cache_hit_tokens": 24500000, "cache_miss_tokens": 930343, "cache_hit_rate": 0.963,
    "credit": 23.21, "credit_per_req": 0.267
  },
  "models": [ { "model": "deepseek-v4.1-flash", "...": "同上字段" } ]
}

设计取向:

  • 采集点在网关侧:网关是所有流量的必经点,因此统计覆盖含绕过网关客户端的全部调用;
    放在客户端侧只能看到自己发出的请求。
  • 持久化:每 20 次记录落盘一次(避免每请求写盘),退出时 Flush 兜底;
    默认写 ./data/metrics.json,重启后累计值不丢。
  • 可关闭server.metrics_enabled=false 时端点返回 {"enabled":false}
    且不采集、不落盘。不想要这个能力可直接关掉。
  • 纯内存可选server.metrics_file="" 时只在内存累计,重启清零。

实现

新增 internal/metrics/(432 行 + 267 行测试),只依赖标准库,不引用本仓库任何内部包。

接入 4 处,全部为插入式改动:

文件 改动
internal/server/logging.go 新增 UsageDetail/ParseUsagechatStat 挂 collector;done() 里记一笔
internal/server/handler.go 两个路由 + 两个处理函数;流式/非流式各采集一次 usage
cmd/server/config.go server.metrics_enabled(默认 true)、server.metrics_file(默认 ./data/metrics.json
cmd/server/main.go 装配 collector

采集点选在 chatStat.done() 而非各 return 点:done()defer 调用,
成功/失败/轮转耗尽/panic 恢复所有出口都会走到,统计不会漏记。

parseSSELine 改为宽松 map 解析:usage 字段名在不同模型/区域间有差异
(OpenAI 风格的 prompt_cache_hit_tokens 与 Anthropic 风格的 cache_read_input_tokens
都出现过),结构体标签写死会漏字段。缺失字段一律按 0 处理,不猜、不估算。

与既有实现的关系

社区 fork 287775856/workbuddy2api 有同一端点(提交 3cf32d0,MIT 同源),
本 PR 是把它搬回主线并适配当前 master。同时注意本仓库另有 PR #60
(内嵌面板)也含一个统计视图,两者取舍不同:

若两者都要,端点可作为面板的数据源;若只保留其一,本 PR 的好处是不引入前端构建链。

验证

零回归go test -count=1 ./... 的失败集合与未改动的上游基线逐条一致
本机(Windows)有 2 个既有失败(TestRateLimitedModelsInStatus
TestStatusRateLimitedModelsLedger),在纯净 origin/master 上同样失败,与本改动无关。

端到端(本机真实网关,独立端口起实例验证后切换生产):

  • 真实流式 + 非流式请求 → 统计正确采集(请求数、TTFB、吞吐、缓存字段均解析正常)
  • 落盘 data/metrics.json,重启后累计值保留
  • 上表 JSON 即本机实际响应

边界

  • metrics_enabled=false{"enabled":false},不采集
  • 未带 usage 的请求(HasUsage=false)不计 token,只计请求数与延迟
  • credit 字段缺失时不计扣费(缺失≠0:不能把"缺观测"当"0 成本")

面板(workbuddy2api-gui)的「请求统计」页依赖网关的 /v1/stats,该端点在
上游 Sliverkiss 版本中不存在,导致面板该页恒报 404。本次移植补齐该能力。

来源:287775856/workbuddy2api 的 3cf32d0(同源项目的扩展端点)。

## 改动

新增 internal/metrics(自包含,仅依赖标准库):
- 按模型维度的请求量/成功失败/流式、TTFB 与耗时、token 明细、
  缓存命中/未命中/写入、扣费累计
- 弱化边界:每 20 次记录落盘一次(flushEvery),退出时 Flush 兜底

接入(4 个文件,全部为插入式):
- logging.go:新增 UsageDetail/ParseUsage(宽松 map 解析,兼容 OpenAI 与
  Anthropic 两套缓存字段命名);chatStat 挂 collector;done() 里记一笔。
  选在 done() 是因为它由 defer 调用,覆盖成功/失败/轮转耗尽所有出口,统计不漏记
- handler.go:GET /v1/stats、POST /v1/stats/reset 两个路由;流式与非流式
  各采集一次 usage
- config.go:server.metrics_enabled(默认 true)、server.metrics_file
  (默认 ./data/metrics.json)
- main.go:装配 collector

## 与来源版本的差异(重要)

本仓库的 parseSSELine 比来源版本**新**,含来源没有的成本记账语义
(Credit() 的「缺观测≠0」:字段缺失不能当 0 成本写入账本)。改宽松 map 解析后
该语义靠键存在性 + null 判断保留:

    if v, ok := chunk.Usage["credit"]; ok && v != nil {

且非流式路径保留原有的 NoteModelCost 调用。既有测试
TestChatStatsReaderJSONNullCredit / CreditMissing / CreditExplicitZero 全部通过。

## 验证

- 全量测试失败集合与改动前**逐条一致**(零回归)。既有失败项
  (TestPromptFileOverride 的 Windows 路径转义、TestPickAntiThunderingHerd
  概率性、TestChatStatsReaderTokensFromUsage 计时精度)在未改动的基线上
  同样失败,与本改动无关——两边各重跑 6 次通过率相同(1/6)
- 独立端口(7899)起实例做端到端:真实流式+非流式请求 → 统计正确采集
  (请求 2、流式 1、平均首字 739ms、吞吐 309 tok/s、缓存字段解析正常)
  → 面板「请求统计」页在真实浏览器中正常渲染,无 404
- 已确认仅 cmd/server 依赖改动的包,其余二进制(activity/credit/login/
  signin/trial)不受影响

## 维护成本

此为有意分叉:本仓库原先与上游零差异,此后 internal/server/logging.go 与
handler.go 在上游更新时需手动合并。改动均为插入式且锚点明确
(done()/parseSSELine()/Config/路由表),冲突级别可控。
@github-actions github-actions Bot added the enhancement New feature or request label Sep 14, 2026
@github-actions

Copy link
Copy Markdown

🤖 本机器人已对这条 PR 做要点提炼,并关联到对应主题的规范 issue(机器人整理,如有异议请联系维护者)。

要点:新增按模型聚合的请求统计端点 /v1/stats 及 /v1/stats/reset,提供持久化的可编程用量与性能指标读取入口。
要做的事:

  • 新增 GET /v1/stats 与 POST /v1/stats/reset 两个路由及处理函数,按模型返回请求数、延迟、吞吐、token、缓存、信用等聚合统计,并附 total 汇总行。
  • 新增 internal/metrics 包,实现只依赖标准库的持久化累加器,每 20 次记录落盘一次并在退出时 flush,默认写入 ./data/metrics.json。
  • 在 internal/server/logging.go 的 chatStat.done() 采集点挂载 collector,覆盖所有请求出口(成功/失败/轮转/panic 恢复),并扩展现有解析逻辑以兼容不同 usage 字段风格。
  • 在 cmd/server/config.go 增加 server.metrics_enabled(默认 true)与 server.metrics_file(默认 ./data/metrics.json)配置,main.go 装配 collector,支持关闭或纯内存模式。
  • 确保改动零回归:go test 失败集合与未改动基线一致,并完成端到端验证,包括真实流式/非流式请求、重启后数据保留、关闭开关及缺失字段处理等边界情况。
    已为本 PR 开辟的新主题创建规范 issue [Feature] feat(server): 新增 /v1/stats 按模型请求统计端点 #81

此 PR 保持开启,治理机器人不会关闭它。

@Sliverkiss

Copy link
Copy Markdown
Owner

这个 PR 暂时保持 open,尚未合并。原因:设计还需要思考优化。

设计讨论与后续演进方向已整合至 #83,重新设计时请先在那边对齐方案(网关薄事件出口 + 面板事件投影),感谢贡献。

@SunshineR04

Copy link
Copy Markdown
Contributor Author

#83 的方向说明,主动关闭本 PR —— 感谢维护者的详细评审。

为什么关闭

维护者在 #83 里指出的两点我认同:

  1. 累加器无法重建历史internal/metrics 存的是"当前累计值"快照,维度一改就得迁移重算;
  2. 边界不符[Feature] usage 用量监控:网关薄出口 + 管理面板事件投影(整合 /v1/stats 设计讨论) #83 明确「2api 网关要实现的是提供 chat 流的出口就足够了」,而本 PR 把统计持久化耦合回了网关进程。

这与我在实现时的取舍不一致:我为了让 CLI(cmd/stats)有数据可读,选择了"网关侧聚合 + 落盘"这条最省事的路,而没有先对齐项目边界。方向确实该按 #83 的「网关薄出口 + 面板事件投影」重做。

后续

本 PR 不再推进。若之后要按 #83 的薄事件出口(内存 ring buffer / 单端点 publish、默认关闭、不聚合)重做,我会先在该 issue 下对齐方案再提。

本地这套累加器实现会留在自己的分支上自用(它给本地 CLI 供数),不入上游。分支与 fork 保留以便追溯,需要我删除可以说一声。

再次感谢评审与 #83 里那份对照分析(含 CPAMP 的 source 级核实),信息量很大。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] feat(server): 新增 /v1/stats 按模型请求统计端点

2 participants