(上の画像をクリックするとこのレッスンの動画が表示されます)
ツールは、AIエージェントにより広い範囲の能力を付与できるため興味深いものです。エージェントが行える動作のセットが限られている代わりに、ツールを追加することで、エージェントはより幅広い動作を実行できるようになります。本章では、AIエージェントが特定のツールを使用して目標を達成する方法を説明するツール使用デザインパターンについて説明します。
このレッスンでは、次の問いに答えます:
このレッスンを終えた後、あなたは次のことができるようになります:
ツール使用デザインパターンは、大規模言語モデル(LLM)が特定の目標達成のために外部ツールと連携できる能力を与えることに焦点を当てています。ツールはエージェントが動作を実行するために実行できるコードです。ツールは計算機のような単純な関数でも、株価の照会や天気予報などのサードパーティサービスへのAPI呼び出しでもかまいません。AIエージェントの文脈では、ツールはモデル生成の関数呼び出しに応答してエージェントが実行するよう設計されています。
AIエージェントはツールを活用して複雑なタスクを完了したり、情報を取得したり、意思決定を行うことができます。ツール使用デザインパターンは、データベース、ウェブサービス、コードインタープリターなどの外部システムとの動的なやり取りが必要なシナリオでよく使われます。以下のようなユースケースで役立ちます:
これらの構成要素により、AIエージェントは多様なタスクを実行可能になります。ツール使用デザインパターンを実装するための主要な要素を見てみましょう:
関数/ツールスキーマ: 利用可能なツールの詳細定義。関数名、目的、必要なパラメーター、期待される出力など。これらのスキーマにより、LLMはどのツールが使用可能で、有効なリクエストをどのように構築するかを理解できます。
関数実行ロジック: ユーザーの意図や会話コンテキストに基づいてツールの呼び出しを管理。プランナーモジュール、ルーティング機構、条件分岐などで動的にツール使用を決定。
メッセージ処理システム: ユーザー入力、LLMレスポンス、ツール呼び出しおよびツール出力間の会話フローを管理。
ツール統合フレームワーク: 単純な関数や複雑な外部サービスなど、様々なツールをエージェントに接続するインフラ。
エラー処理と検証: ツール実行時の失敗処理、パラメーターの検証、予期せぬ応答の管理メカニズム。
状態管理: 会話コンテキストや過去のツール使用情報、永続データを追跡し、複数ターンのやり取りにおける一貫性を保証。
次に、関数/ツール呼び出しについて詳しく見ていきましょう。
関数呼び出しは、大規模言語モデル(LLM)がツールと連携する主な方法です。『関数』と『ツール』はよく同義で使われますが、『関数』は再利用可能なコードのブロックであり、エージェントがタスクを実行するための『ツール』です。関数のコードを呼び出すには、LLMがユーザーの要求を関数の説明と比較しなければなりません。そのため、利用可能な関数の説明を含むスキーマをLLMに送ります。LLMはその中から最適な関数を選び、その名前と引数を返します。選択された関数が呼び出され、その応答がLLMに返され、LLMはそれを使ってユーザーの要求に応答します。
開発者がエージェント用の関数呼び出しを実装するには、以下が必要です:
例として都市の現在時刻取得を使って説明しましょう:
関数呼び出しをサポートするLLMを初期化する:
すべてのモデルが関数呼び出しをサポートしているわけではないため、使用中のLLMがサポートしているか確認が必要です。Azure OpenAI は関数呼び出しをサポートします。まずAzure OpenAIのResponses API(安定版の /openai/v1/ エンドポイントで api_version は不要)に対するOpenAIクライアントを起動します。
# Azure OpenAI(Responses API、v1エンドポイント)用のOpenAIクライアントを初期化する
client = OpenAI(
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
api_key=os.environ["AZURE_OPENAI_API_KEY"],
)
deployment_name = os.environ["AZURE_OPENAI_DEPLOYMENT"]
関数スキーマを作成する:
続いて、関数名、関数の動作説明、パラメーター名と説明を含むJSONスキーマを定義します。 このスキーマをさきほど作成したクライアントに渡し、ユーザーの「サンフランシスコの時刻を取得する」リクエストとともに送ります。重要なのは、返されるのはツール呼び出しであり、質問の最終答えではありません。前述のように、LLMはタスクに最適な関数名と引数を返します。
# モデルが読み取るための関数説明(Responses APIフラットツール形式)
tools = [
{
"type": "function",
"name": "get_current_time",
"description": "Get the current time in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city name, e.g. San Francisco",
},
},
"required": ["location"],
},
}
]
# 初期ユーザーメッセージ
messages = [{"role": "user", "content": "What's the current time in San Francisco"}]
# 最初のAPI呼び出し: モデルに関数を使うように依頼する
response = client.responses.create(
model=deployment_name,
input=messages,
tools=tools,
tool_choice="auto",
store=False,
)
# Responses APIはfunction_call項目としてのツール呼び出しをresponse.outputに返します。
# 次のターンでモデルが完全なコンテキストを持つように、それらを会話に追加します。
messages += response.output
print("Model's response:")
print(response.output)
Model's response:
[ResponseFunctionToolCall(arguments='{"location":"San Francisco"}', call_id='call_pOsKdUlqvdyttYB67MOj434b', name='get_current_time', type='function_call')]
タスクを実行するための関数コード:
LLMが実行すべき関数を選択したので、そのタスクを実装し実行するコードが必要です。
Pythonで現在時刻を取得するコードを実装し、さらに結果を得るために response_message から名前と引数を抽出するコードも書きます。
def get_current_time(location):
"""Get the current time for a given location"""
print(f"get_current_time called with location: {location}")
location_lower = location.lower()
for key, timezone in TIMEZONE_DATA.items():
if key in location_lower:
print(f"Timezone found for {key}")
current_time = datetime.now(ZoneInfo(timezone)).strftime("%I:%M %p")
return json.dumps({
"location": location,
"current_time": current_time
})
print(f"No timezone data found for {location_lower}")
return json.dumps({"location": location, "current_time": "unknown"})
# 関数呼び出しを処理する
tool_calls = [item for item in response.output if item.type == "function_call"]
if tool_calls:
for tool_call in tool_calls:
if tool_call.name == "get_current_time":
function_args = json.loads(tool_call.arguments)
time_response = get_current_time(
location=function_args.get("location")
)
# ツールの結果をfunction_call_output項目として返す
messages.append({
"type": "function_call_output",
"call_id": tool_call.call_id,
"output": time_response,
})
else:
print("No tool calls were made by the model.")
# 2回目のAPI呼び出し:モデルから最終応答を取得する
final_response = client.responses.create(
model=deployment_name,
input=messages,
tools=tools,
store=False,
)
return final_response.output_text
get_current_time called with location: San Francisco
Timezone found for san francisco
The current time in San Francisco is 09:24 AM.
関数呼び出しは多くのエージェントツール使用デザインの中心となりますが、ゼロから実装するのは時に困難です。 Lesson 2で学んだように、エージェントフレームワークはツール使用のための構成要素を予め提供してくれます。
ここでは異なるエージェントフレームワークでツール使用デザインパターンを実装する例を示します:
Microsoft Agent Framework はAIエージェント構築のためのオープンソースフレームワークです。@toolデコレータでPython関数としてツールを定義可能にし、関数呼び出しの処理を簡素化しています。モデルとコード間の通信を処理し、FoundryChatClientを介してファイル検索やコードインタープリターなどの予め組み込まれたツールにもアクセスできます。
次の図はMicrosoft Agent Frameworkにおける関数呼び出しの流れを示しています:

Microsoft Agent Frameworkでは、デコレータ付き関数としてツールを定義します。先ほどの get_current_time 関数を @tool デコレータでツールに変換し、関数とパラメーターのシリアライズやスキーマ作成をフレームワークが自動処理します。
import os
from agent_framework import tool
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
@tool(approval_mode="never_require")
def get_current_time(location: str) -> str:
"""Get the current time for a given location"""
...
# クライアントを作成する
provider = FoundryChatClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
# エージェントを作成し、ツールで実行する
agent = provider.as_agent(name="TimeAgent", instructions="Use available tools to answer questions.", tools=get_current_time)
response = await agent.run("What time is it?")
Microsoft Foundry Agent Service は、開発者が基盤の計算・ストレージ管理なしに安全に高品質で拡張可能なAIエージェントを構築・配備・スケールできるよう設計された新しいエージェントフレームワークです。特に企業向けに設計された完全マネージドサービスであり、企業レベルのセキュリティを備えています。
LLM APIを直接使って開発する場合と比べ、Microsoft Foundry Agent Serviceは以下の利点があります:
Microsoft Foundry Agent Serviceで利用可能なツールは以下の2種類に分けられます:
Agent Serviceではこれらのツールを toolset としてまとめて利用でき、特定の会話履歴を管理する threads も利用します。
例えば、Contosoという会社の営業担当者だと想像してください。営業データに関する質問に答えられる会話型エージェントを開発したいと考えています。
次の図はMicrosoft Foundry Agent Serviceを利用して営業データを分析する例です:

これらのツールをサービスで使うにはクライアントを作成し、ツールやツールセットを定義します。実際の実装例として以下のPythonコードがあります。LLMはツールセットを見て、ユーザーの要求に応じてユーザー定義の関数 fetch_sales_data_using_sqlite_query か、組み込みのコードインタープリターのどちらかを選択します。
import os
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from fetch_sales_data_functions import fetch_sales_data_using_sqlite_query # fetch_sales_data_functions.py ファイルにある fetch_sales_data_using_sqlite_query 関数。
from azure.ai.projects.models import ToolSet, FunctionTool, CodeInterpreterTool
project_client = AIProjectClient.from_connection_string(
credential=DefaultAzureCredential(),
conn_str=os.environ["PROJECT_CONNECTION_STRING"],
)
# ツールセットを初期化する
toolset = ToolSet()
# fetch_sales_data_using_sqlite_query 関数を使用して関数呼び出しエージェントを初期化し、ツールセットに追加する
fetch_data_function = FunctionTool(fetch_sales_data_using_sqlite_query)
toolset.add(fetch_data_function)
# Code Interpreter ツールを初期化し、ツールセットに追加する。
code_interpreter = CodeInterpreterTool()toolset.add(code_interpreter)
agent = project_client.agents.create_agent(
model="gpt-5-mini", name="my-agent", instructions="You are helpful agent",
toolset=toolset
)
LLMによって動的に生成されるSQLに関する一般的な懸念事項はセキュリティです。特にSQLインジェクションやデータベースの削除・改ざんといった悪質な行為のリスクが挙げられます。これらの懸念は、データベースアクセス権限を適切に設定することで効果的に軽減可能です。多くのデータベースでは読み取り専用(Read-only)として設定し、PostgreSQLやAzure SQLのようなデータベースサービスではアプリに読み取り専用(SELECT)権限を割り当てます。
アプリをセキュアな環境で実行すればさらに保護が強化されます。企業環境では、運用システムから抽出・変換されたデータをユーザーフレンドリーなスキーマの読み取り専用データベースやデータウェアハウスに保管することが一般的です。このアプローチによりデータは安全に保たれ、性能とアクセシビリティの最適化がなされ、アプリのアクセスは制限された読み取り専用にとどまります。
他の学習者と出会い、オフィスアワーに参加し、AIエージェントに関する質問を解決するためにMicrosoft Foundry Discordに参加しましょう。
Lesson 16でエージェントのデプロイ方法を学んだ後、このレッスンのTravelToolAgentがまだツールを呼び出し回答しているかをtests/lesson-04-smoke-tests.jsonでスモークテストできます。実行方法はtests/README.mdを参照してください。
Understanding Agentic Design Patterns
免責事項: 本書類は AI 翻訳サービス Co-op Translator を使用して翻訳されています。正確性を期していますが、自動翻訳には誤りや不正確な部分が含まれる可能性があることをご承知おきください。原文の原語版が正式な情報源とみなされるべきです。重要な情報については、専門の人間による翻訳を推奨します。本翻訳の利用により生じたいかなる誤解や解釈違いについても、当方は責任を負いかねます。