fix: lessons
This commit is contained in:
@@ -1,330 +1,77 @@
|
||||
# Les 14: AI Chat Interface & Streaming
|
||||
# Les 14: Agents — LLM in een loop met tools
|
||||
|
||||
> ✅ **Deze les is volledig uitgewerkt**
|
||||
|
||||
## Lesmateriaal
|
||||
|
||||
- [Slide Overzicht](../Les14-Agents/Les14-Slide-Overzicht.md)
|
||||
- [Docenttekst](../Les14-Agents/Les14-Docenttekst.md)
|
||||
- [Lesstof](../Les14-Agents/Les14-Lesstof.md)
|
||||
- [Lesopdracht](../Les14-Agents/Les14-Lesopdracht.md)
|
||||
- [Huiswerk](../Les14-Agents/Les14-Huiswerk.md)
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 4: Advanced AI & Deployment** (Les 13-18)
|
||||
## Deel
|
||||
**Deel 3 — AI Development** (Les 11-16)
|
||||
|
||||
## Beschrijving
|
||||
Bouwen van professionele chat interfaces met streaming responses. Message rendering, markdown support, error handling, loading states, en UX patterns voor AI-powered features.
|
||||
We gaan een stap verder dan multi-step tool calling. Een agent kan 20-50 stappen autonoom uitvoeren, eigen plannen maken en dynamisch bijsturen. We bouwen een research-agent from scratch met `ToolLoopAgent`, `stopWhen` en `prepareStep`.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen (~45 min)
|
||||
## Te Behandelen
|
||||
|
||||
- Chat UI patterns en best practices
|
||||
- useChat hook deep dive (state, loading, error)
|
||||
- Streaming response handling en real-time updates
|
||||
- Message rendering strategies en optimizations
|
||||
- Markdown rendering in chat messages
|
||||
- Error handling en error boundaries
|
||||
- Loading states en skeleton loaders
|
||||
- User input validation and sanitization
|
||||
- Accessibility in chat interfaces (ARIA labels)
|
||||
- Message persistence (localStorage of database)
|
||||
- Performance optimization
|
||||
### Theorie
|
||||
- Wat is een agent eigenlijk? (LLM + loop + tools + stopconditie)
|
||||
- Wanneer wel/niet een agent gebruiken
|
||||
- AI SDK v6 agent-primitives: `ToolLoopAgent`, `stopWhen`, `prepareStep`
|
||||
- `prepareStep`: dynamisch model + tools per stap wisselen
|
||||
- Cost & latency van agents — wat moet je weten?
|
||||
|
||||
---
|
||||
|
||||
### useChat Hook Deep Dive
|
||||
|
||||
**State management met useChat:**
|
||||
|
||||
```typescript
|
||||
'use client'
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
export function ChatComponent() {
|
||||
const {
|
||||
messages, // All messages in conversation
|
||||
input, // Current input text
|
||||
handleInputChange, // Update input
|
||||
handleSubmit, // Send message
|
||||
isLoading, // Is AI responding?
|
||||
error, // Any errors?
|
||||
} = useChat({
|
||||
api: '/api/chat',
|
||||
initialMessages: [], // Optional: pre-load messages
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id}>
|
||||
<strong>{msg.role}:</strong> {msg.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{isLoading && <div>AI is thinking...</div>}
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<button type="submit" disabled={isLoading}>Send</button>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Message Rendering Patterns
|
||||
|
||||
**Basic pattern:**
|
||||
```typescript
|
||||
<div className="space-y-4">
|
||||
{messages.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className={msg.role === 'user' ? 'ml-auto' : 'mr-auto'}
|
||||
>
|
||||
<div className="bg-gray-200 p-3 rounded">
|
||||
{msg.content}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
**With markdown rendering:**
|
||||
```typescript
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
<div className="bg-gray-200 p-3 rounded">
|
||||
<ReactMarkdown>{msg.content}</ReactMarkdown>
|
||||
</div>
|
||||
```
|
||||
|
||||
**With message types:**
|
||||
```typescript
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id} className={msg.role === 'user' ? 'user-message' : 'ai-message'}>
|
||||
{msg.content}
|
||||
</div>
|
||||
))}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Error Handling
|
||||
|
||||
**Structured error handling:**
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const response = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ messages }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`)
|
||||
}
|
||||
|
||||
// Handle streaming...
|
||||
} catch (error) {
|
||||
console.error('Chat error:', error)
|
||||
setError({
|
||||
message: 'Failed to send message',
|
||||
code: error instanceof Error ? error.message : 'unknown'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Error boundary:**
|
||||
```typescript
|
||||
<ErrorBoundary fallback={<div>Chat error occurred</div>}>
|
||||
<ChatComponent />
|
||||
</ErrorBoundary>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Loading States
|
||||
|
||||
**Skeleton loader:**
|
||||
```typescript
|
||||
function MessageSkeleton() {
|
||||
return (
|
||||
<div className="animate-pulse">
|
||||
<div className="bg-gray-300 h-4 rounded w-48 mb-2" />
|
||||
<div className="bg-gray-300 h-4 rounded w-64" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{isLoading && <MessageSkeleton />}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Input Validation
|
||||
|
||||
**Validate before sending:**
|
||||
|
||||
```typescript
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
|
||||
// Trim whitespace
|
||||
const trimmedInput = input.trim()
|
||||
|
||||
// Validate non-empty
|
||||
if (!trimmedInput) {
|
||||
setError('Message cannot be empty')
|
||||
return
|
||||
}
|
||||
|
||||
// Validate length
|
||||
if (trimmedInput.length > 1000) {
|
||||
setError('Message too long (max 1000 chars)')
|
||||
return
|
||||
}
|
||||
|
||||
// Send message
|
||||
handleSubmit(e)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Message Persistence
|
||||
|
||||
**Save to localStorage:**
|
||||
|
||||
```typescript
|
||||
const [messages, setMessages] = useState(() => {
|
||||
const saved = localStorage.getItem('chat_history')
|
||||
return saved ? JSON.parse(saved) : []
|
||||
})
|
||||
|
||||
// Save whenever messages change
|
||||
useEffect(() => {
|
||||
localStorage.setItem('chat_history', JSON.stringify(messages))
|
||||
}, [messages])
|
||||
```
|
||||
|
||||
**Save to database:**
|
||||
```typescript
|
||||
const saveMessage = async (message: Message) => {
|
||||
await fetch('/api/messages', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
content: message.content,
|
||||
role: message.role,
|
||||
userId: user.id,
|
||||
}),
|
||||
})
|
||||
}
|
||||
```
|
||||
### Live demo's
|
||||
1. Setup nieuwe research-agent (los project)
|
||||
2. Tools: webSearch, fetchPage, summarize, writeReport
|
||||
3. `stopWhen: stepCountIs(20)` + `hasToolCall("writeReport")`
|
||||
4. `prepareStep` om Sonnet te gebruiken voor summarize, Haiku voor search
|
||||
5. Live: vraag de agent een onderwerp te onderzoeken
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Vercel AI SDK
|
||||
- React Markdown
|
||||
- Cursor
|
||||
- TypeScript
|
||||
- Vercel AI SDK v6 (`ToolLoopAgent`, `stopWhen`, `prepareStep`)
|
||||
- Tavily of Exa API voor web search
|
||||
- Anthropic + OpenAI providers
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur, klassikaal)
|
||||
## Lesopdracht (in-class, 60 min)
|
||||
|
||||
### Build Professional Chat Interface
|
||||
Studenten kijken mee. In-class oefening:
|
||||
- Voeg een eigen tool toe aan de research-agent
|
||||
- Tweak de stopconditie: laat de agent na 5 minuten stoppen
|
||||
- Bekijk de trace: welke stappen koos de agent?
|
||||
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de Vercel AI SDK ervaringen uit Les 13 - welke tool calling patterns werkten goed?
|
||||
|
||||
**Deel 1: Chat Component (45 min)**
|
||||
|
||||
Build components/ChatInterface.tsx:
|
||||
1. Use useChat hook
|
||||
2. Render messages with proper styling
|
||||
3. User vs AI message styling
|
||||
4. Input form with validation
|
||||
5. Tailwind + shadcn/ui components
|
||||
|
||||
**Deel 2: Markdown & Error Handling (30 min)**
|
||||
|
||||
1. Install react-markdown: `npm install react-markdown`
|
||||
2. Render AI responses with markdown
|
||||
3. Add error boundary
|
||||
4. Show error messages to user
|
||||
5. Proper loading states
|
||||
|
||||
**Deel 3: UX Improvements (30 min)**
|
||||
|
||||
1. Auto-scroll to latest message
|
||||
2. Disable input while loading
|
||||
3. Show message count/token usage
|
||||
4. Add clear chat history button
|
||||
5. Save messages to localStorage
|
||||
|
||||
**Deel 4: Testing (15 min)**
|
||||
|
||||
Test chat interface locally with various inputs and error scenarios.
|
||||
|
||||
### Deliverable
|
||||
- Werkende chat interface component
|
||||
- Markdown rendering working
|
||||
- Error handling implemented
|
||||
- LocalStorage persistence
|
||||
- GitHub commit with chat UI
|
||||
**Inleveren:** Screenshot van werkende agent-loop.
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
## Huiswerk (take-home)
|
||||
|
||||
### Integrate Chat into Your Project
|
||||
Bouw je eigen agent voor een eigen use-case:
|
||||
- Minimaal 3 tools
|
||||
- Slimme stop-conditie
|
||||
- `prepareStep` voor model-routing
|
||||
- Reflectie 500 woorden: kan dit ook met gewone tool calling?
|
||||
|
||||
**Deel 1: Project Integration (1 uur)**
|
||||
|
||||
1. Add chat component to your app
|
||||
2. Connect to your API route with tools
|
||||
3. Style to match your design
|
||||
4. Test with actual tools/integrations
|
||||
5. Fix any bugs
|
||||
|
||||
**Deel 2: Enhanced Features (30 min)**
|
||||
|
||||
Add one of these:
|
||||
- Message copy button
|
||||
- Regenerate response option
|
||||
- Clear history confirmation
|
||||
- Export chat history
|
||||
- Message timestamps
|
||||
|
||||
**Deel 3: Performance & Polish (30 min)**
|
||||
|
||||
1. Optimize re-renders (useMemo, useCallback)
|
||||
2. Virtual scrolling for long chats
|
||||
3. Better accessibility (keyboard nav)
|
||||
4. Mobile responsive tweaks
|
||||
5. Update docs/AI-DECISIONS.md
|
||||
|
||||
### Deliverable
|
||||
- Chat fully integrated in project
|
||||
- Enhanced features implemented
|
||||
- Performance optimized
|
||||
- GitHub commits with improvements
|
||||
**Inleveren:** GitHub URL via Teams.
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- useChat hook volledig begrijpen en gebruiken
|
||||
- Professionele chat UI patterns implementeren
|
||||
- Markdown rendering in chat messages
|
||||
- Error handling en error boundaries toepassen
|
||||
- Loading states en skeletons bouwen
|
||||
- User input valideren en sanitizen
|
||||
- Message persistence (localStorage/DB)
|
||||
- Accessibility in chat interfaces verbeteren
|
||||
- Performance optimizations toepassen
|
||||
- Complete chat feature in Next.js app integreren
|
||||
- Het verschil uitleggen tussen tool calling en een agent
|
||||
- Een agent opzetten met `ToolLoopAgent` en custom tools
|
||||
- Een doordachte stopconditie schrijven met `stopWhen`
|
||||
- Modellen dynamisch routen per stap via `prepareStep`
|
||||
- Inschatten wanneer een agent de juiste oplossing is
|
||||
|
||||
Reference in New Issue
Block a user