Моделі та порівнянняПосібник
Порівняйте дві LLM чесно на своєму завданні: обмін сторонами та журнал витрат
Проведіть порівняння віч-на-віч двох моделей на власних тікетах: однакові налаштування, кожна пара оцінюється в обох порядках, і журнал calls.jsonl, який дає вартість прогону.

На цій сторінці
Ви прогнали три запити через дві моделі, і друга перемогла. Перш ніж довіряти цьому, перевірте, яка з них була другою в списку. Для чесного порівняння потрібні три речі: однакові налаштування для обох моделей, кожна пара оцінюється в обох порядках, і журнал токенів на кожен виклик, щоб ви знали, скільки коштував весь прогін. Нижче наведено невеликий скрипт Python, який робить усі три речі на вашому власному завданні.
Що робить публічний турнір, що ви можете запозичити#
Clocktower Bench від Dwarkesh Patel — це неофіційний дослідницький проєкт, де мовні моделі грають у соціальну дедуктивну гру Blood on the Clocktower одна проти одної. Гра тут не головне; головне — це тестовий стенд. Згідно з сторінкою About проєкту (перевірено 8 листопада 2026 року), він робить кілька речей, які має робити будь-яке порівняння віч-на-віч:
- одна модель грає всі добрі місця, а інша — всі злі, і кожна роздача грається двічі з обміном сторонами, щоб щасливе місце не вирішувало результат;
- усі моделі отримують однакові налаштування: однакові зусилля міркування, ліміт 8 000 токенів на хід і обмеження мовлення у 150 слів;
- Storyteller запрограмований, тому жодна модель не судить гру;
- кожен виклик API записується в
calls.jsonlз його токенами та вартістю. На сторінці сказано, що одна гра коштує від приблизно $2 до $50 залежно від моделей (на API OpenAI та Anthropic).
Стенд створений для моделей OpenAI та Anthropic, і ваше завдання, ймовірно, не настільна гра. Але три звички переносяться: рівні налаштування, обмін сторонами та журнал витрат. Решта цього посібника застосовує їх до чогось звичайного.

Завдання: складання чернеток відповідей на 30 тікетів підтримки#
Припустімо, ви керуєте невеликою службою підтримки і хочете знати, чи DeepSeek-V4-Flash, чи GLM-5.3-Flash пише кращі перші чернетки. У вас є файл tickets.jsonl з 30 реальними тікетами, по одному JSON-об'єкту на рядок: {"id": 1, "text": "..
API сумісний з OpenAI, тому офіційний пакет openai працює з CheapTokens за адресою https:/. Використовуйте повні ідентифікатори моделей з документації: deepseek- та zai-. Обидві моделі також отримують третю модель як суддю, MiniMaxAI/, щоб жоден учасник не оцінював власну роботу.
Почніть з частини, яка робить журнал можливим: одна функція, через яку проходить кожен виклик.
import json, os, re
from openai import OpenAI
client = OpenAI(base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"])
CONTESTANTS = {"deepseek": "deepseek-ai/DeepSeek-V4-Flash-0731",
"glm": "zai-org/GLM-5.3-Flash"}
JUDGE = "MiniMaxAI/MiniMax-M2.7"
SETTINGS = {"temperature": 0.2, "max_tokens": 800} # same for both
def call(model, messages, role, ticket_id, **settings):
r = client.chat.completions.create(model=model, messages=messages, **settings)
with open("calls.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps({
"role": role, "ticket": ticket_id, "model": model,
"input_tokens": r.usage.prompt_tokens,
"output_tokens": r.usage.completion_tokens,
"finish": r.choices[0].finish_reason}) + "\n")
return r.choices[0].message.content or ""
def draft(model, ticket):
msgs = [{"role": "system", "content": "You answer support tickets. "
"Reply in under 80 words, politely, with no promises about refunds."},
{"role": "user", "content": ticket["text"]}]
return call(model, msgs, "draft", ticket["id"], **SETTINGS)Поля в calls.jsonl — це ті, які вибирає цей скрипт; числа використання надходять з об'єкта usage відповіді API. Обидві моделі отримують однаковий системний запит, однаковий temperature та однаковий max_tokens. Змініть одне з них для однієї моделі — і ви порівнюєте налаштування, а не моделі.
Обмін сторонами: оцінюйте кожну пару двічі#
Тепер суддя. Модель-суддя може схилятися до відповіді в певному слоті, тому один вердикт змішує якість із позицією. Трюк Clocktower підходить ідеально: покажіть кожну пару в обох ролях. Відповідь виграє тікет, лише якщо суддя вибирає її в обох порядках. Якщо вердикт слідує за слотом, а не за відповіддю, тікет — нічия.
def judge(ticket, first, second):
prompt = (f"Ticket:\n{ticket['text']}\n\nReply A:\n{first}\n\nReply B:\n{second}\n\n"
"Which reply is better for the customer? Finish with a last line: Verdict: A or Verdict: B.")
out = call(JUDGE, [{"role": "user", "content": prompt}], "judge",
ticket["id"], temperature=0, max_tokens=1000)
letters = re.findall(r"Verdict:\s*([AB])\b", out)
return letters[-1] if letters else None
tickets = [json.loads(l) for l in open("tickets.jsonl", encoding="utf-8")]
wins = {"deepseek": 0, "glm": 0, "tie": 0}
for t in tickets:
a = draft(CONTESTANTS["deepseek"], t)
b = draft(CONTESTANTS["glm"], t)
v1 = judge(t, a, b) # DeepSeek's reply shown as A
v2 = judge(t, b, a) # GLM's reply shown as A
pick1 = {"A": "deepseek", "B": "glm"}.get(v1)
pick2 = {"A": "glm", "B": "deepseek"}.get(v2)
wins[pick1 if pick1 and pick1 == pick2 else "tie"] += 1
print(wins)Дві деталі легко пропустити. Суддя працює при temperature=0, щоб його вердикти були стабільнішими; не кожна модель дотримується цього налаштування, тому перезапустіть кілька пар і порівняйте вердикти, перш ніж довіряти їм. Вердикт читається лише з фінального рядка Verdict:, а обмеження щедрі (800 для чернеток, 1000 для судді), щоб модель, яка міркує перед відповіддю, не обривалася посеред думки; оцінка нижче використовує типові розміри, а не обмеження. Нерозбірлива відповідь дає None, що перетворюється на нічию, а не зриває прогін.
Спробуйте саміСпробуйте: суддя сказав "A" в обох порядках для тікета 7. Хто виграє цей тікет?Показати відповідь
Ніхто. У першому порядку A була відповіддю DeepSeek, у другому порядку A була відповіддю GLM, тому суддя віддав перевагу слоту, а не моделі. Код зараховує це як нічию. Купа таких нічиїх сама по собі є результатом: дві моделі близькі на вашому завданні, або ваш запит до судді занадто розпливчастий.
Порядок — це лише один спосіб викривити тест. Налаштування, запити та журналювання також можуть його викривити, тому перевіряйте всю установку, а не лише те, хто йде першим. Ось той самий експеримент до і після.
Порівняйте
Що змінюється, коли ви припиняєте порівнювати недбало
Недбало
Один запит на модель, залишені за замовчуванням налаштування, пара показана в одному порядку, і рахунок зчитується з панелі в кінці місяця. Переможцем може бути модель, яка випадково була вказана першою.
Чесне порівняння
Один спільний системний запит і однакові temperature та max_tokens, кожна пара оцінюється в обох порядках, і рядок у calls.jsonl для кожного виклику. Переможець — той, хто виграє з обох місць.
Журналюйте кожен виклик, потім оцініть вартість прогону#
Журнал — це те, що перетворює порівняння на бюджет. Після прогону підсумуйте токени за моделлю:
from collections import defaultdict
totals = defaultdict(lambda: [0, 0, 0])
for line in open("calls.jsonl", encoding="utf-8"):
c = json.loads(line)
s = totals[c["model"]]
s[0] += c["input_tokens"]; s[1] += c["output_tokens"]; s[2] += 1
for model, (tin, tout, n) in totals.items():
print(model, n, "calls", tin, "input", tout, "output")Ви отримаєте один рядок на модель із кількістю викликів, вхідними токенами та вихідними токенами. Помножте на дві ціни моделі (вхід і вихід оплачуються окремо) або, простіше, вставте підсумки в живий маркер вартості.
Ви також можете оцінити перед запуском. Припустімо, кожен тікет плюс системний запит — це близько 300 вхідних токенів, чернетка — близько 250 вихідних токенів, а суддя бачить приблизно 800 вхідних токенів (тікет плюс обидві відповіді плюс питання) і відповідає приблизно 200 токенами (рядок вердикту плюс трохи місця для міркування):
- чернетки: 30 тікетів × 2 моделі = 60 викликів, 30 × 300 = 9000 вхідних і 30 × 250 = 7500 вихідних токенів на учасника;
- вердикти: 30 тікетів × 2 порядки = 60 викликів, 60 × 800 = 48000 вхідних і 60 × 200 = 12000 вихідних токенів на суддю.
За поточними цінами 30 чернеток одного учасника коштують $0.00089 на DeepSeek-V4-Flash і $0.00048 на GLM-5.3-Flash. 60 вердиктів на MiniMax-M2.7 коштують $0.0042. Додайте три — і ви маєте ціну всього порівняння, перш ніж надіслати жодного запиту. Те саме навантаження зі складання чернеток для всіх трьох моделей, з урахуванням вхідних і вихідних токенів разом:
| Модель | Вхід $/1М | Вихід $/1М | Вартість | OpenRouter |
|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.00048 | $0.00092 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.00089 | $0.0017 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.0016 | $0.0189 |
9 000 вхідних і 7 500 вихідних токенів загалом. Ціни за 1 млн токенів актуальні та змінюються разом із нашими тарифами; стовпець OpenRouter показує довідкові ціни для того самого навантаження.
Таблиця також показує довідку OpenRouter, щоб ви могли побачити, як порівнюється загальна сума. Припущення щодо токенів — ваші, їх можна змінювати: якщо ваші тікети довгі, вхід судді зростає найшвидше, бо він містить обидві відповіді двічі. Якщо ваш суддя міркує вголос перед відповіддю, його вихідне число також зростає, і ваш журнал це покаже. Про звичку рахувати токени перед запитом дивіться count tokens before you send a request.
Той самий ліміт, який використовує Clocktower, варто запозичити. Він встановлює 8 000 токенів на хід, і у вас уже є max_tokens. Помістіть його в SETTINGS, і неконтрольована відповідь не зможе тихо помножити вашу оцінку.
Перед запуском повного прогону#
Чек-лист
Передполітна перевірка для чесного порівняння
Останній пункт важливий. Прогоніть три тікети, відкрийте calls.jsonl і прочитайте його. Перевірте, що finish каже stop, а не length; якщо чернетку було обрізано через max_tokens, одна модель може програвати лише тому, що її відповідь була обрізана. Те саме стосується викликів судді: length там перетворюється на тиху нічию, тому порахуйте й вердикти None. Якщо ваше завдання має єдину правильну відповідь, вам, можливо, взагалі не потрібен суддя: test your prompt on 20 cases with exact-match grading.
Читання результату без самообману#
Подивіться на три числа в wins. Тридцять тікетів — це мала вибірка: розподіл, як-от 12 проти 9 із 9 нічиїми, говорить вам, що моделі близькі, а не що одна краща. Журнал тут допомагає. Якщо одна модель перемагає трохи, але використовує набагато більше вихідних токенів, справжнє питання — це ціна завдання, а не кількість перемог; price per 1M tokens versus cost per task розглядає це порівняння.
Якщо результат близький, виберіть дешевшу модель для завдання або додайте більше тікетів і повторіть прогін. Обидва варіанти дешеві для спроби, бо вся механіка вже є циклом. Коли ви захочете повторити вправу на іншій моделі, три моделі вище стоять за одним API та одним ключем: налаштування в docs однакові для кожної, а pricing page перелічує, скільки кожна коштує прямо зараз. Власний висновок Dwarkesh Patel щодо моделей OpenAI проти Anthropic (виявлення проти обману) — це його результат для його гри; ваш походитиме з ваших тікетів.



