Conversational Workflows

Conversational workflows are meant to be integrated in conversations interface, allowing a user to trigger a workflow, interact with it by providing inputs during its execution, and follow its progress.

Getting started

Getting started

To use Conversational workflow features, install the Mistral plugin:

uv add 'mistralai-workflows[mistralai]'

The simplest conversational workflow sends an assistant message to the user, then waits for their response. Extend InteractiveWorkflow and use send_assistant_message() followed by wait_for_input() with ChatInput:

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="greeting-workflow",
    workflow_display_name="Greeting",
    workflow_description="A simple conversational workflow",
)
class GreetingWorkflow(workflows.InteractiveWorkflow):
    @workflows.workflow.entrypoint
    async def run(self) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        # Send a message to the user
        await workflows_mistralai.send_assistant_message(
            "Hello! I'm here to help you get started. What's your name?"
        )

        # Wait for the user's response
        user_input = await self.wait_for_input(
            workflows_mistralai.ChatInput()
        )

        name = user_input.message[0].text if user_input.message else "friend"

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text=f"Nice to meet you, {name}!")]
        )

send_assistant_message() displays a message to the user in the chat interface. It also accepts an optional canvas keyword argument to include a CanvasResource alongside the text (see Canvas Editing). ChatInput() pauses the workflow and waits for the user to respond. You can optionally pass a prompt to ChatInput() to provide additional context (in placeholder) and suggestions to offer pre-filled options that users can select directly.

Timeout

wait_for_input() accepts an optional timeout parameter. If the user doesn't respond within the specified duration, an asyncio.TimeoutError is raised. The timeout can be a timedelta or a number of seconds (float). By default, the workflow waits indefinitely.

from datetime import timedelta

# Wait for user response, but time out after 5 minutes
user_input = await self.wait_for_input(
    workflows_mistralai.ChatInput(),
    timeout=timedelta(minutes=5),
)
tip

Wrap the call in a try/except asyncio.TimeoutError block to handle the timeout gracefully instead of failing the workflow.

Structured Form Inputs

Structured Form Inputs

For workflows that need structured form input with typed fields, validation, and custom UI rendering, use FormInput instead of ChatInput:

from datetime import date, datetime

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai
from mistralai.workflows.conversational import (
    FormInput,
    TextField,
    NumberField,
    DateField,
    DateTimeField,
    SingleChoice,
)


class ExpenseForm(FormInput):
    """Structured form for expense submission."""

    description: str = TextField(description="Expense description")
    amount: float = NumberField(
        description="Amount in USD",
        minimum=0,
        maximum=10000,
    )
    category: str = SingleChoice(
        options=[
            ("travel", "Travel"),
            ("equipment", "Equipment"),
            ("software", "Software"),
        ],
        description="Expense category",
    )
    expense_date: date = DateField(description="Date of expense")
    due_date: datetime = DateTimeField(description="Reimbursement due date")
    receipt_id: str = TextField(
        description="Receipt ID",
        pattern=r"^RCP-\d{6}$",
    )


@workflows.workflow.define(
    name="expense-submission-workflow",
    workflow_display_name="Expense Submission",
    workflow_description="Submit an expense with structured form",
)
class ExpenseSubmissionWorkflow(workflows.InteractiveWorkflow):
    @workflows.workflow.entrypoint
    async def run(self) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        expense = await self.wait_for_input(
            ExpenseForm,
            label="Submit Expense",
        )

        result = f"""Expense submitted:
- Description: {expense.description}
- Amount: ${expense.amount:.2f}
- Category: {expense.category}
- Date: {expense.expense_date.isoformat()}
- Due date: {expense.due_date.isoformat()}
- Receipt: {expense.receipt_id}"""

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text=result)]
        )

Field Types

Field TypeDescriptionProperties
TextFieldText inputdescription, pattern (optional regex)
NumberFieldNumeric inputdescription, minimum, maximum, exclusive_minimum, exclusive_maximum
DateTimeFieldDate/time pickerdescription
DateFieldDate pickerdescription
SingleChoiceDropdown/selectoptions (list of tuples or strings), description
MultiChoiceMulti-selectoptions (list of tuples or strings), description
FileFieldFile uploaddescription, multiple (default False)

TextField

name: str = TextField(description="Your name")
email: str = TextField(
    description="Email address",
    pattern=r"^[\w.-]+@[\w.-]+\.\w+$",  # Optional regex validation
)

NumberField

amount: float = NumberField(
    description="Amount",
    minimum=0,           # Inclusive minimum
    maximum=10000,       # Inclusive maximum
)
price: float = NumberField(
    description="Price",
    exclusive_minimum=0,   # Must be greater than 0
    exclusive_maximum=100, # Must be less than 100
)

DateTimeField

from datetime import datetime

scheduled_at: datetime = DateTimeField(description="Schedule date and time")

DateField

from datetime import date

scheduled_at: date = DateField(description="Schedule date")

SingleChoice

# With labels (value, display_label)
priority: str = SingleChoice(
    options=[
        ("low", "Low Priority"),
        ("medium", "Medium Priority"),
        ("high", "High Priority"),
    ],
    description="Select priority",
)

# Simple string options (value = label)
status: str = SingleChoice(
    options=["pending", "approved", "rejected"],
    description="Status",
)

MultiChoice

# With labels (value, display_label)
tags: list[str] = MultiChoice(
    options=[
        ("frontend", "Frontend"),
        ("backend", "Backend"),
        ("infra", "Infrastructure"),
    ],
    description="Select applicable tags",
)

# Simple string options (value = label)
colors: list[str] = MultiChoice(
    options=["red", "green", "blue"],
    description="Pick colors",
)

FileField

from mistralai.workflows.conversational import FileField

# Single file upload
document: str = FileField(description="Upload a document")

# Multiple file uploads
attachments: list[str] = FileField(description="Upload files", multiple=True)

The workflow receives URLs (strings) pointing to files uploaded by the user. To process a file, download it from the provided URL within your workflow. These URLs may expire, so if your workflow needs long-term access to the files, it is responsible for storing them elsewhere.

Confirmation Inputs

Confirmation Inputs

For workflows that need a simple single-choice confirmation with direct submit, use ConfirmationInput or AcceptDeclineConfirmation. These helpers create a single-field form where selecting an option immediately submits the form.

ConfirmationInput

ConfirmationInput provides a list of options that should be rendered as buttons. Selection of an option should immediately submit the form:

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="type-selection-workflow",
    workflow_display_name="Type Selection",
    workflow_description="Select your favorite type",
)
class TypeSelectionWorkflow(workflows.InteractiveWorkflow):
    @workflows.workflow.entrypoint
    async def run(self) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        await workflows_mistralai.send_assistant_message("Let's find out your type preference!")

        selection = await self.wait_for_input(
            workflows_mistralai.ConfirmationInput(
                options=[
                    ("fire", "Fire"),
                    ("water", "Water"),
                    ("grass", "Grass"),
                    ("electric", "Electric"),
                ],
                description="What is your favorite type?",
            )
        )

        selected_type = selection.choice  # Returns the value, e.g., "fire"
        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text=f"You selected {selected_type}!")]
        )
PropertyTypeDescription
optionslist[tuple[str, str]] or list[str]List of options as (value, label) tuples or simple strings
descriptionstrDescription shown above the options

The returned object has a choice property containing the selected option value.

AcceptDeclineConfirmation

AcceptDeclineConfirmation is a specialized confirmation with two options: accept and decline. Clients can render this as a standard validation UI with keyboard shortcuts for quick responses:

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="approval-workflow",
    workflow_display_name="Approval",
    workflow_description="Confirm an action",
)
class ApprovalWorkflow(workflows.InteractiveWorkflow):
    @workflows.workflow.entrypoint
    async def run(self) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        confirmation = await self.wait_for_input(
            workflows_mistralai.AcceptDeclineConfirmation(
                description="Do you want to proceed with this action?",
                accept_label="Yes, proceed",
                decline_label="Cancel",
            )
        )

        if workflows_mistralai.is_accepted(confirmation):
            return workflows_mistralai.ChatAssistantWorkflowOutput(
                content=[workflows_mistralai.TextOutput(text="Action confirmed!")]
            )
        else:
            return workflows_mistralai.ChatAssistantWorkflowOutput(
                content=[workflows_mistralai.TextOutput(text="Action cancelled.")]
            )
PropertyTypeDescription
descriptionstrDescription shown above the buttons
accept_labelstrLabel for the accept button
decline_labelstrLabel for the decline button

Use the is_accepted() helper function to check whether the user accepted or declined:

if workflows_mistralai.is_accepted(confirmation):
    # User accepted
    pass
else:
    # User declined
    pass
Todo List Progress Tracking

Todo List Progress Tracking

Display a checklist of steps with real-time status updates using TodoList:

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="expense-processing-workflow",
    workflow_display_name="Expense Processing",
    workflow_description="Process expense with step-by-step progress",
)
class ExpenseProcessingWorkflow(workflows.InteractiveWorkflow):
    @workflows.workflow.entrypoint
    async def run(self, expense_id: str) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        # Define the steps
        validate_item = workflows_mistralai.TodoListItem(
            title="Validate expense",
            description="Check expense details and receipts"
        )
        approve_item = workflows_mistralai.TodoListItem(
            title="Get approval",
            description="Route to manager for approval"
        )
        process_item = workflows_mistralai.TodoListItem(
            title="Process payment",
            description="Submit for reimbursement"
        )

        async with workflows_mistralai.TodoList(
            items=[validate_item, approve_item, process_item]
        ) as todo_list:
            # Step 1: Validate (using context manager for automatic status)
            async with validate_item:
                pass  # ... validation logic ...

            # Step 2: Approve (using context manager for automatic status)
            async with approve_item:
                pass  # ... approval logic ...

            # Step 3: Process (using context manager for automatic status)
            async with process_item:
                pass  # ... processing logic ...

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text=f"Expense {expense_id} processed successfully")]
        )

Updating Item Status

There are two ways to update the status of a TodoListItem:

Context Manager:

async with item:
    # Status automatically set to "in_progress" on enter
    # ... do work ...
    # Status automatically set to "done" on successful exit

Manual Control (for fine-grained status updates):

await item.set_status("in_progress")
# ... do work ...
await item.set_status("done")

Use manual control when you need to update status at specific points, handle conditional flows, or implement custom exception handling.

TodoListItem Properties

PropertyTypeDescription
idstrAuto-generated UUID
titlestrDisplay title for the step
descriptionstrDetailed description
status"todo" | "in_progress" | "done"Current status (defaults to "todo")
Streaming Agent Responses

Streaming Agent Responses

When using agents with RemoteSession(stream=True), responses are automatically streamed to the UI as custom events. No additional code is needed:

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="expense-analysis-workflow",
    workflow_display_name="Expense Analysis",
    workflow_description="AI-powered expense analysis",
)
class ExpenseAnalysisWorkflow:
    @workflows.workflow.entrypoint
    async def run(self, expense_data: str) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        # Create a streaming session - responses will automatically
        # stream to the UI as they are generated
        session = workflows_mistralai.RemoteSession(stream=True)

        analyst_agent = workflows_mistralai.Agent(
            model="mistral-medium-2508",
            name="expense-analyst",
            description="Analyzes expense reports for policy compliance",
            instructions="""You are an expense report analyst. Review the expense data
and provide insights on:
1. Policy compliance
2. Unusual patterns
3. Optimization suggestions

Be concise and professional.""",
        )

        # The agent's response streams automatically custom events with JSON patches to update the UI.
        await workflows_mistralai.Runner.run(
            agent=analyst_agent,
            inputs=f"Analyze this expense report:\n\n{expense_data}",
            session=session,
        )

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text="Analysis complete.")]
        )
tip

When stream=True, the agent's text output is streamed token-by-token to the UI. This provides a responsive experience for longer responses.

Rich Outputs (Canvas)

Rich Outputs (Canvas)

Return rich content like markdown, code, or diagrams using ResourceOutput:

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="expense-report-workflow",
    workflow_display_name="Expense Report",
    workflow_description="Generate expense report with charts",
)
class ExpenseReportWorkflow:
    @workflows.workflow.entrypoint
    async def run(self, department: str) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        # Generate a mermaid chart
        chart_content = """
pie title Expenses by Category
    "Travel" : 45
    "Equipment" : 25
    "Software" : 20
    "Other" : 10
"""

        canvas = workflows_mistralai.CanvasPayload(
            type="mermaid",
            title="Expense Breakdown",
            content=chart_content,
        )

        resource = workflows_mistralai.CanvasResource(
            canvas=canvas,
        )

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[
                workflows_mistralai.TextOutput(text=f"Expense report for {department}:"),
                workflows_mistralai.ResourceOutput(resource=resource),
            ]
        )

Canvas Types

TypeDescription
text/markdownMarkdown content
text/htmlHTML content
image/svg+xmlSVG images
slidesPresentation slides
reactReact components
codeCode with syntax highlighting
mermaidMermaid diagrams
Canvas Editing (Human-in-the-Loop)

Canvas Editing (Human-in-the-Loop)

You can send a canvas mid-workflow using send_assistant_message() and then let the user edit it. The canvas_uri passed to CanvasInput must match the uri of a CanvasResource output from a previous step.

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai
from mistralai.workflows.conversational import CanvasInput


@workflows.workflow.define(
    name="report-review-workflow",
    workflow_display_name="Report Review",
    workflow_description="Generate a report and let the user edit it",
)
class ReportReviewWorkflow(workflows.InteractiveWorkflow):
    @workflows.workflow.entrypoint
    async def run(self) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        # Send a canvas to the user as an assistant message
        canvas_resource = workflows_mistralai.CanvasResource(
            canvas=workflows_mistralai.CanvasPayload(
                type="text/markdown",
                title="Weekly Report",
                content="# Weekly Report\n\n## Summary\n\nTODO: fill in",
            ),
        )
        await workflows_mistralai.send_assistant_message(
            "Here is your report draft. You can review and edit it below.",
            canvas=canvas_resource,
        )

        # Wait for the user to edit the canvas
        edited = await self.wait_for_input(
            CanvasInput(canvas_uri=canvas_resource.uri, prompt="Any feedback?"),
            label="Review & Edit Report",
        )

        # Use the edited content
        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[
                workflows_mistralai.TextOutput(text="Report finalized!"),
                workflows_mistralai.ResourceOutput(
                    resource=workflows_mistralai.CanvasResource(
                        canvas=workflows_mistralai.CanvasPayload(
                            type="text/markdown",
                            title=edited.canvas.title,
                            content=edited.canvas.content,
                        ),
                    )
                ),
            ]
        )

CanvasInput returns a model with:

  • canvas.title — the title of the edited canvas
  • canvas.content — the edited content
  • chatInput — optional chat message (only present when prompt is provided and the user sends a message)
Rich UI Components

Rich UI Components

Workflows can render rich, interactive UI components in the chat interface using the design system component library. Components are defined as Python objects and sent as UIComponentResource resources.

Basic Usage

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai
from mistralai.workflows.conversational_ui_components import (
    Badge,
    Card,
    Column,
    Markdown,
    Row,
)


@workflows.workflow.define(
    name="report-workflow",
    workflow_display_name="Report",
    workflow_description="Generate a rich UI report",
)
class ReportWorkflow:
    @workflows.workflow.entrypoint
    async def run(self) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        report = Card(
            title="Summary",
            children=[
                Row(
                    children=[
                        Markdown(content="**Score:** 0.82"),
                        Badge(variant="success", children="Pass"),
                    ],
                ),
            ],
        )

        await workflows_mistralai.send_assistant_message(
            [
                workflows_mistralai.TextOutput(text="Here is your report:"),
                workflows_mistralai.ResourceOutput(
                    resource=workflows_mistralai.UIComponentResource(component=report),
                ),
            ]
        )

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[
                workflows_mistralai.ResourceOutput(
                    resource=workflows_mistralai.UIComponentResource(component=report),
                ),
            ],
        )

Available Components

All components are imported from mistralai.workflows.conversational_ui_components.

ComponentDescriptionKey Props
AlertImportant messages with severity levelstitle, variant (info/warning/error/success), children
AvatarUser avatar imagesrc, alt, text, size
BadgeSmall label for status indicatorsvariant (default/primary/success/warning/error), size, children
ButtonLinkLink styled as a buttonhref, variant, size, external, children
CardContainer with optional title and descriptiontitle, description, padding, children
ChartLine or bar chartvariant (line/bar), data, xAxis, yAxis, title
ColumnVertical layout containeralignment, distribution, gap, children
ImageImage displaysrc, alt, size
MarkdownMarkdown-formatted textcontent
PieChartPie chart with labeled segmentsdata, title
RowHorizontal layout containeralignment, distribution, gap, wrap, children
TooltipAdditional information on hovertrigger, children

Nesting Components

Components that accept children can contain other components, allowing you to build complex layouts:

from mistralai.workflows.conversational_ui_components import (
    Card,
    Chart,
    Column,
    Row,
)

layout = Row(
    children=[
        Card(
            title="Revenue",
            children=[
                Chart(
                    variant="line",
                    xAxis="month",
                    yAxis=["actual", "target"],
                    data=[
                        {"month": "Jan", "actual": 100, "target": 120},
                        {"month": "Feb", "actual": 140, "target": 130},
                        {"month": "Mar", "actual": 160, "target": 140},
                    ],
                ),
            ],
        ),
        Card(
            title="Distribution",
            children=[
                Chart(
                    variant="bar",
                    xAxis="category",
                    yAxis="count",
                    data=[
                        {"category": "A", "count": 42},
                        {"category": "B", "count": 28},
                        {"category": "C", "count": 15},
                    ],
                ),
            ],
        ),
    ],
)
Tool UI States

Tool UI States

Tool UI States provide optional visual representations of tool execution in the chat interface. When attached to ChatAssistantWorkingTask, they enable specialized UI feedback for different types of tool operations, allowing workflows to display the status and results of tool calls in a structured, user-friendly way.

Tool UI State Types

There are three types of tool UI states:

File Tool UI State

Represents file operations such as creating, replacing, or deleting files:

from mistralai.workflows.conversational import (
    FileToolUIState,
    CreateFileOperation,
    ReplaceFileOperation,
    DeleteFileOperation,
)

# Create a file
create_state = FileToolUIState(
    toolCallId="tc-1",
    operations=[
        CreateFileOperation(
            uri="file:///workspace/new.py",
            content="print('Hello World')"
        )
    ],
)

# Replace file content
replace_state = FileToolUIState(
    toolCallId="tc-2",
    operations=[
        ReplaceFileOperation(
            uri="file:///workspace/main.py",
            fileContentBefore="old content",
            blocks=[SearchReplaceBlock(search="old", replace="new")],
        )
    ],
)

# Delete a file
delete_state = FileToolUIState(
    toolCallId="tc-3",
    operations=[
        DeleteFileOperation(uri="file:///workspace/old.py")
    ],
)

Generic Tool UI State

Represents generic tool execution with various status states:

from mistralai.workflows.conversational import (
    GenericToolUIState,
    ToolResultRunning,
    ToolResultSuccess,
    ToolResultFailed,
)

# Tool is running
running_state = GenericToolUIState(
    toolCallId="tc-1",
    name="bash",
    arguments={"command": "ls -la"},
    result=ToolResultRunning(),
)

# Tool completed successfully
success_state = GenericToolUIState(
    toolCallId="tc-2",
    name="grep",
    arguments={"pattern": "TODO"},
    result=ToolResultSuccess(value={"matches": ["line1", "line2"]}),
)

# Tool failed
failed_state = GenericToolUIState(
    toolCallId="tc-3",
    name="bash",
    arguments={"command": "false"},
    result=ToolResultFailed(error="exit code 1"),
)

Command Tool UI State

Represents command execution with running/success/failed states:

from mistralai.workflows.conversational import (
    CommandToolUIState,
    CommandResultRunning,
    CommandResultSuccess,
    CommandResultFailed,
)

# Command is running
running_state = CommandToolUIState(
    toolCallId="tc-1",
    command="npm install",
    result=CommandResultRunning(),
)

# Command completed successfully
success_state = CommandToolUIState(
    toolCallId="tc-2",
    command="pytest",
    result=CommandResultSuccess(output="All tests passed"),
)

# Command failed
failed_state = CommandToolUIState(
    toolCallId="tc-3",
    command="invalid-command",
    result=CommandResultFailed(error="Command not found"),
)

Using Tool UI States in Working Tasks

Tool UI States can be attached to ChatAssistantWorkingTask to provide visual feedback during tool execution:

from mistralai.workflows.conversational import ChatAssistantWorkingTask

# Show a working task with file operations
task = ChatAssistantWorkingTask(
    title="Creating file",
    content="Generating new.py",
    toolUIState=FileToolUIState(
        toolCallId="tc-1",
        operations=[CreateFileOperation(uri="file:///workspace/new.py", content="print('hi')")],
    ),
)

# Show a working task with command execution
command_task = ChatAssistantWorkingTask(
    title="Running tests",
    content="Executing pytest",
    toolUIState=CommandToolUIState(
        toolCallId="tc-2",
        command="pytest",
        result=CommandResultRunning(),
    ),
)
Publish in Le Chat

Publish in Le Chat

To publish a conversational workflow as an assistant in Le Chat (Mistral's chat interface), your workflow must return a ChatAssistantWorkflowOutput. The output won't be displayed in Le Chat, but we enforce a common interface for inter-operability.

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="expense-summary-workflow",
    workflow_display_name="Expense Summary",
    workflow_description="Generates a summary of expenses",
)
class ExpenseSummaryWorkflow:
    @workflows.workflow.entrypoint
    async def run(self, department: str) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        summary = f"Expense summary for {department}: Total $12,500"

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text=summary)]
        )

Tagging Input Variants

When a workflow accepts a union of input types, clients may need a way to know which variant to use. The @input_tag decorator adds an x-input-tag field to a model's JSON schema so clients can identify and select the right one.

from pydantic import BaseModel

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai
from mistralai.workflows.plugins.mistralai import input_tag


class FullParams(BaseModel):
    config: str
    options: dict[str, str]


@input_tag("simplified")
class SimpleParams(BaseModel):
    prompt: str


@workflows.workflow.define(
    name="multi-client-workflow",
    workflow_display_name="Multi-Client",
)
class MultiClientWorkflow:
    @workflows.workflow.entrypoint
    async def run(self, params: FullParams | SimpleParams) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        if isinstance(params, SimpleParams):
            resolved = params.prompt
        else:
            resolved = params.config

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text=f"Received: {resolved}")]
        )

SimpleParams.model_json_schema() now contains "x-input-tag": "simplified". Clients inspect the union members in the workflow's input_schema and select the variant whose tag they recognise.

Error Handling

To signal that a workflow has failed, set isError=True on the output. The text content is used as the error message displayed to the user, and the workflow is marked as failed in the conversation:

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="expense-validation-workflow",
    workflow_display_name="Expense Validation",
    workflow_description="Validates expense data",
)
class ExpenseValidationWorkflow:
    @workflows.workflow.entrypoint
    async def run(self, expense_id: str) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        expense = lookup_expense(expense_id)
        if expense is None:
            return workflows_mistralai.ChatAssistantWorkflowOutput(
                content=[workflows_mistralai.TextOutput(text=f"Expense {expense_id} not found.")],
                isError=True,
            )

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text=f"Expense {expense_id} is valid.")]
        )

Structured Content

ChatAssistantWorkflowOutput accepts an optional structuredContent field (dict[str, Any]) for attaching arbitrary structured data to the workflow output.

return workflows_mistralai.ChatAssistantWorkflowOutput(
    content=[workflows_mistralai.TextOutput(text="Done.")],
    structuredContent={"tool": "web_search", "results": [{"url": "https://example.com"}]},
)
Complete Example

Complete Example

Here's a full expense approval workflow combining todo list, user input, and streaming agent analysis:

import mistralai.workflows as workflows
import mistralai.workflows.plugins.mistralai as workflows_mistralai


@workflows.workflow.define(
    name="full-expense-workflow",
    workflow_display_name="Full Expense Processing",
    workflow_description="Complete expense workflow with AI analysis and approval",
)
class FullExpenseWorkflow(workflows.InteractiveWorkflow):
    @workflows.workflow.entrypoint
    async def run(self, expense_data: str) -> workflows_mistralai.ChatAssistantWorkflowOutput:
        # Define workflow steps
        analyze_item = workflows_mistralai.TodoListItem(
            title="AI Analysis",
            description="Analyze expense for compliance"
        )
        review_item = workflows_mistralai.TodoListItem(
            title="Manager Review",
            description="Get manager approval"
        )
        process_item = workflows_mistralai.TodoListItem(
            title="Process",
            description="Complete processing"
        )

        async with workflows_mistralai.TodoList(
            items=[analyze_item, review_item, process_item]
        ) as todo_list:
            # Step 1: AI Analysis with streaming (using context manager)
            async with analyze_item:
                session = workflows_mistralai.RemoteSession(stream=True)
                analyst = workflows_mistralai.Agent(
                    model="mistral-medium-2508",
                    name="expense-analyst",
                    description="Expense policy analyst",
                    instructions="Analyze the expense for policy compliance. Be brief.",
                )

                await workflows_mistralai.Runner.run(
                    agent=analyst,
                    inputs=expense_data,
                    session=session,
                )

            # Step 2: Manager Review (using context manager)
            async with review_item:
                decision = await self.wait_for_input(
                    workflows_mistralai.ChatInput(
                        "Do you approve or reject this expense? Please explain.",
                        suggestions=[
                            [workflows_mistralai.TextChunk(text="Yes, approve this expense")],
                            [workflows_mistralai.TextChunk(text="Reject")],
                        ],
                    )
                )

            # Step 3: Process (using manual status control for conditional logic)
            await process_item.set_status("in_progress")
            decision_text = decision.message[0].text if decision.message else ""
            if "approve" in decision_text.lower():
                result = f"Expense approved. {decision_text}"
            else:
                result = f"Expense rejected. {decision_text}"
            await process_item.set_status("done")

        return workflows_mistralai.ChatAssistantWorkflowOutput(
            content=[workflows_mistralai.TextOutput(text=result)]
        )


if __name__ == "__main__":
    import asyncio
    asyncio.run(workflows.run_worker([FullExpenseWorkflow]))