.NET: Add Foundry Memory Context Provider (#3522)

* Add Azure AI Foundry Memory Context Provider with unit tests

* Add FoundryMemory integration tests and sample application

* Fix ClearStoredMemoriesAsync to handle 404 gracefully and rename to EnsureStoredMemoriesDeletedAsync

* Refactor FoundryMemory: simplify architecture and add memory store creation

- Remove IFoundryMemoryOperations interface (was only for test mocking)
- Remove AIProjectClientMemoryOperations wrapper class
- Provider now directly uses AIProjectClient with internal extension methods
- Extension methods return actual response models instead of extracted values
- Remove WaitForUpdateCompletionAsync from provider (sample uses delay)
- Simplify EnsureMemoryStoreCreatedAsync to return Task instead of Task<bool>
- Add memory store creation with chat_model and embedding_model
- Add UpdateMemoriesResponse with SupersededBy and Error fields
- Simplify unit tests to focus on constructor validation and serialization
- Update sample to use simple delay for memory processing wait

* Add waiting operation for memory store updates

* Fix UTF-8 BOM encoding for FoundryMemory csproj files

* Update copilot instructions for UTF-8 BOM and fix sample API rename

* Fix UTF-8 BOM encoding for TestableAIProjectClient.cs

* Add missing response headers for TS

* Changing default embedding

* Using the SDK Models

* Program update

* Remove debugging code from sample

* Adapt FoundryMemoryProvider to new AIContextProvider API and add UTF-8 BOM instruction

- Override ProvideAIContextAsync/StoreAIContextAsync instead of removed virtual InvokingAsync/InvokedAsync
- Use ProviderSessionState<State> for session-scoped state management (matching Mem0Provider pattern)
- Replace constructor-based scope with stateInitializer delegate
- Remove Serialize method (no longer on base class)
- Add SearchInputMessageFilter, StorageInputMessageFilter, StateKey to options
- Update sample to use AIContextProviders list instead of AIContextProviderFactory
- Update unit and integration tests for new API
- Add UTF-8 BOM encoding and --tl:off instructions to dotnet/AGENTS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use DefaultAzureCredential in Foundry Memory sample

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR review comments for FoundryMemoryProvider

- Move memoryStoreName from options to required constructor parameter
- Make FoundryMemoryProviderScope require non-null/whitespace scope in constructor
- Make Scope property read-only (getter only)
- Replace ConcurrentQueue with single last update ID to fix memory leak
- Only clear pending update ID after successful completion
- Add delete success logging
- Mark FoundryMemoryProvider with [Experimental] attribute
- Update unit tests for new API signatures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use Throw.IfNullOrWhitespace for scope and memoryStoreName validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Roger Barreto
2026-02-20 11:25:06 +00:00
committed by GitHub
Unverified
parent 0086d38f58
commit 0e2fcb1c7f
18 changed files with 1331 additions and 0 deletions
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.FoundryMemory\Microsoft.Agents.AI.FoundryMemory.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to use the FoundryMemoryProvider to persist and recall memories for an agent.
// The sample stores conversation messages in an Azure AI Foundry memory store and retrieves relevant
// memories for subsequent invocations, even across new sessions.
//
// Note: Memory extraction in Azure AI Foundry is asynchronous and takes time. This sample demonstrates
// a simple polling approach to wait for memory updates to complete before querying.
using System.Text.Json;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.FoundryMemory;
string foundryEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
string memoryStoreName = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_MEMORY_STORE_NAME") ?? "memory-store-sample";
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_MODEL") ?? "gpt-4.1-mini";
string embeddingModelName = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_EMBEDDING_MODEL") ?? "text-embedding-ada-002";
// Create an AIProjectClient for Foundry with Azure Identity authentication.
DefaultAzureCredential credential = new();
AIProjectClient projectClient = new(new Uri(foundryEndpoint), credential);
// Get the ChatClient from the AIProjectClient's OpenAI property using the deployment name.
// The stateInitializer can be used to customize the Foundry Memory scope per session and it will be called each time a session
// is encountered by the FoundryMemoryProvider that does not already have state stored on the session.
// If each session should have its own scope, you can create a new id per session via the stateInitializer, e.g.:
// new FoundryMemoryProvider(projectClient, memoryStoreName, stateInitializer: _ => new(new FoundryMemoryProviderScope(Guid.NewGuid().ToString())), ...)
// In our case we are storing memories scoped by user so that memories are retained across sessions.
FoundryMemoryProvider memoryProvider = new(
projectClient,
memoryStoreName,
stateInitializer: _ => new(new FoundryMemoryProviderScope("sample-user-123")));
AIAgent agent = await projectClient.CreateAIAgentAsync(deploymentName,
options: new ChatClientAgentOptions()
{
Name = "TravelAssistantWithFoundryMemory",
ChatOptions = new() { Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
AIContextProviders = [memoryProvider]
});
AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine("\n>> Setting up Foundry Memory Store\n");
// Ensure the memory store exists (creates it with the specified models if needed).
await memoryProvider.EnsureMemoryStoreCreatedAsync(deploymentName, embeddingModelName, "Sample memory store for travel assistant");
// Clear any existing memories for this scope to demonstrate fresh behavior.
await memoryProvider.EnsureStoredMemoriesDeletedAsync(session);
Console.WriteLine(await agent.RunAsync("Hi there! My name is Taylor and I'm planning a hiking trip to Patagonia in November.", session));
Console.WriteLine(await agent.RunAsync("I'm travelling with my sister and we love finding scenic viewpoints.", session));
// Memory extraction in Azure AI Foundry is asynchronous and takes time to process.
// WhenUpdatesCompletedAsync polls all pending updates and waits for them to complete.
Console.WriteLine("\nWaiting for Foundry Memory to process updates...");
await memoryProvider.WhenUpdatesCompletedAsync();
Console.WriteLine("Updates completed.\n");
Console.WriteLine(await agent.RunAsync("What do you already know about my upcoming trip?", session));
Console.WriteLine("\n>> Serialize and deserialize the session to demonstrate persisted state\n");
JsonElement serializedSession = await agent.SerializeSessionAsync(session);
AgentSession restoredSession = await agent.DeserializeSessionAsync(serializedSession);
Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredSession));
Console.WriteLine("\n>> Start a new session that shares the same Foundry Memory scope\n");
Console.WriteLine("\nWaiting for Foundry Memory to process updates...");
await memoryProvider.WhenUpdatesCompletedAsync();
AgentSession newSession = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Summarize what you already know about me.", newSession));
@@ -0,0 +1,57 @@
# Agent with Memory Using Azure AI Foundry
This sample demonstrates how to create and run an agent that uses Azure AI Foundry's managed memory service to extract and retrieve individual memories across sessions.
## Features Demonstrated
- Creating a `FoundryMemoryProvider` with Azure Identity authentication
- Automatic memory store creation if it doesn't exist
- Multi-turn conversations with automatic memory extraction
- Memory retrieval to inform agent responses
- Session serialization and deserialization
- Memory persistence across completely new sessions
## Prerequisites
1. Azure subscription with Azure AI Foundry project
2. Azure OpenAI resource with a chat model deployment (e.g., gpt-4o-mini) and an embedding model deployment (e.g., text-embedding-ada-002)
3. .NET 10.0 SDK
4. Azure CLI logged in (`az login`)
## Environment Variables
```bash
# Azure AI Foundry project endpoint and memory store name
export FOUNDRY_PROJECT_ENDPOINT="https://your-account.services.ai.azure.com/api/projects/your-project"
export FOUNDRY_PROJECT_MEMORY_STORE_NAME="my_memory_store"
# Model deployment names (models deployed in your Foundry project)
export FOUNDRY_PROJECT_MODEL="gpt-4o-mini"
export FOUNDRY_PROJECT_EMBEDDING_MODEL="text-embedding-ada-002"
```
## Run the Sample
```bash
dotnet run
```
## Expected Output
The agent will:
1. Create the memory store if it doesn't exist (using the specified chat and embedding models)
2. Learn your name (Taylor), travel destination (Patagonia), timing (November), companions (sister), and interests (scenic viewpoints)
3. Wait for Foundry Memory to index the memories
4. Recall those details when asked about the trip
5. Demonstrate memory persistence across session serialization/deserialization
6. Show that a brand new session can still access the same memories
## Key Differences from Mem0
| Aspect | Mem0 | Azure AI Foundry Memory |
|--------|------|------------------------|
| Authentication | API Key | Azure Identity (DefaultAzureCredential) |
| Scope | ApplicationId, UserId, AgentId, ThreadId | Single `Scope` string |
| Memory Types | Single memory store | User Profile + Chat Summary |
| Hosting | Mem0 cloud or self-hosted | Azure AI Foundry managed service |
| Store Creation | N/A (automatic) | Explicit via `EnsureMemoryStoreCreatedAsync` |
@@ -7,3 +7,4 @@ These samples show how to create an agent with the Agent Framework that uses Mem
|[Chat History memory](./AgentWithMemory_Step01_ChatHistoryMemory/)|This sample demonstrates how to enable an agent to remember messages from previous conversations.|
|[Memory with MemoryStore](./AgentWithMemory_Step02_MemoryUsingMem0/)|This sample demonstrates how to create and run an agent that uses the Mem0 service to extract and retrieve individual memories.|
|[Custom Memory Implementation](./AgentWithMemory_Step03_CustomMemory/)|This sample demonstrates how to create a custom memory component and attach it to an agent.|
|[Memory with Azure AI Foundry](./AgentWithMemory_Step04_MemoryUsingFoundry/)|This sample demonstrates how to create and run an agent that uses Azure AI Foundry's managed memory service to extract and retrieve individual memories.|