Max Tokens and Stop Sequences

Two Ways Generation Ends Early

  • max_tokens: a hard limit on how many tokens the model may write. It is required in the Claude API. If the limit is hit, stop_reason is "max_tokens" and the text is cut off mid-sentence.
  • stop sequences: strings that end generation as soon as they appear (for example "\n\nCustomer:"). stop_reason becomes "stop_sequence".
  • Normal finish: stop_reason is "end_turn".

Simulated Generation

Example

A stand-in model "writes" a fixed reply token by token; the loop shows how each limit stops it:

reply_tokens = ["Your", " order", " ships", " today", ".", "\n", "Customer", ":", " thanks"]

def generate(max_tokens, stop_sequences=()):
    text = ""
    for i, tok in enumerate(reply_tokens):
        if i == max_tokens:
            return text, "max_tokens"
        text += tok
        for s in stop_sequences:
            if s in text:
                return text[: text.index(s)], "stop_sequence"
    return text, "end_turn"

for args in [(3, ()), (50, ("\nCustomer:",)), (50, ())]:
    text, reason = generate(*args)
    print(f"max_tokens={args[0]:<3} stop={args[1]!r:18} -> {text!r} ({reason})")

Output

max_tokens=3   stop=()                 -> 'Your order ships' (max_tokens)
max_tokens=50  stop=('\nCustomer:',)   -> 'Your order ships today.' (stop_sequence)
max_tokens=50  stop=()                 -> 'Your order ships today.\nCustomer: thanks' (end_turn)

In the Claude API

response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=300,
    stop_sequences=["</answer>"],
    messages=[{"role": "user", "content": "Answer inside <answer> tags: what is ShopEase's return window?"}],
)
print(response.stop_reason, response.stop_sequence)
if response.stop_reason == "max_tokens":
    print("Reply was cut off - raise max_tokens or ask for a shorter answer")

Run it

python stop_demo.py

Example output

stop_sequence </answer>