🔧 Phase 5: エージェントにツールを追加する
所要時間
約20分
🎯 学習目標
- 関数定義を使って tools を作成する
- エージェントに tools を追加する
- UI で Tool calling (ツール呼び出し) を処理する
- チャットでリアルタイムの天気データを確認する
🤔 ツール呼び出しとは?
ツール呼び出し(Tool Calling)とは、エージェントが外部の関数や API を呼び出して情報を取得する仕組みです。以下のフローで動作します:
ユーザーが質問する
↓
エージェントが考える(ツールが必要か判断)
↓
エージェントが行動する(ツールを呼び出す)
↓
ツールが結果を返す
↓
エージェントが応答する(結果を自然言語にまとめる)
例えば、「東京の天気は?」と聞くと、エージェントは get_weather ツールを呼び出し、リアルタイムの天気データを取得して回答します。
📁 ステップ 1: プロジェクトフォルダの作成
phase-03 ディレクトリにいる場合は、cd .. でワークスペースのルートに戻りましょう。
そして以下のコマンドを実行します。
mkdir -p phase-05
cd phase-05
touch app.py tools.py
mkdir phase-05
cd phase-05
New-Item app.py, tools.py
ここまでの手順が完了すると、プロジェクトは以下の構成になります。
cogbot-handson/
├── .venv/
├── phase-02/
├── phase-03/
├── phase-04/
├── phase-05/
| └── app.py
| └── tools.py
├── .env
├── .gitignore
└── requirements.txt
🔑 ステップ 2: Weather API キーを取得する
ヒント
weatherapi.com で無料アカウントを作成し、API キーを取得してください。無料プランで十分です。
.env ファイルに以下を追加します:
WEATHER_API_KEY=your_api_key_here
🛠️ ステップ 3: tools.py の作成
天気情報を取得するツール関数を作成します。
import os
from typing import Annotated
import httpx
from pydantic import Field
def get_weather(
city: Annotated[str, Field(description="The name of the city (e.g., 'London', 'Tokyo')")]
) -> str:
"""
指定された都市の現在の天気を取得します。
気温、天候、湿度などの情報を返します。
"""
api_key = os.getenv("WEATHER_API_KEY")
if not api_key:
return "エラー: .env に WEATHER_API_KEY が設定されていません"
try:
response = httpx.get(
"http://api.weatherapi.com/v1/current.json",
params={"key": api_key, "q": city},
timeout=10.0
)
response.raise_for_status()
data = response.json()
location = data["location"]["name"]
country = data["location"]["country"]
temp_c = data["current"]["temp_c"]
condition = data["current"]["condition"]["text"]
humidity = data["current"]["humidity"]
return f"""Weather for {location}, {country}:
🌡️ Temperature: {temp_c}°C
☁️ Condition: {condition}
💧 Humidity: {humidity}%"""
except httpx.HTTPStatusError:
return f"'{city}' の天気情報を取得できませんでした"
except Exception as e:
return f"エラー: {e}"
# エージェントに渡すツールのリスト
TOOLS = [get_weather]
ツール定義の解説
| 要素 | 目的 |
|---|---|
Annotated[str, Field(...)] |
LLM にパラメータを説明 |
| ドキュメント文字列 | LLM がツールの使用タイミングを判断するために読む |
| 戻り値の文字列 | エージェントが受け取る結果 |
🚀 ステップ 4: app.py の作成
ツール呼び出しに対応した Agent を作成します。
import os
from datetime import date
import chainlit as cl
from dotenv import load_dotenv
from agent_framework import Agent
from agent_framework.azure import AzureOpenAIChatClient
from tools import TOOLS
load_dotenv(override=True)
SYSTEM_PROMPT = f"""あなたは、Aria という名の役に立つ AI アシスタントです。
あなたのできること:
- あらゆるトピックに関する質問に答える
- コーディングや技術的な問題のサポート
- 説明と分析を提供する
- フレンドリーで会話的な態度
ガイドライン:
- 簡潔かつ綿密に伝える
- わからないことがあれば認める
- 必要に応じて、より明確な質問をする
現在時刻: {date.today().strftime("%B %d, %Y")}
"""
def get_chat_client():
"""Azure OpenAI Chat クライアント"""
return AzureOpenAIChatClient(
endpoint=os.environ["MSF_MODEL_ENDPOINT"],
api_key=os.environ["MSF_MODEL_API_KEY"],
api_version=os.environ["MSF_MODEL_API_VERSION"],
deployment_name=os.environ["MSF_MODEL_DEPLOYMENT_NAME"]
)
def create_agent():
"""設定済みの Agent を作成"""
chat_client = get_chat_client()
# Agent を作成
agent: Agent = chat_client.as_agent(
name="Aria",
description="天気予報の機能を備えた便利なAIアシスタント",
instructions=SYSTEM_PROMPT,
tools=TOOLS, # ツールを追加
default_options={
# "temperature": 0.7,
"max_completion_tokens": 3000,
"reasoning_effort": "none" # reasoning model で指定可能
}
)
return agent
@cl.on_chat_start
async def start():
"""チャットセッションを初期化"""
agent = create_agent()
session = agent.create_session()
cl.user_session.set("agent", agent)
cl.user_session.set("session", session)
await cl.Message(content="👋 こんにちは🌞Aria です。天気を調べられます!例えば「パリの天気は?」と聞いてみてください!").send()
@cl.on_message
async def main(message: cl.Message):
agent: Agent = cl.user_session.get("agent")
session = cl.user_session.get("session")
msg = cl.Message(content="")
stream = agent.run(message.content, stream=True, session=session)
async for update in stream:
if update.text:
await msg.stream_token(update.text)
await stream.get_final_response() # 会話履歴を session に自動保存
await msg.send()
🔑 主な変更点
tools.py: 天気予報の API を呼ぶ関数の実装tools=TOOLS: Agent の初期化時に tools を指定
▶️ ステップ 5: 実行とテスト
アプリを起動します:
chainlit run app.py -w
「東京の天気は?」「パリとシアトルの天気は」などの質問で tools を使って正しく回答できるか確認しましょう。
🚀 ステップ 6: Tool calling の状況をストリーミング
Tool calling の開始時と結果を出力するよう Agent を更新します。コードの全体は以下になります。
import os
from datetime import date
import chainlit as cl
from dotenv import load_dotenv
from agent_framework import Agent, Content
from agent_framework.azure import AzureOpenAIChatClient
from tools import TOOLS
load_dotenv(override=True)
SYSTEM_PROMPT = f"""あなたは、Aria という名の役に立つ AI アシスタントです。
あなたのできること:
- あらゆるトピックに関する質問に答える
- コーディングや技術的な問題のサポート
- 説明と分析を提供する
- フレンドリーで会話的な態度
ガイドライン:
- 簡潔かつ綿密に伝える
- わからないことがあれば認める
- 必要に応じて、より明確な質問をする
現在時刻: {date.today().strftime("%B %d, %Y")}
"""
def get_chat_client():
"""Azure OpenAI Chat クライアント"""
return AzureOpenAIChatClient(
endpoint=os.environ["MSF_MODEL_ENDPOINT"],
api_key=os.environ["MSF_MODEL_API_KEY"],
api_version=os.environ["MSF_MODEL_API_VERSION"],
deployment_name=os.environ["MSF_MODEL_DEPLOYMENT_NAME"]
)
def create_agent():
"""設定済みの Agent を作成"""
chat_client = get_chat_client()
# Agent を作成
agent: Agent = chat_client.as_agent(
name="Aria",
description="天気予報の機能を備えた便利なAIアシスタント",
instructions=SYSTEM_PROMPT,
tools=TOOLS, # ツールを追加
default_options={
# "temperature": 0.7,
"max_completion_tokens": 3000,
"reasoning_effort": "none" # reasoning model で指定可能
}
)
return agent
@cl.on_chat_start
async def start():
"""チャットセッションを初期化"""
agent = create_agent()
session = agent.create_session()
cl.user_session.set("agent", agent)
cl.user_session.set("session", session)
await cl.Message(content="👋 こんにちは🌞Aria です。天気を調べられます!例えば「パリの天気は?」と聞いてみてください!").send()
@cl.on_message
async def main(message: cl.Message):
agent: Agent = cl.user_session.get("agent")
session = cl.user_session.get("session")
msg = cl.Message(content="")
tool_steps = {}
stream = agent.run(message.content, stream=True, session=session)
async for update in stream:
# Tool calling のストリーミング
if update.contents:
for content in update.contents:
# Tool calling 開始時
if isinstance(content, Content) and content.type == "function_call":
if content.name and content.call_id not in tool_steps:
tool_steps[content.call_id] = cl.Message(content=f"🔧 ツールを呼び出し中: {content.name}...")
step = cl.Step(
name=f"🔧 {content.name}",
type="tool"
)
await step.send()
tool_steps[content.call_id] = step
# Tool calling 後の結果
elif isinstance(content, Content) and content.type == "function_result":
if content.call_id in tool_steps:
step = tool_steps[content.call_id]
step.output = str(content.result)
await step.update()
# テキストの応答のストリーミング
if update.text:
await msg.stream_token(update.text)
await stream.get_final_response() # 会話履歴を session に自動保存
await msg.send()
🔑 主な変更点
if isinstance(content, Content) and content.type == "function_call":: tool calling 開始時のチェックelif isinstance(content, Content) and content.type == "function_result":: tool calling の結果をチェックcontent.call_id: tool step 追跡用の一意な識別子tool_steps:call_idをキーにステップ参照を保存
▶️ ステップ 6: 実行とテスト
アプリを起動していない場合は起動します:
chainlit run app.py -w
テストシナリオ
テスト 1: 天気(ツール使用)
あなた: ロンドンの天気は?
[ツールステップ表示]
Aria: ロンドンの天気は7°Cで曇りです...
テスト 2: 天気以外(ツールなし)
あなた: Pythonとは?
Aria: Python はプログラミング言語で...
(ツール呼び出しなし)
テスト 3: 複数都市
あなた: 東京とシドニーの天気を比較して
[2つのツール呼び出し]
Aria: 東京は8°C、シドニーは22°C...
📄 完成コード
完成コード: tools.py(クリックで展開)
"""
Phase 5: Tool Definitions
Tools that can be called by the agent.
Key Concepts:
- Function-based tools with type annotations
- Annotated parameters with Field descriptions
- Docstrings are used by the LLM to understand when to use tools
"""
import os
from typing import Annotated
import httpx
from pydantic import Field
def get_weather(
city: Annotated[str, Field(description="The name of the city (e.g., 'London', 'Tokyo')")]
) -> str:
"""
Get the current weather for a city.
Returns current weather conditions including temperature, condition, and humidity.
Use this tool when the user asks about weather in a specific location.
"""
api_key = os.getenv("WEATHER_API_KEY")
if not api_key:
return "Error: WEATHER_API_KEY not set in .env"
try:
response = httpx.get(
"http://api.weatherapi.com/v1/current.json",
params={"key": api_key, "q": city},
timeout=10.0
)
response.raise_for_status()
data = response.json()
location = data["location"]["name"]
country = data["location"]["country"]
temp_c = data["current"]["temp_c"]
temp_f = data["current"]["temp_f"]
condition = data["current"]["condition"]["text"]
humidity = data["current"]["humidity"]
wind_kph = data["current"]["wind_kph"]
return f"""Weather for {location}, {country}:
🌡️ Temperature: {temp_c}°C ({temp_f}°F)
☁️ Condition: {condition}
💧 Humidity: {humidity}%
💨 Wind: {wind_kph} km/h"""
except httpx.HTTPStatusError as e:
if e.response.status_code == 400:
return f"Could not find weather for '{city}'. Please check the city name."
return f"Weather API error: {e.response.status_code}"
except httpx.TimeoutException:
return "Weather API timed out. Please try again."
except Exception as e:
return f"Error fetching weather: {e}"
# List of tools to give to the agent
TOOLS = [get_weather]
完成コード: app.py(クリックで展開)
"""
フェーズ 5: Tool callを備えたエージェント
実行方法: chainlit run app.py -w
このフェーズでは、エージェントに tools を追加し、
外部 API からリアルタイムデータを取得できるようにします。
主要概念:
- Agent へのツール追加
- Tool call のフロー(思考 → 実行 → 観察)
- Chainlit UI でのツールステップ表示
- LLM の知識と外部データの組み合わせ
前提条件:
- フェーズ 4 の完了
- .env に WEATHER_API_KEY を設定済みであること
"""
import os
from datetime import date
import chainlit as cl
from dotenv import load_dotenv
from agent_framework import Agent, Content
from agent_framework.azure import AzureOpenAIChatClient
from tools import TOOLS
load_dotenv(override=True)
SYSTEM_PROMPT = f"""あなたは、Aria という名の役に立つ AI アシスタントです。
あなたのできること:
- あらゆるトピックに関する質問に答える
- コーディングや技術的な問題のサポート
- 説明と分析を提供する
- フレンドリーで会話的な態度
ガイドライン:
- 簡潔かつ綿密に伝える
- わからないことがあれば認める
- 必要に応じて、より明確な質問をする
現在時刻: {date.today().strftime("%B %d, %Y")}
"""
def get_chat_client():
"""Azure OpenAI Chat クライアント"""
return AzureOpenAIChatClient(
endpoint=os.environ["MSF_MODEL_ENDPOINT"],
api_key=os.environ["MSF_MODEL_API_KEY"],
api_version=os.environ["MSF_MODEL_API_VERSION"],
deployment_name=os.environ["MSF_MODEL_DEPLOYMENT_NAME"]
)
def create_agent():
"""設定済みの Agent を作成"""
chat_client = get_chat_client()
# Agent を作成
agent: Agent = chat_client.as_agent(
name="Aria",
description="天気予報の機能を備えた便利なAIアシスタント",
instructions=SYSTEM_PROMPT,
tools=TOOLS, # ツールを追加
default_options={
# "temperature": 0.7,
"max_completion_tokens": 3000,
"reasoning_effort": "none" # reasoning model で指定可能
}
)
return agent
@cl.on_chat_start
async def start():
"""チャットセッションを初期化"""
agent = create_agent()
session = agent.create_session()
cl.user_session.set("agent", agent)
cl.user_session.set("session", session)
await cl.Message(content="👋 こんにちは🌞Aria です。天気を調べられます!例えば「パリの天気は?」と聞いてみてください!").send()
@cl.on_message
async def main(message: cl.Message):
agent: Agent = cl.user_session.get("agent")
session = cl.user_session.get("session")
msg = cl.Message(content="")
tool_steps = {}
stream = agent.run(message.content, stream=True, session=session)
async for update in stream:
# Tool call のストリーミング
if update.contents:
for content in update.contents:
# Tool call 開始時
if isinstance(content, Content) and content.type == "function_call":
if content.name and content.call_id not in tool_steps:
tool_steps[content.call_id] = cl.Message(content=f"🔧 ツールを呼び出し中: {content.name}...")
step = cl.Step(
name=f"🔧 {content.name}",
type="tool"
)
await step.send()
tool_steps[content.call_id] = step
# Tool call 後の結果
elif isinstance(content, Content) and content.type == "function_result":
if content.call_id in tool_steps:
step = tool_steps[content.call_id]
step.output = str(content.result)
await step.update()
# テキストの応答のストリーミング
if update.text:
await msg.stream_token(update.text)
await stream.get_final_response() # 会話履歴を session に自動保存
await msg.send()
💡 ポイント
- ツールは普通の Python 関数: 型ヒントとドキュメント文字列で LLM が使い方を理解します
- エージェントが判断する: どのツールをいつ使うかはエージェントが自動的に決定します
call_id: 複数のツール呼び出しを正しく追跡するための識別子です
✅ チェックポイント
以下を確認してください:
- [x]
tools.pyにget_weather関数が定義されている - [x]
app.pyでエージェントにツールが追加されている - [x] 天気の質問でツールが呼び出される
- [x] 天気以外の質問にはツールなしで回答される
- [x] 複数都市の天気を比較できる
- [x] UI にツールステップが表示される
❗ よくある問題
| 問題 | 解決方法 |
|---|---|
WEATHER_API_KEY エラー |
.env ファイルに正しい API キーが設定されているか確認 |
| 天気データが取得できない | API キーが有効か、都市名が正しいか確認 |
| ツールが呼び出されない | システムプロンプトにツールの説明が含まれているか確認 |
ImportError |
pip install httpx pydantic で必要なパッケージをインストール |
次のステップ: Phase 6: MCP 連携