mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
feat(dashscope): add Anthropic Messages support
Add DashScopeAnthropicMessagesConfig to route /v1/messages calls to DashScope's Anthropic-compatible endpoint at https://dashscope.aliyuncs.com/apps/anthropic/v1/messages (and the international ...dashscope-intl... mirror). The config extends AnthropicMessagesConfig (the same pattern used by MinimaxMessagesConfig and DeepSeekAnthropicMessagesConfig) and is wired into ProviderConfigManager.get_provider_anthropic_messages_config for the existing LlmProviders.DASHSCOPE enum entry. Two DashScope-specific behaviors: - URL rewriting: recognize the OpenAI-compatible base URLs (.../compatible-mode/v1[, /chat/completions]) and rewrite them to the Anthropic Messages endpoint, since callers commonly reuse the chat-completions base URL. - Header normalization: strip anthropic-version / anthropic-beta headers, which DashScope's endpoint does not accept. Pull api_key from DASHSCOPE_API_KEY when not provided.
This commit is contained in:
parent
f005afa146
commit
33dbd05199
4 changed files with 200 additions and 0 deletions
3
litellm/llms/dashscope/messages/__init__.py
Normal file
3
litellm/llms/dashscope/messages/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from .transformation import DashScopeAnthropicMessagesConfig
|
||||
|
||||
__all__ = ["DashScopeAnthropicMessagesConfig"]
|
||||
99
litellm/llms/dashscope/messages/transformation.py
Normal file
99
litellm/llms/dashscope/messages/transformation.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
"""
|
||||
DashScope Anthropic transformation config - extends AnthropicMessagesConfig for
|
||||
DashScope's Anthropic-compatible Messages API.
|
||||
"""
|
||||
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
|
||||
AnthropicMessagesConfig,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
DEFAULT_DASHSCOPE_ANTHROPIC_MESSAGES_API_BASE = "https://dashscope.aliyuncs.com/apps/anthropic/v1/messages"
|
||||
_COMPATIBLE_MODE_CHAT_COMPLETIONS_SUFFIX = "/compatible-mode/v1/chat/completions"
|
||||
_COMPATIBLE_MODE_SUFFIX = "/compatible-mode/v1"
|
||||
_ANTHROPIC_MESSAGES_SUFFIX = "/apps/anthropic"
|
||||
_MESSAGES_SUFFIX = "/v1/messages"
|
||||
|
||||
|
||||
class DashScopeAnthropicMessagesConfig(AnthropicMessagesConfig):
|
||||
"""
|
||||
DashScope Anthropic configuration that extends AnthropicMessagesConfig.
|
||||
|
||||
DashScope (Alibaba Cloud) exposes an Anthropic-compatible Messages API at:
|
||||
- China: https://dashscope.aliyuncs.com/apps/anthropic/v1/messages
|
||||
- International: https://dashscope-intl.aliyuncs.com/apps/anthropic/v1/messages
|
||||
|
||||
The OpenAI-compatible base URLs (``.../compatible-mode/v1`` and
|
||||
``.../compatible-mode/v1/chat/completions``) are also recognized and
|
||||
rewritten to the Anthropic Messages endpoint, since callers commonly reuse
|
||||
the chat-completions base URL.
|
||||
|
||||
Supported models: any DashScope-hosted model that is exposed via the
|
||||
Anthropic Messages endpoint (e.g. ``qwen3-max``, ``qwen-plus``,
|
||||
``qwen-max``).
|
||||
"""
|
||||
|
||||
@property
|
||||
def custom_llm_provider(self) -> Optional[str]:
|
||||
return "dashscope"
|
||||
|
||||
@staticmethod
|
||||
def _get_api_key(api_key: Optional[str] = None) -> Optional[str]:
|
||||
return api_key or get_secret_str("DASHSCOPE_API_KEY")
|
||||
|
||||
@staticmethod
|
||||
def _get_anthropic_messages_api_base(api_base: Optional[str] = None) -> str:
|
||||
if not api_base:
|
||||
return DEFAULT_DASHSCOPE_ANTHROPIC_MESSAGES_API_BASE
|
||||
|
||||
base_url = api_base.rstrip("/")
|
||||
if base_url.endswith(_MESSAGES_SUFFIX):
|
||||
return base_url
|
||||
if base_url.endswith(_COMPATIBLE_MODE_CHAT_COMPLETIONS_SUFFIX):
|
||||
root = base_url[: -len(_COMPATIBLE_MODE_CHAT_COMPLETIONS_SUFFIX)]
|
||||
return f"{root}{_ANTHROPIC_MESSAGES_SUFFIX}{_MESSAGES_SUFFIX}"
|
||||
if base_url.endswith(_COMPATIBLE_MODE_SUFFIX):
|
||||
root = base_url[: -len(_COMPATIBLE_MODE_SUFFIX)]
|
||||
return f"{root}{_ANTHROPIC_MESSAGES_SUFFIX}{_MESSAGES_SUFFIX}"
|
||||
if base_url.endswith(f"{_ANTHROPIC_MESSAGES_SUFFIX}/v1"):
|
||||
return f"{base_url}/messages"
|
||||
if base_url.endswith(_ANTHROPIC_MESSAGES_SUFFIX):
|
||||
return f"{base_url}{_MESSAGES_SUFFIX}"
|
||||
|
||||
return f"{base_url}{_MESSAGES_SUFFIX}"
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
api_key: Optional[str],
|
||||
model: str,
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
stream: Optional[bool] = None,
|
||||
) -> str:
|
||||
return self._get_anthropic_messages_api_base(api_base=api_base)
|
||||
|
||||
def validate_anthropic_messages_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
messages: List[Any],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
) -> Tuple[dict, Optional[str]]:
|
||||
dynamic_api_key = self._get_api_key(api_key=api_key)
|
||||
if dynamic_api_key and "x-api-key" not in headers:
|
||||
headers["x-api-key"] = dynamic_api_key
|
||||
if "content-type" not in headers:
|
||||
headers["content-type"] = "application/json"
|
||||
|
||||
# DashScope's Anthropic-compatible endpoint does not accept Anthropic
|
||||
# version/beta headers. Keep this provider-specific so other Anthropic
|
||||
# Messages providers retain their existing behavior.
|
||||
headers.pop("anthropic-version", None)
|
||||
headers.pop("anthropic-beta", None)
|
||||
return headers, api_base
|
||||
|
|
@ -8301,6 +8301,12 @@ class ProviderConfigManager:
|
|||
)
|
||||
|
||||
return DeepSeekAnthropicMessagesConfig()
|
||||
elif litellm.LlmProviders.DASHSCOPE == provider:
|
||||
from litellm.llms.dashscope.messages.transformation import (
|
||||
DashScopeAnthropicMessagesConfig,
|
||||
)
|
||||
|
||||
return DashScopeAnthropicMessagesConfig()
|
||||
elif litellm.LlmProviders.TENCENT == provider:
|
||||
from litellm.llms.tencent.messages.transformation import (
|
||||
TencentAnthropicMessagesConfig,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
Unit tests for DashScope Anthropic-compatible Messages support.
|
||||
"""
|
||||
|
||||
from litellm.llms.dashscope.messages.transformation import (
|
||||
DEFAULT_DASHSCOPE_ANTHROPIC_MESSAGES_API_BASE,
|
||||
DashScopeAnthropicMessagesConfig,
|
||||
)
|
||||
from litellm.types.utils import LlmProviders
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
|
||||
def test_should_map_dashscope_compatible_base_to_anthropic_messages_endpoint():
|
||||
config = DashScopeAnthropicMessagesConfig()
|
||||
|
||||
assert (
|
||||
config.get_complete_url(
|
||||
api_base=None,
|
||||
api_key=None,
|
||||
model="qwen3-max",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
== DEFAULT_DASHSCOPE_ANTHROPIC_MESSAGES_API_BASE
|
||||
)
|
||||
assert (
|
||||
config.get_complete_url(
|
||||
api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
api_key=None,
|
||||
model="qwen3-max",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
== "https://dashscope.aliyuncs.com/apps/anthropic/v1/messages"
|
||||
)
|
||||
assert (
|
||||
config.get_complete_url(
|
||||
api_base="https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
api_key=None,
|
||||
model="qwen3-max",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
== "https://dashscope-intl.aliyuncs.com/apps/anthropic/v1/messages"
|
||||
)
|
||||
assert (
|
||||
config.get_complete_url(
|
||||
api_base="https://dashscope.aliyuncs.com/apps/anthropic",
|
||||
api_key=None,
|
||||
model="qwen3-max",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
== "https://dashscope.aliyuncs.com/apps/anthropic/v1/messages"
|
||||
)
|
||||
|
||||
|
||||
def test_should_prepare_dashscope_anthropic_messages_headers():
|
||||
config = DashScopeAnthropicMessagesConfig()
|
||||
|
||||
headers, api_base = config.validate_anthropic_messages_environment(
|
||||
headers={
|
||||
"anthropic-version": "2023-06-01",
|
||||
"anthropic-beta": "prompt-caching-2024-07-31",
|
||||
},
|
||||
model="qwen3-max",
|
||||
messages=[{"role": "user", "content": "hello"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key="dashscope-key",
|
||||
api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
)
|
||||
|
||||
assert headers["x-api-key"] == "dashscope-key"
|
||||
assert headers["content-type"] == "application/json"
|
||||
assert "anthropic-version" not in headers
|
||||
assert "anthropic-beta" not in headers
|
||||
assert api_base == "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
|
||||
def test_should_route_dashscope_provider_to_native_messages_config():
|
||||
qwen_max_config = ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model="qwen3-max",
|
||||
provider=LlmProviders.DASHSCOPE,
|
||||
)
|
||||
qwen_plus_config = ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model="qwen-plus",
|
||||
provider=LlmProviders.DASHSCOPE,
|
||||
)
|
||||
|
||||
assert isinstance(qwen_max_config, DashScopeAnthropicMessagesConfig)
|
||||
assert isinstance(qwen_plus_config, DashScopeAnthropicMessagesConfig)
|
||||
Loading…
Add table
Reference in a new issue