mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-12 03:48:56 +00:00
Moves 58 legacy nodes (cyberark, logging callback manager, langfuse handler and helpers, SQS, app crypto, bedrock nova and invoke, cloudflare, litellm_proxy, nvidia nim, replicate, triton, convert_dict_to_response, get_model_info, vcr live-call probe) into the tests/unit files that mirror the modules they exercise. Calls that reached huggingface.co, langfuse or 0.0.0.0 are refused at the HTTP boundary with respx. Tests that need credentials, a license, a spawned server, sleeps, a subprocess, or assert nothing stay in their legacy files
181 lines
5.9 KiB
Python
181 lines
5.9 KiB
Python
import asyncio
|
|
import base64
|
|
import json
|
|
import os
|
|
from copy import deepcopy
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
from urllib.parse import unquote
|
|
|
|
import litellm
|
|
import pytest
|
|
|
|
from litellm.integrations.sqs import SQSLogger
|
|
from litellm.types.utils import StandardLoggingPayload
|
|
|
|
from litellm.litellm_core_utils.app_crypto import AppCrypto
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_sqs_logger_flush():
|
|
expected_queue_url = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
|
|
expected_region = "us-east-1"
|
|
|
|
sqs_logger = SQSLogger(
|
|
sqs_queue_url=expected_queue_url,
|
|
sqs_region_name=expected_region,
|
|
sqs_flush_interval=1,
|
|
)
|
|
|
|
# Mock the httpx client
|
|
mock_response = MagicMock()
|
|
mock_response.raise_for_status = MagicMock()
|
|
sqs_logger.async_httpx_client.post = AsyncMock(return_value=mock_response)
|
|
|
|
litellm.callbacks = [sqs_logger]
|
|
|
|
await litellm.acompletion(
|
|
model="gpt-5.5",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
mock_response="hi",
|
|
)
|
|
|
|
await asyncio.sleep(2)
|
|
|
|
# Verify that httpx post was called
|
|
sqs_logger.async_httpx_client.post.assert_called()
|
|
|
|
# Get the call arguments
|
|
call_args = sqs_logger.async_httpx_client.post.call_args
|
|
|
|
# Verify the URL is correct
|
|
called_url = call_args[0][0] # First positional argument
|
|
assert (
|
|
called_url == expected_queue_url
|
|
), f"Expected URL {expected_queue_url}, got {called_url}"
|
|
|
|
# Verify the payload contains StandardLoggingPayload data
|
|
called_data = call_args.kwargs["data"]
|
|
|
|
# Extract the MessageBody from the URL-encoded data
|
|
# Format: "Action=SendMessage&Version=2012-11-05&MessageBody=<url_encoded_json>"
|
|
assert "Action=SendMessage" in called_data
|
|
assert "Version=2012-11-05" in called_data
|
|
assert "MessageBody=" in called_data
|
|
|
|
# Extract and decode the message body
|
|
message_body_start = called_data.find("MessageBody=") + len("MessageBody=")
|
|
message_body_encoded = called_data[message_body_start:]
|
|
message_body_json = unquote(message_body_encoded)
|
|
|
|
# Parse the JSON to verify it's a StandardLoggingPayload
|
|
payload_data = json.loads(message_body_json)
|
|
|
|
# Verify it has the expected StandardLoggingPayload structure
|
|
assert "model" in payload_data
|
|
assert "messages" in payload_data
|
|
assert "response" in payload_data
|
|
assert payload_data["model"] == "gpt-5.5"
|
|
assert len(payload_data["messages"]) == 1
|
|
assert payload_data["messages"][0]["role"] == "user"
|
|
assert payload_data["messages"][0]["content"] == "hello"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_sqs_logger_error_flush():
|
|
expected_queue_url = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
|
|
expected_region = "us-east-1"
|
|
|
|
sqs_logger = SQSLogger(
|
|
sqs_queue_url=expected_queue_url,
|
|
sqs_region_name=expected_region,
|
|
sqs_flush_interval=1,
|
|
)
|
|
|
|
# Mock the httpx client
|
|
mock_response = MagicMock()
|
|
mock_response.raise_for_status = Exception("Something went wrong")
|
|
sqs_logger.async_httpx_client.post = AsyncMock(return_value=mock_response)
|
|
|
|
litellm.callbacks = [sqs_logger]
|
|
|
|
await litellm.acompletion(
|
|
model="gpt-5.5",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
mock_response="Error occurred",
|
|
)
|
|
|
|
await asyncio.sleep(2)
|
|
|
|
# Verify that httpx post was called
|
|
sqs_logger.async_httpx_client.post.assert_called()
|
|
|
|
# Get the call arguments
|
|
call_args = sqs_logger.async_httpx_client.post.call_args
|
|
|
|
# Verify the URL is correct
|
|
called_url = call_args[0][0] # First positional argument
|
|
assert (
|
|
called_url == expected_queue_url
|
|
), f"Expected URL {expected_queue_url}, got {called_url}"
|
|
|
|
# Verify the payload contains StandardLoggingPayload data
|
|
called_data = call_args.kwargs["data"]
|
|
|
|
# Extract the MessageBody from the URL-encoded data
|
|
# Format: "Action=SendMessage&Version=2012-11-05&MessageBody=<url_encoded_json>"
|
|
assert "Action=SendMessage" in called_data
|
|
assert "Version=2012-11-05" in called_data
|
|
assert "MessageBody=" in called_data
|
|
|
|
# Extract and decode the message body
|
|
message_body_start = called_data.find("MessageBody=") + len("MessageBody=")
|
|
message_body_encoded = called_data[message_body_start:]
|
|
message_body_json = unquote(message_body_encoded)
|
|
|
|
# Parse the JSON to verify it's a StandardLoggingPayload
|
|
payload_data = json.loads(message_body_json)
|
|
|
|
# Verify it has the expected StandardLoggingPayload structure
|
|
assert "model" in payload_data
|
|
assert "messages" in payload_data
|
|
assert "response" in payload_data
|
|
assert payload_data["model"] == "gpt-5.5"
|
|
assert len(payload_data["messages"]) == 1
|
|
assert payload_data["messages"][0]["role"] == "user"
|
|
assert payload_data["messages"][0]["content"] == "hello"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_send_batch_triggers_tasks(monkeypatch):
|
|
monkeypatch.setattr("litellm.aws_sqs_callback_params", {})
|
|
monkeypatch.setattr(asyncio, "create_task", MagicMock())
|
|
logger = SQSLogger(sqs_queue_url="https://example.com", sqs_region_name="us-west-2")
|
|
|
|
logger.async_send_message = AsyncMock()
|
|
logger.log_queue = [{"log": 1}, {"log": 2}]
|
|
|
|
await logger.async_send_batch()
|
|
# It uses asyncio.create_task() so direct await count = 0 is expected
|
|
asyncio.create_task.assert_called()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_strip_base64_keeps_non_file_content():
|
|
logger = SQSLogger(sqs_strip_base64_files=True)
|
|
|
|
payload = {
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{"type": "text", "text": "Just text"},
|
|
{"type": "text", "text": "Another message"},
|
|
],
|
|
}
|
|
]
|
|
}
|
|
|
|
stripped = await logger._strip_base64_from_messages(payload)
|
|
|
|
# Should not modify normal text messages
|
|
assert stripped["messages"][0]["content"] == payload["messages"][0]["content"]
|