Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<Folder Name="/Samples/GettingStarted/AgentSkills/">
<File Path="samples/GettingStarted/AgentSkills/README.md" />
<Project Path="samples/GettingStarted/AgentSkills/Agent_Step01_BasicSkills/Agent_Step01_BasicSkills.csproj" />
<Project Path="samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/DeclarativeAgents/">
<Project Path="samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
var skillsProvider = new FileAgentSkillsProvider(skillPath: Path.Combine(AppContext.BaseDirectory, "skills"));

// --- Agent Setup ---
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.AsAIAgent(new ChatClientAgentOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<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>

<!-- 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,49 @@
// Copyright (c) Microsoft. All rights reserved.

// This sample demonstrates how to use Agent Skills with script execution via the hosted code interpreter.
// When SkillScriptExecutor.HostedCodeInterpreter() is configured, the agent can load and execute scripts
// from skill resources using the LLM provider's built-in code interpreter.
//
// This sample includes the password-generator skill:
// - A Python script for generating secure passwords

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

// --- Skills Provider with Script Execution ---
// Discovers skills and enables script execution via the hosted code interpreter
var skillsProvider = new FileAgentSkillsProvider(
skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
options: new FileAgentSkillsProviderOptions
{
Executor = SkillScriptExecutor.HostedCodeInterpreter()
});

// --- Agent Setup ---
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.AsAIAgent(new ChatClientAgentOptions
{
Name = "SkillsAgent",
ChatOptions = new()
{
Instructions = "You are a helpful assistant that can generate secure passwords.",
},
AIContextProviders = [skillsProvider],
});

// --- Example: Password generation with script execution ---
Console.WriteLine("Example: Generating a password with a skill script");
Console.WriteLine("---------------------------------------------------");
AgentResponse response = await agent.RunAsync("Generate a secure password for my database account.");
Console.WriteLine($"Agent: {response.Text}\n");
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Script Execution with Code Interpreter

This sample demonstrates how to use **Agent Skills** with **script execution** via the hosted code interpreter.

## What's Different from Step01?

In the [basic skills sample](../Agent_Step01_BasicSkills/), skills only provide instructions and resources as text. This sample adds **script execution** — the agent can load Python scripts from skill resources and execute them using the LLM provider's built-in code interpreter.

This is enabled by configuring `SkillScriptExecutor.HostedCodeInterpreter()` on the skills provider options:

```csharp
var skillsProvider = new FileAgentSkillsProvider(
skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
options: new FileAgentSkillsProviderOptions
{
Executor = SkillScriptExecutor.HostedCodeInterpreter()
});
```

## Skills Included

### password-generator
Generates secure passwords using a Python script with configurable length and complexity.
- `scripts/generate.py` — Password generation script
- `references/PASSWORD_GUIDELINES.md` — Recommended length and symbol sets by use case

## Project Structure

```
Agent_Step02_ScriptExecutionWithCodeInterpreter/
├── Program.cs
├── Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj
└── skills/
└── password-generator/
├── SKILL.md
├── scripts/
│ └── generate.py
└── references/
└── PASSWORD_GUIDELINES.md
```

## Running the Sample

### Prerequisites
- .NET 10.0 SDK
- Azure OpenAI endpoint with a deployed model that supports code interpreter

### Setup
1. Set environment variables:
```bash
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
```

2. Run the sample:
```bash
dotnet run
```

### Example

The sample asks the agent to generate a secure password. The agent:
1. Loads the password-generator skill
2. Reads the `generate.py` script via `read_skill_resource`
3. Executes the script using the code interpreter with appropriate parameters
4. Returns the generated password

## Learn More

- [Agent Skills Specification](https://agentskills.io/)
- [Step01: Basic Skills](../Agent_Step01_BasicSkills/) — Skills without script execution
- [Microsoft Agent Framework Documentation](../../../../../docs/)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: password-generator
description: Generate secure passwords using a Python script. Use when asked to create passwords or credentials.
---

# Password Generator

This skill generates secure passwords using a Python script.

## Usage

When the user requests a password:
1. First, review `references/PASSWORD_GUIDELINES.md` to determine the recommended password length and character sets for the user's use case
2. Load `scripts/generate.py` and adjust its parameters (length, character set) based on the guidelines and user's requirements
3. Execute the script
4. Present the generated password clearly
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Password Generation Guidelines

## General Rules

- Never reuse passwords across services.
- Always use cryptographically secure randomness (e.g., `random.SystemRandom()`).
- Avoid dictionary words, keyboard patterns, and personal information.

## Recommended Settings by Use Case

| Use Case | Min Length | Character Set | Example |
|-----------------------|-----------|----------------------------------------|--------------------------|
| Web account | 16 | Upper + lower + digits + symbols | `G7!kQp@2xM#nW9$z` |
| Database credential | 24 | Upper + lower + digits + symbols | `aR3$vK8!mN2@pQ7&xL5#wY` |
| Wi-Fi / network key | 20 | Upper + lower + digits + symbols | `Ht4&jL9!rP2#mK7@xQ` |
| API key / token | 32 | Upper + lower + digits (no symbols) | `k8Rm3xQ7nW2pL9vT4jH6yA` |
| Encryption passphrase | 32 | Upper + lower + digits + symbols | `Xp4!kR8@mN2#vQ7&jL9$wT` |

## Symbol Sets

- **Standard symbols**: `!@#$%^&*()-_=+`
- **Extended symbols**: `~`{}[]|;:'",.<>?/\`
- **Safe symbols** (URL/shell-safe): `!@#$&*-_=+`
- If the target system restricts symbols, use only the **safe** set.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Password generator script
# Usage: Adjust 'length' as needed, then run

import random
import string

length = 16 # desired length

pool = string.ascii_lowercase + string.ascii_uppercase + string.digits + string.punctuation
password = "".join(random.SystemRandom().choice(pool) for _ in range(length))
print(f"Generated password ({length} chars): {password}")
1 change: 1 addition & 0 deletions dotnet/samples/GettingStarted/AgentSkills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Samples demonstrating Agent Skills capabilities.
| Sample | Description |
|--------|-------------|
| [Agent_Step01_BasicSkills](Agent_Step01_BasicSkills/) | Using Agent Skills with a ChatClientAgent, including progressive disclosure and skill resources |
| [Agent_Step02_ScriptExecutionWithCodeInterpreter](Agent_Step02_ScriptExecutionWithCodeInterpreter/) | Using Agent Skills with script execution via the hosted code interpreter |
9 changes: 6 additions & 3 deletions dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ internal sealed partial class FileAgentSkillLoader
// Example: "---\nname: foo\n---\nBody" → Group 1: "name: foo\n"
private static readonly Regex s_frontmatterRegex = new(@"\A\uFEFF?^---\s*$(.+?)^---\s*$", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled, TimeSpan.FromSeconds(5));

// Matches markdown links to local resource files. Group 1 = relative file path.
// Matches resource file references in skill markdown. Group 1 = relative file path.
// Supports two forms:
// 1. Markdown links: [text](path/file.ext)
// 2. Backtick-quoted paths: `path/file.ext`
// Supports optional ./ or ../ prefixes; excludes URLs (no ":" in the path character class).
// Intentionally conservative: only matches paths with word characters, hyphens, dots,
// and forward slashes. Paths with spaces or special characters are not supported.
// Examples: [doc](refs/FAQ.md) → "refs/FAQ.md", [s](./s.json) → "./s.json",
// Examples: [doc](refs/FAQ.md) → "refs/FAQ.md", `./scripts/run.py` → "./scripts/run.py",
// [p](../shared/doc.txt) → "../shared/doc.txt"
private static readonly Regex s_resourceLinkRegex = new(@"\[.*?\]\((\.?\.?/?[\w][\w\-./]*\.\w+)\)", RegexOptions.Compiled, TimeSpan.FromSeconds(5));
private static readonly Regex s_resourceLinkRegex = new(@"(?:\[.*?\]\(|`)(\.?\.?/?[\w][\w\-./]*\.\w+)(?:\)|`)", RegexOptions.Compiled, TimeSpan.FromSeconds(5));

// Matches YAML "key: value" lines. Group 1 = key, Group 2 = quoted value, Group 3 = unquoted value.
// Accepts single or double quotes; the lazy quantifier trims trailing whitespace on unquoted values.
Expand Down
45 changes: 18 additions & 27 deletions dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,21 @@ You have access to skills containing domain-specific knowledge and capabilities.
Each skill provides specialized instructions, reference documents, and assets for specific tasks.

<available_skills>
{0}
{skills}
</available_skills>

When a task aligns with a skill's domain:
1. Use `load_skill` to retrieve the skill's instructions
2. Follow the provided guidance
3. Use `read_skill_resource` to read any references or other files mentioned by the skill

- Use `load_skill` to retrieve the skill's instructions
- Follow the provided guidance
- Use `read_skill_resource` to read any references or other files mentioned by the skill, always using the full path as written (e.g. `references/FAQ.md`, not just `FAQ.md`)
{executor_instructions}
Only load what is needed, when it is needed.
""";

private readonly Dictionary<string, FileAgentSkill> _skills;
private readonly ILogger<FileAgentSkillsProvider> _logger;
private readonly FileAgentSkillLoader _loader;
private readonly AITool[] _tools;
private readonly IEnumerable<AITool> _tools;
private readonly string? _skillsInstructionPrompt;

/// <summary>
Expand Down Expand Up @@ -91,9 +91,9 @@ public FileAgentSkillsProvider(IEnumerable<string> skillPaths, FileAgentSkillsPr
this._loader = new FileAgentSkillLoader(this._logger);
this._skills = this._loader.DiscoverAndLoadSkills(skillPaths);

this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills);
this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills, options?.Executor);

this._tools =
AITool[] baseTools =
[
AIFunctionFactory.Create(
this.LoadSkill,
Expand All @@ -104,6 +104,10 @@ public FileAgentSkillsProvider(IEnumerable<string> skillPaths, FileAgentSkillsPr
name: "read_skill_resource",
description: "Reads a file associated with a skill, such as references or assets."),
];

this._tools = options?.Executor?.Tools is { Count: > 0 } executorTools
? baseTools.Concat(executorTools)
: baseTools;
}

/// <inheritdoc />
Expand All @@ -117,7 +121,7 @@ protected override ValueTask<AIContext> ProvideAIContextAsync(InvokingContext co
return new ValueTask<AIContext>(new AIContext
{
Instructions = this._skillsInstructionPrompt,
Tools = this._tools
Tools = this._tools,
});
}

Expand Down Expand Up @@ -166,24 +170,9 @@ private async Task<string> ReadSkillResourceAsync(string skillName, string resou
}
}

private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary<string, FileAgentSkill> skills)
private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary<string, FileAgentSkill> skills, SkillScriptExecutor? executor)
{
string promptTemplate = DefaultSkillsInstructionPrompt;

if (options?.SkillsInstructionPrompt is { } optionsInstructions)
{
try
{
promptTemplate = string.Format(optionsInstructions, string.Empty);
}
catch (FormatException ex)
{
throw new ArgumentException(
"The provided SkillsInstructionPrompt is not a valid format string. It must contain a '{0}' placeholder and escape any literal '{' or '}' by doubling them ('{{' or '}}').",
nameof(options),
ex);
}
}
string promptTemplate = options?.SkillsInstructionPrompt ?? DefaultSkillsInstructionPrompt;

if (skills.Count == 0)
{
Expand All @@ -202,7 +191,9 @@ private async Task<string> ReadSkillResourceAsync(string skillName, string resou
sb.AppendLine(" </skill>");
}

return string.Format(promptTemplate, sb.ToString().TrimEnd());
return promptTemplate
.Replace("{skills}", sb.ToString().TrimEnd())
.Replace("{executor_instructions}", executor?.Instructions ?? "\n");
}

[LoggerMessage(LogLevel.Information, "Loading skill: {SkillName}")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,20 @@ public sealed class FileAgentSkillsProviderOptions
{
/// <summary>
/// Gets or sets a custom system prompt template for advertising skills.
/// Use <c>{0}</c> as the placeholder for the generated skills list.
/// Use <c>{skills}</c> as the placeholder for the generated skills list and
/// <c>{executor_instructions}</c> for executor-provided instructions.
/// When <see langword="null"/>, a default template is used.
/// </summary>
public string? SkillsInstructionPrompt { get; set; }

/// <summary>
/// Gets or sets the skill executor that enables script execution for loaded skills.
/// </summary>
/// <remarks>
/// When <see langword="null"/> (the default), script execution is disabled and skills only provide
/// instructions and resources. Set this to a <see cref="SkillScriptExecutor"/> instance (e.g.,
/// <see cref="SkillScriptExecutor.HostedCodeInterpreter()"/>) to enable script execution with
/// mode-specific instructions and tools.
/// </remarks>
public SkillScriptExecutor? Executor { get; set; }
}
Loading
Loading