fix(ai): handle incomplete responses without reasons (#38374)
This commit is contained in:
parent
d86f732df3
commit
48bcbd09ef
2 changed files with 29 additions and 2 deletions
|
|
@ -253,7 +253,7 @@ const OpenAIResponsesEvent = Schema.Struct({
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
id: Schema.optional(Schema.String),
|
id: Schema.optional(Schema.String),
|
||||||
service_tier: optionalNull(Schema.String),
|
service_tier: optionalNull(Schema.String),
|
||||||
incomplete_details: optionalNull(Schema.Struct({ reason: Schema.String })),
|
incomplete_details: optionalNull(Schema.Struct({ reason: Schema.optional(Schema.String) })),
|
||||||
usage: optionalNull(OpenAIResponsesUsage),
|
usage: optionalNull(OpenAIResponsesUsage),
|
||||||
error: optionalNull(OpenAIResponsesErrorPayload),
|
error: optionalNull(OpenAIResponsesErrorPayload),
|
||||||
}),
|
}),
|
||||||
|
|
@ -602,7 +602,8 @@ const mapUsage = (usage: OpenAIResponsesUsage | null | undefined) => {
|
||||||
|
|
||||||
const mapFinishReason = (event: OpenAIResponsesEvent, hasFunctionCall: boolean): FinishReason => {
|
const mapFinishReason = (event: OpenAIResponsesEvent, hasFunctionCall: boolean): FinishReason => {
|
||||||
const reason = event.response?.incomplete_details?.reason
|
const reason = event.response?.incomplete_details?.reason
|
||||||
if (reason === undefined || reason === null) return hasFunctionCall ? "tool-calls" : "stop"
|
if (reason === undefined || reason === null)
|
||||||
|
return hasFunctionCall ? "tool-calls" : event.type === "response.incomplete" ? "unknown" : "stop"
|
||||||
if (reason === "max_output_tokens") return "length"
|
if (reason === "max_output_tokens") return "length"
|
||||||
if (reason === "content_filter") return "content-filter"
|
if (reason === "content_filter") return "content-filter"
|
||||||
return hasFunctionCall ? "tool-calls" : "unknown"
|
return hasFunctionCall ? "tool-calls" : "unknown"
|
||||||
|
|
|
||||||
|
|
@ -870,6 +870,32 @@ describe("OpenAI Responses route", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("maps incomplete response reasons", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const generate = (incompleteDetails: object) =>
|
||||||
|
LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents({
|
||||||
|
type: "response.incomplete",
|
||||||
|
response: { id: "resp_incomplete", incomplete_details: incompleteDetails },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const length = yield* generate({ reason: "max_output_tokens" })
|
||||||
|
const contentFilter = yield* generate({ reason: "content_filter" })
|
||||||
|
const unknown = yield* generate({})
|
||||||
|
|
||||||
|
expect([length.finishReason, contentFilter.finishReason, unknown.finishReason]).toEqual([
|
||||||
|
"length",
|
||||||
|
"content-filter",
|
||||||
|
"unknown",
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
// OpenAI's documented stream orders output text within one message item; no
|
// OpenAI's documented stream orders output text within one message item; no
|
||||||
// provider-valid same-kind overlap is evidenced, so done boundaries close it.
|
// provider-valid same-kind overlap is evidenced, so done boundaries close it.
|
||||||
it.effect("closes sequential output messages before starting the next", () =>
|
it.effect("closes sequential output messages before starting the next", () =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue