Pular para o conteúdo

O ciclo Thought → Action → Observation

Mapa da Unidade 1

Até aqui vimos as peças separadas:

Agora vamos juntar tudo em um único mecanismo — o ciclo. Ele é o coração de qualquer Agent, e depois de entendê-lo você vai reconhecer a mesma estrutura em todos os frameworks.

Os três componentes

O trabalho de um Agent é um ciclo contínuo:

  1. Thought — o LLM decide qual deve ser o próximo passo.
  2. Action — o Agent executa esse passo, chamando uma Tool com os argumentos escolhidos.
  3. Observation — o resultado da Tool volta para o modelo, que o lê antes de decidir de novo.

Ciclo Thought-Action-Observation

Em termos de programação, o Agent é um while: o laço continua até o objetivo ser cumprido — ou até bater no limite de passos que você definiu.

while não_terminou:
    pensamento = modelo.pensar(historico)
    resultado  = executar(pensamento.tool, pensamento.argumentos)
    historico.append(resultado)

Isso é literalmente o que um framework de agentes faz. O resto é conveniência.

System prompt com o ciclo

Repare no que a system message precisa conter para esse laço funcionar:

Sim: o ciclo também é texto no prompt. O modelo não tem um "modo agente". Ele foi instruído, em linguagem natural, a escrever nesse formato — e o programa em volta é que dá sentido a isso.

O ciclo, no caso do Alfred judiciário

Vamos acompanhar um pedido do começo ao fim.

Usuário: "Alfred, traga os 3 processos mais recentes sobre violência doméstica."

Alfred tem três Tools: consultar_assunto, buscar_processos e detalhar_processo.

Passo 1 — Thought

O modelo escreve seu raciocínio:

Thought: O usuário quer processos sobre violência doméstica, mas a base de processos é consultada por código de assunto, não por nome. Preciso primeiro converter o termo em código. Tenho a Tool consultar_assunto para isso.

Repare no que aconteceu: ele decompôs o pedido. Ninguém disse que existe um código de assunto intermediário — isso veio da descrição das Tools.

Passo 2 — Action

Com base no Thought, ele produz a chamada, no formato combinado:

{
  "action": "consultar_assunto",
  "action_input": {
    "termo": "violência doméstica"
  }
}

Isso é texto gerado pelo modelo. Nada foi executado ainda. O seu programa lê esse texto, reconhece a chamada e roda a função Python de verdade.

Passo 3 — Observation

A função retorna, e o resultado é acrescentado à conversa:

Observation: 1001

Essa linha entra no prompt como uma nova mensagem. É o retorno do mundo real — a confirmação de que a ação funcionou, e o dado de que ele precisava.

Passo 4 — novo Thought

O modelo lê a Observation e decide o próximo passo:

Thought: Obtive o código 1001. Agora posso buscar os processos desse assunto, ordenados do mais recente para o mais antigo, limitando a 3 como o usuário pediu.

Passo 5 — nova Action

{
  "action": "buscar_processos",
  "action_input": {
    "codigo_assunto": 1001,
    "limite": 3
  }
}

Passo 6 — nova Observation

Estes são os dados reais da base sintética do curso:

Observation: [
  {"numero_processo": "0000184-40.2026.8.19.0001", "data_distribuicao": "2026-01-10",
   "situacao": "Arquivado", "orgao_julgador": "2ª Vara de Violência Doméstica e Familiar contra a Mulher"},
  {"numero_processo": "0000031-41.2025.8.19.0001", "data_distribuicao": "2025-08-23",
   "situacao": "Arquivado", "orgao_julgador": "2ª Vara de Violência Doméstica e Familiar contra a Mulher"},
  {"numero_processo": "0000116-27.2025.8.19.0001", "data_distribuicao": "2025-06-02",
   "situacao": "Suspenso", "orgao_julgador": "1ª Vara de Violência Doméstica e Familiar contra a Mulher"}
]
Atenção

Vale parar nesta Observation, porque tem uma ausência dentro dela.

Consultando a base sem filtro de sigilo, o terceiro processo mais recente desse assunto seria o 0000121-49.2025.8.19.0001, distribuído em 27/07/2025 — mais novo que o 0000116-27, que apareceu na lista. Ele não está aí porque está classificado como Sigilo Absoluto, e a sessão que fez a consulta tem teto de acesso em Segredo de Justiça.

Três coisas para reter:

  1. O filtro aconteceu no WHERE. O processo sigiloso não foi lido, não entrou na lista, não passou pelo prompt. O modelo nunca soube que ele existe — e é assim que tem que ser.
  2. O modelo não teve participação nenhuma nisso. Ele pediu "os 3 mais recentes do assunto 1001" e recebeu 3 registros. Não houve decisão de sigilo a ser tomada por ele, e portanto não houve decisão a ser burlada.
  3. A resposta não avisa que algo foi omitido — e nem deveria. Dizer "há 1 processo que você não pode ver" já é informação sobre a existência dele.

Rode a mesma consulta com teto 0 (Público) e o retorno é uma lista vazia: nenhum processo de violência doméstica na base é público. É o comportamento correto, e é o tipo de coisa que só aparece quando você testa o que o sistema não deve fazer.

Passo 7 — resposta final

Com os dados em mãos, o modelo encerra o ciclo:

Thought: Tenho os três processos. Não preciso de mais nenhuma Tool. Posso redigir a resposta.

Final answer: Os três processos mais recentes sobre violência doméstica são o 0000184-40.2026.8.19.0001 (2ª Vara, distribuído em 10/01/2026, arquivado), o 0000031-41.2025.8.19.0001 (2ª Vara, 23/08/2025, arquivado) e o 0000116-27.2025.8.19.0001 (1ª Vara, 02/06/2025, suspenso).

Laço encerrado.

O que esse exemplo mostra

O Agent itera até cumprir o objetivo. Se a Observation do passo 3 tivesse voltado -1 (assunto não encontrado), o modelo poderia ter tentado outro termo, ou informado que não localizou o assunto. O ciclo é o que permite corrigir o rumo — algo que uma chamada única não permite.

A Tool traz o que o modelo não sabe. Nenhum desses números de processo estava no treinamento do modelo. Eles vieram da consulta. É isso que separa "buscar" de "inventar".

Cada volta incorpora informação nova. O raciocínio do passo 4 seria impossível no passo 1 — o código 1001 ainda não existia para ele.

Esse padrão tem nome: ReAct (Reason + Act), que vamos detalhar no próximo capítulo.

Onde esse laço dá errado

Vale conhecer os modos de falha antes de encontrá-los.

O laço não fecha. O modelo continua chamando Tools sem chegar a uma conclusão — às vezes repetindo a mesma chamada. Por isso todo framework tem um max_steps. Sem ele, o Agent roda até acabar o contexto.

O contexto estoura. Cada volta acrescenta Thought, Action e Observation ao histórico, e o histórico inteiro é reenviado a cada passo. Uma Observation com 200 processos consome contexto que faria falta adiante — mais um motivo para o LIMIT na consulta.

A Action vem malformada. O modelo escreve um JSON inválido, ou inventa o nome de uma Tool que não existe, ou erra o nome de um argumento. Modelos menores erram mais. O framework normalmente devolve o erro como Observation e deixa o modelo tentar de novo — o que funciona surpreendentemente bem, e é uma boa razão para suas mensagens de erro serem informativas.

O modelo pula a Tool. Ele decide que já sabe a resposta e responde direto, sem consultar nada. Aqui está o modo de falha mais perigoso para o nosso uso: a resposta vem bem formatada, com números de processo plausíveis, e inteiramente inventada.

Atenção

Esse último caso é o motivo de um hábito que vale adotar desde o primeiro exercício: olhe os passos intermediários, não apenas a resposta final.

Se a resposta traz processos mas o log não mostra nenhuma chamada a buscar_processos, os processos não existem. O smolagents imprime o ciclo inteiro por padrão — leia.

Na prática institucional, isso vira uma regra simples: um resultado que não corresponde a uma consulta registrada não é um resultado.

Fixando o capítulo

Q1: Quem executa a Tool?


Q2: A consulta do exemplo omitiu o processo 0000121-49.2025.8.19.0001, mais recente que um dos listados, porque ele é de Sigilo Absoluto. Por que a resposta final não avisa que houve omissão?


Q3: Para que serve o max_steps?


Agora vamos abrir cada etapa do ciclo separadamente, começando pelo Thought.