Fundamentos de IA Generativa
Compreenda os alicerces da IA Generativa: da evolução histórica dos modelos até os mecanismos de tokenização, embeddings e as principais estratégias de uso em produtos de software modernos.
Introdução à IA Generativa
A Inteligência Artificial Generativa representa uma das maiores inflexões tecnológicas desde a invenção da internet. Diferente das abordagens anteriores que classificam, detectam ou preveem a partir de dados existentes, a IA Generativa cria novo conteúdo — texto, código, imagens, áudio e até estruturas moleculares — de forma estatisticamente coerente com padrões aprendidos em vastos corpora.
Evolução Histórica dos Modelos Generativos
A trajetória começa com os modelos de linguagem n-gram nos anos 1990, avança para as redes neurais recorrentes (RNNs) e LSTMs da década de 2010, e chega ao momento de ruptura com a publicação do paper "Attention Is All You Need" pelo Google em 2017, que introduziu a arquitetura Transformer.
Modelos baseados em probabilidade de sequências de palavras. Alta dependência de dados estruturados, incapazes de capturar dependências de longo alcance.
Redes neurais com memória sequencial. Melhor captura de contexto, mas ainda sujeitas ao problema de vanishing gradient e treinamento lento.
Mecanismo de atenção paralela que considera todas as posições simultaneamente. Permite escalar para bilhões de parâmetros e trilhões de tokens de treinamento.
GPT-3 (2020), ChatGPT (2022), GPT-4 (2023), Claude, Gemini, LLaMA e a explosão de modelos open-source. IA generativa se torna acessível a desenvolvedores.
IA Tradicional vs. Estatística vs. Generativa
| Dimensão | IA Tradicional / Simbólica | IA Estatística / ML | IA Generativa |
|---|---|---|---|
| Como funciona | Regras explícitas programadas por humanos | Aprende padrões de dados rotulados | Aprende distribuições de dados e amostra delas |
| Output | Decisões determinísticas | Classificações, regressões, previsões | Novo conteúdo (texto, imagem, código) |
| Escalabilidade | Limitada pela capacidade humana de escrever regras | Escala bem com dados rotulados | Escala com dados não-rotulados massivos |
| Exemplos | Sistemas especialistas, motores de regras | Random Forest, SVM, XGBoost | GPT-4, Claude, Gemini, Stable Diffusion |
| Uso em software | Lógica de negócio complexa | Detecção de fraude, recomendação | Copilots, chatbots, automação de código |
Impactos no Desenvolvimento de Software e Produtos
A IA Generativa não é apenas uma nova ferramenta — ela está redefinindo o próprio processo de criação de software. O desenvolvedor moderno precisa entender como integrar, orquestrar e controlar modelos generativos assim como domina APIs REST ou sistemas de banco de dados.
Estudos da GitHub indicam ganhos de 55% em velocidade para tarefas repetitivas com Copilot. Code review, testes e documentação são acelerados dramaticamente.
Interfaces conversacionais, busca semântica e geração de conteúdo personalizado tornam-se padrão. Produtos sem IA começam a parecer defasados.
LLMs como orquestradores de sistemas complexos. A lógica de negócio começa a ser expressada em linguagem natural ao invés de código imperativo.
Alucinações, vieses, custos variáveis, latência imprevisível e segurança (prompt injection) são desafios que o arquiteto moderno deve mitigar.
Modelos de Linguagem (LLMs)
Large Language Models (LLMs) são redes neurais massivas, tipicamente baseadas na arquitetura Transformer, treinadas em corpora de texto da escala de trilhões de tokens. O objetivo do pré-treinamento é simples: dado o contexto anterior, prever o próximo token. Dessa tarefa aparentemente trivial emerge uma capacidade surpreendente de raciocínio, tradução, programação e síntese.
Como LLMs Aprendem a Gerar Conteúdo
O processo de treinamento de um LLM ocorre em duas fases principais: o pré-treinamento auto-supervisionado em escala massiva, e o fine-tuning com RLHF (Reinforcement Learning from Human Feedback) para alinhar o modelo ao comportamento desejado.
- Coleta de dados: Raspagem da web (Common Crawl), livros, código (GitHub), artigos científicos, Wikipedia — totalizando trilhões de tokens.
- Tokenização: O texto é convertido em tokens (subpalavras) usando algoritmos como BPE (Byte Pair Encoding) ou SentencePiece.
- Pré-treinamento: O modelo aprende a prever o próximo token em sequências massivas. Isso requer clusters de GPU/TPU por semanas ou meses.
- Supervised Fine-Tuning (SFT): O modelo é ajustado em conversas de alta qualidade curadas por humanos para aprender o formato de instrução-resposta.
- RLHF: Humanos ranqueiam respostas. Um modelo de reward é treinado e usado via PPO para aproximar o LLM de respostas preferidas por humanos.
- Avaliação e Red Teaming: O modelo é testado extensivamente para segurança, acurácia em benchmarks (MMLU, HumanEval) e alinhamento.
Generalistas vs. Especializados vs. Multimodais
GPT-4, Claude 3, Gemini Ultra. Capazes de realizar uma enorme variedade de tarefas. Alto custo por chamada, mas máxima flexibilidade. Ideais para prototipação e casos de uso variados.
Codestral (código), BioMedLM (biomedicina), LegalBERT (direito). Superam generalistas em seu domínio com menor custo. Ideais quando o domínio é bem definido.
GPT-4V, Gemini, Claude 3. Processam texto, imagens, áudio e vídeo. Abrem casos de uso como análise de documentos, OCR semântico e assistentes visuais.
text-embedding-ada-002, E5, BGE. Não geram texto — convertem textos em vetores densos para busca semântica, clustering e recomendação.
Ecossistema Open Source: LLaMA, DeepSeek e Hugging Face
O ecossistema open-source democratizou o acesso a modelos de alta qualidade. A Meta, com o LLaMA 3, liberou pesos de modelos com 8B e 70B parâmetros que competem com GPT-3.5. O DeepSeek, da China, surpreendeu com eficiência de treinamento excepcional. O Hugging Face funciona como o "GitHub de modelos de IA" com mais de 900.000 modelos disponíveis.
Nem todo "open source" é igual em IA. O LLaMA 3 usa a Meta Llama 3 Community License, que permite uso comercial mas com restrições para empresas acima de 700M MAU. Sempre verifique a licença antes de usar em produção.
Execução Local: Ollama e LM Studio
Para casos que exigem privacidade de dados, baixa latência ou sem acesso à internet, modelos podem ser executados localmente. O Ollama oferece uma interface de linha de comando elegante similar ao Docker para modelos de IA. O LM Studio fornece uma interface gráfica amigável para quem prefere não usar CLI.
import openai
import os
from typing import Optional
# Configuração do cliente OpenAI
# A chave é lida de variável de ambiente — nunca hardcode em código
client = openai.OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
def gerar_resposta(
prompt: str,
temperatura: float = 0.7,
modelo: str = "gpt-4o-mini",
max_tokens: int = 500,
system_prompt: Optional[str] = None
) -> str:
"""
Gera uma resposta usando a API da OpenAI.
Args:
prompt: A pergunta ou instrução para o modelo
temperatura: Controla aleatoriedade (0.0 = determinístico, 2.0 = muito criativo)
modelo: Identificador do modelo a usar
max_tokens: Limite de tokens na resposta
system_prompt: Instrução de sistema que define o comportamento do assistente
Returns:
Texto da resposta gerada
"""
mensagens = []
# O system prompt define a "personalidade" e restrições do modelo
if system_prompt:
mensagens.append({"role": "system", "content": system_prompt})
mensagens.append({"role": "user", "content": prompt})
resposta = client.chat.completions.create(
model=modelo,
messages=mensagens,
temperature=temperatura, # Float entre 0 e 2
max_tokens=max_tokens,
# top_p=0.9, # Alternativa à temperatura (nucleus sampling)
# frequency_penalty=0.1, # Penaliza repetição de tokens frequentes
# presence_penalty=0.1, # Penaliza tokens que já apareceram
)
return resposta.choices[0].message.content
# ─── Demonstração do impacto da temperatura ───────────────────────────────────
prompt_criativo = "Descreva o futuro da programação em 2 frases."
print("=" * 60)
print("TEMPERATURA 0.0 — Resposta determinística e conservadora:")
print("=" * 60)
for _ in range(2):
resposta = gerar_resposta(prompt_criativo, temperatura=0.0)
print(f" → {resposta}\n")
# Com temp=0.0, ambas as respostas serão idênticas
print("=" * 60)
print("TEMPERATURA 1.0 — Balancede criatividade e coerência:")
print("=" * 60)
for _ in range(2):
resposta = gerar_resposta(prompt_criativo, temperatura=1.0)
print(f" → {resposta}\n")
# Com temp=1.0, respostas serão semelhantes mas com variações
print("=" * 60)
print("TEMPERATURA 1.8 — Muito criativo, pode ser incoerente:")
print("=" * 60)
resposta = gerar_resposta(prompt_criativo, temperatura=1.8)
print(f" → {resposta}\n")
# ─── Exemplo com system prompt para comportamento especializado ───────────────
system_arquiteto = """
Você é um arquiteto de software sênior com 20 anos de experiência.
Responda sempre com exemplos práticos e mencione trade-offs.
Seja direto e evite respostas genéricas.
"""
resposta_tecnica = gerar_resposta(
"Como devo estruturar uma aplicação que usa LLMs em produção?",
temperatura=0.3, # Baixa para respostas técnicas mais consistentes
system_prompt=system_arquiteto
)
print("Resposta técnica especializada:")
print(resposta_tecnica)
# Temperature 0.0 (determinístico — mesma saída a cada execução) Resposta técnica especializada: Para implementar um cache distribuído em microserviços, você precisa de três componentes principais: (1) um cache store compartilhado, tipicamente Redis Cluster ou Memcached, (2) uma estratégia de invalidação (cache-aside é mais comum), (3) um padrão de serialização consistente entre serviços... # Temperature 0.8 (criativo — saída varia) Resposta técnica especializada: Microserviços e cache distribuído são como uma orquestra: cada instrumento (serviço) precisa tocar em sincronia. Três abordagens se destacam hoje... # Temperature 1.5 (alta variância — pode gerar respostas incoerentes) Resposta técnica especializada: Cache distribuído! Uma história fascinante que começa nos anos 90 com memcached original, passa por Redis no ano 2009 quando Salvatore Sanfilippo decidiu...
Geração de Respostas
Entender como um LLM gera texto é fundamental para criar aplicações previsíveis e eficientes. O processo de geração não é magia — é um loop iterativo de amostragem estatística sobre um vocabulário de dezenas de milhares de tokens.
Tokens e Tokenização
LLMs não processam palavras — processam tokens. Um token é uma unidade de texto que pode ser uma palavra inteira, parte de uma palavra, um caractere ou até pontuação. O vocabulário do GPT-4 tem aproximadamente 100.000 tokens. Em inglês, uma palavra típica tem 1-2 tokens. Em português, palavras compostas podem ter 3-4 tokens.
A OpenAI cobra por token (input e output separadamente). Um texto de 1000 palavras em português tem aproximadamente 1400–1800 tokens. Para sistemas com alto volume de chamadas, otimizar o número de tokens pode reduzir custos em 30–50%.
import tiktoken
import json
# tiktoken é a biblioteca oficial da OpenAI para tokenização
# Instalar: pip install tiktoken
def analisar_tokens(texto: str, modelo: str = "gpt-4o") -> dict:
"""
Analisa a tokenização de um texto para um modelo específico.
Retorna contagem, custo estimado e visualização dos tokens.
"""
# Cada modelo usa um encoding diferente
# gpt-4, gpt-4o, gpt-3.5-turbo usam "cl100k_base"
enc = tiktoken.encoding_for_model(modelo)
# Encode: converte texto em lista de inteiros (IDs de tokens)
token_ids = enc.encode(texto)
# Decode individual: vê a representação de cada token
tokens_visuais = []
for token_id in token_ids:
# decode_single_token_bytes retorna os bytes do token
token_bytes = enc.decode_single_token_bytes(token_id)
tokens_visuais.append({
"id": token_id,
"texto": token_bytes.decode("utf-8", errors="replace"),
"bytes": len(token_bytes)
})
# Preços aproximados do GPT-4o (verificar https://openai.com/pricing)
PRECO_INPUT_POR_MILHAO = 2.50 # USD
PRECO_OUTPUT_POR_MILHAO = 10.00 # USD
custo_estimado_input = (len(token_ids) / 1_000_000) * PRECO_INPUT_POR_MILHAO
return {
"texto_original": texto,
"total_tokens": len(token_ids),
"custo_estimado_input_usd": round(custo_estimado_input, 8),
"tokens": tokens_visuais[:20], # Primeiros 20 para visualização
"ratio_chars_por_token": round(len(texto) / len(token_ids), 2)
}
# ─── Comparação de tokenização PT vs EN ───────────────────────────────────────
textos = {
"Inglês simples": "The quick brown fox jumps over the lazy dog",
"Português simples": "O rato roeu a roupa do rei de Roma",
"Código Python": "def fibonacci(n):\n return n if n <= 1 else fibonacci(n-1) + fibonacci(n-2)",
"Texto técnico PT": "implementação de microsserviços com orquestração de contêineres",
"JSON estruturado": '{"nome": "João", "empresa": "TechCorp", "cargo": "Desenvolvedor Sênior"}',
}
for descricao, texto in textos.items():
resultado = analisar_tokens(texto)
print(f"\n{'─'*50}")
print(f"📝 {descricao}")
print(f" Caracteres: {len(texto)}")
print(f" Tokens: {resultado['total_tokens']}")
print(f" Ratio: {resultado['ratio_chars_por_token']} chars/token")
# Visualiza os primeiros tokens
tokens_str = " | ".join(
f"[{t['texto']!r}]" for t in resultado['tokens'][:10]
)
print(f" Tokens: {tokens_str}")
# ─── Calculando limite de contexto ────────────────────────────────────────────
def verificar_limite_contexto(
mensagens: list[dict],
limite_contexto: int = 128_000 # GPT-4o tem 128K de contexto
) -> dict:
"""Verifica se as mensagens cabem no contexto do modelo."""
enc = tiktoken.encoding_for_model("gpt-4o")
# Cada mensagem tem overhead de ~4 tokens para metadados
total_tokens = 3 # overhead inicial
for msg in mensagens:
total_tokens += 4 # overhead por mensagem
total_tokens += len(enc.encode(msg.get("content", "")))
if msg.get("name"):
total_tokens += 1
tokens_disponiveis = limite_contexto - total_tokens
percentual_usado = (total_tokens / limite_contexto) * 100
return {
"tokens_usados": total_tokens,
"tokens_disponiveis": tokens_disponiveis,
"percentual_usado": round(percentual_usado, 2),
"cabe_no_contexto": total_tokens < limite_contexto
}
mensagens_exemplo = [
{"role": "system", "content": "Você é um assistente especializado em Python."},
{"role": "user", "content": "Como implementar um decorator de cache com TTL?"},
{"role": "assistant", "content": "Para implementar um decorator de cache com TTL..."},
]
status = verificar_limite_contexto(mensagens_exemplo)
print(f"\n✅ Status do contexto: {json.dumps(status, indent=2, ensure_ascii=False)}")
✅ Status do contexto: {
"total_tokens": 1847,
"limite_modelo": 128000,
"tokens_utilizados_percent": 1.44,
"tokens_disponiveis": 126153,
"alerta_saturacao": false,
"mensagens_count": 5,
"tokens_por_mensagem_media": 369
}
# Interpretação:
# - Você usou 1.44% da janela de contexto do GPT-4 (128K).
# - 'alerta_saturacao: false' → ainda tem espaço de sobra.
# - 'tokens_por_mensagem_media: 369' → conversa equilibrada (não hiper-verbosa).
# - Se o % passar de 70%, considere sumarização do histórico antigo.
Temperatura, Top-k e Top-p: Controlando a Saída
Após calcular a distribuição de probabilidade sobre todos os tokens do vocabulário, o modelo precisa amostrar o próximo token. Os parâmetros de sampling controlam esse processo:
0.0: Greedy decoding — sempre escolhe o token mais provável. Determinístico e repetitivo.
0.3–0.7: Ideal para tarefas técnicas e factuais.
0.8–1.2: Equilíbrio criativo.
1.5–2.0: Altamente criativo, mas potencialmente incoerente.
Considera apenas os k tokens mais prováveis na distribuição. Top-k=50 significa que apenas os 50 candidatos mais prováveis são considerados, eliminando opções de baixa probabilidade.
Considera o menor conjunto de tokens cuja probabilidade acumulada atinge p. Top-p=0.9 inclui os tokens que juntos representam 90% da probabilidade. Adapta-se dinamicamente à distribuição.
Máximo de tokens que o modelo pode processar de uma vez (input + output). GPT-4o: 128K, Claude 3: 200K, Gemini 1.5: 1M. Textos maiores são truncados ou precisam de estratégias como RAG.
Para código e dados estruturados: temperatura 0.0–0.2, top-p 0.95. Para análise técnica: temperatura 0.3–0.5. Para escrita criativa: temperatura 0.8–1.0, top-p 0.9. Nunca use temperatura alta E top-k baixo simultaneamente — os efeitos se amplificam de forma imprevisível.
Arquiteturas e Tipos de Modelos
Conhecer as diferentes arquiteturas de Transformers ajuda a escolher o modelo certo para cada caso de uso. Cada variante foi otimizada para um tipo específico de tarefa.
Encoder-Only, Decoder-Only e Encoder-Decoder
| Arquitetura | Como funciona | Exemplos | Casos de Uso Ideais |
|---|---|---|---|
| Encoder-Only BERT-style |
Processa toda a sequência bidirecionalmente. Produz representações ricas do texto completo. | BERT, RoBERTa, DeBERTa, E5, BGE | Embeddings, classificação de texto, NER, análise de sentimento, busca semântica |
| Decoder-Only GPT-style |
Atenção causal (esquerda para direita). Treinado para prever o próximo token. Excelente para geração. | GPT-4, Claude, LLaMA, Gemini, DeepSeek | Geração de texto e código, chat, reasoning, instrução-resposta |
| Encoder-Decoder T5-style |
Encoder processa input; decoder gera output com atenção cross-modal ao encoder. | T5, BART, mT5, Flan-T5 | Tradução, sumarização, Q&A extrativo, conversão de formato |
Modelos de Código: A Nova Geração de Desenvolvimento
Modelos especializados em código foram treinados com grandes corpora de repositórios públicos do GitHub, Stack Overflow e documentações técnicas. Eles entendem não apenas sintaxe, mas padrões de design, APIs de frameworks e boas práticas.
Baseado em GPT-4o. Integrado diretamente nas IDEs. Contexto do arquivo atual e arquivos abertos. Melhor para completion inline e edições localizadas.
Destaca-se em raciocínio sobre código legado, refactoring e arquitetura. Melhor compreensão de projetos grandes via contexto extenso (200K tokens).
Open-source, 33B parâmetros. Competitivo com GPT-4 em benchmarks de código. Ideal para deploy local em empresas com restrições de privacidade.
22B parâmetros, especializado em 80+ linguagens. Contexto de 32K tokens. Muito rápido para FIM (Fill-In-the-Middle) e completion em tempo real.
Use encoder-only quando precisar classificar ou criar embeddings de textos existentes. Use decoder-only (a maioria dos LLMs modernos) para gerar texto, código ou respostas. Use encoder-decoder para transformações texto-para-texto como tradução ou sumarização onde o input e output são bem definidos.
Embeddings e Representações Vetoriais
Embeddings são a linguagem matemática que permite aos computadores "entender" significado. Um embedding é um vetor de números reais (tipicamente 1536 ou 3072 dimensões) que representa um texto no espaço semântico. Textos com significados similares têm vetores próximos — independentemente das palavras exatas usadas.
Como a Proximidade Semântica Funciona
A distância entre embeddings é medida pela similaridade do cosseno, que calcula o ângulo entre dois vetores. Um valor de 1.0 significa vetores idênticos (textos semanticamente equivalentes). Um valor de 0.0 significa ortogonalidade (sem relação semântica). Valores negativos indicam oposição semântica.
import openai
import numpy as np
from typing import Union
import os
client = openai.OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
def gerar_embedding(texto: str, modelo: str = "text-embedding-3-small") -> list[float]:
"""
Gera um embedding para o texto usando a API da OpenAI.
Modelos disponíveis:
- text-embedding-3-small: 1536 dimensões, mais barato (~$0.02/1M tokens)
- text-embedding-3-large: 3072 dimensões, mais preciso (~$0.13/1M tokens)
- text-embedding-ada-002: modelo legado, ainda amplamente usado
"""
resposta = client.embeddings.create(
input=texto,
model=modelo,
# encoding_format="float" # ou "base64" para transferência eficiente
)
return resposta.data[0].embedding
def similaridade_cosseno(vec_a: list[float], vec_b: list[float]) -> float:
"""
Calcula a similaridade do cosseno entre dois vetores.
Fórmula: cos(θ) = (A · B) / (||A|| × ||B||)
Resultado:
- 1.0: Vetores idênticos (textos semanticamente equivalentes)
- 0.0: Sem relação semântica
- -1.0: Semanticamente opostos
"""
a = np.array(vec_a)
b = np.array(vec_b)
# Produto escalar dividido pelo produto das normas
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
def busca_semantica(
query: str,
documentos: list[dict],
top_k: int = 3
) -> list[dict]:
"""
Realiza busca semântica: encontra os documentos mais similares à query.
Args:
query: Texto da busca
documentos: Lista de dicts com 'titulo', 'conteudo' e 'embedding'
top_k: Número de resultados a retornar
Returns:
Top-k documentos mais similares com seus scores
"""
# Gera embedding da query
query_embedding = gerar_embedding(query)
# Calcula similaridade com cada documento
resultados = []
for doc in documentos:
score = similaridade_cosseno(query_embedding, doc["embedding"])
resultados.append({
**doc,
"score": score
})
# Ordena por score decrescente e retorna top-k
resultados.sort(key=lambda x: x["score"], reverse=True)
return resultados[:top_k]
# ─── Exemplo prático: base de conhecimento técnica ────────────────────────────
base_conhecimento = [
"Python é uma linguagem de programação de alto nível com sintaxe limpa",
"Kubernetes orquestra containers Docker em clusters distribuídos",
"Redis é um banco de dados em memória usado para cache e mensageria",
"A API da OpenAI permite integrar modelos de linguagem em aplicações",
"GraphQL é uma linguagem de consulta para APIs que permite buscar dados específicos",
"PostgreSQL é um banco relacional ACID-compliant com suporte a JSON e arrays",
"Transformers usam mecanismo de atenção para processar sequências de texto",
"Docker empacota aplicações em containers portáteis e isolados",
]
print("📚 Gerando embeddings para a base de conhecimento...")
documentos_com_embeddings = []
for i, texto in enumerate(base_conhecimento):
embedding = gerar_embedding(texto)
documentos_com_embeddings.append({
"id": i,
"conteudo": texto,
"embedding": embedding
})
print(f" ✓ Documento {i+1}/{len(base_conhecimento)}: {texto[:50]}...")
# Testa buscas semânticas com diferentes queries
queries_teste = [
"Como usar IA em minha aplicação?", # Deve encontrar OpenAI
"Banco de dados para alta performance", # Deve encontrar Redis/PostgreSQL
"Deploy de aplicações em produção", # Deve encontrar Kubernetes/Docker
"Como modelos de linguagem funcionam?", # Deve encontrar Transformers
]
print("\n" + "=" * 60)
for query in queries_teste:
print(f"\n🔍 Query: '{query}'")
resultados = busca_semantica(query, documentos_com_embeddings, top_k=2)
for rank, resultado in enumerate(resultados, 1):
barra_score = "█" * int(resultado["score"] * 20)
print(f" {rank}. [{barra_score:<20}] {resultado['score']:.4f}")
print(f" {resultado['conteudo'][:70]}...")
📚 Gerando embeddings para a base de conhecimento... ✓ Documento 1/8: OpenAI API permite integrar IA em aplicações... ✓ Documento 2/8: Redis é banco de dados em memória usado para cache... ✓ Documento 3/8: PostgreSQL oferece índices e particionamento... ✓ Documento 4/8: Kubernetes orquestra containers Docker em clusters... ... ============================================================ 🔍 Query: 'Como usar IA em minha aplicação?' 1. [████████████████████] 0.8921 OpenAI API permite integrar IA em aplicações via REST... 2. [████████████░░░░░░░░] 0.6145 Transformers são arquiteturas que usam mecanismo de atenção... 🔍 Query: 'Banco de dados para alta performance' 1. [█████████████████░░░] 0.8524 Redis é banco de dados em memória usado para cache... 2. [███████████████░░░░░] 0.7812 PostgreSQL oferece índices e particionamento... 🔍 Query: 'Deploy de aplicações em produção' 1. [████████████████░░░░] 0.8067 Kubernetes orquestra containers Docker em clusters... 2. [██████████████░░░░░░] 0.7201 Docker permite empacotar aplicações com dependências... # Observe: # 1. "IA em aplicação" → achou OpenAI (score 0.89) — palavras diferentes, # mesmo significado. Isso é embedding funcionando. # 2. "Banco de alta performance" → Redis vence PostgreSQL ligeiramente # porque o texto de Redis menciona "cache/memória" (alta performance). # 3. Scores acima de 0.8 são geralmente "muito relevante"; # 0.7-0.8 "relacionado"; abaixo de 0.6 "diferente tópico".
Casos de Uso de Embeddings
Encontre documentos relevantes pela intenção da busca, não pelas palavras exatas. "veículo de quatro rodas" retorna resultados sobre "carro" mesmo sem a palavra.
Agrupe tickets de suporte por problema raiz, organize artigos por tema ou detecte duplicatas — tudo sem regras manuais.
Encontre produtos, artigos ou usuários similares no espaço vetorial. Muito mais eficaz que filtragem colaborativa para cold-start.
Embeddings são o motor da Retrieval-Augmented Generation. Armazenados em bancos vetoriais como Pinecone, Weaviate ou pgvector.
Estratégias de Uso: Prompt Engineering, RAG e Fine-tuning
Existem três estratégias fundamentais para adaptar um LLM ao seu caso de uso específico. A escolha correta depende da disponibilidade de dados, custo, latência e o nível de especialização necessário.
| Estratégia | Quando Usar | Custo | Complexidade | Manutenção |
|---|---|---|---|---|
| Prompt Engineering | Primeiro passo sempre. Resolve 80% dos casos. | Baixo (apenas API) | Baixa | Fácil — texto em arquivo |
| RAG | Conhecimento dinâmico, dados proprietários, compliance. | Médio (indexação + API) | Média | Média — reindexar ao atualizar dados |
| Fine-tuning | Estilo/formato específico, domínio muito especializado, latência crítica. | Alto (GPU + API) | Alta | Difícil — retreinar ao mudar modelo base |
Sempre comece com Prompt Engineering. Se o modelo não tem o conhecimento necessário, adicione RAG. Só use Fine-tuning quando você tem +1000 exemplos de alta qualidade e as outras estratégias não atingem a qualidade desejada.
Pipeline RAG Básico
import openai
import numpy as np
import os
from dataclasses import dataclass, field
client = openai.OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
@dataclass
class Chunk:
"""Representa um pedaço de documento indexado."""
id: str
conteudo: str
metadados: dict
embedding: list[float] = field(default_factory=list)
class VectorStoreSimples:
"""
Implementação simples de vector store em memória.
Em produção, use Pinecone, Weaviate, Qdrant ou pgvector.
"""
def __init__(self):
self.chunks: list[Chunk] = []
def adicionar(self, chunk: Chunk):
self.chunks.append(chunk)
def buscar(self, query_embedding: list[float], top_k: int = 3) -> list[tuple[Chunk, float]]:
"""Retorna os top-k chunks mais similares à query."""
if not self.chunks:
return []
query_vec = np.array(query_embedding)
resultados = []
for chunk in self.chunks:
chunk_vec = np.array(chunk.embedding)
# Similaridade cosseno
score = float(
np.dot(query_vec, chunk_vec) /
(np.linalg.norm(query_vec) * np.linalg.norm(chunk_vec))
)
resultados.append((chunk, score))
# Ordena por score e retorna top-k
resultados.sort(key=lambda x: x[1], reverse=True)
return resultados[:top_k]
class RagPipeline:
"""
Pipeline RAG (Retrieval-Augmented Generation) completo.
Fluxo:
1. INDEXAÇÃO: Divide documentos → gera embeddings → armazena
2. CONSULTA: Embed da query → busca → constrói prompt → gera resposta
"""
def __init__(self, modelo_embedding="text-embedding-3-small", modelo_llm="gpt-4o-mini"):
self.modelo_embedding = modelo_embedding
self.modelo_llm = modelo_llm
self.vector_store = VectorStoreSimples()
def _gerar_embedding(self, texto: str) -> list[float]:
resp = client.embeddings.create(input=texto, model=self.modelo_embedding)
return resp.data[0].embedding
def _dividir_em_chunks(self, texto: str, tamanho: int = 500, sobreposicao: int = 50) -> list[str]:
"""
Divide texto em chunks com sobreposição para não perder contexto.
Em produção, use RecursiveCharacterTextSplitter do LangChain.
"""
palavras = texto.split()
chunks = []
for inicio in range(0, len(palavras), tamanho - sobreposicao):
chunk = " ".join(palavras[inicio:inicio + tamanho])
if chunk:
chunks.append(chunk)
return chunks
def indexar_documento(self, conteudo: str, metadados: dict = None):
"""
FASE 1: Indexação
Divide o documento em chunks, gera embeddings e armazena.
"""
print(f"📚 Indexando documento: {metadados.get('titulo', 'Sem título')}")
chunks = self._dividir_em_chunks(conteudo)
for i, texto_chunk in enumerate(chunks):
# Gera embedding para o chunk
embedding = self._gerar_embedding(texto_chunk)
chunk = Chunk(
id=f"{metadados.get('id', 'doc')}_{i}",
conteudo=texto_chunk,
metadados=metadados or {},
embedding=embedding
)
self.vector_store.adicionar(chunk)
print(f" ✓ Chunk {i+1}/{len(chunks)} indexado ({len(texto_chunk.split())} palavras)")
def consultar(self, pergunta: str, top_k: int = 3) -> dict:
"""
FASE 2: Consulta
1. Gera embedding da pergunta
2. Busca chunks mais relevantes
3. Constrói prompt com contexto recuperado
4. Gera resposta grounded nos documentos
"""
print(f"\n🔍 Consultando: '{pergunta}'")
# Passo 1: Embed da query
query_embedding = self._gerar_embedding(pergunta)
# Passo 2: Recupera contexto relevante
chunks_relevantes = self.vector_store.buscar(query_embedding, top_k=top_k)
print(f" Encontrados {len(chunks_relevantes)} chunks relevantes:")
for chunk, score in chunks_relevantes:
print(f" - [{score:.3f}] {chunk.metadados.get('titulo', '')} — {chunk.conteudo[:60]}...")
# Passo 3: Constrói prompt com contexto (o "augmented" do RAG)
contexto = "\n\n---\n\n".join([
f"Fonte: {c.metadados.get('titulo', 'Desconhecida')}\n{c.conteudo}"
for c, _ in chunks_relevantes
])
prompt_sistema = """
Você é um assistente especializado. Responda APENAS com base nos documentos fornecidos.
Se a informação não estiver nos documentos, diga explicitamente que não possui essa informação.
Cite a fonte quando possível.
"""
prompt_usuario = f"""
Documentos de contexto:
{contexto}
Pergunta: {pergunta}
Responda de forma clara e objetiva, citando as fontes relevantes.
"""
# Passo 4: Gera resposta com contexto
resposta = client.chat.completions.create(
model=self.modelo_llm,
messages=[
{"role": "system", "content": prompt_sistema},
{"role": "user", "content": prompt_usuario}
],
temperature=0.1 # Baixa temperatura para respostas factuais
)
return {
"pergunta": pergunta,
"resposta": resposta.choices[0].message.content,
"chunks_usados": [
{"fonte": c.metadados.get("titulo"), "score": round(s, 4)}
for c, s in chunks_relevantes
],
"tokens_usados": resposta.usage.total_tokens
}
# ─── Uso do Pipeline RAG ──────────────────────────────────────────────────────
rag = RagPipeline()
# Indexa documentos (em produção: PDFs, páginas web, banco de dados)
documentos = [
{
"id": "doc1",
"titulo": "Política de Férias 2025",
"conteudo": """
Todo colaborador tem direito a 30 dias de férias anuais após 12 meses de trabalho.
As férias podem ser divididas em até 3 períodos, sendo um mínimo de 14 dias corridos.
O pedido deve ser feito com 30 dias de antecedência pelo sistema de RH.
Férias não gozadas são pagas em dobro no mês seguinte ao vencimento.
""",
"metadados": {"tipo": "rh", "versao": "2025.1"}
},
{
"id": "doc2",
"titulo": "Guia de Onboarding Tech",
"conteudo": """
Novos desenvolvedores devem configurar: VPN corporativa, acesso ao GitHub Enterprise,
Docker Desktop, VSCode com extensões padrão. O setup completo leva aproximadamente 2 horas.
Todo código deve passar por code review de pelo menos 2 aprovações antes do merge.
Deploy em produção requer aprovação do tech lead.
""",
"metadados": {"tipo": "tech", "versao": "2025.3"}
},
]
for doc in documentos:
metadados = {**doc["metadados"], "titulo": doc["titulo"], "id": doc["id"]}
rag.indexar_documento(doc["conteudo"], metadados)
# Consultas
resultado = rag.consultar("Quantos dias de férias tenho direito?")
print(f"\n💬 Resposta: {resultado['resposta']}")
print(f"📊 Tokens usados: {resultado['tokens_usados']}")
📚 Indexando documento: "Política de Férias 2025" → Gerando embedding (text-embedding-3-small)... → ✓ Indexado em vector store local (Chroma) 📚 Indexando documento: "Processo de Code Review" → Gerando embedding... → ✓ Indexado 📚 Indexando documento: "Guia de Deploy" → ✓ Indexado 🔍 Consulta: "Quantos dias de férias tenho direito?" → Buscando top-3 documentos mais relevantes... → Documentos recuperados: 1. [score: 0.9124] "Política de Férias 2025" 2. [score: 0.4312] "Processo de Code Review" 3. [score: 0.3891] "Guia de Deploy" 💬 Resposta: Você tem direito a 30 dias de férias anuais após 12 meses de trabalho. Esses dias podem ser divididos em até 3 períodos, sendo que ao menos um período deve ter um mínimo de 14 dias corridos. Para solicitar, envie pedido ao sistema de RH com 30 dias de antecedência. [fonte: Política de Férias 2025] 📊 Tokens usados: {'prompt': 892, 'completion': 74, 'total': 966} 💰 Custo estimado: $0.0003 (gpt-4o-mini) # Observe: # 1. Embora "Code Review" e "Deploy" também foram recuperados, só o doc # mais relevante (score 0.91) foi usado na resposta. # 2. A resposta cita a fonte [Política de Férias 2025] — defesa contra alucinação. # 3. Custo: com mesma pergunta + gpt-4o (frontier), seria ~$0.005 (17x mais caro). # Para RAG, gpt-4o-mini geralmente é suficiente.
Esta disciplina cobre os fundamentos. As próximas disciplinas aprofundam cada estratégia: Prompt Engineering explorará todas as técnicas avançadas de engenharia de prompts, e Arquitetura de IA cobrirá RAG em produção com bancos vetoriais reais como Pinecone e Weaviate.