Hace unos seis meses, mi equipo estaba construyendo un chatbot interno para una empresa de logística. Tenían cientos de documentos operativos — procedimientos de aduana, tarifas actualizadas cada trimestre, manuales de flota — y necesitaban que los empleados pudieran hacerle preguntas en lenguaje natural. La primera decisión real que tuve que tomar fue esta: ¿entreno el modelo con ese conocimiento, o construyo un sistema que lo recupere dinámicamente?
Spoiler: tomé la decisión equivocada la primera vez. Y fue cara.
Ahora, después de implementar ambas técnicas en ese proyecto y en dos más, tengo opiniones bastante concretas sobre cuándo usar cada una.
RAG no es complicado, pero la gente lo sobrediseña desde el inicio
RAG (Retrieval-Augmented Generation) es conceptualmente simple: en vez de que el modelo “sepa” algo de memoria, tú le das el contexto relevante en el prompt. Tienes una base de documentos, los conviertes en vectores, y cuando el usuario pregunta algo, recuperas los fragmentos más parecidos semánticamente y los metes en el contexto del LLM.
El pipeline básico en Python con LangChain se ve así:
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Cargar y chunkar documentos
loader = DirectoryLoader("./docs", glob="**/*.pdf")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200 # Overlap es clave — sin esto pierdes contexto en los bordes
)
chunks = splitter.split_documents(docs)
# Crear vectorstore
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory="./chroma_db")
# Construir chain de QA
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 4}),
return_source_documents=True # Siempre devuelve fuentes para auditoría
)
response = qa_chain.invoke({"query": "¿Cuál es el procedimiento para importaciones de la UE?"})
print(response["result"])
Esto funciona. Bien. Desde el primer día.
Lo que más me sorprendió al principio fue qué tan rápido puedes iterar. Cambias los documentos, los re-indexas, y el sistema ya sabe la versión nueva. No hay epochs, no hay cuda out of memory, no hay que esperar a que terminen los trabajos de entrenamiento.
RAG brilla especialmente cuando:
- El conocimiento cambia frecuentemente. Tarifas, precios, documentación de APIs en evolución activa. Si tu empresa actualiza procedimientos cada mes, no puedes estar fine-tuneando cada mes.
- Necesitas trazabilidad. Puedes devolver exactamente qué fragmento usó el modelo para generar la respuesta. En entornos regulados (legal, salud, finanzas) esto no es opcional.
- El dominio es muy específico y propietario. El modelo base no sabe nada de los contratos internos de tu empresa. Nunca va a saberlo. RAG es la única opción realista.
- Tu presupuesto de GPU es cero. No necesitas nada más que una API key y almacenamiento.
Una cosa que noto constantemente: la calidad del chunking importa muchísimo más de lo que la gente cree. Si partes mal los documentos, el retrieval va a traer basura y el LLM va a alucinar aunque tenga el documento correcto. Mi heurística actual es chunk_size entre 800-1200 tokens con un overlap del 15-20%. Más grande no siempre es mejor — a veces diluye la señal relevante.
Fine-tuning: cuando el problema no es el conocimiento sino el comportamiento
Aquí está el malentendido más común que veo: la gente trata el fine-tuning como si fuera una forma de “enseñarle hechos” al modelo. No funciona así. O bueno, funciona parcialmente, pero es la forma más cara y frágil de hacerlo.
Fine-tuning es para cambiar cómo responde el modelo, no qué sabe.
Casos donde sí tiene sentido:
- Quieres que el modelo adopte un tono muy específico que es difícil de lograr con prompting. Una empresa con un estilo de comunicación muy particular, jerga interna, nivel de formalidad consistente.
- Necesitas que el modelo haga una tarea estructurada de forma muy consistente. Extracción de JSON con un schema específico, clasificación con categorías propietarias, formateo de salidas que deben ser idénticas entre llamadas.
- Latencia y costo importan mucho. Un modelo fine-tuneado más pequeño (Llama 3 8B, Mistral 7B) puede ser significativamente más barato en inferencia que GPT-4o si vas a hacer millones de llamadas al mes.
- Tienes datos de demostración de calidad. Cientos o miles de pares (input, output) que muestran exactamente el comportamiento que quieres.
El script de fine-tuning con la API de OpenAI es bastante directo ahora (esto con el formato actual de gpt-4o-mini, que es donde tiene más sentido económico):
import openai
import json
client = openai.OpenAI()
# Tu archivo JSONL de entrenamiento debe verse así:
# {"messages": [
# {"role": "system", "content": "Eres un asistente de logística que responde en español formal..."},
# {"role": "user", "content": "¿Cómo proceso una devolución internacional?"},
# {"role": "assistant", "content": "Para procesar una devolución internacional, debe seguir..."}
# ]}
# Subir dataset de entrenamiento
with open("training_data.jsonl", "rb") as f:
training_file = client.files.create(file=f, purpose="fine-tune")
# Crear job de fine-tuning
job = client.fine_tuning.jobs.create(
training_file=training_file.id,
model="gpt-4o-mini-2024-07-18",
hyperparameters={
"n_epochs": 3, # Empieza conservador — overfitting es real
}
)
print(f"Job creado: {job.id}")
# Puedes monitorear con: client.fine_tuning.jobs.retrieve(job.id)
Lo que no te dice la documentación: necesitas datos de buena calidad, no solo muchos datos. Entrenar con 500 ejemplos excelentes supera a 5000 ejemplos mediocres. Aprendí esto de la peor manera.
El error que me costó tres días y algo de dignidad
Volviendo al proyecto de logística. Mi instinto inicial fue: “el modelo necesita saber mucho sobre aduanas y logística internacional, voy a fine-tunearlo con los documentos de la empresa.”
Pasé tres días preparando el dataset, convirtiendo PDFs a texto, creando pares de Q&A, esperando que terminaran los jobs. El resultado fue… decepcionante. El modelo respondía con información que sonaba plausible pero mezclaba datos de diferentes documentos, y lo más grave: cuando actualizaron las tarifas de aduana (lo cual pasó a la semana de hacer el deploy), el modelo seguía dando los precios viejos con total confianza.
El problema fundamental es que el fine-tuning “hornea” el conocimiento en los pesos. No puedes actualizar eso sin reentrenar. Y para un dominio donde la información cambia — logística, legal, finanzas — eso es un problema estructural, no técnico.
Tiré ese trabajo y construí un RAG. Me tomó día y medio tener algo funcional en producción, con mejor precisión y con la ventaja de que cuando cambian los documentos, solo hay que re-indexar.
Honestamente, el fine-tuning hubiera tenido sentido si el objetivo fuera diferente: que el modelo respondiera con el tono y la estructura específica que usa la empresa, no que memorizara las tarifas.
La comparación que nadie hace: costo total real
Mucha gente compara RAG vs fine-tuning en términos de precisión de respuestas y para ahí. Pero el costo total importa.
RAG:
– Setup inicial: medio día a dos días, dependiendo de la complejidad del pipeline.
– Costo por query: embedding de la pregunta + llamada al LLM con contexto extendido. El contexto extra cuesta tokens.
– Mantenimiento: re-indexar cuando cambian los docs. Fácil de automatizar.
– Escalabilidad del conocimiento: lineal — más documentos, más vectores, pero el proceso es el mismo.
Fine-tuning:
– Setup inicial: de dos días a dos semanas, dependiendo de la calidad de los datos de entrenamiento.
– Costo de entrenamiento: varía. Con gpt-4o-mini son centavos por token de entrenamiento. Con modelos open-source en tu propia GPU, el costo es tiempo y electricidad.
– Costo por query: más barato si usas un modelo más pequeño fine-tuneado vs un modelo grande de propósito general.
– Mantenimiento: cada vez que cambian los requerimientos, potencialmente hay que reentrenar. Esto se acumula.
Para la mayoría de los casos de uso que he visto en equipos de menos de 20 personas, RAG tiene mejor ROI. No porque sea técnicamente superior en abstracto, sino porque el ciclo de iteración es más corto y el mantenimiento es más predecible.
No estoy 100% seguro de que esto escale igual para equipos con millones de queries al día — ahí los costos de inferencia pueden cambiar la ecuación completamente.
Mi recomendación concreta, sin matices innecesarios
Después de todo esto, aquí está lo que yo haría según el caso:
Usa RAG si:
– Tus datos cambian más de una vez al trimestre.
– Necesitas citar fuentes o tener trazabilidad de dónde vino la respuesta.
– Estás construyendo un prototipo y necesitas iterar rápido.
– El dominio es altamente factual (preguntas con respuestas correctas e incorrectas claras).
Considera fine-tuning si:
– Tienes un problema de comportamiento o estilo que el prompting no resuelve de forma consistente.
– Vas a hacer un volumen muy alto de llamadas y el costo de tokens de contexto se vuelve significativo.
– Tienes datos de calidad (cientos de ejemplos buenos, no miles de ejemplos malos).
– La información en el dominio es estable.
La combinación que más me ha funcionado: RAG para conocimiento, fine-tuning para comportamiento. Un modelo fine-tuneado para responder en el tono correcto y con el formato correcto, augmentado con RAG para acceder a información actualizada. Es más complejo de mantener, pero cuando el problema lo justifica, es la mejor solución.
Lo que no haría: fine-tuning como primera opción solo porque suena más “técnico” o más impresionante para una demo. He visto equipos gastar semanas en pipelines de fine-tuning cuando un RAG bien hecho hubiera resuelto el problema en dos días. El objetivo es que funcione en producción, no que el enfoque sea glamoroso.
Si tuvieras que empezar mañana: construye el RAG primero. Mídelo. Si después de tener métricas reales el comportamiento del modelo es el cuello de botella — y puedes articular exactamente qué comportamiento necesitas cambiar — entonces empieza a explorar fine-tuning con datos que ya tienes de las interacciones reales del RAG. Esos datos van a ser mucho mejores que cualquier cosa que construyas artificialmente desde cero.