> Discover all available pages from the documentation index: https://mastra.zisheng.pro/ja/llms.txt # AI SDK UI を使用する [AI SDK UI](https://sdk.vercel.ai) は、AI を活用したインターフェースを構築するための React ユーティリティとコンポーネントのライブラリです。このガイドでは、`@mastra/ai-sdk` を使用して Mastra の出力を AI SDK 互換形式に変換し、フロントエンドで AI SDK のフックとコンポーネントを使用する方法を説明します。 > **注記:** AI SDK v4 から v5 に移行する場合は、[移行ガイド](https://mastra.zisheng.pro/ja/guides/migrations/ai-sdk-v4-to-v5)を参照してください。 > **ヒント:** さらに例を確認するには、Mastra の [**UI Dojo**](https://ui-dojo.mastra.ai/) または [Next.js クイックスタートガイド](https://mastra.zisheng.pro/ja/guides/getting-started/next-js)を参照してください。 ## はじめに Mastra と AI SDK UI を併用するには、`@mastra/ai-sdk` パッケージをインストールします。`@mastra/ai-sdk` は、Mastra Agent を AI SDK 互換形式でストリーミングするためのカスタム API Route とユーティリティを提供します。これには、チャット、Workflow、Network の Route Handler、UI 統合用のユーティリティとエクスポートされた型が含まれます。 `@mastra/ai-sdk` は、AI SDK UI の 3 つの主要なフック、[`useChat()`](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot)、[`useCompletion()`](https://ai-sdk.dev/docs/ai-sdk-ui/completion)、[`useObject()`](https://ai-sdk.dev/docs/ai-sdk-ui/object-generation) と統合できます。 必要なパッケージをインストールします。 **npm**: ```bash npm install @mastra/ai-sdk@latest @ai-sdk/react ai ``` **pnpm**: ```bash pnpm add @mastra/ai-sdk@latest @ai-sdk/react ai ``` **Yarn**: ```bash yarn add @mastra/ai-sdk@latest @ai-sdk/react ai ``` **Bun**: ```bash bun add @mastra/ai-sdk@latest @ai-sdk/react ai ``` これで、以下の統合ガイドとレシピを実行できます。 ## 統合ガイド 通常は、Mastra のコンテンツを AI SDK 互換形式でストリーミングする API Route を設定し、その Route を `useChat()` などの AI SDK UI フックで使用します。次のいずれかの方法を選択してください。 - [Mastra サーバー](#mastras-server) - [フレームワーク非依存](#framework-agnostic) API Route の設定後、[`useChat()`](#usechat) フックで使用できます。 ### Mastra サーバー Mastra をスタンドアロンサーバーとして実行し、フロントエンド(Vite + React など)を API エンドポイントに接続します。ここでは Mastra の[カスタム API Route](https://mastra.zisheng.pro/ja/docs/server/custom-api-routes) 機能を使用します。 > **情報:** Mastra の [**UI Dojo**](https://ui-dojo.mastra.ai/) は、この構成の例です。 [`chatRoute()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/chat-route)、[`workflowRoute()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/workflow-route)、[`networkRoute()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/network-route) を使用して、Mastra のコンテンツを AI SDK 互換形式でストリーミングする API Route を作成できます。実装後、これらの API Route を [`useChat()`](#usechat) で使用できます。 **chatRoute()**: この例では、ID が `weatherAgent` の Agent を使用するチャットルートを `/chat` エンドポイントに設定します。 ```typescript 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()` リファレンス](https://mastra.zisheng.pro/ja/reference/ai-sdk/chat-route)を参照してください。 **workflowRoute()**: この例では、ID が `weatherWorkflow` の Workflow を使用する Workflow Route を `/workflow` エンドポイントに設定します。 ```typescript 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()` リファレンス](https://mastra.zisheng.pro/ja/reference/ai-sdk/workflow-route)を参照してください。 > **Workflow 内の Agent ストリーミング:** Workflow Step が Agent のストリームを Workflow Writer にパイプすると(`await response.fullStream.pipeTo(writer)` など)、Agent が Workflow Step 内で実行されている場合でも、Agent のテキストチャンクと Tool 呼び出しが UI ストリームへリアルタイムで転送されます。 > > 詳しくは [Workflow ストリーミング](https://mastra.zisheng.pro/ja/docs/workflows/overview)を参照してください。 **networkRoute()**: この例では、ID が `weatherAgent` の Agent を使用する Network Route を `/network` エンドポイントに設定します。 ```typescript import { Mastra } from '@mastra/core' import { networkRoute } from '@mastra/ai-sdk' export const mastra = new Mastra({ server: { apiRoutes: [ networkRoute({ path: '/network', agent: 'weatherAgent', }), ], }, }) ``` 動的な Network ルーティングも使用できます。詳しくは [`networkRoute()` リファレンス](https://mastra.zisheng.pro/ja/reference/ai-sdk/network-route)を参照してください。 ### フレームワーク非依存 Mastra サーバーを実行せず、Next.js や Express などのフレームワークを使用する場合は、独自の API Route Handler で [`handleChatStream()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/handle-chat-stream)、[`handleWorkflowStream()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/handle-workflow-stream)、[`handleNetworkStream()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/handle-network-stream) 関数を使用できます。 これらは [`createUIMessageStreamResponse()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream-response) でラップできる `ReadableStream` を返します。 > **AI SDK v6 との互換性:** フレームワーク非依存の Handler は、既存の AI SDK v5 またはデフォルトの動作を維持します。アプリが AI SDK v6 に対して型付けされている場合は、`version: 'v6'` を渡します。`handleChatStream()` と `handleNetworkStream()` で最適な TypeScript 型推論を得るには、インストール済みの `ai` バージョンの `UIMessage[]` として `messages` を渡してください。 以下の例では、Next.js App Router での使用方法を示します。 **handleChatStream()**: この例では、ID が `weatherAgent` の Agent を使用するチャットルートを `/chat` エンドポイントに設定します。 ```typescript 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 }) } ``` **handleWorkflowStream()**: この例では、ID が `weatherWorkflow` の Workflow を使用する Workflow Route を `/workflow` エンドポイントに設定します。 ```typescript 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 }) } ``` **handleNetworkStream()**: この例では、ID が `routingAgent` の Agent を使用する Network Route を `/network` エンドポイントに設定します。 ```typescript 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()` [Mastra サーバー](#mastras-server)で API Route を作成した場合も、[任意のフレームワーク](#framework-agnostic)を使用した場合も、その API エンドポイントを `useChat()` フックで使用できます。 天気 Agent を使用する Route を `/chat` に設定した場合、次のように質問できます。正しい `api` URL を設定することが重要です。 ```ts 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 (
{JSON.stringify(messages, null, 2)}
setInputValue(e.target.value)} placeholder="Name of the city" />
) } ``` Agent に追加設定を渡すなど、チャットルートへ送信するリクエストをカスタマイズするには、[`prepareSendMessagesRequest`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#transport.default-chat-transport.prepare-send-messages-request) を使用します。 ## Mastra Memory を使用する Agent に [Memory](https://mastra.zisheng.pro/ja/docs/memory/overview) が設定されている場合、Mastra はサーバー上のストレージから会話履歴を読み込みます。クライアントからは、会話履歴全体ではなく新しいメッセージだけを送信してください。 履歴全体の送信は冗長であり、クライアント側のタイムスタンプがデータベースに保存されたタイムスタンプと競合して、メッセージの順序に問題が生じることがあります。 ```typescript 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` と `memory.resource` には、URL Param、Auth Context、データベースなど、アプリ独自の状態から値を設定します。 Mastra Memory がメッセージを読み込み、保存する仕組みについては、[メッセージ履歴](https://mastra.zisheng.pro/ja/docs/memory/message-history)を参照してください。 [`chatRoute()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/chat-route) と [`handleChatStream()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/handle-chat-stream) は、すでに Memory に対応しています。新しいメッセージだけを送信し、Thread と Resource の識別子を含めるようにクライアントを設定してください。 ### `useCompletion()` `useCompletion()` フックは、フロントエンドと Mastra Agent 間の単一 Turn の Completion を処理し、プロンプトを送信して HTTP 経由でストリーミングレスポンスを受信できるようにします。 フロントエンドは次のように実装できます。 ```typescript import { useCompletion } from '@ai-sdk/react' export default function Page() { const { completion, input, handleInputChange, handleSubmit } = useCompletion({ api: '/api/completion', }) return (
{completion}
) } ``` バックエンドの実装を選択します。 **Mastra Server**: ```ts 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 }) }, }), ], }, }) ``` **Next.js**: ```ts 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(Generative UI とも呼ばれます)では、Mastra からストリーミングされるデータに基づいて、カスタム React コンポーネントをレンダリングできます。生のテキストや JSON を表示する代わりに、Tool の出力や Workflow の進行状況(Agent Network の実行やカスタムイベントを含む)を視覚化するコンポーネントを作成できます。 次のような場合にカスタム UI を使用します。 - Tool の出力を視覚的なコンポーネントとしてレンダリングする(JSON の代わりに天気カードを表示するなど) - Workflow Step の進行状況をステータスインジケーターで表示する - Agent Network の実行を Step ごとの更新で視覚化する - 長時間実行される処理中に、進行状況やステータスの更新を表示する ### Data Part のタイプ Mastra は、メッセージ内の「Part」としてデータをフロントエンドにストリーミングします。各 Part には、レンダリング方法を決める `type` があります。`@mastra/ai-sdk` パッケージは Mastra ストリームを AI SDK 互換の [UI Message DataParts](https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message#datauipart) に変換します。 | Data Part のタイプ | ソース | 説明 | | ---------------------- | ---------------------- | -------------------------------------------------------------------- | | `tool-{toolKey}` | AI SDK 組み込み | `input-available`、`output-available`、`output-error` の状態を持つ Tool 呼び出し | | `data-workflow` | `workflowRoute()` | Step のステータスと最終出力を含む Workflow 実行状態の Snapshot | | `data-workflow-step` | `workflowRoute()` | 変更された Step の完全な Payload を持つ Workflow Step の差分 | | `data-network` | `networkRoute()` | 順序付けられた Step と出力を含む Agent Network の実行 | | `data-tool-agent` | Tool 内のネストされた Agent | 現在の Step の実行中に送出される、ネストされた Agent のコンパクトな Snapshot | | `data-tool-agent-step` | Tool 内のネストされた Agent | ネストされた Step の完了時に送出される、その Step の完全な Payload | | `data-tool-workflow` | Tool 内のネストされた Workflow | Tool の `execute()` 内からストリーミングされる Workflow 出力 | | `data-tool-network` | Tool 内のネストされた Network | Tool の `execute()` 内からストリーミングされる Network 出力 | | `data-{custom}` | `writer.custom()` | 進行状況、ステータス更新などのカスタムイベント | ### Tool の出力をレンダリングする Agent が Tool を呼び出すと、AI SDK は `tool-{toolKey}` Part を自動的に作成します。これらの Part には Tool の状態と出力が含まれ、カスタムコンポーネントのレンダリングに使用できます。 Tool Part は次の状態を遷移します。 - `input-streaming`:Tool 呼び出しのストリーミングが有効な場合に、Tool の入力をストリーミング中 - `input-available`:完全な入力で Tool が呼び出され、実行を待機中 - `output-available`:Tool の実行が完了し、出力を利用可能 - `output-error`:Tool の実行に失敗 天気 Tool の出力をカスタム `WeatherCard` コンポーネントとしてレンダリングする例を示します。 **Backend**: ```typescript 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, } }, }) ``` **Frontend**: メッセージ内の `tool-{toolKey}` Part を確認し、Tool の状態と出力に基づいてカスタムコンポーネントをレンダリングします。 ```typescript 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 (
{messages.map(message => (
{message.parts.map((part, index) => { // Handle user text messages if (part.type === 'text' && message.role === 'user') { return

{part.text}

} // Handle weather tool output if (part.type === 'tool-weatherTool') { switch (part.state) { case 'input-available': return case 'output-available': return case 'output-error': return
Error: {part.errorText}
default: return null } } return null })}
))}
) } ``` > **ヒント:** Tool Part のタイプは `tool-{toolKey}` パターンに従います。`toolKey` は Agent に Tool を登録するときに使用したキーです。たとえば `tools: { weatherTool }` として登録すると、Part のタイプは `tool-weatherTool` になります。 ### Workflow データをレンダリングする `workflowRoute()` または `handleWorkflowStream()` を使用すると、Mastra は Workflow 状態の Snapshot を `data-workflow` Part として、変更された Step の完全な Payload を `data-workflow-step` Part として送出します。これにより、長時間実行される Workflow で、完了済みの全 Step の出力が中間 Snapshot ごとに繰り返されることを防ぎます。 **Backend**: 実行中に `data-workflow` Part と `data-workflow-step` Part を送出する、複数 Step の Workflow を定義します。 ```typescript 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()` で公開して、Workflow イベントをフロントエンドにストリーミングします。 ```typescript 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', }), ], }, }) ``` **Frontend**: `data-workflow` Part を確認し、Workflow のステータス Snapshot をレンダリングします。直前に変更された Step の完全な Payload が必要な場合は、`data-workflow-step` Part も読み取ります。 ```typescript 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 (
{name} {status}
{status === 'success' && output &&
{JSON.stringify(output, null, 2)}
}
) } 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 (
{messages.map(message => (
{message.parts.map((part, index) => { if (part.type === 'data-workflow') { const workflowData = part.data as WorkflowData const steps = Object.values(workflowData.steps) return (

Workflow: {workflowData.name}

Status: {workflowData.status}

{steps.map(step => ( ))}
) } if (part.type === 'data-workflow-step') { const stepData = part.data as WorkflowStepData return ( ) } return null })}
))}
) } ``` Workflow ストリーミングについて詳しくは、[Workflow ストリーミング](https://mastra.zisheng.pro/ja/docs/workflows/overview)を参照してください。 ### Network データをレンダリングする `networkRoute()` または `handleNetworkStream()` を使用すると、Mastra は呼び出された Agent とその出力を含む、Agent Network の実行状態を保持した `data-network` Part を送出します。 **Backend**: Agent を Mastra に登録し、`networkRoute()` で Routing Agent を公開して、Network の実行イベントをフロントエンドにストリーミングします。 ```typescript 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', }), ], }, }) ``` **Frontend**: `data-network` Part を確認し、型安全性のために `NetworkDataPart` 型を使用して各 Agent の実行 Step をレンダリングします。 ```typescript 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 (
{step.name} {step.status}
{step.input && (
Input:
{JSON.stringify(step.input, null, 2)}
)} {step.output && (
Output:
            {typeof step.output === 'string' ? step.output : JSON.stringify(step.output, null, 2)}
          
)}
) } export function NetworkChat() { const { messages, sendMessage, status } = useChat({ transport: new DefaultChatTransport({ api: 'http://localhost:4111/network', }), }) return (
{messages.map(message => (
{message.parts.map((part, index) => { if (part.type === 'data-network') { const networkData = part.data as NetworkData return (

Agent Network: {networkData.name}

{networkData.status}
{networkData.steps.map((step, stepIndex) => ( ))}
) } return null })}
))}
) } ``` Agent Network について詳しくは、[Agent Network](https://mastra.zisheng.pro/ja/docs/agents/networks) を参照してください。 ### カスタムイベント Tool の `execute()` 関数内で `writer.custom()` を使用して、カスタム Data Part を送出します。Tool の実行中に進行状況、ステータス更新、その他のカスタム UI 更新を表示する場合に便利です。 Data Part として認識されるには、カスタムイベントのタイプを `data-` で始める必要があります。 > **警告:** `writer.custom()` の呼び出しは必ず `await` してください。そうしないと `WritableStream is locked` エラーが発生する可能性があります。 **Backend**: Tool の `execute()` 関数内で `writer.custom()` を使用し、実行の各段階で `data-` プレフィックス付きのカスタムイベントを送出します。 ```typescript 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', } }, }) ``` **Frontend**: メッセージの Part をカスタムイベントタイプで絞り込み、新しいイベントが届くたびに更新される進行状況インジケーターをレンダリングします。 ```typescript 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 (
{progress.status === 'in-progress' ? ( ) : ( )} {progress.message}
) } 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 (
{latestProgress && } {messages.map(message => (
{message.parts.map((part, index) => { if (part.type === 'text') { return

{part.text}

} return null })}
))}
) } ``` ### Tool ストリーミング Tool は、より低レベルの制御が必要な場合に `context.writer.write()` でデータをストリーミングしたり、Agent のストリームを Tool の Writer に直接パイプしたりできます。詳しくは [Tool ストリーミング](https://mastra.zisheng.pro/ja/docs/agents/using-tools)を参照してください。 ### 例 カスタム UI パターンの動作例は、[Mastra の UI Dojo](https://ui-dojo.mastra.ai/) で確認できます。リポジトリには次の実装が含まれます。 - [Generative UI](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/generative-user-interfaces.tsx):Tool 出力用のカスタムコンポーネント - [Workflow](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow.tsx):Workflow Step の視覚化 - [Agent Network](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/network.tsx):Network 実行の表示 - [カスタムイベント](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/generative-user-interfaces-with-custom-events.tsx):カスタムイベントを使用した進行状況インジケーター ## レシピ ### ストリーム変換 Mastra のストリームを手動で AI SDK 互換形式に変換するには、[`toAISdkStream()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/to-ai-sdk-stream) ユーティリティを使用します。具体的な使用パターンは[例](https://mastra.zisheng.pro/ja/reference/ai-sdk/to-ai-sdk-stream)を参照してください。 `toAISdkStream()` は、既存の AI SDK v5 またはデフォルトの動作を維持します。アプリが AI SDK v6 に対して型付けされている場合は、`version: 'v6'` を渡します。 ```typescript import { toAISdkStream } from '@mastra/ai-sdk' const v5Stream = toAISdkStream(mastraStream, { from: 'agent' }) const v6Stream = toAISdkStream(mastraStream, { from: 'agent', version: 'v6' }) ``` ### 過去のメッセージを読み込む チャット UI に表示するために Mastra Memory からメッセージを読み込む場合は、[`toAISdkV5Messages()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/to-ai-sdk-v5-messages) または [`toAISdkV4Messages()`](https://mastra.zisheng.pro/ja/reference/ai-sdk/to-ai-sdk-v4-messages) を使用し、`useChat()` の `initialMessages` に適した AI SDK 形式へ変換します。 ### 追加データを渡す [`sendMessage()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#send-message) を使用すると、フロントエンドから Mastra に追加データを渡せます。このデータは、サーバー上で [`RequestContext`](https://mastra.zisheng.pro/ja/docs/server/request-context) として使用できます。 フロントエンドコードの例を示します。 ```typescript 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 (
{JSON.stringify(messages, null, 2)}
setInputValue(e.target.value)} placeholder="Name of the city" />
) } ``` 次のいずれかの例でバックエンドを実装します。 **Mastra Server**: 前述のように、Mastra 設定に `chatRoute()` を追加します。次に、サーバーレベルの Middleware を追加します。 ```typescript 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` パラメーターからこのデータにアクセスできます。詳しくは [Request Context ドキュメント](https://mastra.zisheng.pro/ja/docs/server/request-context)を参照してください。 **Next.js**: ```typescript 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 は実行を中断し、続行前にユーザー入力を待つことができます。承認フロー、確認、Human-in-the-loop のシナリオに便利です。 Workflow では次の要素を使用します。 - `suspendSchema` / `resumeSchema`:中断 Payload と再開入力のデータ構造を定義する - `suspend()`:Workflow を一時停止し、中断 Payload を UI に送信する - `resumeData`:Workflow の再開時にユーザーの応答を保持する - `bail()`:Workflow を早期終了する(ユーザーが拒否した場合など) **Backend**: 承認のために中断する Workflow Step を作成します。Step は `resumeData` を確認して再開時の実行かどうかを判断し、初回実行時に `suspend()` を呼び出します。 ```typescript 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 を登録します。中断と再開の状態を永続化するにはストレージが必要です。 ```typescript 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' }), ], }, }) ``` **Frontend**: Workflow の中断を検出し、`runId`、`step`、`resumeData` とともに再開データを送信します。 ```typescript 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 // 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 (
{!suspended ? (
{ e.preventDefault() setMessages([]) sendMessage({ text: 'Start', metadata: { requestId, summary } }) }} > setRequestId(e.target.value)} placeholder="Request ID" /> setSummary(e.target.value)} placeholder="Summary" />
) : (

{ (suspended.data.steps['request-approval']?.suspendPayload as { message: string }) ?.message }

)}
) } ``` 重要なポイントは次のとおりです。 - 中断 Payload には `step.suspendPayload` からアクセスできます - 再開するには、Request Body に `runId`、`step`(Step ID)、`resumeData` を含めます - 中断と再開の Workflow 状態を永続化するには、ストレージの設定が必要です 完全な実装については、UI Dojo の [workflow-suspend-resume の例](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-suspend-resume.tsx)を参照してください。 ### Tool 内でネストされた Agent のストリーム Tool は内部で Agent を呼び出し、その出力を UI にストリーミングできます。ネストされた Step の実行中はコンパクトな `data-tool-agent` Snapshot、Step の完了時は `data-tool-agent-step` Part、ネストされた Run の完了時は完全な `data-tool-agent` Snapshot が作成されます。 このパターンでは次の要素を使用します。 - `context.mastra.getAgent()`:Tool 内から Agent インスタンスを取得する - `agent.stream()`:Agent のレスポンスをストリーミングする - `stream.fullStream.pipeTo(context.writer)`:Agent のストリームを Tool の Writer にパイプする **Backend**: Agent を呼び出し、そのストリームを Tool の Writer にパイプする Tool を作成します。 ```typescript 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 を作成します。 ```typescript 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 }, }) ``` **Frontend**: 実行中の Snapshot には `data-tool-agent` Part を、完了したネスト Step の Payload には `data-tool-agent-step` Part を使用します。 ```typescript 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 (
{ e.preventDefault() sendMessage({ text: input }) setInput('') }} > setInput(e.target.value)} placeholder="Enter a city" />
{messages.map(message => (
{message.parts.map((part, index) => { if (part.type === 'text') { return

{part.text}

} if (part.type === 'data-tool-agent') { const { id, data } = part as AgentDataPart return (
Nested Agent: {id} {data.text &&

{data.text}

}
) } if (part.type === 'data-tool-agent-step') { const { data } = part as AgentStepDataPart return (
Completed nested step {data.stepIndex + 1} {data.step.text &&

{data.step.text}

}
) } return null })}
))}
) } ``` 重要なポイントは次のとおりです。 - `fullStream` を `context.writer` にパイプすると、`data-tool-agent` Part が作成されます - 直前に完了したネスト Step の完全な Payload が必要な場合は、`data-tool-agent-step` を読み取ります - `AgentDataPart` には `id`(Part 上)と `data.text`(現在のネスト Agent テキストの Snapshot)があります - ストリームの完了後も、Tool は独自の出力を返します 完全な実装については、UI Dojo の [tool-nested-streams の例](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/tool-nested-streams.tsx)を参照してください。 ### Workflow Step から Agent テキストをストリーミングする Workflow Step は、Agent のストリームを Step の `writer` にパイプして、Agent のテキスト出力をリアルタイムでストリーミングできます。これにより、Step の完了を待つのではなく、Workflow の実行中に Agent が「思考」する様子をユーザーに表示できます。 このパターンでは次の要素を使用します。 - Workflow Step の `writer`:Agent の `fullStream` を Step の Writer にパイプする - `text` Part と `data-workflow` Part:フロントエンドが Step の進行状況とともにストリーミングテキストを受信する **Backend**: Agent のレスポンスを Step の `writer` にパイプしてストリーミングする Workflow Step を作成します。 ```typescript 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() ``` `workflowRoute()` で Workflow を登録します。テキストストリーミングはデフォルトで有効です。 ```typescript 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' })], }, }) ``` **Frontend**: `text` Part(Agent 出力のストリーミング)と `data-workflow` Part(Step の進行状況)の両方をレンダリングします。 ```typescript 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 (
{ e.preventDefault() sendMessage({ text: location }) setLocation('') }} > setLocation(e.target.value)} placeholder="Enter city" />
{messages.map(message => (
{message.parts.map((part, index) => { // Streaming agent text if (part.type === 'text' && message.role === 'assistant') { return (
{status === 'streaming' && (

Agent analyzing...

)}

{part.text}

) } // Workflow step progress if (part.type === 'data-workflow') { const workflow = part.data as WorkflowData return (
{Object.entries(workflow.steps).map(([stepId, step]) => (
{stepId}: {step.status}
))}
) } return null })}
))}
) } ``` 重要なポイントは次のとおりです。 - Step の `writer` は `execute` 関数で使用できます(`context` 経由ではありません) - `workflowRoute()` の `includeTextStreamParts` はデフォルトで `true` のため、テキストはデフォルトでストリーミングされます - `data-workflow` Part で Step のステータスが更新される間、テキスト Part はリアルタイムでストリーミングされます 完全な実装については、UI Dojo の [workflow-agent-text-stream の例](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-agent-text-stream.tsx)を参照してください。 ### 分岐 Workflow で複数段階の進行状況を表示する 条件分岐を持つ Workflow(速達配送と通常配送など)では、カスタムイベントに識別子を含めることで、異なる分岐の進行状況を追跡できます。 UI Dojo の例では、イベントデータの `stage` フィールドを使用して、実行中の分岐(`"validation"`、`"standard-processing"`、`"express-processing"` など)を識別します。フロントエンドはこのフィールドでイベントをグループ化し、Pipeline 形式の進行状況 UI を表示します。 UI Dojo の [branching-workflow.ts](https://github.com/mastra-ai/ui-dojo/blob/main/src/mastra/workflows/branching-workflow.ts)(バックエンド)と [workflow-custom-events.tsx](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-custom-events.tsx)(フロントエンド)を参照してください。 ### Agent Network の進行状況インジケーター Agent Network を使用する場合、Subagent が使用する Tool からカスタムの進行状況イベントを送出して、現在アクティブな Agent を表示できます。 UI Dojo の例では、イベントデータの `stage` フィールドを使用して、実行中の Subagent(`"report-generation"`、`"report-review"` など)を識別します。フロントエンドはこのフィールドでイベントをグループ化し、それぞれの最新ステータスを表示します。 UI Dojo の [report-generation-tool.ts](https://github.com/mastra-ai/ui-dojo/blob/main/src/mastra/tools/report-generation-tool.ts)(バックエンド)と [agent-network-custom-events.tsx](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/agent-network-custom-events.tsx)(フロントエンド)を参照してください。 フロントエンドがレンダリングするデータの構造を認識できるように、`outputSchema` を持つ Tool を定義します。