From dfe83a4fac6601647e0faa326377a16e6202e0af Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:08:26 +0000 Subject: [PATCH 1/3] Enable Microsoft.Agents.AI.FoundryMemory for NuGet release - Remove IsPackable=false override from .csproj to inherit IsPackable=true from nuget-package.props - Add project to agent-framework-release.slnf for inclusion in build/sign/publish pipeline Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/agent-framework-release.slnf | 1 + .../Microsoft.Agents.AI.FoundryMemory.csproj | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/dotnet/agent-framework-release.slnf b/dotnet/agent-framework-release.slnf index ebd33c0767..1c8f477b16 100644 --- a/dotnet/agent-framework-release.slnf +++ b/dotnet/agent-framework-release.slnf @@ -14,6 +14,7 @@ "src\\Microsoft.Agents.AI.Declarative\\Microsoft.Agents.AI.Declarative.csproj", "src\\Microsoft.Agents.AI.DevUI\\Microsoft.Agents.AI.DevUI.csproj", "src\\Microsoft.Agents.AI.DurableTask\\Microsoft.Agents.AI.DurableTask.csproj", + "src\\Microsoft.Agents.AI.FoundryMemory\\Microsoft.Agents.AI.FoundryMemory.csproj", "src\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj", "src\\Microsoft.Agents.AI.Hosting.A2A\\Microsoft.Agents.AI.Hosting.A2A.csproj", "src\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj", diff --git a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/Microsoft.Agents.AI.FoundryMemory.csproj b/dotnet/src/Microsoft.Agents.AI.FoundryMemory/Microsoft.Agents.AI.FoundryMemory.csproj index 75da2bccc5..a1b8f85ae8 100644 --- a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/Microsoft.Agents.AI.FoundryMemory.csproj +++ b/dotnet/src/Microsoft.Agents.AI.FoundryMemory/Microsoft.Agents.AI.FoundryMemory.csproj @@ -13,10 +13,6 @@ - - - false - From b103d2f002727d3fd606d2a180a50d629ccf7885 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:59:08 +0000 Subject: [PATCH 2/3] Update FoundryMemoryProvider and MemorySearch sample - StoreAIContextAsync fires UpdateMemoriesAsync immediately (non-accumulation) - WhenUpdatesCompletedAsync polls last updateId via GetUpdateResultAsync - Updated FoundryAgents_Step22_MemorySearch sample to create/destroy memory store (matching features/foundry-agent-client pattern) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Program.cs | 126 ++++++++++-------- .../FoundryMemoryProvider.cs | 21 +-- 2 files changed, 76 insertions(+), 71 deletions(-) diff --git a/dotnet/samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/Program.cs b/dotnet/samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/Program.cs index 836bf1b684..1f6b0f2ddc 100644 --- a/dotnet/samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/Program.cs +++ b/dotnet/samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/Program.cs @@ -12,11 +12,8 @@ string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."); string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - -// Memory store configuration -// NOTE: Memory stores must be created beforehand via Azure Portal or Python SDK. -// The .NET SDK currently only supports using existing memory stores with agents. -string memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? throw new InvalidOperationException("AZURE_AI_MEMORY_STORE_ID is not set."); +string embeddingModelName = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002"; +string memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? $"foundry-memory-sample-{Guid.NewGuid():N}"; const string AgentInstructions = """ You are a helpful assistant that remembers past conversations. @@ -32,71 +29,57 @@ Use the memory search tool to recall relevant information from previous interact string userScope = $"user_{Environment.MachineName}"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +DefaultAzureCredential credential = new(); +AIProjectClient aiProjectClient = new(new Uri(endpoint), credential); -// Create the Memory Search tool configuration -MemorySearchPreviewTool memorySearchTool = new(memoryStoreName, userScope) -{ - // Optional: Configure how quickly new memories are indexed (in seconds) - UpdateDelay = 1, +// Ensure the memory store exists and has memories to retrieve. +await EnsureMemoryStoreAsync(); - // Optional: Configure search behavior - SearchOptions = new MemorySearchToolOptions - { - // Additional search options can be configured here if needed - } -}; +// Create the Memory Search tool configuration +MemorySearchPreviewTool memorySearchTool = new(memoryStoreName, userScope) { UpdateDelay = 0 }; // Create agent using Option 1 (MEAI) or Option 2 (Native SDK) AIAgent agent = await CreateAgentWithMEAI(); // AIAgent agent = await CreateAgentWithNativeSDK(); -Console.WriteLine("Agent created with Memory Search tool. Starting conversation...\n"); - -// Conversation 1: Share some personal information -Console.WriteLine("User: My name is Alice and I love programming in C#."); -AgentResponse response1 = await agent.RunAsync("My name is Alice and I love programming in C#."); -Console.WriteLine($"Agent: {response1.Messages.LastOrDefault()?.Text}\n"); - -// Allow time for memory to be indexed -await Task.Delay(2000); +try +{ + Console.WriteLine("Agent created with Memory Search tool. Starting conversation...\n"); -// Conversation 2: Test if the agent remembers -Console.WriteLine("User: What's my name and what programming language do I prefer?"); -AgentResponse response2 = await agent.RunAsync("What's my name and what programming language do I prefer?"); -Console.WriteLine($"Agent: {response2.Messages.LastOrDefault()?.Text}\n"); + // The agent uses the memory search tool to recall stored information. + Console.WriteLine("User: What's my name and what programming language do I prefer?"); + AgentResponse response = await agent.RunAsync("What's my name and what programming language do I prefer?"); + Console.WriteLine($"Agent: {response.Messages.LastOrDefault()?.Text}\n"); -// Inspect memory search results if available in raw response items -// Note: Memory search tool call results appear as AgentResponseItem types -foreach (var message in response2.Messages) -{ - if (message.RawRepresentation is AgentResponseItem agentResponseItem && - agentResponseItem is MemorySearchToolCallResponseItem memorySearchResult) + // Inspect memory search results if available in raw response items. + foreach (var message in response.Messages) { - Console.WriteLine($"Memory Search Status: {memorySearchResult.Status}"); - Console.WriteLine($"Memory Search Results Count: {memorySearchResult.Results.Count}"); - - foreach (var result in memorySearchResult.Results) + if (message.RawRepresentation is MemorySearchToolCallResponseItem memorySearchResult) { - var memoryItem = result.MemoryItem; - Console.WriteLine($" - Memory ID: {memoryItem.MemoryId}"); - Console.WriteLine($" Scope: {memoryItem.Scope}"); - Console.WriteLine($" Content: {memoryItem.Content}"); - Console.WriteLine($" Updated: {memoryItem.UpdatedAt}"); + Console.WriteLine($"Memory Search Status: {memorySearchResult.Status}"); + Console.WriteLine($"Memory Search Results Count: {memorySearchResult.Results.Count}"); + + foreach (var result in memorySearchResult.Results) + { + var memoryItem = result.MemoryItem; + Console.WriteLine($" - Memory ID: {memoryItem.MemoryId}"); + Console.WriteLine($" Scope: {memoryItem.Scope}"); + Console.WriteLine($" Content: {memoryItem.Content}"); + Console.WriteLine($" Updated: {memoryItem.UpdatedAt}"); + } } } } +finally +{ + // Cleanup: Delete the agent and memory store. + Console.WriteLine("\nCleaning up..."); + await aiProjectClient.Agents.DeleteAgentAsync(agent.Name); + Console.WriteLine("Agent deleted."); + await aiProjectClient.MemoryStores.DeleteMemoryStoreAsync(memoryStoreName); + Console.WriteLine("Memory store deleted."); +} -// Cleanup: Delete the agent (memory store persists and should be cleaned up separately if needed) -Console.WriteLine("\nCleaning up agent..."); -await aiProjectClient.Agents.DeleteAgentAsync(agent.Name); -Console.WriteLine("Agent deleted successfully."); - -// NOTE: Memory stores are long-lived resources and are NOT deleted with the agent. -// To delete a memory store, use the Azure Portal or Python SDK: -// await project_client.memory_stores.delete(memory_store.name) - -// --- Agent Creation Options --- #pragma warning disable CS8321 // Local function is declared but never used // Option 1 - Using MemorySearchTool wrapped as MEAI AITool @@ -122,3 +105,36 @@ async Task CreateAgentWithNativeSDK() }) ); } + +// Helpers — kept at the bottom so the main agent flow above stays clean. +async Task EnsureMemoryStoreAsync() +{ + Console.WriteLine($"Creating memory store '{memoryStoreName}'..."); + try + { + await aiProjectClient.MemoryStores.GetMemoryStoreAsync(memoryStoreName); + Console.WriteLine("Memory store already exists."); + } + catch (System.ClientModel.ClientResultException ex) when (ex.Status == 404) + { + MemoryStoreDefaultDefinition definition = new(deploymentName, embeddingModelName); + await aiProjectClient.MemoryStores.CreateMemoryStoreAsync(memoryStoreName, definition, "Sample memory store for Memory Search demo"); + Console.WriteLine("Memory store created."); + } + + Console.WriteLine("Storing memories from a prior conversation..."); + MemoryUpdateOptions memoryOptions = new(userScope) { UpdateDelay = 0 }; + memoryOptions.Items.Add(ResponseItem.CreateUserMessageItem("My name is Alice and I love programming in C#.")); + + MemoryUpdateResult updateResult = await aiProjectClient.MemoryStores.WaitForMemoriesUpdateAsync( + memoryStoreName: memoryStoreName, + options: memoryOptions, + pollingInterval: 500); + + if (updateResult.Status == MemoryStoreUpdateStatus.Failed) + { + throw new InvalidOperationException($"Memory update failed: {updateResult.ErrorDetails}"); + } + + Console.WriteLine($"Memory update completed (status: {updateResult.Status}).\n"); +} diff --git a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProvider.cs b/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProvider.cs index 35baa055d1..791c82f910 100644 --- a/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProvider.cs +++ b/dotnet/src/Microsoft.Agents.AI.FoundryMemory/FoundryMemoryProvider.cs @@ -349,14 +349,7 @@ public async Task WhenUpdatesCompletedAsync( } TimeSpan interval = pollingInterval ?? TimeSpan.FromSeconds(5); - await this.WaitForUpdateAsync(updateId, interval, cancellationToken).ConfigureAwait(false); - // Only clear the pending update ID after successful completion - Interlocked.CompareExchange(ref this._lastPendingUpdateId, null, updateId); - } - - private async Task WaitForUpdateAsync(string updateId, TimeSpan interval, CancellationToken cancellationToken) - { while (true) { cancellationToken.ThrowIfCancellationRequested(); @@ -379,7 +372,7 @@ private async Task WaitForUpdateAsync(string updateId, TimeSpan interval, Cancel if (status == MemoryStoreUpdateStatus.Completed || status == MemoryStoreUpdateStatus.Superseded) { - return; + break; } if (status == MemoryStoreUpdateStatus.Failed) @@ -387,15 +380,11 @@ private async Task WaitForUpdateAsync(string updateId, TimeSpan interval, Cancel throw new InvalidOperationException($"Memory update operation '{updateId}' failed: {response.ErrorDetails}"); } - if (status == MemoryStoreUpdateStatus.Queued || status == MemoryStoreUpdateStatus.InProgress) - { - await Task.Delay(interval, cancellationToken).ConfigureAwait(false); - } - else - { - throw new InvalidOperationException($"Unknown update status '{status}' for update '{updateId}'."); - } + await Task.Delay(interval, cancellationToken).ConfigureAwait(false); } + + // Only clear the pending update ID after successful completion. + Interlocked.CompareExchange(ref this._lastPendingUpdateId, null, updateId); } private static MessageResponseItem ToResponseItem(ChatRole role, string text) From 155b8aa9f55894fcba90cde90058fd2d12cb163e Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:59:08 +0000 Subject: [PATCH 3/3] Update FoundryAgents_Step22_MemorySearch sample - Sample now creates/destroys memory store (self-contained lifecycle) - Uses WaitForMemoriesUpdateAsync for seeding memories - Cleanup in finally block deletes both agent and memory store - Matches features/foundry-agent-client pattern Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Program.cs | 126 ++++++++++-------- 1 file changed, 71 insertions(+), 55 deletions(-) diff --git a/dotnet/samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/Program.cs b/dotnet/samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/Program.cs index 836bf1b684..1f6b0f2ddc 100644 --- a/dotnet/samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/Program.cs +++ b/dotnet/samples/02-agents/FoundryAgents/FoundryAgents_Step22_MemorySearch/Program.cs @@ -12,11 +12,8 @@ string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."); string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - -// Memory store configuration -// NOTE: Memory stores must be created beforehand via Azure Portal or Python SDK. -// The .NET SDK currently only supports using existing memory stores with agents. -string memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? throw new InvalidOperationException("AZURE_AI_MEMORY_STORE_ID is not set."); +string embeddingModelName = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002"; +string memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? $"foundry-memory-sample-{Guid.NewGuid():N}"; const string AgentInstructions = """ You are a helpful assistant that remembers past conversations. @@ -32,71 +29,57 @@ Use the memory search tool to recall relevant information from previous interact string userScope = $"user_{Environment.MachineName}"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +DefaultAzureCredential credential = new(); +AIProjectClient aiProjectClient = new(new Uri(endpoint), credential); -// Create the Memory Search tool configuration -MemorySearchPreviewTool memorySearchTool = new(memoryStoreName, userScope) -{ - // Optional: Configure how quickly new memories are indexed (in seconds) - UpdateDelay = 1, +// Ensure the memory store exists and has memories to retrieve. +await EnsureMemoryStoreAsync(); - // Optional: Configure search behavior - SearchOptions = new MemorySearchToolOptions - { - // Additional search options can be configured here if needed - } -}; +// Create the Memory Search tool configuration +MemorySearchPreviewTool memorySearchTool = new(memoryStoreName, userScope) { UpdateDelay = 0 }; // Create agent using Option 1 (MEAI) or Option 2 (Native SDK) AIAgent agent = await CreateAgentWithMEAI(); // AIAgent agent = await CreateAgentWithNativeSDK(); -Console.WriteLine("Agent created with Memory Search tool. Starting conversation...\n"); - -// Conversation 1: Share some personal information -Console.WriteLine("User: My name is Alice and I love programming in C#."); -AgentResponse response1 = await agent.RunAsync("My name is Alice and I love programming in C#."); -Console.WriteLine($"Agent: {response1.Messages.LastOrDefault()?.Text}\n"); - -// Allow time for memory to be indexed -await Task.Delay(2000); +try +{ + Console.WriteLine("Agent created with Memory Search tool. Starting conversation...\n"); -// Conversation 2: Test if the agent remembers -Console.WriteLine("User: What's my name and what programming language do I prefer?"); -AgentResponse response2 = await agent.RunAsync("What's my name and what programming language do I prefer?"); -Console.WriteLine($"Agent: {response2.Messages.LastOrDefault()?.Text}\n"); + // The agent uses the memory search tool to recall stored information. + Console.WriteLine("User: What's my name and what programming language do I prefer?"); + AgentResponse response = await agent.RunAsync("What's my name and what programming language do I prefer?"); + Console.WriteLine($"Agent: {response.Messages.LastOrDefault()?.Text}\n"); -// Inspect memory search results if available in raw response items -// Note: Memory search tool call results appear as AgentResponseItem types -foreach (var message in response2.Messages) -{ - if (message.RawRepresentation is AgentResponseItem agentResponseItem && - agentResponseItem is MemorySearchToolCallResponseItem memorySearchResult) + // Inspect memory search results if available in raw response items. + foreach (var message in response.Messages) { - Console.WriteLine($"Memory Search Status: {memorySearchResult.Status}"); - Console.WriteLine($"Memory Search Results Count: {memorySearchResult.Results.Count}"); - - foreach (var result in memorySearchResult.Results) + if (message.RawRepresentation is MemorySearchToolCallResponseItem memorySearchResult) { - var memoryItem = result.MemoryItem; - Console.WriteLine($" - Memory ID: {memoryItem.MemoryId}"); - Console.WriteLine($" Scope: {memoryItem.Scope}"); - Console.WriteLine($" Content: {memoryItem.Content}"); - Console.WriteLine($" Updated: {memoryItem.UpdatedAt}"); + Console.WriteLine($"Memory Search Status: {memorySearchResult.Status}"); + Console.WriteLine($"Memory Search Results Count: {memorySearchResult.Results.Count}"); + + foreach (var result in memorySearchResult.Results) + { + var memoryItem = result.MemoryItem; + Console.WriteLine($" - Memory ID: {memoryItem.MemoryId}"); + Console.WriteLine($" Scope: {memoryItem.Scope}"); + Console.WriteLine($" Content: {memoryItem.Content}"); + Console.WriteLine($" Updated: {memoryItem.UpdatedAt}"); + } } } } +finally +{ + // Cleanup: Delete the agent and memory store. + Console.WriteLine("\nCleaning up..."); + await aiProjectClient.Agents.DeleteAgentAsync(agent.Name); + Console.WriteLine("Agent deleted."); + await aiProjectClient.MemoryStores.DeleteMemoryStoreAsync(memoryStoreName); + Console.WriteLine("Memory store deleted."); +} -// Cleanup: Delete the agent (memory store persists and should be cleaned up separately if needed) -Console.WriteLine("\nCleaning up agent..."); -await aiProjectClient.Agents.DeleteAgentAsync(agent.Name); -Console.WriteLine("Agent deleted successfully."); - -// NOTE: Memory stores are long-lived resources and are NOT deleted with the agent. -// To delete a memory store, use the Azure Portal or Python SDK: -// await project_client.memory_stores.delete(memory_store.name) - -// --- Agent Creation Options --- #pragma warning disable CS8321 // Local function is declared but never used // Option 1 - Using MemorySearchTool wrapped as MEAI AITool @@ -122,3 +105,36 @@ async Task CreateAgentWithNativeSDK() }) ); } + +// Helpers — kept at the bottom so the main agent flow above stays clean. +async Task EnsureMemoryStoreAsync() +{ + Console.WriteLine($"Creating memory store '{memoryStoreName}'..."); + try + { + await aiProjectClient.MemoryStores.GetMemoryStoreAsync(memoryStoreName); + Console.WriteLine("Memory store already exists."); + } + catch (System.ClientModel.ClientResultException ex) when (ex.Status == 404) + { + MemoryStoreDefaultDefinition definition = new(deploymentName, embeddingModelName); + await aiProjectClient.MemoryStores.CreateMemoryStoreAsync(memoryStoreName, definition, "Sample memory store for Memory Search demo"); + Console.WriteLine("Memory store created."); + } + + Console.WriteLine("Storing memories from a prior conversation..."); + MemoryUpdateOptions memoryOptions = new(userScope) { UpdateDelay = 0 }; + memoryOptions.Items.Add(ResponseItem.CreateUserMessageItem("My name is Alice and I love programming in C#.")); + + MemoryUpdateResult updateResult = await aiProjectClient.MemoryStores.WaitForMemoriesUpdateAsync( + memoryStoreName: memoryStoreName, + options: memoryOptions, + pollingInterval: 500); + + if (updateResult.Status == MemoryStoreUpdateStatus.Failed) + { + throw new InvalidOperationException($"Memory update failed: {updateResult.ErrorDetails}"); + } + + Console.WriteLine($"Memory update completed (status: {updateResult.Status}).\n"); +}