「Claude APIを使ってチャットボットは作れたけど、コードがぐちゃぐちゃで管理しにくい…」「会話ログを保存したい」「複数の用途でボットを使い分けたい」

そんな中級者の悩みを、この記事で一気に解決しましょう!

本記事は「環境構築済みで、APIの基本呼び出しは理解している」方を対象に、チャットボットを実用レベルに引き上げるための実践テクニックを解説します。環境構築からAPIの基本的な使い方を確認したい方は、まずClaude APIをPythonで使う完全入門ガイド|初心者でもAIチャットアプリが作れる!をご覧ください。

この記事で学べること

python chatbot design
python chatbot class design / Photo by Tim Witzdam via Pexels
Claude API chatbot advanced python
Claude API chatbot advanced python / Photo by Matheus Bertelli via Pexels

📌 対象読者:Claude APIの基本呼び出しができるPython中級者

✅ この記事を読むとわかること:

  • クラス設計でチャットボットコードを整理・再利用しやすくする方法
  • 会話ログをJSONに保存して復元する仕組みの作り方
  • システムプロンプト切り替えで複数ボットを運用するパターン
  • Haiku/Sonnet/Opusの使い分けとコスト最適化の考え方
  • FlaskやLINE Messaging APIへの発展ステップ

STEP 1:クラス設計でコードを整理しよう

最初に「動くコード」を書けたとしても、機能追加のたびにグローバル変数や関数が増えてカオスになりがちです。クラスとして設計し直すと、保守性・再利用性が一気に上がります。

なぜクラス化が必要なのか?

手続き型のコードと比較して考えてみましょう。

  • ❌ 手続き型:会話履歴・システムプロンプト・モデル名がバラバラにグローバル変数として散在
  • ✅ クラス化:インスタンスごとに状態を管理でき、複数ボットを同時に扱える

以下が基本となる ChatBot クラスの実装です。

import anthropic
from dotenv import load_dotenv
from datetime import datetime
import json
import os

load_dotenv()

class ChatBot:
    """
    Claude APIを使ったチャットボットクラス。
    会話履歴の管理・JSON保存・モデル切り替えをサポート。
    """

    def __init__(
        self,
        model: str = "claude-haiku-4-5",
        system_prompt: str = "あなたは親切なAIアシスタントです。日本語で回答してください。",
        max_tokens: int = 1024,
        bot_name: str = "default"
    ):
        self.client = anthropic.Anthropic()
        self.model = model
        self.system_prompt = system_prompt
        self.max_tokens = max_tokens
        self.bot_name = bot_name
        self.conversation_history: list[dict] = []
        self.created_at = datetime.now().isoformat()

    def chat(self, user_input: str) -> str:
        """ユーザーの入力を受け取り、Claudeの返答を返す"""
        self.conversation_history.append({
            "role": "user",
            "content": user_input
        })

        response = self.client.messages.create(
            model=self.model,
            max_tokens=self.max_tokens,
            system=self.system_prompt,
            messages=self.conversation_history
        )

        assistant_reply = response.content[0].text

        self.conversation_history.append({
            "role": "assistant",
            "content": assistant_reply
        })

        return assistant_reply

    def reset(self):
        """会話履歴をリセットする"""
        self.conversation_history = []
        print(f"[{self.bot_name}] 会話をリセットしました。")

    def __repr__(self) -> str:
        return (
            f"ChatBot(name={self.bot_name!r}, "
            f"model={self.model!r}, "
            f"turns={len(self.conversation_history) // 2})"
        )

使い方はシンプルです:

bot = ChatBot(bot_name="汎用アシスタント")
print(bot.chat("Pythonのリスト内包表記を一行で説明して"))
print(bot.chat("もう少し詳しく教えて"))  # 文脈を覚えている
print(bot)  # ChatBot(name='汎用アシスタント', model='claude-haiku-4-5', turns=2)

クラス化によってインスタンスを作るだけで独立したボットが増やせるようになります。次のセクションでその強みを活かした複数ボット運用を見ていきましょう。

STEP 2:会話ログをJSONで保存・復元しよう

チャットボットを本格的に使うなら、会話ログの永続化は必須です。アプリを再起動しても前回の会話を引き継げると、ユーザー体験が大きく向上します。

保存・復元メソッドをクラスに追加する

class ChatBot:
    # ...(前述のコードに続けて追記)...

    def save_log(self, filepath: str | None = None) -> str:
        """
        会話履歴をJSONファイルに保存する。
        filepath未指定時はbot_nameとタイムスタンプで自動命名。
        """
        if filepath is None:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filepath = f"chat_log_{self.bot_name}_{timestamp}.json"

        log_data = {
            "bot_name": self.bot_name,
            "model": self.model,
            "system_prompt": self.system_prompt,
            "created_at": self.created_at,
            "saved_at": datetime.now().isoformat(),
            "conversation_history": self.conversation_history
        }

        with open(filepath, "w", encoding="utf-8") as f:
            json.dump(log_data, f, ensure_ascii=False, indent=2)

        print(f"会話ログを保存しました: {filepath}")
        return filepath

    @classmethod
    def load_log(cls, filepath: str) -> "ChatBot":
        """
        保存したJSONから会話履歴を復元してChatBotインスタンスを返す。
        クラスメソッドなので ChatBot.load_log(path) で呼べる。
        """
        with open(filepath, "r", encoding="utf-8") as f:
            log_data = json.load(f)

        bot = cls(
            model=log_data["model"],
            system_prompt=log_data["system_prompt"],
            bot_name=log_data["bot_name"]
        )
        bot.conversation_history = log_data["conversation_history"]
        bot.created_at = log_data["created_at"]

        print(f"会話ログを復元しました: {filepath}")
        print(f"  → 復元したターン数: {len(bot.conversation_history) // 2}")
        return bot

⚠️ 補足:str | None という型ヒントの書き方はPython 3.10以降で動作します。Python 3.9以前を使っている場合は、from typing import Optional を追加して Optional[str] と書いてください。

使い方のイメージ

# セッション1:会話してログを保存
bot = ChatBot(bot_name="料理アシスタント")
bot.chat("カルボナーラの作り方を教えて")
bot.chat("卵は何個必要?")
log_path = bot.save_log()  # chat_log_料理アシスタント_20260710_120000.json

# セッション2:ログから復元して会話を継続
bot2 = ChatBot.load_log(log_path)
print(bot2.chat("チーズは何を使えばいい?"))  # 前の文脈を引き継いでいる

💡 ポイント:@classmethod を使うと ChatBot.load_log(path) という自然な書き方で復元できます。cls() でインスタンスを生成しているため、サブクラスに継承しても正しく動きます。

保存されるJSONの構造

{
  "bot_name": "料理アシスタント",
  "model": "claude-haiku-4-5",
  "system_prompt": "あなたは料理専門のAIアシスタントです。",
  "created_at": "2026-07-10T12:00:00",
  "saved_at": "2026-07-10T12:05:30",
  "conversation_history": [
    {"role": "user", "content": "カルボナーラの作り方を教えて"},
    {"role": "assistant", "content": "カルボナーラは..."},
    {"role": "user", "content": "卵は何個必要?"},
    {"role": "assistant", "content": "2人分なら全卵1個+卵黄2個が目安です。"}
  ]
}

STEP 3:システムプロンプト切り替えで複数ボットを運用しよう

システムプロンプトはClaudeの「キャラクター設定」です。これを切り替えるだけで、同じAPIを使いながらまったく異なる専門性を持つボットを作れます。なお、ここからのコードはSTEP 1で作成した ChatBot クラスを chatbot.py というファイル名で保存している前提です(STEP 6のFlask連携でも同じファイル名を使います)。

ボット設定をまとめて管理するファクトリパターン

# bot_configs.py
from chatbot import ChatBot  # STEP 1のChatBotクラス(chatbot.py)をimport

BOT_CONFIGS = {
    "general": {
        "model": "claude-haiku-4-5",
        "system_prompt": "あなたは親切で博識なAIアシスタントです。簡潔かつ正確に日本語で回答してください。",
        "max_tokens": 1024,
        "description": "汎用アシスタント(低コスト・高速)"
    },
    "code_reviewer": {
        "model": "claude-sonnet-4-5",
        "system_prompt": (
            "あなたはシニアソフトウェアエンジニアです。"
            "提出されたコードに対して、バグ・セキュリティリスク・可読性の3点を中心にレビューし、"
            "改善案を具体的なコードで示してください。"
        ),
        "max_tokens": 4096,
        "description": "コードレビュー専門(高品質・中コスト)"
    },
    "technical_writer": {
        "model": "claude-sonnet-4-5",
        "system_prompt": (
            "あなたはテクニカルライターです。"
            "エンジニア向けのドキュメントや仕様書を、構造化されたMarkdown形式で執筆します。"
            "見出し・箇条書き・コード例を適切に使い、読み手が迷わない文書を作成してください。"
        ),
        "max_tokens": 2048,
        "description": "技術文書作成専門(Sonnetで品質重視)"
    },
    "deep_analyst": {
        "model": "claude-opus-4-5",
        "system_prompt": (
            "あなたは高度な分析・推論の専門家です。"
            "複雑な問題を多角的に分析し、論理的な根拠とともに詳細な考察を提供してください。"
            "必要に応じて仮説・反論・リスクも含めて検討してください。"
        ),
        "max_tokens": 8192,
        "description": "深度分析専門(最高品質・高コスト)"
    }
}


def create_bot(bot_type: str) -> "ChatBot":
    """設定キーからChatBotインスタンスを生成するファクトリ関数"""
    if bot_type not in BOT_CONFIGS:
        raise ValueError(f"不明なbot_typeです: {bot_type}。選択肢: {list(BOT_CONFIGS.keys())}")

    config = BOT_CONFIGS[bot_type]
    bot = ChatBot(
        model=config["model"],
        system_prompt=config["system_prompt"],
        max_tokens=config["max_tokens"],
        bot_name=bot_type
    )
    print(f"✅ ボット作成: {config['description']}")
    return bot

複数ボットを使い分けるスクリプト例

from bot_configs import create_bot

# 目的に応じてボットを使い分け
general_bot = create_bot("general")
code_bot = create_bot("code_reviewer")

# 汎用ボット:素早い質問回答
answer = general_bot.chat("Pythonのデコレータをひとことで説明して")
print(f"汎用: {answer}\n")

# コードレビューボット:コードの品質確認
code_snippet = """
def get_user(user_id):
    query = f"SELECT * FROM users WHERE id = {user_id}"
    return db.execute(query)
"""
review = code_bot.chat(f"このコードをレビューしてください:\n```python{code_snippet}```")
print(f"レビュー: {review}")

# それぞれ独立した会話履歴を持っている
print(general_bot)  # ChatBot(name='general', model='claude-haiku-4-5', turns=1)
print(code_bot)     # ChatBot(name='code_reviewer', model='claude-sonnet-4-5', turns=1)

この設計のメリットは、新しいボットを追加するときに BOT_CONFIGS に1エントリ追加するだけでよい点です。本体のコードを一切変更せずに拡張できます。

STEP 4:Haiku・Sonnet・Opusの使い分けとコスト最適化


Claude APIには複数のモデルが用意されており、用途に合わせて選ぶことがコスト最適化の核心です。「とりあえずOpus」は品質は高いですが費用が跳ね上がります。

モデル特性の比較

モデル 特徴 コスト感 向いているユースケース
claude-haiku-4-5 高速・軽量 低コスト💰 FAQ応答、分類タスク、リアルタイムチャット
claude-sonnet-4-5 品質・速度バランス 中コスト💰💰 コードレビュー、文書生成、複雑なQ&A
claude-opus-4-5 最高品質・深度推論 高コスト💰💰💰 複雑な分析、戦略立案、高精度要約

※上記は2026年8月時点の情報です。最新のモデルIDやラインナップは公式ドキュメント(platform.claude.com)で確認してください。

コスト最適化の実装パターン:動的モデル選択

入力の複雑さに応じて自動的にモデルを切り替える仕組みを実装できます。

class AdaptiveChatBot(ChatBot):
    """
    入力の複雑さに応じてモデルを動的に切り替えるチャットボット。
    短い・単純な質問はHaiku、長い・複雑な質問はSonnetを使う。
    """

    # 複雑度判定のしきい値(文字数):これを超えたらSonnetに切り替え
    HAIKU_THRESHOLD = 100

    def _select_model(self, user_input: str) -> str:
        """入力の長さと内容からモデルを自動選択する"""
        length = len(user_input)

        # 「詳細に」「詳しく」「分析」などのキーワードがあれば格上げ
        complex_keywords = ["詳細", "詳しく", "分析", "比較", "設計", "アーキテクチャ"]
        has_complex_keyword = any(kw in user_input for kw in complex_keywords)

        if length > self.HAIKU_THRESHOLD or has_complex_keyword:
            return "claude-sonnet-4-5"
        else:
            return "claude-haiku-4-5"

    def chat(self, user_input: str) -> str:
        """動的モデル選択付きのchat()"""
        selected_model = self._select_model(user_input)

        if selected_model != self.model:
            print(f"💡 モデル切り替え: {self.model} → {selected_model}")
            self.model = selected_model

        return super().chat(user_input)

コスト管理のベストプラクティス

  • 🔢 max_tokensを用途に合わせて絞る:FAQなら512、詳細分析なら4096など
  • 💾 会話履歴が長くなったらトリミング:古いターンを削除してプロンプト長を制御
  • 📊 使用量を記録する習慣をつける:APIレスポンスの usage フィールドを活用
  • 🧪 開発中はHaikuで動作確認してからSonnetに上げる
# APIレスポンスからトークン使用量を記録する例
class ChatBot:
    def __init__(
        self,
        model: str = "claude-haiku-4-5",
        system_prompt: str = "あなたは親切なAIアシスタントです。日本語で回答してください。",
        max_tokens: int = 1024,
        bot_name: str = "default"
    ):
        # ...(STEP 1と同じ初期化処理)...
        self.total_input_tokens = 0
        self.total_output_tokens = 0

    def chat(self, user_input: str) -> str:
        self.conversation_history.append({"role": "user", "content": user_input})

        response = self.client.messages.create(
            model=self.model,
            max_tokens=self.max_tokens,
            system=self.system_prompt,
            messages=self.conversation_history
        )

        # トークン使用量を累積記録
        self.total_input_tokens += response.usage.input_tokens
        self.total_output_tokens += response.usage.output_tokens

        assistant_reply = response.content[0].text
        self.conversation_history.append({"role": "assistant", "content": assistant_reply})
        return assistant_reply

    def usage_report(self) -> dict:
        """累積トークン使用量を返す"""
        return {
            "input_tokens": self.total_input_tokens,
            "output_tokens": self.total_output_tokens,
            "total_tokens": self.total_input_tokens + self.total_output_tokens
        }

STEP 5:長い会話履歴のトリミング

会話ターン数が増えると、毎回のAPIリクエストに含まれるトークン数が増大してコストが跳ね上がります。会話履歴の上限を設けてトリミングする仕組みを実装しておきましょう。

class ChatBot:
    def __init__(
        self,
        model: str = "claude-haiku-4-5",
        system_prompt: str = "あなたは親切なAIアシスタントです。日本語で回答してください。",
        max_tokens: int = 1024,
        bot_name: str = "default",
        max_history_turns: int = 20
    ):
        # ...(STEP 1と同じ初期化処理)...
        self.max_history_turns = max_history_turns  # 保持する最大ターン数

    def _trim_history(self):
        """
        会話履歴が上限を超えたら古いターンから削除する。
        user/assistantのペアを1ターンとしてカウント。
        最初のuser発言は文脈の起点として残すオプションも考慮できる。
        """
        max_messages = self.max_history_turns * 2  # 1ターン = user + assistant の2メッセージ
        if len(self.conversation_history) > max_messages:
            # 古い順から削除(先頭から切り捨て)
            excess = len(self.conversation_history) - max_messages
            self.conversation_history = self.conversation_history[excess:]
            print(f"[{self.bot_name}] 履歴トリミング: {excess}件のメッセージを削除")

    def chat(self, user_input: str) -> str:
        self.conversation_history.append({"role": "user", "content": user_input})
        self._trim_history()  # 送信前にトリミング

        response = self.client.messages.create(
            model=self.model,
            max_tokens=self.max_tokens,
            system=self.system_prompt,
            messages=self.conversation_history
        )

        assistant_reply = response.content[0].text
        self.conversation_history.append({"role": "assistant", "content": assistant_reply})
        return assistant_reply

💡 運用のコツ:Haikuは安価ですが最大コンテキスト長はSonnet/Opusより短いため、長期会話ではHaikuの場合 max_history_turns=10 程度に絞るのが安全です。

STEP 6:Web連携・LINE連携への発展


クラスとして整理したチャットボットは、Webアプリやメッセージングサービスへの組み込みが容易になります。ここでは実装の骨格を示します。

Flask APIサーバーへの組み込み

from flask import Flask, request, jsonify
from chatbot import ChatBot  # 先ほど作ったクラスをimport

app = Flask(__name__)

# セッション管理:実際のアプリではRedisやDBを使う
sessions: dict[str, ChatBot] = {}

@app.route("/chat", methods=["POST"])
def chat():
    data = request.get_json()
    session_id = data.get("session_id", "default")
    user_message = data.get("message", "")

    if not user_message:
        return jsonify({"error": "messageが空です"}), 400

    # セッションにボットがなければ新規作成
    if session_id not in sessions:
        sessions[session_id] = ChatBot(
            model="claude-haiku-4-5",
            bot_name=session_id
        )

    bot = sessions[session_id]
    reply = bot.chat(user_message)

    return jsonify({
        "session_id": session_id,
        "reply": reply,
        "turns": len(bot.conversation_history) // 2,
        "usage": bot.usage_report()
    })

@app.route("/reset", methods=["POST"])
def reset():
    data = request.get_json()
    session_id = data.get("session_id", "default")
    if session_id in sessions:
        sessions[session_id].reset()
    return jsonify({"message": "リセット完了"})

if __name__ == "__main__":
    app.run(debug=True, port=5000)

このAPIをcurlで確認するとこうなります:

curl -X POST http://localhost:5000/chat \
  -H "Content-Type: application/json" \
  -d '{"session_id": "user_001", "message": "Pythonとは何ですか?"}'

LINE Messaging APIへの連携イメージ

LINE Botと組み合わせると、スマホのLINEから直接Claudeと会話できます。基本的な流れは以下のとおりです。

from flask import Flask, request, abort
from linebot.v3 import WebhookHandler
from linebot.v3.messaging import Configuration, ApiClient, MessagingApi, ReplyMessageRequest, TextMessage
from linebot.v3.webhooks import MessageEvent, TextMessageContent
from chatbot import ChatBot
import os

app = Flask(__name__)

configuration = Configuration(access_token=os.environ["LINE_CHANNEL_ACCESS_TOKEN"])
handler = WebhookHandler(os.environ["LINE_CHANNEL_SECRET"])

# LINEユーザーごとにボットを管理
user_bots: dict[str, ChatBot] = {}

@app.route("/callback", methods=["POST"])
def callback():
    signature = request.headers["X-Line-Signature"]
    body = request.get_data(as_text=True)
    try:
        handler.handle(body, signature)
    except Exception:
        abort(400)
    return "OK"

@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    user_id = event.source.user_id
    user_text = event.message.text

    # ユーザーごとのボット管理
    if user_id not in user_bots:
        user_bots[user_id] = ChatBot(
            model="claude-haiku-4-5",
            system_prompt="あなたはLINE上で動くAIアシスタントです。簡潔に返答してください。",
            bot_name=user_id,
            max_history_turns=10  # LINEは短いやり取りが多いので少なめに
        )

    reply_text = user_bots[user_id].chat(user_text)

    with ApiClient(configuration) as api_client:
        line_bot_api = MessagingApi(api_client)
        line_bot_api.reply_message_with_http_info(
            ReplyMessageRequest(
                reply_token=event.reply_token,
                messages=[TextMessage(text=reply_text)]
            )
        )

⚠️ 本番運用の注意点:上記のコードでは user_bots をメモリ上で管理しているため、サーバー再起動で消えます。本番ではRedisやデータベースへのセッション永続化が必要です。その際、先ほど作った save_log() / load_log() が活きてきます。

まとめ:「動くコード」から「使えるコード」へ

この記事では、Claude APIチャットボットを実用レベルに引き上げる以下の5つのテクニックを解説しました。

テクニック 得られる価値
クラス設計 コードの整理・再利用・複数ボット管理が容易に
JSON保存・復元 会話の永続化・セッション引き継ぎが実現
システムプロンプト切り替え 専門特化ボットの量産・設定の一元管理
モデル選択・コスト最適化 品質を維持しながらAPI費用を最小化
Web・LINE連携 実際のユーザーに届けられるサービスへ発展

「動くコード」をそのまま使い続けると、機能追加のたびに複雑さが増していきます。クラス設計と状態管理を最初に整えておくことで、後の発展がずっとスムーズになります

まだ環境構築や基本的なAPI呼び出しができていない方は、Claude APIをPythonで使う完全入門ガイド|初心者でもAIチャットアプリが作れる!からスタートしてみてください。基礎が固まったら、ぜひこの記事のコードを手元で試してみてください 🚀

📚 関連商品・おすすめ書籍

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

もしも

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

初心者に定番のPython入門書

Amazonで見る

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

もしも

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

電子工作とプログラミングを同時に学べる

Amazonで見る

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

もしも

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

AIコーディングの現場活用法を学ぶ一冊

Amazonで見る

※本記事にはアフィリエイトリンクが含まれます。

ABOUT ME
やまちゃん
これまで学生と社会人を合わせて5000人以上にプログラミング学習を指導。 ゼロからイチをわかりやすく解説する専門家として活動しており、本業ではArduinoを用いたIoT開発とロボットプログラミングが専門。 Pythonを用いたアプリ開発、ウェブアプリケーションの開発で業務の効率化をサポートしています。