When standard_logging_object is None (failure case), Langfuse was falling
back to litellm_call_id while the DB used litellm_trace_id as session_id.
This caused the Session ID in LiteLLM logs to not match the trace in
Langfuse. Now Langfuse checks litellm_trace_id first, matching the DB.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The proxy uses async_failure_handler → LangfusePromptManagement.async_log_failure_event(),
which silently returned when standard_logging_object was None. This meant failed LLM calls
never created traces in Langfuse. Remove the early return and fall back to extracting the
error message from kwargs["exception"] when standard_logging_object is unavailable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace simulated test with one that invokes the real
Logging.failure_handler(), mocks LangFuseHandler to capture kwargs,
and asserts original_response is excluded and session_id is preserved.
This ensures the test catches regressions if the production code changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix: The Langfuse failure logging path was passing self.model_call_details
(which includes original_response, potentially a coroutine) instead of the
clean local kwargs copy. This aligns the failure path with the success path
behavior (litellm_logging.py:2956).
Reverted the session_id-as-trace_id approach as it causes trace collisions
in Langfuse (multiple calls in the same session would overwrite each other).
Instead, session_id is correctly used only for Langfuse session grouping via
trace_params["session_id"], while each call retains its own unique trace_id.
Added 4 tests:
- session_id correctly passed as trace session_id (not trace_id)
- session_id preserved for ERROR level (failure) logs
- explicit trace_id takes priority over session_id
- failure path kwargs excludes original_response
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save and restore litellm.initialized_langfuse_clients around
test_max_langfuse_clients_limit to prevent ordering-dependent failures
in other tests that rely on the counter's value.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three test failures caused by the real langfuse SDK import being triggered
at test time:
1. test_langfuse_prompt_management.py: Both tests create LangfusePromptManagement()
which calls `import langfuse`. Since earlier TestLangfuseUsageDetails tests
remove sys.modules["langfuse"] via patch.dict teardown, the real langfuse
import runs and fails on Python 3.14 (pydantic v1 incompatibility).
Fix: add setup_method/teardown_method to mock sys.modules["langfuse"].
2. test_langfuse.py::test_max_langfuse_clients_limit: Same root cause — creates
LangFuseLogger() without mocking sys.modules["langfuse"].
Fix: wrap test body with patch.dict("sys.modules", {"langfuse": mock}).
3. test_langfuse_otel.py::test_extract_langfuse_metadata_with_header_enrichment:
Replaces sys.modules["litellm.integrations.langfuse.langfuse"] with a stub
without restoring it, causing patch() in later tests to target the stub
instead of the real module.
Fix: use monkeypatch.setitem() which auto-restores after the test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This test has failed repeatedly in CI with:
'Expected _add_prompt_to_generation_params to have been called once. Called 0 times.'
Root cause: _add_prompt_to_generation_params is only called when _supports_prompt()
returns True. Under cross-test state contamination in CI (parallel workers),
langfuse_sdk_version can be in an unexpected state, causing _supports_prompt() to
return False and silently skip the call (exception swallowed by the outer try/except).
Fixes:
- Use reset_mock(side_effect=True) so setUp's trace side_effect is cleared and the
explicit return_value assignment actually takes effect
- Patch _supports_prompt on the logger instance to always return True, making the
_add_prompt_to_generation_params assertion independent of SDK version state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test was creating fresh mocks but not fully isolating from setUp state,
causing intermittent CI failures with 'Expected generation to be called once.
Called 0 times.'
Instead of creating fresh mocks, properly reset the existing setUp mocks to
ensure clean state while maintaining proper mock chain configuration.
Fixes persistent test isolation issue in TestLangfuseUsageDetails by
saving and restoring the global litellm.initialized_langfuse_clients
counter.
Changes:
- Save litellm.initialized_langfuse_clients in setUp
- Restore original value in tearDown
- Prevents counter accumulation across tests
Root Cause:
PR #21248 added logger cleanup but missed the global client counter.
Each test increments litellm.initialized_langfuse_clients when creating
a LangFuseLogger, but the counter was never reset. This caused state
accumulation that could affect test behavior when tests run in certain
orders, leading to "Expected 'generation' to have been called once.
Called 0 times" failures.
Impact:
- test_log_langfuse_v2_handles_null_usage_values was still flaky
- Counter would accumulate: 1, 2, 3... across all tests
- While unlikely to hit MAX (50), accumulated state affected behavior
This completes the test isolation fix started in PR #21248.
Related: #21248
Fixes: Remaining test isolation issues in Langfuse tests
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Follow-up to PR #21248 addressing greptile code review feedback.
Removes hasattr checks for non-existent attributes that were identified
as dead code by greptile automated code review.
Changes:
- Remove hasattr check for LangFuseLogger._langfuse_clients (class attribute doesn't exist)
- Remove hasattr check for self.logger._langfuse_client_cache (instance attribute doesn't exist)
- Update comments to be more accurate about what cleanup is being done
The core fix from PR #21248 (nulling Langfuse reference and deleting
logger instance) remains unchanged and effective. This just removes
misleading dead code that serves no purpose.
Context:
These checks were added defensively but reference attributes that don't
actually exist on the LangFuseLogger class, making them always no-ops.
Greptile correctly identified these as dead code in PR #21248 review,
but the PR was merged before the cleanup could be applied.
Related: #21248
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Enhances test isolation in TestLangfuseUsageDetails by ensuring the
logger instance is completely fresh for each test and properly cleaned
up afterward.
Changes:
- Clear any class-level cached Langfuse clients before creating logger
- Reset logger's cached client instances in setUp
- Properly clean up logger instance and its state in tearDown
Root Cause:
The test_log_langfuse_v2_handles_null_usage_values test was failing
when run after other tests due to lingering state in the logger instance.
While the test passes in isolation, test ordering issues caused it to
fail with "Expected 'generation' to have been called once. Called 0 times."
This builds on PR #21214 which added sys.modules cleanup, but that wasn't
sufficient to prevent all state leakage between tests.
Fixes: Test isolation issues in test_log_langfuse_v2_handles_null_usage_values
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The manual sys.modules restoration code was redundant because
patch.dict.stop() automatically handles the cleanup. This simplifies
the tearDown method and removes the now-unused _original_langfuse_module
instance variable.
Addresses review comment: https://github.com/BerriAI/litellm/pull/21214#pullrequestreview-3802348462
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixes test_log_langfuse_v2_handles_null_usage_values flaky test failure
by properly cleaning up sys.modules['langfuse'] in tearDown.
Changes:
- Store original langfuse module in setUp before mocking
- Restore original or remove mock in tearDown to prevent state pollution
- Remove invalid print_verbose parameter from log_event_on_langfuse
Root Cause:
The tearDown method was not cleaning up sys.modules['langfuse'] after
each test, causing mock state to leak between tests. This caused
intermittent failures in CI, especially when tests run in parallel or
in different orders.
Impact:
This test has a long history of flakiness with multiple attempted fixes
(#20475, #17599, #17594, #17591, #17588). The missing sys.modules cleanup
was the underlying issue causing continued failures despite those patches.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Create fresh mock objects within the test instead of reusing mocks
from setUp that have side_effect configured. The setUp's side_effect
on mock_langfuse_client.trace can interfere with return_value settings
when tests try to reset and reconfigure mocks.
Using dedicated mock objects for this test avoids state pollution
from setUp's side_effect configuration and makes the test more
deterministic in parallel execution environments.
- Reset mock call counts at start of test to ensure clean state
- Add span method to mock trace to handle log_provider_specific_information_as_span calls
- Re-establish mock chain before test call to ensure fresh state
- Add exception handling to catch and report errors during test execution
- Add verification that trace was called before checking generation
This should fix the flaky test that was failing intermittently with
'Expected generation to have been called once. Called 0 times.'
Fixed three flaky tests that were intermittently failing in CI:
1. test_no_duplicate_spend_logs (test_litellm/responses/test_no_duplicate_spend_logs.py)
Problem: Used await asyncio.sleep(1) to wait for async logging completion,
which created race conditions. The async logging worker queues tasks
in the background, and sleep() doesn't guarantee completion.
Fix: Replaced sleep() with GLOBAL_LOGGING_WORKER.flush() which properly waits
for the logging queue to empty, ensuring all async logging tasks complete
before assertions run.
2. test_log_langfuse_v2_handles_null_usage_values (test_litellm/integrations/test_langfuse.py)
Problem: Used datetime.datetime.now() twice for start_time and end_time, which
could cause timing inconsistencies between test runs, especially in
CI environments with variable execution speeds.
Fix: Use fixed timestamps instead of datetime.now() to ensure consistent timing
across all test runs, eliminating timing-related flakiness.
3. test_watsonx_gpt_oss_prompt_transformation (test_litellm/llms/watsonx/test_watsonx.py)
Problem: Directly accessed mock_post.call_args without checking if it exists,
which could be None if the mock wasn't called or if an exception
occurred before the POST request. The test catches exceptions and
continues, making this a potential failure point.
Fix: Added proper assertions and use call_args_list[0] for safer access:
- Assert that call_args_list has at least one call
- Assert that call_args is not None
- Assert that 'data' key exists in kwargs
This ensures the test fails with clear error messages rather than
intermittent AttributeError exceptions.
All fixes maintain the original test intent while making them deterministic
and reliable in CI environments.
Reapplies the fix from commit a885e21543 that was
reverted in 6c9556be67.
The original revert was done because the test was flaky and giving false
negatives. This fix properly mocks the Langfuse client to ensure the test
can correctly verify that _log_langfuse_v2 converts None usage values to 0.
Changes:
- Add mock_langfuse_client.client attribute to prevent errors during init
- Add trace_id to mock_langfuse_generation for proper return value handling
- Remove redundant mock setup code
- Explicitly set logger.Langfuse to mock client after initialization
- Set logger.langfuse_sdk_version to ensure _supports_* methods work correctly
* Fix test_log_langfuse_v2_handles_null_usage_values test failure
The test was failing because the logger's Langfuse client wasn't properly
mocked. Even though sys.modules was mocked, the logger's __init__ method
creates its own Langfuse client instance that wasn't using the test's mock.
Changes:
- Explicitly set logger.Langfuse to the mock client after initialization
- Set logger.langfuse_sdk_version to ensure _supports_* methods work correctly
- Added mock_langfuse_client.client attribute to prevent errors during init
- Added trace_id to mock_langfuse_generation for proper return value handling
- Removed redundant mock setup code
This ensures the test can properly verify that _log_langfuse_v2 correctly
converts None usage values to 0 by allowing the mock's generation method
to be called and asserted.
Fixes: AssertionError: Expected 'generation' to have been called once. Called 0 times.
The test was failing because it was trying to patch MAX_LANGFUSE_INITIALIZED_CLIENTS
at the wrong path. The constant is imported from litellm.constants into the langfuse
module namespace, so we need to use patch.object on the imported module reference.
Changes:
- Import langfuse module explicitly for patching
- Use patch.object instead of patch string path
- This fixes the AttributeError that was causing CI failures