feat(server): 新增 /v1/stats 按模型请求统计端点 - #80
Closed
SunshineR04 wants to merge 1 commit into
Closed
SunshineR04 wants to merge 1 commit into
SunshineR04 wants to merge 1 commit into
Conversation
面板(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/路由表),冲突级别可控。
|
🤖 本机器人已对这条 PR 做要点提炼,并关联到对应主题的规范 issue(机器人整理,如有异议请联系维护者)。 要点:新增按模型聚合的请求统计端点 /v1/stats 及 /v1/stats/reset,提供持久化的可编程用量与性能指标读取入口。
此 PR 保持开启,治理机器人不会关闭它。 |
3 tasks
Owner
|
这个 PR 暂时保持 open,尚未合并。原因:设计还需要思考优化。
设计讨论与后续演进方向已整合至 #83,重新设计时请先在那边对齐方案(网关薄事件出口 + 面板事件投影),感谢贡献。 |
Contributor
Author
|
按 #83 的方向说明,主动关闭本 PR —— 感谢维护者的详细评审。 为什么关闭维护者在 #83 里指出的两点我认同:
这与我在实现时的取舍不一致:我为了让 CLI( 后续本 PR 不再推进。若之后要按 #83 的薄事件出口(内存 ring buffer / 单端点 publish、默认关闭、不聚合)重做,我会先在该 issue 下对齐方案再提。 本地这套累加器实现会留在自己的分支上自用(它给本地 CLI 供数),不入上游。分支与 fork 保留以便追溯,需要我删除可以说一声。 再次感谢评审与 #83 里那份对照分析(含 CPAMP 的 source 级核实),信息量很大。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
关联 canonical issue
Closes #81
动机
当前网关的可观测性只有
/status(账号池计数:可用/冷却/禁用、在途、粘性)与每请求一行的 stdout 日志。缺的是按模型聚合的性能视图:哪个模型首字慢、吞吐低、缓存命中率差、每次扣多少积分。这类信息排障时很关键 —— 例如缓存命中率直接决定实际扣费(本机实测命中时输入 token 价格约为未命中的一半),
但只看两端的 agent 无法判断"这个号是不是走了缓存"、"换模型后是变快还是变慢"。
issue #26「查不到用量」也反映了同类诉求:用量与性能缺少可编程的读取入口。
方案
新增
GET /v1/stats与POST /v1/stats/reset,与既有/v1/models同组、同一把 api_key。返回按模型分组的聚合(另含一行
total汇总):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", "...": "同上字段" } ] }设计取向:
放在客户端侧只能看到自己发出的请求。
默认写
./data/metrics.json,重启后累计值不丢。server.metrics_enabled=false时端点返回{"enabled":false},且不采集、不落盘。不想要这个能力可直接关掉。
server.metrics_file=""时只在内存累计,重启清零。实现
新增
internal/metrics/(432 行 + 267 行测试),只依赖标准库,不引用本仓库任何内部包。接入 4 处,全部为插入式改动:
internal/server/logging.goUsageDetail/ParseUsage;chatStat挂 collector;done()里记一笔internal/server/handler.gocmd/server/config.goserver.metrics_enabled(默认 true)、server.metrics_file(默认./data/metrics.json)cmd/server/main.go采集点选在
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
(内嵌面板)也含一个统计视图,两者取舍不同:
(58 文件 / +7765 行)
若两者都要,端点可作为面板的数据源;若只保留其一,本 PR 的好处是不引入前端构建链。
验证
零回归:
go test -count=1 ./...的失败集合与未改动的上游基线逐条一致。本机(Windows)有 2 个既有失败(
TestRateLimitedModelsInStatus、TestStatusRateLimitedModelsLedger),在纯净origin/master上同样失败,与本改动无关。端到端(本机真实网关,独立端口起实例验证后切换生产):
data/metrics.json,重启后累计值保留边界:
metrics_enabled=false→{"enabled":false},不采集HasUsage=false)不计 token,只计请求数与延迟credit字段缺失时不计扣费(缺失≠0:不能把"缺观测"当"0 成本")