PythonとOpenAI APIで実践！はじめてのMCP開発入門【第19回】もはやSFじゃない！Pythonで「目」と「手」を持つAIエージェント開発、はじめの一歩

# はじめに：テキストの壁を超え、AIが世界を「認識」し「操作」する時代へ
皆さん、こんにちは！
このブログは、「PythonとOpenAI APIで実践！はじめてのモデルコンテキストプロトコル（MCP）開発入門」シリーズの第19回です。

私たちはこの壮大なシリーズを通して、AIアプリケーションを知の深淵から現実世界へと引き出してきました。パート1〜3で基礎と応用を学び、パート4ではコスト（第15回）、セキュリティ（第16回）、安定性（第17回）、そして品質（第18回）という、プロフェッショナルな開発に不可欠な要素を一つずつコンプリートしてきました。

これまでの私たちのAIは、いわば「テキストの宇宙」に住む、非常に賢い知性体でした。しかし、その世界はあくまで文字によって構成されており、現実世界の「見た目」や「操作」からは切り離されていました。

今日の第19回で、私たちはその壁を打ち破ります。

本シリーズの核であるMCP（モデルコンテキストプロトコル）、すなわち「AIに賢く文脈を伝える技術」は、新たな次元へと進化します。AIに「目」を与えて現実世界を認識させるマルチモーダルAI。そして、AIに「手」を与えて外部ツールやAPIを操作させるAIエージェント。

これらはもはや遠い未来のSFではありません。gpt-4oの登場により、すべての開発者がその入り口に立つことができるようになりました。この記事では、その具体的な第一歩を、動かせるPythonコードと共に示します。AI開発の新たなパラダイムシフトを、一緒に体感しましょう！

![はじめに：テキストの壁を超え、AIが世界を「認識」し「操作」する時代へ - visual selection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4058228/65b45961-6913-4df0-99a4-6a2765295e93.png)

# 1. AIに「目」を与える：GPT-4o Vision APIによる画像認識の実践
第6回で私たちは、テキスト情報をJSON形式で構造化し、MCPとしてAIに渡す方法を学びました。マルチモーダルAIは、このコンテキストに「画像」という新たな形式を加えるものです。

gpt-4oのVision機能を使えば、驚くほど簡単にてキストと画像を同時にコンテキストとして扱うことができます。

## どうやって画像をAPIに渡すのか？
messages配列に含めるオブジェクトのcontent部分を、テキストと画像を組み合わせた配列形式で記述します。画像はURLで指定するか、Base64形式でエンコードしたデータを直接埋め込みます。

###  Pythonコード実践例：手書きの設計図をMermaid記法に変換する
開発者にとって非常に実用的な例を見てみましょう。ホワイトボードに手書きした簡単なシステム構成図を写真に撮り、それをドキュメント化（Mermaid記法化）するタスクです。

```Python
import base64
import requests
from openai import OpenAI

client = OpenAI()

def image_to_base64(image_path: str) -> str:
    """ローカルの画像ファイルをBase64エンコードする"""
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

def analyze_whiteboard_diagram(image_path_or_url: str, prompt: str):
    """画像とプロンプトをGPT-4oに渡し、分析させる"""
    
    content_parts = [
        {
            "type": "text",
            "text": prompt
        }
    ]
    
    if image_path_or_url.startswith("http"):
        # URLの場合
        content_parts.append({
            "type": "image_url",
            "image_url": {
                "url": image_path_or_url,
            }
        })
    else:
        # ローカルファイルパスの場合
        base64_image = image_to_base64(image_path_or_url)
        content_parts.append({
            "type": "image_url",
            "image_url": {
                "url": f"data:image/jpeg;base64,{base64_image}"
            }
        })

    try:
        print("🤖 AIに画像とプロンプトを送信中...")
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {
                    "role": "user",
                    "content": content_parts
                }
            ],
            max_tokens=1000,
            temperature=0.1 # 正確性を重視
        )
        
        result_text = response.choices[0].message.content
        print("\n✅ AIからの応答:\n" + "="*50)
        print(result_text)
        return result_text

    except Exception as e:
        print(f"エラーが発生しました: {e}")
        return None

# --- 実行例 ---
# ここに手書きの図の画像パスを指定（例: 'whiteboard.jpg'）
# または画像のURLを指定
# 例：https://.../some_diagram.png のような
image_source = "path/to/your/whiteboard_drawing.jpg" 
user_prompt = "この手書きのシステム構成図を、Qiitaの記事に埋め込めるようにMermaid記法のコードに変換してください。各要素の名称も推測して記述してください。"

analyze_whiteboard_diagram(image_source, user_prompt)

# --- 期待されるAIの応答例 ---
# もちろんです。以下に手書きの図をMermaid記法で表現しました。
#
# ```mermaid
# graph TD
#     A[User Browser] --> B{Load Balancer};
#     B --> C1[Web Server 1];
#     B --> C2[Web Server 2];
#     C1 --> D(Database);
#     C2 --> D(Database);
# ```
```
このコードが示すのは、もはやテキストだけのやり取りではない、リッチなコンテキストを持ったMCPの姿です。UIのスクリーンショットを渡してテストケースを生成させたり、エラー画面の写真を送って原因を推測させたりと、開発プロセスにおける応用範囲は無限大です。

# 2. AIに「手」を与える：AIエージェントとFunction Calling/Tools
AIに「目」が備わった次は、「手」です。AIエージェントとは、自律的に思考し、行動計画を立て、外部のツール（APIや関数）を使ってその計画を実行するAIシステムのことです。

「難しそう…」と感じるかもしれませんが、その中核技術である **Function Calling（現: Tools）** は、私たちがこれまで学んできたAPIコールとエラーハンドリングの延長線上にあります。

- AIエージェントの基本ループ：思考 → 決断 → 行動
- 思考 (Think): ユーザーの目的と現在のコンテキストを理解する。
- 決断 (Decide): 目的達成のために、利用可能なツール（関数）の中からどれを使うべきか、どんな引数を渡すべきかをLLM自身が判断する。
- 行動 (Act): 私たちのPythonコードが、LLMの「決断」に従って実際の関数を実行する。
- （結果をコンテキストに追加して、1に戻る）
- 
第15回で実装した「モデル選択ルーター」を思い出してください。あれは、AIが「どのモデルを使うか」を決断する単純なエージェントでした。AIエージェントは、これを「どの関数（ツール）を使うか」に拡張した、より汎用的な仕組みなのです。

### Python実践例：天気を調べてニュースを検索する簡単なエージェント
```Python
import json
from openai import OpenAI
client = OpenAI()

# --- ステップ1: AIに「手」として与えるツール（Python関数）を定義 ---
def get_current_weather(location: str, unit: str = "celsius"):
    """指定された場所の現在の天気を取得します。"""
    print(f"TOOL: get_current_weather(location={location}, unit={unit}) を実行中...")
    # 本来はここで天気APIを叩く
    if "東京" in location:
        return json.dumps({"location": "東京", "temperature": "25", "weather": "晴れ"})
    return json.dumps({"location": location, "temperature": "不明", "weather": "不明"})

def search_news(topic: str):
    """指定されたトピックに関する最新ニュースを検索します。"""
    print(f"TOOL: search_news(topic={topic}) を実行中...")
    # 本来はここでニュースAPIを叩く
    if "AI" in topic:
        return json.dumps([
            {"headline": "GPT-4oがリリース、マルチモーダル機能が強化"},
            {"headline": "国内企業、AIエージェント導入を加速"}
        ])
    return json.dumps([])

# --- ステップ2: エージェントのメインループを実装 ---
def run_agent(user_prompt: str):
    messages = [{"role": "user", "content": user_prompt}]
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "指定された場所の現在の天気を取得する",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {"type": "string", "description": "天気予報を知りたい都市名。例: 東京"},
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["location"],
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "search_news",
                "description": "特定のトピックに関する最新ニュースを検索する",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "topic": {"type": "string", "description": "検索したいニュースのトピック。例: AI"},
                    },
                    "required": ["topic"],
                },
            },
        },
    ]

    # 思考 -> 決断
    print(f"🤖 USER: {user_prompt}")
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )
    response_message = response.choices[0].message
    messages.append(response_message) # アシスタントの応答（ツールコール含む）を履歴に追加

    # 決断の結果、ツールを使うべきか判断
    tool_calls = response_message.tool_calls
    if tool_calls:
        print("🤖 AIがツールの使用を決断しました。")
        available_functions = {
            "get_current_weather": get_current_weather,
            "search_news": search_news,
        }
        
        # 行動
        for tool_call in tool_calls:
            function_name = tool_call.function.name
            function_to_call = available_functions[function_name]
            function_args = json.loads(tool_call.function.arguments)
            function_response = function_to_call(**function_args)
            
            # 行動結果を履歴に追加
            messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": function_response,
                }
            )
        
        # 行動結果を踏まえて、最終的な応答を生成
        print("🤖 行動結果を基に最終応答を生成中...")
        final_response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
        )
        final_text = final_response.choices[0].message.content
        print("\n✅ AGENTの最終応答:\n" + "="*50)
        print(final_text)
    else:
        # ツールを使わずに直接応答する場合
        print("\n✅ AGENTの最終応答（ツール未使用）:\n" + "="*50)
        print(response_message.content)

# --- 実行例 ---
run_agent("東京の天気はどう？あと、AI関連のニュースも教えて。")
```
このコードを実行すると、AIが自らget_current_weatherとsearch_newsという2つの関数を呼び出し、その結果を統合して自然な日本語で回答を生成する様子が確認できます。これこそがAIエージェントの基本的な動作原理です。

# 3. 未来の展望：MCPの進化と「AIオーケストレーター」としての私たち
Vision API（目）とTools（手）を手に入れたAIは、私たちのMCPをさらに進化させます。

## 想像してみてください
ユーザーがスマートフォンのエラー画面のスクリーンショットをあなたのアプリにアップロードします（目）。AIは画像からエラーメッセージを読み取り、それが特定ライブラリのバージョン非互換性の問題であると推測します（思考）。次に、AIはsearch_github_issuesというあなたが用意したツールを使い、関連するIssueを検索します（決断＆行動）。そして、発見した解決策をユーザーに提示します。

この未来において、私たち開発者の役割は、一行一行コードを書くプログラマーから、 **AIの能力を最大限に引き出す「AIオーケストレーター」** へと変わっていきます。私たちの仕事は、

- 高品質なツール（関数やAPI）をAIに提供すること。
- AIが正しく思考・決断できるよう、効果的なプロンプトとコンテキスト（MCP）を設計すること。
- Vision、Tools、データベースなどを組み合わせたエージェントのループを円滑に回すオーケストレーションを実装すること。

にシフトしていくでしょう。

# まとめ：AI開発の新章へようこそ
今回は、AI開発の最前線である「マルチモーダル」と「AIエージェント」の世界への扉を開きました。

Vision APIを使えば、MCPに画像コンテキストを含めることができ、AIの認識範囲が劇的に広がる。
Tools (Function Calling) はAIに「手」を与え、自律的に外部システムと連携させるための鍵である。
AIエージェントは「思考→決断→行動」のループであり、私たちがこれまで学んだ技術の延長線上で実装できる。
これらは、もはや一部の研究者だけのものではありません。すべてのPython開発者がアクセスできる、強力なツールキットです。

# 次回予告
ついに、この長いシリーズも最終回を迎えます。次回、第20回は **「【シリーズ完走！】MCP的思考とPython×OpenAI APIで切り拓く、あなたのAI活用プロジェクト最前線！」** と題し、本シリーズで学んだすべての知識と技術を総括します。そして、あなたが独自のAIプロジェクトを始めるための最後の後押しをします。ぜひ、最後までお付き合いください！

未来の扉を開ける興奮を少しでも感じていただけたら、ぜひ **Like** で応援をよろしくお願いします！