chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
# Agent Skills Samples
These samples demonstrate how to use **Agent Skills** — modular packages of instructions, resources, and scripts that extend an agent's capabilities. Skills follow the [Agent Skills specification](https://agentskills.io/) and use progressive disclosure to optimize token usage.
## Learning Path
Start with file-based or code-defined skills, then explore combining them and adding approval workflows.
| Sample | Description |
|--------|-------------|
| [**file_based_skill**](file_based_skill/) | Define skills as `SKILL.md` files on disk with reference documents and executable scripts. Uses the unit-converter skill. |
| [**code_defined_skill**](code_defined_skill/) | Define skills entirely in Python code using `Skill`, `@skill.resource`, and `@skill.script` decorators. Uses a code-defined unit-converter skill. |
| [**class_based_skill**](class_based_skill/) | Define skills as Python classes using `ClassSkill` with `@ClassSkill.resource` and `@ClassSkill.script` decorators for auto-discovery. Uses a class-based unit-converter skill. |
| [**mixed_skills**](mixed_skills/) | Combine code-defined, class-based, and file-based skills in a single agent. Uses a code-defined volume-converter, a class-based temperature-converter, and a file-based unit-converter. |
| [**mcp_based_skill**](mcp_based_skill/) | Discover skills served over the [Model Context Protocol (MCP)](https://modelcontextprotocol.io) via `MCPSkillsSource`. Connects to a remote MCP server that exposes skills as `skill://...` resources following the SEP-2640 convention. |
| [**script_approval**](script_approval/) | Require manual human-in-the-loop approval before running skill tools (the default). |
| [**skills_auto_approval**](skills_auto_approval/) | Configure auto-approval rules with `ToolApprovalMiddleware` so read-only skill tools are approved automatically while script execution still prompts. |
## Key Concepts
### Progressive Disclosure
Skills use a three-step interaction model to minimize token usage:
1. **Advertise** — Skill names and descriptions (~100 tokens each) are injected into the system prompt
2. **Load** — Full instructions are loaded on-demand via the `load_skill` tool
3. **Access** — Resources are read via `read_skill_resource`; scripts are executed via `run_skill_script`
### File-Based vs Code-Defined vs Class-Based Skills
| Aspect | File-Based | Code-Defined | Class-Based |
|--------|-----------|--------------|-------------|
| Definition | `SKILL.md` files on disk | `Skill` instances in Python | Classes extending `ClassSkill` |
| Resources | Static files in `references/` and `assets/` directories | Callable functions via `@skill.resource` decorator | `@ClassSkill.resource` decorator (auto-discovered) |
| Scripts | Python files in `scripts/` directory (executed via subprocess) | Callable functions via `@skill.script` decorator (executed in-process) | `@ClassSkill.script` decorator (executed in-process) |
| Discovery | Automatic via `skill_paths` parameter | Explicit via `skills` parameter | Explicit via `skills` parameter |
| Dynamic content | No (static files only) | Yes (functions can generate content at runtime) | Yes (functions can generate content at runtime) |
| Sharing pattern | Copy skill directory | Inline or shared instances | Package in shared libraries/PyPI |
All three types can be combined in a single `SkillsProvider` — see the [mixed_skills](mixed_skills/) sample.
### Script Execution
Skills can include executable scripts. How a script runs depends on how it was defined:
| | Code-Defined Scripts | File-Based Scripts |
|---|---|---|
| **Defined via** | `@skill.script` decorator | `.py` files in `scripts/` directory |
| **Execution** | In-process (direct function call) | Delegated to a `script_runner` |
| **`script_runner` needed?** | No — runs in-process automatically | **Yes** — required |
The `script_runner` parameter on `SkillsProvider` is only applicable to **file-based** scripts. Code-defined scripts are always executed in-process regardless of this setting. See [file_based_skill](file_based_skill/) for an example using a `SkillScriptRunner` callable with a subprocess runner, and [code_defined_skill](code_defined_skill/) for in-process scripts that need no runner.
## Prerequisites
All samples require:
- An [Azure AI Foundry](https://ai.azure.com/) project with a deployed model (e.g. `gpt-4o-mini`)
- Azure CLI authentication (`az login`)
- Environment variables set in a `.env` file (see `python/.env.example`)
## Suppressing the experimental MCP Skills warning
The core Agent Skills APIs are stable. MCP-based skill discovery
(`MCPSkillsSource`) is still experimental, so the [mcp_based_skill](mcp_based_skill/)
sample includes a short commented `warnings.filterwarnings(...)` snippet near the
imports. Uncomment it if you want to suppress the MCP Skills warning.
@@ -0,0 +1,71 @@
# Class-Based Agent Skills
This sample demonstrates how to define **Agent Skills as Python classes** using `ClassSkill`.
## What's Demonstrated
- Creating skills as classes that extend `ClassSkill`
- Bundling name, description, instructions, resources, and scripts into a single class
- Using `@ClassSkill.resource` decorator for automatic resource discovery
- Using `@ClassSkill.script` decorator for automatic script discovery
- Lazy-loading and caching of resources and scripts
- Registering class-based skills with `SkillsProvider`
## Skills Included
### unit-converter (class-based)
A `UnitConverterSkill` class that converts between common units. Defined in `class_based_skill.py`:
- `conversion-table` — Static resource with factor table
- `convert` — Script that performs `value × factor` conversion
## Project Structure
```
class_based_skill/
├── class_based_skill.py
└── README.md
```
## Running the Sample
### Prerequisites
- An [Azure AI Foundry](https://ai.azure.com/) project with a deployed model (e.g. `gpt-4o-mini`)
### Environment Variables
Set the required environment variables in a `.env` file (see `python/.env.example`):
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `FOUNDRY_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
### Authentication
This sample uses `AzureCliCredential` for authentication. Run `az login` in your terminal before running the sample.
### Run
```bash
cd python
uv run samples/02-agents/skills/class_based_skill/class_based_skill.py
```
### 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**
```
## Learn More
- [Agent Skills Specification](https://agentskills.io/)
- [Code-Defined Skills Sample](../code_defined_skill/)
- [Mixed Skills Sample](../mixed_skills/)
- [Microsoft Agent Framework Documentation](../../../../../docs/)
@@ -0,0 +1,148 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import json
import os
from textwrap import dedent
from agent_framework import Agent, ClassSkill, SkillFrontmatter, SkillsProvider, ToolApprovalMiddleware
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
"""
Class-Based Agent Skills — Define skills as Python classes
This sample demonstrates how to define Agent Skills as reusable Python classes
by subclassing ``ClassSkill``. Class-based skills bundle all components (name,
description, instructions, resources, scripts) into a single class, making
them easy to package and distribute via shared libraries or PyPI.
Key concepts shown:
- Subclassing ``ClassSkill`` to create a self-contained skill
- Using ``@property`` + ``@ClassSkill.resource`` (bare) — name defaults to method name
- Using ``@ClassSkill.script(name=..., description=...)`` — explicit name and description
- Lazy-loading and caching of resources and scripts
"""
# Load environment variables from .env file
load_dotenv()
# ---------------------------------------------------------------------------
# Class-Based Skill: UnitConverterSkill
# ---------------------------------------------------------------------------
class UnitConverterSkill(ClassSkill):
"""A unit-converter skill defined as a Python class.
Converts between common units (miles↔km, pounds↔kg) using a
conversion factor. Resources and scripts are discovered automatically
via decorators.
"""
def __init__(self) -> None:
super().__init__(
frontmatter=SkillFrontmatter(
name="unit-converter",
description=(
"Convert between common units using a multiplication factor. "
"Use when asked to convert miles, kilometers, pounds, or kilograms."
),
),
)
@property
def instructions(self) -> str:
return dedent("""\
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.
""")
# 1. Property with bare decorator — name defaults to the method name
# ("conversion_table" → "conversion-table"), no description.
# Place @property first, then @ClassSkill.resource.
@property
@ClassSkill.resource
def conversion_table(self) -> str:
"""Lookup table of multiplication factors for common unit conversions."""
return dedent("""\
# 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 |
""")
# 2. Explicit name — overrides the method name
# 3. Explicit description — provides a description for the script
@ClassSkill.script(name="convert", description="Multiplies a value by a conversion factor.")
def convert_units(self, value: float, factor: float) -> str:
"""Convert a value using a multiplication factor: result = value × factor.
Args:
value: The numeric value to convert.
factor: Conversion factor from the conversion table.
Returns:
JSON string with the inputs and converted result.
"""
result = round(value * factor, 4)
return json.dumps({"value": value, "factor": factor, "result": result})
async def main() -> None:
"""Run the class-based skills demo."""
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
# Instantiate the class-based skill and pass it to the provider
unit_converter = UnitConverterSkill()
# All skill tools require approval by default; auto-approve them so the
# sample runs unattended. See the script_approval / skills_auto_approval
# samples for interactive and selective approval handling.
async with Agent(
client=client,
instructions="You are a helpful assistant that can convert units.",
context_providers=[SkillsProvider(unit_converter)],
middleware=[ToolApprovalMiddleware(auto_approval_rules=[SkillsProvider.all_tools_auto_approval_rule])],
) as agent:
print("Converting units with class-based skills")
print("-" * 60)
session = agent.create_session()
response = await agent.run(
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?",
session=session,
)
print(f"Agent: {response}\n")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample 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**
"""
@@ -0,0 +1,49 @@
# Code-Defined Agent Skills
This sample demonstrates how to create **Agent Skills** in Python code, without needing `SKILL.md` files on disk. A unit-converter skill shows three approaches:
## What's Demonstrated
1. **Static Resources** — Pass inline content via the `resources` parameter when constructing a `Skill`
2. **Dynamic Resources** — Attach callable functions via the `@skill.resource` decorator that return content computed at runtime
3. **Dynamic Scripts** — Attach callable scripts via the `@skill.script` decorator (unit conversion via a single factor parameter)
All three can be combined with file-based skills in a single `SkillsProvider`.
## Project Structure
```
code_defined_skill/
├── code_defined_skill.py
└── README.md
```
## Running the Sample
### Prerequisites
- An [Azure AI Foundry](https://ai.azure.com/) project with a deployed model (e.g. `gpt-4o-mini`)
### Environment Variables
Set the required environment variables in a `.env` file (see `python/.env.example`):
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `AZURE_OPENAI_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
### Authentication
This sample uses `AzureCliCredential` for authentication. Run `az login` in your terminal before running the sample.
### Run
```bash
cd python
uv run samples/02-agents/skills/code_defined_skill/code_defined_skill.py
```
## Learn More
- [Agent Skills Specification](https://agentskills.io/)
- [File-Based Skills Sample](../file_based_skill/)
- [Mixed Skills Sample](../mixed_skills/)
- [Microsoft Agent Framework Documentation](../../../../../docs/)
@@ -0,0 +1,184 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import json
import os
from textwrap import dedent
from typing import Any
from agent_framework import (
Agent,
InlineSkill,
InlineSkillResource,
SkillFrontmatter,
SkillsProvider,
ToolApprovalMiddleware,
)
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
"""
Code-Defined Agent Skills — Define skills in Python code
This sample demonstrates how to create Agent Skills in code,
without needing SKILL.md files on disk. Three approaches are shown
using a unit-converter skill:
1. Static Resources
Pass inline content directly via the ``resources`` parameter when
constructing the Skill.
2. Dynamic Resources
Attach a callable resource via the @skill.resource decorator. The
function is invoked on demand, so it can return data computed at
runtime.
3. Dynamic Scripts
Attach a callable script via the @skill.script decorator. Scripts are
executable functions the agent can invoke directly in-process.
Code-defined skills can be combined with file-based skills in a single
SkillsProvider — see the mixed_skills sample.
"""
# Load environment variables from .env file
load_dotenv()
# ---------------------------------------------------------------------------
# 1. Static Resources — inline content passed at construction time
# ---------------------------------------------------------------------------
unit_converter_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="unit-converter", description="Convert between common units using a conversion factor"
),
instructions=dedent("""\
Use this skill when the user asks to convert between units.
1. Review the conversion-tables resource to find the factor for the
requested conversion.
2. Check the conversion-policy resource for rounding and formatting rules.
3. Use the convert script, passing the value and factor from the table.
"""),
resources=[
InlineSkillResource(
name="conversion-tables",
content=dedent("""\
# 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 |
"""),
),
],
)
# ---------------------------------------------------------------------------
# 2. Dynamic Resources — callable function via @skill.resource
# ---------------------------------------------------------------------------
@unit_converter_skill.resource(
name="conversion-policy", description="Current conversion formatting and rounding policy"
)
def conversion_policy(**kwargs: Any) -> Any:
"""Return the current conversion policy.
Dynamic resources are evaluated at runtime, so they can include
live data such as dates, configuration values, or database lookups.
When the resource function accepts ``**kwargs``, runtime keyword
arguments passed to ``agent.run()`` are forwarded automatically.
Args:
**kwargs: Runtime keyword arguments from ``agent.run()``.
For example, ``agent.run(..., function_invocation_kwargs={"precision": 2})``
makes ``kwargs["precision"]`` available here.
"""
precision = kwargs.get("precision", 4)
return dedent(f"""\
# Conversion Policy
**Decimal places:** {precision}
**Format:** Always show both the original and converted values with units
""")
# ---------------------------------------------------------------------------
# 3. Dynamic Scripts — in-process callable function
# ---------------------------------------------------------------------------
@unit_converter_skill.script(name="convert", description="Convert a value: result = value × factor")
def convert_units(value: float, factor: float, **kwargs: Any) -> str:
"""Convert a value using a multiplication factor: result = value × factor.
The caller looks up the correct factor from the conversion-tables
resource and passes it here.
Args:
value: The numeric value to convert.
factor: Conversion factor from the conversion table.
**kwargs: Runtime keyword arguments from ``agent.run()``.
The ``precision`` kwarg controls how many decimal places
the result is rounded to (default 4).
Returns:
JSON string with the inputs and converted result.
"""
precision = kwargs.get("precision", 4)
result = round(value * factor, precision)
return json.dumps({"value": value, "factor": factor, "result": result})
async def main() -> None:
"""Run the code-defined skills demo."""
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
# Create the skills provider with the code-defined skill and pass it to the agent
# All skill tools require approval by default; auto-approve them so the
# sample runs unattended. See the script_approval / skills_auto_approval
# samples for interactive and selective approval handling.
async with Agent(
client=client,
instructions="You are a helpful assistant that can convert units.",
context_providers=[SkillsProvider(unit_converter_skill)],
middleware=[ToolApprovalMiddleware(auto_approval_rules=[SkillsProvider.all_tools_auto_approval_rule])],
) as agent:
print("Converting units")
print("-" * 60)
session = agent.create_session()
response = await agent.run(
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?",
function_invocation_kwargs={"precision": 2},
session=session,
)
print(f"Agent: {response}\n")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Converting units
------------------------------------------------------------
Agent: Here are your conversions:
1. **26.2 miles → 42.16 km** (a marathon distance)
2. **75 kg → 165.35 lbs**
I used the conversion factors from the reference table:
miles × 1.60934 and kilograms × 2.20462.
"""
@@ -0,0 +1,69 @@
# File-Based Agent Skills
This sample demonstrates how to use **file-based Agent Skills** with a `SkillsProvider` in the Microsoft Agent Framework. File-based skills are discovered from `SKILL.md` files on disk and can include reference documents and executable scripts.
## What are Agent Skills?
Agent Skills are modular packages of instructions and resources that enable AI agents to perform specialized tasks. They follow the [Agent Skills specification](https://agentskills.io/) and implement progressive disclosure:
1. **Advertise**: Skills are advertised with name + description (~100 tokens per skill)
2. **Load**: Full instructions are loaded on-demand via `load_skill` tool
3. **Resources**: References and other files loaded via `read_skill_resource` tool
4. **Scripts**: Executable scripts run via `run_skill_script` tool
## Skills Included
### unit-converter
Converts between common units (miles↔km, pounds↔kg) using a multiplication factor following [agentskills.io guidelines](https://agentskills.io/skill-creation/using-scripts).
- `references/CONVERSION_TABLES.md` — Supported conversions and their factors
- `scripts/convert.py` — Executable script with `--value` and `--factor` flags, JSON output, and `--help` support
## Key Components
- **`SkillsProvider`** — Discovers skills from `SKILL.md` files in a directory and registers tools for the agent
- **`subprocess_script_runner`** — A `SkillScriptRunner` callback that runs scripts as local Python subprocesses, enabling the `run_skill_script` tool. Converts argument dicts to CLI flags (e.g. `{"value": 26.2, "factor": 1.60934}``--value 26.2 --factor 1.60934`). Shared across samples in [`../subprocess_script_runner.py`](../subprocess_script_runner.py).
## Project Structure
```
file_based_skill/
├── file_based_skill.py
├── README.md
└── skills/
└── unit-converter/
├── SKILL.md
├── references/
│ └── CONVERSION_TABLES.md
└── scripts/
└── convert.py
```
## Running the Sample
### Prerequisites
- An [Azure AI Foundry](https://ai.azure.com/) project with a deployed model (e.g. `gpt-4o-mini`)
### Environment Variables
Set the required environment variables in a `.env` file (see `python/.env.example`):
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `AZURE_OPENAI_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
### Authentication
This sample uses `AzureCliCredential` for authentication. Run `az login` in your terminal before running the sample.
### Run
```bash
cd python
uv run samples/02-agents/skills/file_based_skill/file_based_skill.py
```
## Learn More
- [Agent Skills Specification](https://agentskills.io/)
- [Code-Defined Skills Sample](../code_defined_skill/)
- [Mixed Skills Sample](../mixed_skills/)
- [Microsoft Agent Framework Documentation](../../../../../docs/)
@@ -0,0 +1,98 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
import sys
from pathlib import Path
from agent_framework import Agent, SkillsProvider, ToolApprovalMiddleware
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
# Add the skills folder root to sys.path so the shared subprocess_script_runner can be imported
_SKILLS_ROOT = str(Path(__file__).resolve().parent.parent)
if _SKILLS_ROOT not in sys.path:
sys.path.insert(0, _SKILLS_ROOT)
from subprocess_script_runner import subprocess_script_runner # pyrefly: ignore[missing-import] # noqa: E402
"""
File-Based Agent Skills
This sample demonstrates how to use file-based Agent Skills with a SkillsProvider.
Agent Skills are modular packages of instructions and resources that extend an agent's
capabilities. They follow progressive disclosure:
1. Advertise — skill names and descriptions are injected into the system prompt
2. Load — full instructions are loaded on-demand via the load_skill tool
3. Read resources — supplementary files are read via the read_skill_resource tool
4. Run scripts — skill scripts are run via the run_skill_script tool
This sample includes the unit-converter skill which demonstrates all three
file-based capabilities: instructions (SKILL.md), resources (CONVERSION_TABLES.md),
and scripts (convert.py).
"""
# Load environment variables from .env file
load_dotenv()
async def main() -> None:
"""Run the file-based skills demo."""
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
# Create the chat client
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
# Create the skills provider
# Discovers skills from the 'skills' directory and configures the
# subprocess_script_runner to run file-based scripts.
skills_dir = Path(__file__).parent / "skills"
skills_provider = SkillsProvider.from_paths(
skill_paths=str(skills_dir),
script_runner=subprocess_script_runner,
)
# Create the agent with skills. All skill tools require approval by
# default; auto-approve them so the sample runs unattended. See the
# script_approval / skills_auto_approval samples for approval handling.
async with Agent(
client=client,
instructions="You are a helpful assistant.",
context_providers=[skills_provider],
middleware=[ToolApprovalMiddleware(auto_approval_rules=[SkillsProvider.all_tools_auto_approval_rule])],
) as agent:
# The agent will: load the unit-converter skill, read the conversion
# tables resource, then execute the convert.py script.
print("Converting units")
print("-" * 60)
session = agent.create_session()
response = await agent.run(
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?",
session=session,
)
print(f"Agent: {response}\n")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Converting units
------------------------------------------------------------
Agent: Here are your conversions:
1. **26.2 miles → 42.16 km** (a marathon distance)
2. **75 kg → 165.35 lbs**
I used the conversion factors from the reference table:
miles × 1.60934 and kilograms × 2.20462.
"""
@@ -0,0 +1,17 @@
---
name: unit-converter
description: Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.
license: MIT
compatibility: Works with any model that supports tool use.
allowed-tools: convert
metadata:
author: agent-framework-samples
version: "1.0"
---
## Usage
When the user requests a unit conversion:
1. First, review `references/CONVERSION_TABLES.md` to find the correct factor
2. Run the `scripts/convert.py` script with `--value <number> --factor <factor>` (e.g. `--value 26.2 --factor 1.60934`)
3. Present the converted value clearly with both units
@@ -0,0 +1,10 @@
# 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 |
@@ -0,0 +1,28 @@
# Unit conversion script
# Converts a value using a multiplication factor: result = value × factor
#
# Usage:
# python scripts/convert.py --value 26.2 --factor 1.60934
# python scripts/convert.py --value 75 --factor 2.20462
import argparse
import json
def main() -> None:
parser = argparse.ArgumentParser(
description="Convert a value using a multiplication factor.",
epilog="Examples:\n"
" python scripts/convert.py --value 26.2 --factor 1.60934\n"
" python scripts/convert.py --value 75 --factor 2.20462",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--value", type=float, required=True, help="The numeric value to convert.")
parser.add_argument("--factor", type=float, required=True, help="The conversion factor from the table.")
args = parser.parse_args()
result = round(args.value * args.factor, 4)
print(json.dumps({"value": args.value, "factor": args.factor, "result": result}))
if __name__ == "__main__":
main()
@@ -0,0 +1,61 @@
# MCP-Based Agent Skills Sample
This sample demonstrates how to discover **Agent Skills served over MCP** with an `Agent`.
## What it demonstrates
- Connecting to a remote MCP server (over streamable HTTP) that exposes skill
resources following the SEP-2640 convention.
- Building a `SkillsProvider` from an `MCPSkillsSource`, which reads
`skill://index.json` (SEP-2640 canonical discovery) and constructs skills from
the index entries.
- The progressive disclosure pattern across MCP: advertise → load → read
resources, exactly as for filesystem-backed skills.
## Running the Sample
### Prerequisites
- Python 3.10+
- An [Azure AI Foundry](https://ai.azure.com/) project with a deployed model
- Azure CLI authentication (`az login`)
- A running MCP server that hosts SEP-2640 skill resources (see "Providing
an MCP server" below)
### Setup
Set the following environment variables (in a `.env` file or your shell):
```powershell
$env:FOUNDRY_PROJECT_ENDPOINT="https://your-endpoint.services.ai.azure.com/api/projects/your-project"
$env:FOUNDRY_MODEL="gpt-4o-mini"
$env:MCP_SKILLS_SERVER_URL="https://your-mcp-server.example.com/mcp"
```
### Run
```powershell
python mcp_based_skill.py
```
## Providing an MCP server
This sample is a **consumer**: it does not host an MCP server itself. To try
it end-to-end you need an MCP server that exposes the SEP-2640 skill
resources (`skill://index.json` plus per-skill `SKILL.md`).
- See [`samples/02-agents/mcp/agent_as_mcp_server.py`](../../mcp/agent_as_mcp_server.py)
for an example of hosting an MCP server via the Agent Framework.
- The Model Context Protocol working group maintains reference MCP-skills
servers at
[`modelcontextprotocol/experimental-ext-skills`](https://github.com/modelcontextprotocol/experimental-ext-skills).
## Security Considerations
Discovering skills over MCP means an *external* MCP server controls what skill content
(including instructions and, for script-capable skills, the scripts the agent may run)
reaches the agent. A compromised or untrustworthy server could return adversarial content
designed to manipulate the agent (indirect prompt injection) or to exfiltrate data through
skill instructions/scripts. This source is never enabled by default — connecting
`MCPSkillsSource` to a server is an explicit opt-in. Only connect to MCP servers you have
vetted and trust, and treat their responses as untrusted input.
@@ -0,0 +1,77 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
# Uncomment this filter to suppress the experimental MCP Skills warning before
# using the sample's MCP Skills APIs.
# import warnings
# warnings.filterwarnings("ignore", message=r"\[MCP_SKILLS\].*", category=FutureWarning)
from agent_framework import Agent, MCPSkillsSource, SkillsProvider, ToolApprovalMiddleware
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamable_http_client
"""
MCP-Based Agent Skills
This sample demonstrates how to discover Agent Skills served over the
Model Context Protocol (MCP) using :class:`MCPSkillsSource`.
The sample connects to a remote MCP server that exposes skill resources
under the ``skill://`` URI scheme:
* ``skill://index.json`` — discovery document listing all skills
* ``skill://<skill-name>/SKILL.md`` — the skill instructions
To run, set ``MCP_SKILLS_SERVER_URL`` to the streamable HTTP endpoint of an
MCP server that hosts the skill resources.
"""
async def main() -> None:
"""Connect to a remote MCP skills server and run the agent."""
load_dotenv()
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
mcp_url = os.environ["MCP_SKILLS_SERVER_URL"]
print("Discovering MCP-based skills")
print("-" * 60)
# 1. Connect to the MCP server over streamable HTTP.
async with streamable_http_client(url=mcp_url) as (read, write, _), ClientSession(read, write) as session:
await session.initialize()
# 2. Build a SkillsProvider that discovers skills over MCP.
# MCPSkillsSource reads skill://index.json and creates one
# MCPSkill per skill-md entry; SKILL.md bodies are fetched
# on demand via resources/read.
skills_provider = SkillsProvider(MCPSkillsSource(client=session))
# 3. Run the agent.
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
async with Agent(
client=client,
instructions="You are a helpful assistant. Use available skills to answer the user.",
context_providers=[skills_provider],
middleware=[ToolApprovalMiddleware(auto_approval_rules=[SkillsProvider.all_tools_auto_approval_rule])],
) as agent:
query = input("User: ").strip() # noqa: ASYNC250
if not query:
return
session = agent.create_session()
response = await agent.run(query, session=session)
print(f"Agent: {response}\n")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,118 @@
# Mixed Skills — Code, Class, and File Skills
This sample demonstrates how to combine **code-defined skills**,
**class-based skills**, and **file-based skills** in a single agent using
`SkillsProvider`.
## Concepts
| Concept | Description |
|---------|-------------|
| **Code skill** | A `Skill` created in Python with `@skill.script` decorators for in-process callable functions and `@skill.resource` for dynamic content |
| **Class skill** | A self-contained skill class extending `ClassSkill`, bundling instructions, resources, and scripts |
| **File skill** | A skill discovered from a `SKILL.md` file on disk, with reference documents and executable script files |
| **`script_runner`** | A callable (sync or async) satisfying the `SkillScriptRunner` protocol — required when file skills have scripts |
| **`SkillsProvider`** | Registers code-defined, class-based, and file-based skills in a single provider |
## Skills in This Sample
### volume-converter (code skill)
Defined entirely in Python code using decorators:
- **`@skill.resource`** — `conversion-table`: gallons↔liters conversion factors
- **`@skill.script`** — `convert`: converts a value using a multiplication factor
Code scripts run **in-process** — no subprocess or external runner needed.
### temperature-converter (class skill)
Defined as a `TemperatureConverterSkill` class extending `ClassSkill`:
- **`@ClassSkill.resource`** — `temperature-conversion-formulas`: °F↔°C↔K formulas
- **`@ClassSkill.script`** — `convert-temperature`: converts between temperature scales
Class-based scripts run **in-process** — no subprocess or external runner needed.
### unit-converter (file skill)
Discovered from `skills/unit-converter/SKILL.md`:
- **Reference**: `references/CONVERSION_TABLES.md` — supported unit conversions and their factors
- **Script**: `scripts/convert.py` — converts a value using a multiplication factor (e.g. miles to kilometers)
File scripts are executed as **local Python subprocesses** via the
`script_runner` callback.
## How It Works
```
┌─────────────────────────────────────────────────────────────┐
│ SkillsProvider( │
│ DeduplicatingSkillsSource( │
│ AggregatingSkillsSource([ │
│ FileSkillsSource("./skills", # file skills │
│ script_runner=runner), │
│ InMemorySkillsSource([ │
│ volume_skill, # code skill │
│ temp_converter, # class skill │
│ ]), │
│ ]) │
│ ) │
│ ) │
└─────────────┬───────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ script_runner(skill, script, args) │
│ │
│ • Code scripts (@skill.script) → in-process call │
│ • Class scripts (@ClassSkill.script) → in-process call │
│ • File scripts (scripts/*.py) → subprocess via │
│ the callback function │
└─────────────────────────────────────────────────────────────┘
```
## Prerequisites
Set environment variables (or create a `.env` file):
```
FOUNDRY_PROJECT_ENDPOINT=https://your-project.openai.azure.com/
AZURE_OPENAI_MODEL=gpt-4o-mini
```
Authenticate with Azure CLI:
```bash
az login
```
## Running the Sample
```bash
cd python
uv run samples/02-agents/skills/mixed_skills/mixed_skills.py
```
## Directory Structure
```
mixed_skills/
├── mixed_skills.py # Main sample — wires code + file skills together
├── README.md
└── skills/
└── unit-converter/ # File-based skill (discovered from SKILL.md)
├── SKILL.md
├── references/
│ └── CONVERSION_TABLES.md
└── scripts/
└── convert.py
```
## Learn More
- [File-Based Skills Sample](../file_based_skill/)
- [Code-Defined Skills Sample](../code_defined_skill/)
- [Script Approval Sample](../script_approval/)
- [Agent Skills Specification](https://agentskills.io/)
@@ -0,0 +1,248 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import json
import os
import sys
from pathlib import Path
from textwrap import dedent
from typing import Any
from agent_framework import (
Agent,
AggregatingSkillsSource,
ClassSkill,
DeduplicatingSkillsSource,
FileSkillsSource,
InlineSkill,
InMemorySkillsSource,
SkillFrontmatter,
SkillsProvider,
ToolApprovalMiddleware,
)
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
# Add the skills folder root to sys.path so the shared subprocess_script_runner can be imported
_SKILLS_ROOT = str(Path(__file__).resolve().parent.parent)
if _SKILLS_ROOT not in sys.path:
sys.path.insert(0, _SKILLS_ROOT)
from subprocess_script_runner import subprocess_script_runner # pyrefly: ignore[missing-import] # noqa: E402
"""
Mixed Skills — Code, class, and file skills in a single agent
This sample demonstrates how to combine **code-defined skills** (with
``@skill.script`` and ``@skill.resource`` decorators), **class-based skills**
(subclassing ``ClassSkill``), and **file-based skills** (discovered from
``SKILL.md`` files on disk) in a single agent using ``SkillsProvider`` and
a ``SkillScriptRunner`` callable.
Key concepts shown:
- Code skills with ``@skill.script``: executable Python functions the agent
can invoke directly in-process.
- Code skills with ``@skill.resource``: dynamic content the agent can read
on demand.
- Class skills: self-contained skill classes extending ``ClassSkill``.
- File skills from disk: ``SKILL.md`` files with reference documents and
executable script files.
- ``script_runner``: routes **file-based** script execution
through a callback, enabling custom handling (e.g. subprocess calls).
Code-defined and class-based scripts run in-process automatically.
The sample registers three skills:
1. **volume-converter** (code skill) — converts between gallons and liters using
``@skill.script`` for conversion and ``@skill.resource`` for the factor table.
2. **temperature-converter** (class skill) — converts between temperature scales
(°F↔°C↔K) using a ``ClassSkill`` subclass.
3. **unit-converter** (file skill) — converts between common units (miles↔km,
pounds↔kg) via a subprocess-executed Python script discovered from
``skills/unit-converter/SKILL.md``.
"""
# Load environment variables from .env file
load_dotenv()
# ---------------------------------------------------------------------------
# 1. Define a code skill with @skill.script and @skill.resource decorators
# ---------------------------------------------------------------------------
volume_converter_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="volume-converter", description="Convert between gallons and liters using a conversion factor"
),
instructions=dedent("""\
Use this skill when the user asks to convert between gallons and liters.
1. Review the conversion-table resource to find the correct factor.
2. Use the convert script, passing the value and factor.
"""),
)
@volume_converter_skill.resource(name="conversion-table", description="Volume conversion factors")
def volume_table() -> Any:
"""Return the volume conversion factor table."""
return dedent("""\
# Volume Conversion Table
Formula: **result = value × factor**
| From | To | Factor |
|---------|--------|---------|
| gallons | liters | 3.78541 |
| liters | gallons| 0.264172|
""")
@volume_converter_skill.script(name="convert", description="Convert a value: result = value × factor")
def convert_volume(value: float, factor: float) -> str:
"""Convert a value using a multiplication factor.
Args:
value: The numeric value to convert.
factor: Conversion factor from the table.
Returns:
JSON string with the conversion result.
"""
result = round(value * factor, 4)
return json.dumps({"value": value, "factor": factor, "result": result})
# ---------------------------------------------------------------------------
# 2. Define a class-based skill for temperature conversion
# ---------------------------------------------------------------------------
class TemperatureConverterSkill(ClassSkill):
"""A temperature-converter skill defined as a Python class.
Converts between temperature scales (Fahrenheit, Celsius, Kelvin).
Resources and scripts are discovered automatically via decorators.
"""
def __init__(self) -> None:
super().__init__(
frontmatter=SkillFrontmatter(
name="temperature-converter",
description="Convert between temperature scales (Fahrenheit, Celsius, Kelvin).",
)
)
@property
def instructions(self) -> str:
return dedent("""\
Use this skill when the user asks to convert temperatures.
1. Read the temperature-conversion-formulas resource to find the factor and offset
for the requested conversion.
2. Use the convert-temperature script, passing value, factor, and offset.
3. Present the result clearly with both temperature scales.
""")
@ClassSkill.resource(name="temperature-conversion-formulas")
def formulas(self) -> str:
"""Temperature conversion formulas reference table."""
return dedent("""\
# Temperature Conversion Formulas
Formula: **result = value × factor + offset**
| From | To | Factor | Offset |
|-------------|-------------|----------|-----------|
| Fahrenheit | Celsius | 0.555556 | -17.7778 |
| Celsius | Fahrenheit | 1.8 | 32 |
| Celsius | Kelvin | 1 | 273.15 |
| Kelvin | Celsius | 1 | -273.15 |
""")
@ClassSkill.script(name="convert-temperature")
def convert_temperature(self, value: float, factor: float, offset: float = 0) -> str:
"""Convert a temperature value using factor and offset from the formulas resource.
Args:
value: The numeric temperature value to convert.
factor: Conversion factor from the formulas resource.
offset: Offset to add after multiplying (default 0).
Returns:
JSON string with the conversion result.
"""
result = round(value * factor + offset, 4)
return json.dumps({"value": value, "factor": factor, "offset": offset, "result": result})
# ---------------------------------------------------------------------------
# 3. Wire everything together and run the agent
# ---------------------------------------------------------------------------
async def main() -> None:
"""Run the combined skills demo."""
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
# Create the chat client
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
# Create the SkillsProvider with code, class, and file skills.
# The script_runner handles file-based scripts; code-defined and
# class-based scripts run in-process automatically.
temperature_converter = TemperatureConverterSkill()
skills_provider = SkillsProvider(
DeduplicatingSkillsSource(
AggregatingSkillsSource([
FileSkillsSource(
str(Path(__file__).parent / "skills"),
script_runner=subprocess_script_runner,
),
InMemorySkillsSource([volume_converter_skill, temperature_converter]),
])
)
)
# Run the agent. All skill tools require approval by default; auto-approve
# them so the sample runs unattended. See the script_approval /
# skills_auto_approval samples for approval handling.
async with Agent(
client=client,
instructions="You are a helpful assistant that can convert units, volumes, and temperatures.",
context_providers=[skills_provider],
middleware=[ToolApprovalMiddleware(auto_approval_rules=[SkillsProvider.all_tools_auto_approval_rule])],
) as agent:
# Ask the agent to use all three skills
print("Converting with mixed skills (file + code + class)")
print("-" * 60)
session = agent.create_session()
response = await agent.run(
"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?",
session=session,
)
print(f"Agent: {response}\n")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Converting with mixed skills (file + code + class)
------------------------------------------------------------
Agent: Here are your conversions:
1. **26.2 miles → 42.16 km** (a marathon distance)
2. **5 gallons → 18.93 liters**
3. **98.6°F → 37.0°C**
"""
@@ -0,0 +1,17 @@
---
name: unit-converter
description: Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.
license: MIT
compatibility: Works with any model that supports tool use.
allowed-tools: convert
metadata:
author: agent-framework-samples
version: "1.0"
---
## Usage
When the user requests a unit conversion:
1. First, review `references/CONVERSION_TABLES.md` to find the correct factor
2. Run the `scripts/convert.py` script with `--value <number> --factor <factor>` (e.g. `--value 26.2 --factor 1.60934`)
3. Present the converted value clearly with both units
@@ -0,0 +1,10 @@
# 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 |
@@ -0,0 +1,28 @@
# Unit conversion script
# Converts a value using a multiplication factor: result = value × factor
#
# Usage:
# python scripts/convert.py --value 26.2 --factor 1.60934
# python scripts/convert.py --value 75 --factor 2.20462
import argparse
import json
def main() -> None:
parser = argparse.ArgumentParser(
description="Convert a value using a multiplication factor.",
epilog="Examples:\n"
" python scripts/convert.py --value 26.2 --factor 1.60934\n"
" python scripts/convert.py --value 75 --factor 2.20462",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--value", type=float, required=True, help="The numeric value to convert.")
parser.add_argument("--factor", type=float, required=True, help="The conversion factor from the table.")
args = parser.parse_args()
result = round(args.value * args.factor, 4)
print(json.dumps({"value": args.value, "factor": args.factor, "result": result}))
if __name__ == "__main__":
main()
@@ -0,0 +1,60 @@
# Skill Tool Approval — Human-in-the-Loop for Skill Tools
This sample demonstrates the **manual human-in-the-loop** approval pattern for
skill tools. Every tool exposed by `SkillsProvider` (`load_skill`,
`read_skill_resource`, and `run_skill_script`) requires host approval by
default, so the agent pauses and returns approval requests that your
application approves or rejects.
## How It Works
By default, skill tools require approval. The agent pauses before running any of
them and returns approval requests instead:
1. The agent tries to call a skill tool (e.g. `load_skill` or `run_skill_script`) — execution is paused
2. `result.user_input_requests` contains approval request(s) with function name and arguments
3. The application inspects each request and decides to approve or reject
4. `request.to_function_approval_response(approved=True|False)` creates the response
5. The response is sent back via `agent.run(approval_response, session=session)`
6. If approved, the tool runs; if rejected, the agent receives an error
## Key Components
- **Approval-by-default** — All skill tools require host approval; no extra configuration is needed
- **`result.user_input_requests`** — Contains pending approval requests after `agent.run()`
- **`request.to_function_approval_response()`** — Creates an approval or rejection response
To approve skill tools automatically instead of prompting for each one, use
`ToolApprovalMiddleware` with one of the static auto-approval rules — see the
[Skills Auto-Approval Sample](../skills_auto_approval/).
## Running the Sample
### Prerequisites
- An [Azure AI Foundry](https://ai.azure.com/) project with a deployed model (e.g. `gpt-4o-mini`)
### Environment Variables
Set the required environment variables in a `.env` file (see `python/.env.example`):
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `FOUNDRY_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
### Authentication
This sample uses `AzureCliCredential` for authentication. Run `az login` in your terminal before running the sample.
### Run
```bash
cd python
uv run samples/02-agents/skills/script_approval/script_approval.py
```
## Learn More
- [Skills Auto-Approval Sample](../skills_auto_approval/)
- [File-Based Skills Sample](../file_based_skill/)
- [Code-Defined Skills Sample](../code_defined_skill/)
- [Mixed Skills Sample](../mixed_skills/)
- [Agent Skills Specification](https://agentskills.io/)
@@ -0,0 +1,141 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from textwrap import dedent
from agent_framework import Agent, Content, InlineSkill, Message, SkillFrontmatter, SkillsProvider
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
"""
Skill Tool Approval — Require human approval before running skill tools
Every tool exposed by :class:`SkillsProvider` (``load_skill``,
``read_skill_resource``, and ``run_skill_script``) requires host approval by
default. This sample shows the manual human-in-the-loop pattern: the agent
pauses and returns approval requests, and the application approves or rejects
each one before the agent continues.
How it works:
1. A code-defined skill with a script is registered via SkillsProvider.
2. Because skill tools require approval by default, the agent pauses and returns
approval requests in ``result.user_input_requests`` instead of executing
tools immediately.
3. The application inspects each request and calls
``request.to_function_approval_response(approved=True|False)`` to approve
or reject.
4. The approval response is sent back via ``agent.run(approval_response, session=session)``
and the agent continues — running the tool if approved, or receiving an
error if rejected.
To approve skill tools automatically instead of prompting, use
``ToolApprovalMiddleware`` with one of the static auto-approval rules — see
``samples/02-agents/skills/skills_auto_approval/skills_auto_approval.py``.
Prerequisites:
- FOUNDRY_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
- FOUNDRY_MODEL (defaults to "gpt-4o-mini").
"""
# Load environment variables from .env file
load_dotenv()
# Define a code skill with a script that performs a sensitive operation
deployment_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="deployment", description="Tools for deploying application versions to production"
),
instructions=dedent("""\
Use this skill when the user asks to deploy an application.
1. Run the deploy script with the version and environment parameters.
"""),
)
@deployment_skill.script
def deploy(version: str, environment: str = "staging") -> str:
"""Deploy the application to the specified environment."""
return f"Deployed version {version} to {environment}"
async def main() -> None:
"""Run the skill script approval demo."""
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
# Create the skills provider. All skill tools require approval by default.
skills_provider = SkillsProvider(
source=[deployment_skill],
)
async with Agent(
client=client,
instructions="You are a deployment assistant. Use the deployment skill to deploy applications.",
context_providers=[skills_provider],
) as agent:
session = agent.create_session()
print("Starting agent with skill tool approval (the default)...")
print("-" * 60)
# Step 1: Send the user request — the agent will try to call the script
query = "Deploy the latest application version 2.5.0 to the production environment"
print(f"User: {query}")
result = await agent.run(query, session=session)
# Step 2: Handle approval requests (with sessions, context is
# maintained automatically). Collect a response for every request and
# send them in one run so the loop always makes progress.
while result.user_input_requests:
approval_responses: list[Content] = []
for request in result.user_input_requests:
if request.function_call is None:
# Not a function-approval request; reject it so the run can proceed.
approval_responses.append(request.to_function_approval_response(approved=False))
continue
print("\nApproval needed:")
print(f" Function: {request.function_call.name}")
print(f" Arguments: {request.function_call.arguments}")
# In a real application, prompt the user here
approved = True # Change to False to see rejection
print(f" Decision: {'Approved' if approved else 'Rejected'}")
approval_responses.append(request.to_function_approval_response(approved=approved))
# Send the approval responses — session preserves conversation history
result = await agent.run(Message(role="user", contents=approval_responses), session=session)
print(f"\nAgent: {result}")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Starting agent with skill tool approval (the default)...
------------------------------------------------------------
User: Deploy the latest application version 2.5.0 to the production environment
Approval needed:
Function: load_skill
Arguments: {"skill_name": "deployment"}
Decision: Approved
Approval needed:
Function: run_skill_script
Arguments: {"skill_name": "deployment", "script_name": "deploy", ...}
Decision: Approved
Agent: Successfully deployed version 2.5.0 to production.
"""
@@ -0,0 +1,94 @@
# Skill Filtering — FilteringSkillsSource
This sample demonstrates how to use `FilteringSkillsSource` to control
which file-based skills an agent sees by applying a predicate.
## Concepts
| Concept | Description |
|---------|-------------|
| **`FileSkillsSource`** | Discovers skills from `SKILL.md` files on disk |
| **`FilteringSkillsSource`** | Wraps a source and applies a predicate to include or exclude skills |
| **`DeduplicatingSkillsSource`** | Removes duplicate skill names (first-one-wins) |
## Skills in This Sample
### volume-converter (kept)
Converts between gallons and liters via `scripts/convert.py`.
### length-converter (filtered out)
Converts between miles ↔ km, feet ↔ meters. Excluded by the filter predicate
so the agent never sees it.
## How It Works
```
┌──────────────────────────────────────────────────────────┐
│ FileSkillsSource("./skills") │
│ discovers: volume-converter, length-converter │
└─────────────┬────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ FilteringSkillsSource(predicate=...) │
│ keeps: volume-converter │
│ drops: length-converter │
└─────────────┬────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ DeduplicatingSkillsSource → SkillsProvider │
└──────────────────────────────────────────────────────────┘
```
> **Note:** `FilteringSkillsSource` works with any source — file-based,
> in-memory, custom, or a mix. If you only need a single skill, point
> `FileSkillsSource` directly at that skill's directory instead of filtering.
## Prerequisites
Set environment variables (or create a `.env` file):
```
FOUNDRY_PROJECT_ENDPOINT=https://your-project.openai.azure.com/
FOUNDRY_MODEL=gpt-4o-mini
```
Authenticate with Azure CLI:
```bash
az login
```
## Running the Sample
```bash
cd python
uv run samples/02-agents/skills/skill_filtering/skill_filtering.py
```
## Directory Structure
```
skill_filtering/
├── skill_filtering.py
├── README.md
└── skills/
├── volume-converter/
│ ├── SKILL.md
│ └── scripts/
│ └── convert.py
└── length-converter/
├── SKILL.md
└── scripts/
└── convert.py
```
## Learn More
- [File-Based Skills Sample](../file_based_skill/)
- [Mixed Skills Sample](../mixed_skills/)
- [Code-Defined Skills Sample](../code_defined_skill/)
- [Agent Skills Specification](https://agentskills.io/)
@@ -0,0 +1,113 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
import sys
from pathlib import Path
from agent_framework import (
Agent,
DeduplicatingSkillsSource,
FileSkillsSource,
FilteringSkillsSource,
SkillsProvider,
ToolApprovalMiddleware,
)
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
# Add the skills folder root to sys.path so the shared subprocess_script_runner can be imported
_SKILLS_ROOT = str(Path(__file__).resolve().parent.parent)
if _SKILLS_ROOT not in sys.path:
sys.path.insert(0, _SKILLS_ROOT)
from subprocess_script_runner import subprocess_script_runner # pyrefly: ignore[missing-import] # noqa: E402
"""
Skill Filtering — Using FilteringSkillsSource with file-based skills
This sample demonstrates how to use **FilteringSkillsSource** to control
which skills an agent sees. Although this example uses file-based skills,
``FilteringSkillsSource`` works equally well with in-memory skills,
custom sources, or any combination of them.
A single ``skills/`` directory contains two file-based skills discovered via
``FileSkillsSource``:
- **volume-converter** — converts between gallons and liters
- **length-converter** — converts between miles ↔ km, feet ↔ meters
A ``FilteringSkillsSource`` wraps the file source and excludes the
``length-converter`` skill, so the agent only sees the volume-converter skill.
Note: if you only need a single skill, you could point ``FileSkillsSource``
directly at that skill's directory and skip filtering entirely. This sample
intentionally points at the parent directory to demonstrate filtering.
Key concepts shown:
1. **FileSkillsSource** — discovers skills from ``SKILL.md`` files on disk.
2. **FilteringSkillsSource** — applies a predicate to include or exclude
specific skills by name (or any custom logic).
3. **DeduplicatingSkillsSource** — ensures no duplicate skill names survive.
"""
# Load environment variables from .env file
load_dotenv()
async def main() -> None:
"""Run the skill filtering demo."""
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
# 1. Create the chat client
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
# 2. Compose the source pipeline:
# file discovery → filter out length-converter → deduplicate
skills_dir = Path(__file__).parent / "skills"
source = DeduplicatingSkillsSource(
FilteringSkillsSource(
FileSkillsSource(str(skills_dir), script_runner=subprocess_script_runner),
# Only keep the volume-converter skill
predicate=lambda skill, context: skill.frontmatter.name != "length-converter",
)
)
skills_provider = SkillsProvider(source)
# 3. Run the agent — it can only see the volume-converter skill. All skill
# tools require approval by default; auto-approve them so the sample runs
# unattended. See the script_approval / skills_auto_approval samples for
# approval handling.
async with Agent(
client=client,
instructions="You are a helpful assistant that can convert units.",
context_providers=[skills_provider],
middleware=[ToolApprovalMiddleware(auto_approval_rules=[SkillsProvider.all_tools_auto_approval_rule])],
) as agent:
print("Skill filtering demo")
print("-" * 60)
session = agent.create_session()
response = await agent.run("How many liters is a 5-gallon bucket?", session=session)
print(f"Agent: {response}\n")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Skill filtering demo
------------------------------------------------------------
Agent: A 5-gallon bucket is equal to **18.9271 liters**.
I used the conversion factor: 5 × 3.78541 = 18.9271
"""
@@ -0,0 +1,21 @@
---
name: length-converter
description: Convert between common length units (miles, km, feet, meters) using a multiplication factor.
license: MIT
compatibility: Works with any model that supports tool use.
allowed-tools: convert
metadata:
author: agent-framework-samples
version: "1.0"
---
## Usage
When the user requests a length conversion, run the `scripts/convert.py`
script with `--value <number> --factor <factor>`.
Common factors:
- miles → km: 1.60934
- km → miles: 0.621371
- feet → meters: 0.3048
- meters → feet: 3.28084
@@ -0,0 +1,18 @@
"""Convert a value by multiplying it with a factor."""
import argparse
import json
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--value", type=float, required=True)
parser.add_argument("--factor", type=float, required=True)
args = parser.parse_args()
result = round(args.value * args.factor, 4)
print(json.dumps({"value": args.value, "factor": args.factor, "result": result}))
if __name__ == "__main__":
main()
@@ -0,0 +1,17 @@
---
name: volume-converter
description: Convert between gallons and liters using a conversion factor.
license: MIT
compatibility: Works with any model that supports tool use.
allowed-tools: convert
metadata:
author: agent-framework-samples
version: "1.0"
---
## Usage
When the user requests a volume conversion:
1. Run the `scripts/convert.py` script with `--value <number> --factor <factor>`
2. Use factor 3.78541 for gallons → liters, or 0.264172 for liters → gallons
3. Present the converted value clearly with both units
@@ -0,0 +1,18 @@
"""Convert a value by multiplying it with a factor."""
import argparse
import json
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--value", type=float, required=True)
parser.add_argument("--factor", type=float, required=True)
args = parser.parse_args()
result = round(args.value * args.factor, 4)
print(json.dumps({"value": args.value, "factor": args.factor, "result": result}))
if __name__ == "__main__":
main()
@@ -0,0 +1,62 @@
# Skills Auto-Approval — Configure Auto-Approval Rules for Skill Tools
This sample demonstrates how to configure **auto-approval rules** for skill
tools using `ToolApprovalMiddleware`. Every tool exposed by `SkillsProvider`
(`load_skill`, `read_skill_resource`, and `run_skill_script`) requires host
approval by default. Auto-approval rules let you selectively bypass the approval
prompt for safe operations.
## How It Works
1. A code-defined unit-converter skill (with a resource and a script) is registered via `SkillsProvider`.
2. The agent installs `ToolApprovalMiddleware` with `SkillsProvider.read_only_tools_auto_approval_rule`.
3. The read-only tools (`load_skill`, `read_skill_resource`) are approved automatically.
4. `run_skill_script` still requires explicit approval and is handled with the standard `result.user_input_requests` loop.
## Auto-Approval Rules
`SkillsProvider` exposes two static rules to pass to `ToolApprovalMiddleware(auto_approval_rules=[...])`:
- **`SkillsProvider.read_only_tools_auto_approval_rule`** — approves only the read-only tools (`load_skill`, `read_skill_resource`), while still prompting for `run_skill_script`.
- **`SkillsProvider.all_tools_auto_approval_rule`** — approves every skill tool, including `run_skill_script` (no manual approval loop needed).
Both rules reject any call carrying a `server_label`, so they stay scoped to this provider's local tools and never auto-approve a same-named hosted tool.
> **Note:** To use auto-approval rules, the agent must have `ToolApprovalMiddleware` in its middleware stack.
## Key Components
- **`ToolApprovalMiddleware(auto_approval_rules=[...])`** — Drives the approval handshake and applies the rules
- **`SkillsProvider.read_only_tools_auto_approval_rule`** — Auto-approves read-only skill tools
- **`SkillsProvider.all_tools_auto_approval_rule`** — Auto-approves all skill tools
- **`SkillsProvider.LOAD_SKILL_TOOL_NAME` / `READ_SKILL_RESOURCE_TOOL_NAME` / `RUN_SKILL_SCRIPT_TOOL_NAME`** — Tool-name constants for building custom rules
## Running the Sample
### Prerequisites
- An [Azure AI Foundry](https://ai.azure.com/) project with a deployed model (e.g. `gpt-4o-mini`)
### Environment Variables
Set the required environment variables in a `.env` file (see `python/.env.example`):
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `FOUNDRY_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
### Authentication
This sample uses `AzureCliCredential` for authentication. Run `az login` in your terminal before running the sample.
### Run
```bash
cd python
uv run samples/02-agents/skills/skills_auto_approval/skills_auto_approval.py
```
## Learn More
- [Skill Tool Approval Sample](../script_approval/) — manual human-in-the-loop approval
- [Code-Defined Skills Sample](../code_defined_skill/)
- [File-Based Skills Sample](../file_based_skill/)
- [Agent Skills Specification](https://agentskills.io/)
@@ -0,0 +1,191 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import json
import os
from textwrap import dedent
from typing import Any
from agent_framework import (
Agent,
Content,
InlineSkill,
InlineSkillResource,
Message,
SkillFrontmatter,
SkillsProvider,
ToolApprovalMiddleware,
)
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
"""
Skills Auto-Approval — Configure auto-approval rules for skill tools
Every tool exposed by :class:`SkillsProvider` (``load_skill``,
``read_skill_resource``, and ``run_skill_script``) requires host approval by
default. Rather than prompting for every call, this sample uses
:class:`ToolApprovalMiddleware` with a static auto-approval rule so that the
read-only tools are approved automatically while script execution still
requires explicit user approval.
How it works:
1. A code-defined unit-converter skill (with a resource and a script) is
registered via SkillsProvider.
2. The agent installs ``ToolApprovalMiddleware`` with
``SkillsProvider.read_only_tools_auto_approval_rule``. This auto-approves
``load_skill`` and ``read_skill_resource`` while still prompting for
``run_skill_script``.
3. The application handles the remaining ``run_skill_script`` approval requests
via the standard ``result.user_input_requests`` loop.
Available auto-approval rules:
- ``SkillsProvider.read_only_tools_auto_approval_rule`` — approves only the
read-only tools (``load_skill``, ``read_skill_resource``).
- ``SkillsProvider.all_tools_auto_approval_rule`` — approves every skill tool,
including ``run_skill_script`` (no manual approval loop needed).
To use auto-approval rules, the agent must have ``ToolApprovalMiddleware`` in
its middleware stack.
Prerequisites:
- FOUNDRY_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
- FOUNDRY_MODEL (defaults to "gpt-4o-mini").
"""
# Load environment variables from .env file
load_dotenv()
# A code-defined unit-converter skill with a resource (read-only) and a script.
unit_converter_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="unit-converter", description="Convert between common units using a conversion factor"
),
instructions=dedent("""\
Use this skill when the user asks to convert between units.
1. Review the conversion-tables resource to find the factor for the
requested conversion.
2. Use the convert script, passing the value and factor from the table.
"""),
resources=[
InlineSkillResource(
name="conversion-tables",
content=dedent("""\
# 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 |
"""),
),
],
)
@unit_converter_skill.script(name="convert", description="Convert a value: result = value × factor")
def convert_units(value: float, factor: float, **kwargs: Any) -> str:
"""Convert a value using a multiplication factor: result = value × factor.
Args:
value: The numeric value to convert.
factor: Conversion factor from the conversion table.
**kwargs: Runtime keyword arguments from ``agent.run()``.
Returns:
JSON string with the inputs and converted result.
"""
result = round(value * factor, 2)
return json.dumps({"value": value, "factor": factor, "result": result})
async def main() -> None:
"""Run the skills auto-approval demo."""
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
skills_provider = SkillsProvider(unit_converter_skill)
# Install ToolApprovalMiddleware with the read-only auto-approval rule.
# load_skill and read_skill_resource are approved automatically; the agent
# only pauses for run_skill_script.
#
# To approve every skill tool without prompting, swap the rule for
# SkillsProvider.all_tools_auto_approval_rule (the manual approval loop
# below then becomes a no-op).
approval_middleware = ToolApprovalMiddleware(
auto_approval_rules=[SkillsProvider.read_only_tools_auto_approval_rule]
)
async with Agent(
client=client,
instructions="You are a helpful assistant that can convert units.",
context_providers=[skills_provider],
middleware=[approval_middleware],
) as agent:
session = agent.create_session()
print("Converting units with skill tools and read-only auto-approval")
print("-" * 60)
query = "How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?"
print(f"User: {query}")
result = await agent.run(query, session=session)
# Read-only tools (load_skill, read_skill_resource) were auto-approved.
# Only run_skill_script reaches this loop and needs explicit approval.
# Collect a response for every request and send them in one run so the
# loop always makes progress.
while result.user_input_requests:
approval_responses: list[Content] = []
for request in result.user_input_requests:
if request.function_call is None:
# Not a function-approval request; reject it so the run can proceed.
approval_responses.append(request.to_function_approval_response(approved=False))
continue
print("\nApproval needed:")
print(f" Function: {request.function_call.name}")
print(f" Arguments: {request.function_call.arguments}")
# In a real application, prompt the user here.
approved = True
print(f" Decision: {'Approved' if approved else 'Rejected'}")
approval_responses.append(request.to_function_approval_response(approved=approved))
result = await agent.run(Message(role="user", contents=approval_responses), session=session)
print(f"\nAgent: {result}")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Converting units with skill tools and read-only auto-approval
------------------------------------------------------------
User: How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?
Approval needed:
Function: run_skill_script
Arguments: {"skill_name": "unit-converter", "script_name": "convert", ...}
Decision: Approved
Agent: Here are your conversions:
1. 26.2 miles -> 42.16 km (a marathon distance)
2. 75 kg -> 165.35 lbs
"""
@@ -0,0 +1,72 @@
# Copyright (c) Microsoft. All rights reserved.
"""Sample subprocess-based skill script runner.
Executes file-based skill scripts as local Python subprocesses.
This is provided for demonstration purposes only.
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
from typing import Any
from agent_framework import FileSkill, FileSkillScript
def subprocess_script_runner(
skill: FileSkill, script: FileSkillScript, args: dict[str, Any] | list[str] | None = None
) -> str:
"""Run a skill script as a local Python subprocess.
Uses ``FileSkillScript.full_path`` as the script path, converts the
``args`` to CLI arguments, and returns captured output.
Args:
skill: The file-based skill that owns the script.
script: The file-based script to run.
args: Optional arguments. A ``list[str]`` is forwarded as
positional CLI arguments. Passing a ``dict`` or any other
type raises :class:`TypeError` — file-based scripts expect
positional arguments as a JSON array of strings.
Returns:
The combined stdout/stderr output, or an error message.
Raises:
TypeError: If ``args`` is not a ``list[str]`` or ``None``, or if
any list element is not a string.
"""
script_path = Path(script.full_path)
if not script_path.is_file():
return f"Error: Script file not found: {script_path}"
cmd = [sys.executable, str(script_path)]
if isinstance(args, list):
for item in args:
if not isinstance(item, str):
raise TypeError(
f"File-based skill scripts only accept string CLI arguments "
f"but received a {type(item).__name__}. "
f"All array elements must be strings."
)
cmd.extend(args)
elif args is not None:
raise TypeError(
f"Expected a list of CLI arguments but received {type(args).__name__}. "
f"File-based skill scripts expect positional arguments as a list of strings."
)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
cwd=str(script_path.parent),
)
output = result.stdout
if result.stderr:
output += f"\nStderr:\n{result.stderr}"
if result.returncode != 0:
output += f"\nScript exited with code {result.returncode}"
return output.strip() or "(no output)"
except subprocess.TimeoutExpired:
return f"Error: Script '{script.name}' timed out after 30 seconds."
except OSError as e:
return f"Error: Failed to execute script '{script.name}': {e}"