Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,6 @@ local.settings.json
# Database files
*.db
python/dotnet-ref

# Generated filtered solution files (created by eng/scripts/New-FilteredSolution.ps1)
dotnet/filtered-*.slnx
2 changes: 1 addition & 1 deletion dotnet/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ using types like `IChatClient`, `FunctionInvokingChatClient`, `AITool`, `AIFunct
- **Async**: Use `Async` suffix for methods returning `Task`/`ValueTask`
- **Private classes**: Should be `sealed` unless subclassed
- **Config**: Read from environment variables with `UPPER_SNAKE_CASE` naming
- **Tests**: Add Arrange/Act/Assert comments; use Moq for mocking
- **Tests**: Add Arrange/Act/Assert comments; use Moq for mocking; test methods returning `Task`/`ValueTask` must use the `Async` suffix.

## Key Design Principles

Expand Down
3 changes: 3 additions & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@
<File Path="samples/02-agents/AgentSkills/README.md" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Agent_Step01_FileBasedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step02_CodeDefinedSkills/Agent_Step02_CodeDefinedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Agent_Step03_ClassBasedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj" />
<Project Path="samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AGUI/Step05_StateManagement/">
<Project Path="samples/02-agents/AGUI/Step05_StateManagement/Client/Client.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This sample demonstrates how to use **file-based Agent Skills** with a `ChatClie

- Discovering skills from `SKILL.md` files on disk via `AgentFileSkillsSource`
- The progressive disclosure pattern: advertise → load → read resources → run scripts
- Using the `AgentSkillsProvider` constructor with a skill directory path and script executor
- Using the `AgentSkillsProvider` constructor with a skill directory path and script runner
- Running file-based scripts (Python) via a subprocess-based executor

## Skills Included
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>

<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);MAAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft. All rights reserved.

// This sample demonstrates how to define Agent Skills as C# classes using AgentClassSkill.
// Class-based skills bundle all components into a single class implementation.

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-4o-mini";

// --- Class-Based Skill ---
// Instantiate the skill class.
var unitConverter = new UnitConverterSkill();

// --- Skills Provider ---
var skillsProvider = new AgentSkillsProvider(unitConverter);

// --- Agent Setup ---
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "UnitConverterAgent",
ChatOptions = new()
{
Instructions = "You are a helpful assistant that can convert units.",
},
AIContextProviders = [skillsProvider],
},
model: deploymentName);

// --- Example: Unit conversion ---
Console.WriteLine("Converting units with class-based skills");
Console.WriteLine(new string('-', 60));

AgentResponse response = await agent.RunAsync(
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?");

Console.WriteLine($"Agent: {response.Text}");

/// <summary>
/// A unit-converter skill defined as a C# class.
/// </summary>
/// <remarks>
/// Class-based skills bundle all components (name, description, body, resources, scripts)
/// into a single class.
/// </remarks>
internal sealed class UnitConverterSkill : AgentClassSkill
{
private IReadOnlyList<AgentSkillResource>? _resources;
private IReadOnlyList<AgentSkillScript>? _scripts;

/// <inheritdoc/>
public override AgentSkillFrontmatter Frontmatter { get; } = new(
"unit-converter",
"Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.");

/// <inheritdoc/>
protected override string Instructions => """
Use this skill when the user asks to convert between units.

1. Review the conversion-table resource to find the factor for the requested conversion.
2. Use the convert script, passing the value and factor from the table.
3. Present the result clearly with both units.
""";

/// <inheritdoc/>
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
[
CreateResource(
"conversion-table",
"""
# Conversion Tables

Formula: **result = value × factor**

| From | To | Factor |
|-------------|-------------|----------|
| miles | kilometers | 1.60934 |
| kilometers | miles | 0.621371 |
| pounds | kilograms | 0.453592 |
| kilograms | pounds | 2.20462 |
"""),
];

/// <inheritdoc/>
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
[
CreateScript("convert", ConvertUnits),
];

private static string ConvertUnits(double value, double factor)
{
double result = Math.Round(value * factor, 4);
return JsonSerializer.Serialize(new { value, factor, result });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Class-Based Agent Skills Sample

This sample demonstrates how to define **Agent Skills as C# classes** using `AgentClassSkill`.

## What it demonstrates

- Creating skills as classes that extend `AgentClassSkill`
- Bundling name, description, body, resources, and scripts into a single class
- Using the `AgentSkillsProvider` constructor with class-based skills

## Skills Included

### unit-converter (class-based)

A `UnitConverterSkill` class that converts between common units. Defined in `Program.cs`:

- `conversion-table` — Static resource with factor table
- `convert` — Script that performs `value × factor` conversion

## Running the Sample

### Prerequisites

- .NET 10.0 SDK
- Azure OpenAI endpoint with a deployed model

### Setup

```bash
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
```

### Run

```bash
dotnet run
```

### Expected Output

```
Converting units with class-based skills
------------------------------------------------------------
Agent: Here are your conversions:

1. **26.2 miles → 42.16 km** (a marathon distance)
2. **75 kg → 165.35 lbs**
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>

<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);MAAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\SubprocessScriptRunner.cs" Link="SubprocessScriptRunner.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>

<!-- Copy skills directory to output -->
<ItemGroup>
<None Include="skills\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 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
//
// 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.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-4o-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.
/// </summary>
internal sealed class TemperatureConverterSkill : AgentClassSkill
{
private IReadOnlyList<AgentSkillResource>? _resources;
private IReadOnlyList<AgentSkillScript>? _scripts;

/// <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.
""";

/// <inheritdoc/>
public override IReadOnlyList<AgentSkillResource>? Resources => this._resources ??=
[
CreateResource(
"temperature-conversion-formulas",
"""
# 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 |
"""),
];

/// <inheritdoc/>
public override IReadOnlyList<AgentSkillScript>? Scripts => this._scripts ??=
[
CreateScript("convert-temperature", ConvertTemperature),
];

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 });
}
}
Loading
Loading