1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Functionsの実装とCopilot Studio/Azure AI Foundry Agent Serviceからのツール呼出

Last updated at Posted at 2025-07-06

Azure FunctionsでREST APIを作って、Copilot StudioとAzure AI Foundry Agent Serviceから呼び出してみました。色々と時間かけたポイントあったので、記事にしておきます。

Step

0. 前提

Azure Functionsの従量課金プランはWindowsの方がクラウド上にリソース多そうだったので、Windowsにしています。

種類 Version 備考
OS Ubuntu22.04.5 LTS WSL2で動かしています
Python 3.12.9 3.13はVS CodeのExtensionで使えなかった
Poetry 2.1.3
VS Code 1.101.2
Azure Functions Core Tools 4.0.6821

VSCode 拡張

種類 Version 備考
Azure Functions 1.17.3
Azurite 3.34.0

Python パッケージ

種類 Version 備考
azure-functions 1.23.0
fastapi 0.115.14
pydantic 2.11.7
python-dotenv 1.1.1
openapi-generator-cli 7.14.0 jdk4pyもインストール

openapi-generator-cliは、以下のようにjdkもインストール。

poetry add openapi-generator-cli
poetry add openapi-generator-cli[jdk4py]

その他

  • Azure上にサブスクリプションとリソースグループ作成済
  • ローカルのプロジェクトのディレクトリ作成、Poetryの初期設定済
  • Copilot Studioの基本設定済
  • Azure AI Foundryの基本設定済

1. Azure Functions 実装

1.1. Azure Functions 作成

Azure Portalから作成します。
フレックス従量課金を選択。
image.png

Functionsの基本的な項目。あとはデフォルトのまま作成。
image.png

1.2. Azure Functions 実装

基本的な Azure Functions の Python でのHTTP Trigger実装方法は記事「Azure FunctionsにPythonでHTTP Triggerの関数実装と注意点」を参照してください。
今回は、以下のサンプルを改変して作っています。

レポジトリの重要部分だけピックアップすると以下のディレクトリ/ファイルです。

レポジトリの重要部分
├── WrapperFunction
│   └── __init__.py
├── .env
├── function_app.py
├── host.json
└── requirements.txt

1.2.1. host.json

Using FastAPI Framework with Azure Functions内の指示に従っています。
routePrefixを設定することで、今までエンドポイントのPrefixがapiだったのが、無くなります。FastAPIだとこうするべきなのかまで調べていませんが、エンドポイントが変わるのが注意点です(多分、この設定起因でAzure Portalからテスト実行もできなくなる原因になっている気がします)。

host.json
{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensions": {
    "http": {
      "routePrefix": ""
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

1.2.2. function_app.py

シンプルなルートのスクリプトです。
今回は、シンプルに認証なしにしていますがhttp_auth_level=func.AuthLevel.ANONYMOUSでキー認証を付加できます。

function_app.py
import azure.functions as func

from WrapperFunction import app as fastapi_app

app = func.AsgiFunctionApp(app=fastapi_app, http_auth_level=func.AuthLevel.ANONYMOUS)

1.2.3. WrapperFunction/__init__.py

今回のメインのスクリプトです。
getメソッド2つとpostメソッド1つで計3つをサンプルとして実装しています。

WrapperFunction/__init__.py
import os
from typing import Optional

from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field

load_dotenv(override=True)

ITEMS = [
    {"id": 1, "name": "みそラーメン", "price": 900},
    {"id": 2, "name": "醤油ラーメン", "price": 600},
    {"id": 3, "name": "塩ラーメン", "price": 700},
]

URL = (
    os.getenv("WEBSITE_HOSTNAME")
    if os.getenv("WEBSITE_SITE_NAME")
    else "http://localhost:7071"
)

app = FastAPI(
    title="Function API for agents",
    version="1.0.0",
    description="FastAPI on Azure Functions for agents",
    servers=[
        {
            "url": URL,
            "description": "Azure Function Endpoint",
        }
    ],
)
# Copilot StudioのでImportできるバージョンに下げる
app.openapi_version = "3.0.3"


class Item(BaseModel):
    id: int = Field(..., description="商品のID")
    name: str = Field(..., description="商品の名前(例:とんこつラーメン)")
    price: int = Field(..., description="商品の価格(単位:円)")


class ItemListResponse(BaseModel):
    message: str = Field(..., description="メッセージ")
    item: Optional[list[Item]] = Field(None, description="登録された商品の情報")


class ItemResponse(BaseModel):
    message: str = Field(..., description="処理結果のメッセージ")
    item: Optional[Item] = Field(None, description="登録された商品の情報")


@app.get("/items", response_model=ItemListResponse)
async def get_items(
    q: Optional[str] = Query(
        "",  # No Default
        title="検索クエリ",
        max_length=50,
        description="オプションの検索文字列。結果を絞り込みます。",
    ),
):
    """
    アイテムの一覧を返します。検索条件`q`を入力してフィルタ可能です。

    - **q**: 検索クエリ
    """
    print(f"{q=}")

    if q:
        results = [item for item in ITEMS if q.lower() in item["name"].lower()]
    else:
        results = ITEMS

    if results:
        # q が渡らないケースもあるが省略
        return {
            "message": f"'{q}' に一致するレコードが{len(results)}件見つかりました。",
            "item": results,
        }
    else:
        return {
            "message": f"'{q}' に一致するレコードは見つかりませんでした。",
            "item": [],
        }


@app.get(
    "/items/{item_id}",
    response_model=Item,
    summary="アイテム情報を取得する",
)
def read_item(item_id: int):
    """
    指定した ID のアイテム情報を取得します。

    - **item_id**: 取得したいアイテムの一意な識別子
    """
    # 実際はデータベースなどから取得する想定
    item = next((item for item in ITEMS if item["id"] == item_id), None)

    if item:
        return item
    else:
        raise HTTPException(
            status_code=404, detail=f"ID {item_id} の商品は見つかりませんでした。"
        )


@app.post("/items", response_model=ItemResponse)
def create_item(item: Item):
    """
    アイテムを登録します。

    - **item**: アイテム情報
    """

    return {"message": "アイテムを受け取りました。", "item": item}

1.2.4. requirements.txt

Poetryからrequirements.txt 作成。以下のコマンドで出しています。
--without-hashesをつけないとすごく長くなります。
結果に出ている警告は無視しています。

$ poetry export -f requirements.txt --output requirements.txt --without-hashes
Warning: poetry-plugin-export will not be installed by default in a future version of Poetry.
In order to avoid a breaking change and make your automation forward-compatible, please install poetry-plugin-export explicitly. See https://python-poetry.org/docs/plugins/#using-plugins for details on how to install a plugin.
To disable this warning run 'poetry config warnings.export false'.
requirements.txt
annotated-types==0.7.0 ; python_version >= "3.12" and python_version < "4.0"
anyio==4.9.0 ; python_version >= "3.12" and python_version < "4.0"
azure-functions==1.23.0 ; python_version >= "3.12" and python_version < "4.0"
fastapi==0.115.14 ; python_version >= "3.12" and python_version < "4.0"
idna==3.10 ; python_version >= "3.12" and python_version < "4.0"
markupsafe==3.0.2 ; python_version >= "3.12" and python_version < "4.0"
pydantic-core==2.33.2 ; python_version >= "3.12" and python_version < "4.0"
pydantic==2.11.7 ; python_version >= "3.12" and python_version < "4.0"
python-dotenv==1.1.1 ; python_version >= "3.12" and python_version < "4.0"
sniffio==1.3.1 ; python_version >= "3.12" and python_version < "4.0"
starlette==0.46.2 ; python_version >= "3.12" and python_version < "4.0"
typing-extensions==4.14.1 ; python_version >= "3.12" and python_version < "4.0"
typing-inspection==0.4.1 ; python_version >= "3.12" and python_version < "4.0"
werkzeug==3.1.3 ; python_version >= "3.12" and python_version < "4.0"

1.2.5. .env

FunctionのURLを設定しています。

.env
WEBSITE_HOSTNAME="<Function URL>

1.3. Azure Functions テストとデプロイ

1.3.1. ローカルテスト

VS CodeのコマンドパレットでAzuriteを開始して、以下のコマンドでローカルテストをします。

host func start

FastAPIを使っているのでローカルテストは見やすいです。
http://localhost:7071/docs または http://localhost:7071/redoc
からAPIの定義を確認およびテストができます。

image.png

image.png

1.3.2. デプロイ

Azure Functionsをデプロイします。特記事項はないです。

1.4. Open API 定義取得

デプロイした先で https://hostname/docs または https://hostname/redoc のリンクから、または直接 https:///openapi.json からOpenAPIの定義ダウンロードします。
ダウンロードしたopenapi.jsonはCopilot Studioにインポートする際にv2形式に変える必要があります(Azure AI Foundry側では不要なステップ)。API Management使うとこんな面倒なことは不要になるかもしれません。
openapi.json のファイルをカレントディレクトリに置いて、以下のコマンド実行します。

$ poetry run openapi-generator-cli generate   -i openapi.json   -g openapi   -o ./swagger2-json

[main] INFO  o.o.codegen.DefaultGenerator - Generating with dryRun=false
[main] INFO  o.o.c.ignore.CodegenIgnoreProcessor - Output directory (/home/fukuhara/repositories/function-tool/./swagger2-json) does not exist, or is inaccessible. No file (.openapi-generator-ignore) will be evaluated.
[main] INFO  o.o.codegen.DefaultGenerator - OpenAPI Generator: openapi (documentation)
[main] INFO  o.o.codegen.DefaultGenerator - Generator 'openapi' is considered stable.
[main] INFO  o.o.c.languages.OpenAPIGenerator - Output file name [outputFileName=openapi.json]
[main] INFO  o.o.codegen.InlineModelResolver - Inline schema created as Location_inner. To have complete control of the model name, set the `title` field or use the modelNameMapping option (e.g. --model-name-mappings Location_inner=NewModel,ModelA=NewModelA in CLI) or inlineSchemaNameMapping option (--inline-schema-name-mappings Location_inner=NewModel,ModelA=NewModelA in CLI).
[main] INFO  o.o.c.languages.OpenAPIGenerator - wrote file to /home/fukuhara/repositories/function-tool/./swagger2-json/openapi.json
[main] INFO  o.o.codegen.TemplateManager - writing file /home/fukuhara/repositories/function-tool/./swagger2-json/README.md
[main] INFO  o.o.codegen.TemplateManager - writing file /home/fukuhara/repositories/function-tool/./swagger2-json/.openapi-generator-ignore
[main] INFO  o.o.codegen.TemplateManager - writing file /home/fukuhara/repositories/function-tool/./swagger2-json/.openapi-generator/VERSION
[main] INFO  o.o.codegen.TemplateManager - writing file /home/fukuhara/repositories/function-tool/./swagger2-json/.openapi-generator/FILES
############################################################################################
# Thanks for using OpenAPI Generator.                                                      #
# We appreciate your support! Please consider donation to help us maintain this project.   #
# https://opencollective.com/openapi_generator/donate                                      #
############################################################################################

./swagger2-jsonディレクトリに以下のファイルが生成されます。

生成されたファイル
.
├── .openapi-generator
│   ├── FILES
│   └── VERSION
├── .openapi-generator-ignore
├── README.md
└── openapi.json

2. Copilot Studio Agent 登録

2.1. Copilot Studio Agent 登録

Agentの登録は省略します。
「ツール」タブで「+ツールを追加する」をクリック。
image.png

「新しいツール」ボタンをクリック。
image.png

REST APIを選択。
image.png

openapi.jsonをアップロード
image.png

特に変更せずに次画面へ遷移。この画面でOpenAPI v3だとエラーが起きます。画面上だとエラーメッセージ詳細がわからないので、ブラウザの開発者ツールを開いて、HTTPのレスポンス見ると詳しい原因が出ています。
image.png

今回は認証なし
image.png

ここから各ツールを選択します(詳細省略)。基本的にはOpenAPI上にパラメータも定義しているので、各項目説明も自動入力されています(1つだけ抜けありました)。
image.png

「接続の作成」をクリック
image.png

これでツールが追加されたので、「戻る」を押して戻ります。
image.png

「function」で検索して、先ほど登録したツールを選択。今回は試しに「Get Items」を追加。
image.png

「Create New Connection」を選択し、作成後、エージェントに追加します。
image.png

「概要」タブにいき、オーケストレーションは生成AIにやらせます。
image.png

テストで問題なく呼べています。
image.png

2.2. 公開

「公開」ボタンをクリックして公開します。
image.png

2.3. Copilot / Teams から呼出

タブ「チャネル」で「Teams と Micorosoft 365 Copilot」を選択し、「エージェント を Microsoft 365 Copilot で使用可能にする」をONにして、「Microsoft 365 エージェントを表示する」または「Teams でエージェントを表示する」をクリックします。
image.png

これでTeams と Micorosoft 365 Copilotから使えるような設定になります。
Copilot 画面
image.png

Teams画面
image.png

3. Azure AI Foundry Agent Service 登録

3.1. Agent登録

細かい手順は省略してアクション追加部分のみ。
Azure AI Foundryからアクションを追加。
image.png

OpenAPI3.0指定ツールを選択。
image.png

簡単な情報をいれます。
image.png

最後にopenapi.jsonの内容をコピペで入れて完成です。こちらはわざわざv2に変換しないopenapi.jsonで大丈夫です。

3.2. Agentテスト

プレイグラウンドで試してみます。
image.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?