mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-21 08:20:44 +00:00
Merge pull request #21718 from BerriAI/yj_release_feb20
[Infra] litellm_release_day_02_19_2026 fixes
This commit is contained in:
@@ -485,6 +485,7 @@ router_settings:
|
||||
| CUSTOM_TIKTOKEN_CACHE_DIR | Custom directory for Tiktoken cache
|
||||
| CONFIDENT_API_KEY | API key for Confident AI (Deepeval) Logging service
|
||||
| COHERE_API_BASE | Base URL for Cohere API. Default is https://api.cohere.com
|
||||
| COMPETITOR_LLM_TEMPERATURE | Temperature setting for the LLM used in competitor discovery. Default is 0.3
|
||||
| DATABASE_HOST | Hostname for the database server
|
||||
| DATABASE_NAME | Name of the database
|
||||
| DATABASE_PASSWORD | Password for the database user
|
||||
@@ -806,6 +807,7 @@ router_settings:
|
||||
| LOGGING_WORKER_MAX_QUEUE_SIZE | Maximum size of the logging worker queue. When the queue is full, the worker aggressively clears tasks to make room instead of dropping logs. Default is 50,000
|
||||
| LOGGING_WORKER_MAX_TIME_PER_COROUTINE | Maximum time in seconds allowed for each coroutine in the logging worker before timing out. Default is 20.0
|
||||
| LOGGING_WORKER_CLEAR_PERCENTAGE | Percentage of the queue to extract when clearing. Default is 50%
|
||||
| MAX_COMPETITOR_NAMES | Maximum number of competitor names allowed in policy template enrichment. Default is 100
|
||||
| MAX_EXCEPTION_MESSAGE_LENGTH | Maximum length for exception messages. Default is 2000
|
||||
| MAX_ITERATIONS_TO_CLEAR_QUEUE | Maximum number of iterations to attempt when clearing the logging worker queue during shutdown. Default is 200
|
||||
| MAX_TIME_TO_CLEAR_QUEUE | Maximum time in seconds to spend clearing the logging worker queue during shutdown. Default is 5.0
|
||||
|
||||
@@ -603,6 +603,7 @@ OPENAI_CHAT_COMPLETION_PARAMS = [
|
||||
"prompt_cache_retention",
|
||||
"safety_identifier",
|
||||
"verbosity",
|
||||
"store",
|
||||
]
|
||||
|
||||
OPENAI_TRANSCRIPTION_PARAMS = [
|
||||
|
||||
@@ -8201,6 +8201,7 @@
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"tool_use_system_prompt_tokens": 159
|
||||
},
|
||||
"claude-sonnet-4-5": {
|
||||
|
||||
@@ -98,9 +98,8 @@ def _get_spend_logs_metadata(
|
||||
# Filter the metadata dictionary to include only the specified keys
|
||||
clean_metadata = SpendLogsMetadata(
|
||||
**{ # type: ignore
|
||||
key: metadata[key]
|
||||
key: metadata.get(key)
|
||||
for key in SpendLogsMetadata.__annotations__.keys()
|
||||
if key in metadata
|
||||
}
|
||||
)
|
||||
clean_metadata["applied_guardrails"] = applied_guardrails
|
||||
|
||||
@@ -8201,6 +8201,7 @@
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"tool_use_system_prompt_tokens": 159
|
||||
},
|
||||
"claude-sonnet-4-5": {
|
||||
|
||||
@@ -19,7 +19,7 @@ class TestMapReasoningEffort:
|
||||
def test_none_returns_none_for_other_models(self):
|
||||
"""reasoning_effort=None should return None for non-Opus models."""
|
||||
result = AnthropicConfig._map_reasoning_effort(
|
||||
reasoning_effort=None, model="claude-3-7-sonnet-20250219"
|
||||
reasoning_effort=None, model="claude-4-sonnet-20250514"
|
||||
)
|
||||
assert result is None
|
||||
|
||||
@@ -37,14 +37,14 @@ class TestMapReasoningEffort:
|
||||
|
||||
def test_other_model_low_returns_enabled_with_budget(self):
|
||||
result = AnthropicConfig._map_reasoning_effort(
|
||||
reasoning_effort="low", model="claude-3-7-sonnet-20250219"
|
||||
reasoning_effort="low", model="claude-4-sonnet-20250514"
|
||||
)
|
||||
assert result["type"] == "enabled"
|
||||
assert "budget_tokens" in result
|
||||
|
||||
def test_other_model_high_returns_enabled_with_budget(self):
|
||||
result = AnthropicConfig._map_reasoning_effort(
|
||||
reasoning_effort="high", model="claude-3-7-sonnet-20250219"
|
||||
reasoning_effort="high", model="claude-4-sonnet-20250514"
|
||||
)
|
||||
assert result["type"] == "enabled"
|
||||
assert "budget_tokens" in result
|
||||
@@ -59,6 +59,6 @@ class TestMapReasoningEffort:
|
||||
def test_none_string_returns_none_for_other_models(self):
|
||||
"""reasoning_effort='none' should return None for non-Opus models."""
|
||||
result = AnthropicConfig._map_reasoning_effort(
|
||||
reasoning_effort="none", model="claude-3-7-sonnet-20250219"
|
||||
reasoning_effort="none", model="claude-4-sonnet-20250514"
|
||||
)
|
||||
assert result is None
|
||||
|
||||
@@ -79,7 +79,7 @@ def test_multiturn_tool_calls():
|
||||
],
|
||||
'type': 'message'
|
||||
}],
|
||||
model='anthropic/claude-sonnet-4-5',
|
||||
model='anthropic/claude-4-sonnet-20250514',
|
||||
instructions='You are a helpful coding assistant.',
|
||||
tools=[shell_tool]
|
||||
)
|
||||
@@ -105,7 +105,7 @@ def test_multiturn_tool_calls():
|
||||
|
||||
# Use await with asyncio.run for the async function
|
||||
follow_up_response = litellm.responses(
|
||||
model='anthropic/claude-sonnet-4-5',
|
||||
model='anthropic/claude-4-sonnet-20250514',
|
||||
previous_response_id=response_id,
|
||||
input=[{
|
||||
'type': 'function_call_output',
|
||||
|
||||
@@ -489,7 +489,7 @@ class TestAnthropicCompletion(BaseLLMChatTest, BaseAnthropicChatTest):
|
||||
|
||||
def get_base_completion_call_args_with_thinking(self) -> dict:
|
||||
return {
|
||||
"model": "anthropic/claude-3-7-sonnet-latest",
|
||||
"model": "anthropic/claude-sonnet-4-5-20250929",
|
||||
"thinking": {"type": "enabled", "budget_tokens": 16000},
|
||||
}
|
||||
|
||||
@@ -701,7 +701,7 @@ def test_anthropic_tool_with_image():
|
||||
]
|
||||
|
||||
result = prompt_factory(
|
||||
model="claude-3-5-sonnet-20240620",
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
messages=messages,
|
||||
custom_llm_provider="anthropic",
|
||||
)
|
||||
@@ -761,7 +761,7 @@ def test_anthropic_map_openai_params_tools_and_json_schema():
|
||||
mapped_params = litellm.AnthropicConfig().map_openai_params(
|
||||
non_default_params=args["non_default_params"],
|
||||
optional_params={},
|
||||
model="claude-3-5-sonnet-20240620",
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
@@ -803,7 +803,7 @@ def test_anthropic_map_openai_params_tools_with_defs():
|
||||
mapped_params = litellm.AnthropicConfig().map_openai_params(
|
||||
non_default_params=args["non_default_params"],
|
||||
optional_params={},
|
||||
model="claude-3-5-sonnet-20240620",
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
@@ -1039,8 +1039,8 @@ def test_anthropic_citations_api_streaming():
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"anthropic/claude-3-7-sonnet-20250219",
|
||||
"bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
"anthropic/claude-sonnet-4-5-20250929",
|
||||
"bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
],
|
||||
)
|
||||
def test_anthropic_thinking_output(model):
|
||||
@@ -1068,9 +1068,9 @@ def test_anthropic_thinking_output(model):
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"anthropic/claude-3-7-sonnet-20250219",
|
||||
# "bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
# "bedrock/invoke/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
"anthropic/claude-sonnet-4-5-20250929",
|
||||
# "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
# "bedrock/invoke/us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
],
|
||||
)
|
||||
def test_anthropic_thinking_output_stream(model):
|
||||
@@ -1133,7 +1133,7 @@ def test_anthropic_custom_headers():
|
||||
with patch.object(client, "post") as mock_post:
|
||||
try:
|
||||
resp = completion(
|
||||
model="claude-3-5-sonnet-20240620",
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
headers={"anthropic-beta": "computer-use-2025-01-24"},
|
||||
messages=[
|
||||
{"role": "user", "content": "What is the capital of France?"}
|
||||
@@ -1152,8 +1152,8 @@ def test_anthropic_custom_headers():
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"anthropic/claude-3-7-sonnet-20250219",
|
||||
# "bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
"anthropic/claude-sonnet-4-5-20250929",
|
||||
# "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
],
|
||||
)
|
||||
def test_anthropic_thinking_in_assistant_message(model):
|
||||
@@ -1189,8 +1189,8 @@ def test_anthropic_thinking_in_assistant_message(model):
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"anthropic/claude-3-7-sonnet-20250219",
|
||||
# "bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
"anthropic/claude-sonnet-4-5-20250929",
|
||||
# "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
],
|
||||
)
|
||||
def test_anthropic_redacted_thinking_in_assistant_message(model):
|
||||
@@ -1226,7 +1226,7 @@ def test_just_system_message():
|
||||
litellm._turn_on_debug()
|
||||
litellm.modify_params = True
|
||||
params = {
|
||||
"model": "anthropic/claude-3-7-sonnet-20250219",
|
||||
"model": "anthropic/claude-sonnet-4-5-20250929",
|
||||
"messages": [{"role": "system", "content": "You are a helpful assistant."}],
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -864,7 +864,7 @@ def test_convert_to_model_response_object_with_thinking_content():
|
||||
"response_object": {
|
||||
"id": "chatcmpl-8cc87354-70f3-4a14-b71b-332e965d98d2",
|
||||
"created": 1741057687,
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"object": "chat.completion",
|
||||
"system_fingerprint": None,
|
||||
"choices": [
|
||||
|
||||
@@ -3242,7 +3242,7 @@ def vertex_ai_anthropic_thinking_mock_response(*args, **kwargs):
|
||||
"id": "msg_vrtx_011pL6Np3MKxXL3R8theMRJW",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"content": [
|
||||
{
|
||||
"type": "thinking",
|
||||
|
||||
@@ -57,7 +57,7 @@ async def test_litellm_anthropic_prompt_caching_tools():
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "text", "text": "Hello!"}],
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
"stop_reason": "end_turn",
|
||||
"stop_sequence": None,
|
||||
"usage": {"input_tokens": 12, "output_tokens": 6},
|
||||
@@ -74,7 +74,7 @@ async def test_litellm_anthropic_prompt_caching_tools():
|
||||
# Act: Call the litellm.acompletion function
|
||||
response = await litellm.acompletion(
|
||||
api_key="mock_api_key",
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=[
|
||||
{"role": "user", "content": "What's the weather like in Boston today?"}
|
||||
],
|
||||
@@ -154,7 +154,7 @@ async def test_litellm_anthropic_prompt_caching_tools():
|
||||
}
|
||||
],
|
||||
"max_tokens": 64000,
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
}
|
||||
|
||||
mock_post.assert_called_once_with(
|
||||
@@ -240,7 +240,7 @@ async def test_anthropic_vertex_ai_prompt_caching(anthropic_messages, sync_mode)
|
||||
async def test_anthropic_api_prompt_caching_basic():
|
||||
litellm.set_verbose = True
|
||||
response = await litellm.acompletion(
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=[
|
||||
# System Message
|
||||
{
|
||||
@@ -308,7 +308,7 @@ async def test_anthropic_api_prompt_caching_basic_with_cache_creation():
|
||||
|
||||
litellm.set_verbose = True
|
||||
response = await litellm.acompletion(
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=[
|
||||
# System Message
|
||||
{
|
||||
@@ -460,7 +460,7 @@ async def test_anthropic_api_prompt_caching_with_content_str():
|
||||
async def test_anthropic_api_prompt_caching_no_headers():
|
||||
litellm.set_verbose = True
|
||||
response = await litellm.acompletion(
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=[
|
||||
# System Message
|
||||
{
|
||||
@@ -520,7 +520,7 @@ async def test_anthropic_api_prompt_caching_no_headers():
|
||||
@pytest.mark.asyncio()
|
||||
async def test_anthropic_api_prompt_caching_streaming():
|
||||
response = await litellm.acompletion(
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=[
|
||||
# System Message
|
||||
{
|
||||
@@ -603,7 +603,7 @@ async def test_litellm_anthropic_prompt_caching_system():
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "text", "text": "Hello!"}],
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
"stop_reason": "end_turn",
|
||||
"stop_sequence": None,
|
||||
"usage": {"input_tokens": 12, "output_tokens": 6},
|
||||
@@ -620,7 +620,7 @@ async def test_litellm_anthropic_prompt_caching_system():
|
||||
# Act: Call the litellm.acompletion function
|
||||
response = await litellm.acompletion(
|
||||
api_key="mock_api_key",
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
@@ -681,7 +681,7 @@ async def test_litellm_anthropic_prompt_caching_system():
|
||||
}
|
||||
],
|
||||
"max_tokens": 64000,
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
}
|
||||
|
||||
mock_post.assert_called_once_with(
|
||||
|
||||
@@ -2647,13 +2647,13 @@ def test_caching_with_reasoning_content():
|
||||
litellm.cache = Cache()
|
||||
|
||||
response_1 = completion(
|
||||
model="anthropic/claude-3-7-sonnet-latest",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=messages,
|
||||
thinking={"type": "enabled", "budget_tokens": 1024},
|
||||
)
|
||||
|
||||
response_2 = completion(
|
||||
model="anthropic/claude-3-7-sonnet-latest",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=messages,
|
||||
thinking={"type": "enabled", "budget_tokens": 1024},
|
||||
)
|
||||
@@ -2671,14 +2671,14 @@ def test_caching_reasoning_args_miss(): # test in memory cache
|
||||
litellm.set_verbose = True
|
||||
litellm.cache = Cache()
|
||||
response1 = completion(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
caching=True,
|
||||
reasoning_effort="low",
|
||||
mock_response="My response",
|
||||
)
|
||||
response2 = completion(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
caching=True,
|
||||
mock_response="My response",
|
||||
@@ -2697,14 +2697,14 @@ def test_caching_reasoning_args_hit(): # test in memory cache
|
||||
litellm.set_verbose = True
|
||||
litellm.cache = Cache()
|
||||
response1 = completion(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
caching=True,
|
||||
reasoning_effort="low",
|
||||
mock_response="My response",
|
||||
)
|
||||
response2 = completion(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
caching=True,
|
||||
reasoning_effort="low",
|
||||
@@ -2724,14 +2724,14 @@ def test_caching_thinking_args_miss(): # test in memory cache
|
||||
litellm.set_verbose = True
|
||||
litellm.cache = Cache()
|
||||
response1 = completion(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
caching=True,
|
||||
thinking={"type": "enabled", "budget_tokens": 1024},
|
||||
mock_response="My response",
|
||||
)
|
||||
response2 = completion(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
caching=True,
|
||||
mock_response="My response",
|
||||
@@ -2750,14 +2750,14 @@ def test_caching_thinking_args_hit(): # test in memory cache
|
||||
litellm.set_verbose = True
|
||||
litellm.cache = Cache()
|
||||
response1 = completion(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
caching=True,
|
||||
thinking={"type": "enabled", "budget_tokens": 1024},
|
||||
mock_response="My response",
|
||||
)
|
||||
response2 = completion(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
caching=True,
|
||||
thinking={"type": "enabled", "budget_tokens": 1024},
|
||||
|
||||
@@ -286,7 +286,7 @@ def test_completion_claude_3_empty_response():
|
||||
},
|
||||
]
|
||||
try:
|
||||
response = litellm.completion(model="claude-3-7-sonnet-20250219", messages=messages)
|
||||
response = litellm.completion(model="claude-sonnet-4-5-20250929", messages=messages)
|
||||
print(response)
|
||||
except litellm.InternalServerError as e:
|
||||
pytest.skip(f"InternalServerError - {str(e)}")
|
||||
@@ -313,7 +313,7 @@ def test_completion_claude_3():
|
||||
try:
|
||||
# test without max tokens
|
||||
response = completion(
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=messages,
|
||||
)
|
||||
# Add any assertions, here to check response args
|
||||
@@ -326,7 +326,7 @@ def test_completion_claude_3():
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
["anthropic/claude-3-7-sonnet-20250219", "anthropic.claude-3-sonnet-20240229-v1:0"],
|
||||
["anthropic/claude-sonnet-4-5-20250929", "anthropic.claude-3-sonnet-20240229-v1:0"],
|
||||
)
|
||||
def test_completion_claude_3_function_call(model):
|
||||
litellm.set_verbose = True
|
||||
@@ -411,7 +411,7 @@ def test_completion_claude_3_function_call(model):
|
||||
"model, api_key, api_base",
|
||||
[
|
||||
("gpt-3.5-turbo", None, None),
|
||||
("claude-3-7-sonnet-20250219", None, None),
|
||||
("claude-sonnet-4-5-20250929", None, None),
|
||||
("anthropic.claude-3-sonnet-20240229-v1:0", None, None),
|
||||
# (
|
||||
# "azure_ai/command-r-plus",
|
||||
@@ -512,7 +512,7 @@ async def test_anthropic_no_content_error():
|
||||
try:
|
||||
litellm.drop_params = True
|
||||
response = await litellm.acompletion(
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
||||
messages=[
|
||||
{
|
||||
@@ -630,7 +630,7 @@ def test_completion_claude_3_multi_turn_conversations():
|
||||
]
|
||||
try:
|
||||
response = completion(
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=messages,
|
||||
)
|
||||
print(response)
|
||||
@@ -644,7 +644,7 @@ def test_completion_claude_3_stream():
|
||||
try:
|
||||
# test without max tokens
|
||||
response = completion(
|
||||
model="anthropic/claude-3-7-sonnet-20250219",
|
||||
model="anthropic/claude-sonnet-4-5-20250929",
|
||||
messages=messages,
|
||||
max_tokens=10,
|
||||
stream=True,
|
||||
@@ -669,7 +669,7 @@ def encode_image(image_path):
|
||||
[
|
||||
"gpt-4o",
|
||||
"azure/gpt-4.1-mini",
|
||||
"anthropic/claude-3-7-sonnet-20250219",
|
||||
"anthropic/claude-sonnet-4-5-20250929",
|
||||
],
|
||||
) #
|
||||
def test_completion_base64(model):
|
||||
|
||||
@@ -158,7 +158,7 @@ def test_aaparallel_function_call(model):
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"anthropic/claude-3-7-sonnet-20250219",
|
||||
"anthropic/claude-4-sonnet-20250514",
|
||||
"bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import sys
|
||||
from litellm._uuid import uuid
|
||||
from functools import partial
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
@@ -535,10 +536,27 @@ async def test_pass_through_endpoint_bing(client, monkeypatch):
|
||||
first_transformed_url = captured_requests[0][1]["url"]
|
||||
second_transformed_url = captured_requests[1][1]["url"]
|
||||
|
||||
# Assert the response
|
||||
# Parse URLs to compare query params order-independently
|
||||
# Parse first URL
|
||||
parsed_first = urlparse(str(first_transformed_url))
|
||||
first_params = parse_qs(parsed_first.query)
|
||||
|
||||
# Parse second URL
|
||||
parsed_second = urlparse(str(second_transformed_url))
|
||||
second_params = parse_qs(parsed_second.query)
|
||||
|
||||
# Expected values (parse_qs decodes + as space)
|
||||
expected_first_params = {"q": ["bob barker"], "setLang": ["en-US"], "mkt": ["en-US"]}
|
||||
expected_second_params = {"setLang": ["en-US"], "mkt": ["en-US"]}
|
||||
|
||||
# Assert the response - compare base URL and params separately
|
||||
assert (
|
||||
first_transformed_url
|
||||
== "https://api.bing.microsoft.com/v7.0/search?q=bob+barker&setLang=en-US&mkt=en-US"
|
||||
and second_transformed_url
|
||||
== "https://api.bing.microsoft.com/v7.0/search?setLang=en-US&mkt=en-US"
|
||||
parsed_first.scheme == "https"
|
||||
and parsed_first.netloc == "api.bing.microsoft.com"
|
||||
and parsed_first.path == "/v7.0/search"
|
||||
and first_params == expected_first_params
|
||||
and parsed_second.scheme == "https"
|
||||
and parsed_second.netloc == "api.bing.microsoft.com"
|
||||
and parsed_second.path == "/v7.0/search"
|
||||
and second_params == expected_second_params
|
||||
)
|
||||
|
||||
@@ -1387,7 +1387,7 @@ def test_bedrock_claude_3_streaming():
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-4-sonnet-20250514",
|
||||
"cohere.command-r-plus-v1:0", # bedrock
|
||||
"gpt-3.5-turbo",
|
||||
],
|
||||
@@ -2883,7 +2883,7 @@ def test_completion_claude_3_function_call_with_streaming():
|
||||
try:
|
||||
# test without max tokens
|
||||
response = completion(
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
tool_choice="required",
|
||||
|
||||
@@ -54,7 +54,7 @@ class BaseAnthropicMessagesTest(ABC):
|
||||
print("making request to anthropic passthrough with thinking")
|
||||
client = self.get_client()
|
||||
response = client.messages.create(
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
model="claude-4-sonnet-20250514",
|
||||
max_tokens=20000,
|
||||
thinking={"type": "enabled", "budget_tokens": 16000},
|
||||
messages=[
|
||||
@@ -75,7 +75,7 @@ class BaseAnthropicMessagesTest(ABC):
|
||||
collected_response = []
|
||||
client = self.get_client()
|
||||
with client.messages.stream(
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
model="claude-4-sonnet-20250514",
|
||||
max_tokens=20000,
|
||||
thinking={"type": "enabled", "budget_tokens": 16000},
|
||||
messages=[
|
||||
|
||||
@@ -313,7 +313,7 @@ async def test_anthropic_messages_streaming_cost_injection():
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"max_tokens": 10,
|
||||
"stream": True,
|
||||
"messages": [{"role": "user", "content": "Say 'Hi'"}],
|
||||
|
||||
@@ -18,7 +18,9 @@ def test_update_pass_through_route_updates_registry():
|
||||
# Setup - Unique IDs to avoid collision with other tests
|
||||
endpoint_id = "regression-test-endpoint"
|
||||
path = "/regression-test-path"
|
||||
route_key = f"{endpoint_id}:exact:{path}"
|
||||
# Default methods are sorted: DELETE,GET,PATCH,POST,PUT
|
||||
methods_str = "DELETE,GET,PATCH,POST,PUT"
|
||||
route_key = f"{endpoint_id}:exact:{path}:{methods_str}"
|
||||
target = "http://example.com"
|
||||
|
||||
# Cleanup: Ensure clean state before test
|
||||
@@ -90,7 +92,9 @@ def test_update_subpath_route_updates_registry():
|
||||
# Setup
|
||||
endpoint_id = "regression-test-subpath"
|
||||
path = "/regression-test-wildcard"
|
||||
route_key = f"{endpoint_id}:subpath:{path}"
|
||||
# Default methods are sorted: DELETE,GET,PATCH,POST,PUT
|
||||
methods_str = "DELETE,GET,PATCH,POST,PUT"
|
||||
route_key = f"{endpoint_id}:subpath:{path}:{methods_str}"
|
||||
target = "http://example.com"
|
||||
|
||||
if route_key in _registered_pass_through_routes:
|
||||
|
||||
@@ -50,4 +50,7 @@ model_list:
|
||||
vertex_ai_location: "asia-southeast1"
|
||||
|
||||
general_settings:
|
||||
forward_client_headers_to_llm_api: true
|
||||
forward_client_headers_to_llm_api: true
|
||||
|
||||
litellm_settings:
|
||||
drop_params: true
|
||||
@@ -1176,8 +1176,11 @@ async def test_async_increment_tokens_with_ttl_preservation():
|
||||
)
|
||||
|
||||
# Test keys - use hash tags to ensure they map to same Redis cluster slot
|
||||
test_key_with_ttl = "{test_ttl}:with_ttl"
|
||||
test_key_without_ttl = "{test_ttl}:without_ttl"
|
||||
# Use a unique suffix per test run to avoid stale state from prior runs
|
||||
import uuid
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
test_key_with_ttl = f"{{test_ttl}}:with_ttl:{unique_suffix}"
|
||||
test_key_without_ttl = f"{{test_ttl}}:without_ttl:{unique_suffix}"
|
||||
|
||||
try:
|
||||
# Clean up any existing test keys
|
||||
|
||||
@@ -287,6 +287,18 @@ ignored_keys = [
|
||||
"metadata.additional_usage_values.speed",
|
||||
"metadata.litellm_overhead_time_ms",
|
||||
"metadata.cost_breakdown",
|
||||
"metadata.user_api_key",
|
||||
"metadata.user_api_key_alias",
|
||||
"metadata.user_api_key_team_id",
|
||||
"metadata.user_api_key_project_id",
|
||||
"metadata.user_api_key_org_id",
|
||||
"metadata.user_api_key_user_id",
|
||||
"metadata.user_api_key_team_alias",
|
||||
"metadata.spend_logs_metadata",
|
||||
"metadata.requester_ip_address",
|
||||
"metadata.status",
|
||||
"metadata.proxy_server_request",
|
||||
"metadata.error_information",
|
||||
]
|
||||
|
||||
MODEL_LIST = [
|
||||
@@ -1263,7 +1275,7 @@ class TestSpendLogsPayload:
|
||||
mock_response.json.return_value = {
|
||||
"content": [{"text": "Hi! My name is Claude.", "type": "text"}],
|
||||
"id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"role": "assistant",
|
||||
"stop_reason": "end_turn",
|
||||
"stop_sequence": None,
|
||||
@@ -1290,7 +1302,7 @@ class TestSpendLogsPayload:
|
||||
client, "post", side_effect=self.mock_anthropic_response
|
||||
):
|
||||
response = await litellm.acompletion(
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
model="claude-4-sonnet-20250514",
|
||||
messages=[{"role": "user", "content": "Hello, world!"}],
|
||||
metadata={"user_api_key_end_user_id": "test_user_1"},
|
||||
client=client,
|
||||
@@ -1319,10 +1331,10 @@ class TestSpendLogsPayload:
|
||||
"completionStartTime": datetime.datetime(
|
||||
2025, 3, 24, 22, 2, 42, 989132, tzinfo=datetime.timezone.utc
|
||||
),
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"user": "",
|
||||
"team_id": "",
|
||||
"metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "vector_store_request_metadata": null, "guardrail_information": null, "usage_object": {"completion_tokens": 503, "prompt_tokens": 2095, "total_tokens": 2598, "completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}, "model_map_information": {"model_map_key": "claude-3-7-sonnet-20250219", "model_map_value": {"key": "claude-3-7-sonnet-20250219", "max_tokens": 128000, "max_input_tokens": 200000, "max_output_tokens": 128000, "input_cost_per_token": 3e-06, "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": null, "output_cost_per_token_batches": null, "output_cost_per_token": 1.5e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "anthropic", "mode": "chat", "supports_system_messages": null, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": true, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": true, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": false, "supports_reasoning": true, "search_context_cost_per_query": null, "tpm": null, "rpm": null, "supported_openai_params": ["stream", "stop", "temperature", "top_p", "max_tokens", "max_completion_tokens", "tools", "tool_choice", "extra_headers", "parallel_tool_calls", "response_format", "user", "reasoning_effort", "thinking"]}}, "additional_usage_values": {"completion_tokens_details": {"accepted_prediction_tokens": null, "audio_tokens": null, "reasoning_tokens": null, "rejected_prediction_tokens": null, "text_tokens": 503, "image_tokens": null}, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}',
|
||||
"metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "vector_store_request_metadata": null, "guardrail_information": null, "usage_object": {"completion_tokens": 503, "prompt_tokens": 2095, "total_tokens": 2598, "completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}, "model_map_information": {"model_map_key": "claude-4-sonnet-20250514", "model_map_value": {"key": "claude-4-sonnet-20250514", "max_tokens": 128000, "max_input_tokens": 200000, "max_output_tokens": 128000, "input_cost_per_token": 3e-06, "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": null, "output_cost_per_token_batches": null, "output_cost_per_token": 1.5e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "anthropic", "mode": "chat", "supports_system_messages": null, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": true, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": true, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": false, "supports_reasoning": true, "search_context_cost_per_query": null, "tpm": null, "rpm": null, "supported_openai_params": ["stream", "stop", "temperature", "top_p", "max_tokens", "max_completion_tokens", "tools", "tool_choice", "extra_headers", "parallel_tool_calls", "response_format", "user", "reasoning_effort", "thinking"]}}, "additional_usage_values": {"completion_tokens_details": {"accepted_prediction_tokens": null, "audio_tokens": null, "reasoning_tokens": null, "rejected_prediction_tokens": null, "text_tokens": 503, "image_tokens": null}, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}',
|
||||
"cache_key": "Cache OFF",
|
||||
"spend": 0.01383,
|
||||
"total_tokens": 2598,
|
||||
@@ -1364,7 +1376,7 @@ class TestSpendLogsPayload:
|
||||
{
|
||||
"model_name": "my-anthropic-model-group",
|
||||
"litellm_params": {
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
},
|
||||
"model_info": {
|
||||
"id": "my-unique-model-id",
|
||||
@@ -1411,10 +1423,10 @@ class TestSpendLogsPayload:
|
||||
"completionStartTime": datetime.datetime(
|
||||
2025, 3, 24, 22, 2, 42, 989132, tzinfo=datetime.timezone.utc
|
||||
),
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"user": "",
|
||||
"team_id": "",
|
||||
"metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "vector_store_request_metadata": null, "guardrail_information": null, "usage_object": {"completion_tokens": 503, "prompt_tokens": 2095, "total_tokens": 2598, "completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}, "model_map_information": {"model_map_key": "claude-3-7-sonnet-20250219", "model_map_value": {"key": "claude-3-7-sonnet-20250219", "max_tokens": 128000, "max_input_tokens": 200000, "max_output_tokens": 128000, "input_cost_per_token": 3e-06, "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": null, "output_cost_per_token_batches": null, "output_cost_per_token": 1.5e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "anthropic", "mode": "chat", "supports_system_messages": null, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": true, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": true, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": false, "supports_reasoning": true, "search_context_cost_per_query": null, "tpm": null, "rpm": null, "supported_openai_params": ["stream", "stop", "temperature", "top_p", "max_tokens", "max_completion_tokens", "tools", "tool_choice", "extra_headers", "parallel_tool_calls", "response_format", "user", "reasoning_effort", "thinking"]}}, "additional_usage_values": {"completion_tokens_details": {"accepted_prediction_tokens": null, "audio_tokens": null, "reasoning_tokens": null, "rejected_prediction_tokens": null, "text_tokens": 503, "image_tokens": null}, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}',
|
||||
"metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "vector_store_request_metadata": null, "guardrail_information": null, "usage_object": {"completion_tokens": 503, "prompt_tokens": 2095, "total_tokens": 2598, "completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}, "model_map_information": {"model_map_key": "claude-4-sonnet-20250514", "model_map_value": {"key": "claude-4-sonnet-20250514", "max_tokens": 128000, "max_input_tokens": 200000, "max_output_tokens": 128000, "input_cost_per_token": 3e-06, "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": null, "output_cost_per_token_batches": null, "output_cost_per_token": 1.5e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "anthropic", "mode": "chat", "supports_system_messages": null, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": true, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": true, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": false, "supports_reasoning": true, "search_context_cost_per_query": null, "tpm": null, "rpm": null, "supported_openai_params": ["stream", "stop", "temperature", "top_p", "max_tokens", "max_completion_tokens", "tools", "tool_choice", "extra_headers", "parallel_tool_calls", "response_format", "user", "reasoning_effort", "thinking"]}}, "additional_usage_values": {"completion_tokens_details": {"accepted_prediction_tokens": null, "audio_tokens": null, "reasoning_tokens": null, "rejected_prediction_tokens": null, "text_tokens": 503, "image_tokens": null}, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}',
|
||||
"cache_key": "Cache OFF",
|
||||
"spend": 0.01383,
|
||||
"total_tokens": 2598,
|
||||
|
||||
@@ -225,13 +225,15 @@ class TestProxyInitializationHelpers:
|
||||
assert modified_url == ""
|
||||
|
||||
@patch("uvicorn.run")
|
||||
@patch("atexit.register") # 🔥 critical
|
||||
def test_skip_server_startup(self, mock_atexit_register, mock_uvicorn_run):
|
||||
@patch("atexit.register") # critical
|
||||
@patch("litellm.proxy.db.prisma_client.PrismaManager.setup_database")
|
||||
@patch("litellm.proxy.db.prisma_client.should_update_prisma_schema", return_value=False)
|
||||
def test_skip_server_startup(self, mock_should_update, mock_setup_db, mock_atexit_register, mock_uvicorn_run):
|
||||
from click.testing import CliRunner
|
||||
|
||||
from litellm.proxy.proxy_cli import run_server
|
||||
|
||||
runner = CliRunner()
|
||||
runner = CliRunner(mix_stderr=False)
|
||||
|
||||
mock_proxy_module = MagicMock(
|
||||
app=MagicMock(),
|
||||
@@ -594,7 +596,7 @@ class TestHealthAppFactory:
|
||||
|
||||
from litellm.proxy.proxy_cli import run_server
|
||||
|
||||
runner = CliRunner()
|
||||
runner = CliRunner(mix_stderr=False)
|
||||
|
||||
# Mock subprocess.run to simulate prisma being available
|
||||
mock_subprocess_run.return_value = MagicMock(returncode=0)
|
||||
@@ -602,20 +604,18 @@ class TestHealthAppFactory:
|
||||
# Mock should_update_prisma_schema to return True (so setup_database gets called)
|
||||
mock_should_update_schema.return_value = True
|
||||
|
||||
mock_app = MagicMock()
|
||||
mock_proxy_config = MagicMock()
|
||||
mock_key_mgmt = MagicMock()
|
||||
mock_save_worker_config = MagicMock()
|
||||
mock_proxy_module = MagicMock(
|
||||
app=MagicMock(),
|
||||
ProxyConfig=MagicMock(),
|
||||
KeyManagementSettings=MagicMock(),
|
||||
save_worker_config=MagicMock(),
|
||||
)
|
||||
|
||||
with patch.dict(
|
||||
"sys.modules",
|
||||
{
|
||||
"proxy_server": MagicMock(
|
||||
app=mock_app,
|
||||
ProxyConfig=mock_proxy_config,
|
||||
KeyManagementSettings=mock_key_mgmt,
|
||||
save_worker_config=mock_save_worker_config,
|
||||
)
|
||||
"proxy_server": mock_proxy_module,
|
||||
"litellm.proxy.proxy_server": mock_proxy_module,
|
||||
},
|
||||
), patch(
|
||||
"litellm.proxy.proxy_cli.ProxyInitializationHelpers._get_default_unvicorn_init_args"
|
||||
|
||||
+2
-2
@@ -319,7 +319,7 @@ async def test_should_check_cold_storage_for_full_payload():
|
||||
]
|
||||
}
|
||||
],
|
||||
"model": "anthropic/claude-3-7-sonnet-20250219",
|
||||
"model": "anthropic/claude-4-sonnet-20250514",
|
||||
"stream": True,
|
||||
"litellm_trace_id": "16b86861-c120-4ecb-865b-4d2238bfd8f0"
|
||||
}
|
||||
@@ -333,7 +333,7 @@ async def test_should_check_cold_storage_for_full_payload():
|
||||
"content": "Hello, this is a regular message"
|
||||
}
|
||||
],
|
||||
"model": "anthropic/claude-3-7-sonnet-20250219",
|
||||
"model": "anthropic/claude-4-sonnet-20250514",
|
||||
"stream": True
|
||||
}
|
||||
|
||||
|
||||
@@ -429,7 +429,7 @@ def test_anthropic_web_search_in_model_info():
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
supported_models = [
|
||||
"anthropic/claude-3-7-sonnet-20250219",
|
||||
"anthropic/claude-4-sonnet-20250514",
|
||||
"anthropic/claude-sonnet-4-5-20250929",
|
||||
"anthropic/claude-3-5-sonnet-20241022",
|
||||
"anthropic/claude-3-5-haiku-20241022",
|
||||
@@ -1050,7 +1050,7 @@ def test_supports_computer_use_utility():
|
||||
try:
|
||||
# Test a model known to support computer_use from backup JSON
|
||||
supports_cu_anthropic = supports_computer_use(
|
||||
model="anthropic/claude-3-7-sonnet-20250219"
|
||||
model="anthropic/claude-4-sonnet-20250514"
|
||||
)
|
||||
assert supports_cu_anthropic is True
|
||||
|
||||
@@ -1073,7 +1073,7 @@ def test_supports_computer_use_utility():
|
||||
def test_get_model_info_shows_supports_computer_use():
|
||||
"""
|
||||
Tests if 'supports_computer_use' is correctly retrieved by get_model_info.
|
||||
We'll use 'claude-3-7-sonnet-20250219' as it's configured
|
||||
We'll use 'claude-4-sonnet-20250514' as it's configured
|
||||
in the backup JSON to have supports_computer_use: True.
|
||||
"""
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
@@ -1082,7 +1082,7 @@ def test_get_model_info_shows_supports_computer_use():
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
# This model should have 'supports_computer_use': True in the backup JSON
|
||||
model_known_to_support_computer_use = "claude-3-7-sonnet-20250219"
|
||||
model_known_to_support_computer_use = "claude-4-sonnet-20250514"
|
||||
info = litellm.get_model_info(model_known_to_support_computer_use)
|
||||
print(f"Info for {model_known_to_support_computer_use}: {info}")
|
||||
|
||||
|
||||
+9
-9
@@ -1,5 +1,5 @@
|
||||
import * as useAuthorizedModule from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { renderWithProviders, screen, waitFor } from "../../../../../tests/test-utils";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import AllModelsTab from "./AllModelsTab";
|
||||
|
||||
@@ -116,7 +116,7 @@ describe("AllModelsTab", () => {
|
||||
|
||||
mockUseModelCostMap.mockReturnValueOnce(createModelCostMapMock({}));
|
||||
|
||||
render(<AllModelsTab {...defaultProps} />);
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />);
|
||||
expect(screen.getByText("Current Team:")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -172,7 +172,7 @@ describe("AllModelsTab", () => {
|
||||
|
||||
mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null });
|
||||
|
||||
render(<AllModelsTab {...defaultProps} />);
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />);
|
||||
|
||||
// Component shows API total_count (2), not filtered count
|
||||
// Since default is "personal" team and models don't have direct_access, they're filtered out
|
||||
@@ -233,7 +233,7 @@ describe("AllModelsTab", () => {
|
||||
|
||||
mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null });
|
||||
|
||||
render(<AllModelsTab {...defaultProps} />);
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />);
|
||||
|
||||
// Component shows API total_count (2), not filtered count
|
||||
// Since default is "personal" team and models don't have direct_access, they're filtered out
|
||||
@@ -280,7 +280,7 @@ describe("AllModelsTab", () => {
|
||||
|
||||
mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null });
|
||||
|
||||
render(<AllModelsTab {...defaultProps} />);
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />);
|
||||
|
||||
// Component shows API total_count (2), but only 1 model has direct_access
|
||||
await waitFor(() => {
|
||||
@@ -338,7 +338,7 @@ describe("AllModelsTab", () => {
|
||||
|
||||
mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null });
|
||||
|
||||
render(<AllModelsTab {...defaultProps} />);
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Config Model")).toBeInTheDocument();
|
||||
@@ -380,7 +380,7 @@ describe("AllModelsTab", () => {
|
||||
|
||||
mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null });
|
||||
|
||||
render(<AllModelsTab {...defaultProps} />);
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Defined in config")).toBeInTheDocument();
|
||||
@@ -426,7 +426,7 @@ describe("AllModelsTab", () => {
|
||||
return { data: page1Data, isLoading: false, error: null };
|
||||
});
|
||||
|
||||
render(<AllModelsTab {...defaultProps} />);
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
// Component calculates: ((1-1)*50)+1 = 1, Math.min(1*50, 2) = 2
|
||||
@@ -479,7 +479,7 @@ describe("AllModelsTab", () => {
|
||||
return { data: singlePageData, isLoading: false, error: null };
|
||||
});
|
||||
|
||||
render(<AllModelsTab {...defaultProps} />);
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Showing 1 - 1 of 1 results")).toBeInTheDocument();
|
||||
|
||||
+56
-43
@@ -19,68 +19,62 @@ describe("GuardrailViewer", () => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("shows header, status pill color, duration rounding, and time labels", () => {
|
||||
it("shows header, status pill, and duration", () => {
|
||||
const data = makeGuardrailInformation({ duration: 1.23456, guardrail_status: "success" });
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
|
||||
expect(screen.getByText("Guardrail Information")).toBeInTheDocument();
|
||||
// header status pill (success => green)
|
||||
const statusBadges = screen.getAllByText("success");
|
||||
// there are two status locations: header chip and grid "Status"
|
||||
expect(statusBadges.length).toBeGreaterThanOrEqual(1);
|
||||
// Quick class assertion for at least one of them
|
||||
expect(statusBadges[0].className).toMatch(/bg-green-100/);
|
||||
expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument();
|
||||
// header shows passed count
|
||||
expect(screen.getByText(/1 Passed/)).toBeInTheDocument();
|
||||
// The PASSED badge in the evaluation card
|
||||
expect(screen.getByText("PASSED")).toBeInTheDocument();
|
||||
|
||||
// duration displays with 4 decimals
|
||||
expect(screen.getByText(/1\.2346s/)).toBeInTheDocument();
|
||||
|
||||
// time labels exist
|
||||
expect(screen.getByText("Start Time:")).toBeInTheDocument();
|
||||
expect(screen.getByText("End Time:")).toBeInTheDocument();
|
||||
// duration displays in ms format: Math.round(1.23456 * 1000) = 1235
|
||||
expect(screen.getByText("1235ms")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calculates and displays masked entity totals with pluralization", () => {
|
||||
it("calculates and displays masked entity totals", async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeGuardrailInformation({
|
||||
masked_entity_count: { EMAIL_ADDRESS: 2, PHONE_NUMBER: 1 },
|
||||
});
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
|
||||
expect(screen.getByText("3 masked entities")).toBeInTheDocument();
|
||||
// summary chips for each entry
|
||||
// In collapsed state, the match count badge is visible
|
||||
expect(screen.getByText("3 matched")).toBeInTheDocument();
|
||||
|
||||
// Expand the evaluation card to see entity details
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
// summary chips for each entry inside expanded card
|
||||
expect(screen.getByText("EMAIL_ADDRESS: 2")).toBeInTheDocument();
|
||||
expect(screen.getByText("PHONE_NUMBER: 1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides masked badge & summary when count is zero/empty", () => {
|
||||
it("hides matched badge when count is zero/empty", () => {
|
||||
const data = makeGuardrailInformation({ masked_entity_count: {} });
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
|
||||
expect(screen.queryByText(/masked entity/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Masked Entity Summary")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/matched/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("toggles main section open/closed and chevron rotation class", async () => {
|
||||
it("toggles evaluation card open/closed on click", async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeGuardrailInformation();
|
||||
const { container } = renderWithProviders(<GuardrailViewer data={data} />);
|
||||
|
||||
const header = screen.getByText("Guardrail Information").closest(".ant-collapse-header")!;
|
||||
// Initially expanded (content is visible)
|
||||
expect(screen.getByText("Masked Entity Summary")).toBeInTheDocument();
|
||||
|
||||
// Click to collapse
|
||||
await user.click(header);
|
||||
// Wait for collapse animation and content to be hidden
|
||||
await waitFor(() => {
|
||||
const contentBox = container.querySelector(".ant-collapse-content-box");
|
||||
expect(contentBox).not.toBeVisible();
|
||||
const data = makeGuardrailInformation({
|
||||
masked_entity_count: { EMAIL_ADDRESS: 2 },
|
||||
});
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
|
||||
// Click to expand again
|
||||
await user.click(header);
|
||||
// Wait for expand animation
|
||||
// Initially collapsed — masked entity details not visible
|
||||
expect(screen.queryByText("EMAIL_ADDRESS: 2")).not.toBeInTheDocument();
|
||||
|
||||
// Click to expand
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
expect(screen.getByText("EMAIL_ADDRESS: 2")).toBeInTheDocument();
|
||||
|
||||
// Click again to collapse
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Masked Entity Summary")).toBeVisible();
|
||||
expect(screen.queryByText("EMAIL_ADDRESS: 2")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,6 +91,9 @@ describe("GuardrailViewer", () => {
|
||||
});
|
||||
renderWithProviders(<Component data={data} />);
|
||||
|
||||
// Expand the card to see provider-specific content
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
expect(screen.getByTestId("presidio-mock")).toHaveTextContent("presidio 2");
|
||||
});
|
||||
|
||||
@@ -112,6 +109,10 @@ describe("GuardrailViewer", () => {
|
||||
guardrail_response: [makeEntity()],
|
||||
});
|
||||
renderWithProviders(<Component data={data} />);
|
||||
|
||||
// Expand the card to see provider-specific content
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
expect(screen.getByTestId("presidio-mock")).toHaveTextContent("count:1");
|
||||
});
|
||||
|
||||
@@ -127,22 +128,31 @@ describe("GuardrailViewer", () => {
|
||||
guardrail_response: makeBedrockResponse({ action: "GUARDRAIL_INTERVENED" }),
|
||||
});
|
||||
renderWithProviders(<Component data={data} />);
|
||||
|
||||
// Expand the card to see provider-specific content
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
expect(screen.getByTestId("bedrock-mock")).toHaveTextContent("GUARDRAIL_INTERVENED");
|
||||
});
|
||||
|
||||
it("unknown provider renders neither Presidio nor Bedrock details", () => {
|
||||
it("unknown provider renders neither Presidio nor Bedrock details", async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeGuardrailInformation({
|
||||
guardrail_provider: "unknown",
|
||||
});
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
// Summary still present
|
||||
expect(screen.getByText("Guardrail Information")).toBeInTheDocument();
|
||||
// No provider sections
|
||||
// Header still present
|
||||
expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument();
|
||||
|
||||
// Expand the card
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
// No Presidio or Bedrock sections
|
||||
expect(screen.queryByText(/Detected Entities/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Raw Bedrock Guardrail Response/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("integration: renders with real Bedrock details without mocks", () => {
|
||||
it("integration: renders with real Bedrock details without mocks", async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeGuardrailInformation({
|
||||
guardrail_provider: "bedrock",
|
||||
guardrail_response: makeBedrockResponse({
|
||||
@@ -152,6 +162,9 @@ describe("GuardrailViewer", () => {
|
||||
});
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
|
||||
// Expand the card to reveal Bedrock details
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
|
||||
// Bedrock summary bits
|
||||
expect(screen.getByText("Outputs")).toBeInTheDocument();
|
||||
expect(screen.getByText("ok")).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user