Table of Contents

Function Calling

Why Function Calling?

LLMs can only generate text — they cannot check the weather, query a database, or call an API on their own. Without function calling, you'd have to parse the model's intent manually:

// ❌ Without function calling — manual intent parsing
var reply = await service.GetCompletionAsync("What's the weather in Seoul?");
// reply = "I'd need to check a weather service for that information."

// You have to figure out the user wants weather, extract "Seoul", call the API yourself
if (reply.Contains("weather"))
{
    var city = ExtractCity(reply); // fragile regex or keyword matching
    var weather = await weatherApi.GetAsync(city);
    // Now ask again with the weather data injected...
}

This is brittle, doesn't scale, and requires you to anticipate every possible user intent. With function calling, the model decides when to call your code and what arguments to pass:

// ✅ With function calling — the model handles intent + extraction
var service = new OpenAIService(apiKey, http)
    .WithFunction(
        "get_weather",
        "Gets the current weather for a location",
        ("location", "The city and country", required: true),
        (string location) => weatherApi.Get(location)
    );

var response = await service.GetCompletionAsync("What's the weather in Seoul?");
// The model calls get_weather("Seoul, Korea"), gets the result, and answers naturally.

You define what your code can do; the model figures out when and how to use it.

Quick Example

var service = new OpenAIService(apiKey, http)
    .WithFunction(
        "get_weather",
        "Gets the current weather for a location",
        ("location", "The city and country", required: true),
        (string location) => $"The weather in {location} is sunny, 22°C"
    );

var response = await service.GetCompletionAsync("What's the weather in Seoul?");
// The model calls get_weather("Seoul, Korea") and incorporates the result.

Defining Functions with Attributes

For more complex functions, use [AiFunction] and [AiParameter] attributes:

using Mythosia.AI.Attributes;
using Mythosia.AI.Extensions;

public sealed class ProductFunctions
{
    [AiFunction("search_products", "Search the product catalog")]
    public string SearchProducts(
        [AiParameter("Search query", required: true)] string query,
        [AiParameter("Maximum results to return")] int limit = 5)
    {
        // ... your implementation
        return JsonSerializer.Serialize(results);
    }
}

Then register it:

service.WithFunctions(new ProductFunctions());

Function Calling Policy

Registered functions are available to the model by default. Disable them globally, or force one named function when a provider supports forced tool selection:

using Mythosia.AI.Models.Functions;

// Let the model decide (default)
service.FunctionCallMode = FunctionCallMode.Auto;
service.ForceFunctionName = null;

// Force a specific registered function
service.ForceFunctionName = "search_products";

// Disable function calling
service.FunctionCallMode = FunctionCallMode.None;

FunctionCallingPolicy controls the multi-round loop and local handler scheduling; it does not select whether the provider may call a function. Calls from one assistant response execute sequentially by default. Opt in to bounded parallel execution only for independent, thread-safe handlers:

service.DefaultPolicy = new FunctionCallingPolicy
{
    MaxRounds = 20,
    TimeoutSeconds = 120,
    ExecutionMode = FunctionExecutionMode.Parallel,
    MaxConcurrency = 4
};

Parallel handlers may finish out of order, but Mythosia preserves the provider's original call order in the FunctionCallResultBatch. Once a validated batch starts, its handlers run to completion so conversation history cannot contain calls without matching results; registered handlers do not currently receive a CancellationToken.

Bulk Registration from a Class

Register all [AiFunction]-annotated methods from an object at once:

var tools = new MyTools();
service.WithFunctions(tools);  // scans instance methods with [AiFunction]

For static methods:

service.WithStaticFunctions<MyTools>();  // scans static methods with [AiFunction]

Async Function Handlers

All WithFunction overloads have WithFunctionAsync counterparts that accept Func<..., Task<string>>:

service.WithFunctionAsync<string>(
    "fetch_data",
    "Fetches data from an external API",
    ("url", "The URL to fetch", required: true),
    async (string url) =>
    {
        var result = await httpClient.GetStringAsync(url);
        return result;
    }
);

Supports 0 to 3 parameters, same as the sync variants.

Temporarily Disabling Functions

Disable function calling for a single request without removing registrations:

// Extension method — returns result with functions disabled
string answer = await service.AskWithoutFunctionsAsync("Just answer directly");

// Or toggle the property
service.WithoutFunctions();  // sets FunctionsDisabled = true

Using FunctionBuilder

Build function definitions programmatically:

using Mythosia.AI.Builders;
using Mythosia.AI.Extensions;

var fn = FunctionBuilder
    .Create("get_stock_price")
    .WithDescription("Returns the current stock price")
    .AddParameter("ticker", "string", "Stock ticker symbol", required: true)
    .WithHandler(args => FetchStockPrice(args["ticker"].ToString() ?? string.Empty))
    .Build();

service.WithFunction(fn);