「AIエージェントを作ってみたいけど、LangChainとかLangGraphって難しそう……」
そう思っていませんか? 実は、AIエージェントの本質は驚くほどシンプルです。
本記事では、特定のライブラリを一切使わず、OpenAIのAPIとPythonだけで「最小構成のAIエージェント」を実装し、その仕組みを丸裸にします。
1. AIエージェントとは?
AIエージェントとは、LLM(大規模言語モデル)を「脳」として使い、自律的に「考えて行動(ツール実行)」 するシステムのことです。
単なるチャット(ChatGPT)との違いは、AIが自分自身の知識だけで答えるのではなく、「外の世界にある道具(天気予報、計算機、Google検索など)」を自分の判断で使いこなす点にあります。
- チャット: 「今日の東京の天気は?」 → 「学習データによると……(古い情報の可能性)」
- エージェント: 「今日の東京の天気は?」 → 「(ツールを使って最新情報を調べよう)……晴れですね!」
2. AIエージェントはどのように動いているのか?(ReAct)
AIエージェントの動きの基本は、「ReAct(Reasoning + Acting)」 と呼ばれるモデルです。
- Reasoning(思考): ユーザーの依頼を見て、「何が必要か」を考える。
- Acting(行動): 必要な道具(ツール)を呼び出す。
- Observation(観察): 道具の実行結果を受け取り、状況を確認する。
- Thinking Again(再考): 結果を元に、さらに行動が必要か、回答できるかを判断する。
このサイクルを、回答が出るまで繰り返します。
3. 実装してみる:世界一シンプルなAIエージェント
「天気予報を教えるツール」を持つ最小のエージェントを書いてみます。
import os
import json
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY")
# --- ツール実装 ---
def get_weather(location):
return f"{location}の天気は『晴れ』、気温は25度です。"
# --- ツールレジストリ ---
tool_functions = {
"get_weather": get_weather
}
# --- OpenAIに渡すツール定義 ---
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された場所の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}]
messages = [{"role": "user", "content": "東京の天気を教えて"}]
for _ in range(5):
response = client.chat.completions.create(
model="gpt-5-mini-2025-08-07",
messages=messages,
tools=tools
)
res_msg = response.choices[0].message
messages.append(res_msg)
if not res_msg.tool_calls:
break
# --- ツール実行 ---
for tool_call in res_msg.tool_calls:
function_name = tool_call.function.name
# AIが呼び出してほしいツールの関数名を取り出す
arguments = json.loads(tool_call.function.arguments)
# 続いて引数も取り出す
# 対応する関数を取得
function_to_call = tool_functions[function_name]
# 実行
result = function_to_call(**arguments)
# AIに結果を返す
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
print("最終回答:", messages[-1].content)
4. 実際の動きの流れ
実際に weather_agent.py を実行した際の、リアルなデータのやり取りを追ってみましょう。AIエージェントが「思考」し、「行動」し、その結果を元に「回答」するまでの全記録です。
STEP 1: 初回の思考(Loop 1)
まず、ユーザーの問いかけと「ツールの定義」が OpenAIのAPI に送られます。
送信データ (Request Messages):
[
{ "role": "user", "content": "東京の天気を教えて" }
]
送信されるツール定義
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された場所の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"]
}
}
}]
その後、下記のAPIレスポンスがOpenAIから返ってきます。
AI からの返答 (Response):
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_bTQc4vyuTlvxW4D9ZIdkRHH1",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"東京\"}"
}
}
]
}
このレスポンスでは、AIは通常の回答文(content)を返していません。
その代わりに、tool_calls を使って 「ツール(関数)を実行してほしい」という指示を返しています。
具体的には、
get_weather という関数を
location = 東京 という引数で
実行してほしい、という意味です。
つまりAIは、この時点ではまだ最終回答を出しておらず、
「東京の天気を取得するために get_weather を実行してください」
と、プログラム側に依頼している状態です。
この指示を受け取ったプログラムは、get_weather("東京") を実行し、その結果を再びAIに渡すことで、最終的な回答を生成します。
STEP 2: ツールの実行と結果の報告
AIからのレスポンスに tool_calls が含まれている場合、
それは 「このツール(関数)を実行してほしい」 というAIからの指示です。
プログラム側では、この tool_calls の内容を読み取り、
指定された関数を実行し、その結果を 次のリクエストの会話履歴(messages)に追加 します。
以下のコードでは、
-
tool_callsが存在するか確認 - AIが指定した関数名と引数を取り出す
- 対応する関数を実行
- 実行結果を
messagesに追加
という流れで処理を行っています。
# OpenAIのレスポンスにtool_callsが含まれるかチェック
if not res_msg.tool_calls:
break
# --- ツール実行 ---
for tool_call in res_msg.tool_calls:
# AIが呼び出してほしい関数名を取得
function_name = tool_call.function.name
# 引数(JSON文字列)をPythonの辞書に変換
arguments = json.loads(tool_call.function.arguments)
# 対応する関数を取得
function_to_call = tool_functions[function_name]
# 関数を実行
result = function_to_call(**arguments)
# 実行結果をAIに返す
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
関数呼び出しの仕組み
上記のコードの次の部分では、
AIが指定した関数名をもとに、実際のPython関数を取得して実行しています。
# 対応する関数を取得
function_to_call = tool_functions[function_name]
# 実行
result = function_to_call(**arguments)
ここで参照している tool_functions は、
ツールとして利用できる関数を登録した辞書(ツールレジストリ) です。
def get_weather(location):
return f"{location}の天気は『晴れ』、気温は25度です。"
# --- ツールレジストリ ---
tool_functions = {
"get_weather": get_weather
}
この仕組みにより、AIが
get_weather(location="東京")
Python側では自動的に get_weather 関数が実行されます。
ツール実行結果の保存
get_weather の実行が終わると、その結果は
OpenAIとの会話履歴(messages)に「ツールの実行結果」として追加 されます。
追加されるデータは次のような形式になります。
{
"role": "tool",
"tool_call_id": "call_bTQc4vyuTlvxW4D9ZIdkRHH1",
"name": "get_weather",
"content": "東京の天気は『晴れ』、気温は25度です。"
}
ここが重要
tool_call_id を付けることで、
「さっきAIが依頼したツール実行(call_bTQc4vyu...)の結果はこれです」
という形で、AIのリクエストとツールの実行結果を正しく紐付けることができます。
これによりAIはツールの結果を理解し、
その情報をもとに 最終的な回答を生成できるようになります。
STEP 3: 最終的な判断(Loop 2)
ここまでで、会話履歴には次の3つの情報が揃いました。
- ユーザーの質問
- AIが依頼したツール実行
- ツールの実行結果
この すべての履歴(messages) を、もう一度 OpenAI に送ります。
送信データ(Request Messages
[
{ "role": "user", "content": "東京の天気を教えて" },
{ "role": "assistant", "tool_calls": [...] },
{ "role": "tool", "content": "東京の天気は『晴れ』..." }
]
この情報を受け取ったAIは、
-
ユーザーの質問
-
自分が依頼したツール実行
-
そのツールの実行結果
をすべて確認し、
「もう回答できるだけの情報が揃っているか?」 を判断します。
もし 情報がまだ足りない と判断した場合は、
再び tool_calls を返して、別のツールを呼び出すよう指示します。
今回は、ツールの結果だけで十分だと判断されたため、
AIは次のような最終回答を返してきました。
AIからの最終返答(Response)
{
"role": "assistant",
"content": "東京の天気は晴れで、気温は25度です。"
}
このレスポンスでは tool_calls が存在せず、
content に 人間への回答文 が入っています。
つまりAIは、
「必要な情報はすべて揃った」
と判断し、
ツールの結果をもとに 最終的な回答を生成した ということです。
実行結果のまとめ
最終的にターミナルには、次のような結果が表示されます。
=== Final Answer ===
東京の天気は晴れで、気温は25度です。
この一連の流れを見ると、AIは次のステップで問題を解決していることが分かります。
-
ユーザーの質問を理解する
-
必要な情報を取得するためにツールを呼び出す
-
ツールの結果を確認する
-
最終的な回答を生成する
つまり、AIは単に文章を生成しているだけではなく、
ツール(外部機能)を使いながら問題を解決するエージェントとして動いている のです。
5. LangGraph やほかのフレームワークはどう動いている?
有名なLangGraphやそのほかのフレームワークも、
根本的な仕組みは ここまで見てきたコードとほぼ同じ です。
では、これらのフレームワークは何をしてくれるのでしょうか?
-
処理の自動化
今回はforループで AI → ツール → AI の流れを自分で実装しましたが、フレームワークではこの処理を自動で回してくれます。 -
ワークフロー管理
「Aの処理のあとにBを実行する」「失敗したら別の処理へ分岐する」といった流れを、コードやグラフ構造で分かりやすく定義できます。 -
状態管理
長い会話の履歴やエージェントの状態を保存し、途中で処理を止めたり、後から再開するような仕組みも簡単に扱えます。
つまりフレームワークは、
今回作ったシンプルなエージェントのループを、より大規模で複雑な用途でも扱えるように整理・拡張したものと言えます。
ですが、最後に大事なポイントとして、
フレームワークを使っても 基本的な仕組みは変わりません。
AIがツールを呼び出し、ツールの結果を受け取り、再度判断して回答を生成する。
この一連の流れを 頭の中にイメージしておくことが、AIエージェントを理解する一番の近道です。
6. ツールとMCPの関係
ここまでの例では、AIがツールを呼び出す仕組みとして
get_weather というPythonの関数を登録し、AIの指示に従ってプログラム側で実行しました。
流れを整理すると、次のようになっていました。
- ユーザーが質問する
- AIが「ツールを使うべきか」を判断する
- ツール(関数)を呼び出す
- ツールの結果をAIに返す
- AIが最終回答を生成する
この 「AI → ツール → AI」 の流れが、AIエージェントの基本構造です。
しかし実際のアプリケーションでは、ツールはPythonの関数だけではありません。
たとえば次のような外部サービスと連携することも多くなります。
- データベース
- 社内API
- GitHub
- Slack
- ファイルシステム
もしツールごとに接続方法が違うと、AIから利用するたびに
個別の連携コードを書く必要があり、実装が非常に複雑になります。
そこで登場したのが
MCPです。
MCPは、AIが外部ツールを呼び出すための共通ルール(プロトコル) を定義しています。
ツールを MCPサーバー として公開することで、AIはさまざまなツールを同じ方法で利用できるようになります。
USBの仕組みに例えると理解しやすい
この関係は、PCの USB の仕組みによく似ています。
PCにはさまざまな機器を接続できます。
- マウス
- キーボード
- USBメモリ
- 外付けSSD
これらはすべて種類の違う機器ですが、
USBという共通規格があるおかげで、PCは同じ方法で接続できます。
AIエージェントの世界でも同じで、
- ツール → USB機器
- MCP → USB規格
のような関係になっています。
つまりMCPは、
AIと外部ツールの間に共通ルールを作り、さまざまなサービスを簡単に接続できるようにする仕組みです。
ここまで理解すると、AIエージェントの全体像は次のように整理できます。
ユーザー
↓
LLM(判断)
↓
ツール呼び出し
↓
MCP / API / 関数
↓
結果をLLMへ返す
↓
最終回答
今回作ったシンプルなエージェントの仕組みは、
実は 現在のAIエージェントフレームワークやMCPベースのツール連携の基礎そのもの になっています。
まとめ:仕組みを知れば、エージェントは怖くない
AIエージェントの本質は、
「状態(メッセージ履歴)を持ちながら、外部関数の実行結果をフィードバックするループ処理」
に過ぎません。
特定のライブラリの使い方を覚える前に、
この標準機能のみ の実装を理解することで、
AIを思い通りに制御する真のスキルが身につきます。
「脳(LLM)」に「手足(ツール)」を繋ぐ「神経(ループ)」を自分で書く。これこそがエージェント開発の基礎となるでしょう。