-
Notifications
You must be signed in to change notification settings - Fork 4
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 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.
| 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 |
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;
}
}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:
LastReasoningSummaryis automatically populated when the API returns areasoningoutput item.
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 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 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-codexdoes not supportNonereasoning effort and will be automatically adjusted toLow.
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-codexdoes not supportNonereasoning effort and will be automatically adjusted toLow.
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) |
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.
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 allows AI models to execute custom functions during conversations, enabling dynamic interactions with external systems, APIs, and computations.
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.
// 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 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}";
}
);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>();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);Policies control how functions are executed, including timeouts, retry logic, and concurrency limits.
// 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// 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;// 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();// 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 the AI to always use a specific function
service.FunctionCallMode = FunctionCallMode.Force;
service.ForceFunctionName = "get_weather";
var response = await service.GetCompletionAsync("Any query");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;
}
}// 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
};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
}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;
}
}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}");
}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 takenpublic 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 ?? "");
}- Clear Naming: Use descriptive function names that clearly indicate what the function does
- Comprehensive Descriptions: Provide detailed descriptions for both functions and parameters
- Error Handling: Always return meaningful error messages that the AI can understand
- JSON Responses: Return structured JSON for complex data to help AI parse results
- Idempotency: Design functions to be safe to retry if needed
-
Policy Selection:
- Use
Fastpolicy for simple, quick operations - Use
Complexpolicy for database queries or API calls - Use
Visionpolicy when processing images - Create custom policies for specific use cases
- Use
-
Streaming Optimization:
- Use
TextOnlyOptionswhen metadata isn't needed - Use
WithFunctionsfor real-time function feedback - Use
FullOptionsonly when debugging or analyzing - Use
WithReasoning()only when you need to display reasoning data
- Use
-
GPT-5 Family Optimization:
- Use
Gpt5Reasoning.MinimalorGpt5_2Reasoning.Nonefor simple tasks to reduce latency - Use
XHighonly for the most complex problems (GPT-5.2/5.3/5.4) - Set adequate
max_output_tokens(minimum 4096 enforced automatically) - Use
Verbosity.Lowfor concise responses - Monitor
LastReasoningSummaryfor debugging
- Use
- Input Validation: Always validate function inputs before processing
- SQL Injection: Use parameterized queries for database operations
- API Keys: Never expose sensitive credentials in function responses
- Rate Limiting: Implement rate limiting for resource-intensive operations
- Permissions: Validate user permissions before executing sensitive functions
- Enable Logging:
service.DefaultPolicy.EnableLogging = true;- Monitor Execution:
await foreach (var content in service.StreamAsync(message, StreamOptions.FullOptions))
{
LogStreamingContent(content);
}- 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}");Functions not being called:
- Verify function descriptions are clear and specific
- Check that
service.EnableFunctionsistrue - Ensure the model supports function calling
- Try more explicit prompts
Function timeout:
- Increase
TimeoutSecondsin 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=incompletein response warnings
Empty response from GPT-5 family:
- Library automatically detects reasoning-only output and returns a descriptive warning
- Increase
max_output_tokensto give room for both reasoning and text output - Consider using
Gpt5Reasoning.MinimalorGpt5_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
verbosityvalue is a validVerbosityenum:Verbosity.Low,Verbosity.Medium, orVerbosity.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 toLow
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}");
}