litellm/litellm/proxy/read_model_list.py
Yassin Kortam f22eeb2ce0
fix(proxy): initialize the secret manager before resolving os.environ config references (#37544)
`ProxyConfig.get_config()` walked the parsed config and replaced every
`os.environ/<KEY>` string with `get_secret(value)` before anything initialized
the secret manager, so a key held only by the manager resolved to `None` and
that `None` was written back into the config. The later fallback in
`load_config` could not recover it, because the key now existed with a `None`
value.

Hoist the initialization into `get_config()`, ahead of the resolution pass, so
every entrypoint gets it: the CLI already did this itself, but the microservice
entrypoints (`gateway/main.py`, `backend/main.py`) uvicorn the app directly and
bypass the CLI. `load_config`'s own call is now redundant and is dropped, so
startup builds the manager once instead of building one and discarding it.

`get_config()` also runs on management-endpoint request paths, so this returns
early once a manager exists rather than rebuilding the client per request.

Also warn when a reference the manager would have been asked for resolves to
`None`. The reporter had no log line at all to work from. `get_secret` only
reaches the manager when reads are enabled and the name is in `hosted_keys`, so
`secret_manager_would_be_consulted` mirrors that gate and keeps the warning off
env-only references, which are expected rather than an error.
2026-08-19 17:00:26 -07:00

29 lines
1.3 KiB
Python

"""Resolve a proxy config's ``model_list`` for the Rust AI gateway.
The Rust gateway calls this once at load time (via an embedded interpreter) and
builds its own (Rust) router from the returned ``model_list``. We do NOT call
``ProxyConfig.load_config`` here: that returns a *Python* ``litellm.Router`` (not
usable from Rust) and boots the whole proxy (callbacks, cache, DB, auth) as side
effects.
Instead we reuse ``ProxyConfig.get_config`` — the actual config reader — so the
gateway inherits the same heavy lifting the proxy does: ``include:`` merging,
``os.environ/`` + secret-manager resolution, and DB-stored models (when a DB is
configured). Its only proxy-setup side effect is bringing up the configured
secret manager, which is what makes that resolution work. Returns the resolved
``model_list``; the Rust side deserializes each entry into its ``Deployment``.
"""
from __future__ import annotations
import asyncio
from typing import Any, Final
def read_model_list(config_path: str) -> list[dict[str, Any]]:
"""Load ``config_path`` via the proxy's own reader and return its
resolved ``model_list``."""
from litellm.proxy.proxy_server import ProxyConfig
config: Final = asyncio.run(ProxyConfig().get_config(config_file_path=config_path))
return config.get("model_list") or []