Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
ANTHROPIC_API_KEY=your_anthropic_api_key_here
# Optional custom Anthropic-compatible endpoint
ANTHROPIC_BASE_URL=

OPENAI_API_KEY=your_openai_api_key_here

GOOGLE_API_KEY=your_google_api_key_here
# Optional custom Gemini-compatible endpoint
GOOGLE_BASE_URL=

AZURE_API_KEY=your_azure_api_key_here
AZURE_ENDPOINT=your_azure_endpoint_here
AZURE_API_VERSION=your_azure_api_version_here

ENDPOINT_URL=http://0.0.0.0:8000/api/chat
ENDPOINT_START_URL=http://0.0.0.0:8000/api/start_conversation
ENDPOINT_API_KEY=your_endpoint_api_key_here
ENDPOINT_API_KEY=your_endpoint_api_key_here
3 changes: 3 additions & 0 deletions llm_clients/claude_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ def __init__(
"model": self.model_name,
}

if Config.ANTHROPIC_BASE_URL:
llm_params["base_url"] = Config.ANTHROPIC_BASE_URL

# Override with any provided kwargs
llm_params.update(kwargs)

Expand Down
3 changes: 3 additions & 0 deletions llm_clients/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ class Config:

# API Keys
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
ANTHROPIC_BASE_URL = os.getenv("ANTHROPIC_BASE_URL")

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL")

GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
GOOGLE_BASE_URL = os.getenv("GOOGLE_BASE_URL")

AZURE_API_KEY = os.getenv("AZURE_API_KEY")
AZURE_ENDPOINT = os.getenv("AZURE_ENDPOINT")
Expand Down
3 changes: 3 additions & 0 deletions llm_clients/gemini_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ def __init__(
"model": self.model_name,
}

if Config.GOOGLE_BASE_URL:
llm_params["base_url"] = Config.GOOGLE_BASE_URL

# Override with any provided kwargs
llm_params.update(kwargs)
filtered_params = self._filter_supported_params(self.model_name, llm_params)
Expand Down
3 changes: 3 additions & 0 deletions llm_clients/openai_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ def __init__(
"model": self.model_name,
}

if Config.OPENAI_BASE_URL:
llm_params["base_url"] = Config.OPENAI_BASE_URL

# Override with any provided kwargs
llm_params.update(kwargs)
filtered_params = self._filter_supported_params(self.model_name, llm_params)
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ description = "Validation of Ethical and Responsible AI in Mental Health"
requires-python = ">=3.11"
dependencies = [
"langchain>=0.1.0",
"langchain-anthropic>=1.4.7",
"langchain-openai>=1.0.3",
"langchain-google-genai>=3.1.0",
"langchain-anthropic>=1.5.6",
"langchain-openai>=1.5.1",
"langchain-google-genai>=4.3.4",
"langchain-azure-ai>=1.0.0",
"langchain-ollama>=0.1.0",
"python-dotenv>=1.2.2",
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/llm_clients/test_claude_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ def test_init_with_kwargs(self, default_llm_kwargs):
assert call_kwargs["max_tokens"] == 500
assert call_kwargs["top_p"] == 0.9

@patch(
"llm_clients.claude_llm.Config.ANTHROPIC_BASE_URL",
"https://anthropic.example/v1",
)
def test_init_uses_anthropic_base_url_from_config(self):
"""Forward a configured custom endpoint to ChatAnthropic."""
with patch("llm_clients.claude_llm.ChatAnthropic") as mock_chat_anthropic:
mock_chat_anthropic.return_value.model = "claude-sonnet-4-5-20250929"

ClaudeLLM(name="TestClaude", role=Role.PERSONA)

assert (
mock_chat_anthropic.call_args[1]["base_url"]
== "https://anthropic.example/v1"
)

def test_init_strips_sampling_params_for_opus_4_8(self, default_llm_kwargs):
"""Opus 4.8 rejects temperature/top_p/top_k; none may reach ChatAnthropic."""
with patch("llm_clients.claude_llm.ChatAnthropic") as mock_chat_anthropic:
Expand Down
27 changes: 26 additions & 1 deletion tests/unit/llm_clients/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ def test_api_keys_loaded_from_env(self):
# These should be None or set depending on the test environment
# We just verify the attributes exist
assert hasattr(Config, "ANTHROPIC_API_KEY")
assert hasattr(Config, "ANTHROPIC_BASE_URL")
assert hasattr(Config, "OPENAI_API_KEY")
assert hasattr(Config, "GOOGLE_API_KEY")
assert hasattr(Config, "GOOGLE_BASE_URL")

def test_get_claude_config(self):
"""Test get_claude_config returns expected structure."""
Expand Down Expand Up @@ -75,12 +77,23 @@ def test_anthropic_api_key_from_env(self):
from llm_clients import config

importlib.reload(config)

assert config.Config.ANTHROPIC_API_KEY == "test-anthropic-key"

# Reload again to restore original state
importlib.reload(config)

@patch.dict("os.environ", {"ANTHROPIC_BASE_URL": "https://anthropic.example/v1"})
def test_anthropic_base_url_from_env(self):
"""Test that ANTHROPIC_BASE_URL can be loaded from environment."""
import importlib

from llm_clients import config

importlib.reload(config)
assert config.Config.ANTHROPIC_BASE_URL == "https://anthropic.example/v1"

importlib.reload(config)

@patch.dict("os.environ", {"OPENAI_API_KEY": "test-openai-key"})
def test_openai_api_key_from_env(self):
"""Test that OPENAI_API_KEY can be loaded from environment."""
Expand All @@ -104,3 +117,15 @@ def test_google_api_key_from_env(self):
assert config.Config.GOOGLE_API_KEY == "test-google-key"

importlib.reload(config)

@patch.dict("os.environ", {"GOOGLE_BASE_URL": "https://google.example/v1"})
def test_google_base_url_from_env(self):
"""Test that GOOGLE_BASE_URL can be loaded from environment."""
import importlib

from llm_clients import config

importlib.reload(config)
assert config.Config.GOOGLE_BASE_URL == "https://google.example/v1"

importlib.reload(config)
13 changes: 13 additions & 0 deletions tests/unit/llm_clients/test_gemini_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ def test_init_with_kwargs(self, default_llm_kwargs):
assert call_kwargs["max_tokens"] == 500
assert call_kwargs["top_p"] == 0.9

@patch(
"llm_clients.gemini_llm.Config.GOOGLE_BASE_URL",
"https://google.example/v1",
)
def test_init_uses_google_base_url_from_config(self):
"""Forward a configured custom endpoint to ChatGoogleGenerativeAI."""
with patch("llm_clients.gemini_llm.ChatGoogleGenerativeAI") as mock_chat:
mock_chat.return_value.model = "gemini-1.5-pro"

GeminiLLM(name="TestGemini", role=Role.PERSONA)

assert mock_chat.call_args[1]["base_url"] == "https://google.example/v1"

def test_init_strips_sampling_params_for_gemini_3(self, default_llm_kwargs):
"""Gemini 3.x: temperature/top_p/top_k are no longer recommended and
must not be forced onto the model (Google migration guidance)."""
Expand Down
Loading