From 20e3555b84960aa8e2b3fbd456363b02621f7030 Mon Sep 17 00:00:00 2001 From: clh02467605 Date: Tue, 28 Jul 2026 17:19:05 +0800 Subject: [PATCH] fix(text,auth): drop enable_thinking retry and omit the field by default Pass through model constraint errors instead of auto-retrying, switch token-plan default text model to qwen3.7-plus, and align related e2e expectations. --- .../src/commands/auth/login-api-key.ts | 56 ++---- packages/commands/src/commands/text/chat.ts | 33 ++-- packages/commands/tests/e2e/auth.e2e.test.ts | 8 +- .../commands/tests/e2e/text-chat.e2e.test.ts | 13 +- .../tests/e2e/vision-describe.e2e.test.ts | 4 +- packages/core/src/config/profile-presets.ts | 2 +- packages/core/src/index.ts | 1 - packages/core/src/models/index.ts | 8 - packages/core/src/models/thinking.ts | 88 --------- packages/core/tests/thinking.test.ts | 174 ------------------ 10 files changed, 38 insertions(+), 349 deletions(-) delete mode 100644 packages/core/src/models/index.ts delete mode 100644 packages/core/src/models/thinking.ts delete mode 100644 packages/core/tests/thinking.test.ts diff --git a/packages/commands/src/commands/auth/login-api-key.ts b/packages/commands/src/commands/auth/login-api-key.ts index 9408498..476e6bd 100644 --- a/packages/commands/src/commands/auth/login-api-key.ts +++ b/packages/commands/src/commands/auth/login-api-key.ts @@ -4,9 +4,6 @@ import { chatPath, requestJson, normalizeModelBaseUrl, - applyChatEnableThinking, - resolveChatEnableThinking, - withEnableThinkingRetry, type AuthPersistPatch, type AuthStore, type Identity, @@ -61,48 +58,31 @@ export async function validateAndPersistApiKey( ? normalizeModelBaseUrl(profile.persistBaseUrl) : undefined; const validationModel = profile.defaultTextModel || "qwen3.7-max"; - const body: { - model: string; - messages: Array<{ role: string; content: string }>; - max_tokens: number; - stream: boolean; - enable_thinking?: boolean; - } = { - model: validationModel, - messages: [{ role: "user", content: "hi" }], - max_tokens: 1, - stream: false, - }; - const requestOpts = { url: baseUrl + chatPath(), method: "POST", headers: { Authorization: `Bearer ${key}` }, timeout: Math.min(deps.settings.timeout, 30), - body, + body: { + model: validationModel, + messages: [{ role: "user", content: "hi" }], + max_tokens: 1, + stream: false, + }, }; - try { - await withEnableThinkingRetry({ - // Validation requests are always non-streaming. - initial: resolveChatEnableThinking({ stream: false }), - apply: (value) => applyChatEnableThinking(body, value), - run: async () => { - for (let attempt = 1; attempt <= 3; attempt++) { - try { - await requestJson(httpDeps, requestOpts); - return; - } catch (error) { - if (attempt >= 3 || !canRetry(error)) throw error; - const delayMs = RETRY_DELAY_BASE_MS * 2 ** (attempt - 1); - await new Promise((resolve) => setTimeout(resolve, delayMs)); - } - } - }, - }); - } catch (error) { - process.stderr.write("Failed\n"); - throw error; + for (let attempt = 1; attempt <= 3; attempt++) { + try { + await requestJson(httpDeps, requestOpts); + break; + } catch (error) { + if (attempt >= 3 || !canRetry(error)) { + process.stderr.write("Failed\n"); + throw error; + } + const delayMs = RETRY_DELAY_BASE_MS * 2 ** (attempt - 1); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } } process.stderr.write("Valid\n"); diff --git a/packages/commands/src/commands/text/chat.ts b/packages/commands/src/commands/text/chat.ts index 1107c75..85879d1 100644 --- a/packages/commands/src/commands/text/chat.ts +++ b/packages/commands/src/commands/text/chat.ts @@ -4,9 +4,6 @@ import { parseSSE, detectOutputFormat, readTextFromPathOrStdin, - applyChatEnableThinkingWithBudget, - resolveChatEnableThinking, - withEnableThinkingRetry, type ChatMessage, type ChatRequest, type ChatResponse, @@ -127,8 +124,7 @@ export default defineCommand({ const { system, messages } = parseMessages(flags); const model = flags.model || settings.defaultTextModel || "qwen3.7-max"; - // Coerce isTTY (may be undefined) so stream:false is serialized. - const shouldStream = Boolean(flags.stream || process.stdout.isTTY); + const shouldStream = flags.stream || process.stdout.isTTY; const format = detectOutputFormat(settings.output); // Build messages array with system prompt @@ -148,14 +144,12 @@ export default defineCommand({ if (flags.temperature !== undefined) body.temperature = flags.temperature; if (flags.topP !== undefined) body.top_p = flags.topP; - const enableThinking = resolveChatEnableThinking({ - enableThinking: flags.enableThinking, - stream: shouldStream, - }); - const applyThinking = (value: boolean | undefined) => { - applyChatEnableThinkingWithBudget(body, value, flags.thinkingBudget); - }; - applyThinking(enableThinking); + if (flags.enableThinking) { + body.enable_thinking = true; + if (flags.thinkingBudget !== undefined) { + body.thinking_budget = flags.thinkingBudget; + } + } if (flags.tool) { const tools = flags.tool.map((t) => { @@ -230,15 +224,10 @@ export default defineCommand({ resultOut.write("\n"); } } else { - const response = await withEnableThinkingRetry({ - initial: enableThinking, - apply: applyThinking, - run: () => - ctx.client.requestJson({ - path: chatPath(), - method: "POST", - body, - }), + const response = await ctx.client.requestJson({ + path: chatPath(), + method: "POST", + body, }); const text = response.choices?.[0]?.message?.content ?? ""; diff --git a/packages/commands/tests/e2e/auth.e2e.test.ts b/packages/commands/tests/e2e/auth.e2e.test.ts index 3a04b47..29fe132 100644 --- a/packages/commands/tests/e2e/auth.e2e.test.ts +++ b/packages/commands/tests/e2e/auth.e2e.test.ts @@ -219,7 +219,6 @@ describe("e2e: auth", () => { body: { model: "qwen3.7-max", stream: false, - enable_thinking: false, }, }); @@ -267,7 +266,7 @@ describe("e2e: auth", () => { expect(config["token-plan"]).toMatchObject({ api_key: "sk-sp-e2e-placeholder", base_url: validationServer.baseUrl, - default_text_model: "qwen3.8-max-preview", + default_text_model: "qwen3.7-plus", default_video_model: "happyhorse-1.1-t2v", default_image_to_video_model: "happyhorse-1.1-i2v", default_reference_to_video_model: "happyhorse-1.1-r2v", @@ -315,9 +314,8 @@ describe("e2e: auth", () => { authorization: "Bearer sk-sp-e2e-placeholder", sourceConfig: expect.any(String), body: { - model: "qwen3.8-max-preview", + model: "qwen3.7-plus", stream: false, - enable_thinking: false, }, }); @@ -330,7 +328,7 @@ describe("e2e: auth", () => { expect(config["token-plan"]).toMatchObject({ api_key: "sk-sp-e2e-placeholder", base_url: "https://token-plan.cn-beijing.maas.aliyuncs.com", - default_text_model: "qwen3.8-max-preview", + default_text_model: "qwen3.7-plus", default_video_model: "happyhorse-1.1-t2v", default_image_to_video_model: "happyhorse-1.1-i2v", default_reference_to_video_model: "happyhorse-1.1-r2v", diff --git a/packages/commands/tests/e2e/text-chat.e2e.test.ts b/packages/commands/tests/e2e/text-chat.e2e.test.ts index cbd7064..a5f4d5e 100644 --- a/packages/commands/tests/e2e/text-chat.e2e.test.ts +++ b/packages/commands/tests/e2e/text-chat.e2e.test.ts @@ -34,7 +34,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: text chat(DashScope)", () => { "--model", "qwen3.7-max", "--message", - "dry-run", + "干跑", "--max-tokens", "8", "--output", @@ -42,17 +42,10 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: text chat(DashScope)", () => { ]); expect(exitCode, stderr).toBe(0); const data = parseStdoutJson<{ - request?: { - model?: string; - messages?: Array<{ content?: string }>; - enable_thinking?: boolean; - stream?: boolean; - }; + request?: { model?: string; messages?: Array<{ content?: string }> }; }>(stdout); expect(data.request?.model).toBe("qwen3.7-max"); - expect(data.request?.messages?.some((message) => message.content === "dry-run")).toBe(true); - expect(data.request?.stream).toBe(false); - expect(data.request?.enable_thinking).toBe(false); + expect(data.request?.messages?.some((m) => m.content === "干跑")).toBe(true); }); test("【qwen3.7-max】文本对话", async () => { diff --git a/packages/commands/tests/e2e/vision-describe.e2e.test.ts b/packages/commands/tests/e2e/vision-describe.e2e.test.ts index a063911..6d92d3c 100644 --- a/packages/commands/tests/e2e/vision-describe.e2e.test.ts +++ b/packages/commands/tests/e2e/vision-describe.e2e.test.ts @@ -13,7 +13,7 @@ describe("e2e: vision describe", () => { "token-plan": { api_key: "sk-sp-e2e-placeholder", base_url: "https://token-plan.cn-beijing.maas.aliyuncs.com", - default_text_model: "qwen3.8-max-preview", + default_text_model: "qwen3.7-plus", }, }), ); @@ -40,6 +40,6 @@ describe("e2e: vision describe", () => { expect(exitCode, stderr).toBe(0); const data = parseStdoutJson<{ request?: { model?: string } }>(stdout); - expect(data.request?.model).toBe("qwen3.8-max-preview"); + expect(data.request?.model).toBe("qwen3.7-plus"); }); }); diff --git a/packages/core/src/config/profile-presets.ts b/packages/core/src/config/profile-presets.ts index f1d167c..94bf0fe 100644 --- a/packages/core/src/config/profile-presets.ts +++ b/packages/core/src/config/profile-presets.ts @@ -10,7 +10,7 @@ interface ModelProfilePreset { const MODEL_PROFILE_PRESETS: Readonly> = { "token-plan": { baseUrl: "https://token-plan.cn-beijing.maas.aliyuncs.com", - defaultTextModel: "qwen3.8-max-preview", + defaultTextModel: "qwen3.7-plus", defaultVideoModel: "happyhorse-1.1-t2v", defaultImageToVideoModel: "happyhorse-1.1-i2v", defaultReferenceToVideoModel: "happyhorse-1.1-r2v", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 10d0911..e49ee6c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -14,6 +14,5 @@ export * from "./finetune/index.ts"; export * from "./deploy/index.ts"; export * from "./types/index.ts"; export * from "./utils/index.ts"; -export * from "./models/index.ts"; export * from "./telemetry/index.ts"; export * from "./advisor/index.ts"; diff --git a/packages/core/src/models/index.ts b/packages/core/src/models/index.ts deleted file mode 100644 index 7fbcca2..0000000 --- a/packages/core/src/models/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { - adjustEnableThinkingAfterError, - applyChatEnableThinking, - applyChatEnableThinkingWithBudget, - resolveChatEnableThinking, - withEnableThinkingRetry, - type EnableThinkingAdjustResult, -} from "./thinking.ts"; diff --git a/packages/core/src/models/thinking.ts b/packages/core/src/models/thinking.ts deleted file mode 100644 index 2334b75..0000000 --- a/packages/core/src/models/thinking.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** resolve / adjust / retry helpers for chat `enable_thinking`. */ - -/** Resolve the initial `enable_thinking` value (`undefined` omits the field). */ -export function resolveChatEnableThinking(options: { - enableThinking?: boolean; - /** Whether the request is streaming. */ - stream?: boolean; -}): boolean | undefined { - if (options.enableThinking) return true; - if (options.stream === false) return false; - return undefined; -} - -export type EnableThinkingAdjustResult = - | { kind: "retry"; value: boolean | undefined } - | { kind: "none" }; - -/** Map clear `enable_thinking` constraint errors to a one-shot retry adjustment. */ -export function adjustEnableThinkingAfterError( - current: boolean | undefined, - errorMessage: string, -): EnableThinkingAdjustResult { - if (current !== true && /enable_thinking parameter is restricted to\s*true/i.test(errorMessage)) { - return { kind: "retry", value: true }; - } - - if (current === undefined && /enable_thinking must be set to false/i.test(errorMessage)) { - return { kind: "retry", value: false }; - } - - if (current !== undefined && /does not support enable_thinking/i.test(errorMessage)) { - return { kind: "retry", value: undefined }; - } - - return { kind: "none" }; -} - -/** Set or remove `enable_thinking`; clear `thinking_budget` when disabled or omitted. */ -export function applyChatEnableThinking( - body: { enable_thinking?: boolean; thinking_budget?: number }, - value: boolean | undefined, -): void { - if (value === undefined) { - delete body.enable_thinking; - delete body.thinking_budget; - return; - } - if (value === false) { - body.enable_thinking = false; - delete body.thinking_budget; - return; - } - body.enable_thinking = true; -} - -/** Set `enable_thinking` and optionally write `thinking_budget` when enabled. */ -export function applyChatEnableThinkingWithBudget( - body: { enable_thinking?: boolean; thinking_budget?: number }, - value: boolean | undefined, - thinkingBudget?: number, -): void { - applyChatEnableThinking(body, value); - if (value === true && thinkingBudget !== undefined) { - body.thinking_budget = thinkingBudget; - } -} - -function errorMessageOf(error: unknown): string { - if (error instanceof Error) return error.message; - return String(error); -} - -/** Run once, then retry once if the error indicates an `enable_thinking` constraint. */ -export async function withEnableThinkingRetry(options: { - initial: boolean | undefined; - apply: (value: boolean | undefined) => void; - run: () => Promise; -}): Promise { - options.apply(options.initial); - try { - return await options.run(); - } catch (error) { - const adjusted = adjustEnableThinkingAfterError(options.initial, errorMessageOf(error)); - if (adjusted.kind === "none") throw error; - options.apply(adjusted.value); - return await options.run(); - } -} diff --git a/packages/core/tests/thinking.test.ts b/packages/core/tests/thinking.test.ts deleted file mode 100644 index 9977f2a..0000000 --- a/packages/core/tests/thinking.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { expect, test } from "vite-plus/test"; -import { - adjustEnableThinkingAfterError, - applyChatEnableThinking, - applyChatEnableThinkingWithBudget, - resolveChatEnableThinking, - withEnableThinkingRetry, -} from "../src/models/thinking.ts"; - -test("resolveChatEnableThinking:显式开启为 true,非流式默认 false,流式默认 omit", () => { - expect(resolveChatEnableThinking({ enableThinking: true })).toBe(true); - expect(resolveChatEnableThinking({ enableThinking: true, stream: false })).toBe(true); - expect(resolveChatEnableThinking({ stream: false })).toBe(false); - expect(resolveChatEnableThinking({ enableThinking: false, stream: false })).toBe(false); - expect(resolveChatEnableThinking({ stream: true })).toBeUndefined(); - expect(resolveChatEnableThinking({})).toBeUndefined(); -}); - -test("adjustEnableThinkingAfterError:false/omit 被要求 true 时重试为 true", () => { - expect( - adjustEnableThinkingAfterError( - false, - "The value of the enable_thinking parameter is restricted to True.", - ), - ).toEqual({ kind: "retry", value: true }); - expect( - adjustEnableThinkingAfterError( - undefined, - "The value of the enable_thinking parameter is restricted to True.", - ), - ).toEqual({ kind: "retry", value: true }); -}); - -test("adjustEnableThinkingAfterError:omit 被要求 false 时重试为 false", () => { - expect( - adjustEnableThinkingAfterError( - undefined, - "parameter.enable_thinking must be set to false for non-streaming calls", - ), - ).toEqual({ kind: "retry", value: false }); -}); - -test("adjustEnableThinkingAfterError:不支持时去掉字段", () => { - expect( - adjustEnableThinkingAfterError(false, "The model qwen-turbo does not support enable_thinking."), - ).toEqual({ kind: "retry", value: undefined }); - expect( - adjustEnableThinkingAfterError(true, "The model qwen-turbo does not support enable_thinking."), - ).toEqual({ kind: "retry", value: undefined }); -}); - -test("adjustEnableThinkingAfterError:无关错误不调整", () => { - expect(adjustEnableThinkingAfterError(undefined, "Access denied")).toEqual({ kind: "none" }); - expect(adjustEnableThinkingAfterError(false, "Model not exist")).toEqual({ kind: "none" }); - expect( - adjustEnableThinkingAfterError( - true, - "The value of the enable_thinking parameter is restricted to True.", - ), - ).toEqual({ kind: "none" }); -}); - -test("applyChatEnableThinking:设置 / 删除字段,并在关闭时清 thinking_budget", () => { - const body: { enable_thinking?: boolean; thinking_budget?: number } = { - thinking_budget: 1024, - }; - applyChatEnableThinking(body, true); - expect(body.enable_thinking).toBe(true); - expect(body.thinking_budget).toBe(1024); - - applyChatEnableThinking(body, false); - expect(body.enable_thinking).toBe(false); - expect(body).not.toHaveProperty("thinking_budget"); - - body.thinking_budget = 2048; - applyChatEnableThinking(body, undefined); - expect(body).not.toHaveProperty("enable_thinking"); - expect(body).not.toHaveProperty("thinking_budget"); -}); - -test("withEnableThinkingRetry:restricted-to-true 时从 false 重试为 true", async () => { - const values: Array = []; - let calls = 0; - - const result = await withEnableThinkingRetry({ - initial: false, - apply: (value) => { - values.push(value); - }, - run: async () => { - calls += 1; - if (calls === 1) { - throw new Error("The value of the enable_thinking parameter is restricted to True."); - } - return "ok"; - }, - }); - - expect(result).toBe("ok"); - expect(calls).toBe(2); - expect(values).toEqual([false, true]); -}); - -test("withEnableThinkingRetry:must-be-false 时从 omit 重试为 false", async () => { - const values: Array = []; - let calls = 0; - - const result = await withEnableThinkingRetry({ - initial: undefined, - apply: (value) => { - values.push(value); - }, - run: async () => { - calls += 1; - if (calls === 1) { - throw new Error("parameter.enable_thinking must be set to false for non-streaming calls"); - } - return "ok"; - }, - }); - - expect(result).toBe("ok"); - expect(calls).toBe(2); - expect(values).toEqual([undefined, false]); -}); - -test("applyChatEnableThinkingWithBudget:仅在 enable_thinking=true 时写入 budget", () => { - const body: { enable_thinking?: boolean; thinking_budget?: number } = {}; - applyChatEnableThinkingWithBudget(body, false, 2048); - expect(body.enable_thinking).toBe(false); - expect(body).not.toHaveProperty("thinking_budget"); - - applyChatEnableThinkingWithBudget(body, undefined, 2048); - expect(body).not.toHaveProperty("enable_thinking"); - expect(body).not.toHaveProperty("thinking_budget"); - - applyChatEnableThinkingWithBudget(body, true, 2048); - expect(body.enable_thinking).toBe(true); - expect(body.thinking_budget).toBe(2048); -}); - -test("withEnableThinkingRetry:restricted-to-true 重试时保留 thinking_budget", async () => { - const body: { enable_thinking?: boolean; thinking_budget?: number } = {}; - let calls = 0; - - const result = await withEnableThinkingRetry({ - initial: false, - apply: (value) => applyChatEnableThinkingWithBudget(body, value, 2048), - run: async () => { - calls += 1; - if (calls === 1) { - throw new Error("The value of the enable_thinking parameter is restricted to True."); - } - return "ok"; - }, - }); - - expect(result).toBe("ok"); - expect(calls).toBe(2); - expect(body.enable_thinking).toBe(true); - expect(body.thinking_budget).toBe(2048); -}); - -test("withEnableThinkingRetry:无关错误原样抛出", async () => { - await expect( - withEnableThinkingRetry({ - initial: false, - apply: () => {}, - run: async () => { - throw new Error("Access denied"); - }, - }), - ).rejects.toThrow(/Access denied/); -});