fix(llm): finalize pending response tool calls
This commit is contained in:
parent
b95a5dc259
commit
8952cef65f
2 changed files with 38 additions and 7 deletions
|
|
@ -878,10 +878,15 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function*
|
|||
return [state, NO_EVENTS] satisfies StepResult
|
||||
})
|
||||
|
||||
const onResponseFinish = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||
const events: LLMEvent[] = []
|
||||
const onResponseFinish = Effect.fn("OpenAIResponses.onResponseFinish")(function* (
|
||||
state: ParserState,
|
||||
event: OpenAIResponsesEvent,
|
||||
) {
|
||||
const pending = yield* ToolStream.finishAll(ADAPTER, state.tools)
|
||||
const events = [...pending.events]
|
||||
const hasFunctionCall = state.hasFunctionCall || pending.events.some(LLMEvent.is.toolCall)
|
||||
const lifecycle = Lifecycle.finish(state.lifecycle, events, {
|
||||
reason: mapFinishReason(event, state.hasFunctionCall),
|
||||
reason: mapFinishReason(event, hasFunctionCall),
|
||||
usage: mapUsage(event.response?.usage),
|
||||
providerMetadata:
|
||||
event.response?.id || event.response?.service_tier
|
||||
|
|
@ -891,8 +896,8 @@ const onResponseFinish = (state: ParserState, event: OpenAIResponsesEvent): Step
|
|||
})
|
||||
: undefined,
|
||||
})
|
||||
return [{ ...state, lifecycle }, events]
|
||||
}
|
||||
return [{ ...state, tools: pending.tools, hasFunctionCall, lifecycle }, events] satisfies StepResult
|
||||
})
|
||||
|
||||
// Build a single human-readable message from whatever the provider supplied.
|
||||
// When both code and message are present, prefix the code so consumers see
|
||||
|
|
@ -939,8 +944,7 @@ const step = (state: ParserState, event: OpenAIResponsesEvent) => {
|
|||
if (event.type === "response.output_item.added") return Effect.succeed(onOutputItemAdded(state, event))
|
||||
if (event.type === "response.function_call_arguments.delta") return onFunctionCallArgumentsDelta(state, event)
|
||||
if (event.type === "response.output_item.done") return onOutputItemDone(state, event)
|
||||
if (event.type === "response.completed" || event.type === "response.incomplete")
|
||||
return Effect.succeed(onResponseFinish(state, event))
|
||||
if (event.type === "response.completed" || event.type === "response.incomplete") return onResponseFinish(state, event)
|
||||
if (event.type === "response.failed") return providerError(event, "OpenAI Responses response failed")
|
||||
if (event.type === "error") return providerError(event, "OpenAI Responses stream error")
|
||||
return Effect.succeed<StepResult>([state, NO_EVENTS])
|
||||
|
|
|
|||
|
|
@ -1259,6 +1259,33 @@ describe("OpenAI Responses route", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("finalizes an empty function call when the terminal response omits output_item.done", () =>
|
||||
Effect.gen(function* () {
|
||||
const body = sseEvents(
|
||||
{
|
||||
type: "response.output_item.added",
|
||||
item: { type: "function_call", id: "item_1", call_id: "call_1", name: "patch", arguments: "" },
|
||||
},
|
||||
{ type: "response.completed", response: { usage: { input_tokens: 5, output_tokens: 1 } } },
|
||||
)
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.updateRequest(request, {
|
||||
tools: [{ name: "patch", description: "Apply a patch", inputSchema: { type: "object" } }],
|
||||
}),
|
||||
).pipe(Effect.provide(fixedResponse(body)))
|
||||
|
||||
expect(response.events).toContainEqual({
|
||||
type: "tool-call",
|
||||
id: "call_1",
|
||||
name: "patch",
|
||||
input: {},
|
||||
providerExecuted: undefined,
|
||||
providerMetadata: { openai: { itemId: "item_1" } },
|
||||
})
|
||||
expect(response.finishReason).toBe("tool-calls")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("decodes web_search_call as provider-executed tool-call + tool-result", () =>
|
||||
Effect.gen(function* () {
|
||||
const item = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue