Microsoft Agent Framework で Skill を使う

Microsoft Agent Framework には、エージェントへ後付けで専門知識や実行手順を与えるための Skill という仕組みがあります。SKILL.md を中心に instructions、resources、scripts をまとめて扱えるため、エージェント本体の instructions を肥大化させずに、必要な時だけ知識や処理を読み込ませることができます。

特に Skill が有効なのは、次のような場面です。

  • 社内手順や運用ルールを、エージェントへ必要時だけ参照させたい場合
  • 単位変換や申請チェックのような、特定ドメインの作業を再利用可能な形でまとめたい場合
  • 参照資料と実行スクリプトをセットで持たせ、エージェントの判断で段階的に使わせたい場合
  • エージェント本体のシステムプロンプトを大きくせず、知識や手順を外出ししたい場合

この記事では、Microsoft Agent Framework の Skill の概要と使いどころを整理し、Python で code-defined Skill と file-based Skill を実装する基本的な流れを紹介します。

実行環境

今回の検証で前提にしている環境は次のとおりです。

  • Python: 3.12
  • agent-framework: 1.7.0
  • モデル接続先: Microsoft Foundry

環境変数

今回の記事では Microsoft Foundry のプロジェクトエンドポイントとモデルデプロイメントを使います。次の環境変数を設定してください。

  • FOUNDRY_PROJECT_ENDPOINT="https://<リソース名>.services.ai.azure.com/api/projects/<プロジェクト名>"
  • FOUNDRY_MODEL="<モデル名>"

Skill の概要

Agent Skills は instructions、resources、scripts をまとめたパッケージで、エージェントへ専門知識や実行手順を追加する仕組みです。

理解のポイントは次の 3 つです。

観点 要点
何を入れるか SKILL.md の指示、参照資料、必要なら実行スクリプト
どう使われるか エージェントが必要になった時だけ Skill をロードする
いつ向いているか エージェントが判断しながら進める単一ドメイン作業

Skill は次の流れで利用されます。

  1. Advertise: Skill 名と説明だけを最初にシステムプロンプトへ載せる
  2. Load: 必要になった Skill の本文だけ load_skill で読む
  3. Read resources: 参照ファイルを read_skill_resource で読む
  4. Run scripts: 必要な時だけ run_skill_script を実行する

つまり、最初から大量の業務知識をプロンプトに直書きするのではなく、必要になった分だけ Skill を開く形です。これにより、コンテキスト消費を抑えながら必要な知識だけを取り込めます。

Skill の基本構造

Skill の基本構造は次のとおりです。

my-skill/
├── SKILL.md
├── scripts/
├── references/
└── assets/

SKILL.md には YAML frontmatter が必要です。最低限必要なのは次の 2 項目です。

  • name
  • description

name は小文字英数字とハイフンだけで、親ディレクトリ名と一致している必要があります。`description` には「何をする Skill か」だけではなく、「どういう依頼で使う Skill か」まで書くのが重要です。これがエージェントによる Skill 発見の手掛かりになります。

Python での Skill 定義

Python では Skill を次の 3 パターンで扱えます。

パターン 使う API 向いているケース
Code-defined Skill InlineSkill アプリコードの近くで小さく定義したい
Class-based Skill ClassSkill 再利用しやすい形でまとめたい
File-based Skill SkillsProvider.from_paths() SKILL.md を中心に配布・共有したい

まず試すなら InlineSkill が分かりやすく、社内共有や Git 管理を意識するなら file-based Skill が扱いやすいです。ClassSkill は PyPI などで再利用したいときに向いています。

利用方法

今回は Skill の実装方法を分かりやすく説明することを優先しているため、Skill の処理内容そのものは簡単にしています。

最小サンプルとして code-defined と file-based の 2 例を記載します。

Code-defined Skill

InlineSkill と SkillFrontmatter を使って Skill をコード上で定義します。

実際のサンプルコードは次のとおりです。

from __future__ import annotations

import argparse
import asyncio
import json
import os
from textwrap import dedent
from typing import Any

from agent_framework import Agent, InlineSkill, InlineSkillResource, SkillFrontmatter, SkillsProvider
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv

load_dotenv(override=True)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Run a code-defined Agent Skills sample with Microsoft Foundry.")
    parser.add_argument("--project-endpoint", default=os.getenv("FOUNDRY_PROJECT_ENDPOINT"))
    parser.add_argument("--model", default=os.getenv("FOUNDRY_MODEL", "gpt-4o-mini"))
    parser.add_argument("--message", default="26.2マイルは何キロメートルですか。75キログラムは何ポンドですか。")
    parser.add_argument("--precision", type=int, default=2)
    return parser.parse_args()


def build_skill() -> InlineSkill:
    skill = InlineSkill(
        frontmatter=SkillFrontmatter(
            name="unit-converter",
            description="マイル/キロメートル、ポンド/キログラムの変換で使う Skill。",
        ),
        instructions=dedent(
            """\
            ユーザーが単位変換を求めたときにこの Skill を使ってください。

            1. conversion-tables を読んで変換係数を確認します。
            2. conversion-policy を読んで丸め方を確認します。
            3. convert スクリプトを使って結果を計算します。
            4. 元の値と変換後の値を単位付きで返します。
            """
        ),
        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  |
                    """
                ),
            )
        ],
    )

    @skill.resource(name="conversion-policy", description="丸め桁数と表示ルール")
    def conversion_policy(**kwargs: Any) -> str:
        precision = kwargs.get("precision", 4)
        return dedent(
            f"""\
            # Conversion Policy
            - Decimal places: {precision}
            - Return both the original and converted values with units.
            """
        )

    @skill.script(name="convert", description="result = value * factor で変換する")
    def convert_units(value: float, factor: float, **kwargs: Any) -> str:
        precision = kwargs.get("precision", 4)
        result = round(value * factor, precision)
        return json.dumps({"value": value, "factor": factor, "result": result}, ensure_ascii=False)

    return skill


async def main() -> None:
    args = parse_args()
    if not args.project_endpoint:
        raise ValueError("FOUNDRY_PROJECT_ENDPOINT が未設定です。")

    print(f"Project Endpoint: {args.project_endpoint}")
    print(f"Model: {args.model}")
    print(f"Precision: {args.precision}")
    print(f"Message: {args.message}")

    agent = Agent(
        client=FoundryChatClient(
            project_endpoint=args.project_endpoint,
            model=args.model,
            credential=AzureCliCredential(),
        ),
        instructions="You are a helpful assistant that can convert units.",
        context_providers=[SkillsProvider(build_skill())],
    )

    async with agent:
        result = await agent.run(
            args.message,
            function_invocation_kwargs={"precision": args.precision},
        )
        print(f"Agent: {result}")


if __name__ == "__main__":
    asyncio.run(main())

このサンプルで確認できる内容は次のとおりです。

  • InlineSkill に static resource を持たせる方法
  • @skill.resource で動的 resource を返す方法
  • @skill.script で in-process の script を持たせる方法
  • function_invocation_kwargs で runtime 引数を渡す方法

File-based Skill

file-based Skill は SKILL.md を持つディレクトリを SkillsProvider.from_paths() で読み込む方式です。

resources は references/ と assets/、scripts は scripts/ から見つける構成が基本になっています。今回は skills/ 配下に複数の Skill ディレクトリを置き、1 つの agent に複数 Skill を持たせる例にしています。

file-based サンプル構成は次のとおりです。

skills/
├── percentage-calculator/
│   ├── SKILL.md
│   ├── references/
│   │   └── PERCENTAGE_RULES.md
│   └── scripts/
│       └── calculate_percentage.py
└── unit-converter/
    ├── SKILL.md
    ├── references/
    │   └── CONVERSION_TABLES.md
    └── scripts/
        └── convert.py

実行用 Python ファイルと各ファイルの内容は以下になります。

SkillsProvider により、エージェントは必要に応じて複数 Skill の中から使うものを見つけ、対象の SKILL.md をロードし、必要なら reference を読み、最後に該当 script を実行します。今回の例では、単位変換の問いには unit-converter、割合計算の問いには percentage-calculator が使われる想定です。

file_based_skill_demo.py

実行用 Python ファイルです。

from __future__ import annotations

import argparse
import asyncio
import os
from pathlib import Path

from agent_framework import Agent, SkillsProvider
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv

from subprocess_script_runner import subprocess_script_runner

load_dotenv(override=True)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Run a file-based Agent Skills sample with Microsoft Foundry.")
    parser.add_argument("--project-endpoint", default=os.getenv("FOUNDRY_PROJECT_ENDPOINT"))
    parser.add_argument("--model", default=os.getenv("FOUNDRY_MODEL", "gpt-4o-mini"))
    parser.add_argument(
        "--message",
        default="26.2マイルをキロメートルに、75キログラムをポンドに変換して、あわせて80の15%も計算してください。",
    )
    return parser.parse_args()


async def main() -> None:
    args = parse_args()
    if not args.project_endpoint:
        raise ValueError("FOUNDRY_PROJECT_ENDPOINT が未設定です。")

    skills_dir = Path(__file__).parent / "skills"
    if not skills_dir.exists():
        raise FileNotFoundError(f"Skill ディレクトリが見つかりません: {skills_dir}")

    skill_names = sorted(path.name for path in skills_dir.iterdir() if path.is_dir())

    print(f"Project Endpoint: {args.project_endpoint}")
    print(f"Model: {args.model}")
    print(f"Skills Directory: {skills_dir}")
    print(f"Available Skills: {', '.join(skill_names)}")
    print(f"Message: {args.message}")

    agent = Agent(
        client=FoundryChatClient(
            project_endpoint=args.project_endpoint,
            model=args.model,
            credential=AzureCliCredential(),
        ),
        instructions=(
            "You are a helpful assistant that can use multiple file-based skills when needed. "
            "After answering, include a short 'Used Skills' line listing the skills you used."
        ),
        context_providers=[
            SkillsProvider.from_paths(
                skill_paths=skills_dir,
                script_runner=subprocess_script_runner,
            )
        ],
    )

    async with agent:
        result = await agent.run(args.message)
        print(f"Agent: {result}")


if __name__ == "__main__":
    asyncio.run(main())

このように skills/ ディレクトリを渡すだけで、その配下にある複数の Skill をまとめて agent へ持たせられます。今回の例では、単位変換と割合計算の 2 Skill が同時に読み込み対象になり、最終応答の末尾に使用した Skill 名も出力させています。

subprocess_script_runner.py

SkillsProvider.from_paths() で file-based script を実行するには、script_runner が必要です。今回のサンプルでは、各 Skill の script をローカル Python subprocess で呼び出す共通 runner を使っています。

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:
    script_path = Path(script.full_path)
    if not script_path.is_file():
        return f"Error: Script file not found: {script_path}"

    if args is None:
        cli_args: list[str] = []
    elif isinstance(args, list) and all(isinstance(item, str) for item in args):
        cli_args = args
    else:
        raise TypeError("File-based skill scripts expect positional arguments as a list of strings.")

    cmd = [sys.executable, str(script_path), *cli_args]

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=30,
            cwd=str(script_path.parent),
            check=False,
        )
    except subprocess.TimeoutExpired:
        return f"Error: Script '{script.name}' timed out after 30 seconds."
    except OSError as exc:
        return f"Error: Failed to execute script '{script.name}': {exc}"

    output = result.stdout.strip()
    if result.stderr:
        output = f"{output}\nStderr:\n{result.stderr.strip()}".strip()
    if result.returncode != 0:
        output = f"{output}\nScript exited with code {result.returncode}".strip()

    return output or "(no output)"
skills/unit-converter/SKILL.md

file-based Skill の中心になるのが SKILL.md です。frontmatter で Skill 名や description を定義し、本文で「どの resource を読み、どの script をどう使うか」を指示します。

---
name: unit-converter
description: マイル/キロメートル、ポンド/キログラムを変換する Skill。miles, kilometers, pounds, kilograms の変換要求で使います。
compatibility: Requires Python 3.12 and agent-framework 1.7.0 or later. File script execution needs a local Python interpreter.
metadata:
  author: sample
  source: agent-framework-test-blog
---

この Skill は単位変換専用です。

1. `references/CONVERSION_TABLES.md` を読んで変換係数を確認します。
2. `scripts/convert.py``[value, factor]` の順で実行します。
3. 結果は元の値と変換後の値が分かるように返します。

注意点:

- 変換係数が表にない場合は、推測せずに対応外と説明します。
- スクリプトの引数は文字列の配列として渡します。
- 出力は JSON なので、そのままではなくユーザー向けに整形して返します。
skills/unit-converter/references/CONVERSION_TABLES.md

Skill から参照する conversion table は次のように定義しています。

# Conversion Tables

Formula: result = value * factor

| From       | To          | Factor   |
|------------|-------------|----------|
| miles      | kilometers  | 1.60934  |
| kilometers | miles       | 0.621371 |
| pounds     | kilograms   | 0.453592 |
| kilograms  | pounds      | 2.20462  |
skills/unit-converter/scripts/convert.py

実際に数値変換を行う script は次のとおりです。file-based Skill では、この script が subprocess として呼ばれます。

from __future__ import annotations

import argparse
import json


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Convert a numeric value with a multiplication factor.")
    parser.add_argument("value", type=float)
    parser.add_argument("factor", type=float)
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    result = round(args.value * args.factor, 4)
    print(
        json.dumps(
            {
                "value": args.value,
                "factor": args.factor,
                "result": result,
            },
            ensure_ascii=False,
        )
    )


if __name__ == "__main__":
    main()
skills/percentage-calculator/SKILL.md

2 つ目の Skill として、複数 Skill をまとめて持たせる例を分かりやすくするため、簡単な割合計算 Skill も追加しています。

---
name: percentage-calculator
description: 割合計算を行う Skill。"80の15%" や "売上の8%" のような percentage 計算依頼で使います。
compatibility: Requires Python 3.12 and agent-framework 1.7.0 or later. File script execution needs a local Python interpreter.
metadata:
  author: sample
  source: agent-framework-test-blog
---

この Skill は単純な割合計算専用です。

1. `references/PERCENTAGE_RULES.md` を読んで計算式を確認します。
2. `scripts/calculate_percentage.py``[value, rate]` の順で実行します。
3. 結果は「何の何%か」が分かるように返します。

注意点:

- `rate` は 15% の場合でも `15` として渡します。
- より複雑な税計算や複利計算は扱いません。
- 出力は JSON なので、そのままではなくユーザー向けに整形して返します。
skills/percentage-calculator/references/PERCENTAGE_RULES.md

割合計算 Skill から参照するルールは、次のように定義しています。

# Percentage Rules

Formula: result = value * (rate / 100)

Examples:

| Value | Rate | Result |
|-------|------|--------|
| 80    | 15   | 12     |
| 500   | 8    | 40     |

Use this skill when the user asks for a simple percentage amount.
skills/percentage-calculator/scripts/calculate_percentage.py

割合計算を実行する script は次のとおりです。file-based Skill では、この script も subprocess として呼ばれます。

from __future__ import annotations

import argparse
import json


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Calculate a percentage amount from a value and rate.")
    parser.add_argument("value", type=float)
    parser.add_argument("rate", type=float)
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    result = round(args.value * (args.rate / 100), 4)
    print(
        json.dumps(
            {
                "value": args.value,
                "rate": args.rate,
                "result": result,
            },
            ensure_ascii=False,
        )
    )


if __name__ == "__main__":
    main()

実行結果

File-based Skill のコードを実行した結果です。

Model: gpt-5.4-mini
Skills Directory: C:\...\skills
Available Skills: percentage-calculator, unit-converter
Message: 26.2マイルをキロメートルに、75キログラムをポンドに変換して、あわせて80の15%も計算してください。
Agent: - 26.2マイル = **42.1647キロメートル**
- 75キログラム = **165.3465ポンド**
- 80の15% = **12**

Used Skills: unit-converter, percentage-calculator

最後に

本記事では、Microsoft Agent Framework の Skill について、概要、使いどころ、そして Python での実装方法を整理しました。あわせて、InlineSkill を使う code-defined Skill と、SkillsProvider.from_paths() を使う file-based Skill の両方を、そのまま試せる形で紹介しました。

Skill は、エージェント本体の instructions を大きくしすぎずに、必要な知識や処理だけを後から追加したい場面で役に立ちます。特に、社内手順の参照、単位変換や割合計算のような小さな業務ロジックの切り出し、参照資料と実行スクリプトをまとめて管理したいケースでは使いやすい選択肢です。

今後は、今回のような最小サンプルを起点にして、ClassSkill や MCP-based Skill まで広げていくと、Skill の活用範囲をより具体的にイメージしやすくなります。また、実運用を考える場合は、Skill のレビュー手順や script 実行時の安全性もあわせて整理していくのがよいと思います。

執筆担当者プロフィール
寺澤 駿

寺澤 駿(日本ビジネスシステムズ株式会社)

IoTやAzure Cognitive ServicesのAIを活用したデモ環境・ソリューション作成を担当。

担当記事一覧