Example T1281509
Visible to All Users

DevExpress Blazor AI Chat — Implement Function/Tool Calling

This example uses function/tool calling with the DevExpress Blazor DxAIChat component. Function calling allows a model to invoke external functions or APIs in response to user queries.

What's Inside

The solution includes the following projects:

  • Project 1: DXBlazorChatFunctionCalling implements function calling using the IChatClient interface from the Microsoft.Extensions.AI library.
  • Project 2: DXBlazorChatFunctionCalling.Semantic creates a Microsoft Semantic Kernel plugin with a callable function.

How Function Calling Works

  1. The AI model detects when a request requires external execution.
  2. The AI model identifies the appropriate function and necessary parameters. In this example, these are CustomAIFunctions.GetWeatherTool() and WeatherPlugin.MyGetWhether(string city) functions.
  3. The AI model calls the function and processes the result.
  4. The response is formatted and returned to the Blazor AI Chat control.

Register AI Service

Before you run the example, register your AI service. This example uses Azure OpenAI. You’ll need to specify your credentials in Program.cs:

C#
// Replace with your endpoint, API key, and deployed AI model name. string azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); string azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); string deploymentName = string.Empty;

Implementation Details

Project 1: Tool Calling with IChatClient

  • The GetWeatherTool function retrieves weather information for a specified city.
  • The System.ComponentModel.Description attribute helps the AI model select and call the appropriate method (in this example this is the GetWeather method). By using this attribute, the AI-powered system can better analyze, select, and invoke the correct function when responding to user queries.
    C#
    using System.ComponentModel; using Microsoft.Extensions.AI; namespace DXBlazorChatFunctionCalling.Services; public class CustomAIFunctions { // Describe the function so the AI service understands its purpose. public static AIFunction GetWeatherTool => AIFunctionFactory.Create(GetWeather); [Description("Gets the current weather in the city")] public static string GetWeather([Description("The name of the city")] string city) { switch (city) { case "Los Angeles": case "LA": return GetTemperatureValue(20); case "London": return GetTemperatureValue(15); default: return $"The information about the weather in {city} is not available."; } } static string GetTemperatureValue(int value) { var valueInFahrenheits = value * 9 / 5 + 32; return $"{valueInFahrenheits}\u00b0F ({value}\u00b0C)"; } }
  • To enable function calling in a chat client, you must first configure chat client options. Once configured, call the UseFunctionInvocation() method to activate function invocation.
    C#
    using Azure; using Azure.AI.OpenAI; using Microsoft.Extensions.AI; //... IChatClient chatClient = new ChatClientBuilder(azureChatClient) .ConfigureOptions(x => { x.Tools = [CustomAIFunctions.GetWeatherTool]; }) .UseFunctionInvocation() .Build(); builder.Services.AddChatClient(chatClient);

When a user asks the AI Chat about the weather, the AI model automatically calls the GetWeatherTool function and returns the formatted result to the AI Chat control.

Project 2: Integrate Microsoft Semantic Kernel Plugins

The DXBlazorChatFunctionCalling.Semantic project implements a custom IChatClient to invoke IChatCompletionService methods from the Semantic Kernel. DevExpress AI-powered APIs use this interface to operate with LLMs.

The WeatherPlugin class implements a Microsoft Semantic Kernel plugin. The Microsoft.SemanticKernel.KernelFunction attribute annotates the GetWeather() method as a callable function within the Semantic Kernel runtime.

C#
using Microsoft.SemanticKernel; using System.ComponentModel; public class WeatherPlugin { [KernelFunction] [Description("Gets the current weather in the city, returning a value in Celsius")] public static string GetWeather([Description("The name of the city")] string city) { switch(city) { case "Los Angeles": case "LA": return GetTemperatureValue(20); case "London": return GetTemperatureValue(15); default: return $"The information about the weather in {city} is not available."; } } static string GetTemperatureValue(int value) { var valueInFahrenheits = value * 9 / 5 + 32; return $"{valueInFahrenheits}\u00b0F ({value}\u00b0C)"; } }

Files to Review

DXBlazorChatFunctionCalling

DXBlazorChatFunctionCalling.Semantic

Documentation

Online Demo

More Examples

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

Example Code

DXBlazorChatFunctionCalling/Program.cs
C#
using Azure; using Azure.AI.OpenAI; using DXBlazorChatFunctionCalling.Components; using DXBlazorChatFunctionCalling.Services; using Microsoft.Extensions.AI; var builder = WebApplication.CreateBuilder(args); // Replace with your endpoint, API key, and deployed AI model name. string azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); string azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); string deploymentName = string.Empty; // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddDevExpressBlazor(options => { options.BootstrapVersion = DevExpress.Blazor.BootstrapVersion.v5; }); builder.Services.AddMvc(); var azureChatClient = new AzureOpenAIClient( new Uri(azureOpenAIEndpoint), new AzureKeyCredential(azureOpenAIKey)).AsChatClient(deploymentName); IChatClient chatClient = new ChatClientBuilder(azureChatClient) .ConfigureOptions(x => { x.Tools = [CustomAIFunctions.GetWeatherTool]; }) .UseFunctionInvocation() .Build(); builder.Services.AddChatClient(chatClient); builder.Services.AddDevExpressAI(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.Run();
DXBlazorChatFunctionCalling/Services/CustomAIFunctions.cs
C#
using System.ComponentModel; using Microsoft.Extensions.AI; namespace DXBlazorChatFunctionCalling.Services; public class CustomAIFunctions { public static AIFunction GetWeatherTool => AIFunctionFactory.Create(GetWeather); [Description("Gets the current weather in the city")] public static string GetWeather([Description("The name of the city")] string city) { switch (city) { case "Los Angeles": case "LA": return GetTemperatureValue(20); case "London": return GetTemperatureValue(15); default: return $"The information about the weather in {city} is not available."; } } static string GetTemperatureValue(int value) { var valueInFahrenheits = value * 9 / 5 + 32; return $"{valueInFahrenheits}\u00b0F ({value}\u00b0C)"; } }
DXBlazorChatFunctionCalling.Semantic/Program.cs
C#
using DXBlazorChatFunctionCalling.Semantic.Components; using DXBlazorChatFunctionCalling.Semantic.Services; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Plugins.Core; var builder = WebApplication.CreateBuilder(args); // Replace with your endpoint, API key, and deployed AI model name. string azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); string azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); string deploymentName = string.Empty; // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddDevExpressBlazor(options => { options.BootstrapVersion = DevExpress.Blazor.BootstrapVersion.v5; }); builder.Services.AddMvc(); var semanticKernelBuilder = Kernel.CreateBuilder(); semanticKernelBuilder.AddAzureOpenAIChatCompletion( deploymentName, azureOpenAIEndpoint, azureOpenAIKey); // Add plugins from Microsoft.SemanticKernel.Plugins.Core #pragma warning disable SKEXP0050 semanticKernelBuilder.Plugins.AddFromType<TimePlugin>(); semanticKernelBuilder.Plugins.AddFromType<WeatherPlugin>(); #pragma warning restore SKEXP0050 var globalKernel = semanticKernelBuilder.Build(); builder.Services.AddChatClient(new SemanticKernelPluginCallingChatClient(globalKernel)); builder.Services.AddDevExpressAI(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.Run();
DXBlazorChatFunctionCalling.Semantic/Services/WeatherPlugin.cs
C#
using Microsoft.SemanticKernel; using System.ComponentModel; namespace DXBlazorChatFunctionCalling.Semantic.Services { public class WeatherPlugin { [KernelFunction] [Description("Gets the current weather in the city")] public static string GetWeather([Description("The name of the city")] string city) { switch(city) { case "Los Angeles": case "LA": return GetTemperatureValue(20); case "London": return GetTemperatureValue(15); default: return $"The information about the weather in {city} is not available."; } } static string GetTemperatureValue(int value) { var valueInFahrenheits = value * 9 / 5 + 32; return $"{valueInFahrenheits}\u00b0F ({value}\u00b0C)"; } } }
DXBlazorChatFunctionCalling.Semantic/Services/SemanticKernelPluginCallingChatClient.cs
C#
using System.ComponentModel; using Microsoft.Extensions.AI; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; namespace DXBlazorChatFunctionCalling.Semantic.Services { public class SemanticKernelPluginCallingChatClient : IChatClient { public ChatClientMetadata Metadata => new ChatClientMetadata(); private IChatCompletionService _chatCompletionService; private Kernel _kernel; private OpenAIPromptExecutionSettings _executionSettings; public SemanticKernelPluginCallingChatClient(Kernel kernel) { _kernel = kernel; _chatCompletionService = _kernel.GetRequiredService<IChatCompletionService>(); _executionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }; } public async Task<ChatCompletion> CompleteAsync(IList<ChatMessage> chatMessages, ChatOptions? options = null, CancellationToken cancellationToken = default) { var history = GetChatHistory(chatMessages); ChatMessageContent message = await _chatCompletionService.GetChatMessageContentAsync(history, _executionSettings, _kernel, cancellationToken); return new ChatCompletion(new ChatMessage(ChatRole.Assistant, message.Content)); } public async IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(IList<ChatMessage> chatMessages, ChatOptions? options = null, CancellationToken cancellationToken = default) { var history = GetChatHistory(chatMessages); await foreach(var item in _chatCompletionService.GetStreamingChatMessageContentsAsync(history, _executionSettings, _kernel, cancellationToken)) { yield return new StreamingChatCompletionUpdate() { Text = item.Content, Role = ChatRole.Assistant }; } } AuthorRole GetRole(ChatRole chatRole) { if(chatRole == ChatRole.User) return AuthorRole.User; if(chatRole == ChatRole.System) return AuthorRole.System; if(chatRole == ChatRole.Assistant) return AuthorRole.Assistant; if(chatRole == ChatRole.Tool) return AuthorRole.Tool; throw new Exception(); } private ChatHistory GetChatHistory(IList<ChatMessage> chatMessages) { var history = new ChatHistory(chatMessages.Select(x => new ChatMessageContent(GetRole(x.Role), x.Text))); return history; } public void Dispose() {} public object? GetService(Type serviceType, object? serviceKey = null) => null; } }

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.