🤖

Microsoft Agent Framework と Microsoft Foundry の連携について色々試してみる

に公開

はじめに

Microsoft Agent Frameworkでエージェントを作り、Microsoft Foundry で作成したエージェントを管理する というエージェントの実行管理の仕組みは、今後定番の構成になりそうだなと思ったので、現時点で何ができるのかについてまとめます。

Microsoft Foundry でも、エージェントを画面操作で作ることはできますが、個人的にエージェントの実装はコードで書くことが多いので、上記の役割分担としています。

やること

Microsoft Agent Framework(MAF)で作成したエージェントを、Microsoft Foundry 上で管理する方法や、Microsoft Foundry を使ってできることについて把握する

  • エージェントの呼び出し
  • エージェント実行ログを Microsoft Foundry で監視する
  • Microsoft Foundry のツール実行、評価機能を呼び出してみる

用語整理

Microsoft Foundry 周りの関連製品名が多く紛らわしいので、簡単に整理しておきます。

Microsoft Agent Framework

AI エージェント開発フレームワーク。以降はMAFと呼ぶことにします。
https://github.com/microsoft/agent-framework

Microsoft Foundry

AIアプリケーションの開発・デプロイ・管理を統合的に行えるプラットフォーム。
https://learn.microsoft.com/ja-jp/azure/ai-foundry/what-is-foundry?view=foundry-classic

Microsoft Foundry Agent Service

Microsoft Foundry 内でエージェントを作る機能のこと
https://azure.microsoft.com/ja-jp/products/ai-foundry/agent-service/?msockid=368d4027cb3a641e27e55690ca3c65e1

Microsoft Foundry SDK

Microsoft Foundry の主要機能を操作するSDK
https://learn.microsoft.com/ja-jp/azure/ai-foundry/how-to/develop/sdk-overview?view=foundry-classic&pivots=programming-language-python

基本理解

Microsoft Agent Framework では OpenAI をはじめ、複数のプロバイダーに対応しており、それぞれのクライアントが用意されています。
Microsoft Foundry をクライアントとして設定する場合、AzureAIClientまたは AzureAIAgentClient を使用します。

AzureAIClientは Azure SDK(Foundry SDK) の AIProjectClient を内部的に呼び出しています。
https://github.com/microsoft/agent-framework/blob/main/python/packages/azure-ai/agent_framework_azure_ai/_client.py

MAF では Foundry の機能呼び出しはこれらの実装に依存していそうです。

Microsoft Foundry の 整理

MAF には Microsoft Foundry のクライアントとして、AzureAIAgentClientAzureAIClient の2つが存在し、どちらも Microsoft Foundry Agent Service(Microsoft Foundry)に接続しているようだったので、違いについて少しまとめてみます。

結論としては、歴史的背景による違いでした。
AzureAIClient の方が現状新しいのでこちらを利用します。

AzureAIAgentClient

from agent_framework_azure_ai import AzureAIAgentClient
from azure.identity.aio import DefaultAzureCredential

credential = DefaultAzureCredential()
client = AzureAIAgentClient(credential=credential)

こちらは、azure.ai.agents.aio モジュールの AgentsClient クラスを使用しています
https://github.com/microsoft/agent-framework/blob/main/python/packages/azure-ai/agent_framework_azure_ai/_chat_client.py#L414

AzureAIClient

from agent_framework.azure import AzureAIClient
from azure.identity.aio import DefaultAzureCredential

credential = DefaultAzureCredential()
client = AzureAIClient(credential=credential)

こちらは、azure.ai.projects.aio モジュールの AIProjectClient クラスを使用しています。
https://github.com/microsoft/agent-framework/blob/main/python/packages/azure-ai/agent_framework_azure_ai/_client.py#L113

歴史的背景について
元々は、Microsoft Foundry の機能別パッケージが別のパッケージとして存在し、azure-ai-projects に依存している設計だったようです。

azure-ai-projects v1
|- azure-ai-agents
|- azure-ai-inference
|- azure-ai-evaluation

2025/12 ~ 2026/1 に、これらの個別パッケージが azure-ai-projects の1つに統合され、v2となった背景のようです。
https://devblogs.microsoft.com/foundry/whats-new-in-microsoft-foundry-dec-2025-jan-2026/

  • azure-ai-projects v2 beta: Agents, inference, evaluations, and memory are now unified in a single package — the dependency is gone; shipped January 6, 2026.azure-ai-agents``2.0.0b3

v2になり、エージェントごとのバージョン管理や、メモリ機能が搭載されました。

Microsoft Foundry には、現時点(2026/2/27)で新旧2つの画面が存在しています。
この UI の刷新も、内部で利用している Foundry SDK(azure-ai-projects)のバージョン新旧と対応しているという理解でなんとなく把握できました。

  • Classic(azure-ai-projects v1と対応)

  • New(azure-ai-project v2と対応)

以降は v2 AzureAIClient を利用してサンプルコードを実行していきます。

事前準備

以下の環境変数を .env に追加します。

.env
# Azure AI
AZURE_AI_PROJECT_ENDPOINT="<YOUR-AI-PROJECT-ENDPOINT>"
AZURE_AI_MODEL_DEPLOYMENT_NAME="<YOUR-DEPLOYMENT-MODEL-NAME>"

下記の.env.sample をとりあえず MAF を始めるプロジェクトで貼り付けておくと後々色々試すときに便利かなと思います。
https://github.com/microsoft/agent-framework/blob/main/python/.env.example

エージェント実行

MAF 上でエージェントを作成し、Microsoft Foundry 上から作成したエージェントを確認します。
とりあえずサンプル的に実行します。
ここではモデルは gpt-5-nano を使用しています。

import asyncio
import os
from agent_framework import Agent
from agent_framework.azure import AzureAIClient
from azure.identity.aio import DefaultAzureCredential
from dotenv import load_dotenv

load_dotenv()

async def main():
    async with (
        DefaultAzureCredential() as credential,
        AzureAIClient(
            project_endpoint=os.getenv("AZURE_AI_PROJECT_ENDPOINT"),
            model_deployment_name=os.getenv("AZURE_AI_MODEL_DEPLOYMENT_NAME"),
            credential=credential,
        ) as client,
    ):
        agent = Agent(
            client=client,
            name="haiku-agent",
            instructions="あなたはuserの質問に対して、季語を含む俳句のみ回答してください。",
        )

        response = await agent.run("春の訪れを感じる瞬間はいつですか?")
        print(f"Response:\n{response.text}\n")

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

実行後

Response:
梅の香や朝の霧
川はひそかに光を拾い
春の声届く

Microsoft Foundry 上で haiku-agent が v1 として作成されました。

エージェントの詳細を開くと、実行時のシステムプロンプトや deploy model が設定されています。

[Traces]のタブから実行ログをみることができます。
GPT-5シリーズは、reasoning modelなので、実行完了まで約12秒と少し応答に時間がかかりました。

[Trace ID]を開くと、詳細なエージェントのトレース情報が参照できます。
トレースはツリー構造となっており、reasoning 含めて内部で2回 LLM がコールされていることがわかります。
トレースのattributes.gen_ai.operation.nameInvoke AgentChat といったバッジアイコンと対応しているのがわかります。

チャットログを開くと、LLM にどのようなメッセージを送信しているか確認できます。
Instructions が重複している??のが気になりました。

推測ですが以下が発生していそうです。Issue も特に見当たらなかったので仕様なのかなーと思いました。
Microsoft Foundry 上でエージェントを管理する場合、プロンプトもコードベースで定義するのではなく、Microsoft Foundry で設定することが前提なのかもしれません。

① MAF で agent.run() 呼び出し

② MAF が Foundry に「haiku-agent」を作成
   → instructions がエージェント定義として Foundry に保存される

③ MAF が LLM をコールする
   → このとき instructions をリクエストに含める

④ Foundry 側で処理するとき
   → エージェント定義の instructions(②で保存)を適用
   → リクエスト本文の instructions(③で送った)も適用
   → 同じ message (instructions)が LLM にリクエストされる

モデルを gpt-4.1-nano に変更して再度実行すると、 version:2 になりました。

Microsoft Foundry 上で作成されたエージェントをMAFで呼び出す

先ほどの実行で、Microsoft Foundry 上に haiku-agent が新規作成できたので、これを MAF で呼び出します。

main.py
import asyncio
import os
from agent_framework import Agent
from agent_framework.azure import AzureAIClient
from azure.identity.aio import DefaultAzureCredential
from dotenv import load_dotenv

load_dotenv()

async def main():
    async with (
        DefaultAzureCredential() as credential,
        AzureAIClient(
            project_endpoint=os.getenv("AZURE_AI_PROJECT_ENDPOINT"),
            model_deployment_name=os.getenv("AZURE_AI_MODEL_DEPLOYMENT_NAME"),
            credential=credential,
            use_latest_version=True
        ) as client,
    ):
        agent = Agent(
            client=client,
            name="haiku-agent",
        )

        response = await agent.run("春の訪れを感じる瞬間はいつですか?")
        print(f"Response:\n{response.text}\n")

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

変更点は、use_latest_version=TrueAzureAIClient の引数に渡すことと、
Agent定義の instructionsを除去している点です。

  • 実行結果

すでにMicrosoft Foundry に定義済みのエージェントを呼び出しているので、instructions の重複もなくなりました。エージェント定義に設定された Instruction が注入されています。

use_latest_version=True がないと、以下のように新しいバージョンのエージェントとして Userメッセージのみで LLM 呼び出しされたものができてしまいます。

Workflow 機能の相互連携について

Microsoft Foundry には、Dify や OpenAI の Agent Builder のような GUI でワークフローを作成する機能があります。(発表当時に試して以降触ってませんが)

一方、Microsoft Agent Framework にも Workflowという機能(設計思想的なもの)があります。
これらを相互に連携する方法がないか調べてみましたが、直接連携する方法はなさそうに思えます。

公式ドキュメントによると、[YAML] のエディタを開き、VS Code web を立ち上げて、あとはGitHub Copilot 使って変換しましょう、的な連携方法が紹介されていました。

YAML ベースのワークフローをカスタマイズするには、GitHub Copilot を使用してそれらを Agent Framework コードに変換します。

https://learn.microsoft.com/ja-jp/azure/ai-foundry/agents/how-to/vs-code-agents-workflow-low-code?view=foundry#convert-a-yaml-workflow-to-agent-framework-code


  • Hosted Agent

MAF で作成した Workflow を Microsoft Foundry 上にデプロイすることは Hosted Agent という機能を使用することで可能なようです。
Workflow として上記の GUI と同期されるのではなく、エージェントとして作成されるようです。
https://learn.microsoft.com/ja-jp/azure/ai-foundry/agents/concepts/hosted-agents?view=foundry

Hosted Agent を試してみましたが、デプロイはできたのですが、チャットがうまく動かなかったのでまた時間のある時にやろうかなと思います。
とりあえず、MAF で作った Workflow を Foundry 上で実行することができることがわかりました。
Hosted Agent としてデプロイされたエージェントは、typehosted になります

以下のサンプルコードで試すことができます。
https://github.com/microsoft/agent-framework/tree/main/python/samples/05-end-to-end/hosted_agents

Microsoft Foundry の機能を MAF で実行してみる

エージェントの実行ログを Microsoft Foundry で確認する

Microsoft Foundry のクライアント経由で実行したエージェントごとのログは、先ほどのように、Microsoft Foundry のエージェントタブ(Foundry Agent Service)で確認することができます。

MAF では、エージェントの実行トレースを Application Insights に送信できます。

Microsoft Foundry のクライアントではなく、 AzureOpenAIResponsesClient を使用してエージェントを実行し、 Microsoft Foundry 上でエージェントの実行トレースを確認してみます。

.env に以下を追加します。

APPLICATIONINSIGHTS_CONNECTION_STRING=<your-conn-str>

先ほどのコードからクライアントを変更し、ログの接続設定を追加します
https://learn.microsoft.com/en-us/agent-framework/user-guide/agents/agent-observability

import asyncio
import os

from agent_framework import Agent
from agent_framework.azure import AzureOpenAIResponsesClient
from agent_framework.observability import enable_instrumentation
from azure.identity.aio import DefaultAzureCredential
from azure.monitor.opentelemetry import configure_azure_monitor
from dotenv import load_dotenv

load_dotenv()

configure_azure_monitor() # Application Insights の接続設定
enable_instrumentation(enable_sensitive_data=True) # 

async def main():
    async with DefaultAzureCredential() as credential:
        client = AzureOpenAIResponsesClient(
            project_endpoint=os.getenv("AZURE_AI_PROJECT_ENDPOINT", ""),
            deployment_name=os.getenv("AZURE_AI_MODEL_DEPLOYMENT_NAME", ""),
            credential=credential,
        )
        agent = Agent(
            client=client,
            name="create-haiku-agent",
            instructions="あなたはuserの質問に対して、季語を含む俳句のみ回答してください。",
        )
        response = await agent.run("春の海についての俳句を1つ詠んでください。")

    print(f"\nResponse:\n{response.text}")


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

これらのトレースは Application Insights (Log Analytics) に保存され、記録された OpenTelemetry のトレースデータに対して、KQLクエリでフィルターされたものがFoundry の UI として表示されています。

Classic には以下のようなトレースタブからエージェントの実行トレースを Foundry 上で確認できます。
※New の UI には現状、2026/2/27 時点ではトレースを表示する機能は確認できませんでした

あとは、 "入力" "出力" 列が歯抜けになってしまうのが気になりました。
トレースの詳細を開くと確認できますが、対応するspanがうまく渡っていないようです。
MAF が出力するトレースのスパン属性と、classic (v1) の表示形式に互換性がないのかもしれません。

OpenTelemetry で出力したエージェントのトレースデータの確認ですが、Application Insights の Agents(preview) という機能を知りました。
https://learn.microsoft.com/ja-jp/azure/azure-monitor/app/agents-view

上記の赤枠からエージェントの実行詳細を確認でき、基本的に Foundry と似たようなエージェントログが確認できます。
個人的には Microsoft Foundry 上でログを見るよりはこちらをみた方が、直感的でわかりやすく感じます。

system message 重複再発?

AzureOpenAIResponsesClient を使用しましたが、system role の message が重複していますね、issue も見当たらないので仕様か不具合かわからないです、単純に実装が悪いのかも。

[
  {
    "type": "system",
    "content": "{\"type\":\"text\",\"content\":\"あなたはuserの質問に対して、季語を含む俳句のみ回答してください。\"}"
  },
  {
    "type": "system",
    "content": "{\"type\":\"text\",\"content\":\"あなたはuserの質問に対して、季語を含む俳句のみ回答してください。\"}"
  },
  {
    "type": "user",
    "content": "{\"type\":\"text\",\"content\":\"春の海についての俳句を1つ詠んでください。\"}"
  },
  {
    "type": "output",
    "content": "{\"type\":\"text\",\"content\":\"春の海や  \\n波音響きて  \\n鯉泳ぐ\"}"
  }
]

OpenTelemetry GenAI Attributes

今回は詳細は割愛しますが、OpenTelemetry GenAI Semantic Conventions に準拠してテレメトリが計装されています。
生成 AI システムの可観測性データを標準化するための仕様であり、エージェント操作(invoke_agent)、モデル呼び出し(chat)、ツール実行(execute_tool
といったスパンの命名規則や、gen_ai.usage.input_tokens などの属性名が統一的に定義されています。

この規約に準拠することで、異なるフレームワークやプロバイダー間でも一貫した可観測性を実現することができるようです。

ツール実行

Microsoft Foundry で登録したツールや、デフォルトで用意されたツールを呼び出すことができます。
Microsoft Agent Framework の AzureAIClient (Foundry v2) では以下が用意されていました。

  • get_code_interpreter_tool(): コード実行ツール
  • get_file_search_tool(): Foundry に登録したファイルをベクトル検索
  • get_web_search_tool(): Bing SearchでWeb検索
  • get_image_generation_tool(): 画像生成
  • get_mcp_tool(): 外部MCPサーバ接続

標準ツール実行
code_interpreter_tool を内部的に呼び出しています。

import asyncio
import os

from agent_framework import Agent
from agent_framework.azure import AzureAIClient
from azure.identity.aio import DefaultAzureCredential
from dotenv import load_dotenv

load_dotenv()

async def main():
    async with (
        DefaultAzureCredential() as credential,
        AzureAIClient(
            project_endpoint=os.getenv("AZURE_AI_PROJECT_ENDPOINT"),
            model_deployment_name=os.getenv("AZURE_AI_MODEL_DEPLOYMENT_NAME"),
            credential=credential,
        ) as client,
    ):
        await client.configure_azure_monitor(enable_sensitive_data=True)

        code_tool = AzureAIClient.get_code_interpreter_tool()

        agent = Agent(
            client=client,
            name="code-interpreter-tool-agent",
            instructions=(
                "あなたは有能な計算処理を得意とするアシスタントです。"
                "計算処理や日付取得が必要な場合などは、CodeInterpreterToolを使用する。"
            ),
            tools=[code_tool],
        )

        query = "出張してきた。交通費は往復6,200円、日当は2,000円、会議費として3,500円(上限3,000円)だった。精算額を計算して欲しい。"
        response = await agent.run(query)
        print(f"\nResponse:\n{response.text}")

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

  • 出力
Response:
交通費は往復で6,200円です。日当は2,000円、会議費は上限が3,000円ですが、実際の会議費は3,500円なので、上限の3,000円を適用します。

合計金額は以下の通りです:
- 交通費:6,200円
- 日当:2,000円
- 会議費:3,000円(上限適用後)

これらを合計します。計算しますね。 精算額は11,200円です。

ツール実行は5秒程度でした。簡単な計算処理ですが若干遅いような感じもします

また、先ほどは Foundry Agent Service 上でツールの実行ログを見てましたが、Application Insights 上では Foundry 内部で実行されたツールの入出力ログは、LLMコール の input/output から確認することができました。

  • MCP

    Connect a toolから新しいツールを登録できます。
    MCP / OpenAPI / A2A などがあり、カタログとして MCP が用意されています。

とりあえず、FoundryMCPServer を登録してみました。

  • FoundryMCPServerPreview でできること
    • Microsoft Foundry 上に作成したエージェントの一覧確認/作成/削除 ができる
    • データセット(ナレッジ)の更新や読み取りができる
    • 評価の作成や実行、実行結果の比較を作成できる
    • モデルカタログを確認したり、モデルデプロイの一覧取得/作成/削除 ができるなど

とりあえず Microsoft Foundry 上の操作を一通り実行可能な MCP みたいです。
詳細は以下にツールの説明があります。
https://learn.microsoft.com/ja-jp/azure/ai-foundry/mcp/available-tools?view=foundry

  • 実行
    Microsoft Foundry の画面から Tools を開き、MCPサーバのエンドポイントを取得します。

approval_mode はツール実行時にユーザーに承認を求める設定となり、
ここでは never_require = 承認なしで実行OKとしています

  • approval_mode
    • always_require: ツール実行には承認を必須とする
    • never_require: 承認なしツール実行OK
import asyncio
import os

from agent_framework import Agent
from agent_framework.azure import AzureAIClient
from azure.identity.aio import DefaultAzureCredential
from dotenv import load_dotenv

load_dotenv()

async def main():
    async with (
        DefaultAzureCredential() as credential,
        AzureAIClient(
            project_endpoint=os.getenv("AZURE_AI_PROJECT_ENDPOINT"),
            model_deployment_name=os.getenv("AZURE_AI_MODEL_DEPLOYMENT_NAME"),
            credential=credential,
        ) as client,
    ):
        await client.configure_azure_monitor(enable_sensitive_data=True)

        foundry_mcp_tool = AzureAIClient.get_mcp_tool(
            name="FoundryMCPServer", # MCPServer名
            url="https://mcp.ai.azure.com", # MCPサーバのエンドポイントを設定
            project_connection_id="FoundryMCPServerpreview", # ツール名
            approval_mode="never_require", # 承認モード
        )

        agent = Agent(
            client=client,
            name="foundry-mcp-agent",
            instructions=(
                "あなたは Microsoft Foundry の管理者としてユーザーの要望に対応するアシスタントです。"
                "ユーザーの質問がMicrosoft Foundry に関する操作要望や質問である場合、FoundryMCPServer ツールを実行して対応してください。"
            ),
            tools=[foundry_mcp_tool],
        )

        query = "最近新しくMicrosoft Foundryで使えるようになったモデルって何?"
        print(f"Query: {query}")

        response = await agent.run(query)

        print(f"\nResponse:\n{response.text}")

if __name__ == "__main__":
    asyncio.run(main())
Query: 最近新しくMicrosoft Foundryで使えるようになったモデルって何?

Response:
最近Azure上で使えるようになったモデルには様々な種類があります。例えば、AzureMLのモデル登録状況や評価から、次のような新しいモデルが追加されています:

- **AzureML Models like "Kimi-K2-Thinking"**:高い正確性を持ち、チャットコラムや安全性の評価も良好です。
- **OpenAIモデルの新バージョン "gpt-4.1-mini" など**:コスト効率と性能のバランスが良いモデルが追加され、さまざまな評価指標もクリアしています。
- **AzureMLとAzureML-OpenAIの最新モデル**:性能やコスト、安全性を考慮した多様なバリエーションで提供されています。

具体的なモデル名や評価指標について詳細をお知りになりたい場合は教えてください!

Kimi-K2-Thinking2025/12/8に提供されたモデルですね
gpt-realtime-1.5gpt-5.3-codex などが最近登場したモデルのことを回答として期待していましたが、gpt-4.1-nano での推論では難しかったようです。

実行から5分ぐらい経過した後で、ログが表示され、こちらでもツール実行できていることを確認できました。トークン消費がかなり激しいですね

MCPツールの設定で、利用するツールのみ allowed_tools で絞ることができます。
以下は先ほどの Foundry MCP を用いて、モデル一覧取得ツールのみ設定しています。

foundry_mcp_tool = AzureAIClient.get_mcp_tool(
    name="FoundryMCPServer",
    url="https://mcp.ai.azure.com",
    project_connection_id="FoundryMCPServerpreview",
    allowed_tools=[
        "model_catalog_list",
    ],
    approval_mode="never_require",
)
  • 再度実行
Query: 最近新しくMicrosoft Foundryで使えるようになったモデルって何?

Response:
最近Microsoft Foundryで利用できるようになった新しいモデルには、OpenAIやその他の著名なAI研究機関が提供する最新モデルなど、多数あります。具体的には、GPTシリーズの新バージョン(例:gpt-5.3-codexやgpt-5.2-chat)、音声生成や画像生成に対応したモデル、さらには多言語対応や特定の用途に特化したモデルなどが追加されています。

これらは、AIの最新技術を活用したさまざまなタスクに対応できるようになっており、ユーザーが高度なAIアプリケーションを構築するのに役立ちます。もし特定のモデルについて詳しく知りたい場合は教えてください。

若干 OpenAI 系のモデルに情報の偏りがありますが、先ほどよりは最新のモデルを引っ張ってきてくれました。
参照する MCPツールを限定したことで、 入力トークン(6276→367)も減り、ツールで取得したコンテキストを含む出力トークン(125065→15810)も減少しました。それでも多いかなーとは思いますが・・
とりあえず、ツールの絞り込みが効いていることが確認できます。

評価

最後に、Foundry の評価機能について実行してみます。
MAF はほとんど関係しない(Foundry SDK の機能)ですが、あまり使ったことがないので試してみます。

主要な評価指標については、ある程度デフォルトで Evaluator が用意されています。
かなり数も多く、ここは別のタイミングで深掘りしたいなーと思いました。

以下のエージェントに関する評価軸(Evaluator)も用意されており、かなり実用的だと思いました。

  • Task Adherence: システムプロンプトにどれだけ準拠しているか
  • Task Completion: エージェントのタスクの完遂度はどれくらいか
  • Tool Selection: エージェントが適切なツールを選択できているか など

自分で Evaluator を定義することもできます
また、評価にはコードベースとプロンプトベースの評価があります。
https://learn.microsoft.com/ja-jp/azure/ai-foundry/concepts/evaluation-evaluators/custom-evaluators?view=foundry

評価をプロンプトベース(LLM-as-a-Judge)で試してみます。

  • 評価データ(ダミー)を用意
eval_data.jsonl
{"query": "出張の交通費はどうやって精算すればいい?", "response": "出張の交通費は、領収書を添付して経費精算システムから申請してください。申請期限は出張後2週間以内です。", "context": "出張交通費の精算は、領収書原本を添付のうえ経費精算システムで申請する。申請期限は出張終了後14日以内。上長承認後に翌月給与で支給される。", "ground_truth": "出張交通費は、領収書を添付して経費精算システムから申請します。期限は出張後14日以内で、上長承認後に翌月給与で支給されます。"}
{"query": "タクシー代は経費で落とせる?", "response": "タクシー代は、終電後の帰宅や重い荷物がある場合など、合理的な理由があれば経費精算できます。事前に上長の承認を得てください。", "context": "タクシー利用は原則禁止だが、終電後・早朝・荷物搬送・体調不良など合理的理由がある場合は経費として認められる。1回5,000円以上は事前承認が必要。", "ground_truth": "タクシー代は終電後や荷物搬送など合理的な理由があれば経費精算可能です。ただし1回5,000円以上は事前に上長承認が必要です。"}
{"query": "接待の飲食費に上限はある?", "response": "接待の飲食費は1人あたり10,000円が上限です。超過する場合は部長以上の事前承認が必要になります。", "context": "接待飲食費の上限は1人あたり10,000円(税込)。超過する場合は部長以上の事前承認が必要。参加者名・目的・店名を精算時に記載すること。", "ground_truth": "接待飲食費は1人あたり税込10,000円が上限です。超過時は部長以上の事前承認が必要で、参加者名・目的・店名の記載が必要です。"}

以下 2つの Evaluator を使用しました。どちらも LLM を評価者とした判定になります。
groundedness: 正解データとどれだけ一致しているか
relevance: 質問との関連性

  • 評価実行
import asyncio
import json
import os

from pathlib import Path

from agent_framework.azure import AzureAIClient
from azure.identity.aio import DefaultAzureCredential
from dotenv import load_dotenv

load_dotenv()

EVAL_DATA_PATH = Path(__file__).parent / "eval_data.jsonl"


def load_eval_data(path: Path) -> list[dict]:
    items = []
    with open(path, encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if line:
                items.append({"item": json.loads(line)})
    return items


async def main():

    model = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME")

    async with (
        DefaultAzureCredential() as credential,
        AzureAIClient(
            project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
            model_deployment_name=model,
            credential=credential,
        ) as maf_client,
    ):
        openai_client = maf_client.project_client.get_openai_client()
        eval_items = load_eval_data(EVAL_DATA_PATH)

        testing_criteria = [
            {
                "type": "azure_ai_evaluator",
                "name": "groundedness",
                "evaluator_name": "builtin.groundedness",
                "data_mapping": {
                    "query": "{{item.query}}",
                    "response": "{{item.response}}",
                    "context": "{{item.context}}",
                },
                "initialization_parameters": {"deployment_name": model},
            },
            {
                "type": "azure_ai_evaluator",
                "name": "relevance",
                "evaluator_name": "builtin.relevance",
                "data_mapping": {
                    "query": "{{item.query}}",
                    "response": "{{item.response}}",
                    "context": "{{item.context}}",
                },
                "initialization_parameters": {"deployment_name": model},
            },
        ]

        data_source_config = {
            "type": "custom",
            "item_schema": {
                "type": "object",
                "properties": {
                    "query": {"type": "string"},
                    "response": {"type": "string"},
                    "context": {"type": "string"},
                },
                "required": ["query", "response", "context"],
            },
            "include_sample_schema": True,
        }

        evaluation = await openai_client.evals.create(
            name="test-qa-eval",
            data_source_config=data_source_config,
            testing_criteria=testing_criteria,
        )

        eval_run = await openai_client.evals.runs.create(
            eval_id=evaluation.id,
            name="expense-qa-eval-run",
            data_source={
                "type": "jsonl",
                "source": {"type": "file_content", "content": eval_items},
            },
        )
        print(f"Evaluate Running: {eval_run.id}")

        while True:
            run = await openai_client.evals.runs.retrieve(
                run_id=eval_run.id, eval_id=evaluation.id
            )
            if run.status in ("completed", "failed"):
                break
            await asyncio.sleep(5)

        if run.status == "completed":
            print(f"結果: {run.result_counts}")
            output_items_page = await openai_client.evals.runs.output_items.list(
                run_id=run.id, eval_id=evaluation.id
            )
            for i, item in enumerate(output_items_page.data):
                for result in item.results:
                    score = getattr(result, "score", "N/A")
                    passed = getattr(result, "passed", "N/A")
                    print(f"  [{i+1}] {result.name}: score={score}, passed={passed}")
            print(f"\nFoundry: {run.report_url}")
        else:
            print(f"Evaluate Error: {getattr(run, 'error', run.status)}")


if __name__ == "__main__":
    asyncio.run(main())
  • 実行結果
Evaluate Running: evalrun_96ccd4b6a8fa484c8550d1ca1d4fe2cd
結果: ResultCounts(errored=0, failed=0, passed=3, total=3)
  [1] groundedness: score=5.0, passed=True
  [1] relevance: score=4.0, passed=True
  [2] groundedness: score=5.0, passed=True
  [2] relevance: score=4.0, passed=True
  [3] groundedness: score=5.0, passed=True
  [3] relevance: score=4.0, passed=True
  • 評価結果を Microsoft Foundry で確認
    以下のように、実行した評価が履歴として Microsoft Foundry 上に表示されます。

  • 詳細を開くと、評価実行に発生したトークン数や、評価結果を確認することができます。
    評価指標は2つで、実行しています。テストデータ3件でトークン消費はこれくらいでした。

参考までに、テストデータの件数を 3件 -> 2件にしたところ、トークン数は 9330 -> 6236 になりました。評価指標を2軸設定し、1件あたり 3000トークンほど消費しました。
複数のテストデータをバッチで一括実行しているわけではなく、1件ずつシステムプロンプトを設定し、実行されていることが確認できました。
テスト件数が1000件とかになると、2指標で約 3M トークン消費する見積もりです。

rouge_score(コードベース) と violence も回してみて、コストが発生するかどうかも確認してみました。

  • rouge_score
    • 正解要約の中の N-gram のうち、どの程度が生成文に含まれているか N-gram の一致数で評価します
    • コードベースで実行される評価については、 Foundry 上の計算資源を使って実行されるようです。ただし課金形態については見つかりませんでした。(Freeなんですかね)
  • violence
    • 生成された文章が暴力的な内容(脅迫等)でないかを判断します。
    • Self-Harm など Contents Sagety で提供されている指標で、評価指標(Evaluator)に含めると、こちらのリソースを消費するようです。(1件につき1レコード)
    • 月5000レコードまで無料で、以降は従量課金となるようです。
      https://azure.microsoft.com/ja-jp/pricing/details/content-safety/

最後に

本記事では、解説というよりは検証ベースですが Microsoft Agent Framework から Microsoft Foundry へ連携してできることについて書きました。

Microsoft Agent Framework の主要機能の紹介というよりは、一つのプロバイダー(ラッパー)を使った内容だったので本質的には Foundry SDK の内容理解になってしまいましたが、結果的に Microsoft Foundry について理解が深まったと思います。

今後も Microsoft Agent Framework について理解を深めていきたいと思います!

補足

Haiku Agent ネタは以下の RC 版の発表 Blog から拾ってきました。
https://devblogs.microsoft.com/foundry/microsoft-agent-framework-reaches-release-candidate/

以下の Issue によると、MAF は3月頃 GA の見込みだそうです。楽しみですね
https://github.com/microsoft/agent-framework/issues/4078

ヘッドウォータース

Discussion