Skip to content

Commit e5d9d74

Browse files
westey-mCopilot
andauthored
.NET: Chathistory memory provider add (#1867)
* Add ChatHistoryMemoryProvider with unit tests * Set new project to not packable. * Fix bugs * Add serialization support. * Update dotnet/src/Microsoft.Agents.AI.VectorDataMemory/ChatHistoryMemoryProvider.cs Co-authored-by: Copilot <[email protected]> * Remove unnecessary line * Convert ChatHistoryMemoryProvider to use Dynamic collections. * Sealing options and scope classes. * Add sample, add scope to logs and improve scope validation * Move ChatHistoryMemoryProvider to MAAI project. --------- Co-authored-by: Copilot <[email protected]>
1 parent 64826b8 commit e5d9d74

File tree

11 files changed

+1081
-6
lines changed

11 files changed

+1081
-6
lines changed

dotnet/agent-framework-dotnet.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
<Project Path="samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Agent_Step18_TextSearchRag.csproj" />
6767
<Project Path="samples/GettingStarted/Agents/Agent_Step19_Mem0Provider/Agent_Step19_Mem0Provider.csproj" />
6868
<Project Path="samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/Agent_Step20_BackgroundResponsesWithToolsAndPersistence.csproj" />
69+
<Project Path="samples/GettingStarted/Agents/Agent_Step21_ChatHistoryMemoryProvider/Agent_Step21_ChatHistoryMemoryProvider.csproj" />
6970
</Folder>
7071
<Folder Name="/Samples/GettingStarted/DevUI/">
7172
<File Path="samples/GettingStarted/DevUI/README.md" />

dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// capabilities to an AI agent. The provider runs a search against an external knowledge base
55
// before each model invocation and injects the results into the model context.
66

7+
// Also see the AgentWithRAG folder for more advanced RAG scenarios.
8+
79
using Azure.AI.OpenAI;
810
using Azure.Identity;
911
using Microsoft.Agents.AI;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
7+
<Nullable>enable</Nullable>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Azure.AI.OpenAI" />
13+
<PackageReference Include="Azure.Identity" />
14+
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
15+
<PackageReference Include="Microsoft.SemanticKernel.Connectors.InMemory" />
16+
<PackageReference Include="System.Linq.Async" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
// This sample shows how to create and use a simple AI agent that stores chat messages in a vector store using the ChatHistoryMemoryProvider.
4+
// It can then use the chat history from prior conversations to inform responses in new conversations.
5+
6+
using Azure.AI.OpenAI;
7+
using Azure.Identity;
8+
using Microsoft.Agents.AI;
9+
using Microsoft.Extensions.AI;
10+
using Microsoft.Extensions.VectorData;
11+
using Microsoft.SemanticKernel.Connectors.InMemory;
12+
using OpenAI;
13+
14+
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
15+
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
16+
var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large";
17+
18+
// Create a vector store to store the chat messages in.
19+
// For demonstration purposes, we are using an in-memory vector store.
20+
// Replace this with a vector store implementation of your choice that can persist the chat history long term.
21+
VectorStore vectorStore = new InMemoryVectorStore(new InMemoryVectorStoreOptions()
22+
{
23+
EmbeddingGenerator = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
24+
.GetEmbeddingClient(embeddingDeploymentName)
25+
.AsIEmbeddingGenerator()
26+
});
27+
28+
// Create the agent and add the ChatHistoryMemoryProvider to store chat messages in the vector store.
29+
AIAgent agent = new AzureOpenAIClient(
30+
new Uri(endpoint),
31+
new AzureCliCredential())
32+
.GetChatClient(deploymentName)
33+
.CreateAIAgent(new ChatClientAgentOptions
34+
{
35+
Instructions = "You are good at telling jokes.",
36+
Name = "Joker",
37+
AIContextProviderFactory = (ctx) => new ChatHistoryMemoryProvider(
38+
vectorStore,
39+
collectionName: "chathistory",
40+
vectorDimensions: 3072,
41+
// Configure the scope values under which chat messages will be stored.
42+
// In this case, we are using a fixed user ID and a unique thread ID for each new thread.
43+
storageScope: new() { UserId = "UID1", ThreadId = new Guid().ToString() },
44+
// Configure the scope which would be used to search for relevant prior messages.
45+
// In this case, we are searching for any messages for the user across all threads.
46+
searchScope: new() { UserId = "UID1" })
47+
});
48+
49+
// Start a new thread for the agent conversation.
50+
AgentThread thread = agent.GetNewThread();
51+
52+
// Run the agent with the thread that stores conversation history in the vector store.
53+
Console.WriteLine(await agent.RunAsync("I like jokes about Pirates. Tell me a joke about a pirate.", thread));
54+
55+
// Start a second thread. Since we configured the search scope to be across all threads for the user,
56+
// the agent should remember that the user likes pirate jokes.
57+
AgentThread thread2 = agent.GetNewThread();
58+
59+
// Run the agent with the second thread.
60+
Console.WriteLine(await agent.RunAsync("Tell me a joke that I might like.", thread2));

dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public override async ValueTask<AIContext> InvokingAsync(InvokingContext context
153153
if (this._logger is not null)
154154
{
155155
this._logger.LogInformation(
156-
"Mem0AIContextProvider: Retrieved {Count} memories. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
156+
"Mem0AIContextProvider: Retrieved {Count} memories. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'.",
157157
memories.Count,
158158
this._searchScope.ApplicationId,
159159
this._searchScope.AgentId,
@@ -162,7 +162,7 @@ public override async ValueTask<AIContext> InvokingAsync(InvokingContext context
162162
if (outputMessageText is not null)
163163
{
164164
this._logger.LogTrace(
165-
"Mem0AIContextProvider: Search Results\nInput:{Input}\nOutput:{MessageText}\nApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
165+
"Mem0AIContextProvider: Search Results\nInput:{Input}\nOutput:{MessageText}\nApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'.",
166166
queryText,
167167
outputMessageText,
168168
this._searchScope.ApplicationId,
@@ -185,7 +185,7 @@ public override async ValueTask<AIContext> InvokingAsync(InvokingContext context
185185
{
186186
this._logger?.LogError(
187187
ex,
188-
"Mem0AIContextProvider: Failed to search Mem0 for memories due to error. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
188+
"Mem0AIContextProvider: Failed to search Mem0 for memories due to error. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'.",
189189
this._searchScope.ApplicationId,
190190
this._searchScope.AgentId,
191191
this._searchScope.ThreadId,
@@ -211,7 +211,7 @@ public override async ValueTask InvokedAsync(InvokedContext context, Cancellatio
211211
{
212212
this._logger?.LogError(
213213
ex,
214-
"Mem0AIContextProvider: Failed to send messages to Mem0 due to error. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
214+
"Mem0AIContextProvider: Failed to send messages to Mem0 due to error. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'.",
215215
this._storageScope.ApplicationId,
216216
this._storageScope.AgentId,
217217
this._storageScope.ThreadId,

dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ private static JsonSerializerOptions CreateDefaultOptions()
6868
// Agent abstraction types
6969
[JsonSerializable(typeof(ChatClientAgentThread.ThreadState))]
7070
[JsonSerializable(typeof(TextSearchProvider.TextSearchProviderState))]
71+
[JsonSerializable(typeof(ChatHistoryMemoryProvider.ChatHistoryMemoryProviderState))]
7172

7273
[ExcludeFromCodeCoverage]
7374
internal sealed partial class JsonContext : JsonSerializerContext;

dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptio
166166
var state = new ThreadState
167167
{
168168
ConversationId = this.ConversationId,
169-
StoreState = storeState,
170-
AIContextProviderState = aiContextProviderState
169+
StoreState = storeState is { ValueKind: not JsonValueKind.Undefined } ? storeState : null,
170+
AIContextProviderState = aiContextProviderState is { ValueKind: not JsonValueKind.Undefined } ? aiContextProviderState : null,
171171
};
172172

173173
return JsonSerializer.SerializeToElement(state, AgentJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ThreadState)));

0 commit comments

Comments
 (0)