diff --git a/apps/site/docs/en/model-common-config.mdx b/apps/site/docs/en/model-common-config.mdx index b6638e1405..7c5102e755 100644 --- a/apps/site/docs/en/model-common-config.mdx +++ b/apps/site/docs/en/model-common-config.mdx @@ -400,6 +400,7 @@ MIDSCENE_INSIGHT_MODEL_FAMILY="xiaomi-mimo" | Model version | Commonly used model names | `MIDSCENE_MODEL_FAMILY` | Notes | | --- | --- | --- | --- | +| GLM-5.3 series | `glm-5.3-flash` | `glm-v` | Always-thinking: `thinking.type` cannot be disabled, so disabling reasoning sends `reasoning_effort: low` instead. | | GLM-5V series | `glm-5v-turbo` | `glm-v` | — | | GLM-4.6 series | `glm-4.6v` | `glm-v` | `glm-4.6v` is open-source. | diff --git a/apps/site/docs/en/model-config.mdx b/apps/site/docs/en/model-config.mdx index 33f43c8054..f78563043e 100644 --- a/apps/site/docs/en/model-config.mdx +++ b/apps/site/docs/en/model-config.mdx @@ -107,6 +107,7 @@ The following model families currently support `MIDSCENE_MODEL_REASONING_ENABLED - Gemini: Maps to `thinking_config.thinking_level`. - GPT-5: Maps to `reasoning_effort`. - Kimi K3 series: Maps to `reasoning_effort`. +- Zhipu GLM: Maps to `reasoning_effort` (glm-5.3-flash only). Provider support and accepted values vary. See each provider's official documentation for supported model versions and values. If the current model does not support an explicit setting, Midscene ignores it instead of guessing a provider-specific private parameter. diff --git a/apps/site/docs/zh/model-common-config.mdx b/apps/site/docs/zh/model-common-config.mdx index d28bbf412b..33c96d2696 100644 --- a/apps/site/docs/zh/model-common-config.mdx +++ b/apps/site/docs/zh/model-common-config.mdx @@ -401,6 +401,7 @@ MIDSCENE_INSIGHT_MODEL_FAMILY="xiaomi-mimo" | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | +| GLM-5.3 系列 | `glm-5.3-flash` | `glm-v` | 始终思考:不支持关闭 `thinking.type`,关闭推理时会改发 `reasoning_effort: low`。 | | GLM-5V 系列 | `glm-5v-turbo` | `glm-v` | — | | GLM-4.6 系列 | `glm-4.6v` | `glm-v` | `glm-4.6v` 是开源模型。 | diff --git a/apps/site/docs/zh/model-config.mdx b/apps/site/docs/zh/model-config.mdx index 7fb3c20d8d..95441c84aa 100644 --- a/apps/site/docs/zh/model-config.mdx +++ b/apps/site/docs/zh/model-config.mdx @@ -106,6 +106,7 @@ Midscene 默认关闭模型原生思考,以获得更好的执行速度和稳 - Gemini:对应 `thinking_config.thinking_level`。 - GPT-5:对应 `reasoning_effort`。 - Kimi K3 系列:对应 `reasoning_effort`。 +- 智谱 GLM:对应 `reasoning_effort`(仅 glm-5.3-flash)。 不同模型服务商支持的参数和值不同。具体取值和适用模型版本请参考对应服务商的官方文档。如果当前模型不支持某项显式配置,Midscene 会忽略该配置,不会猜测服务商的私有参数。 diff --git a/packages/core/src/ai-model/model-adapter/types.ts b/packages/core/src/ai-model/model-adapter/types.ts index 65b5b9a87f..21ea0d1232 100644 --- a/packages/core/src/ai-model/model-adapter/types.ts +++ b/packages/core/src/ai-model/model-adapter/types.ts @@ -63,6 +63,12 @@ export type ChatCompletionUnsupportedUserConfig = export interface ChatCompletionCallInput { intent?: TIntent; + /** + * Model name resolved for this call. Adapters may use it to branch within a + * family when a specific model requires different request parameters (for + * example, always-thinking variants). + */ + modelName?: string; userConfig?: ChatCompletionCallUserConfig; /** * Number of preceding semantic parsing failures for this request. @@ -83,6 +89,10 @@ export interface ChatCompletionCallInput { export interface ChatCompletionCallContext { intent?: TIntent; + /** + * Model name resolved for this call; see ChatCompletionCallInput. + */ + modelName?: string; userConfig: ChatCompletionCallUserConfig; semanticRetryAttempt?: number; requiresOriginalImageDetail?: boolean; diff --git a/packages/core/src/ai-model/models/glm.ts b/packages/core/src/ai-model/models/glm.ts index 0736b8dec0..52190d684e 100644 --- a/packages/core/src/ai-model/models/glm.ts +++ b/packages/core/src/ai-model/models/glm.ts @@ -5,14 +5,31 @@ import type { ModelAdapterDefinition, } from '../model-adapter/types'; +const ALWAYS_THINKING_GLM_MODEL_PATTERN = /^glm-5\.3-flash\b/; + const buildGlmChatCompletionParams = ( input: ChatCompletionCallContext, ): ChatCompletionParamsResult => { - const { midsceneDefaults, userConfig } = input; + const { midsceneDefaults, userConfig, modelName } = input; const { reasoningEnabled } = userConfig; const commonOverrideConfig: Record = {}; - if (userConfig.temperature !== undefined) { + // GLM models that cannot turn thinking off. GLM-5.3-Flash rejects + // `thinking.type: 'disabled'` with error 1210 ("该模型始终思考,不支持关闭 + // 思考"), so for these models the adapter keeps thinking enabled and steers + // its depth with `reasoning_effort` instead. Field-tested against the + // Zhipu Coding Plan endpoint in KSL-49 (2026-08-26). + const alwaysThinking = ALWAYS_THINKING_GLM_MODEL_PATTERN.test( + modelName ?? '', + ); + + if (alwaysThinking) { + // Officially recommended sampling parameters for GLM-5.3-Flash + // (https://docs.bigmodel.cn/cn/guide/models/vlm/glm-5.3-flash), and the + // values its vision integration was field-tested with. + commonOverrideConfig.temperature = userConfig.temperature ?? 1; + commonOverrideConfig.top_p = 0.95; + } else if (userConfig.temperature !== undefined) { commonOverrideConfig.temperature = userConfig.temperature; } @@ -27,7 +44,27 @@ const buildGlmChatCompletionParams = ( const modelSpecificConfig: Record = {}; - if (reasoningEnabled !== 'default') { + if (alwaysThinking) { + if (reasoningEnabled !== 'default') { + modelSpecificConfig.thinking = { + type: 'enabled', + clear_thinking: false, + }; + if (reasoningEnabled !== true) { + // Thinking cannot be disabled, so a "no reasoning" intent (explicit + // false or unset) maps to the cheapest effort level. `low` was + // sufficient for the KSL-49 vision probes; users can raise it via + // the reasoning-effort config. + modelSpecificConfig.reasoning_effort = + userConfig.reasoningEffort ?? 'low'; + } else if (userConfig.reasoningEffort) { + modelSpecificConfig.reasoning_effort = userConfig.reasoningEffort; + } + } + // reasoningEnabled='default' sends no thinking or effort override, per + // the documented contract in docs/model-config.mdx: default follows the + // provider behavior and ignores explicit effort settings. + } else if (reasoningEnabled !== 'default') { modelSpecificConfig.thinking = { type: (reasoningEnabled ?? false) ? 'enabled' : 'disabled', }; @@ -45,6 +82,8 @@ const buildGlmChatCompletionParams = ( export const glmAdapters = { 'glm-v': { chatCompletion: { + // reasoningEffort is honored only by the always-thinking GLM models + // (see above); reasoningBudget is not supported by the family at all. unsupportedUserConfig: ['reasoningEffort', 'reasoningBudget'], buildChatCompletionParams: buildGlmChatCompletionParams, useReasoningAsContentFallback: true, diff --git a/packages/core/src/ai-model/service-caller/index.ts b/packages/core/src/ai-model/service-caller/index.ts index 162909f568..8ff2f8040f 100644 --- a/packages/core/src/ai-model/service-caller/index.ts +++ b/packages/core/src/ai-model/service-caller/index.ts @@ -403,6 +403,7 @@ export async function callAI( : undefined; const chatCompletionInput = { intent: modelConfig.intent, + modelName: modelConfig.modelName, userConfig: { temperature: modelConfig.temperature, reasoningEnabled: modelConfig.reasoningEnabled, diff --git a/packages/core/tests/unit-test/model-adapter/glm.test.ts b/packages/core/tests/unit-test/model-adapter/glm.test.ts index ef07b3d888..5eb08c7d75 100644 --- a/packages/core/tests/unit-test/model-adapter/glm.test.ts +++ b/packages/core/tests/unit-test/model-adapter/glm.test.ts @@ -110,3 +110,118 @@ describe('glm model adapter', () => { expect(result.config.response_format).toBeUndefined(); }); }); + +describe('glm-5.3-flash always-thinking contract', () => { + it('never sends thinking.type=disabled for glm-5.3-flash', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5.3-flash', + userConfig: {}, + }); + expect(result.config).toEqual({ + temperature: 1, + top_p: 0.95, + thinking: { type: 'enabled', clear_thinking: false }, + reasoning_effort: 'low', + }); + }); + + it('maps a reasoning-disable intent to effort=low for glm-5.3-flash', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5.3-flash', + userConfig: { reasoningEnabled: false }, + }); + expect(result.config).toEqual({ + temperature: 1, + top_p: 0.95, + thinking: { type: 'enabled', clear_thinking: false }, + reasoning_effort: 'low', + }); + }); + + it('forwards an explicit reasoning effort for glm-5.3-flash', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5.3-flash', + userConfig: { reasoningEnabled: true, reasoningEffort: 'high' }, + }); + expect(result.config).toEqual({ + temperature: 1, + top_p: 0.95, + thinking: { type: 'enabled', clear_thinking: false }, + reasoning_effort: 'high', + }); + }); + + it('keeps the provider effort default when reasoning is explicitly enabled without effort', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5.3-flash', + userConfig: { reasoningEnabled: true }, + }); + expect(result.config).toEqual({ + temperature: 1, + top_p: 0.95, + thinking: { type: 'enabled', clear_thinking: false }, + }); + expect(result.config.reasoning_effort).toBeUndefined(); + }); + + it('ignores an explicit effort in default reasoning mode for glm-5.3-flash', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5.3-flash', + userConfig: { reasoningEnabled: 'default', reasoningEffort: 'high' }, + }); + expect(result.config).toEqual({ + temperature: 1, + top_p: 0.95, + }); + }); + + it('follows provider default for glm-5.3-flash when reasoningEnabled=default', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5.3-flash', + userConfig: { reasoningEnabled: 'default' }, + }); + expect(result.config).toEqual({ + temperature: 1, + top_p: 0.95, + }); + }); + + it('keeps an explicit user temperature for glm-5.3-flash', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5.3-flash', + userConfig: { temperature: 0 }, + }); + expect(result.config).toEqual({ + temperature: 0, + top_p: 0.95, + thinking: { type: 'enabled', clear_thinking: false }, + reasoning_effort: 'low', + }); + }); + + it('combines json_object response format with always-thinking params', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5.3-flash', + expectedJsonObjectResponse: true, + userConfig: {}, + }); + expect(result.config).toEqual({ + temperature: 1, + top_p: 0.95, + response_format: { type: 'json_object' }, + thinking: { type: 'enabled', clear_thinking: false }, + reasoning_effort: 'low', + }); + }); + + it('keeps toggleable thinking for non-flash glm-v models', () => { + const result = glmAdapter.chatCompletion.buildChatCompletionParams({ + modelName: 'glm-5v-turbo', + userConfig: {}, + }); + expect(result.config).toEqual({ + temperature: 0, + thinking: { type: 'disabled' }, + }); + }); +});