AI SDK UI 사용
AI SDK UIAI 기반 인터페이스를 구축하기 위한 React 유틸리티 및 구성 요소의 라이브러리입니다. 이 가이드에서는 다음을 사용하는 방법을 배웁니다.@mastra/ai-sdkMastra의 출력을 AI SDK 호환 형식으로 변환하여 프런트엔드에서 후크와 구성 요소를 사용할 수 있도록 합니다.
:::참고 AI SDK v4에서 v5로 마이그레이션하시나요? 참조migration guide. :::
더 많은 예를 보고 싶으십니까? 마스트라(Mastra)를 방문해 보세요UI Dojo or the Next.js quickstart guide.
시작하기시작하기에 대한 직접 링크
Mastra와 AI SDK UI를 함께 설치하여 사용하세요.@mastra/ai-sdk package. @mastra/ai-sdk 는 AI SDK 호환 형식으로 Mastra 에이전트를 스트리밍하기 위한 사용자 지정 API 경로와 유틸리티를 제공합니다. 여기에는 채팅, Workflow, 네트워크 경로 핸들러와 UI 통합을 위한 유틸리티 및 내보낸 타입이 포함됩니다.
@mastra/ai-sdkAI SDK UI의 세 가지 주요 후크와 통합됩니다.useChat(), useCompletion(), and useObject().
시작하려면 필수 패키지를 설치하세요.
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/ai-sdk@latest @ai-sdk/react ai
pnpm add @mastra/ai-sdk@latest @ai-sdk/react ai
yarn add @mastra/ai-sdk@latest @ai-sdk/react ai
bun add @mastra/ai-sdk@latest @ai-sdk/react ai
이제 아래 통합 가이드와 레시피를 따를 준비가 되었습니다!
통합 가이드통합 가이드에 대한 직접 링크
일반적으로 AI SDK 호환 형식으로 Mastra 콘텐츠를 스트리밍하는 API 경로를 설정한 다음 다음과 같은 AI SDK UI 후크에서 해당 경로를 사용합니다.useChat(). Choose one of these approaches:
API 경로를 설정한 후에는 다음에서 사용할 수 있습니다.useChat() hook.
마스트라의 서버마스트라의 서버에 대한 직접 링크
Mastra를 독립형 서버로 실행하고 프런트엔드(예: Vite + React 사용)를 API 엔드포인트에 연결하세요. 마스트라(Mastra)를 사용하게 됩니다.custom API routes feature for this.
마스트라의UI Dojo is an example of this setup.
당신은 사용할 수 있습니다chatRoute(), workflowRoute(), and networkRoute() 하여 Mastra 콘텐츠를 AI SDK 호환 형식으로 스트리밍하는 API 경로를 생성합니다. 구현한 후에는 이러한 API 경로를 useChat().
- chatRoute()
- workflowRoute()
- networkRoute()
이 예에서는 채팅 경로를 설정하는 방법을 보여줍니다./chat endpoint that uses an agent with the ID weatherAgent.
import { Mastra } from '@mastra/core'
import { chatRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
server: {
apiRoutes: [
chatRoute({
path: '/chat',
agent: 'weatherAgent',
}),
],
},
})
동적 Agent 라우팅을 사용할 수도 있습니다.chatRoute() reference documentation for more details.
이 예에서는 Workflow 경로를 설정하는 방법을 보여줍니다./workflow endpoint that uses a workflow with the ID weatherWorkflow.
import { Mastra } from '@mastra/core'
import { workflowRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
server: {
apiRoutes: [
workflowRoute({
path: '/workflow',
workflow: 'weatherWorkflow',
}),
],
},
})
동적 Workflow 라우팅을 사용할 수도 있습니다.workflowRoute() reference documentation for more details.
Workflow 단계에서 Agent의 스트림을 Workflow 작성자에게 파이프하는 경우(예:await response.fullStream.pipeTo(writer)), 에이전트가 Workflow 단계 내에서 실행되는 경우에도 에이전트의 텍스트 청크와 도구 호출이 실시간으로 UI 스트림에 전달됩니다.
보다Workflow Streaming for more details.
이 예에서는 네트워크 경로를 설정하는 방법을 보여줍니다./network endpoint that uses an agent with the ID weatherAgent.
import { Mastra } from '@mastra/core'
import { networkRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
server: {
apiRoutes: [
networkRoute({
path: '/network',
agent: 'weatherAgent',
}),
],
},
})
동적 네트워크 라우팅을 사용할 수도 있습니다.networkRoute() reference documentation for more details.
프레임워크에 구애받지 않음프레임워크에 구애받지 않음에 대한 직접 링크
Mastra의 서버를 실행하지 않고 대신 Next.js 또는 Express와 같은 프레임워크를 사용하려는 경우 다음을 사용할 수 있습니다.handleChatStream(), handleWorkflowStream(), and handleNetworkStream() functions in your own API route handlers.
그들은ReadableStream that you can wrap with createUIMessageStreamResponse().
프레임워크에 구애받지 않는 핸들러는 기존 AI SDK v5/기본 동작을 유지합니다. 앱이 AI SDK v6에 대해 입력된 경우 다음을 통과하세요.version: 'v6'. For best TypeScript inference with handleChatStream() and handleNetworkStream(), pass messages as UIMessage[] from your installed ai version.
아래 예는 Next.js App Router와 함께 사용하는 방법을 보여줍니다.
- handleChatStream()
- handleWorkflowStream()
- handleNetworkStream()
이 예에서는 채팅 경로를 설정하는 방법을 보여줍니다./chat endpoint that uses an agent with the ID weatherAgent.
import { handleChatStream } from '@mastra/ai-sdk'
import { createUIMessageStreamResponse } from 'ai'
import { mastra } from '@/src/mastra'
export async function POST(req: Request) {
const params = await req.json()
const stream = await handleChatStream({
mastra,
agentId: 'weatherAgent',
params,
})
return createUIMessageStreamResponse({ stream })
}
이 예에서는 Workflow 경로를 설정하는 방법을 보여줍니다./workflow endpoint that uses a workflow with the ID weatherWorkflow.
import { handleWorkflowStream } from '@mastra/ai-sdk'
import { createUIMessageStreamResponse } from 'ai'
import { mastra } from '@/src/mastra'
export async function POST(req: Request) {
const params = await req.json()
const stream = await handleWorkflowStream({
mastra,
workflowId: 'weatherWorkflow',
params,
})
return createUIMessageStreamResponse({ stream })
}
이 예에서는 네트워크 경로를 설정하는 방법을 보여줍니다./network endpoint that uses an agent with the ID routingAgent.
import { handleNetworkStream } from '@mastra/ai-sdk'
import { createUIMessageStreamResponse } from 'ai'
import { mastra } from '@/src/mastra'
export async function POST(req: Request) {
const params = await req.json()
const stream = await handleNetworkStream({
mastra,
agentId: 'routingAgent',
params,
})
return createUIMessageStreamResponse({ stream })
}
useChat()usechat에 대한 직접 링크
API 경로를 생성했는지 여부Mastra's server or used a framework of your choice, you can now use the API endpoints in the useChat() hook.
경로를 설정했다고 가정하면/chat 에서 날씨 에이전트를 사용하면 아래와 같이 질문할 수 있습니다. 올바른 api URL.
import { useChat } from '@ai-sdk/react'
import { useState } from 'react'
import { DefaultChatTransport } from 'ai'
export default function Chat() {
const [inputValue, setInputValue] = useState('')
const { messages, sendMessage } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/chat',
}),
})
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault()
sendMessage({ text: inputValue })
}
return (
<div>
<pre>{JSON.stringify(messages, null, 2)}</pre>
<form onSubmit={handleFormSubmit}>
<input
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="Name of the city"
/>
</form>
</div>
)
}
사용prepareSendMessagesRequest 하여 채팅 경로로 전송되는 요청을 사용자 지정할 수 있습니다. 예를 들어 에이전트에 추가 구성을 전달할 수 있습니다.
마스트라 Memory 사용마스트라 Memory 사용에 대한 직접 링크
귀하의 대리인이memory 가 구성되어 있으면 Mastra가 서버의 스토리지에서 대화 기록을 불러옵니다. 전체 대화 기록 대신 새 메시지만 클라이언트에서 전송하세요.
전체 기록을 보내는 것은 중복되며 클라이언트 측 타임스탬프가 데이터베이스에 저장된 타임스탬프와 충돌할 수 있으므로 메시지 순서 버그가 발생할 수 있습니다.
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
const { messages, sendMessage } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/chat/weatherAgent',
prepareSendMessagesRequest({ messages }) {
return {
body: {
messages: [messages[messages.length - 1]],
memory: {
thread: 'user-thread-123',
resource: 'user-123',
},
},
}
},
}),
})
세트memory.thread and memory.resource 를 URL 매개변수, 인증 컨텍스트 또는 데이터베이스와 같은 앱 자체 상태에서 가져옵니다.
보다Message history 에서 Mastra Memory가 메시지를 불러오고 저장하는 방법을 자세히 알아보세요.
chatRoute()그리고handleChatStream() 는 이미 Memory와 함께 작동합니다. 새 메시지만 전송하고 스레드 및 리소스 식별자를 포함하도록 클라이언트를 구성하세요.
useCompletion()usecompletion에 대한 직접 링크
그만큼useCompletion() 훅은 프런트엔드와 Mastra 에이전트 간의 단일 턴 완성을 처리하여 Prompt를 전송하고 HTTP를 통해 스트리밍 응답을 받을 수 있게 합니다.
프런트엔드는 다음과 같습니다.
import { useCompletion } from '@ai-sdk/react'
export default function Page() {
const { completion, input, handleInputChange, handleSubmit } = useCompletion({
api: '/api/completion',
})
return (
<form onSubmit={handleSubmit}>
<input name="prompt" value={input} onChange={handleInputChange} id="input" />
<button type="submit">Submit</button>
<div>{completion}</div>
</form>
)
}
백엔드 구현을 선택하세요.
- Mastra Server
- Next.js
import { Mastra } from '@mastra/core/mastra'
import { registerApiRoute } from '@mastra/core/server'
import { handleChatStream } from '@mastra/ai-sdk'
import { createUIMessageStreamResponse } from 'ai'
export const mastra = new Mastra({
server: {
apiRoutes: [
registerApiRoute('/completion', {
method: 'POST',
handler: async c => {
const { prompt } = await c.req.json()
const mastra = c.get('mastra')
const stream = await handleChatStream({
mastra,
agentId: 'weatherAgent',
params: {
messages: [
{
id: '1',
role: 'user',
parts: [
{
type: 'text',
text: prompt,
},
],
},
],
},
})
return createUIMessageStreamResponse({ stream })
},
}),
],
},
})
import { handleChatStream } from '@mastra/ai-sdk'
import { createUIMessageStreamResponse } from 'ai'
import { mastra } from '@/src/mastra'
// Allow streaming responses up to 30 seconds
export const maxDuration = 30
export async function POST(req: Request) {
const { prompt }: { prompt: string } = await req.json()
const stream = await handleChatStream({
mastra,
agentId: 'weatherAgent',
params: {
messages: [
{
id: '1',
role: 'user',
parts: [
{
type: 'text',
text: prompt,
},
],
},
],
},
})
return createUIMessageStreamResponse({ stream })
}
커스텀 UI커스텀 UI에 대한 직접 링크
사용자 정의 UI(Generative UI라고도 함)를 사용하면 Mastra에서 스트리밍된 데이터를 기반으로 사용자 정의 React 구성 요소를 렌더링할 수 있습니다. 원시 텍스트나 JSON을 표시하는 대신 Agent 네트워크 실행 및 사용자 정의 이벤트를 포함하여 Tool 출력 및 Workflow 진행을 위한 시각적 구성 요소를 생성할 수 있습니다.
다음과 같은 경우 맞춤 UI를 사용하세요.
- 시각적 구성 요소(예: JSON 대신 날씨 카드)로 Tool 출력 렌더링
- 상태 표시기로 Workflow 단계 진행 상황 표시
- 단계별 업데이트를 통해 Agent 네트워크 실행 시각화
- 장기 실행 작업 중에 진행률 표시기 또는 상태 업데이트 표시
데이터 부분 유형데이터 부분 유형에 대한 직접 링크
Mastra는 메시지 내의 "부분"으로 데이터를 프런트엔드로 스트리밍합니다. 각 부분에는type that determines how to render it. The @mastra/ai-sdk package transforms Mastra streams into AI SDK-compatible UI Message DataParts.
| 데이터 부분 유형 | 소스 | 설명 |
|---|---|---|
tool-{toolKey} | AI SDK built-in | Tool invocation with states: input-available, output-available, output-error |
data-workflow | workflowRoute() | 단계 상태와 최종 출력이 포함된 Workflow 실행 상태 스냅샷 |
data-workflow-step | workflowRoute() | 변경된 단계의 전체 페이로드가 포함된 Workflow 단계 델타 |
data-network | networkRoute() | 순서가 지정된 단계와 출력이 포함된 Agent 네트워크 실행 |
data-tool-agent | Tool 내 중첩 Agent | 현재 단계가 아직 실행 중일 때의 간결한 중첩 Agent 스냅샷 |
data-tool-agent-step | Tool 내 중첩 Agent | 중첩 단계가 완료될 때 내보내는 전체 중첩 Agent 단계 페이로드 |
data-tool-workflow | Tool 내 중첩 Workflow | Tool의 execute() |
data-tool-network | Tool 내 중첩 네트워크 | Tool의 execute() |
data-{custom} | writer.custom() | 진행률 표시기, 상태 업데이트 등을 위한 사용자 지정 이벤트 |
렌더링 Tool 출력렌더링 Tool 출력에 대한 직접 링크
AI SDK가 자동으로 생성tool-{toolKey} 부분은 에이전트가 도구를 호출할 때 생성됩니다. 이러한 부분에는 도구의 상태와 출력이 포함되며, 이를 사용하여 사용자 지정 컴포넌트를 렌더링할 수 있습니다.
Tool 부분은 다음 상태를 순환합니다.
input-streaming: Tool 입력이 스트리밍되고 있습니다. (Tool 호출 스트리밍이 활성화된 경우)input-available: Tool이 완전한 입력으로 호출되었으며 실행을 기다리고 있습니다.output-available: 출력과 함께 Tool 실행이 완료되었습니다.output-error: Tool 실행 실패
다음은 날씨 Tool의 출력을 사용자 정의로 렌더링하는 예입니다.WeatherCard component.
- Backend
- Frontend
다음을 사용하여 Tool을 정의합니다.outputSchema 하여 프런트엔드가 렌더링할 데이터의 구조를 알 수 있도록 합니다.
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const weatherTool = createTool({
id: 'get-weather',
description: 'Get current weather for a location',
inputSchema: z.object({
location: z.string().describe('The location to get the weather for'),
}),
outputSchema: z.object({
temperature: z.number(),
feelsLike: z.number(),
humidity: z.number(),
windSpeed: z.number(),
conditions: z.string(),
location: z.string(),
}),
execute: async inputData => {
const response = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${inputData.location}`,
)
const data = await response.json()
return {
temperature: data.current.temp_c,
feelsLike: data.current.feelslike_c,
humidity: data.current.humidity,
windSpeed: data.current.wind_kph,
conditions: data.current.condition.text,
location: data.location.name,
}
},
})
확인tool-{toolKey} 부분을 메시지에서 찾아 도구의 상태와 출력에 따라 사용자 지정 컴포넌트를 렌더링합니다.
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { WeatherCard } from './weather-card'
import { Loader } from './loader'
export function Chat() {
const { messages, sendMessage } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/chat/weatherAgent',
}),
})
return (
<div>
{messages.map(message => (
<div key={message.id}>
{message.parts.map((part, index) => {
// Handle user text messages
if (part.type === 'text' && message.role === 'user') {
return <p key={index}>{part.text}</p>
}
// Handle weather tool output
if (part.type === 'tool-weatherTool') {
switch (part.state) {
case 'input-available':
return <Loader key={index} />
case 'output-available':
return <WeatherCard key={index} {...part.output} />
case 'output-error':
return <div key={index}>Error: {part.errorText}</div>
default:
return null
}
}
return null
})}
</div>
))}
</div>
)
}
Tool 부품 유형은 패턴을 따릅니다.tool-{toolKey}, where toolKey 는 에이전트에 도구를 등록할 때 사용하는 키입니다. 예를 들어 도구를 tools: { weatherTool }, the part type will be tool-weatherTool.
Workflow 데이터 렌더링Workflow 데이터 렌더링에 대한 직접 링크
사용시workflowRoute() or handleWorkflowStream(), Mastra emits data-workflow parts for workflow state snapshots and data-workflow-step 부분에서 변경된 단계의 전체 페이로드를 확인할 수 있습니다. 이렇게 하면 장시간 실행되는 Workflow가 모든 중간 스냅샷에서 완료된 모든 단계의 출력을 반복하지 않습니다.
- Backend
- Frontend
여러 단계를 내보내는 Workflow를 정의합니다.data-workflow and data-workflow-step parts as it executes.
import { createStep, createWorkflow } from '@mastra/core/workflows'
import { z } from 'zod'
const fetchWeather = createStep({
id: 'fetch-weather',
inputSchema: z.object({
location: z.string(),
}),
outputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
}),
execute: async ({ inputData }) => {
// Fetch weather data...
return { temperature: 22, conditions: 'Sunny' }
},
})
const planActivities = createStep({
id: 'plan-activities',
inputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
}),
outputSchema: z.object({
activities: z.string(),
}),
execute: async ({ inputData, mastra }) => {
const agent = mastra?.getAgent('activityAgent')
const response = await agent?.generate(
`Suggest activities for ${inputData.conditions} weather at ${inputData.temperature}°C`,
)
return { activities: response?.text || '' }
},
})
export const activitiesWorkflow = createWorkflow({
id: 'activities-workflow',
inputSchema: z.object({
location: z.string(),
}),
outputSchema: z.object({
activities: z.string(),
}),
})
.then(fetchWeather)
.then(planActivities)
activitiesWorkflow.commit()
Workflow를 Mastra에 등록하고 다음을 통해 노출합니다.workflowRoute() to stream workflow events to the frontend.
import { Mastra } from '@mastra/core'
import { workflowRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
workflows: { activitiesWorkflow },
server: {
apiRoutes: [
workflowRoute({
path: '/workflow/activitiesWorkflow',
workflow: 'activitiesWorkflow',
}),
],
},
})
확인data-workflow 부분을 사용하여 Workflow 상태 스냅샷을 렌더링합니다. 방금 변경된 단계의 전체 페이로드가 필요하다면 data-workflow-step parts.
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import type { WorkflowDataPart, WorkflowStepDataPart } from '@mastra/ai-sdk'
type WorkflowData = WorkflowDataPart['data']
type WorkflowStepData = WorkflowStepDataPart['data']
type StepStatus = 'running' | 'success' | 'failed' | 'suspended' | 'waiting'
function StepIndicator({
name,
status,
output,
}: {
name: string
status: StepStatus
output: unknown
}) {
return (
<div className="step">
<div className="step-header">
<span>{name}</span>
<span className={`status status-${status}`}>{status}</span>
</div>
{status === 'success' && output && <pre>{JSON.stringify(output, null, 2)}</pre>}
</div>
)
}
export function WorkflowChat() {
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/workflow/activitiesWorkflow',
prepareSendMessagesRequest: ({ messages }) => ({
body: {
inputData: {
location: messages[messages.length - 1]?.parts[0]?.text,
},
},
}),
}),
})
return (
<div>
{messages.map(message => (
<div key={message.id}>
{message.parts.map((part, index) => {
if (part.type === 'data-workflow') {
const workflowData = part.data as WorkflowData
const steps = Object.values(workflowData.steps)
return (
<div key={index} className="workflow-progress">
<h3>Workflow: {workflowData.name}</h3>
<p>Status: {workflowData.status}</p>
{steps.map(step => (
<StepIndicator
key={step.name}
name={step.name}
status={step.status}
output={step.output}
/>
))}
</div>
)
}
if (part.type === 'data-workflow-step') {
const stepData = part.data as WorkflowStepData
return (
<StepIndicator
key={index}
name={stepData.step.name}
status={stepData.step.status}
output={stepData.step.output}
/>
)
}
return null
})}
</div>
))}
</div>
)
}
Workflow 스트리밍에 대한 자세한 내용은 다음을 참조하세요.Workflow Streaming.
네트워크 데이터 렌더링네트워크 데이터 렌더링에 대한 직접 링크
사용시networkRoute() or handleNetworkStream(), Mastra emits data-network 부분에는 호출된 에이전트와 각 출력 등 Agent 네트워크의 실행 상태가 포함됩니다.
- Backend
- Frontend
Mastra에 Agent를 등록하고 다음을 통해 라우팅 Agent를 노출합니다.networkRoute() 하여 네트워크 실행 이벤트를 프런트엔드로 스트리밍합니다.
import { Mastra } from '@mastra/core'
import { networkRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
agents: { routingAgent, researchAgent, weatherAgent },
server: {
apiRoutes: [
networkRoute({
path: '/network',
agent: 'routingAgent',
}),
],
},
})
확인data-network 부분을 사용하고, NetworkDataPart type for type safety.
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import type { NetworkDataPart } from '@mastra/ai-sdk'
type NetworkData = NetworkDataPart['data']
function AgentStep({ step }: { step: NetworkData['steps'][number] }) {
return (
<div className="agent-step">
<div className="step-header">
<span className="agent-name">{step.name}</span>
<span className={`status status-${step.status}`}>{step.status}</span>
</div>
{step.input && (
<div className="step-input">
<strong>Input:</strong>
<pre>{JSON.stringify(step.input, null, 2)}</pre>
</div>
)}
{step.output && (
<div className="step-output">
<strong>Output:</strong>
<pre>
{typeof step.output === 'string' ? step.output : JSON.stringify(step.output, null, 2)}
</pre>
</div>
)}
</div>
)
}
export function NetworkChat() {
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/network',
}),
})
return (
<div>
{messages.map(message => (
<div key={message.id}>
{message.parts.map((part, index) => {
if (part.type === 'data-network') {
const networkData = part.data as NetworkData
return (
<div key={index} className="network-execution">
<div className="network-header">
<h3>Agent Network: {networkData.name}</h3>
<span className={`status status-${networkData.status}`}>
{networkData.status}
</span>
</div>
<div className="network-steps">
{networkData.steps.map((step, stepIndex) => (
<AgentStep key={stepIndex} step={step} />
))}
</div>
</div>
)
}
return null
})}
</div>
))}
</div>
)
}
Agent 네트워크에 대한 자세한 내용은 다음을 참조하세요.Agent Networks.
맞춤 이벤트맞춤 이벤트에 대한 직접 링크
사용writer.custom() within a tool's execute() 사용자 지정 데이터 파트를 내보내는 함수입니다. Tool 실행 중 진행률 표시기, 상태 업데이트 또는 사용자 지정 UI 업데이트에 유용합니다.
맞춤 이벤트 유형은 다음으로 시작해야 합니다.data- to be recognized as data parts.
당신은해야합니다await the writer.custom() call, otherwise you may encounter a WritableStream is locked error.
- Backend
- Frontend
사용writer.custom() inside the tool's execute() function to emit custom data- prefixed events at different stages of execution.
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const taskTool = createTool({
id: 'process-task',
description: 'Process a task with progress updates',
inputSchema: z.object({
task: z.string().describe('The task to process'),
}),
outputSchema: z.object({
result: z.string(),
status: z.string(),
}),
execute: async (inputData, context) => {
const { task } = inputData
// Emit "in progress" custom event
await context?.writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message: 'Gathering information...',
},
})
// Simulate work
await new Promise(resolve => setTimeout(resolve, 3000))
// Emit "done" custom event
await context?.writer?.custom({
type: 'data-tool-progress',
data: {
status: 'done',
message: `Successfully processed "${task}"`,
},
})
return {
result: `Task "${task}" has been completed successfully!`,
status: 'completed',
}
},
})
사용자 정의 이벤트 유형에 대한 메시지 부분을 필터링하고 새 이벤트가 도착할 때 업데이트되는 진행률 표시기를 렌더링합니다.
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useMemo } from 'react'
type ProgressData = {
status: 'in-progress' | 'done'
message: string
}
function ProgressIndicator({ progress }: { progress: ProgressData }) {
return (
<div className="progress-indicator">
{progress.status === 'in-progress' ? (
<span className="spinner" />
) : (
<span className="check-icon" />
)}
<span className={`status-${progress.status}`}>{progress.message}</span>
</div>
)
}
export function TaskChat() {
const { messages, sendMessage } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/chat/taskAgent',
}),
})
// Extract the latest progress event from messages
const latestProgress = useMemo(() => {
const allProgressParts: ProgressData[] = []
messages.forEach(message => {
message.parts.forEach(part => {
if (part.type === 'data-tool-progress') {
allProgressParts.push(part.data as ProgressData)
}
})
})
return allProgressParts[allProgressParts.length - 1]
}, [messages])
return (
<div>
{latestProgress && <ProgressIndicator progress={latestProgress} />}
{messages.map(message => (
<div key={message.id}>
{message.parts.map((part, index) => {
if (part.type === 'text') {
return <p key={index}>{part.text}</p>
}
return null
})}
</div>
))}
</div>
)
}
Tool 스트리밍Tool 스트리밍에 대한 직접 링크
Tool은 다음을 사용하여 데이터를 스트리밍할 수도 있습니다.context.writer.write() 더 세밀하게 제어하거나 Agent의 스트림을 Tool의 writer로 직접 파이프할 수 있습니다. 자세한 내용은 Tool Streaming.
예예에 대한 직접 링크
사용자 정의 UI 패턴의 실제 예를 보려면 다음을 방문하세요.Mastra's UI Dojo. The repository includes implementations for:
- 생성 UI: Tool 출력을 위한 사용자 정의 구성 요소
- Workflow: Workflow 단계 시각화
- Agent 네트워크: 네트워크 실행 화면
- 맞춤 이벤트: 맞춤 이벤트가 포함된 진행률 표시기
조리법조리법에 대한 직접 링크
스트림 변환스트림 변환에 대한 직접 링크
Mastra의 스트림을 AI SDK 호환 형식으로 수동으로 변환하려면toAISdkStream() utility. See the examples for concrete usage patterns.
toAISdkStream()기존 AI SDK v5/기본 동작을 유지합니다. 앱이 AI SDK v6에 대해 입력된 경우 다음을 통과하세요.version: 'v6'.
import { toAISdkStream } from '@mastra/ai-sdk'
const v5Stream = toAISdkStream(mastraStream, { from: 'agent' })
const v6Stream = toAISdkStream(mastraStream, { from: 'agent', version: 'v6' })
기록 메시지 로드 중기록 메시지 로드 중에 대한 직접 링크
Mastra의 Memory에서 메시지를 로드하여 채팅 UI에 표시할 때 다음을 사용하세요.toAISdkV5Messages() or toAISdkV4Messages() 이를 다음에 적합한 AI SDK 형식으로 변환하려면 useChat()'s initialMessages.
추가 데이터 전달추가 데이터 전달에 대한 직접 링크
sendMessage()프런트엔드에서 Mastra로 추가 데이터를 전달할 수 있습니다. 이 데이터는 서버에서 다음과 같이 사용될 수 있습니다.RequestContext.
다음은 프런트엔드 코드의 예입니다.
import { useChat } from '@ai-sdk/react'
import { useState } from 'react'
import { DefaultChatTransport } from 'ai'
export function ChatAdditional() {
const [inputValue, setInputValue] = useState('')
const { messages, sendMessage } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/chat-extra',
}),
})
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault()
sendMessage(
{ text: inputValue },
{
body: {
data: {
userId: 'user123',
preferences: {
language: 'en',
temperature: 'celsius',
},
},
},
},
)
}
return (
<div>
<pre>{JSON.stringify(messages, null, 2)}</pre>
<form onSubmit={handleFormSubmit}>
<input
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="Name of the city"
/>
</form>
</div>
)
}
다음 예제 중 하나를 사용하여 백엔드를 구현합니다.
- Mastra Server
- Next.js
추가chatRoute() 위에 표시된 것처럼 Mastra 구성에 추가합니다. 그런 다음 서버 수준 미들웨어를 추가합니다:
import { Mastra } from '@mastra/core'
export const mastra = new Mastra({
server: {
middleware: [
async (c, next) => {
const requestContext = c.get('requestContext')
if (c.req.method === 'POST') {
const clonedReq = c.req.raw.clone()
const body = await clonedReq.json()
if (body?.data) {
for (const [key, value] of Object.entries(body.data)) {
requestContext.set(key, value)
}
}
}
await next()
},
],
},
})
다음을 통해 Tool에서 이 데이터에 액세스할 수 있습니다.requestContext parameter. See the Request Context documentation for more details.
import { handleChatStream } from '@mastra/ai-sdk'
import { RequestContext } from '@mastra/core/request-context'
import { createUIMessageStreamResponse } from 'ai'
import { mastra } from '@/src/mastra'
export async function POST(req: Request) {
const { messages, data } = await req.json()
const requestContext = new RequestContext()
if (data) {
for (const [key, value] of Object.entries(data)) {
requestContext.set(key, value)
}
}
const stream = await handleChatStream({
mastra,
agentId: 'weatherAgent',
params: {
messages,
requestContext,
},
})
return createUIMessageStreamResponse({ stream })
}
사용자 승인으로 Workflow 일시 중지/재개사용자 승인으로 Workflow 일시 중지/재개에 대한 직접 링크
Workflow는 실행을 일시 중단하고 계속하기 전에 사용자 입력을 기다릴 수 있습니다. 이는 승인 흐름, 확인 또는 인간 참여 시나리오에 유용합니다.
Workflow에서는 다음을 사용합니다.
suspendSchema/resumeSchema- 일시 중단 페이로드와 재개 입력의 데이터 구조 정의suspend()- Workflow를 일시 중지하고 일시 중지 페이로드를 UI로 보냅니다.resumeData- Workflow가 재개될 때 사용자의 응답을 포함합니다.bail()- Workflow를 조기에 종료합니다(예: 사용자가 거부하는 경우).
- Backend
- Frontend
승인을 위해 일시 중지되는 Workflow 단계를 만듭니다. 단계 확인resumeData to determine if it's resuming, and calls suspend() on first execution.
import { createStep, createWorkflow } from '@mastra/core/workflows'
import { z } from 'zod'
const requestApproval = createStep({
id: 'request-approval',
inputSchema: z.object({ requestId: z.string(), summary: z.string() }),
outputSchema: z.object({
approved: z.boolean(),
requestId: z.string(),
approvedBy: z.string().optional(),
}),
resumeSchema: z.object({
approved: z.boolean(),
approverName: z.string().optional(),
}),
suspendSchema: z.object({
message: z.string(),
requestId: z.string(),
}),
execute: async ({ inputData, resumeData, suspend, bail }) => {
// User rejected - bail out
if (resumeData?.approved === false) {
return bail({ message: 'Request rejected' })
}
// User approved - continue
if (resumeData?.approved) {
return {
approved: true,
requestId: inputData.requestId,
approvedBy: resumeData.approverName || 'User',
}
}
// First execution - suspend and wait
return await suspend({
message: `Please approve: ${inputData.summary}`,
requestId: inputData.requestId,
})
},
})
export const approvalWorkflow = createWorkflow({
id: 'approval-workflow',
inputSchema: z.object({ requestId: z.string(), summary: z.string() }),
outputSchema: z.object({
approved: z.boolean(),
requestId: z.string(),
approvedBy: z.string().optional(),
}),
}).then(requestApproval)
approvalWorkflow.commit()
Workflow를 등록합니다. 상태를 유지하려면 일시중단/재개하려면 스토리지가 필요합니다.
import { Mastra } from '@mastra/core'
import { workflowRoute } from '@mastra/ai-sdk'
import { LibSQLStore } from '@mastra/libsql'
export const mastra = new Mastra({
workflows: { approvalWorkflow },
storage: new LibSQLStore({
id: 'mastra-storage',
url: 'file:../mastra.db',
}),
server: {
apiRoutes: [
workflowRoute({ path: '/workflow/approvalWorkflow', workflow: 'approvalWorkflow' }),
],
},
})
Workflow가 일시 중단된 시기를 감지하고 다음을 사용하여 이력서 데이터를 보냅니다.runId, step, and resumeData.
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useMemo, useState } from 'react'
import type { WorkflowDataPart } from '@mastra/ai-sdk'
type WorkflowData = WorkflowDataPart['data']
export function ApprovalWorkflow() {
const [requestId, setRequestId] = useState('')
const [summary, setSummary] = useState('')
const { messages, sendMessage, setMessages, status } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/workflow/approvalWorkflow',
prepareSendMessagesRequest: ({ messages }) => {
const lastMessage = messages[messages.length - 1]
const text = lastMessage.parts.find(p => p.type === 'text')?.text
const metadata = lastMessage.metadata as Record<string, string>
// Resuming: send runId, step, and resumeData
if (text === 'Approve' || text === 'Reject') {
return {
body: {
runId: metadata.runId,
step: 'request-approval',
resumeData: { approved: text === 'Approve' },
},
}
}
// Starting: send inputData
return {
body: { inputData: { requestId: metadata.requestId, summary: metadata.summary } },
}
},
}),
})
// Find suspended workflow
const suspended = useMemo(() => {
for (const m of messages) {
for (const p of m.parts) {
if (p.type === 'data-workflow' && (p.data as WorkflowData).status === 'suspended') {
return { data: p.data as WorkflowData, runId: p.id }
}
}
}
return null
}, [messages])
const handleApprove = () => {
setMessages([])
sendMessage({ text: 'Approve', metadata: { runId: suspended?.runId } })
}
const handleReject = () => {
setMessages([])
sendMessage({ text: 'Reject', metadata: { runId: suspended?.runId } })
}
return (
<div>
{!suspended ? (
<form
onSubmit={e => {
e.preventDefault()
setMessages([])
sendMessage({ text: 'Start', metadata: { requestId, summary } })
}}
>
<input
value={requestId}
onChange={e => setRequestId(e.target.value)}
placeholder="Request ID"
/>
<input value={summary} onChange={e => setSummary(e.target.value)} placeholder="Summary" />
<button type="submit" disabled={status !== 'ready'}>
Submit
</button>
</form>
) : (
<div>
<p>
{
(suspended.data.steps['request-approval']?.suspendPayload as { message: string })
?.message
}
</p>
<button onClick={handleApprove}>Approve</button>
<button onClick={handleReject}>Reject</button>
</div>
)}
</div>
)
}
핵심 사항:
- 일시 중지 페이로드는 다음을 통해 액세스할 수 있습니다.
step.suspendPayload - 재개하려면 다음을 보내세요.
runId,step(the step ID), andresumeDatain the request body - Workflow 상태를 유지하려면 일시 중지/재개에 대한 스토리지를 구성해야 합니다.
전체 구현을 보려면 다음을 참조하세요.workflow-suspend-resume example in UI Dojo.
중첩된 Agent 스트림 인 Tool중첩된 Agent 스트림 인 Tool에 대한 직접 링크
Tool은 내부적으로 Agent를 호출하고 Agent의 출력을 다시 UI로 스트리밍할 수 있습니다. 이렇게 하면 컴팩트한data-tool-agent 중첩된 단계가 아직 실행 중인 동안의 스냅샷, data-tool-agent-step 중첩된 단계가 완료될 때의 파트와 하나의 전체 data-tool-agent snapshot when the nested run finishes.
패턴은 다음을 사용합니다.
context.mastra.getAgent()- Tool 내에서 Agent 인스턴스 가져오기agent.stream()- Agent의 응답을 스트리밍합니다.stream.fullStream.pipeTo(context.writer)- Agent의 스트림을 Tool 작성자에게 파이프합니다.
- Backend
- Frontend
Agent를 호출하고 해당 스트림을 Tool 작성자에게 파이프하는 Tool을 만듭니다.
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const nestedAgentTool = createTool({
id: 'nested-agent-stream',
description: 'Analyze weather using a nested agent',
inputSchema: z.object({
city: z.string().describe('The city to analyze'),
}),
outputSchema: z.object({
summary: z.string(),
}),
execute: async (inputData, context) => {
const agent = context?.mastra?.getAgent('weatherAgent')
if (!agent) {
return { summary: 'Weather agent not available' }
}
const stream = await agent.stream(
`Analyze the weather in ${inputData.city} and provide a summary.`,
)
// Pipe the agent's stream to emit data-tool-agent parts
await stream.fullStream.pipeTo(context!.writer!)
return { summary: (await stream.text) ?? 'No summary available' }
},
})
이 Tool을 사용하는 Agent를 만듭니다.
import { Agent } from '@mastra/core/agent'
import { nestedAgentTool } from '../tools/nested-agent-tool'
export const forecastAgent = new Agent({
id: 'forecast-agent',
instructions: 'Use the nested-agent-stream tool when asked about weather.',
model: 'openai/gpt-5.6-sol',
tools: { nestedAgentTool },
})
핸들data-tool-agent parts for the live snapshot and data-tool-agent-step parts for the completed nested step payload.
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useState } from 'react'
import type { AgentDataPart, AgentStepDataPart } from '@mastra/ai-sdk'
export function NestedAgentChat() {
const [input, setInput] = useState('')
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/chat/forecastAgent',
}),
})
return (
<div>
<form
onSubmit={e => {
e.preventDefault()
sendMessage({ text: input })
setInput('')
}}
>
<input value={input} onChange={e => setInput(e.target.value)} placeholder="Enter a city" />
<button type="submit" disabled={status !== 'ready'}>
Get Forecast
</button>
</form>
{messages.map(message => (
<div key={message.id}>
{message.parts.map((part, index) => {
if (part.type === 'text') {
return <p key={index}>{part.text}</p>
}
if (part.type === 'data-tool-agent') {
const { id, data } = part as AgentDataPart
return (
<div key={index} className="nested-agent">
<strong>Nested Agent: {id}</strong>
{data.text && <p>{data.text}</p>}
</div>
)
}
if (part.type === 'data-tool-agent-step') {
const { data } = part as AgentStepDataPart
return (
<div key={index} className="nested-agent-step">
<strong>Completed nested step {data.stepIndex + 1}</strong>
{data.step.text && <p>{data.step.text}</p>}
</div>
)
}
return null
})}
</div>
))}
</div>
)
}
핵심 사항:
- 관
fullStreamtocontext.writercreatesdata-tool-agentparts - 읽다
data-tool-agent-step방금 완료된 중첩 단계의 전체 페이로드가 필요할 때 - 그만큼
AgentDataParthasid(on the part) anddata.text(the current nested-agent text snapshot) - 스트림이 완료된 후에도 Tool은 여전히 자체 출력을 반환합니다.
전체 구현을 보려면 다음을 참조하세요.tool-nested-streams example in UI Dojo.
Workflow 단계의 스트리밍 Agent 텍스트Workflow 단계의 스트리밍 Agent 텍스트에 대한 직접 링크
Workflow 단계에서는 Agent의 스트림을 단계의 스트림으로 파이프하여 Agent의 텍스트 출력을 실시간으로 스트리밍할 수 있습니다.writer. 이를 통해 사용자는 단계가 완료될 때까지 기다리지 않고 Workflow가 실행되는 동안 Agent가 "생각하는" 과정을 볼 수 있습니다.
패턴은 다음을 사용합니다.
writerWorkflow 단계에서 - Agent의 파이프라인fullStreamto the step's writertext그리고data-workflow파트 - 프런트엔드는 단계 진행 상황과 함께 스트리밍 텍스트를 수신합니다
- Backend
- Frontend
단계의 응답으로 파이프를 연결하여 Agent의 응답을 스트리밍하는 Workflow 단계를 만듭니다.writer.
import { createStep, createWorkflow } from '@mastra/core/workflows'
import { z } from 'zod'
import { weatherAgent } from '../agents/weather-agent'
const analyzeWeather = createStep({
id: 'analyze-weather',
inputSchema: z.object({ location: z.string() }),
outputSchema: z.object({ analysis: z.string(), location: z.string() }),
execute: async ({ inputData, writer }) => {
const response = await weatherAgent.stream(
`Analyze the weather in ${inputData.location} and provide insights.`,
)
// Pipe agent stream to step writer for real-time text streaming
await response.fullStream.pipeTo(writer)
return {
analysis: await response.text,
location: inputData.location,
}
},
})
const calculateScore = createStep({
id: 'calculate-score',
inputSchema: z.object({ analysis: z.string(), location: z.string() }),
outputSchema: z.object({ score: z.number(), summary: z.string() }),
execute: async ({ inputData }) => {
const score = inputData.analysis.includes('sunny') ? 85 : 50
return { score, summary: `Comfort score for ${inputData.location}: ${score}/100` }
},
})
export const weatherWorkflow = createWorkflow({
id: 'weather-workflow',
inputSchema: z.object({ location: z.string() }),
outputSchema: z.object({ score: z.number(), summary: z.string() }),
})
.then(analyzeWeather)
.then(calculateScore)
weatherWorkflow.commit()
Workflow를workflowRoute(). Text streaming is enabled by default.
import { Mastra } from '@mastra/core'
import { workflowRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
agents: { weatherAgent },
workflows: { weatherWorkflow },
server: {
apiRoutes: [workflowRoute({ path: '/workflow/weather', workflow: 'weatherWorkflow' })],
},
})
둘 다 렌더링text parts (streaming agent output) and data-workflow parts (step progress).
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useState } from 'react'
import type { WorkflowDataPart } from '@mastra/ai-sdk'
type WorkflowData = WorkflowDataPart['data']
export function WeatherWorkflow() {
const [location, setLocation] = useState('')
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: 'http://localhost:4111/workflow/weather',
prepareSendMessagesRequest: ({ messages }) => ({
body: {
inputData: {
location: messages[messages.length - 1].parts.find(p => p.type === 'text')?.text,
},
},
}),
}),
})
return (
<div>
<form
onSubmit={e => {
e.preventDefault()
sendMessage({ text: location })
setLocation('')
}}
>
<input
value={location}
onChange={e => setLocation(e.target.value)}
placeholder="Enter city"
/>
<button type="submit" disabled={status !== 'ready'}>
Analyze
</button>
</form>
{messages.map(message => (
<div key={message.id}>
{message.parts.map((part, index) => {
// Streaming agent text
if (part.type === 'text' && message.role === 'assistant') {
return (
<div key={index}>
{status === 'streaming' && (
<p>
<em>Agent analyzing...</em>
</p>
)}
<p>{part.text}</p>
</div>
)
}
// Workflow step progress
if (part.type === 'data-workflow') {
const workflow = part.data as WorkflowData
return (
<div key={index}>
{Object.entries(workflow.steps).map(([stepId, step]) => (
<div key={stepId}>
<strong>{stepId}</strong>: {step.status}
</div>
))}
</div>
)
}
return null
})}
</div>
))}
</div>
)
}
핵심 사항:
- 단계의
writeris available in theexecutefunction (not viacontext) includeTextStreamParts기본값은trueonworkflowRoute(), so text streams by default- 텍스트 부분은 실시간으로 스트리밍됩니다.
data-workflowparts update with step status
전체 구현을 보려면 다음을 참조하세요.workflow-agent-text-stream example in UI Dojo.
분기 Workflow를 통한 다단계 진행분기 Workflow를 통한 다단계 진행에 대한 직접 링크
조건부 분기가 있는 Workflow(예: 빠른 배달과 표준 배달)의 경우 사용자 지정 이벤트에 식별자를 포함하여 여러 분기의 진행 상황을 추적할 수 있습니다.
UI Dojo 예제에서는 다음을 사용합니다.stage 이벤트 데이터의 필드를 사용하여 실행 중인 분기를 식별합니다(예: "validation", "standard-processing", "express-processing"). 프런트엔드는 이 필드를 기준으로 이벤트를 그룹화하여 파이프라인 스타일의 진행 상황 UI를 표시합니다.
참조branching-workflow.ts (backend) and workflow-custom-events.tsx (frontend) in UI Dojo.
Agent 네트워크의 진행률 표시기Agent 네트워크의 진행률 표시기에 대한 직접 링크
Agent 네트워크를 사용할 때 하위 Agent가 사용하는 Tool에서 사용자 정의 진행 이벤트를 내보내 현재 활성 상태인 Agent를 표시할 수 있습니다.
UI Dojo 예제에는 다음이 포함됩니다.stage 이벤트 데이터의 필드를 사용하여 실행 중인 하위 Agent를 식별합니다(예: "report-generation", "report-review"). 프런트엔드는 이 필드를 기준으로 이벤트를 그룹화하고 각각의 최신 상태를 표시합니다.
참조report-generation-tool.ts (backend) and agent-network-custom-events.tsx (frontend) in UI Dojo.