Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .env.example
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
Comment thread
yauheniya-adesso marked this conversation as resolved.
Outdated
# =============================================================================

# 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
31 changes: 28 additions & 3 deletions rlm/clients/anthropic.py
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):
"""
Expand All @@ -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,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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

Expand Down
13 changes: 12 additions & 1 deletion rlm/clients/gemini.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand All @@ -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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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

Expand Down
7 changes: 6 additions & 1 deletion rlm/clients/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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
Expand Down
Loading