Microsoft Agent Framework Microsoft.Extensions.AI Created: 28 Feb 2026 Updated: 28 Feb 2026

Function Calling with Microsoft.Extensions.AI

Function calling lets the AI model invoke real .NET methods during a conversation. Instead of making up an answer, the model recognises when a registered tool is needed, emits a tool call request, the framework executes your method and passes the result back, and the model uses that real data to compose its final reply. All of this happens transparently — your outer code just sends messages and receives answers.

Key Concepts

1. Enabling Automatic Function Invocation

Add .UseFunctionInvocation() to the ChatClientBuilder pipeline. This middleware intercepts tool-call responses from the model, executes the matching .NET function, appends the result to the conversation, and re-sends — all without any code in your outer loop:

IChatClient client = new ChatClientBuilder(
new OpenAIClient(apiKey).GetChatClient("gpt-4o-mini").AsIChatClient())
.UseFunctionInvocation()
.Build();

2. Registering Tools with AIFunctionFactory

Wrap any static or instance method as an AIFunction. Provide a name and description so the model knows when to call it:

var options = new ChatOptions
{
Tools =
[
AIFunctionFactory.Create(
GetLeaveBalance,
"GetLeaveBalance",
"Returns the remaining annual leave balance for an employee by their ID"),
AIFunctionFactory.Create(
GetNextPayDate,
"GetNextPayDate",
"Returns the next scheduled pay date for an employee by their ID"),
]
};

3. Conversational Chat History with a System Prompt

Maintain a List<ChatMessage> that starts with a system prompt. Append each user message and the model's reply with history.AddMessages(response) so the model keeps full context across turns — and can call tools at any point in the conversation:

List<ChatMessage> history =
[
new ChatMessage(ChatRole.System, "You are a helpful HR self-service assistant.")
];

history.Add(new ChatMessage(ChatRole.User, "Hi! I'm employee E002. How many leave days do I have left?"));
ChatResponse response = await client.GetResponseAsync(history, options);
history.AddMessages(response);

4. Tool Execution Flow

When the model needs data, the sequence is:

  1. Your code calls GetResponseAsync(history, options).
  2. The model returns a tool-call request (e.g. GetLeaveBalance("E002")).
  3. FunctionInvokingChatClient executes your .NET method and appends the result.
  4. The model receives the result and returns a natural-language answer.
  5. GetResponseAsync returns that final answer to your code.

Full Example

using Microsoft.Extensions.AI;
using OpenAI;

namespace MicrosoftAgentFrameworkLesson.ConsoleApp.FunctionCalling;

/// <summary>
/// Demonstrates function calling via AIFunctionFactory with a conversational chat loop.
/// Scenario: HR employee self-service assistant with leave balance,
/// next pay date, and department lookup tools.
/// </summary>
public static class FunctionCallingDemo
{
private static string GetLeaveBalance(string employeeId)
{
var balances = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["E001"] = 14,
["E002"] = 3,
["E003"] = 21,
["E004"] = 0,
};
return balances.TryGetValue(employeeId, out var days)
? $"{days} days remaining"
: "Employee not found.";
}

private static string GetNextPayDate(string employeeId)
{
var dates = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["E001"] = "March 28, 2026",
["E002"] = "March 28, 2026",
["E003"] = "March 31, 2026",
["E004"] = "March 28, 2026",
};
return dates.TryGetValue(employeeId, out var date)
? $"Next pay date: {date}"
: "Employee not found.";
}

private static string GetDepartmentInfo(string departmentCode)
{
var departments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["ENG"] = "Engineering — Manager: Sarah Kim",
["HR"] = "Human Resources — Manager: David Nkosi",
["FIN"] = "Finance — Manager: Laura Perez",
["MKTG"] = "Marketing — Manager: James Chen",
};
return departments.TryGetValue(departmentCode, out var info)
? info
: "Department not found.";
}

public static async Task RunAsync()
{
var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY")
?? throw new InvalidOperationException("Set OPEN_AI_KEY environment variable.");

IChatClient client = new ChatClientBuilder(
new OpenAIClient(apiKey).GetChatClient("gpt-4o-mini").AsIChatClient())
.UseFunctionInvocation()
.Build();

var options = new ChatOptions
{
Tools =
[
AIFunctionFactory.Create(
GetLeaveBalance,
"GetLeaveBalance",
"Returns the remaining annual leave balance for an employee by their ID"),
AIFunctionFactory.Create(
GetNextPayDate,
"GetNextPayDate",
"Returns the next scheduled pay date for an employee by their ID"),
AIFunctionFactory.Create(
GetDepartmentInfo,
"GetDepartmentInfo",
"Returns the department name and manager for a given department code"),
]
};

List<ChatMessage> history =
[
new ChatMessage(ChatRole.System,
"You are a helpful HR self-service assistant. " +
"Answer employee questions about leave balances, pay dates, and departments. " +
"Be concise and professional.")
];

Console.WriteLine("====== Function Calling — HR Self-Service Assistant ======\n");

string[] questions =
[
"Hi! I'm employee E002. How many leave days do I have left?",
"When will I get my next paycheck?",
"Can you also tell me about the Engineering department?",
"Employee E003 wants to know their leave balance and next pay date."
];

foreach (var question in questions)
{
Console.WriteLine($"User >>> {question}");
history.Add(new ChatMessage(ChatRole.User, question));

ChatResponse response = await client.GetResponseAsync(history, options);
Console.WriteLine($"Assistant >>> {response.Text}");
history.AddMessages(response);
Console.WriteLine();
}
}
}


Share this lesson: