fix(ai): fail unknown Bedrock stream exceptions (#45004)

This commit is contained in:
Aiden Cline 2026-08-25 10:08:21 -05:00 committed by GitHub
parent b71291c05a
commit cce86ac166
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 32 additions and 18 deletions

View file

@ -212,11 +212,7 @@ const BedrockEvent = Schema.Struct({
metrics: Schema.optional(Schema.Unknown),
}),
),
internalServerException: Schema.optional(BedrockStreamException),
modelStreamErrorException: Schema.optional(BedrockStreamException),
validationException: Schema.optional(BedrockStreamException),
throttlingException: Schema.optional(BedrockStreamException),
serviceUnavailableException: Schema.optional(BedrockStreamException),
exception: Schema.optional(Schema.Struct({ type: Schema.String, details: BedrockStreamException })),
})
type BedrockEvent = Schema.Schema.Type<typeof BedrockEvent>
@ -650,22 +646,14 @@ const step = (state: ParserState, event: BedrockEvent) =>
] as const
}
const exception = (
[
["internalServerException", event.internalServerException],
["modelStreamErrorException", event.modelStreamErrorException],
["serviceUnavailableException", event.serviceUnavailableException],
["throttlingException", event.throttlingException],
["validationException", event.validationException],
] as const
).find((entry) => entry[1] !== undefined)
if (exception) {
if (event.exception) {
return yield* new AIError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({
message: exception[1]?.message ?? exception[1]?.originalMessage ?? "Bedrock Converse stream error",
code: exception[0],
message:
event.exception.details.message ?? event.exception.details.originalMessage ?? "Bedrock Converse stream error",
code: event.exception.type,
}),
})
}

View file

@ -82,7 +82,7 @@ const consumeFrames = (route: string) => (state: FrameBufferState, chunk: Uint8A
"Failed to parse Bedrock Converse event-stream payload",
)) as Record<string, unknown>
delete parsed.p
out.push({ [eventType]: parsed })
out.push(messageType === "exception" ? { exception: { type: eventType, details: parsed } } : { [eventType]: parsed })
}
return [cursor, out] as const
})

View file

@ -716,6 +716,32 @@ describe("Bedrock Converse route", () => {
}),
)
it.effect("ignores unknown normal stream events", () =>
Effect.gen(function* () {
const body = concat([
eventFrame("messageStart", { role: "assistant" }),
eventFrame("futureEvent", { message: "Ignore this" }),
eventFrame("messageStop", { stopReason: "end_turn" }),
])
const response = yield* LLMClient.generate(baseRequest).pipe(Effect.provide(fixedBytes(body)))
expect(response.finishReason).toEqual({ normalized: "stop", raw: "end_turn" })
}),
)
it.effect("fails unknown stream exceptions after message stop", () =>
Effect.gen(function* () {
const body = concat([
eventFrame("messageStart", { role: "assistant" }),
eventFrame("messageStop", { stopReason: "end_turn" }),
exceptionFrame("futureException", { message: "A future provider failure" }),
])
const error = yield* LLMClient.generate(baseRequest).pipe(Effect.provide(fixedBytes(body)), Effect.flip)
expect(error.reason).toMatchObject({ _tag: "UnknownProvider", message: "A future provider failure" })
}),
)
it.effect("classifies throttlingException as a rate limit", () =>
Effect.gen(function* () {
const body = concat([