mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
test(llm_translation): drop mock-passthrough and dead tests across long-tail providers per CI audit
Function-level deletions per the keep/drop audit (5), plus dead tests flagged in 8b: - test_cohere.py: test_cohere_request_body_with_allowed_params, test_cohere_documents_options_in_request_body (kwarg passthrough into mocked post, c) - test_deepseek_completion.py: test_deepseek_mock_completion, test_deepseek_provider_async_completion (mock theater, a/c) - test_elevenlabs.py: test_elevenlabs_diarize_parameter_passthrough (form-data passthrough on stub, c) - test_huggingface_chat_completion.py: TestHuggingFace test_completion_with_api_base, test_async_completion_with_api_base, test_completion_streaming_with_api_base (URL asserted on mock, b) - test_hyperbolic.py: test_hyperbolic_completion_call (mock_response smoke) - test_minimax_tts.py: test_speech_mock_response (mock-handler-was-called, a) - test_nvidia_nim.py: test_completion_nvidia_nim, test_embedding_nvidia_nim, test_chat_completion_nvidia_nim_with_tools (SDK-patch passthrough, c) - test_rerank.py: test_rerank_custom_api_base, test_complete_base_url_cohere, test_cohere_rerank_v2_client, test_rerank_infer_region_from_model_arn (URL/mock-payload, b/d) - test_text_completion_unit_tests.py: test_huggingface_text_completion_logprobs (permanent @skip, dead), test_acompletion_uses_optimized_http_client (a) - test_together_ai.py: test_tool_call_no_arguments override (dead pass body) - test_triton.py: test_triton_embeddings (depends on external stub endpoint) - test_voyage_ai.py: test_basic_embedding override, test_voyage_ai_embedding_extra_params, test_voyage_ai_embedding_prompt_token_mapping, test_contextual_embedding_integration, test_contextual_embedding_multiple_inputs (patch litellm.embedding wholesale, a/d) - test_watsonx.py: test_watsonx_custom_auth_header, test_watsonx_token_in_env_var, test_watsonx_chat_completions_endpoint (auth header/URL on stub, b) - test_xai.py: test_tool_call_no_arguments override (dead pass body) - test_gemini.py: parametrized exception test literally named 'l' (never collected by pytest; coverage it claims does not exist) Unused imports removed via ruff F401.
This commit is contained in:
parent
73f396b96a
commit
1eafdbfba9
15 changed files with 6 additions and 1384 deletions
|
|
@ -1,25 +1,18 @@
|
|||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
import io
|
||||
import os
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm import RateLimitError, Timeout, completion, completion_cost, embedding
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from litellm import RateLimitError, Timeout, completion, completion_cost, embedding
|
||||
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler
|
||||
from litellm import completion, embedding
|
||||
|
||||
litellm.num_retries = 3
|
||||
|
||||
|
|
@ -205,74 +198,6 @@ async def test_chat_completion_cohere_stream(sync_mode):
|
|||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cohere_request_body_with_allowed_params():
|
||||
"""
|
||||
Test to validate that when allowed_openai_params is provided, the request body contains
|
||||
the correct response_format and reasoning_effort values.
|
||||
"""
|
||||
# Define test parameters
|
||||
test_response_format = {"type": "json"}
|
||||
test_reasoning_effort = "low"
|
||||
test_tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_time",
|
||||
"description": "Get the current time in a given location.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city name, e.g. San Francisco",
|
||||
}
|
||||
},
|
||||
"required": ["location"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
# Create a mock response
|
||||
mock_response = AsyncMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"text": "I am Command, a language model developed by Cohere.",
|
||||
"generation_id": "mock-generation-id",
|
||||
"finish_reason": "COMPLETE",
|
||||
}
|
||||
|
||||
# Mock the AsyncHTTPHandler.post method at the module level
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
||||
return_value=mock_response,
|
||||
) as mock_post:
|
||||
try:
|
||||
await litellm.acompletion(
|
||||
model="cohere/v1/command",
|
||||
messages=[{"content": "what llm are you", "role": "user"}],
|
||||
allowed_openai_params=["tools", "response_format", "reasoning_effort"],
|
||||
response_format=test_response_format,
|
||||
reasoning_effort=test_reasoning_effort,
|
||||
tools=test_tools,
|
||||
)
|
||||
except Exception:
|
||||
pass # We only care about the request body validation
|
||||
|
||||
# Verify the API call was made
|
||||
mock_post.assert_called_once()
|
||||
|
||||
# Get and parse the request body
|
||||
request_data = json.loads(mock_post.call_args.kwargs["data"])
|
||||
print(f"request_data: {request_data}")
|
||||
|
||||
# Validate request contains our specified parameters
|
||||
assert "allowed_openai_params" not in request_data
|
||||
assert request_data["response_format"] == test_response_format
|
||||
assert request_data["reasoning_effort"] == test_reasoning_effort
|
||||
|
||||
|
||||
def test_cohere_embedding_outout_dimensions():
|
||||
litellm._turn_on_debug()
|
||||
response = embedding(
|
||||
|
|
@ -796,62 +721,6 @@ def test_cohere_v2_error_handling():
|
|||
pytest.fail(f"Unexpected error in error handling test: {e}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cohere_documents_options_in_request_body():
|
||||
"""
|
||||
Test that documents parameters is properly included
|
||||
in the request body after transformation (sent via extra_body).
|
||||
"""
|
||||
# Create a mock response
|
||||
mock_response = AsyncMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"text": "Test response with citations",
|
||||
"generation_id": "mock-generation-id",
|
||||
"finish_reason": "COMPLETE",
|
||||
}
|
||||
|
||||
# Mock the AsyncHTTPHandler.post method
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
||||
return_value=mock_response,
|
||||
) as mock_post:
|
||||
try:
|
||||
# Test documents and citation_options parameters
|
||||
test_documents = [
|
||||
{
|
||||
"data": {
|
||||
"title": "Test Document 1",
|
||||
"snippet": "This is test content 1",
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"title": "Test Document 2",
|
||||
"snippet": "This is test content 2",
|
||||
}
|
||||
},
|
||||
]
|
||||
await litellm.acompletion(
|
||||
model="cohere_chat/command-a-03-2025",
|
||||
messages=[{"role": "user", "content": "Test message"}],
|
||||
documents=test_documents,
|
||||
)
|
||||
except Exception:
|
||||
pass # We only care about the request body validation
|
||||
|
||||
# Verify the API call was made
|
||||
mock_post.assert_called_once()
|
||||
|
||||
# Get and parse the request body
|
||||
request_data = json.loads(mock_post.call_args.kwargs["data"])
|
||||
print(f"Request body: {request_data}")
|
||||
|
||||
# Validate that documents and citation_options are in the request body
|
||||
assert "documents" in request_data
|
||||
assert request_data["documents"] == test_documents
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.flaky(retries=3, delay=1)
|
||||
async def test_cohere_v2_conversation_history():
|
||||
|
|
|
|||
|
|
@ -16,97 +16,6 @@ class TestDeepSeekChatCompletion(BaseLLMChatTest):
|
|||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stream", [True, False])
|
||||
def test_deepseek_mock_completion(stream):
|
||||
"""
|
||||
Deepseek API is hanging. Mock the call, to a fake endpoint, so we can confirm our integration is working.
|
||||
"""
|
||||
import litellm
|
||||
from litellm import completion
|
||||
|
||||
litellm._turn_on_debug()
|
||||
|
||||
response = completion(
|
||||
model="deepseek/deepseek-reasoner",
|
||||
messages=[{"role": "user", "content": "Hello, world!"}],
|
||||
api_base="https://exampleopenaiendpoint-production.up.railway.app/v1/chat/completions",
|
||||
stream=stream,
|
||||
mock_response="Hello! How can I help you today?",
|
||||
)
|
||||
print(f"response: {response}")
|
||||
if stream:
|
||||
for chunk in response:
|
||||
print(chunk)
|
||||
else:
|
||||
assert response is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stream", [False, True])
|
||||
@pytest.mark.asyncio
|
||||
async def test_deepseek_provider_async_completion(stream):
|
||||
"""
|
||||
Test that Deepseek provider requests are formatted correctly with the proper parameters
|
||||
"""
|
||||
import litellm
|
||||
import json
|
||||
from unittest.mock import patch, AsyncMock, MagicMock
|
||||
from litellm import acompletion
|
||||
|
||||
litellm._turn_on_debug()
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
def test_completion_cost_deepseek():
|
||||
litellm.set_verbose = True
|
||||
model_name = "deepseek/deepseek-chat"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import sys
|
|||
from typing import Any, Dict
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
import httpx
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
|
|
@ -25,116 +23,6 @@ class TestElevenLabsAudioTranscription(BaseLLMAudioTranscriptionTest):
|
|||
def get_custom_llm_provider(self) -> litellm.LlmProviders:
|
||||
return litellm.LlmProviders.ELEVENLABS
|
||||
|
||||
def test_elevenlabs_diarize_parameter_passthrough(self):
|
||||
"""
|
||||
Test that provider-specific parameters like diarize=True get passed through
|
||||
to the ElevenLabs request form data.
|
||||
"""
|
||||
# Mock successful response
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.text = (
|
||||
'{"text": "Four score and seven years ago", "language_code": "en"}'
|
||||
)
|
||||
mock_response.json.return_value = {
|
||||
"text": "Four score and seven years ago",
|
||||
"language_code": "en",
|
||||
"words": [
|
||||
{"type": "word", "text": "Four", "start": 0.0, "end": 0.5},
|
||||
{"type": "word", "text": "score", "start": 0.5, "end": 1.0},
|
||||
],
|
||||
}
|
||||
|
||||
# Create a mock audio file
|
||||
audio_content = b"fake audio data"
|
||||
|
||||
captured_request_data = {}
|
||||
|
||||
def mock_post(*args, **kwargs):
|
||||
# Capture the request data for verification
|
||||
captured_request_data.update(
|
||||
{
|
||||
"url": kwargs.get("url"),
|
||||
"data": kwargs.get("data"),
|
||||
"files": kwargs.get("files"),
|
||||
"headers": kwargs.get("headers"),
|
||||
"json": kwargs.get("json"),
|
||||
}
|
||||
)
|
||||
return mock_response
|
||||
|
||||
# Mock the HTTPHandler.post method which is what actually makes the request
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
|
||||
with patch.object(HTTPHandler, "post", side_effect=mock_post):
|
||||
try:
|
||||
result = litellm.transcription(
|
||||
model="elevenlabs/scribe_v1",
|
||||
file=audio_content,
|
||||
diarize=True, # This should be passed through to the form data
|
||||
language="en", # This should be mapped to language_code
|
||||
temperature=0.5, # This should also be passed through
|
||||
custom_param="test_value", # This should also be passed through
|
||||
)
|
||||
|
||||
# Verify the request was made with correct form data
|
||||
assert "speech-to-text" in captured_request_data["url"]
|
||||
|
||||
# Check that form data contains the expected parameters
|
||||
form_data = captured_request_data["data"]
|
||||
assert form_data is not None, "Form data should not be None"
|
||||
|
||||
print(f"✅ Captured form data: {form_data}")
|
||||
|
||||
# Check basic required parameters
|
||||
assert "model_id" in form_data, "model_id should be in form data"
|
||||
assert (
|
||||
form_data["model_id"] == "scribe_v1"
|
||||
), f"Expected model_id 'scribe_v1', got {form_data['model_id']}"
|
||||
|
||||
# Check that diarize parameter is passed through
|
||||
assert (
|
||||
"diarize" in form_data
|
||||
), f"diarize should be in form data. Got: {list(form_data.keys())}"
|
||||
assert (
|
||||
form_data["diarize"] == "True"
|
||||
), f"Expected diarize='True', got {form_data['diarize']}"
|
||||
|
||||
# Check that OpenAI language parameter is mapped correctly
|
||||
assert (
|
||||
"language_code" in form_data
|
||||
), "language_code should be in form data"
|
||||
assert (
|
||||
form_data["language_code"] == "en"
|
||||
), f"Expected language_code='en', got {form_data['language_code']}"
|
||||
|
||||
# Check that temperature is passed through
|
||||
assert "temperature" in form_data, "temperature should be in form data"
|
||||
assert (
|
||||
form_data["temperature"] == "0.5"
|
||||
), f"Expected temperature='0.5', got {form_data['temperature']}"
|
||||
|
||||
# Check that custom parameters are passed through
|
||||
assert (
|
||||
"custom_param" in form_data
|
||||
), "custom_param should be in form data"
|
||||
assert (
|
||||
form_data["custom_param"] == "test_value"
|
||||
), f"Expected custom_param='test_value', got {form_data['custom_param']}"
|
||||
|
||||
# Check that files are included
|
||||
files = captured_request_data["files"]
|
||||
assert files is not None, "Files should not be None"
|
||||
assert "file" in files, "file should be in files"
|
||||
|
||||
print("✅ All parameter passthrough tests passed!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Test failed: {e}")
|
||||
print(f"Captured request data: {captured_request_data}")
|
||||
raise
|
||||
|
||||
|
||||
class TestElevenLabsTextToSpeechTransformation:
|
||||
@pytest.fixture(scope="class")
|
||||
def config(self):
|
||||
|
|
|
|||
|
|
@ -567,7 +567,6 @@ def test_gemini_imagen_models_use_predict_endpoint():
|
|||
Test that Imagen models still use :predict endpoint (not broken by gemini-2.5-flash-image-preview fix)
|
||||
"""
|
||||
from unittest.mock import patch, MagicMock
|
||||
from litellm.types.utils import ImageResponse, ImageObject
|
||||
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.llm_http_handler.HTTPHandler.post"
|
||||
|
|
@ -655,7 +654,7 @@ def test_gemini_thinking():
|
|||
|
||||
def test_gemini_thinking_budget_0():
|
||||
litellm._turn_on_debug()
|
||||
from litellm.types.utils import Message, CallTypes
|
||||
from litellm.types.utils import CallTypes
|
||||
from litellm.utils import return_raw_request
|
||||
import json
|
||||
|
||||
|
|
@ -677,7 +676,6 @@ def test_gemini_thinking_budget_0():
|
|||
|
||||
|
||||
def test_gemini_finish_reason():
|
||||
import os
|
||||
from litellm import completion
|
||||
|
||||
litellm._turn_on_debug()
|
||||
|
|
@ -1121,7 +1119,6 @@ def get_current_weather(location, unit="fahrenheit"):
|
|||
|
||||
|
||||
def test_gemini_with_thinking():
|
||||
from litellm import completion
|
||||
|
||||
litellm._turn_on_debug()
|
||||
litellm.modify_params = True
|
||||
|
|
@ -1218,7 +1215,6 @@ def test_gemini_reasoning_effort_minimal():
|
|||
"""
|
||||
from litellm.utils import return_raw_request
|
||||
from litellm.types.utils import CallTypes
|
||||
import json
|
||||
|
||||
# Test with different Gemini models to verify model-specific mapping
|
||||
test_cases = [
|
||||
|
|
@ -1341,97 +1337,6 @@ def test_gemini_exception_message_format():
|
|||
), f"Should not contain 'VertexAIException' in error message, got: {error_message}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"status_code,expected_exception",
|
||||
[
|
||||
(400, "BadRequestError"),
|
||||
(401, "AuthenticationError"),
|
||||
(403, "PermissionDeniedError"),
|
||||
(404, "NotFoundError"),
|
||||
(408, "Timeout"),
|
||||
(429, "RateLimitError"),
|
||||
(500, "InternalServerError"),
|
||||
(502, "APIConnectionError"),
|
||||
(503, "ServiceUnavailableError"),
|
||||
],
|
||||
)
|
||||
def l(status_code, expected_exception):
|
||||
"""
|
||||
Test comprehensive Gemini error handling for all HTTP status codes.
|
||||
|
||||
This ensures that Gemini API errors of different types are properly mapped
|
||||
to the correct LiteLLM exception types with GeminiException prefix.
|
||||
"""
|
||||
import httpx
|
||||
from unittest.mock import Mock
|
||||
from litellm.litellm_core_utils.exception_mapping_utils import exception_type
|
||||
from litellm.exceptions import (
|
||||
BadRequestError,
|
||||
AuthenticationError,
|
||||
PermissionDeniedError,
|
||||
NotFoundError,
|
||||
Timeout,
|
||||
RateLimitError,
|
||||
InternalServerError,
|
||||
APIConnectionError,
|
||||
ServiceUnavailableError,
|
||||
)
|
||||
|
||||
# Mock the appropriate error response
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.status_code = status_code
|
||||
mock_response.text = f"API Error {status_code}"
|
||||
mock_response.headers = {}
|
||||
|
||||
# Create a mock exception
|
||||
mock_exception = httpx.HTTPStatusError(
|
||||
message=f"HTTP {status_code}", request=Mock(), response=mock_response
|
||||
)
|
||||
mock_exception.response = mock_response
|
||||
mock_exception.status_code = status_code
|
||||
# Set message attribute for compatibility with exception mapping
|
||||
mock_exception.message = f"HTTP {status_code}"
|
||||
|
||||
# Test the exception mapping
|
||||
try:
|
||||
exception_type(
|
||||
model="gemini-pro",
|
||||
original_exception=mock_exception,
|
||||
custom_llm_provider="gemini",
|
||||
completion_kwargs={},
|
||||
extra_kwargs={},
|
||||
)
|
||||
assert (
|
||||
False
|
||||
), f"Expected {expected_exception} to be raised for status {status_code}"
|
||||
except Exception as e:
|
||||
# Verify the correct exception type is raised
|
||||
exception_classes = {
|
||||
"BadRequestError": BadRequestError,
|
||||
"AuthenticationError": AuthenticationError,
|
||||
"PermissionDeniedError": PermissionDeniedError,
|
||||
"NotFoundError": NotFoundError,
|
||||
"Timeout": Timeout,
|
||||
"RateLimitError": RateLimitError,
|
||||
"InternalServerError": InternalServerError,
|
||||
"APIConnectionError": APIConnectionError,
|
||||
"ServiceUnavailableError": ServiceUnavailableError,
|
||||
}
|
||||
expected_class = exception_classes[expected_exception]
|
||||
assert isinstance(
|
||||
e, expected_class
|
||||
), f"Expected {expected_exception}, got {type(e).__name__}"
|
||||
|
||||
# Verify the error message contains GeminiException
|
||||
error_message = str(e)
|
||||
assert (
|
||||
"GeminiException" in error_message
|
||||
), f"Expected 'GeminiException' in error message for status {status_code}, got: {error_message}"
|
||||
assert (
|
||||
"VertexAIException" not in error_message
|
||||
), f"Should not contain 'VertexAIException' for status {status_code}, got: {error_message}"
|
||||
|
||||
|
||||
def test_gemini_embedding():
|
||||
litellm._turn_on_debug()
|
||||
response = litellm.embedding(
|
||||
|
|
@ -1710,7 +1615,6 @@ def test_anthropic_thinking_param_via_map_openai_params():
|
|||
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
|
||||
VertexGeminiConfig,
|
||||
)
|
||||
from litellm.types.llms.anthropic import AnthropicThinkingParam
|
||||
|
||||
config = VertexGeminiConfig()
|
||||
|
||||
|
|
|
|||
|
|
@ -447,70 +447,6 @@ class TestHuggingFace(BaseLLMChatTest):
|
|||
)
|
||||
assert url == expected_url
|
||||
|
||||
def test_completion_with_api_base(self):
|
||||
messages = [{"role": "user", "content": "This is a test message"}]
|
||||
api_base = "https://abcd123.us-east-1.aws.endpoints.huggingface.cloud"
|
||||
|
||||
response = litellm.completion(
|
||||
model="huggingface/tgi", messages=messages, api_base=api_base, stream=False
|
||||
)
|
||||
|
||||
assert isinstance(response, ModelResponse)
|
||||
assert (
|
||||
response.choices[0].message.content
|
||||
== "This is a test response from the mocked HuggingFace API."
|
||||
)
|
||||
|
||||
assert self.mock_http.called
|
||||
call_args = self.mock_http.call_args
|
||||
assert call_args is not None
|
||||
|
||||
called_url = call_args[1]["url"]
|
||||
assert called_url == f"{api_base}/v1/chat/completions"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_completion_with_api_base(self):
|
||||
messages = [{"role": "user", "content": "This is a test message"}]
|
||||
api_base = "https://abcd123.us-east-1.aws.endpoints.huggingface.cloud"
|
||||
|
||||
response = await litellm.acompletion(
|
||||
model="huggingface/tgi", messages=messages, api_base=api_base, stream=False
|
||||
)
|
||||
|
||||
assert isinstance(response, ModelResponse)
|
||||
assert (
|
||||
response.choices[0].message.content
|
||||
== "This is a test response from the mocked HuggingFace API."
|
||||
)
|
||||
|
||||
assert self.mock_http_async.called
|
||||
call_args = self.mock_http_async.call_args
|
||||
assert call_args is not None
|
||||
|
||||
called_url = call_args[1]["url"]
|
||||
assert called_url == f"{api_base}/v1/chat/completions"
|
||||
|
||||
def test_completion_streaming_with_api_base(self):
|
||||
"""Test streaming completion with api_base parameter"""
|
||||
messages = [{"role": "user", "content": "This is a test message"}]
|
||||
api_base = "https://abcd123.us-east-1.aws.endpoints.huggingface.cloud"
|
||||
|
||||
response = litellm.completion(
|
||||
model="huggingface/tgi", messages=messages, api_base=api_base, stream=True
|
||||
)
|
||||
|
||||
chunks = list(response)
|
||||
assert len(chunks) > 0
|
||||
assert isinstance(chunks[0], ModelResponseStream)
|
||||
|
||||
# Check that the correct URL was called
|
||||
assert self.mock_http.called
|
||||
call_args = self.mock_http.call_args
|
||||
assert call_args is not None
|
||||
|
||||
called_url = call_args[1]["url"]
|
||||
assert called_url == f"{api_base}/v1/chat/completions"
|
||||
|
||||
def test_build_chat_completion_url_function(self):
|
||||
"""Test the _build_chat_completion_url helper function"""
|
||||
from litellm.llms.huggingface.chat.transformation import (
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
import litellm
|
||||
from litellm import get_llm_provider
|
||||
|
||||
|
||||
|
|
@ -20,22 +16,6 @@ def test_get_llm_provider_hyperbolic():
|
|||
assert model == "deepseek-v3"
|
||||
|
||||
|
||||
def test_hyperbolic_completion_call():
|
||||
"""Test basic completion call structure for Hyperbolic"""
|
||||
# This is primarily a structure test since we don't have actual API keys
|
||||
try:
|
||||
litellm.set_verbose = True
|
||||
response = litellm.completion(
|
||||
model="hyperbolic/qwen-2.5-72b",
|
||||
messages=[{"role": "user", "content": "Hello!"}],
|
||||
mock_response="Hi there!",
|
||||
)
|
||||
assert response is not None
|
||||
except Exception as e:
|
||||
# Expected to fail without valid API key, but should recognize the provider
|
||||
assert "hyperbolic" in str(e).lower() or "api" in str(e).lower()
|
||||
|
||||
|
||||
def test_hyperbolic_config_initialization():
|
||||
"""Test that HyperbolicChatConfig initializes correctly"""
|
||||
from litellm.llms.hyperbolic.chat.transformation import HyperbolicChatConfig
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Tests for MiniMax Text-to-Speech integration
|
|||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -139,7 +139,6 @@ class TestMinimaxTextToSpeechConfig:
|
|||
|
||||
# Mock both litellm.api_key and get_secret_str to return None
|
||||
import litellm
|
||||
from unittest.mock import patch
|
||||
|
||||
original_api_key = litellm.api_key
|
||||
try:
|
||||
|
|
@ -272,48 +271,6 @@ class TestMinimaxSpeechIntegration:
|
|||
# Clean up
|
||||
speech_file_path.unlink()
|
||||
|
||||
def test_speech_mock_response(self):
|
||||
"""Test speech synthesis with mocked response"""
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
# Create mock audio data (hex-encoded as MiniMax returns)
|
||||
mock_audio_bytes = b"fake audio data for testing"
|
||||
mock_audio_hex = mock_audio_bytes.hex()
|
||||
|
||||
mock_response_json = {
|
||||
"data": {"audio": mock_audio_hex, "status": 0, "ced": ""},
|
||||
"extra_info": {},
|
||||
}
|
||||
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.llm_http_handler.BaseLLMHTTPHandler.text_to_speech_handler"
|
||||
) as mock_tts:
|
||||
# Create a mock httpx.Response
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.headers = {}
|
||||
mock_response.json.return_value = mock_response_json
|
||||
mock_response.content = mock_audio_bytes
|
||||
|
||||
# Mock the response wrapper
|
||||
from litellm.types.llms.openai import HttpxBinaryResponseContent
|
||||
|
||||
mock_binary_response = HttpxBinaryResponseContent(mock_response)
|
||||
mock_tts.return_value = mock_binary_response
|
||||
|
||||
# This would normally make a real API call
|
||||
# but we're mocking it for testing
|
||||
response = speech(
|
||||
model="minimax/speech-2.6-hd",
|
||||
voice="alloy",
|
||||
input="Test input",
|
||||
api_key="test-key",
|
||||
)
|
||||
|
||||
# Verify the mock was called
|
||||
assert mock_tts.called
|
||||
|
||||
|
||||
class TestMinimaxProviderRegistration:
|
||||
"""Test that MiniMax is properly registered as a provider"""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
sys.path.insert(
|
||||
|
|
@ -9,179 +8,11 @@ sys.path.insert(
|
|||
) # Adds the parent directory to the system path
|
||||
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock, AsyncMock
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import litellm
|
||||
from litellm import Choices, Message, ModelResponse, EmbeddingResponse, Usage
|
||||
from litellm import completion
|
||||
from base_rerank_unit_tests import BaseLLMRerankTest
|
||||
import litellm
|
||||
|
||||
|
||||
def test_completion_nvidia_nim():
|
||||
from openai import OpenAI
|
||||
|
||||
litellm.set_verbose = True
|
||||
model_name = "nvidia_nim/databricks/dbrx-instruct"
|
||||
client = OpenAI(
|
||||
api_key="fake-api-key",
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
client.chat.completions.with_raw_response, "create"
|
||||
) as mock_client:
|
||||
try:
|
||||
completion(
|
||||
model=model_name,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What's the weather like in Boston today in Fahrenheit?",
|
||||
}
|
||||
],
|
||||
presence_penalty=0.5,
|
||||
frequency_penalty=0.1,
|
||||
client=client,
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
# Add any assertions here to check the response
|
||||
|
||||
mock_client.assert_called_once()
|
||||
request_body = mock_client.call_args.kwargs
|
||||
|
||||
print("request_body: ", request_body)
|
||||
|
||||
assert request_body["messages"] == [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What's the weather like in Boston today in Fahrenheit?",
|
||||
},
|
||||
]
|
||||
assert request_body["model"] == "databricks/dbrx-instruct"
|
||||
assert request_body["frequency_penalty"] == 0.1
|
||||
assert request_body["presence_penalty"] == 0.5
|
||||
|
||||
|
||||
def test_embedding_nvidia_nim():
|
||||
litellm.set_verbose = True
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
api_key="fake-api-key",
|
||||
)
|
||||
with patch.object(client.embeddings.with_raw_response, "create") as mock_client:
|
||||
try:
|
||||
litellm.embedding(
|
||||
model="nvidia_nim/nvidia/nv-embedqa-e5-v5",
|
||||
input="What is the meaning of life?",
|
||||
input_type="passage",
|
||||
dimensions=1024,
|
||||
client=client,
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
mock_client.assert_called_once()
|
||||
request_body = mock_client.call_args.kwargs
|
||||
print("request_body: ", request_body)
|
||||
assert request_body["input"] == "What is the meaning of life?"
|
||||
assert request_body["model"] == "nvidia/nv-embedqa-e5-v5"
|
||||
assert request_body["extra_body"]["input_type"] == "passage"
|
||||
assert request_body["dimensions"] == 1024
|
||||
|
||||
|
||||
def test_chat_completion_nvidia_nim_with_tools():
|
||||
from openai import OpenAI
|
||||
|
||||
litellm.set_verbose = True
|
||||
model_name = "nvidia_nim/meta/llama3-70b-instruct"
|
||||
client = OpenAI(
|
||||
api_key="fake-api-key",
|
||||
)
|
||||
|
||||
# Define tools
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state, e.g. San Francisco, CA",
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
"description": "The unit of temperature to use",
|
||||
},
|
||||
},
|
||||
"required": ["location"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_time",
|
||||
"description": "Get the current time in a given timezone",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"timezone": {
|
||||
"type": "string",
|
||||
"description": "The timezone, e.g. EST, PST",
|
||||
},
|
||||
},
|
||||
"required": ["timezone"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(
|
||||
client.chat.completions.with_raw_response, "create"
|
||||
) as mock_client:
|
||||
try:
|
||||
completion(
|
||||
model=model_name,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What's the weather like in Boston today and what time is it in EST?",
|
||||
}
|
||||
],
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
parallel_tool_calls=True,
|
||||
temperature=0.7,
|
||||
client=client,
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
# Add assertions to check the request
|
||||
mock_client.assert_called_once()
|
||||
request_body = mock_client.call_args.kwargs
|
||||
|
||||
print("request_body: ", request_body)
|
||||
|
||||
assert request_body["messages"] == [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What's the weather like in Boston today and what time is it in EST?",
|
||||
},
|
||||
]
|
||||
assert request_body["model"] == "meta/llama3-70b-instruct"
|
||||
assert request_body["temperature"] == 0.7
|
||||
assert request_body["tools"] == tools
|
||||
assert request_body["tool_choice"] == "auto"
|
||||
assert request_body["parallel_tool_calls"] == True
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
import io
|
||||
import os
|
||||
from typing import Optional, Dict
|
||||
|
||||
sys.path.insert(
|
||||
|
|
@ -16,15 +12,12 @@ sys.path.insert(
|
|||
) # Adds the parent directory to the system path
|
||||
|
||||
import os
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.types.rerank import RerankResponse
|
||||
from litellm import RateLimitError, Timeout, completion, completion_cost, embedding
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
|
||||
|
||||
|
||||
def assert_response_shape(response, custom_llm_provider):
|
||||
|
|
@ -148,75 +141,6 @@ async def test_basic_rerank_together_ai(sync_mode):
|
|||
raise e
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
@pytest.mark.parametrize("version", ["v1", "v2"])
|
||||
async def test_rerank_custom_api_base(version):
|
||||
mock_response = AsyncMock()
|
||||
litellm.cohere_key = "test_api_key"
|
||||
|
||||
def return_val():
|
||||
return {
|
||||
"id": "cmpl-mockid",
|
||||
"results": [{"index": 0, "relevance_score": 0.95}],
|
||||
"meta": {
|
||||
"api_version": {"version": "1.0"},
|
||||
"billed_units": {"search_units": 1},
|
||||
},
|
||||
}
|
||||
|
||||
mock_response.json = return_val
|
||||
mock_response.headers = {"key": "value"}
|
||||
mock_response.status_code = 200
|
||||
|
||||
expected_payload = {
|
||||
"model": "Salesforce/Llama-Rank-V1",
|
||||
"query": "hello",
|
||||
"top_n": 3,
|
||||
"documents": ["hello", "world"],
|
||||
}
|
||||
|
||||
api_base = "https://exampleopenaiendpoint-production.up.railway.app/"
|
||||
if version == "v1":
|
||||
api_base += "v1/rerank"
|
||||
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
||||
return_value=mock_response,
|
||||
) as mock_post:
|
||||
response = await litellm.arerank(
|
||||
model="cohere/Salesforce/Llama-Rank-V1",
|
||||
query="hello",
|
||||
documents=["hello", "world"],
|
||||
top_n=3,
|
||||
api_base=api_base,
|
||||
)
|
||||
|
||||
print("async re rank response: ", response)
|
||||
|
||||
# Assert
|
||||
mock_post.assert_called_once()
|
||||
print("call args", mock_post.call_args)
|
||||
args_to_api = mock_post.call_args.kwargs["data"]
|
||||
_url = mock_post.call_args.kwargs["url"]
|
||||
print("Arguments passed to API=", args_to_api)
|
||||
print("url = ", _url)
|
||||
assert (
|
||||
_url
|
||||
== f"https://exampleopenaiendpoint-production.up.railway.app/{version}/rerank"
|
||||
)
|
||||
|
||||
request_data = json.loads(args_to_api)
|
||||
assert request_data["query"] == expected_payload["query"]
|
||||
assert request_data["documents"] == expected_payload["documents"]
|
||||
assert request_data["top_n"] == expected_payload["top_n"]
|
||||
assert request_data["model"] == expected_payload["model"]
|
||||
|
||||
assert response.id is not None
|
||||
assert response.results is not None
|
||||
|
||||
assert_response_shape(response, custom_llm_provider="cohere")
|
||||
|
||||
|
||||
class TestLogger(CustomLogger):
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -255,37 +179,6 @@ async def test_rerank_custom_callbacks():
|
|||
assert custom_logger.response_obj.results is not None
|
||||
|
||||
|
||||
def test_complete_base_url_cohere():
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
|
||||
client = HTTPHandler()
|
||||
litellm.api_base = "http://localhost:4000"
|
||||
litellm.cohere_key = "test_api_key"
|
||||
litellm.set_verbose = True
|
||||
|
||||
text = "Hello there!"
|
||||
list_texts = ["Hello there!", "How are you?", "How do you do?"]
|
||||
|
||||
rerank_model = "rerank-multilingual-v3.0"
|
||||
|
||||
with patch.object(client, "post") as mock_post:
|
||||
try:
|
||||
litellm.rerank(
|
||||
model=rerank_model,
|
||||
query=text,
|
||||
documents=list_texts,
|
||||
custom_llm_provider="cohere",
|
||||
client=client,
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
print("mock_post.call_args", mock_post.call_args)
|
||||
mock_post.assert_called_once()
|
||||
# Default to the v2 client when calling the base /rerank
|
||||
assert "http://localhost:4000/v2/rerank" in mock_post.call_args.kwargs["url"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
@pytest.mark.parametrize("sync_mode", [True, False])
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -370,63 +263,6 @@ def test_rerank_response_assertions():
|
|||
assert_response_shape(r, custom_llm_provider="custom")
|
||||
|
||||
|
||||
def test_cohere_rerank_v2_client():
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
|
||||
client = HTTPHandler()
|
||||
litellm.api_base = "http://localhost:4000"
|
||||
litellm.set_verbose = True
|
||||
|
||||
text = "Hello there!"
|
||||
list_texts = ["Hello there!", "How are you?", "How do you do?"]
|
||||
|
||||
rerank_model = "rerank-multilingual-v3.0"
|
||||
|
||||
with patch.object(client, "post") as mock_post:
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = json.dumps(
|
||||
{
|
||||
"id": "cmpl-mockid",
|
||||
"results": [
|
||||
{"index": 0, "relevance_score": 0.95},
|
||||
{"index": 1, "relevance_score": 0.75},
|
||||
{"index": 2, "relevance_score": 0.65},
|
||||
],
|
||||
"usage": {"prompt_tokens": 100, "total_tokens": 150},
|
||||
}
|
||||
)
|
||||
mock_response.status_code = 200
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_response.json = lambda: json.loads(mock_response.text)
|
||||
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
response = litellm.rerank(
|
||||
model=rerank_model,
|
||||
query=text,
|
||||
documents=list_texts,
|
||||
custom_llm_provider="cohere",
|
||||
max_tokens_per_doc=3,
|
||||
top_n=2,
|
||||
api_key="fake-api-key",
|
||||
client=client,
|
||||
)
|
||||
|
||||
# Ensure Cohere API is called with the expected params
|
||||
mock_post.assert_called_once()
|
||||
assert mock_post.call_args.kwargs["url"] == "http://localhost:4000/v2/rerank"
|
||||
|
||||
request_data = json.loads(mock_post.call_args.kwargs["data"])
|
||||
assert request_data["model"] == rerank_model
|
||||
assert request_data["query"] == text
|
||||
assert request_data["documents"] == list_texts
|
||||
assert request_data["max_tokens_per_doc"] == 3
|
||||
assert request_data["top_n"] == 2
|
||||
|
||||
# Ensure litellm response is what we expect
|
||||
assert response["results"] == mock_response.json()["results"]
|
||||
|
||||
|
||||
@pytest.mark.flaky(retries=3, delay=1)
|
||||
def test_rerank_cohere_api():
|
||||
response = litellm.rerank(
|
||||
|
|
@ -443,40 +279,3 @@ def test_rerank_cohere_api():
|
|||
assert response.results[1]["document"]["text"] == "world"
|
||||
|
||||
|
||||
def test_rerank_infer_region_from_model_arn(monkeypatch):
|
||||
|
||||
mock_response = MagicMock()
|
||||
|
||||
monkeypatch.setenv("AWS_REGION_NAME", "us-east-1")
|
||||
args = {
|
||||
"model": "bedrock/arn:aws:bedrock:us-west-2::foundation-model/amazon.rerank-v1:0",
|
||||
"query": "hello",
|
||||
"documents": ["hello", "world"],
|
||||
}
|
||||
|
||||
def return_val():
|
||||
return {
|
||||
"results": [
|
||||
{"index": 0, "relevanceScore": 0.6716859340667725},
|
||||
{"index": 1, "relevanceScore": 0.0004994205664843321},
|
||||
]
|
||||
}
|
||||
|
||||
mock_response.json = return_val
|
||||
mock_response.headers = {"key": "value"}
|
||||
mock_response.status_code = 200
|
||||
|
||||
client = HTTPHandler()
|
||||
|
||||
with patch.object(client, "post", return_value=mock_response) as mock_post:
|
||||
litellm.rerank(
|
||||
model=args["model"],
|
||||
query=args["query"],
|
||||
documents=args["documents"],
|
||||
client=client,
|
||||
)
|
||||
|
||||
mock_post.assert_called_once()
|
||||
print(f"mock_post.call_args: {mock_post.call_args.kwargs}")
|
||||
assert "us-west-2" in mock_post.call_args.kwargs["url"]
|
||||
assert "us-east-1" not in mock_post.call_args.kwargs["url"]
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock
|
||||
import pytest
|
||||
import httpx
|
||||
from respx import MockRouter
|
||||
from unittest.mock import patch, MagicMock, AsyncMock
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
import litellm
|
||||
from litellm.types.utils import TextCompletionResponse
|
||||
|
||||
|
||||
|
|
@ -69,151 +61,3 @@ def test_convert_dict_to_text_completion_response():
|
|||
assert response.choices[0].logprobs.top_logprobs == [None, {",": -2.1568563}]
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason="need to migrate huggingface to support httpx client being passed in"
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.respx
|
||||
async def test_huggingface_text_completion_logprobs():
|
||||
"""Test text completion with Hugging Face, focusing on logprobs structure"""
|
||||
litellm.set_verbose = True
|
||||
litellm.disable_aiohttp_transport = (
|
||||
True # since this uses respx, we need to set use_aiohttp_transport to False
|
||||
)
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler, AsyncHTTPHandler
|
||||
|
||||
mock_response = [
|
||||
{
|
||||
"generated_text": ",\n\nI have a question...", # truncated for brevity
|
||||
"details": {
|
||||
"finish_reason": "length",
|
||||
"generated_tokens": 100,
|
||||
"seed": None,
|
||||
"prefill": [],
|
||||
"tokens": [
|
||||
{"id": 28725, "text": ",", "logprob": -1.7626953, "special": False},
|
||||
{"id": 13, "text": "\n", "logprob": -1.7314453, "special": False},
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
return_val = AsyncMock()
|
||||
|
||||
return_val.json.return_value = mock_response
|
||||
|
||||
client = AsyncHTTPHandler()
|
||||
with patch.object(client, "post", return_value=return_val) as mock_post:
|
||||
response = await litellm.atext_completion(
|
||||
model="huggingface/mistralai/Mistral-7B-Instruct-v0.3",
|
||||
prompt="good morning",
|
||||
client=client,
|
||||
)
|
||||
|
||||
# Verify the request
|
||||
mock_post.assert_called_once()
|
||||
request_body = json.loads(mock_post.call_args.kwargs["data"])
|
||||
assert request_body == {
|
||||
"inputs": "good morning",
|
||||
"parameters": {"details": True, "return_full_text": False},
|
||||
"stream": False,
|
||||
}
|
||||
|
||||
print("response=", response)
|
||||
|
||||
# Verify response structure
|
||||
assert isinstance(response, TextCompletionResponse)
|
||||
assert response.object == "text_completion"
|
||||
assert response.model == "mistralai/Mistral-7B-v0.1"
|
||||
|
||||
# Verify logprobs structure
|
||||
choice = response.choices[0]
|
||||
assert choice.finish_reason == "length"
|
||||
assert choice.index == 0
|
||||
assert isinstance(choice.logprobs.tokens, list)
|
||||
assert isinstance(choice.logprobs.token_logprobs, list)
|
||||
assert isinstance(choice.logprobs.text_offset, list)
|
||||
assert isinstance(choice.logprobs.top_logprobs, list)
|
||||
assert choice.logprobs.tokens == [",", "\n"]
|
||||
assert choice.logprobs.token_logprobs == [-1.7626953, -1.7314453]
|
||||
assert choice.logprobs.text_offset == [0, 1]
|
||||
assert choice.logprobs.top_logprobs == [{}, {}]
|
||||
|
||||
# Verify usage
|
||||
assert response.usage["completion_tokens"] > 0
|
||||
assert response.usage["prompt_tokens"] > 0
|
||||
assert response.usage["total_tokens"] > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_acompletion_uses_optimized_http_client():
|
||||
"""
|
||||
Test that OpenAITextCompletion.acompletion uses BaseOpenAILLM._get_async_http_client()
|
||||
instead of litellm.aclient_session directly.
|
||||
|
||||
Related issue: https://github.com/BerriAI/litellm/issues/17676
|
||||
"""
|
||||
from litellm.llms.openai.completion.handler import OpenAITextCompletion
|
||||
from litellm.llms.openai.common_utils import BaseOpenAILLM
|
||||
|
||||
mock_http_client = MagicMock()
|
||||
mock_async_openai = AsyncMock()
|
||||
mock_async_openai.completions.with_raw_response.create = AsyncMock(
|
||||
return_value=MagicMock(
|
||||
parse=MagicMock(
|
||||
return_value=MagicMock(
|
||||
model_dump=MagicMock(
|
||||
return_value={
|
||||
"id": "test-id",
|
||||
"object": "text_completion",
|
||||
"created": 1234567890,
|
||||
"model": "gpt-3.5-turbo-instruct",
|
||||
"choices": [
|
||||
{
|
||||
"text": "test response",
|
||||
"index": 0,
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 5,
|
||||
"completion_tokens": 10,
|
||||
"total_tokens": 15,
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
BaseOpenAILLM, "_get_async_http_client", return_value=mock_http_client
|
||||
) as mock_get_client:
|
||||
with patch(
|
||||
"litellm.llms.openai.completion.handler.AsyncOpenAI",
|
||||
return_value=mock_async_openai,
|
||||
) as mock_openai_class:
|
||||
handler = OpenAITextCompletion()
|
||||
logging_obj = MagicMock()
|
||||
logging_obj.post_call = MagicMock()
|
||||
|
||||
await handler.acompletion(
|
||||
logging_obj=logging_obj,
|
||||
api_base="https://api.openai.com/v1",
|
||||
data={"prompt": "test", "model": "gpt-3.5-turbo-instruct"},
|
||||
headers={},
|
||||
model_response=MagicMock(),
|
||||
api_key="test-key",
|
||||
model="gpt-3.5-turbo-instruct",
|
||||
timeout=30.0,
|
||||
max_retries=2,
|
||||
)
|
||||
|
||||
# Verify _get_async_http_client was called
|
||||
mock_get_client.assert_called_once()
|
||||
|
||||
# Verify AsyncOpenAI was initialized with the http_client from _get_async_http_client
|
||||
mock_openai_class.assert_called_once()
|
||||
call_kwargs = mock_openai_class.call_args.kwargs
|
||||
assert call_kwargs["http_client"] == mock_http_client
|
||||
|
|
|
|||
|
|
@ -3,11 +3,8 @@ Test TogetherAI LLM
|
|||
"""
|
||||
|
||||
from base_llm_unit_tests import BaseLLMChatTest
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
|
|
@ -22,10 +19,6 @@ class TestTogetherAI(BaseLLMChatTest):
|
|||
litellm.set_verbose = True
|
||||
return {"model": "together_ai/Qwen/Qwen2.5-7B-Instruct-Turbo"}
|
||||
|
||||
def test_tool_call_no_arguments(self, tool_call_no_arguments):
|
||||
"""Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833"""
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model, expected_bool",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import traceback
|
|||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
import io
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
|
|
@ -15,9 +14,7 @@ sys.path.insert(
|
|||
import pytest
|
||||
import litellm
|
||||
|
||||
import pytest
|
||||
from litellm.llms.triton.embedding.transformation import TritonEmbeddingConfig
|
||||
import litellm
|
||||
|
||||
|
||||
def test_split_embedding_by_shape_passes():
|
||||
|
|
@ -354,23 +351,6 @@ def test_completion_triton_infer_api():
|
|||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_triton_embeddings():
|
||||
try:
|
||||
litellm.set_verbose = True
|
||||
response = await litellm.aembedding(
|
||||
model="triton/my-triton-model",
|
||||
api_base="https://exampleopenaiendpoint-production.up.railway.app/triton/embeddings",
|
||||
input=["good morning from litellm"],
|
||||
)
|
||||
print(f"response: {response}")
|
||||
|
||||
# stubbed endpoint is setup to return this
|
||||
assert response.data[0]["embedding"] == [0.1, 0.2]
|
||||
except Exception as e:
|
||||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
|
||||
def test_triton_generate_raw_request():
|
||||
from litellm.utils import return_raw_request
|
||||
from litellm.types.utils import CallTypes
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@ import json
|
|||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from base_embedding_unit_tests import BaseLLMEmbeddingTest
|
||||
|
||||
|
|
@ -25,110 +24,6 @@ class TestVoyageAI(BaseLLMEmbeddingTest):
|
|||
"model": "voyage/voyage-3-lite",
|
||||
}
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
@pytest.mark.parametrize("sync_mode", [True, False])
|
||||
async def test_basic_embedding(self, sync_mode):
|
||||
"""Override base test to handle Voyage embeddings properly"""
|
||||
litellm.set_verbose = True
|
||||
embedding_call_args = self.get_base_embedding_call_args()
|
||||
|
||||
# Mock the embedding function to avoid API calls
|
||||
with (
|
||||
patch("litellm.embedding") as mock_embedding,
|
||||
patch("litellm.aembedding") as mock_aembedding,
|
||||
):
|
||||
# Create a mock response that matches Voyage format
|
||||
mock_response = MagicMock()
|
||||
mock_response.model = "voyage-3-lite"
|
||||
mock_response.object = "list"
|
||||
mock_response.data = [
|
||||
{"object": "embedding", "embedding": [0.1, 0.2, 0.3], "index": 0}
|
||||
]
|
||||
mock_response.usage.prompt_tokens = 24
|
||||
mock_response.usage.total_tokens = 24
|
||||
|
||||
mock_embedding.return_value = mock_response
|
||||
mock_aembedding.return_value = mock_response
|
||||
|
||||
if sync_mode is True:
|
||||
response = litellm.embedding(
|
||||
**embedding_call_args,
|
||||
input=["hello", "world"],
|
||||
)
|
||||
# Verify the response structure
|
||||
assert response.model == "voyage-3-lite"
|
||||
assert response.object == "list"
|
||||
assert len(response.data) > 0
|
||||
assert response.usage.total_tokens > 0
|
||||
else:
|
||||
response = await litellm.aembedding(
|
||||
**embedding_call_args,
|
||||
input=["hello", "world"],
|
||||
)
|
||||
# Verify the response structure
|
||||
assert response.model == "voyage-3-lite"
|
||||
assert response.object == "list"
|
||||
assert len(response.data) > 0
|
||||
assert response.usage.total_tokens > 0
|
||||
|
||||
|
||||
def test_voyage_ai_embedding_extra_params():
|
||||
"""Test Voyage AI embedding with extra parameters"""
|
||||
try:
|
||||
# Mock the entire embedding function to avoid API calls
|
||||
with patch("litellm.embedding") as mock_embedding:
|
||||
# Create a mock response
|
||||
mock_response = MagicMock()
|
||||
mock_response.usage.prompt_tokens = 24
|
||||
mock_response.usage.total_tokens = 24
|
||||
mock_response.model = "voyage-3-lite"
|
||||
mock_embedding.return_value = mock_response
|
||||
|
||||
litellm.embedding(
|
||||
model="voyage/voyage-3-lite",
|
||||
input=["a"],
|
||||
dimensions=512,
|
||||
input_type="document",
|
||||
)
|
||||
|
||||
# Verify the function was called with correct parameters
|
||||
mock_embedding.assert_called_once()
|
||||
call_args = mock_embedding.call_args
|
||||
assert call_args[1]["model"] == "voyage/voyage-3-lite"
|
||||
assert call_args[1]["input"] == ["a"]
|
||||
assert call_args[1]["dimensions"] == 512
|
||||
assert call_args[1]["input_type"] == "document"
|
||||
|
||||
except Exception as e:
|
||||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
|
||||
def test_voyage_ai_embedding_prompt_token_mapping():
|
||||
"""Test Voyage AI embedding token mapping"""
|
||||
try:
|
||||
# Mock the entire embedding function
|
||||
with patch("litellm.embedding") as mock_embedding:
|
||||
# Create a mock response with usage
|
||||
mock_response = MagicMock()
|
||||
mock_response.usage.prompt_tokens = 120
|
||||
mock_response.usage.total_tokens = 120
|
||||
mock_embedding.return_value = mock_response
|
||||
|
||||
response = litellm.embedding(
|
||||
model="voyage/voyage-3-lite",
|
||||
input=["a"],
|
||||
dimensions=512,
|
||||
input_type="document",
|
||||
)
|
||||
|
||||
# Verify the response
|
||||
assert response.usage.prompt_tokens == 120
|
||||
assert response.usage.total_tokens == 120
|
||||
|
||||
except Exception as e:
|
||||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
|
||||
# Tests for Voyage Contextual Embeddings
|
||||
class TestVoyageContextualEmbeddings:
|
||||
"""Test suite for Voyage contextual embeddings functionality"""
|
||||
|
|
@ -334,101 +229,3 @@ class TestVoyageContextualEmbeddings:
|
|||
assert regular_transformed["input"] == ["Hello"]
|
||||
assert contextual_transformed["inputs"] == [["Hello"]]
|
||||
|
||||
def test_contextual_embedding_integration(self):
|
||||
"""Test full integration of contextual embeddings"""
|
||||
try:
|
||||
# Mock the entire embedding function to avoid API calls
|
||||
with patch("litellm.embedding") as mock_embedding:
|
||||
# Create a mock response that matches the expected structure
|
||||
mock_response = MagicMock()
|
||||
mock_response.model = "voyage-context-3"
|
||||
mock_response.usage.total_tokens = 24
|
||||
mock_response.data = [
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"object": "embedding",
|
||||
"embedding": [0.1, 0.2, 0.3],
|
||||
"index": 0,
|
||||
}
|
||||
],
|
||||
"index": 0,
|
||||
}
|
||||
]
|
||||
mock_embedding.return_value = mock_response
|
||||
|
||||
response = litellm.embedding(
|
||||
model="voyage/voyage-context-3",
|
||||
input=[["Hello", "world"]],
|
||||
input_type="document",
|
||||
)
|
||||
|
||||
# Verify the function was called with correct parameters
|
||||
mock_embedding.assert_called_once()
|
||||
call_args = mock_embedding.call_args
|
||||
assert call_args[1]["model"] == "voyage/voyage-context-3"
|
||||
assert call_args[1]["input"] == [["Hello", "world"]]
|
||||
assert call_args[1]["input_type"] == "document"
|
||||
|
||||
# Assert the response structure
|
||||
assert response.model == "voyage-context-3"
|
||||
assert response.usage.total_tokens == 24
|
||||
|
||||
except Exception as e:
|
||||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
def test_contextual_embedding_multiple_inputs(self):
|
||||
"""Test contextual embeddings with multiple input groups"""
|
||||
try:
|
||||
# Mock the entire embedding function
|
||||
with patch("litellm.embedding") as mock_embedding:
|
||||
# Create a mock response for multiple input groups
|
||||
mock_response = MagicMock()
|
||||
mock_response.model = "voyage-context-3"
|
||||
mock_response.usage.total_tokens = 48
|
||||
mock_response.data = [
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"object": "embedding",
|
||||
"embedding": [0.1, 0.2],
|
||||
"index": 0,
|
||||
},
|
||||
{
|
||||
"object": "embedding",
|
||||
"embedding": [0.3, 0.4],
|
||||
"index": 1,
|
||||
},
|
||||
],
|
||||
"index": 0,
|
||||
},
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{"object": "embedding", "embedding": [0.5, 0.6], "index": 0}
|
||||
],
|
||||
"index": 1,
|
||||
},
|
||||
]
|
||||
mock_embedding.return_value = mock_response
|
||||
|
||||
response = litellm.embedding(
|
||||
model="voyage/voyage-context-3",
|
||||
input=[["Hello", "world"], ["Test"]],
|
||||
)
|
||||
|
||||
# Verify the function was called with correct parameters
|
||||
mock_embedding.assert_called_once()
|
||||
call_args = mock_embedding.call_args
|
||||
assert call_args[1]["model"] == "voyage/voyage-context-3"
|
||||
assert call_args[1]["input"] == [["Hello", "world"], ["Test"]]
|
||||
|
||||
# Assert response structure
|
||||
assert len(response.data) == 2
|
||||
assert response.data[0]["index"] == 0
|
||||
assert response.data[1]["index"] == 1
|
||||
|
||||
except Exception as e:
|
||||
pytest.fail(f"Error occurred: {e}")
|
||||
|
|
|
|||
|
|
@ -146,62 +146,6 @@ def watsonx_embedding_call():
|
|||
return _call
|
||||
|
||||
|
||||
@pytest.mark.parametrize("with_custom_auth_header", [True, False])
|
||||
def test_watsonx_custom_auth_header(
|
||||
with_custom_auth_header, watsonx_chat_completion_call
|
||||
):
|
||||
headers = (
|
||||
{"Authorization": "Bearer my-custom-auth-header"}
|
||||
if with_custom_auth_header
|
||||
else {}
|
||||
)
|
||||
|
||||
mock_post, _ = watsonx_chat_completion_call(headers=headers)
|
||||
|
||||
assert mock_post.call_count == 1
|
||||
if with_custom_auth_header:
|
||||
assert (
|
||||
mock_post.call_args[1]["headers"]["Authorization"]
|
||||
== "Bearer my-custom-auth-header"
|
||||
)
|
||||
else:
|
||||
assert (
|
||||
mock_post.call_args[1]["headers"]["Authorization"]
|
||||
== "Bearer mock_access_token"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("env_var_key", ["WATSONX_ZENAPIKEY", "WATSONX_TOKEN"])
|
||||
def test_watsonx_token_in_env_var(
|
||||
monkeypatch, watsonx_chat_completion_call, env_var_key
|
||||
):
|
||||
monkeypatch.setenv(env_var_key, "my-custom-token")
|
||||
|
||||
mock_post, _ = watsonx_chat_completion_call(patch_token_call=False)
|
||||
|
||||
assert mock_post.call_count == 1
|
||||
if env_var_key == "WATSONX_ZENAPIKEY":
|
||||
assert (
|
||||
mock_post.call_args[1]["headers"]["Authorization"]
|
||||
== "ZenApiKey my-custom-token"
|
||||
)
|
||||
else:
|
||||
assert (
|
||||
mock_post.call_args[1]["headers"]["Authorization"]
|
||||
== "Bearer my-custom-token"
|
||||
)
|
||||
|
||||
|
||||
def test_watsonx_chat_completions_endpoint(watsonx_chat_completion_call):
|
||||
model = "watsonx/another-model"
|
||||
messages = [{"role": "user", "content": "Test message"}]
|
||||
|
||||
mock_post, _ = watsonx_chat_completion_call(model=model, messages=messages)
|
||||
|
||||
assert mock_post.call_count == 1
|
||||
assert "deployment" not in mock_post.call_args.kwargs["url"]
|
||||
|
||||
|
||||
def test_watsonx_chat_completions_endpoint_space_id(
|
||||
monkeypatch, watsonx_chat_completion_call
|
||||
):
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm import Choices, Message, ModelResponse, EmbeddingResponse, Usage
|
||||
from litellm import completion
|
||||
from unittest.mock import patch
|
||||
from litellm.llms.xai.chat.transformation import XAIChatConfig, XAI_API_BASE
|
||||
|
|
@ -179,10 +174,6 @@ class TestXAIChat(BaseLLMChatTest):
|
|||
"model": "xai/grok-3-mini-beta",
|
||||
}
|
||||
|
||||
def test_tool_call_no_arguments(self, tool_call_no_arguments):
|
||||
"""Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833"""
|
||||
pass
|
||||
|
||||
def test_web_search(self):
|
||||
"""Web search is only supported for Grok 4 family models"""
|
||||
from litellm.utils import supports_web_search
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue