RAG barato en 2026: construye un chatbot sobre tus documentos sin gastar en embeddings caros
Datos y precios verificados al 22 de mayo de 2026. Las cifras de costos de APIs provienen de las páginas oficiales de cada proveedor y de fuentes independientes de seguimiento de precios (EmbeddingCost.com, TokenMix.ai, AwesomeAgents.ai).
Si alguna vez has calculado cuánto costaría indexar toda la documentación interna de tu empresa usando embeddings de pago, probablemente hayas cerrado la calculadora con una mueca. RAG (Retrieval-Augmented Generation) es hoy la arquitectura estándar para construir chatbots sobre documentos propios, pero el modelo mental dominante sigue siendo el mismo de 2023: pagar por cada token que embedeas, escalar con Pinecone o Weaviate Cloud, y rezar para que el presupuesto aguante. La realidad de 2026 es radicalmente distinta, y la mayoría de equipos todavía no lo sabe.
El problema concreto es este: un startup que quiere indexar 100,000 documentos de soporte técnico, con un promedio de 500 tokens por documento, necesita procesar 50 millones de tokens solo en la fase de ingesta. Con text-embedding-3-large de OpenAI a $0.13 por millón de tokens, eso son $6.50 de entrada —parece poco— pero multiplícalo por las re-indexaciones periódicas, los entornos de staging, los experimentos de chunking y los cambios de esquema, y el número crece rápido. Ahora imagina ese mismo flujo a escala de millones de documentos o con varias decenas de colecciones distintas.
Lo que este artículo te muestra es cómo construir un pipeline RAG completamente funcional en 2026, con calidad de producción, sin pagar un centavo en embeddings. Usaremos modelos locales de código abierto que en benchmarks actuales igualan o superan a las APIs de pago, una base vectorial que persiste en disco sin servidor, y un LLM externo únicamente para la generación final —donde sí tiene sentido pagar por tokens.
El mapa de costos real de los embeddings en 2026
Antes de hablar de alternativas, conviene entender exactamente contra qué estamos optimizando. OpenAI ofrece dos modelos de embeddings principales: text-embedding-3-small a $0.02 por millón de tokens para la mayoría de tareas de retrieval, y text-embedding-3-large a $0.13 por millón de tokens para casos que requieren mayor precisión. La API Batch de OpenAI ofrece un 50% de descuento a cambio de hasta 24 horas de tiempo de procesamiento, bajando text-embedding-3-small a $0.01/M en operaciones no urgentes.
En el extremo opuesto del espectro de precios de pago, Google text-embedding-005 cuesta solo $0.006 por millón de tokens, situándose a 1/30 del precio de las opciones premium, con puntuación MTEB dentro de 1.3 puntos del líder del benchmark.
Pero el cambio más importante del año vino en abril de 2026. Microsoft lanzó Harrier, una familia de modelos de embeddings de código abierto que alcanzó el primer lugar en el benchmark multilingual MTEB-v2 el 6 de abril de 2026, superando tanto a modelos open-source como a alternativas propietarias. La familia incluye un modelo flagship de 27B parámetros, una variante de 0.6B y un modelo edge de 270M parámetros, todos con una ventana de contexto de 32,768 tokens, disponibles bajo licencia MIT.
La llegada de Harrier al tope del leaderboard multilingual es significativa porque es completamente open-source. Los líderes previos de benchmarks de NVIDIA, Google, OpenAI y Amazon mantenían sus modelos de embeddings como propietarios o los ofrecían solo a través de APIs de pago.
Para equipos que no quieren gestionar infraestructura GPU, el modelo all-MiniLM-L6-v2 de Sentence Transformers sigue siendo la opción más pragmática: ocupa solo ~80 MB en disco, corre en CPU sin lag perceptible para consultas individuales, y se ubica en el cuartil superior de los benchmarks de retrieval MTEB.
¿Cuándo tiene sentido pagar por embeddings?
Hay escenarios legítimos donde pagar tiene sentido: volúmenes muy altos con SLAs estrictos donde no quieres gestionar infraestructura, dominios especializados (código, legal, médico) donde Voyage AI saca 4-6 puntos de ventaja en MTEB específico, o cuando necesitas embeddings multimodales. Para el 80% de los casos de RAG sobre documentos internos, los modelos locales gratuitos son la respuesta correcta.
La arquitectura: RAG local sin embeddings de pago
El stack que vamos a construir tiene cuatro componentes, todos gratuitos en la fase de indexación:
Sentence Transformers para generar embeddings localmente, sin API keys ni costos por token. ChromaDB como base de datos vectorial que persiste en disco, sin servidor externo. LangChain para orquestar el pipeline de retrieval. Y en la fase de generación, un LLM de tu elección —puedes usar Ollama localmente o una API externa solo para las respuestas finales.
Por qué ChromaDB y no Pinecone/Weaviate Cloud
La decisión no es solo de costos. ChromaDB es una base vectorial ligera que corre localmente sin necesidad de servidor, y el índice persiste entre reinicios sin necesidad de re-embeddear. Para proyectos con menos de 5-10 millones de vectores, el rendimiento es más que suficiente. La migración a Qdrant, pgvector o Weaviate self-hosted es trivial si escala el proyecto.
Implementación práctica: el pipeline completo en Python
Aquí está el código de un pipeline RAG funcional. Asume que tienes una carpeta docs/ con archivos PDF, Markdown o texto plano que quieres hacer consultables.
Instalación del entorno
bash# Crear entorno virtual para aislar dependencias python -m venv rag-env source rag-env/bin/activate # En Windows: rag-env\Scripts\activate # Instalar dependencias core (sin API keys externas para embeddings) pip install langchain langchain-community langchain-chroma \ chromadb sentence-transformers pymupdf \ langchain-text-splitters
El pipeline de indexación y consulta
python# rag_pipeline.py # Pipeline RAG completo con embeddings locales — sin costo por token import os from langchain_community.document_loaders import DirectoryLoader, PyMuPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_chroma import Chroma from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough # ─── 1. CONFIGURACIÓN CENTRAL ──────────────────────────────────────────────── DOCS_DIR = "./docs" # Carpeta con tus documentos fuente CHROMA_DIR = "./chroma_store" # Directorio donde persiste el índice vectorial EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2" # ~80MB, corre en CPU CHUNK_SIZE = 800 # Tokens por chunk — ajustar según longitud promedio de docs CHUNK_OVERLAP = 100 # Overlap entre chunks para no perder contexto en bordes # ─── 2. CARGA DE DOCUMENTOS ────────────────────────────────────────────────── def cargar_documentos(directorio: str): """ Carga PDFs y archivos de texto desde una carpeta. DirectoryLoader detecta el tipo de archivo automáticamente. """ # Cargador para PDFs — PyMuPDF es más rápido que pypdf para archivos grandes loader_pdf = DirectoryLoader( directorio, glob="**/*.pdf", loader_cls=PyMuPDFLoader, show_progress=True ) # Cargador para Markdown y texto plano loader_txt = DirectoryLoader( directorio, glob="**/*.{md,txt}", show_progress=True ) documentos = loader_pdf.load() + loader_txt.load() print(f"✓ Documentos cargados: {len(documentos)} páginas/archivos") return documentos # ─── 3. CHUNKING INTELIGENTE ───────────────────────────────────────────────── def dividir_en_chunks(documentos): """ RecursiveCharacterTextSplitter respeta la estructura semántica: intenta cortar en párrafos antes que en oraciones, y en oraciones antes que en palabras arbitrarias. """ splitter = RecursiveCharacterTextSplitter( chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP, # Separadores en orden de preferencia: párrafos > líneas > oraciones > palabras separators=["\n\n", "\n", ". ", " ", ""] ) chunks = splitter.split_documents(documentos) print(f"✓ Chunks generados: {len(chunks)} (tamaño ~{CHUNK_SIZE} tokens c/u)") return chunks # ─── 4. EMBEDDINGS LOCALES — COSTO: $0.00 ──────────────────────────────────── def crear_embedder(): """ HuggingFaceEmbeddings carga el modelo desde Hugging Face la primera vez y lo cachea localmente. Las ejecuciones posteriores usan el cache. Cambiar device="cuda" si tienes GPU disponible para ~10x más velocidad. """ embedder = HuggingFaceEmbeddings( model_name=EMBED_MODEL, model_kwargs={"device": "cpu"}, # Usar "cuda" si hay GPU encode_kwargs={"normalize_embeddings": True} # Normalizar para similitud coseno ) return embedder # ─── 5. BASE DE DATOS VECTORIAL PERSISTENTE ────────────────────────────────── def construir_o_cargar_indice(chunks=None): """ Si ya existe el índice en disco, lo carga directamente (sin re-embeddear). Si no existe, crea uno nuevo desde los chunks proporcionados. Esto es clave para no pagar tiempo de CPU en cada reinicio. """ embedder = crear_embedder() if os.path.exists(CHROMA_DIR) and os.listdir(CHROMA_DIR): print("✓ Índice existente detectado — cargando desde disco...") vectorstore = Chroma( persist_directory=CHROMA_DIR, embedding_function=embedder ) else: print("Construyendo índice nuevo — esto tarda solo en la primera ejecución...") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedder, persist_directory=CHROMA_DIR ) print(f"✓ Índice guardado en {CHROMA_DIR}") return vectorstore # ─── 6. CHAIN DE RETRIEVAL + GENERACIÓN ────────────────────────────────────── def construir_chain(vectorstore, llm): """ Construye la cadena RAG completa usando LCEL (LangChain Expression Language). El retriever busca los k chunks más relevantes y los inyecta como contexto. El LLM solo ve esos chunks, no el documento completo — esto controla costos. """ # Recuperar los 4 chunks más similares semánticamente a la pregunta retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4} ) # Prompt que fuerza al modelo a responder solo desde el contexto recuperado prompt = ChatPromptTemplate.from_template(""" Eres un asistente técnico especializado. Responde la pregunta usando ÚNICAMENTE la información del contexto proporcionado. Si la respuesta no está en el contexto, di explícitamente que no tienes esa información. No inventes datos. Contexto: {context} Pregunta: {question} Respuesta:""") # Función auxiliar para formatear los chunks recuperados def formatear_contexto(docs): return "\n\n---\n\n".join( f"[Fuente: {doc.metadata.get('source', 'desconocida')}]\n{doc.page_content}" for doc in docs ) # Chain LCEL: retrieve → format → prompt → LLM → parse chain = ( {"context": retriever | formatear_contexto, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) return chain # ─── 7. SCRIPT PRINCIPAL ───────────────────────────────────────────────────── if __name__ == "__main__": # Paso 1: Indexar documentos (solo necesario la primera vez o tras actualizaciones) if not os.path.exists(CHROMA_DIR): docs = cargar_documentos(DOCS_DIR) chunks = dividir_en_chunks(docs) vectorstore = construir_o_cargar_indice(chunks) else: vectorstore = construir_o_cargar_indice() # Paso 2: Conectar con el LLM para generación # Opción A — LLM local con Ollama (costo cero, requiere Ollama instalado): # from langchain_ollama import ChatOllama # llm = ChatOllama(model="llama3.2") # Opción B — API externa solo para generación (pagas tokens de output, no de embedding): from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # ~$0.60/M output tokens chain = construir_chain(vectorstore, llm) # Paso 3: Consultar en modo interactivo print("\nChatbot listo. Escribe 'salir' para terminar.\n") while True: pregunta = input("Tu pregunta: ").strip() if pregunta.lower() == "salir": break respuesta = chain.invoke(pregunta) print(f"\nRespuesta: {respuesta}\n")
Este script hace varias cosas importantes que vale la pena destacar. El chunking con overlap (líneas 42-51) evita el problema clásico de RAG donde la respuesta queda partida entre dos chunks adyacentes. La persistencia en disco de ChromaDB (líneas 67-84) significa que la fase de embedding solo ocurre una vez, y cada consulta posterior carga el índice ya construido. El prompt con restricción de contexto (líneas 95-104) es la medida más efectiva contra alucinaciones en RAG: instruyes explícitamente al modelo a no inventar información fuera del contexto recuperado.
Optimizaciones avanzadas para reducir costos de generación
Resolver los embeddings gratis es la mitad de la batalla. La otra mitad es minimizar el costo de los tokens de generación, que sí son inevitables si usas una API externa.
Comprimir el contexto antes de enviarlo al LLM
El retriever recupera chunks completos, pero no siempre necesitas el chunk entero. Una técnica efectiva es usar un reranker cross-encoder gratuito para reordenar los chunks por relevancia real, y luego truncar los menos relevantes:
pythonfrom langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder # cross-encoder/ms-marco-MiniLM-L-6-v2 — modelo gratuito, 80MB, corre en CPU reranker_model = HuggingFaceCrossEncoder( model_name="cross-encoder/ms-marco-MiniLM-L-6-v2" ) compressor = CrossEncoderReranker(model=reranker_model, top_n=2) # Solo los 2 mejores chunks # Retriever comprimido: recupera 6, reordena, devuelve los 2 más relevantes compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=vectorstore.as_retriever(search_kwargs={"k": 6}) )
Pasar de 4 chunks a 2 chunks bien seleccionados puede reducir hasta un 50% los tokens enviados al LLM, manteniendo o mejorando la calidad de respuesta.
Elegir el LLM correcto para cada tarea
GPT-4.1 Mini a $0.75 de input es el modelo mid-tier más competitivo del mercado en este momento. Para RAG sobre documentación técnica, un modelo de esta gama supera a modelos más grandes en tareas de grounding porque el contexto relevante ya está en el prompt —no necesitas el razonamiento complejo de un modelo frontier.
Usar Ollama para entornos de desarrollo y staging
Ejecutar nomic-embed-text a través de Ollama mantiene un único runtime para embeddings y generación, sin salir de la máquina local. Para equipos que quieren costo cero absoluto durante desarrollo, Ollama con Llama 3.2 o Gemma 3 es la combinación más pragmática.
Cuándo romper la regla y pagar por embeddings
Este artículo promueve los embeddings gratuitos, pero hay tres escenarios donde sí conviene pagar:
Multilingüismo complejo en producción. Si tus documentos mezclan idiomas como árabe, chino y español en el mismo corpus, Harrier de Microsoft con soporte para más de 100 idiomas y ventana de contexto de 32,000 tokens entrenado sobre más de 2 mil millones de ejemplos es una opción open-source sin costo de API, pero sí requiere GPU para el modelo de 27B parámetros. Si no tienes GPU, Google text-embedding-005 a $0.006/M sigue siendo la alternativa de pago más barata.
Documentos muy largos sin chunking viable. Algunos contratos legales o papers científicos pierden coherencia al chunkear. voyage-4-lite ofrece una ventana de contexto de 32K tokens al mismo precio de $0.02/M que text-embedding-3-small de OpenAI, con mejor performance en contextos largos.
Equipos sin ancho de banda para gestionar infraestructura. Si el equipo es pequeño y el tiempo de ingeniería vale más que $5-10 al mes en embeddings, usar una API de pago es la decisión correcta. Optimizar prematuramente infraestructura de embeddings cuando el producto todavía no tiene usuarios es un clásico error de prioridades.
Conclusión: el costo real de los embeddings en 2026 es opcional
El mensaje central de este artículo es simple: en 2026, pagar por embeddings es una decisión de conveniencia, no una necesidad técnica. Los modelos locales como all-MiniLM-L6-v2 cubren el 80% de los casos de uso con calidad de producción. El reciente lanzamiento de Harrier bajo licencia MIT demuestra que la brecha de calidad entre modelos open-source y propietarios ha desaparecido, al menos en retrieval.
El pipeline que construiste en este artículo —Sentence Transformers + ChromaDB + LangChain— te da un chatbot sobre tus documentos donde el costo de embeddings es exactamente $0.00, sin sacrificar calidad ni escalabilidad razonable.
El siguiente paso concreto: clona el código, apunta DOCS_DIR a una carpeta con tus documentos reales, y ejecuta el script. El primer indexado puede tardar unos minutos dependiendo del volumen. A partir de ahí, cada consulta carga el índice desde disco en segundos.
Preguntas frecuentes
Fredo
Ingeniero de Sistemas · Especialista en costos de IA
"Ingeniero de sistemas especializado en arquitectura de costos para APIs de IA. Analiza y compara modelos de lenguaje en producción para ayudar a equipos de desarrollo latinoamericanos a optimizar su infraestructura de IA sin destruir sus márgenes."