using AutoGen.Core;public partial class WeatherTools{ /// <summary> /// Get weather report for a city /// </summary> /// <param name="city">city name</param> /// <param name="date">date in YYYY-MM-DD format</param> [Function] public async Task<string> GetWeather(string city, string date) { return $"Weather in {city} on {date} is sunny"; } /// <summary> /// Convert temperature between units /// </summary> /// <param name="value">temperature value</param> /// <param name="fromUnit">source unit (C, F, K)</param> /// <param name="toUnit">target unit (C, F, K)</param> [Function] public async Task<string> ConvertTemperature( double value, string fromUnit, string toUnit) { // Conversion logic here return $"{value}°{fromUnit} = {value}°{toUnit}"; }}
The class must be partial for the source generator to add generated code.
2
Register functions with agent
using AutoGen.Core;using AutoGen.OpenAI;using AutoGen.OpenAI.Extension;var tools = new WeatherTools();var gpt4 = GetOpenAIClient(); // Your OpenAI client setupvar functionCallMiddleware = new FunctionCallMiddleware( functions: [ tools.GetWeatherFunctionContract, tools.ConvertTemperatureFunctionContract, ], functionMap: new Dictionary<string, Func<string, Task<string>>> { { nameof(tools.GetWeather), tools.GetWeatherWrapper }, { nameof(tools.ConvertTemperature), tools.ConvertTemperatureWrapper }, });var agent = new OpenAIChatAgent( chatClient: gpt4, name: "assistant", systemMessage: "You are a helpful weather assistant") .RegisterMessageConnector() .RegisterStreamingMiddleware(functionCallMiddleware) .RegisterPrintMessage();
3
Use the agent
var response = await agent.SendAsync( "What's the weather in Seattle on 2024-03-15?");Console.WriteLine(response.GetContent());// Output: Weather in Seattle on 2024-03-15 is sunny
When you mark a method with [Function], the source generator creates:
Function Contract - Schema definition from method signature and XML docs
Function Wrapper - Type-safe deserialization and invocation
public partial class WeatherTools{ /// <summary> /// Get weather report /// </summary> /// <param name="city">city name</param> [Function] public async Task<string> GetWeather(string city) { return $"Weather in {city} is sunny"; }}
/// <summary>/// This becomes the function description/// </summary>/// <param name="city">This describes the city parameter</param>/// <param name="date">This describes the date parameter</param>[Function]public async Task<string> GetWeather(string city, string date){ return $"Weather in {city} on {date}";}
The LLM uses these descriptions to understand when and how to call functions.
var response = await agent.SendAsync("What's the weather in Paris?");if (response is ToolCallAggregateMessage toolCallMsg){ var toolCalls = toolCallMsg.GetToolCalls(); foreach (var call in toolCalls) { Console.WriteLine($"Function: {call.FunctionName}"); Console.WriteLine($"Arguments: {call.FunctionArguments}"); Console.WriteLine($"Result: {call.Result}"); } // Get final content after function execution Console.WriteLine(response.GetContent());}
Send function results back for natural language response:
var toolCallResponse = await agent.SendAsync("Calculate tax for $100 at 10%");// Send the tool call result back to get a natural language responsevar finalResponse = await agent.SendAsync(toolCallResponse);Console.WriteLine(finalResponse.GetContent());// "The tax on $100 at a rate of 10% is $10."
public partial class WeatherTools{ [Function] public async Task<string> GetWeather(string city) => $"Weather in {city}"; [Function] public async Task<string> GetForecast(string city, int days) => $"{days}-day forecast for {city}";}public partial class MathTools{ [Function] public async Task<string> Add(int a, int b) => (a + b).ToString(); [Function] public async Task<string> Multiply(int a, int b) => (a * b).ToString();}// Register bothvar weather = new WeatherTools();var math = new MathTools();var middleware = new FunctionCallMiddleware( functions: [ weather.GetWeatherFunctionContract, weather.GetForecastFunctionContract, math.AddFunctionContract, math.MultiplyFunctionContract, ], functionMap: new Dictionary<string, Func<string, Task<string>>> { { nameof(weather.GetWeather), weather.GetWeatherWrapper }, { nameof(weather.GetForecast), weather.GetForecastWrapper }, { nameof(math.Add), math.AddWrapper }, { nameof(math.Multiply), math.MultiplyWrapper }, });
public partial class StatefulTools{ private readonly Dictionary<string, string> _memory = new(); /// <summary> /// Store a value with a key /// </summary> /// <param name="key">storage key</param> /// <param name="value">value to store</param> [Function] public async Task<string> Store(string key, string value) { _memory[key] = value; return $"Stored {value} at {key}"; } /// <summary> /// Retrieve a stored value /// </summary> /// <param name="key">storage key</param> [Function] public async Task<string> Retrieve(string key) { return _memory.TryGetValue(key, out var value) ? value : $"No value found for {key}"; }}
[Fact]public async Task TestWeatherFunction(){ var tools = new WeatherTools(); var result = await tools.GetWeather("Seattle", "2024-03-15"); Assert.Contains("Seattle", result); Assert.Contains("2024-03-15", result);}