Skip to content

Commit 3c39c4e

Browse files
Use ChatHistoryAgentThread with OpenAIResponseAgent
1 parent 3e5601c commit 3c39c4e

File tree

5 files changed

+48
-50
lines changed

5 files changed

+48
-50
lines changed

dotnet/samples/GettingStartedWithAgents/OpenAIResponse/Step02_ConversationState.cs

-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ public async Task ManageConversationStateWithResponseApiAsync()
9898
await foreach (AgentResponseItem<ChatMessageContent> responseItem in responseItems)
9999
{
100100
agentThread = responseItem.Thread;
101-
this.Output.WriteLine(agentThread.Id);
102101
WriteAgentChatMessage(responseItem.Message);
103102
}
104103
}

dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<ItemGroup>
3636
<ProjectReference Include="..\..\Connectors\Connectors.OpenAI\Connectors.OpenAI.csproj" />
3737
<ProjectReference Include="..\Abstractions\Agents.Abstractions.csproj" />
38+
<ProjectReference Include="..\Core\Agents.Core.csproj" />
3839
</ItemGroup>
3940

4041
<ItemGroup>

dotnet/src/Agents/OpenAI/OpenAIResponseAgent.cs

+39-13
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
namespace Microsoft.SemanticKernel.Agents.OpenAI;
1515

1616
/// <summary>
17-
/// Represents a <see cref="Agent"/> specialization based on Open AI Assistant / GPT.
17+
/// Represents a <see cref="Agent"/> specialization based on OpenAI Response API.
1818
/// </summary>
1919
[ExcludeFromCodeCoverage]
2020
public sealed class OpenAIResponseAgent : Agent
@@ -45,11 +45,7 @@ public override async IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> In
4545
{
4646
Verify.NotNull(messages);
4747

48-
var agentThread = await this.EnsureThreadExistsWithMessagesAsync(
49-
messages,
50-
thread,
51-
() => new OpenAIResponseAgentThread(this.Client, this.StoreEnabled),
52-
cancellationToken).ConfigureAwait(false);
48+
var agentThread = await this.EnsureThreadExistsWithMessagesAsync(messages, thread, cancellationToken).ConfigureAwait(false);
5349

5450
// Invoke responses with the updated chat history.
5551
var chatHistory = new ChatHistory();
@@ -97,10 +93,20 @@ protected override Task<AgentChannel> RestoreChannelAsync(string channelState, C
9793
}
9894

9995
#region private
96+
private async Task<AgentThread> EnsureThreadExistsWithMessagesAsync(ICollection<ChatMessageContent> messages, AgentThread? thread, CancellationToken cancellationToken)
97+
{
98+
if (this.StoreEnabled)
99+
{
100+
return await this.EnsureThreadExistsWithMessagesAsync(messages, thread, () => new OpenAIResponseAgentThread(this.Client), cancellationToken).ConfigureAwait(false);
101+
}
102+
103+
return await this.EnsureThreadExistsWithMessagesAsync(messages, thread, () => new ChatHistoryAgentThread(), cancellationToken).ConfigureAwait(false);
104+
}
105+
100106
private async IAsyncEnumerable<ChatMessageContent> InternalInvokeAsync(
101107
string? agentName,
102108
ChatHistory history,
103-
OpenAIResponseAgentThread agentThread,
109+
AgentThread agentThread,
104110
AgentInvokeOptions? options,
105111
[EnumeratorCancellation] CancellationToken cancellationToken)
106112
{
@@ -110,28 +116,27 @@ private async IAsyncEnumerable<ChatMessageContent> InternalInvokeAsync(
110116
if (!this.StoreEnabled)
111117
{
112118
// Use the thread chat history
113-
overrideHistory = [.. agentThread.ChatHistory, .. history];
119+
overrideHistory = [.. this.GetChatHistory(agentThread), .. history];
114120
}
115121

116122
var inputItems = overrideHistory.Select(c => c.ToResponseItem());
117123
var creationOptions = new ResponseCreationOptions()
118124
{
119125
EndUserId = this.GetDisplayName(),
120126
Instructions = $"{this.Instructions}\n{options?.AdditionalInstructions}",
121-
StoredOutputEnabled = agentThread.StoreEnabled,
127+
StoredOutputEnabled = this.StoreEnabled,
122128
};
123-
if (agentThread.StoreEnabled && agentThread.ResponseId != null)
129+
if (this.StoreEnabled && agentThread.Id != null)
124130
{
125-
creationOptions.PreviousResponseId = agentThread.ResponseId;
131+
creationOptions.PreviousResponseId = agentThread.Id;
126132
}
127133

128134
var clientResult = await this.Client.CreateResponseAsync(inputItems, creationOptions, cancellationToken).ConfigureAwait(false);
129135
var response = clientResult.Value;
130136

131137
if (this.StoreEnabled)
132138
{
133-
// Update the response id
134-
agentThread.ResponseId = response.Id;
139+
this.UpdateResponseId(agentThread, response.Id);
135140
}
136141

137142
var messages = response.OutputItems.Select(o => o.ToChatMessageContent());
@@ -143,5 +148,26 @@ private async IAsyncEnumerable<ChatMessageContent> InternalInvokeAsync(
143148
yield return message;
144149
}
145150
}
151+
152+
private ChatHistory GetChatHistory(AgentThread agentThread)
153+
{
154+
if (agentThread is ChatHistoryAgentThread chatHistoryAgentThread)
155+
{
156+
return chatHistoryAgentThread.ChatHistory;
157+
}
158+
159+
throw new InvalidOperationException("The agent thread is not a ChatHistoryAgentThread.");
160+
}
161+
162+
private void UpdateResponseId(AgentThread agentThread, string id)
163+
{
164+
if (agentThread is OpenAIResponseAgentThread openAIResponseAgentThread)
165+
{
166+
openAIResponseAgentThread.ResponseId = id;
167+
return;
168+
}
169+
170+
throw new InvalidOperationException("The agent thread is not an OpenAIResponseAgentThread.");
171+
}
146172
#endregion
147173
}

dotnet/src/Agents/OpenAI/OpenAIResponseAgentThread.cs

+6-34
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,49 @@
66
using System.Runtime.CompilerServices;
77
using System.Threading;
88
using System.Threading.Tasks;
9-
using Microsoft.SemanticKernel.ChatCompletion;
109
using OpenAI.Responses;
1110

1211
namespace Microsoft.SemanticKernel.Agents.OpenAI;
1312

1413
/// <summary>
15-
/// Represents a conversation thread for an OpenAI responses-based agent.
14+
/// Represents a conversation thread for an OpenAI Response API based agent when store is enabled.
1615
/// </summary>
1716
[ExcludeFromCodeCoverage]
1817
public sealed class OpenAIResponseAgentThread : AgentThread
1918
{
2019
private readonly OpenAIResponseClient _client;
21-
private readonly ChatHistory _chatHistory = new();
2220
private bool _isDeleted = false;
2321

2422
/// <summary>
2523
/// Initializes a new instance of the <see cref="OpenAIResponseAgentThread"/> class.
2624
/// </summary>
2725
/// <param name="client">The agents client to use for interacting with responses.</param>
28-
/// <param name="enableStore">Enable storing messages on the server.</param>
29-
public OpenAIResponseAgentThread(OpenAIResponseClient client, bool enableStore = false)
26+
public OpenAIResponseAgentThread(OpenAIResponseClient client)
3027
{
3128
Verify.NotNull(client);
3229

3330
this._client = client;
34-
this.StoreEnabled = enableStore;
3531
}
3632

3733
/// <summary>
3834
/// Initializes a new instance of the <see cref="OpenAIResponseAgentThread"/> class that resumes an existing response.
3935
/// </summary>
4036
/// <param name="client">The agents client to use for interacting with responses.</param>
4137
/// <param name="id">The ID of an existing response to resume.</param>
42-
/// <param name="enableStore">Enable storing messages on the server.</param>
43-
public OpenAIResponseAgentThread(OpenAIResponseClient client, string id, bool enableStore = false)
38+
public OpenAIResponseAgentThread(OpenAIResponseClient client, string id)
4439
{
4540
Verify.NotNull(client);
4641
Verify.NotNull(id);
4742

4843
this._client = client;
4944
this.ResponseId = id;
50-
this.StoreEnabled = enableStore;
5145
}
5246

53-
/// <summary>
54-
/// Storing of messages is enabled.
55-
/// </summary>
56-
public bool StoreEnabled { get; private set; } = false;
57-
5847
/// <summary>
5948
/// The current response id.
6049
/// </summary>
6150
internal string? ResponseId { get; set; }
6251

63-
/// <summary>
64-
/// The current chat history.
65-
/// </summary>
66-
internal ChatHistory ChatHistory => this._chatHistory;
67-
6852
/// <inheritdoc />
6953
public override string? Id => this.ResponseId;
7054

@@ -93,7 +77,6 @@ protected override Task DeleteInternalAsync(CancellationToken cancellationToken
9377
throw new InvalidOperationException("This thread cannot be deleted, since it has not been created.");
9478
}
9579

96-
this._chatHistory.Clear();
9780
this._isDeleted = true;
9881

9982
return Task.CompletedTask;
@@ -107,12 +90,6 @@ protected override Task OnNewMessageInternalAsync(ChatMessageContent newMessage,
10790
throw new InvalidOperationException("This thread has been deleted and cannot be used anymore.");
10891
}
10992

110-
// Keep track of locally
111-
if (string.IsNullOrEmpty(this.ResponseId))
112-
{
113-
this._chatHistory.Add(newMessage);
114-
}
115-
11693
return Task.CompletedTask;
11794
}
11895

@@ -124,7 +101,7 @@ public async IAsyncEnumerable<ChatMessageContent> GetMessagesAsync([EnumeratorCa
124101
throw new InvalidOperationException("This thread has been deleted and cannot be used anymore.");
125102
}
126103

127-
if (this.StoreEnabled && !string.IsNullOrEmpty(this.ResponseId))
104+
if (!string.IsNullOrEmpty(this.ResponseId))
128105
{
129106
var options = new ResponseItemCollectionOptions();
130107
var collectionResult = this._client.GetResponseInputItemsAsync(this.ResponseId, options, cancellationToken).ConfigureAwait(false);
@@ -133,12 +110,7 @@ public async IAsyncEnumerable<ChatMessageContent> GetMessagesAsync([EnumeratorCa
133110
yield return responseItem.ToChatMessageContent();
134111
}
135112
}
136-
else
137-
{
138-
foreach (var message in this._chatHistory)
139-
{
140-
yield return message;
141-
}
142-
}
113+
114+
yield break;
143115
}
144116
}

dotnet/src/Agents/UnitTests/OpenAI/OpenAIResponseAgentThreadTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public void ConstructorForResumingThreadShouldUseParams()
4747
/// Verify <see cref="OpenAIResponseAgentThread.GetMessagesAsync(System.Threading.CancellationToken)"/> returned when store is disabled.
4848
/// </summary>
4949
[Fact]
50-
public async Task VerifyGetMessagesWhenStoreDisabledAsync()
50+
public async Task VerifyGetMessagesWhenThreadIsUnusedAsync()
5151
{
5252
// Arrange
5353
var thread = new OpenAIResponseAgentThread(this.Client);
@@ -72,7 +72,7 @@ public async Task VerifyGetMessagesWhenStoreEnabledAsync()
7272
new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent(MessagesResponse) }
7373
);
7474
var responseId = "resp_67e8ff743ea08191b085bea42b4d83e809a3a922c4f4221b";
75-
var thread = new OpenAIResponseAgentThread(this.Client, id: responseId, enableStore: true);
75+
var thread = new OpenAIResponseAgentThread(this.Client, id: responseId);
7676

7777
// Act
7878
var messages = thread.GetMessagesAsync();

0 commit comments

Comments
 (0)