Saltar al contenido
giron.ai
← Proyectos

Agentes RAG en producción

Un agente sólo es útil cuando consulta sistemas reales sin inventarse las respuestas.

Cuándo
en curso
Con
embeddings, retrieval, tool use, clientes reales

Agentes de IA con retrieval-augmented generation funcionando con clientes de verdad: un chatbot con retrieval para un estudio de fotografía, y Juanito, el asistente conversacional de El Empleado Juan — clasifica conversaciones, consulta datos de asistencia y nómina, y opera contra la plataforma.

Lo que los tutoriales no cuentan

Un demo de RAG se construye en una tarde. Lo difícil empieza después, cuando hay alguien del otro lado esperando un dato correcto:

  • El retrieval falla en silencio. Si la búsqueda no devuelve nada útil, el modelo no se calla: responde igual, en un tono perfectamente seguro y sin la información. Es el peor modo de fallo posible, porque no parece un fallo.
  • Trocear el documento es una decisión de producto, no de infraestructura. El tamaño del fragmento decide qué preguntas se pueden contestar.
  • Un agente que consulta datos reales necesita un contrato, no un prompt. Herramientas tipadas, resultados validados, y un camino explícito para «no lo sé» que sea más barato que inventar.
  • Sin evaluación no hay mejora, sólo una sensación. Un conjunto de preguntas con respuesta conocida vale más que cualquier ajuste de temperatura.

La regla que aplico: el agente puede equivocarse de tono, nunca de dato.

El contrato del «no lo sé»

El fallo caro no es equivocarse: es responder con seguridad cuando el retrieval no trajo nada. Aquí eso no puede pasar en silencio, porque «no lo sé» es un valor de retorno y no una virtud que se le pide al modelo.

// Dos formas de terminar, y ambas son válidas. Si la única forma de
// éxito es «dato», el modelo aprende que callarse es fracasar — y deja
// de callarse.
const Respuesta = z.discriminatedUnion('tipo', [
  z.object({ tipo: z.literal('dato'), valor: z.string(), fuente: z.string().min(1) }),
  z.object({ tipo: z.literal('sin_fundamento'), falta: z.string() }),
]);

async function responder(pregunta: string): Promise<Respuesta> {
  const trozos = await recuperar(pregunta, { k: 8, minScore: 0.35 });

  // Sin contexto suficiente no se llama al modelo. Llamarlo aquí es
  // pedirle explícitamente que invente, y lo hará en un tono
  // perfectamente seguro.
  if (trozos.length === 0) {
    return { tipo: 'sin_fundamento', falta: pregunta };
  }

  const salida = await modelo.completar({ pregunta, trozos, schema: Respuesta });

  // La cita se comprueba contra lo recuperado, no contra lo que el
  // modelo dice haber citado. Es la diferencia entre tener fuentes y
  // parecer que las tienes.
  return trozos.some((t) => t.id === salida.fuente)
    ? salida
    : { tipo: 'sin_fundamento', falta: 'la cita no corresponde a lo recuperado' };
}

El minScore es el parámetro que más discusión ahorra: por debajo de él no hay respuesta que valga, y eso se decide una vez con un conjunto de preguntas de respuesta conocida en vez de discutirlo caso por caso.

Escríbeme

Si estás pensando en hacer algo con IA y quieres hablarlo con alguien que ya lo puso en producción, escríbeme. Contesto yo.

hola@giron.ai