Skip to content
Shiny.Maui.Shell v6 support for AI routing tools Learn More

AI Tools

Shiny Mediator can expose your request and command contracts as AI-callable tools via Microsoft.Extensions.AI. The source generator discovers contracts annotated with [Description] and generates AIFunction wrappers with JSON schemas automatically.

  1. Install the Microsoft.Extensions.AI NuGet package:
Terminal window
dotnet add package Microsoft.Extensions.AI
  1. Enable AI tool generation in your .csproj:
<PropertyGroup>
<ShinyMediatorGenerateAITools>true</ShinyMediatorGenerateAITools>
</PropertyGroup>

Add [Description] (from System.ComponentModel) to your contracts and their properties. Only contracts that implement IRequest<T> or ICommand and have a [Description] attribute will be picked up.

using System.ComponentModel;
[Description("Gets the weather forecast for a given city")]
public record GetWeatherRequest(
[property: Description("The city name to get weather for")]
string City
) : IRequest<WeatherResult>;
[Description("Performs a mathematical calculation")]
public record CalculateRequest(
[property: Description("First operand")]
double A,
[property: Description("Math operator: +, -, *, /")]
string Operator,
[property: Description("Second operand")]
double B
) : IRequest<double>;
[Description("Sends a notification to a user")]
public record SendNotificationCommand(
[property: Description("The user ID to notify")]
int UserId,
[property: Description("The notification message")]
string Message
) : ICommand;

Register the generated AI tools with the mediator builder:

builder.Services.AddShinyMediator(cfg =>
{
cfg.AddMediatorRegistry();
cfg.AddGeneratedAITools();
});

This registers each annotated contract as an AITool singleton in the DI container.

Retrieve the tools from DI and pass them to any IChatClient:

using Microsoft.Extensions.AI;
var tools = host.Services.GetServices<AITool>().ToList();
var options = new ChatOptions { Tools = tools };
var response = await chatClient.GetResponseAsync(history, options);

For automatic tool invocation (the AI calls your tools and the client handles the round-trip), use UseFunctionInvocation():

IChatClient chatClient = openAiClient
.GetChatClient("gpt-4.1")
.AsIChatClient()
.AsBuilder()
.UseFunctionInvocation()
.Build();

For each contract with [Description] that implements IRequest<T> or ICommand, the source generator produces:

  1. An AIFunction class that wraps the mediator call with a JSON schema built from the contract’s properties
  2. AddGeneratedAITools() extension method on ShinyMediatorBuilder that registers all generated tools as AITool singletons

The generated JSON schema includes property types, descriptions, required/optional fields, enum values, and default values.

  • IRequest<T> contracts return the result to the AI
  • ICommand contracts return "Command executed successfully" to the AI
TypeJSON Schema Type
string, Guid, Uri, DateTime, DateTimeOffset, DateOnlystring
boolboolean
int, long, short, byteinteger
float, double, decimalnumber
Enumsstring with enum values
Arrays / IEnumerable<T>array
Complex typesobject

Nullable properties and properties with default values are marked as optional in the schema. Non-nullable properties without defaults are required.

Here is a complete example using GitHub Copilot as the AI backend:

using System.ComponentModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
// Build host with mediator + AI tools
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddShinyMediator(cfg =>
{
cfg.AddMediatorRegistry();
cfg.AddGeneratedAITools();
});
var host = builder.Build();
// Get AI tools from DI
var tools = host.Services.GetServices<AITool>().ToList();
// Use with any IChatClient
var options = new ChatOptions { Tools = tools };
var history = new List<ChatMessage>();
history.Add(new ChatMessage(ChatRole.User, "What's the weather in Seattle?"));
var response = await chatClient.GetResponseAsync(history, options);
[Description("Gets the current weather forecast for a city")]
public record GetWeatherRequest(
[property: Description("The city to get weather for")]
string City
) : IRequest<string>;
[MediatorSingleton]
public class GetWeatherHandler : IRequestHandler<GetWeatherRequest, string>
{
public Task<string> Handle(
GetWeatherRequest request,
IMediatorContext context,
CancellationToken ct)
{
return Task.FromResult($"Weather in {request.City}: 72F and sunny");
}
}

When the AI receives “What’s the weather in Seattle?”, it will call the GetWeatherRequest tool with City: "Seattle", the mediator routes it to the handler, and the result is returned to the AI.