Skip to content

fix(llm): --provider openai sends a model litellm does not know as OpenAI's to the OpenAI endpoint - #256

Open
vlad-perevezentsev wants to merge 15 commits into
intel:mainfrom
vlad-perevezentsev:fix/openai-compatible-model-route
Open

vlad-perevezentsev wants to merge 15 commits into
intel:mainfrom
vlad-perevezentsev:fix/openai-compatible-model-route

Conversation

@vlad-perevezentsev

@vlad-perevezentsev vlad-perevezentsev commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #136. With --provider openai, a model name that litellm does not take for OpenAI's now goes out as openai/<model>, which means to OPENAI_API_BASE. A name litellm already sends to OpenAI keeps its route. On provider openai the response-cache key holds the model name as given, as on main, so every stored entry still hits.

The --judges spec is split correctly. The provider is lost in llm.resolve_model, which sent every openai model to litellm bare. litellm infers OpenAI only for names on its own lists and for ft:gpt-* fine-tunes. It refused any other name before sending a request ("LLM Provider NOT provided"): glm-5.3 behind OPENAI_API_BASE, meta-llama/x, a model newer than the installed litellm. A bare name it assigns to another cloud (claude-*, amazon.nova-pro-v1:0, gemini-*) went to that cloud with the OpenAI key. Every --provider openai / --judge-provider openai path goes through resolve_model: answers, judge, panel, novelty, question and persona generation.

 resolve_model(model, "openai")
-  "/" in model  -> model
-  else          -> model
+  "/" in model  -> model if the prefix is a litellm provider (its provider_list or JSON registry: openai/, azure/, ollama/, crusoe/ …), else openai/<model>
+  else          -> model if litellm takes it for OpenAI's (its chat or text-completion lists, ft:gpt-3.5-turbo / ft:gpt-4 fine-tunes), else openai/<model>

The openai/openai/<model> workaround keeps working.

Also:

  • Response-cache key on provider openai (6296697): the model name as given, not the route. The route of a new model depends on litellm's price map. The remote map and the installed backup (LITELLM_LOCAL_MODEL_COST_MAP=True, or offline) disagree: gpt-6.1-terra is on the remote OpenAI list (206 names) and not on the backup (197), so it goes out bare under one and as openai/gpt-6.1-terra under the other. Both reach the same endpoint, and the key stays the same under either map. It is also the key main wrote, because main sent every openai name bare.
  • docs/reviewing.md (the README paragraph moved there in docs: illustrated intro on docs home, short README with start-here #271): how to use an OpenAI-compatible server. It covers OPENAI_API_BASE / OPENAI_BASE_URL and which names reach the server. A name with a litellm provider prefix (deepseek/, groq/, ollama/) keeps that route, so it should be written openai/<name>. OPENAI_API_KEY must be set even if the server ignores it. Cost is unknown for such a model, so novelty --max-usd stops after one call.
  • llm.py is at its growth baseline: two passages that repeat others are dropped (the docstring's closing paragraph and a comment), and the file stays at 1118 lines.

Evidence

Live eval cross-judge (3 questions, 2 arms) against a loopback stub /v1/chat/completions via OPENAI_API_BASE. On main b226dba, --judges openai/glm-5.3 gives skill: NOT MEASURED and exit 0, and litellm prints its "Provider List" banner 12 times. "After" is head 6296697.

--judges requests before requests after
openai/glm-5.3 0 6
openai/meta-llama/Llama-3.1-8B 0 6
openai/amazon.nova-pro-v1:0 0 (sent to Bedrock) 6
openai/openai/glm-5.3 (workaround) 6 6
openai/gpt-6.1-sol (control) 6 6
openai/ft:gpt-4o-mini:org::id (control) 6 6, model sent bare

With --provider openai on main and the transport intercepted (litellm 1.106.0), a bare name of another cloud reached it with the OpenAI key:

bare name OPENAI_API_KEY set unset
claude-sonnet-4-5 api.anthropic.com, x-api-key: <OpenAI key> ANTHROPIC_API_KEY
amazon.nova-pro-v1:0 bedrock-runtime, Authorization: Bearer <OpenAI key> AWS SigV4

Tests:

  • tests/test_openai_compatible_route.py (new) runs the production adapter with litellm's real provider lookup (mock_response, nothing sent). It covers LLM(...).ask for names that get openai/ and for names kept bare, eval cross-judge --judges openai/glm-5.3 end to end, and a reply stored with the model on the price map that replays once it is off the map.
  • tests/test_llm.py: route rows for the prefixed names (including claude-* and a bare Bedrock id) and for the names that must not change (gpt-6.1-sol, gpt-3.5-turbo-instruct, both ft: families, openai/glm-5.3, azure/gpt-4o, ollama/llama3, crusoe/x). test_an_entry_main_wrote_is_hit gets main's key for gpt-6.1-terra, which is routed openai/ under the installed map.
  • tests/test_grader_characterization.py (refactor(eval): grade the judge and panel loops with llm.ask_parsed #251) is as on main: its pinned cache-key file names hold for gpt-x, now routed openai/gpt-x.
  • On main: 10 failed, 101 passed (BadRequestError: LLM Provider NOT provided, and the routes). On this branch: 111 passed.
  • Each mutation fails the tests: dropping the text-completion list, dropping the ft: test, loosening it to any ft:, always prefixing, taking any / as a route, keeping bare names off the lists, keying the cache on the route. Replacing the provider_list test with in ("openai", "azure") is caught by the ollama/ row.
  • make ci-local at 6296697: 27 passed, 1 failed, 7 skipped (pytest 10146 passed). The FAIL is pip-audit, which reports filelock 3.32.7 advisories from the lock files; this PR does not touch them. The docker smoke passed in round 1 and was not rerun, since nothing docker-related changed.

Merge Danger

Door: two-way. A revert restores the bare route; no records or schemas change.

Blast Radius: names that failed before every request, plus one set that changes route. A bare name litellm assigns to another cloud now goes to OPENAI_API_BASE: about 450 in litellm 1.106 (Bedrock 351, Vertex 46, Anthropic 22, Cohere 16, AI21 12, NLP Cloud 2). On main it reached that cloud only while OPENAI_API_KEY was unset. With the key set, the OpenAI key went to that cloud and was refused. Those clouds have their own providers.

Overlap: main is merged in (f8279bf, after #271). #251's characterization test is unchanged from main. #255 edits other hunks of llm.py, and a one-line merge may be needed next to it.

Not in this PR:

Checklist

  • Tests added/updated and pytest passes
  • ruff, mypy, and schema checks pass
  • Docs updated (or N/A) — see the docs-drift checklist in CONTRIBUTING.md
  • Self-reviewed per "Before opening a PR" in AGENTS.md
  • No ad-hoc run artifacts committed
  • New files carry the SPDX license header (or are exempt in .github/.licenserc.yaml)

…ai/<model>

resolve_model sent every --provider openai model to litellm bare, and
litellm infers OpenAI only for names on its own lists. glm-5.3 behind
OPENAI_API_BASE, an org/name id (meta-llama/Llama-3.1-8B) or a model
newer than the installed litellm was refused before any request ("LLM
Provider NOT provided"); a bare claude-* name went to Anthropic. So
`eval cross-judge --judges openai/glm-5.3` scored nothing and read
NOT MEASURED (intel#136), and answers, the judge, the panel and the question
and persona generators failed the same way.

Such names now go out as openai/<model>. A name litellm already sends to
OpenAI (its chat and text-completion lists) or one with a litellm
provider prefix (openai/..., azure/...) keeps its route, so its
response-cache key, which holds the litellm model string, is unchanged.

llm.py is at its growth baseline: the module docstring's closing
paragraph, which repeats the opening one, and a comment that repeats the
legacy-section header are dropped, so the file stays at 1118 lines.

- tests/test_openai_compatible_route.py: the production adapter with
  litellm's real provider lookup (mock_response, nothing sent), for
  LLM(...).ask and eval cross-judge end to end; fails on main.
- tests/test_llm.py: route rows for the prefixed names and for the ones
  that must not change.

Fixes intel#136

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
…penai

Nothing in the docs said how to point dokimi at vLLM or a gateway; the
intel#136 reporter found the openai/openai/<model> form by trial. The setup
paragraph now names OPENAI_API_BASE / OPENAI_BASE_URL, says any served
model name works (--judges openai/glm-5.3), that OPENAI_API_KEY must be
set even when the server ignores it (the request fails with "Missing
credentials" otherwise), and that cost is unknown for such a model, so a
--max-usd run stops after its first call.

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
…t as gpt-*/o*

A gpt-* name off litellm's OpenAI lists (gpt-7-new) gets openai/ too, so
the fragment's "(gpt-*, o*) keeps its route" overstated it.

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
@codecov-commenter

codecov-commenter commented Oct 27, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.26%. Comparing base (0a63f4e) to head (6296697).
⚠️ Report is 1 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #256      +/-   ##
==========================================
- Coverage   93.26%   93.26%   -0.01%     
==========================================
  Files         272      272              
  Lines       35117    35121       +4     
  Branches     5964     5964              
==========================================
+ Hits        32752    32754       +2     
- Misses       1547     1548       +1     
- Partials      818      819       +1     
Files with missing lines Coverage Δ
dokimi/dokimi/llm.py 95.82% <100.00%> (+0.03%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@napetrov napetrov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix for glm-5.3 and meta-llama/… works, and the tests pass (100 in test_llm and test_openai_compatible_route; ruff, growth ratchet and import-architecture clean).

One thing to settle before approval: the openai branch in dokimi/llm.py:146-149 also reroutes bare names that litellm sends to other providers today. That is 449 catalog names: bedrock 351 (e.g. amazon.nova-pro-v1:0), vertex_ai 46, anthropic 22, cohere 16, ai21 12, nlp_cloud 2. They work on main with the cloud's credentials, and after this PR they would go to OPENAI_API_BASE. The body's "numbers do not change for a model that worked before" does not cover them. I checked this against litellm 1.106.0 from the lock; I did not run it against live Bedrock. Options: narrow the rule to names litellm cannot route, or list the affected set in the body and changelog and add a test row that pins one bare Bedrock id.

Nits:

  • README.md:152-153 says any served model name works. An org prefix that matches a litellm provider (deepseek/, groq/, ollama/, together_ai/) still routes natively and ignores OPENAI_API_BASE. Say "a name without a litellm provider prefix", or tell users to write openai/<name>.
  • README.md:155 describes --max-usd for every --provider openai run. Only novelty takes it (SpendCap, novelty/client.py:46-80).

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix does what it says for glm-5.3 and meta-llama/…, and CI is green. One point from @napetrov is still open and I confirm it, so this does not approve yet. One nit inline.

I agree with @napetrov that the openai branch also reroutes bare names that litellm sends to other providers. New evidence: at this head resolve_model returns openai/claude-sonnet-4-5, openai/amazon.nova-pro-v1:0 and openai/gemini-2.5-flash for provider openai, where litellm.get_llm_provider on the bare name gives anthropic, bedrock and vertex_ai, and ft:gpt-4o-mini:org::id becomes openai/ft:gpt-4o-mini:org::id.

I also agree with @napetrov's README points: the "any model name works" sentence is false for deepseek/, groq/ and ollama/ prefixes, and --max-usd exists only on novelty.

What I ran, on a Linux review host at 81c0d01:

  • make ci-local: 28 passed, 0 failed, 7 skipped (jobs that need a PR event or tools not installed locally).
  • A reproduction of the reroute above with the LLM transport mocked.
  • Mutations of the new branch: dropping the text-completion list, always prefixing, and letting any / pass are all caught; replacing the provider_list check with in ("openai", "azure") passes the full test_llm.py and test_openai_compatible_route.py.

Checked, no issues

  • Callers: resolve_model feeds pricing, usage and LLM._route; openrouter, openai-codex, google-vertex and amazon-bedrock return before the new branch and are unchanged.
  • Kept routes: gpt-4o, o3, gpt-5, gpt-3.5-turbo-instruct, azure/gpt-4o and openai/glm-5.3 come out unchanged, so the openai/openai/<model> workaround still works.
  • Repo rules: the changelog fragment passes its check, llm.py is at its 1118-line growth baseline, new files carry the SPDX header, and the new tests run offline.
  • Env vars: litellm reads both OPENAI_BASE_URL and OPENAI_API_BASE, as the README says.

Comment thread dokimi/tests/test_llm.py
antonwolfy and others added 7 commits October 28, 2026 10:40
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
…der openai

resolve_model kept a bare name bare only when it was on litellm's OpenAI
chat or text-completion lists. litellm also takes a fine-tune id
(ft:gpt-4o-mini:org::id; its test is "ft:gpt-3.5-turbo" or "ft:gpt-4" in
the name) for OpenAI's, so such a name went out as openai/ft:... : the
same endpoint, but a new response-cache key, which the changelog said
would not happen. The bare test now repeats litellm's, so the name keeps
its route and its cache entries. litellm.get_llm_provider is not called
instead: on a name it cannot place (glm-5.3, the case this fixes) it
prints a "Provider List" banner to stdout.

- tests/test_openai_compatible_route.py: names kept bare (gpt-4o,
  gpt-3.5-turbo-instruct, ft:gpt-4o-mini:org::id) still route through
  litellm's real lookup, and a fine-tune litellm does not place
  (ft:gpt-5-mini:org::id) gets openai/, so a looser ft: test fails.

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
…t on provider openai

Review of intel#256: the openai rule also reroutes the bare names litellm
assigns to another cloud (Bedrock, Vertex, Anthropic, Cohere, AI21).
That is intended: on main such a name reached that cloud with
OPENAI_API_KEY passed as its key, which Anthropic and Bedrock refuse.
The row pins amazon.nova-pro-v1:0 -> openai/amazon.nova-pro-v1:0 next
to the claude-* one.

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
…that only novelty takes --max-usd

Review of intel#256. "Any model name the server serves works" was false for
a name with a litellm provider prefix (deepseek/, groq/, ollama/): it
keeps that provider's route and ignores OPENAI_API_BASE, so the text
says to write it as openai/<name>. It also says that a bare claude-*,
Bedrock or Vertex id now goes to the server, and needs its cloud's
provider to reach that cloud. --max-usd is a novelty flag (SpendCap),
not one every --provider openai run takes.

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
…he openai endpoint

Review of intel#256. The fragment said a bare claude-* name "went to
Anthropic instead" and left out the rest of that set: about 450 bare
names in litellm 1.106 (Bedrock, Vertex, Anthropic, Cohere, AI21) that
went to their cloud with OPENAI_API_KEY as the key and worked only with
it unset. A Fixed entry now names the set, why it moves and which
provider reaches those clouds. The first entry no longer says "any model
name", lists a ft: fine-tune among the names that keep their route, and
the README entry matches the reworded paragraph.

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
…nai/gpt-x

test_grader_characterization (intel#251) pins the response-cache file of a
judge and a panel rerun at model gpt-x, provider openai. gpt-x is off
litellm's OpenAI lists, so this branch sends it as openai/gpt-x, and the
key, which holds the litellm model string, changes with it. The new
names are the keys main computes for model openai/gpt-x: only the route
moved, the messages and draws behind the key are the same.

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
@vlad-perevezentsev

Copy link
Copy Markdown
Contributor Author

@napetrov thanks. Taken as your second option: the rule stays, and the set is named and pinned. 1f13597 adds the bare Bedrock row (amazon.nova-pro-v1:0 -> openai/amazon.nova-pro-v1:0), and a6063b5 adds a changelog entry for those names.

I kept the rule rather than narrowing it because those names do not work on main once OPENAI_API_KEY is set. dokimi passes the openai provider's key to litellm as api_key, and litellm hands it to whichever cloud it picked. With the transport intercepted (litellm 1.106.0), --provider openai on main sends:

bare name OPENAI_API_KEY set unset
claude-sonnet-4-5 api.anthropic.com, x-api-key: <OpenAI key> ANTHROPIC_API_KEY
amazon.nova-pro-v1:0 bedrock-runtime, Authorization: Bearer <OpenAI key> AWS SigV4

So they reach their cloud only while OPENAI_API_KEY is unset, and otherwise the OpenAI key goes to another vendor and is refused. Narrowing the rule would keep that. Those clouds have their own providers (anthropic, amazon-bedrock, google-vertex), and the changelog points to them. I did not check Vertex: it needs the Google SDK, which the lock does not install.

README nits, both fixed in feb0d7e. The paragraph now says a name with a litellm provider prefix (deepseek/, groq/, ollama/) keeps that route and should be written openai/<name>. It also says novelty --max-usd.

@vlad-perevezentsev

Copy link
Copy Markdown
Contributor Author

@antonwolfy thanks for the mutation run and the ollama/ row (9372736). On the reroute, see my reply to napetrov: the rule stays, with the Bedrock row in 1f13597 and the changelog entry in a6063b5.

Your ft:gpt-4o-mini:org::id example was a real regression: the name kept its endpoint but got a new response-cache key. 598fbfa keeps a bare name bare when litellm itself takes it for OpenAI's, which means its lists or its ft:gpt-3.5-turbo / ft:gpt-4 test. Two new tests guard this. The names kept bare still route through litellm's real lookup, and an ft: name litellm does not place (ft:gpt-5-mini:org::id) still gets openai/, so a looser ft: check fails them. I did not call litellm.get_llm_provider because on an unknown name, which is the #136 case, it prints its banner to stdout.

Also: 13165af merges main, and ea2acaa re-pins two cache-key file names in #251's test_grader_characterization.py. Its gpt-x at provider openai now routes as openai/gpt-x. The new names are the keys main computes for model openai/gpt-x, so only the route changed.

@vlad-perevezentsev vlad-perevezentsev changed the title fix(llm): --provider openai reaches an OpenAI-compatible server with any model name fix(llm): --provider openai sends a model litellm does not know as OpenAI's to the OpenAI endpoint Oct 28, 2026
litellm also routes providers from its openai_like JSON registry
(crusoe/, sarvam/, ...), which are not in provider_list. On provider
openai such a name got openai/ and went to OPENAI_API_BASE instead.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 29, 2026

Copy link
Copy Markdown
Contributor
  • Ran: full suite, ruff, mypy and bandit on the head and on the head plus the fix below, plus end-to-end routing with the HTTP transport intercepted.
  • --provider openai, model crusoe/deepseek-ai/DeepSeek-R1-0528 or sarvam/sarvam-m, OPENAI_API_KEY unset -> openai/ is added and the call fails with "Missing credentials". On main it reached the provider's own endpoint. Expected: the prefix keeps its route, as the README and changelog say. litellm routes these 8 providers from its JSON registry, and they are not in provider_list.
  • gpt-6.1-terra on provider openai -> bare with litellm's remote price map, openai/gpt-6.1-terra with LITELLM_LOCAL_MODEL_COST_MAP=True. So there are two response-cache keys for one model and prompt, and replay_only misses. Expected: one key, whatever the map or litellm version.
  • Pushed 4cc10a6: it also checks litellm.JSONProviderRegistry.exists(prefix), adds a crusoe/x row, and adds no lines.
  • For the author: the cache-key scheme. One option is to hash the caller's bare name on provider openai, which keeps main's keys. Another is to always use openai/<model>, which changes every stored key once.

README.md conflicted because intel#271 moved the credentials paragraph this branch extends into docs/reviewing.md; README.md is main's, and this branch's OpenAI-compatible server text is carried verbatim into that paragraph in docs/reviewing.md.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Merged main after #271 (f8279bf): #271 moved the README credentials paragraph this PR extends into dokimi/docs/reviewing.md, so the README is main's and the OpenAI-compatible server text is added to that paragraph in reviewing.md, unchanged. The changelog fragment still says "README: running against an OpenAI-compatible server"; it may want to name docs/reviewing.md instead. Full suite, ruff, mypy and bandit pass on Linux.

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The route rule holds up at this head: the ft:gpt-* regression, the ollama/ row and the Bedrock row from the earlier round are in, and the JSON-registry prefix fix (crusoe/) is right. Two nits inline.

@xaleryb's question on the response-cache key waits for the author: the route now depends on litellm's cost map, so a model that is in the remote map but not in the installed backup gets a different key depending on whether the fetch succeeds.

What I ran, on a Linux review host at f8279bf:

  • make ci-local: 27 passed, 1 failed, 7 skipped; 10,196 tests; the changed-line coverage gate passes. The one FAIL is pip-audit, which lists filelock 3.32.7 advisories from the lock files; this PR does not touch them.
  • resolve_model on edge names against litellm's own get_llm_provider (bare and prefixed names, JSON-registry and provider_list prefixes, ft: variants), with the LLM transport mocked.
  • A mutation run over the new branch against test_llm.py, test_openai_compatible_route.py and test_grader_characterization.py.

Checked, no issues

  • Callers: resolve_model feeds pricing, usage and LLM._route; openai/<x> is a valid litellm route for cost and completion, and openrouter and openai-codex still return before the new branch.
  • Repo rules: all ten non-merge commits carry Signed-off-by; llm.py stays at its 1118-line baseline; the new test file has the SPDX header and the tests write nothing outside tmp_path; the changelog fragment check passes.
  • Docs: --judges, novelty --max-usd (SpendCap stops after the first call when cost is unknown) and both base-URL variables match the code.

Comment thread dokimi/tests/test_llm.py
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I pushed a commit per nit, up to 5b6b90c, which fix the two nits from my review:

  • 01406a0: dokimi/tests/test_llm.py:62: added the ft:gpt-3.5-turbo:org::id row after the ft:gpt-4o-mini row.
  • 5b6b90c: dokimi/changelog.d/20261027_120000_vlad-perevezentsev_openai-compatible-model-route.md:23: the entry's lead-in now names docs/reviewing.md instead of README.

make ci-local at 5b6b90c: 27 passed, 1 failed, 7 skipped.

Not merging yet: @xaleryb on the cache-key scheme: the openai route depends on litellm's cost map (remote vs installed backup), so one model and prompt can have two response-cache keys; hash the caller's bare name, or always use openai/ waits for your answer, @vlad-perevezentsev.

… on its route

Review of intel#256 (xaleryb): the openai route of a bare name depends on
litellm's price map. litellm loads the remote map, or the installed
backup under LITELLM_LOCAL_MODEL_COST_MAP=True or offline, and the two
disagree on a new model: gpt-6.1-terra is on the remote one's OpenAI
list (206 names) and not on the backup's (197), so it went out bare in
one run and as openai/gpt-6.1-terra in the other. Both reach the same
endpoint, but the response-cache key held the route, so one model and
prompt had two keys, and a rerun on the other map paid for every call
again and could give other numbers.

On provider openai the key now holds self.model, which is what main
keyed on, since main sent every openai name bare. Every key main wrote
for that provider still hits, including those of the names this PR
reroutes, and the map no longer moves it. Other providers are
unchanged. OPENAI_API_BASE was never part of the key, so keying on the
name loses nothing the route held.

- test_grader_characterization.py: the two file names ea2acaa re-pinned
  for openai/gpt-x are back to main's, so the file matches main again.
- test_llm.py: main's key for gpt-6.1-terra (routed openai/ under the
  installed map the tests use), recorded by running main's code.
- test_openai_compatible_route.py: a reply stored with the model on the
  map replays once it is off the map and the route is openai/.
- llm.py stays at 1118 lines: the response-cache paragraph of the module
  docstring, which names the key's parts, is rewrapped to the project's
  100 columns.

Signed-off-by: Vladislav Perevezentsev <vladislav.perevezentsev@intel.com>
@vlad-perevezentsev

Copy link
Copy Markdown
Contributor Author

@xaleryb thanks for 4cc10a6 and the merge. On the cache key I took your first option: 6296697 keys a provider-openai reply on the model name as given. That is what main keyed on, since main sent every openai name bare. So every key main wrote still hits, including those of the names this PR reroutes, and litellm's price map no longer moves the key. The route can still be bare or openai/ depending on the map, but both reach the same endpoint, and OPENAI_API_BASE was never part of the key. Other providers are unchanged.

Your second option would change every stored OpenAI key once and the route of every OpenAI model, so I left it out.

Tests:

Keying on the route again fails all three. make ci-local: 1 failed (pip-audit, the filelock advisories antonwolfy saw), 27 passed. llm.py stays at 1118 lines.

@vchamarthi

vchamarthi commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

🔍 OpenCodeReview found 2 issue(s) in this PR.

  • ✅ Successfully posted inline: 2 comment(s)

Comment thread dokimi/dokimi/llm.py
import litellm
known = litellm.open_ai_chat_completion_models | litellm.open_ai_text_completion_models
ours = model in known or "ft:gpt-3.5-turbo" in model or "ft:gpt-4" in model # as litellm
routed = (p := model.split("/", 1)[0]) in litellm.provider_list or litellm.JSONProviderRegistry.exists(p) if "/" in model else ours

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maintainability · low
Nit: this one-liner mixes a walrus, or and a trailing conditional expression, and ours is computed even when the name has a /. It parses as intended: (p in provider_list or JSONProviderRegistry.exists(p)) if "/" in model else ours. Separate branches would be easier to review. It also reads litellm.JSONProviderRegistry unguarded, and pyproject.toml only pins litellm>=1.105.0. If that attribute is missing in some supported version, every provider=openai name containing / (e.g. meta-llama/Llama-3.1-8B) raises AttributeError instead of being routed. Confirm the attribute exists at the minimum pin, or raise the pin.

Suggestion:

Suggested change
routed = (p := model.split("/", 1)[0]) in litellm.provider_list or litellm.JSONProviderRegistry.exists(p) if "/" in model else ours
if "/" in model:
prefix = model.split("/", 1)[0]
routed = prefix in litellm.provider_list or litellm.JSONProviderRegistry.exists(prefix)
else:
routed = model in known or "ft:gpt-3.5-turbo" in model or "ft:gpt-4" in model # as litellm

Comment thread dokimi/tests/test_llm.py
("22510fd74bf01210757177f302f9b6728b81e9e3b31572bd6245f0c88af78dfd",
lambda: get_llm("openrouter", "openai/gpt-oss-120b", "k", temperature=0.0).invoke(
"ask", draw=1)),
# Off the installed price map, so routed as openai/gpt-6.1-terra here; main sent it bare.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test · low
Nit: this entry is named openai-rerouted, but the key is built from the caller's name on openai, so the hash is the same whether resolve_model returns gpt-6.1-terra or openai/gpt-6.1-terra. If resolve_model stopped rerouting, the test would still pass. It only pins key stability, which is fine. The comment is also fragile: it stops being true once litellm lists gpt-6.1-terra. Rename the entry and reword the comment, or leave routing to the route tests.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

eval cross-judge drops the provider when splitting the judge spec

6 participants