PythonとOpenAI APIで実践！はじめてのMCP開発入門【第7回】Pythonコードで実践MCP - 構造化コンテキストを付与してOpenAI APIを呼び出すテクニック（総合演習）

# はじめに：理論をコードへ！コンテキストとプロンプトを組み合わせたAI対話の実践

皆さん、こんにちは！AI開発の冒険、第7回です。前回までの道のりで、私たちはAIの能力を引き出す「プロンプトエンジニアリング」の基礎（第5回）と、AIの理解を深めるための「構造化コンテキスト情報」をJSONで設計するパターン（第6回、MCPの核心技術）について学んできました。

それぞれの要素技術は理解できたものの、「じゃあ、実際にこれらをどう組み合わせて、意味のあるAIアプリケーションを作るの？」という疑問が湧いてきている頃ではないでしょうか。まさに今回のテーマは、その疑問に答える**「総合演習」**です。

これまで学んだ、
- 効果的なプロンプトの作成方法
- ユーザープロファイルや外部データといった「コンテキスト」のJSON形式での設計
- Pythonを使ったOpenAI API（gpt-3.5-turboやgpt-4o, gpt-4o-miniなどのChat Completions API）の呼び出し方

これらの知識を総動員し、具体的なシナリオに基づいて、構造化コンテキストとプロンプトを組み合わせたPythonプログラムを実際に組んでいきます。これにより、本シリーズで提唱する「モデルコンテキストプロトコル（MCP）」の考え方を、実際のコードを通じて体現し、AIとの対話をより高度で実践的なレベルへと引き上げます。

（ここ東京のような技術開発が盛んな地域では、アイデアを素早く形にする実践力が求められます。2025年5月22日、今日の学びがあなたの武器となるでしょう！）

# 本日の演習シナリオ：AIによる「パーソナライズド技術記事解説アシスタント」

今回の総合演習では、以下の機能を持つシンプルなAIアシスタントをPythonで開発することを目指します。

- 目的
    - ユーザーの技術的スキルレベルと、提示された技術記事の断片（スニペット）に基づいて、そのユーザーに分かりやすいように記事の内容を解説し、可能であれば簡単なコード例も提示する。
- MCPで設計するコンテキスト情報
    1. user_profile
        - ユーザーのスキルレベル（例：「Python初心者」「Web開発中級者」）や、好みの解説スタイル（例：「専門用語を避けて平易に」「アナロジーを多用して」）など。
    1. article_snippet
        - 解説対象となる技術記事のタイトルと本文の一部。
- AIに期待する出力
    - ユーザーのスキルレベルに合わせた平易な解説文、および概念を理解するためのシンプルなPythonコード例。
このシナリオは、Qiita読者の皆さんにとっても身近で、コンテキストの重要性が分かりやすい題材かと思います。

## ステップ1：コンテキストデータ構造の設計（Python辞書 → JSON）

まずは、AIに提供するコンテキスト情報をPythonの辞書型で設計します。これがAPIコール時にはJSON形式に変換されて送信されるイメージです。
```Python

# user_profile_context.py (例として別ファイルに定義するイメージ)
def get_user_profile_for_qiita_user():
    return {
        "user_id": "qiita_user_20250521",
        "skill_level_description": "Pythonの基本的な文法（変数、制御構文、関数）は理解しているが、非同期処理や高度なライブラリの経験は浅い初心者レベルの開発者。",
        "learning_objectives": ["asyncioの基本概念を理解したい", "具体的な利用シーンを知りたい"],
        "preferred_explanation_style": "専門用語は避け、具体的な例え話や非常にシンプルなコード例を交えて解説してほしい。"
    }

# article_context.py (例として別ファイルに定義するイメージ)
def get_article_snippet_about_asyncio():
    return {
        "article_id": "tech_blog_001",
        "article_title": "Pythonにおけるasyncio入門：非同期処理でパフォーマンスを向上させる",
        "snippet_text": "Pythonのasyncioライブラリは、イベントループとコルーチンを活用することで、単一スレッドでありながら並行処理を実現します。特にネットワークI/Oのような待機時間が発生しやすい処理（I/Oバウンドな処理）において、メインスレッドをブロックすることなく他のタスクを実行できるため、アプリケーション全体の応答性やパフォーマンスを大幅に改善できます。async/await構文を用いることで、非同期コードを同期コードに近い形で直感的に記述することが可能です。"
    }
```
### ポイント
- skill_level_description や preferred_explanation_style は、AIがユーザーに合わせて解説のトーンや深さを調整するための重要なコンテキストです。
- snippet_text には、実際にAIに解説してほしい技術記事の断片を含めます。

## ステップ2：コンテキストを活用するプロンプトの設計

次に、上記のコンテキスト情報をAIが効果的に利用できるように、プロンプトを設計します。ここでは、システムメッセージに固定的な指示とコンテキストの「型枠」を定義し、ユーザーメッセージで具体的なリクエストを行う形を取ります。
```Python

# prompt_template.py (例として別ファイルに定義するイメージ)

# プロンプト内でコンテキスト情報を埋め込むためのプレースホルダーを使用
SYSTEM_MESSAGE_TEMPLATE = """
あなたは、経験豊富なPython技術メンターAIです。
ユーザーのスキルレベルや好みに合わせて、提供された技術記事の断片を非常に分かりやすく解説する役割を担います。

以下のユーザープロファイルと記事スニペットを正確に理解し、ユーザーのリクエストに応答してください。
技術的な概念は、ユーザーのスキルレベルに合わせて、必要であれば具体的なアナロジー（例え話）や非常にシンプルなコード例を用いて説明してください。

<user_profile>
{user_profile_json_string}
</user_profile>

<article_snippet_to_explain>
{article_snippet_json_string}
</article_snippet_to_explain>

上記の情報を踏まえ、ユーザーの指示に従い、丁寧かつ正確な解説を提供してください。
"""

USER_REQUEST_TEMPLATE = "上記の記事スニペットの主要な概念について、私のスキルレベルに合わせて解説してください。特に、asyncioが解決する問題点と、それがどのように機能するのか基本的な仕組みを、簡単なPythonコード例（もしあれば）と共に教えてください。"
```
### ポイント
システムメッセージ内に、<user_profile> や <article_snippet_to_explain> といった**区切り文字（デリミタ）**で囲まれたプレースホルダーを用意します。ここに後ほどPythonでJSON文字列化したコンテキストデータを挿入します。
AIの役割（ロールプロンプティング）や、解説のスタイルについても指示しています。
ユーザーリクエストも、提供されたコンテキスト情報を参照するように促しています。

## ステップ3：Pythonスクリプトの実装（総合演習コード）

いよいよ、これらの要素を統合したPythonスクリプトを作成します。
```Python

# main_mcp_exaplainer.py

import os
import json
from dotenv import load_dotenv
from openai import OpenAI

# (仮に別ファイルからコンテキスト定義をインポートするなら)
# from user_profile_context import get_user_profile_for_qiita_user
# from article_context import get_article_snippet_about_asyncio
# from prompt_template import SYSTEM_MESSAGE_TEMPLATE, USER_REQUEST_TEMPLATE

# --- (演習のため、ここでは直接定義します) ---
def get_user_profile_for_qiita_user():
    # (ステップ1のuser_profile定義をここにコピー)
    return {
        "user_id": "qiita_user_20250521",
        "skill_level_description": "Pythonの基本的な文法（変数、制御構文、関数）は理解しているが、非同期処理や高度なライブラリの経験は浅い初心者レベルの開発者。",
        "learning_objectives": ["asyncioの基本概念を理解したい", "具体的な利用シーンを知りたい"],
        "preferred_explanation_style": "専門用語は避け、具体的な例え話や非常にシンプルなコード例を交えて解説してほしい。"
    }

def get_article_snippet_about_asyncio():
    # (ステップ1のarticle_snippet定義をここにコピー)
    return {
        "article_id": "tech_blog_001",
        "article_title": "Pythonにおけるasyncio入門：非同期処理でパフォーマンスを向上させる",
        "snippet_text": "Pythonのasyncioライブラリは、イベントループとコルーチンを活用することで、単一スレッドでありながら並行処理を実現します。特にネットワークI/Oのような待機時間が発生しやすい処理（I/Oバウンドな処理）において、メインスレッドをブロックすることなく他のタスクを実行できるため、アプリケーション全体の応答性やパフォーマンスを大幅に改善できます。async/await構文を用いることで、非同期コードを同期コードに近い形で直感的に記述することが可能です。"
    }

SYSTEM_MESSAGE_TEMPLATE = """
あなたは、経験豊富なPython技術メンターAIです。
ユーザーのスキルレベルや好みに合わせて、提供された技術記事の断片を非常に分かりやすく解説する役割を担います。

以下のユーザープロファイルと記事スニペットを正確に理解し、ユーザーのリクエストに応答してください。
技術的な概念は、ユーザーのスキルレベルに合わせて、必要であれば具体的なアナロジー（例え話）や非常にシンプルなコード例を用いて説明してください。

<user_profile>
{user_profile_json_string}
</user_profile>

<article_snippet_to_explain>
{article_snippet_json_string}
</article_snippet_to_explain>

上記の情報を踏まえ、ユーザーの指示に従い、丁寧かつ正確な解説を提供してください。
"""

USER_REQUEST_TEMPLATE = "上記の記事スニペットの主要な概念について、私のスキルレベルに合わせて解説してください。特に、asyncioが解決する問題点と、それがどのように機能するのか基本的な仕組みを、簡単なPythonコード例（もしあれば）と共に教えてください。"
# --- (定義ここまで) ---


def format_system_message(user_profile_dict, article_snippet_dict):
    """コンテキストデータをJSON文字列に変換し、システムメッセージテンプレートに埋め込む"""
    user_profile_json = json.dumps(user_profile_dict, ensure_ascii=False, indent=2)
    article_snippet_json = json.dumps(article_snippet_dict, ensure_ascii=False, indent=2)
    
    return SYSTEM_MESSAGE_TEMPLATE.format(
        user_profile_json_string=user_profile_json,
        article_snippet_json_string=article_snippet_json
    )

def get_personalized_explanation(system_message, user_request, model="gpt-4o-mini", max_tokens=1500, temperature=0.3):
    """OpenAI APIにリクエストを送信し、パーソナライズされた解説を取得する"""
    load_dotenv()
    client = OpenAI() # APIキーは環境変数から自動ロードされる

    messages = [
        {"role": "system", "content": system_message},
        {"role": "user", "content": user_request}
    ]
    
    try:
        print("\n--- AIへの送信情報（システムメッセージ一部抜粋＋ユーザーリクエスト） ---")
        print(f"System (Snippet): {system_message[:400]}...") # 長いので一部表示
        print(f"User: {user_request}")
        print("--- OpenAI APIにリクエスト送信中... ---")
        
        completion = client.chat.completions.create(
            model=model,
            messages=messages,
            max_tokens=max_tokens,
            temperature=temperature
        )
        ai_response = completion.choices[0].message.content
        
        print("\n--- AIからのパーソナライズド解説 ---")
        print(ai_response)
        print(f"\n(使用トークン数 - Prompt: {completion.usage.prompt_tokens}, Completion: {completion.usage.completion_tokens}, Total: {completion.usage.total_tokens})")
        return ai_response
    except Exception as e:
        print(f"APIリクエストエラー: {e}")
        return None

# --- メイン処理 ---
if __name__ == "__main__":
    print("パーソナライズド技術記事解説AIアシスタントを開始します。")
    
    # 1. コンテキストデータを取得
    current_user_profile = get_user_profile_for_qiita_user()
    current_article_snippet = get_article_snippet_about_asyncio()
    
    # 2. システムメッセージをコンテキストでフォーマット
    formatted_system_message = format_system_message(current_user_profile, current_article_snippet)
    
    # 3. ユーザーリクエストを定義 (今回はテンプレートをそのまま使用)
    user_main_request = USER_REQUEST_TEMPLATE
    
    # 4. AIに解説をリクエスト
    explanation = get_personalized_explanation(formatted_system_message, user_main_request)
    
    if explanation:
        print("\n--- 解説生成完了 ---")
    else:
        print("\n--- 解説生成失敗 ---")
```
### コード解説のポイント
- モジュール化の意識
    - コンテキスト取得、システムメッセージ整形、APIコールといった処理を関数に分けています。実際のアプリケーションでは、これらの関数を別ファイルにモジュールとして保存すると、コードの見通しが良くなります。
json.dumps(): Pythonの辞書オブジェクトをJSON形式の文字列に変換しています。ensure_ascii=Falseで日本語が正しく扱われ、indent=2で整形されて読みやすくなります。これはAPIに送るためというより、システムメッセージ内で人間が（そしてAIが）構造を把握しやすくするためです。
- システムメッセージへのコンテキスト埋め込み
    - SYSTEM_MESSAGE_TEMPLATE.format(...) を使って、JSON文字列化したコンテキストをシステムメッセージの適切な場所に挿入しています。AIはこのシステムメッセージを「基本指示」として強く意識します。
- モデル選択とパラメータ
    - model="gpt-4o-mini" を指定しています。GPT-4o miniは計算リソースを効率化するために設計されており、必要最低限のリソースで効率的に動作することが可能です。それに対しt、GPT-4oは非常に高性能で、複雑な指示や長文コンテキストの理解に優れています（ただし、料金や速度は gpt-3.5-turbo と比較検討が必要です）。temperature=0.3 と低めに設定し、より事実に基づいた、安定した解説を目指しています。max_tokensも、十分な解説が得られるように少し大きめに設定しています。
- トークン数表示
    - API応答からトークン使用量を取得し表示することで、コスト意識を高めます。コンテキスト情報もプロンプトトークン数に含まれることを実感できるでしょう。

## ステップ4：実行結果の分析と反復的改善
このスクリプトを実行すると、AIは提供されたユーザープロファイル（Python初心者レベル、平易な解説とコード例を好む）と記事スニペット（asyncioに関する技術的な記述）を考慮し、パーソナライズされた解説を生成しようとします。

- 期待される応答のイメージ
    -「asyncioは、たくさんの作業を一人でこなすスーパー店員さんのようなものです。例えば、お客さんAの注文を聞いている間（これがネットワーク待ちのような時間のかかる処理）、手が空いているわけではなく、他のお客さんBの飲み物の準備を進めたりできますよね？ asyncioも、一つの処理が終わるのをただ待つのではなく、その間に他の処理を進めることで、プログラム全体を効率よく動かす仕組みなんです。Pythonでは async def で『この作業は途中で待つことがあるよ』と印をつけ、await で『ここで一旦待って、他のことができるならしてね』と指示します。例えば…（非常にシンプルな疑似コード例）…といった感じです。」

- 反復的改善の重要性
    -もしAIの応答が期待通りでなければ、以下の点を調整して試行錯誤します。
- プロンプトの具体性
    - システムメッセージやユーザーリクエストの指示をより明確に、あるいは別の表現に変えてみる。
- コンテキスト情報の内容
    - user_profile の記述をより詳細にする、あるいは逆にシンプルにする。article_snippet の範囲を調整する。
- 例示の追加（フューショット的アプローチ）
    - システムメッセージ内に、「良い解説の例」「悪い解説の例」といったお手本を少数含めることで、AIの出力をさらに誘導できる場合があります。
- APIパラメータ調整
    - temperature を変えて応答の創造性を調整したり、max_tokens を増減させたりする。
- モデルの変更
    - より高性能なモデル（例：最新のGPTモデル）を試す、あるいは特定のタスクに特化したモデルがあればそれを利用する。

この **「設計→実装→評価→改善」のイテレーション（反復）** こそが、AIアプリケーション開発、特にプロンプトエンジニアリングとMCP設計における成功の鍵です。

# コンテキストリッチなAPIコールのトークン数とコスト効率

忘れてはならないのが、システムメッセージに埋め込むコンテキスト情報、そしてユーザーリクエストの全てが入力トークンとしてカウントされるという点です。今回の例のように詳細なコンテキストを与える場合、プロンプトトークン数は数百から数千に達することもあります。

## コスト効率化のヒント（再掲）
- コンテキストの凝縮
    - 長すぎる情報はAIで事前要約する。
- 関連性フィルタリング
    - ユーザーリクエストに最も関連性の高いコンテキスト部分だけを選択して提供する。
- ベクトルデータベースとRAG（Retrieval Augmented Generation）の導入検討
    - これは本シリーズの範囲を超えますが、膨大な知識ベースを扱う場合、関連情報を動的に検索してコンテキストとして注入するRAGというアーキテクチャが、コスト効率と性能の両立において非常に有効です。

# おわりに：MCP的思考で、AIの真価を引き出す開発者へ

今回の総合演習では、プロンプトエンジニアリングの技法と、構造化されたコンテキスト情報（MCPの考え方）の設計・実装を組み合わせることで、AIの応答をいかにパーソナライズし、タスク適合性を高められるか、その一端を体験しました。

Pythonコードで、ユーザープロファイルや外部データといった多様な情報をJSONライクな辞書として準備し、それを効果的にシステムメッセージやプロンプトに組み込み、OpenAI APIを呼び出すという一連の流れは、より高度なAIアプリケーションを構築するための基本的な設計パターンです。

この「コンテキストを設計し、AIに的確に伝える」というMCP的思考は、今後のAI開発においてますます重要になるでしょう。ぜひ、今回の演習をベースに、ご自身のアイデアで様々なコンテキスト、様々なプロンプトを試し、AIとの対話を通じてその無限の可能性を探求してみてください。そして、その成果をQiitaで共有し、コミュニティと共に学びを深めていくのも素晴らしいでしょう。

## 次回予告
AIとの基本的な対話、そしてコンテキストの与え方もマスターしました。しかし、実際のアプリケーション開発では、AIからの応答をプログラムで処理し、時にはエラーにも対処しなければなりません。
次回、第8回「AIの応答（JSON）を徹底解析！エラーコードの理解とPythonでの堅牢なエラーハンドリング入門」では、OpenAI APIからのレスポンスJSONのより詳細な構造と、予期せぬエラーが発生した場合にプログラムが停止しないようにするための、堅牢なエラーハンドリング技術について学びます。より安定したAIアプリ開発のために必須の知識です！