[Bug]: Deepseek error on proxy after upgrading to 1.61.13-stable (#8860)

* fix deepseek error

* test_deepseek_provider_async_completion

* fix get_complete_url
This commit is contained in:
Ishaan Jaff
2025-02-26 21:11:06 -08:00
committed by GitHub
parent 3de4209569
commit 6231052b18
12 changed files with 103 additions and 26 deletions
@@ -26,7 +26,7 @@ else:
class AiohttpOpenAIChatConfig(OpenAILikeChatConfig):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
@@ -35,6 +35,8 @@ class AiohttpOpenAIChatConfig(OpenAILikeChatConfig):
Ensure - /v1/chat/completions is at the end of the url
"""
if api_base is None:
api_base = "https://api.openai.com"
if not api_base.endswith("/chat/completions"):
api_base += "/chat/completions"
+5 -1
View File
@@ -51,7 +51,7 @@ class AzureAIStudioConfig(OpenAIConfig):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
@@ -72,6 +72,10 @@ class AzureAIStudioConfig(OpenAIConfig):
- A complete URL string, e.g.,
"https://litellm8397336933.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview"
"""
if api_base is None:
raise ValueError(
f"api_base is required for Azure AI Studio. Please set the api_base parameter. Passed `api_base={api_base}`"
)
original_url = httpx.URL(api_base)
# Extract api_version or use default
+3 -1
View File
@@ -249,7 +249,7 @@ class BaseConfig(ABC):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
@@ -261,6 +261,8 @@ class BaseConfig(ABC):
Some providers need `model` in `api_base`
"""
if api_base is None:
raise ValueError("api_base is required")
return api_base
@abstractmethod
@@ -73,7 +73,7 @@ class AmazonInvokeConfig(BaseConfig, BaseAWSLLM):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
@@ -11,6 +11,7 @@ from litellm.llms.base_llm.chat.transformation import (
BaseLLMException,
LiteLLMLoggingObj,
)
from litellm.secret_managers.main import get_secret_str
from litellm.types.llms.openai import AllMessageValues
from litellm.types.utils import (
ChatCompletionToolCallChunk,
@@ -75,11 +76,16 @@ class CloudflareChatConfig(BaseConfig):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
) -> str:
if api_base is None:
account_id = get_secret_str("CLOUDFLARE_ACCOUNT_ID")
api_base = (
f"https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run/"
)
return api_base + model
def get_supported_openai_params(self, model: str) -> List[str]:
@@ -34,3 +34,21 @@ class DeepSeekChatConfig(OpenAIGPTConfig):
) # type: ignore
dynamic_api_key = api_key or get_secret_str("DEEPSEEK_API_KEY")
return api_base, dynamic_api_key
def get_complete_url(
self,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
) -> str:
"""
If api_base is not provided, use the default DeepSeek /chat/completions endpoint.
"""
if not api_base:
api_base = "https://api.deepseek.com/beta"
if not api_base.endswith("/chat/completions"):
api_base = f"{api_base}/chat/completions"
return api_base
@@ -353,7 +353,7 @@ class OllamaConfig(BaseConfig):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
@@ -365,6 +365,8 @@ class OllamaConfig(BaseConfig):
Some providers need `model` in `api_base`
"""
if api_base is None:
api_base = "http://localhost:11434"
if api_base.endswith("/api/generate"):
url = api_base
else:
@@ -263,7 +263,7 @@ class OpenAIGPTConfig(BaseLLMModelInfo, BaseConfig):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
@@ -274,6 +274,8 @@ class OpenAIGPTConfig(BaseLLMModelInfo, BaseConfig):
Returns:
str: The complete URL for the API call.
"""
if api_base is None:
api_base = "https://api.openai.com"
endpoint = "chat/completions"
# Remove trailing slash from api_base if present
@@ -138,7 +138,7 @@ class ReplicateConfig(BaseConfig):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
+1 -1
View File
@@ -80,7 +80,7 @@ class IBMWatsonXChatConfig(IBMWatsonXMixin, OpenAIGPTConfig):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
@@ -315,7 +315,7 @@ class IBMWatsonXAIConfig(IBMWatsonXMixin, BaseConfig):
def get_complete_url(
self,
api_base: str,
api_base: Optional[str],
model: str,
optional_params: dict,
stream: Optional[bool] = None,
@@ -47,26 +47,67 @@ def test_deepseek_mock_completion(stream):
assert response is not None
@pytest.mark.parametrize("stream", [True, False])
@pytest.mark.parametrize("stream", [False, True])
@pytest.mark.asyncio
async def test_deepseek_mock_async_completion(stream):
async def test_deepseek_provider_async_completion(stream):
"""
Deepseek API is hanging. Mock the call, to a fake endpoint, so we can confirm our integration is working.
Test that Deepseek provider requests are formatted correctly with the proper parameters
"""
import litellm
from litellm import completion, acompletion
import json
from unittest.mock import patch, AsyncMock, MagicMock
from litellm import acompletion
litellm._turn_on_debug()
response = await acompletion(
model="deepseek/deepseek-reasoner",
messages=[{"role": "user", "content": "Hello, world!"}],
api_base="https://exampleopenaiendpoint-production.up.railway.app/v1/chat/completions",
stream=stream,
)
print(f"response: {response}")
if stream:
async for chunk in response:
print(chunk)
else:
assert response is not None
# Set up the test parameters
api_key = "fake_api_key"
model = "deepseek/deepseek-reasoner"
messages = [{"role": "user", "content": "Hello, world!"}]
# Mock AsyncHTTPHandler.post method for async test
with patch(
"litellm.llms.custom_httpx.llm_http_handler.AsyncHTTPHandler.post"
) as mock_post:
mock_response_data = litellm.ModelResponse(
choices=[
litellm.Choices(
message=litellm.Message(content="Hello!"),
index=0,
finish_reason="stop",
)
]
).model_dump()
# Create a proper mock response
mock_response = MagicMock() # Use MagicMock instead of AsyncMock
mock_response.status_code = 200
mock_response.text = json.dumps(mock_response_data)
mock_response.headers = {"Content-Type": "application/json"}
# Make json() return a value directly, not a coroutine
mock_response.json.return_value = mock_response_data
# Set the return value for the post method
mock_post.return_value = mock_response
await acompletion(
custom_llm_provider="deepseek",
api_key=api_key,
model=model,
messages=messages,
stream=stream,
)
# Verify the request was made with the correct parameters
mock_post.assert_called_once()
call_args = mock_post.call_args
print("request call=", json.dumps(call_args.kwargs, indent=4, default=str))
# Check request body
request_body = json.loads(call_args.kwargs["data"])
assert call_args.kwargs["url"] == "https://api.deepseek.com/beta/chat/completions"
assert (
request_body["model"] == "deepseek-reasoner"
) # Model name should be stripped of provider prefix
assert request_body["messages"] == messages
assert request_body["stream"] == stream