mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
* test: drop the cwd-relative sys.path.insert calls from the test suite
TQ003 stands at 1,077 across 1,058 files, and 1,015 of them are the same shape:
sys.path.insert(0, os.path.abspath("../..")) and its deeper siblings. The
argument resolves against the working directory rather than the file, so from
the repo root, where every job runs pytest, it inserts the directory two levels
above the checkout. It has never pointed at litellm. The package is installed
into the environment anyway, which is what actually makes the import work, and
what the rule's message has said all along.
Removing them leaves 1,634 imports of sys and os with no remaining reference,
and those go too, except where another test module imports the name back out of
the file. The rest of TQ003 is 62 call sites that resolve against __file__ or a
variable, which are a different question and are left alone.
Collection is identical either way: 45,871 tests and the same 51 pre-existing
collection errors before and after, and ruff reports no new undefined name.
* test: drop the duplicate imports the sys.path sweep exposed to F811
* test(pre-call-utils): restore the os import the new bedrock tests need
178 lines
6 KiB
Python
178 lines
6 KiB
Python
import json
|
|
from typing import Optional
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
# Adds the grandparent directory to sys.path to allow importing project modules
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
|
|
|
|
import litellm
|
|
from litellm.integrations.arize.arize import ArizeLogger
|
|
from litellm.integrations.opentelemetry import OpenTelemetryConfig
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_arize_dynamic_params():
|
|
"""Test that the OpenTelemetry logger uses the correct dynamic headers for each Arize request."""
|
|
|
|
# Create ArizeLogger instance
|
|
arize_logger = ArizeLogger()
|
|
|
|
# Capture the get_tracer_to_use_for_request calls
|
|
tracer_calls = []
|
|
original_get_tracer = arize_logger.get_tracer_to_use_for_request
|
|
|
|
def mock_get_tracer_to_use_for_request(kwargs):
|
|
# Capture the kwargs to see what dynamic headers are being used
|
|
tracer_calls.append(kwargs)
|
|
# Return the default tracer
|
|
return arize_logger.tracer
|
|
|
|
# Mock the get_tracer_to_use_for_request method
|
|
arize_logger.get_tracer_to_use_for_request = mock_get_tracer_to_use_for_request
|
|
|
|
# Set up callbacks
|
|
litellm.callbacks = [arize_logger]
|
|
|
|
# First request with team1 credentials
|
|
await litellm.acompletion(
|
|
model="gpt-3.5-turbo",
|
|
messages=[{"role": "user", "content": "hi test from arize dynamic config"}],
|
|
temperature=0.1,
|
|
mock_response="test_response",
|
|
arize_api_key="team1_key",
|
|
arize_space_id="team1_space_id",
|
|
)
|
|
|
|
# Second request with team2 credentials
|
|
await litellm.acompletion(
|
|
model="gpt-3.5-turbo",
|
|
messages=[{"role": "user", "content": "hi test from arize dynamic config"}],
|
|
temperature=0.1,
|
|
mock_response="test_response",
|
|
arize_api_key="team2_key",
|
|
arize_space_id="team2_space_id",
|
|
)
|
|
|
|
# Allow some time for async processing
|
|
await asyncio.sleep(5)
|
|
|
|
# Assertions
|
|
print(f"Tracer calls: {len(tracer_calls)}")
|
|
|
|
# We should have captured calls for both requests
|
|
assert (
|
|
len(tracer_calls) >= 2
|
|
), f"Expected at least 2 tracer calls, got {len(tracer_calls)}"
|
|
|
|
# Check that we have the expected dynamic params in the kwargs
|
|
team1_found = False
|
|
team2_found = False
|
|
|
|
print("args to tracer calls", tracer_calls)
|
|
|
|
for call_kwargs in tracer_calls:
|
|
dynamic_params = call_kwargs.get("standard_callback_dynamic_params", {})
|
|
if dynamic_params.get("arize_api_key") == "team1_key":
|
|
team1_found = True
|
|
assert dynamic_params.get("arize_space_id") == "team1_space_id"
|
|
elif dynamic_params.get("arize_api_key") == "team2_key":
|
|
team2_found = True
|
|
assert dynamic_params.get("arize_space_id") == "team2_space_id"
|
|
|
|
# Verify both teams were found
|
|
assert team1_found, "team1 dynamic params not found"
|
|
assert team2_found, "team2 dynamic params not found"
|
|
|
|
print(
|
|
"✅ All assertions passed - OpenTelemetry logger correctly received dynamic params"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_arize_dynamic_headers_in_grpc_requests():
|
|
"""Test that dynamic Arize params are passed as headers to the gRPC/HTTP exporter."""
|
|
|
|
# Track all exporter calls and their headers
|
|
exporter_headers = []
|
|
|
|
def mock_otlp_http_exporter(*args, **kwargs):
|
|
# Capture the headers passed to the HTTP exporter
|
|
headers = kwargs.get("headers", {})
|
|
exporter_headers.append(headers)
|
|
|
|
# Return a mock exporter
|
|
mock_exporter = MagicMock()
|
|
mock_exporter.export = MagicMock(return_value=None)
|
|
return mock_exporter
|
|
|
|
# Patch the HTTP exporter (Arize uses HTTP by default)
|
|
with patch(
|
|
"opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter",
|
|
mock_otlp_http_exporter,
|
|
):
|
|
|
|
# Create ArizeLogger with HTTP configuration
|
|
config = OpenTelemetryConfig(
|
|
exporter="otlp_http", endpoint="https://otlp.arize.com/v1"
|
|
)
|
|
arize_logger = ArizeLogger(config=config)
|
|
litellm.callbacks = [arize_logger]
|
|
|
|
# Request 1: team1 dynamic params
|
|
await litellm.acompletion(
|
|
model="gpt-3.5-turbo",
|
|
messages=[{"role": "user", "content": "hi from team1"}],
|
|
mock_response="response1",
|
|
arize_api_key="team1_api_key",
|
|
arize_space_id="team1_space_id",
|
|
)
|
|
|
|
# Request 2: team2 dynamic params
|
|
await litellm.acompletion(
|
|
model="gpt-3.5-turbo",
|
|
messages=[{"role": "user", "content": "hi from team2"}],
|
|
mock_response="response2",
|
|
arize_api_key="team2_api_key",
|
|
arize_space_id="team2_space_id",
|
|
)
|
|
|
|
# Allow time for async processing
|
|
await asyncio.sleep(3)
|
|
|
|
# Assertions
|
|
print(f"Captured exporter headers: {exporter_headers}")
|
|
|
|
# Should have multiple exporter calls (default + dynamic)
|
|
assert (
|
|
len(exporter_headers) >= 2
|
|
), f"Expected at least 2 exporter calls, got {len(exporter_headers)}"
|
|
|
|
# Find team1 and team2 headers
|
|
team1_found = False
|
|
team2_found = False
|
|
|
|
for headers in exporter_headers:
|
|
if (
|
|
headers.get("api_key") == "team1_api_key"
|
|
and headers.get("arize-space-id") == "team1_space_id"
|
|
):
|
|
team1_found = True
|
|
print(f"✅ Found team1 headers: {headers}")
|
|
elif (
|
|
headers.get("api_key") == "team2_api_key"
|
|
and headers.get("arize-space-id") == "team2_space_id"
|
|
):
|
|
team2_found = True
|
|
print(f"✅ Found team2 headers: {headers}")
|
|
|
|
# Verify both dynamic header sets were used
|
|
assert team1_found, "team1 dynamic headers not found in exporter calls"
|
|
assert team2_found, "team2 dynamic headers not found in exporter calls"
|
|
|
|
print(
|
|
"✅ Test passed - Dynamic Arize params correctly passed to gRPC/HTTP exporter"
|
|
)
|