Connect Your Agent to External Tools

Equip your agent with custom .NET functions to query live system health data and provide grounded responses.

Foundational modules skipped

You've skipped some modules in this path. For a better understanding of the concepts used here, we recommend following the sequence.

Go to first skipped module

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.

🎭
Persona

Jordan’s on-call identity.

🧠
Brain

Reasoning about alerts.

Building
🛠️
Tools

External capabilities.

Upcoming
💾
Memory

State and history.

Upcoming
☁️
Hosting

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 restore
Package Anatomy
🔌Microsoft.Agents.AI.OpenAI

The core bridge that connects the Agent Framework to OpenAI-compatible models.

🤖OpenAI

The official SDK for standard OpenAI REST API interactions and streaming.

🛠️Microsoft.Extensions.AI

Provides 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 restore
Package Anatomy
🔌Microsoft.Agents.AI.OpenAI

The core bridge that connects the Agent Framework to OpenAI-compatible models.

☁️Azure.AI.OpenAI

The official SDK for accessing GPT-4o and text embeddings within Azure.

🛠️Microsoft.Extensions.AI

Provides 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.