openai Python SDK, an API key with access to both models, and a spreadsheet for scoring.router.json file that sends each of your task types to the model that actually won in your own test, instead of guessing.
openai (latest version from PyPI).Install and verify the toolchain:
python3 --version
pip install --upgrade openai
python3 -c "import openai; print(openai.__version__)"
Example of expected output:
Python 3.11.6
Successfully installed openai-1.x.x
1.x.x
Then export the connection settings. Replace every placeholder before running anything:
export OPENAI_API_KEY="sk-..." # your key
export OPENAI_BASE_URL="https://api.openai.com/v1" # change only if you use a proxy/gateway
export MODEL_FAST="<gpt-5.5-instant-model-id>" # exact ID from the vendor model page — check the documentation
export MODEL_DEEP="<gpt-6.1-sol-model-id>" # exact ID from the vendor model page — check the documentation
Why this works: the SDK reads the base URL and key from the environment, so the same script runs against the vendor API or your own gateway without code changes. Model IDs are kept in variables because the exact API strings are not stated in the channel posts — take them from the vendor's model list, not from this article.
| Model | Role you are testing | Price per 1M input tokens | Price per 1M output tokens | Context window | Source |
|---|---|---|---|---|---|
| GPT-5.5 Instant | Low-latency chat and everyday tasks | Not stated in source posts — check the documentation | Not stated in source posts — check the documentation | Not stated in source posts — check the documentation | Vendor pricing page |
| GPT-6.1 Sol | API work and heavier reasoning | Not stated in source posts — check the documentation | Not stated in source posts — check the documentation | Not stated in source posts — check the documentation | Vendor pricing page |
Fill the three "not stated" columns from the vendor's pricing and model pages and keep the table next to your results file. Without those numbers you can compare quality and latency but not cost. For background on how Sol differs from an older generation, see GPT-6.1 Sol vs GPT-4o: Model Comparison and Which to Choose; for choosing the chat surface itself (web app vs office suite), see ChatGPT Office vs Microsoft 365 with Copilot: which to choose.
Checklist before Step 1:
python3 --version prints 3.10 or higherpython3 -c "import openai" prints no errorecho $OPENAI_API_KEY prints your key, not an empty lineMODEL_FAST and MODEL_DEEP contain real model IDs
What to do: create tasks.jsonl, one JSON object per line, split into three buckets that match how you will actually use the models: chat (short conversational replies), everyday (summaries, rewrites, email drafting), and api (structured extraction that must return valid JSON).
Why: a single "which model is smarter" question produces an opinion. Thirty identical prompts across your own task types produce a decision. The api bucket matters most: a model that chats well but breaks JSON makes your integration fail.
Example of the first three lines of tasks.jsonl:
{"id": "chat-01", "bucket": "chat", "prompt": "Rewrite in one line: 'The meeting moved to Thursday 3pm.'"}
{"id": "everyday-01", "bucket": "everyday", "prompt": "Summarize this email in three bullets: <paste a real email here>"}
{"id": "api-01", "bucket": "api", "prompt": "Return only JSON with keys name, amount, due_date for: 'Invoice from Acme, 1200 EUR, due 2025-03-01'"}
How to check: run wc -l tasks.jsonl. Expected output is 30 tasks.jsonl (10 lines per bucket). If the count is wrong, add or delete lines before moving on — the script in Step 2 processes every line it finds.
What to do: save this as bench.py and run it. It sends every prompt to both models with temperature=0 and writes one result row per run to results.jsonl.
import json, os, time
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
base_url=os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1"),
)
MODELS = {"instant": os.environ["MODEL_FAST"], "sol": os.environ["MODEL_DEEP"]}
def run(model_id, prompt):
t0 = time.perf_counter()
resp = client.chat.completions.create(
model=model_id,
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
ms = int((time.perf_counter() - t0) * 1000)
return {
"latency_ms": ms,
"prompt_tokens": resp.usage.prompt_tokens,
"completion_tokens": resp.usage.completion_tokens,
"answer": resp.choices[0].message.content,
}
rows = []
with open("tasks.jsonl") as f:
for line in f:
task = json.loads(line)
for label, model_id in MODELS.items():
row = run(model_id, task["prompt"])
row.update({"task_id": task["id"], "bucket": task["bucket"], "label": label})
rows.append(row)
print(task["id"], label, row["latency_ms"], "ms")
with open("results.jsonl", "w") as f:
for row in rows:
f.write(json.dumps(row) + "\n")
Why: temperature=0 makes the output as repeatable as the API allows, so a difference between the two models comes from the model and not from randomness. Reading resp.usage gives you token counts straight from the response, which is the only reliable input for cost math — no estimate needed.
Example of the console lines while it runs:
chat-01 instant 640 ms
chat-01 sol 1880 ms
everyday-01 instant 910 ms
everyday-01 sol 2410 ms
How to check: wc -l results.jsonl must print 60 results.jsonl (30 prompts × 2 models). Then score the answers blind: copy only the answer and task_id fields into a spreadsheet, hide the label column, and rate each 1–5. For the api bucket, score pass/fail instead — pass means json.loads(answer) succeeds and all three required keys exist.
What to do: compute medians per model and per bucket from results.jsonl, then write the winning mapping into router.json.
python3 - <<'PY'
import json, statistics, collections
rows = [json.loads(l) for l in open("results.jsonl")]
lat = collections.defaultdict(list)
for r in rows:
lat[r["label"]].append(r["latency_ms"])
for label, values in lat.items():
print(label, "median latency", statistics.median(values), "ms", "runs", len(values))
PY
Example of the printed line format (your numbers come from your own run):
instant median latency <your-value> ms runs 30
sol median latency <your-value> ms runs 30
Then encode the decision. The mapping below is a template — set it from your own scores, not from this article, because the source posts contain no published benchmark for either model:
{
"routes": {
"chat": "MODEL_FAST",
"everyday": "MODEL_FAST",
"api_extraction": "MODEL_DEEP"
},
"fallback": "MODEL_DEEP",
"note": "mapping taken from results.jsonl run on <date>"
}
Why: routing is the only way to get both benefits — the low-latency model answers conversational traffic while the stronger model handles extraction where a malformed answer costs you a retry. The fallback key prevents a hard failure when the fast model returns an error.
How to check: run one smoke call per route and confirm the model name the API reports back matches the route you configured:
python3 - <<'PY'
import json, os
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"], base_url=os.environ["OPENAI_BASE_URL"])
routes = json.load(open("router.json"))["routes"]
for name, env_key in routes.items():
r = client.chat.completions.create(
model=os.environ[env_key],
messages=[{"role": "user", "content": "Reply with the word OK."}],
temperature=0,
)
print(name, "->", r.model, "|", r.choices[0].message.content.strip())
PY
Success looks like one line per route, each showing the model ID you configured and the word OK:
chat -> <model-id> | OK
everyday -> <model-id> | OK
api_extraction -> <model-id> | OK
If a line shows a different model ID than the one in router.json, your environment variable points at the wrong model — fix the export and rerun.
Problem. The fast model returns 404 on the first call
Error code: 404 - {'error': {'message': 'The model does not exist or you do not have access to it', 'type': 'invalid_request_error'}}MODEL_FAST holds a display name or a typo instead of the exact API model ID, or your key is not enabled for that model.python3 -c "import os; from openai import OpenAI; print([m.id for m in OpenAI(api_key=os.environ['OPENAI_API_KEY']).models.list()])". If the model is absent from that list, request access; if it is present, re-export MODEL_FAST with the printed string and rerun bench.py.Problem. The api bucket keeps failing the JSON check
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0){.json.loads(answer) with a check that strips fences first: text = answer.strip().removeprefix("\\\json").removeprefix("\\\").removesuffix("\\\").strip(), then json.loads(text)`. Record how many retries each model needed — that count belongs in your decision sheet next to latency.Problem. Runs stop halfway with a 429
Error code: 429 - {'error': {'message': 'Rate limit reached for requests', 'type': 'requests'}}client.chat.completions.create(...) call in a retry loop with backoff, e.g. for attempt in range(5): try: ... except openai.RateLimitError: time.sleep(2 ** attempt). Delete the partial results.jsonl before rerunning, otherwise wc -l will report more than 60 rows and your medians will be wrong.You now have a measured routing decision: a results.jsonl file with latency, tokens and blind scores for both models, plus a router.json that applies it. Next, extend the same test set with your production prompts monthly and re-run the script — model behaviour and pricing change, and a routing file without a re-run date becomes a guess; when you move from single calls to multi-step pipelines, continue with AI agents for task automation: a guide.
Which model is cheaper for chat?
The channel posts state no prices for either model. Record the per-1M-token input and output prices from the vendor's pricing page in the decision sheet, multiply by the prompt_tokens and completion_tokens values your own run produced, and compare those totals — check the documentation for the current rates.
Can I call both models with one API key?
Yes, if the key's project has access to both. Verify by listing models with the one-liner in Troubleshooting; if only one ID appears, the second model needs to be enabled for that key.
Does GPT-5.5 Instant return structured JSON reliably?
Not stated in the source posts. That is exactly what the api bucket in Step 1 measures — score it pass/fail over 10 prompts and let the result decide whether extraction traffic goes to the fast model or the stronger one.
How many test prompts do I need before trusting the result?
Thirty is the practical floor used here: ten per bucket, enough to see a consistent latency gap and catch repeated JSON failures. Below ten per bucket a single outlier can flip the median.