In this final chapter, we'll step back and review the complete Study Buddy architecture, understand how all pieces fit together, and explore ideas for extending the system.
Branch:
workshop/chapter-05-completegit checkout workshop/chapter-05-complete
| Chapter | Key AI SDK Concept | React Parallel |
|---|---|---|
| 0 | useChat / streamText |
useState + useEffect for async data |
| 1 | Tools with Zod schemas | Custom hooks with typed props |
| 2 | Tool-as-agent pattern | HOCs that fetch their own data |
| 3 | Multi-agent orchestration | React Router for user intent |
| 4 | Artifacts with persistence | Controlled components with form state |
-
"The AI SDK is React-friendly by design"
- Same mental models: hooks, components, state
- Streaming = Suspense for AI responses
- Tools/agents = just functions with extra metadata
-
"Start simple, add complexity as needed"
- Chapter 0 → 5 is a natural progression
- Don't start with 10 agents; start with 1
- Artifacts are optional until you need persistence
-
"The orchestrator is your routing layer"
- Good descriptions = good routing
- Test with edge cases ("What's the weather in a fictional city?")
- The AI is smarter than you think at understanding intent
- Show a complete flow: learn → quiz → study plan → check progress
- Demonstrate version history on artifacts
- Show the database (Mongo Express) to prove persistence
- AI SDK Documentation - Official docs
- Vercel AI Templates - More examples
- This workshop repo - Reference code
- "Can I use this with OpenAI/GPT-4?" - Yes! Just change the provider
- "How do I add authentication?" - It's already built in (NextAuth)
- "What about rate limiting?" - Already implemented (see
lib/ai/entitlements.ts) - "How do I deploy this?" - Standard Vercel/Next.js deployment
By the end of this chapter, you'll understand:
- The complete data flow from user input to AI response
- How orchestration, agents, and artifacts work together
- Best practices for production deployments
- Ideas for extending the system
┌─────────────────────────────────────────────────────────────────────────┐
│ User Interface │
│ ┌─────────────────────────────────────────────────────────────────────┐│
│ │ Chat Panel ││
│ │ ┌─────────────────────────────────────────────────────────────────┐││
│ │ │ User: "Quiz me on JavaScript closures" │││
│ │ │ │││
│ │ │ AI: I've created a quiz for you! [Artifact opens →] │││
│ │ └─────────────────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ┌─────────────────────────────────┼───────────────────────────────────┐│
│ │ Artifact Panel ││
│ │ ┌─────────────────────────────────────────────────────────────────┐││
│ │ │ Quiz: JavaScript Closures │││
│ │ │ ┌───────────────────────────────────────────────────────────┐ │││
│ │ │ │ Q1: What is a closure? │ │││
│ │ │ │ ○ A) A function with access to outer scope │ │││
│ │ │ │ ○ B) A JavaScript class │ │││
│ │ │ │ ○ C) A loop construct │ │││
│ │ │ │ ○ D) A data type │ │││
│ │ │ └───────────────────────────────────────────────────────────┘ │││
│ │ └─────────────────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────┘
Let's trace a complete request through the system:
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. USER INPUT │
│ User types: "Quiz me on JavaScript closures" │
│ → useChat hook captures input │
│ → POST request to /api/chat │
└─────────────────────────────────────┬───────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 2. API ROUTE │
│ /app/(chat)/api/chat/route.ts │
│ → Authenticates user (session) │
│ → Creates dataStream for streaming │
│ → Calls streamText with orchestrator model │
└─────────────────────────────────────┬───────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 3. ORCHESTRATOR (Claude Haiku) │
│ Analyzes user intent │
│ Reads tool descriptions: │
│ - tutor: "explain", "teach me" │
│ - quizMaster: "quiz me", "test me" ← MATCHES! │
│ - planner: "study plan", "roadmap" │
│ → Decides to call quizMaster tool │
└─────────────────────────────────────┬───────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 4. QUIZ MASTER AGENT │
│ lib/ai/agents/quiz-master.ts │
│ → Writes artifact metadata to dataStream: │
│ data-id: "abc-123" │
│ data-title: "Quiz: JavaScript Closures" │
│ data-kind: "flashcard" │
│ data-clear: null │
│ → Calls generateObject to create quiz questions │
│ → Streams content: data-flashcardDelta: "{...quiz JSON...}" │
│ → Signals completion: data-finish: null │
│ → Saves to database │
│ → Returns AgentResult │
└─────────────────────────────────────┬───────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 5. DATA STREAM HANDLER (Client) │
│ components/data-stream-handler.tsx │
│ → Receives streamed data parts │
│ → data-id → Sets artifact ID │
│ → data-kind → Finds flashcardArtifact definition │
│ → data-flashcardDelta → Calls onStreamPart handler │
│ → Updates useArtifact global state │
│ → data-finish → Sets status to "idle" │
└─────────────────────────────────────┬───────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 6. ARTIFACT VIEWER │
│ artifacts/flashcard/client.tsx │
│ → FlashcardViewer component renders │
│ → Parses JSON content │
│ → Displays interactive quiz UI │
│ → Handles user clicks on answers │
│ → Shows explanations and tracks score │
└─────────────────────────────────────────────────────────────────────────┘
ai-chatbot/
├── app/
│ └── (chat)/
│ └── api/
│ └── chat/
│ └── route.ts # Orchestrator + all agents
│
├── lib/
│ ├── ai/
│ │ ├── providers.ts # AI model configuration
│ │ ├── prompts.ts # System prompts
│ │ ├── tools/
│ │ │ └── get-weather.ts # Weather tool
│ │ └── agents/
│ │ ├── index.ts # Barrel export for all agents
│ │ ├── types.ts # Agent type definitions
│ │ ├── tutor.ts # Tutor agent (text response)
│ │ ├── quiz-master.ts # Quiz Master agent (flashcard artifact)
│ │ ├── planner.ts # Planner agent (study-plan artifact)
│ │ └── analyst.ts # Analyst agent (text response)
│ ├── db/
│ │ ├── queries.ts # Database operations
│ │ └── types.ts # MongoDB types
│ └── types.ts # Custom data stream types
│
├── artifacts/
│ ├── flashcard/
│ │ ├── server.ts # FlashcardData type
│ │ └── client.tsx # Flashcard artifact + viewer
│ ├── study-plan/
│ │ ├── server.ts # StudyPlanData type
│ │ └── client.tsx # Study plan artifact + viewer
│ ├── text/ # Built-in text artifact
│ ├── code/ # Built-in code artifact
│ └── sheet/ # Built-in spreadsheet artifact
│
├── components/
│ ├── artifact.tsx # Artifact panel + registry
│ └── data-stream-handler.tsx # Processes streaming data
│
└── hooks/
└── use-artifact.ts # Global artifact state
┌─────────────────────────────────────────────────────────────────────────┐
│ ORCHESTRATOR │
│ (Main Claude Model) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐│
│ │ System Prompt: ││
│ │ - You have specialized agents available ││
│ │ - tutor: for explanations ││
│ │ - quizMaster: for quizzes (creates flashcard artifact) ││
│ │ - planner: for study plans (creates study-plan artifact) ││
│ │ - analyst: for summarizing and analyzing content ││
│ │ - Match user intent to the right agent ││
│ └─────────────────────────────────────────────────────────────────────┘│
│ │
│ Available Tools: │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────┐ │
│ │ tutor │ │ quizMaster │ │ planner │ │ analyst │ │weather │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ generateText│ │creates │ │creates │ │generateText│ │ Simple │ │
│ │ → text │ │artifact │ │artifact │ │→ text │ │ tool │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Why wrap agents as tools?
| Aspect | Benefit |
|---|---|
| Routing | Orchestrator uses natural language understanding to pick the right agent |
| Parameters | Zod schemas ensure agents receive valid, typed input |
| Isolation | Each agent has its own prompt and model configuration |
| Composability | Agents can be added/removed without changing core logic |
| Observability | Tool calls are logged and traceable |
// lib/types.ts
export type CustomUIDataTypes = {
// Artifact content (each kind has its own delta type)
textDelta: string;
imageDelta: string;
codeDelta: string;
sheetDelta: string;
flashcardDelta: string; // Quiz questions JSON
studyPlanDelta: string; // Study plan JSON
// Other data types
suggestion: Suggestion; // Document suggestions
appendMessage: string; // Append to message
// Artifact metadata
id: string; // Unique document ID
title: string; // Display title
kind: ArtifactKind; // Artifact type (typed enum)
// Control signals
clear: null; // Clear previous content
finish: null; // Signal completion
error: string; // Error signaling from agents
// Analytics
usage: AppUsage; // Token usage data (enriched with costs)
};Always return graceful errors from agents:
execute: async ({ topic }): Promise<AgentResult> => {
try {
// Agent logic...
return { agentName: "tutor", success: true, summary: text };
} catch (error) {
console.error(`[Tutor] Error:`, error);
return {
agentName: "tutor",
success: false,
summary: "I had trouble with that request. Please try again.",
data: { error: String(error) },
};
}
}The app already includes rate limiting:
// lib/ai/entitlements.ts
- Guest users: 20 messages/day
- Regular users: 100 messages/dayEach agent call uses tokens. Consider:
- Using smaller models for simple agents
- Caching common responses
- Monitoring usage with the built-in TokenLens integration
Ensure proper indexes for document queries:
// Important indexes for document retrieval
db.documents.createIndex({ id: 1, createdAt: -1 })
db.documents.createIndex({ userId: 1, createdAt: -1 })export const createCodeReviewerAgent = ({ session, dataStream }: CreateAgentProps) =>
tool({
description: "Review code for best practices, bugs, and improvements.",
inputSchema: z.object({
code: z.string().describe("The code to review"),
language: z.string().describe("Programming language of the code"),
focusAreas: z
.array(z.string())
.optional()
.describe("Specific areas to focus on (security, performance, etc.)"),
}),
execute: async ({ code, language, focusAreas }): Promise<AgentResult> => {
// Generate code review with generateText...
return {
agentName: "code-reviewer",
success: true,
summary: reviewText,
data: { language, focusAreas },
};
},
});An agent that can search the web and summarize findings:
export const createResearcherAgent = ({ session, dataStream }: CreateAgentProps) =>
tool({
description: "Research a topic and provide summarized findings.",
inputSchema: z.object({
query: z.string().describe("The topic or question to research"),
depth: z
.enum(["quick", "thorough"])
.default("quick")
.describe("How deep to research"),
}),
execute: async ({ query, depth }): Promise<AgentResult> => {
// Fetch from APIs, summarize with generateText...
return {
agentName: "researcher",
success: true,
summary: researchFindings,
data: { query, depth },
};
},
});Store and retrieve conversation context:
// Track what topics the user has learned
const userProgress = await getUserProgress(session.user.id);
// Pass to agents for personalized responses
execute: async ({ topic }) => {
const previousTopics = userProgress.completedTopics;
// Adjust explanation based on what they already know
}Let agents call each other:
// After explaining a topic, tutor suggests a quiz
return {
agentName: "tutor",
success: true,
summary: explanation,
suggestNext: {
agent: "quizMaster",
params: { topic }
}
};Create an artifact that shows learning progress:
// artifacts/progress/client.tsx
export const progressArtifact = new Artifact<"progress", ProgressMetadata>({
kind: "progress",
description: "Track learning progress across topics.",
content: ({ content }) => <ProgressDashboard data={content} />,
});| Chapter | What You Learned |
|---|---|
| 0 | Basic chat architecture, streaming, AI SDK fundamentals |
| 1 | Tools, Zod schemas, weather example, tool rendering |
| 2 | Tool-as-agent pattern, tutor agent, generateText |
| 3 | Multi-agent orchestration, quiz master, planner, generateObject |
| 4 | Custom artifacts, data streaming, interactive viewers |
| 5 | Complete architecture, production considerations, extensions |
- Orchestration is Key: The main model decides which agent to use based on intent
- Agents are Tools: The tool-as-agent pattern provides structure and type safety
- Artifacts are Powerful: Interactive documents enhance the chat experience
- Streaming is Essential: Real-time updates create a responsive feel
- Structure Enables Scale: Clean separation makes adding new agents easy
You now have a fully functional Study Buddy with:
- A tutor that explains concepts
- A quiz master that tests knowledge
- A planner that creates study schedules
- Interactive artifacts for quizzes and study plans
Take this foundation and make it your own! Add new agents, create custom artifacts, and build the learning assistant of your dreams.
Happy coding!