Skip to content

Commit

Permalink
feat (ui/solid): useAssistant, useObject, fix deep object updating (#…
Browse files Browse the repository at this point in the history
…3829)

Co-authored-by: Ian Pascoe <ian.g.pascoe@gmail.com>
  • Loading branch information
lgrammel and ian-pascoe authored Nov 21, 2024
1 parent b78b7fe commit 88b364b
Show file tree
Hide file tree
Showing 34 changed files with 1,569 additions and 98 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-rats-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/ui-utils': patch
---

fix (ui-utils): deep clone messages
5 changes: 5 additions & 0 deletions .changeset/chilled-carrots-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/solid': patch
---

feat (ui/solid): add useObject
5 changes: 5 additions & 0 deletions .changeset/ten-emus-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/solid': patch
---

feat (ui/solid): add useAssistant
5 changes: 5 additions & 0 deletions .changeset/young-squids-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/solid': patch
---

fix (ui/solid): fix useChat deep object updates
4 changes: 2 additions & 2 deletions content/docs/04-ai-sdk-ui/01-overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ Here is a comparison of the supported functions across these frameworks:
| [useChat](/docs/reference/ai-sdk-ui/use-chat) | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
| [useChat](/docs/reference/ai-sdk-ui/use-chat) attachments | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
| [useCompletion](/docs/reference/ai-sdk-ui/use-completion) | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
| [useObject](/docs/reference/ai-sdk-ui/use-object) | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
| [useAssistant](/docs/reference/ai-sdk-ui/use-assistant) | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
| [useObject](/docs/reference/ai-sdk-ui/use-object) | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Check size={18} /> |
| [useAssistant](/docs/reference/ai-sdk-ui/use-assistant) | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |

<Note>
[Contributions](https://github.com/vercel/ai/blob/main/CONTRIBUTING.md) are
Expand Down
5 changes: 4 additions & 1 deletion content/docs/07-reference/02-ai-sdk-ui/03-use-object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ description: API reference for the useObject hook.

# `experimental_useObject()`

<Note>`useObject` is an experimental feature and only available in React.</Note>
<Note>
`useObject` is an experimental feature and only available in React and
SolidJS.
</Note>

Allows you to consume text streams that represent a JSON object and parse them into a complete object based on a schema.
You can use it together with [`streamObject`](/docs/reference/ai-sdk-core/stream-object) in the backend.
Expand Down
4 changes: 0 additions & 4 deletions content/docs/07-reference/02-ai-sdk-ui/20-use-assistant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ with the UI updated automatically as the assistant is streaming its execution.

This works in conjunction with [`AssistantResponse`](./assistant-response) in the backend.

<Note>
`useAssistant` is supported in `ai/react`, `ai/svelte`, and `ai/vue`.
</Note>

## Import

<Tabs items={['React', 'Svelte']}>
Expand Down
4 changes: 2 additions & 2 deletions content/docs/07-reference/02-ai-sdk-ui/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ Here is a comparison of the supported functions across these frameworks:
| [useChat](/docs/reference/ai-sdk-ui/use-chat) | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
| [useChat](/docs/reference/ai-sdk-ui/use-chat) attachments | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
| [useCompletion](/docs/reference/ai-sdk-ui/use-completion) | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
| [useObject](/docs/reference/ai-sdk-ui/use-object) | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
| [useAssistant](/docs/reference/ai-sdk-ui/use-assistant) | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
| [useObject](/docs/reference/ai-sdk-ui/use-object) | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Check size={18} /> |
| [useAssistant](/docs/reference/ai-sdk-ui/use-assistant) | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |

<Note>
[Contributions](https://github.com/vercel/ai/blob/main/CONTRIBUTING.md) are
Expand Down
3 changes: 3 additions & 0 deletions examples/next-openai/app/api/use-chat-tools/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export async function POST(req: Request) {
description: 'show the weather in a given city to the user',
parameters: z.object({ city: z.string() }),
execute: async ({}: { city: string }) => {
// Add artificial delay of 2 seconds
await new Promise(resolve => setTimeout(resolve, 2000));

const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
return weatherOptions[
Math.floor(Math.random() * weatherOptions.length)
Expand Down
2 changes: 1 addition & 1 deletion examples/next-openai/app/api/use-object/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function POST(req: Request) {
const context = await req.json();

const result = streamObject({
model: openai('gpt-4-turbo'),
model: openai('gpt-4o'),
prompt: `Generate 3 notifications for a messages app in this context: ${context}`,
schema: notificationSchema,
});
Expand Down
13 changes: 12 additions & 1 deletion examples/nuxt-openai/server/api/use-chat-streamdata.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createOpenAI } from '@ai-sdk/openai';
import { StreamData, streamText } from 'ai';
import { generateId, StreamData, streamText } from 'ai';

export default defineLazyEventHandler(async () => {
const openai = createOpenAI({
Expand All @@ -16,8 +16,19 @@ export default defineLazyEventHandler(async () => {
const result = streamText({
model: openai('gpt-4o'),
messages,
onChunk() {
data.appendMessageAnnotation({ chunk: '123' });
},
onFinish() {
// message annotation:
data.appendMessageAnnotation({
id: generateId(), // e.g. id from saved DB record
other: 'information',
});

// call annotation:
data.append('call completed');

data.close();
},
});
Expand Down
3 changes: 3 additions & 0 deletions examples/nuxt-openai/server/api/use-chat-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export default defineLazyEventHandler(async () => {
description: 'show the weather in a given city to the user',
parameters: z.object({ city: z.string() }),
execute: async ({}: { city: string }) => {
// Add artificial delay of 2 seconds
await new Promise(resolve => setTimeout(resolve, 2000));

const weatherOptions = [
'sunny',
'cloudy',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { openai } from '@ai-sdk/openai';
import { APIEvent } from '@solidjs/start/server';
import { StreamData, streamText } from 'ai';
import { generateId, StreamData, streamText } from 'ai';

export const POST = async (event: APIEvent) => {
const { messages } = await event.request.json();
Expand All @@ -12,8 +12,19 @@ export const POST = async (event: APIEvent) => {
const result = streamText({
model: openai('gpt-4o'),
messages,
onChunk() {
data.appendMessageAnnotation({ chunk: '123' });
},
onFinish() {
// message annotation:
data.appendMessageAnnotation({
id: generateId(), // e.g. id from saved DB record
other: 'information',
});

// call annotation:
data.append('call completed');

data.close();
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ export const POST = async (event: APIEvent) => {
const { messages } = await event.request.json();

const result = streamText({
model: openai('gpt-4-turbo'),
model: openai('gpt-4o-mini'),
messages,
tools: {
// server-side tool with execute function:
getWeatherInformation: {
description: 'show the weather in a given city to the user',
parameters: z.object({ city: z.string() }),
execute: async ({}: { city: string }) => {
execute: async ({ city }: { city: string }) => {
// Add artificial delay of 2 seconds
await new Promise(resolve => setTimeout(resolve, 2000));

const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
return weatherOptions[
Math.floor(Math.random() * weatherOptions.length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const POST = async (event: APIEvent) => {
const currentMessage = messages[messages.length - 1];

const result = streamText({
model: openai('gpt-4o'),
model: openai('gpt-4o-mini'),
messages: [
...initialMessages,
{
Expand Down
19 changes: 19 additions & 0 deletions examples/solidstart-openai/src/routes/api/use-object/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { openai } from '@ai-sdk/openai';
import { streamObject } from 'ai';
import { notificationSchema } from './schema';
import { APIHandler } from '@solidjs/start/server';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export const POST: APIHandler = async ({ request }) => {
const context = await request.json();

const result = streamObject({
model: openai('gpt-4o'),
prompt: `Generate 3 notifications for a messages app in this context: ${context}`,
schema: notificationSchema,
});

return result.toTextStreamResponse();
};
16 changes: 16 additions & 0 deletions examples/solidstart-openai/src/routes/api/use-object/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DeepPartial } from 'ai';
import { z } from 'zod';

// define a schema for the notifications
export const notificationSchema = z.object({
notifications: z.array(
z.object({
name: z.string().describe('Name of a fictional person.'),
message: z.string().describe('Message. Do not use emojis or links.'),
minutesAgo: z.number(),
}),
),
});

// define a type for the partial notifications during generation
export type PartialNotification = DeepPartial<typeof notificationSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export default function Chat() {
<strong>{`${m.role}: `}</strong>
{m.content}
<br />
<Show when={m.annotations}>
<strong>Annotations:</strong>
<pre class="p-4 text-sm bg-gray-100">
{JSON.stringify(m.annotations, null, 2)}
</pre>
</Show>
<br />
<br />
</div>
)}
Expand Down
71 changes: 71 additions & 0 deletions examples/solidstart-openai/src/routes/use-object/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { experimental_useObject as useObject } from '@ai-sdk/solid';
import { notificationSchema } from '../api/use-object/schema';
import { For, Show } from 'solid-js';

export default function Page() {
const { submit, isLoading, object, stop, error } = useObject({
api: '/api/use-object',
schema: notificationSchema,
});

return (
<div class="flex flex-col items-center min-h-screen p-4 m-4">
<button
class="px-4 py-2 mt-4 text-white bg-blue-500 rounded-md disabled:bg-blue-200"
onClick={async () => {
submit('Messages during finals week.');
}}
disabled={isLoading()}
>
Generate notifications
</button>

<Show when={error()}>
{error => (
<div class="mt-4 text-red-500">
An error occurred. {error()?.message}
</div>
)}
</Show>

<Show when={isLoading()}>
<div class="mt-4 text-gray-500">
<div>Loading...</div>
<button
type="button"
class="px-4 py-2 mt-4 text-blue-500 border border-blue-500 rounded-md"
onClick={() => stop()}
>
STOP
</button>
</div>
</Show>

<div class="flex flex-col gap-4 mt-4">
<For each={object()?.notifications}>
{(notification, index) => (
<div
class="flex items-start gap-4 p-4 bg-gray-100 rounded-md dark:bg-gray-800"
data-index={index()}
>
<div class="flex-1 space-y-1">
<div class="flex items-center justify-between">
<p class="font-medium dark:text-white">
{notification?.name}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400">
{notification?.minutesAgo}
{notification?.minutesAgo != null ? ' minutes ago' : ''}
</p>
</div>
<p class="text-gray-700 dark:text-gray-300">
{notification?.message}
</p>
</div>
</div>
)}
</For>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createOpenAI } from '@ai-sdk/openai';
import { StreamData, streamText } from 'ai';
import { generateId, StreamData, streamText } from 'ai';

import { env } from '$env/dynamic/private';
// You may want to replace the above with a static private env variable
Expand All @@ -23,8 +23,19 @@ export const POST = (async ({ request }) => {
const result = streamText({
model: openai('gpt-4o'),
messages,
onChunk() {
data.appendMessageAnnotation({ chunk: '123' });
},
onFinish() {
// message annotation:
data.appendMessageAnnotation({
id: generateId(), // e.g. id from saved DB record
other: 'information',
});

// call annotation:
data.append('call completed');

data.close();
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const POST = (async ({ request }) => {
description: 'show the weather in a given city to the user',
parameters: z.object({ city: z.string() }),
execute: async ({}: { city: string }) => {
// Add artificial delay of 2 seconds
await new Promise(resolve => setTimeout(resolve, 2000));

const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
return weatherOptions[
Math.floor(Math.random() * weatherOptions.length)
Expand Down
8 changes: 5 additions & 3 deletions packages/solid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
],
"dependencies": {
"@ai-sdk/provider-utils": "2.0.1",
"@ai-sdk/ui-utils": "1.0.1"
"@ai-sdk/ui-utils": "1.0.1",
"@solid-primitives/trigger": "^1.1.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/user-event": "^14.5.2",
"@solidjs/testing-library": "0.8.10",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/user-event": "^14.5.1",
"@types/node": "^18",
"@vercel/ai-tsconfig": "workspace:*",
"@vitejs/plugin-vue": "5.2.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/solid/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './use-chat';
export * from './use-completion';
export * from './use-object';
export * from './use-assistant';
Loading

0 comments on commit 88b364b

Please sign in to comment.