Kończenie na podstawie stop_reason
Pozwól, aby end_turn kończył pętlę, zamiast dopasowywać ciągi znaków
Kończenie na podstawie stop_reason to bezpłatna lekcja Claude Architect na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Claude Architect, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Claude Architect zawiera 4 lekcji w sumie.
Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.
The Loop Needs an Exit
An agentic loop is simple: you send a request, Claude responds, and you decide whether to keep going. The hard question is when to stop.
Every API response carries a stop_reason field. This is the model's own signal about why it stopped generating. Your loop should listen to that signal — not guess by reading the words in the reply.
This lesson teaches one rule that separates robust agents from fragile ones: let end_turn end the loop, not your string matching.
The Four Stop Reasons
Claude returns one of four stop_reason values on every turn:
end_turn— the model finished its response naturally. The task turn is complete.tool_use— the model wants to call a tool. Run it, append the result, and continue.max_tokens— output was truncated by yourmax_tokenslimit.stop_sequence— a custom stop sequence you configured was hit.
These four values are a complete, reliable contract. Your control flow should branch on them directly.
resp = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
print(resp.stop_reason) # end_turn | tool_use | max_tokens | stop_sequenceThe Anti-Pattern: Parsing Text
A tempting shortcut is to read the model's text and look for a keyword like "done" or "finished" to decide the loop is over.
This is a classic anti-pattern. Text is probabilistic. The model might say "I'm done thinking, now let me call a tool" — and your matcher stops too early. Or it phrases completion differently and your loop runs forever.
Never parse text for completion signals. The structured stop_reason exists precisely so you don't have to.
# ANTI-PATTERN: do NOT do this
text = resp.content[0].text
if "done" in text.lower():
break # fragile, unreliable, exam-wrongThe Canonical Loop
Here is the correct shape of the loop. You inspect stop_reason each turn. When it is tool_use, you run the tools and append their results to the conversation. You repeat until stop_reason is end_turn.
Notice the loop is driven entirely by the structured signal — no text inspection decides termination.
while True:
resp = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
break
if resp.stop_reason == "tool_use":
results = run_tools(resp.content)
messages.append({"role": "user", "content": results})
continueFull History, Every Turn
One reason the loop works: the model keeps no state between requests. Each API call must include the FULL messages history.
That is why, after a tool_use turn, you append the assistant's tool-call content AND the tool results back into messages before the next request. The model re-reads the entire conversation and decides whether more tools are needed or it can finish with end_turn.
Drop history, and the model loses the thread — it can't reach a coherent end_turn.
# Each request resends EVERYTHING
messages = [
{"role": "user", "content": "Refund order 4471."},
{"role": "assistant", "content": [tool_use_block]}, # prior turn
{"role": "user", "content": [tool_result_block]}, # prior turn
]
resp = client.messages.create(model=MODEL, max_tokens=1024,
tools=tools, messages=messages)tool_use Is Not a Stop
A common mistake is treating tool_use as a terminal state. It is not. It means "pause, run this tool, then come back to me."
When you see tool_use, you:
- Execute the requested tool(s) in your own code.
- Append the
tool_resultblocks tomessages. - Send the request again so the model can continue.
Only end_turn means the work for this turn is genuinely finished.
if resp.stop_reason == "tool_use":
tool_results = []
for block in resp.content:
if block.type == "tool_use":
output = dispatch(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
messages.append({"role": "user", "content": tool_results})
# loop continues -> next requestDecisions Are Model-Driven
The deeper principle: let the model decide when it is done. The model has the full context — the user's goal, the tool results, the conversation so far. It is better positioned than a hard-coded rule to judge whether the task is complete.
Your job as the architect is to provide good tools and clear instructions, then trust the end_turn signal. Reserve hard-coded control flow for guarantees you cannot leave to probability.
The Iteration Cap Is a Safety Net
You should still add a maximum-iteration guard — but understand its role. An iteration cap is a safety net to prevent runaway loops or cost blowouts. It is NOT the primary stop mechanism.
The primary stop is always end_turn. The cap only fires in the rare pathological case where the model never converges. If your loop relies on the cap to end normally, your design is broken.
MAX_ITERS = 20 # safety net, NOT the normal exit
for i in range(MAX_ITERS):
resp = client.messages.create(model=MODEL, max_tokens=1024,
tools=tools, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
break # normal exit
# ... handle tool_use ...
else:
log.warning("Hit iteration cap without end_turn")Handling max_tokens
max_tokens is a distinct case that needs its own handling. It means the response was truncated mid-generation — the model did not finish its thought.
Treating this as a clean completion would silently cut off the agent's work. Depending on your design you might raise max_tokens, ask the model to continue, or flag the turn. What you must NOT do is fall through and assume the task is done.
if resp.stop_reason == "max_tokens":
# output was cut off - NOT a completion
log.warning("Response truncated; consider raising max_tokens or continuing")
# handle explicitly; do not treat as end_turnWhere Hard Code Belongs
If model-driven decisions are the default, when do you reach for deterministic code?
For guarantees — outcomes that must hold every single time regardless of the model's judgment. Examples: a precondition that blocks a refund until get_customer returns a verified ID, or a hook that rejects any refund over a policy threshold.
These are 100% deterministic enforcement points. Termination, by contrast, is a model-driven decision you read from stop_reason. Don't confuse the two: hard-code guarantees, trust end_turn for flow.
Putting It Together
A production-grade agentic loop combines all the pieces:
- Branch on
stop_reason— never on text. - Resend full history each turn (the model is stateless).
tool_use→ run, append, continue.end_turn→ stop.- Handle
max_tokensexplicitly — truncation is not completion. - Keep an iteration cap as a safety net only.
This is the backbone of every reliable agent you'll architect.
for _ in range(MAX_ITERS):
resp = client.messages.create(model=MODEL, max_tokens=2048,
tools=tools, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
break
if resp.stop_reason == "max_tokens":
handle_truncation(resp); break
if resp.stop_reason == "tool_use":
messages.append({"role": "user",
"content": run_tools(resp.content)})
continueQuick Check: Ending the Loop
A support agent built on the Agent SDK sometimes loops forever and sometimes stops before calling a needed tool. The loop currently breaks when the assistant's text contains the word "resolved". What is the correct fix?
Recap: Trust the Signal
Key takeaways:
- Terminate on
stop_reason, never by parsing text for words like "done" or "resolved". end_turnends the loop;tool_usemeans run tools, append results, and continue.- Handle
max_tokensexplicitly — truncation is not completion. - The model is stateless: resend the full message history every turn.
- Termination is a model-driven decision; the iteration cap is only a safety net.
- Reserve hard-coded enforcement for guarantees (preconditions, hooks), not for ending the loop.
Let end_turn end the loop. That single discipline makes your agents predictable and production-ready.
Ucz się Python dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 26
- Lekcje
- 104
Często zadawane pytania
Czy lekcja „Kończenie na podstawie stop_reason” jest bezpłatna?
Tak — pełny tekst „Kończenie na podstawie stop_reason” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Claude Architect, przejdź na CoddyKit PRO. Kurs Claude Architect zawiera 4 lekcji w sumie.
Co nauczysz się w „Kończenie na podstawie stop_reason”?
Pozwól, aby end_turn kończył pętlę, zamiast dopasowywać ciągi znaków Ćwiczysz Claude Architect z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Claude Architect?
Nie wymagamy żadnego doświadczenia. Claude Architect w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Kończenie na podstawie stop_reason”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Claude Architect?
Tak. Każda lekcja Claude Architect zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Główna pętla
- Kończenie na podstawie stop_reason
- Antywzorzec: analizowanie tekstu w celu wykrycia zakończenia
- Antywzorzec: arbitralne limity iteracji