La parte 1 puso nombre a cuatro ejes de varianza del LLM como juez. Lo más sencillo que puede hacer una herramienta para combatirla es restringir lo que el juez puede decir, y en eso consiste la estrategia de restricción del alcance. En esta parte recorro cómo la aplica Ragas en dos de sus métricas.
Estoy construyendo AgentAnvil, un framework de pruebas guiado por contratos para agentes basados en LLM, y leo el código de otros evaluadores para averiguar qué forma deberían tener los contratos de AgentAnvil. El pipeline de fidelidad (faithfulness) de Ragas es de los ejemplos más claros de este patrón que he visto. Un año más tarde, sus autores aplicaron el mismo esquema a los agentes.
La intuición
Con un LLM como juez, la varianza contra la que peleas es mayor en el centro de la escala. Si le pides a un juez una puntuación de 1 a 5, el ruido se concentra en la frontera entre el 3 y el 4. Ahí es donde discrepan dos jueces entre sí y donde un mismo juez se contradice. Los extremos son más fáciles, porque un 1 y un 5 suelen ser casos claramente malos o claramente buenos, que no se deciden a cara o cruz.
Si eliminas el centro, el ruido se queda sin sitio.
La idea es sustituir una rúbrica continua por un veredicto binario, y así «¿Es esta respuesta fiel al contexto?» pasa a ser «¿Está respaldada la afirmación K, sí o no?». El juez ya no recorre una escala y se limita a caer a un lado u otro de una línea. La varianza no desaparece de la frontera, pero ahora esa frontera es una decisión de implicación (entailment) y ha dejado de ser una nota de calidad.
Al agregar los bits vuelves a tener un número de aspecto continuo, la fracción de afirmaciones respaldadas. Sale de átomos binarios, sin ningún juicio continuo de por medio. Cada veredicto que cambia mueve la puntuación 0,1 si hay diez afirmaciones y 0,05 si hay veinte. En la medida en que los errores de los veredictos sean independientes, el ruido del juez se promedia y no se acumula. Ragas juzga todas las afirmaciones juntas en una sola llamada y vuelve a descomponer la respuesta en cada ejecución, así que en la práctica la dispersión se reduce más despacio de lo que muestra la demo de abajo.
El pipeline de fidelidad de Ragas gira en torno a esta idea.
La fidelidad en Ragas, paso a paso
En Ragas, la fidelidad se calcula con dos llamadas al LLM y un paso aritmético. La primera llamada descompone, la segunda juzga y la puntuación es una fracción.
Los fragmentos que siguen salen de los módulos originales ragas.metrics._faithfulness y _goal_accuracy. Desde la v0.4.2, Ragas recomienda importar estas métricas desde ragas.metrics.collections, que conserva los mismos prompts (salvo la frase sobre el formato JSON), los tipos de veredicto y el cálculo de la puntuación.
La descomposición está en StatementGeneratorPrompt.
class StatementGeneratorPrompt( PydanticPrompt[StatementGeneratorInput, StatementGeneratorOutput]): instruction = ( "Given a question and an answer, analyze the complexity of each " "sentence in the answer. Break down each sentence into one or more " "fully understandable statements. Ensure that no pronouns are used " "in any statement. Format the outputs in JSON." )La instrucción de no usar pronombres es la pieza que hace funcionar el resto del pipeline. Cada afirmación tiene que entenderse por sí sola, porque la siguiente llamada la juzga sin tener delante el resto de la respuesta. El propio prompt de descomposición de Ragas trae un ejemplo few-shot en el que una respuesta de dos frases sobre Einstein se descompone en cuatro afirmaciones independientes que pueden juzgarse por separado. Por los pronombres se cuela la dependencia, y una buena descomposición le cierra el paso.
El veredicto lo emite NLIStatementPrompt.
class NLIStatementPrompt(PydanticPrompt[NLIStatementInput, NLIStatementOutput]): instruction = ( "Your task is to judge the faithfulness of a series of statements " "based on a given context. For each statement you must return " "verdict as 1 if the statement can be directly inferred based on " "the context or 0 if the statement can not be directly inferred " "based on the context." )Una sola llamada juzga todas las afirmaciones a la vez y devuelve un veredicto por cada una. La instrucción acota explícitamente la salida a 1 o 0, sin término medio.
Después viene la puntuación.
def _compute_score(self, answers: NLIStatementOutput): # check the verdicts and compute the score faithful_statements = sum( 1 if answer.verdict else 0 for answer in answers.statements ) num_statements = len(answers.statements) if num_statements: score = faithful_statements / num_statements else: logger.warning("No statements were generated from the answer.") score = np.nan
return scoreEs la fracción de afirmaciones respaldadas, una salida de aspecto continuo obtenida al promediar juicios binarios.
Quedan tres detalles de implementación.
En primer lugar, lo que hace en realidad el juez cuando puntúa la «fidelidad» es inferencia en lenguaje natural, una tarea muy estudiada. Ragas reduce a dos las tres etiquetas habituales de esa tarea (implicación, neutral y contradicción) y solo distingue si hay implicación o no la hay. El planteamiento lo toma prestado de una tarea que ya existía.
En segundo lugar, todas las afirmaciones cuentan lo mismo, sin ponderar por importancia, sin penalizar la longitud y sin tener en cuenta la confianza. Esa uniformidad hace que acotar la varianza salga barato, y también que la puntuación se pueda manipular. Basta con rellenar una respuesta con tres afirmaciones triviales que el contexto respalda, colocadas delante de la que de verdad importa, para que la fidelidad suba. La puntuación no sabe en qué átomo está el significado.
Por último, el contrato de tipos es verdict: int (cualquier entero). El prompt es el que acota el valor a 0 o 1, y _compute_score recurre a la semántica truthy. La restricción está, por tanto, en la capa del prompt; la métrica de agentes la endurece en la capa de tipos.
El mismo patrón en AgentGoalAccuracy
Más o menos un año después de su artículo de 2023, Ragas llevó el mismo patrón a los agentes.
AgentGoalAccuracy hace dos llamadas al LLM sobre la traza de una conversación de varios turnos, y el veredicto es directamente la métrica. La primera deduce qué buscaba la ejecución y cómo terminó.
class WorkflowOutput(BaseModel): user_goal: str = Field(...) end_state: str = Field(...)La segunda compara las dos cosas y responde a una pregunta binaria.
class CompareOutcomeOutput(BaseModel): reason: str = Field(...) verdict: t.Literal["0", "1"] = Field(...)Con Literal["0", "1"], el alcance queda fijado en el tipo, además de en el prompt. Pydantic no admite nada que no sea "0" o "1", así que, si un LLM intenta devolver "1.0", "yes" o un objeto JSON, el parser lo rechaza. El paso de reintento de Ragas puede pedirle a un LLM que arregle el formato, pero a la puntuación nunca llega otra cosa que "0" o "1".
Es una vuelta de tuerca respecto a la fidelidad, donde verdict: int acepta cualquier entero. La métrica tiene dos variantes. WithReference compara con un objetivo que se le pasa y WithoutReference, con el que acaba de deducir el propio modelo. El mecanismo (deducir y luego hacer una sola pregunta binaria) es idéntico, y se transfiere porque también se transfiere el juicio de fondo. En la mayoría de los planteamientos razonables, «¿Ha conseguido la ejecución el objetivo del usuario?» se responde con un sí o un no.
Un detalle para quien vaya a meter esto en la CI: la traza nunca llega en bruto al juez. Antes de que la vea el primer prompt, la métrica llama a sample.pretty_repr() para aplanar la conversación de varios turnos en una sola cadena, y el segundo prompt solo ve lo que ha deducido el primero (más la referencia, cuando se pasa). Por eso dos pipelines con formateadores distintos podrían dar veredictos distintos a partir de la misma traza. El formateador forma parte de la métrica, aunque nunca aparezca en su nombre.
Lo que pierdes
Los veredictos binarios no salen gratis.
Un veredicto binario no puede decirte que una respuesta es correcta en parte y otra es excelente, ya que las dos acaban en «respaldada» o «no respaldada». Para una puerta de pasa o no pasa, esa granularidad es la adecuada, pero a la hora de orientar al desarrollador («¿cómo lo mejoro?») los veredictos binarios se quedan cortos. Una puntuación de 0,6 no dice cuáles eran las tres afirmaciones respaldadas de las cinco, ni si las dos sin respaldo eran pequeñas licencias o alucinaciones en toda regla. La forma del fallo se pierde.
Para ser justos con el diseño, que la forma sea binaria no quiere decir que Ragas se olvidara de la granularidad. Sus autores eligieron lo binario porque el caso de uso con el que creció la herramienta, la evaluación de la salida de sistemas RAG, tiene justo esa forma. En RAG, la fidelidad es implicación: o la afirmación está en el contexto recuperado o no está. El cumplimiento del objetivo del agente, la métrica que añadieron más tarde para agentes, también tiene forma de implicación. En los dos casos, la forma de la pregunta encaja con la del veredicto.
«¿Es bueno lo binario?» es la pregunta equivocada. La que hay que hacerse es «¿Tiene el juicio forma binaria?».
Cuándo encaja este enfoque
Los veredictos binarios son el enfoque adecuado cuando se cumplen tres condiciones.
La decisión es pasa o no pasa, sin grados intermedios. En el fondo, una puerta de CI es un pasa o no pasa, aunque la puntuación sea un float de 0 a 1. En cambio, una métrica de cara al usuario que tenga que distinguir entre «bueno», «muy bueno» y «excelente» no lo es.
Los juicios intermedios se descomponen en comprobaciones independientes de sí o no. La fidelidad se presta a ello de forma natural: dado el contexto, cada afirmación es independiente de las demás. Un juicio cuyas partes solo tienen sentido juntas no sobrevivirá al pipeline.
La varianza de la zona media sale más cara que perder información. Una puerta de CI inestable que falla 1 de cada 20 ejecuciones por los vaivenes del juez es peor que una puerta que no da ningún detalle de diagnóstico. De los dos costes, quédate con el que te salga más barato.
En cuanto a los umbrales, elige a la vez el punto de corte y el N habitual. Un umbral de fidelidad de 0,8 con tres afirmaciones equivale a «tres de tres»: basta una afirmación sin respaldo para que la puerta falle, de modo que ya es binaria en la propia métrica. Con veinte, equivale a «dieciséis de veinte», una puerta más cercana a lo continuo. Mientras no decidas cuántos átomos esperas, el umbral es un número sin significado operativo.
Cuando se cumplen las tres condiciones, los veredictos binarios son la herramienta más limpia que tienes.