mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:40:42 +00:00
fix: default kimi thinking to off (#68907)
Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
This commit is contained in:
23
extensions/kimi-coding/index.test.ts
Normal file
23
extensions/kimi-coding/index.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
|
||||
import plugin from "./index.js";
|
||||
|
||||
describe("kimi provider plugin", () => {
|
||||
it("uses binary thinking with thinking off by default", async () => {
|
||||
const provider = await registerSingleProviderPlugin(plugin);
|
||||
|
||||
expect(
|
||||
provider.isBinaryThinking?.({
|
||||
provider: "kimi",
|
||||
modelId: "kimi-code",
|
||||
} as never),
|
||||
).toBe(true);
|
||||
expect(
|
||||
provider.resolveDefaultThinkingLevel?.({
|
||||
provider: "kimi",
|
||||
modelId: "kimi-code",
|
||||
reasoning: true,
|
||||
} as never),
|
||||
).toBe("off");
|
||||
});
|
||||
});
|
||||
@@ -96,6 +96,8 @@ export default definePluginEntry({
|
||||
},
|
||||
},
|
||||
buildReplayPolicy: () => KIMI_REPLAY_POLICY,
|
||||
isBinaryThinking: () => true,
|
||||
resolveDefaultThinkingLevel: () => "off",
|
||||
wrapStreamFn: wrapKimiProviderStream,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import type { Context, Model } from "@mariozechner/pi-ai";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createKimiToolCallMarkupWrapper, wrapKimiProviderStream } from "./stream.js";
|
||||
import {
|
||||
createKimiThinkingWrapper,
|
||||
createKimiToolCallMarkupWrapper,
|
||||
resolveKimiThinkingType,
|
||||
wrapKimiProviderStream,
|
||||
} from "./stream.js";
|
||||
|
||||
type FakeStream = {
|
||||
result: () => Promise<unknown>;
|
||||
@@ -29,6 +34,19 @@ const KIMI_MULTI_TOOL_TEXT =
|
||||
' <|tool_calls_section_begin|> <|tool_call_begin|> functions.read:0 <|tool_call_argument_begin|> {"file_path":"./package.json"} <|tool_call_end|> <|tool_call_begin|> functions.write:1 <|tool_call_argument_begin|> {"file_path":"./out.txt","content":"done"} <|tool_call_end|> <|tool_calls_section_end|>';
|
||||
|
||||
describe("kimi tool-call markup wrapper", () => {
|
||||
it("defaults Kimi thinking to disabled unless explicitly enabled", () => {
|
||||
expect(resolveKimiThinkingType({ configuredThinking: undefined })).toBe("disabled");
|
||||
expect(resolveKimiThinkingType({ configuredThinking: undefined, thinkingLevel: "high" })).toBe(
|
||||
"enabled",
|
||||
);
|
||||
expect(resolveKimiThinkingType({ configuredThinking: "off", thinkingLevel: "high" })).toBe(
|
||||
"disabled",
|
||||
);
|
||||
expect(resolveKimiThinkingType({ configuredThinking: "enabled", thinkingLevel: "off" })).toBe(
|
||||
"enabled",
|
||||
);
|
||||
});
|
||||
|
||||
it("converts tagged Kimi tool-call text into structured tool calls", async () => {
|
||||
const partial = {
|
||||
role: "assistant",
|
||||
@@ -243,4 +261,105 @@ describe("kimi tool-call markup wrapper", () => {
|
||||
stopReason: "toolUse",
|
||||
});
|
||||
});
|
||||
|
||||
it("forces Kimi thinking disabled and strips proxy reasoning fields", () => {
|
||||
let capturedPayload: Record<string, unknown> | undefined;
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {
|
||||
reasoning: { effort: "high" },
|
||||
reasoning_effort: "high",
|
||||
reasoningEffort: "high",
|
||||
};
|
||||
options?.onPayload?.(payload as never, model as never);
|
||||
capturedPayload = payload;
|
||||
return createFakeStream({
|
||||
events: [],
|
||||
resultMessage: { role: "assistant", content: [] },
|
||||
}) as never;
|
||||
};
|
||||
|
||||
const wrapped = createKimiThinkingWrapper(baseStreamFn, "disabled");
|
||||
void wrapped(
|
||||
{
|
||||
api: "anthropic-messages",
|
||||
provider: "kimi",
|
||||
id: "kimi-code",
|
||||
} as Model<"anthropic-messages">,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(capturedPayload).toEqual({
|
||||
thinking: { type: "disabled" },
|
||||
});
|
||||
});
|
||||
|
||||
it("lets explicit model params keep Kimi thinking disabled even when session thinking is on", () => {
|
||||
let capturedPayload: Record<string, unknown> | undefined;
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
options?.onPayload?.(payload as never, model as never);
|
||||
capturedPayload = payload;
|
||||
return createFakeStream({
|
||||
events: [],
|
||||
resultMessage: { role: "assistant", content: [] },
|
||||
}) as never;
|
||||
};
|
||||
|
||||
const wrapped = wrapKimiProviderStream({
|
||||
provider: "kimi",
|
||||
modelId: "kimi-code",
|
||||
extraParams: { thinking: "off" },
|
||||
thinkingLevel: "high",
|
||||
streamFn: baseStreamFn,
|
||||
} as never);
|
||||
|
||||
void wrapped(
|
||||
{
|
||||
api: "anthropic-messages",
|
||||
provider: "kimi",
|
||||
id: "kimi-code",
|
||||
} as Model<"anthropic-messages">,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(capturedPayload).toEqual({
|
||||
thinking: { type: "disabled" },
|
||||
});
|
||||
});
|
||||
|
||||
it("enables Kimi thinking only when explicitly requested", () => {
|
||||
let capturedPayload: Record<string, unknown> | undefined;
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
options?.onPayload?.(payload as never, model as never);
|
||||
capturedPayload = payload;
|
||||
return createFakeStream({
|
||||
events: [],
|
||||
resultMessage: { role: "assistant", content: [] },
|
||||
}) as never;
|
||||
};
|
||||
|
||||
const wrapped = wrapKimiProviderStream({
|
||||
provider: "kimi",
|
||||
modelId: "kimi-code",
|
||||
thinkingLevel: "high",
|
||||
streamFn: baseStreamFn,
|
||||
} as never);
|
||||
|
||||
void wrapped(
|
||||
{
|
||||
api: "anthropic-messages",
|
||||
provider: "kimi",
|
||||
id: "kimi-code",
|
||||
} as Model<"anthropic-messages">,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(capturedPayload).toEqual({
|
||||
thinking: { type: "enabled" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import { streamSimple } from "@mariozechner/pi-ai";
|
||||
import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { streamWithPayloadPatch } from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const TOOL_CALLS_SECTION_BEGIN = "<|tool_calls_section_begin|>";
|
||||
const TOOL_CALLS_SECTION_END = "<|tool_calls_section_end|>";
|
||||
@@ -15,6 +17,46 @@ type KimiToolCallBlock = {
|
||||
arguments: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type KimiThinkingType = "enabled" | "disabled";
|
||||
type KimiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
|
||||
|
||||
function normalizeKimiThinkingType(value: unknown): KimiThinkingType | undefined {
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "enabled" : "disabled";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const normalized = normalizeOptionalLowercaseString(value);
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
if (["enabled", "enable", "on", "true"].includes(normalized)) {
|
||||
return "enabled";
|
||||
}
|
||||
if (["disabled", "disable", "off", "false"].includes(normalized)) {
|
||||
return "disabled";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
return normalizeKimiThinkingType((value as Record<string, unknown>).type);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveKimiThinkingType(params: {
|
||||
configuredThinking: unknown;
|
||||
thinkingLevel?: KimiThinkingLevel;
|
||||
}): KimiThinkingType {
|
||||
const configured = normalizeKimiThinkingType(params.configuredThinking);
|
||||
if (configured) {
|
||||
return configured;
|
||||
}
|
||||
if (!params.thinkingLevel || params.thinkingLevel === "off") {
|
||||
return "disabled";
|
||||
}
|
||||
return "enabled";
|
||||
}
|
||||
|
||||
function stripTaggedToolCallCounter(value: string): string {
|
||||
return value.trim().replace(/:\d+$/, "");
|
||||
}
|
||||
@@ -181,6 +223,24 @@ export function createKimiToolCallMarkupWrapper(baseStreamFn: StreamFn | undefin
|
||||
};
|
||||
}
|
||||
|
||||
export function wrapKimiProviderStream(ctx: ProviderWrapStreamFnContext): StreamFn {
|
||||
return createKimiToolCallMarkupWrapper(ctx.streamFn);
|
||||
export function createKimiThinkingWrapper(
|
||||
baseStreamFn: StreamFn | undefined,
|
||||
thinkingType: KimiThinkingType,
|
||||
): StreamFn {
|
||||
const underlying = baseStreamFn ?? streamSimple;
|
||||
return (model, context, options) =>
|
||||
streamWithPayloadPatch(underlying, model, context, options, (payloadObj) => {
|
||||
payloadObj.thinking = { type: thinkingType };
|
||||
delete payloadObj.reasoning;
|
||||
delete payloadObj.reasoning_effort;
|
||||
delete payloadObj.reasoningEffort;
|
||||
});
|
||||
}
|
||||
|
||||
export function wrapKimiProviderStream(ctx: ProviderWrapStreamFnContext): StreamFn {
|
||||
const thinkingType = resolveKimiThinkingType({
|
||||
configuredThinking: ctx.extraParams?.thinking,
|
||||
thinkingLevel: ctx.thinkingLevel,
|
||||
});
|
||||
return createKimiToolCallMarkupWrapper(createKimiThinkingWrapper(ctx.streamFn, thinkingType));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user