Почему функции вознаграждения — это самое слабое звено в кастомизации AI
Вы настраиваете Amazon Nova под свою задачу. SFT (Supervised Fine-Tuning) прошло успешно — модель освоила ваш стиль ответов. Пришло время Reinforcement Fine-Tuning (RFT) — той самой финальной настройки, где Nova учится не просто повторять, а выбирать лучшие ответы. И здесь все упёрлось в одну деталь: как вы определяете, что такое «лучший»?
Функция вознаграждения (reward function) — это математическая совесть вашей модели. Она шепчет Nova: «Этот ответ хорош, получи +1 балл. А этот — полная чушь, -3». Классический RLHF (Reinforcement Learning from Human Feedback) предполагает, что эту функцию в голове держит человек-аннотатор. Но в 2026 году это непозволительная роскошь для большинства проектов. Медленно, дорого, субъективно.
Главная ловушка: если ваша функция вознаграждения плохо определена, модель оптимизируется не под задачу, а под формулу. Вы получите идеально оптимизированную чушь. Например, если награждать только за длину ответа, Nova научится генерировать бесконечные бессмысленные тексты.
Serverless как выход: зачем переносить логику вознаграждения в AWS Lambda
RLAIF (Reinforcement Learning from AI Feedback) и RLVR (Reinforcement Learning with Video/Verbal Rewards) стали стандартом де-факто для кастомизации больших моделей в 2025-2026 годах. Идея проста: вместо человека «судит» другая AI-система. А её логику нужно где-то запускать.
Три причины выбрать Lambda:
- Масштабирование до нуля. Когда процесс RFT в SageMaker или Bedrock не активен, вы не платите за оценку ответов.
- Сложная логика без сложной инфраструктуры. Ваша функция может вызывать другие модели (например, детерминированную модель для проверки фактов), работать с базами данных, анализировать тон текста — всё в одном месте.
- Скорость разработки. От идеи до работающей функции — 15 минут. Это меняет итерационный цикл настройки модели.
1 Создаём скелет Lambda-функции на Python
Забудьте про сложные Docker-образы для начала. Bedrock ожидает от функции определённый формат ввода и вывода. Вот минимальный шаблон, который работает с Amazon Nova 2 (последняя стабильная версия на апрель 2026).
import json
import boto3
from typing import Dict, Any, List
# Инициализируем клиента для Bedrock, если нужно вызывать другие модели внутри функции
bedrock_runtime = boto3.client('bedrock-runtime', region_name='us-east-1')
def lambda_handler(event: Dict[str, Any], context) -> Dict[str, Any]:
"""
Основной обработчик Lambda.
Ожидает event в формате от Amazon Bedrock RFT job.
Структура event (упрощённо):
{
"model_input": "Вопрос пользователя",
"candidate_responses": ["Ответ модели 1", "Ответ модели 2", ...],
"custom_parameters": {"domain": "medical", "strict_facts": True}
}
"""
try:
# 1. Извлекаем данные из события
prompt = event.get('model_input', '')
candidates = event.get('candidate_responses', [])
params = event.get('custom_parameters', {})
# 2. Проверяем входные данные
if not prompt or not candidates:
return {
'statusCode': 400,
'rewards': [],
'error': 'Invalid input: missing prompt or candidates'
}
# 3. Вычисляем вознаграждение для каждого кандидата
# Это ядро вашей логики!
rewards = []
for candidate in candidates:
score = compute_reward(prompt, candidate, params)
rewards.append(float(score)) # Bedrock ждёт список float
# 4. Возвращаем результат в формате, ожидаемом Bedrock
return {
'statusCode': 200,
'rewards': rewards,
'evaluation_metadata': {
'scoring_model': 'custom_lambda_v1',
'params_used': params
}
}
except Exception as e:
# В случае ошибки возвращаем нулевые вознаграждения, чтобы не сломать job
print(f"Critical error in reward function: {e}")
return {
'statusCode': 500,
'rewards': [0.0] * len(candidates),
'error': str(e)
}
def compute_reward(prompt: str, response: str, params: Dict) -> float:
"""
Здесь живёт ваша бизнес-логика.
Возвращает числовой скор от -10.0 до +10.0.
"""
base_score = 0.0
# Пример 1: Награда за отсутствие отказов
if is_refusal(response):
base_score -= 5.0
# Пример 2: Награда за структурированность (наличие маркеров списка)
if contains_structured_elements(response):
base_score += 2.0
# Пример 3: Штраф за излишнюю многословность
word_count = len(response.split())
if word_count > 300:
base_score -= (word_count - 300) * 0.01
return max(-10.0, min(10.0, base_score)) # Ограничиваем диапазон
# --- Вспомогательные функции (заглушки) ---
def is_refusal(text: str) -> bool:
"""Определяет, является ли ответ отказом."""
refusal_phrases = ["не могу", "я не", "извините", "как ai", "этического" ]
lower_text = text.lower()
return any(phrase in lower_text for phrase in refusal_phrases)
def contains_structured_elements(text: str) -> bool:
"""Проверяет наличие маркеров списка или структуры."""
markers = ["1.", "2.", "•", "-", "*", "во-первых", "во-вторых"]
return any(marker in text for marker in markers)
Этот код — отправная точка. Он обрабатывает несколько candidate_responses (так работает Nova 2 в режиме RFT — генерирует несколько вариантов для одного промпта), применяет простые эвристики и возвращает scores. Самое главное — обработка ошибок. Если ваша функция упадёт, RFT job прервётся. Поэтому мы ловим все исключения и возвращаем нулевые оценки в случае беды.
2 Подключаем сложную логику: вызов других моделей внутри Lambda
Сила подхода в том, что compute_reward может быть сколь угодно сложной. Например, вы хотите проверять фактические утверждения ответа Nova против вашей базы знаний. Или оценивать тон — должен ли он быть формальным для финансовых отчётов.
def compute_reward_advanced(prompt: str, response: str, params: Dict) -> float:
"""Продвинутая функция с вызовом других моделей Bedrock."""
scores = []
# 1. Проверка на токсичность с помощью Nova-Lite (меньшая модель)
toxicity_score = check_toxicity(response)
scores.append(toxicity_score * 3.0) # Большой вес безопасности
# 2. Проверка фактов через RAG-систему (если в params есть контекст)
if params.get('fact_checking'):
fact_score = fact_check_via_rag(prompt, response, params['fact_context'])
scores.append(fact_score * 4.0) # Самый высокий вес для factual accuracy
# 3. Оценка соответствия инструкции (Instruction Following)
instruction_score = evaluate_instruction_following(prompt, response)
scores.append(instruction_score * 2.0)
# 4. Длина ответа (предпочтение золотой середине)
length_penalty = evaluate_length(response, target_length=150)
scores.append(length_penalty)
# Итоговый скор — взвешенная сумма
final_score = sum(scores) / len(scores) if scores else 0.0
return final_score
def check_toxicity(text: str) -> float:
"""Используем компактную модель для классификации токсичности."""
# В реальности здесь будет invoke_model к Nova-Lite или специальной модели модерации
# Это пример запроса к API Bedrock 2026 года
try:
response = bedrock_runtime.invoke_model(
modelId='amazon.nova-lite-text-v2:0',
contentType='application/json',
accept='application/json',
body=json.dumps({
'prompt': f"Classify toxicity (0-10) of text: {text}",
'max_tokens': 10,
'temperature': 0.1
})
)
result = json.loads(response['body'].read())
# Предполагаем, что модель возвращает число от 0 до 10
toxicity_value = float(result.get('completion', '5'))
# Преобразуем в награду: 0 токсичности = +5, 10 токсичности = -5
return 5.0 - toxicity_value
except Exception as e:
print(f"Toxicity check failed: {e}")
return 0.0 # Нейтральный скор при ошибке
Важный нюанс 2026 года: вызовы invoke_model внутри Lambda теперь не требуют увеличения timeout до 15 минут, если вы используете Nova-Lite или аналогичные быстрые модели. Для Nova 2 используйте асинхронный вызов, чтобы не упираться в лимит выполнения Lambda в 15 минут.
3 Интеграция с Bedrock RFT: настраиваем конвейер обучения
Код функции готов. Теперь нужно подключить её к реальному процессу обучения в Bedrock. С ноября 2025 года это делается через консоль или CDK/CloudFormation.
Шаги интеграции:
- Создайте Lambda-функцию с кодом выше. Обязательно назначьте роль с правами на вызов bedrock-runtime (если используете внутренние вызовы моделей).
- В консоли Bedrock перейдите в "Custom Models" → "Create Training Job". Выберите тип "Reinforcement Fine-Tuning (RFT)".
- На шаге "Reward configuration" выберите "AWS Lambda function" и укажите ARN вашей функции.
- Настройте гиперпараметры обучения. Критически важный параметр —
reward_scale. Начните с 0.1, чтобы не сломать политику модели резкими скачками градиента.
# Пример запуска через AWS CLI (актуально для версии CLI 2026)
aws bedrock create-model-customization-job \
--job-name nova2-custom-reward-01 \
--custom-model-name my-financial-nova \
--role-arn arn:aws:iam::123456789012:role/BedrockCustomizationRole \
--base-model-id amazon.nova2-v1:0 \
--customization-type RFT \
--training-data-config '{"s3Uri": "s3://my-bucket/rft-data.jsonl"}' \
--reward-config '{"lambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:nova-reward-v1"}' \
--hyperparameters '{"rewardScale": 0.1, "klCoef": 0.05, "entropyCoef": 0.001}'
Процесс обучения займет от нескольких часов до суток в зависимости от объема данных. Стоимость — около $15-20 за час использования инстансов P4de в 2026 году. Полный гайд по кастомизации Nova описывает, как комбинировать SFT и RFT для устойчивых результатов.
Ошибки, которые сломают вашу модель: перекос вознаграждения и как его избежать
Я видел десятки сломанных кастомизаций из-за плохих reward functions. Вот типичные сценарии:
| Ошибка | Симптом | Решение |
|---|---|---|
| Положительная обратная связь | Модель генерирует одно слово или символ бесконечно, скор растёт | Добавить penalty за повторение n-грамм. Ввести decay награды за длину. |
| Несбалансированные веса | Модель идеально следует инструкциям, но начинает врать | Использовать методику New Relic: факт-чекинг с весом в 3 раза выше, чем за стиль. |
| Локальный максимум | Качество перестаёт расти после 1000 шагов | Ввести стохастичность в функцию (небольшой случайный шум ±0.1). Или использовать curriculum learning — менять веса критериев в процессе обучения. |
Правило отладки: всегда запускайте RFT сначала на маленьком датасете (100 примеров) с увеличенной частотой валидации. Смотрите не только на средний reward, но и на распределение. Если 95% ответов получают скор 9.8-10.0 — ваша функция слишком щедрая. Идеальное распределение — нормальное с центром около 5.0.
Самый опасный антипаттерн — создание функции, которую модель может «обмануть» без реального улучшения. Пример: награждать за наличие ключевых фраз. Nova быстро научится вставлять «Исходя из вышеизложенного, можно заключить, что...» в каждый ответ, не неся смысловой нагрузки.
Дальнейшие шаги: от простых эвристик к сложным AI-судиям
Ваша первая Lambda-функция с набором if-else работает. Но настоящая магия начинается, когда вы превращаете её в многослойную систему оценки.
- Мультимодальные вознаграждения. Nova Act и другие агенты работают с изображениями. Ваша функция может анализировать скриншоты интерфейса, которые генерирует модель, используя Nova Multimodal Embeddings для оценки соответствия.
- Динамическая загрузка правил. Храните конфигурацию функции в S3 или DynamoDB. Меняйте веса критериев без деплоя нового кода.
- А/B тестирование reward functions. Запустите две разные Lambda-функции на параллельных RFT jobs. Сравните, какая даст лучшую модель по человеческой оценке.
В 2026 году тренд — автоматическая оптимизация самих функций вознаграждения с помощью мета-обучения. Но это уже тема для отдельной статьи.
Последний совет: никогда не делайте функцию, которая возвращает только 0 или 1 (бинарная классификация «хорошо/плохо»). Nova нужен градиент, плавное изменение. Ваши оценки должны быть непрерывными. Иначе обучение превратится в лотерею.
Теперь у вас есть рабочий инструмент. Осталось самое сложное — решить, что для вашей задачи действительно значит «хороший ответ». Машина ждёт ваших указаний.