using System.ComponentModel;
using Microsoft.Extensions.AI;
using OpenAI;
namespace MicrosoftAgentFrameworkLesson.ConsoleApp;
// ── Tool functions (Fitness / Gym domain) ────────────────
public static class GymTools
{
[Description("Get a workout suggestion for a specific muscle group")]
public static string GetWorkoutSuggestion(
[Description("The muscle group to target, e.g. chest, back, legs, shoulders, arms, core")]
string muscleGroup)
{
var workouts = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["chest"] = "Bench press 4x8, incline dumbbell press 3x10, cable flyes 3x12",
["back"] = "Deadlift 4x6, barbell row 4x8, lat pulldown 3x10, face pulls 3x15",
["legs"] = "Squat 4x8, Romanian deadlift 3x10, leg press 3x12, calf raises 4x15",
["shoulders"] = "Overhead press 4x8, lateral raises 3x12, rear delt flyes 3x15",
["arms"] = "Barbell curl 3x10, tricep dips 3x10, hammer curls 3x12, skull crushers 3x10",
["core"] = "Plank 3x60s, hanging leg raises 3x12, Russian twists 3x20, ab wheel rollouts 3x10"
};
return workouts.TryGetValue(muscleGroup, out var plan)
? $"Workout for {muscleGroup}: {plan}"
: $"No specific plan found for '{muscleGroup}'. Try: chest, back, legs, shoulders, arms, or core.";
}
[Description("Calculate BMI (Body Mass Index) from weight and height")]
public static string CalculateBmi(
[Description("Body weight in kilograms")] double weightKg,
[Description("Height in centimeters")] double heightCm)
{
if (weightKg <= 0 || heightCm <= 0)
return "Invalid input. Weight and height must be positive numbers.";
var heightM = heightCm / 100.0;
var bmi = weightKg / (heightM * heightM);
var category = bmi switch
{
< 18.5 => "Underweight",
< 25.0 => "Normal weight",
< 30.0 => "Overweight",
_ => "Obese"
};
return $"BMI: {bmi:F1} — Category: {category} (weight {weightKg} kg, height {heightCm} cm)";
}
[Description("Get the gym class schedule for a specific day of the week")]
public static string GetGymSchedule(
[Description("Day of the week, e.g. Monday, Tuesday")] string dayOfWeek)
{
var schedule = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["Monday"] = "06:00 Spin Class | 08:00 Yoga | 12:00 HIIT | 18:00 Boxing",
["Tuesday"] = "07:00 Pilates | 09:00 Strength Training | 17:00 Zumba | 19:00 CrossFit",
["Wednesday"] = "06:00 Spin Class | 10:00 Aqua Aerobics | 16:00 Kickboxing | 18:00 Yoga",
["Thursday"] = "07:00 HIIT | 09:00 Body Pump | 17:00 Pilates | 19:00 Boxing",
["Friday"] = "06:00 Yoga | 08:00 Spin Class | 12:00 Zumba | 17:00 Open Gym",
["Saturday"] = "08:00 CrossFit | 10:00 Yoga | 14:00 Family Fitness",
["Sunday"] = "09:00 Gentle Yoga | 11:00 Open Gym"
};
return schedule.TryGetValue(dayOfWeek, out var classes)
? $"{dayOfWeek} schedule: {classes}"
: $"No schedule found for '{dayOfWeek}'. The gym is open Monday through Sunday.";
}
[Description("Search for exercises filtered by difficulty level")]
public static string SearchExercises(
[Description("Search query — exercise name, body part, or keywords")] string query,
[Description("Difficulty filter: 'beginner', 'intermediate', 'advanced', or 'all'")] string difficulty = "all",
[Description("Maximum number of results to return (1-10)")] int maxResults = 5)
{
// Simplified catalog
var exercises = new List<(string Name, string Difficulty, string BodyPart)>
{
("Push-ups", "beginner", "chest"),
("Bench Press", "intermediate", "chest"),
("Weighted Dips", "advanced", "chest"),
("Bodyweight Squat", "beginner", "legs"),
("Barbell Squat", "intermediate", "legs"),
("Pistol Squat", "advanced", "legs"),
("Plank", "beginner", "core"),
("Dragon Flag", "advanced", "core"),
("Pull-ups", "intermediate", "back"),
("Muscle-up", "advanced", "back"),
("Dumbbell Curl", "beginner", "arms"),
("Barbell Row", "intermediate", "back"),
("Burpees", "intermediate", "full body"),
("Jumping Jacks", "beginner", "full body"),
("Kettlebell Swing", "intermediate", "full body")
};
var results = exercises
.Where(e => e.Name.Contains(query, StringComparison.OrdinalIgnoreCase) ||
e.BodyPart.Contains(query, StringComparison.OrdinalIgnoreCase))
.Where(e => difficulty == "all" ||
e.Difficulty.Equals(difficulty, StringComparison.OrdinalIgnoreCase))
.Take(maxResults)
.Select(e => $" • {e.Name} [{e.Difficulty}] — {e.BodyPart}")
.ToList();
return results.Count > 0
? $"Found {results.Count} exercise(s):\n{string.Join("\n", results)}"
: $"No exercises found for query '{query}' with difficulty '{difficulty}'.";
}
[Description("Estimate calories burned for a given exercise session")]
public static string EstimateCaloriesBurned(
[Description("Type of exercise performed")] string exercise,
[Description("Duration of the session in minutes")] int durationMinutes,
[Description("Body weight in kilograms")] double weightKg)
{
// Simplified MET-based estimation
var metValues = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase)
{
["running"] = 9.8,
["cycling"] = 7.5,
["swimming"] = 8.0,
["walking"] = 3.8,
["yoga"] = 3.0,
["weightlifting"] = 6.0,
["hiit"] = 10.0,
["boxing"] = 9.0,
["crossfit"] = 9.5,
["pilates"] = 4.0
};
if (!metValues.TryGetValue(exercise, out var met))
return $"Unknown exercise '{exercise}'. Try: {string.Join(", ", metValues.Keys)}.";
var calories = met * weightKg * (durationMinutes / 60.0);
return $"Estimated calories burned: {calories:F0} kcal ({exercise}, {durationMinutes} min, {weightKg} kg body weight)";
}
}
// ── Demo class ───────────────────────────────────────────
public static class FunctionCallingDemo
{
public static async Task RunAsync()
{
// 1. Retrieve API key
var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY");
if (string.IsNullOrWhiteSpace(apiKey))
{
Console.WriteLine("Please set the OPEN_AI_KEY environment variable.");
return;
}
// 2. Create an IChatClient with function invocation enabled
IChatClient chatClient = new OpenAIClient(apiKey)
.GetChatClient("gpt-4o-mini")
.AsIChatClient()
.AsBuilder()
.UseFunctionInvocation() // Enables automatic function calling
.Build();
// ══════════════════════════════════════════════
// PART 1 — Basic Function Call
// ══════════════════════════════════════════════
Console.WriteLine("═══ Part 1: Basic Function Call ═══\n");
var basicOptions = new ChatOptions
{
Tools = [AIFunctionFactory.Create(GymTools.GetWorkoutSuggestion)]
};
var response = await chatClient.GetResponseAsync(
"I want to train my legs today. What exercises should I do?",
basicOptions);
Console.WriteLine(response.Text);
// ══════════════════════════════════════════════
// PART 2 — Functions with Multiple Parameters
// ══════════════════════════════════════════════
Console.WriteLine("\n═══ Part 2: Multi-Parameter Function ═══\n");
var bmiOptions = new ChatOptions
{
Tools = [AIFunctionFactory.Create(GymTools.CalculateBmi)]
};
response = await chatClient.GetResponseAsync(
"I weigh 78 kilograms and I'm 175 cm tall. What's my BMI? Is that healthy?",
bmiOptions);
Console.WriteLine(response.Text);
// ══════════════════════════════════════════════
// PART 3 — Parameter Descriptions & Defaults
// ══════════════════════════════════════════════
Console.WriteLine("\n═══ Part 3: Search with Filters ═══\n");
var searchOptions = new ChatOptions
{
Tools = [AIFunctionFactory.Create(GymTools.SearchExercises)]
};
response = await chatClient.GetResponseAsync(
"Show me beginner-friendly chest exercises.",
searchOptions);
Console.WriteLine(response.Text);
// ══════════════════════════════════════════════
// PART 4 — Multiple Functions
// ══════════════════════════════════════════════
Console.WriteLine("\n═══ Part 4: Multiple Functions ═══\n");
var multiOptions = new ChatOptions
{
Tools =
[
AIFunctionFactory.Create(GymTools.GetWorkoutSuggestion),
AIFunctionFactory.Create(GymTools.CalculateBmi),
AIFunctionFactory.Create(GymTools.GetGymSchedule),
AIFunctionFactory.Create(GymTools.SearchExercises),
AIFunctionFactory.Create(GymTools.EstimateCaloriesBurned)
]
};
// AI picks the right function automatically
response = await chatClient.GetResponseAsync(
"What classes are available at the gym this Saturday?",
multiOptions);
Console.WriteLine($"Schedule question → {response.Text}\n");
response = await chatClient.GetResponseAsync(
"If I run for 30 minutes and weigh 80 kg, how many calories will I burn?",
multiOptions);
Console.WriteLine($"Calorie question → {response.Text}\n");
// AI may call multiple functions for a complex query
response = await chatClient.GetResponseAsync(
"I weigh 90 kg and I'm 180 cm. Give me my BMI and suggest a good back workout.",
multiOptions);
Console.WriteLine($"Combined question → {response.Text}");
// ══════════════════════════════════════════════
// PART 5 — Functions in Conversations
// ══════════════════════════════════════════════
Console.WriteLine("\n═══ Part 5: Function Calling with Conversation History ═══\n");
List<ChatMessage> history =
[
new(ChatRole.System,
"You are a friendly gym assistant. Keep answers concise and motivational.")
];
// Turn 1 — general question (no function needed)
history.Add(new(ChatRole.User, "I'm new to the gym. Where should I start?"));
var turn1 = await chatClient.GetResponseAsync(history, multiOptions);
history.AddMessages(turn1);
Console.WriteLine($"User : I'm new to the gym. Where should I start?");
Console.WriteLine($"Coach: {turn1.Text}\n");
// Turn 2 — triggers GetGymSchedule
history.Add(new(ChatRole.User, "What group classes do you have on Wednesday?"));
var turn2 = await chatClient.GetResponseAsync(history, multiOptions);
history.AddMessages(turn2);
Console.WriteLine($"User : What group classes do you have on Wednesday?");
Console.WriteLine($"Coach: {turn2.Text}\n");
// Turn 3 — triggers GetWorkoutSuggestion (AI uses conversation context)
history.Add(new(ChatRole.User, "I'd like a shoulder workout for after the Spin Class."));
var turn3 = await chatClient.GetResponseAsync(history, multiOptions);
history.AddMessages(turn3);
Console.WriteLine($"User : I'd like a shoulder workout for after the Spin Class.");
Console.WriteLine($"Coach: {turn3.Text}");
// ══════════════════════════════════════════════
// PART 6 — Function Calling with Streaming
// ══════════════════════════════════════════════
Console.WriteLine("\n═══ Part 6: Function Calling + Streaming ═══\n");
await foreach (var update in chatClient.GetStreamingResponseAsync(
"How many calories does a 70 kg person burn doing 45 minutes of boxing? " +
"Also suggest a core workout to finish the session.",
multiOptions))
{
Console.Write(update.Text);
}
Console.WriteLine("\n");
// ══════════════════════════════════════════════
// PART 7 — Error Handling in Functions
// ══════════════════════════════════════════════
Console.WriteLine("═══ Part 7: Graceful Error Handling ═══\n");
response = await chatClient.GetResponseAsync(
"How many calories does a 75 kg person burn doing trampolining for 20 minutes?",
multiOptions);
Console.WriteLine(response.Text);
}
}