チャットボットが「使えない」のはアーキテクチャ選択ミス、コードの問題ではない
以前、純粋な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行程度のコードで十分だ。
