Skip to content

Advanced Features

AJ-comp edited this page Apr 5, 2026 · 2 revisions

Advanced Features

This page covers the advanced features of Mythosia.AI, including Function Calling, Enhanced Streaming, Policy System, and GPT-5 family reasoning support.

GPT-5 Family Reasoning Support

GPT-5 family models include a built-in reasoning engine that "thinks" before responding. Mythosia.AI provides full support for streaming and extracting this reasoning data across all GPT-5 variants.

Supported Models

Model Status Reasoning Effort Verbosity Notes
gpt-5 ✅ Full Support Minimal/Low/Medium/High - Reasoning + streaming + function calling
gpt-5-mini ✅ Full Support Minimal/Low/Medium/High - Lightweight reasoning model
gpt-5-nano ✅ Full Support Minimal/Low/Medium/High - Fastest GPT-5 variant
gpt-5-chat-latest ✅ Full Support Minimal/Low/Medium/High - Latest conversation model
gpt-5.1 ✅ Full Support None/Low/Medium/High Low/Medium/High Reasoning with verbosity control
gpt-5.2 ✅ Full Support None/Low/Medium/High/XHigh Low/Medium/High Best for complex, coding, agentic tasks
gpt-5.2-pro ✅ Full Support Medium/High/XHigh Low/Medium/High High-compute model; defaults to Medium
gpt-5.2-codex ✅ Full Support Low/Medium/High/XHigh Low/Medium/High Coding model; defaults to Medium (no None)
gpt-5.3-codex ✅ Full Support Low/Medium/High/XHigh Low/Medium/High Latest coding model; defaults to Medium (no None)
gpt-5.4 ✅ Full Support None/Low/Medium/High/XHigh Low/Medium/High Latest general model
gpt-5.4-mini ✅ Full Support None/Low/Medium/High/XHigh Low/Medium/High Lightweight GPT-5.4
gpt-5.4-nano ✅ Full Support None/Low/Medium/High/XHigh Low/Medium/High Fastest GPT-5.4
gpt-5.4-pro ✅ Full Support Medium/High/XHigh Low/Medium/High High-compute GPT-5.4; defaults to Medium
o3 ✅ Full Support Fixed (Medium) - Reasoning effort is hardcoded
o3-pro ✅ Full Support Fixed (High) - Reasoning effort is hardcoded

Reasoning Streaming

Stream GPT-5 family reasoning data in real-time alongside text content:

var options = new StreamOptions().WithReasoning().WithMetadata();

await foreach (var content in service.StreamAsync("Solve step by step: 15 * 17", options))
{
    switch (content.Type)
    {
        case StreamingContentType.Reasoning:
            // Reasoning chunks arrive before text
            Console.Write($"[Thinking] {content.Content}");
            break;

        case StreamingContentType.Text:
            Console.Write(content.Content);
            break;

        case StreamingContentType.Completion:
            if (content.Metadata != null)
            {
                Console.WriteLine($"\nModel: {content.Metadata["model"]}");
                Console.WriteLine($"Total length: {content.Metadata["total_length"]}");
                if (content.Metadata.TryGetValue("usage", out var usage))
                    Console.WriteLine($"Usage: {usage}");
            }
            break;
    }
}

Non-Streaming Reasoning (LastReasoningSummary)

For non-streaming requests, access the reasoning summary via the LastReasoningSummary property:

var openAIService = (OpenAIService)service;

// Configure reasoning effort
openAIService.WithGpt5Parameters(reasoningEffort: Gpt5Reasoning.High);

var response = await openAIService.GetCompletionAsync("What is 15 * 17?");

Console.WriteLine($"Answer: {response}");
Console.WriteLine($"Reasoning: {openAIService.LastReasoningSummary ?? "(none)"}");

Note: LastReasoningSummary is automatically populated when the API returns a reasoning output item.

GPT-5 Parameters

GPT-5 (Base)

For gpt-5, gpt-5-mini, gpt-5-nano, gpt-5-chat-latest:

var openAIService = (OpenAIService)service;

// Minimal reasoning - fastest, least detailed
openAIService.WithGpt5Parameters(reasoningEffort: Gpt5Reasoning.Minimal);

// Medium reasoning (default)
openAIService.WithGpt5Parameters(reasoningEffort: Gpt5Reasoning.Medium);

// High reasoning - slowest, most detailed
openAIService.WithGpt5Parameters(reasoningEffort: Gpt5Reasoning.High);

// With reasoning summary control
openAIService.WithGpt5Parameters(reasoningEffort: Gpt5Reasoning.Medium, reasoningSummary: ReasoningSummary.Concise);
openAIService.WithGpt5Parameters(reasoningEffort: Gpt5Reasoning.High, reasoningSummary: ReasoningSummary.Detailed);

// Disable reasoning summary
openAIService.WithGpt5Parameters(reasoningEffort: Gpt5Reasoning.Medium, reasoningSummary: null);

GPT-5.1

GPT-5.1 adds text verbosity control alongside reasoning effort:

var openAIService = (OpenAIService)service;

// Default: no reasoning, medium verbosity
openAIService.WithGpt5_1Parameters();

// Low reasoning with concise output
openAIService.WithGpt5_1Parameters(reasoningEffort: Gpt5_1Reasoning.Low, verbosity: Verbosity.Low);

// High reasoning with verbose output
openAIService.WithGpt5_1Parameters(reasoningEffort: Gpt5_1Reasoning.High, verbosity: Verbosity.High);

// Full configuration
openAIService.WithGpt5_1Parameters(
    reasoningEffort: Gpt5_1Reasoning.Medium,
    verbosity: Verbosity.Low,
    reasoningSummary: ReasoningSummary.Concise
);

GPT-5.1 Parameters:

Parameter Type Values Default Description
reasoningEffort Gpt5_1Reasoning None, Low, Medium, High None How much "thinking" before responding
verbosity Verbosity Low, Medium, High Medium Text output detail level
reasoningSummary ReasoningSummary? Auto, Concise, Detailed, null Auto Reasoning summary mode (null to disable)

GPT-5.2

GPT-5.2 supports XHigh reasoning effort. gpt-5.2-pro and gpt-5.2-codex default to Medium; gpt-5.2-codex does not support None:

var openAIService = (OpenAIService)service;

// Default: no reasoning, medium verbosity
openAIService.WithGpt5_2Parameters();

// Maximum reasoning for complex problems
openAIService.WithGpt5_2Parameters(reasoningEffort: Gpt5_2Reasoning.XHigh, verbosity: Verbosity.High);

// Balanced configuration
openAIService.WithGpt5_2Parameters(
    reasoningEffort: Gpt5_2Reasoning.High,
    verbosity: Verbosity.Medium,
    reasoningSummary: ReasoningSummary.Detailed
);

GPT-5.2 Parameters:

Parameter Type Values Default Description
reasoningEffort Gpt5_2Reasoning None, Low, Medium, High, XHigh None (Pro/Codex: Medium) How much "thinking" before responding
verbosity Verbosity Low, Medium, High Medium Text output detail level
reasoningSummary ReasoningSummary? Auto, Concise, Detailed, null Auto Reasoning summary mode (null to disable)

Note: gpt-5.2-codex does not support None reasoning effort and will be automatically adjusted to Low.

GPT-5.3

GPT-5.3 is currently available as gpt-5.3-codex, which defaults to Medium and does not support None:

var openAIService = (OpenAIService)service;

// Default: auto (Medium for codex)
openAIService.WithGpt5_3Parameters();

// High reasoning with verbose output
openAIService.WithGpt5_3Parameters(reasoningEffort: Gpt5_3Reasoning.High, verbosity: Verbosity.High);

// Maximum reasoning
openAIService.WithGpt5_3Parameters(
    reasoningEffort: Gpt5_3Reasoning.XHigh,
    verbosity: Verbosity.Medium,
    reasoningSummary: ReasoningSummary.Detailed
);

GPT-5.3 Parameters:

Parameter Type Values Default Description
reasoningEffort Gpt5_3Reasoning None, Low, Medium, High, XHigh None (Codex: Medium) How much "thinking" before responding
verbosity Verbosity Low, Medium, High Medium Text output detail level
reasoningSummary ReasoningSummary? Auto, Concise, Detailed, null Auto Reasoning summary mode (null to disable)

Note: gpt-5.3-codex does not support None reasoning effort and will be automatically adjusted to Low.

GPT-5.4

GPT-5.4 is the latest model family. gpt-5.4-pro defaults to Medium effort:

var openAIService = (OpenAIService)service;

// Default: no reasoning, medium verbosity
openAIService.WithGpt5_4Parameters();

// Maximum reasoning
openAIService.WithGpt5_4Parameters(reasoningEffort: Gpt5_4Reasoning.XHigh, verbosity: Verbosity.High);

// Balanced configuration
openAIService.WithGpt5_4Parameters(
    reasoningEffort: Gpt5_4Reasoning.High,
    verbosity: Verbosity.Medium,
    reasoningSummary: ReasoningSummary.Detailed
);

GPT-5.4 Parameters:

Parameter Type Values Default Description
reasoningEffort Gpt5_4Reasoning None, Low, Medium, High, XHigh None (Pro: Medium) How much "thinking" before responding
verbosity Verbosity Low, Medium, High Medium Text output detail level
reasoningSummary ReasoningSummary? Auto, Concise, Detailed, null Auto Reasoning summary mode (null to disable)

max_output_tokens Minimum (4096)

GPT-5 family reasoning tokens consume the max_output_tokens budget. Mythosia.AI automatically enforces a minimum floor of 4096 tokens:

// This will be automatically raised to 4096 with a logged warning
service.WithMaxTokens(500);

// Recommended: use a larger value for reasoning-heavy tasks
service.WithMaxTokens(8192);

If reasoning exhausts the entire budget, the library returns a descriptive warning message instead of an empty string.

Combining Reasoning with Function Calling

Reasoning works seamlessly with function calling:

var openAIService = (OpenAIService)service;
openAIService.WithGpt5_2Parameters(reasoningEffort: Gpt5_2Reasoning.High);

openAIService.WithFunction(
    "calculate",
    "Performs calculation",
    ("expression", "Math expression", true),
    (string expression) => $"Result: {EvaluateExpression(expression)}"
);

var options = new StreamOptions()
    .WithReasoning()
    .WithMetadata()
    .WithFunctionCalls(true);

await foreach (var content in openAIService.StreamAsync(
    "Calculate the compound interest on $10,000 at 5% for 10 years", options))
{
    switch (content.Type)
    {
        case StreamingContentType.Reasoning:
            Console.ForegroundColor = ConsoleColor.DarkGray;
            Console.Write(content.Content);
            Console.ResetColor();
            break;
        case StreamingContentType.FunctionCall:
            Console.WriteLine($"\n[Calling: {content.Metadata?["function_name"]}]");
            break;
        case StreamingContentType.Text:
            Console.Write(content.Content);
            break;
    }
}

Function Calling

Function Calling allows AI models to execute custom functions during conversations, enabling dynamic interactions with external systems, APIs, and computations.

Overview

When you register functions with the AI service, the model can automatically decide when and how to call them based on the conversation context. The function results are then used to generate more accurate and contextual responses.

Basic Function Registration

Simple Functions

// Parameterless function
var service = new OpenAIService(apiKey, httpClient)
    .WithFunction(
        "get_current_time",
        "Gets the current system time",
        () => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
    );

// Single parameter function
service.WithFunction(
    "get_weather",
    "Gets weather for a location",
    ("location", "City name", required: true),
    (string location) => $"Weather in {location}: Sunny, 22°C"
);

// Multiple parameters
service.WithFunction(
    "calculate",
    "Performs calculation",
    ("x", "First number", true),
    ("y", "Second number", true),
    ("operation", "Operation type", false),
    (double x, double y, string operation = "add") => 
    {
        return operation switch
        {
            "add" => (x + y).ToString(),
            "subtract" => (x - y).ToString(),
            "multiply" => (x * y).ToString(),
            "divide" => (x / y).ToString(),
            _ => "Invalid operation"
        };
    }
);

Async Functions

// Async function with single parameter
service.WithFunctionAsync(
    "fetch_data",
    "Fetches data from API",
    ("endpoint", "API endpoint URL", true),
    async (string endpoint) => 
    {
        var response = await httpClient.GetAsync(endpoint);
        return await response.Content.ReadAsStringAsync();
    }
);

// Async function with multiple parameters
service.WithFunctionAsync(
    "send_email",
    "Sends an email",
    ("to", "Recipient email", true),
    ("subject", "Email subject", true),
    ("body", "Email body", true),
    async (string to, string subject, string body) =>
    {
        await emailService.SendAsync(to, subject, body);
        return $"Email sent to {to}";
    }
);

Attribute-Based Registration

Defining Functions with Attributes

public class WeatherService
{
    private readonly HttpClient _httpClient;
    
    public WeatherService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    
    [AiFunction("get_current_weather", "Gets current weather data for a location")]
    public async Task<string> GetCurrentWeather(
        [AiParameter("The city name", required: true)] string city,
        [AiParameter("Country code (ISO)", required: false)] string country = "US",
        [AiParameter("Temperature unit (celsius/fahrenheit)", required: false)] string unit = "celsius")
    {
        var url = $"https://api.weather.com/v1/weather?city={city}&country={country}";
        var response = await _httpClient.GetAsync(url);
        var data = await response.Content.ReadAsStringAsync();
        
        var weather = JsonSerializer.Deserialize<WeatherData>(data);
        var temp = unit == "fahrenheit" ? weather.TempF : weather.TempC;
        
        return JsonSerializer.Serialize(new
        {
            city = city,
            country = country,
            temperature = temp,
            unit = unit,
            condition = weather.Condition,
            humidity = weather.Humidity,
            wind_speed = weather.WindSpeed
        });
    }
    
    [AiFunction("get_forecast", "Gets weather forecast for multiple days")]
    public string GetForecast(
        [AiParameter("City name")] string city,
        [AiParameter("Number of days (1-7)")] int days = 3)
    {
        var forecast = new List<object>();
        for (int i = 0; i < days; i++)
        {
            forecast.Add(new
            {
                day = DateTime.Now.AddDays(i).DayOfWeek.ToString(),
                high = 20 + i,
                low = 15 + i,
                condition = i % 2 == 0 ? "Sunny" : "Cloudy"
            });
        }
        
        return JsonSerializer.Serialize(forecast);
    }
}

// Register all functions from the class
var weatherService = new WeatherService(httpClient);
var service = new OpenAIService(apiKey, httpClient)
    .WithFunctions(weatherService);

// Register static functions from a class
public static class MathFunctions
{
    [AiFunction("calculate_fibonacci")]
    public static string Fibonacci([AiParameter("The n-th number")] int n)
    {
        if (n <= 0) return "0";
        if (n == 1) return "1";
        
        long a = 0, b = 1;
        for (int i = 2; i <= n; i++)
        {
            long temp = a + b;
            a = b;
            b = temp;
        }
        return b.ToString();
    }
}

service.WithStaticFunctions<MathFunctions>();

Advanced Function Builder

The FunctionBuilder provides fine-grained control over function definitions:

var function = FunctionBuilder.Create("search_products")
    .WithDescription("Searches for products in the catalog")
    .AddParameter(
        name: "query",
        type: "string",
        description: "Search query",
        required: true)
    .AddParameter(
        name: "category",
        type: "string",
        description: "Product category",
        required: false,
        defaultValue: "all")
    .AddParameter(
        name: "min_price",
        type: "number",
        description: "Minimum price",
        required: false,
        defaultValue: 0)
    .AddParameter(
        name: "max_price",
        type: "number",
        description: "Maximum price",
        required: false,
        defaultValue: 999999)
    .AddEnumParameter(
        name: "sort_by",
        description: "Sort results by",
        values: new List<string> { "relevance", "price_low", "price_high", "rating" },
        required: false,
        defaultValue: "relevance")
    .WithHandler(async (Dictionary<string, object> args) =>
    {
        var query = args["query"].ToString();
        var category = args.GetValueOrDefault("category", "all").ToString();
        var minPrice = Convert.ToDouble(args.GetValueOrDefault("min_price", 0));
        var maxPrice = Convert.ToDouble(args.GetValueOrDefault("max_price", 999999));
        var sortBy = args.GetValueOrDefault("sort_by", "relevance").ToString();
        
        var results = await SearchProducts(query, category, minPrice, maxPrice, sortBy);
        return JsonSerializer.Serialize(results);
    })
    .Build();

service.WithFunction(function);

Function Calling Policies

Policies control how functions are executed, including timeouts, retry logic, and concurrency limits.

Pre-defined Policies

// Fast execution for simple operations
service.DefaultPolicy = FunctionCallingPolicy.Fast;
// - MaxRounds: 10
// - TimeoutSeconds: 30
// - MaxConcurrency: 10

// Complex operations with more time
service.DefaultPolicy = FunctionCallingPolicy.Complex;
// - MaxRounds: 50
// - TimeoutSeconds: 300
// - MaxConcurrency: 3

// Vision/image analysis operations
service.DefaultPolicy = FunctionCallingPolicy.Vision;
// - MaxRounds: 20
// - TimeoutSeconds: 200
// - MaxConcurrency: 3

// Unlimited (use with caution)
service.DefaultPolicy = FunctionCallingPolicy.Unlimited;
// - MaxRounds: 100
// - TimeoutSeconds: null (no timeout)
// - MaxConcurrency: 20

Custom Policies

// Create custom policy
service.DefaultPolicy = new FunctionCallingPolicy
{
    MaxRounds = 15,
    TimeoutSeconds = 60,
    MaxConcurrency = 5,
    EnableLogging = true
};

// Clone and modify existing policy
var customPolicy = FunctionCallingPolicy.Default.Clone();
customPolicy.MaxRounds = 30;
customPolicy.EnableLogging = true;
service.DefaultPolicy = customPolicy;

Per-Request Policy Override

// Override policy for specific request
var response = await service
    .WithPolicy(FunctionCallingPolicy.Fast)
    .GetCompletionAsync("Process this quickly");

// Inline policy configuration
var response = await service
    .WithMaxRounds(5)
    .WithTimeout(30)
    .GetCompletionAsync("Limited operation");

// Using message builder
var response = await service
    .BeginMessage()
    .AddText("Analyze this data")
    .WithMaxRounds(10)
    .WithTimeout(120)
    .WithVisionPolicy()
    .SendAsync();

Function Execution Control

Temporarily Disable Functions

// Disable all functions for a request
var response = await service
    .WithoutFunctions()
    .GetCompletionAsync("Don't use functions");

// Alternative method
var response = await service.AskWithoutFunctionsAsync(
    "Process without functions"
);

// Disable functions at service level
service.EnableFunctions = false;

// Quick toggle
service.FunctionsDisabled = true;
var response = await service.GetCompletionAsync("No functions");
service.FunctionsDisabled = false;

Force Specific Function

// Force the AI to always use a specific function
service.FunctionCallMode = FunctionCallMode.Force;
service.ForceFunctionName = "get_weather";

var response = await service.GetCompletionAsync("Any query");

Function Calling in Streaming

var options = StreamOptions.WithFunctions;

await foreach (var content in service.StreamAsync(
    "What's the weather in Seoul and Tokyo?", options))
{
    switch (content.Type)
    {
        case StreamingContentType.Text:
            Console.Write(content.Content);
            break;
            
        case StreamingContentType.FunctionCall:
            if (content.Metadata != null)
                Console.WriteLine($"\n[Calling function: {content.Metadata["function_name"]}]");
            break;
            
        case StreamingContentType.FunctionResult:
            if (content.Metadata != null)
            {
                Console.WriteLine($"[Function completed: {content.Metadata["status"]}]");
                if (content.Metadata.TryGetValue("result", out var result))
                    Console.WriteLine($"[Result: {result}]");
            }
            break;
            
        case StreamingContentType.Completion:
            Console.WriteLine("\n[Stream completed]");
            break;
    }
}

Enhanced Streaming

StreamOptions Configuration

// Pre-defined options
var textOnly     = StreamOptions.TextOnlyOptions;  // Minimal overhead
var withFuncs    = StreamOptions.WithFunctions;    // Include function calls
var full         = StreamOptions.FullOptions;      // All metadata + reasoning
var minimal      = StreamOptions.Minimal;          // Absolutely minimal

// Custom options with fluent API
var customOptions = new StreamOptions()
    .WithMetadata(true)
    .WithFunctionCalls(true)
    .WithReasoning()
    .AsTextOnly(false);

// Or create directly
var options = new StreamOptions
{
    IncludeMetadata      = true,
    IncludeFunctionCalls = true,
    IncludeReasoning     = true,
    TextOnly             = false
};

StreamingContent Structure

public class StreamingContent
{
    public string? Content { get; set; }
    public StreamingContentType Type { get; set; }
    public Dictionary<string, object>? Metadata { get; set; }
}

public enum StreamingContentType
{
    Text,           // Regular text content
    FunctionCall,   // Function is being called
    FunctionResult, // Function execution result
    Status,         // Status message (e.g., finish_reason)
    Error,          // Error occurred
    Completion,     // Stream completed
    Reasoning       // GPT-5 family reasoning data
}

Advanced Streaming Examples

Processing Different Content Types

await foreach (var content in service.StreamAsync(message, StreamOptions.FullOptions))
{
    switch (content.Type)
    {
        case StreamingContentType.Text:
            Console.Write(content.Content);
            break;
            
        case StreamingContentType.Reasoning:
            Console.ForegroundColor = ConsoleColor.DarkGray;
            Console.Write(content.Content);
            Console.ResetColor();
            break;
            
        case StreamingContentType.FunctionCall:
            var funcName = content.Metadata?["function_name"]?.ToString();
            Console.WriteLine($"\n[Calling: {funcName}]");
            break;
            
        case StreamingContentType.FunctionResult:
            Console.WriteLine($"[Function {content.Metadata?["status"]}]");
            break;
            
        case StreamingContentType.Status:
            if (content.Metadata?.TryGetValue("finish_reason", out var reason) == true)
                Console.WriteLine($"[Finish: {reason}]");
            break;
            
        case StreamingContentType.Error:
            Console.WriteLine($"[Error: {content.Metadata?["error"]}]");
            break;
            
        case StreamingContentType.Completion:
            if (content.Metadata?.TryGetValue("total_length", out var length) == true)
                Console.WriteLine($"\n[Completed — {length} chars]");
            break;
    }
}

Metadata Extraction

await foreach (var content in service.StreamAsync(message, StreamOptions.FullOptions))
{
    if (content.Metadata == null) continue;

    if (content.Metadata.TryGetValue("model", out var model))
        Console.WriteLine($"Model: {model}");
    if (content.Metadata.TryGetValue("response_id", out var id))
        Console.WriteLine($"Response ID: {id}");
    if (content.Metadata.TryGetValue("finish_reason", out var reason))
        Console.WriteLine($"Finish: {reason}");
    if (content.Metadata.TryGetValue("usage", out var usage))
        Console.WriteLine($"Usage: {usage}");
}

Complete Examples

Multi-Tool Assistant

public class MultiToolAssistant
{
    private readonly OpenAIService _service;
    private readonly HttpClient _httpClient;
    private readonly IEmailService _emailService;
    private readonly ICalendarService _calendarService;
    
    public MultiToolAssistant(string apiKey, IEmailService emailService, ICalendarService calendarService)
    {
        _httpClient = new HttpClient();
        _service = new OpenAIService(apiKey, _httpClient)
            .WithSystemMessage("You are a helpful personal assistant with access to email, calendar, weather, and calculation tools.");
        
        _emailService = emailService;
        _calendarService = calendarService;
        
        RegisterFunctions();
        
        _service.DefaultPolicy = new FunctionCallingPolicy
        {
            MaxRounds = 20,
            TimeoutSeconds = 60,
            EnableLogging = true
        };
    }
    
    private void RegisterFunctions()
    {
        _service.WithFunctionAsync(
            "send_email",
            "Sends an email",
            ("to", "Recipient email", true),
            ("subject", "Email subject", true),
            ("body", "Email content", true),
            async (string to, string subject, string body) =>
            {
                await _emailService.SendAsync(to, subject, body);
                return $"Email sent to {to} with subject '{subject}'";
            }
        );
        
        _service.WithFunctionAsync(
            "check_emails",
            "Checks for new emails",
            ("folder", "Email folder", false),
            ("unread_only", "Only show unread", false),
            async (string folder = "inbox", bool unreadOnly = true) =>
            {
                var emails = await _emailService.GetEmailsAsync(folder, unreadOnly);
                return JsonSerializer.Serialize(emails);
            }
        );
        
        _service.WithFunctionAsync(
            "create_event",
            "Creates a calendar event",
            ("title", "Event title", true),
            ("date", "Event date (yyyy-mm-dd)", true),
            ("time", "Event time (HH:mm)", true),
            ("duration", "Duration in minutes", false),
            async (string title, string date, string time, int duration = 60) =>
            {
                var eventTime = DateTime.Parse($"{date} {time}");
                var eventId = await _calendarService.CreateEventAsync(title, eventTime, duration);
                return $"Event '{title}' created for {date} at {time} (ID: {eventId})";
            }
        );
        
        _service.WithFunctionAsync(
            "get_events",
            "Gets calendar events",
            ("date", "Date to check (yyyy-mm-dd)", false),
            ("days_ahead", "Number of days to look ahead", false),
            async (string date = null, int daysAhead = 7) =>
            {
                var startDate = string.IsNullOrEmpty(date) ? DateTime.Today : DateTime.Parse(date);
                var events = await _calendarService.GetEventsAsync(startDate, daysAhead);
                return JsonSerializer.Serialize(events);
            }
        );
        
        _service.WithFunctionAsync(
            "get_weather",
            "Gets weather information",
            ("location", "City name", true),
            ("days", "Forecast days", false),
            async (string location, int days = 1) =>
            {
                var url = $"https://api.weather.com/v1/forecast?location={location}&days={days}";
                return await _httpClient.GetStringAsync(url);
            }
        );
        
        _service.WithFunction(
            "calculate",
            "Performs mathematical calculations",
            ("expression", "Math expression to evaluate", true),
            (string expression) =>
            {
                try { return $"Result: {EvaluateExpression(expression)}"; }
                catch (Exception ex) { return $"Error: {ex.Message}"; }
            }
        );
    }
    
    public async Task<string> ProcessRequestAsync(string request)
        => await _service.GetCompletionAsync(request);
    
    public async Task StreamResponseAsync(string request, Action<string> onChunk)
    {
        var options = new StreamOptions().WithMetadata(true).WithFunctionCalls(true);
        
        await foreach (var content in _service.StreamAsync(request, options))
        {
            if (content.Type == StreamingContentType.Text && content.Content != null)
                onChunk(content.Content);
            else if (content.Type == StreamingContentType.FunctionCall)
                onChunk($"\n[Calling {content.Metadata?["function_name"]}...]\n");
        }
    }
}

// Usage
var assistant = new MultiToolAssistant(apiKey, emailService, calendarService);

var response = await assistant.ProcessRequestAsync(
    "Check my calendar for tomorrow, and if I have any meetings before noon, " +
    "send an email to john@example.com letting him know I'll be busy. " +
    "Also, what's the weather forecast for tomorrow?"
);
// The assistant will:
// 1. Call get_events() to check calendar
// 2. Call get_weather() for forecast
// 3. Conditionally call send_email() based on calendar results
// 4. Provide a natural language summary of all actions taken

Database Query Assistant

public class DatabaseAssistant
{
    private readonly AIService _service;
    private readonly string _connectionString;
    
    public DatabaseAssistant(AIService service, string connectionString)
    {
        _service = service;
        _connectionString = connectionString;
        _service.WithFunctions(this);
        _service.DefaultPolicy = FunctionCallingPolicy.Complex;
    }
    
    [AiFunction("query_database", "Executes a SELECT query on the database")]
    public async Task<string> QueryDatabase(
        [AiParameter("SQL SELECT query", required: true)] string query,
        [AiParameter("Maximum rows to return", required: false)] int maxRows = 100)
    {
        if (!query.TrimStart().StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
            return "Error: Only SELECT queries are allowed";
        
        using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync();
        using var command = new SqlCommand(query, connection);
        using var reader = await command.ExecuteReaderAsync();
        
        var results = new List<Dictionary<string, object>>();
        int rowCount = 0;
        
        while (await reader.ReadAsync() && rowCount < maxRows)
        {
            var row = new Dictionary<string, object>();
            for (int i = 0; i < reader.FieldCount; i++)
                row[reader.GetName(i)] = reader.GetValue(i);
            results.Add(row);
            rowCount++;
        }
        
        return JsonSerializer.Serialize(new
        {
            query,
            row_count = rowCount,
            truncated = rowCount >= maxRows,
            data = results
        });
    }
    
    [AiFunction("get_table_schema", "Gets the schema of a database table")]
    public async Task<string> GetTableSchema(
        [AiParameter("Table name", required: true)] string tableName)
    {
        var query = @"
            SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT
            FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_NAME = @tableName
            ORDER BY ORDINAL_POSITION";
        
        using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync();
        using var command = new SqlCommand(query, connection);
        command.Parameters.AddWithValue("@tableName", tableName);
        using var reader = await command.ExecuteReaderAsync();
        
        var columns = new List<object>();
        while (await reader.ReadAsync())
        {
            columns.Add(new
            {
                name = reader["COLUMN_NAME"],
                type = reader["DATA_TYPE"],
                nullable = reader["IS_NULLABLE"],
                max_length = reader["CHARACTER_MAXIMUM_LENGTH"],
                default_value = reader["COLUMN_DEFAULT"]
            });
        }
        
        return JsonSerializer.Serialize(new { table = tableName, columns });
    }
    
    [AiFunction("list_tables", "Lists all tables in the database")]
    public async Task<string> ListTables()
    {
        var query = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'";
        
        using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync();
        using var command = new SqlCommand(query, connection);
        using var reader = await command.ExecuteReaderAsync();
        
        var tables = new List<string>();
        while (await reader.ReadAsync())
            tables.Add(reader.GetString(0));
        
        return JsonSerializer.Serialize(tables);
    }
}

// Usage
var dbAssistant = new DatabaseAssistant(aiService, connectionString);

// Natural language to SQL — AI will:
// 1. Call list_tables() to understand database structure
// 2. Call get_table_schema() for relevant tables
// 3. Construct and execute appropriate SQL query
// 4. Format and present results in natural language
var response = await aiService.GetCompletionAsync(
    "Show me all customers from New York who made purchases over $1000 last month"
);

// Streaming with function calling
await foreach (var content in aiService.StreamAsync(
    "Analyze sales trends by region and provide insights",
    StreamOptions.WithFunctions))
{
    Console.Write(content.Content ?? "");
}

Best Practices

Function Design

  1. Clear Naming: Use descriptive function names that clearly indicate what the function does
  2. Comprehensive Descriptions: Provide detailed descriptions for both functions and parameters
  3. Error Handling: Always return meaningful error messages that the AI can understand
  4. JSON Responses: Return structured JSON for complex data to help AI parse results
  5. Idempotency: Design functions to be safe to retry if needed

Performance Optimization

  1. Policy Selection:

    • Use Fast policy for simple, quick operations
    • Use Complex policy for database queries or API calls
    • Use Vision policy when processing images
    • Create custom policies for specific use cases
  2. Streaming Optimization:

    • Use TextOnlyOptions when metadata isn't needed
    • Use WithFunctions for real-time function feedback
    • Use FullOptions only when debugging or analyzing
    • Use WithReasoning() only when you need to display reasoning data
  3. GPT-5 Family Optimization:

    • Use Gpt5Reasoning.Minimal or Gpt5_2Reasoning.None for simple tasks to reduce latency
    • Use XHigh only for the most complex problems (GPT-5.2/5.3/5.4)
    • Set adequate max_output_tokens (minimum 4096 enforced automatically)
    • Use Verbosity.Low for concise responses
    • Monitor LastReasoningSummary for debugging

Security Considerations

  1. Input Validation: Always validate function inputs before processing
  2. SQL Injection: Use parameterized queries for database operations
  3. API Keys: Never expose sensitive credentials in function responses
  4. Rate Limiting: Implement rate limiting for resource-intensive operations
  5. Permissions: Validate user permissions before executing sensitive functions

Debugging

  1. Enable Logging:
service.DefaultPolicy.EnableLogging = true;
  1. Monitor Execution:
await foreach (var content in service.StreamAsync(message, StreamOptions.FullOptions))
{
    LogStreamingContent(content);
}
  1. Debug Reasoning:
var openAIService = (OpenAIService)service;
openAIService.WithGpt5_2Parameters(
    reasoningEffort: Gpt5_2Reasoning.High,
    reasoningSummary: ReasoningSummary.Detailed
);
var response = await openAIService.GetCompletionAsync("Complex question");
Console.WriteLine($"[Reasoning] {openAIService.LastReasoningSummary}");
Console.WriteLine($"[Answer] {response}");

Troubleshooting

Common Issues

Functions not being called:

  • Verify function descriptions are clear and specific
  • Check that service.EnableFunctions is true
  • Ensure the model supports function calling
  • Try more explicit prompts

Function timeout:

  • Increase TimeoutSeconds in policy
  • Use async functions for long operations
  • Consider breaking complex functions into smaller ones

Incorrect function selection:

  • Improve function names and descriptions
  • Use more specific parameter descriptions

GPT-5 family reasoning exhausts output tokens:

  • Increase max_output_tokens (minimum 4096 enforced automatically)
  • Use lower reasoning effort for simple tasks
  • Check for status=incomplete in response warnings

Empty response from GPT-5 family:

  • Library automatically detects reasoning-only output and returns a descriptive warning
  • Increase max_output_tokens to give room for both reasoning and text output
  • Consider using Gpt5Reasoning.Minimal or Gpt5_2Reasoning.None

GPT-5.1/5.2/5.3/5.4 verbosity not taking effect:

  • Ensure you're using the correct method for your model variant
  • Verify the verbosity value is a valid Verbosity enum: Verbosity.Low, Verbosity.Medium, or Verbosity.High

GPT-5.2-codex or GPT-5.3-codex with None reasoning:

  • These Codex variants do not support None — it will be automatically adjusted to Low

Error Handling

try
{
    await foreach (var content in service.StreamAsync(message, options))
    {
        if (content.Type == StreamingContentType.Error)
        {
            LogError($"Stream error: {content.Metadata?["error"]}");
            break;
        }
        ProcessContent(content);
    }
}
catch (TaskCanceledException)
{
    Console.WriteLine("Stream cancelled or timed out");
}
catch (AIServiceException ex)
{
    Console.WriteLine($"Service error: {ex.Message}");
}