mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(ai): send reasoning effort to OpenAI models on Bedrock Converse (#10142)
buildAdditionalModelRequestFields only handled Claude, so the configured thinking level never reached OpenAI models on Bedrock and they always ran at Bedrock's default effort. gpt-oss now gets the flat reasoning_effort, clamped to low/medium/high. Other GPT models (GPT-5.x, GPT-6) get nested reasoning.effort, with minimal sent as low since Bedrock rejects it. closes #9331
This commit is contained in:
parent
311f0e020d
commit
2989eb581f
2 changed files with 98 additions and 0 deletions
|
|
@ -1308,9 +1308,45 @@ function buildAdditionalModelRequestFields(
|
|||
return result;
|
||||
}
|
||||
|
||||
const candidates = getModelMatchCandidates(model.id, model.name);
|
||||
|
||||
if (candidates.some((s) => s.includes("gpt-oss"))) {
|
||||
return { reasoning_effort: OPENAI_GPT_OSS_EFFORT[options.reasoning] };
|
||||
}
|
||||
|
||||
if (candidates.some((s) => s.includes("gpt-"))) {
|
||||
const mapped = model.thinkingLevelMap?.[options.reasoning];
|
||||
return {
|
||||
reasoning: { effort: typeof mapped === "string" ? mapped : OPENAI_GPT_EFFORT[options.reasoning] },
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
type OpenAIGptEffort = "low" | "medium" | "high" | "xhigh" | "max";
|
||||
type OpenAIGptOssEffort = "low" | "medium" | "high";
|
||||
|
||||
/** OpenAI GPT models (GPT-5.x, GPT-6) take a nested `reasoning.effort` and reject `minimal`. */
|
||||
const OPENAI_GPT_EFFORT: Record<ThinkingLevel, OpenAIGptEffort> = {
|
||||
minimal: "low",
|
||||
low: "low",
|
||||
medium: "medium",
|
||||
high: "high",
|
||||
xhigh: "xhigh",
|
||||
max: "max",
|
||||
};
|
||||
|
||||
/** gpt-oss takes a flat `reasoning_effort` and only accepts low, medium and high. */
|
||||
const OPENAI_GPT_OSS_EFFORT: Record<ThinkingLevel, OpenAIGptOssEffort> = {
|
||||
minimal: "low",
|
||||
low: "low",
|
||||
medium: "medium",
|
||||
high: "high",
|
||||
xhigh: "high",
|
||||
max: "high",
|
||||
};
|
||||
|
||||
function createImageBlock(mimeType: string, data: string) {
|
||||
let format: ImageFormat;
|
||||
switch (mimeType) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ interface BedrockThinkingPayload {
|
|||
};
|
||||
output_config?: { effort?: string };
|
||||
anthropic_beta?: string[];
|
||||
reasoning?: { effort?: string };
|
||||
reasoning_effort?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -197,6 +199,66 @@ describe("Bedrock thinking payload", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("Bedrock OpenAI reasoning payload", () => {
|
||||
// Regression for #9331: the configured thinking level never reached OpenAI models on Bedrock.
|
||||
it.each([
|
||||
["minimal", "low"],
|
||||
["low", "low"],
|
||||
["medium", "medium"],
|
||||
["high", "high"],
|
||||
["xhigh", "xhigh"],
|
||||
["max", "max"],
|
||||
] as const)("sends reasoning=%s as reasoning.effort=%s for GPT-6 and GPT-5.6", async (reasoning, effort) => {
|
||||
for (const id of ["global.openai.gpt-6-sol", "us.openai.gpt-6-luna", "global.openai.gpt-5.6-sol"] as const) {
|
||||
const payload = await capturePayload(getModel("amazon-bedrock", id), { reasoning });
|
||||
|
||||
expect(payload.additionalModelRequestFields, id).toEqual({ reasoning: { effort } });
|
||||
}
|
||||
});
|
||||
|
||||
it("sends reasoning.effort when only model.name identifies a GPT model", async () => {
|
||||
const model: Model<"bedrock-converse-stream"> = {
|
||||
...getModel("amazon-bedrock", "global.openai.gpt-6-sol"),
|
||||
id: "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/my-profile",
|
||||
name: "GPT-6 Sol",
|
||||
};
|
||||
|
||||
const payload = await capturePayload(model, { reasoning: "medium" });
|
||||
|
||||
expect(payload.additionalModelRequestFields).toEqual({ reasoning: { effort: "medium" } });
|
||||
});
|
||||
|
||||
it("sends flat reasoning_effort for gpt-oss, clamped to high", async () => {
|
||||
const model = getModel("amazon-bedrock", "openai.gpt-oss-120b-1:0");
|
||||
|
||||
expect((await capturePayload(model, { reasoning: "minimal" })).additionalModelRequestFields).toEqual({
|
||||
reasoning_effort: "low",
|
||||
});
|
||||
expect((await capturePayload(model, { reasoning: "medium" })).additionalModelRequestFields).toEqual({
|
||||
reasoning_effort: "medium",
|
||||
});
|
||||
expect((await capturePayload(model, { reasoning: "xhigh" })).additionalModelRequestFields).toEqual({
|
||||
reasoning_effort: "high",
|
||||
});
|
||||
});
|
||||
|
||||
it("sends no reasoning fields when reasoning is off", async () => {
|
||||
let captured: BedrockThinkingPayload | undefined;
|
||||
const s = streamBedrock(getModel("amazon-bedrock", "global.openai.gpt-6-sol"), normalizeContext(makeContext()), {
|
||||
onPayload: (payload) => {
|
||||
captured = payload as BedrockThinkingPayload;
|
||||
throw new PayloadCaptured();
|
||||
},
|
||||
});
|
||||
for await (const event of s) {
|
||||
if (event.type === "error") break;
|
||||
}
|
||||
|
||||
expect(captured).toBeDefined();
|
||||
expect(captured?.additionalModelRequestFields).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!hasBedrockCredentials())("Bedrock Claude max tokens E2E", () => {
|
||||
it(
|
||||
"uses the model maxTokens cap instead of Bedrock's 4096-token default for adaptive Claude models",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue