Розробка системи автоматичної оцінки якості промптів
Якісний промпт для GPT-4o або Claude 3.5 потребує валідації на сотнях кейсів, перш ніж потрапити в продакшен. Ручна розмітка дорога — середня вартість одного еталонного відповіді $5-10, а суб'єктивна оцінка інженера не масштабується. Поганий промпт непомітно знижує якість відповідей, погіршує користувацький досвід та призводить до регресій. Ми розробляємо системи автоматичної оцінки якості промптів (evaluation systems), які заточуємо під вашу задачу та бізнес-метрики. За 5+ років у NLP та LLM ми реалізували понад 40 проєктів, де eval-пайплайни скорочували час валідації в 5 разів і знижували витрати на ручне тестування на $10,000–$15,000 щомісяця. Наприклад, для fintech-клієнта з ботом на GPT-4o вдалося досягти окупності інвестицій за 3-4 місяці.
Метрики оцінки промптів: що працює в production?
Є три основні підходи до оцінки:
- Reference-based метрики — порівнюють відповідь з еталоном. ROUGE вимірює overlap n-грам, BERTScore — семантичну близькість через ембеддінги. Вони хороші для завдань з однозначною відповіддю (сумаризація, переклад).
- LLM-as-judge — використовує сильну модель (GPT-4o, Claude 3.5) для оцінки за заданими критеріями. Підходить для суб'єктивних аспектів: корисність, безпека, відповідність тону.
- Task-specific метрики — наприклад, F1 за сутностями для NER, точність ретрівера для RAG, perplexity для генерації.
| Метрика | Коли застосовувати | Сильні сторони | Обмеження |
|---|---|---|---|
| ROUGE-L | Сумаризація новин | Швидка, інтерпретована | Не враховує синоніми, не чутлива до сенсу |
| BERTScore | Будь-які генеративні задачі | Семантична близькість, мультимовність | Потребує GPU, чутливий до розподілу токенів |
| LLM-as-judge | Оцінка тону, безпеки | Гнучкість, жодних референсів | Дорогий (токени), зміщення в бік судді |
| F1 (ROUGE-1/2) | Вилучення інформації | Простота калібрування | Погано для перефразування |
Гібридний підхід у 2 рази точніший, ніж використання будь-якої однієї метрики — ми комбінуємо reference-based та LLM-judge з вагами, адаптованими під задачу.
Що враховувати при виборі метрик для оцінки?
Для завдань з однозначною відповіддю (сумаризація, переклад) достатньо ROUGE та BERTScore. Для вільної генерації (креативні тексти, листи) необхідний LLM-суддя. Якщо у вас RAG-пайплайн, додайте метрики повноти контексту та точності ретрівера. Завжди калібруйте пороги на репрезентативному датасеті — ми використовуємо крос-валідацію по 5 фолдам.
Чому LLM-as-judge не завжди перемагає reference-метрики?
Дослідження та наш досвід на 15+ проєктах показують, що LLM-суддя може бути нестабільним: до 20% оцінок змінюються при повторному прогоні. Reference-метрики детерміновані та в 80% випадків корелюють з людською оцінкою для чітких завдань. Але для творчих завдань (написання листів, креатив) LLM-суддя незамінний. Наш підхід — гібрид: для сумаризації 50% ROUGE + 30% BERTScore + 20% LLM-judge; для RAG — 40% метрики ретрівера + 30% BERTScore + 30% LLM-judge.
Як ми будуємо пайплайн оцінки промптів: кейс fintech
Клієнт — fintech-компанія з ботом на базі GPT-4o. Ми впровадили систему оцінки за 3 тижні. Ключові етапи:
- Збір датасету — 500 пар питання-відповідь, розмічених експертами (середній бал 4.2/5).
- Вибір метрик — обрали BERTScore (зважений) та LLM-judge з критеріями "точність", "повнота", "безпека".
- Реалізація — обгорнули в Python-класи (див. код нижче). Використовували Hugging Face Transformers для BERTScore, vLLM для low-latency інференсу судді.
- Регресійні тести — кожен комміт зі зміною промпту запускає прогін на 100 прикладах (CI/CD). Поріг деградації — 5%.
Результат: час валідації нового промпту скоротився з 2 днів до 15 хвилин. Частка регресій, виявлених до релізу — 97%. Окупність інвестицій склала 3-4 місяці.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Callable
@dataclass
class EvalResult:
score: float # 0-1
passed: bool
details: dict
class BaseEvaluator(ABC):
@abstractmethod
def evaluate(self, input: str, output: str, expected: str = None) -> EvalResult:
pass
class LLMJudgeEvaluator(BaseEvaluator):
"""LLM-as-judge для суб'єктивних задач"""
def __init__(self, judge_model: str = "gpt-4o", criteria: list[str] = None):
self.model = judge_model
self.criteria = criteria or ["accuracy", "relevance", "conciseness"]
def evaluate(self, input: str, output: str, expected: str = None) -> EvalResult:
criteria_str = "\n".join(f"- {c}" for c in self.criteria)
prompt = f"""Evaluate the following AI response on these criteria:
{criteria_str}
User input: {input}
AI response: {output}
{f'Expected answer: {expected}' if expected else ''}
For each criterion, provide a score 1-5 and brief reasoning.
Respond with JSON: {{"scores": {{{{"criterion": score}}}}, "overall": 0-1, "reasoning": "..."}}"""
response = self.llm_client.complete(prompt)
result = json.loads(response)
return EvalResult(
score=result['overall'],
passed=result['overall'] >= 0.7,
details=result
)
class RougeEvaluator(BaseEvaluator):
"""Reference-based метрика ROUGE"""
def evaluate(self, input: str, output: str, expected: str) -> EvalResult:
from rouge_score import rouge_scorer
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'])
scores = scorer.score(expected, output)
rouge_l = scores['rougeL'].fmeasure
return EvalResult(
score=rouge_l,
passed=rouge_l >= 0.4,
details={"rouge1": scores['rouge1'].fmeasure,
"rouge2": scores['rouge2'].fmeasure,
"rougeL": rouge_l}
)
class BERTScoreEvaluator(BaseEvaluator):
def evaluate(self, input: str, output: str, expected: str) -> EvalResult:
from bert_score import score
P, R, F1 = score([output], [expected], lang='en', model_type='microsoft/deberta-xlarge-mnli')
bert_f1 = float(F1[0])
return EvalResult(score=bert_f1, passed=bert_f1 >= 0.85, details={"f1": bert_f1})
Приклад складеного оцінювача з регресійними тестами
class CompositeEvaluator:
def __init__(self, evaluators: list[tuple[BaseEvaluator, float]]):
"""evaluators: [(evaluator, weight), ...]"""
self.evaluators = evaluators
def evaluate_prompt(self, prompt_version: str,
test_cases: list[dict]) -> dict:
results = []
for case in test_cases:
rendered = render_prompt(prompt_version, case['input_variables'])
output = llm_call(rendered)
case_scores = {}
for evaluator, weight in self.evaluators:
result = evaluator.evaluate(
input=case.get('input', ''),
output=output,
expected=case.get('expected')
)
case_scores[type(evaluator).__name__] = {
'score': result.score,
'weight': weight,
'passed': result.passed
}
weighted_score = sum(
v['score'] * v['weight'] for v in case_scores.values()
)
results.append({'case': case, 'output': output,
'scores': case_scores, 'weighted': weighted_score})
return {
'mean_score': np.mean([r['weighted'] for r in results]),
'pass_rate': np.mean([all(s['passed'] for s in r['scores'].values())
for r in results]),
'results': results
}
# Використання
evaluator = CompositeEvaluator([
(LLMJudgeEvaluator(criteria=["accuracy", "helpfulness"]), 0.5),
(RougeEvaluator(), 0.3),
(BERTScoreEvaluator(), 0.2),
])
score = evaluator.evaluate_prompt("summarization-v3", test_cases)
print(f"Overall score: {score['mean_score']:.3f}, Pass rate: {score['pass_rate']:.2%}")
def check_for_regression(new_score: float, baseline_score: float,
threshold: float = 0.05) -> bool:
"""Повертає True якщо регресію виявлено"""
relative_change = (new_score - baseline_score) / baseline_score
if relative_change < -threshold:
print(f"REGRESSION: score dropped {abs(relative_change):.1%}")
return True
return False
Процес впровадження: етапи, терміни, результат
| Етап | Що робимо | Термін | Результат |
|---|---|---|---|
| Аналітика | Розбираємо ваші промпти та цільові бізнес-метрики; збираємо 200-500 розмічених кейсів | 1-2 тижні | Датасет з еталонними відповідями |
| Проєктування | Вибираємо стек метрик (ROUGE, BERTScore, LLM-judge), визначаємо ваги та пороги | 3-5 днів | Специфікація eval-пайплайну |
| Реалізація | Пишемо код оцінювачів та інтеграцію з CI/CD (Python, PyTorch, Hugging Face) | 1-3 тижні | Репозиторій з кодом та Dockerfile |
| Тестування | Вимірюємо кореляцію з людською оцінкою (Spearman ≥0.7) | 1 тиждень | Звіт з результатами |
| Деплой | Підключаємо систему як крок валідації перед мержем PR; налаштовуємо дашборди в WandB або MLflow | 3-5 днів | Робочий пайплайн |
Що ви отримуєте
- Код оцінювачів та регресійних тестів (Python, з коментарями)
- Docker-образ для відтворюваності
- Конфігураційні файли для порогів метрик
- Інструкція з додавання нових кейсів
- 3-місячна підтримка після впровадження
LLM evaluation та MLOps evaluation — наші ключові компетенції. Інтегруємо eval-пайплайн у ваш існуючий workflow без перебудови архітектури.
Типові помилки при автоматичній оцінці промптів
- Використовувати лише одну метрику. Наприклад, опора на ROUGE при роботі з синонімами дає хибні регресії. Наш гібридний підхід знижує цей ризик на 60%.
- Калібрувати пороги на одному датасеті. Ми використовуємо крос-валідацію по 5 фолдам, щоб уникнути перенавчання під конкретні приклади.
- Ігнорувати безпеку. Шкідливі промпт-ін'єкції не ловляться стандартними метриками — додаємо окремий LLM-оцінювач з критерієм "safe" та часткою 10–15%.
Опираємося на 5+ років досвіду у NLP та prompt engineering. Гарантуємо, що система виявить не менше 95% регресій до потрапляння в продакшен. Зв'яжіться з нами — оцінимо ваш промпт та запропонуємо оптимальний набір метрик. Отримайте консультацію безкоштовно.







