Construindo o Lucas AI: transformando um portfólio em um produto

← Voltar ao blog

22 de abril de 20268 min lido

Construindo o Lucas AI: transformando um portfólio em um produto

LLM UX limitado: inferência em dois saltos, streaming SSE, aterramento estruturado e os limites do produto que mantêm um portfólio de bate-papo honesto.

produtoaifrontend

A maioria dos portfólios deixa de evoluir. Eu queria que este se comportasse um pouco como um produto: i18n-first, UI compartilhada em um monorepo e uma camada Lucas AI que responde em primeira pessoa sobre meu trabalho — somente a partir de contexto estruturado incorporado na implantação. Este post é para engenheiros que desejam a visão geral do sistema: forma de inferência, streaming, aterramento e metas explícitas — não um tour por onde os arquivos estão ou como executar um fork localmente.

Qual problema ele resolve

Páginas estáticas são ruins para acompanhamentos. Se sua vantagem é julgamento — escopo, trade-offs, como você entregou sob restrição — um PDF e um formulário de contato não escalam curiosidade. O Lucas AI é um destino dedicado (/[locale]/ai): pergunte sobre experiência, o site ou o próprio recurso, sem fingir que estou do outro lado do fio em tempo real.

Arquitetura de alto nível

O navegador possui a UI de transcrição; o servidor possui política e gastos. Cada envio é um POST /api/chat com { message, locale } — sem anexos, sem chamadas de ferramentas, sem campos ocultos.

O manipulador constrói um sistema, encaminha para o Groq's OpenAI-compatible /v1/chat/completions, e proxya o fluxo upstream como Server-Sent Events (SSE) para que os tokens sejam renderizados incrementalmente sem armazenar o preenchimento completo na memória na borda. Esse é o mesmo padrão que você usaria por trás de qualquer API de inferência rápida: trate a rota como um adaptador fino, mantenha o formato de fio estável para o cliente.

O aterramento vive no código da aplicação: um objeto de carreira tipado é formatado uma vez em uma string CONTEXT e injetado na mensagem do sistema. Não há vetor de banco de dados e nenhum passo de recuperação no momento da solicitação — o modelo só vê o que você serializou quando construiu o prompt. Essa é uma troca de capacidade (você não pode responder de documentos arbitrários) em troca de latência previsível, custo e superfície de auditoria.

Modelos e chamadas

  • Resposta principal: padrão llama-3.3-70b-versatile no Groq (sobrecarga GROQ_MODEL). O streaming permanece ligado; a rota permite até 60s de geração — o suficiente para uma resposta cuidadosa sem transformar a borda em um trabalhador ilimitado.
  • Classificador de pré-vôo: llama-3.1-8b-instant (configurável como CLASSIFIER_MODEL). É uma chamada separada, não de streaming, com max_tokens: 5, temperature: 0, e um prompt de sistema minimalista que colapsa a decisão em um único token: CAREER ou OFF_TOPIC.

Se o veredito for OFF_TOPIC, a API nunca chama o grande modelo. Ela transmite uma recusa localizada fixa como SSE para que o código do cliente corresponda a uma conclusão “real” — sem ramificação de UI especial para curto-circuito. Se o classificador errar ou expirar, o manipulador falha aberta e executa o modelo principal de qualquer forma: um portão barato não deve se tornar um único ponto de falha para tráfego legítimo.

O portão ainda pode ser desabilitado no host (ambiente do servidor) quando você aceita conscientemente cada turno atingindo o grande modelo — útil se o endpoint pequeno estiver doente, se você estiver testando a carga da resposta sozinho, ou se as políticas do produto mudarem e você abandonar temporariamente o gating. Esse controle vive fora da visão do visitante; é uma alavanca de operações, não uma bandeira de recurso na UI.

Orçamento de saída: assistente max_tokens é limitado (CHAT_MAX_TOKENS, limitado a 256–8192, padrão 2048). Ele combina UX (“as respostas devem terminar”) com economia de unidade na fatura do provedor.

O que enviamos para o servidor (e o que não enviamos)

Cada solicitação contém:

  • message: a vez atual do usuário, aparada, com um teto de 2 000 caracteres (redução de superfície de abuso e injeção de prompt).
  • locale: normalizado para um locale de site suportado. A mensagem do sistema termina com Visitor locale: … para que o modelo responda na idioma do site, não no idioma que a UI do chrome acontece de usar.

O que não enviamos:

  • Nenhuma volta anterior no fio. O backend é uma volta por solicitação: prompt do sistema + uma mensagem do usuário. A thread que você vê na UI não é retransmitida para o modelo em cada envio.

Então “memória” é: CONTEXT publicado + o que ainda é visível no cliente. Essa é uma fronteira deliberada: nenhum histórico de bate-papo do lado do servidor, nenhuma sincronização entre dispositivos, nenhum treinamento em conversas.

Onde a thread vive

A UI persiste mensagens em sessionStorage (lucas-ai-messages). Atualizar na mesma guia mantém a thread; uma nova guia ou dispositivo começa limpa. Há uma segunda chave, lucas-ai-pending, usada quando redirecionamos o usuário após uma troca de locale (mais abaixo).

Se GROQ_API_KEY estiver ausente, a rota ainda retorna SSE: ela transmite a string de erro de configuração localizada para que o shell degrade sem jogar fora o layout.

Aterramento estruturado (não RAG)

O corpus de carreira começa como registros tipados — papéis, pilhas, iniciativas, métricas, tipo (trabalho vs voluntário vs educação vs pessoal), intervalos de tempo e campos narrativos opcionais. Um formatador transforma isso em um bloco markdown-ähnlich envolvido em delimitadores explícitos, por exemplo:

--- CONTEXT (verdade; não contradiga ou estenda além disso) ---

--- Fim do contexto ---

O que entra importa tanto quanto o que fica fora:

  • Bio, pontos fortes, princípios, estilo de trabalho — orientação sem ruído de currículo.
  • Experiência — por empregador: escopo, iniciativas (o quê / impacto / evidência), métricas e campos opcionais contexto / problema / solução / impacto quando você deseja que o modelo fale em resultados, não em palavras-chave.
  • Resumo de duração — meses deduplicados para trabalho profissional com regras que impedem a dupla contagem de sobreposições ou a introdução de projetos de hobby em “anos de experiência”.
  • Uma fatia “produto + implementação” auto-descritiva — não é copy de marketing, mas fatos que você está disposto a defender: forma de hospedagem, abordagem de i18n, como o bate-papo é invocado, comportamento de streaming, comportamento do classificador, onde o estado vive, postura de privacidade. O objetivo dessa fatia é perguntas meta respondidas a partir do mesmo objeto de verdade que as perguntas de carreira — para que “como funciona esse recurso?” não se torne um cheque em branco para o modelo inventar árvores de arquivos ou dependências.

Sobreposto, o sistema é uma lei estrita: primeira pessoa, não fingindo estar ao vivo no Slack, sem acesso a sistemas privados, nenhum “Lucas disse…” de terceira pessoa, CONTEXT como única fonte factual e nenhuma fabricação além do que o CONTEXT afirma para detalhes de implementação.

Essa é como você transforma “não alucine” de uma vibe em escopo testável: o modelo é tão inteligente quanto o pacote que você envia, e o pacote é versionado como código.

Classificador de tópico (o que conta como dentro do escopo)

O trabalho do pequeno modelo é apenas roteamento, não utilidade. CAREER é definido de forma ampla: histórico, habilidades, trabalho enviado, julgamento de produto e perguntas legítimas sobre o próprio site — pilha, pipeline de localização, como o assistente é conectado — na medida em que essa informação existe no CONTEXT. OFF_TOPIC captura tudo o mais (tempo, lição de casa, trivia não relacionada).

Tratar “meta” perguntas como dentro do escopo é uma decisão de produto: um assistente de portfólio deve explicar suas próprias condições de contorno sem abrir toda a web como fonte de conhecimento.

Idioma de entrada vs locale do site (lado do cliente)

A locale da rota dirige idioma de resposta (por meio do prompt). Mas os usuários às vezes digitam em outro idioma enquanto permanecem no UI em inglês, por exemplo.

Ao enviar, o cliente executa franc-min na entrada (comprimento mínimo ~15 chars). Se o idioma detectado não corresponder ao mapeamento ISO 639-3 esperado da locale atual, não postamos silenciosamente para a API. Mostramos um cartão de oferta: botões para router.push(/${targetLocale}/ai) para locales correspondentes, além de “continuar no idioma atual”. Se eles mudarem de locale, armazenamos a mensagem pendente em sessionStorage, navegamos e enviamos automaticamente após a montagem para que a pergunta seja executada com a locale certa no corpo JSON.

Essa é o comportamento do produto: alinhar o idioma do site com o idioma que o usuário está realmente escrevendo, em vez de forçar o modelo a adivinhar ou misturar políticas.

Colocação de UX

O Lucas AI é um destino de navegação e uma seção inicial (distintivo, título, CTA, prompts de exemplo) — não um widget flutuante sobre o fluxo de leitura. O bate-papo de página inteira mantém o padrão opt-in e evita o anti-padrão “surpresa copiloto” onde a UI gerativa luta contra o resto do layout por atenção.

Modos de falha que nos importam

  • Classificador baixo: prossiga para o modelo principal (falha aberta).
  • Groq 401/403/400: retorne JSON llm_auth para o cliente — sem corpo upstream cru (evite vazar chave ou dicas de modelo).
  • 5xx / 429: transmita um erro genérico localizado via mock SSE quando apropriado.
  • Mensagem vazia ou muito grande: 400 com um código de erro estável.

Conclusão

O Lucas AI não é um assistente geral jogado em uma página de marketing. É um produto estreito: um corpo de fatos que você defende, um caminho de resposta de streaming, um pequeno modelo que apenas decide “dentro do escopo ou não” e um servidor que esquece cada turno de propósito. O objetivo é comportamento previsível — o que é enviado ao provedor, o que você paga à API de inferência por solicitação e o que o visitante pode tratar como factual.

Se você construir algo assim, a alavanca não é escolher o maior modelo. É tratar a mensagem do sistema e o CONTEXT como uma especificação — simples, factual, auditável — em vez de copy de marketing, e decidir na arquitetura quando o grande modelo é permitido executar (por exemplo, somente após um portão de tópico).