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.
Prerequisites
Section titled “Prerequisites”- Install the
Microsoft.Extensions.AINuGet package:
dotnet add package Microsoft.Extensions.AI- Enable AI tool generation in your
.csproj:
<PropertyGroup> <ShinyMediatorGenerateAITools>true</ShinyMediatorGenerateAITools></PropertyGroup>Annotating Contracts
Section titled “Annotating Contracts”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;Registration
Section titled “Registration”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.
Using with a Chat Client
Section titled “Using with a Chat Client”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();What Gets Generated
Section titled “What Gets Generated”For each contract with [Description] that implements IRequest<T> or ICommand, the source generator produces:
- An
AIFunctionclass that wraps the mediator call with a JSON schema built from the contract’s properties AddGeneratedAITools()extension method onShinyMediatorBuilderthat registers all generated tools asAIToolsingletons
The generated JSON schema includes property types, descriptions, required/optional fields, enum values, and default values.
Request vs Command
Section titled “Request vs Command”IRequest<T>contracts return the result to the AIICommandcontracts return"Command executed successfully"to the AI
Supported Property Types
Section titled “Supported Property Types”| Type | JSON Schema Type |
|---|---|
string, Guid, Uri, DateTime, DateTimeOffset, DateOnly | string |
bool | boolean |
int, long, short, byte | integer |
float, double, decimal | number |
| Enums | string with enum values |
Arrays / IEnumerable<T> | array |
| Complex types | object |
Nullable properties and properties with default values are marked as optional in the schema. Non-nullable properties without defaults are required.
Complete Example
Section titled “Complete Example”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 toolsvar builder = Host.CreateApplicationBuilder(args);builder.Services.AddShinyMediator(cfg =>{ cfg.AddMediatorRegistry(); cfg.AddGeneratedAITools();});var host = builder.Build();
// Get AI tools from DIvar tools = host.Services.GetServices<AITool>().ToList();
// Use with any IChatClientvar 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);Contracts
Section titled “Contracts”[Description("Gets the current weather forecast for a city")]public record GetWeatherRequest( [property: Description("The city to get weather for")] string City) : IRequest<string>;Handler
Section titled “Handler”[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.