ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

opencodex 请求日志 Effort 列实现解析:从 reasoning 请求标签到 GUI 展示的完整链路

opencodex 请求日志 Effort 列实现解析:从 reasoning 请求标签到 GUI 展示的完整链路 【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址https://gitcode.com/gh_mirrors/ope/opencodex点击查看免费下载导读本文围绕开发计划文档 devlog/_fin/260629_logs_effort_col/00_plan.md 展开深入剖析 opencodexUniversal provider proxy for OpenAI Codex Claude Code如何在请求日志/api/logs与 GUI Logs 表格中新增一个独立的Effort推理强度列。文章将从展示请求方原始标签而非上游 wire 映射值这一核心设计出发串联响应体解析、请求日志上下文、最终日志落库、管理 API 与 GUI 渲染、i18n 多语言与测试验证的完整实现链路帮助你掌握 opencodex 请求日志数据流的扩展方法以及requested / effective / wire三组推理强度字段的区别与用途。一、需求背景为什么日志里需要一列 Effort在多模型、多 Provider 代理场景下同一份 Codex 请求会被 opencodex 转发给不同的上游Claude、Gemini、Grok、DeepSeek、Kiro 等。不同 Provider 对推理强度的表达方式各不相同Codex 客户端发送的是逻辑标签none / minimal / low / medium / high / xhigh / max而各 Provider 真正消费的wire 值可能完全不同例如 OpenAI 系接受reasoning_effortKiro 这类状态化 Provider 则可能用思考预算百分比表达。在计划落地之前请求日志/api/logs与 GUI Logs 表格缺少对本次请求到底申请了多强推理的记录。运维侧排查为什么这轮思考这么慢 / 为什么 budget 消耗异常时只能靠推测。本计划的 Objective 非常明确见 00_plan.md在请求日志/api/logs GUI Logs 表格中增加单个 Effort 列展示请求方声明的推理强度标签例如xhigh作为最终值——而不是映射后的 wire 值max。所有 Provider 包括kiro都原样展示请求标签kiro 显示xhigh而不是 budget 百分比。这里最关键的语义是日志列记录的是用户/客户端想要什么而不是上游最终收到什么。这样无论路由到哪个 Provider日志的 Effort 列都保持一致的、可横向对比的语义。二、设计决策展示请求标签而不是 wire 映射值计划文档明确给出了本功能的两条事实来源Source of truth约束请求方声明的 effort 位于parsed.options.reasoning计划编写时的引用为 src/responses/parser.ts当前源码中解析发生在 src/responses/parser.ts#L538-L541取值集合为none/minimal/low/medium/high/xhigh/maxwire 映射函数mapReasoningEffortsrc/reasoning-effort.ts#L229在这里故意不被使用。为什么特意强调不用 mapReasoningEffort因为该函数的职责是把 Codex 逻辑标签翻译成 Provider 真正能消费的上游 wire 值例如xhigh按 Provider 的modelReasoningEffortMap/reasoningEffortMap配置映射成max、low或其他别名甚至可能返回undefined表示该模型不支持显式推理强度直接省略字段见 src/reasoning-effort.ts#L22 的REASONING_EFFORT_OMIT_SENTINEL哨兵值。若日志列直接展示 wire 值就会出现用户请求了xhigh日志里却写着max的失真且不同 Provider 之间无法比较。因此本功能的铁律是Effort 列只落parsed.options.reasoning的原始标签。可以结合 src/reasoning-effort.ts#L6-L13 的CODEX_REASONING_LEVELS理解各档位的官方语义描述标签语义来源CODEX_REASONING_LEVELSlowFast responses with lighter reasoningmediumBalances speed and reasoning depth for everyday taskshighGreater reasoning depth for complex problemsxhighExtra high reasoning depth for complex problemsmaxMaximum reasoning depth for the hardest problemsultraMaximum reasoning with automatic task delegation其中none、minimal是计划文档中同样合法的声明级哨兵值——isDeclaredReasoningEffortsrc/reasoning-effort.ts#L40将二者与low..ultra阶梯一起视为合法声明但它们不属于默认阶梯、不参与 clamp 排序minimal在 wire 层会被映射为low。三、数据流从解析器到最终日志的完整链路计划文档的 File change map 编写于src/server.ts单文件时代其中的行号RequestLogContext~:100、handleResponses~:364、RequestLogEntry~:710、addFinalRequestLog~:919在经历了 devlog/_fin/260701_server-ts-split 重构后已经迁移到src/server/目录下的多个文件。当前仓库中该功能实际落在以下链路3.1 解析阶段把响应体里的 reasoning 提到 optionsResponses 请求解析器在 src/responses/parser.ts#L538-L541 完成 effort 提取const requestedEffort data.reasoning?.effort ultra ? max : data.reasoning?.effort; if (requestedEffort REASONING_EFFORTS.has(requestedEffort)) { options.reasoning requestedEffort; }注意这里沿用了上游 codex-rs 的边界语义客户端声明的ultra在进入 Provider 前会被降级为max与上游reasoning_effort_for_request一致但日志侧记录的是客户端原始声明。也就是说若客户端发送ultraoptions.reasoning落库的是max还是ultra取决于采集点的位置——这一点也正是实现中采集于parsed.options.reasoning与采集于原始 body的差别所在详见下文 3.3 关于 pin/cap/clamp 改写链的说明。3.2 采集阶段写入请求日志上下文真正把 effort 写入日志上下文的位置在 src/server/responses/request-prepare.ts#L397logCtx.requestedEffort parsed.options.reasoning;这与requestedServiceTier、callerServiceTier、requestedSpeedLabel等字段的采集方式完全同构见同文件 src/server/responses/request-prepare.ts#L406-L409即从解析后的 options 中摘取调用方意图随请求上下文流转。3.3 中间改写pin / cap / clamp 产生from-to链requestedEffort并非一成不变。opencodex 会在请求进入上游前对 effort 做策略性改写并在日志字段中保留改写痕迹这是理解日志值的关键Effort pin固定当配置或组合combo为某个模型固定了 effort 时src/server/responses/core-normalize.ts#L264 会把日志值改写为from-to形式如none-highEffort cap上限硬性推理强度上限策略位于 src/server/effort-policy.ts。effortCapForsrc/server/effort-policy.ts#L46-L54综合config.effortCap与config.subagentEffortCap仅对 v2 子代理生效取更低的档位作为天花板命中时 src/server/responses/core-normalize.ts#L277 将日志值改写为${capped.from}-${capped.to}并在注入调试开启时输出injectionDebugLog[opencodex] effort cap applied (max - high, sub-agent turn)之类的说明Clamp钳制到模型能力内当模型不支持请求的档位时src/server/responses/core-normalize.ts#L296 会以${logCtx.requestedEffort ?? max}-${clamped}的形式追加记录Chat 原生面chat-native/v1/chat/completions侧的 Claude 兼容面同样维护这一字段src/server/chat-native.ts#L113-L141 中 pin 与 cap 命中时同样生成from-to链并写入快照。因此在最终日志里requestedEffort可能是一个链式值例如max-high表达原始请求 max被策略钳制到 high的完整过程。GUI 端对链式值的渲染专门做了处理见第五节。3.4 落库阶段addFinalRequestLog输出字段所有请求无论走 HTTP 还是 WS、无论成功失败最终都会经过 src/server/request-log.ts#L1298 的addFinalRequestLog这一唯一接缝。当前源码在 src/server/request-log.ts#L1403-L1406 输出...(logCtx.requestedEffort ? { requestedEffort: logCtx.requestedEffort } : {}), ...(logCtx.effectiveEffort ? { effectiveEffort: logCtx.effectiveEffort } : {}), ...(logCtx.reasoningWireField ? { reasoningWireField: logCtx.reasoningWireField } : {}), ...(logCtx.reasoningWireValue ! undefined ? { reasoningWireValue: logCtx.reasoningWireValue } : {}),这里实际上有四组推理相关字段超出计划文档最初描述的单个requestedEffort是后续实现的自然扩展字段语义requestedEffort客户端声明的 effort 标签可能带from-to改写链本列的核心effectiveEffortadapter 归一化后实际发给上游的 wire 档位reasoningWireField上游请求中实际使用的参数字段名如reasoning_effort、thinking_budgetreasoningWireValue该参数字段的精确 wire 值字符串、数字或布尔此外 src/server/request-log.ts#L611-L618 的recordAttemptRequestedEffort会把logCtx.requestedEffort同步到当前 attempt 上并经过redactSecretString脱敏 .slice(0, 64)截断确保日志字段不会夹带敏感内容且不超长且请求日志整体遵循best-effort绝不影响请求投递的原则异常被捕获吞掉。四、API 层/api/logs直接服务请求日志计划文档特别注明无过滤器变更/api/logs直接服务requestLog。当前实现中管理 API 路由 src/server/management/logs-usage-routes.ts#L110-L124 对GET /api/logs的处理是const all getRequestLogEntries(); const total filteredRequestLogCount(all, url.searchParams); const logs filterRequestLogs(all, url.searchParams).map(entry requestLogDto(entry)); const poll selectRequestLogPoll(logs, url.searchParams, cursor); return jsonResponse({ timeZone: ..., logs, ... });requestLogDto会把RequestLogEntry中的requestedEffort/effectiveEffort/reasoningWireField/reasoningWireValue一并序列化进响应对应 src/server/request-log.ts#L383-L394 与 src/server/request-log.ts#L537-L548 的 DTO 映射。也就是说后端只需在日志条目上挂字段API 无需任何额外改动这正是计划中无 filter 变更的落地形态。五、GUI 展示Logs 表格与详情对话框5.1 表格新增列gui/src/pages/Logs.tsx 中LogEntry类型在 gui/src/pages/Logs.tsx#L157-L160 增加requestedEffort?、effectiveEffort?、reasoningWireField?、reasoningWireValue?表头在Model 列之后插入th{t(logs.col.effort)}/thgui/src/pages/Logs.tsx#L768单元格渲染走effortLabel()辅助函数。5.2effortLabel链式值与 effective 的展示规则gui/src/pages/Logs.tsx#L243-L251 的渲染逻辑值得细读function effortLabel(log: ReasoningLogFields): string { const requested log.requestedEffort?.replace(/\s*-\s*/g, → ); const effective log.effectiveEffort; if (!requested) return effective ?? -; // requestedEffort may already contain a cap/clamp chain (for example max-high). // Only append the adapter result when it differs from that chains terminal value. if (!effective || requested effective || requested.split( → ).at(-1) effective) return requested; return ${requested} → ${effective}; }规则要点以requestedEffort为优先展示缺失时退化为effectiveEffort都没有则显示-与计划中render cell: requested effort label or-一致-改写链在 UI 上渲染为箭头→如max→high只有当effectiveEffort与链的末端值不同时才追加→ effective避免max-high → high这类冗余展示。5.3 详情对话框行详情对话框gui/src/pages/Logs.tsx#L1002-L1004在基本信息区展示 Effort 行并把reasoningWirewireFieldwireValue如reasoning_efforthigh以括号附注的形式追加在标签后attempt 明细表格gui/src/pages/Logs.tsx#L1135-L1140对每个物理 attempt 同样展示其各自的requestedEffort/effectiveEffort/ wire 证据。这意味着重试failover场景下每个尝试档位各不相同也能被精确追溯。六、i18n多语言键计划要求为en/ko/zh三个语言包增加logs.col.effort键。当前仓库中该键已覆盖全部 10 个语言文件例如gui/src/i18n/en.ts#L872Effortgui/src/i18n/zh.ts#L835推理强度gui/src/i18n/ko.ts#L854추론 강도gui/src/i18n/ja.ts#L784負荷、gui/src/i18n/zh-TW.ts#L689推理強度等七、持久化决策从仅内存到重启后恢复的演进计划文档的 Persistence decision 明确写道Effort 是仅内存的日志字段类似requestedServiceTier不加入PersistedUsageEntry——usage.jsonl用于 token 核算effort 在那里不需要。但从当前源码看这一决策在后续实现中发生了演进src/usage/log.ts中的持久化条目类型src/usage/log.ts#L302-L306确实包含requestedEffort?、effectiveEffort?、reasoningWireField?、reasoningWireValue?字段注释明确写着Reasoning effort / service-tier metadata for GUI Logs after restart.即为了让GUI Logs 在服务重启后仍能恢复并展示 Effort 列持久化层后来也纳入了这组字段且在序列化时统一经过capMetadataString长度/内容收敛src/usage/log.ts#L834-L836。与此同时usage.jsonl作为 token 核算账本的定位没有改变——effort 字段只是作为元数据附加列随行落盘不参与任何 token 汇总与计费逻辑。如果你要在自己的实现中做类似扩展可以按核算字段与诊断元数据分离的原则决定取舍。八、验证与测试计划文档给出了三条验证路径当前仓库均可对应到具体实现类型检查bunx tsc --noEmitroot gui保证RequestLogContext/RequestLogEntry/ GUILogEntry三处类型定义同步单元测试bun test tests/usage/request-log.test.ts注计划文档中的tests/request-log.test.ts在仓库整理后位于 tests/usage/request-log.test.ts透传断言测试扩展为显式断言requestedEffort完整通过addFinalRequestLog。测试文件中的代表性用例records the adapters exact outbound reasoning parametertests/usage/request-log.test.ts#L275-L308构造requestedEffort: max的上下文经recordAdapterReasoning后断言logCtx与 attempt 同时携带requestedEffort: max、effectiveEffort: high、reasoningWireField: reasoning_effort、reasoningWireValue: highfinal combo logging keeps one logical row...tests/usage/request-log.test.ts#L611-L693组合combo请求中多个 attempt 各自的requestedEffort如minimal、max被逐条保留在最终日志行内deferred JSON logging preserves response service tier before final logtests/usage/request-log.test.ts#L988-L1026requestedEffort: xhigh经完整请求处理后出现在entries[0]中验证了从采集到addFinalRequestLog输出的整条链路。九、小结给扩展请求日志字段的实践清单从本计划及其实施中可以提炼出一条在 opencodex 中扩展请求日志诊断字段的可复用路径定义类型在 src/server/request-log.ts 的RequestLogContext与RequestLogEntry上同步增加可选字段采集在 src/server/responses/request-prepare.tsResponses 面与 src/server/chat-native.tsChat 面从parsed.options/ 请求体中提取调用方原始意图不要在采集点使用 wire 映射透传addFinalRequestLog在 src/server/request-log.ts#L1403-L1406 一带输出字段含 attempt 级同步、脱敏与截断API 免改requestLogDto会自动带上新字段/api/logs无需改动GUI在 gui/src/pages/Logs.tsx 补类型、表头、渲染逻辑与详情对话框i18n为 gui/src/i18n 下各语言包补logs.col.effort键测试在 tests/usage/request-log.test.ts 中断言新字段从采集到最终日志的透传持久化取舍若需要在重启后恢复展示可在 src/usage/log.ts 的持久化条目中附加字段并做capMetadataString收敛若仅用于当前进程诊断则可保持内存字段。至此Effort 列从需求、设计、数据流、API、GUI、多语言、持久化到测试验证的完整实现脉络已经清晰可见它记录的是用户意图而非上游产物这正是 opencodex 请求日志作为跨 Provider 统一诊断面所坚持的语义。赞分享【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址https://gitcode.com/gh_mirrors/ope/opencodex点击查看免费下载相关推荐PermissionScope权限请求流程从UI展示到系统回调的完整链路解析PermissionScope权限请求流程从UI展示到系统回调的完整链路解析 PermissionScope是iOS开发中一个智能的权限请求UI框架它提供了移动开发UI组件深入解析 Fresh 请求生命周期从请求进入到岛屿水合的完整链路深入解析 Fresh 请求生命周期从请求进入到岛屿水合的完整链路 本篇技术指南聚焦 Fresh 框架的核心架构一个请求从进入服务器到经过中间件链、路由匹配后端前端如何用Open3D实现3D数据处理从入门到实战的完整指南如何用Open3D实现3D数据处理从入门到实战的完整指南 Open3D是一个功能强大的开源3D数据处理库专为现代3D计算机视觉和几何处理而设计。无论你是从事计算机视觉图形学3D渲染科学计算创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进