> Discover all available pages from the documentation index: https://mastra.zisheng.pro/fr/llms.txt
# Utiliser AI SDK UI
[AI SDK UI](https://sdk.vercel.ai) est une bibliothèque d'utilitaires et de composants React permettant de créer des interfaces alimentées par l'IA. Dans ce guide, vous apprendrez à utiliser `@mastra/ai-sdk` pour convertir les sorties de Mastra dans des formats compatibles avec AI SDK, afin d'employer ses hooks et ses composants dans votre frontend.
> **Remarque:** Vous migrez d'AI SDK v4 vers v5 ? Consultez le [guide de migration](https://mastra.zisheng.pro/fr/guides/migrations/ai-sdk-v4-to-v5).
> **Astuce:** Vous souhaitez voir davantage d'exemples ? Consultez le [**UI Dojo**](https://ui-dojo.mastra.ai/) de Mastra ou le [guide de démarrage rapide Next.js](https://mastra.zisheng.pro/fr/guides/getting-started/next-js).
## Bien démarrer
Utilisez Mastra et AI SDK UI ensemble en installant le package `@mastra/ai-sdk`. `@mastra/ai-sdk` fournit des routes d'API personnalisées et des utilitaires permettant de diffuser les agents Mastra dans des formats compatibles avec AI SDK. Il comprend des gestionnaires de routes pour le chat, les workflows et les réseaux, ainsi que des utilitaires et des types exportés destinés aux intégrations d'interface utilisateur.
`@mastra/ai-sdk` s'intègre aux trois principaux hooks d'AI SDK UI : [`useChat()`](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot), [`useCompletion()`](https://ai-sdk.dev/docs/ai-sdk-ui/completion) et [`useObject()`](https://ai-sdk.dev/docs/ai-sdk-ui/object-generation).
Pour commencer, installez les packages requis :
**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
```
Vous pouvez maintenant suivre les guides d'intégration et les recettes ci-dessous !
## Guides d'intégration
En général, vous configurez des routes d'API qui diffusent le contenu Mastra dans un format compatible avec AI SDK, puis utilisez ces routes dans des hooks AI SDK UI tels que `useChat()`. Choisissez l'une des approches suivantes :
- [Serveur Mastra](#mastras-server)
- [Indépendante du framework](#framework-agnostic)
Une fois vos routes d'API configurées, vous pouvez les utiliser dans le hook [`useChat()`](#usechat).
### Serveur Mastra
Exécutez Mastra comme serveur autonome et connectez votre frontend (par exemple avec Vite + React) à ses points de terminaison d'API. Pour cela, vous utiliserez la fonctionnalité de [routes d'API personnalisées](https://mastra.zisheng.pro/fr/docs/server/custom-api-routes) de Mastra.
> **Info:** Le [**UI Dojo**](https://ui-dojo.mastra.ai/) de Mastra illustre cette configuration.
Vous pouvez utiliser [`chatRoute()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/chat-route), [`workflowRoute()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/workflow-route) et [`networkRoute()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/network-route) pour créer des routes d'API qui diffusent le contenu Mastra dans un format compatible avec AI SDK. Une fois mises en œuvre, ces routes d'API peuvent être utilisées dans [`useChat()`](#usechat).
**chatRoute()**:
Cet exemple montre comment configurer, sur le point de terminaison `/chat`, une route de chat qui utilise un agent portant l'ID `weatherAgent`.
```typescript
import { Mastra } from '@mastra/core'
import { chatRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
server: {
apiRoutes: [
chatRoute({
path: '/chat',
agent: 'weatherAgent',
}),
],
},
})
```
Vous pouvez également utiliser le routage dynamique des agents. Consultez la [documentation de référence de `chatRoute()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/chat-route) pour en savoir plus.
**workflowRoute()**:
Cet exemple montre comment configurer, sur le point de terminaison `/workflow`, une route de workflow qui utilise un workflow portant l'ID `weatherWorkflow`.
```typescript
import { Mastra } from '@mastra/core'
import { workflowRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
server: {
apiRoutes: [
workflowRoute({
path: '/workflow',
workflow: 'weatherWorkflow',
}),
],
},
})
```
Vous pouvez également utiliser le routage dynamique des workflows. Consultez la [documentation de référence de `workflowRoute()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/workflow-route) pour en savoir plus.
> **Streaming des agents dans les workflows:** Lorsqu'une étape de workflow redirige le stream d'un agent vers le writer du workflow (par exemple, `await response.fullStream.pipeTo(writer)`), les segments de texte et les appels de tools de l'agent sont transmis en temps réel au stream de l'interface utilisateur, même si l'agent s'exécute au sein d'étapes de workflow.
>
> Consultez la section [Streaming des workflows](https://mastra.zisheng.pro/fr/docs/workflows/overview) pour en savoir plus.
**networkRoute()**:
Cet exemple montre comment configurer, sur le point de terminaison `/network`, une route de réseau qui utilise un agent portant l'ID `weatherAgent`.
```typescript
import { Mastra } from '@mastra/core'
import { networkRoute } from '@mastra/ai-sdk'
export const mastra = new Mastra({
server: {
apiRoutes: [
networkRoute({
path: '/network',
agent: 'weatherAgent',
}),
],
},
})
```
Vous pouvez également utiliser le routage dynamique des réseaux. Consultez la [documentation de référence de `networkRoute()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/network-route) pour en savoir plus.
### Indépendante du framework
Si vous ne souhaitez pas exécuter le serveur Mastra et préférez utiliser des frameworks comme Next.js ou Express, vous pouvez employer les fonctions [`handleChatStream()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/handle-chat-stream), [`handleWorkflowStream()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/handle-workflow-stream) et [`handleNetworkStream()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/handle-network-stream) dans vos propres gestionnaires de routes d'API.
Elles renvoient un `ReadableStream` que vous pouvez encapsuler avec [`createUIMessageStreamResponse()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream-response).
> **Compatibilité avec AI SDK v6:** Les gestionnaires indépendants du framework conservent le comportement existant d'AI SDK v5, utilisé par défaut. Si votre application est typée avec AI SDK v6, transmettez `version: 'v6'`. Pour bénéficier de la meilleure inférence TypeScript avec `handleChatStream()` et `handleNetworkStream()`, transmettez `messages` sous la forme `UIMessage[]` provenant de la version de `ai` installée.
Les exemples ci-dessous montrent comment les utiliser avec Next.js App Router.
**handleChatStream()**:
Cet exemple montre comment configurer, sur le point de terminaison `/chat`, une route de chat qui utilise un agent portant l'ID `weatherAgent`.
```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()**:
Cet exemple montre comment configurer, sur le point de terminaison `/workflow`, une route de workflow qui utilise un workflow portant l'ID `weatherWorkflow`.
```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()**:
Cet exemple montre comment configurer, sur le point de terminaison `/network`, une route de réseau qui utilise un agent portant l'ID `routingAgent`.
```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()`
Que vous ayez créé des routes d'API avec le [serveur Mastra](#mastras-server) ou utilisé le [framework de votre choix](#framework-agnostic), vous pouvez désormais employer les points de terminaison de l'API dans le hook `useChat()`.
En supposant que vous ayez configuré sur `/chat` une route utilisant un agent météo, vous pouvez lui poser des questions comme dans l'exemple ci-dessous. Veillez à définir correctement l'URL `api`.
```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)}
)
}
```
Utilisez [`prepareSendMessagesRequest`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#transport.default-chat-transport.prepare-send-messages-request) pour personnaliser la requête envoyée à la route de chat, par exemple afin de transmettre une configuration supplémentaire à l'agent.
## Utiliser la Memory de Mastra
Lorsque la [memory](https://mastra.zisheng.pro/fr/docs/memory/overview) de votre agent est configurée, Mastra charge l'historique des conversations depuis le stockage sur le serveur. Depuis le client, envoyez uniquement le nouveau message plutôt que l'intégralité de l'historique.
L'envoi de l'historique complet est redondant et peut provoquer des erreurs d'ordre des messages, car les horodatages côté client peuvent entrer en conflit avec ceux stockés dans votre base de données.
```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',
},
},
}
},
}),
})
```
Définissez `memory.thread` et `memory.resource` à partir de l'état propre à votre application, par exemple les paramètres d'URL, le contexte d'authentification ou votre base de données.
Consultez la section [Historique des messages](https://mastra.zisheng.pro/fr/docs/memory/message-history) pour en savoir plus sur la façon dont la Memory de Mastra charge et stocke les messages.
[`chatRoute()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/chat-route) et [`handleChatStream()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/handle-chat-stream) fonctionnent déjà avec la Memory. Configurez le client pour qu'il envoie uniquement le nouveau message et inclue les identifiants de thread et de ressource.
### `useCompletion()`
Le hook `useCompletion()` gère les complétions à un seul tour entre votre frontend et un agent Mastra, ce qui vous permet d'envoyer un prompt et de recevoir une réponse diffusée via HTTP.
Votre frontend pourrait ressembler à ceci :
```typescript
import { useCompletion } from '@ai-sdk/react'
export default function Page() {
const { completion, input, handleInputChange, handleSubmit } = useCompletion({
api: '/api/completion',
})
return (
)
}
```
Choisissez une implémentation backend :
**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 })
}
```
## Interface utilisateur personnalisée
Custom UI (également appelée Generative UI) permet d'afficher des composants React personnalisés à partir de données diffusées par Mastra. Au lieu d'afficher du texte brut ou du JSON, vous pouvez créer des composants visuels pour les sorties de tools et la progression des workflows, notamment l'exécution des réseaux d'agents et les événements personnalisés.
Utilisez Custom UI lorsque vous souhaitez :
- Afficher les sorties de tools sous forme de composants visuels (par exemple, une carte météo plutôt que du JSON)
- Afficher la progression des étapes d'un workflow à l'aide d'indicateurs d'état
- Visualiser l'exécution d'un réseau d'agents avec des mises à jour étape par étape
- Afficher des indicateurs de progression ou des mises à jour d'état pendant les opérations de longue durée
### Types de parties de données
Mastra diffuse les données vers le frontend sous forme de « parties » au sein des messages. Chaque partie possède un `type` qui détermine son rendu. Le package `@mastra/ai-sdk` transforme les streams Mastra en [UI Message DataParts](https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message#datauipart) compatibles avec AI SDK.
| Type de partie de données | Source | Description |
| ------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------- |
| `tool-{toolKey}` | Fonctionnalité intégrée d'AI SDK | Appel de Tool avec les états : `input-available`, `output-available`, `output-error` |
| `data-workflow` | `workflowRoute()` | Instantanés de l'état d'exécution du workflow avec l'état des étapes et les sorties finales |
| `data-workflow-step` | `workflowRoute()` | Delta d'une étape de workflow avec le payload complet de l'étape modifiée |
| `data-network` | `networkRoute()` | Exécution du réseau d'agents avec les étapes et sorties ordonnées |
| `data-tool-agent` | Agent imbriqué dans un Tool | Instantané compact de l'agent imbriqué pendant que l'étape courante est encore en cours |
| `data-tool-agent-step` | Agent imbriqué dans un Tool | Payload complet de l'étape de l'agent imbriqué émis à la fin d'une étape imbriquée |
| `data-tool-workflow` | Workflow imbriqué dans un Tool | Sortie du workflow diffusée depuis la fonction `execute()` d'un Tool |
| `data-tool-network` | Réseau imbriqué dans un Tool | Sortie du réseau diffusée depuis la fonction `execute()` d'un Tool |
| `data-{custom}` | `writer.custom()` | Événements personnalisés pour les indicateurs de progression, les mises à jour d'état, etc. |
### Afficher les sorties des Tools
AI SDK crée automatiquement des parties `tool-{toolKey}` lorsqu'un agent appelle un Tool. Ces parties comprennent l'état et la sortie du Tool, que vous pouvez utiliser pour afficher des composants personnalisés.
La partie du Tool passe successivement par les états suivants :
- `input-streaming` : l'entrée du Tool est en cours de diffusion (lorsque le streaming des appels de Tools est activé)
- `input-available` : le Tool a été appelé avec une entrée complète et attend son exécution
- `output-available` : l'exécution du Tool est terminée et a produit une sortie
- `output-error` : l'exécution du Tool a échoué
Voici un exemple de rendu de la sortie d'un Tool météo sous forme de composant `WeatherCard` personnalisé.
**Backend**:
Définissez un Tool avec un `outputSchema` afin que le frontend connaisse la structure des données à afficher.
```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**:
Recherchez les parties `tool-{toolKey}` dans le message et affichez un composant personnalisé en fonction de l'état et de la sortie du 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
})}
))}
)
}
```
> **Astuce:** Le type de partie du Tool suit le modèle `tool-{toolKey}`, où `toolKey` est la clé utilisée lors de l'enregistrement du Tool auprès de l'agent. Par exemple, si vous enregistrez des Tools sous la forme `tools: { weatherTool }`, le type de la partie sera `tool-weatherTool`.
### Afficher les données de workflow
Lorsque vous utilisez `workflowRoute()` ou `handleWorkflowStream()`, Mastra émet des parties `data-workflow` pour les instantanés de l'état du workflow et des parties `data-workflow-step` pour le payload complet de l'étape modifiée. Les workflows de longue durée évitent ainsi de répéter la sortie de chaque étape terminée dans tous les instantanés intermédiaires.
**Backend**:
Définissez un workflow en plusieurs étapes qui émettra des parties `data-workflow` et `data-workflow-step` au fil de son exécution.
```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()
```
Enregistrez le workflow auprès de Mastra et exposez-le avec `workflowRoute()` afin de diffuser les événements du workflow vers le frontend.
```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**:
Recherchez les parties `data-workflow` afin d'afficher les instantanés de l'état du workflow. Si vous avez besoin du payload complet de l'étape qui vient d'être modifiée, lisez également les parties `data-workflow-step`.
```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 (
)
}
if (part.type === 'data-workflow-step') {
const stepData = part.data as WorkflowStepData
return (
)
}
return null
})}
))}
)
}
```
Pour en savoir plus sur le streaming des workflows, consultez la section [Streaming des workflows](https://mastra.zisheng.pro/fr/docs/workflows/overview).
### Afficher les données de réseau
Lorsque vous utilisez `networkRoute()` ou `handleNetworkStream()`, Mastra émet des parties `data-network` qui contiennent l'état d'exécution du réseau d'agents, notamment les agents appelés et leurs sorties.
**Backend**:
Enregistrez les agents auprès de Mastra et exposez l'agent de routage avec `networkRoute()` afin de diffuser les événements d'exécution du réseau vers le frontend.
```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**:
Recherchez les parties `data-network` et affichez l'étape d'exécution de chaque agent en utilisant le type `NetworkDataPart` pour garantir la sûreté des types.
```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 (
)
}
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
})}
))}
)
}
```
Pour en savoir plus sur les réseaux d'agents, consultez la section [Réseaux d'agents](https://mastra.zisheng.pro/fr/docs/agents/networks).
### Événements personnalisés
Utilisez `writer.custom()` dans la fonction `execute()` d'un Tool afin d'émettre des parties de données personnalisées. Cette méthode est utile pour les indicateurs de progression, les mises à jour d'état ou toute mise à jour personnalisée de l'interface utilisateur pendant l'exécution du Tool.
Les types d'événements personnalisés doivent commencer par `data-` pour être reconnus comme des parties de données.
> **Attention:** Vous devez utiliser `await` lors de l'appel à `writer.custom()`, faute de quoi vous risquez de rencontrer une erreur `WritableStream is locked`.
**Backend**:
Utilisez `writer.custom()` dans la fonction `execute()` du Tool afin d'émettre, à différentes étapes de l'exécution, des événements personnalisés préfixés par `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**:
Filtrez les parties du message selon votre type d'événement personnalisé et affichez un indicateur de progression qui s'actualise à mesure que de nouveaux événements arrivent.
```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 (
{message.parts.map((part, index) => {
if (part.type === 'text') {
return
{part.text}
}
return null
})}
))}
)
}
```
### Streaming des Tools
Les Tools peuvent également diffuser des données à l'aide de `context.writer.write()` pour un contrôle de plus bas niveau, ou rediriger directement le stream d'un agent vers le writer du Tool. Pour en savoir plus, consultez la section [Streaming des Tools](https://mastra.zisheng.pro/fr/docs/agents/using-tools).
### Exemples
Pour voir des exemples interactifs de modèles Custom UI, consultez le [UI Dojo de Mastra](https://ui-dojo.mastra.ai/). Le dépôt contient des implémentations pour :
- [Interfaces utilisateur génératives](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/generative-user-interfaces.tsx) : composants personnalisés pour les sorties de Tools
- [Workflows](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow.tsx) : visualisation des étapes du workflow
- [Réseaux d'agents](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/network.tsx) : affichage de l'exécution du réseau
- [Événements personnalisés](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/generative-user-interfaces-with-custom-events.tsx) : indicateurs de progression avec des événements personnalisés
## Recettes
### Transformer les streams
Pour transformer manuellement les streams Mastra dans un format compatible avec AI SDK, utilisez l'utilitaire [`toAISdkStream()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/to-ai-sdk-stream). Consultez les [exemples](https://mastra.zisheng.pro/fr/reference/ai-sdk/to-ai-sdk-stream) pour découvrir des modèles d'utilisation concrets.
`toAISdkStream()` conserve le comportement existant d'AI SDK v5, utilisé par défaut. Si votre application est typée avec AI SDK v6, transmettez `version: 'v6'`.
```typescript
import { toAISdkStream } from '@mastra/ai-sdk'
const v5Stream = toAISdkStream(mastraStream, { from: 'agent' })
const v6Stream = toAISdkStream(mastraStream, { from: 'agent', version: 'v6' })
```
### Charger les messages historiques
Lorsque vous chargez des messages depuis la Memory de Mastra pour les afficher dans une interface de chat, utilisez [`toAISdkV5Messages()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/to-ai-sdk-v5-messages) ou [`toAISdkV4Messages()`](https://mastra.zisheng.pro/fr/reference/ai-sdk/to-ai-sdk-v4-messages) afin de les convertir au format AI SDK adapté à `useChat()`, dans sa propriété `initialMessages`.
### Transmettre des données supplémentaires
[`sendMessage()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#send-message) permet de transmettre des données supplémentaires du frontend à Mastra. Ces données peuvent ensuite être utilisées sur le serveur sous la forme d'un [`RequestContext`](https://mastra.zisheng.pro/fr/docs/server/request-context).
Voici un exemple de code frontend :
```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)}
)
}
```
Implémentez le backend à l'aide de l'un des exemples suivants.
**Mastra Server**:
Ajoutez une `chatRoute()` à votre configuration Mastra comme illustré ci-dessus. Ajoutez ensuite un middleware au niveau du serveur :
```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()
},
],
},
})
```
> **Info:** Vous pouvez accéder à ces données dans vos Tools à l'aide du paramètre `requestContext`. Consultez la [documentation sur Request Context](https://mastra.zisheng.pro/fr/docs/server/request-context) pour en savoir plus.
**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 })
}
```
### Suspendre et reprendre un workflow avec l'approbation de l'utilisateur
Les workflows peuvent suspendre leur exécution et attendre une saisie de l'utilisateur avant de continuer. Cette fonctionnalité est utile pour les processus d'approbation, les confirmations ou tout scénario impliquant une intervention humaine.
Le workflow utilise les éléments suivants :
- `suspendSchema` / `resumeSchema` : définissent la structure des données du payload de suspension et de l'entrée de reprise
- `suspend()` : suspend le workflow et envoie le payload de suspension à l'interface utilisateur
- `resumeData` : contient la réponse de l'utilisateur lors de la reprise du workflow
- `bail()` : met fin au workflow de façon anticipée (par exemple, lorsque l'utilisateur refuse)
**Backend**:
Créez une étape de workflow qui se suspend dans l'attente d'une approbation. L'étape examine `resumeData` pour déterminer s'il s'agit d'une reprise et appelle `suspend()` lors de la première exécution.
```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()
```
Enregistrez le workflow. Un stockage est nécessaire pour conserver l'état lors des opérations de suspension et de reprise.
```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**:
Détectez la suspension du workflow et envoyez les données de reprise avec `runId`, `step` et `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 ? (
) : (
{
(suspended.data.steps['request-approval']?.suspendPayload as { message: string })
?.message
}
)}
)
}
```
Points essentiels :
- Le payload de suspension est accessible via `step.suspendPayload`
- Pour reprendre le workflow, envoyez `runId`, `step` (l'ID de l'étape) et `resumeData` dans le corps de la requête
- Le stockage doit être configuré afin de conserver l'état du workflow lors des opérations de suspension et de reprise
Pour consulter une implémentation complète, reportez-vous à l'[exemple workflow-suspend-resume](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-suspend-resume.tsx) dans UI Dojo.
### Streams d'agents imbriqués dans des Tools
Les Tools peuvent appeler des agents en interne et rediffuser la sortie de l'agent vers l'interface utilisateur. Cela crée des instantanés `data-tool-agent` compacts tant que l'étape imbriquée est encore en cours, des parties `data-tool-agent-step` lorsqu'une étape imbriquée se termine et un instantané `data-tool-agent` complet à la fin de l'exécution imbriquée.
Ce modèle utilise les éléments suivants :
- `context.mastra.getAgent()` : récupère une instance d'agent depuis un Tool
- `agent.stream()` : diffuse la réponse de l'agent
- `stream.fullStream.pipeTo(context.writer)` : redirige le stream de l'agent vers le writer du Tool
**Backend**:
Créez un Tool qui appelle un agent et redirige son stream vers le writer du 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' }
},
})
```
Créez un agent qui utilise ce Tool.
```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**:
Traitez les parties `data-tool-agent` pour l'instantané en direct et les parties `data-tool-agent-step` pour le payload de l'étape imbriquée terminée.
```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 (
{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 (
)
}
```
Points essentiels :
- Rediriger `fullStream` vers `context.writer` crée des parties `data-tool-agent`
- Lisez `data-tool-agent-step` lorsque vous avez besoin du payload complet de l'étape imbriquée qui vient de se terminer
- `AgentDataPart` possède `id` (sur la partie) et `data.text` (l'instantané de texte actuel de l'agent imbriqué)
- Le Tool renvoie toujours sa propre sortie une fois le stream terminé
Pour consulter une implémentation complète, reportez-vous à l'[exemple tool-nested-streams](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/tool-nested-streams.tsx) dans UI Dojo.
### Diffuser le texte d'un agent depuis les étapes d'un workflow
Les étapes d'un workflow peuvent diffuser la sortie textuelle d'un agent en temps réel en redirigeant le stream de l'agent vers le `writer` de l'étape. Les utilisateurs peuvent ainsi voir l'agent « réfléchir » pendant l'exécution du workflow, au lieu d'attendre la fin de l'étape.
Ce modèle utilise les éléments suivants :
- `writer` dans l'étape du workflow : redirige le `fullStream` de l'agent vers le writer de l'étape
- Parties `text` et `data-workflow` : le frontend reçoit le texte diffusé en même temps que la progression de l'étape
**Backend**:
Créez une étape de workflow qui diffuse la réponse d'un agent en la redirigeant vers le `writer` de l'étape.
```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()
```
Enregistrez le workflow avec une `workflowRoute()`. Le streaming du texte est activé par défaut.
```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**:
Affichez à la fois les parties `text` (sortie diffusée de l'agent) et les parties `data-workflow` (progression de l'étape).
```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 (
{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 (
)
}
```
Points essentiels :
- Le `writer` de l'étape est disponible dans la fonction `execute` (et non via `context`)
- `includeTextStreamParts` vaut `true` par défaut sur `workflowRoute()` ; le texte est donc diffusé par défaut
- Les parties textuelles sont diffusées en temps réel tandis que les parties `data-workflow` sont mises à jour avec l'état de l'étape
Pour consulter une implémentation complète, reportez-vous à l'[exemple workflow-agent-text-stream](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-agent-text-stream.tsx) dans UI Dojo.
### Progression en plusieurs étapes avec des workflows à branchements
Pour les workflows comportant des branchements conditionnels (par exemple, livraison express ou standard), vous pouvez suivre la progression dans les différentes branches en incluant un identifiant dans vos événements personnalisés.
L'exemple UI Dojo utilise un champ `stage` dans les données de l'événement pour identifier la branche en cours d'exécution (par exemple, `"validation"`, `"standard-processing"`, `"express-processing"`). Le frontend regroupe les événements selon ce champ afin d'afficher une interface de progression sous forme de pipeline.
Consultez les fichiers [branching-workflow.ts](https://github.com/mastra-ai/ui-dojo/blob/main/src/mastra/workflows/branching-workflow.ts) (backend) et [workflow-custom-events.tsx](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/workflow-custom-events.tsx) (frontend) dans UI Dojo.
### Indicateurs de progression dans les réseaux d'agents
Lorsque vous utilisez des réseaux d'agents, vous pouvez émettre des événements de progression personnalisés depuis les Tools employés par les sous-agents afin d'indiquer l'agent actuellement actif.
L'exemple UI Dojo inclut un champ `stage` dans les données de l'événement afin d'identifier le sous-agent en cours d'exécution (par exemple, `"report-generation"`, `"report-review"`). Le frontend regroupe les événements selon ce champ et affiche l'état le plus récent de chacun.
Consultez les fichiers [report-generation-tool.ts](https://github.com/mastra-ai/ui-dojo/blob/main/src/mastra/tools/report-generation-tool.ts) (backend) et [agent-network-custom-events.tsx](https://github.com/mastra-ai/ui-dojo/blob/main/src/pages/ai-sdk/agent-network-custom-events.tsx) (frontend) dans UI Dojo.