Indexação: gerando embeddings e armazenando partes
Gere um embedding para cada parte usando a API de embeddings da OpenAI e insira os vetores resultantes com metadados em um armazenamento vetorial, criando um índice pesquisável dos seus documentos.
Indexação: gerando embeddings e armazenando partes é uma aula grátis de AI Engineering Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Engineering Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Engineering Academy inclui 4 aulas no total.
A Etapa de Indexação: Visão Geral
Depois de carregar e dividir seus documentos em blocos, você chega à etapa de indexação: converter blocos de texto em incorporações vetoriais e armazená-los em um banco de dados vetorial pesquisável. Esta é a última etapa offline antes que as consultas possam ser respondidas. A qualidade das incorporações e a eficiência da sua estratégia de armazenamento e indexação determinam diretamente a velocidade e a precisão do seu sistema RAG no momento da consulta.
Gerando Incorporações pela API da OpenAI
A abordagem mais comum é chamar a API de incorporações da OpenAI com o texto do bloco. O modelo text-embedding-3-small produz vetores de 1.536 dimensões e custa US$ 0,02 por milhão de tokens — um valor extremamente baixo para a maioria das cargas de trabalho. Envie vários textos em uma única chamada à API (até 2.048 entradas) para maximizar a vazão. A resposta contém um vetor de incorporação para cada texto de entrada, na mesma ordem.
from openai import OpenAI
client = OpenAI()
def embed_batch(texts, model='text-embedding-3-small'):
response = client.embeddings.create(
model=model,
input=texts # up to 2048 texts per call
)
return [item.embedding for item in response.data]
# Embed one batch of 100 chunk texts
texts = [chunk['text'] for chunk in chunks[:100]]
vectors = embed_batch(texts)
print(f'Embedding dimension: {len(vectors[0])}')
print(f'Embedded {len(vectors)} chunks')Processamento em Lotes para Ganhar Eficiência
Ao indexar milhares de blocos, a eficiência é importante. Processe os blocos em lotes de 100 a 500 para equilibrar a vazão e o uso de memória. Registre sua posição para poder retomar após uma falha sem gerar novamente as incorporações dos blocos já processados. Registre o progresso regularmente. Para 100.000 blocos em lotes de 500, você fará 200 chamadas à API — normalmente isso é concluído em poucos minutos.
def embed_all_chunks(chunks, batch_size=200):
embedded = []
total = len(chunks)
for i in range(0, total, batch_size):
batch = chunks[i:i+batch_size]
texts = [c['text'] for c in batch]
vectors = embed_batch(texts)
for chunk, vector in zip(batch, vectors):
embedded.append({
**chunk,
'embedding': vector
})
if (i // batch_size) % 10 == 0:
print(f'Progress: {min(i+batch_size, total)}/{total}')
return embeddedTratamento de Limites de Taxa Durante a Indexação
A API de incorporações da OpenAI possui limites de taxa medidos em tokens por minuto (TPM). Grandes trabalhos de indexação atingem esses limites e recebem RateLimitError. Implemente um retrocesso exponencial com variação aleatória: quando ocorrer um erro de limite de taxa, aguarde um breve intervalo aleatório antes de tentar novamente, dobrando o tempo de espera a cada falha subsequente. Isso distribui as novas tentativas e impede que todos os trabalhadores paralelos sobrecarreguem a API no mesmo instante.
import time
import random
from openai import RateLimitError
def embed_batch_with_retry(texts, max_retries=5):
for attempt in range(max_retries):
try:
return embed_batch(texts)
except RateLimitError:
if attempt == max_retries - 1:
raise
wait = (2 ** attempt) + random.uniform(0, 1)
print(f'Rate limited. Waiting {wait:.1f}s...')
time.sleep(wait)
return []Inserindo Vetores no Pinecone
Depois de gerar os embeddings, insira-os ou atualize-os no armazenamento vetorial. Inserir ou atualizar significa inserir novos vetores ou atualizar os existentes caso o mesmo ID já exista — por definição, a operação é idempotente. No Pinecone, cada registro inserido ou atualizado contém o ID do vetor, os valores do embedding e um dicionário de metadados com os campos que deseja usar posteriormente para filtrar ou exibir. Faça essas operações em lotes de até 100 registros por chamada para obter o melhor desempenho.
import pinecone
pc = pinecone.Pinecone(api_key='YOUR_KEY')
index = pc.Index('rag-index')
def upsert_to_pinecone(embedded_chunks, batch_size=100):
for i in range(0, len(embedded_chunks), batch_size):
batch = embedded_chunks[i:i+batch_size]
vectors = [
(
chunk['id'],
chunk['embedding'],
{
'text': chunk['text'],
'source': chunk['metadata']['source'],
'page': chunk['metadata'].get('page', 0)
}
)
for chunk in batch
]
index.upsert(vectors=vectors)
print(f'Upserted {min(i+batch_size, len(embedded_chunks))}/{len(embedded_chunks)}')Armazenamento no pgvector
Com o pgvector, insira os embeddings diretamente em uma tabela do PostgreSQL usando SQL padrão. O tipo de dado vector aceita uma lista Python de números de ponto flutuante serializada como uma string. Depois de inserir todas as linhas, crie um índice HNSW para realizar consultas rápidas de vizinhos mais próximos aproximados. Indexar uma tabela existente com milhões de linhas pode levar vários minutos; por isso, crie o índice depois da inserção em massa, e não antes.
import psycopg2
from psycopg2.extras import execute_values
def upsert_to_pgvector(conn, embedded_chunks):
with conn.cursor() as cur:
records = [
(
chunk['id'],
chunk['text'],
chunk['metadata']['source'],
chunk['metadata'].get('page', 0),
chunk['embedding'] # list of floats
)
for chunk in embedded_chunks
]
execute_values(cur, '''
INSERT INTO document_chunks (id, text, source, page, embedding)
VALUES %s
ON CONFLICT (id) DO UPDATE
SET text = EXCLUDED.text, embedding = EXCLUDED.embedding
''', records)
conn.commit()Criando o índice HNSW
HNSW (Hierarchical Navigable Small World) é o tipo de índice que permite realizar buscas rápidas de vizinhos mais próximos aproximados. Ao contrário da busca por força bruta (que compara o vetor da consulta com todos os vetores armazenados), o HNSW cria uma estrutura de grafo com várias camadas que reduz o espaço de busca. O parâmetro m controla quantas conexões cada nó possui (um valor maior proporciona melhor revocação, mas exige mais memória), e ef_construction controla a qualidade do índice durante sua criação.
-- Build HNSW index after bulk insertion
CREATE INDEX CONCURRENTLY ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Set ef_search at query time to trade recall vs speed
SET hnsw.ef_search = 100;
-- Verify index was created
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'document_chunks';Projetando o esquema de metadados
Os metadados armazenados junto de cada vetor permitem uma recuperação filtrada poderosa. Projete o esquema de metadados antes de indexar — adicionar novos campos posteriormente exige reindexar os dados. Inclua os campos que serão usados em filtros (departamento, tipo de documento, intervalo de datas), os campos que serão exibidos nas citações (título, página, autor) e os campos úteis para depuração (índice do trecho, total de trechos, data de indexação). Mantenha os valores dos metadados simples: strings, números e valores booleanos são indexados e filtrados com eficiência; objetos aninhados não são.
# Well-designed metadata schema
METADATA_SCHEMA = {
# For filtering at retrieval time
'department': 'HR', # string
'doc_type': 'policy', # string
'year': 2025, # integer
'is_active': True, # boolean
# For display in citations
'title': 'Employee Handbook 2025',
'author': 'HR Team',
'page': 12,
'source': 's3://docs/handbook_2025.pdf',
# For debugging and updates
'chunk_index': 3,
'total_chunks': 24,
'indexed_at': '2025-09-01T10:00:00Z'
}Criando pontos de verificação para tarefas longas de indexação
Indexar um corpus grande pode levar horas. Uma falha no meio do processo desperdiça todo o progresso. Implemente um arquivo de ponto de verificação que registre quais trechos foram indexados com sucesso. Ao reiniciar, ignore os trechos já indexados e continue de onde parou. Isso torna a tarefa de indexação idempotente e segura para ser retomada. Armazene o ponto de verificação como um conjunto de IDs de trechos processados em um arquivo JSON ou em uma tabela de banco de dados.
import json
from pathlib import Path
CHECKPOINT_FILE = '/tmp/index_checkpoint.json'
def load_checkpoint():
if Path(CHECKPOINT_FILE).exists():
return set(json.loads(Path(CHECKPOINT_FILE).read_text()))
return set()
def save_checkpoint(indexed_ids):
Path(CHECKPOINT_FILE).write_text(json.dumps(list(indexed_ids)))
def index_with_checkpoint(chunks, index):
done = load_checkpoint()
remaining = [c for c in chunks if c['id'] not in done]
print(f'Resuming: {len(done)} done, {len(remaining)} remaining')
for chunk in remaining:
upsert_to_pinecone([chunk], index)
done.add(chunk['id'])
save_checkpoint(done)Verificando a integridade do índice
Depois de indexar, verifique se todos os trechos foram inseridos no armazenamento vetorial. Compare o número de trechos produzidos pelo seu divisor com a quantidade de vetores informada pelo índice. Consulte o índice usando o texto de um documento conhecido e confirme se o resultado esperado aparece entre os 5 primeiros. Execute algumas consultas conhecidas do seu conjunto de testes de referência e verifique se a precisão está no nível esperado. Nunca presuma que o índice está completo sem verificá-lo.
def verify_index(index, chunks, sample_size=10):
index_stats = index.describe_index_stats()
total_vectors = index_stats.total_vector_count
expected = len(chunks)
print(f'Index vectors: {total_vectors}, Expected: {expected}')
if total_vectors != expected:
print('WARNING: mismatch — some chunks may not have been indexed')
# Spot-check retrieval
import random
sample = random.sample(chunks, sample_size)
for chunk in sample:
vec = embed_batch([chunk['text']])[0]
results = index.query(vector=vec, top_k=1, include_metadata=True)
top_id = results.matches[0].id if results.matches else None
if top_id != chunk['id']:
print(f'WARNING: expected {chunk["id"]}, got {top_id}')Alternativas locais para embeddings
Para dados sensíveis à privacidade que não podem sair da sua infraestrutura, use modelos de embedding hospedados localmente. A biblioteca sentence-transformers fornece modelos de alta qualidade, como all-MiniLM-L6-v2 (384 dimensões, 22 MB, muito rápido) e bge-large-en-v1.5 (1024 dimensões, melhor qualidade). Execute-os na CPU para cargas de trabalho moderadas ou na GPU para tarefas grandes de indexação. Os modelos locais eliminam os custos de API e a transferência de dados para fora da infraestrutura, mas exigem o gerenciamento dos arquivos dos modelos e dos recursos computacionais.
from sentence_transformers import SentenceTransformer
# Load once at startup
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
def embed_locally(texts, batch_size=64):
# encode() handles batching internally
embeddings = model.encode(
texts,
batch_size=batch_size,
show_progress_bar=True,
convert_to_numpy=True
)
return embeddings.tolist() # convert numpy array to Python list
vectors = embed_locally([c['text'] for c in chunks])Verificação rápida
Teste sua compreensão dos conceitos de Engenharia de IA abordados nesta lição.
Recapitulação da lição
Nesta lição, você aprendeu a: gerar embeddings em lotes com a API da OpenAI e lidar com limites de frequência usando recuo exponencial; inserir ou atualizar vetores no Pinecone e no pgvector com metadados; criar índices HNSW para buscas rápidas de vizinhos mais próximos aproximados; e aplicar práticas recomendadas para produção, incluindo retomada baseada em pontos de verificação, projeto do esquema de metadados e verificação da integridade do índice. A seguir, criaremos o pipeline de consulta que recupera trechos e gera respostas fundamentadas.
Perguntas Frequentes
A aula “Indexação: gerando embeddings e armazenando partes” é grátis?
Sim — o texto completo de “Indexação: gerando embeddings e armazenando partes” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Engineering Academy, atualize para CoddyKit PRO. O curso de AI Engineering Academy inclui 4 aulas no total.
O que vou aprender em “Indexação: gerando embeddings e armazenando partes”?
Gere um embedding para cada parte usando a API de embeddings da OpenAI e insira os vetores resultantes com metadados em um armazenamento vetorial, criando um índice pesquisável dos seus documentos. Você pratica AI Engineering Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar AI Engineering Academy?
Nenhuma experiência prévia é necessária. AI Engineering Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Indexação: gerando embeddings e armazenando partes”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de AI Engineering Academy?
Sim. Cada aula de AI Engineering Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Carregamento de documentos e extração de texto
- Estratégias de divisão: fixa, por sentença e recursiva
- Indexação: gerando embeddings e armazenando partes
- Consultar, recuperar e gerar