第3週では、ローカルLLMを使ってループ型エージェントを実装し、AIが「終わるまで考え続ける」仕組みを作りました。
これにより、AIは単発の処理ではなく、状況に応じて判断と実行を繰り返せるようになりました。
しかし実際のシステムでは、1つのAIだけですべてを判断・実行するのではなく、
役割ごとに複数のAIが分担して動く構成が一般的です。
本記事では、「司令塔」と「実行役」に分けたマルチエージェント構成を実装し、より実践的なAIエージェントの形を学びます。
AIエージェントとは
AIエージェントとは、単体で判断するだけでなく、複数の役割に分かれて連携しながら目的を達成する仕組みです。
第4週では「司令塔」と「実行役」に分けたマルチエージェント構成を実装します。
学習ロードマップ
| 週 | 内容 |
|---|---|
| 第1週 | チャット + JSON |
| 第2週 | ツール呼び出し |
| 第3週 | ループ |
| 第4週 | マルチエージェント |
作業環境について
本記事は、第1週の内容を前提として進めています。
そのため、以下の環境がすでに整っている状態を想定しています。
- ローカルLLM実行環境:Ollama
- 使用モデル:qwen3
- Python仮想環境(venv)
- ollama Pythonライブラリのインストール済み
作業環境の準備ができていない場合
第1週の記事で、環境構築からJSON出力までを解説しています。
未実施の場合は、先に第1週から進めることをおすすめします。
AIエージェント入門|ローカルLLMで学ぶチャットとJSON出力【第1週】
第4週のゴール
- AIの役割を分けて設計できる
- 複数のAIを連携させる
- より実用的な構成を理解する
なぜマルチエージェントが必要なのか?
1つのAIでは何に限界があるのか
これまでの構成では、すべてを1つのAIが担当していました。
AI → 判断 → 実行 → 繰り返し一見シンプルですが、実際の処理が増えてくると、ここに問題が出てきます。
限界①:判断が複雑になりすぎる
例えば、処理が増えると、1つのAIが、以下の「どの処理を選ぶか」の判断を毎回考える必要がでます。
- タスクを追加する
- タスクを削除する
- 優先度を変更する
- データを確認する
その結果、判断ロジックがどんどん複雑になります
限界②:役割が混ざる
1つのAIが、以下の様にすべて担当する必要があります。
- 何をするか決める
- 実行方法を考える
- 結果を評価する
その結果、「何をしているAIなのか」が曖昧になります
限界③:拡張しにくい
例えば、以下の様に機能を追加した場合、すべて1つのAIに影響する。
- 新しいツールを追加
- 新しいルールを追加
その結果、コードの修正がどんどん難しくなります
限界④:コンテキスト長の制限
LLMの制約に、コンテキスト長(コンテキストウィンドウ)の上限があります。
コンテキスト長(単位:トークン)とは、LLMが一度に扱える情報量の限界のことです。
コンテキスト長は、LLMのモデル・バージョンやシリーズによって異なります。
例えば、Qwen3-4Bでは32Kトークン・Qwen3-8Bでは128Kトークンになります。
参考:Qwen3 公式ブログ
ループ型エージェントでは、次のように会話や処理履歴を蓄積していきます。
過去の指示
↓
過去の実行結果
↓
新しい入力
↓
AIが判断しかし、履歴が増え情報量が増え、コンテキスト長の上限を超えるとLLMは処理ができなくなります。
これにより、以下の問題が生じてきます。
- 古い情報が切り捨てられる
- 文脈が欠ける
- 判断の精度が下がる
マルチエージェントでどう解決されるか
これらの問題を解決するのが、AIを役割ごとに分ける方法です。
司令塔AI(Planner) → 何をするか決める
実行AI(Worker) → 実際に処理する① 判断がシンプルになる
司令塔AIは、「何をするか」だけ考えればいい
② 役割が明確になる
司令塔 → 判断専門
実行役 → 実行専門
これにより、責任がはっきり分けることができます。
③ 拡張しやすくなる
以下の場合でも、
- 実行AIを増やす
- 新しいツールを追加
司令塔はそのまま使うことができます。
1つのAI
→ 1人で全部やる
マルチエージェント
→ チームで分担する④扱う情報を制限できる
それぞれのLLMが扱う情報量を限定することができます。
司令塔AI → 判断に必要な情報だけ持つ
実行AI → 個別の処理だけ担当これにより、以下のメリットが生じます。
- コンテキストが短くなる
- 情報の整理がしやすくなる
- 精度が安定する
マルチエージェント動作の図解

実装してみる
完成コード
Multi-agent
from ollama import chat
def add_todo(task: str) -> str:
return f"タスクを追加しました: {task}"
TOOLS = [
{
"type": "function",
"function": {
"name": "add_todo",
"description": "ToDo追加",
"parameters": {
"type": "object",
"properties": {
"task": {"type": "string"}
},
"required": ["task"]
}
}
}
]
# 司令塔AI
def planner(messages):
return chat(
model="qwen3",
messages=messages,
tools=TOOLS,
)
# 実行AI(Worker)
def worker(tool_call):
name = tool_call["function"]["name"]
args = tool_call["function"]["arguments"]
if name == "add_todo":
return add_todo(args["task"])
messages = [
{"role": "system", "content": "あなたは司令塔AIです。"},
{"role": "user", "content": "牛乳を買うタスクを追加して"}
]
response = planner(messages)
message = response["message"]
tool_calls = message.get("tool_calls", [])
if tool_calls:
for call in tool_calls:
result = worker(call)
messages.append(message)
messages.append({
"role": "tool",
"content": result
})
final = chat(
model="qwen3",
messages=messages
)
print(final["message"]["content"])
else:
print(message.get("content", ""))マルチエージェントコード説明
マルチエージェントでは、「考える役割」と「動く役割」を分けて設計します。
司令塔AI(Planner)
def planner(messages):
return chat(..., tools=TOOLS)何をするかを決める役割
ユーザーのメッセージ(messages)をもとに、
- どの処理が必要か
- どの関数を使うべきか
を判断します。
実行AI(Worker)
def worker(tool_call):
name = tool_call["function"]["name"]
args = tool_call["function"]["arguments"]
if name == "add_todo":
return add_todo(args["task"])実際に処理を行う役割
司令塔AIが決めた内容を受け取り、
- 関数(add_todo)を実行する
- 結果を返す
といった「実際の動き」を担当します。
Plannerの指示を取り出す
tool_calls = message.get("tool_calls", [])messageの中に(tool_calls)が含まれている場合は、messageリストの中身(指示書)を tool_calls に入れる。
(tool_calls)が含まれていない場合は、空のリスト [] をtool_calls に入れる。
tool_callsの中身
tool_calls =[
{
"function": {
"name": "add_todo",
"arguments": {
"task": "牛乳を買う"
}
},
"type": "function"
}
]Workerが指示を実行し会話履歴(messages)を更新
for call in tool_calls:
result = worker(call)
messages.append(message)
messages.append({
"role": "tool",
"content": result
})tool_calls(Plannerの指示リスト)から順次指示を取り出し、Workerが実行する。
Plannerの指示内容(message)とWorkerの実行結果(result)を、会話履歴(messages)に追加する。
Workerの動作報告
final = chat(
model="qwen3",
messages=messages
)
print(final["message"]["content"])Workerの動作結果を報告するために、会話履歴(messages)を、再度LLM(qwen3)にchat 関数で再度送り、返答を画面に表示する。
AIマルチエージェント処理フローまとめ
AIによるプランニング (planner)
- ユーザーの依頼(「牛乳を買うタスクを追加して」)を受け取り、AIが「どの道具(関数)を使うべきか」を判断します。
AIの回答(指示書)を受け取る
- AIの返答から「メッセージ本体(
message)」と「道具の使用指示(tool_calls)」を取り出します。
道具を使う必要があるか判定 (if tool_calls)
- AIが「道具を使いたい」と言った場合はステップ4へ進みます。
- 「道具は不要(ただの雑談など)」と判断した場合はステップ8へ飛びます。
実際の関数を実行 (worker)
for文を使って、AIからの指示(call)を一つずつ取り出し、プログラム内の関数(add_todo)を実際に動かして結果(result)を得ます。
AIの「考え」を履歴に記録 (messages.append(message))
- 「AIがこの関数を使おうとした」という過程を、会話履歴に追加します。
関数の「実行結果」を履歴に記録 (messages.append(...))
- 実際に道具を動かして得られた結果(「追加しました」という報告など)を、
tool役として履歴に追加します。
最終回答の作成と表示 (final = chat)
- 「ユーザーの依頼」「AIの判断」「実際の実行結果」が揃った履歴をAIに送り直し、ユーザーへの丁寧な完了報告を作成して表示します。
(道具不要な場合)回答をそのまま表示 (else)
- 道具を使わなかった場合は、AIが返したメッセージ(「こんにちは」など)をそのまま画面に表示します。
関数の実行
Git Bashで、保存したフォルダに移動してから実行します。
./.venv/Scripts/python.exe Multi-agent.py上記コマンドは、第1週で構築したPython仮想環境での実行例です。
Python仮想環境の構築に関しては、第1週ステップ5およびステップ6を参考にしてください。
正常に動けば、AIからの返答がターミナルに表示されます。
牛乳を買うタスクを追加しました。第4週のまとめ
この週では、AIエージェントを「役割ごとに分けて動かす」仕組みを学びました。
1. AIは分けて設計できる
AIは1つにまとめる必要はなく、役割ごとに分けて設計することができます。
2. 役割ごとに動かす
マルチエージェントでは、
- 司令塔(Planner) → 何をするか決める
- 実行役(Worker) → 実際に処理する
というように、判断と実行を分けて動かします。
3. これが実用的な構成
役割を分けることで、
- 処理がシンプルになる
- 拡張しやすくなる
- 安定した動作になる
システムとして使える形になります。
シリーズまとめ
- 第1週:
AIを呼ぶ(使えるようにする) - 第2週:
行動させる(処理を実行する) - 第3週:
考え続ける(ループする) - 第4週:
分担させる(役割を分ける)
