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
|
# 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