Skip to content

Function Calling

Gunpal Jain edited this page Nov 15, 2026 · 7 revisions

Function Calling

Function calling lets Gemini call your own C# code to look up information or take actions. You describe your functions; the model decides when to call them; the SDK runs the call and sends the result back to the model, which then writes the final answer.

NuGet packages

dotnet add package Google_GenerativeAI
dotnet add package Google_GenerativeAI.Tools

Google_GenerativeAI.Tools brings in the CSharpToJsonSchema source generator, which provides the [FunctionTool] and [GenerateJsonSchema] attributes (namespace CSharpToJsonSchema).

Ways to define tools

Style Main types NativeAOT / trimming Best for
Reflection-based QuickTool, QuickTools Needs a JsonSerializerContext (see NativeAOT and Trimming Guidelines) Prototyping, lambdas, third-party methods
Method-based (source generated) [FunctionTool] + generated Tools class Fully compatible A few static or instance methods
Interface-based (source generated) [GenerateJsonSchema] + GenericFunctionTool Fully compatible Reusable tool sets with several methods
MCP servers McpTool See MCP integration Tools exposed by an external MCP server

All of them end up as an IFunctionTool that you register with GenerativeModel.AddFunctionTool(...) (or ChatSession.AddFunctionTool(...)).

1. Reflection-based QuickTool and QuickTools

Wrap any delegate (lambda, static or instance method) at runtime.

using System.ComponentModel;
using GenerativeAI;
using GenerativeAI.Tools;

var func = async ([Description("Request to query student record")] QueryStudentRecordRequest query) =>
{
    return new StudentRecord
    {
        StudentId = "12345",
        FullName = query.FullName,
        EnrolledCourses = new List<string> { "Math", "Physics", "Chemistry" },
        Grades = new Dictionary<string, double> { { "Math", 95.0 }, { "Physics", 89.0 }, { "Chemistry", 88.0 } },
        EnrollmentDate = new DateTime(2023, 1, 10),
        IsActive = true
    };
};

var quickTool = new QuickTool(func, "GetStudentRecordAsync", "Return student record for the year");
// For several delegates: new QuickTools(new Delegate[] { f1, f2, f3 })

var model = new GenerativeModel(apiKey, GoogleAIModels.Gemini38Flash);
model.AddFunctionTool(quickTool);

var result = await model.GenerateContentAsync(
    "How is John Joe doing in senior grade? Enrollment year 01-01-2024 to 01-01-2025");
Console.WriteLine(result.Text);

public class QueryStudentRecordRequest
{
    public string FullName { get; set; } = string.Empty;
    public DateTime EnrollmentYearFrom { get; set; }
    public DateTime EnrollmentYearTo { get; set; }
}

public class StudentRecord
{
    public string StudentId { get; set; } = string.Empty;
    public string FullName { get; set; } = string.Empty;
    public List<string> EnrolledCourses { get; set; } = new();
    public Dictionary<string, double> Grades { get; set; } = new();
    public DateTime EnrollmentDate { get; set; }
    public bool IsActive { get; set; }
}

The QuickTool constructor is QuickTool(Delegate func, string? name = null, string? description = null, JsonSerializerOptions? options = null). Pass options (or register a resolver in DefaultSerializerOptions.CustomJsonTypeResolvers) when you publish with NativeAOT or trimming.

For Microsoft.Extensions.AI, convert with quickTool.AsMeaiTool() or quickTools.ToMeaiFunctions().

2. Method-based tools with [FunctionTool]

Mark methods with [FunctionTool]. The source generator creates a Tools class that wraps them, with no reflection at runtime.

using System.ComponentModel;
using CSharpToJsonSchema;
using GenerativeAI;

public static class BookTools
{
    [FunctionTool(GoogleFunctionTool = true, MeaiFunctionTool = true)]
    [Description("Get book page content")]
    public static Task<string> GetBookPageContentAsync(string bookName, int bookPageNumber,
        CancellationToken cancellationToken = default)
    {
        return Task.FromResult("There is nice weather outside, and I am stuck at home.");
    }
}

// Gemini (GoogleFunctionTool = true)
var model = new GenerativeModel(apiKey, GoogleAIModels.Gemini38Flash);
var tools = new Tools([BookTools.GetBookPageContentAsync]);
model.AddFunctionTool(tools);

// Microsoft.Extensions.AI (MeaiFunctionTool = true)
chatOptions.Tools = tools.AsMeaiTools();
  • GoogleFunctionTool = true makes the generated Tools usable with AddFunctionTool.
  • MeaiFunctionTool = true generates AsMeaiTools() for use with ChatOptions.Tools (see Microsoft.Extensions.AI).

3. Interface-based tools with [GenerateJsonSchema]

Define the interface

Put [GenerateJsonSchema] on an interface. Add [Description] attributes to methods and parameters so the model knows what they do.

using System.ComponentModel;
using CSharpToJsonSchema;

[GenerateJsonSchema(GoogleFunctionTool = true, MeaiFunctionTool = true)]
public interface IWeatherFunctions
{
    [Description("Get the current weather in a given location")]
    Weather GetCurrentWeather(
        [Description("The city and state, e.g. San Francisco, CA")] string location,
        Unit unit = Unit.Celsius);

    [Description("Get the current weather in a given location asynchronously")]
    Task<Weather> GetCurrentWeatherAsync(
        [Description("The city and state, e.g. San Francisco, CA")] string location,
        Unit unit = Unit.Celsius,
        CancellationToken cancellationToken = default);
}

public enum Unit { Celsius, Fahrenheit, Imperial }

public class Weather
{
    public string Location { get; set; } = string.Empty;
    public double Temperature { get; set; }
    public Unit Unit { get; set; }
    public string Description { get; set; } = string.Empty;
}

Both GoogleFunctionTool and MeaiFunctionTool are optional (default false):

  • GoogleFunctionTool = true generates AsGoogleFunctionTool().
  • MeaiFunctionTool = true generates AsMeaiTools().

Implement the interface

public class WeatherService : IWeatherFunctions
{
    public Weather GetCurrentWeather(string location, Unit unit = Unit.Celsius) =>
        new() { Location = location, Temperature = 30.0, Unit = unit, Description = "Sunny" };

    public Task<Weather> GetCurrentWeatherAsync(string location, Unit unit = Unit.Celsius,
        CancellationToken cancellationToken = default) =>
        Task.FromResult(new Weather { Location = location, Temperature = 22.0, Unit = unit, Description = "Sunny" });
}

Replace the placeholder values with your real logic.

Register the tools

using GenerativeAI;
using GenerativeAI.Tools;

var service = new WeatherService();
var model = new GenerativeModel(apiKey, GoogleAIModels.Gemini38Flash);

// With GoogleFunctionTool = true:
model.AddFunctionTool(service.AsGoogleFunctionTool());

// Always available (works without GoogleFunctionTool = true):
model.AddFunctionTool(new GenericFunctionTool(service.AsTools(), service.AsCalls()));

// Microsoft.Extensions.AI, with MeaiFunctionTool = true:
chatOptions.Tools = service.AsMeaiTools();

Making a request

var result = await model.GenerateContentAsync("What is the weather in San Francisco today?");
Console.WriteLine(result.Text);

The model reads your descriptions and decides whether to call a function. With the default settings the SDK runs the function, sends the result back, and returns the model's final answer.

Tools also work in a multi-turn chat:

var chat = model.StartChat();
chat.AddFunctionTool(service.AsGoogleFunctionTool());
var reply = await chat.GenerateContentAsync("Is it warmer in Paris or in Rome?");

Function calling behaviour

Use GenerativeModel.FunctionCallingBehaviour (type GenerativeAI.Core.FunctionCallingBehaviour) to control the automatic loop:

model.FunctionCallingBehaviour = new FunctionCallingBehaviour
{
    FunctionEnabled = true,            // send the tools to the model (default true)
    AutoCallFunction = true,           // run requested functions automatically (default true)
    AutoReplyFunction = true,          // send results back and return the model's final answer (default true)
    AutoHandleBadFunctionCalls = false // try to recover from invalid function calls (default false)
};
Property Default Meaning
FunctionEnabled true Turns function calling on or off. model.DisableFunctions() and model.EnableFunctions() are shortcuts.
AutoCallFunction true If true, the SDK runs the function the model asks for. If false, the function call is returned in the response for you to handle.
AutoReplyFunction true If true, the function result is sent back to the model and you get its final answer. If false, the function runs but the SDK returns the model's function-call response instead of sending the result back.
AutoHandleBadFunctionCalls false If the model calls a function that no registered tool contains: false throws a GenerativeAIException; true sends an error result ("Invalid function name or function doesn't exist.") back to the model instead.

When the model asks for several functions in one turn (parallel function calling), the SDK runs all of them and returns all results in a single turn.

You can also pass a ToolConfig and a behaviour per tool: model.AddFunctionTool(tool, toolConfig, functionCallingBehaviour). For example, to force the model to call one of your functions:

model.AddFunctionTool(tool, new ToolConfig
{
    FunctionCallingConfig = new FunctionCallingConfig
    {
        Mode = FunctionCallingMode.ANY, // AUTO, ANY, NONE or VALIDATED
        AllowedFunctionNames = new List<string> { "GetCurrentWeather" }
    }
});

Handling function calls manually

Set AutoCallFunction = false to get the function call back and run it yourself. Send the result back as a FunctionResponse part in a turn with the user role:

using GenerativeAI;
using GenerativeAI.Types;

model.FunctionCallingBehaviour.AutoCallFunction = false;

var userTurn = new Content { Role = Roles.User };
userTurn.AddText("What's the weather in San Francisco?");

var request = new GenerateContentRequest { Contents = new List<Content> { userTurn } };
var response = await model.GenerateContentAsync(request);

var call = response.GetFunction(); // or response.GetFunctions() for parallel calls
if (call != null)
{
    FunctionResponse? result = await tool.CallAsync(call); // any IFunctionTool, e.g. the GenericFunctionTool above

    request.Contents.Add(response.Candidates![0].Content!);   // the model's function-call turn
    var resultTurn = new Content { Role = Roles.User };         // Gemini 3.x rejects the "function" role
    resultTurn.AddPart(new Part { FunctionResponse = result });
    request.Contents.Add(resultTurn);

    var final = await model.GenerateContentAsync(request);
    Console.WriteLine(final.Text);
}

v4 change: function results use the user role

Since v4.0.0 the SDK sends function results in a user turn, as the official SDKs do, because Gemini 3.x models reject the legacy function role. Before each request, GenerativeModel converts any content with Role == Roles.Function to Roles.User, so chat histories saved by earlier SDK versions keep working without changes.

Choosing a style

Interface-based ([GenerateJsonSchema]) and method-based ([FunctionTool]) tools

  • Fully compatible with NativeAOT and trimming, because the schema and the call code are generated at compile time.
  • Interface-based tools suit reusable tool sets with several methods: define the set once and reuse it across your solution. Use cases: plugin systems, microservice communication, domain-specific languages, data access layers, workflow automation, AI-driven data validation (validation rules defined as an interface), personalised learning platforms (one interface per learning module).
  • Method-based tools suit a small number of class-scoped or static methods. Use cases: command-line integration, configuration management, hardware control, simple utility functions, real-time data analysis (wrapping single analysis methods), context-aware notifications.

Reflection-based QuickTool and QuickTools

  • Not recommended when NativeAOT or trimming matters (or provide a JsonSerializerContext, see NativeAOT and Trimming Guidelines).
  • Useful for calling functions from third-party libraries whose source you don't control, and for anonymous functions while testing prompts or prototyping.
  • Use cases: dynamic scripting, rapid prototyping of API integrations, debugging and testing, event handling, data transformation pipelines, AI-powered data scraping, quickly creating input-validation tools, A/B testing of function logic.

In summary

  • For performance and NativeAOT/trimming compatibility, use interface-based tools or method-based tools with [FunctionTool].
  • For rapid prototyping, testing or third-party libraries, use QuickTool and QuickTools.
  • Use interface-based tools for reusable tool sets and method-based tools for a few methods.

Related pages

Clone this wiki locally