From 1a59adf5a58f5dbb349b80a05c3a376f647ec171 Mon Sep 17 00:00:00 2001 From: second_constantine Date: Fri, 16 Jan 2026 19:58:29 +0300 Subject: [PATCH] feat: vibe code done --- README.md | 85 ++++++++- requirements.txt | 3 + results/rnj-1:8b/codegen_20260116_195424.md | 26 +++ .../rnj-1:8b/summarization_20260116_195424.md | 23 +++ results/rnj-1:8b/summary_20260116_195424.md | 44 +++++ .../rnj-1:8b/translation_20260116_195424.md | 25 +++ run.sh | 37 ++++ .../__pycache__/base.cpython-313.pyc | Bin 0 -> 3854 bytes .../__pycache__/codegen.cpython-313.pyc | Bin 0 -> 3175 bytes .../__pycache__/summarization.cpython-313.pyc | Bin 0 -> 3180 bytes .../__pycache__/translation.cpython-313.pyc | Bin 0 -> 3160 bytes src/benchmarks/base.py | 100 +++++++++++ src/benchmarks/codegen.py | 62 +++++++ src/benchmarks/summarization.py | 62 +++++++ src/benchmarks/translation.py | 63 +++++++ src/main.py | 97 +++++++++++ .../__pycache__/ollama_client.cpython-313.pyc | Bin 0 -> 3958 bytes src/models/ollama_client.py | 85 +++++++++ src/utils/__pycache__/report.cpython-313.pyc | Bin 0 -> 10171 bytes src/utils/report.py | 162 ++++++++++++++++++ tests/codegen/test1.json | 4 + tests/summarization/test1.json | 4 + tests/translation/test1.json | 4 + tests/translation/test2.json | 4 + 24 files changed, 889 insertions(+), 1 deletion(-) create mode 100644 requirements.txt create mode 100644 results/rnj-1:8b/codegen_20260116_195424.md create mode 100644 results/rnj-1:8b/summarization_20260116_195424.md create mode 100644 results/rnj-1:8b/summary_20260116_195424.md create mode 100644 results/rnj-1:8b/translation_20260116_195424.md create mode 100755 run.sh create mode 100644 src/benchmarks/__pycache__/base.cpython-313.pyc create mode 100644 src/benchmarks/__pycache__/codegen.cpython-313.pyc create mode 100644 src/benchmarks/__pycache__/summarization.cpython-313.pyc create mode 100644 src/benchmarks/__pycache__/translation.cpython-313.pyc create mode 100644 src/benchmarks/base.py create mode 100644 src/benchmarks/codegen.py create mode 100644 src/benchmarks/summarization.py create mode 100644 src/benchmarks/translation.py create mode 100644 src/main.py create mode 100644 src/models/__pycache__/ollama_client.cpython-313.pyc create mode 100644 src/models/ollama_client.py create mode 100644 src/utils/__pycache__/report.cpython-313.pyc create mode 100644 src/utils/report.py create mode 100644 tests/codegen/test1.json create mode 100644 tests/summarization/test1.json create mode 100644 tests/translation/test1.json create mode 100644 tests/translation/test2.json 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 0000000000000000000000000000000000000000..9ec44899b0290e1f203126d5cf314191d6e14ecb GIT binary patch literal 3854 zcma)9U2qfE6~3$8)k>=$%Rd+}*j~vtK_<3<*x{#Xs3Ejqz$M;@3ANjuMP56i@=CqC z>IC|NNt3!m+L{Lnfi^e~ow_r9FcgAVc&Y15hZq37Q9 z$~MrSJEQw^&bjBFbMCp{S_j7bXj z$zmYMI@}A{jOnm_`Z0&wpEb;+4;DLjIZ^e9VPZ97j%(&C`H>{!guj~0spIOdTvpS~ zjH}G;0}G4!AHw7e8b-uY?2{SH%0A4=>@eTsles2zl*3?&%nt{KgTwr=*yAG(lOOQ? zgx|ZM3Wq>W80O^w{P^K;k6#Wh<40t1I4XxIcZn{L3)6FI$Ej%U9;Y|F59Q9_ay7OHD}G*6ZV8;U9sluA4$NNu&&y#OV%aps&&); zo^=BRPJkfn>2^3>9oI6x1!KuTqDy}B1JIp8FUN8)x^1W`H^_Ok#W8_~{NAdUac60i zt4hLc`HC9QMeCe((<<3-SS2ui4*F|g60C^1k6Jm~EsA}3%-AkU(1^9$CF`fu>C4cA z9_#88c8LQ?9z#+hN66*J#;c6I?g)_9=DBWTZHzmpG zO{EPD8!2^UJgcWN`4KIpX1k7fmyj}WCRJS-sUxbPbr+5i`#0@?wlN z(49b)C|a@l&EUPp=DUqgebo5WF9UZPH%)Rs2+acnz0LSM~|}HHhVPYgV#P!lh3@PcwsD{}mdooAxRDbqMJoRlX2a>mBO@i2ei! zUw?cj(C6&eAaQ0Pfi8;!zd%U_*TJa@!{G?0xX zeIWob6pY|Tx_um?n^Ul66!N;EIRWj}f|fD0kqmLyYfp0jBVdZw+mJqx-PCo`fwivt zkRy`yKE&ZN5KfZC4e;^`2t(d2OReP=K$5-f?V<{nZKS#AnMg#fcOef+gs)mR+=o~_ z^U+xUgct%3u=HeO*Kh3FloSW>YVa$`(m3O_Fvrc%86+C;4BVIpv~R1qqH1bO6V0Qw z5b=dLud2p5lBdw8WGMDIqU$upfTe*ZSxWQlGk6MTP#O&`%|c+3<{_NUrjX7JRrjY^ zndx0Xih8u?3Lq7%3Wr*Vo+=u+I!HfAtbC-&zBC_&dN3697-`nS6rgjUiPj#E>yAw7p^a0LoD?PczQ4Z7lj-W$>XxUQ& z5I9du8L6=+z2Xbcd=|6-7TBjGsLAe!2Sty?mj_!Pt7l6<>QVfd28_7|I6r5jsOu4#iI*$Sh@`LP-vjI?hX@c`P~p0?v;YOebV&;{^??P;a!cQQH2p z13syEY9w9ql73-F|8jvcSxjWHaV z)t6Xu+_zmy`fw+e*9d=qI}Sx=Ow(PVRwCqA3k6Ldae}*F&1lqF z+(qatd_E?lE)vIqfD?o&R$VvnCPHAct|y{ls@PP>32sm*dOuAz66YL7a~NzyVG2OL zq`8>R;-c~Qr5Qq+a1=ym+l@sY#%&)`#V;A z=;PS%LqBRsTs(aK@P(l%vEoNfZPQ!r)lX0P%kl2J@hx_IOS!4Nye3|5?wr|Wt=(7= zP-1HZaf!OA=))*l(|R#^K6zoojA%D)o)SR8>gJ1)w<6Q}Ezy2<-!mA z4J2!E3>J>4DDbX|Ir8}#R+MiQ)!f3zXpN$bX0c)Bvbv_{fh{P?NIs(|cpK3|@D-C{ ziAg!Yq~KywPu-m7N8v-u#3Yq5`5%MH-veibqCh^H*^Hu^Ce9uyLfV2p0-W^Ai)I}r z6@zv}yYXG<-avog_Exw6zxkZ}5W!GsK>mh`Pv9fd-1JK`Z8OHq`gguzOFi=lX!ESc zt<=$32;__kf?+m8hmtl=RtthKt2Axp#p$&b1ViZ=I^62H&EA%82Y&th=Y;ASNNh`n zF?leUlx)Xu9xDK(E%Bd(!(r7U8BG3w&=5Hd6uXS>Whm_ihI+d^0#4vL_fDS2{h)$i oC%xqg%P`C*DEbKseTot`N_>ji{)oCBa$jc{afsc21W9(qAZny z+t?wX0*ccoY;P7UEht)?wmH30R$)WAG^iBj?ET4% zu3$YW=VnzcNmL|db?mfz_emXRlg*j#2*4SM@_8Ht1$O`T>q)q0lCaWu0Qqtb-RFZp z^ICl0LjRxn;Vs}H+k+LOg0&~yhWM+yJqNB`K}n=hBBjwS=hMXKZ7qs2w0jlZ=;fay zq%j#L?Z$4KwAhpzX>fC(?|1~EQ{F^Xt3*a*MQ(zjk;a}1 zxRU8kchu^^X4bV?<1$pbtF`8iz=v*k-0DSI^tk6`IMGYogq|n8=u0rJR>(j<`MI(J zI(`|U4kVxq6+_RXQPjKxxa&MEpdnZTsrM0@Vunzjig#3C2lAW0g!sK~%~@}mzk_I9 zX@xh0wspz+2?=`=;kVrh$$|eC>VALP`S$vxUw~#_GyeeT@g^j~jjt*P{!QyGNU{~k zosaw%f`+0S%{aWkCC=(4&8F}Lu43fJ_5*P65WA(EsugUAvNA5smJQ&YD=S$;$q}s4 zeBwD4` z=Ao~o){_Dx|MX=bb7->@^@gjyjqYfbf6zCu=zrJ0wExq-(dx?^q40W0utLHT^zm9K ze!sW>z3AfZtGgFRuZ}LAs|`%99r%zDtjIV^(qBUV^Bl^P9xoe4y*vRsL6-#~YWnb9&2(i84#~j)zQdV#Ja}M>7 zZT6rY{U+b{xQlQ#4@DP1C!Tl-WKRo5LJ7)oKiV{}za!CUmA|E76kk4%_ z3>+kUAHo!H5NN$Y(zMGpxO*G@)6(Bvb5lfP+O*OCw>Gu~BCX^WsmYTF$zXMXhJ^Mt z%xmjfvH%2(o0M|69h5ShYjfKf%~T%)Q2I6{(wZm3{tx?`0My*?Jy{q7%i3)g8Q=l7 zkbixZNNfD5CJ<(ze7Yb51(U1%1626UUqgK(wT=J=A&|iJnh4cUfShhs!uBG08!+Iy zd8bts{r*F^(7=f&LuFEcGcH1LU9~O><{d(AIi+;fylJjEz~bgS5jB4c<(A;os(H)0 zWd7dSQs2EDj@~0Gp+v7e-T0C5u|07=CYWDvzvDwPdr0Cj?y(tNF>J0_dIKsjrqUm$QP>)zE{E&gzeA9Yafd zY8|nS?wx-R53h&Et?>BDP%V6@8u)DLTkjlM2-JFZ{f1dSRvVeP+cWWrUiE!8b?lw) z`I%Z*^aF8usJ3h3Zr6mF&Qv{{9u(YZ1qNM(8^O-`xb;lT3hr9&x8g5a!2?zLL8xaw zz0h^(MFMG6m)NCOmWP-1<&kTz zSwiX&0^a6mgnM>L-$c-@WSwqJc}!;iBE)gu(F)bfTPzg{Q2+4r;f?=r$I#reNcfu2x;7^@lVk{1?bD2iAL<%O$*d7ep4z34*JyD zBaft2=b`8loZX$BotxR6`DV96p&)`*FPv7BJVO5@6R&a2!FC!3myw7>>IlM(p2Czn zr!fsZeS}$HF}uKFZo!9r0+C^k_!oH03up+9Bat0JBDcuzWg0qhpg_fa_sQf~oN?F} z)vW0-2lcZKdsH>dIOPNmDSGzQl8j%=HnudVuQ}QNHVj?`s}>Ql1gxMiBhr}7pcExC zBj_}bIgwrTE&3Pv#XyQC&X9kK7CBgDaZvOv@*=E)R9DI;@-DYq4B$`(%~QB1hT=hI zP%JNj5$ZYFREv7cru%+SRsIqX>!;Q=>!$r9>xQ*%FIyi7*8A3V`=Vf2&)z(!_xsZ5(B2?{NCiOFuzK zXEIFM3+4`Ku_ZUzez+iTtkeCYGW?OvospZC2CC;Ew-(DS4h0|~~{3Mm;RKTlRb zFJDBc3kfJg#n3Zo3boDv&N@#EXbjds>Rp89nK6{7;$7tmC=FUagQ)$Dy==b$u?rF0 zj&ulQ`-1&Lk^v;xZ+a7w4v&rcU@+}ox-l0NpjlTzY6J4(7Nk)te>|xu1rz)izZ%*6H)88)`p8p*Q^`%1?%_jmd5U#81xQN z3H5p7@m5aGOz(*YF+uo(cVYL*>^=#+xX)n>#dJ8W_&U^H&M0XrylWuUoo-b(p~eF0 zz&pa>aIviC;sF;T@Dyxyqul9kxM>z&Q*^@#dp<}F22O#X6DVONs~SWN6wR!xX%3y= zV~{W+=;5{ogz(CDWvwim%I6kmPhb~pHf{h}Mh}M2P;}+MW;AI>leOrc&FDTmx~~?U ztwrW4-FHWxUOBa@U(su$iOtcJJ({YGexWw9uM)o3)l>O?t!u2hr`8p#_YM9dvTHLk zV@GDz$7+#-mF|z{zx>wWmF`;q#IKpPqqWJ|+x@d28kNAu^GDz6yR=yA9si}cHddRM zz1=%&r85=ZmJfvn?e0-eVLjAyDQ-U*vqKYW!*=`yJG8$--wXF&O0V=@_@2c_@sr@d zuTVTp9z6tHCCLd&5*%q+BjavK`c7Hank!*R%B$EgHC0#iA}sqQDOb!&lKY&&q<~^_ zr*NYt#sqTkb7acp0dY=Gws zfLl_C8Wzrz5joUmIPGs73Z@ LzWFJF&QTAn_0_8wmYb1W(HY(o8KjVOka>`qH;7>8dN9ICt!c z-4GrS*YdgNo_p?@bMHOpJ9i@z2_k5JDqc{3$|Lj-GVmJLU^X(q+(aT0sS^m-dkRzT zn8q~p^a*B)#q1P^xhWs^2}FlE;h*9$FQ9HTfJC+ziQF{5pQ+o#fg+XkJt2crNycH1 zt2xtQj_8*h_M~cDXpw_i76(P&G%vy|XobdnBJWDWVgN_7Xp+Kh36u;v zou{#^8=7pYC4JMQCw?$gIS$17k+p2yx4&=QvzF{x>yBW3VBNK^3-%Rj*?tq~3P?Y+ z-nW*l`@mlptlz=JEG&8-#2=8URE`2eUL)s2{u>D(hpC(BtT%&BQ(5#4*n&vyZ>$hd z5hdwUPRMLJnWP=gsFW4#@RFqJswqhzVhu$rH~~q@X|iERl0k!6Vb(d6&Ke3f(sF)A z)zi6BUP;U9$T{!m(+19^H%CUgdM3?abkk4n)BR=v2zs+~g z?fNsncLQX|@nOZRVEwrxfH15d!7Oa^CQ2in5-FW-9z=#XzoiYJEbSdj2YTg)2lp|j*#-PZCg;?0(p32hO;@XX-;hF2mZvLZLm&`4*`hCR(p zo5wYK@R{{|*10T|X>ZPX9PFXPnJUJRrd&nhHhko-Jd0lj)1 zp%4;KmP(+P&@kFO132pfEuemw1FiQEnq>M>fl7udSHWn|`Z0v?uk2a-E$cVd9c!r> z;Sj?1HT!!c@=1{2_XZ>lwu$y&FymgiF%cA?S+_xF#eNeq;m&{B1o>V2El95=k}<){ z?WSUw&xPS-E_J~u=?;Z2a}~2NauA?{hwv@sRlVpyfR%A+rfh=jVp++VN}iyNF3%{q z8@7defSiIfr%Q(8*8q9)DkP4g=Z!a1^Sr~B%8Kr=Wts37N-FT~*I2D2=N+G1hUvWH z$4Xg~b4t>O$t^np;-!?AP1)fIm0Y6*Ra3}^9jf54L~IZ&BWEB8m}H34MyyKB#e>^s z*7Hgv1&u`@vuM2yb;YWIwT?Yi{&9EjeDIy%!oiQbhpVrwMPsW`!Hxh zcL(P8-r74qd~0~&Vy$;#<=97iA9Q7`uYLVdynlZDo$-aQ{^Zb&<7=I}Ryz}RXJSnl zS`|iZVRR|BGEfsH*9P`1T)drHOAIac+KI7G{p_9{PZ8S{d&Z$1B4u}FKIKr)$a*Jw zp~vF8pS2U|$6c9!8N}n?_Z^{+rno<(f=4Fu2^=QXXBrAOsS*UKODpZpDVBR4{l17aOqfd_+tL zVCo8#%w_wEV0}o)J-2)=TX(G$7e~DGCaTu2trZuImaTjCHS4$TlKSec=<_Zy3H5ko zdmo2JNA@Lym>_%6yRIi>^n`?6(&sRSVmh2wdIRb$XOuM+UNMm7PFU4VsH=cD@NRH8 zT&n2#WWa?7JPb?S7~NoFa4phyBWb^wup_${d+g-PcH~f%ejM$*k(q11_8p6l;}>BAzd*?- zdFBvUl_V!9NpPSQjqqVf`c_5O8Z%KzDyY~nHC0#i5={FgDPPJ-lKX_gq-^7&Qt7xvU=EX572`#QOv1$N=|;^3mOICT3pTNrzaK(;=-liRsqtRv`_ za&EVxJR_rj65?*(wF;Q HxkmmCN#|Ht literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7d3f8cec4cf0dd6f223d679be01d863a22aa53e1 GIT binary patch literal 3958 zcmcgv-ESMm5#ReHb)w`CI+RrFEhUbtE87Bcai!@KO@}%v zdn8iJEnv$@sz7dnv=(X%F0-W8OotfR8o%zjjxv5Db(0*#2%YFi*zu`x%$<@K~^Dvkp1~G&oLa`Tzgh7#t zgA$bnWh(2)lZKQ*m8v>vB|XHDzeWt@faqJGfs7DSKgEv|G06+QJYLA=?NrW_U&y8l zo|Lf9#{^G3lFM3lA?@q?XfY0i{f6CU zWj4p|xRW}&$!@#v=UV%OQcJLyjA?j_yV)DtI<_jUi zd=AJA`Qg?a^nM#bK5t3E5c-8A$<;MSYEB@-!J5=c-$O?D4I<}&;PXMdu9FdCQgIsi z8oS2su(JCm9HhjL^C2tY7o3HCv~oUH>eaLa%{T*^4y|^Q0iC@IdLOcRcEg=!xAf%j zi#1oUyTHAPM%=gEpQD?qR%=diFR?lB*A1lZWeqM{KAU$6Ct?ALo2R&2Pfhdt_X4?m zCX=-@UPHzzya;29dO_2)Q)8BCdMeIZG$wkoW93G|-8aa~P#-qLaH9Li4vgG;aWf z&|`CJ6nGf>7XXsKbl>2%?f|`|v%9eFJea@hPI9>?o_OW86b8S}uEGQc_H7={ld#R( z?iE0QX|#VAc8dV;F@*Fa1od=17a*&As}S@hO&jJnUQH(U9{Zqv+%jL<-!oc;-P8{b zda0~qRn?CEByDlXgk8;mS@1$_=Vi=DcM4eCRa0>K>yU`>fhPm8mG!uLSy`Lt{ElT? zG*y5c)2Zc*=WWN*NApzY-qd3vZGj7=tw6kBO<-c@QA$B3vr%_IKk-KrQ|UHTkh31C54JvJ?4hQ?q6s=bhJqKx%es z4cnJOTi+RaYv@6!-3_&~uII{D`3J1y=wj&GOD#Kpec{~;4_fxzYuQsFa%=NaWam;# z$7c!*D=G=MeHMVx)Uk?2cC=p`bi+{=ivIKAmeyq=t}@)=hB}yjsGKPGvCeNThMr#v zZTn9}0g-<@=(fp(kdVoOtRcF3XHwrnB_RBjOA<&0Ts~wN+BTdCucVIym=2&whXy5&pra{(9N^! z?puDX!qmH=yRSngU4&&z06%m3$b~4)=d9>}9xXyui9V@E(|Nl9FBD|;_Vz}9_M{Ho zkG=}r_+1SVH{#)!1{F}b`N_439Y@Zl3LG9@%NEYAg9WGs#0ykC>jkPlnb_jPL)O;Q zpbU^e+d;633bX^pJ}AgIO}p@0_XSpf08P_wB!~jE2gn8-cpRACwlyUhE%u`96p+m^ zps#ea^bG4bv>1A}jsekoEzvp#v~@oM1CG1l-LvQ2@IDsWw>c2ZZYc@xJFK&BF|>aJ z4#?3WNsH2y4Hgu(xo{RdDVnhg&DLqJ1e@_o?} zg7ufl@D{4Q$m?FF~o_UGI$L`Ac@R_mI$Ts3nXbes$jd_5{qNos{C>b6Ahx5TE7EybTH2k*rb_rr&Q&RW-Y zUwz4qC*1I17CQW|d*Q>I2OrZ9mR>ETS?9oF=+LU@!z`NY?k9g}-Y;Jl`^inEpWMUtnC57fI)z-; zw(LAi2Tb!!J`Dp297uPdDXi6WFA~gFin-}MW|~d`-W+KYpoL~n7YmkSnj9o(1f_VD z$44hcf-&oS7s#9B6S==4Hz-ffhCe6JRU&Q5_G{@1f$sf2-j$8d@vCwWXTI*w?C@uf zRA)jk^Sv(!bpC3%Ml491q4U(j`Ehs)QA|RLw@Yz#Pxb#J@INPfPwO`81)ueO^iBE= lkjIpE5a%kAAP9dZ&3`4kU9$Txr0om&q#$hn2SLhL|1VpL&8`3d literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8a7363b27432f02e1e9f69969b8bda403574d7eb GIT binary patch literal 10171 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 literal 0 HcmV?d00001 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?" +}