diff --git a/README.md b/README.md index e21ca3e..092dd83 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,86 @@ # ai_benchmark -Эксперименты и тестирование LLM, VLM и прочих тулов \ No newline at end of file +Эксперименты и тестирование LLM, VLM и прочих тулов + +## Установка + +```bash +pip install -r requirements.txt +``` + +## Использование + +```bash +python src/main.py --model llama3 --ollama-url http://localhost:11434 +``` + +### Аргументы + +- `--model`: Название модели для тестирования (обязательный) +- `--ollama-url`: URL подключения к Ollama серверу (обязательный) +- `--benchmarks`: Список бенчмарков для выполнения (translation, summarization, codegen). По умолчанию все. +- `--output`: Директория для сохранения результатов. По умолчанию: `results` +- `--verbose`: Подробный режим вывода + +### Примеры + +Запуск всех бенчмарков: +```bash +python src/main.py --model llama3 --ollama-url http://localhost:11434 +``` + +Запуск только тестов переводов: +```bash +python src/main.py --model llama3 --ollama-url http://localhost:11434 --benchmarks translation +``` + +Запуск с подробным выводом: +```bash +python src/main.py --model llama3 --ollama-url http://localhost:11434 --verbose +``` + +## Структура проекта + +``` +ai-benchmark/ +├── src/ +│ ├── benchmarks/ # Модули с тестовыми наборами +│ │ ├── translation.py # Тесты переводов +│ │ ├── summarization.py # Тесты пересказов +│ │ ├── codegen.py # Тесты генерации кода +│ │ └── base.py # Базовый класс для тестов +│ ├── models/ # Модули для работы с моделями +│ │ └── ollama_client.py # Клиент для Ollama +│ ├── utils/ # Утилиты +│ │ └── report.py # Генерация отчетов +│ └── main.py # Основной скрипт запуска +├── tests/ # Тестовые данные +│ ├── translation/ # Данные для тестов переводов +│ ├── summarization/ # Данные для тестов пересказов +│ └── codegen/ # Данные для тестов генерации кода +├── results/ # Результаты выполнения +├── requirements.txt # Зависимости проекта +└── README.md # Документация +``` + +## Добавление новых тестов + +1. Создайте новый файл в `src/benchmarks/` наследуя от `Benchmark` +2. Реализуйте методы `load_test_data()` и `evaluate()` +3. Добавьте тестовые данные в соответствующую директорию в `tests/` +4. Обновите список бенчмарков в `src/main.py` + +## Формат тестовых данных + +Тестовые данные должны быть в формате JSON: + +```json +{ + "prompt": "Текст промпта для модели", + "expected": "Ожидаемый ответ" +} +``` + +## Результаты + +После выполнения бенчмарков в директории `results/` будут сгенерированы файлы в формате Markdown с таблицами результатов. Каждый бенчмарк будет иметь свой отчет, а также будет создан сводный отчет со статистикой по всем тестам. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..19242d2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +ollama>=0.1.0 +py-markdown-table>=1.3.0 +tqdm>=4.60.0 diff --git a/results/rnj-1:8b/codegen_20260116_195424.md b/results/rnj-1:8b/codegen_20260116_195424.md new file mode 100644 index 0000000..24423ed --- /dev/null +++ b/results/rnj-1:8b/codegen_20260116_195424.md @@ -0,0 +1,26 @@ +# Отчет бенчмарка: codegen + +**Дата:** 2026-01-16 19:54:24 + +**Общее количество тестов:** 1 + +**Успешно выполнено:** 1 + +## Результаты тестов + +``` ++-----+-----+---------+-----------------------------------------------------+-----------------------------------------------------+-----------------------------------------------------+ +| Тест| Скор|Время (с)| Промпт | Ожидаемый | Ответ модели | ++-----+-----+---------+-----------------------------------------------------+-----------------------------------------------------+-----------------------------------------------------+ +| Тест| Скор|Время (с)| Промпт | Ожидаемый | Ответ модели | ++-----+-----+---------+-----------------------------------------------------+-----------------------------------------------------+-----------------------------------------------------+ +|test1|0.239| 3.51 |Write a Python function that calculates the factor...|def factorial(n):\n if n == 0 or n == 1:\n ...|```python +def factorial(n): + """ + Calculate ...| ++-----+-----+---------+-----------------------------------------------------+-----------------------------------------------------+-----------------------------------------------------+``` + +## Статистика + +- **Средний скор:** 0.239 +- **Среднее время ответа:** 3.507 секунд diff --git a/results/rnj-1:8b/summarization_20260116_195424.md b/results/rnj-1:8b/summarization_20260116_195424.md new file mode 100644 index 0000000..8b0cc2c --- /dev/null +++ b/results/rnj-1:8b/summarization_20260116_195424.md @@ -0,0 +1,23 @@ +# Отчет бенчмарка: summarization + +**Дата:** 2026-01-16 19:54:24 + +**Общее количество тестов:** 1 + +**Успешно выполнено:** 1 + +## Результаты тестов + +``` ++-----+-----+---------+-----------------------------------------------------+-----------------------------------------------------+-----------------------------------------------------+ +| Тест| Скор|Время (с)| Промпт | Ожидаемый | Ответ модели | ++-----+-----+---------+-----------------------------------------------------+-----------------------------------------------------+-----------------------------------------------------+ +| Тест| Скор|Время (с)| Промпт | Ожидаемый | Ответ модели | ++-----+-----+---------+-----------------------------------------------------+-----------------------------------------------------+-----------------------------------------------------+ +|test1|0.571| 1.21 |Summarize the following text in 1-2 sentences: 'Th...|A quick fox jumps over a lazy dog, surprising it. ...|In a brief summary, the quick brown fox jumps over...| ++-----+-----+---------+-----------------------------------------------------+-----------------------------------------------------+-----------------------------------------------------+``` + +## Статистика + +- **Средний скор:** 0.571 +- **Среднее время ответа:** 1.206 секунд diff --git a/results/rnj-1:8b/summary_20260116_195424.md b/results/rnj-1:8b/summary_20260116_195424.md new file mode 100644 index 0000000..c6110a0 --- /dev/null +++ b/results/rnj-1:8b/summary_20260116_195424.md @@ -0,0 +1,44 @@ +# Сводный отчет по всем бенчмаркам + +**Дата:** 2026-01-16 19:54:24 + +**Модель:** rnj-1:8b + +## Общие результаты + +``` ++-------------+------+-------+------------+-------------+ +| Бенчмарк |Тестов|Успешно|Средний скор|Среднее время| ++-------------+------+-------+------------+-------------+ +| Бенчмарк |Тестов|Успешно|Средний скор|Среднее время| ++-------------+------+-------+------------+-------------+ +| translation | 2 | 2 | 0.666 | 1.262 | ++-------------+------+-------+------------+-------------+ +|summarization| 1 | 1 | 0.571 | 1.206 | ++-------------+------+-------+------------+-------------+ +| codegen | 1 | 1 | 0.239 | 3.507 | ++-------------+------+-------+------------+-------------+``` + +## Подробности + +### translation + +- **Тестов:** 2 +- **Успешно:** 2 +- **Средний скор:** 0.666 +- **Среднее время:** 1.262 секунд + +### summarization + +- **Тестов:** 1 +- **Успешно:** 1 +- **Средний скор:** 0.571 +- **Среднее время:** 1.206 секунд + +### codegen + +- **Тестов:** 1 +- **Успешно:** 1 +- **Средний скор:** 0.239 +- **Среднее время:** 3.507 секунд + diff --git a/results/rnj-1:8b/translation_20260116_195424.md b/results/rnj-1:8b/translation_20260116_195424.md new file mode 100644 index 0000000..860f448 --- /dev/null +++ b/results/rnj-1:8b/translation_20260116_195424.md @@ -0,0 +1,25 @@ +# Отчет бенчмарка: translation + +**Дата:** 2026-01-16 19:54:24 + +**Общее количество тестов:** 2 + +**Успешно выполнено:** 2 + +## Результаты тестов + +``` ++-----+-----+---------+-----------------------------------------------------+-------------------------+-------------------------+ +| Тест| Скор|Время (с)| Промпт | Ожидаемый | Ответ модели | ++-----+-----+---------+-----------------------------------------------------+-------------------------+-------------------------+ +| Тест| Скор|Время (с)| Промпт | Ожидаемый | Ответ модели | ++-----+-----+---------+-----------------------------------------------------+-------------------------+-------------------------+ +|test1| 1.0 | 2.21 |Translate the following English text to Russian: '...|Привет, как дела сегодня?|Привет, как дела сегодня?| ++-----+-----+---------+-----------------------------------------------------+-------------------------+-------------------------+ +|test2|0.333| 0.32 |Translate the following Russian text to English: '...| How are you? | "How are you?" | ++-----+-----+---------+-----------------------------------------------------+-------------------------+-------------------------+``` + +## Статистика + +- **Средний скор:** 0.666 +- **Среднее время ответа:** 1.262 секунд diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..e205708 --- /dev/null +++ b/run.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Получаем имя ядра (Linux – Linux, macOS – Darwin, FreeBSD – FreeBSD …) +OS_NAME=$(uname -s) + +init() { + if [[ "$OS_NAME" == "Darwin" ]]; then + python3.13 -m venv z + else + python3 -m venv z + fi + upd +} + +upd() { + activate + pip install -r requirements.txt --upgrade + git submodule update --remote --merge +} + +activate() { + source z/bin/activate +} + +echo "_= Project Scripts =_" +if [ -n "$1" ]; then + if [[ "$1" == "init" ]]; then + init + elif [[ "$1" == "upd" ]]; then + upd + fi +else + echo " Аргументом необходимо написать название скрипта (+опционально аргументы скрипта)" + echo "Скрипты:" + echo " * init - инициализация, устанавливает env" + echo " * upd - обновление зависимостей" +fi diff --git a/src/benchmarks/__pycache__/base.cpython-313.pyc b/src/benchmarks/__pycache__/base.cpython-313.pyc new file mode 100644 index 0000000..9ec4489 Binary files /dev/null and b/src/benchmarks/__pycache__/base.cpython-313.pyc differ diff --git a/src/benchmarks/__pycache__/codegen.cpython-313.pyc b/src/benchmarks/__pycache__/codegen.cpython-313.pyc new file mode 100644 index 0000000..2044106 Binary files /dev/null and b/src/benchmarks/__pycache__/codegen.cpython-313.pyc differ diff --git a/src/benchmarks/__pycache__/summarization.cpython-313.pyc b/src/benchmarks/__pycache__/summarization.cpython-313.pyc new file mode 100644 index 0000000..9c1dbd9 Binary files /dev/null and b/src/benchmarks/__pycache__/summarization.cpython-313.pyc differ diff --git a/src/benchmarks/__pycache__/translation.cpython-313.pyc b/src/benchmarks/__pycache__/translation.cpython-313.pyc new file mode 100644 index 0000000..784ad31 Binary files /dev/null and b/src/benchmarks/__pycache__/translation.cpython-313.pyc differ diff --git a/src/benchmarks/base.py b/src/benchmarks/base.py new file mode 100644 index 0000000..cd9850c --- /dev/null +++ b/src/benchmarks/base.py @@ -0,0 +1,100 @@ +import logging +import time +from typing import Dict, Any, List +from abc import ABC, abstractmethod +from models.ollama_client import OllamaClient + +class Benchmark(ABC): + """Базовый класс для всех бенчмарков.""" + + def __init__(self, name: str): + """ + Инициализация бенчмарка. + + Args: + name: Название бенчмарка + """ + self.name = name + self.logger = logging.getLogger(__name__) + + @abstractmethod + def load_test_data(self) -> List[Dict[str, Any]]: + """ + Загрузка тестовых данных. + + Returns: + Список тестовых случаев + """ + pass + + @abstractmethod + def evaluate(self, model_response: str, expected: str) -> float: + """ + Оценка качества ответа модели. + + Args: + model_response: Ответ от модели + expected: Ожидаемый ответ + + Returns: + Метрика качества (0-1) + """ + pass + + def run(self, ollama_client: OllamaClient, model_name: str) -> Dict[str, Any]: + """ + Запуск бенчмарка. + + Args: + ollama_client: Клиент для работы с Ollama + model_name: Название модели + + Returns: + Результаты бенчмарка + """ + test_cases = self.load_test_data() + results = [] + + for i, test_case in enumerate(test_cases, 1): + try: + self.logger.info(f"Running test case {i}/{len(test_cases)} for {self.name}") + + # Замер времени + start_time = time.time() + + # Получение ответа от модели + prompt = test_case['prompt'] + model_response = ollama_client.generate( + model=model_name, + prompt=prompt, + options={'temperature': 0.7} + ) + + # Замер времени + latency = time.time() - start_time + + # Оценка качества + score = self.evaluate(model_response, test_case['expected']) + + results.append({ + 'test_case': test_case['name'], + 'prompt': prompt, + 'expected': test_case['expected'], + 'model_response': model_response, + 'score': score, + 'latency': latency + }) + + except Exception as e: + self.logger.error(f"Error in test case {i}: {e}") + results.append({ + 'test_case': test_case['name'], + 'error': str(e) + }) + + return { + 'benchmark_name': self.name, + 'total_tests': len(test_cases), + 'successful_tests': len([r for r in results if 'score' in r]), + 'results': results + } diff --git a/src/benchmarks/codegen.py b/src/benchmarks/codegen.py new file mode 100644 index 0000000..73681ef --- /dev/null +++ b/src/benchmarks/codegen.py @@ -0,0 +1,62 @@ +import logging +import json +import os +from typing import Dict, Any, List +from benchmarks.base import Benchmark + +class CodeGenBenchmark(Benchmark): + """Бенчмарк для тестирования генерации кода.""" + + def __init__(self): + super().__init__("codegen") + + def load_test_data(self) -> List[Dict[str, Any]]: + """ + Загрузка тестовых данных для генерации кода. + + Returns: + Список тестовых случаев + """ + test_data = [] + data_dir = "tests/codegen" + + for filename in os.listdir(data_dir): + if filename.endswith('.json'): + with open(os.path.join(data_dir, filename), 'r', encoding='utf-8') as f: + data = json.load(f) + test_data.append({ + 'name': filename.replace('.json', ''), + 'prompt': data['prompt'], + 'expected': data['expected'] + }) + + return test_data + + def evaluate(self, model_response: str, expected: str) -> float: + """ + Оценка качества сгенерированного кода. + + Args: + model_response: Ответ от модели + expected: Ожидаемый ответ + + Returns: + Метрика качества (0-1) + """ + # Простая оценка на основе совпадения токенов + model_tokens = set(model_response.lower().split()) + expected_tokens = set(expected.lower().split()) + + if len(expected_tokens) == 0: + return 0.0 + + intersection = model_tokens.intersection(expected_tokens) + precision = len(intersection) / len(model_tokens) if model_tokens else 0.0 + recall = len(intersection) / len(expected_tokens) if expected_tokens else 0.0 + + # F1-score + if (precision + recall) == 0: + return 0.0 + f1 = 2 * (precision * recall) / (precision + recall) + + return round(f1, 3) diff --git a/src/benchmarks/summarization.py b/src/benchmarks/summarization.py new file mode 100644 index 0000000..96d79ab --- /dev/null +++ b/src/benchmarks/summarization.py @@ -0,0 +1,62 @@ +import logging +import json +import os +from typing import Dict, Any, List +from benchmarks.base import Benchmark + +class SummarizationBenchmark(Benchmark): + """Бенчмарк для тестирования пересказов.""" + + def __init__(self): + super().__init__("summarization") + + def load_test_data(self) -> List[Dict[str, Any]]: + """ + Загрузка тестовых данных для пересказов. + + Returns: + Список тестовых случаев + """ + test_data = [] + data_dir = "tests/summarization" + + for filename in os.listdir(data_dir): + if filename.endswith('.json'): + with open(os.path.join(data_dir, filename), 'r', encoding='utf-8') as f: + data = json.load(f) + test_data.append({ + 'name': filename.replace('.json', ''), + 'prompt': data['prompt'], + 'expected': data['expected'] + }) + + return test_data + + def evaluate(self, model_response: str, expected: str) -> float: + """ + Оценка качества пересказа. + + Args: + model_response: Ответ от модели + expected: Ожидаемый ответ + + Returns: + Метрика качества (0-1) + """ + # Простая оценка на основе совпадения токенов + model_tokens = set(model_response.lower().split()) + expected_tokens = set(expected.lower().split()) + + if len(expected_tokens) == 0: + return 0.0 + + intersection = model_tokens.intersection(expected_tokens) + precision = len(intersection) / len(model_tokens) if model_tokens else 0.0 + recall = len(intersection) / len(expected_tokens) if expected_tokens else 0.0 + + # F1-score + if (precision + recall) == 0: + return 0.0 + f1 = 2 * (precision * recall) / (precision + recall) + + return round(f1, 3) diff --git a/src/benchmarks/translation.py b/src/benchmarks/translation.py new file mode 100644 index 0000000..9c86c38 --- /dev/null +++ b/src/benchmarks/translation.py @@ -0,0 +1,63 @@ +import logging +import json +import os +from typing import Dict, Any, List +from benchmarks.base import Benchmark + +class TranslationBenchmark(Benchmark): + """Бенчмарк для тестирования переводов.""" + + def __init__(self): + super().__init__("translation") + + def load_test_data(self) -> List[Dict[str, Any]]: + """ + Загрузка тестовых данных для переводов. + + Returns: + Список тестовых случаев + """ + test_data = [] + data_dir = "tests/translation" + + for filename in os.listdir(data_dir): + if filename.endswith('.json'): + with open(os.path.join(data_dir, filename), 'r', encoding='utf-8') as f: + data = json.load(f) + test_data.append({ + 'name': filename.replace('.json', ''), + 'prompt': data['prompt'], + 'expected': data['expected'] + }) + + return test_data + + def evaluate(self, model_response: str, expected: str) -> float: + """ + Оценка качества перевода. + + Args: + model_response: Ответ от модели + expected: Ожидаемый ответ + + Returns: + Метрика качества (0-1) + """ + # Простая оценка на основе совпадения токенов + # В реальном проекте можно использовать более сложные метрики + model_tokens = set(model_response.lower().split()) + expected_tokens = set(expected.lower().split()) + + if len(expected_tokens) == 0: + return 0.0 + + intersection = model_tokens.intersection(expected_tokens) + precision = len(intersection) / len(model_tokens) if model_tokens else 0.0 + recall = len(intersection) / len(expected_tokens) if expected_tokens else 0.0 + + # F1-score + if (precision + recall) == 0: + return 0.0 + f1 = 2 * (precision * recall) / (precision + recall) + + return round(f1, 3) diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..cb7653c --- /dev/null +++ b/src/main.py @@ -0,0 +1,97 @@ +import logging +import argparse +from typing import List +from models.ollama_client import OllamaClient +from benchmarks.translation import TranslationBenchmark +from benchmarks.summarization import SummarizationBenchmark +from benchmarks.codegen import CodeGenBenchmark +from utils.report import ReportGenerator + +def setup_logging(verbose: bool = False): + """Настройка логирования.""" + level = logging.DEBUG if verbose else logging.INFO + logging.basicConfig( + level=level, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler() + ] + ) + +def run_benchmarks(ollama_client: OllamaClient, model_name: str, benchmarks: List[str]) -> List[dict]: + """ + Запуск выбранных бенчмарков. + + Args: + ollama_client: Клиент для работы с Ollama + model_name: Название модели + benchmarks: Список имен бенчмарков для запуска + + Returns: + Список результатов бенчмарков + """ + benchmark_classes = { + 'translation': TranslationBenchmark, + 'summarization': SummarizationBenchmark, + 'codegen': CodeGenBenchmark + } + + results = [] + + for benchmark_name in benchmarks: + if benchmark_name not in benchmark_classes: + logging.warning(f"Unknown benchmark: {benchmark_name}") + continue + + logging.info(f"Running {benchmark_name} benchmark...") + benchmark = benchmark_classes[benchmark_name]() + result = benchmark.run(ollama_client, model_name) + results.append(result) + + return results + +def main(): + """Основная функция запуска.""" + parser = argparse.ArgumentParser(description='LLM Benchmarking Tool') + parser.add_argument('--model', required=True, help='Название модели для тестирования') + parser.add_argument('--ollama-url', required=True, help='URL подключения к Ollama серверу') + parser.add_argument('--benchmarks', nargs='+', default=['translation', 'summarization', 'codegen'], + help='Список бенчмарков для выполнения (translation, summarization, codegen)') + parser.add_argument('--output', default='results', help='Директория для сохранения результатов') + parser.add_argument('--verbose', action='store_true', help='Подробный режим вывода') + + args = parser.parse_args() + + # Настройка логирования + setup_logging(args.verbose) + + logging.info(f"Starting benchmarking for model: {args.model}") + logging.info(f"Ollama URL: {args.ollama_url}") + logging.info(f"Benchmarks to run: {', '.join(args.benchmarks)}") + + try: + # Инициализация клиента + ollama_client = OllamaClient(args.ollama_url) + + # Запуск бенчмарков + results = run_benchmarks(ollama_client, args.model, args.benchmarks) + + # Генерация отчетов + report_generator = ReportGenerator() + + for result in results: + report_generator.generate_benchmark_report(result, args.output, args.model) + + if len(results) > 1: + report_generator.generate_summary_report(results, args.output, args.model) + + logging.info("Benchmarking completed successfully!") + + except Exception as e: + logging.error(f"Error during benchmarking: {e}") + return 1 + + return 0 + +if __name__ == '__main__': + exit(main()) diff --git a/src/models/__pycache__/ollama_client.cpython-313.pyc b/src/models/__pycache__/ollama_client.cpython-313.pyc new file mode 100644 index 0000000..7d3f8ce Binary files /dev/null and b/src/models/__pycache__/ollama_client.cpython-313.pyc differ diff --git a/src/models/ollama_client.py b/src/models/ollama_client.py new file mode 100644 index 0000000..f4915b6 --- /dev/null +++ b/src/models/ollama_client.py @@ -0,0 +1,85 @@ +import logging +from typing import Optional, Dict, Any +from ollama import Client + +class OllamaClient: + """Клиент для работы с Ollama API.""" + + def __init__(self, base_url: str): + """ + Инициализация клиента. + + Args: + base_url: Базовый URL для подключения к Ollama серверу + """ + self.base_url = base_url + self.client = Client(host=base_url) + self.logger = logging.getLogger(__name__) + + def generate(self, model: str, prompt: str, **kwargs) -> str: + """ + Генерация ответа от модели. + + Args: + model: Название модели + prompt: Входной промпт + **kwargs: Дополнительные параметры для запроса + + Returns: + Сгенерированный ответ + + Raises: + Exception: Если произошла ошибка при генерации + """ + try: + self.logger.info(f"Generating response for model {model}") + response = self.client.generate( + model=model, + prompt=prompt, + **kwargs + ) + return response['response'] + except Exception as e: + self.logger.error(f"Error generating response: {e}") + raise + + def chat(self, model: str, messages: list, **kwargs) -> str: + """ + Диалог с моделью. + + Args: + model: Название модели + messages: Список сообщений в формате [{'role': 'user', 'content': '...'}, ...] + **kwargs: Дополнительные параметры для запроса + + Returns: + Ответ от модели + + Raises: + Exception: Если произошла ошибка при чате + """ + try: + self.logger.info(f"Chatting with model {model}") + response = self.client.chat( + model=model, + messages=messages, + **kwargs + ) + return response['message']['content'] + except Exception as e: + self.logger.error(f"Error in chat: {e}") + raise + + def list_models(self) -> list: + """ + Получение списка доступных моделей. + + Returns: + Список моделей + """ + try: + response = self.client.list() + return [model['name'] for model in response['models']] + except Exception as e: + self.logger.error(f"Error listing models: {e}") + raise diff --git a/src/utils/__pycache__/report.cpython-313.pyc b/src/utils/__pycache__/report.cpython-313.pyc new file mode 100644 index 0000000..8a7363b Binary files /dev/null and b/src/utils/__pycache__/report.cpython-313.pyc differ diff --git a/src/utils/report.py b/src/utils/report.py new file mode 100644 index 0000000..ca0000e --- /dev/null +++ b/src/utils/report.py @@ -0,0 +1,162 @@ +import logging +import os +from typing import Dict, Any, List +from datetime import datetime +from py_markdown_table.markdown_table import markdown_table + +class ReportGenerator: + """Генератор отчетов в формате Markdown.""" + + def __init__(self): + self.logger = logging.getLogger(__name__) + + def generate_benchmark_report(self, results: Dict[str, Any], output_dir: str = "results", model_name: str = None) -> str: + """ + Генерация отчета для одного бенчмарка. + + Args: + results: Результаты бенчмарка + output_dir: Директория для сохранения отчета + model_name: Имя модели (для структурирования результатов) + + Returns: + Путь к сгенерированному файлу + """ + if model_name: + model_dir = os.path.join(output_dir, model_name) + os.makedirs(model_dir, exist_ok=True) + output_dir = model_dir + + os.makedirs(output_dir, exist_ok=True) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"{results['benchmark_name']}_{timestamp}.md" + file_path = os.path.join(output_dir, filename) + + with open(file_path, 'w', encoding='utf-8') as f: + f.write(f"# Отчет бенчмарка: {results['benchmark_name']}\n\n") + f.write(f"**Дата:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") + f.write(f"**Общее количество тестов:** {results['total_tests']}\n\n") + f.write(f"**Успешно выполнено:** {results['successful_tests']}\n\n") + + # Таблица с результатами + table_data = [ + { + "Тест": "Тест", + "Скор": "Скор", + "Время (с)": "Время (с)", + "Промпт": "Промпт", + "Ожидаемый": "Ожидаемый", + "Ответ модели": "Ответ модели" + } + ] + + for result in results['results']: + if 'error' in result: + table_data.append({ + "Тест": result['test_case'], + "Скор": "Ошибка", + "Время (с)": "-", + "Промпт": result['prompt'][:50] + "..." if len(result['prompt']) > 50 else result['prompt'], + "Ожидаемый": result['expected'][:50] + "..." if len(result['expected']) > 50 else result['expected'], + "Ответ модели": result['error'] + }) + else: + table_data.append({ + "Тест": result['test_case'], + "Скор": str(result['score']), + "Время (с)": f"{result['latency']:.2f}", + "Промпт": result['prompt'][:50] + "..." if len(result['prompt']) > 50 else result['prompt'], + "Ожидаемый": result['expected'][:50] + "..." if len(result['expected']) > 50 else result['expected'], + "Ответ модели": result['model_response'][:50] + "..." if len(result['model_response']) > 50 else result['model_response'] + }) + + f.write("## Результаты тестов\n\n") + f.write(markdown_table(table_data).get_markdown()) + f.write("\n\n") + + # Статистика + successful = [r for r in results['results'] if 'score' in r] + if successful: + avg_score = sum(r['score'] for r in successful) / len(successful) + avg_latency = sum(r['latency'] for r in successful) / len(successful) + + f.write("## Статистика\n\n") + f.write(f"- **Средний скор:** {avg_score:.3f}\n") + f.write(f"- **Среднее время ответа:** {avg_latency:.3f} секунд\n") + + self.logger.info(f"Report saved to {file_path}") + return file_path + + def generate_summary_report(self, all_results: List[Dict[str, Any]], output_dir: str = "results", model_name: str = None) -> str: + """ + Генерация сводного отчета по всем бенчмаркам. + + Args: + all_results: Список результатов всех бенчмарков + output_dir: Директория для сохранения отчета + model_name: Имя модели (для структурирования результатов) + + Returns: + Путь к сгенерированному файлу + """ + if model_name: + model_dir = os.path.join(output_dir, model_name) + os.makedirs(model_dir, exist_ok=True) + output_dir = model_dir + + os.makedirs(output_dir, exist_ok=True) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"summary_{timestamp}.md" + file_path = os.path.join(output_dir, filename) + + with open(file_path, 'w', encoding='utf-8') as f: + f.write("# Сводный отчет по всем бенчмаркам\n\n") + f.write(f"**Дата:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") + if model_name: + f.write(f"**Модель:** {model_name}\n\n") + + # Таблица с общими результатами + table_data = [ + { + "Бенчмарк": "Бенчмарк", + "Тестов": "Тестов", + "Успешно": "Успешно", + "Средний скор": "Средний скор", + "Среднее время": "Среднее время" + } + ] + + for result in all_results: + successful = [r for r in result['results'] if 'score' in r] + avg_score = sum(r['score'] for r in successful) / len(successful) if successful else 0 + avg_latency = sum(r['latency'] for r in successful) / len(successful) if successful else 0 + + table_data.append({ + "Бенчмарк": result['benchmark_name'], + "Тестов": str(result['total_tests']), + "Успешно": str(result['successful_tests']), + "Средний скор": f"{avg_score:.3f}" if successful else "0", + "Среднее время": f"{avg_latency:.3f}" if successful else "0" + }) + + f.write("## Общие результаты\n\n") + f.write(markdown_table(table_data).get_markdown()) + f.write("\n\n") + + # Подробности по каждому бенчмарку + f.write("## Подробности\n\n") + for result in all_results: + f.write(f"### {result['benchmark_name']}\n\n") + f.write(f"- **Тестов:** {result['total_tests']}\n") + f.write(f"- **Успешно:** {result['successful_tests']}\n") + + successful = [r for r in result['results'] if 'score' in r] + if successful: + avg_score = sum(r['score'] for r in successful) / len(successful) + avg_latency = sum(r['latency'] for r in successful) / len(successful) + f.write(f"- **Средний скор:** {avg_score:.3f}\n") + f.write(f"- **Среднее время:** {avg_latency:.3f} секунд\n") + f.write("\n") + + self.logger.info(f"Summary report saved to {file_path}") + return file_path diff --git a/tests/codegen/test1.json b/tests/codegen/test1.json new file mode 100644 index 0000000..ad22db6 --- /dev/null +++ b/tests/codegen/test1.json @@ -0,0 +1,4 @@ +{ + "prompt": "Write a Python function that calculates the factorial of a number using recursion.", + "expected": "def factorial(n):\\n if n == 0 or n == 1:\\n return 1\\n else:\\n return n * factorial(n-1)" +} diff --git a/tests/summarization/test1.json b/tests/summarization/test1.json new file mode 100644 index 0000000..ca6e7da --- /dev/null +++ b/tests/summarization/test1.json @@ -0,0 +1,4 @@ +{ + "prompt": "Summarize the following text in 1-2 sentences: 'The quick brown fox jumps over the lazy dog. The dog, surprised by the fox's agility, barks loudly. The fox continues running without looking back.'", + "expected": "A quick fox jumps over a lazy dog, surprising it. The fox keeps running while the dog barks." +} diff --git a/tests/translation/test1.json b/tests/translation/test1.json new file mode 100644 index 0000000..3c900b3 --- /dev/null +++ b/tests/translation/test1.json @@ -0,0 +1,4 @@ +{ + "prompt": "Translate the following English text to Russian: 'Hello, how are you today?'", + "expected": "Привет, как дела сегодня?" +} diff --git a/tests/translation/test2.json b/tests/translation/test2.json new file mode 100644 index 0000000..2446d4b --- /dev/null +++ b/tests/translation/test2.json @@ -0,0 +1,4 @@ +{ + "prompt": "Translate the following Russian text to English: 'Как ваши дела?'", + "expected": "How are you?" +}