Intermediate

πŸ’¬ Multi-Turn Conversations

Build agents that remember context across multiple messages β€” the foundation of every chatbot.

By default, each call to RunAsync() is stateless β€” the agent doesn't remember previous messages. To build a conversational agent, you need to maintain and pass the chat history between turns. Agent Framework makes this straightforward via the AgentResponse.Messages property.

Each AgentResponse contains all the messages produced in that turn β€” including the user input, any tool calls, and the final assistant reply. By passing these messages back on the next RunAsync() call, the model has full context of the conversation so far.

For scenarios where you want the service to store conversation history automatically (no local state), use agents backed by the Azure AI Foundry Agents service or the OpenAI Responses API β€” both support server-side conversation threads.

Key Concepts

  • AgentResponse.Messages β€” the list of all messages produced in a single run
  • Chat history accumulation β€” pass prior messages back to RunAsync() to build up context
  • AgentResponse.Text β€” convenience property that extracts just the text of the reply
  • Stateless vs stateful β€” local history vs service-managed threads (Foundry, Assistants API)

NuGet Packages

dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
dotnet add package Azure.AI.OpenAI --prerelease
dotnet add package Azure.Identity

Code Sample

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

AIAgent agent = new AzureOpenAIClient(
        new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
        new AzureCliCredential())
    .GetChatClient("gpt-4o-mini")
    .AsAIAgent(instructions: "You are a helpful assistant.");

// Accumulate the full conversation history across turns.
var history = new List<ChatMessage>();

// Turn 1 β€” introduce ourselves.
var response1 = await agent.RunAsync("Hi! My name is Alex and I love astronomy.", history);
history.AddRange(response1.Messages);
Console.WriteLine($"Agent: {response1.Text}");

// Turn 2 β€” the agent remembers who we are.
var response2 = await agent.RunAsync("What is my name, and what subject do I love?", history);
history.AddRange(response2.Messages);
Console.WriteLine($"Agent: {response2.Text}");

// Turn 3 β€” follow-up building on prior turns.
var response3 = await agent.RunAsync(
    "Can you suggest a beginner book on that subject for me?", history);
history.AddRange(response3.Messages);
Console.WriteLine($"Agent: {response3.Text}");

Step-by-Step Explanation

  1. Maintain a history list β€” List<ChatMessage> accumulates all messages from every turn. ChatMessage comes from Microsoft.Extensions.AI.
  2. Pass history to RunAsync() β€” The second parameter is the prior conversation. The agent prepends these messages to the new request so the model has full context.
  3. Accumulate the response β€” After each turn, add response.Messages to the history list. These messages include the user turn, any tool calls, and the assistant's reply.
  4. Use .Text to read the reply β€” AgentResponse.Text aggregates all TextContent items across all returned messages into a single string.

Expected Output

Agent: Nice to meet you, Alex! Astronomy is a fascinating subject.
Agent: Your name is Alex, and you love astronomy!
Agent: A great beginner book on astronomy is "NightWatch" by Terence Dickinson. ...

Next Steps