Skip to content

Commit

Permalink
feat (ui/react): add experimental_useObject hook (#2019)
Browse files Browse the repository at this point in the history
  • Loading branch information
lgrammel authored Jun 19, 2024
1 parent 520fb2d commit 008725e
Show file tree
Hide file tree
Showing 37 changed files with 1,070 additions and 60 deletions.
5 changes: 5 additions & 0 deletions .changeset/clever-numbers-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'ai': patch
---

feat (ai): add textStream, toTextStreamResponse(), and pipeTextStreamToResponse() to streamObject
5 changes: 5 additions & 0 deletions .changeset/metal-dots-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/react': patch
---

feat (@ai-sdk/react): add experimental_useObject to @ai-sdk/react
5 changes: 5 additions & 0 deletions .changeset/tough-chicken-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/ui-utils': patch
---

chore (@ai-sdk/ui-utils): move functions
8 changes: 5 additions & 3 deletions content/docs/05-ai-sdk-ui/01-overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ Vercel AI SDK UI is designed to help you build interactive chat, completion, and

Vercel AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently. With three main hooks — **`useChat`**, **`useCompletion`**, and **`useAssistant`** — you can incorporate real-time chat capabilities, text completions, and interactive assistant features into your app.

- **`useChat`** offers real-time streaming of chat messages, abstracting state management for inputs, messages, loading, and errors, allowing for seamless integration into any UI design.
- **`useCompletion`** enables you to handle text completions in your applications, managing chat input state and automatically updating the UI as new completions are streamed from your AI provider.
- **`useAssistant`** is designed to facilitate interaction with OpenAI-compatible assistant APIs, managing UI state and updating it automatically as responses are streamed.
- **[`useChat`](/docs/reference/ai-sdk-ui/use-chat)** offers real-time streaming of chat messages, abstracting state management for inputs, messages, loading, and errors, allowing for seamless integration into any UI design.
- **[`useCompletion`](/docs/reference/ai-sdk-ui/use-completion)** enables you to handle text completions in your applications, managing chat input state and automatically updating the UI as new completions are streamed from your AI provider.
- **[`useObject`](/docs/reference/ai-sdk-ui/use-object)** is a hook that allows you to consume streamed JSON objects, providing a simple way to handle and display structured data in your application.
- **[`useAssistant`](/docs/reference/ai-sdk-ui/use-assistant)** is designed to facilitate interaction with OpenAI-compatible assistant APIs, managing UI state and updating it automatically as responses are streamed.

These hooks are designed to reduce the complexity and time required to implement AI interactions, letting you focus on creating exceptional user experiences.

Expand All @@ -25,6 +26,7 @@ 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) tool calling | <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} /> | <Cross size={18} /> | <Cross size={18} /> |

<Note>
Expand Down
115 changes: 115 additions & 0 deletions content/docs/05-ai-sdk-ui/08-object-generation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
title: Object Generation
description: Learn how to use the useObject hook.
---

# Object Generation

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

The [`useObject`](/docs/reference/ai-sdk-ui/use-object) hook allows you to create interfaces that represent a structured JSON object that is being streamed.

In this guide, you will learn how to use the `useObject` hook in your application to generate UIs for structured data on the fly.

## Example

The example shows a small notfications demo app that generates fake notifications in real-time.

### Schema

It is helpful to set up the schema in a separate file that is imported on both the client and server.

```ts filename='app/api/use-object/schema.ts'
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>;
```

### Client

The client uses [`useObject`](/docs/reference/ai-sdk-ui/use-object) to stream the object generation process.

The results are partial and are displayed as they are received.
Please note the code for handling `undefined` values in the JSX.

```tsx filename='app/page.tsx'
'use client';

import { experimental_useObject as useObject } from '@ai-sdk/react';
import { notificationSchema } from './api/use-object/schema';

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

return (
<div>
<button
onClick={async () => {
setInput('Messages during finals week.');
}}
>
Generate notifications
</button>

<div>
{object?.notifications?.map((notification, index) => (
<div key={index}>
<div>
<div>
<p>{notification?.name}</p>
<p>
{notification?.minutesAgo}
{notification?.minutesAgo != null ? ' minutes ago' : ''}
</p>
</div>
<p>{notification?.message}</p>
</div>
</div>
))}
</div>
</div>
);
}
```

### Server

On the server, we use [`streamObject`](/docs/reference/ai-sdk-core/stream-object) to stream the object generation process.

```typescript filename='app/api/use-object/route.ts'
import { openai } from '@ai-sdk/openai'
import { streamObject } from 'ai'
import { notificationSchema } from './schema'

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

export async function POST(req: Request) {
const context = await req.json()

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

return result.toTextStreamResponse()
}
```
5 changes: 5 additions & 0 deletions content/docs/05-ai-sdk-ui/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ description: Learn about the Vercel AI SDK UI.
description: 'Learn how to integrate an interface for text completion.',
href: '/docs/ai-sdk-ui/completion',
},
{
title: 'Object Generation',
description: 'Learn how to integrate an interface for object generation.',
href: '/docs/ai-sdk-ui/object-generation',
},
{
title: 'OpenAI Assistants',
description: 'Learn how to integrate an interface for OpenAI Assistants.',
Expand Down
43 changes: 40 additions & 3 deletions content/docs/07-reference/ai-sdk-core/04-stream-object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -429,12 +429,19 @@ for await (const partialObject of partialObjectStream) {
name: 'partialObjectStream',
type: 'AsyncIterableStream<DeepPartial<T>>',
description:
'Note that the partial object is not validated. If you want to be certain that the actual content matches your schema, you need to implement your own validation for partial results.',
'Stream of partial objects. It gets more complete as the stream progresses. Note that the partial object is not validated. If you want to be certain that the actual content matches your schema, you need to implement your own validation for partial results.',
},
{
name: 'textStream',
type: 'AsyncIterableStream<string>',
description:
'Text stream of the JSON representation of the generated object. It contains text chunks. When the stream is finished, the object is valid JSON that can be parsed.',
},
{
name: 'fullStream',
type: 'AsyncIterableStream<ObjectStreamPart<T>>',
description: 'The full stream of the object.',
description:
'Stream of different types of events, including partial objects, errors, and finish events.',
properties: [
{
type: 'ObjectPart',
Expand All @@ -450,6 +457,20 @@ for await (const partialObject of partialObjectStream) {
},
],
},
{
type: 'TextDeltaPart',
parameters: [
{
name: 'type',
type: "'text-delta'",
},
{
name: 'textDelta',
type: 'string',
description: 'The text delta for the underlying raw JSON text.',
},
],
},
{
type: 'ErrorPart',
parameters: [
Expand Down Expand Up @@ -514,6 +535,18 @@ for await (const partialObject of partialObjectStream) {
description:
'Warnings from the model provider (e.g. unsupported settings).',
},
{
name: 'pipeTextStreamToResponse',
type: '(response: ServerResponse, init?: { headers?: Record<string, string>; status?: number } => void',
description:
'Writes text delta output to a Node.js response-like object. It sets a `Content-Type` header to `text/plain; charset=utf-8` and writes each text delta as a separate chunk.',
},
{
name: 'toTextStreamResponse',
type: '(init?: ResponseInit) => Response',
description:
'Creates a simple text stream response. Each text delta is encoded as UTF-8 and sent as a separate chunk. Non-text-delta events are ignored.',
},
]}
/>

Expand All @@ -522,9 +555,13 @@ for await (const partialObject of partialObjectStream) {
<ExampleLinks
examples={[
{
title: 'Streaming Object Generation (Next.js App Router)',
title: 'Streaming Object Generation with RSC',
link: '/examples/next-app/basics/streaming-object-generation',
},
{
title: 'Streaming Object Generation with useObject',
link: '/examples/next-pages/basics/streaming-object-generation',
},
{
title: 'Streaming Partial Objects',
link: '/examples/node/streaming-structured-data/stream-object',
Expand Down
2 changes: 1 addition & 1 deletion content/docs/07-reference/ai-sdk-ui/02-use-completion.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: API reference for the useCompletion hook.

# `useCompletion()`

Allows you to create text completion based capibilities for your application. It enables the streaming of text completions from your AI provider, manages the state for chat input, and updates the UI automatically as new messages are received.
Allows you to create text completion based capabilities for your application. It enables the streaming of text completions from your AI provider, manages the state for chat input, and updates the UI automatically as new messages are received.

## Import

Expand Down
107 changes: 107 additions & 0 deletions content/docs/07-reference/ai-sdk-ui/03-use-object.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
title: useObject
description: API reference for the useObject hook.
---

# `experimental_useObject()`

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

Allows you to consume text streams that represent a JSON object and parse them into a complete object based on a Zod schema.
You can use it together with [`streamObject`](/docs/reference/ai-sdk-core/stream-object) in the backend.

```tsx
'use client';

import { experimental_useObject as useObject } from '@ai-sdk/react';

export default function Page() {
const { setInput, object } = useObject({
api: '/api/use-object',
schema: z.object({ content: z.string() }),
});

return (
<div>
<button onClick={() => setInput('example input')}>Generate</button>
{object?.content && <p>{object.content}</p>}
</div>
);
}
```

## Import

<Snippet
text="import { experimental_useObject as useObject } from '@ai-sdk/react'"
dark
prompt={false}
/>

## API Signature

### Parameters

<PropertiesTable
content={[
{
name: 'api',
type: 'string',
description:
'The API endpoint. It should stream JSON that matches the schema as chunked text.',
},
{
name: 'schema',
type: 'ZodSchema<RESULT>',
description:
'A Zod schema that defines the shape of the complete object.',
},
{
name: 'id?',
type: 'string',
description:
'Allows you to consume text streams that represent a JSON object and parse them into a complete object based on a Zod schema.',
},
{
name: 'initialValue?',
type: 'DeepPartial<RESULT> | undefined',
description: 'An optional value for the initial object.',
},
]}
/>

### Returns

<PropertiesTable
content={[
{
name: 'setInput',
type: '(input: INPUT) => void',
description: 'Calls the API with the provided input as JSON body.',
},
{
name: 'object',
type: 'DeepPartial<RESULT> | undefined',
description:
'The current value for the generated object. Updated as the API streams JSON chunks.',
},
{
name: 'error',
type: 'undefined | unknown',
description: 'The error object if the API call fails.',

}

]}
/>

## Examples

<ExampleLinks
examples={[
{
title: 'Streaming Object Generation with useObject',
link: '/examples/next-pages/basics/streaming-object-generation',
},
]}
/>
8 changes: 7 additions & 1 deletion content/docs/07-reference/ai-sdk-ui/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ AI SDK UI contains the following hooks:
'Use a hook to interact with language models in a completion interface.',
href: '/docs/reference/ai-sdk-ui/use-completion',
},
{
title: 'useObject',
description: 'Use a hook for consuming a streamed JSON objects.',
href: '/docs/reference/ai-sdk-ui/use-object',
},
{
title: 'useAssistant',
description: 'Use a hook to interact with OpenAI assistants.',
Expand All @@ -46,14 +51,15 @@ It also contains the following helper functions:

## UI Framework Support

AI SDK UI supports several frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), [Vue.js](https://vuejs.org/), and [SolidJS](https://www.solidjs.com/).
AI SDK UI supports the following frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), [Vue.js](https://vuejs.org/), and [SolidJS](https://www.solidjs.com/).
Here is a comparison of the supported functions across these frameworks:

| Function | React | Svelte | Vue.js | SolidJS |
| ---------------------------------------------------------- | ------------------- | ------------------- | ------------------- | ------------------- |
| [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) tool calling | <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} /> | <Cross size={18} /> | <Cross size={18} /> |

<Note>
Expand Down
Loading

0 comments on commit 008725e

Please sign in to comment.