Um agente que conversa e um grafo que redige: o intake da guria.lat

Como funciona o chat da guria.lat: um agente de LangChain coleta os dados do projeto e um grafo de LangGraph monta o brief e a proposta.

5 min de leitura

Compartilhar WhatsAppLinkedInX
Ilustração isométrica de uma janela de chat que alimenta um grafo de nós, do qual saem uma apresentação e um relatório

Se você abriu o chat deste site, conversou com um agente. Você conta o que acontece no seu negócio, ele faz algumas perguntas e, quando o problema fica claro, mostra um resumo para você confirmar. O que vem depois você não vê: em segundo plano são montados um brief interno para a equipe e uma proposta em PDF, e os dois chegam no nosso e-mail para revisão antes de qualquer resposta.

Construí isso com LangChain e LangGraph. A decisão que mais pesou foi separar o sistema em duas partes com trabalhos diferentes.

Conversar é aberto, redigir não

Uma conversa com um possível cliente não tem roteiro. Tem quem chegue com o problema bem definido e quem escreva “quero um app com IA”. O assistente precisa decidir o que perguntar, quando já tem o suficiente e quando encerrar. Isso é trabalho para um agente: create_agent do LangChain com duas ferramentas, enviar_intake e cerrar_conversacion, e um checkpointer em SQLite que guarda cada conversa por thread_id. O navegador envia só a mensagem nova e o histórico fica no servidor.

Redigir o brief, por outro lado, é um processo conhecido: ver se o intake basta para estimar, buscar projetos parecidos, estimar, revisar e escrever a proposta. Aí não quero que o modelo decida a ordem. É um StateGraph fixo, e o modelo só trabalha dentro de alguns nós.

evaluar → triage ─(vaga)──→ pedir_datos
                 └(clara)─→ buscar ─┬→ redactar ⇄ revisar ─(encaja)→ componer ⇄ revisar_propuesta
                                    └→ marketing   (em paralelo com redactar)

redactar escreve o brief como faria um product owner com olhar técnico: se é viável, o que dá para reaproveitar, o que depende do cliente e qual hipótese a primeira etapa testa. marketing lê o mesmo intake pelo lado de quem compra: qual é a dor de verdade, que objeções vão aparecer e por onde vale começar a proposta. Como só precisa do intake, roda ao mesmo tempo que redactar. No LangGraph isso se resolve com duas arestas saindo do mesmo nó.

As regras ficam no código

No começo estava tudo no prompt: não dar preço, não inventar projetos, não pressionar. O modelo respeitava quase sempre, e quase sempre não basta para algo que um cliente vai ler. Hoje, o que dá para verificar é verificado em código.

revisar rejeita o brief se as horas mínimas passam das máximas, se ele cita um projeto que não existe ou se diz “no viable hoy” e ao mesmo tempo marca que encaixa. Quando falha, o rascunho volta para redactar com a lista de problemas, no máximo duas vezes. Se o brief conclui que o projeto não tem a ver com o que fazemos, nenhuma proposta é escrita e a equipe decide lendo o brief.

Com a proposta acontece o mesmo, com regras mais rígidas porque quem lê é o cliente:

PRECIO = re.compile(r"(US\$|R\$|\$|€|\b(usd|brl)\b|\d\s*(mil\s+)?(dólares?|dolares?|reais|reales)\b)", re.I)
PLAZO = re.compile(r"\b\d+\s*(semanas?|meses|mes|días?|dias?|mês)\b", re.I)

Se aparece uma moeda, um prazo, um travessão ou uma frase de pressão como “últimas vagas”, a proposta volta para componer. A palavra “reais” tinha uma pegadinha: também é adjetivo (“dados reais”), então a expressão só conta como moeda quando vem colada a um número.

O modelo nunca define o preço

O brief interno traz o esforço em horas por entrega. O código converte essas horas em custo e semanas com a tarifa da equipe, que fica numa variável de ambiente, então o número final nunca sai do modelo. A proposta usa outro esquema, sem campos de preço nem de data: o modelo não tem onde escrevê-los. A única chamada para ação é agendar uma reunião online de 30 minutos, e o dinheiro é conversado lá.

O assistente do chat também não pergunta sobre orçamento. Um orçamento dito logo no começo ancora a estimativa, e prefiro entender o problema primeiro.

O design vem do código

O modelo preenche um esquema com o conteúdo da proposta e deck.py transforma isso em slides HTML com a identidade visual do site, que o Chromium headless converte em PDF. O modelo não mexe em tipografia, cores nem layout, então todas as propostas saem iguais e nenhuma sai quebrada.

Tudo é gravado em disco antes de enviar o e-mail. Se o envio falhar, o intake, o brief e a proposta já estão salvos e o contato não se perde.

Aprende, mas com alguém no meio

Toda conversa fica salva, mas nenhuma muda o agente diretamente. Se o que um visitante escreve pudesse alterar o comportamento dele, envenená-lo seria trivial.

Toda noite um processo classifica as conversas com o Jev, da TypeSafe: em que categoria cada uma cai, que objeções apareceram e qual pergunta o cliente fez sem receber resposta. Essa pergunta é escolhida entre as mensagens reais do cliente, o modelo não a redige. Às segundas chega um relatório com conversão, objeções e conversas que ficaram pela metade. Com isso ajusto o prompt ou uma regra, e a mudança passa pelo git como qualquer outra.

Como sei que continua funcionando

Uma mudança no prompt pode consertar uma coisa e quebrar outra. Para perceber, tenho evals de conversas completas: um modelo faz o papel de cliente, com uma personalidade tirada de um arquivo de personas, e conversa com o agente até a sessão terminar. Depois a conversa é medida. O que aparece no texto é checado em código: que não use voseo, que não pergunte sobre orçamento, que não mande markdown (o widget não exibe). Outro modelo faz de juiz só onde uma expressão regular não dá conta. Cada rodada fica no LangSmith como experimento, então dá para comparar antes e depois de uma mudança.

O que tem em volta

A API é FastAPI e responde em streaming. Em produção ela roda no Coolify ao lado do site, e o Caddy repassa /api/* pela rede interna: o navegador fala com o mesmo domínio e não é preciso configurar CORS. Os limites também ficam na API, não no modelo: 2.000 caracteres por mensagem, 30 mensagens por sessão e 60 por IP por hora. Uma sessão encerrada recebe um texto fixo e nunca chega ao modelo.

Se quiser ver funcionando, abra o chat e conte um projeto real. A equipe escreve para você com a proposta depois de revisá-la.