mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(otel/v2): guard deferred langfuse span against duplicate emit without call id
The deferred close path is deduped by popping the carrier keyed on the call id, so a real call with no litellm_call_id could emit a span from both the success and failure hooks. Skip the deferred span when there is no call id to dedup on, and correct the noop test docstring that no longer described the behavior. Addresses the Greptile P2 on logger.py. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
fc89e5f0d8
commit
775a0c6e21
2 changed files with 15 additions and 2 deletions
|
|
@ -394,7 +394,7 @@ class OpenTelemetryV2(CustomLogger):
|
|||
# it (named provisionally) so it isn't leaked as an open span.
|
||||
carrier.span.end(end_time=to_ns(end_time))
|
||||
return None
|
||||
if carrier is None and call.is_no_upstream_call:
|
||||
if carrier is None and (call.is_no_upstream_call or call_id is None):
|
||||
return None
|
||||
data = LLMCallSpanData.from_standard_logging_payload(
|
||||
payload,
|
||||
|
|
|
|||
|
|
@ -284,7 +284,8 @@ def test_sync_log_event_is_noop():
|
|||
|
||||
|
||||
def test_missing_standard_logging_object_is_noop():
|
||||
"""No carrier (``pre_call`` never ran) → the callback emits nothing."""
|
||||
"""With no standard_logging_object (no payload) and no carrier, there is
|
||||
nothing to record, so the callback emits nothing."""
|
||||
logger, exporter = _logger()
|
||||
asyncio.run(
|
||||
logger.async_log_success_event({"litellm_params": {}}, None, None, None)
|
||||
|
|
@ -327,6 +328,18 @@ def test_dynamic_callback_emits_deferred_span_without_pre_call():
|
|||
assert span.status.status_code is StatusCode.UNSET
|
||||
|
||||
|
||||
def test_no_carrier_without_call_id_does_not_double_emit():
|
||||
"""The deferred path is deduped by popping the carrier keyed on the call id.
|
||||
Without a call id there is nothing to dedup on, so both the success and failure
|
||||
hooks would each emit a span for the same event; a call with no ``litellm_call_id``
|
||||
must therefore produce no deferred span rather than a duplicated one."""
|
||||
logger, exporter = _logger()
|
||||
kwargs = _kwargs(_payload(litellm_call_id=None))
|
||||
asyncio.run(logger.async_log_success_event(kwargs, None, None, None))
|
||||
asyncio.run(logger.async_log_failure_event(kwargs, None, None, None))
|
||||
assert exporter.get_finished_spans() == ()
|
||||
|
||||
|
||||
def test_real_llm_failure_still_emitted():
|
||||
"""A genuine LLM failure: ``pre_call`` ran (the call was attempted), so the
|
||||
CLIENT span is opened at the boundary and closed ERROR."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue