Repository navigation
Conversation
应用内排查(GLM-5.2 未统计)确认根因链:OPENAI_GENERIC/OPENAI_LOCAL 流式请求 不带 stream_options.include_usage,严格端点按规范省略 usage 分块, applyUsageToCounters 静默 early-return,RequestTracker.finish() 拿不到成功 attempt 的快照返回 null,落库被静默跳过且无任何日志;标题/总结等非流式辅助 请求走固定模型索引,导致只承担主对话流式的模型永远没有记录。 - 工厂对两个类型显式传 includeUsageInStream = true,与官方 OPENAI 类型及 Kimi/DeepSeek 既有做法对齐,不做按端点降级 - finish() 四个跳过分支全部留痕(取消为 debug 级,其余 warning 级, 带 configId 与 providerModel),下次排查看日志即可定位 - usage 非 null 但解析失败时输出字段名列表;usage 为 null 的分块不刷屏, 由 finish() 的无上报日志兜住观测面
统计页此前按模型名合并展示,不同渠道(模型配置)的同名模型 token 混在一起。
DB 与 DAO 聚合本就携带 configId 粒度,本次纯展示层改造:
- TokenStatsGrouping 枚举(CONFIG_MODEL 默认 / MERGED_MODEL)经
TokenStatsQueryParams 透传 lifetime 与 range 查询,CONFIG_MODEL 按组键
config:{configId}|model:{model} 划分并暴露组内配置,legacy 空 configId
独立成组;MERGED_MODEL 维持原键
- ViewModel 收集三处查询结果的配置 ID 拼装"配置名/模型名",已删除配置沿用
既有文案,合并组与 legacy 记录保持纯模型名
- 范围分析卡新增"合并相同模型"开关:开启按模型名合并,关闭按配置名/模型;
切换时清空模型筛选(displayModelId 语义随分组变化),设置持久化到
token_stats_preferences
- strings 补齐全部 8 个语言目录
- 新增 TokenStatsDisplayGroupingTest 覆盖配置拆分、legacy 组、合并归一与
同配置不同模型不合并
- TODO 计划与实施记录见 docs/TODO/token_stats_config_grouping_20261113/
CATMIAOZHI
left a comment
There was a problem hiding this comment.
审计结论:❌ 请求修改
改动(25 个文件,+488/−22):流式 usage 落库修复 + token 统计按模型配置分组并新增「合并相同模型」开关。
通过的检查:
- 根因链核对无误:
OpenAIProvider.createRequestBodyInternal仅在stream && includeUsageInStream时附加stream_options.include_usage;工厂此前只有官方 OPENAI 分支传true,本次把 OPENAI_GENERIC / OPENAI_LOCAL 对齐到同一做法,改动点准确。 TokenTrackingAIService.finish()四个静默跳过分支全部改为带configId/providerModel的日志(取消 debug、其余 warning),语义与旧代码一致(取消仍不落库),只是补观测面;applyUsageToCounters解析失败时只在 usage 非 null 时输出字段名,不刷屏。- 分组数据层正确:CONFIG_MODEL 用
config:{configId}|model:{model}作组键(legacy 空 configId 独立成组),MERGED_MODEL 保持原model:组键;configId字段只在 CONFIG_MODEL 下填充,MERGED_MODEL 为 null,与注释一致;新增单测覆盖分组键、legacy 成组、合并聚合、不同模型不合并四种情形。 - ViewModel 层:分组选择经三处查询(lifetime / range / available)透传,配置名解析覆盖三处结果;切换分组时清空模型筛选(displayModelId 语义随分组变化,旧选中集会错指),选择持久化到
token_stats_preferences;已删除配置沿用「已删除的配置」文案。 - 无 Room 结构变化、无新增权限、无密钥/敏感信息;8 个语言目录的 strings 均已补齐新文案;目标分支为
dev。
问题:
- [P1]
stream_options.include_usage无降级:OPENAI_GENERIC / OPENAI_LOCAL 现在每次流式请求都带该参数,拒绝未知参数的端点会直接返回 400——影响面从「统计缺失」扩大到「流式对话本身不可用」。PR 描述已披露该风险并明确选择不降级,但这是需要 maintainer 明确接受的取舍:请 @AAswordman 确认接受该风险,或改为请求失败时去掉该参数重试一次(仅 OPENAI_GENERIC / OPENAI_LOCAL)。在 maintainer 拍板前不建议合并。 - [P3]
TokenStatsPreferences.loadModelGrouping()用valueOf直接解析存值,与loadTargetCurrency/loadActivityViewMode等既有函数同款写法;存值非法时会抛IllegalArgumentException,虽被loadInternal的通用 catch 接住并显示加载失败,但顺手可改为runCatching { valueOf(it) }.getOrDefault(CONFIG_MODEL)更稳妥。 - [P3]
docs/TODO/token_stats_config_grouping_20261113/index.md顶部的working_branch: feat/file-tool-hardening与实际分支feat/token-stats-grouping不符,应为复制粘贴残留,顺手改掉。 - [P3] 单测只覆盖了
buildDisplayModels的分组键;resolveDisplayNames的「配置名/模型名」拼装、已删除配置兜底、setModelGrouping切换清空选中集等 ViewModel 层行为没有对应测试(index.md 提到「fake store 适配」但 diff 中未见相关测试文件),建议补上。 - [question] PR 描述称单元测试未在本地执行、交由 CI:请确认 CI 中
TokenStatsDisplayGroupingTest已通过。
后续可跟进(不阻塞):统计页默认口径由「按模型名合并」改为「按配置 + 模型」展示是已披露的有意变化,开关可切回旧口径;合并后请在真机上按 PR 描述的回归步骤验证一次流式落库与开关切换。
由 水晴喵的muse 审计
对应 PR AAswordman#1497 评审(水晴喵的muse 审计): - P1:OPENAI_GENERIC / OPENAI_LOCAL 携带 stream_options.include_usage 可能被 严格端点以 400 拒绝,影响面从统计缺失扩大到对话不可用。按评审建议与用户 决策加入降级:仅这两个类型的基类注入在首次 400 时去掉该参数立即重试一次 (不消耗 enableRetry 的重试预算;400 在响应头阶段失败、无内容可回滚); 官方 OPENAI / Xai / Kimi / DeepSeek 不参与降级。实现取"构建后剥离"—— createRequestBody 有大量子类覆写(Kimi/Deepseek 自己注入 stream_options), 改签名会破坏覆写链 - P3 loadModelGrouping 用 runCatching 解析存值,非法值回退默认口径 - P3 docs TODO index 的 working_branch 元数据改为实际分支名 - P3 展示名拼装抽为 TokenStatsDisplayNames 纯函数、分组切换状态转移抽为 applyGroupingChange,新增 TokenStatsDisplayNamesTest 五个用例
|
感谢审计,按评审处理完毕(commit P1 P3-1 P3-2 docs 元数据 — P3-3 ViewModel 层测试 — 已补一部分:展示名拼装抽为纯函数 [question] CI 中 [question] 真机回归 — 合并前会按 PR 描述的步骤在真机验证流式落库与开关切换。 |
CATMIAOZHI
left a comment
There was a problem hiding this comment.
审计结论:✅ 批准合并
复审(针对新提交 3276a4f2,作者逐项回复并修复后请求复审):上次评审的 [P1]×1、[P3]×3、[question]×2 均已处理,无新的 P0/P1。
改动(新提交 6 个文件,+209/−27):OPENAI_GENERIC / OPENAI_LOCAL 流式 400 后去掉 stream_options.include_usage 重试一次;展示名拼装与分组切换抽为纯函数并补 5 个单测;loadModelGrouping 非法存值回退默认;docs working_branch 修正。
通过的检查:
- [P1] 已修好且实现经源码核对无误:降级重试路径与基类
createRequestBody的构建步骤完全一致(createRequestBodyInternal→ 剥离stream_options→ThinkingConfigurationApplier.apply→createJsonRequestBody);400 只在call.execute()返回非 2xx 的响应头阶段抛出,此时尚未读取流式内容,continue前无需回滚;循环内每次重新构建请求体并newCall;降级后includeUsageForRequest=false,再次 400 走正常重试路径并暴露原始错误;工厂确认仅 GENERIC/LOCAL 在基类OpenAIProvider上带includeUsageInStream=true,且无 GENERIC/LOCAL 子类覆写createRequestBody,Kimi/DeepSeek 自注入的stream_options不受影响,官方 OPENAI / Xai 不参与降级。 - [P3-1]
loadModelGrouping已改为runCatching { valueOf }.getOrNull(),非法存值回退默认口径。 - [P3-2] docs
working_branch已改为feat/token-stats-grouping。 - [P3-3] 展示名拼装(
TokenStatsDisplayNames)与分组切换清空选中集(applyGroupingChange)已抽为纯函数并补 5 个单测(配置名前缀 / 合并组保持原名 / legacy 空 configId / 已删除配置兜底 / 切换清空选中集),与 ViewModel 内原有逻辑逐行一致;完整 ViewModel 行为测试需 Robolectric 级环境,暂以纯函数抽测覆盖,作者已说明原因。 - 作者回复确认:本地完整
:app:testDebugUnitTest已通过(含TokenStatsDisplayGroupingTest与新增的TokenStatsDisplayNamesTest);真机回归承诺合并前按 PR 描述步骤验证。
问题:
- [P3] 降级日志措辞不够严谨:
catch分支对includeUsageForRequest为 true 时的任何 400 都会触发降级并记录"端点以 400 拒绝 stream_options.include_usage",但未校验 errorBody 是否与该参数相关;若 400 实为模型名、参数等其他原因,日志会误导排查。建议措辞改为"疑似"或仅在错误体提及stream_options/未知参数时再断言。非阻塞,顺手修即可。
后续可跟进(不阻塞):
- 新提交的 CI 检查结果尚未回传(check-runs 为空),合并前以仓库流水线为准。
- 真机回归(流式落库与开关切换)作者承诺合并前验证,尚未提供实测证据,合并前请确认已完成。
由 水晴喵的muse 审计
变更说明 / Description
背景与动机 / Context and motivation
应用内排查(某模型成功生成却完全不出现在 token 统计里)确认了根因链:OPENAI_GENERIC / OPENAI_LOCAL 的流式请求不携带
stream_options.include_usage,按 OpenAI 规范省略 usage 分块的端点就不会回传用量;applyUsageToCounters解析失败即静默返回,RequestTracker.finish()拿不到成功 attempt 的快照后返回 null,落库被静默跳过且没有任何日志。标题与总结这类辅助请求走 stream=false,响应必带 usage 因而总能落库,于是表现为"部分模型统计不到"。另一半是展示口径:统计页把所有记录仅按模型名合并,同一模型走不同渠道(模型配置)的用量混在一起,无法区分渠道成本。
改动范围 / Changes
includeUsageInStream = true,与官方 OPENAI 类型及 Kimi / DeepSeek 的既有做法对齐;不为个别端点做试探或降级RequestTracker.finish()的四个跳过分支全部留痕(取消为 debug 级,未收到完成回调 / 成功 attempt 无 usage 上报 / 快照字段全未知为 warning 级,带 configId 与 providerModel);usage 非 null 但结构不识别时输出字段名列表config:{configId}|model:{model}划分并暴露组内配置,legacy 导入的空 configId 记录独立成组不包含:Room 表结构(records 本就带 configId 列,纯展示层改造)、取消请求不落库的既有约定、配置详情卡(原本就是配置粒度)。
兼容性与风险 / Compatibility and risks
stream_options.include_usage,少数拒绝未知参数的端点会返回 400,属于端点不符合 OpenAI 规范关联 Issue / Related issue
N/A。排查过程与实施记录见 docs/TODO/token_stats_config_grouping_20261113/。
验证方式 / Verification
流式落库回归:用同一配置向此前未落库的模型发一条流式消息,日志应出现 usage 上报且无"token统计跳过"warning,数据库中该 configId + 模型应新增记录;统计页切换合并开关,模型累计与筛选列表应在两种口径间正确切换。
检查清单 / Checklist
dev;如目标为main,我已说明这是维护者发布同步 / The target branch isdevfor regular work; if it ismain, I explained why this is a maintainer-led release syncCandidate checks覆盖改动范围,并会处理技术失败项 /Candidate checkscovers the change scope and technical failures will be addressed