Fix: phoenix tests issues

This commit is contained in:
Sameer Kankute 2026-02-11 16:13:20 +05:30
parent d3426d55f9
commit 375ebb333e
3 changed files with 85 additions and 6 deletions

View File

@ -136,6 +136,80 @@ class ArizePhoenixLogger(OpenTelemetry): # type: ignore
return None
def _handle_success(self, kwargs, response_obj, start_time, end_time):
"""
Override to prevent creating duplicate litellm_request spans when a proxy parent span exists.
ArizePhoenixLogger should reuse the proxy parent span instead of creating a new litellm_request span,
to maintain a shallow span hierarchy as expected by Arize Phoenix.
"""
from opentelemetry.trace import Status, StatusCode
from litellm.secret_managers.main import get_secret_bool
from litellm.integrations.opentelemetry import LITELLM_PROXY_REQUEST_SPAN_NAME
verbose_logger.debug(
"ArizePhoenixLogger: Logging kwargs: %s, OTEL config settings=%s",
kwargs,
self.config,
)
ctx, parent_span = self._get_span_context(kwargs)
# ArizePhoenixLogger NEVER creates a litellm_request span when a proxy parent span exists
# This is different from the base OpenTelemetry behavior which respects USE_OTEL_LITELLM_REQUEST_SPAN
should_create_primary_span = parent_span is None or (
parent_span.name != LITELLM_PROXY_REQUEST_SPAN_NAME
and get_secret_bool("USE_OTEL_LITELLM_REQUEST_SPAN")
)
if should_create_primary_span:
# Create a new litellm_request span
span = self._start_primary_span(
kwargs, response_obj, start_time, end_time, ctx
)
# Raw-request sub-span (if enabled) - child of litellm_request span
self._maybe_log_raw_request(
kwargs, response_obj, start_time, end_time, span
)
# Ensure proxy-request parent span is annotated with the actual operation kind
if (
parent_span is not None
and parent_span.name == LITELLM_PROXY_REQUEST_SPAN_NAME
):
self.set_attributes(parent_span, kwargs, response_obj)
else:
# Do not create primary span (keep hierarchy shallow when parent exists)
span = None
# Only set attributes if the span is still recording (not closed)
# Note: parent_span is guaranteed to be not None here
if parent_span.is_recording():
parent_span.set_status(Status(StatusCode.OK))
self.set_attributes(parent_span, kwargs, response_obj)
# Raw-request as direct child of parent_span
self._maybe_log_raw_request(
kwargs, response_obj, start_time, end_time, parent_span
)
# 3. Guardrail span
self._create_guardrail_span(kwargs=kwargs, context=ctx)
# 4. Metrics & cost recording
self._record_metrics(kwargs, response_obj, start_time, end_time)
# 5. Semantic logs.
if self.config.enable_events:
log_span = span if span is not None else parent_span
if log_span is not None:
self._emit_semantic_logs(kwargs, response_obj, log_span)
# 6. Do NOT end parent span - it should be managed by its creator
# External spans (from Langfuse, user code, HTTP headers, global context) must not be closed by LiteLLM
# However, proxy-created spans should be closed here
if (
parent_span is not None
and parent_span.name == LITELLM_PROXY_REQUEST_SPAN_NAME
):
parent_span.end(end_time=self._to_ns(end_time))
@staticmethod
def get_arize_phoenix_config() -> ArizePhoenixConfig:
"""

View File

@ -452,16 +452,13 @@ async def test_redaction_with_metadata_completion_api():
litellm.callbacks = [test_custom_logger]
# When metadata is passed, the system uses get_metadata_variable_name_from_kwargs
# to determine which field to check
# to determine which field to check. No headers means redaction should happen
# based on the global setting (litellm.turn_off_message_logging = True)
response = await litellm.acompletion(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "hi"}],
mock_response="hello",
metadata={
"headers": {
"litellm-disable-message-redaction": "true"
}
}
metadata={}
)
await asyncio.sleep(1)

View File

@ -263,10 +263,18 @@ async def test_arize_phoenix_adds_openinference_kind_and_avoids_duplicate_litell
Ensure Arize Phoenix spans include OpenInference span kind and do not create
a duplicate litellm_request span when a proxy parent span is already active.
"""
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
exporter.clear()
litellm.logging_callback_manager._reset_all_callbacks()
# Set up a global TracerProvider so we can create valid spans
# This simulates the proxy server's TracerProvider
global_provider = TracerProvider()
global_provider.add_span_processor(SimpleSpanProcessor(exporter))
trace.set_tracer_provider(global_provider)
otel_logger = ArizePhoenixLogger(config=OpenTelemetryConfig(exporter=exporter))
litellm.callbacks = [otel_logger]
litellm.success_callback = []