mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-20 08:23:47 +00:00
[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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user