Lab BMA3 - Connect your Foundry agent with Microsoft Agent Framework
This is where the two halves come together. Your Foundry agent has the persona and the knowledge; your Agents SDK project has the host and the channel. Microsoft Agent Framework is the layer that connects them.
Note
If you want to start directly from this lab without completing the previous ones, you can download the agent's complete source code (as it is at the end of the previous lab) from here. You still need the Microsoft Foundry agent you created in "Lab BMA1", and the prerequisites from "Lab BMA0".
Lab objectives
By the end of this lab you will be able to:
- Add Microsoft Agent Framework to a Microsoft 365 Agents SDK project
- Resolve a published Foundry agent as an
AIAgentfrom .NET - Stream responses and citations back to the user in real time
- Persist an
AgentSessionso the agent remembers the conversation - Run the agent in Microsoft Teams
Agent Framework replaces Semantic Kernel here
Microsoft Agent Framework is the successor to Semantic Kernel for agent development. If you followed an earlier version of this lab, the mapping is:
| Semantic Kernel | Agent Framework |
|---|---|
AzureAIAgent |
AIAgent / FoundryAgent |
InvokeStreamingAsync |
RunStreamingAsync |
AgentResponseItem<StreamingChatMessageContent> |
AgentResponseUpdate |
AzureAIAgentThread |
AgentSession |
builder.Services.AddKernel() |
not required |
There is no Kernel to configure, and the preview SKEXP0110 warning no longer applies.
Exercise 1: Add Agent Framework to the project
Step 1: Install the NuGet packages
Right-click the ContosoHRAgent project and select Manage NuGet Packages..., then open the Browse tab and check Include prerelease.
Install these two packages:
| Package | Purpose |
|---|---|
Microsoft.Agents.AI.Foundry |
Agent Framework integration for Microsoft Foundry agents |
Azure.Identity |
Credentials used to authenticate to your Foundry project |
Your ContosoHRAgent.csproj should now contain:
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.21.0" />
<PackageReference Include="Microsoft.Agents.AI.Foundry" Version="1.16.0-preview.260730.1" />
<PackageReference Include="Microsoft.Agents.Authentication.Msal" Version="1.*" />
<PackageReference Include="Microsoft.Agents.Hosting.AspNetCore" Version="1.*" />
</ItemGroup>
Microsoft.Agents.AI.Foundryis published as a prerelease package because it depends on preview Foundry project APIs. Leave Include prerelease checked or the package won't appear.
Step 2: Clean up Program.cs
Agent Framework doesn't need a Kernel. Open Program.cs and make sure the agent registration looks like this:
// Add the bot (which is transient)
builder.AddAgent<EchoBot>();
var app = builder.Build();
If you're upgrading an existing project, delete these lines โ they are Semantic Kernel leftovers and a duplicate storage registration:
builder.Services.AddSingleton<IStorage, MemoryStorage>();
// Add the Semantic Kernel services
builder.Services.AddKernel();
Step 3: Track the conversation in state
Each Teams conversation maps to one Foundry conversation. Store its id in conversation state so the agent keeps its memory across turns.
Right-click the ContosoHRAgent project, select Add > Class, name it ConversationStateExtensions.cs, and replace the contents with:
using Microsoft.Agents.Builder.State;
namespace ContosoHRAgent
{
public static class ConversationStateExtensions
{
public static int MessageCount(this ConversationState state) => state.GetValue<int>("countKey");
public static void MessageCount(this ConversationState state, int value) => state.SetValue("countKey", value);
public static int IncrementMessageCount(this ConversationState state)
{
int count = state.GetValue<int>("countKey");
state.SetValue("countKey", ++count);
return count;
}
public static string ConversationId(this ConversationState state) => state.GetValue<string>("conversationIdKey");
public static void ConversationId(this ConversationState state, string value) => state.SetValue("conversationIdKey", value);
}
}
Exercise 2: Connect to your Foundry agent
Step 1: Create the project client
Open Bot/EchoBot.cs and replace the using block at the top of the file with:
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry;
using Microsoft.Agents.Builder;
using Microsoft.Agents.Builder.App;
using Microsoft.Agents.Builder.State;
using Microsoft.Agents.Core.Models;
using Microsoft.Extensions.AI;
Then replace the class fields and constructor with:
private readonly AIProjectClient _projectClient;
private readonly string _agentName;
public EchoBot(AgentApplicationOptions options, IConfiguration configuration) : base(options)
{
OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync);
// Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS
OnActivity(ActivityTypes.Message, OnMessageAsync);
// Microsoft Foundry project endpoint
string projectEndpoint = configuration["AIServices:ProjectEndpoint"];
if (string.IsNullOrEmpty(projectEndpoint))
{
throw new InvalidOperationException("ProjectEndpoint is not configured.");
}
_projectClient = new AIProjectClient(new Uri(projectEndpoint), new AzureCliCredential());
// Name of the agent you published in Microsoft Foundry
_agentName = configuration["AIServices:AgentName"];
if (string.IsNullOrEmpty(_agentName))
{
throw new InvalidOperationException("AgentName is not configured.");
}
}
AzureCliCredentialuses the identity from youraz loginsession โ ideal for local development. In production you'd switch to a managed identity.
Step 2: Run the agent and stream the response
Replace the entire OnMessageAsync method with:
protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
// send the initial message to the user
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Working on it...", cancellationToken);
// resolve the agent version published in Microsoft Foundry and wrap it as an AIAgent
var agentRecord = await _projectClient.AgentAdministrationClient.GetAgentAsync(_agentName, cancellationToken);
FoundryAgent agent = _projectClient.AsAIAgent(agentRecord);
// retrieve the conversation id from the conversation state
// this is set if the agent has been invoked before in the same conversation
var conversationId = turnState.Conversation.ConversationId();
// if there is no conversation id yet, start a new Foundry conversation
// otherwise resume the existing one so the agent keeps its memory
AgentSession session = string.IsNullOrEmpty(conversationId)
? await agent.CreateConversationSessionAsync(cancellationToken)
: await agent.CreateSessionAsync(conversationId, cancellationToken);
try
{
// increment the message count in state and queue the count to the user
int count = turnState.Conversation.IncrementMessageCount();
turnContext.StreamingResponse.QueueTextChunk($"({count}) ");
// run the agent and stream the responses to the user
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(
turnContext.Activity.Text, session, cancellationToken: cancellationToken))
{
if (!string.IsNullOrEmpty(update.Text))
{
turnContext.StreamingResponse.QueueTextChunk(update.Text);
}
// surface any document citations returned by the File Search tool
foreach (AIContent content in update.Contents)
{
var citations = content.Annotations?.OfType<CitationAnnotation>()
?? Enumerable.Empty<CitationAnnotation>();
foreach (CitationAnnotation citation in citations)
{
var label = citation.Title ?? citation.FileId;
if (!string.IsNullOrEmpty(label))
{
turnContext.StreamingResponse.QueueTextChunk($" [{label}]");
}
}
}
}
// persist the conversation id so the next turn resumes the same session
if (session is ChatClientAgentSession chatSession && !string.IsNullOrEmpty(chatSession.ConversationId))
{
turnState.Conversation.ConversationId(chatSession.ConversationId);
}
}
finally
{
// ensure we end the streaming response
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken);
}
}
What happens in OnMessageAsync?
GetAgentAsync(_agentName)looks up the agent version you published in Foundry โ the instructions and knowledge stay server-side.AsAIAgent(...)wraps that definition as anAIAgentyou can run from .NET.CreateConversationSessionAsync/CreateSessionAsyncstart or resume a Foundry conversation, which is what gives the agent memory across turns.RunStreamingAsyncyieldsAgentResponseUpdateobjects as the model produces them, so text appears progressively instead of all at once.CitationAnnotationexposes the documents the File Search tool used, so users can see where an answer came from.
Step 3: Configure your Foundry connection
Open appsettings.json and add an AIServices section. Add a comma after the closing brace of the last existing section, then append:
"AIServices": {
"AgentName": "<YourFoundryAgentName>",
"ProjectEndpoint": "<YourProjectEndpoint>"
}
So the end of the file looks like this:
"ConnectionsMap": [
{
"ServiceUrl": "*",
"Connection": "BotServiceConnection"
}
],
"AIServices": {
"AgentName": "<YourFoundryAgentName>",
"ProjectEndpoint": "<YourProjectEndpoint>"
}
}
Fill in the two values you recorded at the end of Lab BMA1:
| Setting | Value | Where to find it |
|---|---|---|
AgentName |
Contoso HR Agent |
The Name field in the agent's Setup panel in Foundry |
ProjectEndpoint |
https://<your-resource>.services.ai.azure.com/api/projects/<your-project> |
The project Overview page, under Endpoints and keys |
The agent name must match exactly
Agent Framework resolves the agent by name. A typo or a trailing space produces a "resource not found" error at runtime.
Exercise 3: Run your agent in Microsoft Teams
Step 1: Sign in to Azure
Open Tools > Command Line > Developer Command Prompt and run:
az login
Complete the sign-in in the browser window that opens. This is the identity AzureCliCredential will use to reach your Foundry project.
Your account needs at least the Foundry User role on the Foundry project to invoke the agent.
Step 2: Create a dev tunnel and select your account
Expand Start and select Dev Tunnels > Create a Tunnel:
- Select Sign in and Work or school account, using the same account as above.
- Name the tunnel
DevTunnel. - Keep the tunnel type Temporary.
- Set access to Public, then select Create.
Right-click the M365Agent project and select Microsoft 365 Agents Toolkit > Select Microsoft 365 Account, then choose the same account and select Continue.
Finally, change the startup item from <Multiple Startup Projects> to Microsoft Teams (browser).
Step 3: Test the grounded agent
Select Start or press F5. Microsoft Teams launches and your agent appears โ select Add, then Open.
Ask the same questions you tested in the Foundry playground:
- What's the difference between Northwind Standard and Northwind Health Plus for emergency and mental health coverage?
- Can I use PerksPlus to pay for both a rock climbing class and a virtual fitness program?
- What values guide behavior and decision-making at Contoso Electronics?
Expected result:
- Answers match what you saw in the Foundry playground, because the instructions and knowledge live in Foundry.
- Text streams in progressively rather than appearing all at once.
- Each response is prefixed with the running message count.
- Follow-up questions like "and what about dental?" work, proving the
AgentSessionis being resumed.
Troubleshooting
- "AgentName is not configured" โ the
AIServicessection is missing fromappsettings.json. - Resource not found โ the agent name doesn't match the name in Foundry exactly.
- Unauthorized โ re-run
az login, or check your role assignment on the Foundry project.
CONGRATULATIONS!
You have completed Lab BMA3 - Connect your Foundry agent with Microsoft Agent Framework!
Your custom engine agent now runs in Microsoft Teams. In the final lab, you'll bring it into Microsoft 365 Copilot.