-
Notifications
You must be signed in to change notification settings - Fork 862
feat: auto-load base URL env vars for OpenAI, Anthropic, and Gemini c… #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
bcf2b42
499df0e
f96350c
15ec3ce
b38364f
31449f1
8087e53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # ============================================================================= | ||
| # OpenAI | ||
| # ============================================================================= | ||
| OPENAI_API_KEY=your-openai-api-key | ||
|
|
||
| # Optional: override the API base URL (e.g. for org proxies, vLLM, or LiteLLM gateways) | ||
| # OPENAI_API_BASE=https://your-org-gateway.example.com/v1 | ||
|
|
||
| # ============================================================================= | ||
| # Anthropic | ||
| # ============================================================================= | ||
| ANTHROPIC_API_KEY=your-anthropic-api-key | ||
|
|
||
| # Optional: override the API base URL (e.g. for org proxies or self-hosted endpoints) | ||
| # ANTHROPIC_BASE_URL=https://your-org-gateway.example.com | ||
|
|
||
| # ============================================================================= | ||
| # Google Gemini | ||
| # ============================================================================= | ||
| GEMINI_API_KEY=your-gemini-api-key | ||
|
|
||
| # Optional: override the API base URL (e.g. for Vertex AI proxies) | ||
| # GEMINI_API_BASE=https://your-org-gateway.example.com | ||
|
|
||
| # ============================================================================= | ||
| # Other supported backends | ||
| # ============================================================================= | ||
|
|
||
| # OpenRouter (https://openrouter.ai) | ||
| # OPENROUTER_API_KEY=your-openrouter-api-key | ||
|
|
||
| # Vercel AI Gateway (https://ai-gateway.vercel.sh) | ||
| # AI_GATEWAY_API_KEY=your-vercel-ai-gateway-api-key | ||
|
|
||
| # Azure OpenAI | ||
| # AZURE_OPENAI_API_KEY=your-azure-openai-api-key | ||
| # AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com | ||
| # AZURE_OPENAI_API_VERSION=2024-02-01 | ||
| # AZURE_OPENAI_DEPLOYMENT=your-deployment-name | ||
|
|
||
| # Prime Intellect | ||
| # PRIME_API_KEY=your-prime-intellect-api-key | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,18 @@ | ||
| import os | ||
| from collections import defaultdict | ||
| from typing import Any | ||
|
|
||
| import anthropic | ||
| from dotenv import load_dotenv | ||
|
|
||
| from rlm.clients.base_lm import BaseLM | ||
| from rlm.core.types import ModelUsageSummary, UsageSummary | ||
|
|
||
| load_dotenv() | ||
|
|
||
| DEFAULT_ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") | ||
| DEFAULT_ANTHROPIC_BASE_URL = os.getenv("ANTHROPIC_BASE_URL") | ||
|
|
||
|
|
||
| class AnthropicClient(BaseLM): | ||
| """ | ||
|
|
@@ -14,14 +21,32 @@ class AnthropicClient(BaseLM): | |
|
|
||
| def __init__( | ||
| self, | ||
| api_key: str, | ||
| api_key: str | None = None, | ||
| model_name: str | None = None, | ||
| base_url: str | None = None, | ||
| max_tokens: int = 32768, | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — moved base_url after max_tokens to preserve positional argument order. |
||
| **kwargs, | ||
| ): | ||
| super().__init__(model_name=model_name, **kwargs) | ||
| self.client = anthropic.Anthropic(api_key=api_key, timeout=self.timeout) | ||
| self.async_client = anthropic.AsyncAnthropic(api_key=api_key, timeout=self.timeout) | ||
|
|
||
| if api_key is None: | ||
| api_key = DEFAULT_ANTHROPIC_API_KEY | ||
|
|
||
| if api_key is None: | ||
| raise ValueError( | ||
| "Anthropic API key is required. Set ANTHROPIC_API_KEY env var or pass api_key." | ||
| ) | ||
|
|
||
| # Fall back to ANTHROPIC_BASE_URL env var if base_url is not explicitly provided. | ||
| if base_url is None: | ||
| base_url = DEFAULT_ANTHROPIC_BASE_URL | ||
|
|
||
| client_kwargs = {"api_key": api_key, "timeout": self.timeout} | ||
|
Comment on lines
+40
to
+44
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unit tests for this exact behavior were added in tests/clients/test_base_url_env_vars.py. Specifically, TestAnthropicClientEnvVars::test_base_url_from_env patches DEFAULT_ANTHROPIC_BASE_URL and asserts the value is forwarded to the anthropic.Anthropic constructor, and test_base_url_not_passed_when_env_not_set covers the case where the env var is unset. |
||
| if base_url is not None: | ||
| client_kwargs["base_url"] = base_url | ||
|
Comment on lines
+32
to
+46
|
||
|
|
||
| self.client = anthropic.Anthropic(**client_kwargs) | ||
| self.async_client = anthropic.AsyncAnthropic(**client_kwargs) | ||
| self.model_name = model_name | ||
| self.max_tokens = max_tokens | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| load_dotenv() | ||
|
|
||
| DEFAULT_GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") | ||
| DEFAULT_GEMINI_API_BASE = os.getenv("GEMINI_API_BASE") | ||
|
|
||
|
|
||
| class GeminiClient(BaseLM): | ||
|
|
@@ -24,6 +25,7 @@ def __init__( | |
| self, | ||
| api_key: str | None = None, | ||
| model_name: str | None = "gemini-2.5-flash", | ||
| base_url: str | None = None, | ||
| **kwargs, | ||
| ): | ||
| super().__init__(model_name=model_name, **kwargs) | ||
|
|
@@ -36,8 +38,17 @@ def __init__( | |
| "Gemini API key is required. Set GEMINI_API_KEY env var or pass api_key." | ||
| ) | ||
|
|
||
| # Fall back to GEMINI_API_BASE env var if base_url is not explicitly provided. | ||
| if base_url is None: | ||
| base_url = DEFAULT_GEMINI_API_BASE | ||
|
|
||
| # Configure HTTP options with timeout | ||
|
Comment on lines
+41
to
45
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed — tests/clients/test_base_url_env_vars.py covers this. Specifically, TestGeminiClientBaseUrl::test_base_url_from_env patches DEFAULT_GEMINI_API_BASE and types.HttpOptions and asserts the expected base_url is passed to HttpOptions, test_explicit_base_url_overrides_env verifies an explicit argument takes precedence over the env var, and test_base_url_absent_when_env_not_set covers the unset case. |
||
| http_options = types.HttpOptions(timeout=int(self.timeout * 1000)) # milliseconds | ||
| http_options = types.HttpOptions( | ||
| timeout=int(self.timeout * 1000), # milliseconds | ||
| **({ | ||
| "base_url": base_url | ||
| } if base_url is not None else {}), | ||
| ) | ||
|
Comment on lines
+41
to
+51
|
||
| self.client = genai.Client(api_key=api_key, http_options=http_options) | ||
| self.model_name = model_name | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,8 +10,9 @@ | |
|
|
||
| load_dotenv() | ||
|
|
||
| # Load API keys from environment variables | ||
| # Load API keys and base URL from environment variables | ||
| DEFAULT_OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | ||
| DEFAULT_OPENAI_API_BASE = os.getenv("OPENAI_API_BASE") | ||
| DEFAULT_OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY") | ||
| DEFAULT_VERCEL_API_KEY = os.getenv("AI_GATEWAY_API_KEY") | ||
| DEFAULT_PRIME_API_KEY = os.getenv("PRIME_API_KEY") | ||
|
|
@@ -36,6 +37,10 @@ def __init__( | |
| ): | ||
| super().__init__(model_name=model_name, **kwargs) | ||
|
|
||
| # Fall back to OPENAI_API_BASE env var if base_url is not explicitly provided. | ||
| if base_url is None: | ||
| base_url = DEFAULT_OPENAI_API_BASE | ||
|
Comment on lines
+40
to
+42
Comment on lines
+41
to
+42
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed — tests/clients/test_base_url_env_vars.py covers this. Specifically, TestOpenAIClientBaseUrl::test_base_url_from_env patches DEFAULT_OPENAI_API_BASE and asserts the value is forwarded to the openai.OpenAI constructor, test_explicit_base_url_overrides_env verifies an explicit argument takes precedence, and test_base_url_is_none_when_env_not_set covers the unset case. |
||
|
|
||
| if api_key is None: | ||
| if base_url == "https://api.openai.com/v1" or base_url is None: | ||
| api_key = DEFAULT_OPENAI_API_KEY | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.