Skip to content

Commit 67e8304

Browse files
CopilotrogerbarretoCopilot
authored
.NET: Add Conversation State Sample (Step05) (#2697)
* Initial plan * Add Agent_OpenAI_Step05_Conversation sample for conversation state management Co-authored-by: rogerbarreto <[email protected]> * Update Program.cs comment to accurately describe the sample Co-authored-by: rogerbarreto <[email protected]> * Update the code to use the ConversationClient more in line with the samples in OpenAI * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * Changing sample to use ChatClientAgent and conversationId in GetNewThread --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: rogerbarreto <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 5da1c2f commit 67e8304

File tree

5 files changed

+206
-1
lines changed

5 files changed

+206
-1
lines changed

dotnet/agent-framework-dotnet.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
130130
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
131131
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
132+
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/Agent_OpenAI_Step05_Conversation.csproj" />
132133
</Folder>
133134
<Folder Name="/Samples/Purview/" />
134135
<Folder Name="/Samples/Purview/AgentWithPurview/">
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFrameworks>net10.0</TargetFrameworks>
6+
7+
<Nullable>enable</Nullable>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
13+
</ItemGroup>
14+
15+
</Project>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
// This sample demonstrates how to maintain conversation state using the OpenAIResponseClientAgent
4+
// and AgentThread. By passing the same thread to multiple agent invocations, the agent
5+
// automatically maintains the conversation history, allowing the AI model to understand
6+
// context from previous exchanges.
7+
8+
using System.ClientModel;
9+
using System.ClientModel.Primitives;
10+
using System.Text.Json;
11+
using Microsoft.Agents.AI;
12+
using Microsoft.Extensions.AI;
13+
using OpenAI;
14+
using OpenAI.Chat;
15+
using OpenAI.Conversations;
16+
17+
string apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
18+
string model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
19+
20+
// Create a ConversationClient directly from OpenAIClient
21+
OpenAIClient openAIClient = new(apiKey);
22+
ConversationClient conversationClient = openAIClient.GetConversationClient();
23+
24+
// Create an agent directly from the OpenAIResponseClient using OpenAIResponseClientAgent
25+
ChatClientAgent agent = new(openAIClient.GetOpenAIResponseClient(model).AsIChatClient(), instructions: "You are a helpful assistant.", name: "ConversationAgent");
26+
27+
ClientResult createConversationResult = await conversationClient.CreateConversationAsync(BinaryContent.Create(BinaryData.FromString("{}")));
28+
29+
using JsonDocument createConversationResultAsJson = JsonDocument.Parse(createConversationResult.GetRawResponse().Content.ToString());
30+
string conversationId = createConversationResultAsJson.RootElement.GetProperty("id"u8)!.GetString()!;
31+
32+
// Create a thread for the conversation - this enables conversation state management for subsequent turns
33+
AgentThread thread = agent.GetNewThread(conversationId);
34+
35+
Console.WriteLine("=== Multi-turn Conversation Demo ===\n");
36+
37+
// First turn: Ask about a topic
38+
Console.WriteLine("User: What is the capital of France?");
39+
UserChatMessage firstMessage = new("What is the capital of France?");
40+
41+
// After this call, the conversation state associated in the options is stored in 'thread' and used in subsequent calls
42+
ChatCompletion firstResponse = await agent.RunAsync([firstMessage], thread);
43+
Console.WriteLine($"Assistant: {firstResponse.Content.Last().Text}\n");
44+
45+
// Second turn: Follow-up question that relies on conversation context
46+
Console.WriteLine("User: What famous landmarks are located there?");
47+
UserChatMessage secondMessage = new("What famous landmarks are located there?");
48+
49+
ChatCompletion secondResponse = await agent.RunAsync([secondMessage], thread);
50+
Console.WriteLine($"Assistant: {secondResponse.Content.Last().Text}\n");
51+
52+
// Third turn: Another follow-up that demonstrates context continuity
53+
Console.WriteLine("User: How tall is the most famous one?");
54+
UserChatMessage thirdMessage = new("How tall is the most famous one?");
55+
56+
ChatCompletion thirdResponse = await agent.RunAsync([thirdMessage], thread);
57+
Console.WriteLine($"Assistant: {thirdResponse.Content.Last().Text}\n");
58+
59+
Console.WriteLine("=== End of Conversation ===");
60+
61+
// Show full conversation history
62+
Console.WriteLine("Full Conversation History:");
63+
ClientResult getConversationResult = await conversationClient.GetConversationAsync(conversationId);
64+
65+
Console.WriteLine("Conversation created.");
66+
Console.WriteLine($" Conversation ID: {conversationId}");
67+
Console.WriteLine();
68+
69+
CollectionResult getConversationItemsResults = conversationClient.GetConversationItems(conversationId);
70+
foreach (ClientResult result in getConversationItemsResults.GetRawPages())
71+
{
72+
Console.WriteLine("Message contents retrieved. Order is most recent first by default.");
73+
using JsonDocument getConversationItemsResultAsJson = JsonDocument.Parse(result.GetRawResponse().Content.ToString());
74+
foreach (JsonElement element in getConversationItemsResultAsJson.RootElement.GetProperty("data").EnumerateArray())
75+
{
76+
string messageId = element.GetProperty("id"u8).ToString();
77+
string messageRole = element.GetProperty("role"u8).ToString();
78+
Console.WriteLine($" Message ID: {messageId}");
79+
Console.WriteLine($" Message Role: {messageRole}");
80+
81+
foreach (var content in element.GetProperty("content").EnumerateArray())
82+
{
83+
string messageContentText = content.GetProperty("text"u8).ToString();
84+
Console.WriteLine($" Message Text: {messageContentText}");
85+
}
86+
Console.WriteLine();
87+
}
88+
}
89+
90+
ClientResult deleteConversationResult = conversationClient.DeleteConversation(conversationId);
91+
using JsonDocument deleteConversationResultAsJson = JsonDocument.Parse(deleteConversationResult.GetRawResponse().Content.ToString());
92+
bool deleted = deleteConversationResultAsJson.RootElement
93+
.GetProperty("deleted"u8)
94+
.GetBoolean();
95+
96+
Console.WriteLine("Conversation deleted.");
97+
Console.WriteLine($" Deleted: {deleted}");
98+
Console.WriteLine();
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Managing Conversation State with OpenAI
2+
3+
This sample demonstrates how to maintain conversation state across multiple turns using the Agent Framework with OpenAI's Conversation API.
4+
5+
## What This Sample Shows
6+
7+
- **Conversation State Management**: Shows how to use `ConversationClient` and `AgentThread` to maintain conversation context across multiple agent invocations
8+
- **Multi-turn Conversations**: Demonstrates follow-up questions that rely on context from previous messages in the conversation
9+
- **Server-Side Storage**: Uses OpenAI's Conversation API to manage conversation history server-side, allowing the model to access previous messages without resending them
10+
- **Conversation Lifecycle**: Demonstrates creating, retrieving, and deleting conversations
11+
12+
## Key Concepts
13+
14+
### ConversationClient for Server-Side Storage
15+
16+
The `ConversationClient` manages conversations on OpenAI's servers:
17+
18+
```csharp
19+
// Create a ConversationClient from OpenAIClient
20+
OpenAIClient openAIClient = new(apiKey);
21+
ConversationClient conversationClient = openAIClient.GetConversationClient();
22+
23+
// Create a new conversation
24+
ClientResult createConversationResult = await conversationClient.CreateConversationAsync(BinaryContent.Create(BinaryData.FromString("{}")));
25+
```
26+
27+
### AgentThread for Conversation State
28+
29+
The `AgentThread` works with `ChatClientAgentRunOptions` to link the agent to a server-side conversation:
30+
31+
```csharp
32+
// Set up agent run options with the conversation ID
33+
ChatClientAgentRunOptions agentRunOptions = new() { ChatOptions = new ChatOptions() { ConversationId = conversationId } };
34+
35+
// Create a thread for the conversation
36+
AgentThread thread = agent.GetNewThread();
37+
38+
// First call links the thread to the conversation
39+
ChatCompletion firstResponse = await agent.RunAsync([firstMessage], thread, agentRunOptions);
40+
41+
// Subsequent calls use the thread without needing to pass options again
42+
ChatCompletion secondResponse = await agent.RunAsync([secondMessage], thread);
43+
```
44+
45+
### Retrieving Conversation History
46+
47+
You can retrieve the full conversation history from the server:
48+
49+
```csharp
50+
CollectionResult getConversationItemsResults = conversationClient.GetConversationItems(conversationId);
51+
foreach (ClientResult result in getConversationItemsResults.GetRawPages())
52+
{
53+
// Process conversation items
54+
}
55+
```
56+
57+
### How It Works
58+
59+
1. **Create an OpenAI Client**: Initialize an `OpenAIClient` with your API key
60+
2. **Create a Conversation**: Use `ConversationClient` to create a server-side conversation
61+
3. **Create an Agent**: Initialize an `OpenAIResponseClientAgent` with the desired model and instructions
62+
4. **Create a Thread**: Call `agent.GetNewThread()` to create a new conversation thread
63+
5. **Link Thread to Conversation**: Pass `ChatClientAgentRunOptions` with the `ConversationId` on the first call
64+
6. **Send Messages**: Subsequent calls to `agent.RunAsync()` only need the thread - context is maintained
65+
7. **Cleanup**: Delete the conversation when done using `conversationClient.DeleteConversation()`
66+
67+
## Running the Sample
68+
69+
1. Set the required environment variables:
70+
```powershell
71+
$env:OPENAI_API_KEY = "your_api_key_here"
72+
$env:OPENAI_MODEL = "gpt-4o-mini"
73+
```
74+
75+
2. Run the sample:
76+
```powershell
77+
dotnet run
78+
```
79+
80+
## Expected Output
81+
82+
The sample demonstrates a three-turn conversation where each follow-up question relies on context from previous messages:
83+
84+
1. First question asks about the capital of France
85+
2. Second question asks about landmarks "there" - requiring understanding of the previous answer
86+
3. Third question asks about "the most famous one" - requiring context from both previous turns
87+
88+
After the conversation, the sample retrieves and displays the full conversation history from the server, then cleans up by deleting the conversation.
89+
90+
This demonstrates that the conversation state is properly maintained across multiple agent invocations using OpenAI's server-side conversation storage.

dotnet/samples/GettingStarted/AgentWithOpenAI/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ Agent Framework provides additional support to allow OpenAI developers to use th
1313
|[Creating an AIAgent](./Agent_OpenAI_Step01_Running/)|This sample demonstrates how to create and run a basic agent with native OpenAI SDK types. Shows both regular and streaming invocation of the agent.|
1414
|[Using Reasoning Capabilities](./Agent_OpenAI_Step02_Reasoning/)|This sample demonstrates how to create an AI agent with reasoning capabilities using OpenAI's reasoning models and response types.|
1515
|[Creating an Agent from a ChatClient](./Agent_OpenAI_Step03_CreateFromChatClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Chat.ChatClient instance using OpenAIChatClientAgent.|
16-
|[Creating an Agent from an OpenAIResponseClient](./Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Responses.OpenAIResponseClient instance using OpenAIResponseClientAgent.|
16+
|[Creating an Agent from an OpenAIResponseClient](./Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Responses.OpenAIResponseClient instance using OpenAIResponseClientAgent.|
17+
|[Managing Conversation State](./Agent_OpenAI_Step05_Conversation/)|This sample demonstrates how to maintain conversation state across multiple turns using the AgentThread for context continuity.|

0 commit comments

Comments
 (0)