C.W.K.
Stream
Lesson 05 of 06 · published

Stop Sequences, Stop Reasons, and Forcing Structure

~14 min · stop-sequences, stop-reason, json, structured-output

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

The four stop reasons

Every response includes a stop_reason telling you why generation halted. The four you will see in production: end_turn (model finished naturally), max_tokens (you ran out of budget), stop_sequence (your custom stop string fired), tool_use (the model wants to call a tool — handle separately). A response with stop_reason='max_tokens' is truncated; treat it differently from end_turn.

Stop sequences for structured output

You can pass stop_sequences=["</answer>", "END"] and the API halts as soon as any of those strings appears. Combined with prompt design, this is a lightweight way to extract structured fragments. Heavier structure should use tool use or schema-prompted JSON, not stop sequences.

JSON output without tool use

For pure JSON output, the most reliable pattern is: tell the system prompt the exact JSON schema, prefill the assistant turn with {, and parse defensively. Prefilling the assistant message biases the model into the shape you want without any tool overhead.

Principle: Branch on stop_reason, not on response length. Truncated and complete responses look the same; only the reason tells you which is which.

Code

Branching on stop_reason·python
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    messages=[{"role": "user", "content": "Write 30 sentences about JSON."}],
)

match response.stop_reason:
    case "end_turn":
        save(response.content[0].text)
    case "max_tokens":
        # truncated — either raise budget or chunk the request
        retry_with_higher_budget(response)
    case "stop_sequence":
        partial = response.content[0].text
        log.info("stopped on user-defined sequence")
    case "tool_use":
        dispatch_tool_calls(response)
Forcing JSON via prefill·python
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    system='Respond with a single JSON object matching {"intent": str, "confidence": float}. No prose.',
    messages=[
        {"role": "user", "content": "User said: 'cancel my subscription'"},
        {"role": "assistant", "content": "{"},  # prefill — model continues from here
    ],
    stop_sequences=["}\n"],
)
import json
payload = json.loads("{" + response.content[0].text)  # add back the prefill
print(payload)

External links

Exercise

Take an existing Claude prompt that returns JSON via prose. Convert it to (1) prefill + stop_sequences and (2) tool use with a JSON Schema. Compare parse-failure rates over 50 calls.
Hint
If the schema has more than two levels of nesting, tool use will likely win on parse rate.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.