ankuro.dev
← ブログ一覧に戻る
【CCA Foundations対策 / Claude API編 #3】レスポンス制御と構造化出力——ストリーミング・プリフィル・JSON生成
2026-04-01#Claude#API#Python#生成AI#入門#Claude Certified Architect

【CCA Foundations対策 / Claude API編 #3】レスポンス制御と構造化出力——ストリーミング・プリフィル・JSON生成

Anthropic Academyの「Claude APIを使用した構築」コースをもとに解説しています。

APIから返ってくるレスポンスを「どう受け取るか」を工夫するだけで、アプリの使い勝手は大きく変わる。この記事ではストリーミング構造化出力という2つのテクニックを扱う。


ストリーミング——生成しながらリアルタイムで表示する

Claude APIのデフォルト動作は、生成が完全に終わってからレスポンスを返す。長い文章だと10〜30秒かかることもあり、その間ユーザーはただ待つことになる。

ストリーミングを使うと、生成されたテキストをチャンクごとに受け取り、即時で表示できる。ChatGPTやClaude.aiで文字が1つずつ流れてくる、あの動作と同じ仕組み。

ストリーミングなしとありで、ユーザー体験はこれだけ変わる:

【ストリーミングなし】
実行 → (10〜30秒の無音)→ テキスト全体がどっと表示される

【ストリーミングあり】
実行 → 「Python」「は」「読み」「やすい」「構文」「が」... と文字が流れ始める

イベントの種類

ストリーミング時のレスポンスはイベントの連続として届く:

message_start        → 生成開始の通知(テキストはまだない)
content_block_start  → テキストブロックの開始
content_block_delta  → 実際のテキストチャンク(ここが本体)
content_block_stop   → テキストブロックの終了
message_stop         → 生成完了

実装では content_block_delta のチャンクを拾えばいい。

実装:client.messages.stream()

client.messages.stream() を使うと、text_stream プロパティでテキストチャンクだけを直接取り出せる:

import anthropic
from dotenv import load_dotenv

load_dotenv()

client = anthropic.Anthropic()
model = "claude-sonnet-4-6"

messages = [{"role": "user", "content": "Pythonの良いところを3つ挙げてください"}]

with client.messages.stream(
    model=model,
    max_tokens=1000,
    messages=messages,
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

flush=True を忘れないようにする。これがないと出力がバッファに溜まり、まとめて表示されてしまいストリーミングの意味がなくなる。

ターミナルで実行すると、こんなふうに文字が流れてくる:

Pythonの良いところを3つ挙げます。

1. **読みやすい構文**
   Pythonはインデントで構造を表現するため...

全部終わるのを待たず、生成されるそばから文字が現れる。

完全なメッセージをDBに保存する

ストリーミング中に得られるのは断片的なチャンクだけ。チャンクを順番に受け取っても、どこで終わるかわからないため、そのままDBに1レコードとして保存するのは難しい。

get_final_message() を使うと、生成が完了した時点で完全なレスポンスをまとめて取得できる。「画面にはリアルタイム表示しながら、生成完了後にまとめてDBへ保存する」という両立が可能になる。

# get_final_message() なし
# → チャンクは流れるが、完全な文字列が手元に残らない
with client.messages.stream(...) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
# ← ここでstreamが閉じる。full_textを取り出す手段がない

# get_final_message() あり
# → リアルタイム表示しつつ、完全なテキストも取得できる
with client.messages.stream(
    model=model,
    max_tokens=1000,
    messages=messages,
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)  # リアルタイム表示

    final_message = stream.get_final_message()
    full_text = final_message.content[0].text  # 完全なテキスト → DBに保存できる
    print(f"\n\n入力トークン: {final_message.usage.input_tokens}")
    print(f"出力トークン: {final_message.usage.output_tokens}")

get_final_message()with ブロックの中、ループが終わったあとに呼ぶのがポイント。生成完了後に呼ばないと完全なメッセージが得られない。


プリフィルとストップシーケンス——構造化データ生成の前提知識

次のセクションで扱う構造化データの生成は、この2つのテクニックの組み合わせで成り立っている。先に単体で理解しておく。

プリフィル(アシスタントメッセージの先行入力)

messages リストの末尾に role: "assistant" のメッセージを追加すると、Claudeはそれを「自分がすでに書き始めた内容」と認識し、その続きから生成する。

messages = [
    {"role": "user", "content": "コーヒーとお茶、どちらが好きですか?"},
    {"role": "assistant", "content": "コーヒーの方が好きです。なぜなら"},  # プリフィル
]

response = client.messages.create(
    model=model,
    max_tokens=200,
    messages=messages,
)
print(response.content[0].text)

実行すると、プリフィルの続きが返ってくる:

コーヒーには豊かな香りと深みのある味わいがあり、
朝の目覚めにも仕事中の集中にも最適だからです。

プリフィルなしで「コーヒーとお茶どちらが好きですか?」と聞いたとすると、どちらが好きかは不定で、お茶派の回答が返ることもある。プリフィルで「コーヒーの方が好きです。なぜなら」を先置きすることで、必ずコーヒー推しの理由を語る回答になる。

ストップシーケンス

特定の文字列が生成された瞬間に出力を止めるパラメータ。止まった文字列自体はレスポンスに含まれない。

response = client.messages.create(
    model=model,
    max_tokens=200,
    messages=[{"role": "user", "content": "1から10まで数えてください"}],
    stop_sequences=["五"],  # 「五」が出た時点で停止
)
print(response.content[0].text)

出力:

一、二、三、四、

「五」が現れようとした瞬間に生成が止まる。「五」自体はレスポンスに含まれない。

ストップシーケンスなしで同じ質問をすると「一、二、三、四、五、六、七、八、九、十」と最後まで出力される。どこで止めるかをコードで制御できるのがポイント。


構造化データの生成——クリーンなJSONを取り出す

アプリでJSONやコードをClaudeに生成させると、デフォルトでは余計なものがついてくる。

たとえば「本の情報をJSONで返して」と頼むと、こんなレスポンスが返ってくる:

以下のJSONをご確認ください:

```json
{
  "title": "吾輩は猫である",
  "author": "夏目漱石",
  "year": 1905,
  "genre": "小説"
}

上記のJSONを使用してください。


JSONは正しいが、マークダウンのコードブロックと説明文が混ざっていて、そのまま `json.loads()` に渡すとエラーになる。

### プリフィル + ストップシーケンスで解決する

2つのテクニックを組み合わせると、純粋なデータだけを取り出せる:

```python
import json

def generate_json(prompt: str) -> dict:
    messages = []
    messages.append({"role": "user", "content": prompt})
    messages.append({"role": "assistant", "content": "```json"})  # プリフィル

    response = client.messages.create(
        model=model,
        max_tokens=1000,
        messages=messages,
        stop_sequences=["```"],  # 閉じコードブロックで停止
    )

    raw = response.content[0].text.strip()
    return json.loads(raw)

仕組みをステップで追うと:

1. ユーザーメッセージ: 「本の情報をJSONで返して」
2. アシスタントプリフィル: "```json" を先置き
   → Claudeは「自分がコードブロックを書き始めた」と認識
3. Claudeが生成するのはJSONの中身だけ
4. 閉じ ``` が生成されようとした瞬間にストップシーケンスが発動
   → ``` より前で生成が止まる

使い方と出力:

result = generate_json(
    "本の情報をJSONで生成してください。"
    "フィールド: title(文字列), author(文字列), year(数値), genre(文字列)"
)
print(result)
# 出力(マークダウンも説明文もない、クリーンなdict)
{'title': '吾輩は猫である', 'author': '夏目漱石', 'year': 1905, 'genre': '小説'}

json.loads() が通る状態のデータが直接返ってくる。改行ありの整形出力が必要な場合は json.dumps() を使う:

print(json.dumps(result, indent=2, ensure_ascii=False))
# {
#   "title": "吾輩は猫である",
#   "author": "夏目漱石",
#   "year": 1905,
#   "genre": "小説"
# }

JSONに限らず使える

このパターンはJSON以外でも応用できる:

欲しいもの プリフィル ストップシーケンス
Pythonコード ```python ```
箇条書きリスト - \n\n
CSVデータ name,value\n \n\n

「Claudeが自然に閉じようとする区切り文字」をストップシーケンスに設定するのがコツ。


まとめ

  • ストリーミングで生成中のテキストをリアルタイム表示できる。client.messages.stream() + text_stream が最もシンプルな実装
  • flush=True がないとバッファに溜まって即時表示にならない
  • 完全なレスポンスが必要なら with ブロック内で get_final_message() を呼ぶ
  • プリフィルでアシスタントの発言の続きをClaudeに生成させ、出力の方向を制御できる
  • ストップシーケンスで任意の文字列が出た時点で生成を止められる(その文字列自体はレスポンスに含まれない)
  • 2つを組み合わせるとJSONやコードをクリーンに取り出せる

次回はプロンプトエンジニアリングとEval——プロンプトの品質を数値で測って改善する方法を扱う。


← 前回:マルチターン会話とシステムプロンプト 【振り返りクイズ】#1〜#3の理解度チェック