Construye tu primer sistema RAG con Claude y LangChain: del documento a la respuesta en menos de 50 líneas

Construye tu primer sistema RAG con Claude y LangChain: del documento a la respuesta en menos de 50 líneas
Foto de Anastassia Anufrieva en Unsplash

El problema que nadie te cuenta sobre los LLMs en producción

Entrenar un modelo desde cero para que conozca tus documentos internos es inviable para el 99% de los equipos. El fine-tuning es caro, lento y se queda obsoleto cada vez que actualizas un PDF. La solución real se llama RAG (Retrieval-Augmented Generation), y con Claude de Anthropic más LangChain puedes tenerlo funcionando en menos de 50 líneas de Python.

En este artículo construiremos un sistema RAG completo, cargamos un documento, lo indexamos en una base vectorial y consultamos con lenguaje natural usando Claude como cerebro generativo. Sin magia, solo código que puedes copiar y adaptar hoy.

Por qué RAG y por qué Claude + LangChain

RAG combina dos fases: recuperación (buscar los fragmentos más relevantes de tus documentos) y generación (pedirle al LLM que responda usando solo esos fragmentos). El resultado es un sistema que cita fuentes reales, no alucina datos que no existen y se actualiza sin reentrenar.

Claude destaca aquí por dos razones concretas: su ventana de contexto de hasta 200K tokens tolera fragmentos largos sin degradación, y su capacidad de seguir instrucciones estrictas reduce las alucinaciones cuando le dices explícitamente "responde solo con lo que está en el contexto". LangChain, por su parte, proporciona los conectores, splitters y retrievers que evitan escribir esa infraestructura a mano.

Requisitos previos

  • Python 3.10+
  • API key de Anthropic (ANTHROPIC_API_KEY)
  • Instalar dependencias: pip install langchain langchain-anthropic langchain-community chromadb pypdf

Guía paso a paso: RAG en 47 líneas

Paso 1 — Cargar y fragmentar el documento

Usaremos un PDF como fuente de conocimiento. El chunk size de 1000 caracteres con 200 de solapamiento es un punto de partida sólido para documentos técnicos; ajústalo si trabajas con contratos legales (más largo) o FAQs (más corto).

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

loader = PyPDFLoader("tu_documento.pdf")
docs = loader.load()

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200
)
chunks = splitter.split_documents(docs)
print(f"Total de fragmentos: {len(chunks)}")

Paso 2 — Indexar en ChromaDB con embeddings

Convertimos los fragmentos en vectores y los almacenamos en ChromaDB (base vectorial local, sin servidor). Aquí usamos los embeddings de HuggingFace para no depender de otra API de pago, pero puedes sustituirlos por OpenAIEmbeddings si ya tienes esa clave.

from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(
    model_name="sentence-transformers/all-MiniLM-L6-v2"
)

vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

Paso 3 — Conectar Claude y construir la cadena RAG

El prompt es la pieza crítica. Le decimos a Claude exactamente qué hacer con el contexto recuperado. Nota el uso de claude-3-5-sonnet-20241022, es el balance óptimo entre velocidad y razonamiento para RAG en producción.

from langchain_anthropic import ChatAnthropic
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser

llm = ChatAnthropic(
    model="claude-3-5-sonnet-20241022",
    temperature=0
)

prompt = ChatPromptTemplate.from_template("""
Eres un asistente preciso. Responde ÚNICAMENTE usando el contexto proporcionado.
Si la respuesta no está en el contexto, di explícitamente: "No encontré esa información en el documento".

Contexto:
{context}

Pregunta: {question}
""")

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

Paso 4 — Hacer la primera consulta

respuesta = rag_chain.invoke("¿Cuáles son los plazos de entrega mencionados en el contrato?")
print(respuesta)

El sistema recuperará los 4 fragmentos más similares semánticamente a la pregunta, los inyectará en el prompt de Claude y devolverá una respuesta fundamentada en el documento. Si la información no existe, el modelo lo dirá en lugar de inventarla.

Tres ajustes que marcan la diferencia en producción

  • Aumenta k de 4 a 6-8 si tus documentos son densos en información o las respuestas parecen incompletas.
  • Añade metadatos al recuperar: guarda el número de página y el nombre del archivo en los chunks para que Claude pueda citar la fuente exacta.
  • Usa temperature=0 siempre en RAG. Cualquier valor mayor introduce variabilidad innecesaria cuando el objetivo es fidelidad al documento.

Siguiente paso: de prototipo a sistema real

Con este esquema tienes un RAG funcional sobre cualquier PDF en menos de 10 minutos. El siguiente nivel es añadir persistencia real (ChromaDB ya la soporta con persist_directory), exponer la cadena como una API con FastAPI, y evaluar la calidad de las respuestas con métricas como faithfulness y answer relevancy usando frameworks como RAGAS.

El RAG no es una moda, es la arquitectura que permite llevar conocimiento privado y actualizado a los LLMs sin los costes del fine-tuning. Y como acabas de ver, la barrera de entrada es sorprendentemente baja.