From 665c03b3a91cc89c7481db22859f7247a52ef04d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 27 Jan 2026 06:38:38 +0000 Subject: [PATCH 1/5] chore: strip dev suffix for stable release --- src/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/version.py b/src/version.py index c5f7bb7..dd9eb7d 100644 --- a/src/version.py +++ b/src/version.py @@ -1,7 +1,7 @@ """Version information for OSA.""" -__version__ = "0.5.3.dev0" -__version_info__ = (0, 5, 3, "dev") +__version__ = "0.5.3" +__version_info__ = (0, 5, 3) def get_version() -> str: From a9f0394a429e79c29d55cf49063c1502d95c64be Mon Sep 17 00:00:00 2001 From: Seyed Yahya Shirazi Date: Tue, 27 Jan 2026 10:31:31 -0800 Subject: [PATCH 2/5] feat: auto-select Anthropic provider for Anthropic models Override provider parameter to use 'Anthropic' when model starts with 'anthropic/' for optimal performance. Adds debug logging and updates docstring to document this behavior. Closes #116 --- src/core/services/litellm_llm.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/core/services/litellm_llm.py b/src/core/services/litellm_llm.py index 47c5631..8aa9a64 100644 --- a/src/core/services/litellm_llm.py +++ b/src/core/services/litellm_llm.py @@ -28,12 +28,15 @@ ]) """ +import logging import os from typing import Any from langchain_core.language_models import BaseChatModel from langchain_core.messages import BaseMessage +logger = logging.getLogger(__name__) + def create_openrouter_llm( model: str = "openai/gpt-oss-120b", @@ -50,12 +53,18 @@ def create_openrouter_llm( When caching is enabled, system messages are automatically transformed to include cache_control markers for 90% cost reduction on cache hits. + Provider Selection: + - Anthropic models (anthropic/*) automatically use provider="Anthropic" + for best performance, regardless of the provider parameter + - Other models use the specified provider or default routing + Args: model: Model identifier (e.g., "openai/gpt-oss-120b", "anthropic/claude-haiku-4.5") api_key: OpenRouter API key (defaults to OPENROUTER_API_KEY env var) temperature: Sampling temperature (0.0-1.0) max_tokens: Maximum tokens to generate - provider: Specific provider to use (e.g., "Cerebras", "Anthropic") + provider: Specific provider to use (e.g., "Cerebras", "DeepInfra/FP8"). + Note: Anthropic models always use "Anthropic" provider for optimal performance. user_id: User identifier for cache optimization (sticky routing) enable_caching: Enable prompt caching. If None (default), enabled for all models. OpenRouter/LiteLLM gracefully handles models that don't support caching. @@ -77,10 +86,18 @@ def create_openrouter_llm( }, } + # Auto-select Anthropic provider for Anthropic models (better performance) + # Override any default provider if this is an Anthropic model + if model.startswith("anthropic/"): + effective_provider = "Anthropic" + logger.debug("Auto-selected Anthropic provider for model %s (better performance)", model) + else: + effective_provider = provider + # Provider routing (e.g., {"order": ["DeepInfra/FP8"]}) # Use "order" not "only" - OpenRouter requires exact routing field name - if provider: - model_kwargs["provider"] = {"order": [provider]} + if effective_provider: + model_kwargs["provider"] = {"order": [effective_provider]} # User ID for sticky cache routing if user_id: From 11edddb41c2df7ecfec5cac5ac1b2d7e16ef5414 Mon Sep 17 00:00:00 2001 From: Seyed Yahya Shirazi Date: Tue, 27 Jan 2026 10:35:08 -0800 Subject: [PATCH 3/5] test: add comprehensive tests for provider auto-selection Add 16 unit tests covering: - Anthropic models auto-selecting Anthropic provider - Anthropic models overriding any specified provider - Non-Anthropic models using specified provider - General LLM configuration (temperature, max_tokens, etc.) - Caching wrapper integration All tests pass (16/16) --- tests/test_core/test_litellm_llm.py | 175 ++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 tests/test_core/test_litellm_llm.py diff --git a/tests/test_core/test_litellm_llm.py b/tests/test_core/test_litellm_llm.py new file mode 100644 index 0000000..36227f4 --- /dev/null +++ b/tests/test_core/test_litellm_llm.py @@ -0,0 +1,175 @@ +"""Tests for LiteLLM OpenRouter integration. + +These tests verify the OpenRouter LLM creation logic, particularly the +provider auto-selection behavior for Anthropic models. +""" + +from src.core.services.litellm_llm import create_openrouter_llm + + +class TestCreateOpenRouterLLMProviderSelection: + """Tests for provider auto-selection in create_openrouter_llm.""" + + def test_anthropic_model_uses_anthropic_provider(self) -> None: + """Anthropic models should auto-select Anthropic provider.""" + llm = create_openrouter_llm( + model="anthropic/claude-haiku-4.5", + api_key="test-key", + ) + # Access the wrapped LLM's model_kwargs + assert llm.llm.model_kwargs["provider"] == {"order": ["Anthropic"]} + + def test_anthropic_model_overrides_default_provider(self) -> None: + """Anthropic models should override any specified provider.""" + llm = create_openrouter_llm( + model="anthropic/claude-sonnet-4.5", + api_key="test-key", + provider="DeepInfra/FP8", # Should be ignored for Anthropic models + ) + # Should use Anthropic provider, not the specified one + assert llm.llm.model_kwargs["provider"] == {"order": ["Anthropic"]} + + def test_anthropic_model_with_different_version(self) -> None: + """All Anthropic model versions should auto-select Anthropic provider.""" + llm = create_openrouter_llm( + model="anthropic/claude-opus-4", + api_key="test-key", + provider="SomeOtherProvider", + ) + assert llm.llm.model_kwargs["provider"] == {"order": ["Anthropic"]} + + def test_non_anthropic_model_uses_specified_provider(self) -> None: + """Non-Anthropic models should use the specified provider.""" + llm = create_openrouter_llm( + model="openai/gpt-oss-120b", + api_key="test-key", + provider="Cerebras", + ) + assert llm.llm.model_kwargs["provider"] == {"order": ["Cerebras"]} + + def test_non_anthropic_model_with_deepinfra_provider(self) -> None: + """Non-Anthropic models should use DeepInfra provider when specified.""" + llm = create_openrouter_llm( + model="openai/gpt-oss-120b", + api_key="test-key", + provider="DeepInfra/FP8", + ) + assert llm.llm.model_kwargs["provider"] == {"order": ["DeepInfra/FP8"]} + + def test_non_anthropic_model_without_provider(self) -> None: + """Non-Anthropic models with no provider should have no provider key.""" + llm = create_openrouter_llm( + model="openai/gpt-oss-120b", + api_key="test-key", + provider=None, + ) + assert "provider" not in llm.llm.model_kwargs + + def test_default_model_with_default_provider(self) -> None: + """Default model with default provider should use the specified provider.""" + llm = create_openrouter_llm( + api_key="test-key", + # Uses default model="openai/gpt-oss-120b" and provider="Cerebras" + ) + assert llm.llm.model_kwargs["provider"] == {"order": ["Cerebras"]} + + +class TestCreateOpenRouterLLMConfiguration: + """Tests for general LLM configuration options.""" + + def test_model_prefix(self) -> None: + """LLM should use openrouter/ prefix for LiteLLM.""" + llm = create_openrouter_llm( + model="anthropic/claude-haiku-4.5", + api_key="test-key", + ) + # LiteLLM should receive the model with openrouter/ prefix + assert llm.llm.model.startswith("openrouter/") + + def test_temperature_configuration(self) -> None: + """LLM should respect temperature parameter.""" + llm = create_openrouter_llm( + model="openai/gpt-oss-120b", + api_key="test-key", + temperature=0.5, + ) + assert llm.llm.temperature == 0.5 + + def test_max_tokens_configuration(self) -> None: + """LLM should respect max_tokens parameter.""" + llm = create_openrouter_llm( + model="openai/gpt-oss-120b", + api_key="test-key", + max_tokens=1000, + ) + assert llm.llm.max_tokens == 1000 + + def test_user_id_for_sticky_routing(self) -> None: + """LLM should include user ID for cache optimization.""" + llm = create_openrouter_llm( + model="anthropic/claude-haiku-4.5", + api_key="test-key", + user_id="test-user-123", + ) + assert llm.llm.model_kwargs["user"] == "test-user-123" + + def test_extra_headers_for_openrouter(self) -> None: + """LLM should include required OpenRouter headers.""" + llm = create_openrouter_llm( + model="openai/gpt-oss-120b", + api_key="test-key", + ) + headers = llm.llm.model_kwargs["extra_headers"] + assert "HTTP-Referer" in headers + assert "X-Title" in headers + assert headers["HTTP-Referer"] == "https://osc.earth/osa" + assert headers["X-Title"] == "Open Science Assistant" + + def test_streaming_enabled_by_default(self) -> None: + """LLM should have streaming enabled for LangGraph events.""" + llm = create_openrouter_llm( + model="openai/gpt-oss-120b", + api_key="test-key", + ) + assert llm.llm.streaming is True + + +class TestCreateOpenRouterLLMCachingWrapper: + """Tests for caching wrapper integration.""" + + def test_returns_caching_wrapper(self) -> None: + """create_openrouter_llm should return a CachingLLMWrapper.""" + llm = create_openrouter_llm( + model="anthropic/claude-haiku-4.5", + api_key="test-key", + ) + # Should be wrapped for caching + from src.core.services.litellm_llm import CachingLLMWrapper + + assert isinstance(llm, CachingLLMWrapper) + + def test_caching_enabled_by_default(self) -> None: + """Caching should be enabled by default.""" + from src.core.services.litellm_llm import CachingLLMWrapper + + llm = create_openrouter_llm( + model="anthropic/claude-haiku-4.5", + api_key="test-key", + ) + # Should be wrapped by default + assert isinstance(llm, CachingLLMWrapper) + + def test_caching_can_be_disabled(self) -> None: + """Caching should be disableable via parameter.""" + from langchain_litellm import ChatLiteLLM + + from src.core.services.litellm_llm import CachingLLMWrapper + + llm = create_openrouter_llm( + model="anthropic/claude-haiku-4.5", + api_key="test-key", + enable_caching=False, + ) + # Should NOT be wrapped when disabled + assert not isinstance(llm, CachingLLMWrapper) + assert isinstance(llm, ChatLiteLLM) From c9c85c99e498d8887eb89a80039c55c8c924ad4d Mon Sep 17 00:00:00 2001 From: Seyed Yahya Shirazi Date: Tue, 27 Jan 2026 10:35:51 -0800 Subject: [PATCH 4/5] docs: clarify that provider param is ignored for Anthropic models Make docstring more explicit about provider parameter being completely ignored for Anthropic models, addressing code review feedback. --- src/core/services/litellm_llm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/services/litellm_llm.py b/src/core/services/litellm_llm.py index 8aa9a64..840e40d 100644 --- a/src/core/services/litellm_llm.py +++ b/src/core/services/litellm_llm.py @@ -64,7 +64,7 @@ def create_openrouter_llm( temperature: Sampling temperature (0.0-1.0) max_tokens: Maximum tokens to generate provider: Specific provider to use (e.g., "Cerebras", "DeepInfra/FP8"). - Note: Anthropic models always use "Anthropic" provider for optimal performance. + Ignored for Anthropic models, which always use "Anthropic" provider. user_id: User identifier for cache optimization (sticky routing) enable_caching: Enable prompt caching. If None (default), enabled for all models. OpenRouter/LiteLLM gracefully handles models that don't support caching. From 81f65bf9f52cd7f254a08e2861e66e38f730e332 Mon Sep 17 00:00:00 2001 From: Seyed Yahya Shirazi Date: Tue, 27 Jan 2026 11:04:14 -0800 Subject: [PATCH 5/5] fix: restore dev suffix in version (should only strip on main) The merge incorrectly kept version 0.5.3 instead of 0.5.3.dev0. Develop and feature branches should always have .dev suffix. Version suffix stripping should only happen on main branch releases. --- src/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/version.py b/src/version.py index dd9eb7d..c5f7bb7 100644 --- a/src/version.py +++ b/src/version.py @@ -1,7 +1,7 @@ """Version information for OSA.""" -__version__ = "0.5.3" -__version_info__ = (0, 5, 3) +__version__ = "0.5.3.dev0" +__version_info__ = (0, 5, 3, "dev") def get_version() -> str: