mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
e5f7b9c260
* support reflection for discovery of resources and scripts in class-based skills * fix format issues * refactor samples to use reflection * Validate resource member signatures during discovery Add discovery-time validation in AgentClassSkill.DiscoverResources() to fail fast when [AgentSkillResource] is applied to members with incompatible signatures: - Reject indexer properties (getter has parameters) - Reject methods with parameters other than IServiceProvider or CancellationToken Throws InvalidOperationException with actionable error messages instead of allowing silent runtime failures when ReadAsync invokes the AIFunction with no named arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * prevent duplicates --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
151 lines
6.4 KiB
C#
151 lines
6.4 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
||
|
||
// This sample demonstrates an advanced scenario: combining multiple skill types in a single agent
|
||
// using AgentSkillsProviderBuilder. The builder is designed for cases where the simple
|
||
// AgentSkillsProvider constructors are insufficient — for example, when you need to mix skill
|
||
// sources, apply filtering, or configure cross-cutting options in one place.
|
||
//
|
||
// Three different skill sources are registered here:
|
||
// 1. File-based: unit-converter (miles↔km, pounds↔kg) from SKILL.md on disk
|
||
// 2. Code-defined: volume-converter (gallons↔liters) using AgentInlineSkill
|
||
// 3. Class-based: temperature-converter (°F↔°C↔K) using AgentClassSkill with attributes
|
||
//
|
||
// For simpler, single-source scenarios, see the earlier steps in this sample series
|
||
// (e.g., Step01 for file-based, Step02 for code-defined, Step03 for class-based).
|
||
|
||
using System.ComponentModel;
|
||
using System.Text.Json;
|
||
using Azure.AI.OpenAI;
|
||
using Azure.Identity;
|
||
using Microsoft.Agents.AI;
|
||
using OpenAI.Responses;
|
||
|
||
// --- Configuration ---
|
||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
|
||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||
|
||
// --- 1. Code-Defined Skill: volume-converter ---
|
||
var volumeConverterSkill = new AgentInlineSkill(
|
||
name: "volume-converter",
|
||
description: "Convert between gallons and liters using a multiplication factor.",
|
||
instructions: """
|
||
Use this skill when the user asks to convert between gallons and liters.
|
||
|
||
1. Review the volume-conversion-table resource to find the correct factor.
|
||
2. Use the convert-volume script, passing the value and factor.
|
||
""")
|
||
.AddResource("volume-conversion-table",
|
||
"""
|
||
# Volume Conversion Table
|
||
|
||
Formula: **result = value × factor**
|
||
|
||
| From | To | Factor |
|
||
|---------|---------|---------|
|
||
| gallons | liters | 3.78541 |
|
||
| liters | gallons | 0.264172|
|
||
""")
|
||
.AddScript("convert-volume", (double value, double factor) =>
|
||
{
|
||
double result = Math.Round(value * factor, 4);
|
||
return JsonSerializer.Serialize(new { value, factor, result });
|
||
});
|
||
|
||
// --- 2. Class-Based Skill: temperature-converter ---
|
||
var temperatureConverter = new TemperatureConverterSkill();
|
||
|
||
// --- 3. Build provider combining all three source types ---
|
||
var skillsProvider = new AgentSkillsProviderBuilder()
|
||
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills")) // File-based: unit-converter
|
||
.UseSkill(volumeConverterSkill) // Code-defined: volume-converter
|
||
.UseSkill(temperatureConverter) // Class-based: temperature-converter
|
||
.UseFileScriptRunner(SubprocessScriptRunner.RunAsync)
|
||
.Build();
|
||
|
||
// --- Agent Setup ---
|
||
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
|
||
.GetResponsesClient()
|
||
.AsAIAgent(new ChatClientAgentOptions
|
||
{
|
||
Name = "MultiConverterAgent",
|
||
ChatOptions = new()
|
||
{
|
||
Instructions = "You are a helpful assistant that can convert units, volumes, and temperatures.",
|
||
},
|
||
AIContextProviders = [skillsProvider],
|
||
},
|
||
model: deploymentName);
|
||
|
||
// --- Example: Use all three skills ---
|
||
Console.WriteLine("Converting with mixed skills (file + code + class)");
|
||
Console.WriteLine(new string('-', 60));
|
||
|
||
AgentResponse response = await agent.RunAsync(
|
||
"I need three conversions: " +
|
||
"1) How many kilometers is a marathon (26.2 miles)? " +
|
||
"2) How many liters is a 5-gallon bucket? " +
|
||
"3) What is 98.6°F in Celsius?");
|
||
|
||
Console.WriteLine($"Agent: {response.Text}");
|
||
|
||
/// <summary>
|
||
/// A temperature-converter skill defined as a C# class using attributes for discovery.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Properties annotated with <see cref="AgentSkillResourceAttribute"/> are automatically
|
||
/// discovered as skill resources, and methods annotated with <see cref="AgentSkillScriptAttribute"/>
|
||
/// are automatically discovered as skill scripts.
|
||
/// </remarks>
|
||
internal sealed class TemperatureConverterSkill : AgentClassSkill<TemperatureConverterSkill>
|
||
{
|
||
/// <inheritdoc/>
|
||
public override AgentSkillFrontmatter Frontmatter { get; } = new(
|
||
"temperature-converter",
|
||
"Convert between temperature scales (Fahrenheit, Celsius, Kelvin).");
|
||
|
||
/// <inheritdoc/>
|
||
protected override string Instructions => """
|
||
Use this skill when the user asks to convert temperatures.
|
||
|
||
1. Review the temperature-conversion-formulas resource for the correct formula.
|
||
2. Use the convert-temperature script, passing the value, source scale, and target scale.
|
||
3. Present the result clearly with both temperature scales.
|
||
""";
|
||
|
||
/// <summary>
|
||
/// A reference table of temperature conversion formulas.
|
||
/// </summary>
|
||
[AgentSkillResource("temperature-conversion-formulas")]
|
||
[Description("Formulas for converting between Fahrenheit, Celsius, and Kelvin.")]
|
||
public string ConversionFormulas => """
|
||
# Temperature Conversion Formulas
|
||
|
||
| From | To | Formula |
|
||
|-------------|-------------|---------------------------|
|
||
| Fahrenheit | Celsius | °C = (°F − 32) × 5/9 |
|
||
| Celsius | Fahrenheit | °F = (°C × 9/5) + 32 |
|
||
| Celsius | Kelvin | K = °C + 273.15 |
|
||
| Kelvin | Celsius | °C = K − 273.15 |
|
||
""";
|
||
|
||
/// <summary>
|
||
/// Converts a temperature value between scales.
|
||
/// </summary>
|
||
[AgentSkillScript("convert-temperature")]
|
||
[Description("Converts a temperature value from one scale to another.")]
|
||
private static string ConvertTemperature(double value, string from, string to)
|
||
{
|
||
double result = (from.ToUpperInvariant(), to.ToUpperInvariant()) switch
|
||
{
|
||
("FAHRENHEIT", "CELSIUS") => Math.Round((value - 32) * 5.0 / 9.0, 2),
|
||
("CELSIUS", "FAHRENHEIT") => Math.Round(value * 9.0 / 5.0 + 32, 2),
|
||
("CELSIUS", "KELVIN") => Math.Round(value + 273.15, 2),
|
||
("KELVIN", "CELSIUS") => Math.Round(value - 273.15, 2),
|
||
_ => throw new ArgumentException($"Unsupported conversion: {from} → {to}")
|
||
};
|
||
|
||
return JsonSerializer.Serialize(new { value, from, to, result });
|
||
}
|
||
}
|