Microsoft Agent Framework Microsoft.Extensions.AI Created: 01 Mar 2026 Updated: 01 Mar 2026

Handle Invalid Tool Input from AI Models

AI models sometimes call your .NET functions with invalid arguments — a court number that is out of range, a string where an integer is expected, or malformed JSON that cannot be deserialized. Without proper handling these failures crash the application or produce a poor user experience. Microsoft.Extensions.AI provides three strategies to deal with this gracefully.

Key Concepts

1. IncludeDetailedErrors

By default, when a function throws an exception, the model receives only a generic error notice. Set IncludeDetailedErrors = true on FunctionInvokingChatClient to forward the full exception message to the model. The model can then read the message and retry the call with corrected arguments:

var client = new FunctionInvokingChatClient(baseClient)
{
IncludeDetailedErrors = true
};

Caution: exception messages may contain internal details. Do not enable this in production if exception text could expose secrets or sensitive system information.

2. Custom FunctionInvoker

Set FunctionInvoker to a custom async delegate that wraps the normal call in a try/catch. Instead of letting exceptions propagate, return a descriptive string — the model receives that string as the function result and can self-correct:

var client = new FunctionInvokingChatClient(baseClient)
{
FunctionInvoker = async (context, cancellationToken) =>
{
try
{
return await context.Function.InvokeAsync(context.Arguments, cancellationToken);
}
catch (ArgumentOutOfRangeException ex)
{
return $"Invalid argument: {ex.Message} Please use a value in range and retry.";
}
catch (JsonException ex)
{
return $"Could not parse arguments: {ex.Message} Check parameter types.";
}
catch (Exception ex)
{
return $"Function execution failed: {ex.Message}";
}
}
};

3. Strict JSON Schema (OpenAI only)

Pass Strict = true in AIFunctionFactoryOptions.AdditionalProperties when creating an AIFunction. OpenAI models then enforce that their output strictly matches the function's parameter schema, reducing type mismatches before the function is even called:

AIFunction logWorkout = AIFunctionFactory.Create(
(string exerciseType, double durationMinutes, int intensityLevel) => ...,
new AIFunctionFactoryOptions
{
Name = "LogWorkout",
Description = "Logs a workout. intensityLevel must be 1–5.",
AdditionalProperties = new Dictionary<string, object?> { { "Strict", true } }
});

Full Example

using Microsoft.Extensions.AI;
using OpenAI;
using System.Text.Json;

namespace MicrosoftAgentFrameworkLesson.ConsoleApp.FunctionCalling;

/// <summary>
/// Demonstrates three strategies for handling invalid tool input from AI models:
/// 1. IncludeDetailedErrors — surfaces exception messages so the model can self-correct
/// 2. Custom FunctionInvoker — intercepts exceptions and returns structured error feedback
/// 3. Strict JSON Schema — constrains the model's output to match the function schema
/// Scenario: Fitness center assistant with court booking and workout logging.
/// </summary>
public static class InvalidToolInputDemo
{
// Throws on an invalid court number so we can demonstrate error-handling strategies.
private static string BookCourt(string memberId, string sport, int courtNumber)
{
if (courtNumber < 1 || courtNumber > 8)
throw new ArgumentOutOfRangeException(nameof(courtNumber),
$"Court number must be between 1 and 8. Received: {courtNumber}.");

return $"Member {memberId} booked court {courtNumber} for {sport}.";
}

public static async Task RunAsync()
{
var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY")
?? throw new InvalidOperationException("Set OPEN_AI_KEY environment variable.");

Console.WriteLine("====== Handle Invalid Tool Input — Fitness Center Assistant ======\n");

IChatClient baseClient = new OpenAIClient(apiKey)
.GetChatClient("gpt-4o-mini")
.AsIChatClient();

AIFunction bookCourt = AIFunctionFactory.Create(
BookCourt,
"BookCourt",
"Books a sports court. memberId is the member ID, sport is the sport name, " +
"courtNumber must be an integer between 1 and 8.");

// -----------------------------------------------------------------------
// Demo 1: IncludeDetailedErrors
// When a function throws, the full exception message is sent back to the model
// so it can see exactly what went wrong and retry with corrected arguments.
// -----------------------------------------------------------------------
Console.WriteLine("--- Demo 1: IncludeDetailedErrors ---");
var clientWithDetails = new FunctionInvokingChatClient(baseClient)
{
IncludeDetailedErrors = true
};

ChatResponse r1 = await clientWithDetails.GetResponseAsync(
"Book court 15 for member M001 to play tennis.",
new ChatOptions { Tools = [bookCourt] });
Console.WriteLine($"Assistant: {r1.Text}");
Console.WriteLine();

// -----------------------------------------------------------------------
// Demo 2: Custom FunctionInvoker
// Replaces the default invocation path with a try/catch that returns
// a descriptive error string instead of letting the exception propagate.
// The model receives the error text and can self-correct.
// -----------------------------------------------------------------------
Console.WriteLine("--- Demo 2: Custom FunctionInvoker ---");
var clientWithCustomInvoker = new FunctionInvokingChatClient(baseClient)
{
FunctionInvoker = async (context, cancellationToken) =>
{
try
{
return await context.Function.InvokeAsync(context.Arguments, cancellationToken);
}
catch (ArgumentOutOfRangeException ex)
{
return $"Invalid argument: {ex.Message} " +
"Please use a courtNumber in the range 1–8 and try again.";
}
catch (JsonException ex)
{
return $"Could not parse arguments: {ex.Message} " +
"Check parameter types and retry.";
}
catch (Exception ex)
{
return $"Function execution failed: {ex.Message}";
}
}
};

ChatResponse r2 = await clientWithCustomInvoker.GetResponseAsync(
"Book court 99 for member M002 for badminton.",
new ChatOptions { Tools = [bookCourt] });
Console.WriteLine($"Assistant: {r2.Text}");
Console.WriteLine();

// -----------------------------------------------------------------------
// Demo 3: Strict JSON Schema (OpenAI only)
// Setting Strict = true in AdditionalProperties tells the OpenAI model to
// adhere exactly to the declared parameter schema, reducing type mismatches.
// -----------------------------------------------------------------------
Console.WriteLine("--- Demo 3: Strict JSON Schema ---");
AIFunction logWorkout = AIFunctionFactory.Create(
(string exerciseType, double durationMinutes, int intensityLevel) =>
{
if (intensityLevel < 1 || intensityLevel > 5)
return $"Invalid intensity level '{intensityLevel}'. Must be 1–5.";

double calories = durationMinutes * intensityLevel * 5.5;
return $"{exerciseType} for {durationMinutes} min at intensity {intensityLevel}: " +
$"~{calories:N0} kcal burned.";
},
new AIFunctionFactoryOptions
{
Name = "LogWorkout",
Description = "Logs a workout session and estimates calories burned. " +
"intensityLevel must be an integer between 1 and 5.",
AdditionalProperties = new Dictionary<string, object?> { { "Strict", true } }
});

IChatClient strictClient = new ChatClientBuilder(baseClient)
.UseFunctionInvocation()
.Build();

ChatResponse r3 = await strictClient.GetResponseAsync(
"Log a 45-minute cycling session for member M003 at intensity level 4.",
new ChatOptions { Tools = [logWorkout] });
Console.WriteLine($"Assistant: {r3.Text}");
Console.WriteLine();
}
}
Share this lesson: