Harness baseado em IA
Um guia completo sobre a infraestrutura que transforma um modelo de linguagem em um sistema agêntico de produção — cada componente explicado do zero, com a implementação em Python + LangGraph e a leitura de negócio de cada decisão.
Conceitos essenciais
Antes do harness em si, quatro conceitos que todo o resto pressupõe. Se você já domina LLMs, pule para a próxima seção.
O que um LLM realmente faz
Um LLM (Large Language Model) é uma função estatística gigante: recebe uma sequência de tokens (pedaços de texto — palavras, sílabas, símbolos) e devolve, token a token, a continuação mais provável dessa sequência. Só isso. Ele não "executa" nada, não acessa a internet, não lembra da conversa anterior, não tem estado entre chamadas.
O modelo é stateless: cada chamada à API é independente. Se parece que ele "lembra" da conversa, é porque alguém reenviou o histórico inteiro dentro da requisição. Esse "alguém" é o harness — e essa única observação explica metade da arquitetura deste guia.
Janela de contexto
Cada modelo tem um limite de tokens que consegue processar por chamada — a janela de contexto (context window). Tudo o que o modelo "sabe" sobre a tarefa atual precisa caber ali: instruções, histórico, documentos, resultados de ferramentas. Estourou o limite, a chamada falha ou informação é cortada. Administrar esse espaço finito é uma das funções mais críticas do harness.
Function calling (tool use)
Modelos modernos aceitam, junto do prompt, uma lista de ferramentas (tools) descritas em JSON Schema: nome, descrição, parâmetros. Em vez de responder com texto, o modelo pode responder com um objeto estruturado dizendo "chame a função X com os argumentos Y". Importante: o modelo não executa a função — ele apenas pede. Quem executa, valida e devolve o resultado é o software em volta dele. De novo: o harness.
Agente
Um agente é um sistema em que o LLM decide, iterativamente, quais ações tomar para cumprir um objetivo — chamando ferramentas, observando resultados e ajustando o plano — em vez de apenas responder uma pergunta de uma vez. A diferença entre um chatbot e um agente não está no modelo: está no software que permite (e controla) esse ciclo de decisão.
O que é um harness
Harness (literalmente "arnês" ou "arreio"; em engenharia, "andaime de execução") é toda a infraestrutura de software que envolve o LLM e o transforma em um sistema útil e confiável. A analogia canônica:
O modelo é o motor; o harness é o resto do carro. Um motor sozinho não vai a lugar nenhum — precisa de chassi, transmissão, freios, painel e airbag. Dois carros com o mesmo motor podem ter desempenhos e níveis de segurança completamente diferentes. Com agentes é idêntico: o mesmo modelo, em dois harnesses diferentes, produz sistemas radicalmente distintos.
O termo aparece em dois contextos que compartilham a mesma mecânica:
| Tipo | Objetivo | Exemplos |
|---|---|---|
| Harness de agente (produção) | Executar tarefas reais com confiabilidade: montar contexto, rodar o loop, executar tools, aplicar guardrails. | LangGraph, Claude Code, OpenAI Agents SDK, um supervisor customizado em FastAPI |
| Harness de eval (avaliação) | Rodar o agente contra um dataset de tarefas, capturar trajetórias e medir desempenho de forma reprodutível. | SWE-bench harness, harness internos de benchmark, pipelines de trajectory evals |
Avalie com o mesmo harness que roda em produção. Um benchmark medido em um harness diferente do seu mede outra coisa — os resultados não transferem, porque grande parte da performance vem do scaffolding, não do modelo.
É por isso que dois times com acesso ao mesmo modelo entregam produtos com qualidade radicalmente diferente. O modelo é uma commodity acessível por API; o harness é onde mora o diferencial competitivo — e também onde mora a maior parte do custo de engenharia, do risco operacional e da dívida técnica de um produto de IA.
Parte 1 · Entrada do usuário
No primeiro diagrama, a seta "Usuário" entrando no container do harness parece trivial — mas é a primeira fronteira de segurança e de contrato do sistema.
O que acontece tecnicamente
- Autenticação e identidade. Antes de qualquer token chegar ao modelo, o harness estabelece quem está pedindo: valida o JWT, extrai claims (usuário, tenant, escopos) e materializa isso em um objeto de contexto tipado. Em sistemas multiagente, essa identidade precisa se propagar por toda a cadeia de chamadas (padrão de token propagation).
- Normalização da entrada. A mensagem crua vira uma estrutura interna: texto, anexos, metadados de sessão (
thread_id), idioma, canal de origem. - Primeira camada de defesa. Filtros de entrada rodam aqui: limites de tamanho, sanitização, detecção precoce de prompt injection (instruções maliciosas embutidas na mensagem tentando sequestrar o comportamento do agente).
Tratar a entrada do usuário como "apenas texto" e concatená-la direto no prompt. Toda entrada é dado não confiável — inclusive documentos anexados e resultados de RAG. O harness precisa distinguir estruturalmente instruções do sistema (confiáveis) de conteúdo do usuário (não confiável), porque o modelo, sozinho, não distingue.
Essa fronteira define o contrato do produto: quem pode usar, com quais dados, sob qual isolamento entre clientes (multi-tenancy). Falhas aqui não são bugs — são incidentes de segurança e de compliance (LGPD/GDPR), com custo reputacional e jurídico. É a camada mais barata de acertar no design e a mais cara de corrigir depois.
Parte 1 · Montagem de contexto
Esta é a etapa que decide o que o modelo "vê" a cada chamada. Como o modelo é stateless e a janela de contexto é finita, cada requisição precisa ser montada do zero — e a qualidade dessa montagem determina, mais do que qualquer outra variável, a qualidade do agente. A disciplina que estuda isso hoje se chama context engineering.
Os ingredientes do contexto
| Componente | O que é | Responsabilidade do harness |
|---|---|---|
| System prompt | Instruções fixas: papel do agente, regras, formato de saída, políticas. | Versionar, testar e proteger (o usuário não pode sobrescrevê-lo). |
| Histórico da conversa | Mensagens anteriores do usuário, do modelo e resultados de tools. | Persistir por sessão (thread_id), truncar ou resumir quando crescer demais. |
| Memória de longo prazo | Fatos sobre o usuário/domínio que sobrevivem entre sessões. | Decidir o que gravar, como indexar e o que injetar em cada chamada. |
| RAG (Retrieval-Augmented Generation) | Trechos de documentos recuperados de uma base vetorial, relevantes à pergunta atual. | Chunking, embedding, busca, re-ranking e citação de fontes. |
| Definições de tools | Os JSON Schemas das ferramentas disponíveis. | Escrever descrições claras (o modelo escolhe a tool pela descrição) e filtrar por permissão do usuário. |
O problema central: espaço finito
Tudo isso disputa a mesma janela de tokens. As estratégias clássicas do harness:
- Truncamento por janela deslizante — descartar mensagens antigas, mantendo as N mais recentes. Simples, mas perde informação.
- Sumarização (compactação) — quando o histórico cruza um limiar, o harness chama o próprio LLM para resumir o trecho antigo e substitui as mensagens pelo resumo. Custa uma chamada extra, preserva o essencial.
- Recuperação seletiva — em vez de carregar tudo sempre, guardar em storage externo e recuperar sob demanda (memória como RAG).
- Prompt caching — provedores permitem cachear o prefixo estável do contexto (system prompt + tools), reduzindo custo e latência das chamadas repetidas. O harness precisa ordenar o contexto para maximizar o prefixo cacheável: o que é estável vem primeiro, o que muda vem por último.
Trate a montagem de contexto como uma função pura e testável: build_context(state) -> list[Message]. Isso permite fazer snapshot testing do prompt final, medir tokens por componente e auditar exatamente o que o modelo viu em cada decisão — essencial para depurar comportamento estranho em produção.
Contexto é custo direto: você paga por token de entrada em toda chamada, e o loop reenvia o histórico a cada iteração. Um contexto 2× maior pode significar uma conta de API 2× maior com ganho marginal de qualidade. Context engineering é, na prática, uma alavanca de margem: as maiores reduções de custo unitário em produtos de IA vêm de compactação, caching e curadoria do que entra no prompt — não de trocar de modelo.
Parte 1 · Loop agêntico (visão geral)
O loop é o coração do harness — o mecanismo que transforma um modelo que só completa texto em um sistema que age. Em uma frase: um while que alterna entre chamar o modelo e executar o que ele pediu, realimentando os resultados, até a tarefa terminar.
while não_terminou: resposta = chamar_llm(contexto) if resposta.tool_calls → executar e realimentar else → resposta final, break
Esse padrão tem nome na literatura: ReAct (Reasoning + Acting) — o modelo raciocina, age, observa o resultado e raciocina de novo. Cada volta do laço é uma iteração; a sequência completa de decisões e observações de uma execução chama-se trajetória (conceito central em avaliação de agentes).
A visão geral fica aqui; a Parte 2 disseca cada etapa interna do loop, mostrada na Fig. 2 mais abaixo.
Porque tarefas reais exigem observar antes de decidir. O modelo não sabe o que tem no banco antes de consultá-lo, nem se o deploy passou antes de rodar o teste. O loop permite que cada decisão seja condicionada ao resultado da anterior — é isso que separa "gerar um texto plausível" de "resolver um problema".
Parte 1 · O LLM dentro do harness
No diagrama, o LLM é uma caixa entre as outras — e isso é proposital. Dentro do harness, o modelo é um componente com um contrato estreito: tokens entram, tokens saem. Todo o resto é responsabilidade do software em volta.
O contrato do componente
- Entrada: lista de mensagens + definições de tools + parâmetros de amostragem (temperatura, máximo de tokens de saída).
- Saída: texto e/ou uma lista de tool calls estruturadas. Nada mais.
- Garantias que ele NÃO dá: determinismo (a mesma entrada pode gerar saídas diferentes), veracidade (pode alucinar), aderência perfeita a schema (pode gerar argumentos inválidos), disponibilidade (a API pode falhar ou limitar taxa).
O harness existe, em grande parte, para compensar cada uma dessas não-garantias: validação compensa a falta de aderência a schema; retries e fallback de provedor compensam a disponibilidade; guardrails e citação de fontes mitigam alucinação; temperatura baixa + testes estatísticos lidam com o não-determinismo.
Um bom harness isola o modelo atrás de uma interface (no LangChain/LangGraph, BaseChatModel). Trocar de GPT para Claude, ou rotear tarefas simples para um modelo barato e tarefas complexas para um modelo forte (model routing), vira uma decisão de configuração — não uma reescrita.
Tratar o LLM como componente substituível é uma decisão estratégica: reduz lock-in de fornecedor, permite arbitrar preço entre provedores (os preços caem e mudam constantemente) e habilita otimização de custo por rota — usar o modelo caro só onde ele paga o próprio preço. Empresas que acoplam o produto a um provedor específico perdem essa alavanca de negociação.
Parte 1 · Execução de tools
As tools são as "mãos" do agente: consultas a banco, chamadas HTTP, buscas, escrita de arquivos, envio de mensagens — ou, em arquiteturas multiagente, outros agentes inteiros expostos como ferramentas (via protocolos como MCP). Esta camada do harness é o runtime de execução dessas ações.
Responsabilidades
- Registro e descoberta. Manter o catálogo de tools disponíveis, com schema e descrição. Em setups com MCP, isso inclui conectar-se a servidores externos e importar as tools que eles expõem dinamicamente.
- Dispatch. Receber a tool call do modelo (nome + argumentos em JSON) e rotear para a implementação correta.
- Validação de argumentos. Checar o JSON contra o schema (Pydantic em Python) antes de executar. O modelo erra: inventa campos, troca tipos, esquece obrigatórios.
- Execução protegida. Rodar com timeout, retry com backoff exponencial + jitter para falhas transitórias, e circuit breaker para dependências degradadas (parar de insistir em um serviço que está fora, falhando rápido em vez de acumular timeouts).
- Autorização. Verificar se este usuário, com estas permissões, pode executar esta tool com estes argumentos (ACLs por escopo). O modelo pedir não é autorização.
- Serialização do resultado. Converter o retorno (objeto, DataFrame, erro) em texto/JSON que caiba no contexto — inclusive truncando resultados gigantes.
O modelo propõe; o harness dispõe. Nenhuma tool call vai direto para execução pela vontade do modelo. Entre a intenção e o efeito existem validação, autorização e, para ações destrutivas ou irreversíveis (pagamentos, deleções, e-mails), aprovação humana explícita (human-in-the-loop). Um agente comprometido por prompt injection só causa o dano que essa camada permitir.
As tools definem o raio de ação — e o raio de estrago — do produto. A pergunta de negócio não é "o que o agente consegue fazer?", e sim "qual é o pior efeito de uma ação errada, e quem responde por ele?". A granularidade das permissões e os pontos de aprovação humana são decisões de risco/produto, não detalhes de implementação: determinam desde o preço do seguro cibernético até o que o jurídico deixa ir para produção.
Parte 1 · Guardrails e telemetria
A faixa horizontal na base do primeiro diagrama é proposital: guardrails e observabilidade são transversais — cortam todas as outras camadas, da entrada do usuário à execução de tools.
Guardrails (trilhos de proteção)
- Na entrada: detecção de prompt injection, filtros de conteúdo, limites de tamanho e taxa (rate limiting) por usuário/tenant.
- No meio: separação estrutural entre instruções confiáveis e dados não confiáveis; allowlists de tools por contexto; validação de que o agente permanece dentro do escopo da tarefa.
- Na saída: checagem de vazamento de dados sensíveis (PII, segredos), validação de formato, filtros de conteúdo e, em domínios críticos, verificação factual contra as fontes recuperadas.
- Defesa em profundidade: nenhuma camada é suficiente sozinha; a segurança vem do empilhamento (tipicamente 3–4 camadas independentes entre a entrada e o efeito colateral).
Telemetria (observabilidade)
Sistemas agênticos são não-determinísticos e distribuídos — sem instrumentação, são caixas-pretas impossíveis de depurar. O harness precisa registrar, para cada execução:
- Traces distribuídos (OpenTelemetry): cada chamada ao LLM, cada tool call e cada hop entre serviços como spans correlacionados, propagando o trace context (W3C TraceContext) e o
thread_idda sessão por toda a cadeia. - Métricas: tokens de entrada/saída por chamada, custo estimado, latência por etapa, taxa de erro por tool, iterações por tarefa, taxa de acionamento de guardrails.
- Trajetórias completas: a sequência de decisões do agente, persistida — é a matéria-prima tanto do debugging quanto das avaliações offline (trajectory evals).
Instrumente antes de precisar. A pergunta "por que o agente fez isso ontem às 3h?" só tem resposta se a trajetória e o contexto exato daquela execução foram gravados. Logs de aplicação comuns não bastam: você precisa do prompt completo que o modelo viu e da resposta completa que ele deu, por iteração.
Guardrails são o que permite vender para empresas: auditoria, trilha de decisão e controles são requisitos de procurement em setores regulados (financeiro, saúde, jurídico). E a telemetria é o que torna o custo gerenciável — sem métricas de tokens por rota, o produto tem uma conta de API que ninguém explica. Observabilidade em agentes não é engenharia de plataforma opcional; é pré-requisito de unit economics e de compliance.
Parte 2 · Chamada ao LLM
Cada iteração do loop começa aqui: o harness pega o contexto acumulado — system prompt, histórico e todos os resultados de tools até agora — e envia ao modelo junto com as definições de tools.
Detalhes que importam
- O histórico inteiro vai de novo, sempre. Como o modelo é stateless, a iteração 8 reenvia tudo o que aconteceu nas iterações 1–7. Consequência direta: o custo por iteração cresce ao longo da execução, porque o contexto só acumula. Prompt caching mitiga (o prefixo estável não é reprocessado a preço cheio), mas não elimina.
- Resiliência da chamada. A API do provedor falha: rate limits (HTTP 429), erros de servidor (5xx), timeouts. O harness aplica retry com backoff exponencial + jitter, respeita cabeçalhos de rate limit e, em setups maduros, tem fallback para um segundo provedor ou modelo.
- Parâmetros por propósito. Temperatura baixa (0–0.3) para decisões de roteamento e tool calls (queremos consistência); mais alta para geração criativa. O harness escolhe por etapa, não usa um valor global.
- Streaming. Para UX, o harness pode consumir a resposta token a token e repassar ao usuário em tempo real — o que complica o parsing (tool calls chegam fragmentadas) e é mais um trabalho da camada de harness.
Esta etapa é o taxímetro do sistema. Custo = (tokens de entrada × preço) + (tokens de saída × preço), por iteração, e a entrada cresce a cada volta. Uma tarefa que converge em 3 iterações vs. 10 iterações não é 3× mais barata — é mais, porque as iterações finais carregam contexto maior. Otimizar convergência (boas descrições de tools, contexto limpo) é otimizar margem.
Parte 2 · Parsing da resposta
A resposta do modelo chega e o harness precisa responder uma pergunta: isso é uma ação ou uma resposta final? Esta é a bifurcação central da Fig. 2.
Como funciona
A decisão é determinística — não envolve outra chamada ao LLM. A API do provedor devolve a resposta em campos estruturados: um bloco de texto e/ou uma lista de tool_calls, cada uma com id, name e arguments (JSON). O parsing é inspecionar esse payload:
# A conditional edge do LangGraph faz exatamente isto:
def route(state: AgentState) -> str:
last = state["messages"][-1]
if last.tool_calls: # o modelo pediu ações
return "tools"
return "end" # texto puro = resposta final
Onde mora a complexidade real
- Tool calls paralelas. O modelo pode pedir várias tools numa única resposta ("busque X e consulte Y"). O harness decide se executa em paralelo (mais rápido) ou em série (mais seguro quando há dependências ou efeitos colaterais).
- Respostas mistas. Texto + tool calls na mesma resposta: o texto costuma ser raciocínio ou explicação intermediária. O harness decide se exibe ao usuário, guarda no histórico ou descarta.
- Malformações. Argumentos que não são JSON válido, nomes de tools inexistentes, JSON truncado por limite de tokens de saída. A política correta quase sempre é devolver o erro ao modelo como observação — ele se corrige na iteração seguinte — em vez de derrubar a execução.
- Saída estruturada. Quando a resposta final precisa seguir um schema (um JSON de decisão, por exemplo), o parsing também valida isso — com Pydantic — e reprompta em caso de violação.
O modelo só emitiu tokens. Interpretar esses tokens como intenção de agir, decidir a ordem de execução e tratar malformações é pura engenharia de software — e cada uma dessas políticas muda o comportamento do agente sem mudar uma linha do modelo.
Parte 2 · Executar tool
Com a tool call parseada, entra o pipeline de execução protegida — os três subtítulos do box no diagrama: validação, timeout, retry.
O pipeline, passo a passo
- Autorização. O usuário desta sessão pode executar esta tool? (checagem de escopo/ACL — cacheável para não custar uma ida ao banco por call).
- Validação de schema.
model_validate()do Pydantic sobre os argumentos. Falhou → oValidationErrorvira observação para o modelo, não exceção para o usuário. - Execução com timeout. Toda tool roda dentro de um limite de tempo. Em cadeias de serviços, os timeouts formam um orçamento em cascata: se a requisição toda tem 30s, e já se passaram 12s, a próxima chamada downstream recebe no máximo o que resta (menos uma margem) — nunca um timeout fixo maior que o orçamento restante, o que causaria estouros silenciosos na ponta.
- Retry seletivo. Backoff exponencial com jitter, apenas para falhas transitórias (rede, 429, 5xx). Erros determinísticos (validação, 404, permissão negada) não se repetem — retry neles só queima tempo e dinheiro.
- Circuit breaker. Se uma dependência falha repetidamente, o breaker "abre" e as próximas chamadas falham imediatamente por um período, dando tempo de recuperação ao serviço e devolvendo um erro claro ao modelo ("serviço indisponível, tente outra abordagem").
- Captura do resultado. Sucesso ou erro, o desfecho é serializado em texto — porque o destino dele é a janela de contexto.
Resultados gigantes explodem o contexto. Uma query que retorna 50 mil linhas, ou uma página HTML inteira, não pode ir crua para o histórico. O harness trunca, pagina ou resume resultados de tools — e informa o modelo de que o resultado foi cortado, para ele poder pedir mais se precisar.
Esta etapa concentra a confiabilidade percebida do produto. O usuário não vê "o circuit breaker abriu" — vê "o agente resolveu mesmo com instabilidade" ou "o agente travou". SLAs de produtos agênticos são, na prática, SLAs desta camada: é aqui que se decide se uma dependência instável derruba a experiência ou vira um desvio elegante.
Parte 2 · Realimentar histórico
O resultado da tool (ou o erro) vira uma mensagem do tipo tool_result, vinculada ao id da tool call original, e é anexado ao histórico. O loop volta ao topo — e na próxima chamada, o modelo "vê" o que aconteceu e decide o próximo passo. O símbolo ↻ do diagrama é este retorno.
Três propriedades fundamentais
- O histórico É o estado. Não existe máquina de estados paralela escondida: a lista de mensagens é a única fonte de verdade sobre onde a execução está. Persistir o histórico é persistir o loop inteiro; retomar é continuar de onde as mensagens pararam. É exatamente assim que o checkpointing do LangGraph funciona — o checkpointer (
MemorySaver,RedisSaver, etc.) serializa o estado do grafo porthread_ida cada superstep, habilitando resiliência a crashes, pausas para aprovação humana e time travel de debugging. - Erros são observações, não exceções. Quando a tool falha, o texto do erro volta ao modelo — que tem uma capacidade notável de se autocorrigir: ajusta argumentos, tenta outra tool, muda a abordagem. Essa é a fonte da resiliência dos agentes… e também do risco de laços infinitos, tratado nas saídas forçadas.
- Todo
tool_callexige umtool_result. As APIs rejeitam históricos com tool calls "órfãs" (pedidas e nunca respondidas). Se o harness decidiu não executar uma call (permissão negada, por exemplo), ainda precisa anexar um resultado dizendo isso.
A sequência acumulada de mensagens — pensamentos, ações, observações — é a trajetória da execução. Gravá-la integralmente é o que permite trajectory evals depois: analisar não só se o agente acertou, mas como chegou lá (quantas iterações, quais desvios, quais erros se autocorrigiram).
Parte 2 · Resposta final
A condição de parada natural: o modelo responde sem tool calls. A interpretação semântica é "não preciso de mais informações nem ações — eis a resposta". O harness então:
- Pós-processa a saída — valida formato/schema se a resposta é estruturada, aplica os guardrails de saída (PII, conteúdo, vazamento de dados), anexa citações de fontes quando houve RAG.
- Persiste o estado final — o checkpoint da conversa fica pronto para o próximo turno do usuário, que reabre o loop com o histórico completo.
- Emite a telemetria de fechamento — total de iterações, tokens, custo, latência ponta a ponta, desfecho (sucesso natural vs. saída forçada). São as métricas agregadas que alimentam dashboards e evals.
"Sem tool calls" significa que o modelo acha que terminou — não que a tarefa foi cumprida. Modelos terminam cedo demais ("declaro sucesso!") ou desistem sem avisar. Harnesses maduros adicionam uma verificação: um passo de crítica (o próprio modelo ou um segundo modelo revisa a resposta contra o objetivo) ou validação programática (os testes passam? o arquivo existe?) antes de aceitar o desfecho. É a diferença entre confiar na autoavaliação do modelo e verificar.
Parte 2 · Saídas forçadas (budgets)
A faixa âmbar do diagrama: as condições em que o harness encerra o loop contra a vontade do modelo. Sem elas, um agente pode ciclar para sempre — tentando a mesma tool quebrada, alternando entre duas abordagens, ou simplesmente queimando tokens sem convergir.
| Budget | Mecanismo | Protege contra |
|---|---|---|
| Máximo de iterações | Contador de voltas do loop (no LangGraph, recursion_limit). | Laços infinitos e não-convergência. |
| Timeout global | Deadline da execução inteira, propagado em cascata para cada chamada interna. | Tarefas que "penduram" e seguram recursos/conexões. |
| Teto de custo | Acumulador de tokens/dinheiro por execução, por usuário e por tenant. | Explosão de custo — acidental (bug) ou maliciosa (abuso). |
| Guardrail disparado | Interrupção imediata quando um trilho de segurança detecta violação. | Dano em andamento: injection bem-sucedida, vazamento, ação fora de escopo. |
O que fazer ao estourar o budget
Encerrar bem importa tanto quanto encerrar. As políticas comuns, da mais simples à mais sofisticada:
- Falha explícita — devolver erro claro ao usuário ("não consegui concluir em N passos"), preservando a trajetória para análise.
- Resumo forçado — uma última chamada ao modelo, sem tools, pedindo o melhor resultado parcial possível com o que foi apurado.
- Escalação humana — rotear a tarefa, com todo o contexto, para uma fila de atendimento humano (padrão obrigatório em produtos de suporte).
Budgets são a apólice de seguro do produto — e um instrumento de pricing. O teto de custo por execução define o pior caso da sua margem unitária; sem ele, um único usuário (ou um único bug) pode gerar uma fatura de API de cinco dígitos numa madrugada. Casos assim são recorrentes o suficiente na indústria para que "cost ceiling por tenant" seja checklist de lançamento, não otimização futura. A taxa de saídas forçadas também é um KPI de qualidade: se 20% das execuções morrem por limite de iterações, o problema não é o limite — é o agente que não converge.
O loop completo em LangGraph
O LangGraph materializa tudo que vimos como um grafo de estados: cada etapa vira um nó, a bifurcação do parsing vira uma aresta condicional, e a realimentação vira a aresta que fecha o ciclo. O mapeamento direto entre os diagramas e o código:
| Etapa do diagrama | Construto no LangGraph |
|---|---|
| Chamada ao LLM | Nó agent — invoca llm.bind_tools(tools) |
| Parsing da resposta | Aresta condicional (tools_condition ou função própria) |
| Executar tool + realimentar | Nó tools (ToolNode: dispatch, validação e append do ToolMessage) |
| ↻ nova iteração | Aresta tools → agent, fechando o ciclo |
| Resposta final | Aresta condicional → END |
| Histórico como estado | AgentState com add_messages (reducer de append) |
| Persistência do estado | Checkpointer (MemorySaver / RedisSaver) por thread_id |
| Máximo de iterações | recursion_limit na invocação |
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.tools import tool
from langchain_anthropic import ChatAnthropic
# ---- 1. Estado: o histórico É o estado --------------------------------
class AgentState(TypedDict):
# add_messages é um "reducer": novos itens são anexados, não substituem
messages: Annotated[list, add_messages]
# ---- 2. Tools: schema inferido da assinatura + docstring --------------
# A docstring é o que o modelo lê para decidir QUANDO usar a tool.
@tool
def consultar_pedido(pedido_id: str) -> str:
"""Consulta o status de um pedido pelo seu ID."""
return buscar_no_banco(pedido_id) # timeout/retry ficam aqui dentro
tools = [consultar_pedido]
# ---- 3. Nó agent: a "chamada ao LLM" do diagrama ----------------------
llm = ChatAnthropic(model="claude-sonnet-4-6", temperature=0)
llm_com_tools = llm.bind_tools(tools) # anexa os JSON Schemas à chamada
def agent(state: AgentState):
# montagem de contexto: aqui entrariam compactação, RAG, memória...
resposta = llm_com_tools.invoke(state["messages"])
return {"messages": [resposta]} # reducer anexa ao histórico
# ---- 4. Grafo: as setas do diagrama viram edges -----------------------
builder = StateGraph(AgentState)
builder.add_node("agent", agent)
builder.add_node("tools", ToolNode(tools)) # dispatch + ToolMessage
builder.add_edge(START, "agent")
# parsing da resposta: tool_calls? -> "tools" ; senão -> END
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent") # ↻ fecha o ciclo
# ---- 5. Checkpointer: persistir o histórico = persistir o loop --------
grafo = builder.compile(checkpointer=MemorySaver())
# ---- 6. Invocação com budgets -----------------------------------------
config = {
"configurable": {"thread_id": "sessao-42"}, # isola a conversa
"recursion_limit": 25, # máx. de supersteps
}
resultado = grafo.invoke(
{"messages": [("user", "Qual o status do pedido A-981?")]},
config,
)
O que este código não mostra — e que separa um tutorial de um harness de produção — são as camadas transversais: autenticação na borda, ACL por tool, timeouts em cascata dentro de cada tool, circuit breakers nas dependências, instrumentação OpenTelemetry por nó, teto de custo por tenant e o checkpointer trocado por um backend durável (Redis/Postgres) em vez de memória. Cada uma delas se encaixa exatamente nos pontos descritos nas Partes 1 e 2.
Em arquiteturas multiagente, o mesmo loop se aninha: um agente supervisor roda seu próprio ciclo tratando cada agente especialista como uma "tool" — o resultado do especialista realimenta o roteador do supervisor, que decide o próximo passo. É o mesmo padrão do diagrama, recursivo: harnesses dentro de harnesses, com identidade e budgets propagados entre os níveis.
Custo, risco e decisão de negócio
Consolidando as leituras de negócio espalhadas pelo guia em um único quadro de decisão:
1. O harness é o produto
O modelo é acessível por API a qualquer concorrente pelo mesmo preço. O que diferencia produtos é o que está em volta: a curadoria de contexto, as tools e suas descrições, os guardrails, a experiência de recuperação de erro. Investimento em harness é investimento em moat; investimento em "prompt mágico" é vantagem que evapora no próximo lançamento de modelo.
2. Unit economics vivem no loop
- Custo por tarefa = tokens acumulados ao longo das iterações. As alavancas, em ordem de impacto típico: reduzir iterações até a convergência, compactar contexto, prompt caching, model routing (modelo barato onde ele basta).
- Sem teto de custo por execução/tenant, não há pricing seguro. O pior caso da margem é definido pelos budgets — ou pela sorte.
- Latência é composta: N iterações × (latência do modelo + latência das tools). Produtos interativos precisam de streaming e de convergência rápida; produtos batch podem trocar latência por custo.
3. Risco operacional tem endereço
| Risco | Camada do harness que o controla |
|---|---|
| Vazamento de dados / violação de privacidade | Fronteira de entrada, guardrails de saída, ACLs |
| Ação destrutiva indevida | Autorização por tool + human-in-the-loop |
| Prompt injection | Defesa em camadas (entrada → contexto → tools → saída) |
| Explosão de custo | Budgets (iterações, custo, timeout) |
| Impossibilidade de auditar decisões | Telemetria e persistência de trajetórias |
| Lock-in de fornecedor | Abstração de provedor no componente LLM |
4. Build vs. buy
A pergunta certa não é "framework ou código próprio?", e sim onde está o seu diferencial. Frameworks como LangGraph resolvem bem a mecânica genérica (grafo, checkpointing, ToolNode) e custam pouco a adotar; o que nenhum framework entrega são as suas tools, os seus guardrails, o seu modelo de permissões e a sua observabilidade — que é onde o esforço deveria se concentrar de qualquer forma. O anti-padrão é gastar meses reescrevendo o loop genérico e chegar sem fôlego na parte que diferencia.
5. Qualidade se mede, não se sente
Como o sistema é não-determinístico, "parece bom nas demos" não é evidência. O ciclo de melhoria maduro: gravar trajetórias em produção → construir datasets de avaliação a partir de casos reais → rodar evals (outcome + trajectory) no mesmo harness a cada mudança de prompt, tool ou modelo → tratar regressão de eval como quebra de build. Sem isso, cada ajuste de prompt é um lançamento às cegas.
Glossário
| Termo | Definição |
|---|---|
| Harness | Infraestrutura de software que envolve o LLM: contexto, loop, tools, guardrails e telemetria. |
| Token | Unidade mínima de texto processada pelo modelo; base de cobrança e do limite de contexto. |
| Janela de contexto | Máximo de tokens que o modelo processa por chamada. |
| Function calling / tool use | Capacidade do modelo de responder com pedidos estruturados de execução de funções. |
| Tool call / tool result | O pedido de ação emitido pelo modelo / a observação devolvida pelo harness após executar. |
| Loop agêntico (ReAct) | Ciclo raciocinar → agir → observar que estrutura a execução do agente. |
| Iteração | Uma volta completa do loop (uma chamada ao modelo + eventuais execuções de tools). |
| Trajetória | Sequência completa de decisões, ações e observações de uma execução; insumo de debugging e evals. |
| Checkpointing | Persistência do estado do loop (o histórico) por sessão, habilitando retomada e auditoria. |
| thread_id | Identificador que isola o estado de uma conversa/sessão no checkpointer e na telemetria. |
| Guardrail | Controle automático que restringe entradas, comportamentos ou saídas do agente. |
| Prompt injection | Ataque em que instruções maliciosas embutidas em dados tentam sequestrar o agente. |
| Human-in-the-loop | Ponto de aprovação humana obrigatória antes de ações sensíveis ou irreversíveis. |
| Circuit breaker | Padrão que interrompe chamadas a uma dependência degradada, falhando rápido até ela se recuperar. |
| Backoff exponencial + jitter | Estratégia de retry com esperas crescentes e aleatorizadas para falhas transitórias. |
| Budget | Limite imposto pelo harness (iterações, tempo, custo) que força o encerramento do loop. |
| RAG | Recuperação de trechos relevantes de uma base de conhecimento para enriquecer o contexto. |
| Prompt caching | Reuso do prefixo estável do contexto entre chamadas, reduzindo custo e latência. |
| Model routing | Direcionar cada tarefa ao modelo mais custo-efetivo capaz de resolvê-la. |
| MCP | Model Context Protocol — padrão aberto para expor tools e contexto de servidores externos a agentes. |
| Supervisor / especialista | Padrão multiagente em que um agente roteador orquestra agentes especializados como se fossem tools. |
| Eval (outcome / trajectory) | Avaliação sistemática do agente: pelo resultado final e/ou pelo caminho percorrido. |