Saltar al contenido
Todos los insights
Tutorial12 min de lectura

Para una base de conocimiento de empresa, empiece por RAG y un conjunto de test, y haga fine-tuning solo para el formato

Una tabla de decisión entre RAG y fine-tuning, y una prueba de dos semanas que muestra si la recuperación sobre sus propios documentos responde suficientemente bien a sus propias preguntas.

Los equipos que quieren un asistente sobre sus propios documentos suelen hacerse la misma primera pregunta: ¿entrenamos un modelo con nuestros documentos o conectamos un modelo a ellos? Para una base de conocimiento la respuesta por defecto es conectarlo, que es lo que hace la generación aumentada por recuperación (RAG), y construir un conjunto de test con preguntas reales antes de elegir nada más. Esta nota explica ambos enfoques en términos sencillos, da una tabla de decisión y después recorre una prueba de dos semanas que se puede hacer con un ingeniero y una persona que conozca los documentos.

RAG y fine-tuning en términos sencillos

Un modelo de lenguaje sabe lo que había en sus datos de entrenamiento. No conoce su política de devoluciones, la lista de precios del mes pasado ni el procedimiento interno para aprobar a un proveedor.

RAG lo resuelve en el momento de la pregunta. Cuando alguien pregunta algo, el sistema busca en sus documentos, toma el puñado de pasajes que parecen más relevantes y se los da al modelo con una instrucción: responde a partir de estos pasajes y di cuáles has usado. Los documentos se quedan donde están. Se edita una política, se reindexa, y la siguiente respuesta refleja la edición. La técnica se describió en un artículo de investigación de 2020 que combinaba un generador de texto con un índice de documentos consultable; la idea central no ha cambiado desde entonces.

El fine-tuning continúa el entrenamiento de un modelo con ejemplos de entradas y las salidas deseadas. Cambia los hábitos del modelo: tono, estructura, vocabulario, cómo maneja una tarea acotada. Es una forma débil de enseñar hechos que cambian. Cada edición de la fuente requiere un nuevo entrenamiento, el modelo no puede señalar de dónde salió una respuesta, y todo lo que aprendió está disponible para cualquiera que pueda usarlo.

La mayoría de las empresas ya trabaja así. El informe de Menlo Ventures de diciembre de 2025 sobre IA generativa en la empresa encontró que el diseño de prompts es la forma más común en que las empresas adaptan los modelos, con RAG en segundo lugar; el fine-tuning y el aprendizaje por refuerzo seguían siendo de nicho y los usaban sobre todo equipos de frontera.

Cuánto cuesta. La prueba de dos semanas descrita abajo cuesta el tiempo de un ingeniero y unos días de un experto en la materia, más un uso del modelo que es pequeño a la escala de un conjunto de test. En producción, el coste de operación sigue una fórmula: preguntas al mes × tokens por pregunta × precio por token. A modo de ilustración: 10.000 preguntas al mes, cada una enviando cinco pasajes de unos 400 tokens más 500 tokens de instrucciones y pregunta, y recibiendo una respuesta de 300 tokens, son 10.000 × 2.800 = 28 millones de tokens. Con un precio combinado ilustrativo de 5 USD por millón de tokens, son 140 USD al mes. Use los precios actuales de su proveedor; lo importante es la fórmula. Si quiere el sistema construido y no solo probado, nuestra franja publicada para un copiloto RAG interno es de 8k–18k USD en 4–8 semanas, y la página de precios muestra la tabla de tarifas que hay detrás.

La tabla de decisión

Lo que necesita RAG Fine-tuning
Conocimiento que cambia cada semana o cada día Las ediciones aparecen tras la siguiente reindexación Cada cambio necesita un nuevo entrenamiento y una nueva evaluación
Un enlace a la fuente en cada respuesta Incorporado: la respuesta cita los pasajes que usó No hay una forma fiable de decir de dónde salió una respuesta
Distintas personas con permiso para ver distintos documentos Los pasajes se filtran según los permisos del usuario antes de que el modelo los vea Lo que se entrena es visible para todos los usuarios del modelo
Un formato de salida fijo o el tono de la casa Posible con instrucciones y ejemplos en el prompt, a veces de forma inconsistente Encaja bien
Baja latencia Añade un paso de búsqueda y un prompt más largo Sin paso adicional; un modelo ajustado más pequeño también puede ser más rápido
Coste de actualizar Reindexar los documentos modificados Preparar ejemplos, reentrenar, reevaluar
Datos necesarios para empezar Los documentos y un conjunto de test de preguntas reales Entradas de ejemplo con salidas ideales revisadas por personas, más el mismo conjunto de test

Cuente qué filas importan en su caso. Para una base de conocimiento, las tres primeras suelen decidirlo: el contenido cambia, la gente quiere comprobar la fuente y no todo el mundo puede leerlo todo.

El fine-tuning se gana su sitio en un conjunto de casos más estrecho: las respuestas deben seguir siempre una estructura estricta; una tarea de alto volumen, como clasificar tickets en categorías, podría funcionar con un modelo más pequeño y barato; o el tono debe ajustarse a una guía de estilo que el prompting no mantiene. Incluso entonces suele ir encima de la recuperación en lugar de sustituirla, como un modelo ajustado que sigue leyendo los pasajes recuperados.

Así que lo predeterminado es primero RAG más un conjunto de evaluación. El fine-tuning, después, para formato, tono o una tarea acotada, y solo si el conjunto de test muestra que los errores restantes son de ese tipo.

La prueba de dos semanas

La prueba sustituye opiniones por un número. Al final se tiene una puntuación sobre sus propias preguntas y una lista de fallos ordenados por causa, que indica si construir, qué arreglar y si el fine-tuning ayudaría en absoluto.

Paso 1: recoger preguntas reales

Tome preguntas que la gente haya hecho de verdad: tickets del helpdesk, hilos del chat interno, logs de búsqueda de la intranet, las preguntas que el responsable de una política contesta por correo. Apunte a 150 a 300. Incluya un 10–15% que sus documentos no puedan responder, porque rechazar correctamente es parte del trabajo.

Por qué tantas: con 200 preguntas y una exactitud real de alrededor del 80%, el margen de error al 95% es de unos ±5,5 puntos porcentuales (1,96 × √(0,8 × 0,2 / 200) ≈ 0,055). Un conjunto más pequeño no puede distinguir dos versiones del sistema.

Para cada pregunta, una persona que conozca el material escribe la respuesta esperada y nombra el documento del que debería salir. Una línea JSON por pregunta:

{"id": "q041", "question": "Who approves a new supplier above the purchasing threshold?", "expected": "Procurement lead and finance, both in writing.", "doc": "supplier-onboarding", "groups": ["procurement"], "answerable": true}

Cada respuesta recibe entonces una de cuatro etiquetas:

  • correct: correcta, completa y cita el documento adecuado;
  • partly: correcta pero incompleta, o correcta con una cita errónea o ausente;
  • wrong: afirma algo falso, o responde a una pregunta que los documentos no pueden responder;
  • declined: dice que no puede responder.

Declined es una etiqueta aparte a propósito. En una pregunta con respuesta es un fallo, pero un fallo más barato que una respuesta incorrecta, y el negocio decide cuántos de cada tipo puede aceptar. Acuerde ese umbral antes de la primera ejecución, igual que se acuerda una métrica antes de entrenar un modelo.

Un experto del dominio califica a mano las primeras 50 respuestas. Después, un segundo modelo puede calificar el resto frente a las respuestas esperadas, siempre que sus etiquetas coincidan con las del experto en esas 50. Ambas prácticas son comunes: en la encuesta de LangChain a 1.340 profesionales que construyen agentes, realizada en noviembre y diciembre de 2025, el 59,8% usaba revisión humana, el 53,3% usaba un modelo como juez y el 52,4% ejecutaba evaluaciones offline sobre conjuntos de test.

Paso 2: indexar pasajes con sus permisos

Divida los documentos según sus encabezados en pasajes de unos cientos de palabras. Guarde con cada pasaje el id del documento, el título, el enlace y los grupos de acceso. Los permisos pertenecen al pasaje desde el principio; añadirlos después significa reindexarlo todo.

Paso 3: recuperación híbrida con reciprocal rank fusion

La búsqueda por palabras clave (BM25) encuentra términos exactos como códigos de producto, números de formulario y siglas. La búsqueda por embeddings encuentra paráfrasis: «días libres» cuando la política dice «vacaciones anuales». Cada una pierde lo que la otra encuentra, así que use ambas y combine los rankings.

Reciprocal rank fusion (RRF) combina por posición y no por puntuación, así que nunca hace falta calibrar las dos escalas de puntuación: cada pasaje recibe 1 / (k + rank) de cada lista, sumado. El artículo de 2009 que lo presentó fijó k en 60 y encontró que ese valor era casi óptimo pero no crítico.

import numpy as np
from rank_bm25 import BM25Okapi

def tokenize(text):
    return text.lower().split()

class HybridIndex:
    def __init__(self, chunks, embed):
        # chunks: dicts with "text", "doc", "groups" (a set); embed: list[str] -> unit vectors
        self.chunks, self.embed = chunks, embed
        self.bm25 = BM25Okapi([tokenize(c["text"]) for c in chunks])
        self.vectors = np.asarray(embed([c["text"] for c in chunks]))

    def search(self, query, user_groups, k=5, depth=50, rrf_k=60):
        allowed = [i for i, c in enumerate(self.chunks) if c["groups"] & user_groups]
        lexical = self.bm25.get_scores(tokenize(query))
        semantic = self.vectors @ np.asarray(self.embed([query]))[0]
        fused = {}
        for scores in (lexical, semantic):
            ranked = sorted(allowed, key=lambda i: scores[i], reverse=True)[:depth]
            for rank, i in enumerate(ranked, start=1):
                fused[i] = fused.get(i, 0.0) + 1.0 / (rrf_k + rank)
        top = sorted(fused, key=fused.get, reverse=True)[:k]
        return [{**self.chunks[i], "similarity": float(semantic[i])} for i in top]

embed es cualquier función de embeddings, alojada u open source; cambiarla modifica un argumento. El filtro de permisos se ejecuta antes del ranking, así que un pasaje que el usuario no puede abrir nunca llega al modelo.

Paso 4: responder con citas, o rechazar

Dos comprobaciones evitan que el sistema se invente cosas. Antes de llamar al modelo: si el mejor pasaje no es suficientemente similar a la pregunta, se rechaza sin generar nada. Después: cada respuesta debe citar pasajes, y cada número citado debe existir. Cualquier otra cosa cuenta como rechazo.

import re

PROMPT = """Answer the question using only the numbered passages below.
After each sentence, cite the passages you used, for example [2].
If the passages do not contain the answer, reply with exactly: NOT_FOUND

{passages}

Question: {question}"""

def answer(question, index, user_groups, llm, min_similarity=0.35):
    hits = index.search(question, user_groups)
    if not hits or max(h["similarity"] for h in hits) < min_similarity:
        return {"status": "declined", "text": None, "docs": []}
    passages = "\n\n".join(f"[{n}] ({h['doc']}) {h['text']}" for n, h in enumerate(hits, 1))
    text = llm(PROMPT.format(passages=passages, question=question)).strip()
    cited = {int(n) for n in re.findall(r"\[(\d+)\]", text)}
    if text == "NOT_FOUND" or not cited or not cited <= set(range(1, len(hits) + 1)):
        return {"status": "declined", "text": None, "docs": []}
    return {"status": "answered", "text": text, "docs": [hits[n - 1]["doc"] for n in sorted(cited)]}

llm es cualquier función que recibe un prompt y devuelve texto, así que el modelo se puede sustituir sin tocar este código. El umbral de 0,35 es un valor provisional: las escalas de similitud difieren entre modelos de embeddings. Fíjelo con su conjunto de test de modo que las preguntas sin respuesta se rechacen sin perder demasiadas de las que sí la tienen.

Paso 5: el bucle de puntuación

import json
from collections import Counter

def run_eval(path, index, llm, judge):
    items = [json.loads(line) for line in open(path)]
    labels, found = Counter(), 0
    for item in items:
        groups = set(item["groups"])
        if item["answerable"]:
            hits = index.search(item["question"], groups)
            found += item["doc"] in {h["doc"] for h in hits}
        result = answer(item["question"], index, groups, llm)
        if not item["answerable"]:
            label = "correct" if result["status"] == "declined" else "wrong"
        elif result["status"] == "declined":
            label = "declined"
        else:  # judge returns "correct", "partly" or "wrong"
            label = judge(item["question"], item["expected"], result["text"])
        labels[label] += 1
    answerable = sum(1 for item in items if item["answerable"])
    print({k: round(v / len(items), 3) for k, v in labels.items()})
    print("expected document in top 5:", round(found / answerable, 3))

El segundo número importa tanto como el primero. Si el documento correcto no estaba entre los cinco primeros, el modelo nunca tuvo oportunidad, y el arreglo está en la recuperación o en los documentos, no en el modelo. Guarde las etiquetas de cada ejecución con la fecha y el cambio realizado, para que cada cambio se compare con la misma línea base.

Semana 1 y semana 2

Semana 1: la línea base.

  • Días 1–2: recoger y redactar las preguntas con los responsables de los documentos; acordar el umbral de aceptación.
  • Día 3: indexar los documentos; ejecutar solo la búsqueda por palabras clave, después la híbrida, y registrar la tasa de acierto en el top-5 de ambas.
  • Días 4–5: primera ejecución completa; el experto califica 50 respuestas; contrastar el juez con ellas; calificar el resto.

El resultado de la semana 1 es una puntuación y una lista de fallos clasificados en cuatro causas: el documento falta o está desactualizado; la recuperación no encontró el pasaje correcto; el modelo tenía el pasaje correcto y aun así respondió mal; el sistema debería haber rechazado y no lo hizo.

Semana 2: arreglar por orden de tamaño.

  • Empiece por la causa más grande. Los documentos que faltan o están desactualizados son una tarea de contenido para sus responsables. Los fallos de recuperación suelen mejorar con un mejor chunking, añadiendo a cada pasaje el título y los encabezados del documento, o con sinónimos para la jerga interna. Las malas respuestas a partir de buenos pasajes mejoran con cambios en el prompt y, a veces, con otro modelo.
  • Vuelva a ejecutar el conjunto completo tras cada cambio. Conserve un cambio solo si la puntuación se mueve más que el margen de error.
  • Ajuste el umbral de rechazo al final, sobre la configuración de recuperación definitiva.

Al final de la semana 2 la decisión suele estar clara. Si la tasa de respuestas incorrectas está por debajo del umbral acordado, construya la versión de producción y mantenga el conjunto de test como su control de release. Si los fallos restantes son de formato o tono mientras los hechos son correctos, ese es el caso en el que vale la pena probar el fine-tuning, y el conjunto de test ya existe para mostrar si ayudó. Si la mayoría de los fallos son documentos que faltan, el problema es la base de conocimiento, y ninguna elección de modelo lo arreglará.

Este es el orden que seguimos al construir copilotos RAG: primero preguntas reales, después la recuperación, después una puntuación que cada release tiene que superar.

Fuentes

¿Tiene un proceso que cuesta demasiado?

Cuéntenoslo. La primera llamada es gratuita y, si la IA no es la respuesta, se lo decimos.

Hablar de su tarea