Skip to content

Lazily initialize OpenAI client so local routers import without an API key#91

Open
zl190 wants to merge 1 commit into
lm-sys:mainfrom
zl190:fix/lazy-openai-client
Open

Lazily initialize OpenAI client so local routers import without an API key#91
zl190 wants to merge 1 commit into
lm-sys:mainfrom
zl190:fix/lazy-openai-client

Conversation

@zl190

@zl190 zl190 commented Jun 21, 2026

Copy link
Copy Markdown

Problem

routellm/routers/similarity_weighted/utils.py instantiated the OpenAI client at module import time:

OPENAI_CLIENT = OpenAI()

Constructing OpenAI() requires OPENAI_API_KEY. Because this module is imported transitively by routers.py and matrix_factorization/model.py, importing any router forced a client to be built — even fully-local routers (bert, causal_llm) that never call OpenAI. So from routellm.controller import Controller crashed with openai.OpenAIError: Missing credentials for users who only wanted a local router.

This is the symptom behind issues like #21 and #38.

Reproduce (before this PR)

In a fresh env with no OPENAI_API_KEY:

pip install routellm
python -c "from routellm.controller import Controller"
# openai.OpenAIError: Missing credentials. Please pass an `api_key` ...

Fix

Make the client lazy — instantiate on first use, not at import. Replace the module-level OPENAI_CLIENT = OpenAI() with a cached accessor:

@functools.lru_cache(maxsize=1)
def get_openai_client():
    return OpenAI()

and update the two call sites (SWRankingRouter in routers.py, MFModel in matrix_factorization/model.py) to call get_openai_client(). Behavior is identical when a key is present — the sw_ranking / mf embedding path still gets a (now cached) client. No routing-logic or signature changes.

Scope note: openai_server.py (AsyncOpenAI()) and evals/find_contaminated.py (OpenAI()) also instantiate at import, but neither is on the router/Controller import path — they're a standalone server entrypoint and an eval script — so they don't affect importing routers. Left untouched to keep this diff minimal.

Result

  • bert & causal_llm now import and construct keyless.
  • With a key present, the embedding path still gets a client (verified without making real API calls).
# no OPENAI_API_KEY set
python -c "from routellm.controller import Controller; Controller(routers=['bert'], strong_model='x', weak_model='y', progress_bar=False)"
# succeeds

🤖 Generated with Claude Code

…I key

`similarity_weighted/utils.py` created the OpenAI client at module import
time (`OPENAI_CLIENT = OpenAI()`). Because this module is imported
transitively by `routers.py` and `matrix_factorization/model.py`, importing
*any* router forced an OpenAI client to be constructed, which requires
`OPENAI_API_KEY`. As a result `from routellm.controller import Controller`
raised `openai.OpenAIError: Missing credentials` even when the user only
wanted a fully-local router (e.g. `bert` or `causal_llm`) that never calls
OpenAI.

Replace the module-level instantiation with a cached `get_openai_client()`
accessor that constructs the client on first use. Behavior is identical when
a key is present (the sw_ranking / mf embedding path still gets a client),
but local routers now import without any credentials.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant