Files
Ben Thomas c79f886dc3 .NET: Align Foundry sample environment variables and credentials. (#6422)
* dotnet: refresh Foundry sample guidance

Carry forward the still-relevant sample guidance and Foundry-specific documentation fixes from the old stacked sample migration work, adapted to the current repo layout and policy.

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

* dotnet: rename Foundry sample env vars

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

* dotnet: remove persistent provider sample

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

* dotnet: drop SAMPLE_GUIDELINES.md from this PR

Defer the guidelines doc and its cross-link to a follow-on PR to avoid broken-link failures in CI.

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

* dotnet: add DefaultAzureCredential warning to remaining samples

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

* dotnet: address PR review feedback

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-11 17:26:00 +00:00

91 lines
4.4 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
// Hosted-MemoryAgent
//
// Demonstrates how to host an agent that uses FoundryMemoryProvider so that user-private memories
// persist across requests and across sessions, scoped per user via the Foundry platform's
// isolation key headers.
//
// Memory scope flows from request -> hosting layer -> session -> provider:
// 1. Foundry sets x-agent-user-isolation-key on every inbound request.
// 2. AgentFrameworkResponseHandler reads context.Isolation.UserIsolationKey via the registered
// HostedSessionIsolationKeyProvider and stores it on the session as a HostedSessionContext.
// 3. FoundryMemoryProvider's stateInitializer reads HostedSessionContext.UserId and uses it as
// the FoundryMemoryProviderScope, partitioning memories per user.
using Azure.AI.Projects;
using Azure.Core;
using Azure.Identity;
using DotNetEnv;
using Hosted_Shared_Contributor_Setup;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry;
using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;
// Load .env file if present (for local development).
Env.TraversePath().Load();
var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."));
var agentName = Environment.GetEnvironmentVariable("AGENT_NAME")
?? throw new InvalidOperationException("AGENT_NAME is not set.");
var deployment = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
var embeddingDeployment = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002";
var memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? "hosted-memory-sample";
// 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.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in foundry).
TokenCredential credential = new ChainedTokenCredential(
new DevTemporaryTokenCredential(),
new DefaultAzureCredential());
AIProjectClient projectClient = new(projectEndpoint, credential);
// FoundryMemoryProvider partitions memories per end user via a built-in HostedFoundryMemoryProviderScopes
// helper that reads the platform-injected user isolation key from the HostedSessionContext that the
// hosting layer placed on the session.
FoundryMemoryProvider memoryProvider = new(
projectClient,
memoryStoreName,
stateInitializer: HostedFoundryMemoryProviderScopes.PerUser());
// Provision the memory store on startup if it does not already exist. EnsureMemoryStoreCreatedAsync
// is idempotent. Doing this once at start avoids per-request latency.
await memoryProvider.EnsureMemoryStoreCreatedAsync(deployment, embeddingDeployment, "Memory store for the hosted travel-assistant sample.");
const string AgentInstructions = """
You are a friendly travel assistant. When the user shares trip preferences, destinations,
travel companions, or constraints, remember them and use them in later turns. Use known
memories about the user when responding, and do not invent details.
""";
ChatClientAgent agent = projectClient.AsAIAgent(new ChatClientAgentOptions()
{
Name = agentName,
ChatOptions = new ChatOptions
{
ModelId = deployment,
Instructions = AgentInstructions
},
AIContextProviders = [memoryProvider]
});
// Host the agent as a Foundry Hosted Agent using the Responses API.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFoundryResponses(agent);
builder.Services.AddDevTemporaryLocalContributorSetup(); // Local Docker debugging only - must not be used in production.
var app = builder.Build();
app.MapFoundryResponses();
// Contributor-only: in Development, also map the per-agent OpenAI route shape that live Foundry uses
// so a local REPL client can target this server via AIProjectClient.AsAIAgent(Uri agentEndpoint).
// Do not use this in production. Hosted Foundry agents only support the agent-endpoint path.
app.MapDevTemporaryLocalAgentEndpoint();
app.Run();