O agentic loop e o controle por stop_reason
Objetivos de aprendizagem
- Descrever o ciclo de vida do agentic loop: gather context → take action → verify results → repetir.
- Implementar o controle de fluxo do loop com base no campo
stop_reasonda resposta — continuar emtool_use, encerrar emend_turn. - Saber o que o código deve fazer diante de cada
stop_reason:end_turn,tool_use,max_tokens,pause_turn,refusalestop_sequence. - Distinguir decisão dirigida pelo modelo (o Claude escolhe a próxima ferramenta pelo contexto) de árvores de decisão pré-configuradas.
- Reconhecer os anti-padrões de terminação de loop que a prova adora usar como distrator.
O que é um agente, afinal
Um agente é um modelo que executa um ciclo repetido: reúne contexto, decide uma ação (geralmente chamando uma ferramenta), observa o resultado e decide de novo — até concluir a tarefa. A definição prática usada pela Anthropic: modelos usando ferramentas em um loop, dirigindo dinamicamente seus próprios processos. O ciclo conceitual tem três fases:
- Gather context — o modelo recebe o histórico da conversa, resultados de ferramentas anteriores e instruções de sistema.
- Take action — o modelo responde com blocos
tool_usepedindo a execução de uma ou mais ferramentas, ou com texto final. - Verify results — os resultados das ferramentas voltam ao histórico; o modelo avalia se a tarefa terminou ou se precisa de mais uma iteração.
O ponto central para a prova: quem decide a próxima ação é o modelo, raciocinando sobre o contexto acumulado — não uma árvore de decisão if/else pré-configurada no seu código. Seu código apenas orquestra: envia requisições, executa as ferramentas pedidas e devolve os resultados.
O sinal de controle: stop_reason
Cada resposta da Messages API traz um campo stop_reason dizendo por que o modelo parou de gerar. É esse campo — e somente ele — que dirige o controle de fluxo do loop:
| stop_reason | Significado | O que o seu código faz |
|---|---|---|
end_turn | O modelo concluiu a resposta naturalmente. | Encerra o loop e apresenta a resposta final ao usuário. |
tool_use | O modelo quer executar uma ou mais ferramentas. | Executa as ferramentas pedidas, devolve os tool_result e continua o loop. |
max_tokens | A geração foi truncada pelo limite max_tokens. | Trata como resposta incompleta: aumenta max_tokens e refaz a chamada (ou usa streaming). Nunca executa um tool_use possivelmente truncado. |
pause_turn | Um turno longo (ex.: ferramentas server-side) foi pausado pelo servidor. | Reenvia a conversa com o response.content anexado como turno assistant; o servidor retoma de onde parou. Não adicione uma mensagem "continue". |
refusal | O modelo recusou por razões de segurança. | Encerra o fluxo normal, registra e trata (ex.: mensagem ao usuário, fallback); inspecione stop_details para a categoria. Não executa ferramentas desse turno. |
stop_sequence | Uma sequência de parada customizada foi atingida. | Trata como terminação intencional definida pela aplicação; response.stop_sequence informa qual sequência disparou. |
Regra de ouro da prova: o loop continua enquanto stop_reason == "tool_use" e termina quando stop_reason == "end_turn". Qualquer alternativa que decida a parada olhando o texto da resposta ("verifique se o assistente escreveu 'tarefa concluída'") está errada.
flowchart TD
A[Mensagem do usuário] --> B[messages.create com tools]
B --> C{stop_reason?}
C -- tool_use --> D[Executar ferramentas pedidas]
D --> E[Anexar response.content ao histórico
+ tool_result em mensagem user]
E --> B
C -- pause_turn --> F[Reenviar conversa com o turno pausado]
F --> B
C -- max_tokens --> G[Aumentar max_tokens e repetir]
G --> B
C -- refusal --> H[Tratar recusa / fallback]
C -- stop_sequence --> I[Terminação definida pela aplicação]
C -- end_turn --> J[Resposta final ao usuário]
Esqueleto do loop em Python
O exemplo abaixo mostra o controle de fluxo completo, tratando todos os stop_reason. Note que a condição de parada é sempre o stop_reason — nunca o conteúdo do texto:
import anthropic
client = anthropic.Anthropic()
tools = [{
"name": "lookup_order",
"description": "Busca um pedido pelo numero. Use quando o cliente citar um pedido.",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
}]
def execute_tool(name, tool_input):
# Implementacao real chamaria o backend; aqui, um stub.
return '{"order_id": "%s", "status": "shipped"}' % tool_input.get("order_id", "?")
user_input = "Qual o status do pedido 4412?"
messages = [{"role": "user", "content": user_input}]
max_tokens = 4096
while True:
response = client.messages.create(
model="claude-opus-5",
max_tokens=max_tokens,
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
break # tarefa concluida: sai do loop
if response.stop_reason == "max_tokens":
max_tokens = max_tokens * 2 # resposta truncada: nao execute tools deste turno
continue
if response.stop_reason == "pause_turn":
# turno pausado pelo servidor: reenvie e ele retoma de onde parou
messages.append({"role": "assistant", "content": response.content})
continue
if response.stop_reason == "refusal":
print("Recusa de seguranca; encerrando o fluxo.")
break
if response.stop_reason == "stop_sequence":
print("Parada customizada:", response.stop_sequence)
break
# stop_reason == "tool_use": executar e devolver resultados
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "user", "content": tool_results})
final_text = next((b.text for b in response.content if b.type == "text"), "")
print(final_text)
Atenção com max_tokens + tool_use: se a resposta contém um bloco tool_use mas stop_reason é max_tokens, o input da ferramenta pode ter sido cortado no meio e ainda assim "parsear" como JSON válido parcial. Nunca execute a ferramenta nesse caso — repita a chamada com um limite maior.
Anti-padrões de terminação
Os task statements 1.1 listam explicitamente três anti-padrões — e as questões da prova os usam como distratores plausíveis:
- Parsear linguagem natural para decidir a parada. Procurar frases como "concluído" ou "não há mais nada a fazer" no texto do assistente é frágil: o modelo pode variar a redação, e o texto pode aparecer junto de um
tool_use. O sinal estruturado é ostop_reason. - Cap de iterações como mecanismo primário de parada. Um limite tipo
for i in range(10)é uma rede de segurança legítima contra loops descontrolados (custo/latência), mas nunca o critério principal: ele corta tarefas legítimas mais longas e mascara bugs no loop. Primário =stop_reason; cap = guarda-corpo secundário. - Usar presença de texto como indicador de conclusão. Uma resposta pode conter blocos de texto e blocos
tool_useno mesmo turno ("Vou verificar o pedido..." + chamada de ferramenta). Presença de texto não significa que o modelo terminou.
Pegadinhas da prova
- Distrator típico: "encerre o loop quando a resposta contiver texto do assistente". Errado — texto e
tool_usecoexistem no mesmo turno; sóend_turnindica conclusão. - Distrator típico: "defina um máximo de 5 iterações como condição de término". Cap é proteção secundária contra runaway, não o mecanismo primário; a alternativa correta sempre menciona
stop_reason. - Distrator típico: tratar
pause_turncomo erro ou enviar uma mensagem extra "continue". O correto é reenviar a conversa com o turno pausado anexado — o servidor retoma sozinho. - Distrator típico: executar a ferramenta mesmo com
stop_reason == "max_tokens""porque o JSON parseou". Input truncado pode parsear como objeto parcial válido; repita com limite maior. - Distrator típico: substituir o raciocínio do modelo por uma árvore de decisão fixa de ferramentas ("sempre chame get_customer, depois lookup_order, depois..."). Isso é um workflow pré-configurado, não um agente — e elimina a adaptabilidade que justifica usar um agente.
Resumo em 5 linhas
- Agente = modelo usando ferramentas em um loop: gather context → take action → verify results, com o modelo decidindo a próxima ação.
- O controle de fluxo do loop é dirigido exclusivamente por
stop_reason: continua emtool_use, encerra emend_turn. max_tokens= truncado (repita com limite maior, sem executar tools);pause_turn= reenvie a conversa e o servidor retoma;refusal= trate a recusa;stop_sequence= parada definida pela aplicação.- Anti-padrões: parsear texto para decidir a parada, cap de iterações como mecanismo primário e presença de texto como sinal de conclusão.
- Cap de iterações é aceitável apenas como rede de segurança secundária contra loops descontrolados.
Documentação oficial
Questões de fixação
1. Em um agentic loop com a Messages API, qual condição deve encerrar o loop e apresentar a resposta final ao usuário?
Gabarito: B. O sinal estruturado de conclusão é stop_reason == "end_turn". A está errada porque texto e tool_use coexistem no mesmo turno. C é o anti-padrão de parsear linguagem natural. D usa o cap de iterações como mecanismo primário — ele é apenas rede de segurança.
2. A resposta veio com um bloco tool_use e stop_reason == "max_tokens". O input da ferramenta parseia como JSON válido. O que fazer?
Gabarito: C. max_tokens significa geração truncada — o input pode ter sido cortado e ainda parsear como objeto parcial. A é perigosa (executaria input incompleto). B responderia a um pedido que o próprio modelo não terminou de formular. D descarta a tarefa sem necessidade quando basta repetir com limite maior.
3. Durante um turno com ferramentas server-side, a resposta retorna stop_reason == "pause_turn". Qual é o tratamento correto?
Gabarito: B. Basta reenviar a conversa com o turno pausado anexado — a API detecta o bloco de server tool pendente e retoma automaticamente. A joga fora o progresso do turno. C é desnecessária e pode confundir o modelo. D confunde pausa com conclusão: a tarefa não terminou.
4. (escolha duas) Quais das práticas abaixo são anti-padrões de terminação de agentic loop, segundo o exam guide?
Gabarito: A e D. Parsear texto (A) é frágil e ignora o sinal estruturado; cap como mecanismo primário (D) corta tarefas legítimas. B é exatamente o padrão correto. C é aceitável: o cap como guarda-corpo secundário protege contra loops descontrolados sem substituir o stop_reason.
5. O que diferencia um agente de decisão dirigida pelo modelo de um pipeline pré-configurado?
Gabarito: C. A distinção é quem decide: modelo (agente) vs. código pré-configurado (workflow/pipeline). A está errada — paralelismo existe em ambos e não é o critério. B é falsa: agentes usam ferramentas por definição. D confunde a distinção com local de execução, que é irrelevante aqui.