Como criar um agente RAG humanizado no WhatsApp com LangChain
Um tutorial completo, da instalação ao primeiro atendimento, para criar um assistente de WhatsApp que consulta os documentos da empresa, entende texto, áudio, imagens e arquivos, responde com clareza e sabe quando chamar uma pessoa.
Por Cesar A. Machado · · atualizado · 15 min
- Você montará um fluxo único para texto, áudio, imagem e documento, sem começar com uma arquitetura cara ou difícil de manter.
- O RAG fará o assistente consultar a base da empresa antes de responder, reduzindo palpites e mantendo o conteúdo atualizável.
- Regras simples controlarão custo, privacidade, duplicidade e transferência para atendimento humano.
- A primeira versão pode ser testada com recursos gratuitos, mas dados reais de clientes exigem uma escolha consciente de privacidade e operação.
O problema que este projeto resolve
Quem atende clientes pelo WhatsApp costuma enfrentar o mesmo ciclo: a pergunta chega, alguém procura a resposta em um PDF, confirma uma regra com outra pessoa, grava um áudio e volta para a conversa. Separadamente, cada tarefa parece pequena. Somadas durante o dia, elas consomem tempo, atrasam vendas e tornam a qualidade do atendimento dependente de quem está disponível naquele momento.
Neste tutorial, construiremos uma primeira versão capaz de receber texto, áudio, imagem ou documento; transformar esse material em uma entrada compreensível; pesquisar informações autorizadas da empresa; redigir uma resposta curta e natural; e devolver texto ou áudio. Quando a informação não existir, a confiança for baixa ou o assunto exigir decisão humana, o sistema não improvisará: ele encaminhará o caso.
O que vamos construir — e o que deixaremos para depois
A solução terá cinco partes. A API oficial do WhatsApp recebe e envia mensagens. Uma pequena aplicação em Python organiza o fluxo. O LangChain conecta o modelo de linguagem à busca. O Chroma guarda os trechos dos documentos. O Gemini interpreta as entradas e redige a resposta. Essa composição evita criar uma plataforma inteira antes de provar que o atendimento realmente economiza trabalho.

Não criaremos uma equipe de agentes conversando entre si, memória ilimitada, painel administrativo ou automações irreversíveis. Para a primeira versão, isso acrescentaria custo e pontos de falha sem melhorar a resposta principal. O objetivo é um caminho curto, observável e barato: receber, normalizar, consultar, decidir e responder.
Grátis nem sempre é a opção mais barata
Para estudar e validar o fluxo, o nível gratuito do Gemini e a execução local do Chroma reduzem bastante a barreira de entrada. Porém, gratuidade pode cobrar de outra forma: limite menor, política de dados diferente, mais manutenção local ou instabilidade de recursos em prévia. O melhor custo não é a menor fatura isolada; é a menor soma entre uso, implantação, suporte, privacidade e horas gastas corrigindo o ambiente.
| Escolha | Vantagem | Custo escondido | Quando usar |
|---|---|---|---|
| Gemini gratuito + Chroma local | Começo rápido e baixo desembolso | Limites e condições de uso dos dados | Protótipo com conteúdo fictício ou público |
| API paga + Chroma local | Pouco trabalho operacional | Cobrança por uso e necessidade de monitoramento | Piloto controlado e produção pequena |
| Modelo e busca totalmente locais | Maior controle dos dados | Hardware, atualização e suporte | Quando privacidade ou volume justificam a operação |
| Serviços gerenciados | Menos manutenção | Mensalidade e dependência de fornecedor | Quando o tempo da equipe vale mais que a infraestrutura |
1. Prepare as contas e as ferramentas
Você precisará de Python 3.12, Git, FFmpeg, uma chave da API do Gemini e um aplicativo no Meta for Developers com WhatsApp Cloud API. Para expor o webhook durante o teste, usaremos o Cloudflare Tunnel. Ele fornece um endereço HTTPS temporário sem exigir que você publique um servidor logo no primeiro dia.
Na Meta, crie um aplicativo do tipo Business, adicione o produto WhatsApp e use o número de teste disponibilizado no painel. Guarde o identificador do número, o token de acesso e o segredo do aplicativo. Para um ambiente real, substitua o token temporário por credenciais apropriadas e aplique o menor privilégio possível. A coleção oficial da Meta explica o envio e o download de mídias. [4]
2. Instale o projeto
Crie uma pasta vazia e um ambiente virtual. O ambiente virtual mantém as bibliotecas deste projeto separadas das demais instalações do computador, o que reduz conflitos e facilita repetir a configuração em outro servidor.
mkdir agente-whatsapp-rag
cd agente-whatsapp-rag
python -m venv .venv
# Windows PowerShell
.\.venv\Scripts\Activate.ps1
# macOS ou Linux
source .venv/bin/activateAgora instale apenas o necessário. LangChain coordena a busca e o modelo; FastAPI recebe o webhook; HTTPX conversa com a Meta; Chroma guarda os vetores localmente; e os carregadores leem PDF, texto e DOCX.
python -m pip install --upgrade pip
pip install langchain langchain-google-genai langchain-chroma langchain-community langchain-text-splitters fastapi "uvicorn[standard]" httpx pydantic-settings pypdf docx2txt python-multipartCrie as pastas app, knowledge, data/chroma e data/temp. Coloque em knowledge somente documentos aprovados para atendimento. Nunca transforme automaticamente toda a pasta compartilhada da empresa em base de conhecimento: documentos antigos, contratos internos e informações pessoais podem virar respostas indevidas.
agente-whatsapp-rag/
├── app/
│ ├── main.py
│ ├── rag.py
│ ├── media.py
│ └── whatsapp.py
├── knowledge/
├── data/chroma/
├── data/temp/
├── .env
└── .env.example3. Configure segredos sem colocá-los no código
Crie um arquivo .env local. Ele não deve entrar no Git. O arquivo .env.example pode conter somente os nomes das variáveis, sem valores reais, para documentar o que o projeto exige. Também mantenha o nome do modelo configurável: modelos, preços e disponibilidade mudam, e você não deve precisar editar a lógica do atendimento para trocar um fornecedor.
GOOGLE_API_KEY=troque_por_sua_chave
GEMINI_MODEL=gemini-3.7-flash
EMBEDDING_MODEL=models/gemini-embedding-001
WHATSAPP_TOKEN=troque_por_seu_token
WHATSAPP_PHONE_NUMBER_ID=seu_id
WHATSAPP_VERIFY_TOKEN=crie_uma_frase_longa
META_APP_SECRET=segredo_do_aplicativo
HUMAN_QUEUE_NUMBER=numero_ou_fila_interna4. Monte uma base de conhecimento que realmente ajude
RAG é a sigla para uma ideia simples: antes de responder, o sistema procura trechos relacionados à pergunta e entrega esses trechos ao modelo. Assim, preços, políticas, prazos e instruções ficam nos documentos, onde podem ser revisados, em vez de escondidos em um comando enorme. O LangChain documenta esse padrão e também abordagens mais autônomas; aqui usaremos a forma controlada porque ela custa menos e é mais fácil de investigar. [1]
Comece com poucos arquivos bons: perguntas frequentes, descrição dos serviços, áreas atendidas, horários, critérios de orçamento e regras de encaminhamento. Dê data e responsável a cada documento. Se duas páginas discordarem sobre um prazo, a IA não corrigirá a governança da empresa; apenas encontrará duas versões incompatíveis.
# app/rag.py
import os
from pathlib import Path
from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_chroma import Chroma
def load_documents(folder='knowledge'):
documents = []
for path in Path(folder).glob('**/*'):
suffix = path.suffix.lower()
if suffix == '.pdf':
documents += PyPDFLoader(str(path)).load()
elif suffix == '.txt':
documents += TextLoader(str(path), encoding='utf-8').load()
elif suffix == '.docx':
documents += Docx2txtLoader(str(path)).load()
return documents
def build_index():
docs = load_documents()
chunks = RecursiveCharacterTextSplitter(
chunk_size=900, chunk_overlap=120
).split_documents(docs)
embeddings = GoogleGenerativeAIEmbeddings(
model=os.getenv('EMBEDDING_MODEL', 'models/gemini-embedding-001')
)
Chroma.from_documents(
chunks, embeddings, collection_name='empresa',
persist_directory='data/chroma'
)Execute build_index apenas quando os documentos mudarem. Indexar a mesma pasta a cada mensagem desperdiça tempo, dinheiro e pode criar cópias repetidas. Em produção, registre a versão da base e substitua o índice de forma controlada. A integração oficial do Chroma mostra como persistir o índice em disco. [3]
5. Faça a busca antes de pedir a resposta
Abra o Chroma existente e recupere poucos trechos. Quatro costuma ser um bom ponto inicial, não uma regra universal. Muitos trechos aumentam custo e podem diluir a informação importante. Poucos demais podem esconder a resposta. Ajuste com perguntas reais e um conjunto de respostas esperadas.
import os
from langchain_chroma import Chroma
from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
embeddings = GoogleGenerativeAIEmbeddings(model=os.environ['EMBEDDING_MODEL'])
store = Chroma(
collection_name='empresa',
embedding_function=embeddings,
persist_directory='data/chroma'
)
retriever = store.as_retriever(search_kwargs={'k': 4})
model = ChatGoogleGenerativeAI(
model=os.environ['GEMINI_MODEL'], temperature=0.2, timeout=30
)
def context_for(question: str) -> str:
docs = retriever.invoke(question)
return '\n\n'.join(
f"Fonte: {d.metadata.get('source', 'desconhecida')}\n{d.page_content}"
for d in docs
)O nome do arquivo acompanha cada trecho para que a resposta possa ser auditada. Em uma versão mais madura, registre também página, data e versão. O modelo precisa receber uma instrução inequívoca: usar apenas o contexto aprovado, admitir quando ele não basta e jamais inventar preço, prazo, política ou disponibilidade.
6. Receba mensagens do WhatsApp com segurança
O webhook tem duas tarefas diferentes. Na configuração, a Meta faz uma requisição GET para confirmar o token de verificação. Durante o uso, envia requisições POST com eventos. Responda rapidamente e processe o trabalho mais pesado em segundo plano. Se o servidor demorar ou cair, o provedor pode reenviar o evento. Por isso, grave o identificador de cada mensagem e ignore repetições já concluídas.
# app/main.py — exemplo reduzido
import os
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.get('/webhook')
async def verify(request: Request):
q = request.query_params
if q.get('hub.verify_token') != os.environ['WHATSAPP_VERIFY_TOKEN']:
raise HTTPException(status_code=403)
return int(q['hub.challenge'])
@app.post('/webhook')
async def receive(request: Request):
raw = await request.body()
verify_meta_signature(raw, request.headers.get('x-hub-signature-256'))
payload = await request.json()
events = normalize_whatsapp_payload(payload)
for event in events:
enqueue_once(event.message_id, event)
return {'ok': True}As funções resumidas no exemplo não são detalhes dispensáveis. verify_meta_signature deve comparar a assinatura do corpo bruto usando o segredo do aplicativo. enqueue_once deve persistir o ID antes do processamento. Em um teste local, pode ser SQLite; em produção, use o banco ou fila já operado pela empresa. Uma coleção em memória não protege contra reinício nem contra dois processos simultâneos.
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
# Em outro terminal
cloudflared tunnel --url http://localhost:80007. Faça a primeira pergunta no terminal
Antes de misturar WhatsApp, áudio e arquivos, prove a parte mais importante: uma pergunta deve encontrar os trechos corretos e produzir uma resposta limitada por eles. Esse teste curto separa um problema de conhecimento de um problema de integração. Se ele falhar, você sabe que ainda não precisa investigar token da Meta, webhook ou conversão de mídia.
from typing import Literal
from pydantic import BaseModel, Field
class Decision(BaseModel):
answer: str = Field(description='Resposta curta para o cliente')
confidence: float = Field(ge=0, le=1)
handoff: bool
reason: Literal['answered', 'missing_context', 'sensitive', 'human_requested']
structured_model = model.with_structured_output(Decision)
def answer(question: str) -> Decision:
context = context_for(question)
prompt = f'''
Você é o assistente virtual da EMPRESA.
Use somente o contexto abaixo.
Se o contexto não sustentar a resposta, use handoff=true.
Nunca invente preço, prazo ou regra.
CONTEXTO:
{context}
PERGUNTA:
{question}
'''
decision = structured_model.invoke(prompt)
if decision.confidence < 0.65:
decision.handoff = True
decision.reason = 'missing_context'
return decision
if __name__ == '__main__':
build_index() # execute novamente somente quando a base mudar
print(answer('Qual é o prazo de atendimento?').model_dump_json(indent=2))Faça três perguntas. A primeira deve estar escrita quase da mesma forma no documento. A segunda deve usar palavras diferentes para pedir a mesma informação. A terceira deve estar fora da base. Nas duas primeiras, confira a fonte recuperada e a fidelidade da resposta. Na terceira, o resultado correto é handoff, não uma frase plausível. Ajuste os documentos antes de aumentar o prompt: uma base clara costuma valer mais que uma ordem complicada.
O limite 0,65 é apenas um ponto inicial e não prova verdade. Modelos podem declarar confiança alta e ainda errar. A proteção real combina texto recuperado, tipos de assunto, regras determinísticas e testes. Para perguntas sobre horário, talvez a busca baste. Para desconto fora da tabela, alteração cadastral ou promessa contratual, a regra pode exigir pessoa independentemente da nota informada pelo modelo.
8. Transforme texto, áudio, imagem e arquivo em uma entrada única
O restante do sistema não deveria conhecer cada detalhe do WhatsApp. Crie um formato interno com message_id, contact_id, kind, text, media_path e mime_type. Texto já chega pronto. Para mídia, primeiro obtenha a URL temporária pela API da Meta, faça o download autenticado, valide tamanho e tipo real do arquivo e salve com nome gerado pelo sistema. Nunca use diretamente o nome enviado pelo usuário. [4]
| Entrada | Preparação | Uso no atendimento |
|---|---|---|
| Texto | Limpar espaços e limitar tamanho | Pergunta principal |
| Áudio | Baixar, validar e converter com FFmpeg quando necessário | Transcrição e intenção |
| Imagem | Baixar e validar MIME, dimensão e tamanho | Descrição do que é relevante |
| PDF ou DOCX | Extrair texto em pasta temporária isolada | Contexto específico daquela conversa |
A integração do LangChain com Gemini aceita mensagens multimodais e documenta entradas de imagem, áudio e PDF. [2] Ainda assim, não envie tudo ao modelo por comodidade. Uma foto de produto pode precisar de visão; um comprovante financeiro deve ir para uma fila protegida; um PDF com centenas de páginas pode ser extraído e pesquisado antes. A regra vem antes da capacidade técnica.
def prepare_input(event):
if event.kind == 'text':
return {'question': event.text, 'attachments': []}
path = download_media_with_limits(event.media_id)
try:
if event.kind == 'audio':
return {'question': transcribe_audio(path), 'attachments': []}
if event.kind == 'image':
return {'question': event.caption or 'Explique esta imagem', 'attachments': [path]}
if event.kind == 'document':
return {'question': event.caption or 'Analise este documento', 'attachments': [path]}
raise ValueError('Tipo de mensagem não aceito')
finally:
schedule_secure_delete(path)9. Escreva como alguém que quer resolver, não impressionar
O tom humanizado nasce de regras concretas. Cumprimente apenas quando fizer sentido. Responda primeiro ao que foi perguntado. Use palavras comuns. Faça no máximo uma pergunta de esclarecimento por mensagem. Não repita o nome do cliente em todas as respostas. Não despeje uma lista de oito itens quando duas frases resolvem. E deixe claro que se trata de um assistente automatizado.
Você é o assistente virtual da EMPRESA.
Responda em português claro e use somente o CONTEXTO fornecido.
Comece pela resposta mais útil, sem elogios automáticos.
Se faltar uma informação indispensável, faça uma pergunta curta.
Não invente preço, prazo, política, estoque ou promessa.
Se houver baixa confiança, reclamação séria, dado sensível ou pedido por uma pessoa, marque handoff=true.
Nunca diga que é humano.
CONTEXTO:
{contexto}
MENSAGEM DO CLIENTE:
{mensagem}Peça uma saída estruturada com answer, confidence, handoff e reason. A IA pode sugerir a decisão, mas seu código valida o formato e aplica as regras finais. Por exemplo: pedido explícito de atendente sempre transfere; confiança abaixo do limite não envia uma certeza inventada; termos sensíveis seguem a política da empresa; e uma resposta vazia vira erro, não silêncio.
10. Una o fluxo sem criar um agente descontrolado
Agora conecte as etapas. A mensagem é normalizada, o conteúdo é preparado, a busca recupera contexto e o modelo produz uma decisão. Em seguida, o código escolhe entre responder, pedir esclarecimento ou transferir. Chamar isso de agente é conveniente, mas a autonomia continua limitada por um caminho explícito.
async def handle_message(event):
if already_processed(event.message_id):
return
mark_started(event.message_id)
try:
user_input = prepare_input(event)
context = context_for(user_input['question'])
decision = await answer_with_schema(
question=user_input['question'],
context=context,
attachments=user_input['attachments']
)
decision = enforce_business_rules(decision, event)
if decision.handoff:
await open_human_handoff(event, decision.reason)
else:
await send_preferred_format(event, decision.answer)
mark_completed(event.message_id)
except RecoverableProviderError as exc:
mark_retryable(event.message_id, exc, max_attempts=3)
except Exception as exc:
mark_failed(event.message_id, exc)
await open_human_handoff(event, 'Falha no processamento')Esse desenho permite responder perguntas importantes: a mensagem foi recebida? Qual documento sustentou a resposta? O provedor demorou? Houve reenvio? Por que o atendimento foi transferido? Sem esses registros, uma demonstração bonita vira uma caixa-preta difícil de sustentar quando os clientes passam a depender dela.
11. Envie a resposta pela API oficial
Comece enviando texto. A função precisa de timeout, deve interromper em erro HTTP e deve devolver o identificador informado pela Meta para auditoria. Deixe a versão da Graph API em uma variável de ambiente, porque ela evolui. O destinatário vem do evento validado; nunca aceite um número arbitrário produzido pelo modelo.
# app/whatsapp.py
import os
import httpx
async def send_text(to: str, body: str) -> str:
version = os.getenv('META_GRAPH_VERSION', 'v23.0')
phone_id = os.environ['WHATSAPP_PHONE_NUMBER_ID']
url = f'https://graph.facebook.com/{version}/{phone_id}/messages'
headers = {'Authorization': f"Bearer {os.environ['WHATSAPP_TOKEN']}"}
payload = {
'messaging_product': 'whatsapp',
'to': to,
'type': 'text',
'text': {'preview_url': False, 'body': body[:3500]},
}
async with httpx.AsyncClient(timeout=15) as client:
response = await client.post(url, headers=headers, json=payload)
response.raise_for_status()
data = response.json()
return data['messages'][0]['id']O corte em 3.500 caracteres é uma margem operacional escolhida para manter respostas legíveis; não é uma promessa sobre o limite atual do WhatsApp. Se o conteúdo ultrapassar isso, prefira resumir ou dividir conscientemente, sem disparar uma sequência longa. Grave o ID da mensagem enviada junto do ID recebido. Assim, o suporte consegue reconstruir o caminho sem depender de capturas de tela.
Para imagens, documentos e áudio, siga o fluxo oficial em duas fases: envie o arquivo para obter um media_id e depois envie a mensagem que referencia esse identificador. [4] Centralize isso no mesmo adaptador. A lógica de negócio pede send_audio ou send_document; somente o adaptador conhece URLs, cabeçalhos e formato do provedor.
12. Responda em áudio somente quando isso ajudar
Áudio é útil para quem está dirigindo, trabalhando com as mãos ou prefere conversar dessa maneira. Também pode aumentar custo, tempo e dificuldade de revisar informações. Uma regra econômica funciona bem: se o cliente enviar áudio, responda em áudio curto; se enviar texto, responda em texto; e permita que ele peça outro formato. Informações críticas, como valores e datas, podem acompanhar o áudio em uma linha escrita.
O Gemini oferece geração de fala, mas a documentação atual identifica modelos TTS em prévia. [6] Trate esse recurso como substituível: uma função recebe texto e devolve o arquivo de áudio. Se o modelo mudar, você troca o adaptador, não todo o atendimento. Depois, converta para um formato aceito pela API do WhatsApp e envie como mídia.
# Exemplo de conversão antes do envio
ffmpeg -i resposta.wav -c:a libopus -b:a 32k resposta.ogg13. Teste o caminho completo antes de chamar de pronto
Teste pelo próprio WhatsApp, não apenas chamando funções isoladas. Envie uma pergunta respondida pela base, uma pergunta ausente, um áudio claro, um áudio incompreensível, uma imagem relevante, um arquivo grande demais e a mesma mensagem duas vezes. Depois desligue temporariamente o acesso ao modelo. O sistema deve continuar seguro e encaminhar o caso, não ficar repetindo tentativas indefinidamente.
| Cenário | Resultado esperado |
|---|---|
| Pergunta coberta pela base | Resposta curta sustentada por fonte registrada |
| Informação ausente | Admite o limite e pergunta ou transfere |
| Mensagem repetida | Um único processamento e uma única resposta |
| Áudio inválido | Erro compreensível e opção de enviar texto |
| Provedor indisponível | Timeout, tentativa limitada e transferência |
| Pedido de atendente | Transferência imediata, sem discussão |
| Arquivo proibido ou excessivo | Rejeição segura antes de processar |
Guarde para cada teste a entrada anonimizada, a versão da base, os trechos encontrados, o modelo usado, a decisão e o resultado do envio. Isso cria uma pequena avaliação repetível. Quando você alterar o prompt ou os documentos, rode o conjunto novamente e descubra se melhorou o atendimento inteiro ou apenas uma pergunta.
14. Erros comuns e como encontrá-los
Se o webhook não verificar, confira a URL pública, o caminho /webhook e o token, sem imprimir segredos. Se mensagens chegarem duplicadas, verifique a persistência do message_id. Se a resposta ignorar documentos, registre os trechos recuperados antes de culpar o modelo. Se a busca estiver fraca, revise conteúdo, divisão dos trechos e pergunta; trocar de modelo é uma etapa posterior, não o primeiro reflexo.
Se áudio falhar, guarde apenas metadados necessários para diagnosticar: tipo informado, tipo detectado, tamanho, duração, etapa e código do erro. Confirme se o FFmpeg reconhece o arquivo. Se o envio falhar, registre o status e o corpo de erro da Meta sem expor token ou conteúdo pessoal. Diferencie falha temporária, entrada inválida e configuração incorreta; cada uma pede uma ação diferente.
15. O que muda antes de atender clientes reais
O protótipo local prova o fluxo, não a operação. Antes de produção, publique a aplicação em HTTPS estável; use banco ou fila persistente; configure backup e restauração; limite requisições; monitore tempo, erro, custo e volume; rotacione segredos; defina retenção e exclusão; e documente quem assume a fila humana. Também obtenha consentimentos e faça a revisão jurídica adequada ao seu contexto.
A política do WhatsApp exige caminhos claros de escalonamento humano para automação de atendimento e impõe regras sobre contato e uso da plataforma. [5] Mantenha o assistente restrito ao atendimento e aos serviços da empresa. Não tente transformá-lo em um chatbot de propósito geral escondido dentro do WhatsApp.
Controle custo com quatro alavancas simples: recupere poucos trechos; resuma histórico antigo; gere áudio apenas quando necessário; e defina limite diário por empresa ou canal. Consulte a tabela atual do Gemini e a cobrança vigente do WhatsApp antes de estimar produção, pois modelos, faixas gratuitas e preços por mensagem podem mudar. [7]
Perguntas frequentes
Preciso usar LangChain? Não. É possível escrever as chamadas diretamente. O LangChain compensa quando você quer trocar componentes, usar carregadores, busca e saída estruturada com um padrão conhecido. Para um único prompt sem documentos, ele provavelmente é desnecessário.
Preciso de um banco vetorial na nuvem? Não para começar. Chroma persistido em disco atende um protótipo e pequenos testes. Quando houver múltiplas instâncias, requisitos de alta disponibilidade ou grande volume, avalie uma opção gerenciada com base em métricas, não em ansiedade de escala.
O agente pode fechar vendas sozinho? Ele pode qualificar, explicar, coletar informações e preparar a próxima etapa. Preço excepcional, compromisso contratual, crédito, dado sensível ou decisão irreversível devem obedecer a regras da empresa e, quando necessário, aprovação humana.
Como reduzir respostas inventadas? Melhore a fonte, registre o que foi recuperado, instrua o modelo a admitir ausência, valide a saída e transfira quando a confiança for baixa. RAG reduz o problema, mas não transforma geração probabilística em certeza.
Comece pequeno, mas comece com controle
Ao final deste caminho, você terá a base de um atendimento multimodal: o WhatsApp recebe a conversa, a aplicação organiza cada formato, o RAG consulta documentos aprovados, o modelo redige a resposta e regras simples decidem quando enviar ou transferir. O ganho não vem de colocar IA em todo lugar. Vem de retirar procura repetitiva, manter a informação revisável e devolver tempo às pessoas para os casos que realmente exigem julgamento.
Fontes oficiais e leitura complementar
[1] LangChain — Agentic RAG: https://docs.langchain.com/oss/python/langgraph/agentic-rag
[2] LangChain — integração Google Generative AI: https://docs.langchain.com/oss/python/integrations/chat/google_generative_ai
[3] LangChain — integração Chroma: https://docs.langchain.com/oss/python/integrations/vectorstores/chroma
[4] Meta — documentação oficial da WhatsApp Cloud API: https://www.postman.com/meta/whatsapp-business-platform/documentation/wlk6lh4/whatsapp-cloud-api
[5] WhatsApp — Política de Mensagens para Empresas: https://www.whatsapp.com/legal/business-policy/
[6] Google AI for Developers — geração de fala: https://ai.google.dev/gemini-api/docs/speech-generation
[7] Google AI for Developers — preços e termos: https://ai.google.dev/gemini-api/docs/pricing e https://ai.google.dev/gemini-api/terms
Um bom agente não tenta parecer humano o tempo todo. Ele entende o contexto, responde com utilidade e chama uma pessoa antes de transformar uma dúvida em problema.