Skip to content

Commit

Permalink
Merge branch 'lg/groq' of https://github.com/vercel/ai into lg/groq
Browse files Browse the repository at this point in the history
  • Loading branch information
lgrammel committed Oct 17, 2024
2 parents 19cb9a2 + 2609a38 commit 7fc6bfb
Show file tree
Hide file tree
Showing 20 changed files with 420 additions and 44 deletions.
51 changes: 51 additions & 0 deletions content/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,57 @@ const result = await generateText({
});
```

## Types

Modularizing your code often requires defining types to ensure type safety and reusability.
To enable this, the AI SDK provides several helper types for tools, tool calls, and tool results.

You can use them to strongly type your variables, function parameters, and return types
in parts of the code that are not directly related to `steamText` or `generateText`.

Each tool call is typed with `CoreToolCall<NAME extends string, ARGS>`, depending
on the tool that has been invoked.
Similarly, the tool results are typed with `CoreToolResult<NAME extends string, ARGS, RESULT>`.

The tools in `streamText` and `generateText` are defined as a `Record<string, CoreTool>`.
The type inference helpers `CoreToolCallUnion<TOOLS extends Record<string, CoreTool>>`
and `CoreToolResultUnion<TOOLS extends Record<string, CoreTool>>` can be used to
extract the tool call and tool result types from the tools.

```ts highlight="18-19,23-24"
import { openai } from '@ai-sdk/openai';
import { CoreToolCallUnion, CoreToolResultUnion, generateText, tool } from 'ai';
import { z } from 'zod';

const myToolSet = {
firstTool: tool({
description: 'Greets the user',
parameters: z.object({ name: z.string() }),
execute: async ({ name }) => `Hello, ${name}!`,
}),
secondTool: tool({
description: 'Tells the user their age',
parameters: z.object({ age: z.number() }),
execute: async ({ age }) => `You are ${age} years old!`,
}),
};

type MyToolCall = CoreToolCallUnion<typeof myToolSet>;
type MyToolResult = CoreToolResultUnion<typeof myToolSet>;

async function generateSomething(prompt: string): Promise<{
text: string;
toolCalls: Array<MyToolCall>; // typed tool calls
toolResults: Array<MyToolResult>; // typed tool results
}> {
return generateText({
model: openai('gpt-4o'),
tools: myToolSet,
prompt,
});
}
```

## Prompt Engineering with Tools

When you create prompts that include tools, getting good results can be tricky as the number and complexity of your tools increases.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: Save Messages
description: Learn to save messages in your Next.js application using the AI SDK
---

# Save Messages

You can save the chat to an external database using the `onFinish` callback handler present in the `streamText` function.

```ts filename="@/app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import { saveChat } from '@/utils/queries';
import { streamText, convertToCoreMessages } from 'ai';

export function POST(request) {
const { id, messages } = await request.json();

const coreMessages = convertToCoreMessages(messages);

const result = await streamText({
model: openai('gpt-4o'),
system: 'you are a friendly assistant!',
messages: coreMessages,
onFinish: async ({ responseMessages }) => {
try {
await saveChat({
id,
messages: [...coreMessages, ...responseMessages],
});
} catch (error) {
console.error('Failed to save chat');
}
},
});

return result.toDataStream();
}
```

The `saveChat` function is a server action that saves the chat to the database. It first checks if the chat already exists in the database and updates the chat if it does. If the chat does not exist, it inserts a new chat into the database.

<Note>
This example assumes that the database table schema has a `messages` column that stores the chat messages as a JSON string of type `Array<CoreMessage>`. You can modify the `saveChat` function to suit your database schema.
</Note>

```ts filename="@/app/utils/queries.ts"
export async function saveChat({
id,
messages,
userId,
}: {
id: string;
messages: any;
userId: string;
}) {
try {
const selectedChats = await db.select().from(chat).where(eq(chat.id, id));

if (selectedChats.length > 0) {
return await db
.update(chat)
.set({
messages: JSON.stringify(messages),
})
.where(eq(chat.id, id));
}

return await db.insert(chat).values({
id,
createdAt: new Date(),
messages: JSON.stringify(messages),
userId,
});
} catch (error) {
console.error('Failed to save chat in database');
throw error;
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: Restore Messages
description: Learn to restore chat in your Next.js application using the AI SDK
---

# Restore Messages

The `useChat` hook accepts `initialMessages` to load an earlier chat history of messages.

As messages are typically loaded from an external database, we can use server actions inside the Page component to fetch an already existing chat from the database during static generation and pass the messages as props to the `<Chat/>` component.

<Note>
This example assumes that the database table schema has a `messages` column that stores the chat messages as a JSON string of type `Array<CoreMessage>`. You can modify the `convertToUIMessages` function to suit your database schema, but here's an [example implementation](https://github.com/vercel/ai-chatbot/blob/00b125378c998d19ef60b73fe576df0fe5a0e9d4/lib/utils.ts).
</Note>

```tsx filename="@/app/chat/[id]/page.tsx"
import { Chat } from '@/app/components/chat';
import { getChatById } from '@/utils/queries';
import { convertToUIMessages } from '@/utils/functions';

export default async function Page({ params }: { params: any }) {
const { id } = params;
const chatFromDb = await getChatById({ id });

const chat: Chat = {
...chatFromDb,
messages: convertToUIMessages(chatFromDb.messages),
};

return <Chat key={id} id={chat.id} initialMessages={chat.messages} />;
}
```

In the above code snippet, we fetch the chat from the database using the `getChatById` server action and convert the messages to the format expected by `useChat` component using the `convertToUIMessages` function.

```tsx filename="@/app/components/chat.tsx"
'use client';

import { Message } from 'ai';
import { useChat } from 'ai/react';

export function Chat({
id,
initialMessages,
}: {
id;
initialMessages: Array<Message>;
}) {
const { messages } = useChat({
id,
initialMessages,
});

return (
<div>
{messages.map(message => (
<div key={message.id}>
<div>{message.role}</div>
<div>{message.content}</div>
</div>
))}
</div>
);
}
```

In the `Chat` component, we pass the `initialMessages` prop to the `useChat` hook to load the chat history. The `useChat` hook will then return the messages to be rendered in the chat window.
6 changes: 6 additions & 0 deletions content/examples/02-next-pages/11-state-management/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: State Management
description: Learn to manage state in your Next.js application using the AI SDK
---

In this section you will learn to manage state in your Next.js application using the AI SDK.
118 changes: 118 additions & 0 deletions content/examples/02-next-pages/12-interface/01-route-components.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
title: Route Components
description: Learn to route components using the AI SDK in your Next.js App Router application
---

Language models can call external tools to enhance responses beyond plain text. For weather queries, a React weather component could replace a text-only answer. This approach enables richer, interactive outputs. By combining AI language understanding with custom UI elements, we can create more useful and engaging interfaces. The key is thoughtfully matching user needs to appropriate visual or interactive components.

Let's create a weather tool that fetches weather data for a given location and return the result as props for our React component.

```ts filename="@/app/api/chat/route.ts"
import { z } from 'zod';
import { openai } from '@ai-sdk/openai';
import { getWeather } from '@/utils/queries';
import { streamText, convertToCoreMessages } from 'ai';

export function POST(request) {
const { messages } = await request.json();

const coreMessages = convertToCoreMessages(messages);

const result = await streamText({
model: openai('gpt-4o'),
system: 'you are a friendly assistant!',
messages: coreMessages,
tools: {
displayWeather: {
description: 'Display the weather for a location',
parameters: z.object({
latitude: z.number(),
longitude: z.number(),
}),
execute: async function ({ latitude, longitude }) {
const props = await getWeather({ latitude, longitude });
return props;
},
},
},
});

return result.toDataStream();
}
```

Now we can render the React component that uses the weather data returned by the `displayWeather` tool.

```tsx filename="@/components/chat.tsx"
'use client';

import { useChat } from 'ai/react';
import { Weather } from '@/components/weather';

export default function Chat() {
const { messages, input, setInput, handleSubmit } = useChat();

return (
<div>
{messages.map(message => (
<div key={message.id}>
<div>{message.role}</div>
<div>{message.content}</div>

<div>
{message.toolInvocations.map(toolInvocation => {
const { toolName, toolCallId, state } = toolInvocation;

if (state === 'result') {
const { result } = toolInvocation;

return (
<div key={toolCallId}>
{toolName === 'displayWeather' ? (
<Weather weatherAtLocation={result} />
) : null}
</div>
);
} else {
return (
<div key={toolCallId}>
{toolName === 'displayWeather' ? (
<div>Loading weather...</div>
) : null}
</div>
);
}
})}
</div>
</div>
))}

<form onSubmit={handleSubmit}>
<input
type="text"
value={input}
onChange={event => {
setInput(event.target.value);
}}
/>
<button type="submit">Send</button>
</form>
</div>
);
}
```

The `Weather` component can be a simple React component that displays the weather data.

```tsx filename="@/components/weather.tsx"
import React from 'react';

export function Weather({ weatherAtLocation }) {
const { value, unit } = weatherAtLocation;
return (
<div>
{value}°{unit}
</div>
);
}
```
6 changes: 6 additions & 0 deletions content/examples/02-next-pages/12-interface/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Generative User Interface
description: earn to build generative user interfaces using the AI SDK in your Next.js application
---

In this section you will learn to use the tool calling ability of language models to integrate more than just text responses to your application.
33 changes: 33 additions & 0 deletions examples/ai-core/src/types/tool-set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { openai } from '@ai-sdk/openai';
import { CoreToolCallUnion, CoreToolResultUnion, generateText, tool } from 'ai';
import { z } from 'zod';

const myToolSet = {
firstTool: tool({
description: 'Greets the user',
parameters: z.object({ name: z.string() }),
execute: async ({ name }) => `Hello, ${name}!`,
}),
secondTool: tool({
description: 'Tells the user their age',
parameters: z.object({ age: z.number() }),
execute: async ({ age }) => `You are ${age} years old!`,
}),
};

type MyToolCall = CoreToolCallUnion<typeof myToolSet>;
type MyToolResult = CoreToolResultUnion<typeof myToolSet>;

async function generateSomething(prompt: string): Promise<{
text: string;
toolCalls: Array<MyToolCall>; // typed tool calls
toolResults: Array<MyToolResult>; // typed tool results
}> {
return generateText({
model: openai('gpt-4o'),
tools: myToolSet,
prompt,
});
}

const { text, toolCalls, toolResults } = await generateSomething('...');
6 changes: 6 additions & 0 deletions packages/ai/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# ai

## 3.4.14

### Patch Changes

- e930f40: feat (ai/core): expose core tool result and tool call types

## 3.4.13

### Patch Changes
Expand Down
Loading

0 comments on commit 7fc6bfb

Please sign in to comment.