Connect Your Agent to External Tools Done
Equip your agent with custom .NET functions to query live system health data and provide grounded responses.
Overview
In the previous tutorial, Jordan Miller built a triage assistant that could summarize an incident alert. However, it was limited to the information provided in the prompt. In a real-world scenario, Jordan needs an agent that can act—one that can look up the actual status of a service or query a database to see if a deployment recently failed.
This guide walks you through giving the agent Tools. You’ll expose a C# method as an agent capability, allowing Jordan’s assistant to fetch real-time health data before providing a response. This process is called Grounding, as it ensures the agent’s answers are based on facts rather than just model predictions.
Agent Anatomy
An AI Agent uses Tools to interact with the world. While the Brain (LLM) decides what to do, the Tools are the hands that execute the work.
Jordan’s on-call identity.
Reasoning about alerts.
External capabilities.
State and history.
Exposing as a service.
Grounding vs. Hallucination
LLMs are probabilistic, meaning they might “hallucinate” or guess facts if they aren’t sure. By giving Jordan’s agent Tools, you are providing it with a “source of truth.” This ensures the agent’s responses are grounded in actual system data rather than just likely-sounding predictions.
Setup your environment
If you are continuing from the previous tutorial, you can use your existing project. Otherwise, follow the steps below to initialize a new one.
📋 Pre-flight Checklist
- 🛠️ .NET 10.0 SDK (or later) installed.
- 🤖 AI Provider: Access to Azure OpenAI or a local service (Ollama/LM Studio).
- 📦 Microsoft.Extensions.AI: We will use this for standard AI function metadata.
1 Install required packages
If you are continuing from the previous tutorial, you’ve already installed the core Agent Framework and provider SDKs. In this module, we focus on the new package needed for describing our tools to the LLM.
dotnet add package Microsoft.Agents.AI.OpenAI
dotnet add package OpenAI
dotnet add package Microsoft.Extensions.AI
dotnet restoreMicrosoft.Agents.AI.OpenAIThe core bridge that connects the Agent Framework to OpenAI-compatible models.
OpenAIThe official SDK for standard OpenAI REST API interactions and streaming.
Microsoft.Extensions.AIProvides the unified .NET abstractions for AI. We use its attributes to describe our tools so the LLM can understand them.
dotnet add package Microsoft.Agents.AI.OpenAI
dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.Extensions.AI
dotnet restoreMicrosoft.Agents.AI.OpenAIThe core bridge that connects the Agent Framework to OpenAI-compatible models.
Azure.AI.OpenAIThe official SDK for accessing GPT-4o and text embeddings within Azure.
Microsoft.Extensions.AIProvides the unified .NET abstractions for AI. We use its attributes to describe our tools so the LLM can understand them.
Build the agent
We will now define a C# method that simulates a service health check and register it as a tool for our agent.
sequenceDiagram
participant App as Console App
participant Agent as 🎭 Persona
participant Tool as 🛠️ Tool
participant Brain as 🧠 Brain
App->>Agent: RunAsync("Check checkout...")
Agent->>Brain: Persona + Tool Definitions + Input
Note right of Brain: Brain decides a tool is needed
Brain-->>Agent: Call: GetServiceStatus("checkout")
Agent->>Tool: Execute Tool Logic
Tool-->>Agent: Result: "Degraded..."
Agent->>Brain: Tool Result
Brain-->>Agent: Final Summary
Agent-->>App: Triage Response
1 Implement the Agent and Tools 🎭 Persona 🛠️ Tools
We define a standard C# method and use [Description] (from System.ComponentModel) to provide the “semantic map” the LLM needs to understand what our tool does.
We will now combine everything into a complete program. This includes the using statements, the tool definition with metadata, the agent initialization, and the execution loop.
Replace the contents of Program.cs with the code for your provider:
using System.ComponentModel;
using OpenAI;
using System.ClientModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
// 1. Configure the Provider
var endpoint = Environment.GetEnvironmentVariable("OPENAI_ENDPOINT") ?? "http://localhost:1234/v1";
var modelName = Environment.GetEnvironmentVariable("OPENAI_MODEL_NAME") ?? "google/gemma-4-e4b";
var chatClient = new OpenAIClient(new ApiKeyCredential("dummy"), new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
.GetChatClient(modelName);
// 2. Define the Tool
[Description("Gets the current health status for an enterprise service.")]
static string GetServiceStatus(
[Description("The service name to check, such as checkout, payments, or inventory.")] string serviceName)
{
return serviceName.ToLowerInvariant() switch
{
"checkout" => "Checkout is DEGRADED in West Europe. P P95 latency is 4.8s. Payment retries are elevated.",
"payments" => "Payments is HEALTHY. No active regional alerts.",
"inventory" => "Inventory is HEALTHY. Last sync 2 minutes ago.",
_ => $"{serviceName} has no active status record in the demo store."
};
}
// 3. Initialize the Agent with the Tool
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
Name = "TriageAgent",
ChatOptions = new()
{
Instructions = """
You are an enterprise incident triage assistant.
Summarize the incident, identify likely severity,
and suggest the next investigation step.
IMPORTANT:
- You MUST use the provided service status tools to verify the health of any service mentioned in the incident.
- Do NOT guess the status of a service.
- Always base your triage on the grounded data returned by your tools.
Keep answers concise and operational.
""",
Tools = [AIFunctionFactory.Create(GetServiceStatus, "GetServiceStatus")]
}
});
// 4. Define the Task
var incident = """
We have reports of checkout failures.
Can you check the status of the checkout service and summarize the impact?
""";
// 5. Run and Stream
Console.WriteLine("Agent reasoning and responding...");
await foreach (var update in agent.RunStreamingAsync(incident))
{
Console.Write(update);
}
Console.WriteLine(); using System.ComponentModel;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
// 1. Configure the Provider
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")!;
// 2. Define the Tool
[Description("Gets the current health status for an enterprise service.")]
static string GetServiceStatus(
[Description("The service name to check, such as checkout, payments, or inventory.")] string serviceName)
{
return serviceName.ToLowerInvariant() switch
{
"checkout" => "Checkout is DEGRADED in West Europe. P95 latency is 4.8s. Payment retries are elevated.",
"payments" => "Payments is HEALTHY. No active regional alerts.",
"inventory" => "Inventory is HEALTHY. Last sync 2 minutes ago.",
_ => $"{serviceName} has no active status record in the demo store."
};
}
// 3. Initialize the Agent with the Tool
var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetChatClient(deploymentName);
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
Name = "TriageAgent",
ChatOptions = new()
{
Instructions = """
You are an enterprise incident triage assistant.
Summarize the incident, identify likely severity,
and suggest the next investigation step.
IMPORTANT:
- You MUST use the provided service status tools to verify the health of any service mentioned in the incident.
- Do NOT guess the status of a service.
- Always base your triage on the grounded data returned by your tools.
Keep answers concise and operational.
""",
Tools = [AIFunctionFactory.Create(GetServiceStatus, "GetServiceStatus")]
}
});
// 4. Define the Task
var incident = """
We have reports of checkout failures.
Can you check the status of the checkout service and summarize the impact?
""";
// 5. Run and Stream
Console.WriteLine("Agent reasoning and responding...");
await foreach (var update in agent.RunStreamingAsync(incident))
{
Console.Write(update);
}
Console.WriteLine(); With everything in place, execute the application from your terminal:
dotnet run
Try it
Observe how the agent’s behavior changes when you modify the “facts” available to it.
Add a New Tool
Try adding a second tool to check the On-Call Engineer for a service.
[Description("Gets the name of the engineer currently on-call.")]
static string GetOnCallEngineer(
[Description("The service name to check.")] string serviceName) => "Taylor Vance (@tvance)";Update your AsAIAgent call to include it:
tools: [
AIFunctionFactory.Create(GetServiceStatus,"GetServiceStatus"),
AIFunctionFactory.Create(GetOnCallEngineer,"GetOnCallEngineer")
]Update your incident string to ask for the on-call contact:
var incident = """
Check checkout health and tell me who is on call.
""";Result: The agent will now invoke both tools sequentially and mention Taylor Vance in its response.
Change the Status
Update the logic in your GetServiceStatus method to return a healthy result for the checkout service:
"checkout" => "Checkout is HEALTHY. No active regional alerts.",Run the app again with the original incident query.
Result: Instead of escalating, the agent will report that the internal status is healthy and suggest investigating client-side issues or load balancer configurations.
Automate Tool Usage
You can force the agent to be proactive by updating the Persona. This ensures it always performs specific checks without being asked in every prompt.
Update the instructions to mandate the contact check:
instructions: """
You are a proactive SRE.
For every service mentioned, you MUST check both its health AND the currently on-call engineer.
Always include the contact person in your summary.
"""Revert your incident string to the simple version:
var incident = "Check checkout status.";Result: Even without asking for the contact, the agent will automatically invoke both tools and provide the engineer’s name because the Persona mandates it.
Summary and Next Steps
You’ve successfully bridged the gap between an LLM and your own C# logic! By using Tools, Jordan’s assistant can now provide grounded, accurate responses based on real-time data from your environment.
However, our agent still has a short memory. Because we haven’t implemented state management yet, the agent forgets everything the moment a turn ends. If you were to add a follow-up turn to your Program.cs, the agent would lose the context:
// Turn 1: (Already implemented above)
// await foreach (var update in agent.RunStreamingAsync(incident)) { ... }
// Turn 2: (Add this follow-up)
Console.WriteLine(await agent.RunAsync("Who is on call for that?"));
// Result: "I need to know which service you are referring to in order to tell you who the on-call engineer is."
In the next tutorial, we will solve this by implementing State Management, allowing our agent to maintain a multi-turn conversation.