Skip to content

Commit 7de628e

Browse files
authoredMay 29, 2024··
feat (ui): add onToolCall callback to useChat (#1729)
1 parent a5c633c commit 7de628e

File tree

14 files changed

+410
-306
lines changed

14 files changed

+410
-306
lines changed
 

‎.changeset/swift-lamps-juggle.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'ai': patch
3+
---
4+
5+
chore (ui): deprecate old function/tool call handling

‎.changeset/three-foxes-dream.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'ai': patch
3+
---
4+
5+
feat (ui): add onToolCall handler to useChat
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
---
2-
title: Chatbot with Tool Calling
3-
description: Learn how to use tool calling with the useChat hook.
2+
title: Chatbot with Tools
3+
description: Learn how to use tools with the useChat hook.
44
---
55

6-
# Chatbot with Tool Calling
6+
# Chatbot with Tools
77

88
<Note type="warning">
9-
The tool calling functionality described here is experimental. It is currently
10-
only available for React.
9+
The tool calling functionality described here currently only available for
10+
React.
1111
</Note>
1212

1313
With `useChat` and `streamText`, you can use tools in your chatbot application.
14-
The Vercel AI SDK supports both client and server side tool execution.
14+
The Vercel AI SDK supports three types of tools in this context:
15+
16+
1. Automatically executed server-side tools
17+
2. Automatically executed client-side tools
18+
3. Tools that require user interaction, such as confirmation dialogs
1519

1620
The flow is as follows:
1721

22+
1. The user enters a message in the chat UI.
23+
1. The message is sent to the API route.
24+
1. The messages from the client are converted to AI SDK Core messages using `convertToCoreMessages`.
1825
1. In your server side route, the language model generates tool calls during the `streamText` call.
19-
The messages from the client are converted to AI SDK Core messages using `convertToCoreMessages`.
20-
2. All tool calls are forwarded to the client.
21-
3. Server-side tools are executed using their `execute` method and their results are sent back to the client.
22-
4. The client-side tools are executed on the client.
23-
The results of client side tool calls can be appended to last assigned message using `experimental_addToolResult`.
24-
5. When all tool calls are resolved, the client sends the updated messages with the tool results back to the server, triggering another `streamText` call.
26+
1. All tool calls are forwarded to the client.
27+
1. Server-side tools are executed using their `execute` method and their results are forwarded to the client.
28+
1. Client-side tools that should be automatically executed are handled with the `onToolCall` callback.
29+
You can return the tool result from the callback.
30+
1. Client-side tool that require user interactions can be displayed in the UI.
31+
The tool calls and results are available in the `toolInvocations` property of the last assistant message.
32+
1. When the user interaction is done, `experimental_addToolResult` can be used to add the tool result to the chat.
33+
1. When there are tool calls in the last assistant message and all tool results are available, the client sends the updated messages back to the server.
34+
This triggers another iteration of this flow.
2535

2636
The tool call and tool executions are integrated into the assistant message as `toolInvocations`.
2737
A tool invocation is at first a tool call, and then it becomes a tool result when the tool is executed.
@@ -34,16 +44,15 @@ The tool result contains all information about the tool call as well as the resu
3444
for backward compatibility.
3545
</Note>
3646

37-
## Example: Client-Side Tool Execution
47+
## Example
3848

39-
In this example, we'll define two tools.
40-
The client-side tool is a confirmation dialog that asks the user to confirm the execution of the server-side tool.
41-
The server-side tool is a simple fake tool that restarts an engine.
49+
In this example, we'll use three tools:
4250

43-
### Server-side route
51+
- `getWeatherInformation`: An automatically executed server-side tool that returns the weather in a given city.
52+
- `askForConfirmation`: A user-interaction client-side tool that asks the user for confirmation.
53+
- `getLocation`: An automatically executed client-side tool that returns a random city.
4454

45-
Please note that only the `restartEngine` tool has an `execute` method and is executed on the server side.
46-
The `askForConfirmation` tool is executed on the client side.
55+
### API route
4756

4857
```tsx filename='app/api/chat/route.ts'
4958
import { openai } from '@ai-sdk/openai';
@@ -60,19 +69,30 @@ export async function POST(req: Request) {
6069
model: openai('gpt-4-turbo'),
6170
messages: convertToCoreMessages(messages),
6271
tools: {
63-
restartEngine: {
64-
description:
65-
'Restarts the engine. ' +
66-
'Always ask for confirmation before using this tool.',
67-
parameters: z.object({}),
68-
execute: async () => 'Engine restarted.',
72+
// server-side tool with execute function:
73+
getWeatherInformation: {
74+
description: 'show the weather in a given city to the user',
75+
parameters: z.object({ city: z.string() }),
76+
execute: async ({}: { city: string }) => {
77+
const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
78+
return weatherOptions[
79+
Math.floor(Math.random() * weatherOptions.length)
80+
];
81+
},
6982
},
83+
// client-side tool that starts user interaction:
7084
askForConfirmation: {
7185
description: 'Ask the user for confirmation.',
7286
parameters: z.object({
7387
message: z.string().describe('The message to ask for confirmation.'),
7488
}),
7589
},
90+
// client-side tool that is automatically executed on the client:
91+
getLocation: {
92+
description:
93+
'Get the user location. Always ask for confirmation before using this tool.',
94+
parameters: z.object({}),
95+
},
7696
},
7797
});
7898

@@ -85,24 +105,43 @@ export async function POST(req: Request) {
85105
The client-side page uses the `useChat` hook to create a chatbot application with real-time message streaming.
86106
Tool invocations are displayed in the chat UI.
87107

88-
If the tool is a confirmation dialog, the user can confirm or deny the execution of the server-side tool.
89-
Once the user confirms the execution, the tool result is appended to the assistant message using `experimental_addToolResult`,
90-
and the server route is called again to execute the server-side tool.
108+
There are three things worth mentioning:
109+
110+
1. The `onToolCall` callback is used to handle client-side tools that should be automatically executed.
111+
In this example, the `getLocation` tool is a client-side tool that returns a random city.
112+
113+
1. The `toolInvocations` property of the last assistant message contains all tool calls and results.
114+
The client-side tool `askForConfirmation` is displayed in the UI.
115+
It asks the user for confirmation and displays the result once the user confirms or denies the execution.
116+
The result is added to the chat using `experimental_addToolResult`.
117+
118+
1. The `experimental_maxAutomaticRoundtrips` option is set to 5.
119+
This enables several tool use iterations between the client and the server.
91120

92121
```tsx filename='app/page.tsx'
93-
'use client';
122+
'use client'
94123

95-
import { ToolInvocation } from 'ai';
96-
import { Message, useChat } from 'ai/react';
124+
import { ToolInvocation } from 'ai'
125+
import { Message, useChat } from 'ai/react'
97126

98127
export default function Chat() {
99128
const {
100129
messages,
101130
input,
102131
handleInputChange,
103132
handleSubmit,
104-
experimental_addToolResult,
105-
} = useChat();
133+
experimental_addToolResult
134+
} = useChat({
135+
experimental_maxAutomaticRoundtrips: 5,
136+
137+
// run client-side tools that are automatically executed:
138+
async onToolCall({ toolCall }) {
139+
if (toolCall.toolName === 'getLocation') {
140+
const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco']
141+
return cities[Math.floor(Math.random() * cities.length)]
142+
}
143+
}
144+
})
106145

107146
return (
108147
<div>
@@ -111,9 +150,9 @@ export default function Chat() {
111150
<strong>{`${m.role}: `}</strong>
112151
{m.content}
113152
{m.toolInvocations?.map((toolInvocation: ToolInvocation) => {
114-
const toolCallId = toolInvocation.toolCallId;
153+
const toolCallId = toolInvocation.toolCallId
115154

116-
// render confirmation tool
155+
// render confirmation tool (client-side tool with user interaction)
117156
if (toolInvocation.toolName === 'askForConfirmation') {
118157
return (
119158
<div key={toolCallId}>
@@ -127,7 +166,7 @@ export default function Chat() {
127166
onClick={() =>
128167
experimental_addToolResult({
129168
toolCallId,
130-
result: 'Yes, confirmed.',
169+
result: 'Yes, confirmed.'
131170
})
132171
}
133172
>
@@ -137,7 +176,7 @@ export default function Chat() {
137176
onClick={() =>
138177
experimental_addToolResult({
139178
toolCallId,
140-
result: 'No, denied',
179+
result: 'No, denied'
141180
})
142181
}
143182
>
@@ -147,123 +186,34 @@ export default function Chat() {
147186
)}
148187
</div>
149188
</div>
150-
);
189+
)
151190
}
152191

153192
// other tools:
154193
return 'result' in toolInvocation ? (
155-
<div key={toolCallId}>
156-
<strong>{`${toolInvocation.toolName}:`}</strong>
194+
<div key={toolCallId}
195+
Tool call {`${toolInvocation.toolName}: `}
157196
{toolInvocation.result}
158197
</div>
159198
) : (
160-
<div key={toolCallId}>Calling {toolInvocation.toolName}...</div>
161-
);
199+
<div key={toolCallId}
200+
Calling {toolInvocation.toolName}...
201+
</div>
202+
)
162203
})}
163204
<br />
164205
<br />
165206
</div>
166207
))}
167208

168209
<form onSubmit={handleSubmit}>
169-
<input value={input} onChange={handleInputChange} />
170-
</form>
171-
</div>
172-
);
173-
}
174-
```
175-
176-
## Example: Server-Side Tool Execution with Roundtrips
177-
178-
In this example, we'll define a weather tool that shows the weather in a given city.
179-
180-
When asked about the weather, the assistant will call the weather tool to get the weather information.
181-
The server will then respond with the weather information tool results.
182-
183-
Once the client receives all tool results, it will send the updated messages back to the server for another roundtrip.
184-
The server will then generate a streaming text response that uses the information from the weather tool results.
185-
186-
### Server-side route
187-
188-
The server-side route defines a weather tool that returns the weather in a given city.
189-
190-
```tsx filename='app/api/chat/route.ts'
191-
import { openai } from '@ai-sdk/openai';
192-
import { convertToCoreMessages, streamText } from 'ai';
193-
import { z } from 'zod';
194-
195-
export const dynamic = 'force-dynamic';
196-
export const maxDuration = 60;
197-
198-
export async function POST(req: Request) {
199-
const { messages } = await req.json();
200-
201-
const result = await streamText({
202-
model: openai('gpt-4-turbo'),
203-
system:
204-
'You are a weather bot that can use the weather tool ' +
205-
'to get the weather in a given city. ' +
206-
'Respond to the user with weather information in a friendly ' +
207-
'and helpful manner.',
208-
messages: convertToCoreMessages(messages),
209-
tools: {
210-
weather: {
211-
description: 'show the weather in a given city to the user',
212-
parameters: z.object({ city: z.string() }),
213-
execute: async ({}: { city: string }) => {
214-
// Random weather:
215-
const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
216-
return weatherOptions[
217-
Math.floor(Math.random() * weatherOptions.length)
218-
];
219-
},
220-
},
221-
},
222-
});
223-
224-
return result.toAIStreamResponse();
225-
}
226-
```
227-
228-
### Client-side page
229-
230-
The page uses the `useChat` hook to create a chatbot application with real-time message streaming.
231-
We set `experimental_maxAutomaticRoundtrips` to 2 to automatically
232-
send another request to the server when all server-side tool results are received.
233-
234-
<Note>
235-
The `experimental_maxAutomaticRoundtrips` option is disabled by default for
236-
backward compatibility. It also limits the number of automatic roundtrips to
237-
prevent infinite client-server call loops.
238-
</Note>
239-
240-
```tsx filename='app/page.tsx'
241-
'use client';
242-
243-
import { useChat } from 'ai/react';
244-
245-
export default function Chat() {
246-
const { messages, input, handleInputChange, handleSubmit } = useChat({
247-
experimental_maxAutomaticRoundtrips: 2,
248-
});
249-
250-
return (
251-
<div>
252-
{messages
253-
.filter(m => m.content) // filter out empty messages
254-
.map(m => (
255-
<div key={m.id}>
256-
<strong>{`${m.role}: `}</strong>
257-
{m.content}
258-
<br />
259-
<br />
260-
</div>
261-
))}
262-
263-
<form onSubmit={handleSubmit}>
264-
<input value={input} onChange={handleInputChange} />
210+
<input
211+
value={input}
212+
placeholder="Say something..."
213+
onChange={handleInputChange}
214+
/>
265215
</form>
266216
</div>
267-
);
217+
)
268218
}
269219
```

‎content/docs/07-reference/ai-sdk-ui/01-use-chat.mdx

+180-10
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,190 @@ Allows you to easily create a conversational user interface for your chatbot app
99

1010
## Import
1111

12-
### React
12+
<Tabs items={['React', 'Svelte', 'Vue', 'Solid']}>
13+
<Tab>
14+
<Snippet text="{`import { useChat } from 'ai/react'`}" dark />
15+
</Tab>
16+
<Tab>
17+
<Snippet text="{`import { useChat } from 'ai/svelte'`}" dark />
18+
</Tab>
19+
<Tab>
20+
<Snippet text="{`import { useChat } from 'ai/vue'`}" dark />
21+
</Tab>
22+
<Tab>
23+
<Snippet text="{`import { useChat } from 'ai/solid'`}" dark />
24+
</Tab>
25+
</Tabs>
1326

14-
<Snippet text={`import { useChat } from "ai/react"`} prompt={false} />
27+
## API Signature
1528

16-
### Svelte
29+
### Parameters
1730

18-
<Snippet text={`import { useChat } from "ai/svelte"`} prompt={false} />
31+
<PropertiesTable
32+
content={[
33+
{
34+
name: 'api',
35+
type: "string = '/api/chat'",
36+
description: 'The chat completion API endpoint offered by the provider.',
37+
},
38+
{
39+
name: 'id',
40+
type: 'string',
41+
description:
42+
'An unique identifier for the chat. If not provided, a random one will be generated. When provided, the `useChat` hook with the same `id` will have shared states across components. This is useful when you have multiple components showing the same chat stream.',
43+
},
44+
{
45+
name: 'initialInput',
46+
type: "string = ''",
47+
description: 'An optional string for the initial prompt input.',
48+
},
49+
{
50+
name: 'initialMessages',
51+
type: 'Messages[] = []',
52+
description: 'An optional array of initial chat messages',
53+
},
54+
{
55+
name: 'onToolCall',
56+
type: '({toolCall: ToolCall}) => void | unknown| Promise<unknown>',
57+
description:
58+
'Optional callback function that is invoked when a tool call is received. Intended for automatic client-side tool execution. You can optionally return a result for the tool call, either synchronously or asynchronously.',
59+
},
60+
{
61+
name: 'onResponse',
62+
type: '(response: Response) => void',
63+
description:
64+
'An optional callback that will be called with the response from the API endpoint. Useful for throwing customized errors or logging',
65+
},
66+
{
67+
name: 'onFinish',
68+
type: '(message: Message) => void',
69+
description:
70+
'An optional callback function that is called when the completion stream ends.',
71+
},
72+
{
73+
name: 'onError',
74+
type: '(error: Error) => void',
75+
description:
76+
'An optional callback that will be called when the chat stream encounters an error.',
77+
},
78+
{
79+
name: 'generateId',
80+
type: '() => string',
81+
description: `Optional. A way to provide a function that is going to be used for ids for messages. If not provided nanoid is used by default.`,
82+
},
83+
{
84+
name: 'headers',
85+
type: 'Record<string, string> | Headers',
86+
description:
87+
'An optional object of headers to be passed to the API endpoint.',
88+
},
89+
{
90+
name: 'body',
91+
type: 'any',
92+
description:
93+
'An optional, additional body object to be passed to the API endpoint.',
94+
},
95+
{
96+
name: 'credentials',
97+
type: "'omit' | 'same-origin' | 'include'",
98+
description:
99+
'An optional literal that sets the mode of credentials to be used on the request. Defaults to same-origin.',
100+
},
101+
{
102+
name: 'sendExtraMessageFields',
103+
type: 'boolean',
104+
description:
105+
"An optional boolean that determines whether to send extra fields you've added to `messages`. Defaults to `false` and only the `content` and `role` fields will be sent to the API endpoint.",
106+
},
107+
{
108+
name: 'experimental_maxAutomaticRoundtrips',
109+
type: 'number',
110+
description:
111+
'React only. Maximal number of automatic roundtrips for tool calls. An automatic tool call roundtrip is a call to the server with the tool call results when all tool calls in the last assistant message have results. A maximum number is required to prevent infinite loops in the case of misconfigured tools. By default, it is set to 0, which will disable the feature.',
112+
},
113+
]}
114+
/>
19115

20-
### Vue
116+
### Returns
21117

22-
<Snippet text={`import { useChat } from "ai/vue"`} prompt={false} />
118+
<PropertiesTable
119+
content={[
120+
{
121+
name: 'messages',
122+
type: 'Message[]',
123+
description: 'The current array of chat messages.',
124+
},
125+
{
126+
name: 'error',
127+
type: 'Error | undefined',
128+
description: 'An error object returned by SWR, if any.',
129+
},
130+
{
131+
name: 'append',
132+
type: '(message: Message | CreateMessage, chatRequestOptions: { options: { headers, body } }) => Promise<string | undefined>',
133+
description:
134+
'Function to append a message to the chat, triggering an API call for the AI response. It returns a promise that resolves to full response message content when the API call is successfully finished, or throws an error when the API call fails.',
135+
},
136+
{
137+
name: 'reload',
138+
type: '() => Promise<string | undefined>',
139+
description:
140+
"Function to reload the last AI chat response for the given chat history. If the last message isn't from the assistant, it will request the API to generate a new response.",
141+
},
142+
{
143+
name: 'stop',
144+
type: '() => void',
145+
description: 'Function to abort the current API request.',
146+
},
147+
{
148+
name: 'setMessages',
149+
type: '(messages: Message[]) => void',
150+
description:
151+
'Function to update the `messages` state locally without triggering an API call.',
152+
},
153+
{
154+
name: 'input',
155+
type: 'string',
156+
description: 'The current value of the input field.',
157+
},
158+
{
159+
name: 'setInput',
160+
type: 'React.Dispatch<React.SetStateAction<string>>',
161+
description: 'Function to update the `input` value.',
162+
},
163+
{
164+
name: 'handleInputChange',
165+
type: '(event: any) => void',
166+
description:
167+
"Handler for the `onChange` event of the input field to control the input's value.",
168+
},
169+
{
170+
name: 'handleSubmit',
171+
type: '(event: React.FormEvent<HTMLFormElement>, chatRequestOptions?: ChatRequestOptions) => void',
172+
description:
173+
'Form submission handler that automatically resets the input field and appends a user message. You can use the `options` parameter to send additional data, headers and more to the server.',
174+
},
175+
{
176+
name: 'isLoading',
177+
type: 'boolean',
178+
description:
179+
'Boolean flag indicating whether a request is currently in progress.',
180+
},
181+
{
182+
name: 'data',
183+
type: 'JSONValue[]',
184+
description: 'Data returned from experimental_StreamData',
185+
},
186+
{
187+
name: 'experimental_addToolResult',
188+
type: '({toolCallId: string; result: any;}) => void',
189+
description:
190+
'React only. Function to add a tool result to the chat. This will update the chat messages with the tool result and call the API route if all tool results for the last message are available.',
191+
},
192+
]}
193+
/>
23194

24-
### Solid
195+
## Learn more
25196

26-
<Snippet text={`import { useChat } from "ai/solid"`} prompt={false} />
27-
28-
<ReferenceTable packageName="react" functionName="useChat" />
197+
- [Chatbot](/docs/ai-sdk-ui/chatbot)
198+
- [Chatbot with Tools](/docs/ai-sdk-ui/chatbot-with-tool-calling)

‎examples/next-openai/app/api/use-chat-client-tool/route.ts

-31
This file was deleted.

‎examples/next-openai/app/api/use-chat-tool-result/route.ts

-34
This file was deleted.

‎examples/next-openai/app/api/use-chat-tool-result-roundtrip/route.ts ‎examples/next-openai/app/api/use-chat-tools/route.ts

+15-5
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,32 @@ export async function POST(req: Request) {
1010

1111
const result = await streamText({
1212
model: openai('gpt-4-turbo'),
13-
system:
14-
'You are a weather bot that can use the weather tool to get the weather in a given city. ' +
15-
'Respond to the user with weather information in a friendly and helpful manner.',
1613
messages: convertToCoreMessages(messages),
1714
tools: {
18-
weather: {
15+
// server-side tool with execute function:
16+
getWeatherInformation: {
1917
description: 'show the weather in a given city to the user',
2018
parameters: z.object({ city: z.string() }),
2119
execute: async ({}: { city: string }) => {
22-
// Random weather:
2320
const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
2421
return weatherOptions[
2522
Math.floor(Math.random() * weatherOptions.length)
2623
];
2724
},
2825
},
26+
// client-side tool that starts user interaction:
27+
askForConfirmation: {
28+
description: 'Ask the user for confirmation.',
29+
parameters: z.object({
30+
message: z.string().describe('The message to ask for confirmation.'),
31+
}),
32+
},
33+
// client-side tool that is automatically executed on the client:
34+
getLocation: {
35+
description:
36+
'Get the user location. Always ask for confirmation before using this tool.',
37+
parameters: z.object({}),
38+
},
2939
},
3040
});
3141

‎examples/next-openai/app/use-chat-tool-result-roundtrip/page.tsx

-34
This file was deleted.

‎examples/next-openai/app/use-chat-tool-result/page.tsx

-44
This file was deleted.

‎examples/next-openai/app/use-chat-client-tool/page.tsx ‎examples/next-openai/app/use-chat-tools/page.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@ export default function Chat() {
1010
handleInputChange,
1111
handleSubmit,
1212
experimental_addToolResult,
13-
} = useChat({ api: '/api/use-chat-client-tool' });
13+
} = useChat({
14+
api: '/api/use-chat-tools',
15+
experimental_maxAutomaticRoundtrips: 5,
16+
17+
// run client-side tools that are automatically executed:
18+
async onToolCall({ toolCall }) {
19+
if (toolCall.toolName === 'getLocation') {
20+
const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco'];
21+
return cities[Math.floor(Math.random() * cities.length)];
22+
}
23+
},
24+
});
1425

1526
return (
1627
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
@@ -21,7 +32,7 @@ export default function Chat() {
2132
{m.toolInvocations?.map((toolInvocation: ToolInvocation) => {
2233
const toolCallId = toolInvocation.toolCallId;
2334

24-
// render confirmation tool
35+
// render confirmation tool (client-side tool with user interaction)
2536
if (toolInvocation.toolName === 'askForConfirmation') {
2637
return (
2738
<div key={toolCallId} className="text-gray-500">
@@ -63,7 +74,7 @@ export default function Chat() {
6374
// other tools:
6475
return 'result' in toolInvocation ? (
6576
<div key={toolCallId} className="text-gray-500">
66-
<strong>{`${toolInvocation.toolName}: `}</strong>
77+
Tool call {`${toolInvocation.toolName}: `}
6778
{toolInvocation.result}
6879
</div>
6980
) : (

‎packages/core/react/use-chat.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useCallback, useEffect, useId, useRef, useState } from 'react';
22
import useSWR, { KeyedMutator } from 'swr';
3+
import { ToolCall as CoreToolCall } from '../core/generate-text/tool-call';
34
import { callChatApi } from '../shared/call-chat-api';
45
import { generateId as generateIdFunc } from '../shared/generate-id';
56
import { processChatStream } from '../shared/process-chat-stream';
@@ -95,6 +96,7 @@ const getStreamedResponse = async (
9596
streamMode?: 'stream-data' | 'text',
9697
onFinish?: (message: Message) => void,
9798
onResponse?: (response: Response) => void | Promise<void>,
99+
onToolCall?: UseChatOptions['onToolCall'],
98100
sendExtraMessageFields?: boolean,
99101
) => {
100102
// Do an optimistic update to the chat state to show the updated messages
@@ -206,6 +208,7 @@ const getStreamedResponse = async (
206208
mutate([...chatRequest.messages, ...merged], false);
207209
mutateStreamData([...(existingData || []), ...(data || [])], false);
208210
},
211+
onToolCall,
209212
onFinish,
210213
generateId,
211214
});
@@ -219,6 +222,7 @@ export function useChat({
219222
sendExtraMessageFields,
220223
experimental_onFunctionCall,
221224
experimental_onToolCall,
225+
onToolCall,
222226
experimental_maxAutomaticRoundtrips = 0,
223227
streamMode,
224228
onResponse,
@@ -331,6 +335,7 @@ By default, it's set to 0, which will disable the feature.
331335
streamMode,
332336
onFinish,
333337
onResponse,
338+
onToolCall,
334339
sendExtraMessageFields,
335340
),
336341
experimental_onFunctionCall,
@@ -390,6 +395,7 @@ By default, it's set to 0, which will disable the feature.
390395
sendExtraMessageFields,
391396
experimental_onFunctionCall,
392397
experimental_onToolCall,
398+
onToolCall,
393399
experimental_maxAutomaticRoundtrips,
394400
messagesRef,
395401
abortControllerRef,

‎packages/core/shared/call-chat-api.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { parseComplexResponse } from './parse-complex-response';
2-
import { IdGenerator, JSONValue, Message } from './types';
2+
import { IdGenerator, JSONValue, Message, UseChatOptions } from './types';
33
import { createChunkDecoder } from './utils';
44

55
export async function callChatApi({
@@ -14,6 +14,7 @@ export async function callChatApi({
1414
onResponse,
1515
onUpdate,
1616
onFinish,
17+
onToolCall,
1718
generateId,
1819
}: {
1920
api: string;
@@ -27,6 +28,7 @@ export async function callChatApi({
2728
onResponse?: (response: Response) => void | Promise<void>;
2829
onUpdate: (merged: Message[], data: JSONValue[] | undefined) => void;
2930
onFinish?: (message: Message) => void;
31+
onToolCall?: UseChatOptions['onToolCall'];
3032
generateId: IdGenerator;
3133
}) {
3234
const response = await fetch(api, {
@@ -111,6 +113,7 @@ export async function callChatApi({
111113
abortControllerRef:
112114
abortController != null ? { current: abortController() } : undefined,
113115
update: onUpdate,
116+
onToolCall,
114117
onFinish(prefixMap) {
115118
if (onFinish && prefixMap.text != null) {
116119
onFinish(prefixMap.text);

‎packages/core/shared/parse-complex-response.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import { generateId as generateIdFunction } from './generate-id';
22
import { readDataStream } from './read-data-stream';
3-
import type { FunctionCall, JSONValue, Message, ToolCall } from './types';
3+
import type {
4+
FunctionCall,
5+
JSONValue,
6+
Message,
7+
ToolCall,
8+
UseChatOptions,
9+
} from './types';
410

511
type PrefixMap = {
612
text?: Message;
13+
// @deprecated
714
function_call?: Message & {
815
role: 'assistant';
916
function_call: FunctionCall;
1017
};
18+
// @deprecated
1119
tool_calls?: Message & {
1220
role: 'assistant';
1321
tool_calls: ToolCall[];
@@ -27,6 +35,7 @@ export async function parseComplexResponse({
2735
reader,
2836
abortControllerRef,
2937
update,
38+
onToolCall,
3039
onFinish,
3140
generateId = generateIdFunction,
3241
getCurrentDate = () => new Date(),
@@ -36,6 +45,7 @@ export async function parseComplexResponse({
3645
current: AbortController | null;
3746
};
3847
update: (merged: Message[], data: JSONValue[] | undefined) => void;
48+
onToolCall?: UseChatOptions['onToolCall'];
3949
onFinish?: (prefixMap: PrefixMap) => void;
4050
generateId?: () => string;
4151
getCurrentDate?: () => Date;
@@ -85,6 +95,24 @@ export async function parseComplexResponse({
8595
}
8696

8797
prefixMap.text.toolInvocations.push(value);
98+
99+
// invoke the onToolCall callback if it exists. This is blocking.
100+
// In the future we should make this non-blocking, which
101+
// requires additional state management for error handling etc.
102+
if (onToolCall) {
103+
console.log('onToolCall', value);
104+
105+
const result = await onToolCall({ toolCall: value });
106+
107+
console.log('onToolCall result', result);
108+
109+
if (result != null) {
110+
// store the result in the tool invocation
111+
prefixMap.text.toolInvocations[
112+
prefixMap.text.toolInvocations.length - 1
113+
] = { ...value, result };
114+
}
115+
}
88116
} else if (type === 'tool_result') {
89117
// create message if it doesn't exist
90118
if (prefixMap.text == null) {

‎packages/core/shared/types.ts

+63-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { ToolResult as CoreToolResult } from '../core/generate-text/tool-result'
33

44
export * from './use-assistant-types';
55

6-
// https://github.com/openai/openai-node/blob/07b3504e1c40fd929f4aae1651b83afc19e3baf8/src/resources/chat/completions.ts#L146-L159
6+
/**
7+
* @deprecated use AI SDK 3.1 CoreTool / ToolResult instead
8+
*/
79
export interface FunctionCall {
810
/**
911
* The arguments to call the function with, as generated by the model in JSON
@@ -20,6 +22,8 @@ export interface FunctionCall {
2022
}
2123

2224
/**
25+
* @deprecated use AI SDK 3.1 CoreTool / ToolResult instead
26+
*
2327
* The tool calls generated by the model, such as function calls.
2428
*/
2529
export interface ToolCall {
@@ -40,6 +44,8 @@ export interface ToolCall {
4044
}
4145

4246
/**
47+
* @deprecated use AI SDK 3.1 CoreTool / ToolChoice instead
48+
*
4349
* Controls which (if any) function is called by the model.
4450
* - none means the model will not call a function and instead generates a message.
4551
* - auto means the model can pick between generating a message or calling a function.
@@ -52,6 +58,8 @@ export type ToolChoice =
5258
| { type: 'function'; function: { name: string } };
5359

5460
/**
61+
* @deprecated use AI SDK 3.1 CoreTool instead
62+
*
5563
* A list of tools the model may call. Currently, only functions are supported as a tool.
5664
* Use this to provide a list of functions the model may generate JSON inputs for.
5765
*/
@@ -60,6 +68,9 @@ export interface Tool {
6068
function: Function;
6169
}
6270

71+
/**
72+
* @deprecated use AI SDK 3.1 CoreTool instead
73+
*/
6374
export interface Function {
6475
/**
6576
* The name of the function to be called. Must be a-z, A-Z, 0-9, or contain
@@ -97,35 +108,54 @@ export type ToolInvocation =
97108
| CoreToolResult<string, any, any>;
98109

99110
/**
100-
* Shared types between the API and UI packages.
111+
* AI SDK UI Messages. They are used in the client and to communicate between the frontend and the API routes.
101112
*/
102113
export interface Message {
103114
id: string;
104-
tool_call_id?: string;
105115
createdAt?: Date;
116+
106117
content: string;
107118

119+
// @deprecated
120+
tool_call_id?: string;
121+
108122
/**
109123
@deprecated Use AI SDK RSC instead: https://sdk.vercel.ai/docs/ai-sdk-rsc
110124
*/
111125
ui?: string | JSX.Element | JSX.Element[] | null | undefined;
112126

113-
role: 'system' | 'user' | 'assistant' | 'function' | 'data' | 'tool';
127+
/**
128+
* `function` and `tool` roles are deprecated.
129+
*/
130+
role:
131+
| 'system'
132+
| 'user'
133+
| 'assistant'
134+
| 'function' // @deprecated
135+
| 'data'
136+
| 'tool'; // @deprecated
137+
114138
/**
115139
*
116140
* If the message has a role of `function`, the `name` field is the name of the function.
117141
* Otherwise, the name field should not be set.
118142
*/
119143
name?: string;
144+
120145
/**
146+
* @deprecated Use AI SDK 3.1 `toolInvocations` instead.
147+
*
121148
* If the assistant role makes a function call, the `function_call` field
122149
* contains the function call name and arguments. Otherwise, the field should
123150
* not be set. (Deprecated and replaced by tool_calls.)
124151
*/
125152
function_call?: string | FunctionCall;
126153

127154
data?: JSONValue;
155+
128156
/**
157+
* @deprecated Use AI SDK 3.1 `toolInvocations` instead.
158+
*
129159
* If the assistant role makes a tool call, the `tool_calls` field contains
130160
* the tool call name and arguments. Otherwise, the field should not be set.
131161
*/
@@ -155,15 +185,23 @@ export type ChatRequest = {
155185
// @deprecated
156186
function_call?: FunctionCall;
157187
data?: Record<string, string>;
188+
// @deprecated
158189
tools?: Array<Tool>;
190+
// @deprecated
159191
tool_choice?: ToolChoice;
160192
};
161193

194+
/**
195+
* @deprecated Use AI SDK 3.1 `streamText` and `onToolCall` instead.
196+
*/
162197
export type FunctionCallHandler = (
163198
chatMessages: Message[],
164199
functionCall: FunctionCall,
165200
) => Promise<ChatRequest | void>;
166201

202+
/**
203+
* @deprecated Use AI SDK 3.1 `streamText` and `onToolCall` instead.
204+
*/
167205
export type ToolCallHandler = (
168206
chatMessages: Message[],
169207
toolCalls: ToolCall[],
@@ -176,9 +214,13 @@ export type RequestOptions = {
176214

177215
export type ChatRequestOptions = {
178216
options?: RequestOptions;
217+
// @deprecated
179218
functions?: Array<Function>;
219+
// @deprecated
180220
function_call?: FunctionCall;
221+
// @deprecated
181222
tools?: Array<Tool>;
223+
// @deprecated
182224
tool_choice?: ToolChoice;
183225
data?: Record<string, string>;
184226
};
@@ -208,19 +250,36 @@ export type UseChatOptions = {
208250
initialInput?: string;
209251

210252
/**
253+
* @deprecated Use AI SDK 3.1 `streamText` and `onToolCall` instead.
254+
*
211255
* Callback function to be called when a function call is received.
212256
* If the function returns a `ChatRequest` object, the request will be sent
213257
* automatically to the API and will be used to update the chat.
214258
*/
215259
experimental_onFunctionCall?: FunctionCallHandler;
216260

217261
/**
262+
* @deprecated Use AI SDK 3.1 `streamText` and `onToolCall` instead.
263+
*
218264
* Callback function to be called when a tool call is received.
219265
* If the function returns a `ChatRequest` object, the request will be sent
220266
* automatically to the API and will be used to update the chat.
221267
*/
222268
experimental_onToolCall?: ToolCallHandler;
223269

270+
/**
271+
Optional callback function that is invoked when a tool call is received.
272+
Intended for automatic client-side tool execution.
273+
274+
You can optionally return a result for the tool call,
275+
either synchronously or asynchronously.
276+
*/
277+
onToolCall?: ({
278+
toolCall,
279+
}: {
280+
toolCall: CoreToolCall<string, unknown>;
281+
}) => void | Promise<unknown> | unknown;
282+
224283
/**
225284
* Callback function to be called when the API response is received.
226285
*/

0 commit comments

Comments
 (0)
Please sign in to comment.