前のレッスンでは、1回だけ質問して1回だけ答えが返る道具を作りました。実際のアプリでは、1往復では終わりません。利用者が質問を重ね、前の答えを踏まえて次の質問をする、という会話の形が要ります。
ここで最初につまずきやすいのが、Claude APIには「覚えている」という機能がないという点です。
Claudeはステートレス
Claude APIは、リクエストとリクエストの間に何も覚えていません。2回目の呼び出しで「さっきの話の続きだけど」と送っても、Claude側には「さっき」に相当する記憶が一切残っていません。
覚えているように見せるための仕組みは、呼び出す側のコードが担います。やることは単純で、それまでのやり取り全部を、messagesの配列に積んで毎回送り直すだけです。
1往復だけの呼び出し vs 会話として積み上げる呼び出し
messages = [{"role": "user", "content": "在庫管理の手順を教えて"}]
2回目の質問を送るとき、このmessagesをそのまま使い回すと、Claudeは1回目に何を答えたかを知らないまま次の質問に答えることになります。話がかみ合いません。
messages = [
{"role": "user", "content": "在庫管理の手順を教えて"},
{"role": "assistant", "content": "(1回目の答え全文)"},
{"role": "user", "content": "その中の2番目の手順だけ詳しく"},
]
1回目の質問と答えをそのまま残し、2回目の質問を末尾に足してから送ります。Claudeは配列全体を読んでから答えるので、話の続きとして扱われます。
送るたびに履歴全体を送り直すということは、会話が長くなるほど、1回あたりに送るトークン数も増えるということです。費用と応答速度に直結するので、このコースの後半(レッスン07)で扱うキャッシュの仕組みが効いてきます。
会話を積み上げるコード
自分で毎回配列を書き直すのは面倒なので、短いヘルパー関数にまとめておくと扱いやすくなります。社内の問い合わせ対応メモを整理する道具を例にします。
import anthropic
import os
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
messages = []
def add_user(text):
messages.append({"role": "user", "content": text})
def add_assistant(content):
messages.append({"role": "assistant", "content": content})
def ask(text):
add_user(text)
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=messages,
)
add_assistant(response.content)
return next(b.text for b in response.content if b.type == "text")
print(ask("問い合わせ対応メモを箇条書きで整理して:\n・A社から納期の問い合わせ、来週回答\n・B社は請求書の再発行希望"))
print(ask("いま整理した内容を、上司に送るメール文にして"))
2回目の ask() は、1回目のやり取りを覚えた状態で答えます。add_assistant には response.content をそのまま渡している点に注意してください。文字列だけを取り出して渡すのではなく、ブロックの配列ごと履歴に戻します。この先ツールを使わせる場面では、ブロックの中身が会話の整合性そのものに関わってくるためです。
systemパラメータ: 役割と決まりごとを分けて渡す
messages に積むのは、あくまでuserとassistantの往復です。それとは別に、Claudeにどう振る舞ってほしいかをまとめて指定する場所があります。system というトップレベルの引数です。
systemに書くこと・messagesに書くこと
system
役割、口調、守ってほしい制約。会話全体を通じて効く
messages
その時々のやり取り本体。毎回の質問と答え
問い合わせメモの整理役に、決まった形式を守らせる例です。
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system="あなたは社内の問い合わせ対応を整理するアシスタントです。"
"出力は必ず「会社名/要件/期限」の3項目の箇条書きにしてください。"
"情報が読み取れない項目は「不明」と書いてください。",
messages=messages,
)
system に書いた決まりごとは、会話が何往復続いても効き続けます。毎回のuserメッセージに「箇条書きで、3項目で」と書き直す必要がなくなります。
補足
system は空でも渡せる
system を使わない場合は、そもそも引数を渡さなければよいだけです。多くのSDKでは、値がないときにこの引数自体を省略する書き方をします。空文字を無理に渡す必要はありません。
やってみよう
演習1: systemとmessagesを分ける
自分が作ろうとしている道具を1つ思い浮かべ、「会話全体を通じて守ってほしい決まりごと」と「その時々の質問本体」を分けて書き出してください。前者がsystem、後者がmessagesに入ります。
演習2: 会話が長くなったときの見積もり
その道具で会話が10往復続いたとき、毎回どれくらいの量を送り直すことになるか考えてください。履歴が積み上がるほど費用も応答時間も伸びる、という感覚をつかむための演習です。
今日のまとめ
3行で振り返ります。
- Claude APIはステートレス。会話の記憶は呼び出す側がmessagesの配列で持ち運ぶ
- 2回目以降の呼び出しでは、それまでのやり取り全部を積んだ配列を送り直す
- systemは役割や出力の決まりごと専用。毎回のuserメッセージと分けて書く
次のレッスンでは、技法の前に置くべき評価の仕組みを扱います。頼み方を工夫する前に、その工夫が本当に効いたかをどう確かめるかという話です。
セルフチェック
1. Claude APIの会話の記憶について正しい説明はどれですか。
2. systemパラメータの使いどころとして正しいものはどれですか。
3. 会話を積み上げるコードで、2回目の呼び出しのassistantの履歴に渡すべきものはどれですか。