Blog

Multi-idioma em bot de atendimento: detectar, responder e escalar sem misturar

Um bot que atende em três idiomas parece um problema de tradução, e é aí que o projeto começa errado. O modelo já responde em qualquer idioma sem que você peça, então o código passa a impressão de estar pronto no primeiro teste: você escreve em espanhol, ele responde em espanhol, e a demo convence todo mundo. O que quebra depois não é a fluência do modelo, é tudo que está em volta dela. A base de conhecimento está escrita em português e o retrieval devolve trechos que o cliente não lê. O template do WhatsApp foi aprovado em um idioma só e não pode ser enviado no outro. O cliente manda uma mensagem de duas palavras e o detector muda o idioma da conversa inteira por causa disso. E quando o atendimento escala para um humano, ninguém sabe se existe alguém na fila que fala aquele idioma agora. Este artigo trata multi-idioma como o que ele é de verdade: uma decisão de estado da conversa que atravessa detecção, recuperação, geração, canal e transbordo, e não uma instrução no fim do prompt.

2026-08-07 / IA Aplicada / 14 min

01

O idioma não é um atributo da mensagem, é estado da conversa

A implementação ingênua detecta o idioma de cada mensagem que chega e responde naquele idioma. Funciona nos textos longos e falha exatamente onde o atendimento acontece. Um cliente que conversa em espanhol responde "ok" a uma pergunta, e "ok" não é espanhol para nenhum detector estatístico. Outro escreve "no" para negar algo, e "no" é uma palavra inglesa perfeitamente válida. Um terceiro cola o código de rastreio do pedido, sozinho, e não existe idioma naquilo. Em todos esses casos, um detector por mensagem devolve algo diferente do idioma real da conversa e o bot muda de idioma no meio, o que o cliente lê como falha grave mesmo quando o conteúdo está correto.

A modelagem certa separa dois conceitos que a implementação ingênua confunde. O idioma da conversa é um campo do estado, com um valor e um nível de confiança, e ele governa em que idioma o bot responde. O idioma detectado na mensagem é apenas mais uma evidência que pode ou não atualizar aquele campo. A mensagem curta continua sendo processada normalmente, ela só não tem peso suficiente para virar a chave. O efeito prático é que a conversa fica estável: o bot escolhe o idioma uma vez, com evidência suficiente, e só troca quando existe evidência clara de que o cliente trocou de propósito.

SinalO que ele diz de fatoPeso na decisão
Idioma declarado no perfil ou na contaPreferência explícita do cliente, estável ao longo do tempoAlto, e vence o detector enquanto não houver contradição clara
Detector estatístico na mensagem longaBoa estimativa quando há dezenas de caracteres de texto corridoAlto acima de um mínimo de caracteres e de confiança
Detector na mensagem curta ou sem palavrasRuído: confirmações, números, códigos e emojis não têm idiomaNenhum, a mensagem é atendida mas não move o estado
Idioma da primeira mensagem do atendimentoPonto de partida razoável quando não há perfilMédio, serve de valor inicial com confiança baixa
Pedido explícito para trocar de idiomaIntenção declarada, mais forte que qualquer estatísticaMáximo, troca imediata e registrada na conversa
Idioma do template ou do canal de entradaDiz por onde o cliente veio, não o que ele falaBaixo, apenas desempate quando tudo mais está vazio

A regra de troca precisa ser explícita no código, e não emergir de um limiar solto no detector. Uma formulação que funciona bem em atendimento: troque o idioma da conversa quando o cliente pedir isso em palavras, ou quando duas mensagens consecutivas com texto suficiente forem detectadas no mesmo idioma novo com confiança acima do limiar. Uma única mensagem longa em outro idioma não basta, porque o caso mais comum dela não é troca de idioma, é o cliente colando um texto de terceiro, um e-mail recebido ou uma mensagem de erro do sistema.

02

Detecção com abstenção: o detector precisa poder dizer que não sabe

A maioria dos detectores devolve sempre um idioma, mesmo quando a entrada não tem informação suficiente para sustentar palpite algum. Esse comportamento é razoável em classificação de documentos e é péssimo em atendimento, onde metade das mensagens é curta. O detector útil aqui é o que tem uma terceira saída além de "português" e "espanhol": "indeterminado". Colocar a abstenção antes do classificador é mais barato e mais confiável do que tentar calibrar limiares depois, porque boa parte das mensagens que causam troca indevida nem chega a ser texto: são números, códigos, links e emojis.

// Camada de decisão de idioma: transforma evidência em estado.
// O detector concreto é injetado; o que importa aqui é a política.

const IDIOMAS_SUPORTADOS = new Set(['pt', 'es', 'en']);
const MIN_CARACTERES_UTEIS = 12;
const MIN_CONFIANCA = 0.75;
const MENSAGENS_PARA_TROCAR = 2;

// Remove o que não carrega sinal de idioma: URLs, códigos, números,
// emojis e pontuação. Sobra o texto que realmente pode ser classificado.
const textoUtil = (mensagem) =>
  mensagem
    .replace(/https?:\/\/\S+/g, ' ')
    .replace(/[A-Z0-9]{6,}/g, ' ')
    .replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu, ' ')
    .replace(/[^\p{L}\s]/gu, ' ')
    .replace(/\s+/g, ' ')
    .trim();

// Devolve o idioma OU null. Null aqui significa "não sei", e é uma
// resposta legítima que impede a conversa de trocar de idioma por ruído.
export const detectarIdioma = (mensagem, detector) => {
  const texto = textoUtil(mensagem);
  if (texto.length < MIN_CARACTERES_UTEIS) return null;

  const { idioma, confianca } = detector(texto);
  if (!IDIOMAS_SUPORTADOS.has(idioma)) return null;
  if (confianca < MIN_CONFIANCA) return null;

  return idioma;
};

// Aplica a evidência ao estado. Só troca com pedido explícito ou com
// evidência repetida; caso contrário devolve o estado inalterado.
export const aplicarEvidencia = (estado, mensagem, detector, pedidoExplicito) => {
  if (pedidoExplicito && IDIOMAS_SUPORTADOS.has(pedidoExplicito)) {
    return { idioma: pedidoExplicito, confirmado: true, candidato: null, streak: 0 };
  }

  const detectado = detectarIdioma(mensagem, detector);
  if (!detectado) return estado;

  if (!estado.idioma) {
    return { idioma: detectado, confirmado: false, candidato: null, streak: 0 };
  }

  if (detectado === estado.idioma) {
    return { ...estado, confirmado: true, candidato: null, streak: 0 };
  }

  const streak = estado.candidato === detectado ? estado.streak + 1 : 1;
  if (streak >= MENSAGENS_PARA_TROCAR) {
    return { idioma: detectado, confirmado: true, candidato: null, streak: 0 };
  }

  return { ...estado, candidato: detectado, streak };
};

Duas decisões dessa camada merecem destaque. A primeira é que a limpeza do texto vem antes do detector, não depois: filtrar código de rastreio e link no pré-processamento elimina a maior parte dos falsos positivos sem custo nenhum de modelo. A segunda é que o candidato à troca fica guardado no estado. Sem esse campo, "duas mensagens consecutivas" viraria uma consulta ao histórico a cada turno, e a regra deixaria de ser barata justamente no caminho quente.

Vale reconhecer o limite honesto dessa abordagem: o cliente que mistura dois idiomas na mesma frase, algo comum em fronteiras e em comunidades bilíngues, não é resolvido por classificação por mensagem. Para esse caso, a saída prática não é sofisticar o detector, é registrar a ambiguidade no estado e responder no idioma que o cliente usou para formular a pergunta em si, deixando o restante como contexto.

03

Recuperação: a base de conhecimento é o gargalo real

A parte cara do multi-idioma não é a resposta, é o conhecimento que sustenta a resposta. O modelo escreve em espanhol sem esforço, mas se a base está em português e o retrieval é feito com o texto em espanhol do cliente, a busca por termos praticamente não encontra nada, e a busca vetorial encontra menos do que deveria. O sintoma é característico e enganoso: o bot responde num espanhol impecável, com o conteúdo errado, e a avaliação por idioma dá nota alta enquanto a avaliação por correção despenca.

EstratégiaO que exigeQuando compensa
Traduzir a pergunta para o idioma da baseUma chamada de tradução curta antes do retrievalBase grande em um idioma só e volume moderado por idioma
Embeddings multilíngues no mesmo índiceModelo de embedding treinado entre idiomasConteúdo semanticamente equivalente e busca vetorial dominante
Índice separado por idiomaConteúdo realmente escrito em cada idioma e curadoRegras que mudam por país, não só o texto
Traduzir o trecho recuperado antes de gerarUma tradução por trecho, no caminho quentePoucos trechos por resposta e base que muda com frequência

A escolha entre elas quase nunca é técnica. Se o conteúdo é o mesmo em todos os mercados e só o texto muda, embedding multilíngue ou tradução da consulta resolvem com pouco esforço operacional. Se as regras mudam por país, e em atendimento elas mudam muito, prazo de troca, imposto, política de reembolso, canal de suporte disponível, então índice separado deixa de ser preferência e vira obrigação: traduzir a política brasileira para o espanhol produz uma resposta fluente e factualmente errada para o cliente do México, que é o pior desfecho possível porque parece certo.

Mensagem do cliente
        |
        v
[limpeza do texto] --> vazio/curto? --> mantém idioma do estado
        |
        v
[detector com abstenção] --> null --> mantém idioma do estado
        |
        v
[política de troca] --> idioma da conversa (estado persistido)
        |
        +--> [retrieval] --> índice do idioma OU consulta traduzida
        |                       |
        |                       v
        |                 trechos + idioma de origem do trecho
        |
        +--> [geração] --> responde no idioma da conversa
        |
        +--> [canal] --> template aprovado nesse idioma?
        |                   não --> usa idioma de fallback declarado
        |
        +--> [transbordo] --> fila com atendente do idioma
                                não há --> política explícita de espera

04

Geração: instruir o idioma sem deixar o prompt escolher sozinho

Com o idioma resolvido no estado, a geração fica quase trivial, desde que o prompt seja explícito sobre a assimetria que existe ali. O modelo recebe um idioma de resposta obrigatório e trechos de contexto que podem estar em outro idioma. Sem instrução clara, dois erros aparecem com frequência: o modelo espelha o idioma do contexto recuperado em vez do idioma pedido, e o modelo traduz nomes próprios, códigos de produto e status de pedido que precisam permanecer exatamente como estão no sistema.

// Montagem do prompt com idioma explícito e contexto possivelmente
// em outro idioma. A instrução separa idioma de saída de idioma de fonte.

const montarPrompt = ({ idiomaConversa, trechos, pergunta, nomesLiterais }) => {
  const contexto = trechos
    .map((t, i) => `[${i + 1}] (idioma da fonte: ${t.idioma})\n${t.texto}`)
    .join('\n\n');

  const system = [
    `Responda SEMPRE em ${idiomaConversa}, independentemente do idioma dos trechos de contexto.`,
    'Os trechos podem estar em outro idioma. Use o conteúdo deles, traduza o sentido, nunca copie o texto original.',
    `Não traduza nem reescreva estes valores literais: ${nomesLiterais.join(', ')}.`,
    'Se os trechos não contiverem a informação, diga isso no idioma da resposta em vez de inferir.',
  ].join('\n');

  return {
    system,
    user: `Contexto:\n${contexto}\n\nPergunta do cliente:\n${pergunta}`,
  };
};

// Verificação pós-geração: o idioma da resposta bate com o pedido?
// Falhar aqui é raro, mas é barato de checar e evita entregar errado.
export const responderComIdiomaGarantido = async ({ estado, trechos, pergunta, chamarModelo, detector }) => {
  const nomesLiterais = trechos.flatMap((t) => t.identificadores ?? []);
  const prompt = montarPrompt({
    idiomaConversa: estado.idioma,
    trechos,
    pergunta,
    nomesLiterais,
  });

  const resposta = await chamarModelo(prompt);
  const idiomaSaida = detectarIdioma(resposta, detector);

  // Só reprocessa quando o detector tem certeza de que errou o idioma.
  // Se o detector se absteve, aceita a resposta: reprocessar por dúvida
  // dobra o custo e a latência sem evidência de erro.
  if (idiomaSaida && idiomaSaida !== estado.idioma) {
    return chamarModelo({
      ...prompt,
      system: `${prompt.system}\nA resposta anterior saiu no idioma errado. Responda obrigatoriamente em ${estado.idioma}.`,
    });
  }

  return resposta;
};

A verificação depois da geração é barata porque a resposta do bot costuma ser longa o suficiente para o detector ter confiança, o que é justamente o caso em que ele funciona bem. Repare que o reprocessamento só acontece quando o detector afirma um idioma diferente, nunca quando ele se abstém: tratar abstenção como erro dobraria o custo de uma fração relevante das respostas sem nenhuma evidência de que algo saiu errado.

05

Canal e transbordo: onde o multi-idioma deixa de ser problema de modelo

Fora do modelo estão as duas restrições que mais derrubam projeto multi-idioma em produção, e nenhuma delas se resolve com prompt. A primeira é o template. Em canais como o WhatsApp Cloud API, a mensagem iniciada pela empresa fora da janela de atendimento precisa de um template aprovado, e a aprovação é por idioma. Um template aprovado em português simplesmente não existe em espanhol até que alguém o submeta e a aprovação saia. Isso significa que a matriz de templates por idioma é um item de operação com prazo próprio, e que o código precisa consultar a disponibilidade antes de tentar enviar, com um idioma de fallback declarado por template em vez de deixar o envio falhar.

  1. Antes de enviar, consulte a disponibilidade do template no idioma da conversa, não presuma que ele existe.
  2. Se não existir, use o idioma de fallback declarado naquele template e registre a substituição no evento de envio.
  3. Se nem o fallback existir, não envie o template: converta para uma mensagem dentro da janela de atendimento ou adie.
  4. Monitore a taxa de substituição por idioma, porque ela é o indicador antecedente de que um mercado inteiro está sendo atendido no idioma errado.

A segunda restrição é o transbordo. Escalar para um humano em atendimento multi-idioma exige que a fila conheça o idioma e que exista alguém capaz de atender nele agora, o que raramente é verdade em todos os turnos do dia. O erro comum é tratar isso como detalhe de roteamento e descobrir na madrugada que a conversa em espanhol ficou parada esperando um atendente que só entra às nove da manhã. A decisão precisa ser explícita e escrita antes de acontecer: ou a conversa espera com um aviso honesto de prazo, ou é atendida em um idioma comum com o consentimento do cliente, ou entra num fluxo assíncrono de retorno. Qualquer uma das três é aceitável, e nenhuma delas é aceitável quando descoberta pelo cliente no meio da espera.

Situação no transbordoPolítica ruimPolítica defensável
Não há atendente do idioma no turno atualEnfileirar e deixar o cliente esperando sem avisoInformar o prazo real e oferecer retorno assíncrono ou outro idioma
Há atendente, mas a fila está longaPrometer atendimento imediato para não perder o clienteMostrar a posição e manter o bot resolvendo o que dá para resolver
Cliente aceita ser atendido em outro idiomaTrocar em silêncio e ninguém registraRegistrar o consentimento no estado e marcar a conversa como idioma convertido
Histórico da conversa está em idioma diferente do atendenteEntregar o histórico cru e esperar que ele se vireAnexar um resumo traduzido, preservando o original para auditoria

06

Medir por idioma, porque a média esconde o mercado que está quebrado

Um bot multi-idioma com noventa por cento do volume em português e dez por cento distribuídos em espanhol e inglês tem um problema estrutural de medição: qualquer métrica agregada é, na prática, a métrica do português. O mercado menor pode estar com resolução muito abaixo, e o painel geral não se move o suficiente para alguém perceber. Quando a queda é descoberta, ela costuma vir por reclamação comercial, meses depois, e nesse ponto o dado para diagnosticar a causa raiz já rotacionou.

  • Segmente todas as métricas de desfecho por idioma da conversa: resolução sem humano, transbordos, retorno do cliente sobre o mesmo assunto e custo por conversa.
  • Acompanhe a taxa de trocas de idioma por conversa: um valor subindo indica detector instável, não cliente indeciso.
  • Meça a cobertura do retrieval por idioma, contando as respostas geradas sem nenhum trecho recuperado com pontuação mínima aceitável.
  • Monitore a taxa de substituição de template por idioma, que revela o mercado sendo notificado no idioma errado antes de qualquer reclamação.
  • Mantenha um conjunto de avaliação por idioma com casos escritos por falantes nativos, e não traduzidos do conjunto em português, porque a tradução preserva a estrutura da pergunta original e some justamente com as formas de perguntar que só existem naquele idioma.
  • Alerte por idioma com limiar próprio e volume mínimo, para que o mercado pequeno gere sinal sem inundar o canal de alertas com ruído estatístico.

O ponto mais frequentemente ignorado da lista é o último item sobre o conjunto de avaliação. Traduzir os casos de teste do português é o caminho barato e produz uma avaliação que aprova o sistema enquanto ele falha em produção, porque os casos traduzidos herdam a estrutura da pergunta em português. O cliente mexicano não pergunta a mesma coisa traduzida, ele pergunta outra coisa, com outro vocabulário, sobre outras regras. Um conjunto de trinta casos reais escritos por falante nativo diz mais sobre a qualidade naquele mercado do que trezentos casos traduzidos.

FAQ

Perguntas frequentes

Por que não detectar o idioma a cada mensagem e responder no idioma detectado?

Porque boa parte das mensagens de um atendimento não carrega sinal de idioma nenhum. Confirmações curtas, números de pedido, códigos de rastreio, links e emojis fazem qualquer detector estatístico devolver um palpite fraco, e como a maioria dos detectores sempre devolve algum idioma, esse palpite vira uma troca indevida no meio da conversa. O cliente lê isso como falha grave mesmo quando o conteúdo está correto. A modelagem que aguenta produção guarda o idioma como campo do estado da conversa, com confiança associada, e trata a detecção de cada mensagem apenas como evidência: mensagem sem texto útil suficiente é atendida normalmente mas não move o estado, e a troca só acontece com pedido explícito do cliente ou com duas mensagens consecutivas de texto suficiente no mesmo idioma novo.

A base de conhecimento precisa ser traduzida para cada idioma atendido?

Depende de o conteúdo mudar ou apenas o texto mudar. Se a informação é a mesma em todos os mercados, você evita traduzir a base usando embeddings multilíngues no mesmo índice ou traduzindo a pergunta do cliente para o idioma da base antes do retrieval, o que custa uma chamada curta por consulta. Se as regras mudam por país, e em atendimento elas mudam bastante, prazo de troca, imposto, política de reembolso e canais disponíveis, então índice separado por idioma deixa de ser preferência e vira necessidade: uma tradução da política brasileira produz uma resposta fluente e factualmente errada para o cliente do México, que é o pior desfecho possível porque parece correto e não dispara nenhuma verificação. O sinal de que você está no caso errado é a resposta sair impecável no idioma e errada no conteúdo.

O que fazer quando o cliente precisa de humano e não há atendente do idioma dele?

Decidir antes, e não no momento em que a conversa já está esperando. As três saídas defensáveis são informar o prazo real de espera até entrar um atendente daquele idioma, oferecer atendimento em um idioma comum com consentimento explícito registrado no estado da conversa, ou converter para um fluxo assíncrono com retorno agendado. Todas são aceitáveis para o cliente quando declaradas na hora; nenhuma é aceitável quando ele descobre sozinho, depois de vinte minutos na fila. Independentemente da escolha, o histórico entregue ao atendente deve vir com um resumo no idioma que ele fala, preservando o original para auditoria, senão o transbordo apenas transfere o problema de idioma do bot para a pessoa.

Multi-idioma é decisão de estado, não instrução no prompt

O modelo já fala os idiomas do seu mercado, e é por isso que o problema passa despercebido até chegar em produção. O que quebra é o retrieval buscando em um idioma e respondendo em outro, o template que não existe aprovado naquele idioma, o detector trocando o idioma da conversa por causa de um "ok", e a fila de humanos que não tem quem atenda às três da manhã. Tratar o idioma como campo do estado da conversa, com política explícita de troca, retrieval consciente do idioma, matriz de templates monitorada e transbordo com política escrita, é o que separa a demo convincente do atendimento que funciona no terceiro mercado.