Модели и сравненияИнструкция
Честное сравнение двух LLM на своей задаче: swap и лог затрат
Сравните две модели на своих тикетах: одинаковые настройки, каждая пара оценивается в обоих порядках, а лог calls.jsonl даёт стоимость прогона.

Содержание
Вы прогнали три промпта через две модели, и выиграла вторая. Прежде чем верить, проверьте, какая стояла второй. Честному сравнению нужны три вещи: одинаковые настройки у обеих моделей, оценка каждой пары в обоих порядках и лог токенов по каждому вызову — чтобы знать, сколько стоил весь прогон. Ниже небольшой скрипт на Python, который делает всё это на вашей задаче.
Что делает публичный турнир и что можно взять себе#
Clocktower Bench Дваркеша Пателя — неофициальный исследовательский проект, где языковые модели играют друг против друга в «Кровь на часовой башне». Сама игра тут не важна, важна его обвязка. По странице About (проверено 8 ноября 2026) проект делает то, что стоит делать в любом сравнении:
- одна модель играет за всех «добрых», другая за всех «злых», а каждая раздача проигрывается дважды с обменом сторон, так что удачное место не решает исход;
- настройки у всех моделей одинаковые: то же усилие рассуждения, потолок 8 000 токенов на ход и лимит 150 слов на реплику;
- ведущий игры — скрипт, ни одна модель не судит партию;
- каждый вызов 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:/. Берите полные id моделей из документации: 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, который попадает в ничью, а не роняет весь прогон.
Попробуйте самиПопробуйте: по тикету 7 судья в обоих порядках сказал «A». Кто выиграл тикет?Показать ответ
Никто. В первом порядке A был ответом DeepSeek, во втором — ответом 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. Сложите три числа — и цена всего сравнения известна до первого запроса. Та же нагрузка черновиков на всех трёх моделях, с входом и выходом вместе:
| Модель | Вход $/1M | Выход $/1M | Стоимость | 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 выходных токенов всего. Цены за 1M токенов актуальны и меняются вместе с нашими тарифами; столбец OpenRouter показывает справочные цены для той же нагрузки.
В таблице есть и справочная цена OpenRouter, так что видно, как выглядит итог. Допущения по токенам меняйте под себя: если тикеты длинные, быстрее всего растёт вход судьи — в нём оба ответа, причём дважды. Если судья рассуждает вслух перед ответом, растёт и его выход, и ваш лог это покажет. О привычке считать токены до запроса — в статье как посчитать токены перед запросом.
Стоит перенять и ограничитель из Clocktower: там потолок 8 000 токенов на ход, а у вас есть max_tokens. Положите его в SETTINGS, и слишком длинный ответ не раздует смету незаметно.
Перед полным прогоном#
Чек-лист
Предполётная проверка честного сравнения
Последний пункт важен. Прогоните три тикета, откройте calls.jsonl и прочитайте. Проверьте, что в finish стоит stop, а не length: если черновик обрезан по max_tokens, одна модель может проигрывать лишь потому, что её ответ оборвался. То же с вызовами судьи: length там превращается в молчаливую ничью, поэтому считайте и вердикты None. А если у вашей задачи один правильный ответ, судья может и не понадобиться: проверьте промпт на 20 примерах с точным совпадением.
Как читать результат и не обмануть себя#
Посмотрите на три числа в wins. Тридцать тикетов — маленькая выборка: разброс вроде 12 к 9 при 9 ничьих говорит, что модели близки, а не что одна лучше. Здесь помогает лог. Если одна модель выигрывает чуть-чуть, но пишет заметно длиннее, вопрос уже в цене задачи, а не в числе побед; как это сравнивать, разобрано в статье цена за 1M токенов и стоимость задачи.
Если результат близкий, берите для задачи более дешёвую модель или добавьте тикетов и повторите. Попробовать оба варианта недорого: вся механика уже цикл. Когда захотите повторить упражнение с другой моделью, все три модели доступны через один API и один ключ: настройка в документации одинакова для каждого, а на странице цен видно, сколько каждая стоит сейчас. Вывод Дваркеша Пателя про модели OpenAI и Anthropic (обнаружение против обмана) — результат его игры; ваш результат даст ваша выборка тикетов.



