From 23e7d2c4ab72e15107851f7cdd6c73388ce016b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Nov 2025 01:09:25 +0000 Subject: [PATCH 01/13] Initial plan From e372665d21987dca1b52709e657e89f904f67b32 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Nov 2025 01:20:20 +0000 Subject: [PATCH 02/13] Add comprehensive AIFunction data passing documentation Co-authored-by: gewarren <24882762+gewarren@users.noreply.github.com> --- .../openai/AdvancedDataPassing.cs | 213 ++++++++++++++++++ docs/ai/quickstarts/use-function-calling.md | 76 +++++++ 2 files changed, 289 insertions(+) create mode 100644 docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs diff --git a/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs b/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs new file mode 100644 index 0000000000000..f5881c99fcc7e --- /dev/null +++ b/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs @@ -0,0 +1,213 @@ +// +using Microsoft.Extensions.AI; + +// Function that accepts AIFunctionArguments to access all arguments +AIFunction functionWithArgs = AIFunctionFactory.Create( + (AIFunctionArguments args) => + { + // Access named parameters from the arguments dictionary + string location = args["location"]?.ToString() ?? "Unknown"; + string unit = args["unit"]?.ToString() ?? "celsius"; + + // Access the Context dictionary for additional data + if (args.Context.TryGetValue("userId", out var userId)) + { + Console.WriteLine($"Getting weather for user: {userId}"); + } + + return $"Weather in {location}: 22°{unit}"; + }, + name: "get_weather", + description: "Get the current weather"); +// + +// +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.AI; + +// Setup dependency injection +var services = new ServiceCollection(); +services.AddSingleton(); +IServiceProvider serviceProvider = services.BuildServiceProvider(); + +// Function that accepts IServiceProvider to resolve dependencies +AIFunction functionWithDI = AIFunctionFactory.Create( + (string location, IServiceProvider services) => + { + var weatherService = services.GetRequiredService(); + return weatherService.GetWeather(location); + }, + name: "get_weather", + description: "Get the current weather"); + +// When invoking, provide the service provider +var args = new AIFunctionArguments +{ + ["location"] = "Seattle", + Services = serviceProvider +}; +// + +// +using Microsoft.Extensions.AI; + +// Function that uses CurrentContext to access invocation metadata +AIFunction functionWithContext = AIFunctionFactory.Create( + (string location) => + { + // Access the current function invocation context + var context = FunctionInvokingChatClient.CurrentContext; + + if (context != null) + { + Console.WriteLine($"Function: {context.Function.Metadata.Name}"); + Console.WriteLine($"Call ID: {context.CallId}"); + + // Access chat history or other context data + foreach (var message in context.ChatHistory) + { + Console.WriteLine($"Previous message: {message.Text}"); + } + } + + return $"Weather in {location}: Sunny, 75°F"; + }, + name: "get_weather", + description: "Get the current weather"); +// + +// +using System.Reflection; +using Microsoft.Extensions.AI; + +// Custom parameter binding to source data from Context dictionary +var options = new AIFunctionFactoryOptions +{ + ConfigureParameterBinding = (ParameterInfo parameter) => + { + // Bind 'userId' parameter from Context instead of arguments + if (parameter.Name == "userId") + { + return new AIFunctionFactoryOptions.ParameterBindingOptions + { + // Custom binding logic + BindParameter = (paramInfo, args) => + { + // Get value from Context dictionary + if (args.Context.TryGetValue("userId", out var value)) + { + return value; + } + + // Return default if not found + return paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; + } + }; + } + + // Use default binding for other parameters + return default; + } +}; + +// Create function with custom parameter binding +AIFunction functionWithCustomBinding = AIFunctionFactory.Create( + method: (string location, string userId) => + { + Console.WriteLine($"Getting weather for user {userId} in {location}"); + return $"Weather in {location}: Cloudy, 65°F"; + }, + options: options); + +// When invoking, pass userId via Context +var customArgs = new AIFunctionArguments +{ + ["location"] = "Portland", + Context = { ["userId"] = "user123" } +}; +// + +// When invoking, pass userId via Context +var customArgs = new AIFunctionArguments +{ + ["location"] = "Portland", + Context = { ["userId"] = "user123" } +}; +// + +// +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OpenAI; + +// Setup services +var services = new ServiceCollection(); +services.AddSingleton(); +IServiceProvider provider = services.BuildServiceProvider(); + +// Configure AI client +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(); + +// Create function with dependency injection +var chatOptions = new ChatOptions +{ + Tools = [AIFunctionFactory.Create( + (string location, IServiceProvider services) => + { + var userContext = services.GetRequiredService(); + Console.WriteLine($"User {userContext.UserId} requesting weather for {location}"); + return $"Weather in {location}: 70°F and sunny"; + }, + "get_weather", + "Get the current weather in a given location")] +}; + +// Prepare chat with service provider +List chatHistory = [ + new(ChatRole.System, "You are a helpful weather assistant.") +]; +chatHistory.Add(new ChatMessage(ChatRole.User, "What's the weather in Boston?")); + +// Pass service provider when making the request +var functionArgs = new AIFunctionArguments +{ + Services = provider +}; + +ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); +Console.WriteLine($"Response: {response.Text}"); +// + +// +// Supporting types for examples +public interface IWeatherService +{ + string GetWeather(string location); +} + +public class WeatherService : IWeatherService +{ + public string GetWeather(string location) + { + return $"Weather in {location}: Partly cloudy, 68°F"; + } +} + +public interface IUserContext +{ + string UserId { get; } +} + +public class UserContext : IUserContext +{ + public string UserId { get; set; } = "default-user"; +} +// diff --git a/docs/ai/quickstarts/use-function-calling.md b/docs/ai/quickstarts/use-function-calling.md index ca51d59d16a31..2ec82d9ce5988 100644 --- a/docs/ai/quickstarts/use-function-calling.md +++ b/docs/ai/quickstarts/use-function-calling.md @@ -143,6 +143,82 @@ If you no longer need them, delete the Azure OpenAI resource and GPT-4 model dep :::zone-end +## Pass data to AI functions + +When creating AI functions, you often need to access contextual data beyond the parameters provided by the AI model. The `Microsoft.Extensions.AI` library provides several mechanisms to pass data to function delegates. + +### Access function arguments + +You can access all function arguments, including additional context data, by adding an parameter to your function delegate. + +The `AIFunctionArguments` type provides: + +- A dictionary of named arguments supplied by the AI model. +- A `Context` property for passing loosely-typed additional data. +- A `Services` property for accessing dependency injection services. + +The following example shows how to use `AIFunctionArguments`: + +:::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="UsingAIFunctionArguments"::: + +Parameters of type `AIFunctionArguments` aren't included in the JSON schema sent to the AI model because they're supplied by your code, not by the AI. + +### Access dependency injection services + +Functions can access services from a dependency injection container by adding an parameter. This is useful for accessing databases, HTTP clients, logging, or other services registered in your application's service collection. + +The following example shows how to use `IServiceProvider` in a function: + +:::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="UsingIServiceProvider"::: + +When invoking the function, provide the service provider via the `Services` property of `AIFunctionArguments`: + +```csharp +var args = new AIFunctionArguments +{ + ["location"] = "Seattle", + Services = serviceProvider +}; +``` + +Parameters of type `IServiceProvider` aren't included in the JSON schema sent to the AI model. If the parameter is optional (has a default value), the `Services` property is allowed to be `null`. Otherwise, it must be non-`null`, or the invocation fails with an exception. + +### Access invocation context + +During function execution, you can access the current invocation context using the static property. This property provides access to: + +- Function metadata +- Call ID +- Chat history +- Other contextual information about the current invocation + +The following example shows how to use `CurrentContext`: + +:::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="UsingCurrentContext"::: + +The `CurrentContext` value flows across async calls and is only available during function invocation. + +### Custom parameter binding + +For advanced scenarios, you can customize how function parameters are bound using . This allows you to: + +- Source parameter values from the `Context` dictionary instead of function arguments. +- Exclude parameters from the JSON schema. +- Implement custom binding logic. + +The following example shows how to bind a `userId` parameter from the `Context` dictionary: + +:::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="CustomParameterBinding"::: + +Custom parameter binding is useful when you need to pass trusted or validated data that shouldn't be supplied by the AI model. + +### Best practices + +- Use `IServiceProvider` for accessing application services like databases, logging, and HTTP clients. +- Use the `Context` dictionary in `AIFunctionArguments` for passing loosely-typed contextual data. +- Use custom parameter binding to exclude sensitive or trusted parameters from the AI model's JSON schema. +- Data supplied via the arguments dictionary from an AI service should be considered unvalidated and untrusted. Use service provider or custom binding for trusted data. + ## Next steps - [Quickstart - Build an AI chat app with .NET](build-chat-app.md) From ec638e66ef9fca9600b556daa90756119107ea57 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Thu, 13 Nov 2025 17:52:19 -0800 Subject: [PATCH 03/13] human edits --- .../openai/AdvancedDataPassing.cs | 89 +++++++------------ docs/ai/quickstarts/use-function-calling.md | 39 +++----- 2 files changed, 44 insertions(+), 84 deletions(-) diff --git a/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs b/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs index f5881c99fcc7e..3f84d12b7da0f 100644 --- a/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs +++ b/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs @@ -1,20 +1,22 @@ -// using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OpenAI; -// Function that accepts AIFunctionArguments to access all arguments +// AIFunction functionWithArgs = AIFunctionFactory.Create( (AIFunctionArguments args) => { - // Access named parameters from the arguments dictionary + // Access named parameters from the arguments dictionary. string location = args["location"]?.ToString() ?? "Unknown"; string unit = args["unit"]?.ToString() ?? "celsius"; - - // Access the Context dictionary for additional data + + // Access the Context dictionary for additional data. if (args.Context.TryGetValue("userId", out var userId)) { Console.WriteLine($"Getting weather for user: {userId}"); } - + return $"Weather in {location}: 22°{unit}"; }, name: "get_weather", @@ -22,15 +24,12 @@ // // -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.AI; - -// Setup dependency injection +// Set up dependency injection. var services = new ServiceCollection(); services.AddSingleton(); IServiceProvider serviceProvider = services.BuildServiceProvider(); -// Function that accepts IServiceProvider to resolve dependencies +// Function that accepts IServiceProvider to resolve dependencies. AIFunction functionWithDI = AIFunctionFactory.Create( (string location, IServiceProvider services) => { @@ -40,7 +39,7 @@ name: "get_weather", description: "Get the current weather"); -// When invoking, provide the service provider +// When invoking, provide the service provider. var args = new AIFunctionArguments { ["location"] = "Seattle", @@ -49,27 +48,25 @@ // // -using Microsoft.Extensions.AI; - -// Function that uses CurrentContext to access invocation metadata +// Function that uses CurrentContext to access invocation metadata. AIFunction functionWithContext = AIFunctionFactory.Create( (string location) => { - // Access the current function invocation context + // Access the current function invocation context. var context = FunctionInvokingChatClient.CurrentContext; - + if (context != null) { Console.WriteLine($"Function: {context.Function.Metadata.Name}"); Console.WriteLine($"Call ID: {context.CallId}"); - - // Access chat history or other context data + + // Access chat history or other context data. foreach (var message in context.ChatHistory) { Console.WriteLine($"Previous message: {message.Text}"); } } - + return $"Weather in {location}: Sunny, 75°F"; }, name: "get_weather", @@ -77,40 +74,37 @@ // // -using System.Reflection; -using Microsoft.Extensions.AI; - -// Custom parameter binding to source data from Context dictionary +// Custom parameter binding to source data from Context dictionary. var options = new AIFunctionFactoryOptions { ConfigureParameterBinding = (ParameterInfo parameter) => { - // Bind 'userId' parameter from Context instead of arguments + // Bind 'userId' parameter from Context instead of arguments. if (parameter.Name == "userId") { return new AIFunctionFactoryOptions.ParameterBindingOptions { - // Custom binding logic + // Custom binding logic. BindParameter = (paramInfo, args) => { - // Get value from Context dictionary + // Get value from Context dictionary. if (args.Context.TryGetValue("userId", out var value)) { return value; } - - // Return default if not found + + // Return default if not found. return paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; } }; } - - // Use default binding for other parameters + + // Use default binding for other parameters. return default; } }; -// Create function with custom parameter binding +// Create function with custom parameter binding. AIFunction functionWithCustomBinding = AIFunctionFactory.Create( method: (string location, string userId) => { @@ -119,7 +113,7 @@ }, options: options); -// When invoking, pass userId via Context +// When invoking, pass userId via Context. var customArgs = new AIFunctionArguments { ["location"] = "Portland", @@ -127,26 +121,12 @@ }; // -// When invoking, pass userId via Context -var customArgs = new AIFunctionArguments -{ - ["location"] = "Portland", - Context = { ["userId"] = "user123" } -}; -// - -// -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using OpenAI; - -// Setup services +// Set up services. var services = new ServiceCollection(); services.AddSingleton(); IServiceProvider provider = services.BuildServiceProvider(); -// Configure AI client +// Configure AI client. IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); string? model = config["ModelName"]; string? key = config["OpenAIKey"]; @@ -156,7 +136,7 @@ .UseFunctionInvocation() .Build(); -// Create function with dependency injection +// Create function with dependency injection. var chatOptions = new ChatOptions { Tools = [AIFunctionFactory.Create( @@ -170,13 +150,13 @@ "Get the current weather in a given location")] }; -// Prepare chat with service provider +// Prepare chat with service provider. List chatHistory = [ new(ChatRole.System, "You are a helpful weather assistant.") ]; chatHistory.Add(new ChatMessage(ChatRole.User, "What's the weather in Boston?")); -// Pass service provider when making the request +// Pass service provider when making the request. var functionArgs = new AIFunctionArguments { Services = provider @@ -184,10 +164,8 @@ ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); Console.WriteLine($"Response: {response.Text}"); -// -// -// Supporting types for examples +// Supporting types for examples. public interface IWeatherService { string GetWeather(string location); @@ -210,4 +188,3 @@ public class UserContext : IUserContext { public string UserId { get; set; } = "default-user"; } -// diff --git a/docs/ai/quickstarts/use-function-calling.md b/docs/ai/quickstarts/use-function-calling.md index 2ec82d9ce5988..99534437826b9 100644 --- a/docs/ai/quickstarts/use-function-calling.md +++ b/docs/ai/quickstarts/use-function-calling.md @@ -1,7 +1,7 @@ --- title: Quickstart - Extend OpenAI using Tools and execute a local Function with .NET description: Create a simple chat app using OpenAI and extend the model to execute a local function. -ms.date: 03/13/2025 +ms.date: 11/13/2025 ms.topic: quickstart zone_pivot_groups: openai-library # CustomerIntent: As a .NET developer new to OpenAI, I want deploy and use sample code to interact to learn from the sample code how to extend the model using Tools. @@ -132,17 +132,6 @@ The app uses the [`Microsoft.Extensions.AI`](https://www.nuget.org/packages/Micr The app prints the completion response from the AI model, which includes data provided by the .NET function. The AI model understood that the registered function was available and called it automatically to generate a proper response. -:::zone target="docs" pivot="azure-openai" - -## Clean up resources - -If you no longer need them, delete the Azure OpenAI resource and GPT-4 model deployment. - -1. In the [Azure Portal](https://aka.ms/azureportal), navigate to the Azure OpenAI resource. -1. Select the Azure OpenAI resource, and then select **Delete**. - -:::zone-end - ## Pass data to AI functions When creating AI functions, you often need to access contextual data beyond the parameters provided by the AI model. The `Microsoft.Extensions.AI` library provides several mechanisms to pass data to function delegates. @@ -154,7 +143,7 @@ You can access all function arguments, including additional context data, by add The `AIFunctionArguments` type provides: - A dictionary of named arguments supplied by the AI model. -- A `Context` property for passing loosely-typed additional data. +- A `Context` property for passing loosely typed additional data. - A `Services` property for accessing dependency injection services. The following example shows how to use `AIFunctionArguments`: @@ -171,16 +160,6 @@ The following example shows how to use `IServiceProvider` in a function: :::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="UsingIServiceProvider"::: -When invoking the function, provide the service provider via the `Services` property of `AIFunctionArguments`: - -```csharp -var args = new AIFunctionArguments -{ - ["location"] = "Seattle", - Services = serviceProvider -}; -``` - Parameters of type `IServiceProvider` aren't included in the JSON schema sent to the AI model. If the parameter is optional (has a default value), the `Services` property is allowed to be `null`. Otherwise, it must be non-`null`, or the invocation fails with an exception. ### Access invocation context @@ -212,12 +191,16 @@ The following example shows how to bind a `userId` parameter from the `Context` Custom parameter binding is useful when you need to pass trusted or validated data that shouldn't be supplied by the AI model. -### Best practices +:::zone target="docs" pivot="azure-openai" + +## Clean up resources -- Use `IServiceProvider` for accessing application services like databases, logging, and HTTP clients. -- Use the `Context` dictionary in `AIFunctionArguments` for passing loosely-typed contextual data. -- Use custom parameter binding to exclude sensitive or trusted parameters from the AI model's JSON schema. -- Data supplied via the arguments dictionary from an AI service should be considered unvalidated and untrusted. Use service provider or custom binding for trusted data. +If you no longer need them, delete the Azure OpenAI resource and GPT-4 model deployment. + +1. In the [Azure Portal](https://aka.ms/azureportal), navigate to the Azure OpenAI resource. +1. Select the Azure OpenAI resource, and then select **Delete**. + +:::zone-end ## Next steps From 6fe5961d38ddab00a3ddf5e7cf3dc513c29117f6 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:39:00 -0800 Subject: [PATCH 04/13] add new how-to on accessing data --- docs/ai/how-to/access-data-in-functions.md | 41 ++++ .../snippets/access-data/ArgumentsExample.cs | 92 +++++++++ .../ai/how-to/snippets/access-data/Program.cs | 2 + .../snippets/access-data/Project.csproj | 21 ++ .../quickstarts/includes/create-ai-service.md | 1 + .../quickstarts/includes/semantic-kernel.md | 2 - .../azure-openai/BasicExample.cs | 52 +++++ .../azure-openai/FunctionCallingAI.csproj | 2 +- .../function-calling/azure-openai/Program.cs | 44 +--- .../openai/AdvancedDataPassing.cs | 190 ------------------ .../function-calling/openai/Program.cs | 2 +- docs/ai/quickstarts/use-function-calling.md | 70 +------ docs/ai/toc.yml | 18 +- 13 files changed, 227 insertions(+), 310 deletions(-) create mode 100644 docs/ai/how-to/access-data-in-functions.md create mode 100644 docs/ai/how-to/snippets/access-data/ArgumentsExample.cs create mode 100644 docs/ai/how-to/snippets/access-data/Program.cs create mode 100644 docs/ai/how-to/snippets/access-data/Project.csproj delete mode 100644 docs/ai/quickstarts/includes/semantic-kernel.md create mode 100644 docs/ai/quickstarts/snippets/function-calling/azure-openai/BasicExample.cs delete mode 100644 docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs diff --git a/docs/ai/how-to/access-data-in-functions.md b/docs/ai/how-to/access-data-in-functions.md new file mode 100644 index 0000000000000..38c21ee2dcbe8 --- /dev/null +++ b/docs/ai/how-to/access-data-in-functions.md @@ -0,0 +1,41 @@ +--- +title: +description: +ms.date: 11/13/2025 +--- + +# Access data in AI functions + +When you create AI functions, you might need to access contextual data beyond the parameters provided by the AI model. The `Microsoft.Extensions.AI` library provides several mechanisms to pass data to function delegates. + +You can associate data with the function at the time it's created, either via closure or via . If you're creating your own function, you can populate `AdditionalProperties` however you want. If you use to create the function, you can populate data using . + +## Manual function invocation + +If you manually invoke an by calling , you pass in . The type includes: + +- A dictionary of named arguments. +- : An arbitrary `IDictionary` for passing additional ambient data into the function. +- : An that lets the `AIFunction` resolve arbitrary state from a [dependency injection (DI)](../../core/extensions/dependency-injection.md) container. + +If you want to access either the `AIFunctionArguments` or the `IServiceProvider` from within your delegate, create a parameter typed as `IServiceProvider` or `AIFunctionArguments`. That parameter will be bound to the relevant data from the `AIFunctionArguments` passed to `AIFunction.InvokeAsync()`. + +The following code shows an example: + +:::code language="csharp" source="snippets/access-data/ArgumentsExample.cs" id="UseAIFunctionArguments"::: + +## Invocation through `FunctionInvokingChatClient` + +If you use to invoke functions automatically, that client configures an object that it passes into the `AIFunction`. Because `AIFunctionArguments` includes the `IServiceProvider` that the `FunctionInvokingChatClient` was itself provided with, if you construct your client using standard DI means, that `IServiceProvider` is passed all the way into your `AIFunction`. At that point, you can query it for anything you want from DI. + +`FunctionInvokingChatClient` also publishes state about the current invocation to , including not only the arguments, but all of the input `ChatMessage` objects, the , and details on which function is being invoked (out of how many). You can add any data you want into and extract that inside of your `AIFunction` from `FunctionInvokingChatClient.CurrentContext.Options.AdditionalProperties`. + +The following code shows an example: + +:::code language="csharp" source="snippets/access-data/ArgumentsExample.cs" id="UseAdditionalProperties"::: + +## Advanced techniques + +If you want more fine-grained control over how parameters are bound, you can use , which puts you in control over how each parameter is populated. For example, the [MCP C# SDK uses this technique](https://github.com/modelcontextprotocol/csharp-sdk/blob/d344c651203841ec1c9e828736d234a6e4aebd07/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs#L83-L107) to automatically bind parameters from DI. + +If you use the overload, you can also run your own arbitrary logic when you create the target object that the instance method will be called on, each time. And you can do whatever you want to configure that instance. diff --git a/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs b/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs new file mode 100644 index 0000000000000..0ef821fee190d --- /dev/null +++ b/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs @@ -0,0 +1,92 @@ +// +using Azure; +using Azure.AI.OpenAI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; + +class ArgumentsExample +{ + public static async Task RunManual() + { + // + Delegate getWeatherDelegate = (AIFunctionArguments args) => + { + // Access named parameters from the arguments dictionary. + string? location = args.TryGetValue("location", out object? loc) ? loc.ToString() : "Unknown"; + string? units = args.TryGetValue("units", out object? u) ? u.ToString() : "celsius"; + + return $"Weather in {location}: 35°{units}"; + }; + + // Create the AIFunction. + AIFunction getWeather = AIFunctionFactory.Create(getWeatherDelegate); + + // Call the function manually. + var result = await getWeather.InvokeAsync(new AIFunctionArguments + { + { "location", "Seattle" }, + { "units", "F" } + }); + Console.WriteLine($"Function result: {result}"); + // + } + public static async Task UseFICC() + { + IConfigurationRoot config = new ConfigurationBuilder() + .AddUserSecrets() + .Build(); + + string endpoint = config["AZURE_OPENAI_ENDPOINT"]; + string apiKey = config["AZURE_OPENAI_API_KEY"]; + string model = config["AZURE_OPENAI_GPT_NAME"]; + + FunctionInvokingChatClient client = new FunctionInvokingChatClient( + new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) + .GetChatClient(model).AsIChatClient()); + + AIFunctionFactoryOptions functionFactoryOptions = new() + { + Name = "get_weather", + Description = "Gets the current weather", + AdditionalProperties = new Dictionary + { + { "location", "Seattle" }, + { "units", "F" } + } + }; + + AIFunction getWeather = AIFunctionFactory.Create(() => + { + // Access named parameters from the arguments dictionary. + AdditionalPropertiesDictionary props = FunctionInvokingChatClient.CurrentContext.Options.AdditionalProperties; + + string location = props["location"].ToString(); + string units = props["units"].ToString(); + + return $"Weather in {location}: 35°{units}"; + }); + + var chatOptions = new ChatOptions + { + Tools = [getWeather], + AdditionalProperties = new AdditionalPropertiesDictionary { ["location"] = "Seattle", ["units"] = "F" }, + }; + + // Prepare chat with service provider. + List chatHistory = [ + new(ChatRole.System, "You're a helpful weather assistant.") + ]; + chatHistory.Add(new ChatMessage(ChatRole.User, "What's the weather like?")); + + ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); + Console.WriteLine($"Response: {response.Text}"); + + + //IChatClient client = + // new ChatClientBuilder( + // new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) + // .GetChatClient(model).AsIChatClient()) + // .UseFunctionInvocation() + // .Build(); + } +} diff --git a/docs/ai/how-to/snippets/access-data/Program.cs b/docs/ai/how-to/snippets/access-data/Program.cs new file mode 100644 index 0000000000000..b475775d3536b --- /dev/null +++ b/docs/ai/how-to/snippets/access-data/Program.cs @@ -0,0 +1,2 @@ +//await ArgumentsExample.RunManual(); +await ArgumentsExample.UseFICC(); diff --git a/docs/ai/how-to/snippets/access-data/Project.csproj b/docs/ai/how-to/snippets/access-data/Project.csproj new file mode 100644 index 0000000000000..48ee9eb76a00b --- /dev/null +++ b/docs/ai/how-to/snippets/access-data/Project.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + enable + enable + 4d162886-0da5-4b62-a4db-d09780d06911 + + + + + + + + + + + + + diff --git a/docs/ai/quickstarts/includes/create-ai-service.md b/docs/ai/quickstarts/includes/create-ai-service.md index d000ddb2f3c88..0290feaddcd67 100644 --- a/docs/ai/quickstarts/includes/create-ai-service.md +++ b/docs/ai/quickstarts/includes/create-ai-service.md @@ -10,4 +10,5 @@ dotnet user-secrets init dotnet user-secrets set AZURE_OPENAI_ENDPOINT dotnet user-secrets set AZURE_OPENAI_GPT_NAME + dotnet user-secrets set AZURE_OPENAI_API_KEY ``` diff --git a/docs/ai/quickstarts/includes/semantic-kernel.md b/docs/ai/quickstarts/includes/semantic-kernel.md deleted file mode 100644 index e8fd9cd552194..0000000000000 --- a/docs/ai/quickstarts/includes/semantic-kernel.md +++ /dev/null @@ -1,2 +0,0 @@ -> [!NOTE] -> You can also use [Semantic Kernel](/semantic-kernel) to accomplish the tasks in this article. Semantic Kernel is a lightweight, open-source SDK that lets you build AI agents and integrate the latest AI models into your .NET apps. diff --git a/docs/ai/quickstarts/snippets/function-calling/azure-openai/BasicExample.cs b/docs/ai/quickstarts/snippets/function-calling/azure-openai/BasicExample.cs new file mode 100644 index 0000000000000..cd2cbd5f30014 --- /dev/null +++ b/docs/ai/quickstarts/snippets/function-calling/azure-openai/BasicExample.cs @@ -0,0 +1,52 @@ +using Azure; +using Azure.AI.OpenAI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; + +class BasicExample +{ + public static async Task Run() + { + // + IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); + string endpoint = config["AZURE_OPENAI_ENDPOINT"]; + string deployment = config["AZURE_OPENAI_GPT_NAME"]; + string apiKey = config["AZURE_OPENAI_API_KEY"]; + + IChatClient client = + new ChatClientBuilder( + new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) + .GetChatClient(deployment).AsIChatClient()) + .UseFunctionInvocation() + .Build(); + // + + // Add a new plugin with a local .NET function + // that should be available to the AI model. + var chatOptions = new ChatOptions + { + Tools = [AIFunctionFactory.Create((string location, string unit) => + { + // Here you would call a weather API to + // get the weather for the location. + return "Periods of rain or drizzle, 15 C"; + }, + "get_current_weather", + "Get the current weather in a given location")] + }; + + // System prompt to provide context. + List chatHistory = [new(ChatRole.System, """ + You are a hiking enthusiast who helps people discover fun hikes in their area. You are upbeat and friendly. + """)]; + + // Weather conversation relevant to the registered function. + chatHistory.Add(new ChatMessage(ChatRole.User, + "I live in Montreal and I'm looking for a moderate intensity hike. What's the current weather like? ")); + Console.WriteLine($"{chatHistory.Last().Role} >>> {chatHistory.Last()}"); + + var response = await client.GetResponseAsync(chatHistory, chatOptions); + chatHistory.Add(new ChatMessage(ChatRole.Assistant, response.Text)); + Console.WriteLine($"{chatHistory.Last().Role} >>> {chatHistory.Last()}"); + } +} diff --git a/docs/ai/quickstarts/snippets/function-calling/azure-openai/FunctionCallingAI.csproj b/docs/ai/quickstarts/snippets/function-calling/azure-openai/FunctionCallingAI.csproj index e4747a83d5ed9..48ee9eb76a00b 100644 --- a/docs/ai/quickstarts/snippets/function-calling/azure-openai/FunctionCallingAI.csproj +++ b/docs/ai/quickstarts/snippets/function-calling/azure-openai/FunctionCallingAI.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/ai/quickstarts/snippets/function-calling/azure-openai/Program.cs b/docs/ai/quickstarts/snippets/function-calling/azure-openai/Program.cs index a8d3a53750ed0..0c1e76389e781 100644 --- a/docs/ai/quickstarts/snippets/function-calling/azure-openai/Program.cs +++ b/docs/ai/quickstarts/snippets/function-calling/azure-openai/Program.cs @@ -1,43 +1 @@ -// -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.AI; -using Azure.AI.OpenAI; -using Azure.Identity; - -var config = new ConfigurationBuilder().AddUserSecrets().Build(); -string endpoint = config["AZURE_OPENAI_ENDPOINT"]; -string deployment = config["AZURE_OPENAI_GPT_NAME"]; - -IChatClient client = - new ChatClientBuilder( - new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) - .GetChatClient(deployment).AsIChatClient()) - .UseFunctionInvocation() - .Build(); -// - -// Add a new plugin with a local .NET function that should be available to the AI model -var chatOptions = new ChatOptions -{ - Tools = [AIFunctionFactory.Create((string location, string unit) => - { - // Here you would call a weather API to get the weather for the location - return "Periods of rain or drizzle, 15 C"; - }, - "get_current_weather", - "Get the current weather in a given location")] -}; - -// System prompt to provide context -List chatHistory = [new(ChatRole.System, """ - You are a hiking enthusiast who helps people discover fun hikes in their area. You are upbeat and friendly. - """)]; - -// Weather conversation relevant to the registered function -chatHistory.Add(new ChatMessage(ChatRole.User, - "I live in Montreal and I'm looking for a moderate intensity hike. What's the current weather like? ")); -Console.WriteLine($"{chatHistory.Last().Role} >>> {chatHistory.Last()}"); - -var response = await client.GetResponseAsync(chatHistory, chatOptions); -chatHistory.Add(new ChatMessage(ChatRole.Assistant, response.Text)); -Console.WriteLine($"{chatHistory.Last().Role} >>> {chatHistory.Last()}"); +await BasicExample.Run(); diff --git a/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs b/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs deleted file mode 100644 index 3f84d12b7da0f..0000000000000 --- a/docs/ai/quickstarts/snippets/function-calling/openai/AdvancedDataPassing.cs +++ /dev/null @@ -1,190 +0,0 @@ -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using OpenAI; - -// -AIFunction functionWithArgs = AIFunctionFactory.Create( - (AIFunctionArguments args) => - { - // Access named parameters from the arguments dictionary. - string location = args["location"]?.ToString() ?? "Unknown"; - string unit = args["unit"]?.ToString() ?? "celsius"; - - // Access the Context dictionary for additional data. - if (args.Context.TryGetValue("userId", out var userId)) - { - Console.WriteLine($"Getting weather for user: {userId}"); - } - - return $"Weather in {location}: 22°{unit}"; - }, - name: "get_weather", - description: "Get the current weather"); -// - -// -// Set up dependency injection. -var services = new ServiceCollection(); -services.AddSingleton(); -IServiceProvider serviceProvider = services.BuildServiceProvider(); - -// Function that accepts IServiceProvider to resolve dependencies. -AIFunction functionWithDI = AIFunctionFactory.Create( - (string location, IServiceProvider services) => - { - var weatherService = services.GetRequiredService(); - return weatherService.GetWeather(location); - }, - name: "get_weather", - description: "Get the current weather"); - -// When invoking, provide the service provider. -var args = new AIFunctionArguments -{ - ["location"] = "Seattle", - Services = serviceProvider -}; -// - -// -// Function that uses CurrentContext to access invocation metadata. -AIFunction functionWithContext = AIFunctionFactory.Create( - (string location) => - { - // Access the current function invocation context. - var context = FunctionInvokingChatClient.CurrentContext; - - if (context != null) - { - Console.WriteLine($"Function: {context.Function.Metadata.Name}"); - Console.WriteLine($"Call ID: {context.CallId}"); - - // Access chat history or other context data. - foreach (var message in context.ChatHistory) - { - Console.WriteLine($"Previous message: {message.Text}"); - } - } - - return $"Weather in {location}: Sunny, 75°F"; - }, - name: "get_weather", - description: "Get the current weather"); -// - -// -// Custom parameter binding to source data from Context dictionary. -var options = new AIFunctionFactoryOptions -{ - ConfigureParameterBinding = (ParameterInfo parameter) => - { - // Bind 'userId' parameter from Context instead of arguments. - if (parameter.Name == "userId") - { - return new AIFunctionFactoryOptions.ParameterBindingOptions - { - // Custom binding logic. - BindParameter = (paramInfo, args) => - { - // Get value from Context dictionary. - if (args.Context.TryGetValue("userId", out var value)) - { - return value; - } - - // Return default if not found. - return paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; - } - }; - } - - // Use default binding for other parameters. - return default; - } -}; - -// Create function with custom parameter binding. -AIFunction functionWithCustomBinding = AIFunctionFactory.Create( - method: (string location, string userId) => - { - Console.WriteLine($"Getting weather for user {userId} in {location}"); - return $"Weather in {location}: Cloudy, 65°F"; - }, - options: options); - -// When invoking, pass userId via Context. -var customArgs = new AIFunctionArguments -{ - ["location"] = "Portland", - Context = { ["userId"] = "user123" } -}; -// - -// Set up services. -var services = new ServiceCollection(); -services.AddSingleton(); -IServiceProvider provider = services.BuildServiceProvider(); - -// Configure AI client. -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(); - -// Create function with dependency injection. -var chatOptions = new ChatOptions -{ - Tools = [AIFunctionFactory.Create( - (string location, IServiceProvider services) => - { - var userContext = services.GetRequiredService(); - Console.WriteLine($"User {userContext.UserId} requesting weather for {location}"); - return $"Weather in {location}: 70°F and sunny"; - }, - "get_weather", - "Get the current weather in a given location")] -}; - -// Prepare chat with service provider. -List chatHistory = [ - new(ChatRole.System, "You are a helpful weather assistant.") -]; -chatHistory.Add(new ChatMessage(ChatRole.User, "What's the weather in Boston?")); - -// Pass service provider when making the request. -var functionArgs = new AIFunctionArguments -{ - Services = provider -}; - -ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); -Console.WriteLine($"Response: {response.Text}"); - -// Supporting types for examples. -public interface IWeatherService -{ - string GetWeather(string location); -} - -public class WeatherService : IWeatherService -{ - public string GetWeather(string location) - { - return $"Weather in {location}: Partly cloudy, 68°F"; - } -} - -public interface IUserContext -{ - string UserId { get; } -} - -public class UserContext : IUserContext -{ - public string UserId { get; set; } = "default-user"; -} diff --git a/docs/ai/quickstarts/snippets/function-calling/openai/Program.cs b/docs/ai/quickstarts/snippets/function-calling/openai/Program.cs index d515895d5c11c..5238a08618d6d 100644 --- a/docs/ai/quickstarts/snippets/function-calling/openai/Program.cs +++ b/docs/ai/quickstarts/snippets/function-calling/openai/Program.cs @@ -25,7 +25,7 @@ return "Periods of rain or drizzle, 15 C"; }, "get_current_weather", - "Get the current weather in a given location")] + "Gets the current weather in a given location")] }; // diff --git a/docs/ai/quickstarts/use-function-calling.md b/docs/ai/quickstarts/use-function-calling.md index 99534437826b9..4d3df5a134cd6 100644 --- a/docs/ai/quickstarts/use-function-calling.md +++ b/docs/ai/quickstarts/use-function-calling.md @@ -9,7 +9,7 @@ zone_pivot_groups: openai-library # Invoke .NET functions using an AI model -In this quickstart, you create a .NET console AI chat app to connect to an AI model with local function calling enabled. The app uses the library so you can write code using AI abstractions rather than a specific SDK. AI abstractions enable you to change the underlying AI model with minimal code changes. +In this quickstart, you create a .NET console AI chat app that connects to an AI model with local function calling enabled. The app uses the library so you can write code using AI abstractions rather than a specific SDK. AI abstractions enable you to change the underlying AI model with minimal code changes. :::zone target="docs" pivot="openai" @@ -23,8 +23,6 @@ In this quickstart, you create a .NET console AI chat app to connect to an AI mo :::zone-end -[!INCLUDE [semantic-kernel](includes/semantic-kernel.md)] - ## Create the app Complete the following steps to create a .NET console app to connect to an AI model. @@ -49,7 +47,6 @@ Complete the following steps to create a .NET console app to connect to an AI mo dotnet add package Azure.Identity dotnet add package Azure.AI.OpenAI dotnet add package Microsoft.Extensions.AI - dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease dotnet add package Microsoft.Extensions.Configuration dotnet add package Microsoft.Extensions.Configuration.UserSecrets ``` @@ -103,10 +100,7 @@ The app uses the [`Microsoft.Extensions.AI`](https://www.nuget.org/packages/Micr :::zone target="docs" pivot="azure-openai" - :::code language="csharp" source="snippets/function-calling/azure-openai/program.cs" id="GetChatClient"::: - - > [!NOTE] - > searches for authentication credentials from your local tooling. If you aren't using the `azd` template to provision the Azure OpenAI resource, you'll need to assign the `Azure AI Developer` role to the account you used to sign in to Visual Studio or the Azure CLI. For more information, see [Authenticate to Azure AI services with .NET](../azure-ai-services-authentication.md). + :::code language="csharp" source="snippets/function-calling/azure-openai/BasicExample.cs" id="GetChatClient"::: :::zone-end @@ -132,65 +126,6 @@ The app uses the [`Microsoft.Extensions.AI`](https://www.nuget.org/packages/Micr The app prints the completion response from the AI model, which includes data provided by the .NET function. The AI model understood that the registered function was available and called it automatically to generate a proper response. -## Pass data to AI functions - -When creating AI functions, you often need to access contextual data beyond the parameters provided by the AI model. The `Microsoft.Extensions.AI` library provides several mechanisms to pass data to function delegates. - -### Access function arguments - -You can access all function arguments, including additional context data, by adding an parameter to your function delegate. - -The `AIFunctionArguments` type provides: - -- A dictionary of named arguments supplied by the AI model. -- A `Context` property for passing loosely typed additional data. -- A `Services` property for accessing dependency injection services. - -The following example shows how to use `AIFunctionArguments`: - -:::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="UsingAIFunctionArguments"::: - -Parameters of type `AIFunctionArguments` aren't included in the JSON schema sent to the AI model because they're supplied by your code, not by the AI. - -### Access dependency injection services - -Functions can access services from a dependency injection container by adding an parameter. This is useful for accessing databases, HTTP clients, logging, or other services registered in your application's service collection. - -The following example shows how to use `IServiceProvider` in a function: - -:::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="UsingIServiceProvider"::: - -Parameters of type `IServiceProvider` aren't included in the JSON schema sent to the AI model. If the parameter is optional (has a default value), the `Services` property is allowed to be `null`. Otherwise, it must be non-`null`, or the invocation fails with an exception. - -### Access invocation context - -During function execution, you can access the current invocation context using the static property. This property provides access to: - -- Function metadata -- Call ID -- Chat history -- Other contextual information about the current invocation - -The following example shows how to use `CurrentContext`: - -:::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="UsingCurrentContext"::: - -The `CurrentContext` value flows across async calls and is only available during function invocation. - -### Custom parameter binding - -For advanced scenarios, you can customize how function parameters are bound using . This allows you to: - -- Source parameter values from the `Context` dictionary instead of function arguments. -- Exclude parameters from the JSON schema. -- Implement custom binding logic. - -The following example shows how to bind a `userId` parameter from the `Context` dictionary: - -:::code language="csharp" source="snippets/function-calling/openai/AdvancedDataPassing.cs" id="CustomParameterBinding"::: - -Custom parameter binding is useful when you need to pass trusted or validated data that shouldn't be supplied by the AI model. - :::zone target="docs" pivot="azure-openai" ## Clean up resources @@ -204,5 +139,6 @@ If you no longer need them, delete the Azure OpenAI resource and GPT-4 model dep ## Next steps +- [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 bded8c4e59964..96ee66d741da4 100644 --- a/docs/ai/toc.yml +++ b/docs/ai/toc.yml @@ -54,6 +54,18 @@ items: href: conceptual/rag.md - name: OpenAI function calling href: conceptual/understanding-openai-functions.md +- name: Call functions + items: + - name: "Quickstart: Execute a local function" + href: quickstarts/use-function-calling.md + - name: Access data in AI functions + href: quickstarts/access-data-in-functions.md +- name: Text to image + items: + - name: Generate images using MEAI + href: quickstarts/text-to-image.md + - name: Generate images using OpenAI.Images.ImageClient + href: quickstarts/generate-images.md - name: Chat with your data (RAG) items: - name: Get started with the RAG sample @@ -68,12 +80,6 @@ items: href: quickstarts/build-mcp-server.md - name: Build a minimal MCP client href: quickstarts/build-mcp-client.md -- name: Text to image - items: - - name: Generate images using MEAI - href: quickstarts/text-to-image.md - - name: Generate images using OpenAI.Images.ImageClient - href: quickstarts/generate-images.md - name: Security and content safety items: - name: Authentication for Azure-hosted apps and services From 8e5859b4a1352f3493514bc630e7adaa3fd68cdd Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:43:14 -0800 Subject: [PATCH 05/13] add metadata --- docs/ai/how-to/access-data-in-functions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ai/how-to/access-data-in-functions.md b/docs/ai/how-to/access-data-in-functions.md index 38c21ee2dcbe8..265030d939d10 100644 --- a/docs/ai/how-to/access-data-in-functions.md +++ b/docs/ai/how-to/access-data-in-functions.md @@ -1,7 +1,7 @@ --- -title: -description: -ms.date: 11/13/2025 +title: Access data in AI functions +description: Learn how to pass data to AIFunction objects and how to access the data within the function delegate. +ms.date: 11/17/2025 --- # Access data in AI functions From 198dedbb8ef595c7b4a86e3a04b072be0cccaf83 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:44:14 -0800 Subject: [PATCH 06/13] add snippet tags --- docs/ai/how-to/snippets/access-data/ArgumentsExample.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs b/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs index 0ef821fee190d..e228e47acd72c 100644 --- a/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs +++ b/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs @@ -8,7 +8,7 @@ class ArgumentsExample { public static async Task RunManual() { - // + // Delegate getWeatherDelegate = (AIFunctionArguments args) => { // Access named parameters from the arguments dictionary. @@ -28,7 +28,7 @@ public static async Task RunManual() { "units", "F" } }); Console.WriteLine($"Function result: {result}"); - // + // } public static async Task UseFICC() { @@ -40,6 +40,7 @@ public static async Task UseFICC() string apiKey = config["AZURE_OPENAI_API_KEY"]; string model = config["AZURE_OPENAI_GPT_NAME"]; + // FunctionInvokingChatClient client = new FunctionInvokingChatClient( new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) .GetChatClient(model).AsIChatClient()); @@ -80,6 +81,7 @@ public static async Task UseFICC() ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); Console.WriteLine($"Response: {response.Text}"); + // //IChatClient client = From bba3fb4b418bf2f0645db0c28baa289845f0c7f5 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:45:18 -0800 Subject: [PATCH 07/13] use .net 10 --- docs/ai/how-to/snippets/access-data/Program.cs | 2 +- docs/ai/how-to/snippets/access-data/Project.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ai/how-to/snippets/access-data/Program.cs b/docs/ai/how-to/snippets/access-data/Program.cs index b475775d3536b..226432e318864 100644 --- a/docs/ai/how-to/snippets/access-data/Program.cs +++ b/docs/ai/how-to/snippets/access-data/Program.cs @@ -1,2 +1,2 @@ -//await ArgumentsExample.RunManual(); +await ArgumentsExample.RunManual(); await ArgumentsExample.UseFICC(); diff --git a/docs/ai/how-to/snippets/access-data/Project.csproj b/docs/ai/how-to/snippets/access-data/Project.csproj index 48ee9eb76a00b..8dc5dffa5c556 100644 --- a/docs/ai/how-to/snippets/access-data/Project.csproj +++ b/docs/ai/how-to/snippets/access-data/Project.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable 4d162886-0da5-4b62-a4db-d09780d06911 From 482dce7fa2f745606709938f607a390ef5679c3a Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:49:24 -0800 Subject: [PATCH 08/13] undo file rename --- .../azure-openai/BasicExample.cs | 52 ------------------- .../azure-openai/FunctionCallingAI.csproj | 2 +- .../function-calling/azure-openai/Program.cs | 47 ++++++++++++++++- docs/ai/quickstarts/use-function-calling.md | 3 +- 4 files changed, 49 insertions(+), 55 deletions(-) delete mode 100644 docs/ai/quickstarts/snippets/function-calling/azure-openai/BasicExample.cs diff --git a/docs/ai/quickstarts/snippets/function-calling/azure-openai/BasicExample.cs b/docs/ai/quickstarts/snippets/function-calling/azure-openai/BasicExample.cs deleted file mode 100644 index cd2cbd5f30014..0000000000000 --- a/docs/ai/quickstarts/snippets/function-calling/azure-openai/BasicExample.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Azure; -using Azure.AI.OpenAI; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; - -class BasicExample -{ - public static async Task Run() - { - // - IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); - string endpoint = config["AZURE_OPENAI_ENDPOINT"]; - string deployment = config["AZURE_OPENAI_GPT_NAME"]; - string apiKey = config["AZURE_OPENAI_API_KEY"]; - - IChatClient client = - new ChatClientBuilder( - new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) - .GetChatClient(deployment).AsIChatClient()) - .UseFunctionInvocation() - .Build(); - // - - // Add a new plugin with a local .NET function - // that should be available to the AI model. - var chatOptions = new ChatOptions - { - Tools = [AIFunctionFactory.Create((string location, string unit) => - { - // Here you would call a weather API to - // get the weather for the location. - return "Periods of rain or drizzle, 15 C"; - }, - "get_current_weather", - "Get the current weather in a given location")] - }; - - // System prompt to provide context. - List chatHistory = [new(ChatRole.System, """ - You are a hiking enthusiast who helps people discover fun hikes in their area. You are upbeat and friendly. - """)]; - - // Weather conversation relevant to the registered function. - chatHistory.Add(new ChatMessage(ChatRole.User, - "I live in Montreal and I'm looking for a moderate intensity hike. What's the current weather like? ")); - Console.WriteLine($"{chatHistory.Last().Role} >>> {chatHistory.Last()}"); - - var response = await client.GetResponseAsync(chatHistory, chatOptions); - chatHistory.Add(new ChatMessage(ChatRole.Assistant, response.Text)); - Console.WriteLine($"{chatHistory.Last().Role} >>> {chatHistory.Last()}"); - } -} diff --git a/docs/ai/quickstarts/snippets/function-calling/azure-openai/FunctionCallingAI.csproj b/docs/ai/quickstarts/snippets/function-calling/azure-openai/FunctionCallingAI.csproj index 48ee9eb76a00b..8dc5dffa5c556 100644 --- a/docs/ai/quickstarts/snippets/function-calling/azure-openai/FunctionCallingAI.csproj +++ b/docs/ai/quickstarts/snippets/function-calling/azure-openai/FunctionCallingAI.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable 4d162886-0da5-4b62-a4db-d09780d06911 diff --git a/docs/ai/quickstarts/snippets/function-calling/azure-openai/Program.cs b/docs/ai/quickstarts/snippets/function-calling/azure-openai/Program.cs index 0c1e76389e781..3bf804265772a 100644 --- a/docs/ai/quickstarts/snippets/function-calling/azure-openai/Program.cs +++ b/docs/ai/quickstarts/snippets/function-calling/azure-openai/Program.cs @@ -1 +1,46 @@ -await BasicExample.Run(); +using Azure; +using Azure.AI.OpenAI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; + +// +IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets().Build(); +string endpoint = config["AZURE_OPENAI_ENDPOINT"]; +string deployment = config["AZURE_OPENAI_GPT_NAME"]; +string apiKey = config["AZURE_OPENAI_API_KEY"]; + +IChatClient client = + new ChatClientBuilder( + new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) + .GetChatClient(deployment).AsIChatClient()) + .UseFunctionInvocation() + .Build(); +// + +// Add a new plugin with a local .NET function +// that should be available to the AI model. +var chatOptions = new ChatOptions +{ + Tools = [AIFunctionFactory.Create((string location, string unit) => + { + // Here you would call a weather API to + // get the weather for the location. + return "Periods of rain or drizzle, 15 C"; + }, + "get_current_weather", + "Get the current weather in a given location")] +}; + +// System prompt to provide context. +List chatHistory = [new(ChatRole.System, """ + You are a hiking enthusiast who helps people discover fun hikes in their area. You are upbeat and friendly. + """)]; + +// Weather conversation relevant to the registered function. +chatHistory.Add(new ChatMessage(ChatRole.User, + "I live in Montreal and I'm looking for a moderate intensity hike. What's the current weather like? ")); +Console.WriteLine($"{chatHistory.Last().Role} >>> {chatHistory.Last()}"); + +var response = await client.GetResponseAsync(chatHistory, chatOptions); +chatHistory.Add(new ChatMessage(ChatRole.Assistant, response.Text)); +Console.WriteLine($"{chatHistory.Last().Role} >>> {chatHistory.Last()}"); diff --git a/docs/ai/quickstarts/use-function-calling.md b/docs/ai/quickstarts/use-function-calling.md index 4d3df5a134cd6..b6b700c2241b9 100644 --- a/docs/ai/quickstarts/use-function-calling.md +++ b/docs/ai/quickstarts/use-function-calling.md @@ -47,6 +47,7 @@ Complete the following steps to create a .NET console app to connect to an AI mo dotnet add package Azure.Identity dotnet add package Azure.AI.OpenAI dotnet add package Microsoft.Extensions.AI + dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease dotnet add package Microsoft.Extensions.Configuration dotnet add package Microsoft.Extensions.Configuration.UserSecrets ``` @@ -100,7 +101,7 @@ The app uses the [`Microsoft.Extensions.AI`](https://www.nuget.org/packages/Micr :::zone target="docs" pivot="azure-openai" - :::code language="csharp" source="snippets/function-calling/azure-openai/BasicExample.cs" id="GetChatClient"::: + :::code language="csharp" source="snippets/function-calling/azure-openai/Program.cs" id="GetChatClient"::: :::zone-end From 03afd791c3eda321d81ee3614f7c9c07a56ea830 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:52:57 -0800 Subject: [PATCH 09/13] undo include deletion --- docs/ai/quickstarts/includes/semantic-kernel.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 docs/ai/quickstarts/includes/semantic-kernel.md diff --git a/docs/ai/quickstarts/includes/semantic-kernel.md b/docs/ai/quickstarts/includes/semantic-kernel.md new file mode 100644 index 0000000000000..e8fd9cd552194 --- /dev/null +++ b/docs/ai/quickstarts/includes/semantic-kernel.md @@ -0,0 +1,2 @@ +> [!NOTE] +> You can also use [Semantic Kernel](/semantic-kernel) to accomplish the tasks in this article. Semantic Kernel is a lightweight, open-source SDK that lets you build AI agents and integrate the latest AI models into your .NET apps. From 77efecf015f7f7302bdbdac2d185a6bdc297363d Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:08:54 -0800 Subject: [PATCH 10/13] review edits --- docs/ai/how-to/access-data-in-functions.md | 18 +++++++++++++----- .../snippets/access-data/ArgumentsExample.cs | 17 ++++++----------- docs/ai/toc.yml | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/ai/how-to/access-data-in-functions.md b/docs/ai/how-to/access-data-in-functions.md index 265030d939d10..68d82c5b9069d 100644 --- a/docs/ai/how-to/access-data-in-functions.md +++ b/docs/ai/how-to/access-data-in-functions.md @@ -8,9 +8,15 @@ ms.date: 11/17/2025 When you create AI functions, you might need to access contextual data beyond the parameters provided by the AI model. The `Microsoft.Extensions.AI` library provides several mechanisms to pass data to function delegates. +## Pass data + You can associate data with the function at the time it's created, either via closure or via . If you're creating your own function, you can populate `AdditionalProperties` however you want. If you use to create the function, you can populate data using . -## Manual function invocation +## Access data in function delegates + +You might call your `AIFunction` directly, or you might call it indirectly by using . The following sections describe how to access argument data using either approach. + +### Manual function invocation If you manually invoke an by calling , you pass in . The type includes: @@ -24,16 +30,18 @@ The following code shows an example: :::code language="csharp" source="snippets/access-data/ArgumentsExample.cs" id="UseAIFunctionArguments"::: -## Invocation through `FunctionInvokingChatClient` - -If you use to invoke functions automatically, that client configures an object that it passes into the `AIFunction`. Because `AIFunctionArguments` includes the `IServiceProvider` that the `FunctionInvokingChatClient` was itself provided with, if you construct your client using standard DI means, that `IServiceProvider` is passed all the way into your `AIFunction`. At that point, you can query it for anything you want from DI. +### Invocation through `FunctionInvokingChatClient` -`FunctionInvokingChatClient` also publishes state about the current invocation to , including not only the arguments, but all of the input `ChatMessage` objects, the , and details on which function is being invoked (out of how many). You can add any data you want into and extract that inside of your `AIFunction` from `FunctionInvokingChatClient.CurrentContext.Options.AdditionalProperties`. + publishes state about the current invocation to , including not only the arguments, but all of the input `ChatMessage` objects, the , and details on which function is being invoked (out of how many). You can add any data you want into and extract that inside of your `AIFunction` from `FunctionInvokingChatClient.CurrentContext.Options.AdditionalProperties`. The following code shows an example: :::code language="csharp" source="snippets/access-data/ArgumentsExample.cs" id="UseAdditionalProperties"::: +#### Dependency injection + +If you use to invoke functions automatically, that client configures an object that it passes into the `AIFunction`. Because `AIFunctionArguments` includes the `IServiceProvider` that the `FunctionInvokingChatClient` was itself provided with, if you construct your client using standard DI means, that `IServiceProvider` is passed all the way into your `AIFunction`. At that point, you can query it for anything you want from DI. + ## Advanced techniques If you want more fine-grained control over how parameters are bound, you can use , which puts you in control over how each parameter is populated. For example, the [MCP C# SDK uses this technique](https://github.com/modelcontextprotocol/csharp-sdk/blob/d344c651203841ec1c9e828736d234a6e4aebd07/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs#L83-L107) to automatically bind parameters from DI. diff --git a/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs b/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs index e228e47acd72c..2930d3553291f 100644 --- a/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs +++ b/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs @@ -59,7 +59,8 @@ public static async Task UseFICC() AIFunction getWeather = AIFunctionFactory.Create(() => { // Access named parameters from the arguments dictionary. - AdditionalPropertiesDictionary props = FunctionInvokingChatClient.CurrentContext.Options.AdditionalProperties; + AdditionalPropertiesDictionary props = + FunctionInvokingChatClient.CurrentContext.Options.AdditionalProperties; string location = props["location"].ToString(); string units = props["units"].ToString(); @@ -70,10 +71,12 @@ public static async Task UseFICC() var chatOptions = new ChatOptions { Tools = [getWeather], - AdditionalProperties = new AdditionalPropertiesDictionary { ["location"] = "Seattle", ["units"] = "F" }, + AdditionalProperties = new AdditionalPropertiesDictionary { + ["location"] = "Seattle", + ["units"] = "F" + }, }; - // Prepare chat with service provider. List chatHistory = [ new(ChatRole.System, "You're a helpful weather assistant.") ]; @@ -82,13 +85,5 @@ public static async Task UseFICC() ChatResponse response = await client.GetResponseAsync(chatHistory, chatOptions); Console.WriteLine($"Response: {response.Text}"); // - - - //IChatClient client = - // new ChatClientBuilder( - // new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) - // .GetChatClient(model).AsIChatClient()) - // .UseFunctionInvocation() - // .Build(); } } diff --git a/docs/ai/toc.yml b/docs/ai/toc.yml index 96ee66d741da4..b49af499ac0ef 100644 --- a/docs/ai/toc.yml +++ b/docs/ai/toc.yml @@ -59,7 +59,7 @@ items: - name: "Quickstart: Execute a local function" href: quickstarts/use-function-calling.md - name: Access data in AI functions - href: quickstarts/access-data-in-functions.md + href: how-to/access-data-in-functions.md - name: Text to image items: - name: Generate images using MEAI From 716be8d5bfc6860e5a9694c06c8dd8e061fa9eb6 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:14:06 -0800 Subject: [PATCH 11/13] remove unused options --- .../how-to/snippets/access-data/ArgumentsExample.cs | 11 ----------- docs/ai/how-to/snippets/access-data/Program.cs | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs b/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs index 2930d3553291f..969d18fc26814 100644 --- a/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs +++ b/docs/ai/how-to/snippets/access-data/ArgumentsExample.cs @@ -45,17 +45,6 @@ public static async Task UseFICC() new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) .GetChatClient(model).AsIChatClient()); - AIFunctionFactoryOptions functionFactoryOptions = new() - { - Name = "get_weather", - Description = "Gets the current weather", - AdditionalProperties = new Dictionary - { - { "location", "Seattle" }, - { "units", "F" } - } - }; - AIFunction getWeather = AIFunctionFactory.Create(() => { // Access named parameters from the arguments dictionary. diff --git a/docs/ai/how-to/snippets/access-data/Program.cs b/docs/ai/how-to/snippets/access-data/Program.cs index 226432e318864..b475775d3536b 100644 --- a/docs/ai/how-to/snippets/access-data/Program.cs +++ b/docs/ai/how-to/snippets/access-data/Program.cs @@ -1,2 +1,2 @@ -await ArgumentsExample.RunManual(); +//await ArgumentsExample.RunManual(); await ArgumentsExample.UseFICC(); From 646389e53b40ac077456b081060246e1cf1fc42e Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:22:04 -0800 Subject: [PATCH 12/13] respond to feedback --- docs/ai/how-to/access-data-in-functions.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/ai/how-to/access-data-in-functions.md b/docs/ai/how-to/access-data-in-functions.md index 68d82c5b9069d..4b5ab4d7f7533 100644 --- a/docs/ai/how-to/access-data-in-functions.md +++ b/docs/ai/how-to/access-data-in-functions.md @@ -12,6 +12,12 @@ When you create AI functions, you might need to access contextual data beyond th You can associate data with the function at the time it's created, either via closure or via . If you're creating your own function, you can populate `AdditionalProperties` however you want. If you use to create the function, you can populate data using . +You can also capture any references to data as part of the delegate provided to `AIFunctionFactory`. That is, you can bake in whatever you want to reference as part of the `AIFunction` itself. + +## `AIFunction` class + + is a base class. is one producer of `AIFunction` objects, but you can derive from the base class and implement your own AI function type. provides an easy way to wrap an existing `AIFunction` and layer in additional functionality, including capturing additional data to be used. + ## Access data in function delegates You might call your `AIFunction` directly, or you might call it indirectly by using . The following sections describe how to access argument data using either approach. @@ -30,6 +36,8 @@ The following code shows an example: :::code language="csharp" source="snippets/access-data/ArgumentsExample.cs" id="UseAIFunctionArguments"::: + is also special-cased: if the `AIFunctionFactory.Create` delegate or lambda has a `CancellationToken` parameter, it will be bound to the `CancellationToken` that was passed to `AIFunction.InvokeAsync()`. + ### Invocation through `FunctionInvokingChatClient` publishes state about the current invocation to , including not only the arguments, but all of the input `ChatMessage` objects, the , and details on which function is being invoked (out of how many). You can add any data you want into and extract that inside of your `AIFunction` from `FunctionInvokingChatClient.CurrentContext.Options.AdditionalProperties`. From 757f3a2c16c4612c21ecaf318eca324b71fb885c Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:24:51 -0800 Subject: [PATCH 13/13] respond to feedback --- docs/ai/how-to/access-data-in-functions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ai/how-to/access-data-in-functions.md b/docs/ai/how-to/access-data-in-functions.md index 4b5ab4d7f7533..291b974d6a2f5 100644 --- a/docs/ai/how-to/access-data-in-functions.md +++ b/docs/ai/how-to/access-data-in-functions.md @@ -8,16 +8,16 @@ ms.date: 11/17/2025 When you create AI functions, you might need to access contextual data beyond the parameters provided by the AI model. The `Microsoft.Extensions.AI` library provides several mechanisms to pass data to function delegates. +## `AIFunction` class + +The type represents a function that can be described to an AI service and invoked. You can create `AIFunction` objects by calling one of the overloads. But is also a base class, and you can derive from it and implement your own AI function type. provides an easy way to wrap an existing `AIFunction` and layer in additional functionality, including capturing additional data to be used. + ## Pass data You can associate data with the function at the time it's created, either via closure or via . If you're creating your own function, you can populate `AdditionalProperties` however you want. If you use to create the function, you can populate data using . You can also capture any references to data as part of the delegate provided to `AIFunctionFactory`. That is, you can bake in whatever you want to reference as part of the `AIFunction` itself. -## `AIFunction` class - - is a base class. is one producer of `AIFunction` objects, but you can derive from the base class and implement your own AI function type. provides an easy way to wrap an existing `AIFunction` and layer in additional functionality, including capturing additional data to be used. - ## Access data in function delegates You might call your `AIFunction` directly, or you might call it indirectly by using . The following sections describe how to access argument data using either approach.