

# Implementación de funciones de recompensas
<a name="nova-implementing-reward-functions"></a>

## Descripción general
<a name="nova-reward-overview"></a>

La función de recompensas (también llamada puntuador o calificador) es el componente principal que evalúa las respuestas del modelo y proporciona señales de comentarios para el entrenamiento. Debe implementarse como una función de Lambda que acepte las respuestas del modelo y devuelva las puntuaciones de recompensas.

## Formato de la interfaz
<a name="nova-reward-interface"></a>

La función de recompensas debe aceptar y devolver datos en el formato siguiente:

**Ejemplo de entrada para el entrenamiento**

```
{  
    "messages": [  
        {  
            "role": "user",  
            "content": "Do you have a dedicated security team?"  
        }  
    ],              
   "reference_answer": {  
       "compliant": "No",  
       "explanation": "As an AI developed by Company, I do not have a traditional security team..."  
    }  
}
```

**Ejemplo de carga útil para la función de Lambda de recompensas**

El contenedor transforma automáticamente los datos antes de enviarlos a la función de Lambda de la siguiente manera:

1. Genera una respuesta del modelo para cada petición.

1. Anexa el turno del asistente (respuesta generada) a la matriz de mensajes.

1. Agrega un campo `id` único para el seguimiento.

La función de Lambda recibirá datos en este formato transformado:

```
{    
   "id": "123",  
    "messages": [  
        {  
            "role": "user",  
            "content": "Do you have a dedicated security team?"  
        },  
        {  
            "role": "assistant",  
            "content": "As an AI developed by Amazon, I don not have a dedicated security team..."  
        }  
    ],              
    # Following section will be same as your training dataset sample  
    "reference_answer": {  
        "compliant": "No",  
        "explanation": "As an AI developed by Company, I do not have a traditional security team..."  
    }  
}
```

**Contrato de Lambda de recompensas**

```
def lambda_handler(event, context):  
   return lambda_grader(event)  
  
def lambda_grader(samples: list[dict]) -> list[dict]:  
    """  
    Args:  
        samples: List of dictionaries in OpenAI format  
          
        Example input:  
        {     
            "id": "123",  
            "messages": [  
                {  
                    "role": "user",  
                    "content": "Do you have a dedicated security team?"  
                },  
                {  
                    "role": "assistant",  
                    "content": "As an AI developed by Company, I don nott have a dedicated security team..."  
                }  
            ],              
            # This section will be same as your training dataset  
            "reference_answer": {  
                "compliant": "No",  
                "explanation": "As an AI developed by Company, I do not have a traditional security team..."  
            }  
        }  
      
    Returns:  
        List of dictionaries with reward scores:  
        {  
            "id": str,                              # Same id as input sample  
            "aggregate_reward_score": float,        # Overall score for the sample  
            "metrics_list": [                       # OPTIONAL: Component scores  
                {  
                    "name": str,                    # Name of the component score  
                    "value": float,                 # Value of the component score  
                    "type": str                     # "Reward" or "Metric"  
                }  
            ]  
        }  
    """
```

## Campos de entrada y salida
<a name="nova-reward-fields"></a>

### Campos de entrada
<a name="nova-reward-input-fields"></a>


| Campo | Descripción | Notas adicionales | 
| --- | --- | --- | 
| id | Identificador único para la muestra | Se vuelve a reflejar en la salida. Formato de la cadena | 
| Mensajes | Historial de chat ordenado en formato OpenAI | Matriz de objetos de mensaje | 
| messages[].role | Hablante del mensaje | Valores comunes: “usuario”, “asistente”, “sistema” | 
| messages[].content | Contenido del texto del mensaje | Cadena sin formato | 
| \*\*metadatos | Información en formato libre para facilitar la calificación | Objeto; campos opcionales transferidos de los datos de entrenamiento | 

### Campos de salida
<a name="nova-reward-output-fields"></a>


| Campo | Descripción | Notas adicionales | 
| --- | --- | --- | 
| id | El mismo identificador que en la muestra de entrada | Debe coincidir con la entrada | 
| aggregate\_reward\_score | Puntuación general de la muestra | Flotante (por ejemplo, entre 0,0 y 1,0 o un rango definido por la tarea) | 
| metrics\_list | Puntuaciones de los componentes que forman el agregado | Matriz de objetos métricos | 

## Restricciones técnicas
<a name="nova-reward-constraints"></a>
+ **Límite de tiempo de espera**: dispone de 15 minutos de tiempo máximo de ejecución por invocación a Lambda.
+ **Simultaneidad**: debe gestionar solicitudes simultáneas de `rollout_worker_replicas * 64`.
+ **Fiabilidad**: debe implementar una gestión de errores adecuada y devolver puntuaciones válidas de manera coherente.
+ **Rendimiento**: optimice para una ejecución rápida (segundos, no minutos) que permita un entrenamiento eficiente.

**Prácticas recomendadas**
+ Minimice las llamadas a la API externa.
+ Utilice algoritmos y estructuras de datos eficientes.
+ Implemente una lógica de reintento para los errores transitorios.
+ Almacene en la memoria caché los cálculos reutilizables.
+ Lleve a cabo pruebas exhaustivas antes del entrenamiento para garantizar una ejecución sin errores.

## Uso de funciones de recompensas personalizadas
<a name="nova-reward-using-custom"></a>

Implemente funciones de recompensas personalizadas cuando tenga criterios de evaluación específicos para cada tarea:
+ **Defina los criterios de evaluación**: determine qué es lo que constituye una buena respuesta para su tarea.
+ **Implemente la función de Lambda**: cree una función de Lambda según el formato de interfaz.
+ **Pruebe localmente**: valide que la función devuelva las puntuaciones correctas para las entradas de muestra.
+ **Implemente en AWS**: implemente la función de Lambda y anote el ARN.
+ **Configure la fórmula**: agregue el ARN de Lambda al campo de `reward_lambda_arn` de la fórmula
+ **Lleve a cabo pruebas con un conjunto de datos pequeño**: ejecute el RFT con un mínimo de datos para verificar la integración.

## Permisos de IAM
<a name="nova-reward-iam"></a>

### Permisos necesarios
<a name="nova-reward-required-permissions"></a>

El rol de ejecución de SageMaker debe tener permisos para invocar la función de Lambda. Agregue esta política al rol de ejecución de SageMaker:

```
{  
  "Version": "2012-10-17",		 	 	   
  "Statement": [  
    {  
      "Effect": "Allow",  
      "Action": [  
        "lambda:InvokeFunction"  
      ],  
      "Resource": "arn:aws:lambda:region:account-id:function:function-name"  
    }  
  ]  
}
```

### Rol de ejecución de Lambda
<a name="nova-reward-lambda-role"></a>

El rol de ejecución de una función de Lambda necesita permisos de ejecución de Lambda básicos:

```
{  
  "Version": "2012-10-17",		 	 	   
  "Statement": [  
    {  
      "Effect": "Allow",  
      "Action": [  
        "logs:CreateLogGroup",  
        "logs:CreateLogStream",  
        "logs:PutLogEvents"  
      ],  
      "Resource": "arn:aws:logs:*:*:*"  
    }  
  ]  
}
```

Permisos adicionales: si la función de Lambda accede a otros servicios de AWS (por ejemplo, S3 para los datos de referencia o DynamoDB para el registro), agregue esos permisos al rol de ejecución de Lambda.

## Ejemplo: función de recompensas de LLM como juez
<a name="nova-reward-llm-judge-example"></a>

En este ejemplo, se muestra el uso de los modelos de Amazon Bedrock como jueces para evaluar las respuestas de los modelos al compararlas con las respuestas de referencia. Esta plantilla de Lambda proporciona un marco para que los clientes implementen llamadas a Amazon Bedrock para solicitudes de inferencia a fin de procesar las evaluaciones de los jueces. La función de Lambda mantiene el mismo contrato de entrada/salida que otras funciones de recompensas.

### Implementación
<a name="nova-reward-llm-judge-implementation"></a>

Esta función de Lambda implementa un proceso de evaluación en dos etapas: `lambda_handler` extrae las respuestas del modelo y las respuestas de referencia de las muestras entrantes y, a continuación, la función `lambda_graded` llama a Amazon Bedrock para puntuar la similitud semántica entre ellas. La implementación incluye una sólida gestión de errores con reintentos automáticos para los errores transitorios y admite formatos de respuesta de referencia flexibles (tanto en formato de cadena como en formato de diccionario estructurado).

**Detalles de la implementación:**
+ **Lógica de reintento**: implementa un retroceso exponencial (1, 2 o 4 segundos) para limitar las excepciones a fin de gestionar los límites de frecuencia de la API de Bedrock.
+ **Gestión de errores**: devuelve una puntuación de 0,0 para las evaluaciones fallidas, en lugar de generar excepciones.
+ **Puntuación determinista**: utiliza una temperatura igual a 0,0 para garantizar la coherencia de las puntuaciones en todas las evaluaciones.
+ **Formato de referencia flexible**: gestiona automáticamente las respuestas de cadena y de referencia del diccionario.
+ **Acotación de puntuaciones**: garantiza que todas las puntuaciones estén dentro de un rango válido [0,0, 1,0].
+ **Independencia del modelo**: cambie JUDGE\_MODEL\_ID para usar cualquier modelo de Amazon Bedrock (Nova, Llama, Mistral, etc.).

```
"""  
LLM Judge Lambda POC - Working implementation using Amazon Bedrock  
"""  
  
import json  
import time  
import boto3  
  
bedrock_runtime = boto3.client('bedrock-runtime', region_name='us-east-1')  
JUDGE_MODEL_ID = "anthropic.claude-3-5-sonnet-20240620-v1:0"  
SYSTEM_PROMPT = "You must output ONLY a number between 0.0 and 1.0. No explanations, no text, just the number."  
  
JUDGE_PROMPT_TEMPLATE = """Compare the following two responses and rate how similar they are on a scale of 0.0 to 1.0, where:  
- 1.0 means the responses are semantically equivalent (same meaning, even if worded differently)  
- 0.5 means the responses are partially similar  
- 0.0 means the responses are completely different or contradictory  
  
Response A: {response_a}  
  
Response B: {response_b}  
  
Output ONLY a number between 0.0 and 1.0. No explanations."""  
  
  
def lambda_graded(response_a: str, response_b: str, max_retries: int = 3) -> float:  
    """Call Bedrock to compare responses and return similarity score."""  
    prompt = JUDGE_PROMPT_TEMPLATE.format(response_a=response_a, response_b=response_b)  
      
    for attempt in range(max_retries):  
        try:  
            response = bedrock_runtime.converse(  
                modelId=JUDGE_MODEL_ID,  
                messages=[{"role": "user", "content": [{"text": prompt}]}],  
                system=[{"text": SYSTEM_PROMPT}],  
                inferenceConfig={"temperature": 0.0, "maxTokens": 10}  
            )  
            print(f"Bedrock call successful: {response}")  
            output = response['output']['message']['content'][0]['text'].strip()  
            score = float(output)  
            print(f"Score parsed: {score}")  
            return max(0.0, min(1.0, score))  
                  
        except Exception as e:  
            if "ThrottlingException" in str(e) and attempt < max_retries - 1:  
                time.sleep(2 ** attempt)  
            else:  
                print(f"Bedrock call failed: {e}")  
                return None  
    return None  
  
  
def lambda_handler(event, context):  
    """AWS Lambda handler - processes samples from RFTEvalInvoker."""  
    try:  
        samples = event if isinstance(event, list) else [event]  
        results = []  
          
        for sample in samples:  
            sample_id = sample.get("id", "unknown")  
            messages = sample.get("messages", [])  
              
            # Extract assistant response (response A)  
            response_a = ""  
            for msg in messages:  
                if msg.get("role") in ["assistant", "nova_assistant"]:  
                    response_a = msg.get("content", "")  
                    break  
              
            # Extract reference answer from root level (no longer in metadata)  
            reference_answer = sample.get("reference_answer", "")  
              
            # Handle both string and dict reference_answer formats  
            if isinstance(reference_answer, dict):  
                # If reference_answer is a dict, extract the explanation or compliant field  
                response_b = reference_answer.get("explanation", reference_answer.get("compliant", ""))  
            else:  
                response_b = reference_answer  
              
            if not response_a or not response_b:  
                results.append({  
                    "id": sample_id,  
                    "aggregate_reward_score": 0.0,  
                    "metrics_list": [{"name": "similarity_score", "value": 0.0, "type": "Metric"}]  
                })  
                continue  
              
            # Get similarity score  
            score = lambda_graded(response_a, response_b)  
              
            results.append({  
                "id": sample_id,  
                "aggregate_reward_score": score,  
                "metrics_list": [  
                    {  
                        "name": "similarity_score",  
                        "value": score,  
                        "type": "Metric"  
                    }  
                ]  
            })  
          
        return {"statusCode": 200, "body": json.dumps(results)}  
          
    except Exception as e:  
        print(f"Error: {e}")  
        return {"statusCode": 500, "body": json.dumps({"error": str(e)})}
```

### Formato de entrada
<a name="nova-reward-llm-judge-input"></a>

La función de Lambda recibe el mismo formato de entrada que otras funciones de recompensas:

```
{  
    "id": "sample-001",  
    "messages": [  
        {  
            "role": "user",  
            "content": "Do you have a dedicated security team?"  
        },  
        {  
            "role": "assistant",  
            "content": "As an AI developed by Amazon, I don't have a dedicated security team..."  
        }  
    ],  
    "reference_answer": {  
        "compliant": "No",  
        "explanation": "As an AI developed by Company, I do not have a traditional security team..."  
    },  
    "my_custom_field": "custom_value"  
}
```

### Formato de salida
<a name="nova-reward-llm-judge-output"></a>

```
{  
    "id": "sample-001",  
    "aggregate_reward_score": 0.85,  
    "metrics_list": [  
        {  
            "name": "similarity_score",  
            "value": 0.85,  
            "type": "Metric"  
        }  
    ]  
}
```

### Consideraciones sobre la implementación
<a name="nova-reward-llm-judge-deployment"></a>

Es posible que también tenga que ajustar la plantilla de la petición y los parámetros de inferencia en función de las capacidades del modelo elegido y el formato de la API.
+ **Permisos de IAM**: el rol de ejecución de Lambda debe tener el permiso `bedrock:InvokeModel` para el modelo elegido.
+ **Tiempo de espera**: establezca el tiempo de espera de Lambda en al menos 60 segundos para adaptarse a la latencia y los reintentos de la API de Bedrock.
+ **Región**: implemente en una región en la que esté disponible el modelo de Bedrock que haya elegido.
+ **Costo**: supervise el uso de la API de Bedrock, ya que cada evaluación hace una llamada a la API por muestra.
+ **Rendimiento**: para las evaluaciones a gran escala, solicite un aumento de las cuotas de Bedrock para evitar la limitación.

**Aumento del rendimiento de Bedrock**

Si experimenta limitaciones durante la evaluación, aumente las cuotas del modelo de Bedrock:
+ Vaya a la consola de AWS Service Quotas.
+ Busque “Bedrock” y seleccione la región.
+ Busque la cuota para el modelo que haya elegido (por ejemplo, “Invocaciones por minuto para Claude 3.5 Sonnet”).
+ Haga clic en “Solicitar aumento de cuota” y especifique el rendimiento deseado.
+ Justifique el aumento (por ejemplo, “carga de trabajo de evaluación del RFT”).

La lógica de reintentos integrada de Lambda permite controlar las limitaciones ocasionales, pero las evaluaciones sostenidas de gran volumen requieren un aumento adecuado de las cuotas.

**Política de IAM obligatoria:**

```
{  
    "Version": "2012-10-17",		 	 	   
    "Statement": [  
        {  
            "Effect": "Allow",  
            "Action": [  
                "bedrock:InvokeModel"  
            ],  
            "Resource": "arn:aws:bedrock:*::foundation-model/*"  
        }  
    ]  
}
```