fix(ai): disable Kimi thinking for forced tools
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- Fixed Moonshot Kimi K2.6 forced tool calls to send `thinking: { type: "disabled" }`, avoiding `tool_choice 'specified' is incompatible with thinking enabled` 400s while preserving the requested named tool ([#1077](https://github.com/can1357/oh-my-pi/issues/1077)).
|
||||
|
||||
## [15.0.1] - 2026-05-14
|
||||
### Breaking Changes
|
||||
|
||||
|
||||
@@ -53,6 +53,12 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
||||
const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
|
||||
const isKilo = provider === "kilo" || baseUrl.includes("api.kilo.ai");
|
||||
const isKimiModel = model.id.includes("moonshotai/kimi") || /^kimi[-.]/i.test(model.id);
|
||||
const isMoonshotKimi =
|
||||
isKimiModel &&
|
||||
(provider === "moonshot" ||
|
||||
provider === "kimi-code" ||
|
||||
baseUrl.includes("api.moonshot.ai") ||
|
||||
baseUrl.includes("api.kimi.com"));
|
||||
const isAnthropicModel =
|
||||
provider === "anthropic" ||
|
||||
baseUrl.includes("api.anthropic.com") ||
|
||||
@@ -173,13 +179,14 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
||||
requiresAssistantAfterToolResult: false,
|
||||
requiresThinkingAsText: isMistral,
|
||||
requiresMistralToolIds: isMistral,
|
||||
thinkingFormat: isZai
|
||||
? "zai"
|
||||
: provider === "openrouter" || baseUrl.includes("openrouter.ai")
|
||||
? "openrouter"
|
||||
: isAlibaba || isQwen
|
||||
? "qwen"
|
||||
: "openai",
|
||||
thinkingFormat:
|
||||
isZai || isMoonshotKimi
|
||||
? "zai"
|
||||
: provider === "openrouter" || baseUrl.includes("openrouter.ai")
|
||||
? "openrouter"
|
||||
: isAlibaba || isQwen
|
||||
? "qwen"
|
||||
: "openai",
|
||||
reasoningContentField: "reasoning_content",
|
||||
// Backends that 400 follow-up requests when prior assistant tool-call turns lack `reasoning_content`:
|
||||
// - Kimi: documented invariant on its native API and via OpenCode-Go.
|
||||
|
||||
@@ -1019,12 +1019,14 @@ function buildParams(
|
||||
}
|
||||
|
||||
if (compat.disableReasoningOnForcedToolChoice && isForcedToolChoice(params.tool_choice)) {
|
||||
// Mirrors anthropic.ts:disableThinkingIfToolChoiceForced — backends like
|
||||
// Kimi 400 with `tool_choice 'specified' is incompatible with thinking
|
||||
// enabled`. Drop reasoning for this turn instead of dropping tool_choice;
|
||||
// the agent still gets the forced tool call, just without thinking.
|
||||
// Backends like Kimi 400 with `tool_choice 'specified' is incompatible
|
||||
// with thinking enabled`. Suppress thinking for this single forced-tool
|
||||
// turn while keeping the tool-selection contract intact.
|
||||
delete params.reasoning_effort;
|
||||
delete params.reasoning;
|
||||
if (compat.thinkingFormat === "zai") {
|
||||
params.thinking = { type: "disabled" };
|
||||
}
|
||||
}
|
||||
|
||||
// OpenRouter provider routing preferences
|
||||
|
||||
@@ -613,7 +613,7 @@ export interface OpenAICompat {
|
||||
requiresThinkingAsText?: boolean;
|
||||
/** Whether tool call IDs must be normalized to Mistral format (exactly 9 alphanumeric chars). Default: auto-detected from URL. */
|
||||
requiresMistralToolIds?: boolean;
|
||||
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "zai" uses thinking: { type: "enabled" }, "qwen" uses top-level enable_thinking, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */
|
||||
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "zai" uses thinking: { type: "enabled" | "disabled" } (also used by Moonshot Kimi), "qwen" uses top-level enable_thinking, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */
|
||||
thinkingFormat?: "openai" | "openrouter" | "zai" | "qwen" | "qwen-chat-template";
|
||||
/** Which reasoning content field to emit on assistant messages. Default: auto-detected. */
|
||||
reasoningContentField?: "reasoning_content" | "reasoning" | "reasoning_text";
|
||||
|
||||
@@ -79,6 +79,7 @@ interface CompletionsBody {
|
||||
tools?: unknown[];
|
||||
reasoning_effort?: unknown;
|
||||
reasoning?: unknown;
|
||||
thinking?: unknown;
|
||||
}
|
||||
|
||||
describe("issue #827 — kimi reasoning models drop reasoning under forced tool_choice", () => {
|
||||
@@ -114,6 +115,25 @@ describe("issue #827 — kimi reasoning models drop reasoning under forced tool_
|
||||
expect(body.reasoning).toBeUndefined();
|
||||
expect(body.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
it("sends explicit thinking disabled for Moonshot Kimi K2.6 when a named tool is forced", async () => {
|
||||
const model: Model<"openai-completions"> = {
|
||||
...getBundledModel("openai", "gpt-4o-mini"),
|
||||
api: "openai-completions",
|
||||
provider: "moonshot",
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
id: "kimi-k2.6",
|
||||
name: "Kimi K2.6",
|
||||
reasoning: false,
|
||||
};
|
||||
const body = (await captureBody(model, {
|
||||
toolChoice: { type: "tool", name: "echo" },
|
||||
})) as CompletionsBody;
|
||||
|
||||
expect(body.tool_choice).toMatchObject({ type: "function", function: { name: "echo" } });
|
||||
expect(body.thinking).toEqual({ type: "disabled" });
|
||||
expect(body.reasoning).toBeUndefined();
|
||||
expect(body.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it("strips reasoning_effort for Anthropic Claude models served via openai-completions (e.g. LiteLLM/OpenRouter proxies)", async () => {
|
||||
// LiteLLM / Vertex proxies often expose Claude through chat-completions; Anthropic
|
||||
|
||||
Reference in New Issue
Block a user