Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Оценка LLM
evaluation

Оценка LLM

Метрики качества: Perplexity, BLEU, ROUGE, human evaluation, automated benchmarks

Оценка LLM: От метрик до Production Evaluation

Метрики качества, RAGAS, DeepEval, TruLens, human evaluation, benchmarks, production monitoring

#Введение

Оценка качества LLM — критически важная задача для выбора модели, сравнения подходов и контроля качества в production. Не существует единой метрики — нужна комбинация подходов.

Эволюция оценки LLM:

2020-2022: Perplexity, BLEU, ROUGE
    ↓
2023: Benchmarks (MMLU, GSM8K, HumanEval)
    ↓
2024: LLM-as-a-Judge, RAGAS, TruLens
    ↓
2025: Production evaluation, continuous eval

Типы оценки:

ТипИнструментыКогда использоватьСложность
Автоматические метрикиPerplexity, BLEU, ROUGEБыстрая проверкаНизкая
Benchmark тестыMMLU, GSM8K, HumanEvalСравнение моделейСредняя
RAG evaluationRAGAS, TruLensОценка RAG-системСредняя
LLM-as-a-JudgeGPT-4, ClaudeМасштабная оценкаНизкая
Human evaluationExpert reviewersФинальная валидацияВысокая
Production evalA/B тесты, метрикиContinuous monitoringВысокая

#1. Perplexity — Базовая метрика языковых моделей

Perplexity (PPL) — измеряет, насколько модель "удивлена" тестовыми данными.

#Формула

Perplexity = exp(-1/N * Σ log P(token_i))

где:
- N — количество токенов
- P(token_i) — вероятность, присвоенная моделью правильному токену

#Интерпретация

PerplexityКачество моделиПримеры
5-10ОтличноGPT-4, Claude 3
10-20ХорошоLlama-3-70B, Mistral Large
20-50СреднеLlama-3-8B, Mistral 7B
50-100ПлохоSmall models (<3B)
100+Очень плохоPoorly trained

Важно: Lower perplexity = better model

#Вычисление с HuggingFace

from transformers import AutoTokenizer, AutoModelForCausalLM import torch from tqdm import tqdm def calculate_perplexity(model, tokenizer, text: str, max_length: int = 512) -> float: """Вычисление perplexity для текста""" # Токенизация с ограничением длины encodings = tokenizer( text, return_tensors="pt", truncation=True, max_length=max_length ) input_ids = encodings.input_ids # Вычисление loss with torch.no_grad(): outputs = model(input_ids, labels=input_ids) loss = outputs.loss # Perplexity = exp(loss) perplexity = torch.exp(loss) return perplexity.item() # Пример использования model_name = "meta-llama/Llama-2-7b-hf" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto") text = "Python is a programming language that lets you work quickly and integrate systems effectively" ppl = calculate_perplexity(model, tokenizer, text) print(f"Perplexity: {ppl:.2f}")

#Perplexity на датасете

from datasets import load_dataset import numpy as np def evaluate_perplexity_on_dataset( model, tokenizer, dataset_name: str = "wikitext", dataset_config: str = "wikitext-2-raw-v1", max_samples: int = 100, max_length: int = 512 ) -> dict: """Оценка perplexity на датасете""" dataset = load_dataset(dataset_name, dataset_config, split="test") perplexities = [] failed_samples = 0 for i, sample in enumerate(tqdm(dataset.select(range(min(max_samples, len(dataset))))): text = sample["text"] # Skip пустых и слишком длинных текстов if not text or len(text.split()) < 10: continue try: ppl = calculate_perplexity(model, tokenizer, text, max_length) perplexities.append(ppl) except Exception as e: failed_samples += 1 print(f"Error on sample {i}: {e}") if not perplexities: return {"error": "No valid samples"} perplexities_array = np.array(perplexities) results = { "mean_perplexity": float(np.mean(perplexities_array)), "median_perplexity": float(np.median(perplexities_array)), "std_perplexity": float(np.std(perplexities_array)), "min_perplexity": float(np.min(perplexities_array)), "max_perplexity": float(np.max(perplexities_array)), "p95_perplexity": float(np.percentile(perplexities_array, 95)), "p99_perplexity": float(np.percentile(perplexities_array, 99)), "total_samples": len(perplexities), "failed_samples": failed_samples } print(f"\n=== Perplexity Results ===") print(f"Mean: {results['mean_perplexity']:.2f}") print(f"Median: {results['median_perplexity']:.2f}") print(f"P95: {results['p95_perplexity']:.2f}") print(f"Samples: {results['total_samples']}, Failed: {failed_samples}") return results # Использование results = evaluate_perplexity_on_dataset(model, tokenizer, "wikitext", max_samples=50)

#Ограничения Perplexity

  • ❌ Не измеряет factual accuracy — модель может иметь низкий PPL но генерировать факты
  • ❌ Не измеряет coherence — текст может быть грамматически правильным но бессмысленным
  • ❌ Зависит от токенизатора — разные токенизаторы дают разные PPL
  • ❌ Слабая корреляция с downstream tasks — низкий PPL ≠ лучшая performance

Вывод: Perplexity хороша для быстрой проверки, но недостаточна для production evaluation.


#2. Современные инструменты оценки

#RAGAS — Evaluation для RAG-систем

RAGAS (Retrieval Augmented Generation Assessment) — фреймворк для оценки RAG-систем.

pip install ragas datasets

#Ключевые метрики RAGAS

МетрикаОписаниеДиапазонКогда использовать
FaithfulnessНасколько ответ основан на контексте0-1RAG-системы
Answer RelevancyНасколько ответ релевантен вопросу0-1Все Q&A системы
Context PrecisionКачество retrieval (rank relevant docs higher)0-1RAG-системы
Context RecallПолнота retrieval (нашли ли все нужное)0-1RAG-системы
Context Entity RecallПолнота извлечения сущностей0-1RAG с entity extraction
Answer CorrectnessТочность ответа относительно ground truth0-1Когда есть эталон
Answer SimilarityСемантическая близость к эталону0-1Когда есть эталон

#Пример оценки с RAGAS

from ragas import evaluate from ragas.metrics import ( faithfulness, answer_relevancy, context_precision, context_recall, context_entity_recall ) from datasets import Dataset import pandas as pd # Подготовка evaluation dataset eval_data = { "question": [ "Как аутентифицироваться в API?", "Какие есть лимиты на запросы?", "Как получить API key?", "Что такое rate limiting?", "Как использовать OAuth 2.0?" ], "answer": [ "Для аутентификации используйте Bearer токен в заголовке Authorization: 'Authorization: Bearer <token>'", "Лимит: 100 запросов в минуту для free tier, 1000 для pro tier, unlimited для enterprise", "API key можно получить в настройках аккаунта на сайте в разделе 'API Access'", "Rate limiting ограничивает количество запросов для защиты от злоупотреблений", "OAuth 2.0 требует redirect URI, client_id, client_secret и authorization code" ], "contexts": [ ["Аутентификация: Authorization: Bearer <token>. API использует JWT токены."], ["Rate limiting: Free tier — 100 req/min, Pro — 1000 req/min, Enterprise — unlimited"], ["Получение ключа: Settings → API → Generate New Key. Ключ отображается один раз."], ["Rate limiting защищает API от DDoS и злоупотреблений. При превышении — HTTP 429"], ["OAuth 2.0 flow: 1) Redirect to auth, 2) User approves, 3) Get code, 4) Exchange for token"] ], "ground_truth": [ "Используйте Bearer токен в заголовке Authorization", "100 запросов в минуту для free, 1000 для pro", "В настройках аккаунта на сайте", "Ограничение количества запросов для защиты", "Требуется redirect URI, client_id, client_secret и authorization code" ] } # Создание Dataset для RAGAS ragas_dataset = Dataset.from_dict(eval_data) # Evaluation results = evaluate( dataset=ragas_dataset, metrics=[ faithfulness, answer_relevancy, context_precision, context_recall, context_entity_recall ] ) # Анализ результатов df = results.to_pandas() print(df) # Quality gates — пороги для production quality_gates = { "faithfulness": 0.80, "answer_relevancy": 0.75, "context_precision": 0.70, "context_recall": 0.75 } print("\n=== Quality Gate Check ===") all_passed = True for metric, threshold in quality_gates.items(): mean_score = df[metric].mean() passed = mean_score >= threshold status = "✅ PASS" if passed else "❌ FAIL" print(f"{metric}: {mean_score:.3f} (threshold: {threshold}) — {status}") if not passed: all_passed = False if all_passed: print("\n✅ All quality gates passed!") else: print("\n❌ Some quality gates failed! Review and improve.")

#RAGAS в CI/CD

# scripts/evaluate_ragas.py import sys import json from ragas import evaluate from ragas.metrics import faithfulness, answer_relevancy, context_precision def run_evaluation(dataset_path: str, output_path: str, thresholds_path: str): """Запуск evaluation и проверка quality gates""" # Загрузка dataset dataset = Dataset.from_json(dataset_path) # Evaluation results = evaluate( dataset=dataset, metrics=[faithfulness, answer_relevancy, context_precision] ) df = results.to_pandas() # Quality gates thresholds = { "faithfulness": 0.80, "answer_relevancy": 0.75, "context_precision": 0.70 } all_passed = True report = {"metrics": {}, "passed": True, "details": []} for metric, threshold in thresholds.items(): mean_score = df[metric].mean() passed = mean_score >= threshold report["metrics"][metric] = { "score": float(mean_score), "threshold": threshold, "passed": passed } if not passed: all_passed = False report["passed"] = False # Сохранение отчёта with open(output_path, "w") as f: json.dump(report, f, indent=2) if not all_passed: print("❌ Quality gates failed!") sys.exit(1) print("✅ All quality gates passed!") sys.exit(0) if __name__ == "__main__": run_evaluation( dataset_path="eval_data.json", output_path="eval_report.json", thresholds_path="thresholds.json" )

#DeepEval — pytest для LLM

DeepEval — фреймворк для тестирования LLM в стиле pytest.

pip install deepeval

#Базовые тесты

# test_llm.py from deepeval import assert_test from deepeval.metrics import ( AnswerRelevancyMetric, FaithfulnessMetric, ContextualRelevancyMetric, SummarizationMetric, HallucinationMetric, ToxicityMetric ) from deepeval.test_case import LLMTestCase, LLMTestCaseParams def test_code_assistant_basic(): """Базовый тест code assistant""" metric_relevancy = AnswerRelevancyMetric(threshold=0.7) metric_faithfulness = FaithfulnessMetric(threshold=0.8) test_case = LLMTestCase( input="Как реализовать rate limiting в FastAPI?", expected_output="Используйте slowapi middleware с Redis backend", actual_output="Для rate limiting в FastAPI установите slowapi: pip install slowapi. Создайте Limiter с Redis URL и добавьте middleware в приложение.", retrieval_context=[ "SlowAPI — rate limiting middleware для FastAPI", "Redis используется для распределённого rate limiting" ] ) assert_test(test_case, [metric_relevancy, metric_faithfulness]) def test_code_assistant_advanced(): """Расширенный тест с multiple metrics""" test_case = LLMTestCase( input="Объясни разницу между asyncio и threading", actual_output=""" asyncio использует event loop для конкурентного выполнения в одном потоке, эффективно для I/O-bound задач. Threading использует OS потоки, но ограничен GIL в Python, поэтому подходит только для I/O-bound операций. """, retrieval_context=[ "asyncio: single-threaded concurrency with event loop", "threading: OS threads, limited by GIL for CPU tasks", "GIL prevents true parallel execution of Python bytecode" ] ) assert_test(test_case, [ AnswerRelevancyMetric(threshold=0.7), FaithfulnessMetric(threshold=0.8), HallucinationMetric(threshold=0.3), ToxicityMetric(threshold=0.1) ]) def test_summarization(): """Тест суммаризации""" test_case = LLMTestCase( input="Суммаризируй статью о transformers", actual_output="Transformers — архитектура нейросетей на основе attention механизмов, ставшая стандартом для NLP задач.", expected_output="Transformers используют self-attention для обработки последовательностей, заменив RNN и CNN во многих задачах." ) assert_test(test_case, [ SummarizationMetric(threshold=0.7) ]) # Запуск: pytest test_llm.py -v

#Кастомные метрики

from deepeval.metrics import BaseMetric from deepeval.test_case import LLMTestCase class CodeCorrectnessMetric(BaseMetric): """Кастомная метрика для проверки корректности кода""" def __init__(self, threshold: float = 0.7): self.threshold = threshold def measure(self, test_case: LLMTestCase): # Проверка что код компилируется try: compile(test_case.actual_output, '<string>', 'exec') self.success = True self.score = 1.0 except SyntaxError: self.success = False self.score = 0.0 return self.score def is_successful(self): return self.score >= self.threshold @property def __name__(self): return "CodeCorrectness" # Использование def test_code_generation(): test_case = LLMTestCase( input="Напиши функцию для сортировки списка", actual_output="def sort_list(lst): return sorted(lst)" ) assert_test(test_case, [CodeCorrectnessMetric(threshold=1.0)])

#Конфигурация для CI/CD

# deepeval_config.yaml evaluation: model: api_key: env model: "gpt-4" metrics: answer_relevancy: threshold: 0.7 faithfulness: threshold: 0.8 hallucination: threshold: 0.3 reporting: output_dir: "./eval_reports" format: ["json", "html"]

#TruLens — Сквозная оценка LLM приложений

TruLens — оценка всего pipeline, а не только ответов.

pip install trulens-eval
from trulens_eval import ( Feedback, TruLlama, OpenAI as TruOpenAI ) from trulens_eval.feedback import GroundTruthAgreement, GroundTruthRelevance from trulens_eval.feedback.provider.openai import OpenAI import numpy as np # Инициализация провайдера provider = TruOpenAI() # Определение feedback функций qa_relevance = Feedback( provider.relevance_with_cot_reasons, name="Relevance" ).on_input_output() qs_relevance = Feedback( provider.relevance_with_cot_reasons, name="Ground Truth Relevance" ).on_input_output() toxicity = Feedback( provider.toxicity, name="Toxicity" ).on_output() # Создание TruLlama записи from trulens_eval import TruLlama tru_recorder = TruLlama( app, # Ваше LLM приложение app_id="my_llm_app", feedbacks=[qa_relevance, qs_relevance, toxicity] ) # Запись и оценка with tru_recorder as recording: response = app.query("What is Python?") # Dashboard # Запустите: trulens eval

#3. Benchmark тесты

#MMLU (Massive Multitask Language Understanding)

57 задач по различным предметам: математика, физика, история, медицина, право.

# Пример вопроса MMLU question = """ Question: What is the capital of France? A) London B) Berlin C) Paris D) Madrid Answer: """ # Правильный ответ: C

Типичные результаты (2024):

МодельMMLU Score
GPT-4o88.7%
Claude 3.5 Sonnet88.3%
Gemini 1.5 Pro85.9%
Llama-3-70B79.5%
Llama-3-8B68.4%
Mistral 7B60.1%

#GSM8K (Grade School Math 8K)

Математические задачи уровня начальной школы.

question = """ Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May? Answer: """ # Правильный ответ: 72 # Требуется chain-of-thought reasoning

Типичные результаты:

МодельGSM8K Score
GPT-4o96.8%
Claude 3.5 Sonnet95.0%
Gemini 1.5 Pro91.7%
Llama-3-70B84.2%
Llama-3-8B71.3%

#HumanEval (Code Generation)

164 задачи по программированию на Python.

from human_eval.evaluation import evaluate_functional_correctness # Пример задачи problem = { "task_id": "HumanEval/0", "prompt": "def plus_one(digits: List[int]) -> List[int]:\n ", "canonical_solution": " result = []\n carry = 1\n for i in range(len(digits)-1, -1, -1):...", "test": "assert plus_one([1,2,3]) == [1,2,4]" } # Оценка: pass@k (вероятность что хотя бы одно из k решений работает)

Типичные результаты:

МодельHumanEval (pass@1)
GPT-4o90.2%
Claude 3.5 Sonnet92.0%
Gemini 1.5 Pro87.3%
Llama-3-70B81.0%
Llama-3-8B60.1%
DeepSeek Coder78.5%

#TruthfulQA

Оценка на генерацию фактов — выявление hallucinations.

question = """ Question: What happens to you if you eat watermelon seeds? Model answer: The watermelon seeds will grow inside your stomach. Reference: Nothing happens. The seeds pass through your digestive system unchanged. """ # Оценка: factuality score

#4. Human Evaluation

Автоматические метрики не заменяют человеческую оценку.

#Критерии оценки

КритерийОписаниеScaleWeight
CorrectnessФактическая точность1-530%
RelevanceСоответствие вопросу1-520%
CoherenceЛогичность и связность1-515%
CompletenessПолнота ответа1-520%
HelpfulnessОбщая полезность1-515%

#Шаблон для human evaluation

evaluation_template = """ Оцени ответ модели по критериям: Вопрос: {question} Ответ модели: {answer} Эталонный ответ: {reference} Оценки (1-5): - Correctness (фактическая точность): _ - Relevance (соответствие вопросу): _ - Coherence (логичность): _ - Completeness (полнота): _ - Helpfulness (полезность): _ Комментарии: """ def calculate_weighted_score(evaluation: dict) -> float: """Расчёт взвешенного общего балла""" weights = { "correctness": 0.30, "relevance": 0.20, "coherence": 0.15, "completeness": 0.20, "helpfulness": 0.15 } total = sum( evaluation[criterion] * weight for criterion, weight in weights.items() ) return total / sum(weights.values())

#Pairwise comparison (A/B тестирование)

pairwise_template = """ Сравни два ответа: Вопрос: {question} Ответ A: {answer_a} Ответ B: {answer_b} Какой ответ лучше? □ A значительно лучше □ A немного лучше □ Одинаково □ B немного лучше □ B значительно лучше Комментарии: """ def analyze_pairwise(results: list) -> dict: """Анализ pairwise comparison""" scores = { "A значительно лучше": 2, "A немного лучше": 1, "Одинаково": 0, "B немного лучше": -1, "B значительно лучше": -2 } total_score = sum(scores[r] for r in results) avg_score = total_score / len(results) winner = "A" if avg_score > 0.5 else "B" if avg_score < -0.5 else "Tie" return { "winner": winner, "avg_score": avg_score, "total_comparisons": len(results) }

#5. LLM-as-a-Judge

Использование сильной модели (GPT-4, Claude) для оценки других моделей.

from openai import OpenAI import json from typing import List, Dict client = OpenAI() def llm_judge( question: str, answer: str, reference: str, criteria: List[str] = None ) -> dict: """Оценка ответа модели через GPT-4""" if criteria is None: criteria = ["Correctness", "Relevance", "Coherence", "Completeness"] criteria_str = "\n".join([f"- {c}:" for c in criteria]) prompt = f""" Ты — эксперт по оценке качества ответов LLM. Вопрос: {question} Ответ модели: {answer} Эталонный ответ: {reference} Оцени ответ по критериям (1-5): {criteria_str} Верни ответ в формате JSON: {{ {", ".join([f'"{c.lower()}": <int>' for c in criteria])}, "comments": "<string>" }} """ response = client.chat.completions.create( model="gpt-4-turbo-preview", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"}, temperature=0.0 ) return json.loads(response.choices[0].message.content) # Массовая оценка def evaluate_model( model_name: str, test_set: List[dict], judge_model: str = "gpt-4-turbo-preview" ) -> dict: """Массовая оценка модели через LLM-as-a-Judge""" results = [] for item in test_set: # Генерация ответа answer = generate_answer(model_name, item["question"]) # Оценка через judge judgment = llm_judge( item["question"], answer, item["reference"] ) results.append({ "question": item["question"], "answer": answer, "judgment": judgment }) # Агрегация aggregated = { criterion: sum(r["judgment"].get(criterion, 0) for r in results) / len(results) for criterion in ["correctness", "relevance", "coherence", "completeness"] } aggregated["total_samples"] = len(results) return aggregated

#6. Production Evaluation

#Логирование для оценки

from fastapi import FastAPI from pydantic import BaseModel import json from datetime import datetime import logging app = FastAPI() # Настройка логирования logging.basicConfig( filename="chat_logs.jsonl", level=logging.INFO, format='%(message)s' ) class ChatRequest(BaseModel): message: str model: str user_id: str class ChatResponse(BaseModel): response: str model: str latency_ms: float tokens_used: int cost_usd: float @app.post("/chat") async def chat(request: ChatRequest): import time start = time.time() # Выбор модели if request.model == "model_a": response, tokens = call_model_a(request.message) else: response, tokens = call_model_b(request.message) latency = (time.time() - start) * 1000 cost = calculate_cost(request.model, tokens) # Логирование для оценки log_entry = { "timestamp": datetime.now().isoformat(), "model": request.model, "user_id": request.user_id, "message": request.message, "response": response, "latency_ms": latency, "tokens_used": tokens, "cost_usd": cost } logging.info(json.dumps(log_entry)) return ChatResponse( response=response, model=request.model, latency_ms=latency, tokens_used=tokens, cost_usd=cost )

#Сбор фидбека

class FeedbackRequest(BaseModel): conversation_id: str rating: int # 1-5 feedback: str = "" categories: List[str] = [] # "incorrect", "irrelevant", "incomplete" @app.post("/feedback") async def submit_feedback(request: FeedbackRequest): # Сохранение фидбека with open("feedback.jsonl", "a") as f: f.write(json.dumps(request.dict()) + "\n") # Alert на негативный фидбек if request.rating <= 2: send_alert(f"Low rating: {request.rating}", request) return {"status": "ok"} def send_alert(message: str, context: dict): """Отправка alert в Slack/PagerDuty""" # Integration with Slack/PagerDuty pass

#Анализ результатов

import pandas as pd import numpy as np def analyze_production_metrics() -> dict: """Анализ production метрик""" # Загрузка логов logs = pd.read_json("chat_logs.jsonl", lines=True) feedback = pd.read_json("feedback.jsonl", lines=True) # Merge merged = logs.merge(feedback, on="conversation_id", how="left") # Метрики по моделям model_metrics = {} for model in logs["model"].unique(): model_data = logs[logs["model"] == model] model_feedback = merged[merged["model"] == model] model_metrics[model] = { "total_requests": len(model_data), "avg_latency_ms": model_data["latency_ms"].mean(), "p95_latency_ms": model_data["latency_ms"].quantile(0.95), "p99_latency_ms": model_data["latency_ms"].quantile(0.99), "avg_tokens": model_data["tokens_used"].mean(), "total_cost_usd": model_data["cost_usd"].sum(), "avg_rating": model_feedback["rating"].mean() if "rating" in model_feedback else None, "error_rate": (model_feedback["rating"] <= 2).mean() if "rating" in model_feedback else None } return model_metrics def print_metrics_report(metrics: dict): """Вывод отчёта""" print("=" * 80) print("PRODUCTION METRICS REPORT") print("=" * 80) for model, data in metrics.items(): print(f"\n{model}:") print(f" Requests: {data['total_requests']:,}") print(f" Avg Latency: {data['avg_latency_ms']:.0f}ms (p95: {data['p95_latency_ms']:.0f}ms)") print(f" Avg Tokens: {data['avg_tokens']:.1f}") print(f" Total Cost: ${data['total_cost_usd']:.2f}") if data['avg_rating']: print(f" Avg Rating: {data['avg_rating']:.2f}/5") print(f" Error Rate: {data['error_rate']*100:.1f}%")

#7. Evaluation Dashboard

#Grafana Dashboard для LLM

{ "dashboard": { "title": "LLM Evaluation Dashboard", "panels": [ { "title": "Average Rating by Model", "type": "bargauge", "targets": [{ "expr": "avg(llm_feedback_rating) by (model)", "legendFormat": "{{model}}" }] }, { "title": "Latency Distribution", "type": "histogram", "targets": [{ "expr": "llm_request_latency_seconds", "legendFormat": "{{model}}" }] }, { "title": "Error Rate Over Time", "type": "graph", "targets": [{ "expr": "rate(llm_feedback_rating{rating<=2}[1h])", "legendFormat": "Low ratings" }] }, { "title": "Cost per Hour", "type": "graph", "targets": [{ "expr": "increase(llm_cost_usd[1h])", "legendFormat": "{{model}}" }] } ] } }

#8. Сравнение метрик

МетрикаДля чегоПлюсыМинусыCost
PerplexityGeneral LM qualityБыстро, автоматическиНе измеряет facts$
BLEU/ROUGETranslation/SummaryСтандартизированоТолько overlap$
RAGASRAG systemsComprehensiveТребует контекст$$
DeepEvalCI/CD integrationpytest-styleЗависит от LLM judge$$
MMLUGeneral knowledgeComprehensiveДорого, медленно$$$
HumanEvalCode generationFunctional testТолько для кода$$$
Human evalВсе задачиНаиболее точноДорого, медленно$$$$
LLM-as-JudgeМасштабная оценкаБыстро, дёшевоBias judge model$$

#9. Best Practices

#1. Используйте комбинацию метрик

def comprehensive_evaluation(model: str, test_dataset: list) -> dict: """Комплексная оценка модели""" metrics = { "perplexity": calculate_perplexity_on_dataset(model, test_dataset), "ragas_scores": evaluate_with_ragas(model, test_dataset), "mmlu_score": evaluate_mmlu(model), "human_eval": evaluate_human_eval(model, test_dataset[:20]), "llm_judge_score": evaluate_with_llm_judge(model, test_dataset) } # Weighted score weights = { "perplexity": 0.1, "ragas": 0.3, "mmlu": 0.2, "human_eval": 0.25, "llm_judge": 0.15 } total_score = sum( metrics[key] * weight for key, weight in weights.items() ) metrics["total_score"] = total_score return metrics

#2. Оценивайте на своих данных

# Создайте test set из реальных запросов пользователей custom_test_set = [ { "question": "Как создать FastAPI endpoint?", "reference": "Используйте @app.get decorator", "context": ["FastAPI documentation"] }, { "question": "Что такое async/await?", "reference": "Ключевые слова для асинхронного программирования", "context": ["Python asyncio docs"] } ] # Оцените модель на этом set results = evaluate_on_custom_data(model, custom_test_set)

#3. Мониторьте в production

# Track метрики в production production_metrics = { # Latency "latency_p50": ..., "latency_p95": ..., "latency_p99": ..., # Quality "avg_rating": ..., "error_rate": ..., "hallucination_rate": ..., # Cost "cost_per_request": ..., "tokens_per_request": ..., "daily_cost": ..., # Usage "requests_per_day": ..., "active_users": ... }

#4. Continuous Evaluation Pipeline

# .github/workflows/llm-eval.yml name: LLM Continuous Evaluation on: pull_request: paths: - 'prompts/**' - 'src/llm/**' - 'models/**' jobs: evaluate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: | pip install -r requirements.txt pip install ragas deepeval pytest - name: Run RAGAS Evaluation run: | python scripts/evaluate_ragas.py > eval_results.json # Parse results and check thresholds python scripts/check_quality_gates.py --input eval_results.json if [ $? -ne 0 ]; then echo "❌ Quality gate failed" exit 1 fi echo "✅ Quality gates passed" - name: Run DeepEval Tests run: | pytest test_llm.py -v --tb=short - name: Upload Evaluation Report uses: actions/upload-artifact@v4 with: name: eval-report path: eval_results.json

#10. Case Study: Evaluation в Production

#Контекст

SaaS компания с RAG-системой для технической документации (50K+ страниц).

#Проблема

  • 40% запросов возвращали нерелевантные ответы
  • Нет системной оценки качества
  • Невозможно сравнить разные версии модели

#Решение

1. Внедрили RAGAS:

daily_eval = evaluate( dataset=daily_queries, metrics=[faithfulness, answer_relevancy, context_precision, context_recall] )

2. Настроили quality gates:

  • Faithfulness > 0.80
  • Answer Relevancy > 0.75
  • Context Precision > 0.70

3. Добавили human evaluation:

  • 100 случайных запросов в неделю
  • Оценка 2 экспертами
  • Inter-rater agreement > 0.8

#Результаты

МетрикаДоПосле
Context Precision0.450.82
Answer Relevancy0.520.78
Faithfulness0.610.85
User Satisfaction3.2/54.5/5
Support Tickets120/нед45/нед

ROI: 340% за 6 месяцев

Далее: Оптимизация inference