# 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** """