Breaking Changes from AI SDK 4
// ❌ AI SDK 4 (OLD) import { useChat } from "ai"; const { messages, handleSubmit, input, handleInputChange } = useChat({ api: "/api/chat", });
// ✅ AI SDK 5 (NEW) import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai";
const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat" }), });
Client Setup
import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import { useState } from "react";
export function Chat() { const [input, setInput] = useState("");
const { messages, sendMessage, isLoading, error } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat" }), });
const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!input.trim()) return; sendMessage({ text: input }); setInput(""); };
return ( <div> <div> {messages.map((message) => ( <Message key={message.id} message={message} /> ))} </div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
{error && <div>Error: {error.message}</div>}
</div>
); }
UIMessage Structure (v5)
// ❌ Old: message.content was a string // ✅ New: message.parts is an array
interface UIMessage { id: string; role: "user" | "assistant" | "system"; parts: MessagePart[]; }
type MessagePart = | { type: "text"; text: string } | { type: "image"; image: string } | { type: "tool-call"; toolCallId: string; toolName: string; args: unknown } | { type: "tool-result"; toolCallId: string; result: unknown };
// Extract text from parts function getMessageText(message: UIMessage): string { return message.parts .filter((part): part is { type: "text"; text: string } => part.type === "text") .map((part) => part.text) .join(""); }
// Render message function Message({ message }: { message: UIMessage }) { return ( <div className={message.role === "user" ? "user" : "assistant"}> {message.parts.map((part, index) => { if (part.type === "text") { return <p key={index}>{part.text}</p>; } if (part.type === "image") { return <img key={index} src={part.image} alt="" />; } return null; })} </div> ); }
Server-Side (Route Handler)
// app/api/chat/route.ts import { openai } from "@ai-sdk/openai"; import { streamText } from "ai";
export async function POST(req: Request) { const { messages } = await req.json();
const result = await streamText({ model: openai("gpt-4o"), messages, system: "You are a helpful assistant.", });
return result.toDataStreamResponse(); }
With LangChain
// app/api/chat/route.ts import { toUIMessageStream } from "@ai-sdk/langchain"; import { ChatOpenAI } from "@langchain/openai"; import { HumanMessage, AIMessage } from "@langchain/core/messages";
export async function POST(req: Request) { const { messages } = await req.json();
const model = new ChatOpenAI({ modelName: "gpt-4o", streaming: true, });
// Convert UI messages to LangChain format const langchainMessages = messages.map((m) => { const text = m.parts .filter((p) => p.type === "text") .map((p) => p.text) .join(""); return m.role === "user" ? new HumanMessage(text) : new AIMessage(text); });
const stream = await model.stream(langchainMessages);
return toUIMessageStream(stream).toDataStreamResponse(); }
Streaming with Tools
import { openai } from "@ai-sdk/openai"; import { streamText, tool } from "ai"; import { z } from "zod";
const result = await streamText({ model: openai("gpt-4o"), messages, tools: { getWeather: tool({ description: "Get weather for a location", parameters: z.object({ location: z.string().describe("City name"), }), execute: async ({ location }) => { // Fetch weather data return { temperature: 72, condition: "sunny" }; }, }), }, });
useCompletion (Text Generation)
import { useCompletion } from "@ai-sdk/react"; import { DefaultCompletionTransport } from "ai";
const { completion, complete, isLoading } = useCompletion({ transport: new DefaultCompletionTransport({ api: "/api/complete" }), });
// Trigger completion await complete("Write a haiku about");
Error Handling
const { error, messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat" }), onError: (error) => { console.error("Chat error:", error); toast.error("Failed to send message"); }, });
// Display error {error && ( <div className="error"> {error.message} <button onClick={() => sendMessage({ text: lastInput })}> Retry </button> </div> )}