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
- The AI model detects when a request requires external execution.
- The AI model identifies the appropriate function and necessary parameters. In this example, these are
CustomAIFunctions.GetWeatherTool()
andWeatherPlugin.MyGetWhether(string city)
functions. - The AI model calls the function and processes the result.
- 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 theGetWeather
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
- DevExpress Blazor AI Chat — Implement Function Calling (Blog Post)
- DevExpress AI-powered Extensions for Blazor
- DevExpress Blazor AI Chat Control
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
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();
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)";
}
}
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();
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)";
}
}
}
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;
}
}