☰
Operit 思考质量映射契约:统一 provider 档位描述、wire value 与 UI 渲染的 ThinkingQualityMapping 方案
2026/9/29 2:51:35 网站建设 项目流程
  • AI Agent
  • 人工智能
  • 大模型
  • AI 应用
  • 工具调用
  • 本地部署
  • MCP Clients
  • Agent 记忆

【免费下载链接】Operit

The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent

项目地址:https://gitcode.com/gh_mirrors/op/Operit
点击查看免费下载

导读

本文讲解 Operit 中“思考质量(Thinking Quality)”档位从 provider 内部私有映射走向统一公开契约的改造方案。核心是ThinkingQualityMapping数据模型与ThinkingQualityMappingRegistry注册表:它们同时服务于请求构建与 UI 渲染,让 Android 与 Web 两端能够读取同一份“控制类型、参数名、每档显示值与 wire value”的描述。读完本文,你将掌握该映射契约的完整数据结构、规则解析与模型匹配逻辑、JSON 配置编写方式,以及displayLabel与类型化wireValue分离设计的工程动机。

背景:旧实现的三个痛点

在引入统一映射契约之前,OpenAI、Gemini、DeepSeek、NVIDIA、SiliconFlow 和 OpenRouter 等 provider 的“思考程度”映射是分散且私有的:

  • 每个 provider 在自己的请求构建代码内部保存“全局档位 → 请求参数”的映射表;
  • UI 层无法读取同一份描述,只能显示笼统的全局数字档位;
  • 由于映射定义不在公共位置,请求构建与界面展示之间存在重复描述、容易漂移,也无法为不同模型提供差异化的真实档位文本。

这意味着用户看到的“档位数字”与请求里真正下发的参数值之间隔着一段不可见、不可校验的逻辑。新的映射契约正是为了解决这三个问题而设计:一份定义,两端(请求构建 + UI)共用。

核心契约:ThinkingQualityMapping数据结构

映射契约的核心实现在 ThinkingQualityMapping.kt。整个契约由以下类型协同组成:

控制类型:ThinkingQualityControl

internal enum class ThinkingQualityControl { LEVELS, TOGGLE_ONLY, UNSUPPORTED }

三种取值语义如下:

取值含义
LEVELSprovider 支持多档程度参数,UI 渲染离散滑块
TOGGLE_ONLYprovider 没有程度参数、只有开关,UI 只显示开关
UNSUPPORTEDprovider/模型不支持思考控制,UI 不显示相关控件

契约中特别强调:没有程度参数的 provider 必须显式声明TOGGLE_ONLY,绝不交由 UI 去猜测档位含义。这是“显式优于隐式”的契约约束,避免 UI 对未知 provider 臆造档位。

wire value 类型化:ThinkingQualityWireValue

internal sealed interface ThinkingQualityWireValue { data class Text(val value: String) : ThinkingQualityWireValue data class Number(val value: Int) : ThinkingQualityWireValue data object Omitted : ThinkingQualityWireValue }

不同 provider 的思考参数值形态差异很大:OpenAI 系是字符串(low/high),Gemini 的 thinkingBudget 是整数(如1024/8192),某些场景还会省略值。因此 wire value 采用密封类型,区分Text、Number与Omitted,保证内部请求构建时能拿到类型正确的参数值,而不是统一字符串化后再由各 provider 各自转换。

选项与动作:ThinkingQualityOption/ThinkingQualityJsonAction

internal data class ThinkingQualityOption( val id: String, val displayLabel: String, val wireValue: ThinkingQualityWireValue, val actions: List<ThinkingQualityJsonAction> = emptyList(), ) internal data class ThinkingQualityJsonAction( val path: String, val value: Any?, val overwrite: Boolean = false, )
  • id:内部档位标识(如low、high、8192),也是 UI 与请求构建之间传递的“选中项”契约值;
  • displayLabel:展示给用户看的文本;
  • wireValue:真正写入请求的类型化值;
  • actions:选中该档位时需要额外写入的 JSON 路径动作(支持嵌套路径与overwrite语义)。

映射主体:ThinkingQualityMapping

internal data class ThinkingQualityMapping( val control: ThinkingQualityControl, val parameterLabel: String, val options: List<ThinkingQualityOption>, val reasoningRequired: Boolean = false, val disabledValue: String? = null, val enabledActions: List<ThinkingQualityJsonAction> = emptyList(), val disabledActions: List<ThinkingQualityJsonAction> = emptyList(), )

关键字段语义:

  • parameterLabel:思考参数名(如reasoning_effort、thinkingBudget、thinkingLevel),供请求构建使用,不出现在 UI 文案中;
  • reasoningRequired:该模型是否必须开启思考(如部分模型不支持关闭思考);
  • enabledActions/disabledActions:开启 / 关闭思考开关时对请求 JSON 执行的动作序列(如写reasoning.effort = "none");
  • disabledValue:关闭思考时使用的参数值。

伴侣对象提供两个便捷工厂:toggleOnly(...)构造显式开关型映射,unsupported()构造不支持映射。

单一事实来源:ThinkingQualityMappingRegistry

ThinkingQualityMappingRegistry是契约中的“注册表”,承担从 provider/模型/端点解析出映射的唯一入口,也是“请求构建和 UI 都通过它获取定义”这一原则的实现:

fun resolve( providerTypeId: String, modelName: String, apiEndpoint: String, thinkingConfigurations: String ): ThinkingQualityMapping

解析过程采用规则优先匹配:将thinkingConfigurationsJSON 解析为规则列表,按 JSON 数组顺序取第一条同时命中 provider、模型与端点的启用规则,后续规则不再评估;没有规则命中时返回unsupported()。源码注释明确指出:“The JSON array order is the user-visible priority order”。

规则数据结构:ThinkingConfigurationRule

规则包含:

  • id、enabled:规则标识与开关(enabled=false的规则在解析时被跳过);
  • providerIds:命中哪些 provider(同时兼容providers与providerTypeIds两个 JSON 键);
  • matcher:模型匹配器;
  • endpointSuffixes:端点后缀匹配(用于区分同一 provider 的 chat 与 responses 协议);
  • control/parameterLabel/reasoningRequired:映射核心字段;
  • enabledActions/disabledActions/disabledValue:开关动作与关闭值;
  • options:档位列表。

provider 匹配不区分大小写(统一转大写);端点匹配会先剥离查询串(?之后)与锚点(#之后)、去掉尾部/并转小写,再判断是否以给定后缀结尾。

模型匹配器:ThinkingModelMatcher

模型匹配支持多种模式,命中任意一种即匹配成功(ThinkingConfigurationRule.fromJson同时读取match子对象与规则根级同名键):

匹配键语义
modelPrefix模型名以指定前缀开头
modelContains模型名包含指定子串
modelSuffix模型名以指定后缀结尾
modelRegex正则匹配(忽略大小写)
firstSegment模型名/分割后的首段相等
lastSegmentPrefix/lastSegmentContains/lastSegmentRegex针对末段(如gpt-5.6-luna这类带组织前缀的模型)的前缀 / 包含 / 正则匹配

匹配器为空时视为通配(命中所有模型)。这套设计让一条规则既能精确锁定某类模型(如 Gemini 3.x),也能用正则覆盖模型族。

JSON 配置格式与真实示例

映射规则以 JSON 形式存放在 ModelThinkingConfigDefaultsCollect.kt 的ModelThinkingConfigDefaults.DEFAULT_JSON中(共 434 行)。规则数组既可以直接以[...]顶层数组书写,也可以包在{"rules": [...]}对象中(rulesArray负责兼容两种形态),空串按[]处理。

示例一:OpenAI chat 系(字符串档位)

{ "id": "openai-chat-reasoning-effort", "providers": ["OPENAI", "OPENAI_GENERIC"], "match": {"modelRegex": ["(?:^|/)(?:o[1-9]|gpt-[5-9]|gpt-oss|codex)"]}, "control": "levels", "parameterLabel": "reasoning_effort", "options": [ {"id": "low", "label": "low", "path": "reasoning_effort", "value": "low"}, {"id": "medium", "label": "medium", "path": "reasoning_effort", "value": "medium"}, {"id": "high", "label": "high", "path": "reasoning_effort", "value": "high"}, {"id": "xhigh", "label": "xhigh", "path": "reasoning_effort", "value": "xhigh"}, {"id": "max", "label": "max", "path": "reasoning_effort", "value": "max"} ] }

这里的label会被解析为displayLabel,path指明写入请求 JSON 的路径,value生成类型化wireValue。

示例二:OpenAI Responses 协议(带启用/关闭动作与嵌套路径)

{ "id": "openai-responses-reasoning-effort", "providers": ["OPENAI_RESPONSES", "OPENAI_RESPONSES_GENERIC", "OPENAI_CODEX"], "control": "levels", "parameterLabel": "reasoning.effort", "enable": [ {"path": "reasoning.summary", "value": "auto"}, {"path": "include", "value": ["reasoning.encrypted_content"]} ], "disable": [ {"path": "reasoning.effort", "value": "none"} ], "options": [ {"id": "low", "label": "low", "path": "reasoning.effort", "value": "low"} ] }

enable/disable数组对应enabledActions/disabledActions,支持向include写入数组值,展示出动作系统的表达能力。

示例三:Gemini 数字档位与必开思考

{ "id": "gemini-25-thinking-budget", "providers": ["GOOGLE", "GEMINI_GENERIC"], "match": {"modelPrefix": ["gemini-2.5"]}, "control": "levels", "parameterLabel": "thinkingBudget", "enable": [ {"path": "generationConfig.thinkingConfig.includeThoughts", "value": true} ], "disable": [ {"path": "generationConfig.thinkingConfig.includeThoughts", "value": false}, {"path": "generationConfig.thinkingConfig.thinkingBudget", "value": 0} ], "options": [ {"id": "1024", "label": "1024", "path": "generationConfig.thinkingConfig.thinkingBudget", "value": 1024}, {"id": "4096", "label": "4096", "path": "generationConfig.thinkingConfig.thinkingBudget", "value": 4096} ] }

Gemini 的档位是整数 token 预算,path深入generationConfig.thinkingConfig嵌套结构;disable同时写两个路径实现关闭。而 Gemini 3.x 系列则使用thinkingLevel(MINIMAL/LOW/MEDIUM/HIGH字符串枚举)并标记"required": true,表示思考不可关闭。

此外,默认配置还覆盖 DeepSeek(区分/responses端点与 chat 端点、reasoning.effort三档)、SiliconFlow(数字档位如128/8192)等 provider;thinkingConfigurations也支持通过validateConfigurations校验、formatConfigurations美化格式化,便于在设置界面维护自定义规则。

档位独立与 displayLabel / wireValue 分离

契约的关键设计原则是:每个 level 保留独立位置,即使多个 level 使用相同的 wire value。UI 只展示displayLabel,内部请求则继续使用类型化的wireValue。这一点在ThinkingQualityMapping的辅助方法中得到体现:

fun optionFor(id: String): ThinkingQualityOption? = options.firstOrNull { it.id == id } fun textValueFor(id: String): String? = (optionFor(id)?.wireValue as? ThinkingQualityWireValue.Text)?.value fun numberValueFor(id: String): Int? = (optionFor(id)?.wireValue as? ThinkingQualityWireValue.Number)?.value

UI 通过optionFor按档位 id 定位选项并读取displayLabel;请求构建通过textValueFor/numberValueFor取出类型化的真实值。由于二者都从同一个options列表取数,UI 文本与请求参数永远不会脱节,也天然支持“多个显示档位映射到同一个 wire value”的场景(例如某 provider 的 low 与 medium 都下发"low",但界面仍展示两个独立档位,保留用户的选择状态与未来协议升级空间)。

从契约到请求:ThinkingConfigurationApplier

映射契约不只是“描述”,还负责把档位真正写进请求。ThinkingConfigurationApplier.apply(...)是请求构建侧的执行入口,流程如下:

  1. 通过ThinkingQualityMappingRegistry.resolve(...)解析出当前 provider/模型/端点的映射;
  2. UNSUPPORTED直接返回,不修改请求;
  3. 计算thinkingEnabled = enableThinking || mapping.reasoningRequired(模型必须思考时自动开启);
  4. 按开关状态应用enabledActions或disabledActions;
  5. 若开启且为LEVELS,则按选中的optionId应用该档位的actions;选项不属于当前映射时抛出IllegalArgumentException,防止脏档位写入请求。

动作执行支持path的点号嵌套路径写入(putJsonPath),未开启overwrite时若目标路径已存在则跳过写入(hasJsonPath先探测),避免覆盖请求中的既有字段。modelParameters(...)变体还会把最终请求 JSON 转成ModelParameter列表(字符串 / 整数 / 浮点 / 布尔 / 对象,Gemini 协议的thinkingConfig归入GENERATION分类),供设置界面预览当前模型的实际请求参数。

源码注释强调:选中的档位属于模型配置,绝不在此处读取全局偏好,从机制上保证了“每模型独立档位”的契约。

UI 消费:显示标签而非参数名

在 Android 端,ThinkingQualitySlider.kt 直接以ThinkingQualityMapping为输入:根据mapping.control判断是否渲染滑块(LEVELS且选项非空、当前选中项存在才渲染),通过options.indexOfFirst { it.id == value }定位选中索引,标题右侧展示selectedOption.displayLabel作为当前值,track 下方按档位渲染映射文本标签。契约约束“不在用户界面显示 provider 参数名”——parameterLabel仅用于请求侧,UI 只消费displayLabel。

同一份映射还被 Classic 与 Agent 两套输入样式共享(ClassicChatSettingsBar.kt、AgentChatInputSection.kt),并在 ModelConfigScreen.kt 与 ModelConfigManager.kt 中参与模型配置的读写,内部统一使用thinking_option_id字符串契约传递选中档位,不再保留全局固定档位数字。

Web 同步:映射随模型选择下发

Web 端沿用同一契约:服务端通过 WebChatModels.kt 中的WebModelSelectorState携带thinking_quality_mapping(@SerialName("thinking_quality_mapping"))字段,随当前 provider/model 一并返回;ThinkingQualitySlider.tsx 与 chatTypes.ts 按 mapping 渲染标签,输入设置仍只保存当前内部 level。这样 Android 与 Web 两端读到的档位文本来自同一个解析结果,保证多端一致。

契约验证:测试用例与工程记录

映射契约的测试集中在 ThinkingQualityMappingTest.kt,覆盖了契约的核心保证:

  • 模型级差异化:grok-4.6命中reasoning_effort四档(low/medium/high/xhigh),gpt-5.6-luna命中五档(low/medium/high/xhigh/max);
  • 类型化 wire value:SiliconFlowQwen3的显示标签为128等字符串,但numberValueFor("8192")返回整数8192,验证displayLabel与wireValue类型分离;
  • 显式 TOGGLE_ONLY:Zhipuglm-4.7-thinking断言为TOGGLE_ONLY,参数为thinking.type且reasoningRequired=false;旧模型glm-3-turbo断言为UNSUPPORTED;
  • 必开思考:Zhipuglm-5.3断言LEVELS+reasoningRequired=true;
  • 端点区分:DeepSeekdeepseek-chat与deepseek-reasoner分属不同映射族。

相关测试还包括 OpenAiChatReasoningEffortTest.kt、GeminiThinkingConfigTest.kt 与 OpenCodeThinkingConfigurationTest.kt。整个改造的进度与视觉验收记录见 docs/TODO/thinking_quality_slider_ui/index.md 及同目录下的 02_native_slider.md、03_web_parity.md、04_verification.md。

小结

ThinkingQualityMapping映射契约把“思考程度”从 provider 各自的私有实现中抽离为一份可解析、可校验、请求与 UI 共用的声明式定义:ThinkingQualityControl明确控制形态,ThinkingQualityOption以独立档位承载displayLabel与类型化wireValue的分离,ThinkingQualityMappingRegistry按规则(provider + 模型匹配 + 端点后缀)解析出唯一映射,ThinkingConfigurationApplier将选中档位安全写入请求。Android 与 Web 两端因此能对同一模型展示同一套真实档位文本,同时保留扩展新 provider 时只需新增 JSON 规则的低成本路径。

  • AI Agent
  • 人工智能
  • 大模型
  • AI 应用
  • 工具调用
  • 本地部署
  • MCP Clients
  • Agent 记忆

【免费下载链接】Operit

The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent

项目地址:https://gitcode.com/gh_mirrors/op/Operit
点击查看免费下载

相关推荐

上一篇:Python字节码逆向工程终极指南:深度揭秘pycdc反编译实战
下一篇:Touch Bar革命:重新定义你的MacBook生产力边界

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询