From 6f8c73c5cdbea7a8261ffeffd5cf27cc9f89bb55 Mon Sep 17 00:00:00 2001 From: nos-redacted Date: Thu, 6 Aug 2026 17:06:39 -0700 Subject: [PATCH 1/2] .NET: Add Cosmos NoSQL vector memory sample --- dotnet/Directory.Packages.props | 3 +- dotnet/agent-framework-dotnet.slnx | 1 + dotnet/eng/verify-samples/AgentsSamples.cs | 20 +++++ ...emory_Step08_MemoryUsingCosmosNoSql.csproj | 22 +++++ .../Program.cs | 84 +++++++++++++++++++ .../README.md | 40 +++++++++ .../02-agents/AgentWithMemory/README.md | 1 + 7 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj create mode 100644 dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/Program.cs create mode 100644 dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/README.md diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 3ba3f3b13b..ac7eb21424 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -38,7 +38,7 @@ - + @@ -104,6 +104,7 @@ + diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 9442f98cb1..40c791159b 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -187,6 +187,7 @@ + diff --git a/dotnet/eng/verify-samples/AgentsSamples.cs b/dotnet/eng/verify-samples/AgentsSamples.cs index b19217230a..f629bdf600 100644 --- a/dotnet/eng/verify-samples/AgentsSamples.cs +++ b/dotnet/eng/verify-samples/AgentsSamples.cs @@ -532,6 +532,26 @@ internal static class AgentsSamples ], }, + new SampleDefinition + { + Name = "AgentWithMemory_Step08_MemoryUsingCosmosNoSql", + ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql", + RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT", "COSMOS_ENDPOINT"], + OptionalEnvironmentVariables = ["FOUNDRY_MODEL", "FOUNDRY_EMBEDDING_MODEL", "COSMOS_DATABASE_NAME"], + MustContain = + [ + "First session:", + "Second session (recalling prior chat history from Cosmos DB):", + ], + ExpectedOutputDescription = + [ + "The output should contain two joke responses.", + "The first joke should be about a pirate (as explicitly requested).", + "The second joke should also be pirate-themed or similar to what the user likes, since chat history from the first session should be recalled from Cosmos DB.", + "The output should not contain error messages or stack traces.", + ], + }, + // ── AgentWithRAG ──────────────────────────────────────────────────── new SampleDefinition diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj new file mode 100644 index 0000000000..d8950c8205 --- /dev/null +++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj @@ -0,0 +1,22 @@ + + + + Exe + net10.0 + + enable + enable + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/Program.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/Program.cs new file mode 100644 index 0000000000..d52dbe0436 --- /dev/null +++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/Program.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows how to persist chat history in Azure Cosmos DB for NoSQL using the ChatHistoryMemoryProvider. +// The agent can then use chat history from prior conversations to inform responses in new conversations. + +using System.Text.Json; +using Azure.AI.Projects; +using Azure.Identity; +using CommunityToolkit.VectorData.CosmosNoSql; +using Microsoft.Agents.AI; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.VectorData; + +var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini"; +var embeddingDeploymentName = Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_MODEL") ?? "text-embedding-3-large"; +var cosmosEndpoint = Environment.GetEnvironmentVariable("COSMOS_ENDPOINT") ?? throw new InvalidOperationException("COSMOS_ENDPOINT is not set."); +var cosmosDatabaseName = Environment.GetEnvironmentVariable("COSMOS_DATABASE_NAME") ?? "agent-memory"; + +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +DefaultAzureCredential credential = new(); +AIProjectClient aiProjectClient = new(new Uri(endpoint), credential); + +using CosmosClient cosmosClient = new( + cosmosEndpoint, + credential, + new CosmosClientOptions + { + UseSystemTextJsonSerializerWithOptions = JsonSerializerOptions.Default, + }); + +DatabaseResponse databaseResponse = await cosmosClient.CreateDatabaseIfNotExistsAsync(cosmosDatabaseName); + +VectorStore vectorStore = new CosmosNoSqlVectorStore( + databaseResponse.Database, + new CosmosNoSqlVectorStoreOptions + { + JsonSerializerOptions = JsonSerializerOptions.Default, + EmbeddingGenerator = aiProjectClient + .GetProjectOpenAIClient() + .GetEmbeddingClient(embeddingDeploymentName) + .AsIEmbeddingGenerator(), + }); + +// Create the agent and add the ChatHistoryMemoryProvider to store chat messages in Cosmos DB. +AIAgent agent = aiProjectClient + .AsAIAgent(new ChatClientAgentOptions + { + ChatOptions = new() { ModelId = deploymentName, Instructions = "You are good at telling jokes." }, + Name = "Joker", + AIContextProviders = [new ChatHistoryMemoryProvider( + vectorStore, + collectionName: "chathistory", + vectorDimensions: 3072, + // Callback to configure the initial state of the ChatHistoryMemoryProvider. + // The ChatHistoryMemoryProvider stores its state in the AgentSession and this callback + // will be called whenever the ChatHistoryMemoryProvider cannot find existing state in the session, + // typically the first time it is used with a new session. + session => new ChatHistoryMemoryProvider.State( + // Configure the scope values under which chat messages will be stored. + // In this case, we are using a fixed user ID and a unique session ID for each new session. + storageScope: new() { UserId = "UID1", SessionId = Guid.NewGuid().ToString() }, + // Configure the scope which would be used to search for relevant prior messages. + // In this case, we are searching for any messages for the user across all sessions. + searchScope: new() { UserId = "UID1" }))] + }); + +// Start a new session for the agent conversation. +AgentSession session = await agent.CreateSessionAsync(); + +// Run the agent with the session that stores conversation history in Cosmos DB. +Console.WriteLine("First session:"); +Console.WriteLine(await agent.RunAsync("I like jokes about Pirates. Tell me a joke about a pirate.", session)); + +// Start a second session. Since we configured the search scope to be across all sessions for the user, +// the agent should remember that the user likes pirate jokes. +AgentSession session2 = await agent.CreateSessionAsync(); + +// Run the agent with the second session. +Console.WriteLine("Second session (recalling prior chat history from Cosmos DB):"); +Console.WriteLine(await agent.RunAsync("Tell me a joke that I might like.", session2)); diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/README.md b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/README.md new file mode 100644 index 0000000000..af0f615145 --- /dev/null +++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/README.md @@ -0,0 +1,40 @@ +# Agent with Memory Using Azure Cosmos DB for NoSQL + +This sample uses `ChatHistoryMemoryProvider` with `CosmosNoSqlVectorStore` to persist chat history in Azure Cosmos DB for NoSQL and recall relevant messages in a new agent session. + +## Features Demonstrated + +- Authenticating to Microsoft Foundry and Azure Cosmos DB with `DefaultAzureCredential` +- Storing chat messages in an Azure Cosmos DB vector store +- Creating the configured database and chat-history container when they do not exist +- Recalling relevant chat history across agent sessions + +## Prerequisites + +1. [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) +2. A Microsoft Foundry project with: + - A chat model deployment (the default is `gpt-5.4-mini`) + - A `text-embedding-3-large` deployment with 3,072 dimensions +3. An Azure Cosmos DB for NoSQL account with [vector search enabled](https://learn.microsoft.com/azure/cosmos-db/nosql/vector-search) +4. An Azure identity that can create the configured database and container and read and write items +5. Azure CLI authentication (`az login`) + +## Configuration + +Set the following environment variables: + +| Variable | Description | Default | +|---|---|---| +| `FOUNDRY_PROJECT_ENDPOINT` | Microsoft Foundry project endpoint | *(required)* | +| `COSMOS_ENDPOINT` | Azure Cosmos DB account endpoint | *(required)* | +| `FOUNDRY_MODEL` | Chat model deployment name | `gpt-5.4-mini` | +| `FOUNDRY_EMBEDDING_MODEL` | Embedding model deployment name | `text-embedding-3-large` | +| `COSMOS_DATABASE_NAME` | Database used to store agent memory | `agent-memory` | + +## Run the Sample + +```bash +dotnet run +``` + +The first session stores the user's preference for pirate jokes. The second session uses a different `AgentSession` but the same user search scope, allowing the agent to retrieve that preference from Azure Cosmos DB. \ No newline at end of file diff --git a/dotnet/samples/02-agents/AgentWithMemory/README.md b/dotnet/samples/02-agents/AgentWithMemory/README.md index f1dc4064a5..16096f44fa 100644 --- a/dotnet/samples/02-agents/AgentWithMemory/README.md +++ b/dotnet/samples/02-agents/AgentWithMemory/README.md @@ -11,6 +11,7 @@ These samples show how to create an agent with the Agent Framework that uses Mem |[Bounded Chat History with Overflow](./AgentWithMemory_Step05_BoundedChatHistory/)|This sample demonstrates how to create a bounded chat history provider that overflows older messages to a vector store and recalls them as memories.| |[Memory Using AgentMemory](./AgentWithMemory_Step06_MemoryUsingAgentMemory/)|This sample demonstrates a retail shopping assistant built with [`AgentMemory`](https://www.nuget.org/packages/AgentMemory), an unofficial .NET port of the Neo4j Labs graph-memory provider, to learn customer preferences and recommend products via graph traversal.| |[File Based Memory](./AgentWithMemory_Step07_FileMemoryProvider/)|This sample demonstrates how to use the `FileMemoryProvider` to give an agent tools for storing and recalling memories as files, and how to configure the folder that those memory files are written to.| +|[Memory with Azure Cosmos DB for NoSQL](./AgentWithMemory_Step08_MemoryUsingCosmosNoSql/)|This sample demonstrates how to persist and retrieve chat history across sessions with Azure Cosmos DB for NoSQL.| > **See also**: [Memory Search with Foundry Agents](../AgentProviders/foundry/Agent_Step22_MemorySearch/) - demonstrates using the built-in Memory Search tool with Microsoft Foundry agents. From 862b9074b04b608784a8e36d0d106050e89c37df Mon Sep 17 00:00:00 2001 From: nos-redacted Date: Thu, 6 Aug 2026 18:13:58 -0700 Subject: [PATCH 2/2] Address Cosmos memory sample review feedback --- ...Memory_Step08_MemoryUsingCosmosNoSql.csproj | 1 - .../Program.cs | 18 +++++++++++++----- .../README.md | 3 ++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj index d8950c8205..6aacaa5662 100644 --- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj +++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/AgentWithMemory_Step08_MemoryUsingCosmosNoSql.csproj @@ -12,7 +12,6 @@ - diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/Program.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/Program.cs index d52dbe0436..4552d04653 100644 --- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/Program.cs +++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/Program.cs @@ -15,6 +15,12 @@ var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini"; var embeddingDeploymentName = Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_MODEL") ?? "text-embedding-3-large"; +var embeddingDimensions = 3072; +if (Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_DIMENSIONS") is string embeddingDimensionsValue && + (!int.TryParse(embeddingDimensionsValue, out embeddingDimensions) || embeddingDimensions <= 0)) +{ + throw new InvalidOperationException("FOUNDRY_EMBEDDING_DIMENSIONS must be a positive integer."); +} var cosmosEndpoint = Environment.GetEnvironmentVariable("COSMOS_ENDPOINT") ?? throw new InvalidOperationException("COSMOS_ENDPOINT is not set."); var cosmosDatabaseName = Environment.GetEnvironmentVariable("COSMOS_DATABASE_NAME") ?? "agent-memory"; @@ -45,6 +51,8 @@ .AsIEmbeddingGenerator(), }); +var userId = $"sample-{Guid.NewGuid():N}"; + // Create the agent and add the ChatHistoryMemoryProvider to store chat messages in Cosmos DB. AIAgent agent = aiProjectClient .AsAIAgent(new ChatClientAgentOptions @@ -54,18 +62,18 @@ AIContextProviders = [new ChatHistoryMemoryProvider( vectorStore, collectionName: "chathistory", - vectorDimensions: 3072, + vectorDimensions: embeddingDimensions, // Callback to configure the initial state of the ChatHistoryMemoryProvider. // The ChatHistoryMemoryProvider stores its state in the AgentSession and this callback // will be called whenever the ChatHistoryMemoryProvider cannot find existing state in the session, // typically the first time it is used with a new session. - session => new ChatHistoryMemoryProvider.State( + _ => new ChatHistoryMemoryProvider.State( // Configure the scope values under which chat messages will be stored. - // In this case, we are using a fixed user ID and a unique session ID for each new session. - storageScope: new() { UserId = "UID1", SessionId = Guid.NewGuid().ToString() }, + // In this case, we are using a per-run user ID and a unique session ID for each new session. + storageScope: new() { UserId = userId, SessionId = Guid.NewGuid().ToString("N") }, // Configure the scope which would be used to search for relevant prior messages. // In this case, we are searching for any messages for the user across all sessions. - searchScope: new() { UserId = "UID1" }))] + searchScope: new() { UserId = userId }))] }); // Start a new session for the agent conversation. diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/README.md b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/README.md index af0f615145..16775abcd8 100644 --- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/README.md +++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step08_MemoryUsingCosmosNoSql/README.md @@ -29,6 +29,7 @@ Set the following environment variables: | `COSMOS_ENDPOINT` | Azure Cosmos DB account endpoint | *(required)* | | `FOUNDRY_MODEL` | Chat model deployment name | `gpt-5.4-mini` | | `FOUNDRY_EMBEDDING_MODEL` | Embedding model deployment name | `text-embedding-3-large` | +| `FOUNDRY_EMBEDDING_DIMENSIONS` | Number of dimensions produced by the embedding deployment | `3072` | | `COSMOS_DATABASE_NAME` | Database used to store agent memory | `agent-memory` | ## Run the Sample @@ -37,4 +38,4 @@ Set the following environment variables: dotnet run ``` -The first session stores the user's preference for pirate jokes. The second session uses a different `AgentSession` but the same user search scope, allowing the agent to retrieve that preference from Azure Cosmos DB. \ No newline at end of file +The first session stores the user's preference for pirate jokes. The second session uses a different `AgentSession` but the same per-run user search scope, allowing the agent to retrieve that preference from Azure Cosmos DB without recalling data from earlier sample runs. \ No newline at end of file