Introduction & Overview

Welcome to the first lesson of this course on coordinating OpenAI agent workflows in Python. In the previous course, you learned how to make your agent-powered applications more responsive by using asynchronous and streamed execution modes. You saw how these techniques help your applications feel faster and more interactive, especially in real-time scenarios like chatbots.

In this lesson, we will build on that foundation by focusing on a key aspect of conversational AI: handling multi-turn conversations. Many real-world applications — such as customer support bots, travel assistants, or tutoring systems — require the agent to remember what was said earlier in the conversation. This ability to maintain and use dialogue history is what makes interactions feel natural and coherent.

By the end of this lesson, you will know how to use the to_input_list() method to preserve and pass conversation history to your agent, enabling stateful, multi-turn dialogues. You will see how this method fits into the agent workflow, and you will practice using it to create more engaging and context-aware applications.

Understanding Multi-Turn Conversations

A multi-turn conversation is a dialogue in which the user and the agent exchange several messages back and forth, rather than just a single question and answer. For example, a user might ask for a travel recommendation, then follow up with questions about the best time to visit or what to pack. In these cases, it is important for the agent to remember the previous messages so it can give relevant and accurate responses.

For example, a simple multi-turn conversation history might look like this:

[
    {"role": "user", "content": "Can you suggest a unique destination for a food lover?"},
    {"role": "assistant", "content": "Absolutely! If you’re a passionate food lover seeking a unique destination, consider Oaxaca, Mexico..."},
    {"role": "user", "content": "What is the best time of year to visit?"}
]

The main challenge in multi-turn conversations is maintaining the context. If the agent forgets what was said earlier, its answers may become confusing or repetitive. The goal is to keep track of the entire conversation history and provide it to the agent each time it is asked to respond. This way, the agent can generate answers that make sense in the context of the ongoing dialogue.

Getting Messages and Agent Steps from Result

To help you manage conversation history, the OpenAI Agents SDK provides the to_input_list() method on the result object returned by the runner. This method is designed to make it easy to preserve and reuse the full sequence of messages exchanged so far.

When you call to_input_list() on a result, it returns a list of all the input items that were part of the conversation up to that point. This includes the original user input and any messages generated by the agent. You can then append a new user message to this list and pass it as input to the agent for the next turn. This approach ensures that the agent always has access to the complete conversation history, allowing it to respond in a way that is consistent and context-aware.

Let’s walk through a practical example to see how this works in code.

Step 1: Getting the First Answer

Suppose you are building a travel assistant agent. To enable the agent to answer follow-up questions naturally, it needs to remember the ongoing conversation. Let's begin by running the agent with an initial user question, which will serve as the starting point for the dialogue.

import asyncio
from agents import Agent, Runner

# Define the agent
agent = Agent(
    name="Travel Genie",
    instructions=(
        "You are Travel Genie, a friendly and knowledgeable travel assistant. "
        "Recommend exciting destinations and offer helpful travel tips."
    ),
    model="gpt-4.1"
)

async def main():
    # Run the agent with an initial input
    result = await Runner.run(
        starting_agent=agent,
        input="Can you suggest a unique destination for a food lover?"
    )
    
    # Print the first response
    print("First answer:\n" + result.final_output + "\n")
    
    # Other interactions...

if __name__ == "__main__":
    asyncio.run(main())

When you run this code, the agent responds to the user’s initial question. Here’s an example of what the output might look like:

First answer:
Absolutely! If you’re a passionate food lover seeking a unique destination, consider **Oaxaca, Mexico**.

**Why Oaxaca?**  
Oaxaca is a culinary paradise renowned for its vibrant street food, bustling markets, and deep-rooted food traditions. It’s the birthplace of rich **mole sauces**, **tlayudas** (a kind of Mexican pizza), and **quesillo** (Oaxacan string cheese). The city is also famous for **mezcal**—with tasting rooms and distilleries open to visitors.

**Food Experiences Not to Miss:**  
- **Mercado 20 de Noviembre**: Sample local specialties like grilled meats, chapulines (crispy grasshoppers), and fresh tortillas.
- **Cooking Classes**: Join a local chef to learn the secrets behind mole and other regional dishes.
- **Street Food Tours**: Discover hidden gems and try tamales, empanadas, and more.

**Bonus Tip:**  
Time your visit during the **Guelaguetza Festival** in July for traditional feasts, or in November for the colorful Day of the Dead celebrations featuring special breads and chocolate.

Oaxaca blends extraordinary flavors, culture, and hospitality—perfect for the adventurous foodie!
Step 2: Inspecting the Input List

After receiving the agent’s response, you can inspect the conversation history so far by using the to_input_list() method. For better readability, you can print the input list using json.dumps with indentation.

Add the following lines after printing the first answer:

import json

# Get the conversation history as a list of messages from the result object
input_list = result.to_input_list()

# Convert the input list to a formatted JSON string
input_list_str = json.dumps(input_list, indent=2)

# Print the result's input list
print(f"Input list:\n{input_list_str}\n")

This will display the current state of the conversation history in a nicely formatted JSON structure:

Input list:
[
  {
    "content": "Can you suggest a unique destination for a food lover?",
    "role": "user"
  },
  {
    "id": "msg_6823231482d48198a84f01b839e945970dfdf92ecbda36ed",
    "content": [
      {
        "annotations": [],
        "text": "Absolutely! If you’re a passionate food lover seeking a unique destination, consider **Oaxaca, Mexico**.\n\n**Why Oaxaca?**...",
        "type": "output_text"
      }
    ],
    "role": "assistant",
    "status": "completed",
    "type": "message"
  }
]

This list contains both the user’s original question and the agent’s detailed response. By preserving this structure, you ensure that the agent can reference previous turns in the conversation.

Step 3: Concatenating a Follow-Up and Inspecting the New Input List

To continue the conversation, you’ll want to add a new user message to the input list by creating a new list that combines the existing conversation history (from result.to_input_list()) with a dictionary representing the user's follow-up question. It’s important to maintain the correct format for each message—each entry should be a dictionary with at least a "role" (such as "user" or "assistant") and a "content" field. This ensures the agent receives the full dialogue context in the structure it expects, allowing it to generate coherent, context-aware responses. For example:

# Concatenate a new input
follow_up_input = result.to_input_list() + [
    {
        "role": "user",
        "content": "What is the best time of year to visit?"
    }
]

# Print the follow up input using json.dumps for readability
print("Input list with follow up message:\n" + json.dumps(follow_up_input, indent=2) + "\n")

Now, the input list includes the new user question:

Input list with follow up message:
[
  {
    "content": "Can you suggest a unique destination for a food lover?",
    "role": "user"
  },
  {
    "id": "msg_6823231482d48198a84f01b839e945970dfdf92ecbda36ed",
    "content": [
      {
        "annotations": [],
        "text": "Absolutely! If you’re a passionate food lover seeking a unique destination, consider **Oaxaca, Mexico**.\n\n**Why Oaxaca?**...",
        "type": "output_text"
      }
    ],
    "role": "assistant",
    "status": "completed",
    "type": "message"
  },
  {
    "role": "user",
    "content": "What is the best time of year to visit?"
  }
]

By appending the new message, you’re preparing the agent to answer in the context of the entire conversation, not just the latest question.

Step 4: Running the Agent with the Updated Conversation and Getting the Second Answer

With the updated input list—which consists of the input list from the last response plus the new user message—you can now run the agent again. This time, instead of passing a single string as input, you pass the entire list of messages to the agent. This ensures the agent has access to the full conversation history, including the new user message, allowing it to provide a contextually relevant answer to the follow-up question.

Add the following code after printing the updated input list:

# Run the agent with the follow-up input
result = await Runner.run(
    starting_agent=agent,
    input=follow_up_input
)

# Print the second response
print("Second answer:\n" + result.final_output + "\n")

Here’s an example of the agent’s response to the follow-up:

Second answer:
The **best time to visit Oaxaca** is **from October to March**. During these months, the weather is pleasantly warm and dry, making it ideal for exploring markets, enjoying outdoor dining, and wandering the city’s lively streets.

**Highlights by Month:**

- **Late October – Early November:**  
  Visit for **Día de los Muertos (Day of the Dead)**. This is a magical time to experience unique food offerings—like pan de muerto and chocolate—as well as vibrant cultural traditions.

- **December – February:**  
  Oaxaca enjoys mild temperatures (rarely above 82°F/28°C) and clear skies, perfect for walking tours, dining alfresco, and day trips to mezcal villages or nearby ruins.

- **July:**  
  If you love cultural festivals, **Guelaguetza Festival** in July is unmissable! The city comes alive with parades, dances, and feasting, although there may be occasional rain showers.

**Tip:**  
April and May can get quite hot, while June through September is the rainy season—but even then, showers are often brief and the countryside is lush and green.

**Summary:**  
**October to March** is best for pleasant weather and a lively food scene, but special festivals in **July** and **November** are also fantastic for food lovers.

Because the agent receives the full conversation history, it can answer the follow-up question in a way that feels natural and informed, maintaining the flow and context of the dialogue. This approach is essential for building engaging, stateful conversational applications.

Summary & Preparation For Practice

In this lesson, you learned how to maintain and pass dialogue history to an agent using the to_input_list() method. This technique allows you to build stateful, multi-turn conversations in which the agent can remember and respond to previous messages, making your applications more natural and engaging.

You saw how to use to_input_list() to collect the conversation so far, append new user inputs, and continue the dialogue seamlessly. You are now ready to practice these skills in hands-on exercises. Keep up the great work — mastering multi-turn conversations is a key step toward building powerful, context-aware AI applications!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal