mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-30 14:42:13 +00:00
fix(ai): avoid truncated tool calls (#46040)
This commit is contained in:
parent
8e7190f795
commit
cf014bf2c1
2 changed files with 60 additions and 4 deletions
|
|
@ -1054,10 +1054,10 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||
events.push(...result.events)
|
||||
}
|
||||
|
||||
const contentFiltered = finishReason?.normalized === "content-filter"
|
||||
const incompleteTools = finishReason?.normalized === "content-filter" || finishReason?.normalized === "length"
|
||||
if (
|
||||
finishReason !== undefined &&
|
||||
!contentFiltered &&
|
||||
!incompleteTools &&
|
||||
state.finishReason === undefined &&
|
||||
Object.keys(pendingTools).length
|
||||
)
|
||||
|
|
@ -1067,10 +1067,10 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||
ProviderShared.encodeJson(event),
|
||||
)
|
||||
|
||||
// A content filter terminates the response without confirming pending tool calls.
|
||||
// Filtering or truncation terminates the response without confirming pending tool calls.
|
||||
const finished =
|
||||
finishReason !== undefined &&
|
||||
!contentFiltered &&
|
||||
!incompleteTools &&
|
||||
state.finishReason === undefined &&
|
||||
Object.keys(tools).length > 0
|
||||
? yield* ToolStream.finishAll(ADAPTER, tools)
|
||||
|
|
|
|||
|
|
@ -1516,6 +1516,62 @@ describe("OpenAI Chat route", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("does not finalize streamed tool calls when output is truncated", () =>
|
||||
Effect.gen(function* () {
|
||||
const body = sseEvents(
|
||||
deltaChunk({
|
||||
tool_calls: [{ index: 0, id: "call_1", function: { name: "lookup", arguments: '{"query":"weather"}' } }],
|
||||
}),
|
||||
deltaChunk({}, "length"),
|
||||
)
|
||||
const response = yield* LLMClient.generate(
|
||||
LLMRequest.update(request, {
|
||||
tools: [ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })],
|
||||
}),
|
||||
).pipe(Effect.provide(fixedResponse(body)))
|
||||
|
||||
expect(response.events).toEqual([
|
||||
{ type: "step-start", index: 0 },
|
||||
{
|
||||
type: "tool-input-start",
|
||||
id: "call_1",
|
||||
name: "lookup",
|
||||
providerExecuted: undefined,
|
||||
providerMetadata: undefined,
|
||||
},
|
||||
{
|
||||
type: "tool-input-delta",
|
||||
id: "call_1",
|
||||
name: "lookup",
|
||||
text: '{"query":"weather"}',
|
||||
input: { query: "weather" },
|
||||
},
|
||||
{
|
||||
type: "step-finish",
|
||||
index: 0,
|
||||
reason: { normalized: "length", raw: "length" },
|
||||
usage: undefined,
|
||||
providerMetadata: undefined,
|
||||
},
|
||||
{ type: "finish", reason: { normalized: "length", raw: "length" }, usage: undefined },
|
||||
])
|
||||
expect(response.toolCalls).toEqual([])
|
||||
|
||||
const missingIdentity = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(
|
||||
sseEvents(
|
||||
deltaChunk({ tool_calls: [{ index: 0, id: "call_2", function: { arguments: "{}" } }] }),
|
||||
deltaChunk({}, "length"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
expect(missingIdentity.finishReason).toEqual({ normalized: "length", raw: "length" })
|
||||
expect(missingIdentity.toolCalls).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("ignores empty identity fields on later tool call deltas", () =>
|
||||
Effect.gen(function* () {
|
||||
const body = sseEvents(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue