Vercel AI SDK v5
Production patterns for AI chatbots with persistence, generative UI, and streaming.
Quick Reference
Need Reference
Database schema & persistence references/persistence.md
Tools & generative UI references/tools-and-generative-ui.md
Custom data streaming references/streaming.md
Type definitions references/types.md
Anthropic + reasoning references/anthropic-config.md
Working examples cookbook/ directory
Core Architecture
Message Flow
Client (useChat) → API Route (streamText) → DB (Drizzle) ↑ ↓ └── UIMessageStream ←┘
Key Imports
// Core import { streamText, convertToModelMessages, UIMessage } from 'ai' import { createUIMessageStream, createUIMessageStreamResponse } from 'ai'
// Client import { useChat } from '@ai-sdk/react' import { DefaultChatTransport } from 'ai'
// Provider import { anthropic } from '@ai-sdk/anthropic'
Decision Tree
Streaming Response Type
-
Simple text streaming → streamText().toUIMessageStreamResponse()
-
Need custom data parts → createUIMessageStream()
- writer.write()
- Need both → createUIMessageStream()
- writer.merge(result.toUIMessageStream())
Tool Execution Location
-
Has server-side data/secrets → Server tool with execute
-
Needs client confirmation → Client tool (no execute) + addToolOutput
-
Auto-runs on client → Client tool + onToolCall handler
Data Attachment
-
Message-level info (tokens, model, timestamps) → messageMetadata
-
Dynamic content in message → Data parts with writer.write()
-
Temporary status (not persisted) → Transient data parts
Naming Convention
This skill uses agents instead of chats for all database tables, routes, and methods:
-
Table: agents (not chats )
-
Foreign keys: agentId (not chatId )
-
Actions: createAgent() , loadAgent() , deleteAgent()
File Organization
Follow feature-based architecture:
features/ └── agents/ ├── data/ │ └── get-agent.ts ├── actions/ │ └── create-agent.ts ├── types/ │ └── message.ts └── components/ ├── server/ │ └── agent-messages.tsx └── client/ └── chat-input.tsx
db/ ├── schema.ts # Table definitions ├── relations.ts # Drizzle relations └── actions.ts # DB operations (upsert, load, delete)
Essential Patterns
- Send Only Last Message
// Client transport: new DefaultChatTransport({ api: '/api/agent', prepareSendMessagesRequest: ({ messages, id }) => ({ body: { message: messages.at(-1), agentId: id } }) })
// Server: Load history, append new message const previous = await loadAgent(agentId) const messages = [...previous, message]
- Persist on Finish
return result.toUIMessageStreamResponse({ originalMessages: messages, onFinish: async ({ messages }) => { await upsertMessages({ agentId, messages }) } })
- Handle Disconnects
const result = streamText({ ... }) result.consumeStream() // No await - ensures completion even on disconnect return result.toUIMessageStreamResponse({ ... })
- Type-Safe Tools
const tools = { myTool: tool({ ... }) } satisfies ToolSet type MyTools = InferUITools<typeof tools> type MyUIMessage = UIMessage<MyMetadata, MyDataParts, MyTools>
Common Gotchas
-
Tool part types are tool-${toolName} not generic tool-call
-
Data parts need id for reconciliation - same ID updates existing part
-
Transient parts only in onData
-
never in message.parts
-
sendStart: false when using custom start - avoid duplicate start events
-
result.consumeStream()
-
call without await for disconnect handling