Shell | AI Integration
Shiny Shell’s source generation can produce metadata and navigation methods designed for AI tool calling via Microsoft.Extensions.AI. An AI chat client can discover your app’s routes, understand their purpose, extract parameters from natural language, and navigate to the correct page — all with generated AITool instances.
Prerequisites
Section titled “Prerequisites”Install the Microsoft.Extensions.AI NuGet package:
dotnet add package Microsoft.Extensions.AIHow It Works
Section titled “How It Works”The AI integration builds on the same [ShellMap] and [ShellProperty] attributes you already use. Add description parameters that explain user intent rather than just naming the page, and the source generator produces everything the AI needs.
The flow:
- AI calls
GetAiToolApplicableGeneratedRoutes()to discover what pages exist and what they do - AI matches user intent to the right route based on descriptions
- AI extracts parameter values from natural language
- AI calls
NavigateToRoute()with the route and parameters - The app navigates and the form is pre-populated
Describing Routes for AI
Section titled “Describing Routes for AI”The description parameter on [ShellMap] tells the AI when to use this route. Write it as intent signals, not page names:
// Good — describes user intent[ShellMap<WorkOrderPage>(description: "Use when the user reports something broken, malfunctioning, needing repair, or any physical problem that needs to be fixed")]
// Bad — just names the page[ShellMap<WorkOrderPage>(description: "Work order page")]Describing Parameters for AI
Section titled “Describing Parameters for AI”The description parameter on [ShellProperty] tells the AI how to infer the value from what the user said:
// Good — tells AI to extract and infer[ShellProperty("Summarize what is broken based on what the user said", required: true)]public string Description { get; set; } = string.Empty;
[ShellProperty("Infer urgency from the user's tone. Must be: Low, Medium, High, or Urgent", required: true)]public WorkOrderPriority Priority { get; set; } = WorkOrderPriority.Medium;
// Bad — just names the field[ShellProperty("The description")]public string Description { get; set; } = string.Empty;Supported Property Types
Section titled “Supported Property Types”NavigateToRoute accepts Dictionary<string, string> from the AI and automatically converts string values to the correct property type. The following types are supported:
| Type | Conversion |
|---|---|
string | Direct assignment |
int, long, short, byte | T.Parse(value) |
float, double, decimal | T.Parse(value) |
bool | bool.Parse(value) |
Guid, DateTime, DateTimeOffset, TimeSpan | T.Parse(value) |
Uri | new Uri(value) |
| Enums | Enum.Parse(typeof(T), value, true) — case-insensitive |
| Other types | Convert.ChangeType fallback |
Enums work especially well with AI — the model naturally outputs the enum member name as a string (e.g. "Urgent"), and the generator handles the parsing with case-insensitive matching.
Complete AI-Compatible ViewModel
Section titled “Complete AI-Compatible ViewModel”public enum WorkOrderPriority { Low, Medium, High, Urgent }
[ShellMap<WorkOrderPage>(description: "Use when the user reports something broken, malfunctioning, needing repair, maintenance, or service")]public partial class WorkOrderViewModel(INavigator navigator) : ObservableObject{ [ShellProperty("Summarize what is broken based on what the user said", required: true)] public string Description { get; set; } = string.Empty;
[ShellProperty("Infer urgency from the user's tone. Must be: Low, Medium, High, or Urgent", required: true)] public WorkOrderPriority Priority { get; set; } = WorkOrderPriority.Medium;
[ShellProperty("The physical location if the user mentioned one, otherwise leave empty", required: false)] public string Location { get; set; } = string.Empty;}## Generated AI Class — `AiMauiShellTools`
When AI extensions are enabled, the source generator produces an `AiMauiShellTools` class (name configurable via `ShinyMauiShell_AiToolsClassName`) and a DI registration extension. The class takes `INavigator` via constructor injection and is designed to be registered as a singleton.
### Properties
#### `Prompt`
A pre-formatted string describing all AI-applicable routes, their descriptions, and parameters. Designed to be included in an AI system message so the model knows which routes are available without calling a discovery tool first.
```csharppublic string Prompt { get; }Ready-to-use AITool[] instances for route discovery and navigation. This is the recommended way to wire up AI tools — no manual AIFunctionFactory.Create calls needed.
public AITool[] Tools { get; }Methods
Section titled “Methods”GetAiToolApplicableGeneratedRoutes
Section titled “GetAiToolApplicableGeneratedRoutes”Returns only routes that have a description on [ShellMap] and at least one [ShellProperty]. These are routes an AI can meaningfully discover and navigate to.
[Description("This provides a list of AI tool applicable routes")]public GeneratedRouteInfo[] GetAiToolApplicableGeneratedRoutes();NavigateToRoute
Section titled “NavigateToRoute”AI-friendly navigation that accepts Dictionary<string, string> and dispatches to NavigateTo<TViewModel> with direct property setters. String values are automatically converted to the target property type (see Supported Property Types). Returns a confirmation message string.
[Description("Navigate to a route in the application, passing parameters as key-value pairs. Returns a confirmation message.")]public Task<string> NavigateToRoute( [Description("The route name to navigate to")] string route, [Description("Route parameters as key-value pairs")] Dictionary<string, string>? args = null);DI Registration — AddAiTools()
Section titled “DI Registration — AddAiTools()”A generated extension method on ShinyAppBuilder that registers AiMauiShellTools as a singleton:
public static ShinyAppBuilder AddAiTools(this ShinyAppBuilder builder);Static Extension — GetGeneratedRouteInfo
Section titled “Static Extension — GetGeneratedRouteInfo”GetGeneratedRouteInfo() remains as a static extension method on INavigator (in the AiExtensions class) returning all routes with parameter metadata, regardless of AI applicability:
public static GeneratedRouteInfo[] GetGeneratedRouteInfo(this INavigator navigator);GeneratedRouteInfo Types
Section titled “GeneratedRouteInfo Types”namespace Shiny.Infrastructure;
public record GeneratedRouteInfo( string Route, // Route name from [ShellMap] string Description, // Description from [ShellMap] (empty if not provided) GeneratedRouteParameter[] Parameters // All [ShellProperty] properties);
public record GeneratedRouteParameter( string ParameterName, // Property name — used as key in NavigateToRoute args string Description, // From [ShellProperty("...")] — empty if not provided string TypeName, // CLR type: "string", "int", "bool", etc. bool IsRequired // From [ShellProperty(required: ...)]);Wiring Up AI Tools
Section titled “Wiring Up AI Tools”Register AiMauiShellTools via the generated AddAiTools() extension in your MauiProgram.cs:
builder.UseShinyShell(x => x .AddGeneratedMaps() .AddAiTools() // registers AiMauiShellTools as singleton);Then inject AiMauiShellTools into your ViewModel and use the Tools and Prompt properties:
using Microsoft.Extensions.AI;
public class ChatViewModel(AiMauiShellTools aiTools){ // Seed the system prompt with route info history.Add(new ChatMessage(ChatRole.System, $"You are a helpful assistant. {aiTools.Prompt}"));
// Use ready-to-use AITool instances var options = new ChatOptions { Tools = [.. aiTools.Tools] }; var response = await chatClient.GetResponseAsync(history, options);}Why Two Tools?
Section titled “Why Two Tools?”The two-tool design (discover + navigate) scales to any number of routes without registering a separate tool per page. The AI:
- Calls
GetRoutes(backed byGetAiToolApplicableGeneratedRoutes) to see all available pages, their descriptions, and parameter schemas - Matches the user’s intent to the right route based on descriptions
- Calls
NavigateToRoutewith the extracted route name and parameters
This avoids registering a generated NavigateToWorkOrder(...) tool for every page — which doesn’t scale and pollutes the tool list.
What Makes a Route “AI Applicable”?
Section titled “What Makes a Route “AI Applicable”?”GetAiToolApplicableGeneratedRoutes filters to routes that meet both criteria:
- The
[ShellMap]has a non-nulldescription - The ViewModel has at least one
[ShellProperty]
Routes like your home page or settings page that don’t need AI parameter filling are excluded automatically. This keeps the AI’s tool response focused on pages it can actually act on.
Configuring AI Extensions
Section titled “Configuring AI Extensions”AI extensions are enabled by default and can be disabled or customized via MSBuild properties in your .csproj:
<PropertyGroup> <!-- Disable AI extensions (enabled by default, requires Microsoft.Extensions.AI) --> <ShinyMauiShell_GenerateAiExtensions>false</ShinyMauiShell_GenerateAiExtensions>
<!-- Customize the generated AI tools class name (default: AiMauiShellTools) --> <ShinyMauiShell_AiToolsClassName>MyAppAiTools</ShinyMauiShell_AiToolsClassName>
<!-- Customize the generated static extensions class name (default: AiExtensions) --> <ShinyMauiShell_AiExtensionsClassName>MyAppRouteExtensions</ShinyMauiShell_AiExtensionsClassName>
<!-- Customize the AI navigate method name (default: NavigateToRoute) --> <ShinyMauiShell_AiNavigateMethodName>GoToPage</ShinyMauiShell_AiNavigateMethodName></PropertyGroup>| Property | Default | Controls |
|---|---|---|
ShinyMauiShell_GenerateAiExtensions | true | AiMauiShellTools class, AddAiTools(), GetAiToolApplicableGeneratedRoutes, NavigateToRoute, and Prompt. Requires Microsoft.Extensions.AI (SHINY003 error if missing). Set to false to disable |
ShinyMauiShell_AiToolsClassName | AiMauiShellTools | Class name for the generated AI tools class |
ShinyMauiShell_AiExtensionsClassName | AiExtensions | Class name for the static route info extensions class |
ShinyMauiShell_AiNavigateMethodName | NavigateToRoute | Method name for the AI-friendly navigate method |
Tips for Better AI Descriptions
Section titled “Tips for Better AI Descriptions”| Do | Don’t |
|---|---|
| Describe user intent signals | Name the page |
| ”Use when the user reports something broken" | "Work order form” |
| Tell AI to infer from context | Expect exact field values |
| ”Infer urgency from tone" | "The priority level” |
| Make optional fields truly optional | Require everything |
| ”Leave empty if not mentioned” | Require the user to state everything |