Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/temporalio/edu-ai-workshop-openai-agents-sdk/llms.txt

Use this file to discover all available pages before exploring further.

Exercise 4 graduates from notebooks to a production-style Python project. You will have three separate files—workflow.py, worker.py, and starter.py—and run two terminal processes simultaneously, exactly as you would in a real deployment. The workflow includes a deliberate 10-second pause so you can kill the worker mid-execution and prove that Temporal resumes from the pause point rather than re-running the triage agent.
The Temporal dev server must be running and your OPENAI_API_KEY must be set in .env before starting. Run make env from the project root to verify both.
Timebox: 15 minutes — Explore and run the complete implementation in solutions/04_agent_routing/ during the workshop. Then build it yourself in exercises/04_agent_routing/ as optional homework.

What You’ll Learn

Triage & Routing Pattern

Design a triage agent that never answers directly—it only detects intent and hands off to the right specialist.

Handoff Pattern

Use the handoffs parameter in the OpenAI Agents SDK to enable seamless agent-to-agent transitions.

Production File Structure

Separate workflow.py, worker.py, and starter.py mirrors real Temporal applications and enables independent scaling.

Two-Terminal Workflow

Run a long-lived worker process and a one-shot starter process as separate OS processes—the standard production pattern.

Goal

Build a RoutingWorkflow that routes any user message to the correct language specialist (French, Spanish, or English) via a triage agent, with the full execution wrapped in Temporal for durability and observability.

Architecture

User Query (any language)

┌───────────────────────────┐
│ Temporal Workflow         │  Orchestration
│ (orchestration layer)     │
└───────────────────────────┘

┌───────────────────────────┐
│ Activity: Call Triage     │  Analyze Request
│    Agent                  │
└───────────────────────────┘

[Triage agent detects language]

┌───────────────────────────┐
│ Activity: Handoff to      │  Smart Routing
│    Specialist Agent       │
└───────────────────────────┘

┌───────────────────────────┐
│ Activity: Specialist      │  Expert Response
│    Agent processes query  │
└───────────────────────────┘

    Return response to user

File Structure

solutions/04_agent_routing/
├── workflow.py      # Workflow definition and all agent configurations
├── worker.py        # Worker process that polls and executes workflows
├── starter.py       # One-shot script that starts a workflow execution
├── requirements.txt # Python dependencies
└── README.md        # Exercise guide
The same structure lives in exercises/04_agent_routing/ with TODO markers for you to complete as homework.

Complete Solution Code

"""
Routing workflow implementation using OpenAI Agents SDK.

Demonstrates intelligent request distribution to specialized language agents.
The triage agent analyzes incoming queries and routes them to the appropriate
language specialist (French, Spanish, or English) using the handoff pattern.
"""

from datetime import timedelta

from agents import Agent, RunConfig, Runner, TResponseInputItem, trace
from temporalio import workflow

# Task queue name for this workflow pattern
TASK_QUEUE = "routing-workflow-queue"


def french_agent() -> Agent:
    """Create a French language specialist agent."""
    return Agent(
        name="French Agent",
        instructions="You only speak French. Respond naturally to user queries in French.",
        model="gpt-4",
    )


def spanish_agent() -> Agent:
    """Create a Spanish language specialist agent."""
    return Agent(
        name="Spanish Agent",
        instructions="You only speak Spanish. Respond naturally to user queries in Spanish.",
        model="gpt-4",
    )


def english_agent() -> Agent:
    """Create an English language specialist agent."""
    return Agent(
        name="English Agent",
        instructions="You only speak English. Respond naturally to user queries in English.",
        model="gpt-4",
    )


def triage_agent() -> Agent:
    """
    Create a triage agent that routes requests to language specialists.

    The handoff pattern enables multi-agent architectures where different
    agents have different specializations and can collaborate on tasks.
    """
    return Agent(
        name="Triage Agent",
        instructions=(
            "Identify the primary language of the user's message. "
            "Never answer directly. "
            "Immediately hand off using one of the following rules: "
            "- French → French Agent "
            "- Spanish → Spanish Agent "
            "- English → English Agent "
            "If detection is uncertain or the language is unsupported, hand off to English Agent."
        ),
        handoffs=[french_agent(), spanish_agent(), english_agent()],
        model="gpt-4",
    )


@workflow.defn
class RoutingWorkflow:
    @workflow.run
    async def run(self, msg: str) -> str:
        config = RunConfig()

        with trace("Routing example"):
            inputs: list[TResponseInputItem] = [{"content": msg, "role": "user"}]

            # Run the triage agent to determine which language agent to handoff to
            result = await Runner.run(
                triage_agent(),
                input=inputs,
                run_config=config,
            )

            # 10-second pause to demonstrate Temporal durability:
            # kill the worker here and restart it to see the workflow resume
            # from this point without re-running the triage agent.
            workflow.logger.info("⏸️  Pausing for 10 seconds to demonstrate durability...")
            await workflow.sleep(timedelta(seconds=10))

            workflow.logger.info("Handoff completed")
            return f"Response: {result.final_output}"

How to Run

1

Navigate to the solutions directory

cd solutions/04_agent_routing
2

Terminal 1 — Start the worker

Keep this terminal open. The worker runs indefinitely, polling for tasks.
python worker.py
Expected output:
🚀 Worker started successfully
📋 Task Queue: routing-workflow-queue
🔄 Workflows: RoutingWorkflow
⏳ Polling for tasks... (Press Ctrl+C to stop)
3

Terminal 2 — Start a workflow

In a new terminal, run the starter with a query in any supported language:
# Spanish
python starter.py "¡Hola! Cuéntame un trabalenguas."

# French
python starter.py "Bonjour! Comment allez-vous aujourd'hui?"

# English
python starter.py "Hello! How are you doing today?"
Expected output:
🚀 Starting Routing Workflow
📋 Workflow ID: routing-wed-oct-16-103045est
💬 Query: ¡Hola! Cuéntame un trabalenguas.

✅ Workflow started: routing-wed-oct-16-103045est
🔗 View in Temporal UI: http://localhost:8233/namespaces/default/workflows/routing-wed-oct-16-103045est

⏳ Waiting for agent response...

💬 Agent Response: Response: Tres tristes tigres tragaban trigo en un trigal...
4

Observe the handoff in the Temporal UI

Open the Temporal UI link printed by the starter. In the execution history you will see:
  • ActivityTaskScheduled and ActivityTaskCompleted events for the triage agent’s LLM call
  • A TimerStarted / TimerFired pair representing the 10-second workflow.sleep
  • ActivityTaskScheduled and ActivityTaskCompleted events for the specialist agent’s LLM call
5

Demonstrate durability (optional, highly recommended)

This step makes Temporal’s durability guarantee concrete:
  1. Start a new workflow in Terminal 2.
  2. Watch Terminal 1 for the log line: ⏸️ Pausing for 10 seconds to demonstrate durability...
  3. Press Ctrl+C in Terminal 1 to kill the worker during the pause.
  4. In the Temporal UI, observe the workflow is still Running—it is waiting at the timer.
  5. Restart the worker in Terminal 1: python worker.py
  6. The workflow resumes from the timer. The triage agent call is never re-executed.
  7. Terminal 2 receives the final response.

Language Routing Examples

python starter.py "¡Hola! Cuéntame un trabalenguas."
The triage agent detects Spanish and hands off to Spanish Agent.Expected: Tres tristes tigres tragaban trigo en un trigal…

Stretch Goals

Create a german_agent() function and add it to the triage agent’s handoffs list:
def german_agent() -> Agent:
    return Agent(
        name="German Agent",
        instructions="You only speak German. Respond naturally in German.",
        model="gpt-4",
    )

def triage_agent() -> Agent:
    return Agent(
        name="Triage Agent",
        instructions=(
            "... "
            "- German → German Agent "
            "..."
        ),
        handoffs=[french_agent(), spanish_agent(), english_agent(), german_agent()],
        model="gpt-4",
    )
Test with: python starter.py "Hallo! Wie geht es Ihnen heute?"
Modify the workflow signature to accept an optional history list for multi-turn conversations:
@workflow.run
async def run(self, user_query: str, conversation_history: list | None = None) -> str:
    history = conversation_history or []
    inputs: list[TResponseInputItem] = history + [{"content": user_query, "role": "user"}]
    # Pass inputs to Runner.run(triage_agent(), input=inputs, ...)
Update the starter to persist the returned history and pass it back on subsequent calls.
When the triage agent cannot confidently detect a language, route to a general-purpose agent:
def general_agent() -> Agent:
    return Agent(
        name="General Agent",
        instructions="You handle queries when the language is unclear or mixed. Respond in English.",
        model="gpt-4",
    )
Update the triage agent’s instructions to include: “If language is unclear or mixed → General Agent” and add general_agent() to handoffs.

Build docs developers (and LLMs) love