mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
test: migrate wave 1 phase 1 legacy tests to tests/unit
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
6ef7b86748
commit
03a650c8a9
19 changed files with 14 additions and 229 deletions
|
|
@ -1,153 +0,0 @@
|
|||
"""
|
||||
Regression test for https://github.com/BerriAI/litellm/issues/28505 -
|
||||
the Responses API bridge double-strips the provider prefix from the
|
||||
model name when a Chat Completions request has both `tools` and
|
||||
`reasoning_effort`.
|
||||
|
||||
Root cause: the bridge handler called `litellm.responses()` /
|
||||
`litellm.aresponses()` without passing the already-resolved
|
||||
`custom_llm_provider`. The downstream call then re-invoked
|
||||
`get_llm_provider()` with `custom_llm_provider=None`, which stripped
|
||||
a second provider prefix from a `provider/provider/model` deployment
|
||||
string.
|
||||
|
||||
This test pins both the sync and async bridge handler call sites:
|
||||
the resolved `custom_llm_provider` must be forwarded to the underlying
|
||||
`responses` / `aresponses` call so the provider isn't re-detected.
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.completion_extras.litellm_responses_transformation.handler import (
|
||||
ResponsesToCompletionBridgeHandler,
|
||||
)
|
||||
|
||||
|
||||
def _validated_kwargs():
|
||||
return {
|
||||
"model": "openai/openai/openai/gpt-5.5",
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"optional_params": {},
|
||||
"litellm_params": {},
|
||||
"headers": {},
|
||||
"model_response": MagicMock(),
|
||||
"logging_obj": MagicMock(),
|
||||
"custom_llm_provider": "openai",
|
||||
}
|
||||
|
||||
|
||||
def test_sync_completion_forwards_custom_llm_provider():
|
||||
handler = ResponsesToCompletionBridgeHandler()
|
||||
handler.transformation_handler = MagicMock()
|
||||
handler.transformation_handler.transform_request.return_value = {
|
||||
"model": "openai/openai/openai/gpt-5.5",
|
||||
"input": [],
|
||||
# `_build_sanitized_litellm_params` spreads `custom_llm_provider` from
|
||||
# `litellm_params` into request_data on the real bridge path. Seed
|
||||
# it here so the test exercises the overwrite (not an explicit kwarg
|
||||
# that would TypeError against an already-present key).
|
||||
"custom_llm_provider": "should-be-overwritten",
|
||||
}
|
||||
handler.transformation_handler.transform_response.return_value = (
|
||||
_validated_kwargs()["model_response"]
|
||||
)
|
||||
with (
|
||||
patch.object(
|
||||
handler, "validate_input_kwargs", return_value=_validated_kwargs()
|
||||
),
|
||||
patch(
|
||||
"litellm.responses",
|
||||
return_value=MagicMock(spec=[]),
|
||||
) as mock_responses,
|
||||
):
|
||||
# The handler routes ResponsesAPIResponse through transform_response.
|
||||
# We just want to verify the kwargs going INTO responses().
|
||||
try:
|
||||
handler.completion(acompletion=False)
|
||||
except Exception:
|
||||
# Downstream handling (transform_response, type checks) is not
|
||||
# the subject of this test.
|
||||
pass
|
||||
assert mock_responses.called
|
||||
kwargs = mock_responses.call_args.kwargs
|
||||
assert kwargs.get("custom_llm_provider") == "openai", (
|
||||
"sync bridge must forward custom_llm_provider to litellm.responses() "
|
||||
"so the downstream get_llm_provider() call does not re-strip the "
|
||||
"provider prefix on a provider/provider/model deployment string"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_completion_forwards_custom_llm_provider():
|
||||
handler = ResponsesToCompletionBridgeHandler()
|
||||
handler.transformation_handler = MagicMock()
|
||||
handler.transformation_handler.transform_request.return_value = {
|
||||
"model": "openai/openai/openai/gpt-5.5",
|
||||
"input": [],
|
||||
# `_build_sanitized_litellm_params` spreads `custom_llm_provider` from
|
||||
# `litellm_params` into request_data on the real bridge path. Seed
|
||||
# it here so the test exercises the overwrite (not an explicit kwarg
|
||||
# that would TypeError against an already-present key).
|
||||
"custom_llm_provider": "should-be-overwritten",
|
||||
}
|
||||
|
||||
async def _fake_aresponses(**kwargs):
|
||||
_fake_aresponses.kwargs = kwargs
|
||||
return MagicMock(spec=[])
|
||||
|
||||
_fake_aresponses.kwargs = {}
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
handler, "validate_input_kwargs", return_value=_validated_kwargs()
|
||||
),
|
||||
patch("litellm.aresponses", _fake_aresponses),
|
||||
):
|
||||
try:
|
||||
await handler.acompletion()
|
||||
except Exception:
|
||||
pass
|
||||
assert _fake_aresponses.kwargs.get("custom_llm_provider") == "openai", (
|
||||
"async bridge must forward custom_llm_provider to litellm.aresponses() "
|
||||
"so the downstream get_llm_provider() call does not re-strip the "
|
||||
"provider prefix on a provider/provider/model deployment string"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_completion_forwards_aws_region_name():
|
||||
handler = ResponsesToCompletionBridgeHandler()
|
||||
handler.transformation_handler = MagicMock()
|
||||
handler.transformation_handler.transform_request.return_value = {
|
||||
"model": "openai.gpt-5.5",
|
||||
"input": [],
|
||||
"aws_region_name": "us-east-2",
|
||||
"api_base": "https://bedrock-mantle.us-east-1.api.aws/v1",
|
||||
"custom_llm_provider": "bedrock_mantle",
|
||||
}
|
||||
|
||||
async def _fake_aresponses(**kwargs):
|
||||
_fake_aresponses.kwargs = kwargs
|
||||
return MagicMock(spec=[])
|
||||
|
||||
_fake_aresponses.kwargs = {}
|
||||
|
||||
validated = _validated_kwargs()
|
||||
validated["custom_llm_provider"] = "bedrock_mantle"
|
||||
validated["litellm_params"] = {
|
||||
"aws_region_name": "us-east-2",
|
||||
"api_base": "https://bedrock-mantle.us-east-1.api.aws/v1",
|
||||
"custom_llm_provider": "bedrock_mantle",
|
||||
}
|
||||
|
||||
with (
|
||||
patch.object(handler, "validate_input_kwargs", return_value=validated),
|
||||
patch("litellm.aresponses", _fake_aresponses),
|
||||
):
|
||||
try:
|
||||
await handler.acompletion()
|
||||
except Exception:
|
||||
pass
|
||||
assert _fake_aresponses.kwargs.get("aws_region_name") == "us-east-2"
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
|
@ -571,20 +570,3 @@ def test_config_manager_returns_wxo_provider():
|
|||
)
|
||||
assert config is not None
|
||||
assert config.__class__.__name__ == "WatsonxOrchestrateA2AConfig"
|
||||
|
||||
|
||||
def test_wxo_dashboard_auth_fields():
|
||||
fields_path = (
|
||||
Path(__file__).resolve().parents[5]
|
||||
/ "litellm/proxy/public_endpoints/agent_create_fields.json"
|
||||
)
|
||||
agent_fields = json.loads(fields_path.read_text())
|
||||
wxo_agent = next(
|
||||
agent for agent in agent_fields if agent["agent_type"] == "watsonx_orchestrate"
|
||||
)
|
||||
fields_by_key = {field["key"]: field for field in wxo_agent["credential_fields"]}
|
||||
|
||||
assert fields_by_key["auth_mode"]["default_value"] == "cp4d"
|
||||
# Username is CP4D-only; UI does not require it so ibm_cloud users are not blocked.
|
||||
assert fields_by_key["username"]["required"] is False
|
||||
assert "cp4d" in fields_by_key["username"]["tooltip"].lower()
|
||||
|
|
@ -12,17 +12,28 @@ Line shape decides the parse, not the batch's declared endpoint, so an output
|
|||
file mixing Responses-shaped and chat-shaped lines sums across both.
|
||||
"""
|
||||
|
||||
from typing import Literal, get_args, get_type_hints
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
import litellm.batches.batch_utils as bu
|
||||
from litellm.types.llms.openai import CreateBatchRequest
|
||||
|
||||
MODEL = "gpt-5.6"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def local_model_cost_map(monkeypatch):
|
||||
original_model_cost = litellm.model_cost
|
||||
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
litellm.get_model_info.cache_clear()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
litellm.model_cost = original_model_cost
|
||||
litellm.get_model_info.cache_clear()
|
||||
|
||||
|
||||
def _responses_line(input_tokens: int, output_tokens: int) -> dict:
|
||||
return {
|
||||
"response": {
|
||||
|
|
@ -107,13 +118,3 @@ async def test_mixed_shape_batch_output_sums_across_both_line_shapes(local_model
|
|||
assert result.cost == pytest.approx(
|
||||
133 * model_info["input_cost_per_token_batches"] + 107 * model_info["output_cost_per_token_batches"]
|
||||
)
|
||||
|
||||
|
||||
def test_create_batch_endpoint_accepts_v1_responses():
|
||||
"""A type-checked caller can pass endpoint="/v1/responses", which the runtime
|
||||
already forwarded correctly."""
|
||||
endpoint_annotation = get_type_hints(CreateBatchRequest)["endpoint"]
|
||||
assert "/v1/responses" in get_args(endpoint_annotation)
|
||||
|
||||
for create_fn in (litellm.create_batch, litellm.acreate_batch):
|
||||
assert "/v1/responses" in get_args(get_type_hints(create_fn)["endpoint"])
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
import inspect
|
||||
from collections.abc import Awaitable, Callable, Mapping
|
||||
from typing import Final, cast # noqa: TID251 # narrows legacy callable signatures for inspect
|
||||
from typing import Final, cast # noqa: TID251 # narrows legacy callable signatures
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm import main as python_chat
|
||||
from litellm.chat_completions.dispatch import (
|
||||
_ADISPATCH, # pyright: ignore[reportPrivateUsage] # tests configured dispatch
|
||||
_DISPATCH, # pyright: ignore[reportPrivateUsage] # tests configured dispatch
|
||||
|
|
@ -40,15 +38,6 @@ def acompletion_binding(native: NativeAcompletion | None) -> NativeBinding[Nativ
|
|||
return binding
|
||||
|
||||
|
||||
def test_public_signature_is_the_legacy_signature() -> None:
|
||||
public_completion: Final = cast(Callable[..., object], litellm.completion)
|
||||
legacy_completion: Final = cast(Callable[..., object], python_chat.completion)
|
||||
public_acompletion: Final = cast(Callable[..., object], litellm.acompletion)
|
||||
legacy_acompletion: Final = cast(Callable[..., object], python_chat.acompletion)
|
||||
assert inspect.signature(public_completion) == inspect.signature(legacy_completion)
|
||||
assert inspect.signature(public_acompletion) == inspect.signature(legacy_acompletion)
|
||||
|
||||
|
||||
def test_python_route_forwards_original_call_shape() -> None:
|
||||
metadata: Final = {"user_id": "u"}
|
||||
args: Final[tuple[object, ...]] = ("gpt-4o", MESSAGES)
|
||||
|
|
@ -3,7 +3,6 @@ Test HeliconeLogger Gemini/Vertex AI support.
|
|||
Fixes: https://github.com/BerriAI/litellm/issues/19093
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_helicone_gemini_model_in_list():
|
||||
|
|
@ -36,39 +35,6 @@ def test_helicone_gemini_models_recognized():
|
|||
assert is_recognized, f"{model} should be recognized by helicone_model_list"
|
||||
|
||||
|
||||
def test_helicone_vertex_ai_models_recognized():
|
||||
"""
|
||||
Test that Vertex AI models (GLM, DeepSeek, etc.) are recognized via custom_llm_provider.
|
||||
"""
|
||||
# Test models that don't contain "gemini" but are vertex_ai
|
||||
test_models = [
|
||||
"vertex_ai/zai-org/glm-4.7-maas",
|
||||
"vertex_ai/deepseek-ai/deepseek-v3",
|
||||
"vertex_ai/meta/llama-3.1-405b",
|
||||
]
|
||||
for model in test_models:
|
||||
is_vertex_ai = model.startswith("vertex_ai/")
|
||||
assert is_vertex_ai, f"{model} should be recognized as vertex_ai model"
|
||||
|
||||
|
||||
def test_helicone_vertex_ai_via_custom_llm_provider():
|
||||
"""
|
||||
Test that vertex_ai models are recognized when custom_llm_provider is set.
|
||||
"""
|
||||
# Models without vertex_ai/ prefix but with custom_llm_provider="vertex_ai"
|
||||
test_cases = [
|
||||
("zai-org/glm-4.7-maas", "vertex_ai"),
|
||||
("deepseek-ai/deepseek-v3", "vertex_ai"),
|
||||
]
|
||||
for model, custom_llm_provider in test_cases:
|
||||
is_vertex_ai = custom_llm_provider == "vertex_ai" or model.startswith(
|
||||
"vertex_ai/"
|
||||
)
|
||||
assert (
|
||||
is_vertex_ai
|
||||
), f"{model} with custom_llm_provider={custom_llm_provider} should be recognized as vertex_ai"
|
||||
|
||||
|
||||
def test_helicone_vertex_gemini_gets_vertex_provider_url():
|
||||
"""
|
||||
Test that vertex_ai/gemini-* models route to aiplatform.googleapis.com,
|
||||
Loading…
Add table
Reference in a new issue