Skip to content

Commit f63cd12

Browse files
allquixoticclaude
andcommitted
fix(bedrock): recover when model rejects toolSpec.strict as unknown field
Opus 4.8 on Bedrock fails hard with "tools.0.custom.strict: Extra inputs are not permitted": Bedrock forwards toolSpec.strict into the Anthropic payload and the model rejects it as an extra input instead of Bedrock returning a "does not support strict" error. That wording matched no STRUCTURED_OUTPUT_UNSUPPORTED pattern, so the error classified as GENERIC and the strip-strict retry never fired — every request failed. Add "custom.strict" and "strict: extra inputs are not permitted" to the detection patterns so the existing recovery path (mark model unsupported for 30 days, retry once without strict) handles this rejection shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6566073 commit f63cd12

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

src/api/providers/__tests__/bedrock-structured-output.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,37 @@ describe("AwsBedrockHandler structured output", () => {
250250
expect(noticeChunks.length).toBeGreaterThan(0)
251251
})
252252

253+
it("recovers when the model itself rejects strict as an unknown field (Opus 4.8 shape)", async () => {
254+
// Bedrock forwards toolSpec.strict into the Anthropic payload; models that don't
255+
// know the field fail with a pydantic-style "Extra inputs are not permitted"
256+
// instead of a Bedrock-side "does not support strict" message.
257+
const handler = buildHandler({ apiModelId: "us.anthropic.claude-opus-4-8-20251101-v1:0" } as any)
258+
const markUnsupported = vi.fn()
259+
const metadata: ApiHandlerCreateMessageMetadata = {
260+
taskId: "t1",
261+
tools: sampleTools,
262+
isModelStructuredOutputUnsupported: () => false,
263+
markModelStructuredOutputUnsupported: markUnsupported,
264+
}
265+
266+
mockSend.mockReset()
267+
mockSend.mockRejectedValueOnce(
268+
makeValidationError(
269+
"The model returned the following errors: tools.0.custom.strict: Extra inputs are not permitted",
270+
),
271+
)
272+
mockSend.mockResolvedValueOnce({ stream: [] })
273+
274+
await drain(handler.createMessage("sys", [{ role: "user", content: "hi" }], metadata))
275+
276+
expect(mockSend).toHaveBeenCalledTimes(2)
277+
expect(markUnsupported).toHaveBeenCalledWith("us.anthropic.claude-opus-4-8-20251101-v1:0")
278+
const firstPayload = mockConverseStreamCommand.mock.calls[0][0] as any
279+
const secondPayload = mockConverseStreamCommand.mock.calls[1][0] as any
280+
expect(firstPayload.toolConfig.tools[0].toolSpec.strict).toBe(true)
281+
expect(secondPayload.toolConfig.tools[0].toolSpec.strict).toBeUndefined()
282+
})
283+
253284
it("does not mark model unsupported when strict was already disabled", async () => {
254285
const handler = buildHandler({ awsBedrockStructuredOutput: false } as any)
255286
const markUnsupported = vi.fn()
@@ -312,6 +343,35 @@ describe("AwsBedrockHandler structured output", () => {
312343
)
313344
})
314345

346+
it("classifies model-side 'extra inputs' strict rejections as STRUCTURED_OUTPUT_UNSUPPORTED", () => {
347+
const handler = buildHandler()
348+
const getErrorType = (handler as any).getErrorType.bind(handler)
349+
expect(
350+
getErrorType(
351+
makeValidationError(
352+
"The model returned the following errors: tools.0.custom.strict: Extra inputs are not permitted",
353+
400,
354+
),
355+
),
356+
).toBe("STRUCTURED_OUTPUT_UNSUPPORTED")
357+
// Any tool index and alternate path shapes must also match.
358+
expect(
359+
getErrorType(makeValidationError("tools.3.custom.strict: Extra inputs are not permitted", 400)),
360+
).toBe("STRUCTURED_OUTPUT_UNSUPPORTED")
361+
expect(getErrorType(makeValidationError("tools.0.strict: Extra inputs are not permitted", 400))).toBe(
362+
"STRUCTURED_OUTPUT_UNSUPPORTED",
363+
)
364+
})
365+
366+
it("does not classify unrelated 'extra inputs' rejections as structured-output", () => {
367+
const handler = buildHandler()
368+
const getErrorType = (handler as any).getErrorType.bind(handler)
369+
const result = getErrorType(
370+
makeValidationError("output_config.effort: Extra inputs are not permitted", 400),
371+
)
372+
expect(result).not.toBe("STRUCTURED_OUTPUT_UNSUPPORTED")
373+
})
374+
315375
it("classifies compiling messages as STRUCTURED_OUTPUT_COMPILING", () => {
316376
const handler = buildHandler()
317377
const getErrorType = (handler as any).getErrorType.bind(handler)

src/api/providers/bedrock.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,6 +1758,11 @@ Please check:
17581758
"strict tool",
17591759
"strict mode is not supported",
17601760
"strict: true",
1761+
// Anthropic-side rejection (seen with Opus 4.8): Bedrock forwards toolSpec.strict
1762+
// into the model payload as tools.N.custom.strict and the model rejects it as an
1763+
// unknown field ("Extra inputs are not permitted") instead of a Bedrock-side error.
1764+
"custom.strict",
1765+
"strict: extra inputs are not permitted",
17611766
"textformat is not supported",
17621767
"output_config is not supported",
17631768
"outputconfig.textformat",

0 commit comments

Comments
 (0)