mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 09:20:22 +00:00
fix: default OpenAI reasoning effort to high
This commit is contained in:
@@ -534,6 +534,59 @@ describe("openai transport stream", () => {
|
||||
expect(params.input?.[0]).toMatchObject({ role: "developer" });
|
||||
});
|
||||
|
||||
it("defaults OpenAI Responses reasoning effort to high when unset", () => {
|
||||
const params = buildOpenAIResponsesParams(
|
||||
{
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
api: "openai-responses",
|
||||
provider: "openai",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"openai-responses">,
|
||||
{
|
||||
systemPrompt: "system",
|
||||
messages: [],
|
||||
tools: [],
|
||||
} as never,
|
||||
undefined,
|
||||
) as { reasoning?: unknown; include?: string[] };
|
||||
|
||||
expect(params.reasoning).toEqual({ effort: "high", summary: "auto" });
|
||||
expect(params.include).toEqual(["reasoning.encrypted_content"]);
|
||||
});
|
||||
|
||||
it("uses shared stream reasoning as OpenAI Responses effort", () => {
|
||||
const params = buildOpenAIResponsesParams(
|
||||
{
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
api: "openai-responses",
|
||||
provider: "openai",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"openai-responses">,
|
||||
{
|
||||
systemPrompt: "system",
|
||||
messages: [],
|
||||
tools: [],
|
||||
} as never,
|
||||
{
|
||||
reasoning: "high",
|
||||
} as never,
|
||||
) as { reasoning?: unknown };
|
||||
|
||||
expect(params.reasoning).toEqual({ effort: "high", summary: "auto" });
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: "openai",
|
||||
@@ -980,6 +1033,58 @@ describe("openai transport stream", () => {
|
||||
expect(params.messages?.[0]?.content).toBe("Stable prefix\nDynamic suffix");
|
||||
});
|
||||
|
||||
it("uses shared stream reasoning as OpenAI completions effort", () => {
|
||||
const params = buildOpenAICompletionsParams(
|
||||
{
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
api: "openai-completions",
|
||||
provider: "openai",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"openai-completions">,
|
||||
{
|
||||
systemPrompt: "system",
|
||||
messages: [],
|
||||
tools: [],
|
||||
} as never,
|
||||
{
|
||||
reasoning: "medium",
|
||||
} as never,
|
||||
) as { reasoning_effort?: unknown };
|
||||
|
||||
expect(params.reasoning_effort).toBe("medium");
|
||||
});
|
||||
|
||||
it("defaults OpenAI completions reasoning effort to high when unset", () => {
|
||||
const params = buildOpenAICompletionsParams(
|
||||
{
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
api: "openai-completions",
|
||||
provider: "openai",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"openai-completions">,
|
||||
{
|
||||
systemPrompt: "system",
|
||||
messages: [],
|
||||
tools: [],
|
||||
} as never,
|
||||
undefined,
|
||||
) as { reasoning_effort?: unknown };
|
||||
|
||||
expect(params.reasoning_effort).toBe("high");
|
||||
});
|
||||
|
||||
it("uses system role and streaming usage compat for native Qwen completions providers", () => {
|
||||
const params = buildOpenAICompletionsParams(
|
||||
{
|
||||
|
||||
@@ -40,6 +40,8 @@ import { mergeTransportMetadata, sanitizeTransportPayloadText } from "./transpor
|
||||
|
||||
const DEFAULT_AZURE_OPENAI_API_VERSION = "2024-12-01-preview";
|
||||
|
||||
type OpenAIReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
|
||||
type BaseStreamOptions = {
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
@@ -52,7 +54,8 @@ type BaseStreamOptions = {
|
||||
};
|
||||
|
||||
type OpenAIResponsesOptions = BaseStreamOptions & {
|
||||
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
reasoning?: OpenAIReasoningEffort;
|
||||
reasoningEffort?: OpenAIReasoningEffort;
|
||||
reasoningSummary?: "auto" | "detailed" | "concise" | null;
|
||||
serviceTier?: ResponseCreateParamsStreaming["service_tier"];
|
||||
};
|
||||
@@ -68,7 +71,8 @@ type OpenAICompletionsOptions = BaseStreamOptions & {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
reasoning?: OpenAIReasoningEffort;
|
||||
reasoningEffort?: OpenAIReasoningEffort;
|
||||
};
|
||||
|
||||
type OpenAIModeModel = Model<Api> & {
|
||||
@@ -726,6 +730,10 @@ function getPromptCacheRetention(
|
||||
return baseUrl?.includes("api.openai.com") ? "24h" : undefined;
|
||||
}
|
||||
|
||||
function resolveOpenAIReasoningEffort(options: OpenAIResponsesOptions | undefined) {
|
||||
return options?.reasoningEffort ?? options?.reasoning ?? "high";
|
||||
}
|
||||
|
||||
export function buildOpenAIResponsesParams(
|
||||
model: Model<Api>,
|
||||
context: Context,
|
||||
@@ -770,14 +778,15 @@ export function buildOpenAIResponsesParams(
|
||||
});
|
||||
}
|
||||
if (model.reasoning) {
|
||||
if (options?.reasoningEffort || options?.reasoningSummary) {
|
||||
if (options?.reasoningEffort || options?.reasoning || options?.reasoningSummary) {
|
||||
params.reasoning = {
|
||||
effort: options?.reasoningEffort || "medium",
|
||||
effort: resolveOpenAIReasoningEffort(options),
|
||||
summary: options?.reasoningSummary || "auto",
|
||||
};
|
||||
params.include = ["reasoning.encrypted_content"];
|
||||
} else if (model.provider !== "github-copilot") {
|
||||
params.reasoning = { effort: "none" };
|
||||
params.reasoning = { effort: "high", summary: "auto" };
|
||||
params.include = ["reasoning.encrypted_content"];
|
||||
}
|
||||
}
|
||||
applyOpenAIResponsesPayloadPolicy(params as Record<string, unknown>, payloadPolicy);
|
||||
@@ -1229,6 +1238,10 @@ function mapReasoningEffort(effort: string, reasoningEffortMap: Record<string, s
|
||||
return reasoningEffortMap[effort] ?? effort;
|
||||
}
|
||||
|
||||
function resolveOpenAICompletionsReasoningEffort(options: OpenAICompletionsOptions | undefined) {
|
||||
return options?.reasoningEffort ?? options?.reasoning ?? "high";
|
||||
}
|
||||
|
||||
function convertTools(
|
||||
tools: NonNullable<Context["tools"]>,
|
||||
compat: ReturnType<typeof getCompat>,
|
||||
@@ -1296,13 +1309,14 @@ export function buildOpenAICompletionsParams(
|
||||
if (options?.toolChoice) {
|
||||
params.tool_choice = options.toolChoice;
|
||||
}
|
||||
if (compat.thinkingFormat === "openrouter" && model.reasoning && options?.reasoningEffort) {
|
||||
const completionsReasoningEffort = resolveOpenAICompletionsReasoningEffort(options);
|
||||
if (compat.thinkingFormat === "openrouter" && model.reasoning && completionsReasoningEffort) {
|
||||
params.reasoning = {
|
||||
effort: mapReasoningEffort(options.reasoningEffort, compat.reasoningEffortMap),
|
||||
effort: mapReasoningEffort(completionsReasoningEffort, compat.reasoningEffortMap),
|
||||
};
|
||||
} else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
||||
} else if (completionsReasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
||||
params.reasoning_effort = mapReasoningEffort(
|
||||
options.reasoningEffort,
|
||||
completionsReasoningEffort,
|
||||
compat.reasoningEffortMap,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type WsOptions = Parameters<StreamFn>[2] & {
|
||||
toolChoice?: unknown;
|
||||
textVerbosity?: string;
|
||||
text_verbosity?: string;
|
||||
reasoning?: string;
|
||||
reasoningEffort?: string;
|
||||
reasoningSummary?: string;
|
||||
};
|
||||
@@ -69,15 +70,16 @@ export function buildOpenAIWebSocketResponseCreatePayload(params: {
|
||||
extraParams.tool_choice = streamOpts.toolChoice;
|
||||
}
|
||||
|
||||
if (
|
||||
streamOpts?.reasoningEffort !== "none" &&
|
||||
(streamOpts?.reasoningEffort || streamOpts?.reasoningSummary)
|
||||
) {
|
||||
const reasoningEffort =
|
||||
streamOpts?.reasoningEffort ??
|
||||
streamOpts?.reasoning ??
|
||||
(params.model.reasoning ? "high" : undefined);
|
||||
if (reasoningEffort !== "none" && (reasoningEffort || streamOpts?.reasoningSummary)) {
|
||||
const reasoning: { effort?: string; summary?: string } = {};
|
||||
if (streamOpts.reasoningEffort !== undefined) {
|
||||
reasoning.effort = streamOpts.reasoningEffort;
|
||||
if (reasoningEffort !== undefined) {
|
||||
reasoning.effort = reasoningEffort;
|
||||
}
|
||||
if (streamOpts.reasoningSummary !== undefined) {
|
||||
if (streamOpts?.reasoningSummary !== undefined) {
|
||||
reasoning.summary = streamOpts.reasoningSummary;
|
||||
}
|
||||
extraParams.reasoning = reasoning;
|
||||
|
||||
@@ -3044,6 +3044,65 @@ describe("createOpenAIWebSocketStreamFn", () => {
|
||||
expect(sent.reasoning).toEqual({ effort: "high", summary: "auto" });
|
||||
});
|
||||
|
||||
it("defaults response.create reasoning effort to high for reasoning models", async () => {
|
||||
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-reason-default");
|
||||
const stream = streamFn(
|
||||
{ ...modelStub, reasoning: true } as Parameters<typeof streamFn>[0],
|
||||
contextStub as Parameters<typeof streamFn>[1],
|
||||
undefined,
|
||||
);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
await new Promise((r) => setImmediate(r));
|
||||
MockManager.lastInstance!.simulateEvent({
|
||||
type: "response.completed",
|
||||
response: makeResponseObject("resp-reason-default", "Default thought"),
|
||||
});
|
||||
for await (const _ of await resolveStream(stream)) {
|
||||
/* consume */
|
||||
}
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
|
||||
expect(sent.type).toBe("response.create");
|
||||
expect(sent.reasoning).toEqual({ effort: "high" });
|
||||
});
|
||||
|
||||
it("forwards shared reasoning to response.create reasoning effort", async () => {
|
||||
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-reason-shared");
|
||||
const opts = { reasoning: "medium" };
|
||||
const stream = streamFn(
|
||||
modelStub as Parameters<typeof streamFn>[0],
|
||||
contextStub as Parameters<typeof streamFn>[1],
|
||||
opts as unknown as Parameters<typeof streamFn>[2],
|
||||
);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
await new Promise((r) => setImmediate(r));
|
||||
MockManager.lastInstance!.simulateEvent({
|
||||
type: "response.completed",
|
||||
response: makeResponseObject("resp-reason-shared", "Shared thought"),
|
||||
});
|
||||
for await (const _ of await resolveStream(stream)) {
|
||||
/* consume */
|
||||
}
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
|
||||
expect(sent.type).toBe("response.create");
|
||||
expect(sent.reasoning).toEqual({ effort: "medium" });
|
||||
});
|
||||
|
||||
it("omits response.create reasoning when reasoningEffort is none", async () => {
|
||||
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-reason-none");
|
||||
const opts = { reasoningEffort: "none" };
|
||||
|
||||
Reference in New Issue
Block a user