OpenAI Assistants
The useAssistant
hook allows you to handle the client state when interacting with an OpenAI compatible assistant API.
This hook is useful when you want to integrate assistant capabilities into your application,
with the UI updated automatically as the assistant is streaming its execution.
The useAssistant
hook is supported in ai/react
, ai/svelte
, and ai/vue
.
Example
'use client';
import { Message, useAssistant } from 'ai/react';
export default function Chat() { const { status, messages, input, submitMessage, handleInputChange } = useAssistant({ api: '/api/assistant' });
return ( <div> {messages.map((m: Message) => ( <div key={m.id}> <strong>{`${m.role}: `}</strong> {m.role !== 'data' && m.content} {m.role === 'data' && ( <> {(m.data as any).description} <br /> <pre className={'bg-gray-200'}> {JSON.stringify(m.data, null, 2)} </pre> </> )} </div> ))}
{status === 'in_progress' && <div />}
<form onSubmit={submitMessage}> <input disabled={status !== 'awaiting_message'} value={input} placeholder="What is the temperature in the living room?" onChange={handleInputChange} /> </form> </div> );}
import { AssistantResponse } from 'ai';import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY || '',});
// Allow streaming responses up to 30 secondsexport const maxDuration = 30;
export async function POST(req: Request) { // Parse the request body const input: { threadId: string | null; message: string; } = await req.json();
// Create a thread if needed const threadId = input.threadId ?? (await openai.beta.threads.create({})).id;
// Add a message to the thread const createdMessage = await openai.beta.threads.messages.create(threadId, { role: 'user', content: input.message, });
return AssistantResponse( { threadId, messageId: createdMessage.id }, async ({ forwardStream, sendDataMessage }) => { // Run the assistant on the thread const runStream = openai.beta.threads.runs.stream(threadId, { assistant_id: process.env.ASSISTANT_ID ?? (() => { throw new Error('ASSISTANT_ID is not set'); })(), });
// forward run status would stream message deltas let runResult = await forwardStream(runStream);
// status can be: queued, in_progress, requires_action, cancelling, cancelled, failed, completed, or expired while ( runResult?.status === 'requires_action' && runResult.required_action?.type === 'submit_tool_outputs' ) { const tool_outputs = runResult.required_action.submit_tool_outputs.tool_calls.map( (toolCall: any) => { const parameters = JSON.parse(toolCall.function.arguments);
switch (toolCall.function.name) { // configure your tool calls here
default: throw new Error( `Unknown tool call function: ${toolCall.function.name}`, ); } }, );
runResult = await forwardStream( openai.beta.threads.runs.submitToolOutputsStream( threadId, runResult.id, { tool_outputs }, ), ); } }, );}
Customized UI
useAssistant
also provides ways to manage the chat message and input states via code and show loading and error states.
Loading and error states
To show a loading spinner while the assistant is running the thread, you can use the status
state returned by the useAssistant
hook:
const { status, ... } = useAssistant()
return( <> {status === "in_progress" ? <Spinner /> : null} </>)
Similarly, the error
state reflects the error object thrown during the fetch request. It can be used to display an error message, or show a toast notification:
const { error, ... } = useAssistant()
useEffect(() => { if (error) { toast.error(error.message) }}, [error])
// Or display the error message in the UI:return ( <> {error ? <div>{error.message}</div> : null} </>)
Controlled input
In the initial example, we have handleSubmit
and handleInputChange
callbacks that manage the input changes and form submissions. These are handy for common use cases, but you can also use uncontrolled APIs for more advanced scenarios such as form validation or customized components.
The following example demonstrates how to use more granular APIs like append
with your custom input and submit button components:
const { append } = useAssistant();
return ( <> <MySubmitButton onClick={() => { // Send a new message to the AI provider append({ role: 'user', content: input, }); }} /> </>);
Configure Request Options
By default, the useAssistant
hook sends a HTTP POST request to the /api/assistant
endpoint with the prompt as part of the request body. You can customize the request by passing additional options to the useAssistant
hook:
const { messages, input, handleInputChange, handleSubmit } = useAssistant({ api: '/api/custom-completion', headers: { Authorization: 'your_token', }, body: { user_id: '123', }, credentials: 'same-origin',});
In this example, the useAssistant
hook sends a POST request to the /api/custom-completion
endpoint with the specified headers, additional body fields, and credentials for that fetch request. On your server side, you can handle the request with these additional information.