feat: vibe code done
This commit is contained in:
parent
408f6b86c6
commit
1a59adf5a5
85
README.md
85
README.md
@ -1,3 +1,86 @@
|
||||
# ai_benchmark
|
||||
|
||||
Эксперименты и тестирование LLM, VLM и прочих тулов
|
||||
Эксперименты и тестирование 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 с таблицами результатов. Каждый бенчмарк будет иметь свой отчет, а также будет создан сводный отчет со статистикой по всем тестам.
|
||||
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
ollama>=0.1.0
|
||||
py-markdown-table>=1.3.0
|
||||
tqdm>=4.60.0
|
||||
26
results/rnj-1:8b/codegen_20260116_195424.md
Normal file
26
results/rnj-1:8b/codegen_20260116_195424.md
Normal file
@ -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 секунд
|
||||
23
results/rnj-1:8b/summarization_20260116_195424.md
Normal file
23
results/rnj-1:8b/summarization_20260116_195424.md
Normal file
@ -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 секунд
|
||||
44
results/rnj-1:8b/summary_20260116_195424.md
Normal file
44
results/rnj-1:8b/summary_20260116_195424.md
Normal file
@ -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 секунд
|
||||
|
||||
25
results/rnj-1:8b/translation_20260116_195424.md
Normal file
25
results/rnj-1:8b/translation_20260116_195424.md
Normal file
@ -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 секунд
|
||||
37
run.sh
Executable file
37
run.sh
Executable file
@ -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
|
||||
BIN
src/benchmarks/__pycache__/base.cpython-313.pyc
Normal file
BIN
src/benchmarks/__pycache__/base.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/benchmarks/__pycache__/codegen.cpython-313.pyc
Normal file
BIN
src/benchmarks/__pycache__/codegen.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/benchmarks/__pycache__/summarization.cpython-313.pyc
Normal file
BIN
src/benchmarks/__pycache__/summarization.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/benchmarks/__pycache__/translation.cpython-313.pyc
Normal file
BIN
src/benchmarks/__pycache__/translation.cpython-313.pyc
Normal file
Binary file not shown.
100
src/benchmarks/base.py
Normal file
100
src/benchmarks/base.py
Normal file
@ -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
|
||||
}
|
||||
62
src/benchmarks/codegen.py
Normal file
62
src/benchmarks/codegen.py
Normal file
@ -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)
|
||||
62
src/benchmarks/summarization.py
Normal file
62
src/benchmarks/summarization.py
Normal file
@ -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)
|
||||
63
src/benchmarks/translation.py
Normal file
63
src/benchmarks/translation.py
Normal file
@ -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)
|
||||
97
src/main.py
Normal file
97
src/main.py
Normal file
@ -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())
|
||||
BIN
src/models/__pycache__/ollama_client.cpython-313.pyc
Normal file
BIN
src/models/__pycache__/ollama_client.cpython-313.pyc
Normal file
Binary file not shown.
85
src/models/ollama_client.py
Normal file
85
src/models/ollama_client.py
Normal file
@ -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
|
||||
BIN
src/utils/__pycache__/report.cpython-313.pyc
Normal file
BIN
src/utils/__pycache__/report.cpython-313.pyc
Normal file
Binary file not shown.
162
src/utils/report.py
Normal file
162
src/utils/report.py
Normal file
@ -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
|
||||
4
tests/codegen/test1.json
Normal file
4
tests/codegen/test1.json
Normal file
@ -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)"
|
||||
}
|
||||
4
tests/summarization/test1.json
Normal file
4
tests/summarization/test1.json
Normal file
@ -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."
|
||||
}
|
||||
4
tests/translation/test1.json
Normal file
4
tests/translation/test1.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"prompt": "Translate the following English text to Russian: 'Hello, how are you today?'",
|
||||
"expected": "Привет, как дела сегодня?"
|
||||
}
|
||||
4
tests/translation/test2.json
Normal file
4
tests/translation/test2.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"prompt": "Translate the following Russian text to English: 'Как ваши дела?'",
|
||||
"expected": "How are you?"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user