diff --git a/packages/tools/src/openai/tools.test.ts b/packages/tools/src/openai/tools.test.ts new file mode 100644 index 000000000..387257c7a --- /dev/null +++ b/packages/tools/src/openai/tools.test.ts @@ -0,0 +1,58 @@ +import type OpenAI from "openai" +import { describe, expect, it } from "vitest" +import { createToolCallExecutor, createToolCallsExecutor } from "./tools" + +function toolCall( + name: string, + args: string, + id: string, +): OpenAI.Chat.Completions.ChatCompletionMessageToolCall { + return { + id, + type: "function", + function: { name, arguments: args }, + } +} + +describe("OpenAI tool call execution", () => { + it("returns a structured error for malformed JSON arguments", async () => { + const execute = createToolCallExecutor("test-api-key") + + const result = JSON.parse( + await execute(toolCall("searchMemories", '{"informationToGet":', "bad")), + ) + + expect(result).toEqual({ + success: false, + error: "Invalid JSON arguments for searchMemories", + }) + }) + + it("keeps malformed calls from rejecting an entire batch", async () => { + const execute = createToolCallsExecutor("test-api-key") + + const results = await execute([ + toolCall("searchMemories", "{broken", "bad"), + toolCall("unknownTool", "{}", "unknown"), + ]) + + expect(results).toEqual([ + { + tool_call_id: "bad", + role: "tool", + content: JSON.stringify({ + success: false, + error: "Invalid JSON arguments for searchMemories", + }), + }, + { + tool_call_id: "unknown", + role: "tool", + content: JSON.stringify({ + success: false, + error: "Unknown function: unknownTool", + }), + }, + ]) + }) +}) diff --git a/packages/tools/src/openai/tools.ts b/packages/tools/src/openai/tools.ts index 4695c9205..1ce23cb63 100644 --- a/packages/tools/src/openai/tools.ts +++ b/packages/tools/src/openai/tools.ts @@ -552,6 +552,14 @@ export function getToolDefinitions(): OpenAI.Chat.Completions.ChatCompletionTool ] } +function parseToolArguments(argumentsJson: string) { + try { + return { success: true as const, value: JSON.parse(argumentsJson) } + } catch { + return { success: false as const } + } +} + /** * Execute a tool call based on the function name and arguments */ @@ -565,7 +573,14 @@ export function createToolCallExecutor( toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall, ): Promise { const functionName = toolCall.function.name - const args = JSON.parse(toolCall.function.arguments) + const parsed = parseToolArguments(toolCall.function.arguments) + if (!parsed.success) { + return JSON.stringify({ + success: false, + error: `Invalid JSON arguments for ${functionName}`, + }) + } + const args = parsed.value switch (functionName) { case "searchMemories":