Skip to main content

ai-sdk

This skill should be used when building AI features with Vercel AI SDK, using useChat, streamText, or generateObject, or when "AI SDK", "streaming chat", or "structured outputs" are mentioned.

소스 정보

저장소
outfitter-dev/agents
최근 소스 활동
2026년 1월 27일 15:45
감지된 SKILL.md 언어
영어
스타
28
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
4 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
ai-sdk
description
This skill should be used when building AI features with Vercel AI SDK, using useChat, streamText, or generateObject, or when "AI SDK", "streaming chat", or "structured outputs" are mentioned.
metadata
{"version":"1.0.0"}
# Vercel AI SDK v6 Patterns for building AI-powered applications with the Vercel AI SDK v6. <when_to_use> - Building streaming chat UIs - Structured JSON outputs with Zod schemas - Multi-step agent workflows with tools - Tool approval flows (human-in-the-loop) - Next.js App Router integrations </when_to_use> ## Version Guard Target **AI SDK 6.x** APIs. Default packages: ``` ai@^6 @ai-sdk/react@^2 @ai-sdk/openai@^2 (or @ai-sdk/anthropic, etc.) zod@^3 ``` **Avoid v4/v5 holdovers:** - `StreamingTextResponse` → use `result.toUIMessageStreamResponse()` - Legacy `Message` shape → use `UIMessage` - Input-managed `useChat` → use transport-based pattern ## Core Concepts ### Message Types | Type | Purpose | When to Use | |------|---------|-------------| | `UIMessage` | User-facing, persistence | Store in database, render in UI | | `ModelMessage` | LLM-compatible | Convert at call sites only | **Rule:** Persist `UIMessage[]`. Convert to `ModelMessage[]` only when calling the model. ### Streaming Patterns | Function | Use Case | |----------|----------| | `streamText` | Streaming text responses | | `generateText` | Non-streaming text | | `streamObject` | Streaming JSON with partial updates | | `generateObject` | Non-streaming JSON | | `ToolLoopAgent` | Multi-step agent with tools | ## Golden Path: Streaming Chat ### API Route (App Router) ```typescript // app/api/chat/route.ts import { openai } from '@ai-sdk/openai'; import { streamText, convertToModelMessages, type UIMessage } from 'ai'; export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: openai('gpt-4o-mini'), messages: convertToModelMessages(messages), }); return result.toUIMessageStreamResponse({ originalMessages: messages, getErrorMessage: (e) => e instanceof Error ? e.message : 'An error occurred', }); } ``` ### Client Hook ```tsx 'use client'; import { useState } from 'react'; import { useChat, type UIMessage } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; export function Chat({ initialMessages = [] }: { initialMessages?: UIMessage[] }) { const [input, setInput] = useState(''); const { messages, sendMessage, status, error } = useChat({ messages: initialMessages, transport: new DefaultChatTransport({ api: '/api/chat' }), }); const submit = (e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { sendMessage({ role: 'user', content: [{ type: 'text', text: input }] }); setInput(''); } }; return ( <div> {messages.map((m) => ( <div key={m.id}> <b>{m.role === 'user' ? 'You' : 'AI'}:</b> {m.parts.map((p, i) => (p.type === 'text' ? <span key={i}>{p.text}</span> : null))} </div> ))} {status === 'error' && <div className="text-red-600">{error?.message}</div>} <form onSubmit={submit}> <input value={input} onChange={(e) => setInput(e.target.value)} /> <button type="submit">Send</button> </form> </div> ); } ``` ## Structured Outputs ```typescript import { generateObject, streamObject } from 'ai'; import { openai } from '@ai-sdk/openai'; import { z } from 'zod'; const schema = z.object({ recipe: z.object({ name: z.string(), ingredients: z.array(z.string()), steps: z.array(z.string()), }), }); // One-shot JSON const { object } = await generateObject({ model: openai('gpt-4o'), schema, prompt: 'Generate a lasagna recipe.', }); // Streaming JSON (partial updates) const { partialObjectStream } = streamObject({ model: openai('gpt-4o'), schema, prompt: 'Generate a lasagna recipe.', }); for await (const partial of partialObjectStream) { // Render progressively } ``` ## Tools ### Server-Side Tool Definition ```typescript import { tool } from 'ai'; import { z } from 'zod'; const searchTool = tool({ description: 'Search product catalog', inputSchema: z.object({ query: z.string() }), execute: async ({ query }) => { // Implementation return [{ id: 'p1', name: 'Example Product' }]; }, }); ``` ### Multi-Step Tool Loops ```typescript import { streamText, stepCountIs } from 'ai'; const result = streamText({ model: openai('gpt-4o'), messages: convertToModelMessages(messages), tools: { search: searchTool }, stopWhen: stepCountIs(6), // Max 6 iterations prepareStep: async ({ stepNumber, messages }) => messages.length > 10 ? { messages: messages.slice(-10) } : {}, }); return result.toUIMessageStreamResponse(); ``` ## v6: ToolLoopAgent First-class agent abstraction for autonomous multi-step workflows. ### Basic Agent ```typescript import { ToolLoopAgent, stepCountIs } from 'ai'; const agent = new ToolLoopAgent({ model: 'anthropic/claude-sonnet-4.5', instructions: 'You are a helpful research assistant.', tools: { search: searchTool, calculator: calculatorTool, }, stopWhen: stepCountIs(5), }); // Non-streaming const result = await agent.generate({ prompt: 'What is the weather in NYC?', }); console.log(result.text); console.log(result.steps); // All steps taken // Streaming const stream = agent.stream({ prompt: 'Research quantum computing.' }); for await (const chunk of stream.textStream) { process.stdout.write(chunk); } ``` ### Agent with UI Streaming ```typescript import { ToolLoopAgent, createAgentUIStream } from 'ai'; const agent = new ToolLoopAgent({ model, instructions, tools }); const stream = await createAgentUIStream({ agent, messages: [{ role: 'user', content: 'What is the weather?' }], }); for await (const chunk of stream) { // UI message chunks } ``` ## v6: Tool Approval (Human-in-the-Loop) ### Static Approval (Always Require) ```typescript const dangerousTool = tool({ description: 'Delete user data', inputSchema: z.object({ userId: z.string() }), needsApproval: true, // Always require approval execute: async ({ userId }) => { return await deleteUserData(userId); }, }); ``` ### Dynamic Approval (Conditional) ```typescript const paymentTool = tool({ description: 'Process payment', inputSchema: z.object({ amount: z.number(), recipient: z.string(), }), needsApproval: async ({ amount }) => amount > 1000, // Only large transactions execute: async ({ amount, recipient }) => { return await processPayment(amount, recipient); }, }); ``` ### Client-Side Approval UI ```tsx function ToolApprovalView({ invocation, addToolApprovalResponse }) { if (invocation.state === 'approval-requested') { return ( <div> <p>Approve action: {invocation.input.description}?</p> <button onClick={() => addToolApprovalResponse({ id: invocation.approval.id, approved: true }) } > Approve </button> <button onClick={() => addToolApprovalResponse({ id: invocation.approval.id, approved: false }) } > Deny </button> </div> ); } if (invocation.state === 'output-available') { return <div>Result: {JSON.stringify(invocation.output)}</div>; } return null; } ``` ## Persistence ```typescript return result.toUIMessageStreamResponse({ originalMessages: messages, generateMessageId: createIdGenerator({ prefix: 'msg', size: 16 }), onFinish: async ({ messages: complete }) => { await saveChat({ chatId, messages: complete }); // Persist UIMessage[] }, }); ``` ## Error Handling ```typescript // Server: Surface errors to client return result.toUIMessageStreamResponse({ getErrorMessage: (e) => e instanceof Error ? e.message : typeof e === 'string' ? e : JSON.stringify(e), }); // Client: Handle error state const { status, error } = useChat({ ... }); if (status === 'error') { return <div>Error: {error?.message}</div>; } ``` ## Anti-Patterns | Avoid | Use Instead | |-------|-------------| | `StreamingTextResponse` | `result.toUIMessageStreamResponse()` | | Persisting `ModelMessage` | Persist `UIMessage[]` | | Unbounded tool loops | `stopWhen: stepCountIs(N)` | | Client-only state for long sessions | Add persistence + resumable streams | | `any` types | Zod schemas + typed `UIMessage` | <references> - [agents.md](references/agents.md) - ToolLoopAgent patterns and workflows - [tool-approval.md](references/tool-approval.md) - Human-in-the-loop approval flows - [persistence.md](references/persistence.md) - Chat persistence strategies </references>
GitHub에서 보기