LLM evaluation and benchmarking
An LLM evaluation freezes the question set and prompt template, then has every model under test answer each question several times under the same conditions: questions with an answer key are scored by rule, and open-ended answers are scored against a rubric by a judge model from another vendor. Then you compare each model’s score, latency and cost. One API key calls every model under test. For two full runs a month, each with 1,000 questions and two models answering every question 3 times, at live prices in each model’s cheapest group, model costs are about ¥776 a month, or about ¥388 per run.
Prices updated Nov 13, 2026, 6:13 PM
Monthly cost estimate
Assumptions: two full runs a month; each run has 1,000 questions, and two models under test answer every question 3 times at about 800 input and 1,200 output tokens (including reasoning tokens); half the questions are open-ended, and the judge scores each answer at about 2,000 input tokens (rubric, question, reference and answer) and 200 output tokens.
¥775.68per month
- Under test: flagship model¥624
- Under test: mainstream and small model¥94.08
- Judge-model scoring¥57.6
The business problem
Choosing a model, writing a paper or picking a vendor all mean comparing several models’ quality, cost and speed on the same questions. Public leaderboards use questions that are not your task, scored by others with their own prompts and parameters, so the numbers rarely transfer.
Running your own evaluation is hard in the details: APIs and parameters differ between vendors, and reasoning models often reject temperature; the version behind a model name changes; thousands of requests sent at once hit rate limits; and open-ended answers need a person or a judge model to score them, whose scores must hold up too.
One OpenAI-compatible API calls every model under test, so the same script changes only the model parameter. Give each evaluation run its own API keys, and the console’s Logs filtered by them show what the run cost and the tokens and latency of every request.
Recommended models
Recommended models are picked from the live catalog; prices are each model's cheapest group, updated Nov 13, 2026, 6:13 PM. Click a model to see every group's price.
Under test: flagship and reasoning models
gpt-5.5OpenAI
input ¥10 / output ¥80 per 1M tokensStandard group
Each vendor’s flagship and reasoning models, often the strong baseline in a paper. The price is high and reasoning tokens count as output, so this is usually the biggest line of a run; try a few dozen questions first and estimate from the real usage.
Under test: mainstream and small models
deepseek-v4.1-flashDeepSeek
input ¥2.8 / output ¥11.2 per 1M tokensChina Models group
How far a much cheaper model gets on your questions is usually the question that decides the choice. The alternatives include Chinese and international mainstream and small models; the same script takes one more model parameter.
Judge model (LLM-as-a-judge)
Claude Sonnet 5.5Anthropic
input ¥3.2 / output ¥16 per 1M tokensClaude Lite group
Scores only open-ended answers without an answer key. Pick a vendor that is not under test to lower the chance of a judge favoring its own family; if a model from the same vendor is under test, switch to an alternative. Check the judge against human scores before relying on it.
Reference architecture
- 1
Freeze the test set
Keep questions, answer keys or rubrics and the prompt template as numbered JSONL under version control with the script, and do not change them during the evaluation. Public benchmark questions may be in a model’s training data, so add some new questions of your own that have never been published.
- 2
Fix the conditions
Use one system prompt for every model; set temperature 0 and a fixed seed where supported, and drop and record them where not. Give the run its own API keys: a quota cap equal to the run’s budget, and model limits that allow only the models the run calls. The group decides the line and the price, so keep each model on one group for the whole run and check the group of every record in Logs afterwards.
- 3
Log every call
Record the requested model ID, the model named in the response, the parameters actually sent, call time, latency, finish_reason, usage and raw output. Before starting, save the list from /v1/models of the models this key can call.
- 4
Score by rule first
Multiple-choice, numeric and exact-match short answers are scored in code against the key: stable, reproducible and free.
- 5
Judge-model scoring
Open-ended answers are scored against a written rubric and a reference answer, with model names hidden; for pairwise comparisons, judge each pair twice with the order swapped. Have people score about 100 answers independently and compare them with the judge; use it only once agreement meets the standard of your field.
- 6
Limit concurrency and summarize
Cap requests in flight with a semaphore, start at 8 and adjust by the share of 429 responses; the SDK retries with backoff, and questions that still fail are rerun, not dropped from the statistics. Summarize each model’s mean score and spread, latency and tokens; the run’s total cost is Spend in period in Logs, filtered by the run’s key names.
Cost estimate
Assumptions: two full runs a month; each run has 1,000 questions, and two models under test answer every question 3 times at about 800 input and 1,200 output tokens (including reasoning tokens); half the questions are open-ended, and the judge scores each answer at about 2,000 input tokens (rubric, question, reference and answer) and 200 output tokens.
| Item | Model | Usage assumed | Cost |
|---|---|---|---|
| Under test: flagship model | gpt-5.5 | 6,000 × (800 input + 1,200 output tokens) | ¥624 |
| Under test: mainstream and small model | deepseek-v4.1-flash | 6,000 × (800 input + 1,200 output tokens) | ¥94.08 |
| Judge-model scoring | Claude Sonnet 5.5 | 6,000 × (2,000 input + 200 output tokens) | ¥57.6 |
| Total (per month) | ¥775.68 | ||
Calculated from live prices in each model's cheapest group, updated Nov 13, 2026, 6:13 PM.
Model usage only; human scoring is not included. Each additional model under test adds a line at its own price, and twice the questions or repeats means roughly twice the cost. Reasoning tokens vary widely between models, so run 50 questions first and extrapolate from the real usage. The estimate uses each model’s cheapest group, which may include mixed sources; when results go into a paper and you need to state where the model came from, use groups labeled Official, which cost more; see the group prices on each model’s page.
Sample code
A semaphore caps concurrency and every model answers each question 3 times; answers with a key are scored by rule, and open-ended answers go to the judge with the model name hidden. Models that reject temperature get it dropped automatically, and the parameters actually sent and any failed questions are written to the results file.
import asyncio
import json
import os
import re
import time
from openai import AsyncOpenAI, BadRequestError
client = AsyncOpenAI(base_url="https://www.tutujin.com/v1", api_key=os.environ["TUTUJIN_API_KEY"], max_retries=4)
MODELS = ["gpt-5.5", "deepseek-v4.1-flash"]
JUDGE = "claude-sonnet-5-5" # from a vendor that is not under test
REPEATS = 3
SYSTEM = "Answer the question. End with a last line of the form 'Answer: <answer>'."
limit = asyncio.Semaphore(8) # requests in flight; raise it slowly while watching for 429
async def ask(model, messages, params):
async with limit:
started = time.time()
try:
resp = await client.chat.completions.create(model=model, messages=messages, **params)
except BadRequestError:
# many reasoning models reject temperature: retry without it and keep what was really sent
params = {k: v for k, v in params.items() if k != "temperature"}
resp = await client.chat.completions.create(model=model, messages=messages, **params)
return resp, params, round(time.time() - started, 2)
async def judge(item, answer):
"""Open-ended answers: 0-5 against a written rubric, without the name of the model that wrote them."""
prompt = (
f"Question:\n{item['prompt']}\n\nReference answer:\n{item['reference']}\n\nRubric:\n{item['rubric']}\n\n"
f"Answer to score:\n{answer}\n\n"
'Score the answer from 0 to 5 by the rubric only. Reply with JSON only: {"score": 0, "reason": ""}'
)
resp, _, _ = await ask(JUDGE, [{"role": "user", "content": prompt}], {"temperature": 0})
raw = resp.choices[0].message.content
return json.loads(raw[raw.find("{"): raw.rfind("}") + 1])["score"] / 5
async def run_one(model, item, rep):
messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": item["prompt"]}]
resp, params, latency = await ask(model, messages, {"temperature": 0, "seed": 7})
text = resp.choices[0].message.content or ""
if "answer" in item: # questions with a key are scored by rule
m = re.search(r"Answer:\s*(.+?)\s*$", text)
score = float(bool(m) and m.group(1).strip(" *.").lower() == item["answer"].lower())
else:
score = await judge(item, text)
return {
"item": item["id"], "repeat": rep, "model": model, "returned_model": resp.model, "params": params,
"score": score, "scored_by": "rule" if "answer" in item else JUDGE, "latency_s": latency,
"finish_reason": resp.choices[0].finish_reason, "usage": resp.usage.model_dump() if resp.usage else None,
"called_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "output": text,
}
async def main(path="testset.jsonl"):
items = [json.loads(line) for line in open(path, encoding="utf-8")]
jobs = [(m, it, r) for m in MODELS for it in items for r in range(REPEATS)]
results = await asyncio.gather(*(run_one(*j) for j in jobs), return_exceptions=True)
with open("results.jsonl", "a", encoding="utf-8") as f:
for (m, it, r), res in zip(jobs, results):
row = res if isinstance(res, dict) else {"item": it["id"], "repeat": r, "model": m, "error": repr(res)}
f.write(json.dumps(row, ensure_ascii=False) + "\n")
for m in MODELS:
ok = [x for x in results if isinstance(x, dict) and x["model"] == m]
failed = sum(1 for (jm, _, _), x in zip(jobs, results) if jm == m and not isinstance(x, dict))
print(m, "mean score", round(sum(x["score"] for x in ok) / max(len(ok), 1), 3),
"| mean latency", round(sum(x["latency_s"] for x in ok) / max(len(ok), 1), 2), "s",
"| failed, rerun these:", failed)
asyncio.run(main())Try this with one key
Sign up, create an API key in the console, and put your base_url and key into the code above. For company purchasing or invoices, add our WeChat support JsnonoChat.

Scan the code or search the WeChat ID to add us
FAQ
What does one full evaluation run cost?
For 1,000 questions, two models answering each question 3 times and the judge scoring half of them, the cheapest groups come to about ¥388 per run. Cost is driven by questions × models × repeats × output length, and the reasoning tokens of flagship models are often the largest part.
Can I trust the judge model’s scores?
Not without checking. Known biases include favoring longer answers, being swayed by the order answers appear in, possibly favoring models from its own vendor, and misjudging specialist content. Write a specific rubric, hide model names, judge pairs twice with the order swapped, and measure agreement on a sample scored by people; if a question can be scored by rule, do not send it to the judge.
With temperature 0 and a seed, are results fully reproducible?
No. A seed only brings outputs for the same input closer together and does not guarantee identical results, and reasoning models usually do not support temperature. Record the parameters actually sent, run each question several times and report the mean and spread rather than a single run.
How much concurrency can I use? Will I be rate-limited?
Start at 8 concurrent requests and raise it while watching the share of 429 responses; back off and retry on 429, and record questions that still fail and rerun them instead of dropping them from the statistics. Different groups use different lines, so rate limits can differ too. For sustained higher concurrency, add our WeChat support JsnonoChat with the models, groups and expected request volume, and we will assess it and reply.
How do I report which model I used in a paper?
Give the full model ID (names with a date suffix usually point to a fixed snapshot), the date range of the calls, the group and parameters, the prompt template, the number of repeats and the scoring method, and keep the model name returned in each response. The version behind a model name can change, so record everything again when you rerun later.
Can the model comparison pages replace an evaluation?
No. The comparison pages list facts such as prices, input and output types, endpoints and listing dates, with no quality scores; quality has to be measured on your own test set.