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:
dotnetaddpackageMicrosoft.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.
usingSystem.ComponentModel;[Description("Gets the weather forecast for a given city")]publicrecordGetWeatherRequest([property: Description("The city name to get weather for")]stringCity) : IRequest<WeatherResult>;[Description("Performs a mathematical calculation")]publicrecordCalculateRequest([property: Description("First operand")]doubleA,[property: Description("Math operator: +, -, *, /")]stringOperator,[property: Description("Second operand")]doubleB) : IRequest<double>;[Description("Sends a notification to a user")]publicrecordSendNotificationCommand([property: Description("The user ID to notify")]intUserId,[property: Description("The notification message")]stringMessage) : 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:
usingMicrosoft.Extensions.AI;vartools= host.Services.GetServices<AITool>().ToList();varoptions=newChatOptions { Tools = tools };varresponse=await chatClient.GetResponseAsync(history, options);For automatic tool invocation (the AI calls your tools and the client handles the round-trip), use UseFunctionInvocation():
IChatClientchatClient= 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:
usingSystem.ComponentModel;usingMicrosoft.Extensions.AI;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Hosting;// Build host with mediator + AI toolsvarbuilder= Host.CreateApplicationBuilder(args);builder.Services.AddShinyMediator(cfg=>{cfg.AddMediatorRegistry();cfg.AddGeneratedAITools();});varhost= builder.Build();// Get AI tools from DIvartools= host.Services.GetServices<AITool>().ToList();// Use with any IChatClientvaroptions=newChatOptions { Tools = tools };varhistory=newList<ChatMessage>();history.Add(newChatMessage(ChatRole.User, "What's the weather in Seattle?"));varresponse=await chatClient.GetResponseAsync(history, options);Contracts
Section titled “Contracts”[Description("Gets the current weather forecast for a city")]publicrecordGetWeatherRequest([property: Description("The city to get weather for")]stringCity) : IRequest<string>;Handler
Section titled “Handler”[MediatorSingleton]publicclassGetWeatherHandler : IRequestHandler<GetWeatherRequest, string>{publicTask<string> Handle(GetWeatherRequestrequest,IMediatorContextcontext,CancellationTokenct){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.
