Skip to content

Commit 0a710d8

Browse files
authored
feat (ui): typed tool parts in ui messages (vercel#6736)
## Background Working with tool invocations on the client was untyped and required several levels of resolution. ## Summary * replace `tool-invocation` parts with typed `tool-TOOL` parts in ui messages ## Verification * manually test tools calls in next-openai tools example ## Future Work * fix human in the loop example
1 parent 901df02 commit 0a710d8

File tree

33 files changed

+975
-1376
lines changed

33 files changed

+975
-1376
lines changed

.changeset/clean-ants-brake.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@ai-sdk/svelte': major
3+
'@ai-sdk/react': major
4+
'@ai-sdk/vue': major
5+
'ai': major
6+
---
7+
8+
feat (ui): typed tool parts in ui messages

examples/next-fastapi/app/(examples)/02-chat-data/page.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Card } from '@/app/components';
44
import { useChat } from '@ai-sdk/react';
5+
import { getToolName, isToolUIPart } from 'ai';
56
import { GeistMono } from 'geist/font/mono';
67
import { useState } from 'react';
78

@@ -18,23 +19,21 @@ export default function Page() {
1819

1920
<div className="flex flex-col gap-2">
2021
{message.parts.map((part, index) => {
21-
switch (part.type) {
22-
case 'text':
23-
return <div key={index}>{part.text}</div>;
24-
case 'tool-invocation': {
25-
return (
26-
<div
27-
key={index}
28-
className={`${GeistMono.className} text-sm text-zinc-500 bg-zinc-100 p-3 rounded-lg`}
29-
>
30-
{`${part.toolInvocation.toolName}(${JSON.stringify(
31-
part.toolInvocation.args,
32-
null,
33-
2,
34-
)})`}
35-
</div>
36-
);
37-
}
22+
if (part.type === 'text') {
23+
return <div key={index}>{part.text}</div>;
24+
} else if (isToolUIPart(part)) {
25+
return (
26+
<div
27+
key={index}
28+
className={`${GeistMono.className} text-sm text-zinc-500 bg-zinc-100 p-3 rounded-lg`}
29+
>
30+
{`${getToolName(part)}(${JSON.stringify(
31+
part.args,
32+
null,
33+
2,
34+
)})`}
35+
</div>
36+
);
3837
}
3938
})}
4039
</div>

examples/next-openai-pages/app/api/call-tool/route.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
1-
import { ToolInvocation, streamText } from 'ai';
1+
import { convertToModelMessages, streamText, UIMessage } from 'ai';
22
import { openai } from '@ai-sdk/openai';
33
import { z } from 'zod';
44

5-
interface Message {
6-
role: 'user' | 'assistant';
7-
content: string;
8-
toolInvocations?: ToolInvocation[];
9-
}
10-
115
export async function POST(req: Request) {
12-
const { messages }: { messages: Message[] } = await req.json();
6+
const { messages }: { messages: UIMessage[] } = await req.json();
137

148
const result = streamText({
159
model: openai('gpt-4'),
1610
system: 'You are a helpful assistant.',
17-
messages,
11+
messages: convertToModelMessages(messages),
1812
tools: {
1913
celsiusToFahrenheit: {
2014
description: 'Converts celsius to fahrenheit',

examples/next-openai-pages/app/api/call-tools-in-parallel/route.ts

Lines changed: 0 additions & 40 deletions
This file was deleted.

examples/next-openai-pages/app/api/generative-ui-route/route.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

examples/next-openai-pages/pages/generative-user-interface/route-components/index.tsx

Lines changed: 0 additions & 145 deletions
This file was deleted.

examples/next-openai-pages/pages/tools/call-tool/index.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useChat } from '@ai-sdk/react';
2-
import { DefaultChatTransport } from 'ai';
2+
import { DefaultChatTransport, isToolUIPart } from 'ai';
33
import { useState } from 'react';
44

55
export default function Page() {
@@ -17,16 +17,10 @@ export default function Page() {
1717
<strong>{`${message.role}: `}</strong>
1818

1919
{message.parts.map((part, index) => {
20-
switch (part.type) {
21-
case 'text':
22-
return <div key={index}>{part.text}</div>;
23-
case 'tool-invocation': {
24-
return (
25-
<div key={index}>
26-
{JSON.stringify(part.toolInvocation.args)}
27-
</div>
28-
);
29-
}
20+
if (part.type === 'text') {
21+
return <div key={index}>{part.text}</div>;
22+
} else if (isToolUIPart(part)) {
23+
return <div key={index}>{JSON.stringify(part.args)}</div>;
3024
}
3125
})}
3226
</div>

examples/next-openai-pages/pages/tools/call-tools-in-parallel/index.tsx

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)