feat: support per-role api_key in role_llms for multi-gateway setups (#94)

* feat: support per-role api_key in role_llms for multi-gateway setups

* test: add per-role api_key precedence tests for openai_compatible provider

---------

Co-authored-by: k176060444-lgtm <k176060444-lgtm@users.noreply.github.com>
This commit is contained in:
KK
2026-08-19 15:43:12 +08:00
committed by GitHub
co-authored by k176060444-lgtm
parent 0badc3340c
commit 8db46605b3
3 changed files with 31 additions and 3 deletions
+21
View File
@@ -65,3 +65,24 @@ class TestOpenAICompatibleClient:
) )
client.get_llm() client.get_llm()
assert not [w for w in recwarn if "not in the known model list" in str(w.message)] assert not [w for w in recwarn if "not in the known model list" in str(w.message)]
def test_per_role_api_key_wins_over_env(self, monkeypatch):
monkeypatch.setenv("OPENAI_COMPATIBLE_API_KEY", "env-key")
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
# api_key passed via kwargs (role_llms spec) must take precedence.
client = OpenAIClient(
"my-model", base_url="https://relay.example/v1",
provider="openai_compatible", api_key="role-key",
)
llm = client.get_llm()
assert llm.client._client.api_key == "role-key"
def test_role_api_key_absent_falls_back_to_env(self, monkeypatch):
monkeypatch.setenv("OPENAI_COMPATIBLE_API_KEY", "env-key")
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
client = OpenAIClient(
"my-model", base_url="https://relay.example/v1",
provider="openai_compatible",
)
llm = client.get_llm()
assert llm.client._client.api_key == "env-key"
+5 -2
View File
@@ -373,13 +373,16 @@ class TradingAgentsGraph:
if k not in _PROVIDER_SPECIFIC_KWARGS} if k not in _PROVIDER_SPECIFIC_KWARGS}
) )
key = (provider.lower(), spec["model"], base_url) key = (provider.lower(), spec["model"], base_url, spec.get("api_key"))
if key not in cache: if key not in cache:
client_kwargs = dict(role_kwargs)
if spec.get("api_key"):
client_kwargs["api_key"] = spec["api_key"]
cache[key] = create_llm_client( cache[key] = create_llm_client(
provider=provider, provider=provider,
model=spec["model"], model=spec["model"],
base_url=base_url, base_url=base_url,
**role_kwargs, **client_kwargs,
).get_llm() ).get_llm()
resolved[role] = cache[key] resolved[role] = cache[key]
+5 -1
View File
@@ -187,8 +187,12 @@ class OpenAIClient(BaseLLMClient):
"(例如 https://your-relay.example/v1)。" "(例如 https://your-relay.example/v1)。"
) )
llm_kwargs["base_url"] = self.base_url llm_kwargs["base_url"] = self.base_url
# Per-role api_key (from role_llms spec) wins over env vars, so
# several OpenAI-compatible gateways with different keys can run
# side by side (e.g. opencode-go + a local proxy relay).
api_key = ( api_key = (
os.environ.get("OPENAI_COMPATIBLE_API_KEY") self.kwargs.get("api_key")
or os.environ.get("OPENAI_COMPATIBLE_API_KEY")
or os.environ.get("OPENAI_API_KEY") or os.environ.get("OPENAI_API_KEY")
) )
if api_key: if api_key: