diff --git a/litellm/integrations/langfuse.py b/litellm/integrations/langfuse/langfuse.py similarity index 93% rename from litellm/integrations/langfuse.py rename to litellm/integrations/langfuse/langfuse.py index fa52b3d7a..182c88637 100644 --- a/litellm/integrations/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -4,7 +4,7 @@ import copy import inspect import os import traceback -from typing import Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, Optional from packaging.version import Version from pydantic import BaseModel @@ -16,6 +16,11 @@ from litellm.secret_managers.main import str_to_bool from litellm.types.integrations.langfuse import * from litellm.types.utils import StandardCallbackDynamicParams, StandardLoggingPayload +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import DynamicLoggingCache +else: + DynamicLoggingCache = Any + class LangFuseLogger: # Class variables or attributes @@ -798,53 +803,3 @@ def log_requester_metadata(clean_metadata: dict): returned_metadata.update({"requester_metadata": requester_metadata}) return returned_metadata - - -def get_langfuse_logging_config( - standard_callback_dynamic_params: StandardCallbackDynamicParams, - globalLangfuseLogger: Optional[LangFuseLogger] = None, -) -> LangfuseLoggingConfig: - """ - This function is used to get the Langfuse logging config for the Langfuse Logger. - It checks if the dynamic parameters are provided in the standard_callback_dynamic_params and uses them to get the Langfuse logging config. - - If no dynamic parameters are provided, it uses the default values. - """ - # only use dynamic params if langfuse credentials are passed dynamically - if _dynamic_langfuse_credentials_are_passed(standard_callback_dynamic_params): - return LangfuseLoggingConfig( - langfuse_secret=standard_callback_dynamic_params.get("langfuse_secret") - or standard_callback_dynamic_params.get("langfuse_secret_key"), - langfuse_public_key=standard_callback_dynamic_params.get( - "langfuse_public_key" - ), - langfuse_host=standard_callback_dynamic_params.get("langfuse_host"), - ) - elif globalLangfuseLogger is not None: - return LangfuseLoggingConfig( - langfuse_secret=globalLangfuseLogger.secret_key, - langfuse_public_key=globalLangfuseLogger.public_key, - langfuse_host=globalLangfuseLogger.langfuse_host, - ) - raise ValueError( - "`langfuse` set as a callback but globalLangfuseLogger is not provided and dynamic params are not passed" - ) - - -def _dynamic_langfuse_credentials_are_passed( - standard_callback_dynamic_params: StandardCallbackDynamicParams, -) -> bool: - """ - This function is used to check if the dynamic langfuse credentials are passed in standard_callback_dynamic_params - - Returns: - bool: True if the dynamic langfuse credentials are passed, False otherwise - """ - if ( - standard_callback_dynamic_params.get("langfuse_host") is not None - or standard_callback_dynamic_params.get("langfuse_public_key") is not None - or standard_callback_dynamic_params.get("langfuse_secret") is not None - or standard_callback_dynamic_params.get("langfuse_secret_key") is not None - ): - return True - return False diff --git a/litellm/integrations/langfuse/langfuse_handler.py b/litellm/integrations/langfuse/langfuse_handler.py new file mode 100644 index 000000000..e3ce736b5 --- /dev/null +++ b/litellm/integrations/langfuse/langfuse_handler.py @@ -0,0 +1,168 @@ +""" +This file contains the LangFuseHandler class + +Used to get the LangFuseLogger for a given request + +Handles Key/Team Based Langfuse Logging +""" + +from typing import TYPE_CHECKING, Any, Dict, Optional + +from litellm.litellm_core_utils.litellm_logging import StandardCallbackDynamicParams + +from .langfuse import LangFuseLogger, LangfuseLoggingConfig + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import DynamicLoggingCache +else: + DynamicLoggingCache = Any + + +class LangFuseHandler: + + @staticmethod + def get_langfuse_logger_for_request( + standard_callback_dynamic_params: StandardCallbackDynamicParams, + in_memory_dynamic_logger_cache: DynamicLoggingCache, + globalLangfuseLogger: Optional[LangFuseLogger] = None, + ) -> LangFuseLogger: + """ + This function is used to get the LangFuseLogger for a given request + + 1. If dynamic credentials are passed + - check if a LangFuseLogger is cached for the dynamic credentials + - if cached LangFuseLogger is not found, create a new LangFuseLogger and cache it + + 2. If dynamic credentials are not passed return the globalLangfuseLogger + + """ + temp_langfuse_logger: Optional[LangFuseLogger] = globalLangfuseLogger + if ( + LangFuseHandler._dynamic_langfuse_credentials_are_passed( + standard_callback_dynamic_params + ) + is False + ): + return LangFuseHandler._return_global_langfuse_logger( + globalLangfuseLogger=globalLangfuseLogger, + in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, + ) + + # get langfuse logging config to use for this request, based on standard_callback_dynamic_params + _credentials = LangFuseHandler.get_dynamic_langfuse_logging_config( + globalLangfuseLogger=globalLangfuseLogger, + standard_callback_dynamic_params=standard_callback_dynamic_params, + ) + credentials_dict = dict(_credentials) + + # check if langfuse logger is already cached + temp_langfuse_logger = in_memory_dynamic_logger_cache.get_cache( + credentials=credentials_dict, service_name="langfuse" + ) + + # if not cached, create a new langfuse logger and cache it + if temp_langfuse_logger is None: + temp_langfuse_logger = ( + LangFuseHandler._create_langfuse_logger_from_credentials( + credentials=credentials_dict, + in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, + ) + ) + + return temp_langfuse_logger + + @staticmethod + def _return_global_langfuse_logger( + globalLangfuseLogger: Optional[LangFuseLogger], + in_memory_dynamic_logger_cache: DynamicLoggingCache, + ) -> LangFuseLogger: + """ + Returns the Global LangfuseLogger set on litellm + + (this is the default langfuse logger - used when no dynamic credentials are passed) + + If no Global LangfuseLogger is set, it will check in_memory_dynamic_logger_cache for a cached LangFuseLogger + This function is used to return the globalLangfuseLogger if it exists, otherwise it will check in_memory_dynamic_logger_cache for a cached LangFuseLogger + """ + if globalLangfuseLogger is not None: + return globalLangfuseLogger + + credentials_dict: Dict[str, Any] = ( + {} + ) # the global langfuse logger uses Environment Variables, there are no dynamic credentials + globalLangfuseLogger = in_memory_dynamic_logger_cache.get_cache( + credentials=credentials_dict, + service_name="langfuse", + ) + if globalLangfuseLogger is None: + globalLangfuseLogger = ( + LangFuseHandler._create_langfuse_logger_from_credentials( + credentials=credentials_dict, + in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, + ) + ) + return globalLangfuseLogger + + @staticmethod + def _create_langfuse_logger_from_credentials( + credentials: Dict, + in_memory_dynamic_logger_cache: DynamicLoggingCache, + ) -> LangFuseLogger: + """ + This function is used to + 1. create a LangFuseLogger from the credentials + 2. cache the LangFuseLogger to prevent re-creating it for the same credentials + """ + + langfuse_logger = LangFuseLogger( + langfuse_public_key=credentials.get("langfuse_public_key"), + langfuse_secret=credentials.get("langfuse_secret"), + langfuse_host=credentials.get("langfuse_host"), + ) + in_memory_dynamic_logger_cache.set_cache( + credentials=credentials, + service_name="langfuse", + logging_obj=langfuse_logger, + ) + return langfuse_logger + + @staticmethod + def get_dynamic_langfuse_logging_config( + standard_callback_dynamic_params: StandardCallbackDynamicParams, + globalLangfuseLogger: Optional[LangFuseLogger] = None, + ) -> LangfuseLoggingConfig: + """ + This function is used to get the Langfuse logging config to use for a given request. + + It checks if the dynamic parameters are provided in the standard_callback_dynamic_params and uses them to get the Langfuse logging config. + + If no dynamic parameters are provided, it uses the `globalLangfuseLogger` values + """ + # only use dynamic params if langfuse credentials are passed dynamically + return LangfuseLoggingConfig( + langfuse_secret=standard_callback_dynamic_params.get("langfuse_secret") + or standard_callback_dynamic_params.get("langfuse_secret_key"), + langfuse_public_key=standard_callback_dynamic_params.get( + "langfuse_public_key" + ), + langfuse_host=standard_callback_dynamic_params.get("langfuse_host"), + ) + + @staticmethod + def _dynamic_langfuse_credentials_are_passed( + standard_callback_dynamic_params: StandardCallbackDynamicParams, + ) -> bool: + """ + This function is used to check if the dynamic langfuse credentials are passed in standard_callback_dynamic_params + + Returns: + bool: True if the dynamic langfuse credentials are passed, False otherwise + """ + if ( + standard_callback_dynamic_params.get("langfuse_host") is not None + or standard_callback_dynamic_params.get("langfuse_public_key") is not None + or standard_callback_dynamic_params.get("langfuse_secret") is not None + or standard_callback_dynamic_params.get("langfuse_secret_key") is not None + ): + return True + return False diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 7aee38151..8a80f9b96 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -70,7 +70,8 @@ from ..integrations.gcs_bucket.gcs_bucket import GCSBucketLogger from ..integrations.greenscale import GreenscaleLogger from ..integrations.helicone import HeliconeLogger from ..integrations.lago import LagoLogger -from ..integrations.langfuse import LangFuseLogger +from ..integrations.langfuse.langfuse import LangFuseLogger +from ..integrations.langfuse.langfuse_handler import LangFuseHandler from ..integrations.langsmith import LangsmithLogger from ..integrations.litedebugger import LiteDebugger from ..integrations.literal_ai import LiteralAILogger @@ -1116,74 +1117,13 @@ class Logging: print_verbose("reaches langfuse for streaming logging!") result = kwargs["complete_streaming_response"] - temp_langfuse_logger = langFuseLogger - if langFuseLogger is None or ( - ( - self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ) - is not None - and self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ) - != langFuseLogger.public_key - ) - or ( - self.standard_callback_dynamic_params.get( - "langfuse_secret" - ) - is not None - and self.standard_callback_dynamic_params.get( - "langfuse_secret" - ) - != langFuseLogger.secret_key - ) - or ( - self.standard_callback_dynamic_params.get( - "langfuse_host" - ) - is not None - and self.standard_callback_dynamic_params.get( - "langfuse_host" - ) - != langFuseLogger.langfuse_host - ) - ): - credentials = { - "langfuse_public_key": self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ), - "langfuse_secret": self.standard_callback_dynamic_params.get( - "langfuse_secret" - ), - "langfuse_host": self.standard_callback_dynamic_params.get( - "langfuse_host" - ), - } - temp_langfuse_logger = ( - in_memory_dynamic_logger_cache.get_cache( - credentials=credentials, service_name="langfuse" - ) - ) - if temp_langfuse_logger is None: - temp_langfuse_logger = LangFuseLogger( - langfuse_public_key=self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ), - langfuse_secret=self.standard_callback_dynamic_params.get( - "langfuse_secret" - ), - langfuse_host=self.standard_callback_dynamic_params.get( - "langfuse_host" - ), - ) - in_memory_dynamic_logger_cache.set_cache( - credentials=credentials, - service_name="langfuse", - logging_obj=temp_langfuse_logger, - ) - if temp_langfuse_logger is not None: - _response = temp_langfuse_logger.log_event( + langfuse_logger_to_use = LangFuseHandler.get_langfuse_logger_for_request( + globalLangfuseLogger=langFuseLogger, + standard_callback_dynamic_params=self.standard_callback_dynamic_params, + in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, + ) + if langfuse_logger_to_use is not None: + _response = langfuse_logger_to_use.log_event( kwargs=kwargs, response_obj=result, start_time=start_time, @@ -1909,50 +1849,12 @@ class Logging: ): # copy.deepcopy raises errors as this could be a coroutine kwargs[k] = v # this only logs streaming once, complete_streaming_response exists i.e when stream ends - if langFuseLogger is None or ( - ( - self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ) - is not None - and self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ) - != langFuseLogger.public_key - ) - or ( - self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ) - is not None - and self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ) - != langFuseLogger.public_key - ) - or ( - self.standard_callback_dynamic_params.get( - "langfuse_host" - ) - is not None - and self.standard_callback_dynamic_params.get( - "langfuse_host" - ) - != langFuseLogger.langfuse_host - ) - ): - langFuseLogger = LangFuseLogger( - langfuse_public_key=self.standard_callback_dynamic_params.get( - "langfuse_public_key" - ), - langfuse_secret=self.standard_callback_dynamic_params.get( - "langfuse_secret" - ), - langfuse_host=self.standard_callback_dynamic_params.get( - "langfuse_host" - ), - ) - _response = langFuseLogger.log_event( + langfuse_logger_to_use = LangFuseHandler.get_langfuse_logger_for_request( + globalLangfuseLogger=langFuseLogger, + standard_callback_dynamic_params=self.standard_callback_dynamic_params, + in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, + ) + _response = langfuse_logger_to_use.log_event( start_time=start_time, end_time=end_time, response_obj=None, diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index 1f2eb5d6d..e12e836de 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -120,7 +120,7 @@ async def health_services_endpoint( # noqa: PLR0915 } if service == "langfuse": - from litellm.integrations.langfuse import LangFuseLogger + from litellm.integrations.langfuse.langfuse import LangFuseLogger langfuse_logger = LangFuseLogger() langfuse_logger.Langfuse.auth_check() diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index bae738c73..8f22d0d78 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -1,48 +1,20 @@ model_list: - ################################################################################ - # Azure - - model_name: gpt-4o-mini - litellm_params: - model: azure/gpt-4o-mini - api_base: https://amazin-prod.openai.azure.com - api_key: "os.environ/AZURE_GPT_4O" - deployment_id: gpt-4o-mini - model_name: gpt-4o litellm_params: - model: azure/gpt-4o - api_base: https://very-cool-prod.openai.azure.com - api_key: "os.environ/AZURE_GPT_4O" - deployment_id: gpt-4o + model: gpt-4o + api_key: os.environ/OPENAI_API_KEY + tpm: 1000000 + rpm: 10000 + - ################################################################################ - # Fireworks - - model_name: fireworks-llama-v3p1-405b-instruct - litellm_params: - model: fireworks_ai/accounts/fireworks/models/llama-v3p1-405b-instruct - api_key: "os.environ/FIREWORKS" - - model_name: fireworks-llama-v3p1-70b-instruct - litellm_params: - model: fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct - api_key: "os.environ/FIREWORKS" - general_settings: - alerting_threshold: 300 # sends alerts if requests hang for 5min+ and responses take 5min+ -litellm_settings: # module level litellm settings - https://github.com/BerriAI/litellm/blob/main/litellm/__init__.py - success_callback: ["prometheus"] - service_callback: ["prometheus_system"] - drop_params: False # Raise an exception if the openai param being passed in isn't supported. - cache: false - default_internal_user_params: - user_role: os.environ/DEFAULT_USER_ROLE + # master key is set via env var + # master_key: ####### + proxy_batch_write_at: 60 # Batch write spend updates every 60s - success_callback: ["s3"] - s3_callback_params: - s3_bucket_name: logs-bucket-litellm # AWS Bucket Name for S3 - s3_region_name: us-west-2 # AWS Region Name for S3 - s3_aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID # us os.environ/ to pass environment variables. This is AWS Access Key ID for S3 - s3_aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY # AWS Secret Access Key for S3 - s3_path: my-test-path # [OPTIONAL] set path in bucket you want to write logs to - s3_endpoint_url: https://s3.amazonaws.com # [OPTIONAL] S3 endpoint URL, if you want to use Backblaze/cloudflare s3 buckets +litellm_settings: + success_callback: ["langfuse"] -router_settings: - routing_strategy: simple-shuffle # "simple-shuffle" shown to result in highest throughput. https://docs.litellm.ai/docs/proxy/configs#load-balancing + # https://docs.litellm.ai/docs/proxy/reliability#default-fallbacks + default_fallbacks: ["gpt-4o-2024-08-06", "claude-3-5-sonnet-20240620"] + fallbacks: [{"gpt-4o-2024-08-06": ["claude-3-5-sonnet-20240620"]}, {"gpt-4o-2024-05-13": ["claude-3-5-sonnet-20240620"]}] \ No newline at end of file diff --git a/tests/local_testing/test_alangfuse.py b/tests/local_testing/test_alangfuse.py index e4c139378..f04366b28 100644 --- a/tests/local_testing/test_alangfuse.py +++ b/tests/local_testing/test_alangfuse.py @@ -428,7 +428,7 @@ async def test_aaalangfuse_logging_metadata(langfuse_client): await asyncio.sleep(2) langfuse_client.flush() - # await asyncio.sleep(10) + await asyncio.sleep(4) # Tests the metadata filtering and the override of the output to be the last generation for trace_id, generation_ids in trace_identifiers.items(): @@ -625,7 +625,7 @@ def test_aaalangfuse_existing_trace_id(): import datetime import litellm - from litellm.integrations.langfuse import LangFuseLogger + from litellm.integrations.langfuse.langfuse import LangFuseLogger langfuse_Logger = LangFuseLogger( langfuse_public_key=os.getenv("LANGFUSE_PROJECT2_PUBLIC"), @@ -1125,7 +1125,7 @@ generation_params = { ) def test_langfuse_prompt_type(prompt): - from litellm.integrations.langfuse import _add_prompt_to_generation_params + from litellm.integrations.langfuse.langfuse import _add_prompt_to_generation_params clean_metadata = { "prompt": { @@ -1232,7 +1232,7 @@ def test_langfuse_prompt_type(prompt): def test_langfuse_logging_metadata(): - from litellm.integrations.langfuse import log_requester_metadata + from litellm.integrations.langfuse.langfuse import log_requester_metadata metadata = {"key": "value", "requester_metadata": {"key": "value"}} diff --git a/tests/logging_callback_tests/test_langfuse_unit_tests.py b/tests/logging_callback_tests/test_langfuse_unit_tests.py new file mode 100644 index 000000000..2a6cbe00a --- /dev/null +++ b/tests/logging_callback_tests/test_langfuse_unit_tests.py @@ -0,0 +1,219 @@ +import json +import os +import sys +from datetime import datetime + +from pydantic.main import Model + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system-path + +import pytest +import litellm +import asyncio +import logging +from litellm._logging import verbose_logger +from litellm.integrations.langfuse.langfuse import ( + LangFuseLogger, +) +from litellm.integrations.langfuse.langfuse_handler import LangFuseHandler +from litellm.types.utils import StandardCallbackDynamicParams +from litellm.litellm_core_utils.litellm_logging import DynamicLoggingCache +from unittest.mock import Mock, patch + + +@pytest.fixture +def dynamic_logging_cache(): + return DynamicLoggingCache() + + +global_langfuse_logger = LangFuseLogger( + langfuse_public_key="global_public_key", + langfuse_secret="global_secret", + langfuse_host="https://global.langfuse.com", +) + + +# IMPORTANT: Test that passing both langfuse_secret_key and langfuse_secret works +standard_params_1 = StandardCallbackDynamicParams( + langfuse_public_key="test_public_key", + langfuse_secret="test_secret", + langfuse_host="https://test.langfuse.com", +) + +standard_params_2 = StandardCallbackDynamicParams( + langfuse_public_key="test_public_key", + langfuse_secret_key="test_secret", + langfuse_host="https://test.langfuse.com", +) + + +@pytest.mark.parametrize("globalLangfuseLogger", [None, global_langfuse_logger]) +@pytest.mark.parametrize("standard_params", [standard_params_1, standard_params_2]) +def test_get_langfuse_logger_for_request_with_dynamic_params( + dynamic_logging_cache, globalLangfuseLogger, standard_params +): + """ + If StandardCallbackDynamicParams contain langfuse credentials the returned Langfuse logger should use the dynamic params + + the new Langfuse logger should be cached + + Even if globalLangfuseLogger is provided, it should use dynamic params if they are passed + """ + + result = LangFuseHandler.get_langfuse_logger_for_request( + standard_callback_dynamic_params=standard_params, + in_memory_dynamic_logger_cache=dynamic_logging_cache, + globalLangfuseLogger=globalLangfuseLogger, + ) + + assert isinstance(result, LangFuseLogger) + assert result.public_key == "test_public_key" + assert result.secret_key == "test_secret" + assert result.langfuse_host == "https://test.langfuse.com" + + print("langfuse logger=", result) + print("vars in langfuse logger=", vars(result)) + + # Check if the logger is cached + cached_logger = dynamic_logging_cache.get_cache( + credentials={ + "langfuse_public_key": "test_public_key", + "langfuse_secret": "test_secret", + "langfuse_host": "https://test.langfuse.com", + }, + service_name="langfuse", + ) + assert cached_logger is result + + +@pytest.mark.parametrize("globalLangfuseLogger", [None, global_langfuse_logger]) +def test_get_langfuse_logger_for_request_with_no_dynamic_params( + dynamic_logging_cache, globalLangfuseLogger +): + """ + If StandardCallbackDynamicParams are not provided, the globalLangfuseLogger should be returned + """ + result = LangFuseHandler.get_langfuse_logger_for_request( + standard_callback_dynamic_params=StandardCallbackDynamicParams(), + in_memory_dynamic_logger_cache=dynamic_logging_cache, + globalLangfuseLogger=globalLangfuseLogger, + ) + + assert result is not None + assert isinstance(result, LangFuseLogger) + + print("langfuse logger=", result) + + if globalLangfuseLogger is not None: + assert result.public_key == "global_public_key" + assert result.secret_key == "global_secret" + assert result.langfuse_host == "https://global.langfuse.com" + + +def test_dynamic_langfuse_credentials_are_passed(): + # Test when credentials are passed + params_with_credentials = StandardCallbackDynamicParams( + langfuse_public_key="test_key", + langfuse_secret="test_secret", + langfuse_host="https://test.langfuse.com", + ) + assert ( + LangFuseHandler._dynamic_langfuse_credentials_are_passed( + params_with_credentials + ) + is True + ) + + # Test when no credentials are passed + params_without_credentials = StandardCallbackDynamicParams() + assert ( + LangFuseHandler._dynamic_langfuse_credentials_are_passed( + params_without_credentials + ) + is False + ) + + # Test when only some credentials are passed + params_partial_credentials = StandardCallbackDynamicParams( + langfuse_public_key="test_key" + ) + assert ( + LangFuseHandler._dynamic_langfuse_credentials_are_passed( + params_partial_credentials + ) + is True + ) + + +def test_get_dynamic_langfuse_logging_config(): + # Test with dynamic params + dynamic_params = StandardCallbackDynamicParams( + langfuse_public_key="dynamic_key", + langfuse_secret="dynamic_secret", + langfuse_host="https://dynamic.langfuse.com", + ) + config = LangFuseHandler.get_dynamic_langfuse_logging_config(dynamic_params) + assert config["langfuse_public_key"] == "dynamic_key" + assert config["langfuse_secret"] == "dynamic_secret" + assert config["langfuse_host"] == "https://dynamic.langfuse.com" + + # Test with no dynamic params + empty_params = StandardCallbackDynamicParams() + config = LangFuseHandler.get_dynamic_langfuse_logging_config(empty_params) + assert config["langfuse_public_key"] is None + assert config["langfuse_secret"] is None + assert config["langfuse_host"] is None + + +def test_return_global_langfuse_logger(): + mock_cache = Mock() + global_logger = LangFuseLogger( + langfuse_public_key="global_key", langfuse_secret="global_secret" + ) + + # Test with existing global logger + result = LangFuseHandler._return_global_langfuse_logger(global_logger, mock_cache) + assert result == global_logger + + # Test without global logger, but with cached logger, should return cached logger + mock_cache.get_cache.return_value = global_logger + result = LangFuseHandler._return_global_langfuse_logger(None, mock_cache) + assert result == global_logger + + # Test without global logger and without cached logger, should create new logger + mock_cache.get_cache.return_value = None + with patch.object( + LangFuseHandler, + "_create_langfuse_logger_from_credentials", + return_value=global_logger, + ): + result = LangFuseHandler._return_global_langfuse_logger(None, mock_cache) + assert result == global_logger + + +def test_get_langfuse_logger_for_request_with_cached_logger(): + """ + Test that get_langfuse_logger_for_request returns the cached logger if it exists when dynamic params are passed + """ + mock_cache = Mock() + cached_logger = LangFuseLogger( + langfuse_public_key="cached_key", langfuse_secret="cached_secret" + ) + mock_cache.get_cache.return_value = cached_logger + + dynamic_params = StandardCallbackDynamicParams( + langfuse_public_key="test_key", + langfuse_secret="test_secret", + langfuse_host="https://test.langfuse.com", + ) + + result = LangFuseHandler.get_langfuse_logger_for_request( + standard_callback_dynamic_params=dynamic_params, + in_memory_dynamic_logger_cache=mock_cache, + globalLangfuseLogger=None, + ) + + assert result == cached_logger + mock_cache.get_cache.assert_called_once()