mirror of
https://github.com/tiennm99/litellm.git
synced 2026-06-17 22:48:35 +00:00
3731ee436a
* init enterprise pip * init enterprise pip * init enterprise pip * test: enterprise pip * add litellm-enterprise to pip * litellm ent check * litellm ent check * fix import email router * fix setup_litellm_enterprise_pip * fix local testing with enterprise pip
184 lines
6.5 KiB
Python
184 lines
6.5 KiB
Python
import io
|
|
import os
|
|
import sys
|
|
|
|
|
|
sys.path.insert(0, os.path.abspath("../.."))
|
|
|
|
import asyncio
|
|
import litellm
|
|
import gzip
|
|
import json
|
|
import logging
|
|
import time
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
import litellm
|
|
from litellm import completion
|
|
from litellm._logging import verbose_logger
|
|
from litellm.integrations.gcs_pubsub.pub_sub import *
|
|
from datetime import datetime, timedelta
|
|
from litellm.types.utils import (
|
|
StandardLoggingPayload,
|
|
StandardLoggingModelInformation,
|
|
StandardLoggingMetadata,
|
|
StandardLoggingHiddenParams,
|
|
)
|
|
|
|
verbose_logger.setLevel(logging.DEBUG)
|
|
from litellm_enterprise.enterprise_callbacks.generic_api_callback import GenericAPILogger
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generic_api_callback():
|
|
"""
|
|
Test the GenericAPILogger callback with a standard logging payload.
|
|
This test mocks the HTTP client and validates that the logger properly
|
|
formats and sends the expected payload.
|
|
"""
|
|
|
|
# Create a mock for the async_httpx_client's post method
|
|
mock_post = AsyncMock()
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.text = "OK"
|
|
|
|
# Set up an endpoint for testing
|
|
test_endpoint = "https://example.com/api/logs"
|
|
test_headers = {"Authorization": "Bearer test_token"}
|
|
os.environ["GENERIC_LOGGER_ENDPOINT"] = test_endpoint
|
|
|
|
# Initialize the GenericAPILogger and set the mock
|
|
generic_logger = GenericAPILogger(
|
|
endpoint=test_endpoint,
|
|
headers=test_headers,
|
|
flush_interval=1
|
|
)
|
|
generic_logger.async_httpx_client.post = mock_post
|
|
litellm.callbacks = [generic_logger]
|
|
|
|
# Make the completion call
|
|
response = await litellm.acompletion(
|
|
model="gpt-4o",
|
|
messages=[{"role": "user", "content": "Hello, world!"}],
|
|
mock_response="hi",
|
|
user="test_user",
|
|
)
|
|
|
|
# Wait for async flush
|
|
await asyncio.sleep(3)
|
|
|
|
# Assert httpx post was called
|
|
mock_post.assert_called_once()
|
|
|
|
# Get the actual request body from the mock
|
|
actual_url = mock_post.call_args[1]["url"]
|
|
print("##########\n")
|
|
print("logs were flushed to URL", actual_url, "with the following headers", mock_post.call_args[1]["headers"])
|
|
assert actual_url == test_endpoint, f"Expected URL {test_endpoint}, got {actual_url}"
|
|
|
|
# For the GenericAPILogger, it sends the payload directly as JSON in the data field
|
|
json_data = mock_post.call_args[1]["data"]
|
|
# Parse the JSON string
|
|
|
|
print("##########\n")
|
|
print("json_data", json_data)
|
|
actual_request = json.loads(json_data)
|
|
|
|
# The payload is a list of StandardLoggingPayload objects in the log queue
|
|
assert isinstance(actual_request, list), "Request body should be a list"
|
|
assert len(actual_request) > 0, "Request body list should not be empty"
|
|
|
|
# Validate the first payload item
|
|
payload_item: StandardLoggingPayload = StandardLoggingPayload(**actual_request[0])
|
|
print("##########\n")
|
|
print(json.dumps(payload_item, indent=4))
|
|
print("##########\n")
|
|
|
|
|
|
# Basic assertions for standard logging payload
|
|
assert payload_item["response_cost"] > 0, "Response cost should be greater than 0"
|
|
assert payload_item["model"] == "gpt-4o", "Model should be gpt-4o"
|
|
assert payload_item["model_parameters"]["user"] == "test_user", "User should be test_user"
|
|
assert payload_item["model"] == "gpt-4o", "Model should be gpt-4o"
|
|
assert payload_item["messages"] == [{"role": "user", "content": "Hello, world!"}], "Messages should be the same"
|
|
assert payload_item["response"]["choices"][0]["message"]["content"] == "hi", "Response should be hi"
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generic_api_callback_multiple_logs():
|
|
"""
|
|
Test the GenericAPILogger callback with multiple chat completions
|
|
"""
|
|
# Create a mock for the async_httpx_client's post method
|
|
mock_post = AsyncMock()
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.text = "OK"
|
|
|
|
# Set up an endpoint for testing
|
|
test_endpoint = "https://example.com/api/logs"
|
|
test_headers = {"Authorization": "Bearer test_token"}
|
|
os.environ["GENERIC_LOGGER_ENDPOINT"] = test_endpoint
|
|
|
|
# Initialize the GenericAPILogger and set the mock
|
|
generic_logger = GenericAPILogger(
|
|
endpoint=test_endpoint,
|
|
headers=test_headers,
|
|
flush_interval=5
|
|
)
|
|
generic_logger.async_httpx_client.post = mock_post
|
|
litellm.callbacks = [generic_logger]
|
|
|
|
# Make the completion call
|
|
for _ in range(10):
|
|
response = await litellm.acompletion(
|
|
model="gpt-4o",
|
|
messages=[{"role": "user", "content": "Hello, world!"}],
|
|
mock_response="hi",
|
|
user="test_user",
|
|
)
|
|
|
|
# Wait for async flush
|
|
await asyncio.sleep(6)
|
|
|
|
# Assert httpx post was called
|
|
mock_post.assert_called_once()
|
|
|
|
# Get the actual request body from the mock
|
|
actual_url = mock_post.call_args[1]["url"]
|
|
print("##########\n")
|
|
print("logs were flushed to URL", actual_url, "with the following headers", mock_post.call_args[1]["headers"])
|
|
assert actual_url == test_endpoint, f"Expected URL {test_endpoint}, got {actual_url}"
|
|
|
|
# For the GenericAPILogger, it sends the payload directly as JSON in the data field
|
|
json_data = mock_post.call_args[1]["data"]
|
|
# Parse the JSON string
|
|
|
|
print("##########\n")
|
|
print("json_data", json_data)
|
|
actual_request = json.loads(json_data)
|
|
|
|
# The payload is a list of StandardLoggingPayload objects in the log queue
|
|
assert isinstance(actual_request, list), "Request body should be a list"
|
|
assert len(actual_request) > 0, "Request body list should not be empty"
|
|
assert len(actual_request) == 10, "Request body list should be 10 items, since we made 10 calls"
|
|
|
|
# Validate all payload items
|
|
for payload_item in actual_request:
|
|
payload_item: StandardLoggingPayload = StandardLoggingPayload(**payload_item)
|
|
print("##########\n")
|
|
print(json.dumps(payload_item, indent=4))
|
|
print("##########\n")
|
|
|
|
assert payload_item["response_cost"] > 0, "Response cost should be greater than 0"
|
|
assert payload_item["model"] == "gpt-4o", "Model should be gpt-4o"
|
|
assert payload_item["model_parameters"]["user"] == "test_user", "User should be test_user"
|
|
assert payload_item["model"] == "gpt-4o", "Model should be gpt-4o"
|
|
assert payload_item["messages"] == [{"role": "user", "content": "Hello, world!"}], "Messages should be the same"
|
|
assert payload_item["response"]["choices"][0]["message"]["content"] == "hi", "Response should be hi"
|
|
|