Todos los artículos
ARTÍCULO

RAG en la práctica: cómo construí un asistente que conoce mi codebase

Guía práctica de Retrieval-Augmented Generation: embeddings, vector stores y cómo conectar tus propios documentos a un LLM sin perder el contexto.

RAG en la práctica: cómo construí un asistente que conoce mi codebase

Los LLMs son increíblemente poderosos, pero tienen un límite fundamental: no conocen tu código, tu documentación interna ni tus decisiones de arquitectura. RAG (Retrieval-Augmented Generation) es la técnica que cierra esa brecha. La implementé para un proyecto real y te cuento exactamente cómo funciona.

El problema que RAG resuelve

Imagina que tienes 200 archivos de documentación interna, convenciones de código, ADRs (Architecture Decision Records) y wikis. Ningún LLM sabe que existen. Podrías copiarlos al contexto, pero con 200K tokens de límite, llenas el contexto en segundos y el modelo degrada su rendimiento.

RAG resuelve esto de forma elegante: en lugar de meter todo, recuperas solo lo relevante para cada pregunta.

La arquitectura básica

[Tus documentos] → Chunking → Embeddings → Vector Store

[Pregunta del usuario] → Embedding → Búsqueda semántica → Contexto relevante → LLM → Respuesta

Parece complejo, pero cada paso es simple.

Paso 1: Chunking (dividir tus documentos)

Los embeddings funcionan mejor con fragmentos de 300-500 tokens. No dividas por caracteres, divide por semántica:

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=400,
    chunk_overlap=50,  # overlap evita perder contexto en los bordes
    separators=["\n\n", "\n", ".", " "]
)

chunks = splitter.split_text(documento)

El chunk_overlap es clave: sin él, una idea que cruza el límite de un chunk se pierde.

Paso 2: Embeddings

Un embedding convierte texto en un vector numérico que captura su significado semántico. Textos similares producen vectores cercanos en el espacio vectorial.

from openai import OpenAI

client = OpenAI()

def embed(texto: str) -> list[float]:
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=texto
    )
    return response.data[0].embedding

Para proyectos con presupuesto limitado, nomic-embed-text (open source, corre local) da resultados comparables.

Paso 3: Vector Store

Guarda tus embeddings en una base de datos vectorial. Para proyectos pequeños, ChromaDB es perfecto (corre en memoria o en disco):

import chromadb

cliente_chroma = chromadb.PersistentClient(path="./vectorstore")
coleccion = cliente_chroma.create_collection("mi_codebase")

coleccion.add(
    documents=chunks,
    embeddings=[embed(chunk) for chunk in chunks],
    ids=[f"chunk_{i}" for i in range(len(chunks))]
)

Paso 4: Recuperación + Generación

Cuando el usuario hace una pregunta, la embeds, buscas los chunks más similares y los inyectas como contexto:

def responder(pregunta: str) -> str:
    # Recuperar contexto relevante
    resultado = coleccion.query(
        query_embeddings=[embed(pregunta)],
        n_results=5
    )
    contexto = "\n\n".join(resultado["documents"][0])

    # Generar con contexto
    respuesta = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": f"Usa este contexto:\n{contexto}"},
            {"role": "user", "content": pregunta}
        ]
    )
    return respuesta.choices[0].message.content

Resultados reales

Después de indexar 180 archivos de documentación y 60 ADRs, el asistente responde preguntas como “¿por qué usamos JWT en lugar de sessions?” con la respuesta exacta de nuestro ADR de 2023. Sin RAG, el LLM inventaría una respuesta plausible pero incorrecta.

La diferencia entre alucinación y precisión es el contexto correcto. RAG te da ese control.