diff --git a/.changeset/rotten-peaches-doubt.md b/.changeset/rotten-peaches-doubt.md new file mode 100644 index 000000000000..5ce51b6db75d --- /dev/null +++ b/.changeset/rotten-peaches-doubt.md @@ -0,0 +1,5 @@ +--- +'@ai-sdk/provider': major +--- + +chore: refactor tool call and tool call delta parts (spec) diff --git a/examples/ai-core/src/generate-text/mock-tool-call-repair-reask.ts b/examples/ai-core/src/generate-text/mock-tool-call-repair-reask.ts index 53ce6c3fbf54..864b7dea0bea 100644 --- a/examples/ai-core/src/generate-text/mock-tool-call-repair-reask.ts +++ b/examples/ai-core/src/generate-text/mock-tool-call-repair-reask.ts @@ -12,6 +12,7 @@ async function main() { finishReason: 'tool-calls', toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'cityAttractions', @@ -72,6 +73,7 @@ async function main() { return newToolCall != null ? { + type: 'tool-call' as const, toolCallType: 'function' as const, toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, diff --git a/examples/ai-core/src/generate-text/mock-tool-call-repair-structured-model.ts b/examples/ai-core/src/generate-text/mock-tool-call-repair-structured-model.ts index 1d650414d374..ae97855ba272 100644 --- a/examples/ai-core/src/generate-text/mock-tool-call-repair-structured-model.ts +++ b/examples/ai-core/src/generate-text/mock-tool-call-repair-structured-model.ts @@ -12,6 +12,7 @@ async function main() { finishReason: 'tool-calls', toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'cityAttractions', diff --git a/packages/ai/core/generate-object/generate-object.test.ts b/packages/ai/core/generate-object/generate-object.test.ts index 49ea836a266e..4b37e8843184 100644 --- a/packages/ai/core/generate-object/generate-object.test.ts +++ b/packages/ai/core/generate-object/generate-object.test.ts @@ -176,6 +176,7 @@ describe('output = "object"', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -225,6 +226,7 @@ describe('output = "object"', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -274,6 +276,7 @@ describe('output = "object"', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -337,6 +340,7 @@ describe('output = "object"', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -617,6 +621,7 @@ describe('output = "object"', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -757,6 +762,7 @@ describe('output = "object"', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -809,6 +815,7 @@ describe('output = "object"', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -860,6 +867,7 @@ describe('output = "object"', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -1199,6 +1207,7 @@ describe('telemetry', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', @@ -1274,6 +1283,7 @@ describe('telemetry', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'tool-call-1', toolName: 'json', diff --git a/packages/ai/core/generate-text/generate-text.test.ts b/packages/ai/core/generate-text/generate-text.test.ts index 572471e3fa1f..7e84af5b02c2 100644 --- a/packages/ai/core/generate-text/generate-text.test.ts +++ b/packages/ai/core/generate-text/generate-text.test.ts @@ -226,6 +226,7 @@ describe('result.toolCalls', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', @@ -298,6 +299,7 @@ describe('result.toolResults', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', @@ -386,6 +388,7 @@ describe('result.response.messages', () => { text: 'Hello, world!', toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', @@ -523,6 +526,7 @@ describe('options.maxSteps', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', @@ -991,6 +995,7 @@ describe('options.abortSignal', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', @@ -1092,6 +1097,7 @@ describe('telemetry', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', @@ -1127,6 +1133,7 @@ describe('telemetry', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', @@ -1202,6 +1209,7 @@ describe('tools with custom schema', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', @@ -1572,6 +1580,7 @@ describe('tool execution errors', () => { ...dummyResponseValues, toolCalls: [ { + type: 'tool-call', toolCallType: 'function', toolCallId: 'call-1', toolName: 'tool1', diff --git a/packages/ai/core/generate-text/generate-text.ts b/packages/ai/core/generate-text/generate-text.ts index f758348e6216..e73e39a46dab 100644 --- a/packages/ai/core/generate-text/generate-text.ts +++ b/packages/ai/core/generate-text/generate-text.ts @@ -18,7 +18,7 @@ import { recordSpan } from '../telemetry/record-span'; import { selectTelemetryAttributes } from '../telemetry/select-telemetry-attributes'; import { TelemetrySettings } from '../telemetry/telemetry-settings'; import { LanguageModel, ToolChoice } from '../types'; -import { ProviderMetadata, ProviderOptions } from '../types/provider-metadata'; +import { ProviderOptions } from '../types/provider-metadata'; import { addLanguageModelUsage, calculateLanguageModelUsage, @@ -372,7 +372,15 @@ A function that attempts to repair a tool call that failed to parse. output: () => result.text, }, 'ai.response.toolCalls': { - output: () => JSON.stringify(result.toolCalls), + output: () => + JSON.stringify( + result.toolCalls?.map(toolCall => ({ + toolCallType: toolCall.toolCallType, + toolCallId: toolCall.toolCallId, + toolName: toolCall.toolName, + args: toolCall.args, + })), + ), }, 'ai.response.id': responseData.id, 'ai.response.model': responseData.modelId, @@ -546,7 +554,15 @@ A function that attempts to repair a tool call that failed to parse. output: () => currentModelResponse.text, }, 'ai.response.toolCalls': { - output: () => JSON.stringify(currentModelResponse.toolCalls), + output: () => + JSON.stringify( + currentModelResponse.toolCalls?.map(toolCall => ({ + toolCallType: toolCall.toolCallType, + toolCallId: toolCall.toolCallId, + toolName: toolCall.toolName, + args: toolCall.args, + })), + ), }, // TODO rename telemetry attributes to inputTokens and outputTokens diff --git a/packages/ai/core/generate-text/parse-tool-call.test.ts b/packages/ai/core/generate-text/parse-tool-call.test.ts index a57b8c198cce..e1843985e980 100644 --- a/packages/ai/core/generate-text/parse-tool-call.test.ts +++ b/packages/ai/core/generate-text/parse-tool-call.test.ts @@ -8,6 +8,7 @@ import { ToolCallRepairError } from '../../errors/tool-call-repair-error'; it('should successfully parse a valid tool call', async () => { const result = await parseToolCall({ toolCall: { + type: 'tool-call', toolCallType: 'function', toolName: 'testTool', toolCallId: '123', @@ -37,6 +38,7 @@ it('should successfully parse a valid tool call', async () => { it('should successfully process empty calls for tools that have no parameters', async () => { const result = await parseToolCall({ toolCall: { + type: 'tool-call', toolCallType: 'function', toolName: 'testTool', toolCallId: '123', @@ -64,6 +66,7 @@ it('should throw NoSuchToolError when tools is null', async () => { await expect( parseToolCall({ toolCall: { + type: 'tool-call', toolCallType: 'function', toolName: 'testTool', toolCallId: '123', @@ -81,6 +84,7 @@ it('should throw NoSuchToolError when tool is not found', async () => { await expect( parseToolCall({ toolCall: { + type: 'tool-call', toolCallType: 'function', toolName: 'nonExistentTool', toolCallId: '123', @@ -105,6 +109,7 @@ it('should throw InvalidToolArgumentsError when args are invalid', async () => { await expect( parseToolCall({ toolCall: { + type: 'tool-call', toolCallType: 'function', toolName: 'testTool', toolCallId: '123', @@ -136,6 +141,7 @@ describe('tool call repair', () => { const result = await parseToolCall({ toolCall: { + type: 'tool-call', toolCallType: 'function', toolName: 'testTool', toolCallId: '123', @@ -183,6 +189,7 @@ describe('tool call repair', () => { await expect( parseToolCall({ toolCall: { + type: 'tool-call', toolCallType: 'function', toolName: 'testTool', toolCallId: '123', @@ -210,6 +217,7 @@ describe('tool call repair', () => { const resultPromise = parseToolCall({ toolCall: { + type: 'tool-call', toolCallType: 'function', toolName: 'testTool', toolCallId: '123', diff --git a/packages/ai/core/generate-text/parse-tool-call.ts b/packages/ai/core/generate-text/parse-tool-call.ts index b9f0375b12b9..47d27d4d9693 100644 --- a/packages/ai/core/generate-text/parse-tool-call.ts +++ b/packages/ai/core/generate-text/parse-tool-call.ts @@ -1,10 +1,10 @@ -import { LanguageModelV2FunctionToolCall } from '@ai-sdk/provider'; +import { LanguageModelV2ToolCall } from '@ai-sdk/provider'; import { safeParseJSON, safeValidateTypes } from '@ai-sdk/provider-utils'; -import { Schema, asSchema } from '../util'; import { InvalidToolArgumentsError } from '../../errors/invalid-tool-arguments-error'; import { NoSuchToolError } from '../../errors/no-such-tool-error'; import { ToolCallRepairError } from '../../errors/tool-call-repair-error'; import { CoreMessage } from '../prompt'; +import { asSchema } from '../util'; import { ToolCallUnion } from './tool-call'; import { ToolCallRepairFunction } from './tool-call-repair'; import { ToolSet } from './tool-set'; @@ -16,7 +16,7 @@ export async function parseToolCall({ system, messages, }: { - toolCall: LanguageModelV2FunctionToolCall; + toolCall: LanguageModelV2ToolCall; tools: TOOLS | undefined; repairToolCall: ToolCallRepairFunction | undefined; system: string | undefined; @@ -39,7 +39,7 @@ export async function parseToolCall({ throw error; } - let repairedToolCall: LanguageModelV2FunctionToolCall | null = null; + let repairedToolCall: LanguageModelV2ToolCall | null = null; try { repairedToolCall = await repairToolCall({ @@ -73,7 +73,7 @@ async function doParseToolCall({ toolCall, tools, }: { - toolCall: LanguageModelV2FunctionToolCall; + toolCall: LanguageModelV2ToolCall; tools: TOOLS; }): Promise> { const toolName = toolCall.toolName as keyof TOOLS & string; diff --git a/packages/ai/core/generate-text/tool-call-repair.ts b/packages/ai/core/generate-text/tool-call-repair.ts index d19894774822..48ea1fc01155 100644 --- a/packages/ai/core/generate-text/tool-call-repair.ts +++ b/packages/ai/core/generate-text/tool-call-repair.ts @@ -1,4 +1,4 @@ -import { JSONSchema7, LanguageModelV2FunctionToolCall } from '@ai-sdk/provider'; +import { JSONSchema7, LanguageModelV2ToolCall } from '@ai-sdk/provider'; import { InvalidToolArgumentsError } from '../../errors/invalid-tool-arguments-error'; import { NoSuchToolError } from '../../errors/no-such-tool-error'; import { CoreMessage } from '../prompt'; @@ -20,8 +20,8 @@ import { ToolSet } from './tool-set'; export type ToolCallRepairFunction = (options: { system: string | undefined; messages: CoreMessage[]; - toolCall: LanguageModelV2FunctionToolCall; + toolCall: LanguageModelV2ToolCall; tools: TOOLS; parameterSchema: (options: { toolName: string }) => JSONSchema7; error: NoSuchToolError | InvalidToolArgumentsError; -}) => Promise; +}) => Promise; diff --git a/packages/ai/core/middleware/simulate-streaming-middleware.test.ts b/packages/ai/core/middleware/simulate-streaming-middleware.test.ts index 2ef302c6e415..6c6d743435a4 100644 --- a/packages/ai/core/middleware/simulate-streaming-middleware.test.ts +++ b/packages/ai/core/middleware/simulate-streaming-middleware.test.ts @@ -129,12 +129,14 @@ describe('simulateStreamingMiddleware', () => { text: 'This is a test response', toolCalls: [ { + type: 'tool-call', toolCallId: 'tool-1', toolName: 'calculator', args: '{"expression": "2+2"}', toolCallType: 'function', }, { + type: 'tool-call', toolCallId: 'tool-2', toolName: 'weather', args: '{"location": "New York"}', diff --git a/packages/ai/core/middleware/simulate-streaming-middleware.ts b/packages/ai/core/middleware/simulate-streaming-middleware.ts index 8c7395a20037..a903edb1c4a9 100644 --- a/packages/ai/core/middleware/simulate-streaming-middleware.ts +++ b/packages/ai/core/middleware/simulate-streaming-middleware.ts @@ -67,10 +67,7 @@ export function simulateStreamingMiddleware(): LanguageModelV2Middleware { argsTextDelta: toolCall.args, }); - controller.enqueue({ - type: 'tool-call', - ...toolCall, - }); + controller.enqueue(toolCall); } } diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 8602ebd0d788..c21c4d69d5b2 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -265,6 +265,7 @@ export class BedrockChatLanguageModel implements LanguageModelV2 { toolCalls: response.output?.message?.content ?.filter(part => !!part.toolUse) ?.map(part => ({ + type: 'tool-call' as const, toolCallType: 'function', toolCallId: part.toolUse?.toolUseId ?? this.config.generateId(), toolName: part.toolUse?.name ?? `tool-${this.config.generateId()}`, diff --git a/packages/anthropic/src/anthropic-messages-language-model.test.ts b/packages/anthropic/src/anthropic-messages-language-model.test.ts index 9ae586174ded..a894bec914d0 100644 --- a/packages/anthropic/src/anthropic-messages-language-model.test.ts +++ b/packages/anthropic/src/anthropic-messages-language-model.test.ts @@ -200,14 +200,17 @@ describe('AnthropicMessagesLanguageModel', () => { prompt: TEST_PROMPT, }); - expect(toolCalls).toStrictEqual([ - { - toolCallId: 'toolu_1', - toolCallType: 'function', - toolName: 'test-tool', - args: '{"value":"example value"}', - }, - ]); + expect(toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"value":"example value"}", + "toolCallId": "toolu_1", + "toolCallType": "function", + "toolName": "test-tool", + "type": "tool-call", + }, + ] + `); expect(text).toStrictEqual('Some text\n\n'); expect(finishReason).toStrictEqual('tool-calls'); }); diff --git a/packages/anthropic/src/anthropic-messages-language-model.ts b/packages/anthropic/src/anthropic-messages-language-model.ts index 1ddbdc317209..9ad97a17b4ee 100644 --- a/packages/anthropic/src/anthropic-messages-language-model.ts +++ b/packages/anthropic/src/anthropic-messages-language-model.ts @@ -2,10 +2,10 @@ import { LanguageModelV2, LanguageModelV2CallWarning, LanguageModelV2FinishReason, - LanguageModelV2FunctionToolCall, - SharedV2ProviderMetadata, LanguageModelV2StreamPart, + LanguageModelV2ToolCall, LanguageModelV2Usage, + SharedV2ProviderMetadata, UnsupportedFunctionalityError, } from '@ai-sdk/provider'; import { @@ -266,12 +266,13 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV2 { } // extract tool calls - let toolCalls: LanguageModelV2FunctionToolCall[] | undefined = undefined; + let toolCalls: LanguageModelV2ToolCall[] | undefined = undefined; if (response.content.some(content => content.type === 'tool_use')) { toolCalls = []; for (const content of response.content) { if (content.type === 'tool_use') { toolCalls.push({ + type: 'tool-call' as const, toolCallType: 'function', toolCallId: content.id, toolName: content.name, diff --git a/packages/cohere/src/cohere-chat-language-model.test.ts b/packages/cohere/src/cohere-chat-language-model.test.ts index 43e6c3c08541..05755042341c 100644 --- a/packages/cohere/src/cohere-chat-language-model.test.ts +++ b/packages/cohere/src/cohere-chat-language-model.test.ts @@ -108,14 +108,17 @@ describe('doGenerate', () => { prompt: TEST_PROMPT, }); - expect(toolCalls).toStrictEqual([ - { - toolCallId: 'test-id-1', - toolCallType: 'function', - toolName: 'test-tool', - args: '{"value":"example value"}', - }, - ]); + expect(toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"value":"example value"}", + "toolCallId": "test-id-1", + "toolCallType": "function", + "toolName": "test-tool", + "type": "tool-call", + }, + ] + `); expect(text).toStrictEqual('Hello, World!'); expect(finishReason).toStrictEqual('stop'); }); @@ -422,14 +425,17 @@ describe('doGenerate', () => { ], }); - expect(toolCalls).toStrictEqual([ - { - toolCallId: 'test-id-1', - toolCallType: 'function', - toolName: 'currentTime', - args: '{}', - }, - ]); + expect(toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{}", + "toolCallId": "test-id-1", + "toolCallType": "function", + "toolName": "currentTime", + "type": "tool-call", + }, + ] + `); }); }); diff --git a/packages/cohere/src/cohere-chat-language-model.ts b/packages/cohere/src/cohere-chat-language-model.ts index d56463c22707..d036571e2628 100644 --- a/packages/cohere/src/cohere-chat-language-model.ts +++ b/packages/cohere/src/cohere-chat-language-model.ts @@ -133,6 +133,7 @@ export class CohereChatLanguageModel implements LanguageModelV2 { text, toolCalls: response.message.tool_calls ? response.message.tool_calls.map(toolCall => ({ + type: 'tool-call' as const, toolCallId: toolCall.id, toolName: toolCall.function.name, // Cohere sometimes returns `null` for tool call arguments for tools diff --git a/packages/google/src/google-generative-ai-language-model.test.ts b/packages/google/src/google-generative-ai-language-model.test.ts index 782354b872f7..c76a6beb34a9 100644 --- a/packages/google/src/google-generative-ai-language-model.test.ts +++ b/packages/google/src/google-generative-ai-language-model.test.ts @@ -345,14 +345,17 @@ describe('doGenerate', () => { prompt: TEST_PROMPT, }); - expect(toolCalls).toStrictEqual([ - { - toolCallId: 'test-id', - toolCallType: 'function', - toolName: 'test-tool', - args: '{"value":"example value"}', - }, - ]); + expect(toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"value":"example value"}", + "toolCallId": "test-id", + "toolCallType": "function", + "toolName": "test-tool", + "type": "tool-call", + }, + ] + `); expect(text).toStrictEqual(undefined); expect(finishReason).toStrictEqual('tool-calls'); }); diff --git a/packages/google/src/google-generative-ai-language-model.ts b/packages/google/src/google-generative-ai-language-model.ts index 8036bf8f3eac..dad6cc3cdf72 100644 --- a/packages/google/src/google-generative-ai-language-model.ts +++ b/packages/google/src/google-generative-ai-language-model.ts @@ -405,6 +405,7 @@ function getToolCallsFromParts({ return functionCallParts == null || functionCallParts.length === 0 ? undefined : functionCallParts.map(part => ({ + type: 'tool-call' as const, toolCallType: 'function' as const, toolCallId: generateId(), toolName: part.functionCall.name, diff --git a/packages/groq/src/groq-chat-language-model.test.ts b/packages/groq/src/groq-chat-language-model.test.ts index 931438c5d629..df49c769f2cc 100644 --- a/packages/groq/src/groq-chat-language-model.test.ts +++ b/packages/groq/src/groq-chat-language-model.test.ts @@ -384,14 +384,17 @@ describe('doGenerate', () => { prompt: TEST_PROMPT, }); - expect(result.toolCalls).toStrictEqual([ - { - args: '{"value":"Spark"}', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - }, - ]); + expect(result.toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"value":"Spark"}", + "toolCallId": "call_O17Uplv4lJvD6DVdIvFFeRMw", + "toolCallType": "function", + "toolName": "test-tool", + "type": "tool-call", + }, + ] + `); }); it('should pass response format information', async () => { diff --git a/packages/groq/src/groq-chat-language-model.ts b/packages/groq/src/groq-chat-language-model.ts index 199c84edfd24..30ec885a6f8b 100644 --- a/packages/groq/src/groq-chat-language-model.ts +++ b/packages/groq/src/groq-chat-language-model.ts @@ -189,6 +189,7 @@ export class GroqChatLanguageModel implements LanguageModelV2 { text: choice.message.content ?? undefined, reasoning: choice.message.reasoning ?? undefined, toolCalls: choice.message.tool_calls?.map(toolCall => ({ + type: 'tool-call', toolCallType: 'function', toolCallId: toolCall.id ?? generateId(), toolName: toolCall.function.name, diff --git a/packages/mistral/src/mistral-chat-language-model.test.ts b/packages/mistral/src/mistral-chat-language-model.test.ts index e612f389f473..5f96caf9ab91 100644 --- a/packages/mistral/src/mistral-chat-language-model.test.ts +++ b/packages/mistral/src/mistral-chat-language-model.test.ts @@ -130,14 +130,17 @@ describe('doGenerate', () => { prompt: TEST_PROMPT, }); - expect(toolCalls).toStrictEqual([ - { - toolCallId: 'gSIMJiOkT', - toolCallType: 'function', - toolName: 'weatherTool', - args: '{"location": "paris"}', - }, - ]); + expect(toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"location": "paris"}", + "toolCallId": "gSIMJiOkT", + "toolCallType": "function", + "toolName": "weatherTool", + "type": "tool-call", + }, + ] + `); }); it('should extract usage', async () => { diff --git a/packages/mistral/src/mistral-chat-language-model.ts b/packages/mistral/src/mistral-chat-language-model.ts index f0b7e01b4c67..bc4ed1ca1abb 100644 --- a/packages/mistral/src/mistral-chat-language-model.ts +++ b/packages/mistral/src/mistral-chat-language-model.ts @@ -204,6 +204,7 @@ export class MistralChatLanguageModel implements LanguageModelV2 { return { text, toolCalls: choice.message.tool_calls?.map(toolCall => ({ + type: 'tool-call', toolCallType: 'function', toolCallId: toolCall.id, toolName: toolCall.function.name, diff --git a/packages/openai-compatible/src/openai-compatible-chat-language-model.test.ts b/packages/openai-compatible/src/openai-compatible-chat-language-model.test.ts index b07ce997fbf9..272f885b2d2c 100644 --- a/packages/openai-compatible/src/openai-compatible-chat-language-model.test.ts +++ b/packages/openai-compatible/src/openai-compatible-chat-language-model.test.ts @@ -501,6 +501,7 @@ describe('doGenerate', () => { toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', toolCallType: 'function', toolName: 'test-tool', + type: 'tool-call', }, ]); }); diff --git a/packages/openai-compatible/src/openai-compatible-chat-language-model.ts b/packages/openai-compatible/src/openai-compatible-chat-language-model.ts index eecfdb6362cb..7a912e4bb571 100644 --- a/packages/openai-compatible/src/openai-compatible-chat-language-model.ts +++ b/packages/openai-compatible/src/openai-compatible-chat-language-model.ts @@ -247,6 +247,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { text: choice.message.content ?? undefined, reasoning: choice.message.reasoning_content ?? undefined, toolCalls: choice.message.tool_calls?.map(toolCall => ({ + type: 'tool-call', toolCallType: 'function', toolCallId: toolCall.id ?? generateId(), toolName: toolCall.function.name, diff --git a/packages/openai/src/openai-chat-language-model.test.ts b/packages/openai/src/openai-chat-language-model.test.ts index 3b8d0e088f67..a1de3e13b255 100644 --- a/packages/openai/src/openai-chat-language-model.test.ts +++ b/packages/openai/src/openai-chat-language-model.test.ts @@ -1,4 +1,4 @@ -import { LanguageModelV2, LanguageModelV2Prompt } from '@ai-sdk/provider'; +import { LanguageModelV2Prompt } from '@ai-sdk/provider'; import { convertReadableStreamToArray, createTestServer, @@ -616,14 +616,17 @@ describe('doGenerate', () => { prompt: TEST_PROMPT, }); - expect(result.toolCalls).toStrictEqual([ - { - args: '{"value":"Spark"}', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - }, - ]); + expect(result.toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"value":"Spark"}", + "toolCallId": "call_O17Uplv4lJvD6DVdIvFFeRMw", + "toolCallType": "function", + "toolName": "test-tool", + "type": "tool-call", + }, + ] + `); }); describe('response format', () => { @@ -918,14 +921,17 @@ describe('doGenerate', () => { ], }); - expect(result.toolCalls).toStrictEqual([ - { - args: '{"value":"Spark"}', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - }, - ]); + expect(result.toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"value":"Spark"}", + "toolCallId": "call_O17Uplv4lJvD6DVdIvFFeRMw", + "toolCallType": "function", + "toolName": "test-tool", + "type": "tool-call", + }, + ] + `); }); }); @@ -991,14 +997,17 @@ describe('doGenerate', () => { ], }); - expect(result.toolCalls).toStrictEqual([ - { - args: '{"value":"Spark"}', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - }, - ]); + expect(result.toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"value":"Spark"}", + "toolCallId": "call_O17Uplv4lJvD6DVdIvFFeRMw", + "toolCallType": "function", + "toolName": "test-tool", + "type": "tool-call", + }, + ] + `); }); it('should return cached_tokens in prompt_details_tokens', async () => { diff --git a/packages/openai/src/openai-chat-language-model.ts b/packages/openai/src/openai-chat-language-model.ts index f6d67cbcfab4..848d45948712 100644 --- a/packages/openai/src/openai-chat-language-model.ts +++ b/packages/openai/src/openai-chat-language-model.ts @@ -345,6 +345,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV2 { return { text: choice.message.content ?? undefined, toolCalls: choice.message.tool_calls?.map(toolCall => ({ + type: 'tool-call' as const, toolCallType: 'function', toolCallId: toolCall.id ?? generateId(), toolName: toolCall.function.name, diff --git a/packages/openai/src/responses/openai-responses-language-model.test.ts b/packages/openai/src/responses/openai-responses-language-model.test.ts index 91fb02b786a3..66421185fe90 100644 --- a/packages/openai/src/responses/openai-responses-language-model.test.ts +++ b/packages/openai/src/responses/openai-responses-language-model.test.ts @@ -795,20 +795,24 @@ describe('OpenAIResponsesLanguageModel', () => { tools: TEST_TOOLS, }); - expect(result.toolCalls).toStrictEqual([ - { - toolCallType: 'function', - toolCallId: 'call_0NdsJqOS8N3J9l2p0p4WpYU9', - toolName: 'weather', - args: JSON.stringify({ location: 'San Francisco' }), - }, - { - toolCallType: 'function', - toolCallId: 'call_gexo0HtjUfmAIW4gjNOgyrcr', - toolName: 'cityAttractions', - args: JSON.stringify({ city: 'San Francisco' }), - }, - ]); + expect(result.toolCalls).toMatchInlineSnapshot(` + [ + { + "args": "{"location":"San Francisco"}", + "toolCallId": "call_0NdsJqOS8N3J9l2p0p4WpYU9", + "toolCallType": "function", + "toolName": "weather", + "type": "tool-call", + }, + { + "args": "{"city":"San Francisco"}", + "toolCallId": "call_gexo0HtjUfmAIW4gjNOgyrcr", + "toolCallType": "function", + "toolName": "cityAttractions", + "type": "tool-call", + }, + ] + `); }); it('should have tool-calls finish reason', async () => { diff --git a/packages/openai/src/responses/openai-responses-language-model.ts b/packages/openai/src/responses/openai-responses-language-model.ts index 67a1108148a8..9ebaa3b14607 100644 --- a/packages/openai/src/responses/openai-responses-language-model.ts +++ b/packages/openai/src/responses/openai-responses-language-model.ts @@ -257,6 +257,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { const toolCalls = response.output .filter(output => output.type === 'function_call') .map(output => ({ + type: 'tool-call' as const, toolCallType: 'function' as const, toolCallId: output.call_id, toolName: output.name, diff --git a/packages/provider/src/language-model/v2/index.ts b/packages/provider/src/language-model/v2/index.ts index 644e540f0ed5..e3de0df25a20 100644 --- a/packages/provider/src/language-model/v2/index.ts +++ b/packages/provider/src/language-model/v2/index.ts @@ -5,10 +5,11 @@ export * from './language-model-v2-data-content'; export * from './language-model-v2-file'; export * from './language-model-v2-finish-reason'; export * from './language-model-v2-function-tool'; -export * from './language-model-v2-function-tool-call'; export * from './language-model-v2-logprobs'; export * from './language-model-v2-prompt'; export * from './language-model-v2-provider-defined-tool'; export * from './language-model-v2-source'; +export * from './language-model-v2-tool-call'; +export * from './language-model-v2-tool-call-delta'; export * from './language-model-v2-tool-choice'; export * from './language-model-v2-usage'; diff --git a/packages/provider/src/language-model/v2/language-model-v2-tool-call-delta.ts b/packages/provider/src/language-model/v2/language-model-v2-tool-call-delta.ts new file mode 100644 index 000000000000..5550eeda7099 --- /dev/null +++ b/packages/provider/src/language-model/v2/language-model-v2-tool-call-delta.ts @@ -0,0 +1,10 @@ +export type LanguageModelV2ToolCallDelta = { + type: 'tool-call-delta'; + + toolCallType: 'function'; + toolCallId: string; + toolName: string; + + // The tool call deltas must be partial JSON strings. + argsTextDelta: string; +}; diff --git a/packages/provider/src/language-model/v2/language-model-v2-function-tool-call.ts b/packages/provider/src/language-model/v2/language-model-v2-tool-call.ts similarity index 76% rename from packages/provider/src/language-model/v2/language-model-v2-function-tool-call.ts rename to packages/provider/src/language-model/v2/language-model-v2-tool-call.ts index 016c816964b9..b5d63be3a0b5 100644 --- a/packages/provider/src/language-model/v2/language-model-v2-function-tool-call.ts +++ b/packages/provider/src/language-model/v2/language-model-v2-tool-call.ts @@ -1,4 +1,6 @@ -export type LanguageModelV2FunctionToolCall = { +export type LanguageModelV2ToolCall = { + type: 'tool-call'; + toolCallType: 'function'; toolCallId: string; toolName: string; diff --git a/packages/provider/src/language-model/v2/language-model-v2.ts b/packages/provider/src/language-model/v2/language-model-v2.ts index 0aa672c4dabe..43b9873826f5 100644 --- a/packages/provider/src/language-model/v2/language-model-v2.ts +++ b/packages/provider/src/language-model/v2/language-model-v2.ts @@ -3,9 +3,10 @@ import { LanguageModelV2CallOptions } from './language-model-v2-call-options'; import { LanguageModelV2CallWarning } from './language-model-v2-call-warning'; import { LanguageModelV2File } from './language-model-v2-file'; import { LanguageModelV2FinishReason } from './language-model-v2-finish-reason'; -import { LanguageModelV2FunctionToolCall } from './language-model-v2-function-tool-call'; import { LanguageModelV2LogProbs } from './language-model-v2-logprobs'; import { LanguageModelV2Source } from './language-model-v2-source'; +import { LanguageModelV2ToolCall } from './language-model-v2-tool-call'; +import { LanguageModelV2ToolCallDelta } from './language-model-v2-tool-call-delta'; import { LanguageModelV2Usage } from './language-model-v2-usage'; /** @@ -131,7 +132,7 @@ Sources that have been used as input to generate the response. Tool calls that the model has generated. Can be undefined if the model did not generate any tool calls. */ - toolCalls?: Array; + toolCalls?: Array; /** Logprobs for the completion. @@ -258,18 +259,9 @@ export type LanguageModelV2StreamPart = // Files: | { type: 'file'; file: LanguageModelV2File } - // Complete tool calls: - | ({ type: 'tool-call' } & LanguageModelV2FunctionToolCall) - - // Tool call deltas are only needed for object generation modes. - // The tool call deltas must be partial JSON strings. - | { - type: 'tool-call-delta'; - toolCallType: 'function'; - toolCallId: string; - toolName: string; - argsTextDelta: string; - } + // Tool calls: + | LanguageModelV2ToolCall + | LanguageModelV2ToolCallDelta // metadata for the response. // separate stream part so it can be sent once it is available.