mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-12 03:48:56 +00:00
* test: move guardrails, image, audio, batches, search legacy tests to unit * test: restore the custom guardrail and remaining chunk 1 unit rows * test: move the ocr matrix to e2e * test: drop comments from moved tests and fix vertex ocr credentials * test: move litellm utils and unified google legacy tests to their tiers * test: move logging callback and pass-through unit legacy tests to their tiers Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: move store_model_in_db legacy tests and start the admin ui migration Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: finish the proxy job rows and drop their CircleCI jobs Moves the remaining multi-instance, openai endpoint, otel, pass-through and top-level proxy tests into tests/integration with scripted upstreams, rewrites the weak replacements to assert the outbound wire and exact responses, and removes the build_and_test job group this PR owns from CircleCI * test: tighten the key generation and error log assertions, rename migrated unit files to their mapped names * test: shard the pagerduty unit tests and record the rust harness file on the coverage allowlist * test: drive the SSO sign-in tests through a scripted IdP and isolate the cost center stand-in * test: type the e2e beta header, prompt cache and secret manager values * test: assert the missing model error message on the OpenAI routes * test: drop the deleted otel base class import and restore mcp redaction and exact langfuse usage checks * test: make the langfuse fixture directory a package for the layout check * test: restore collected no-match cases, real langfuse and gcs payload checks, and gate the aws secret lane * test: keep the ocr e2e pdf fixture next to the test * test: put meta below covers on the streaming tool search e2e * test: run the OCR matrix against a scripted upstream and fix two runner-only e2e failures * test: count only OCR calls in the OCR matrix recorder * test: pin OCR parse bodies and billed cost, group new e2e cases into classes * ci: restore the python image anchor dropped in the main sync --------- Co-authored-by: yuneng <yuneng@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
122 lines
5.2 KiB
Python
122 lines
5.2 KiB
Python
import pytest
|
|
from openai import AsyncOpenAI, BadRequestError, OpenAI
|
|
from pydantic import JsonValue
|
|
|
|
from tests.integration._support.client import Gateway, Scenario, eventually, list_value, object_value, string_value
|
|
|
|
|
|
def _restricted_key(gateway: Gateway, scenario: Scenario) -> str:
|
|
return scenario.key(
|
|
models=[
|
|
"gpt-4",
|
|
"text-embedding-ada-002",
|
|
"dall-e-2",
|
|
"fake-openai-endpoint-2",
|
|
"mistral-embed",
|
|
"non-existent-model",
|
|
]
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_chat_completion_bad_model(gateway: Gateway) -> None:
|
|
with gateway.scenario() as scenario:
|
|
async with AsyncOpenAI(
|
|
api_key=_restricted_key(gateway, scenario), base_url=str(gateway.client.base_url)
|
|
) as client:
|
|
with pytest.raises(BadRequestError):
|
|
await client.chat.completions.create(
|
|
model="non-existent-model", messages=[{"role": "user", "content": "Hello!"}]
|
|
)
|
|
|
|
|
|
def test_chat_completion_bad_model(gateway: Gateway) -> None:
|
|
with gateway.scenario() as scenario:
|
|
with OpenAI(api_key=_restricted_key(gateway, scenario), base_url=str(gateway.client.base_url)) as client:
|
|
with pytest.raises(BadRequestError):
|
|
client.chat.completions.create(
|
|
model="non-existent-model", messages=[{"role": "user", "content": "Hello!"}]
|
|
)
|
|
|
|
|
|
def test_completion_bad_model(gateway: Gateway) -> None:
|
|
with gateway.scenario() as scenario:
|
|
with OpenAI(api_key=_restricted_key(gateway, scenario), base_url=str(gateway.client.base_url)) as client:
|
|
with pytest.raises(BadRequestError):
|
|
client.completions.create(model="non-existent-model", prompt="Hello!")
|
|
|
|
|
|
def test_embeddings_bad_model(gateway: Gateway) -> None:
|
|
with gateway.scenario() as scenario:
|
|
with OpenAI(api_key=_restricted_key(gateway, scenario), base_url=str(gateway.client.base_url)) as client:
|
|
with pytest.raises(BadRequestError):
|
|
client.embeddings.create(model="non-existent-model", input="Hello world")
|
|
|
|
|
|
def test_images_bad_model(gateway: Gateway) -> None:
|
|
with gateway.scenario() as scenario:
|
|
with OpenAI(api_key=_restricted_key(gateway, scenario), base_url=str(gateway.client.base_url)) as client:
|
|
with pytest.raises(BadRequestError):
|
|
client.images.generate(model="non-existent-model", prompt="A cute baby sea otter")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("path", "body"),
|
|
(
|
|
("/v1/chat/completions", {"messages": [{"role": "user", "content": "Hello!"}]}),
|
|
("/v1/completions", {"prompt": "Hello!"}),
|
|
("/v1/embeddings", {"input": "Hello world"}),
|
|
("/v1/images/generations", {"prompt": "A cute baby sea otter"}),
|
|
),
|
|
ids=("chat", "completions", "embeddings", "images"),
|
|
)
|
|
def test_missing_model_parameter_returns_bad_request(
|
|
gateway: Gateway, path: str, body: dict[str, JsonValue]
|
|
) -> None:
|
|
response = gateway.request("POST", path, body)
|
|
assert response.status_code == 400, response.text
|
|
error = object_value(response.json())["error"]
|
|
assert isinstance(error, dict)
|
|
assert error["code"] == "400"
|
|
assert "Invalid model name passed in model=None" in str(error["message"])
|
|
|
|
|
|
def test_chat_completion_bad_model_with_spend_logs(gateway: Gateway) -> None:
|
|
with gateway.scenario() as scenario:
|
|
key: str = _restricted_key(gateway, scenario)
|
|
response = gateway.request(
|
|
"POST",
|
|
"/v1/chat/completions",
|
|
{"model": "non-existent-model", "messages": [{"role": "user", "content": "Hello!"}]},
|
|
key=key,
|
|
)
|
|
assert response.status_code == 400, response.text
|
|
call_id: str | None = response.headers.get("x-litellm-call-id")
|
|
assert call_id is not None
|
|
logs_response = eventually(
|
|
lambda: gateway.request("GET", "/spend/logs", params={"request_id": call_id}),
|
|
lambda candidate: candidate.status_code == 200 and bool(candidate.json()),
|
|
seconds=70,
|
|
)
|
|
logs: list[JsonValue] = list_value(logs_response.json())
|
|
assert len(logs) == 1, logs
|
|
log: dict[str, JsonValue] = object_value(logs[0])
|
|
assert log["request_id"] == call_id
|
|
assert log["model"] == "unknown-model"
|
|
assert log["model_group"] in ("", "non-existent-model")
|
|
assert log["spend"] == 0.0
|
|
assert log["total_tokens"] == 0
|
|
assert log["prompt_tokens"] == 0
|
|
assert log["completion_tokens"] == 0
|
|
|
|
metadata: dict[str, JsonValue] = object_value(log["metadata"])
|
|
assert metadata["status"] == "failure"
|
|
assert "user_api_key" in metadata
|
|
error_info: dict[str, JsonValue] = object_value(metadata["error_information"])
|
|
assert "traceback" in error_info
|
|
assert error_info["error_code"] == "400"
|
|
assert error_info["error_class"] in ("ProxyModelNotFoundError", "BadRequestError")
|
|
assert "non-existent-model" not in string_value(error_info["error_message"])
|
|
assert "/chat/completions: Invalid model name passed in" in string_value(error_info["error_message"])
|
|
assert log["cache_hit"] == "False"
|
|
assert log["response"] == {}
|