PythonとLLMでシンプルなチャットボットを構築する:アプローチの比較と実践ガイド

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

チャットボットが「使えない」のはアーキテクチャ選択ミス、コードの問題ではない

以前、純粋なif/elseだけでカスタマーサポートチャットボットを構築するのに2週間近く費やしたことがある。結果は?テンプレート通りに入力した場合のみ正しく認識し、それ以外は完全に無反応。ユーザーが「pw忘れた」と入力しただけで「パスワードを忘れました」と認識できず、ボットはまったく理解できなかった。

問題はコードの質ではなく、最初からアーキテクチャの選択を誤っていたことだ。チャットボット構築のアプローチは実際には3種類しかなく、それらの境界線は当初思っていたよりもずっと明確だった。

チャットボット構築の3つのアプローチ:実践的な比較

方法1:ルールベース(if/else、正規表現)

最も古典的なアプローチだが、侮ってはいけない。ボットがユーザーの入力を読み取り、パターンマッチングして固定のレスポンスを返す。このタイプはFAQシステム、電話IVR、銀行のチャットボットなど、多くのシステムでいまも現役だ。

def simple_bot(user_input):
    msg = user_input.lower()
    if "パスワード" in msg or "pass" in msg:
        return "「パスワードを忘れた方へ」からリセットしてください。"
    elif "営業時間" in msg:
        return "営業時間は月曜〜金曜、8時〜17時です。"
    else:
        return "申し訳ありませんが、その質問は理解できませんでした。"

メリット:

  • APIが不要でコストゼロ、完全オフラインで動作
  • レスポンスを100%制御可能——法律・医療ユースケースで重要
  • デプロイが簡単で、外部依存ゼロ

デメリット:

  • 質問のあらゆるバリエーションにルールを書く必要があり、数百パターン以上では非現実的
  • スラング、略語、タイプミスへの対応が不可能
  • ルールベースが肥大化するとメンテナンスが悪夢になる

方法2:インテント分類 + 従来のML

小さなモデル(Naive Bayes、SVM、または軽量BERT)をトレーニングして質問からインテントを分類し、インテントを固定のレスポンスにマッピングする。scikit-learn + TF-IDFで12インテントのチャットボットを試したことがあり、インテントごとに約200文のトレーニングデータで約80%の精度を達成した。悪くないが、データ準備だけで1週間かかった。

メリット:

  • ルールベースより柔軟で、ある程度の言語バリエーションに対応
  • ローカルで動作し、トレーニング後は外部APIに依存しない

デメリット:

  • ラベル付き学習データが必要——インテントごとに最低数百文
  • 柔軟なレスポンス生成はできず、固定リストからの選択のみ
  • 他の2つの方法に比べて、パイプラインのセットアップが大幅に複雑

方法3:LLMベース(Claude、GPT、Geminiなど)

ユーザーのメッセージをLLMに送信し、自然なレスポンスを受け取る。ボットはコンテキスト、略語、タイプミスを「理解」し、複数のターンにわたって会話履歴を保持できる。

メリット:

  • 自然言語を理解——「pw忘れたんだけど」のような入力も対応
  • コード量が少なく、ロジックを書く代わりにシステムプロンプトに集中できる
  • 再トレーニングやルール修正なしに、動作を簡単に変更できる

デメリット:

  • トークン課金制——スケールアップ前にコスト計算が必要
  • インターネット接続と外部サービスに依存する
  • レスポンスが完全に決定論的でない——時に必要以上に「創造的」になる

プロジェクトに適したアプローチの選び方

実際のプロジェクトで3つすべてを試した結果、選択基準がかなり明確になった:

  • ルールベース:質問の種類が20種類以下で100%の予測可能性が必要な社内ボット——メニューナビゲーションボット、固定FAQ、またはエアギャップ環境向け。
  • 従来のML:ラベル付きデータが既にあり、オフライン実行が必要で、予算が限られているがif/else以上の品質が求められる場合。
  • LLM:その他ほとんどのケース——特に自然な会話が必要、開発期間が短い、または質問パターンを予測しきれない場合。

社内サポートチャットボットにLLMベースをプロダクション環境で適用した——従業員はあらゆる種類の質問をし、一定のパターンに従うことはない。期待以上に安定した結果が得られ、ルールのメンテナンスに時間を取られることもなかった。

PythonでLLMチャットボットを構築する:実践的なステップバイステップガイド

この例ではAnthropic Claude APIを使用する。OpenAIやGeminiでも同様のシンタックスで代替可能——コンセプトはまったく同じだ。

ステップ1:ライブラリのインストール

pip install anthropic

ステップ2:基本チャットボット(シングルターン)

import anthropic

client = anthropic.Anthropic(api_key="your-api-key")

def chat(user_message: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system="あなたはファッションショップXYZのカスタマーサポートアシスタントです。簡潔かつ親切に回答してください。",
        messages=[
            {"role": "user", "content": user_message}
        ]
    )
    return response.content[0].text

# テスト
print(chat("パスワード忘れたんだけどどうすればいい"))
# → "ログインページの「パスワードを忘れた方へ」をクリックして、メールアドレスを入力すれば完了です!"

ステップ3:会話履歴の追加(マルチターン)

実際のチャットボットはコンテキストを記憶する必要がある——ユーザーは毎回最初から説明したくない。コツは、すべての履歴をAPI呼び出しのたびに渡すことだ:

import anthropic
import os

client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

SYSTEM_PROMPT = """あなたは社内ITサポートアシスタントです。
IT、ソフトウェア、社内プロセスに関する質問のみ回答してください。
不明な場合は、推測せず正直に答えてください。"""

def run_chatbot():
    conversation_history = []
    print("社内ITチャットボット。終了するには'quit'と入力してください。\n")

    while True:
        user_input = input("あなた: ").strip()
        if user_input.lower() in ["quit", "exit", "終了"]:
            print("Bot: さようなら!")
            break
        if not user_input:
            continue

        conversation_history.append({"role": "user", "content": user_input})

        try:
            response = client.messages.create(
                model="claude-sonnet-4-6",
                max_tokens=1024,
                system=SYSTEM_PROMPT,
                messages=conversation_history
            )
            bot_reply = response.content[0].text
        except anthropic.APIConnectionError:
            bot_reply = "接続に問題が発生しています。しばらくしてからお試しください。"
        except anthropic.RateLimitError:
            bot_reply = "ボットが過負荷状態です。数秒待ってから再試行してください。"

        conversation_history.append({"role": "assistant", "content": bot_reply})
        print(f"Bot: {bot_reply}\n")

if __name__ == "__main__":
    run_chatbot()

ステップ4:トークンの肥大化を防ぐために履歴を制限する

会話履歴が長い = トークン数が多い = コストが高い。実際には直近N回のやり取りを保持するだけで十分なコンテキストが得られる:

MAX_TURNS = 10  # 直近10ターンを保持(1ターン = ユーザー1つ + アシスタント1つ)

def trim_history(history: list) -> list:
    if len(history) > MAX_TURNS * 2:
        return history[-(MAX_TURNS * 2):]
    return history

# API呼び出し前に使用する:
conversation_history = trim_history(conversation_history)
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    system=SYSTEM_PROMPT,
    messages=conversation_history
)

プロダクション投入前のコスト試算

Claude Sonnetの場合、料金はinputトークンで約$3/100万トークン、outputトークンで$15/100万トークン。10ターンの会話で1ターンあたり約200 inputトークン + 150 outputトークンなら≈$0.003——ユーザー数が少ない社内ボットには十分安価だ。

さらにコストを削減したい場合は、SonnetのかわりにClaude Haikuを使おう——約10倍安く、複雑な推論が不要なシンプルなFAQチャットボットに適している。

まとめ

社内チャットボットや迅速なプロトタイプ開発にはLLMが最も実用的な選択肢だ——コード量が少なく、自然言語処理能力が高く、トレーニングデータも不要。ルールベースは100%の予測可能性やオフライン動作が必要な場合にまだ有効だ。

上記のコードはすぐに動作する——環境変数にAPIキーを設定するだけでいい。さらにアップグレードしたい場合の次のステップ:RAGを統合して社内ドキュメントに基づいた回答ができるようにする。またはFastAPIでラップしてHTTP経由で公開し、Webアプリに組み込む——追加でわずか50行程度のコードで十分だ。

Share: