Skip to main content
Build a durable AI agent with Temporal

Build a durable AI agent: set up the toolkit

An AI agent uses large language models (LLMs) to plan and execute steps towards a goal. While attempting to reach its goal, the agent can perform actions such as searching for information, interacting with external services, and even calling other agents. However, building reliable AI agents presents various challenges. Network failures, long-running workflows, observability challenges, and more make building AI agents a textbook distributed systems problem.

Temporal orchestrates long-running workflows, automatically handles failure cases from network outages to server crashes, provides insights into your running applications, and more. These features provide the resiliency and durability necessary to build reliable agents that users can rely on.

In this tutorial you'll build an AI agent using Temporal that searches for events in a given city, helps you book a plane ticket, and creates an invoice for the trip. The user will interact with this application through a chatbot interface, communicating with the agent using natural language. Throughout this tutorial you will implement the following components:

  • Various tools the agent will use to search for events, find flights, and generate invoices.
  • An agent goal that will specify what overall task the agent is trying to achieve and what tools it is allowed to use.
  • Temporal Workflows that will orchestrate multi-turn conversations and ensure durability across failures.
  • Temporal Activities that execute tools and language model calls with automatic retry logic.
  • A FastAPI backend that connects the web interface to your Temporal Workflows.
  • A web-based chat interface that allows users to interact with the agent.

By the end of this tutorial, you will have a modular, durable AI agent that you can extend to run any goal using any set of tools. Your agent will be able to recover from failure, whether it's a hardware failure, a tool failure, or an LLM failure. And you'll be able to use Temporal to build reliable AI applications that maintain state and provide consistent user experiences.

You can find the code for this tutorial on GitHub in the tutorial-temporal-ai-agent repository.

~30 minutesBeginnerPython
  1. Build the toolkit
  2. Define agent behavior
  3. Workflow & Worker
  4. Run and observe

Prerequisites

Before starting this tutorial, ensure that you have the following on your local machine.

Required

note

OpenAI API Keys require purchasing credits to use. You can succeed with this tutorial with minimal credits; in our experience, less than $1 will suffice.

Optional

You can opt to use real API services for your tools, or use provided mock functions.

Concepts

Additionally, this tutorial assumes you have basic familiarity with:

Programming Concepts

  • Temporal fundamentals such as Workflows, Activities, Workers, Signals, and Queries.
  • Python fundamentals such as functions, classes, async/await syntax, and virtual environments.
  • Command line interface and running commands in a terminal or command prompt.
  • REST API concepts including HTTP requests and JSON responses.
  • How to set and use environment variables in your operating system.

AI Concepts

Setting up your development environment

Before you start coding, you need to set up your Python developer environment. In this step, you will set up your project structure, install the necessary Python packages, and configure the Python environment needed to build your AI agent.

First, create your project using uv:

uv init temporal-ai-agent --python ">=3.9"

uv is a modern Python project and packaging tool that sets up a project structure for you. Running this command creates the following default Python package structure for you:

temporal-ai-agent/
├── .gitignore
├── .python-version
├── main.py
├── README.md
├── pyproject.toml
└── uv.lock

It automatically runs a git init command for you, provides you with the default .gitignore for Python, creates a .python-version file that has the project's default Python version, a README.md, a Hello World main.py program, and a pyproject.toml file for managing the project's packages and environment.

Next, change directories into your newly created project:

cd temporal-ai-agent

You won't need the main.py file, so delete it:

rm main.py

Next, create your virtual environment by running the following command:

uv venv

This creates a virtual environment named .venv in the current working directory.

Now that you have a virtual environment created, add the dependencies needed to build your AI agent system:

uv add python-dotenv fastapi jinja2 litellm stripe temporalio uvicorn requests

This installs all the necessary packages:

  • python-dotenv - For loading environment variables from a .env file
  • fastapi and uvicorn - Web framework and server for the API backend
  • jinja2 - Template engine
  • litellm - Unified interface for different language model providers
  • stripe - Payment processing library for the invoice generation demo
  • temporalio - The Temporal Python SDK
  • requests - HTTP library for API calls

Finally, add the following lines to the end of your pyproject.toml file:

pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# Tell hatchling what to include
[tool.hatch.build.targets.wheel]
packages = ["activities", "api", "models", "prompts", "shared", "tools", "workflows"]

This configures uv as to which packages to include and enable for execution. You will create these packages later in the tutorial.

The pyproject.toml is complete and will need no more revisions. You can review the complete file and copy the code here.
pyproject.toml
[project]
name = "temporal-ai-agent"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"python-dotenv>=1.0.0",
"fastapi>=0.115.12",
"jinja2>=3.1.6",
"litellm>=1.72.2",
"stripe>=12.2.0",
"temporalio>=1.12.0",
"uvicorn>=0.34.3",
"requests>=2.32.4",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# Tell hatchling what to include
[tool.hatch.build.targets.wheel]
packages = ["activities", "api", "models", "prompts", "shared", "tools", "workflows"]

Next, create a .env file to store your configuration:

touch .env

Next, copy the following configuration to your .env file.

.env
# LLM Configuration
LLM_MODEL=openai/gpt-4o
LLM_KEY=YOUR_OPEN_AI_KEY

# Set if the user should click a Confirm button in the UI to allow the tool
# to execute
SHOW_CONFIRM=True

# Temporal Configuration
TEMPORAL_ADDRESS=localhost:7233
TEMPORAL_NAMESPACE=default
TEMPORAL_TASK_QUEUE=agent-task-queue

# (Optional) - Uncomment both lines and set RAPIDAPI_KEY if you plan on
# using the real flights API
# RAPIDAPI_KEY=YOUR_RAPID_API_KEY
# RAPIDAPI_HOST_FLIGHTS=sky-scrapper.p.rapidapi.com

# (Optional) - Uncomment and set STRIPE_API_KEY if you plan on using the Stripe
# API to generate a fake invoice
# STRIPE_API_KEY=YOUR_STRIPE_API_KEY

# Uncomment if connecting to Temporal Cloud using mTLS (not needed for local dev server)
# TEMPORAL_TLS_CERT='path/to/cert.pem'
# TEMPORAL_TLS_KEY='path/to/key.pem'

# Uncomment if connecting to Temporal Cloud using API key (not needed for local dev server)
# TEMPORAL_API_KEY=abcdef1234567890

Once copied, replace YOUR_OPEN_API_KEY with your OpenAI API key. Setting SHOW_CONFIRM=True requires the user to confirm each tool prior to it being executed. This will allow you to see what the agent is doing step by step. These are the only two mandatory variables to set. This tutorial provides both an ability to create pseudo tools that perform simulations, or tools that use external APIs to achieve their goals. If you plan on using the RapidAPI SkyScraper API to look up flight data or the Stripe API to generate an invoice, you can uncomment these lines and provide the API keys here.

Additionally, if you plan on connecting to Temporal Cloud, you will need to update the TEMPORAL_ADDRESS and TEMPORAL_NAMESPACE parameters to connect to your Temporal Cloud instance. You will also need to uncomment and set the TEMPORAL_TLS or TEMPORAL_API_KEY variables, depending on which authentication method you are using.

note

As this project is using LiteLLM, it supports various different LLM providers. This tutorial will use OpenAI's gpt-4o, but you are welcome to use whichever LLM you wish, so long as it is supported by LiteLLM.

At this point, you have configured your developer environment to include a Python project managed by uv with all required dependencies to build a Temporal powered agentic AI, and all necessary environment variables.

Now that you have set up your developer environment, you will build the tools that your agent will use to perform the various tasks it needs to accomplish its goal.

Constructing the agent toolkit

In this step, you will acquire the tools that will be available to your agent. Agents are aware of the tools they have available to them while attempting to achieve their goal. The agent will evaluate which tools are available and execute a tool if the agent believes it will provide the result the agent needs to progress in its task.

These tools can take various forms, but in this tutorial they're implemented as a series of independent Python scripts that provide data in a specific format that the agent can process. There are three tools: a find_events tool, a search_flights tool, and a create_invoice tool. The LLM will decide when to use each tool as it interacts with the user who is trying to find an event and book a flight to attend it. You could implement these tools yourself, or you could download a tool and provide it to an agent. For this tutorial, you will download the tools directly from the companion GitHub repository.

Setting up the tools package

To get started, first create the directory for your tools modules:

mkdir tools

Then change directories into it:

cd tools

However, for this to be an importable tools package, you will need to add a __init__.py file. It can be blank for now, so create it with the following command:

touch __init__.py

Now that you have set up the structure for your tools package, you'll acquire and test the tools needed to have the agent succeed with its goal.

Acquiring the find_events tool

The find_events tool searches for events within a given city during a certain time of year. The tool takes a month and city as inputs and provides events for not only the month that was provided, but the months before and after the given month as well. The LLM will use this tool to search for events when helping the user plan their trip. This tool doesn't use an API, but rather simulates looking events up in a data store using mock data.

First, create a data directory within the tools directory to store the sample event data and change directories into it:

mkdir data
cd data

Next, run the following command to download the sample data from the companion GitHub repository:

curl -o find_events_data.json https://raw.githubusercontent.com/temporal-community/tutorial-temporal-ai-agent/main/tools/data/find_events_data.json

You can confirm you have the correct data by running the following command to sample the file and comparing it to the output:

head -8 find_events_data.json
{
"New York": [
{
"eventName": "Winter Jazzfest",
"dateFrom": "2025-01-10",
"dateTo": "2025-01-19",
"description": "A multi-venue jazz festival featuring emerging and established artists performing across Greenwich Village venues."
},
note

If the dates appear to be far in the past, don't worry. There is logic within the find_events tool that automatically adjusts the date to ensure that no dates can be presented that are in the past.

Next, change directories back up one directory to the tools directory:

cd ..

Now that you have the data, download the find_events tool using the command:

curl -o find_events.py https://raw.githubusercontent.com/temporal-community/tutorial-temporal-ai-agent/main/tools/find_events.py

Open the file and explore the logic; you should never download a file from the internet and just trust it.

Try to answer the following questions about the codebase:

  • Where in the code does it determine the adjacent months?
  • How does the tool prevent the data from find_events_data.json being presented with a date that has already passed?
  • What is the schema for the data that will be returned?

Once you have finished reviewing the code, navigate to the root directory of your project and create a scripts directory for testing this tool. The root of your project should be one level higher than your current directory, so you can get there by running the following command:

cd ..

Create the scripts directory:

mkdir scripts

Now create a test script named find_events_test.py in the scripts directory and add the following to test your script:

scripts/find_events_test.py
import json

from tools.find_events import find_events

if __name__ == "__main__":
search_args = {"city": "Austin", "month": "December"}
results = find_events(search_args)
print(json.dumps(results, indent=2))

This script will check for events in Austin, TX in the month of December.

From the root of your project, run the script using the following command to verify it's configured correctly:

uv run scripts/find_events_test.py

You should see the following output:

{
"note": "Returning events from December plus one month either side (i.e., November, December, January).",
"events": [
{
"city": "Austin",
"eventName": "Austin Celtic Festival",
"dateFrom": "2025-11-08",
"dateTo": "2025-11-09",
"description": "Celebration of Celtic culture featuring traditional music, dance, crafts, and Irish food.",
"month": "previous month"
},
{
"city": "Austin",
"eventName": "Trail of Lights",
"dateFrom": "2025-12-05",
"dateTo": "2025-12-23",
"description": "Holiday light display in Zilker Park featuring festive decorations, food vendors, and family activities.",
"month": "requested month"
}
]
}

Now that you have the find_events tool functioning, it's time to do the same for the search_flights tool.

Acquiring the search_flights tool

The search_flights tool searches roundtrip flights to a destination. The tool takes the origin, destination, arrival date, and departure date as arguments and returns flight data containing details such as carrier, price, and flight code for the flights. The LLM will use this tool to find flights to the location once the user has selected the dates they wish to travel. This tool can either use the RapidAPI SkyScraper API if you have an API key configured in your .env file, or it will generate mock data if it's unable to detect the API key.

First, change directories into the tools directory:

cd tools

Then get the tool by running the following command to download it from the companion GitHub repository:

curl -o search_flights.py https://raw.githubusercontent.com/temporal-community/tutorial-temporal-ai-agent/main/tools/search_flights.py

Next, familiarize yourself with the tool by reviewing the code. Try to answer the following questions about the code:

  • What is the purpose of the search_flights function? (It's not as straightforward of an answer as it may appear.)
  • How many REST API calls does it take to complete the real flight API search?

Once you have finished reviewing the code, you will test it.

Create another test within the scripts directory named search_flights_test.py and add the following code:

scripts/search_flights_test.py
import json

from tools.search_flights import search_flights

if __name__ == "__main__":

flights = search_flights(
{
"origin": "ORD",
"destination": "DFW",
"dateDepart": "2025-09-20",
"dateReturn": "2025-09-22",
}
)
print(json.dumps(flights, indent=2))

This test searches for a flight from Chicago to Dallas-Fort Worth. However, since this tool can operate in either a mock mode or live API mode, there are two ways to verify it.

Testing the mocked search_flight tool

Let's start by testing it without the RapidAPI key. If you have that set in your .env file, comment it out for now, or skip this step.

Change directories back to the root of the project and run the test using the following command:

cd ..
uv run scripts/search_flights_test.py

Your output will vary, as the mock data function randomly generates results. The output should, however, look something like this with more items in the results list:

{
"currency": "USD",
"destination": "DFW",
"origin": "ORD",
"results": [
{
"operating_carrier": "Southwest Airlines",
"outbound_flight_code": "WN427",
"price": 462.43,
"return_flight_code": "WN744",
"return_operating_carrier": "Southwest Airlines"
}
]
}

If you aren't planning on using the Sky Scrapper API, you can skip this next step and continue if you'd like.

Testing the Sky Scrapper powered search_flights tool

Testing the API-powered version of the tool is similar to testing the mocked version.

First, if you haven't uncommented the RAPID_API lines in your .env file and added your API key, do this before running the test. You will also need to uncomment the RAPIDAPI_HOST_FLIGHTS environment variable as this is the endpoint the tool will be accessing.

RAPIDAPI_KEY=YOUR_RAPID_API_KEY
RAPIDAPI_HOST_FLIGHTS=sky-scrapper.p.rapidapi.com

Next, review the code in scripts/search_flights_test.py and make sure that the dateDepart and dateReturn dates are both in the future. At this point you have no way of determining if the dates are in the past, and the API will return an error if you try to search for flights in the past.

Once you've reviewed the code, make sure you are at the root directory of the project. If you are still in the scripts directory, run the following command:

cd ..

Then run the test using the following command:

uv run scripts/search_flights_test.py

If you've changed the dates or cities, you may see different results, but the format should be similar to this:

Searching for: ORD
Searching for: DFW
{
"origin": "ORD",
"destination": "DFW",
"currency": "USD",
"results": [
{
"outbound_flight_code": "NK824",
"operating_carrier": "Spirit Airlines",
"return_flight_code": "NK828",
"return_operating_carrier": "Spirit Airlines",
"price": 119.98
},
]
}
info

If the API gives you cryptic error messages such as Something went wrong or returns an incomplete response, you can try running it a few times and see if you get a different response.

Now that you have finished testing the search_flights tool, you can add the final tool to the agent's toolkit.

Acquiring the create_invoice tool

The final tool is the create_invoice tool. The tool takes the customer's email and trip information such as the cost of the flight, the description of the event, the number of days until the invoice is due, and generates a sample invoice for that user showing the details of the flight and the cost. The LLM will use this tool to invoice the customer once the customer has confirmed their travel plans. This tool can either use the Stripe API if you have an API key configured in your .env file, or it will generate a mock invoice if it is unable to detect an API key.

First, change directories into the tools directory again:

cd tools

Then get the tool by running the following command to download it from the companion GitHub repository:

curl -o create_invoice.py https://raw.githubusercontent.com/temporal-community/tutorial-temporal-ai-agent/main/tools/create_invoice.py

Next, familiarize yourself with the tool by reviewing the code. Try to answer the following questions about the code:

  • What customer related verification does the tool do before creating the invoice?
  • What does the tool do if this verification fails?

Once you have finished reviewing the code, test it.

Create another test within the scripts directory named create_invoice_test.py and add the following code:

scripts/create_invoice_test.py
from tools.create_invoice import create_invoice

if __name__ == "__main__":

args_create = {
"email": "ziggy.tardigrade@example.com",
"amount": 150.00,
"description": "Flight to Replay",
"days_until_due": 7,
}
invoice_details = create_invoice(args_create)
print(invoice_details)

However, since this tool can operate in either a mock mode or live API mode, there are two ways to verify it.

Testing the mocked create_invoice tool

Start by testing it without the Stripe key. If you have it set in your .env file, comment it out for now, or skip this step.

Change directories back to the root project directory and run the test using the following command:

cd ..
uv run scripts/create_invoice_test.py

The output should be:

[CreateInvoice] Creating invoice with: {'email': 'ziggy.tardigrade@example.com', 'amount': 150.0, 'description': 'Flight to Replay', 'days_until_due': 7}
{'invoiceStatus': 'generated', 'invoiceURL': 'https://pay.example.com/invoice/12345', 'reference': 'INV-12345'}

If you aren't planning on using the Stripe API, you can skip this next step and continue if you'd like.

Testing the Stripe-powered create_invoice tool

Testing the Stripe powered version of the tool is nearly identical to testing the mocked version of the tool.

First, if you haven't uncommented the STRIPE_API_KEY lines in your .env file and added your API key, do this before running the test.

STRIPE_API_KEY=YOUR_STRIPE_API_KEY
danger

Make sure you have set up your Stripe account as a sandbox and are using an API key from there. If it is your first time setting up a Stripe account and you haven't added any billing information, this will be the default. Otherwise the invoices will be real.

Make sure you aren't in the scripts directory any more. If you are, run the following command to get back to the root directory of the project:

cd ..

Then run the test using the following command the same way you would the mocked version:

uv run scripts/create_invoice_test.py

The result will contain an invoiceURL, as well as the status of the invoice and a reference.

{'invoiceStatus': 'open', 'invoiceURL': 'https://invoice.stripe.com/i/acct_1RMFbIQej3CO0i8K/test_YWNjdF8xUk1GYklRZWozQ08wThLLF9TVJpYWZ2WXREVXZrcDJqMGhIM0hSdkVEa2hVYmM0LDE0MTI2NjEwNg0200VaZpBdSc?s=ap', 'reference': 'FEUS4MXS-0001'}

By following that invoice link in a browser, Stripe will present you with a sample invoice in your sandbox environment.

Before you move on, verify that you have created all the necessary files in the correct structure.

So far you've implemented and tested the agent's tools. Verify your directory structure and files look and are named appropriately according to the following diagram before continuing:

temporal-ai-agent/
├── .env
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
├── uv.lock
├── scripts/
│ ├── create_invoice_test.py
│ ├── find_events_test.py
│ └── search_flights_test.py
└── tools/
├── __init__.py
├── create_invoice.py
├── find_events.py
├── search_flights.py
└── data/
└── find_events_data.json

And those are the three tools in this agent's toolkit to achieve its goal. Other goals may have different tools, and you could add more tools. Next, you'll make the tools available to the agent to use.

Exposing the tools to the agent

Now that you have the tools necessary to complete the agent's goal, you need to implement a way to inform the agent that these tools are available. To do this, you'll create a tool registry. The tool registry will contain a definition of each tool, along with information such as the tool's name, description, and what arguments it accepts.

However, before you create the registry, you should define the tool definition and tool argument as models that can be shared across your codebase.

Defining the core models

Defining the tool arguments, tool definition, and agent goal as custom types allows for better reusability and type hinting. Temporal also recommends passing a single object between functions, and requires these objects to be serializable. Given these requirements, you'll implement the ToolArgument and ToolDefinition types as a Python dataclass.

Before you define these models, navigate to the root directory of your project and create the models directory:

mkdir models

Since this directory will be imported throughout your project, it needs to be configured as a module. To do this, create a blank __init__.py file by running the following command:

touch models/__init__.py

Next, create the file core.py. This file will contain the tool argument and definition models used in your agent. Open models/core.py and add the following imports:

models/core.py
from dataclasses import dataclass
from typing import List

Next, add the ToolArgument dataclass to the file:

models/core.py
@dataclass
class ToolArgument:
name: str
type: str
description: str

An instance of this dataclass will represent an argument that your tool can accept, including the name of the argument, a description of what the argument represents, and the type of the argument, such as an int or str.

Next, add the ToolDefinition dataclass to the file:

models/core.py
@dataclass
class ToolDefinition:
name: str
description: str
arguments: List[ToolArgument]

This will hold information about the tool that's provided to the agent so it can determine what action to take. It defines the name of the tool, a description of what it can do, and an argument list. This list is composed of your ToolArgument objects.

Now that you have the appropriate model to define your tools, you can create a registry of the tools for the agent to access.

Creating the tool registry

Agents use LLMs to determine what action to take and then execute a tool from their toolkit. However, you have to make those tools available to the agent. Now that you have structure for defining your tools, you should create a registry that your agent reads to load the available tools.

Navigate back to the tools directory and create the file tools/tool_registry.py. In this file you will define all of your tools using the models you defined in the previous step.

First, add the following import to the file to import the models:

tools/tool_registry.py
from models.core import ToolArgument, ToolDefinition

Next, add the first part of the ToolDefinition for the find_events tool:

tools/tool_registry.py
find_events_tool = ToolDefinition(
name="FindEvents",
description="Find upcoming events to travel to a given city (e.g., 'New York City') and a date or month. "
"It knows about events in North America only (e.g. major North American cities). "
"It will search 1 month either side of the month provided. "
"Returns a list of events. ",
# arguments to be inserted here in the next step
)

This defines your tool using the ToolDefinition model you defined, gives it a name and a description that the LLM can use to understand the tool and also use as a prompt. Next you need to add the arguments to this instantiation. The arguments in the ToolDefinition model were defined as a List[ToolArgument], so you may have multiple arguments within your list.

To complete the definition, add the following code to your find_events_tool instantiation to add the arguments:

tools/tool_registry.py
    arguments=[
ToolArgument(
name="city",
type="string",
description="Which city to search for events",
),
ToolArgument(
name="month",
type="string",
description="The month to search for events (will search 1 month either side of the month provided)",
),
]

The find_events tool requires two arguments, the city and month in which to search, and it also provides a string description so the LLM would know how to prompt the user if an argument is missing.

Bringing it all together, the complete ToolDefinition would be:

tools/tool_registry.py
find_events_tool = ToolDefinition(
name="FindEvents",
description="Find upcoming events to travel to a given city (e.g., 'New York City') and a date or month. "
"It knows about events in North America only (e.g. major North American cities). "
"It will search 1 month either side of the month provided. "
"Returns a list of events. ",
arguments=[
ToolArgument(
name="city",
type="string",
description="Which city to search for events",
),
ToolArgument(
name="month",
type="string",
description="The month to search for events (will search 1 month either side of the month provided)",
),
],
)

Now that you have the first tool defined in your registry, implement the remaining tool definitions.

Add the following code to register the search_flights tool. The structure is similar to the find_events tool, except that search_flights requires more arguments, to search for the origin, destination, departure date, return date, and confirmation status. These arguments are a direct mapping of the required parameters to the RAPIDAPI REST API. When creating a tool that maps to an API, be sure to include that API's required parameters as ToolArguments.

tools/tool_registry.py
search_flights_tool = ToolDefinition(
name="SearchFlights",
description="Search for return flights from an origin to a destination within a date range (dateDepart, dateReturn). "
"You are allowed to suggest dates from the conversation history, but ALWAYS ask the user if ok.",
arguments=[
ToolArgument(
name="origin",
type="string",
description="Airport or city (infer airport code from city and store)",
),
ToolArgument(
name="destination",
type="string",
description="Airport or city code for arrival (infer airport code from city and store)",
),
ToolArgument(
name="dateDepart",
type="ISO8601",
description="Start of date range in human readable format, when you want to depart",
),
ToolArgument(
name="dateReturn",
type="ISO8601",
description="End of date range in human readable format, when you want to return",
),
ToolArgument(
name="userConfirmation",
type="string",
description="Indication of the user's desire to search flights, and to confirm the details "
+ "before moving on to the next step",
),
],
)

And then add the following code to register the create_invoice tool. This tool requires three arguments: the amount to be paid, the details of the trip, and a user confirmation.

tools/tool_registry.py
create_invoice_tool = ToolDefinition(
name="CreateInvoice",
description="Generate an invoice for the items described for the total inferred by the conversation history so far. Returns URL to invoice.",
arguments=[
ToolArgument(
name="amount",
type="float",
description="The total cost to be invoiced. Infer this from the conversation history.",
),
ToolArgument(
name="tripDetails",
type="string",
description="A description of the item details to be invoiced, inferred from the conversation history.",
),
ToolArgument(
name="userConfirmation",
type="string",
description="Indication of user's desire to create an invoice",
),
],
)

You now have a tool registry your agent imports to inform it of what tools it has available to execute. Finally, you need to create a mapping between the tool registered in tool_registry.py with the actual functions the Activity will invoke during Workflow execution.

Mapping the registry to the functions

Your agent will use the registry to identify which tool it should use, but it still needs to translate the string name of the tool to the function definition the code will execute. You will modify the code in tool_registry to add this functionality.

First, add the following imports with the other imports in tool_registry.py:

tools/tool_registry.py
from typing import Any, Callable, Dict

from tools.create_invoice import create_invoice
from tools.find_events import find_events
from tools.search_flights import search_flights

These handle the appropriate typings, as well as import the function from each of the tool files.

Next, go to the bottom of the file after the previous tool definitions and add the code to map the string representation of the ToolDefinition to the function:

tools/tool_registry.py
# Dictionary mapping tool names to their handler functions
TOOL_HANDLERS: Dict[str, Callable[..., Any]] = {
"SearchFlights": search_flights,
"CreateInvoice": create_invoice,
"FindEvents": find_events,
}

Finally, add a function named get_handler that returns the function given the tool name:

tools/tool_registry.py
def get_handler(tool_name: str) -> Callable[..., Any]:
"""Get the handler function for a given tool name.

Args:
tool_name: The name of the tool to get the handler for.

Returns:
The handler function for the specified tool.

Raises:
ValueError: If the tool name is not found in the registry.
"""
if tool_name not in TOOL_HANDLERS:
raise ValueError(f"Unknown tool: {tool_name}")

return TOOL_HANDLERS[tool_name]

You have now successfully implemented a structured model for expressing tools available to your AI agent. This is necessary for building a robust, capable agent.

The tools/tool_registry.py is complete and will need no more revisions. You can review the complete file and copy the code here.
tools/tool_registry.py
from typing import Any, Callable, Dict

from models.core import ToolArgument, ToolDefinition
from tools.create_invoice import create_invoice
from tools.find_events import find_events
from tools.search_flights import search_flights

find_events_tool = ToolDefinition(
name="FindEvents",
description="Find upcoming events to travel to a given city (e.g., 'New York City') and a date or month. "
"It knows about events in North America only (e.g. major North American cities). "
"It will search 1 month either side of the month provided. "
"Returns a list of events. ",
arguments=[
ToolArgument(
name="city",
type="string",
description="Which city to search for events",
),
ToolArgument(
name="month",
type="string",
description="The month to search for events (will search 1 month either side of the month provided)",
),
],
)


search_flights_tool = ToolDefinition(
name="SearchFlights",
description="Search for return flights from an origin to a destination within a date range (dateDepart, dateReturn). "
"You are allowed to suggest dates from the conversation history, but ALWAYS ask the user if ok.",
arguments=[
ToolArgument(
name="origin",
type="string",
description="Airport or city (infer airport code from city and store)",
),
ToolArgument(
name="destination",
type="string",
description="Airport or city code for arrival (infer airport code from city and store)",
),
ToolArgument(
name="dateDepart",
type="ISO8601",
description="Start of date range in human readable format, when you want to depart",
),
ToolArgument(
name="dateReturn",
type="ISO8601",
description="End of date range in human readable format, when you want to return",
),
ToolArgument(
name="userConfirmation",
type="string",
description="Indication of the user's desire to search flights, and to confirm the details "
+ "before moving on to the next step",
),
],
)

create_invoice_tool = ToolDefinition(
name="CreateInvoice",
description="Generate an invoice for the items described for the total inferred by the conversation history so far. Returns URL to invoice.",
arguments=[
ToolArgument(
name="amount",
type="float",
description="The total cost to be invoiced. Infer this from the conversation history.",
),
ToolArgument(
name="tripDetails",
type="string",
description="A description of the item details to be invoiced, inferred from the conversation history.",
),
ToolArgument(
name="userConfirmation",
type="string",
description="Indication of user's desire to create an invoice",
),
],
)


# Dictionary mapping tool names to their handler functions
TOOL_HANDLERS: Dict[str, Callable[..., Any]] = {
"SearchFlights": search_flights,
"CreateInvoice": create_invoice,
"FindEvents": find_events,
}


def get_handler(tool_name: str) -> Callable[..., Any]:
"""Get the handler function for a given tool name.

Args:
tool_name: The name of the tool to get the handler for.

Returns:
The handler function for the specified tool.

Raises:
ValueError: If the tool name is not found in the registry.
"""
if tool_name not in TOOL_HANDLERS:
raise ValueError(f"Unknown tool: {tool_name}")

return TOOL_HANDLERS[tool_name]
Before moving on to the next section, verify that your file and directory structure is correct.

You just implemented a model for defining your tools in a way that your agent could discover and use them. Verify that your directory structure and file names are correct according to the following diagram before continuing:

temporal-ai-agent/
├── .env
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
├── uv.lock
├── models/
│ ├── __init__.py
│ └── core.py
├── scripts/
│ ├── create_invoice_test.py
│ ├── find_events_test.py
│ └── search_flights_test.py
└── tools/
├── __init__.py
├── create_invoice.py
├── find_events.py
├── search_flights.py
├── tool_registry.py
└── data/
└── find_events_data.json

In the next step, you will use the tool definitions you just created to define the agent's goal.

Get notified when we launch new educational content

New courses, tutorials, and learning resources - straight to your inbox.

Subscribe
Feedback