DEV Community

Alberto Luiz Souza
Alberto Luiz Souza

Posted on

System design com IA aplicada: desenhando o suporte automatizado de um marketplace de comida

Disclaimer

Este texto foi inicialmente concebido pela IA Generativa em função da transcrição de uma live do canal Dev Eficiente, com Alberto Souza e Daniel Romero. Se preferir acompanhar por vídeo, é só dar o play.

Introdução

Você pediu um hambúrguer sem queijo e o pedido chegou com queijo. Ou pediu dois e veio um. Ou a comida chegou fria. Você abre o chat de suporte do aplicativo e, em poucos segundos, recebe uma resposta que resolve o problema: reembolso, cupom ou reenvio do pedido. Como um sistema desses funciona por dentro?

Nesta live, desenhamos ao vivo a arquitetura de um sistema de automação de suporte ao cliente para um marketplace de comida, deliberadamente inspirado no iFood, como forma de estudo. Daniel Romero, que lidera a Especialização em Engenharia de IA do Dev + Eficiente, conduziu o desenho da arquitetura, e ao final mostramos a implementação funcionando, com o código aberto para quem quiser explorar.

Neste post você encontra o raciocínio completo por trás do design: os requisitos, cada etapa do fluxo, as decisões sobre onde usar LLM e onde usar código puro, a demonstração prática e uma seção dedicada às perguntas que a audiência fez durante a live.

Os requisitos do desafio

Todo exercício de system design começa pelos requisitos. Os funcionais que definimos:

  • Classificar a intenção do usuário entre 15 categorias predefinidas
  • Resolver automaticamente as 6 categorias mais comuns (reembolso, cupom, reenvio do pedido, entre outras), que chegam em grande volume
  • Para as demais categorias, escalar para um atendente humano com um resumo pronto da situação: o que já foi feito, qual a reclamação e o nível de urgência
  • Acessar os dados do pedido: status, localização, tempo estimado
  • Nunca prometer um reembolso acima de R$ 50,00 sem aprovação humana (valor hipotético, definido para o exercício)

E os não funcionais:

  • Primeira resposta rápida, inferior a 5 segundos
  • Meta de 40% das resoluções feitas de forma automática
  • Se o cliente pedir para falar com um humano, transferir imediatamente, sem insistir na interface de chat

O fluxo, etapa por etapa

Mensagem do usuário e verificação inicial

Tudo começa com a mensagem do cliente no chat. E ela pode ser qualquer coisa: um "boa noite", ou já uma reclamação direta como "veio com queijo e eu pedi sem". A primeira etapa é uma verificação que funciona como um loop de triagem: se for apenas uma saudação, uma camada leve responde rapidamente; se já for um pedido bem descrito, o fluxo segue adiante.

Busca no banco e construção do contexto

Com uma mensagem relevante em mãos, o sistema faz uma busca no banco de dados relacional (no projeto de exemplo, um Postgres) para recuperar pedido, restaurante e cliente. Com esses dados, ele monta um contexto em texto que fica disponível para as chamadas de LLM ao longo do fluxo inteiro. Se o pedido nem existir, a API responde direto e não abre o atendimento.

Primeiro guardrail: o cliente pediu um humano?

Antes de qualquer chamada de LLM, entra o primeiro guardrail: verificar se o cliente pediu explicitamente para falar com uma pessoa. Se pediu, o sistema transfere imediatamente para a fila de atendimento humano. Se não pediu, o fluxo continua.

O detalhe importante: esse guardrail é código puro, sem LLM. Não há inteligência artificial nessa verificação, e é proposital. Você não quer depender de um componente probabilístico para garantir uma regra de negócio crítica, ainda mais com um requisito de primeira resposta abaixo de 5 segundos.

Classificação com output estruturado

Passado o guardrail, um classificador identifica a intenção da mensagem entre as 15 categorias predefinidas. Aqui pode entrar um LLM ou um modelo baseado em Transformers treinado para a tarefa (falamos mais sobre essa escolha na seção de perguntas).

A saída dessa classificação é estruturada. No exemplo em Python, usamos a biblioteca Instructor com Pydantic: o modelo é forçado a preencher um esquema de dados com uma das categorias válidas. Se ele gerar algo que não se encaixa no esquema, a validação falha e ele tenta de novo até preencher corretamente. Isso elimina uma camada inteira de possíveis alucinações que virariam bugs difíceis de rastrear no restante do fluxo. Outra vantagem da biblioteca é abstrair o provider: a mesma interface de output estruturado funciona com OpenAI, Gemini, Claude e outros.

Router: literalmente um if

Com a categoria em mãos, um roteador decide o caminho. E aqui não há mistério: é um if em código. A categoria está entre as seis automatizáveis? Segue para a resolução automática. Não está? Escala para o atendimento humano.

Resolução automática: LLM com ferramentas

No caminho da resolução automática, entra um LLM com acesso a um conjunto de tools: consultar dados do pedido, verificar o tempo decorrido, abrir comunicação com a loja, acionar o reembolso automático, salvar o histórico da resolução no banco e encaminhar para o humano se necessário.

Esse LLM gera a ação de resolução e a mensagem de resposta ao cliente. Um exemplo: o classificador identificou um pedido de reembolso, o router confirmou que é automatizável, e o LLM consulta os dados, verifica as condições e aciona o reembolso com a mensagem apropriada.

Segundo guardrail: o limite de R$ 50,00

Antes de disparar um reembolso, entra o segundo guardrail: o valor é maior que R$ 50,00? De novo, código simples, sem LLM. Se o valor está abaixo do limite, o fluxo segue com o reembolso automático e a mensagem ao cliente. Se está acima, o guardrail tem o poder de desviar o fluxo e escalar para o humano.

Escalation: o resumo que chega pronto para a pessoa atendente

Quando o caso escala, seja pela categoria, seja pelo valor, um LLM de escalation entra em cena com um propósito diferente: gerar um resumo do caso para a fila de atendimento humano. Ele analisa todo o histórico e produz um texto com o que aconteceu, há quanto tempo o cliente espera, o senso de urgência e o contexto relevante para a decisão.

É aqui que o uso de LLM se justifica de verdade: extrair contexto textual de diversas fontes e sintetizar algo de valor para quem vai tomar a decisão final compensa muito mais do que tentar prever todos os cenários possíveis em templates predeterminados.

Isso é um agente? Não, e a distinção importa

Olhando de longe, um LLM dentro de uma interface de chat, com acesso a um monte de ferramentas, pode dar a impressão de ser um agente. Mas não é, e a distinção é central no design.

Todas as decisões de caminho do fluxo são determinísticas, amarradas a condicionais em código. O LLM nunca escolhe a próxima etapa: ele executa uma tarefa dentro da etapa em que foi chamado. Um agente, muitas vezes, tende ser a interpretado como algo mais autônomo: ele poderia escolher qual ferramenta chamar, quando chamar e inclusive escolher não chamar.

Uma boa referência de comparação são os assistentes de código, como Claude Code. Neles, o fluxo não é determinístico: você pede uma análise, o modelo escolhe um caminho; fecha a sessão, pede de novo, ele pode escolher outro. Quem determina o fluxo é o modelo. No nosso design, quem determina o fluxo é o código, e o LLM entra só onde ele é necessário. Não faz sentido ter LLM do início ao fim, em todas as fases, simplesmente porque não faz sentido: cada chamada adiciona latência, custo e variabilidade onde muitas vezes um if resolve.

Esse desenho segue os padrões de workflows descritos no artigo Building Effective Agents, da Anthropic, que é uma das bases do módulo de engenharia de agentes da Especialização.

A demonstração prática

Ao final da live, mostramos a arquitetura implementada como uma API em FastAPI, com Postgres e um seed de dados simulando restaurantes, clientes e pedidos com problemas. O código está aberto no GitHub, no repositório suporte-ai do perfil infoslack (o projeto final da Especialização também está público, no repositório finlab).

Quatro cenários demonstrados:

  1. Item errado no pedido. O cliente relata que recebeu itens que não pediu. O fluxo classifica a intenção como wrong item, o router confirma que é automatizável e o sistema responde com o reenvio dos itens corretos.
  2. Reembolso acima do limite. Pedido de reembolso completo de R$ 89,90 por qualidade ruim. O guardrail detecta o valor acima de R$ 50,00 e escala para o humano com um resumo que inclui um dado decisivo: o cliente tem 120 pedidos e apenas 4 reembolsos nos últimos 30 dias. Você não quer perder um cliente desses, e essa síntese de contexto é exatamente o que o atendente precisa para decidir.
  3. Categoria não automatizável. Reclamação sobre o comportamento do entregador. O fluxo escala com resumo detalhado: valor do pedido, atraso na entrega, histórico do cliente e sugestão de ação.
  4. Pedido explícito de atendimento humano. "Quero falar com o atendente humano agora." A resposta é um piscar de olhos: o primeiro guardrail intercepta, transfere direto para a fila e ainda gera o resumo do caso puxando informações do banco.

Em todos os cenários, a comunicação interna acontece como uma sequência de outputs estruturados: o output do classificador vira input do router, o output do router vira input da resolução, e assim por diante, até a resposta final.

As perguntas da live

A audiência mandou mais de 35 perguntas durante a live. Reunimos aqui as principais, com as respostas.

A verificação inicial deveria usar LLM ou SLM?

Depende do momento. Para validar a ideia ou um MVP rapidamente, um LLM resolve. Com um pouco mais de tempo, um Small Language Model dá mais performance. Mas, em produção, para essa tarefa específica de classificação binária (é saudação ou não?), provavelmente nenhum dos dois: um modelo como o ModernBERT, treinado para a tarefa, faz inferência em CPU com baixa latência. A diferença conceitual é que um LLM é treinado massivamente para gerar texto, enquanto o BERT é treinado para compreender e produzir um output extremamente limitado, o que é exatamente o que uma classificação precisa.

O BERT não seria mais lento e mais custoso que um LLM?

Não. Para treinar, depende do cluster de GPU. Para inferência, ele pode ser servido inteiramente em CPU e dá conta.

A abordagem é a mesma do RAG clássico, com conhecimento vetorizado?

Não. No desenho não há nem vector database: o sistema de retrieval vem de uma base relacional. E o LLM não monta a consulta SQL: existem funções predeterminadas para buscar as informações do cliente e da loja, e o modelo só preenche o parâmetro (o ID). São tools parametrizadas, altamente determinísticas.

E se a API do LLM cair ou estourar o timeout?

Para um negócio que precisa escalar e tem preocupação real com latência, a resposta é trazer a camada de LLM para dentro da própria infraestrutura, em vez de depender de um provider externo que pode adicionar latência ou ficar fora do ar. Se você roda na AWS, por exemplo, pode servir o modelo no Bedrock, com possibilidade de customização e fine-tuning. Pense no LLM como mais um componente, mais uma camada dentro do seu software. Quando o negócio não sustenta esse custo, uma camada intermediária de roteamento entre providers é uma alternativa.

Dá para cachear respostas de dúvidas parecidas?

Nesse problema específico, não compensa. Cada caso é um caso, e o risco de colisão é alto: você não quer alguém reclamando de atraso e o chat respondendo com uma promessa de reembolso reaproveitada de outro contexto.

Qual LLM escolher?

Depende das regras de negócio e das decisões técnicas do produto. Resposta em tempo real ou assíncrona muda a infraestrutura, o tamanho e o tipo do modelo. Um domínio específico, como jurídico, contábil ou médico, pede um modelo mais especializado nos termos técnicos da área, em vez de algo genérico que tenta cobrir tudo.

Em que momento as tools são chamadas?

Depois que a mensagem passou pelo guardrail, pelo classificador e pelo router. Só então o LLM da etapa de resolução entra em ação e começa a consumir as ferramentas: consulta os dados do cliente, abre comunicação com a loja e assim por diante. Muitas dessas chamadas podem ser paralelas, com um agregador combinando as respostas em um único output para a decisão seguinte, exatamente o padrão de parallelization descrito no artigo da Anthropic.

Como o LLM sabe qual tool chamar?

Pela cadeia de outputs estruturados. O classificador produz uma saída estruturada, o router lê essa saída e decide, e o que chega à etapa de resolução também é estruturado. As tools são funções determinísticas e parametrizadas, que aguardam a variável do parâmetro ser preenchida pelo modelo com base no que ele recebeu. Não é preciso nada muito dinâmico, como as function callings de antigamente, nem um desenho inchado com MCP.

Esse fluxo poderia ser feito com LangGraph ou MCP?

Poderia. Mas em aplicações grandes no mercado, especialmente fora do Brasil, a preferência tem sido construir internamente e assumir o controle. Frameworks recentes mudam com frequência e quebram coisas no caminho; não é uma crítica técnica ao framework, é uma questão de maturidade. A abordagem apresentada na live, e no projeto dentro da Especialização, usa API própria, com base no que vimos funcionar em produção.

Como controlar alucinações na classificação?

Duas camadas. Primeiro, reduzindo o escopo do modelo: um BERT treinado para classificar entre poucas categorias tem muito menos espaço para inventar. Segundo, o próprio esquema do output estruturado trava a saída: se as alternativas válidas são reembolso ou novo envio, o modelo não consegue preencher com uma terceira opção. Se alucinar, a validação falha e ele tenta de novo dentro da janela de tempo da resposta.

Como equilibrar baixa latência com picos de acesso sem estourar o custo?

O maior gargalo do desenho é a dependência de um provider externo de LLM. Trazer essa camada para dentro da infraestrutura já reduz bastante a probabilidade de picos disparados em requests externos. O restante é estratégia clássica de software: pool de conexões, cache, réplicas de leitura no banco, escala da aplicação.

Todo esse processo é disparado a cada mensagem?

Não. Existem cortes no pipeline: quando o modelo chama uma tool, quando passa pelo classificador. São pequenas interrupções em intervalos de milésimos de segundo, o que dá a falsa sensação de um disparo único. E depois que a conversa foi iniciada, partes do fluxo, como o filtro inicial, não precisam ser executadas de novo.

Não daria para resolver tudo de forma determinística, sem LLM?

Boa parte, sim, e o desenho faz exatamente isso. Mas há dois pontos onde a interpretação semântica agrega: entender a intenção do cliente na classificação e, principalmente, gerar o resumo para o atendente humano. Quando você coleta contexto de várias fontes para sintetizar um relatório que apoia uma decisão, compensa muito mais deixar o modelo escrever do que tentar prever todos os cenários em templates fixos.

E o risco de prompt injection?

O ponto de entrada seria a mensagem do usuário, mas o desenho trava o risco de duas formas. A verificação inicial pode incluir detecção determinística, em código puro, de tentativas de injeção de comandos. E a busca no banco é determinística e parametrizada: não existe text-to-SQL, em que um modelo interpreta o texto do usuário para montar a query, cenário clássico em que uma injeção poderia levar o modelo a interpretar um delete ou um drop. Aqui, o que sai da verificação só tem uma opção: preencher o parâmetro de uma função pronta.

Conclusão

O desenho completo, visto de longe, é bem linear: verificação, busca, guardrail, classificação, roteamento, resolução ou escalation. E essa é a principal lição do exercício: um sistema de suporte com IA em produção é, antes de tudo, um workflow determinístico, em que o LLM entra apenas onde a interpretação semântica agrega valor real. Guardrails de regras críticas ficam em código puro. A comunicação entre etapas acontece por outputs estruturados, que travam alucinações antes de virarem bugs. E ferramentas são funções parametrizadas, não escolhas livres do modelo.

Esse estudo de caso está implementado e disponível como conteúdo dentro da Especialização em Engenharia de IA, liderada por Daniel Romero, junto com os fundamentos que sustentam cada decisão apresentada aqui: information retrieval, RAG e suas estratégias, engenharia de agentes, qualidade e validação de sistemas com LLM.

Dev + Eficiente

Desenvolva software de alta qualidade e domine Engenharia de IA com o Dev + Eficiente. Cursos práticos, acesso vitalício, comunidade ativa e acesso a vagas remotas exclusivas em diversas empresas de tecnologia. Sua jornada para se tornar um dev mais eficiente pode começar agora.

Top comments (0)