
【CCA Foundations対策 / Claude API編 #2】マルチターン会話とシステムプロンプト——APIで対話アプリを作る
Anthropic Academyの「Claude APIを使用した構築」コースをもとに解説しています。
1回限りの質問に答えるだけなら client.messages.create() を呼ぶだけでいい。でも実際のアプリでは「前のやり取りを踏まえた返答」が必要になる。この記事では、APIでマルチターン会話を実現するための仕組みと、Claudeの振る舞いをコントロールする2つのパラメータを解説する。
Claude APIはステートレス
最初に押さえておきたいのが、Claude APIは会話履歴を保持しないという点。
リクエストをするたびに、Claudeはその内容を完全に独立したものとして処理する。前のやり取りは一切覚えていない。
試しにこういうことをするとどうなるか:
# 1回目のリクエスト
response1 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1000,
messages=[{"role": "user", "content": "機械学習とは何ですか?"}]
)
# 2回目のリクエスト(前の文脈を引き継ごうとしている)
response2 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1000,
messages=[{"role": "user", "content": "もう1文追加してください"}]
)
print(response2.content[0].text)
出力例:
# 「機械学習」とは無関係な文章が返ってくる
空は青く、風が心地よく吹いていました。
「もう1文追加して」という指示に対して、Claudeは何について書けばいいかわからないので、脈絡のない文を返してくる。
会話履歴を手動で管理する
解決策はシンプル。すべてのやり取りをリストとして保持し、毎回のリクエストにそのリストをそのまま渡す。
1回目のリクエスト
送信: [user: "機械学習とは?"]
受信: [assistant: "機械学習とは..."]
2回目のリクエスト
送信: [user: "機械学習とは?"]
[assistant: "機械学習とは..."]
[user: "もう1文追加してください"] ← 全履歴を毎回送る
受信: [assistant: "また、機械学習は..."]
Claudeはリクエストごとに渡された messages リストだけを見て返答する。前の会話を「覚えている」のではなく、毎回全履歴を受け取って文脈を理解している。
messages = []
# ユーザーの発言を追加
messages.append({"role": "user", "content": "機械学習とは何ですか?"})
# Claudeのレスポンスを取得
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1000,
messages=messages
)
answer = response.content[0].text
# Claudeの発言も履歴に追加
messages.append({"role": "assistant", "content": answer})
# 次の発言を追加して、履歴ごと送る
messages.append({"role": "user", "content": "もう1文追加してください"})
response2 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1000,
messages=messages # 全履歴を渡す
)
print(response2.content[0].text)
出力例:
また、機械学習はデータの量が増えるほど予測精度が向上する傾向があります。
今度は機械学習についての続きが返ってくる。
ヘルパー関数でスッキリさせる
毎回 append と content[0].text を書くのは冗長なので、ヘルパー関数にまとめる。
import anthropic
from dotenv import load_dotenv
load_dotenv()
client = anthropic.Anthropic()
model = "claude-sonnet-4-6"
def add_user_message(messages: list, text: str) -> None:
messages.append({"role": "user", "content": text})
def add_assistant_message(messages: list, text: str) -> None:
messages.append({"role": "assistant", "content": text})
def chat(messages: list) -> str:
response = client.messages.create(
model=model,
max_tokens=1000,
messages=messages,
)
return response.content[0].text
使い方:
messages = []
add_user_message(messages, "機械学習とは何ですか?")
answer = chat(messages)
print(answer)
add_assistant_message(messages, answer)
add_user_message(messages, "もう1文追加してください")
final_answer = chat(messages)
print(final_answer)
出力例:
# 1回目
機械学習とは、データから自動的にパターンを学習し、予測や判断を行うAI技術の一分野です。
# 2回目(前の文脈を踏まえた続き)
また、機械学習はデータの量が増えるほど予測精度が向上する傾向があります。
会話の流れが読みやすくなる。add_assistant_message を忘れずに呼ぶのがポイント。これを省略すると、Claudeは自分の前の発言を知らない状態になる。
システムプロンプトでキャラクターを設定する
単なる「賢いAPI」ではなく、特定の役割を持つアシスタントを作りたいときに使うのがシステムプロンプト。
system パラメータに文字列で渡す:
system_prompt = """
あなたはフレンドリーなカフェのスタッフです。
メニューや店舗情報に関する質問には丁寧に答えてください。
関係のない話題は「申し訳ありませんが、カフェに関するご質問のみ承っております」と断ってください。
"""
response = client.messages.create(
model=model,
max_tokens=1000,
messages=[{"role": "user", "content": "今日のおすすめは何ですか?"}],
system=system_prompt
)
print(response.content[0].text)
システムプロンプトなしとありで、返ってくる回答がこれだけ変わる:
# システムプロンプトなし
今日のおすすめについてですが、何のおすすめをお探しでしょうか?
レストラン、観光スポット、商品など、もう少し詳しく教えていただけますか?
# システムプロンプトあり(カフェスタッフとして回答)
本日のおすすめはキャラメルラテです!甘さと苦みのバランスが絶妙で、
スタッフ一押しの一杯となっております。ぜひお試しください☕
同じ質問でも、役割を与えるだけで回答のトーンと内容が大きく変わる。
柔軟な chat 関数にする
システムプロンプトの有無をオプションにしておくと再利用しやすい:
def chat(messages: list, system: str | None = None) -> str:
params = {
"model": model,
"max_tokens": 1000,
"messages": messages,
}
if system:
params["system"] = system
response = client.messages.create(**params)
return response.content[0].text
注意点: system=None をAPIに渡すとエラーになる。system キーは値がある場合だけ params に追加する。
使い方:
# システムプロンプトなし
answer = chat(messages)
# システムプロンプトあり
answer = chat(messages, system=system_prompt)
Temperatureで出力のランダム性を制御する
前回の記事で触れたように、Claudeはテキストを生成するとき「次にどのトークンが来るか」を確率で計算し、その確率に基づいて選択(サンプリング)している。Temperature はこのサンプリングの確率分布を調整するパラメータ(0〜1の小数)。
- Temperature 0 → 最も確率の高いトークンをほぼ確実に選ぶ。毎回ほぼ同じ出力になる
- Temperature 1 → 確率が分散し、低確率のトークンも選ばれやすくなる。出力にバラエティが生まれる
| Temperature | 挙動 | 向いているタスク |
|---|---|---|
| 0.0〜0.3 | 最も確率の高いトークンを優先。出力が安定・一貫 | コード生成・データ抽出・事実確認 |
| 0.4〜0.7 | バランス型 | 要約・教育コンテンツ・問題解決 |
| 0.8〜1.0 | 低確率のトークンも選ばれやすくなり、多様性が増す | ブレスト・クリエイティブライティング・マーケティングコピー |
デフォルトは 1.0。
コードへの組み込み
def chat(messages: list, system: str | None = None, temperature: float = 1.0) -> str:
params = {
"model": model,
"max_tokens": 1000,
"messages": messages,
"temperature": temperature,
}
if system:
params["system"] = system
response = client.messages.create(**params)
return response.content[0].text
使い方の比較:
messages = []
add_user_message(messages, "映画のあらすじを1つ考えてください")
# 安定した出力
predictable = chat(messages, temperature=0.0)
print(predictable)
# バラエティのある出力
creative = chat(messages, temperature=1.0)
print(creative)
temperature=0.0 と 1.0 を比べると、出力の多様性が変わる:
# temperature=0.0(何度実行してもほぼ同じ)
主人公の若き探偵が、謎の失踪事件を追ううちに巨大な陰謀に巻き込まれていくサスペンス。
# temperature=1.0(実行するたびに変わる)
1回目: 火星に移住した人類が、地球に残した記憶をめぐって争うSFドラマ。
2回目: 江戸時代の料理人が現代にタイムスリップし、グルメ界を席巻するコメディ。
3回目: 孤島に流れ着いた音楽家が島民と交流しながら最後の交響曲を完成させる物語。
補足: Temperatureを上げても「必ず違う出力になる」わけではない。あくまで低確率トークンが選ばれやすくなるだけなので、たまたま同じ出力になることもある。
まとめ
- Claude APIはステートレス。会話履歴は自分でリストに蓄積して毎回送る
add_user_message/add_assistant_message/chatのヘルパー関数でコードをシンプルに保てる- システムプロンプトで役割・トーン・制約を設定し、アプリの用途に合ったClaudeを作れる
system=NoneはAPIエラーになるため、値がある場合のみパラメータに追加する- Temperature(0〜1)で出力のランダム性を調整。事実系タスクは低め、クリエイティブ系は高めが基本
次回はレスポンスのストリーミング配信と構造化出力(プリフィル・JSONの安定生成)を扱う。