From 99a59a9ba793da1a7a95aaccf1c92948f3481363 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:39:03 +0000 Subject: [PATCH 1/7] Initial plan From d7daa90d172dfa105a2b93d81b16b6088fe03000 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:54:47 +0000 Subject: [PATCH 2/7] Add how-to article for handling invalid tool input with code snippets Co-authored-by: gewarren <24882762+gewarren@users.noreply.github.com> --- docs/ai/how-to/handle-invalid-tool-input.md | 110 ++++++++++++++ .../csharp/Combined.cs | 85 +++++++++++ .../csharp/FunctionInvoker.cs | 143 ++++++++++++++++++ .../csharp/HandleInvalidToolInput.csproj | 18 +++ .../csharp/IncludeDetailedErrors.cs | 45 ++++++ .../csharp/StrictSchema.cs | 45 ++++++ docs/ai/quickstarts/use-function-calling.md | 1 + docs/ai/toc.yml | 2 + 8 files changed, 449 insertions(+) create mode 100644 docs/ai/how-to/handle-invalid-tool-input.md create mode 100644 docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs create mode 100644 docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs create mode 100644 docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/HandleInvalidToolInput.csproj create mode 100644 docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs create mode 100644 docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs diff --git a/docs/ai/how-to/handle-invalid-tool-input.md b/docs/ai/how-to/handle-invalid-tool-input.md new file mode 100644 index 0000000000000..b235e9b6ee642 --- /dev/null +++ b/docs/ai/how-to/handle-invalid-tool-input.md @@ -0,0 +1,110 @@ +--- +title: Handle invalid tool input from AI models +description: Learn strategies to handle invalid tool input when AI models provide incorrect or malformed function call parameters. +ms.date: 01/05/2026 +ai-usage: ai-assisted +--- + +# Handle invalid tool input from AI models + +When AI models call functions in your .NET code, they might sometimes provide invalid input that doesn't match the expected schema. This can happen due to serialization errors, missing required parameters, or incorrect data types. The `Microsoft.Extensions.AI` library provides several strategies to handle these scenarios gracefully. + +## Common scenarios for invalid input + +AI models can provide invalid function call input in several ways: + +- Missing required parameters +- Incorrect data types (for example, sending a string when a number is expected) +- Malformed JSON that can't be deserialized +- Values that violate business rules or constraints + +Without proper error handling, these issues can cause your application to fail or provide poor user experiences. + +## Enable detailed error messages + +By default, when a function invocation fails, the AI model receives a generic error message. You can enable detailed error reporting using the property to send the full exception details back to the AI model. + +### How it works + +When `IncludeDetailedErrors` is set to `true`, the full exception message is added to the chat history if an error occurs during function invocation. This allows the AI model to see what went wrong and potentially self-correct in subsequent attempts. + +:::code language="csharp" source="snippets/handle-invalid-tool-input/IncludeDetailedErrors.cs" id="BasicUsage"::: + +### Security considerations + +Setting `IncludeDetailedErrors` to `true` can expose internal system details to the AI model and potentially to end users. Consider the following: + +- **Development and debugging**: Enable detailed errors to help the AI model understand and fix issues +- **Production environments**: Disable detailed errors to avoid leaking sensitive information +- **Sensitive data**: Ensure exception messages don't contain secrets, connection strings, or other sensitive information + +## Implement custom error handling + +For more control over error handling, you can set a custom delegate. This allows you to intercept function calls, catch exceptions, and return custom error messages to the AI model. + +### Basic custom invoker + +The following example shows how to implement a custom function invoker that catches serialization errors and provides helpful feedback: + +:::code language="csharp" source="snippets/handle-invalid-tool-input/FunctionInvoker.cs" id="BasicInvoker"::: + +### Implement retry logic + +You can extend the custom invoker pattern to implement retry logic. This is useful when you want to give the AI model multiple chances to provide valid input: + +:::code language="csharp" source="snippets/handle-invalid-tool-input/FunctionInvoker.cs" id="RetryInvoker"::: + +### Best practices for retry prompts + +When implementing retry logic, provide clear, actionable feedback to the AI model: + +- **Be specific**: Explain exactly what was wrong with the input +- **Provide examples**: Show the expected format or valid values +- **Set limits**: Avoid infinite retry loops by limiting retry attempts +- **Log attempts**: Track retry attempts for debugging and monitoring + +Example retry messages: + +- ❌ "Invalid input. Please retry." (too vague) +- ✅ "The 'temperature' parameter must be a number between -50 and 50. You provided 'hot'. Please retry with a numeric value." +- ✅ "Missing required parameter 'location'. Please provide a location string and retry." + +## Use strict JSON schema with OpenAI + +When using OpenAI models, you can enable strict JSON schema mode to enforce that the model's output strictly adheres to your function's schema. This helps prevent type mismatches and missing required fields. + +### Enable strict mode + +Strict mode is enabled using the `Strict` additional property on your function metadata. When enabled, OpenAI models try to ensure their output matches your schema exactly: + +:::code language="csharp" source="snippets/handle-invalid-tool-input/StrictSchema.cs" id="StrictMode"::: + +### Supported models + +Strict JSON schema mode is only supported on certain OpenAI models: + +- `gpt-4o-2024-08-06` and later +- `gpt-4o-mini-2024-07-18` and later + +Check the [OpenAI documentation](https://platform.openai.com/docs/guides/structured-outputs) for the latest list of supported models. + +### Limitations + +While strict mode significantly improves schema adherence, keep these limitations in mind: + +- Not all JSON Schema features are supported in strict mode +- Complex schemas might still produce occasional errors +- Always validate outputs even with strict mode enabled +- Strict mode is OpenAI-specific and doesn't apply to other AI providers + +## Combine strategies + +For robust error handling, combine multiple strategies: + +:::code language="csharp" source="snippets/handle-invalid-tool-input/Combined.cs" id="CombinedStrategies"::: + +## Next steps + +- [Access data in AI functions](access-data-in-functions.md) +- [Execute a local .NET function](../quickstarts/use-function-calling.md) +- [Build a chat app](../quickstarts/build-chat-app.md) diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs new file mode 100644 index 0000000000000..7c96c87234b96 --- /dev/null +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs @@ -0,0 +1,85 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using OpenAI; +using System.Text.Json; + +IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); +string? model = config["ModelName"]; +string? key = config["OpenAIKey"]; + +// +IChatClient innerClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o-2024-08-06").AsIChatClient(); + +// Strategy 1: Custom error handling with retry logic +var functionInvokingClient = new FunctionInvokingChatClient(innerClient) +{ + // Strategy 2: Include detailed errors in development + IncludeDetailedErrors = true, + + FunctionInvoker = async (context, cancellationToken) => + { + try + { + return await context.Function.InvokeAsync(context.Arguments, cancellationToken); + } + catch (JsonException ex) + { + return $"JSON parsing error: {ex.Message}. " + + "Ensure all parameters match the expected types and retry."; + } + catch (ArgumentException ex) + { + return $"Validation error: {ex.Message}. Please correct the input and retry."; + } + catch (Exception ex) + { + return $"Unexpected error: {ex.Message}"; + } + } +}; + +IChatClient client = new ChatClientBuilder(functionInvokingClient).Build(); + +// Strategy 3: Use strict schema mode for OpenAI +var bookingFunction = AIFunctionFactory.Create( + (string hotelName, DateTime checkIn, DateTime checkOut, int guests) => + { + if (checkOut <= checkIn) + { + throw new ArgumentException("Check-out date must be after check-in date"); + } + if (guests < 1 || guests > 10) + { + throw new ArgumentException("Number of guests must be between 1 and 10"); + } + + int nights = (checkOut - checkIn).Days; + return $"Booking confirmed at {hotelName} for {guests} guest(s), " + + $"{nights} night(s) from {checkIn:yyyy-MM-dd} to {checkOut:yyyy-MM-dd}"; + }, + new AIFunctionFactoryOptions + { + Name = "book_hotel", + Description = "Books a hotel room with check-in/out dates and number of guests", + // Enable strict schema mode + AdditionalProperties = new Dictionary + { + { "Strict", true } + } + }); + +var chatOptions = new ChatOptions +{ + Tools = [bookingFunction] +}; + +List chatHistory = [ + new(ChatRole.System, "You are a hotel booking assistant. " + + "Always confirm all details before booking."), + new(ChatRole.User, "Book a room at Grand Hotel for 2 guests, " + + "checking in on January 15, 2026 and checking out on January 18, 2026") +]; + +ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); +Console.WriteLine($"Assistant >>> {response.Text}"); +// diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs new file mode 100644 index 0000000000000..8c003f9863793 --- /dev/null +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs @@ -0,0 +1,143 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using OpenAI; +using System.Text.Json; + +IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); +string? model = config["ModelName"]; +string? key = config["OpenAIKey"]; + +// +IChatClient innerClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); + +var functionInvokingClient = new FunctionInvokingChatClient(innerClient) +{ + FunctionInvoker = async (context, cancellationToken) => + { + try + { + // Invoke the function normally + return await context.Function.InvokeAsync(context.Arguments, cancellationToken); + } + catch (JsonException ex) + { + // Catch JSON serialization errors and provide helpful feedback + return $"Error: Unable to parse the function arguments. {ex.Message}. " + + "Please check the parameter types and try again."; + } + catch (ArgumentException ex) + { + // Catch validation errors and provide specific feedback + return $"Error: Invalid argument - {ex.Message}"; + } + catch (Exception ex) + { + // Catch all other errors + return $"Error: Function execution failed - {ex.Message}"; + } + } +}; + +IChatClient client = new ChatClientBuilder(functionInvokingClient).Build(); + +var chatOptions = new ChatOptions +{ + Tools = [AIFunctionFactory.Create((string location, int temperature) => + { + if (string.IsNullOrWhiteSpace(location)) + { + throw new ArgumentException("Location cannot be empty", nameof(location)); + } + return $"Recorded temperature of {temperature}°C for {location}"; + }, + "record_temperature", + "Records the temperature for a location")] +}; + +List chatHistory = [ + new(ChatRole.System, "You are a weather data recorder."), + new(ChatRole.User, "Record the temperature as 25 degrees for London") +]; + +ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); +Console.WriteLine($"Assistant >>> {response.Text}"); +// + +// +IChatClient innerClient2 = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); + +var retryClient = new FunctionInvokingChatClient(innerClient2) +{ + FunctionInvoker = async (context, cancellationToken) => + { + const int maxRetries = 2; + int retryCount = 0; + + while (retryCount <= maxRetries) + { + try + { + return await context.Function.InvokeAsync(context.Arguments, cancellationToken); + } + catch (RetryableException ex) when (retryCount < maxRetries) + { + // Return error message to the AI model and allow it to retry + retryCount++; + return $"Attempt {retryCount} failed: {ex.Message}. Please provide valid input and retry."; + } + catch (JsonException ex) when (retryCount < maxRetries) + { + retryCount++; + return $"Attempt {retryCount} failed: Invalid JSON format - {ex.Message}. " + + "Please check the parameter types match the function signature and retry."; + } + catch (Exception ex) + { + // Don't retry for unexpected errors + return $"Error: {ex.Message}"; + } + } + + return "Maximum retry attempts reached. Unable to execute function."; + } +}; + +IChatClient retryClientBuilt = new ChatClientBuilder(retryClient).Build(); + +var retryOptions = new ChatOptions +{ + Tools = [AIFunctionFactory.Create((decimal amount, string currency) => + { + // Validate the currency code + var validCurrencies = new[] { "USD", "EUR", "GBP", "JPY" }; + if (!validCurrencies.Contains(currency.ToUpper())) + { + throw new RetryableException( + $"Invalid currency '{currency}'. Valid currencies are: {string.Join(", ", validCurrencies)}"); + } + + if (amount <= 0) + { + throw new RetryableException("Amount must be greater than 0"); + } + + return $"Converted {amount} {currency.ToUpper()}"; + }, + "convert_currency", + "Converts an amount from one currency to another")] +}; + +List retryHistory = [ + new(ChatRole.System, "You are a currency converter assistant."), + new(ChatRole.User, "Convert 100 dollars to euros") +]; + +ChatResponse retryResponse = await retryClientBuilt.GetResponseAsync(retryHistory, retryOptions); +Console.WriteLine($"Assistant >>> {retryResponse.Text}"); +// + +// Custom exception for retry scenarios +class RetryableException : Exception +{ + public RetryableException(string message) : base(message) { } +} diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/HandleInvalidToolInput.csproj b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/HandleInvalidToolInput.csproj new file mode 100644 index 0000000000000..89cd890331064 --- /dev/null +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/HandleInvalidToolInput.csproj @@ -0,0 +1,18 @@ + + + + Exe + net9.0 + enable + enable + handle-invalid-tool-input-snippets + + + + + + + + + + diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs new file mode 100644 index 0000000000000..9ad1e345d24b7 --- /dev/null +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using OpenAI; + +// +IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); +string? model = config["ModelName"]; +string? key = config["OpenAIKey"]; + +IChatClient client = new ChatClientBuilder( + new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient()) + .UseFunctionInvocation() + .Build(); + +// Enable detailed error messages to help the AI model self-correct +var functionInvokingClient = client as FunctionInvokingChatClient; +if (functionInvokingClient != null) +{ + functionInvokingClient.IncludeDetailedErrors = true; +} + +var chatOptions = new ChatOptions +{ + Tools = [AIFunctionFactory.Create((int temperature, string location) => + { + // Validate temperature is in a reasonable range + if (temperature < -50 || temperature > 50) + { + throw new ArgumentOutOfRangeException(nameof(temperature), + "Temperature must be between -50 and 50 degrees Celsius."); + } + return $"The temperature in {location} is {temperature}°C"; + }, + "get_temperature", + "Gets the current temperature for a location")] +}; + +List chatHistory = [ + new(ChatRole.System, "You are a helpful weather assistant."), + new(ChatRole.User, "What's the temperature in Paris?") +]; + +ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); +Console.WriteLine($"Assistant >>> {response.Text}"); +// diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs new file mode 100644 index 0000000000000..065de9e9f79ee --- /dev/null +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using OpenAI; + +IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); +string? model = config["ModelName"]; +string? key = config["OpenAIKey"]; + +// +IChatClient client = new ChatClientBuilder( + new OpenAIClient(key).GetChatClient(model ?? "gpt-4o-2024-08-06").AsIChatClient()) + .UseFunctionInvocation() + .Build(); + +// Create a function and enable strict schema mode for OpenAI +var weatherFunction = AIFunctionFactory.Create( + (string location, int temperature, bool isRaining) => + { + string weather = isRaining ? "rainy" : "clear"; + return $"Weather in {location}: {temperature}°C and {weather}"; + }, + new AIFunctionFactoryOptions + { + Name = "report_weather", + Description = "Reports the weather for a location with temperature and rain status", + // Enable strict mode by adding the Strict property + AdditionalProperties = new Dictionary + { + { "Strict", true } + } + }); + +var chatOptions = new ChatOptions +{ + Tools = [weatherFunction] +}; + +List chatHistory = [ + new(ChatRole.System, "You are a weather reporting assistant."), + new(ChatRole.User, "What's the weather in Tokyo? It's 22 degrees and not raining.") +]; + +ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); +Console.WriteLine($"Assistant >>> {response.Text}"); +// diff --git a/docs/ai/quickstarts/use-function-calling.md b/docs/ai/quickstarts/use-function-calling.md index b6b700c2241b9..e1c7b34ffc408 100644 --- a/docs/ai/quickstarts/use-function-calling.md +++ b/docs/ai/quickstarts/use-function-calling.md @@ -140,6 +140,7 @@ If you no longer need them, delete the Azure OpenAI resource and GPT-4 model dep ## Next steps +- [Handle invalid tool input from AI models](../how-to/handle-invalid-tool-input.md) - [Access data in AI functions](../how-to/access-data-in-functions.md) - [Quickstart - Build an AI chat app with .NET](build-chat-app.md) - [Generate text and conversations with .NET and Azure OpenAI Completions](/training/modules/open-ai-dotnet-text-completions/) diff --git a/docs/ai/toc.yml b/docs/ai/toc.yml index c140ef40ed2d5..cbe976e272735 100644 --- a/docs/ai/toc.yml +++ b/docs/ai/toc.yml @@ -68,6 +68,8 @@ items: href: quickstarts/use-function-calling.md - name: Access data in AI functions href: how-to/access-data-in-functions.md + - name: Handle invalid tool input + href: how-to/handle-invalid-tool-input.md - name: Text to image items: - name: Generate images using MEAI From 8e2460556f3f00be64c1015d6d91deaaf592dbe2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:00:35 +0000 Subject: [PATCH 3/7] Fix snippet paths and update target framework to net10.0 Co-authored-by: gewarren <24882762+gewarren@users.noreply.github.com> --- docs/ai/how-to/handle-invalid-tool-input.md | 10 +++++----- .../csharp/HandleInvalidToolInput.csproj | 13 ++++++++++++- .../handle-invalid-tool-input/csharp/Program.cs | 12 ++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Program.cs diff --git a/docs/ai/how-to/handle-invalid-tool-input.md b/docs/ai/how-to/handle-invalid-tool-input.md index b235e9b6ee642..a9c8d068d45e1 100644 --- a/docs/ai/how-to/handle-invalid-tool-input.md +++ b/docs/ai/how-to/handle-invalid-tool-input.md @@ -28,7 +28,7 @@ By default, when a function invocation fails, the AI model receives a generic er When `IncludeDetailedErrors` is set to `true`, the full exception message is added to the chat history if an error occurs during function invocation. This allows the AI model to see what went wrong and potentially self-correct in subsequent attempts. -:::code language="csharp" source="snippets/handle-invalid-tool-input/IncludeDetailedErrors.cs" id="BasicUsage"::: +:::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs" id="BasicUsage"::: ### Security considerations @@ -46,13 +46,13 @@ For more control over error handling, you can set a custom Exe - net9.0 + net10.0 enable enable handle-invalid-tool-input-snippets @@ -15,4 +15,15 @@ + + + + + + + + Never + + + diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Program.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Program.cs new file mode 100644 index 0000000000000..a8de950b5ba02 --- /dev/null +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Program.cs @@ -0,0 +1,12 @@ +// This project contains multiple code snippets demonstrating +// how to handle invalid tool input from AI models. +// +// Each snippet is in a separate file: +// - IncludeDetailedErrors.cs: Enable detailed error messages +// - FunctionInvoker.cs: Custom error handling and retry logic +// - StrictSchema.cs: OpenAI strict JSON schema mode +// - Combined.cs: Combining multiple strategies +// +// To run a specific example, replace this file with the desired snippet file. + +Console.WriteLine("See individual snippet files for examples."); From 0e2a1977c861afb5abf738aca4791a85648844fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:03:48 +0000 Subject: [PATCH 4/7] Fix retry logic and clarify error handling approach Co-authored-by: gewarren <24882762+gewarren@users.noreply.github.com> --- docs/ai/how-to/handle-invalid-tool-input.md | 12 ++--- .../csharp/Combined.cs | 2 +- .../csharp/FunctionInvoker.cs | 50 +++++++++---------- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/docs/ai/how-to/handle-invalid-tool-input.md b/docs/ai/how-to/handle-invalid-tool-input.md index a9c8d068d45e1..aeeeb9ea09c28 100644 --- a/docs/ai/how-to/handle-invalid-tool-input.md +++ b/docs/ai/how-to/handle-invalid-tool-input.md @@ -48,20 +48,20 @@ The following example shows how to implement a custom function invoker that catc :::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs" id="BasicInvoker"::: -### Implement retry logic +### Enable AI model self-correction -You can extend the custom invoker pattern to implement retry logic. This is useful when you want to give the AI model multiple chances to provide valid input: +By returning descriptive error messages instead of throwing exceptions, you allow the AI model to see what went wrong and try again with corrected input. The model will automatically retry the function call with better arguments: :::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs" id="RetryInvoker"::: -### Best practices for retry prompts +### Best practices for error messages -When implementing retry logic, provide clear, actionable feedback to the AI model: +When returning error messages to enable AI self-correction, provide clear, actionable feedback: - **Be specific**: Explain exactly what was wrong with the input - **Provide examples**: Show the expected format or valid values -- **Set limits**: Avoid infinite retry loops by limiting retry attempts -- **Log attempts**: Track retry attempts for debugging and monitoring +- **Use consistent format**: Help the AI model learn from patterns +- **Log errors**: Track error patterns for debugging and monitoring Example retry messages: diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs index 7c96c87234b96..ebdd27f85b9c9 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs @@ -8,7 +8,7 @@ string? key = config["OpenAIKey"]; // -IChatClient innerClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o-2024-08-06").AsIChatClient(); +IChatClient innerClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); // Strategy 1: Custom error handling with retry logic var functionInvokingClient = new FunctionInvokingChatClient(innerClient) diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs index 8c003f9863793..385506d541696 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs @@ -70,35 +70,31 @@ { FunctionInvoker = async (context, cancellationToken) => { - const int maxRetries = 2; - int retryCount = 0; - - while (retryCount <= maxRetries) + try { - try - { - return await context.Function.InvokeAsync(context.Arguments, cancellationToken); - } - catch (RetryableException ex) when (retryCount < maxRetries) - { - // Return error message to the AI model and allow it to retry - retryCount++; - return $"Attempt {retryCount} failed: {ex.Message}. Please provide valid input and retry."; - } - catch (JsonException ex) when (retryCount < maxRetries) - { - retryCount++; - return $"Attempt {retryCount} failed: Invalid JSON format - {ex.Message}. " + - "Please check the parameter types match the function signature and retry."; - } - catch (Exception ex) - { - // Don't retry for unexpected errors - return $"Error: {ex.Message}"; - } + return await context.Function.InvokeAsync(context.Arguments, cancellationToken); + } + catch (RetryableException ex) + { + // Return error message to the AI model to help it retry with valid input + return $"Error: {ex.Message}. Please provide valid input and retry."; + } + catch (JsonException ex) + { + // Return JSON parsing error to help the AI correct the format + return $"Error: Invalid JSON format - {ex.Message}. " + + "Please check the parameter types match the function signature and retry."; + } + catch (ArgumentException ex) + { + // Return validation error to help the AI provide correct values + return $"Error: Invalid argument - {ex.Message}. Please correct the input and retry."; + } + catch (Exception ex) + { + // Don't provide detailed info for unexpected errors + return $"Error: Function execution failed - {ex.Message}"; } - - return "Maximum retry attempts reached. Unable to execute function."; } }; From c512958241ee33c877fd35da26dbdc280b728743 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:06:05 +0000 Subject: [PATCH 5/7] Update StrictSchema to use generic gpt-4o model with explanatory comment Co-authored-by: gewarren <24882762+gewarren@users.noreply.github.com> --- .../snippets/handle-invalid-tool-input/csharp/StrictSchema.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs index 065de9e9f79ee..38679d8b7fb8a 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs @@ -7,8 +7,10 @@ string? key = config["OpenAIKey"]; // +// Note: Strict JSON schema mode requires OpenAI models that support it. +// Ensure your model configuration uses a compatible version (e.g., gpt-4o-2024-08-06 or later). IChatClient client = new ChatClientBuilder( - new OpenAIClient(key).GetChatClient(model ?? "gpt-4o-2024-08-06").AsIChatClient()) + new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient()) .UseFunctionInvocation() .Build(); From 066948f746c07e36680cc2bedee2c57d0aa9d27a Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:08:00 -0800 Subject: [PATCH 6/7] human edits --- docs/ai/how-to/handle-invalid-tool-input.md | 75 ++------ .../csharp/Combined.cs | 85 --------- .../csharp/FunctionInvoker.cs | 163 +++++------------- .../csharp/HandleInvalidToolInput.csproj | 14 +- .../csharp/IncludeDetailedErrors.cs | 58 ++++--- .../csharp/Program.cs | 12 -- .../csharp/StrictSchema.cs | 80 +++++---- 7 files changed, 142 insertions(+), 345 deletions(-) delete mode 100644 docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs delete mode 100644 docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Program.cs diff --git a/docs/ai/how-to/handle-invalid-tool-input.md b/docs/ai/how-to/handle-invalid-tool-input.md index aeeeb9ea09c28..a78547eb2197c 100644 --- a/docs/ai/how-to/handle-invalid-tool-input.md +++ b/docs/ai/how-to/handle-invalid-tool-input.md @@ -7,101 +7,64 @@ ai-usage: ai-assisted # Handle invalid tool input from AI models -When AI models call functions in your .NET code, they might sometimes provide invalid input that doesn't match the expected schema. This can happen due to serialization errors, missing required parameters, or incorrect data types. The `Microsoft.Extensions.AI` library provides several strategies to handle these scenarios gracefully. +When AI models call functions in your .NET code, they might sometimes provide invalid input that doesn't match the expected schema. The `Microsoft.Extensions.AI` library provides several strategies to handle these scenarios gracefully. ## Common scenarios for invalid input AI models can provide invalid function call input in several ways: -- Missing required parameters -- Incorrect data types (for example, sending a string when a number is expected) -- Malformed JSON that can't be deserialized -- Values that violate business rules or constraints +- Missing required parameters. +- Incorrect data types (for example, sending a string when an integer is expected). +- Malformed JSON that can't be deserialized. Without proper error handling, these issues can cause your application to fail or provide poor user experiences. ## Enable detailed error messages -By default, when a function invocation fails, the AI model receives a generic error message. You can enable detailed error reporting using the property to send the full exception details back to the AI model. - -### How it works - -When `IncludeDetailedErrors` is set to `true`, the full exception message is added to the chat history if an error occurs during function invocation. This allows the AI model to see what went wrong and potentially self-correct in subsequent attempts. +By default, when a function invocation fails, the AI model receives a generic error message. You can enable detailed error reporting using the property. When this property is set to `true` and an error occurs during function invocation, the full exception message is added to the chat history. This allows the AI model to see what went wrong and potentially self-correct in subsequent attempts. :::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs" id="BasicUsage"::: -### Security considerations - -Setting `IncludeDetailedErrors` to `true` can expose internal system details to the AI model and potentially to end users. Consider the following: - -- **Development and debugging**: Enable detailed errors to help the AI model understand and fix issues -- **Production environments**: Disable detailed errors to avoid leaking sensitive information -- **Sensitive data**: Ensure exception messages don't contain secrets, connection strings, or other sensitive information +> [!NOTE] +> Setting `IncludeDetailedErrors` to `true` can expose internal system details to the AI model and potentially to end users. Ensure exception messages don't contain secrets, connection strings, or other sensitive information. To avoid leaking sensitive information, consider disabling detailed errors in production environments. ## Implement custom error handling For more control over error handling, you can set a custom delegate. This allows you to intercept function calls, catch exceptions, and return custom error messages to the AI model. -### Basic custom invoker - The following example shows how to implement a custom function invoker that catches serialization errors and provides helpful feedback: :::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs" id="BasicInvoker"::: -### Enable AI model self-correction - -By returning descriptive error messages instead of throwing exceptions, you allow the AI model to see what went wrong and try again with corrected input. The model will automatically retry the function call with better arguments: - -:::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs" id="RetryInvoker"::: +By returning descriptive error messages instead of throwing exceptions, you allow the AI model to see what went wrong and try again with corrected input. ### Best practices for error messages When returning error messages to enable AI self-correction, provide clear, actionable feedback: -- **Be specific**: Explain exactly what was wrong with the input -- **Provide examples**: Show the expected format or valid values -- **Use consistent format**: Help the AI model learn from patterns -- **Log errors**: Track error patterns for debugging and monitoring - -Example retry messages: +- **Be specific**: Explain exactly what was wrong with the input. +- **Provide examples**: Show the expected format or valid values. +- **Use consistent format**: Help the AI model learn from patterns. +- **Log errors**: Track error patterns for debugging and monitoring. -- ❌ "Invalid input. Please retry." (too vague) -- ✅ "The 'temperature' parameter must be a number between -50 and 50. You provided 'hot'. Please retry with a numeric value." -- ✅ "Missing required parameter 'location'. Please provide a location string and retry." - -## Use strict JSON schema with OpenAI +## Use strict JSON schema (OpenAI only) When using OpenAI models, you can enable strict JSON schema mode to enforce that the model's output strictly adheres to your function's schema. This helps prevent type mismatches and missing required fields. -### Enable strict mode - -Strict mode is enabled using the `Strict` additional property on your function metadata. When enabled, OpenAI models try to ensure their output matches your schema exactly: +Enable strict mode using the `Strict` additional property on your function metadata. When enabled, OpenAI models try to ensure their output matches your schema exactly: :::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/StrictSchema.cs" id="StrictMode"::: -### Supported models - -Strict JSON schema mode is only supported on certain OpenAI models: - -- `gpt-4o-2024-08-06` and later -- `gpt-4o-mini-2024-07-18` and later - -Check the [OpenAI documentation](https://platform.openai.com/docs/guides/structured-outputs) for the latest list of supported models. +For the latest list of models that support strict JSON schema, check the [OpenAI documentation](https://platform.openai.com/docs/guides/structured-outputs). ### Limitations While strict mode significantly improves schema adherence, keep these limitations in mind: -- Not all JSON Schema features are supported in strict mode -- Complex schemas might still produce occasional errors -- Always validate outputs even with strict mode enabled -- Strict mode is OpenAI-specific and doesn't apply to other AI providers - -## Combine strategies - -For robust error handling, combine multiple strategies: - -:::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/Combined.cs" id="CombinedStrategies"::: +- Not all JSON Schema features are supported in strict mode. +- Complex schemas might still produce occasional errors. +- Always validate outputs even with strict mode enabled. +- Strict mode is OpenAI-specific and doesn't apply to other AI providers. ## Next steps diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs deleted file mode 100644 index ebdd27f85b9c9..0000000000000 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Combined.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; -using OpenAI; -using System.Text.Json; - -IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); -string? model = config["ModelName"]; -string? key = config["OpenAIKey"]; - -// -IChatClient innerClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); - -// Strategy 1: Custom error handling with retry logic -var functionInvokingClient = new FunctionInvokingChatClient(innerClient) -{ - // Strategy 2: Include detailed errors in development - IncludeDetailedErrors = true, - - FunctionInvoker = async (context, cancellationToken) => - { - try - { - return await context.Function.InvokeAsync(context.Arguments, cancellationToken); - } - catch (JsonException ex) - { - return $"JSON parsing error: {ex.Message}. " + - "Ensure all parameters match the expected types and retry."; - } - catch (ArgumentException ex) - { - return $"Validation error: {ex.Message}. Please correct the input and retry."; - } - catch (Exception ex) - { - return $"Unexpected error: {ex.Message}"; - } - } -}; - -IChatClient client = new ChatClientBuilder(functionInvokingClient).Build(); - -// Strategy 3: Use strict schema mode for OpenAI -var bookingFunction = AIFunctionFactory.Create( - (string hotelName, DateTime checkIn, DateTime checkOut, int guests) => - { - if (checkOut <= checkIn) - { - throw new ArgumentException("Check-out date must be after check-in date"); - } - if (guests < 1 || guests > 10) - { - throw new ArgumentException("Number of guests must be between 1 and 10"); - } - - int nights = (checkOut - checkIn).Days; - return $"Booking confirmed at {hotelName} for {guests} guest(s), " + - $"{nights} night(s) from {checkIn:yyyy-MM-dd} to {checkOut:yyyy-MM-dd}"; - }, - new AIFunctionFactoryOptions - { - Name = "book_hotel", - Description = "Books a hotel room with check-in/out dates and number of guests", - // Enable strict schema mode - AdditionalProperties = new Dictionary - { - { "Strict", true } - } - }); - -var chatOptions = new ChatOptions -{ - Tools = [bookingFunction] -}; - -List chatHistory = [ - new(ChatRole.System, "You are a hotel booking assistant. " + - "Always confirm all details before booking."), - new(ChatRole.User, "Book a room at Grand Hotel for 2 guests, " + - "checking in on January 15, 2026 and checking out on January 18, 2026") -]; - -ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); -Console.WriteLine($"Assistant >>> {response.Text}"); -// diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs index 385506d541696..6c886e374dcee 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs @@ -1,48 +1,52 @@ -using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using OpenAI; using System.Text.Json; -IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); -string? model = config["ModelName"]; -string? key = config["OpenAIKey"]; - -// -IChatClient innerClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); - -var functionInvokingClient = new FunctionInvokingChatClient(innerClient) +class FunctionInvoker { - FunctionInvoker = async (context, cancellationToken) => + static async Task Main() { - try - { - // Invoke the function normally - return await context.Function.InvokeAsync(context.Arguments, cancellationToken); - } - catch (JsonException ex) - { - // Catch JSON serialization errors and provide helpful feedback - return $"Error: Unable to parse the function arguments. {ex.Message}. " + - "Please check the parameter types and try again."; - } - catch (ArgumentException ex) - { - // Catch validation errors and provide specific feedback - return $"Error: Invalid argument - {ex.Message}"; - } - catch (Exception ex) + IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); + string? model = config["ModelName"]; + string? key = config["OpenAIKey"]; + + // + IChatClient innerClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); + + var functionInvokingClient = new FunctionInvokingChatClient(innerClient) { - // Catch all other errors - return $"Error: Function execution failed - {ex.Message}"; - } - } -}; + FunctionInvoker = async (context, cancellationToken) => + { + try + { + // Invoke the function normally. + return await context.Function.InvokeAsync(context.Arguments, cancellationToken); + } + catch (JsonException ex) + { + // Catch JSON serialization errors and provide helpful feedback. + return $"Error: Unable to parse the function arguments. {ex.Message}. " + + "Please check the parameter types and try again."; + } + catch (ArgumentException ex) + { + // Catch validation errors and provide specific feedback. + return $"Error: Invalid argument - {ex.Message}"; + } + catch (Exception ex) + { + // Catch all other errors. + return $"Error: Function execution failed - {ex.Message}"; + } + } + }; -IChatClient client = new ChatClientBuilder(functionInvokingClient).Build(); + IChatClient client = new ChatClientBuilder(functionInvokingClient).Build(); -var chatOptions = new ChatOptions -{ - Tools = [AIFunctionFactory.Create((string location, int temperature) => + var chatOptions = new ChatOptions + { + Tools = [AIFunctionFactory.Create((string location, int temperature) => { if (string.IsNullOrWhiteSpace(location)) { @@ -52,88 +56,15 @@ }, "record_temperature", "Records the temperature for a location")] -}; + }; -List chatHistory = [ - new(ChatRole.System, "You are a weather data recorder."), + List chatHistory = [ + new(ChatRole.System, "You are a weather data recorder."), new(ChatRole.User, "Record the temperature as 25 degrees for London") -]; - -ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); -Console.WriteLine($"Assistant >>> {response.Text}"); -// - -// -IChatClient innerClient2 = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); + ]; -var retryClient = new FunctionInvokingChatClient(innerClient2) -{ - FunctionInvoker = async (context, cancellationToken) => - { - try - { - return await context.Function.InvokeAsync(context.Arguments, cancellationToken); - } - catch (RetryableException ex) - { - // Return error message to the AI model to help it retry with valid input - return $"Error: {ex.Message}. Please provide valid input and retry."; - } - catch (JsonException ex) - { - // Return JSON parsing error to help the AI correct the format - return $"Error: Invalid JSON format - {ex.Message}. " + - "Please check the parameter types match the function signature and retry."; - } - catch (ArgumentException ex) - { - // Return validation error to help the AI provide correct values - return $"Error: Invalid argument - {ex.Message}. Please correct the input and retry."; - } - catch (Exception ex) - { - // Don't provide detailed info for unexpected errors - return $"Error: Function execution failed - {ex.Message}"; - } + ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); + Console.WriteLine($"Assistant >>> {response.Text}"); + // } -}; - -IChatClient retryClientBuilt = new ChatClientBuilder(retryClient).Build(); - -var retryOptions = new ChatOptions -{ - Tools = [AIFunctionFactory.Create((decimal amount, string currency) => - { - // Validate the currency code - var validCurrencies = new[] { "USD", "EUR", "GBP", "JPY" }; - if (!validCurrencies.Contains(currency.ToUpper())) - { - throw new RetryableException( - $"Invalid currency '{currency}'. Valid currencies are: {string.Join(", ", validCurrencies)}"); - } - - if (amount <= 0) - { - throw new RetryableException("Amount must be greater than 0"); - } - - return $"Converted {amount} {currency.ToUpper()}"; - }, - "convert_currency", - "Converts an amount from one currency to another")] -}; - -List retryHistory = [ - new(ChatRole.System, "You are a currency converter assistant."), - new(ChatRole.User, "Convert 100 dollars to euros") -]; - -ChatResponse retryResponse = await retryClientBuilt.GetResponseAsync(retryHistory, retryOptions); -Console.WriteLine($"Assistant >>> {retryResponse.Text}"); -// - -// Custom exception for retry scenarios -class RetryableException : Exception -{ - public RetryableException(string message) : base(message) { } } diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/HandleInvalidToolInput.csproj b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/HandleInvalidToolInput.csproj index 46440db3280f0..c56adaa4960c0 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/HandleInvalidToolInput.csproj +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/HandleInvalidToolInput.csproj @@ -1,11 +1,10 @@ - Exe + Library net10.0 enable enable - handle-invalid-tool-input-snippets @@ -15,15 +14,4 @@ - - - - - - - - Never - - - diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs index 9ad1e345d24b7..1889047aaf318 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs @@ -1,45 +1,51 @@ -using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using OpenAI; -// -IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); -string? model = config["ModelName"]; -string? key = config["OpenAIKey"]; +class IncludeDetailedErrors +{ + static async Task Main() + { + // + IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); + string? model = config["ModelName"]; + string? key = config["OpenAIKey"]; -IChatClient client = new ChatClientBuilder( - new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient()) - .UseFunctionInvocation() - .Build(); + IChatClient client = new ChatClientBuilder( + new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient()) + .UseFunctionInvocation() + .Build(); -// Enable detailed error messages to help the AI model self-correct -var functionInvokingClient = client as FunctionInvokingChatClient; -if (functionInvokingClient != null) -{ - functionInvokingClient.IncludeDetailedErrors = true; -} + // Enable detailed error messages to help the AI model self-correct + var functionInvokingClient = client as FunctionInvokingChatClient; + if (functionInvokingClient != null) + { + functionInvokingClient.IncludeDetailedErrors = true; + } -var chatOptions = new ChatOptions -{ - Tools = [AIFunctionFactory.Create((int temperature, string location) => + var chatOptions = new ChatOptions + { + Tools = [AIFunctionFactory.Create((int temperature, string location) => { // Validate temperature is in a reasonable range if (temperature < -50 || temperature > 50) { - throw new ArgumentOutOfRangeException(nameof(temperature), + throw new ArgumentOutOfRangeException(nameof(temperature), "Temperature must be between -50 and 50 degrees Celsius."); } return $"The temperature in {location} is {temperature}°C"; }, "get_temperature", "Gets the current temperature for a location")] -}; + }; -List chatHistory = [ - new(ChatRole.System, "You are a helpful weather assistant."), + List chatHistory = [ + new(ChatRole.System, "You are a helpful weather assistant."), new(ChatRole.User, "What's the temperature in Paris?") -]; + ]; -ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); -Console.WriteLine($"Assistant >>> {response.Text}"); -// + ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); + Console.WriteLine($"Assistant >>> {response.Text}"); + // + } +} diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Program.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Program.cs deleted file mode 100644 index a8de950b5ba02..0000000000000 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -// This project contains multiple code snippets demonstrating -// how to handle invalid tool input from AI models. -// -// Each snippet is in a separate file: -// - IncludeDetailedErrors.cs: Enable detailed error messages -// - FunctionInvoker.cs: Custom error handling and retry logic -// - StrictSchema.cs: OpenAI strict JSON schema mode -// - Combined.cs: Combining multiple strategies -// -// To run a specific example, replace this file with the desired snippet file. - -Console.WriteLine("See individual snippet files for examples."); diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs index 38679d8b7fb8a..cedcfb755366e 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs @@ -1,47 +1,53 @@ -using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using OpenAI; -IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); -string? model = config["ModelName"]; -string? key = config["OpenAIKey"]; +class StrictSchema +{ + static async Task Main() + { + IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); + string? model = config["ModelName"]; + string? key = config["OpenAIKey"]; -// -// Note: Strict JSON schema mode requires OpenAI models that support it. -// Ensure your model configuration uses a compatible version (e.g., gpt-4o-2024-08-06 or later). -IChatClient client = new ChatClientBuilder( - new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient()) - .UseFunctionInvocation() - .Build(); + // + // Note: Strict JSON schema mode requires OpenAI models that support it. + // Ensure your model configuration uses a compatible version (e.g., gpt-4o-2024-08-06 or later). + IChatClient client = new ChatClientBuilder( + new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient()) + .UseFunctionInvocation() + .Build(); -// Create a function and enable strict schema mode for OpenAI -var weatherFunction = AIFunctionFactory.Create( - (string location, int temperature, bool isRaining) => - { - string weather = isRaining ? "rainy" : "clear"; - return $"Weather in {location}: {temperature}°C and {weather}"; - }, - new AIFunctionFactoryOptions - { - Name = "report_weather", - Description = "Reports the weather for a location with temperature and rain status", - // Enable strict mode by adding the Strict property - AdditionalProperties = new Dictionary - { + // Create a function and enable strict schema mode for OpenAI + var weatherFunction = AIFunctionFactory.Create( + (string location, int temperature, bool isRaining) => + { + string weather = isRaining ? "rainy" : "clear"; + return $"Weather in {location}: {temperature}°C and {weather}"; + }, + new AIFunctionFactoryOptions + { + Name = "report_weather", + Description = "Reports the weather for a location with temperature and rain status", + // Enable strict mode by adding the Strict property + AdditionalProperties = new Dictionary + { { "Strict", true } - } - }); + } + }); -var chatOptions = new ChatOptions -{ - Tools = [weatherFunction] -}; + var chatOptions = new ChatOptions + { + Tools = [weatherFunction] + }; -List chatHistory = [ - new(ChatRole.System, "You are a weather reporting assistant."), + List chatHistory = [ + new(ChatRole.System, "You are a weather reporting assistant."), new(ChatRole.User, "What's the weather in Tokyo? It's 22 degrees and not raining.") -]; + ]; -ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); -Console.WriteLine($"Assistant >>> {response.Text}"); -// + ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); + Console.WriteLine($"Assistant >>> {response.Text}"); + // + } +} From 8166dffc4290ef8bd432bc8d8eb95fccef286c86 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Tue, 6 Jan 2026 11:40:30 -0800 Subject: [PATCH 7/7] shorten snippoets --- docs/ai/how-to/handle-invalid-tool-input.md | 2 +- .../csharp/FunctionInvoker.cs | 13 +++---- .../csharp/IncludeDetailedErrors.cs | 36 +++++++++---------- .../csharp/StrictSchema.cs | 17 +++++---- 4 files changed, 30 insertions(+), 38 deletions(-) diff --git a/docs/ai/how-to/handle-invalid-tool-input.md b/docs/ai/how-to/handle-invalid-tool-input.md index a78547eb2197c..c2a26c0022425 100644 --- a/docs/ai/how-to/handle-invalid-tool-input.md +++ b/docs/ai/how-to/handle-invalid-tool-input.md @@ -36,7 +36,7 @@ The following example shows how to implement a custom function invoker that catc :::code language="csharp" source="snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs" id="BasicInvoker"::: -By returning descriptive error messages instead of throwing exceptions, you allow the AI model to see what went wrong and try again with corrected input. +By returning descriptive error messages instead of throwing exceptions, you allow the AI model to see what went wrong and try again with corrected input. ### Best practices for error messages diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs index 6c886e374dcee..93d4ffbb050f1 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/FunctionInvoker.cs @@ -12,9 +12,9 @@ static async Task Main() string? key = config["OpenAIKey"]; // - IChatClient innerClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); + IChatClient chatClient = new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient(); - var functionInvokingClient = new FunctionInvokingChatClient(innerClient) + var functionInvokingClient = new FunctionInvokingChatClient(chatClient) { FunctionInvoker = async (context, cancellationToken) => { @@ -27,12 +27,7 @@ static async Task Main() { // Catch JSON serialization errors and provide helpful feedback. return $"Error: Unable to parse the function arguments. {ex.Message}. " + - "Please check the parameter types and try again."; - } - catch (ArgumentException ex) - { - // Catch validation errors and provide specific feedback. - return $"Error: Invalid argument - {ex.Message}"; + "Check the parameter types and try again."; } catch (Exception ex) { @@ -41,6 +36,7 @@ static async Task Main() } } }; + // IChatClient client = new ChatClientBuilder(functionInvokingClient).Build(); @@ -65,6 +61,5 @@ static async Task Main() ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); Console.WriteLine($"Assistant >>> {response.Text}"); - // } } diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs index 1889047aaf318..2eff5cde6aa30 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/IncludeDetailedErrors.cs @@ -6,7 +6,6 @@ class IncludeDetailedErrors { static async Task Main() { - // IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); string? model = config["ModelName"]; string? key = config["OpenAIKey"]; @@ -16,27 +15,27 @@ static async Task Main() .UseFunctionInvocation() .Build(); - // Enable detailed error messages to help the AI model self-correct - var functionInvokingClient = client as FunctionInvokingChatClient; - if (functionInvokingClient != null) - { - functionInvokingClient.IncludeDetailedErrors = true; - } + // + // Enable detailed error messages to help the AI model self-correct. + FunctionInvokingChatClient? functionInvokingClient = client as FunctionInvokingChatClient; + functionInvokingClient?.IncludeDetailedErrors = true; + // var chatOptions = new ChatOptions { Tools = [AIFunctionFactory.Create((int temperature, string location) => - { - // Validate temperature is in a reasonable range - if (temperature < -50 || temperature > 50) - { - throw new ArgumentOutOfRangeException(nameof(temperature), - "Temperature must be between -50 and 50 degrees Celsius."); - } - return $"The temperature in {location} is {temperature}°C"; - }, - "get_temperature", - "Gets the current temperature for a location")] + { + // Validate temperature is in a reasonable range. + if (temperature < -50 || temperature > 50) + { + throw new ArgumentOutOfRangeException( + nameof(temperature), + "Temperature must be between -50 and 50 degrees Celsius."); + } + return $"The temperature in {location} is {temperature}°C"; + }, + "get_temperature", + "Gets the current temperature for a location")] }; List chatHistory = [ @@ -46,6 +45,5 @@ static async Task Main() ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); Console.WriteLine($"Assistant >>> {response.Text}"); - // } } diff --git a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs index cedcfb755366e..480aa966cacf0 100644 --- a/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs +++ b/docs/ai/how-to/snippets/handle-invalid-tool-input/csharp/StrictSchema.cs @@ -10,16 +10,14 @@ static async Task Main() string? model = config["ModelName"]; string? key = config["OpenAIKey"]; - // - // Note: Strict JSON schema mode requires OpenAI models that support it. // Ensure your model configuration uses a compatible version (e.g., gpt-4o-2024-08-06 or later). IChatClient client = new ChatClientBuilder( new OpenAIClient(key).GetChatClient(model ?? "gpt-4o").AsIChatClient()) .UseFunctionInvocation() .Build(); - // Create a function and enable strict schema mode for OpenAI - var weatherFunction = AIFunctionFactory.Create( + // + AIFunction weatherFunction = AIFunctionFactory.Create( (string location, int temperature, bool isRaining) => { string weather = isRaining ? "rainy" : "clear"; @@ -29,12 +27,14 @@ static async Task Main() { Name = "report_weather", Description = "Reports the weather for a location with temperature and rain status", - // Enable strict mode by adding the Strict property + // Enable strict mode by adding the Strict property (OpenAI only). AdditionalProperties = new Dictionary { - { "Strict", true } + { "Strict", true } } - }); + } + ); + // var chatOptions = new ChatOptions { @@ -43,11 +43,10 @@ static async Task Main() List chatHistory = [ new(ChatRole.System, "You are a weather reporting assistant."), - new(ChatRole.User, "What's the weather in Tokyo? It's 22 degrees and not raining.") + new(ChatRole.User, "What's the weather in Tokyo? It's 22 degrees and not raining.") ]; ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); Console.WriteLine($"Assistant >>> {response.Text}"); - // } }