chore: import upstream snapshot with attribution
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
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
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
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
@@ -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**
|
||||
"""
|
||||
Reference in New Issue
Block a user