Skip to content

Commit 9b0f871

Browse files
Add /vllm/* and /mistral/* passthrough endpoints (adds support for Mistral OCR via passthrough)
* feat(llm_passthrough_endpoints.py): support mistral passthrough Closes #9051 * feat(llm_passthrough_endpoints.py): initial commit for adding vllm passthrough route * feat(vllm/common_utils.py): add new vllm model info route make it possible to use vllm passthrough route via factory function * fix(llm_passthrough_endpoints.py): add all methods to vllm passthrough route * fix: fix linting error * fix: fix linting error * fix: fix ruff check * fix(proxy/_types.py): add new passthrough routes * docs(config_settings.md): add mistral env vars to docs
1 parent 8faf569 commit 9b0f871

File tree

12 files changed

+450
-176
lines changed

12 files changed

+450
-176
lines changed

docs/my-website/docs/proxy/config_settings.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ router_settings:
449449
| LITELLM_TOKEN | Access token for LiteLLM integration
450450
| LITELLM_PRINT_STANDARD_LOGGING_PAYLOAD | If true, prints the standard logging payload to the console - useful for debugging
451451
| LOGFIRE_TOKEN | Token for Logfire logging service
452+
| MISTRAL_API_BASE | Base URL for Mistral API
453+
| MISTRAL_API_KEY | API key for Mistral API
452454
| MICROSOFT_CLIENT_ID | Client ID for Microsoft services
453455
| MICROSOFT_CLIENT_SECRET | Client secret for Microsoft services
454456
| MICROSOFT_TENANT | Tenant ID for Microsoft Azure

litellm/llms/anthropic/chat/transformation.py

Lines changed: 2 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
token_counter,
4545
)
4646

47-
from ..common_utils import AnthropicError, process_anthropic_headers
47+
from ..common_utils import AnthropicError, AnthropicModelInfo, process_anthropic_headers
4848

4949
if TYPE_CHECKING:
5050
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
@@ -54,7 +54,7 @@
5454
LoggingClass = Any
5555

5656

57-
class AnthropicConfig(BaseConfig):
57+
class AnthropicConfig(AnthropicModelInfo, BaseConfig):
5858
"""
5959
Reference: https://docs.anthropic.com/claude/reference/messages_post
6060
@@ -127,41 +127,6 @@ def get_cache_control_headers(self) -> dict:
127127
"anthropic-beta": "prompt-caching-2024-07-31",
128128
}
129129

130-
def get_anthropic_headers(
131-
self,
132-
api_key: str,
133-
anthropic_version: Optional[str] = None,
134-
computer_tool_used: bool = False,
135-
prompt_caching_set: bool = False,
136-
pdf_used: bool = False,
137-
is_vertex_request: bool = False,
138-
user_anthropic_beta_headers: Optional[List[str]] = None,
139-
) -> dict:
140-
betas = set()
141-
if prompt_caching_set:
142-
betas.add("prompt-caching-2024-07-31")
143-
if computer_tool_used:
144-
betas.add("computer-use-2024-10-22")
145-
if pdf_used:
146-
betas.add("pdfs-2024-09-25")
147-
headers = {
148-
"anthropic-version": anthropic_version or "2023-06-01",
149-
"x-api-key": api_key,
150-
"accept": "application/json",
151-
"content-type": "application/json",
152-
}
153-
154-
if user_anthropic_beta_headers is not None:
155-
betas.update(user_anthropic_beta_headers)
156-
157-
# Don't send any beta headers to Vertex, Vertex has failed requests when they are sent
158-
if is_vertex_request is True:
159-
pass
160-
elif len(betas) > 0:
161-
headers["anthropic-beta"] = ",".join(betas)
162-
163-
return headers
164-
165130
def _map_tool_choice(
166131
self, tool_choice: Optional[str], parallel_tool_use: Optional[bool]
167132
) -> Optional[AnthropicMessagesToolChoice]:
@@ -446,49 +411,6 @@ def _create_json_tool_call_for_response_format(
446411
)
447412
return _tool
448413

449-
def is_cache_control_set(self, messages: List[AllMessageValues]) -> bool:
450-
"""
451-
Return if {"cache_control": ..} in message content block
452-
453-
Used to check if anthropic prompt caching headers need to be set.
454-
"""
455-
for message in messages:
456-
if message.get("cache_control", None) is not None:
457-
return True
458-
_message_content = message.get("content")
459-
if _message_content is not None and isinstance(_message_content, list):
460-
for content in _message_content:
461-
if "cache_control" in content:
462-
return True
463-
464-
return False
465-
466-
def is_computer_tool_used(
467-
self, tools: Optional[List[AllAnthropicToolsValues]]
468-
) -> bool:
469-
if tools is None:
470-
return False
471-
for tool in tools:
472-
if "type" in tool and tool["type"].startswith("computer_"):
473-
return True
474-
return False
475-
476-
def is_pdf_used(self, messages: List[AllMessageValues]) -> bool:
477-
"""
478-
Set to true if media passed into messages.
479-
480-
"""
481-
for message in messages:
482-
if (
483-
"content" in message
484-
and message["content"] is not None
485-
and isinstance(message["content"], list)
486-
):
487-
for content in message["content"]:
488-
if "type" in content and content["type"] != "text":
489-
return True
490-
return False
491-
492414
def translate_system_message(
493415
self, messages: List[AllMessageValues]
494416
) -> List[AnthropicSystemMessageContent]:
@@ -862,47 +784,3 @@ def get_error_class(
862784
message=error_message,
863785
headers=cast(httpx.Headers, headers),
864786
)
865-
866-
def _get_user_anthropic_beta_headers(
867-
self, anthropic_beta_header: Optional[str]
868-
) -> Optional[List[str]]:
869-
if anthropic_beta_header is None:
870-
return None
871-
return anthropic_beta_header.split(",")
872-
873-
def validate_environment(
874-
self,
875-
headers: dict,
876-
model: str,
877-
messages: List[AllMessageValues],
878-
optional_params: dict,
879-
litellm_params: dict,
880-
api_key: Optional[str] = None,
881-
api_base: Optional[str] = None,
882-
) -> Dict:
883-
if api_key is None:
884-
raise litellm.AuthenticationError(
885-
message="Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params. Please set `ANTHROPIC_API_KEY` in your environment vars",
886-
llm_provider="anthropic",
887-
model=model,
888-
)
889-
890-
tools = optional_params.get("tools")
891-
prompt_caching_set = self.is_cache_control_set(messages=messages)
892-
computer_tool_used = self.is_computer_tool_used(tools=tools)
893-
pdf_used = self.is_pdf_used(messages=messages)
894-
user_anthropic_beta_headers = self._get_user_anthropic_beta_headers(
895-
anthropic_beta_header=headers.get("anthropic-beta")
896-
)
897-
anthropic_headers = self.get_anthropic_headers(
898-
computer_tool_used=computer_tool_used,
899-
prompt_caching_set=prompt_caching_set,
900-
pdf_used=pdf_used,
901-
api_key=api_key,
902-
is_vertex_request=optional_params.get("is_vertex_request", False),
903-
user_anthropic_beta_headers=user_anthropic_beta_headers,
904-
)
905-
906-
headers = {**headers, **anthropic_headers}
907-
908-
return headers

litellm/llms/anthropic/common_utils.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
This file contains common utils for anthropic calls.
33
"""
44

5-
from typing import List, Optional, Union
5+
from typing import Dict, List, Optional, Union
66

77
import httpx
88

99
import litellm
1010
from litellm.llms.base_llm.base_utils import BaseLLMModelInfo
1111
from litellm.llms.base_llm.chat.transformation import BaseLLMException
1212
from litellm.secret_managers.main import get_secret_str
13+
from litellm.types.llms.anthropic import AllAnthropicToolsValues
14+
from litellm.types.llms.openai import AllMessageValues
1315

1416

1517
class AnthropicError(BaseLLMException):
@@ -23,6 +25,128 @@ def __init__(
2325

2426

2527
class AnthropicModelInfo(BaseLLMModelInfo):
28+
def is_cache_control_set(self, messages: List[AllMessageValues]) -> bool:
29+
"""
30+
Return if {"cache_control": ..} in message content block
31+
32+
Used to check if anthropic prompt caching headers need to be set.
33+
"""
34+
for message in messages:
35+
if message.get("cache_control", None) is not None:
36+
return True
37+
_message_content = message.get("content")
38+
if _message_content is not None and isinstance(_message_content, list):
39+
for content in _message_content:
40+
if "cache_control" in content:
41+
return True
42+
43+
return False
44+
45+
def is_computer_tool_used(
46+
self, tools: Optional[List[AllAnthropicToolsValues]]
47+
) -> bool:
48+
if tools is None:
49+
return False
50+
for tool in tools:
51+
if "type" in tool and tool["type"].startswith("computer_"):
52+
return True
53+
return False
54+
55+
def is_pdf_used(self, messages: List[AllMessageValues]) -> bool:
56+
"""
57+
Set to true if media passed into messages.
58+
59+
"""
60+
for message in messages:
61+
if (
62+
"content" in message
63+
and message["content"] is not None
64+
and isinstance(message["content"], list)
65+
):
66+
for content in message["content"]:
67+
if "type" in content and content["type"] != "text":
68+
return True
69+
return False
70+
71+
def _get_user_anthropic_beta_headers(
72+
self, anthropic_beta_header: Optional[str]
73+
) -> Optional[List[str]]:
74+
if anthropic_beta_header is None:
75+
return None
76+
return anthropic_beta_header.split(",")
77+
78+
def get_anthropic_headers(
79+
self,
80+
api_key: str,
81+
anthropic_version: Optional[str] = None,
82+
computer_tool_used: bool = False,
83+
prompt_caching_set: bool = False,
84+
pdf_used: bool = False,
85+
is_vertex_request: bool = False,
86+
user_anthropic_beta_headers: Optional[List[str]] = None,
87+
) -> dict:
88+
betas = set()
89+
if prompt_caching_set:
90+
betas.add("prompt-caching-2024-07-31")
91+
if computer_tool_used:
92+
betas.add("computer-use-2024-10-22")
93+
if pdf_used:
94+
betas.add("pdfs-2024-09-25")
95+
headers = {
96+
"anthropic-version": anthropic_version or "2023-06-01",
97+
"x-api-key": api_key,
98+
"accept": "application/json",
99+
"content-type": "application/json",
100+
}
101+
102+
if user_anthropic_beta_headers is not None:
103+
betas.update(user_anthropic_beta_headers)
104+
105+
# Don't send any beta headers to Vertex, Vertex has failed requests when they are sent
106+
if is_vertex_request is True:
107+
pass
108+
elif len(betas) > 0:
109+
headers["anthropic-beta"] = ",".join(betas)
110+
111+
return headers
112+
113+
def validate_environment(
114+
self,
115+
headers: dict,
116+
model: str,
117+
messages: List[AllMessageValues],
118+
optional_params: dict,
119+
litellm_params: dict,
120+
api_key: Optional[str] = None,
121+
api_base: Optional[str] = None,
122+
) -> Dict:
123+
if api_key is None:
124+
raise litellm.AuthenticationError(
125+
message="Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params. Please set `ANTHROPIC_API_KEY` in your environment vars",
126+
llm_provider="anthropic",
127+
model=model,
128+
)
129+
130+
tools = optional_params.get("tools")
131+
prompt_caching_set = self.is_cache_control_set(messages=messages)
132+
computer_tool_used = self.is_computer_tool_used(tools=tools)
133+
pdf_used = self.is_pdf_used(messages=messages)
134+
user_anthropic_beta_headers = self._get_user_anthropic_beta_headers(
135+
anthropic_beta_header=headers.get("anthropic-beta")
136+
)
137+
anthropic_headers = self.get_anthropic_headers(
138+
computer_tool_used=computer_tool_used,
139+
prompt_caching_set=prompt_caching_set,
140+
pdf_used=pdf_used,
141+
api_key=api_key,
142+
is_vertex_request=optional_params.get("is_vertex_request", False),
143+
user_anthropic_beta_headers=user_anthropic_beta_headers,
144+
)
145+
146+
headers = {**headers, **anthropic_headers}
147+
148+
return headers
149+
26150
@staticmethod
27151
def get_api_base(api_base: Optional[str] = None) -> Optional[str]:
28152
return (

litellm/llms/base_llm/base_utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ def get_api_key(api_key: Optional[str] = None) -> Optional[str]:
4444
def get_api_base(api_base: Optional[str] = None) -> Optional[str]:
4545
pass
4646

47+
@abstractmethod
48+
def validate_environment(
49+
self,
50+
headers: dict,
51+
model: str,
52+
messages: List[AllMessageValues],
53+
optional_params: dict,
54+
litellm_params: dict,
55+
api_key: Optional[str] = None,
56+
api_base: Optional[str] = None,
57+
) -> dict:
58+
pass
59+
4760
@staticmethod
4861
@abstractmethod
4962
def get_base_model(model: str) -> Optional[str]:

litellm/llms/openai/chat/gpt_transformation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ def get_api_base(api_base: Optional[str] = None) -> Optional[str]:
389389
)
390390

391391
@staticmethod
392-
def get_base_model(model: str) -> str:
392+
def get_base_model(model: Optional[str] = None) -> Optional[str]:
393393
return model
394394

395395
def get_model_response_iterator(

litellm/llms/topaz/common_utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import List, Optional
22

33
from litellm.secret_managers.main import get_secret_str
4+
from litellm.types.llms.openai import AllMessageValues
45

56
from ..base_llm.base_utils import BaseLLMModelInfo
67
from ..base_llm.chat.transformation import BaseLLMException
@@ -11,6 +12,26 @@ class TopazException(BaseLLMException):
1112

1213

1314
class TopazModelInfo(BaseLLMModelInfo):
15+
def validate_environment(
16+
self,
17+
headers: dict,
18+
model: str,
19+
messages: List[AllMessageValues],
20+
optional_params: dict,
21+
litellm_params: dict,
22+
api_key: Optional[str] = None,
23+
api_base: Optional[str] = None,
24+
) -> dict:
25+
if api_key is None:
26+
raise ValueError(
27+
"API key is required for Topaz image variations. Set via `TOPAZ_API_KEY` or `api_key=..`"
28+
)
29+
return {
30+
# "Content-Type": "multipart/form-data",
31+
"Accept": "image/jpeg",
32+
"X-API-Key": api_key,
33+
}
34+
1435
def get_models(
1536
self, api_key: Optional[str] = None, api_base: Optional[str] = None
1637
) -> List[str]:

0 commit comments

Comments
 (0)