{"slug": "one-endpoint-many-models-switching-between-gpt-claude-and-deepseek-and-logging", "title": "One endpoint, many models: switching between GPT, Claude and DeepSeek and logging what every call costs", "summary": "A developer built a small Python harness that routes requests to GPT, Claude and DeepSeek through a single OpenAI-format Chat Completions endpoint, using the openai SDK's configurable base_url and per-model pricing tables to log the dollar cost of every call. The harness computes spend from the usage object's prompt, cached and completion token counts, and the developer notes that tokenizers and default thinking modes make naive per-token estimates unreliable — Anthropic's Claude 4.7+ tokenizer yields roughly 30% more tokens for the same text, and DeepSeek's Flash model runs in thinking mode by default. A sample 2,000-token prompt with a 500-token answer costs $0.00375 on GPT-5.4 mini, $0.009 on Claude Sonnet 5 and $0.0012 on DeepSeek Flash at peak hours, per list prices checked October 23, 2026.", "body_md": "The OpenAI Chat Completions format has quietly become the common plug of the LLM world. DeepSeek documents an OpenAI-format base URL for its own API, most gateways speak it, and the official `openai` Python SDK lets you point `base_url` anywhere. So switching from GPT to Claude to DeepSeek looks like changing one string.\n\nIt mostly is. But \"mostly\" hides the part that shows up on your invoice: the same prompt is a different number of tokens on different models, and some models think before answering and bill you for it. Below is a small harness for comparing models on real prompts instead of on vibes.\n\nKeep the endpoint and key in environment variables and treat the model as a plain argument.\n\n``` python\nimport os\nfrom openai import OpenAI\n\nclient = OpenAI(\n    base_url=os.environ[\"LLM_BASE_URL\"],  # provider or gateway, e.g. https://api.deepseek.com\n    api_key=os.environ[\"LLM_API_KEY\"],\n)\n\ndef ask(model: str, prompt: str, **extra):\n    resp = client.chat.completions.create(\n        model=model,\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n        **extra,\n    )\n    return resp.choices[0].message.content, resp.usage\n```\n\nOne thing does change between endpoints: model IDs. Talking to OpenAI directly you write `gpt-5.4-mini`; many gateways namespace it as `openai/gpt-5.4-mini`. Keep IDs in config, not scattered through the code.\n\n`usage` into money\nEvery response carries a `usage` object, and that is the only number worth trusting. In the OpenAI format, `prompt_tokens` already includes cached tokens (broken out in `prompt_tokens_details.cached_tokens`), and `completion_tokens` already includes reasoning tokens (broken out in `completion_tokens_details.reasoning_tokens`). So the math is: fresh input at the input price, cached input at the cached price, all completion tokens at the output price.\n\nHere are list prices checked on October 23, 2026:\n\n```\nPRICES = {  # USD per 1M tokens, provider list prices as of 2026-10-23\n    \"openai/gpt-5.4-mini\":        {\"in\": 0.75, \"cached\": 0.075, \"out\": 4.50},\n    \"anthropic/claude-sonnet-5\":  {\"in\": 2.00, \"cached\": 0.20,  \"out\": 10.00},\n    \"deepseek/deepseek-v4-flash\": {\"in\": 0.30, \"cached\": 0.006, \"out\": 1.20},  # peak hours\n}\n\ndef cost_usd(model: str, usage) -> float:\n    p = PRICES[model]\n    details = getattr(usage, \"prompt_tokens_details\", None)\n    cached = (getattr(details, \"cached_tokens\", 0) or 0) if details else 0\n    fresh = usage.prompt_tokens - cached\n    return (fresh * p[\"in\"] + cached * p[\"cached\"]\n            + usage.completion_tokens * p[\"out\"]) / 1_000_000\n```\n\nA quick sanity check with a 2,000-token prompt and a 500-token answer: GPT-5.4 mini comes to $0.00375 per call, Claude Sonnet 5 to $0.009, DeepSeek Flash to $0.0012 at peak hours. Over 10,000 calls that is $37.50, $90 and $12.\n\nThose numbers are almost certainly wrong for your workload. The 2,000 tokens will not be 2,000 on every model: Anthropic says the tokenizer in Claude 4.7 and later produces roughly 30% more tokens for the same text. And the 500-token answer assumes no hidden reasoning, while DeepSeek's docs say its Flash model runs in thinking mode by default.\n\nThe table also skips Anthropic's cache writes (1.25x the input price) and DeepSeek's off-peak rates (half of peak; peak is 01:00–04:00 and 06:00–10:00 UTC on weekdays). Add fields if they matter to you.\n\nTake 20 to 50 prompts from real traffic, run each through every candidate model, and write one CSV row per call:\n\n``` python\nimport csv, time\n\nprompts = [line.strip() for line in open(\"prompts.txt\", encoding=\"utf-8\") if line.strip()]\n\nwith open(\"compare.csv\", \"w\", newline=\"\", encoding=\"utf-8\") as f:\n    w = csv.writer(f)\n    w.writerow([\"model\", \"prompt_tokens\", \"completion_tokens\",\n                \"reasoning_tokens\", \"seconds\", \"usd\", \"answer\"])\n    for model in PRICES:\n        for prompt in prompts:\n            t0 = time.perf_counter()\n            text, usage = ask(model, prompt)\n            dt = time.perf_counter() - t0\n            cd = getattr(usage, \"completion_tokens_details\", None)\n            reasoning = (getattr(cd, \"reasoning_tokens\", 0) or 0) if cd else 0\n            w.writerow([model, usage.prompt_tokens, usage.completion_tokens,\n                        reasoning, round(dt, 2), round(cost_usd(model, usage), 6), text])\n```\n\nBefore judging quality, look at three columns. `prompt_tokens` shows the tokenizer difference on your own text, `reasoning_tokens` shows which models think when you did not ask them to, and `usd` gives a real cost per call. The answer sits in the same row, so you judge quality next to price.\n\nParameters are less portable than the endpoint. Some models reject options that others accept, reasoning models especially around `temperature` and output limits. Keep a small `overrides` dict per model and pass it through `**extra` instead of sprinkling `if model.startswith(...)` around.\n\nCaching differs too: OpenAI caches prefixes automatically, Anthropic needs `cache_control`. If `cached_tokens` stays at zero on a long system prompt, you pay full price for it on every call.\n\nPrices move. Anthropic's pricing page currently notes that Claude Sonnet 5's launch price, announced as introductory, has become the standard one. Date your price table and re-check it before you trust last month's numbers.\n\nAnd if you go through a gateway, its bill is the truth, not the list price. List prices are fine for comparing models; for real spend, reconcile against the gateway's usage export.\n\nUnder 50 lines, and arguments about which model is cheaper turn into a CSV you can sort.\n\n*Disclosure: I'm building Heli (getheli.ru), an OpenAI-compatible gateway for developers in Russia that bills in rubles. The code above works with it if you set `LLM_BASE_URL=https://getheli.ru/v1`, but nothing here depends on it.*\n\n*This post was drafted with the help of an AI assistant and reviewed and edited by me.*", "url": "https://wpnews.pro/news/one-endpoint-many-models-switching-between-gpt-claude-and-deepseek-and-logging", "canonical_source": "https://dev.to/x3team/one-endpoint-many-models-switching-between-gpt-claude-and-deepseek-and-logging-what-every-call-139", "published_at": "2026-10-23 15:09:17+00:00", "updated_at": "2026-10-23 15:13:07.790919+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools", "ai-infrastructure", "developer-tools"], "entities": ["OpenAI", "Anthropic", "DeepSeek", "GPT-5.4 mini", "Claude Sonnet 5", "DeepSeek V4 Flash"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/one-endpoint-many-models-switching-between-gpt-claude-and-deepseek-and-logging", "markdown": "https://wpnews.pro/news/one-endpoint-many-models-switching-between-gpt-claude-and-deepseek-and-logging.md", "text": "https://wpnews.pro/news/one-endpoint-many-models-switching-between-gpt-claude-and-deepseek-and-logging.txt", "jsonld": "https://wpnews.pro/news/one-endpoint-many-models-switching-between-gpt-claude-and-deepseek-and-logging.jsonld"}}