2026-09-13 · Oliver
Olá! Bem vindo ao curso de AI Engineering do CodsLab.
O objetivo desse curso é dar uma primeira visão sobre a área de AI Engineering.
Mais especificamente, daremos uma introdução sobre LLMs e foundation models, um pouco de como esses modelos funcionam, e explorar algumas das principais ferramentas e técnicas usadas para transformá-los em componentes de sistemas de software, com um único propósito: desenvolver um app "powered by AI".
Vale notar que não iremos focar em Machine Learning, nem estudar toda a matemática por trás desses modelos, e sim entender o suficiente para utilizá-los de maneira consciente, avaliável e confiável dentro de aplicações reais.
Existem várias definições para o termo
AI engineering is the process of combining systems engineering principles, software engineering, computer science, and human-centered design to create intelligent systems that can complete certain tasks or reach certain goals. [https://professionalprograms.mit.edu/blog/technology/artificial-intelligence-engineering/]
Outra definição, utilizada por Chip Huyen:
AI engineering refers to the process of building applications on top of foundation models. People have been building AI applications for over a decade—a process often known as ML engineering or MLOps (short for ML operations) [Chip Huyen's AI Engineering, 2025]
E uma definição próxima da prática atual da indústria:
AI Engineers bridge the gap between traditional software engineering and artificial intelligence. They spend their days orchestrating AI APIs, implementing RAG systems, and building robust evaluation systems to ensure their applications perform reliably. [Matt Pocock, AI Hero]
As definições variam um pouco devido à área ser bem recente. No nosso caso, usaremos a seguinte ideia:
AI Engineering é a aplicação de fundamentos de engenharia de software à construção de sistemas cujo comportamento depende, em parte, de modelos de IA.
Dessa forma, continuamos lidando com preocupações de um desenvolvedor comum: APIs, banco de dados, filas, caching, observabilidade, testes, escala, e sistemas distribuídos, mas adicionamos um componente extra: um modelo probabilístico.
Um AI Engineer pode, em geral:
Alguns exemplos concretos (sem wrappers plz):
Em geral, todos os exemplos envolvem um sitema determinístico, denotando que o modelo é apenas uma parte do sistema.
As fronteiras não são absolutas e podem variar de empresa para empresa, mas em suma:
Um MLE tradicional trabalha mais com o ciclo de vida dos modelos de Machine Learning, com fatores importantes: datasets, engenharia de features, treinamento, métricas, pipelines de ML, etc. Normalmente, exige uma base maior em ML, estatística, matemática, otimização e dados.
Já em AI engineering, geralmente partimos de um foundation model já treinado, como GPT, Claude, Qwen, etc, e trabalhamos em como adaptar e integrar esse modelo a um projeto.
Para Ciência de Dados, a pergunta geral é: O que podemos aprender e prever com base em um dataset? Note que isso já difere de SWE, que temos como uma base.
Essa área, também chamada (ou parte) de AI Infrastructure, se preocupa em como executar, implantar e servir um modelo foundation de maneira eficiente. Algumas perguntas típicas são: "como servir um modelo para milhares de usuários?", "como distribuir inferência entre múltiplas GPUs e máquinas?", "como diminuir consumo de memória?".
É aqui que encontramos conceitos como CUDA, quantização, batching, KV cache, escalonamento de GPU, etc. Ou seja, há menos foco na aplicação de modelos, e mais foco na otimização destes.
Promp Engineering é uma das técnicas utilizadas em AI Engineering que se refere à construir prompts considerando especificações: contexto, objetivo, restrições, exemplos, formato de resposta, etc.
Aprender a escrever boas instruções é importante. Mas a área não é só isso.
Também vale notar que um AI Engineer é diferente de um desenvolvedor que utiliza IA para construir. Um não exclui o outro, mas o foco do segundo é em usar ferramentas, agentes, etc que utilizam IA para escrever software, e não necessariamente ter IA em produção.
Quando estamos trabalhando com AI-powered apps (agentes, loops, etc), em geral, utilizamos um modelo de IA no núcleo do componente, e de forma geral, LLMs e modelos foundation são o tipo mais comum para esse propósito.
Um modelo de linguagem é um modelo capaz de codificar e atribuir informações estatísticas (probabilidades) sobre linguagens, possibilitando determinar o quão provável é que uma palavra apareça em um dado contexto.
De maneira simplificada, dado um contexto:
Minha cor favorita é ___
Um modelo de linguagem pode atribuir probabilidades diferentes para possíveis continuações:
A arquitetura abaixo dá um exemplo, com o modelo Transformer, que veremos depois (Referência)

Para aprender a distribuição por trás dessas palavras e determinar a probabilidade, o modelo aprende dado grandes quantidades de texto.
Modelos não recebem diretamente palavras ou frases. A unidade básica de um modelo é um token, que pode representar uma palavra, parte de uma palavra, um caractere, etc. Como exemplo, uma palavra como:
Programming
poderia ser representada, aproximadamente, como:
program + ming
O processo de converter o texto em tokens é chamado de tokenization. É essa a unidade básica que utilizamos quando falamos de custo de APIs, por exemplo. Em modelos tradicionais de NLP, era comum utilizar palavras como tokens, mas com modelos de linguagem, utilizamos uma divisão "subword" devido à eficiência e tamanho de vocabulário.
Por exemplo, a língua inglesa possui em torno de 600 mil palavras, mas um vocabulário de modelo pode ter 32 mil tokens, de forma que possam ser combinados.
Em modelos de linguagem, não trabalhamos diretamente com palavras. Por isso, os inputs são inicialmente divididos em tokens, e cada token é convertido em um vetor númerico denso, uma representação chamada embedding (o processo é um pouco mais complicado, mas podemos pensar em algo assim).
Podemos imaginar um embedding como um vetor:
“the cat sits on a mat” -> [0.11, 0.02, 0.54]
Na realidade, um vetor de embedding pode ser bem maior do que isso. Mas é um bom exemplo :)
O objetivo de embeddings é produzir algo - um vetor - que capture a essência, semântica e nuance do input original. Existem benchmarks voltados à verificar o quão bom um algoritmo de embedding é. Mas de forma geral, um algoritmo é considerado bom se textos similares possuem embeddings similares, o que é medido por algoritmos como similaridade de cosseno ou outras métricas.
Um exemplo interessante é o a seguir (Referência):

Podemos pensar que embeddings vivem em um espaço vetorial high-dimensional, e dois textos/inputs similares irão estar mais próximos do que dois inputs diferentes. Na imagem acima, por exemplo, podemos definir algumas features de comidas (seleção de partes do vetor numérico) e verificar se duas comidas são similares ou não. Neste caso, hot dog e shawarma são bem mais similares do que hot dog e apple strudel.
Note que as features utilizadas aqui estão mais como um exemplo. Na prática, modelos irão aprender muitas dimensões e features automaticamente, com vários nuances e padrões, muitos que nem poderíamos nomear.
Essa representação também está por trás de técnicas como semantic search e RAG.
A maioria dos modelos de linguagens modernos utilizam uma arquitetura chamada de transformer, que foi apresentado em 2017 no artigo Attention Is All You Need.
Sua principal inovação foi permitir que o modelo analisasse relações entre diferentes partes de uma sequência através de um mecanismo chamado attention.
Se você não quiser entrar nos detalhes da arquitetura, tenha em mente essa intuição para a arquitetura transformer:
Em resumo: um Transformer transforma uma sequência de tokens em representações dependentes do contexto, que podem ser usadas para prever o que vem a seguir.
É útil ver a perspectiva que levou à criação do transformer. Ele se popularizou devido à arquiteturas como o seq2seq, que eram úteis para resumos e traduções. Em um high-level, o seq2seq utiliza um encoder que processa inputs e um decoder para gerar outputs, e ambos são sequências de tokens. No flow simples, o encoder processa os input tokens de forma sequencial, e então gera um estado "hidden" intermediário. Daí, o decoder gera o output a partir do estado intermediário e do token anterior (Referência: Chip Huyen).

Um problema existente com o seq2seq é que o decoder gera tokens utilizando um estado intermediário apenas. Pode-se pensar que isso é mais ou menos como responder questões sobre um livro, utilizando apenas o resumo do livro, o que limita a qualidade de outputs gerados. Além disso, o processo de encoding e decoding é sequencial, o que faz com que seja lento para textos grandes.
Com a arquitetura transformer, temos uma mitigação desses dois problemas, utilizando o mecanismo de attention: intuitivamente, esse mecanismo permite com que o modelo calcule a importância de diferentes input tokens ao gerar cada output token. Por exemplo, dado a frase:
The animal didn't cross the street because it was tired.
para construir uma representação útil para it, o modelo pode atribuir maior relevância a animal. Esse processo também é conhecido como self-attention. O mecanismo é implementado através das matrizes Query, Key e Value (Q, K, V), que não iremos abordar tanto. Mas, por intuição:
Comparando Queries e Keys, o modelo calcula os pesos de atenção e combina os Values, e isso ocorre diversas vezes, em diversas camadas.
Assim, um transformer utiliza vários transformer blocks. Geralmente, um bloco possui o módulo de atenção e um MLP ("rede neural"). O número de blocos de transformer num modelo baseado em transformer descreve o número de camadas daquele modelo.

Esse diagrama simplifica um pouco a pipeline. Em um transformer real, o módulo de attention geralmente possui vários mecanismos de atenção executando em paralelo, algo que chamamos de multi-head attention. Passando um pouco por cima, cada head de atenção trabalha com projeções diferentes dos mesmos outputs mas possui suas próprias matrizes Q, K, V, o que possibilita capturar diferentes relações e nuances. Vale notar que esses papéis não são fixos, e sim aprendidos durante o treinamento.
Após o módulo de attention, cada token passa por uma pequena rede neural MLP, que transforma a representação de cada posição individual.
Há outros módulos (como normalization layers, residual connections), mas saber o que é Attention e MLP já é útil para a gente :)
Quando um bloco termina de processar, o seu resultado é passado para o próximo bloco, como se estivessem empilhados em uma pipeline. De forma geral, podemos pensar no processamento de um texto como:
tokens -> embeddings -> transformer block 1 -> transformer block 2 -> ... -> representação final
Que é o que está denotado no diagrama da chip.
Nota: o embedding inicial de um token é, em essência, estático. Como exemplo, o token "banco" começa com uma mesma representação independente da frase. Mas após passar pelas camadas do transformer, sua representação vai depender do contexto. Então, as frases "eu depositei dinheiro no banco" e "sentamos no banco de areia à margem do rio" terão a representação inicial de banco iguais, mas os mecanismos de atenção permitirão que sua representação seja modificada de acordo com o contexto da frase.
Por isso, normalmente distinguimos token embedding (o inicial) de contextual representation (representação produzida pelas camadas).
Ao final, em um modelo de linguagem generativo e autoregressivo (GPT, Claude, etc) essas representações são utilizadas para calcular uma distribuição de probabilidade sobre o próximo token, e um token é escolhido a partir dessa distribuição, adicionando esse novo token ao contexto. Por isso dizemos que esses modelos são autoregressivos: eles geram tokens sequencialmente (mas podem processar os tokens de input em paralelo).
Wow! Bastante coisa. Pra uma introdução de AI Engineering, isso já é suficiente pra entender (nem eu entendo tudo). Se quiser entender mais, recomendamos fortemente o livro da Chip Huyen, e disciplinas voltadas à esses tópicos, como MAC0460 (Machine Learning) e MAC5921 (Deep Learning).
No começo, falamos sobre foundation models e LLMs como se fossem separados. Em essência, um LLM é um tipo específico de foundation model, mas limitado à texto e código. Já modelos foundation possuem capacidades de entender imagens, vídeos, assets 3d, e etc. Ou seja, em geral, podem ser multi-modais, e um modelo multimodal generativo pode ser chamado de Large Multimodal Model (LMM).
Modelos como GPT, Gemini, Claude, etc são melhores categorizados como foundation. Foundation model também pode denotar a importância crítica e o papel pioneiro destes modelos para o ecossistema atual de IA (Chip Huyen, 2025).
Todo modelo possui um tamanho máximo para contexto, isto é, não há acesso ilimitado a toda informação existente. Esse contexto pode ser pensando como o número de tokens de input + o número de tokens de output. Como exemplos de tamanho de context window:
Esse contexto, de forma geral, contém tudo: histórico de conversa, prompt de sistema, mensagens do usuário, documentos, código, resultados de ferramentas, entre outras coisas. O contexto é extremamente importante visto que o modelo só consegue trabalhar diretamente com aquilo que está dentro do contexto atual (exemplo: se você possui milhares de livros, documentações e etc para uma empresa, não é razoável enviar tudo ao modelo em cada requisição).
Essa limitação motiva técnicas como RAG, resumos, memória, e engenharia de contexto. E iremos ver esses tópicos, além de otimizações para a janela de contexto, posteriormente
De forma a escolher tokens, podemos retomar à técnica de sampling, que limita a distribuição à um certo conjunto de tokens. E para controlar a escolha, podemos utilizar temperatura.
Com os conceitos vistos aqui, podemos notar que uma LLM não escolhe necessariamente sempre o token de maior probabilidade. Dependendo do método de sampling, parâmetros como temperatura, e outros fatores, diferentes tokens podem ser selecionados durante a geração.
Isso significa que, para uma mesma entrada, o modelo pode produzir respostas diferentes. Por exemplo, dado:
Explique o que é uma API em uma frase.
uma execução pode gerar:
Uma API permite que diferentes sistemas de software se comuniquem.
enquanto outra pode produzir:
Uma API é uma interface que define como aplicações podem trocar dados e funcionalidades.
Ambas as respostas podem estar corretas, mas não são idênticas.
Esse comportamento é importante em AI Engineering porque quebra uma suposição comum de software tradicional: a de que uma mesma entrada sempre produz exatamente a mesma saída. Em sistemas baseados em LLMs, muitas vezes precisamos avaliar não apenas se a saída é igual ao esperado, mas se ela é correta, relevante e adequada para a tarefa.
Essa é uma das razões pelas quais evaluation se torna tão importante em aplicações de IA.
Até agora, observamos principalmente o que acontece dentro do modelo: o texto é dividido em tokens, processado por camadas de Transformer e usado para produzir uma distribuição sobre o próximo token. Entretanto, quando construímos uma aplicação, o modelo é apenas uma das etapas do sistema.
Podemos representar uma aplicação simples com LLM da seguinte forma:
entrada do usuário
-> preparação do contexto
-> chamada ao modelo
-> resposta probabilística
-> validação pelo software
-> resposta ou efeito no sistema
Cada parte possui uma responsabilidade diferente.
Essa separação é importante porque o modelo não conhece automaticamente o estado real da aplicação. Ele pode produzir um ID inexistente, interpretar uma mensagem de maneira incorreta ou retornar algo fora do formato esperado. Por isso, tratamos a resposta do modelo como uma entrada não confiável até que nosso código a valide.
Uma função determinística costuma produzir o mesmo resultado quando recebe a mesma entrada e trabalha sobre o mesmo estado. Uma chamada a um LLM acrescenta outras preocupações:
Portanto, integrar um modelo não significa apenas substituir uma função comum
por ask_model(...). Precisamos decidir qual parte do problema realmente exige
interpretação probabilística e quais partes devem permanecer determinísticas.
Verificação rápida: imagine um sistema que recebe o e-mail “terminei a tarefa 12” e atualiza um banco de dados. Qual etapa deveria interpretar a frase? Qual etapa deveria verificar se a tarefa 12 existe? E quem deveria executar a alteração no banco?
Dado esses conceitos novos, podemos ver que modelos de IA, foundation, LLMs, etc, não funcionam como componentes usuais de software. Devido à sua natureza probabilística, nós precisamos nos preocupar com diferentes coisas, como:
Por isso, passamos de uma simples chamada de API para pensar em contexto, controle, confiabilidade, memória, segurança, e vários outros fatores.
Na próxima parte, construiremos o fluxo simples para um app. O modelo interpretará um e-mail e proporá uma ação estruturada, mas o código Python continuará responsável por validar as regras e alterar o banco de dados.