diff --git a/docs/my-website/docs/observability/langfuse_integration.md b/docs/my-website/docs/observability/langfuse_integration.md index 34b213f0e2..b6d8c83652 100644 --- a/docs/my-website/docs/observability/langfuse_integration.md +++ b/docs/my-website/docs/observability/langfuse_integration.md @@ -11,6 +11,13 @@ Example trace in Langfuse using multiple models via LiteLLM: +:::info + +For Langfuse v3, we recommend using the [Langfuse OTEL](./langfuse_otel_integration) integration. + +::: + + ## Usage with LiteLLM Proxy (LLM Gateway) 👉 [**Follow this link to start sending logs to langfuse with LiteLLM Proxy server**](../proxy/logging) diff --git a/docs/my-website/docs/observability/langfuse_otel_integration.md b/docs/my-website/docs/observability/langfuse_otel_integration.md index 267738c300..c45c33f0f2 100644 --- a/docs/my-website/docs/observability/langfuse_otel_integration.md +++ b/docs/my-website/docs/observability/langfuse_otel_integration.md @@ -1,7 +1,14 @@ -# Langfuse OpenTelemetry Integration +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import Image from '@theme/IdealImage'; + +# 🪢 Langfuse OpenTelemetry Integration The Langfuse OpenTelemetry integration allows you to send LiteLLM traces and observability data to Langfuse using the OpenTelemetry protocol. This provides a standardized way to collect and analyze your LLM usage data. + + ## Features - Automatic trace collection for all LiteLLM requests @@ -108,15 +115,26 @@ litellm.callbacks = ["langfuse_otel"] Add the integration to your proxy configuration: +1. Add the credentials to your environment variables + +```bash +export LANGFUSE_PUBLIC_KEY="pk-lf-..." +export LANGFUSE_SECRET_KEY="sk-lf-..." +export LANGFUSE_HOST="https://us.cloud.langfuse.com" # Default US region +``` + +2. Setup config.yaml + ```yaml # config.yaml litellm_settings: callbacks: ["langfuse_otel"] +``` -environment_variables: - LANGFUSE_PUBLIC_KEY: "pk-lf-..." - LANGFUSE_SECRET_KEY: "sk-lf-..." - LANGFUSE_HOST: "https://us.cloud.langfuse.com" # Default US region +3. Run the proxy + +```bash +litellm --config /path/to/config.yaml ``` ## Data Collected @@ -163,11 +181,24 @@ This is automatically handled by the integration - you just need to provide the Enable verbose logging to see detailed information: + + + ```python import litellm -litellm.set_verbose = True +litellm._turn_on_debug() ``` + + + +```bash +export LITELLM_LOG="DEBUG" +``` + + + + This will show: - Endpoint resolution logic - Authentication header creation diff --git a/docs/my-website/docs/proxy/prompt_management.md b/docs/my-website/docs/proxy/prompt_management.md index 8ea17425c8..fc35fc5ef3 100644 --- a/docs/my-website/docs/proxy/prompt_management.md +++ b/docs/my-website/docs/proxy/prompt_management.md @@ -210,6 +210,7 @@ These are the params you can pass to the `litellm.completion` function in SDK an ``` prompt_id: str # required prompt_variables: Optional[dict] # optional +prompt_version: Optional[int] # optional langfuse_public_key: Optional[str] # optional langfuse_secret: Optional[str] # optional langfuse_secret_key: Optional[str] # optional diff --git a/docs/my-website/img/langfuse_otel.png b/docs/my-website/img/langfuse_otel.png new file mode 100644 index 0000000000..a91e337f2c Binary files /dev/null and b/docs/my-website/img/langfuse_otel.png differ diff --git a/litellm/integrations/anthropic_cache_control_hook.py b/litellm/integrations/anthropic_cache_control_hook.py index 5c75e452ab..29d9920da4 100644 --- a/litellm/integrations/anthropic_cache_control_hook.py +++ b/litellm/integrations/anthropic_cache_control_hook.py @@ -29,6 +29,7 @@ class AnthropicCacheControlHook(CustomPromptManagement): prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: """ Apply cache control directives based on specified injection points. @@ -80,10 +81,10 @@ class AnthropicCacheControlHook(CustomPromptManagement): # Case 1: Target by specific index if targetted_index is not None: if 0 <= targetted_index < len(messages): - messages[ - targetted_index - ] = AnthropicCacheControlHook._safe_insert_cache_control_in_message( - messages[targetted_index], control + messages[targetted_index] = ( + AnthropicCacheControlHook._safe_insert_cache_control_in_message( + messages[targetted_index], control + ) ) # Case 2: Target by role elif targetted_role is not None: diff --git a/litellm/integrations/custom_logger.py b/litellm/integrations/custom_logger.py index 890482761a..a9cbc65e6f 100644 --- a/litellm/integrations/custom_logger.py +++ b/litellm/integrations/custom_logger.py @@ -89,6 +89,7 @@ class CustomLogger: # https://docs.litellm.ai/docs/observability/custom_callbac litellm_logging_obj: LiteLLMLoggingObj, tools: Optional[List[Dict]] = None, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: """ Returns: @@ -107,6 +108,7 @@ class CustomLogger: # https://docs.litellm.ai/docs/observability/custom_callbac prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: """ Returns: @@ -408,9 +410,10 @@ class CustomLogger: # https://docs.litellm.ai/docs/observability/custom_callbac if len(text) > max_length else text ) - - def _select_metadata_field(self, request_kwargs: Optional[Dict] = None) -> Optional[str]: + def _select_metadata_field( + self, request_kwargs: Optional[Dict] = None + ) -> Optional[str]: """ Select the metadata field to use for logging @@ -418,9 +421,9 @@ class CustomLogger: # https://docs.litellm.ai/docs/observability/custom_callbac 2. Otherwise, use `metadata` """ from litellm.constants import LITELLM_METADATA_FIELD, OLD_LITELLM_METADATA_FIELD + if request_kwargs is None: return None if LITELLM_METADATA_FIELD in request_kwargs: return LITELLM_METADATA_FIELD return OLD_LITELLM_METADATA_FIELD - \ No newline at end of file diff --git a/litellm/integrations/custom_prompt_management.py b/litellm/integrations/custom_prompt_management.py index 061aadc3c0..86cd1dc9f7 100644 --- a/litellm/integrations/custom_prompt_management.py +++ b/litellm/integrations/custom_prompt_management.py @@ -19,6 +19,7 @@ class CustomPromptManagement(CustomLogger, PromptManagementBase): prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: """ Returns: @@ -45,6 +46,7 @@ class CustomPromptManagement(CustomLogger, PromptManagementBase): prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> PromptManagementClient: raise NotImplementedError( "Custom prompt management does not support compile prompt helper" diff --git a/litellm/integrations/humanloop.py b/litellm/integrations/humanloop.py index c62ab1110f..9f43d80626 100644 --- a/litellm/integrations/humanloop.py +++ b/litellm/integrations/humanloop.py @@ -156,7 +156,12 @@ class HumanloopLogger(CustomLogger): prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, - ) -> Tuple[str, List[AllMessageValues], dict,]: + prompt_version: Optional[int] = None, + ) -> Tuple[ + str, + List[AllMessageValues], + dict, + ]: humanloop_api_key = dynamic_callback_params.get( "humanloop_api_key" ) or get_secret_str("HUMANLOOP_API_KEY") diff --git a/litellm/integrations/langfuse/langfuse_otel.py b/litellm/integrations/langfuse/langfuse_otel.py index 6d7f927c3e..7fc222ff6c 100644 --- a/litellm/integrations/langfuse/langfuse_otel.py +++ b/litellm/integrations/langfuse/langfuse_otel.py @@ -1,6 +1,7 @@ import base64 import os from typing import TYPE_CHECKING, Any, Union +from urllib.parse import quote from litellm._logging import verbose_logger from litellm.integrations.arize import _utils @@ -9,10 +10,11 @@ from litellm.types.integrations.langfuse_otel import LangfuseOtelConfig if TYPE_CHECKING: from opentelemetry.trace import Span as _Span + from litellm.integrations.opentelemetry import ( + OpenTelemetryConfig as _OpenTelemetryConfig, + ) from litellm.types.integrations.arize import Protocol as _Protocol - from litellm.integrations.opentelemetry import OpenTelemetryConfig as _OpenTelemetryConfig - Protocol = _Protocol OpenTelemetryConfig = _OpenTelemetryConfig Span = Union[_Span, Any] @@ -54,7 +56,7 @@ class LangfuseOtelLogger: """ public_key = os.environ.get("LANGFUSE_PUBLIC_KEY", None) secret_key = os.environ.get("LANGFUSE_SECRET_KEY", None) - + if not public_key or not secret_key: raise ValueError( "LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY must be set for Langfuse OpenTelemetry integration." @@ -62,7 +64,7 @@ class LangfuseOtelLogger: # Determine endpoint - default to US cloud langfuse_host = os.environ.get("LANGFUSE_HOST", None) - + if langfuse_host: # If LANGFUSE_HOST is provided, construct OTEL endpoint from it if not langfuse_host.startswith("http"): @@ -77,13 +79,13 @@ class LangfuseOtelLogger: # Create Basic Auth header auth_string = f"{public_key}:{secret_key}" auth_header = base64.b64encode(auth_string.encode()).decode() - otlp_auth_headers = f"Authorization=Basic {auth_header}" + # URL encode the entire header value as required by OpenTelemetry specification + otlp_auth_headers = f"Authorization={quote(f'Basic {auth_header}')}" # Set standard OTEL environment variables os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = endpoint os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = otlp_auth_headers return LangfuseOtelConfig( - otlp_auth_headers=otlp_auth_headers, - protocol="otlp_http" - ) \ No newline at end of file + otlp_auth_headers=otlp_auth_headers, protocol="otlp_http" + ) diff --git a/litellm/integrations/langfuse/langfuse_prompt_management.py b/litellm/integrations/langfuse/langfuse_prompt_management.py index 8fe9cb63de..58698ef35a 100644 --- a/litellm/integrations/langfuse/langfuse_prompt_management.py +++ b/litellm/integrations/langfuse/langfuse_prompt_management.py @@ -134,8 +134,14 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge langfuse_prompt_id: str, langfuse_client: LangfuseClass, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> PROMPT_CLIENT: - return langfuse_client.get_prompt(langfuse_prompt_id, label=prompt_label) + + prompt_client = langfuse_client.get_prompt( + langfuse_prompt_id, label=prompt_label, version=prompt_version + ) + + return prompt_client def _compile_prompt( self, @@ -180,7 +186,12 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge litellm_logging_obj: LiteLLMLoggingObj, tools: Optional[List[Dict]] = None, prompt_label: Optional[str] = None, - ) -> Tuple[str, List[AllMessageValues], dict,]: + prompt_version: Optional[int] = None, + ) -> Tuple[ + str, + List[AllMessageValues], + dict, + ]: return self.get_chat_completion_prompt( model, messages, @@ -189,6 +200,7 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge prompt_variables, dynamic_callback_params, prompt_label=prompt_label, + prompt_version=prompt_version, ) def should_run_prompt_management( @@ -203,7 +215,8 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge langfuse_host=dynamic_callback_params.get("langfuse_host"), ) langfuse_prompt_client = self._get_prompt_from_id( - langfuse_prompt_id=prompt_id, langfuse_client=langfuse_client + langfuse_prompt_id=prompt_id, + langfuse_client=langfuse_client, ) return langfuse_prompt_client is not None @@ -213,6 +226,7 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> PromptManagementClient: langfuse_client = langfuse_client_init( langfuse_public_key=dynamic_callback_params.get("langfuse_public_key"), @@ -224,6 +238,7 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge langfuse_prompt_id=prompt_id, langfuse_client=langfuse_client, prompt_label=prompt_label, + prompt_version=prompt_version, ) ## SET PROMPT diff --git a/litellm/integrations/prompt_management_base.py b/litellm/integrations/prompt_management_base.py index c9e7adbccb..4a8bcd2e24 100644 --- a/litellm/integrations/prompt_management_base.py +++ b/litellm/integrations/prompt_management_base.py @@ -34,6 +34,7 @@ class PromptManagementBase(ABC): prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> PromptManagementClient: pass @@ -51,12 +52,14 @@ class PromptManagementBase(ABC): client_messages: List[AllMessageValues], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> PromptManagementClient: compiled_prompt_client = self._compile_prompt_helper( prompt_id=prompt_id, prompt_variables=prompt_variables, dynamic_callback_params=dynamic_callback_params, prompt_label=prompt_label, + prompt_version=prompt_version, ) try: @@ -86,6 +89,7 @@ class PromptManagementBase(ABC): prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: if prompt_id is None: raise ValueError("prompt_id is required for Prompt Management Base class") @@ -100,6 +104,7 @@ class PromptManagementBase(ABC): client_messages=messages, dynamic_callback_params=dynamic_callback_params, prompt_label=prompt_label, + prompt_version=prompt_version, ) completed_messages = prompt_template["completed_messages"] or messages diff --git a/litellm/integrations/vector_store_integrations/bedrock_vector_store.py b/litellm/integrations/vector_store_integrations/bedrock_vector_store.py index a00acefb6a..d3ba3a8ebd 100644 --- a/litellm/integrations/vector_store_integrations/bedrock_vector_store.py +++ b/litellm/integrations/vector_store_integrations/bedrock_vector_store.py @@ -77,6 +77,7 @@ class BedrockVectorStore(BaseVectorStore, BaseAWSLLM): litellm_logging_obj: LiteLLMLoggingObj, tools: Optional[List[Dict]] = None, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: """ Retrieves the context from the Bedrock Knowledge Base and appends it to the messages. @@ -129,9 +130,9 @@ class BedrockVectorStore(BaseVectorStore, BaseAWSLLM): ) ) - litellm_logging_obj.model_call_details[ - "vector_store_request_metadata" - ] = vector_store_request_metadata + litellm_logging_obj.model_call_details["vector_store_request_metadata"] = ( + vector_store_request_metadata + ) return model, messages, non_default_params @@ -143,9 +144,9 @@ class BedrockVectorStore(BaseVectorStore, BaseAWSLLM): """ Transform a BedrockKBResponse to a VectorStoreSearchResponse """ - retrieval_results: Optional[ - List[BedrockKBRetrievalResult] - ] = bedrock_kb_response.get("retrievalResults", None) + retrieval_results: Optional[List[BedrockKBRetrievalResult]] = ( + bedrock_kb_response.get("retrievalResults", None) + ) vector_store_search_response: VectorStoreSearchResponse = ( VectorStoreSearchResponse(search_query=query, data=[]) ) diff --git a/litellm/integrations/vector_stores/bedrock_vector_store.py b/litellm/integrations/vector_stores/bedrock_vector_store.py index a00acefb6a..d3ba3a8ebd 100644 --- a/litellm/integrations/vector_stores/bedrock_vector_store.py +++ b/litellm/integrations/vector_stores/bedrock_vector_store.py @@ -77,6 +77,7 @@ class BedrockVectorStore(BaseVectorStore, BaseAWSLLM): litellm_logging_obj: LiteLLMLoggingObj, tools: Optional[List[Dict]] = None, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: """ Retrieves the context from the Bedrock Knowledge Base and appends it to the messages. @@ -129,9 +130,9 @@ class BedrockVectorStore(BaseVectorStore, BaseAWSLLM): ) ) - litellm_logging_obj.model_call_details[ - "vector_store_request_metadata" - ] = vector_store_request_metadata + litellm_logging_obj.model_call_details["vector_store_request_metadata"] = ( + vector_store_request_metadata + ) return model, messages, non_default_params @@ -143,9 +144,9 @@ class BedrockVectorStore(BaseVectorStore, BaseAWSLLM): """ Transform a BedrockKBResponse to a VectorStoreSearchResponse """ - retrieval_results: Optional[ - List[BedrockKBRetrievalResult] - ] = bedrock_kb_response.get("retrievalResults", None) + retrieval_results: Optional[List[BedrockKBRetrievalResult]] = ( + bedrock_kb_response.get("retrievalResults", None) + ) vector_store_search_response: VectorStoreSearchResponse = ( VectorStoreSearchResponse(search_query=query, data=[]) ) diff --git a/litellm/litellm_core_utils/initialize_dynamic_callback_params.py b/litellm/litellm_core_utils/initialize_dynamic_callback_params.py index e5a19e7bdd..c425319b4d 100644 --- a/litellm/litellm_core_utils/initialize_dynamic_callback_params.py +++ b/litellm/litellm_core_utils/initialize_dynamic_callback_params.py @@ -18,6 +18,7 @@ def initialize_standard_callback_dynamic_params( _supported_callback_params = ( StandardCallbackDynamicParams.__annotations__.keys() ) + for param in _supported_callback_params: if param in kwargs: _param_value = kwargs.pop(param) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index f7be8b1bb5..9ff7903540 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -435,6 +435,7 @@ class Logging(LiteLLMLoggingBaseClass): checks if langfuse_secret_key, gcs_bucket_name in kwargs and sets the corresponding attributes in StandardCallbackDynamicParams """ + return _initialize_standard_callback_dynamic_params(kwargs) def initialize_standard_built_in_tools_params( @@ -556,6 +557,7 @@ class Logging(LiteLLMLoggingBaseClass): prompt_variables: Optional[dict], prompt_management_logger: Optional[CustomLogger] = None, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: custom_logger = ( prompt_management_logger @@ -577,6 +579,7 @@ class Logging(LiteLLMLoggingBaseClass): prompt_variables=prompt_variables, dynamic_callback_params=self.standard_callback_dynamic_params, prompt_label=prompt_label, + prompt_version=prompt_version, ) self.messages = messages return model, messages, non_default_params @@ -591,6 +594,7 @@ class Logging(LiteLLMLoggingBaseClass): prompt_management_logger: Optional[CustomLogger] = None, tools: Optional[List[Dict]] = None, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: custom_logger = ( prompt_management_logger @@ -614,6 +618,7 @@ class Logging(LiteLLMLoggingBaseClass): litellm_logging_obj=self, tools=tools, prompt_label=prompt_label, + prompt_version=prompt_version, ) self.messages = messages return model, messages, non_default_params @@ -2953,8 +2958,6 @@ def set_callbacks(callback_list, function_id=None): # noqa: PLR0915 import sentry_sdk from sentry_sdk.scrubber import EventScrubber - - sentry_sdk_instance = sentry_sdk sentry_trace_rate = ( os.environ.get("SENTRY_API_TRACE_RATE") @@ -2974,8 +2977,7 @@ def set_callbacks(callback_list, function_id=None): # noqa: PLR0915 ), send_default_pii=False, # Prevent sending Personal Identifiable Information event_scrubber=EventScrubber( - denylist=SENTRY_DENYLIST, - pii_denylist=SENTRY_PII_DENYLIST + denylist=SENTRY_DENYLIST, pii_denylist=SENTRY_PII_DENYLIST ), ) capture_exception = sentry_sdk_instance.capture_exception @@ -3033,6 +3035,7 @@ def set_callbacks(callback_list, function_id=None): # noqa: PLR0915 s3Logger = S3Logger() elif callback == "wandb": from litellm.integrations.weights_biases import WeightsBiasesLogger + weightsBiasesLogger = WeightsBiasesLogger() elif callback == "logfire": logfireLogger = LogfireLogger() @@ -3087,6 +3090,7 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 return _openmeter_logger # type: ignore elif logging_integration == "braintrust": from litellm.integrations.braintrust_logging import BraintrustLogger + for callback in _in_memory_loggers: if isinstance(callback, BraintrustLogger): return callback # type: ignore @@ -3453,6 +3457,7 @@ def get_custom_logger_compatible_class( # noqa: PLR0915 return callback elif logging_integration == "braintrust": from litellm.integrations.braintrust_logging import BraintrustLogger + for callback in _in_memory_loggers: if isinstance(callback, BraintrustLogger): return callback diff --git a/litellm/main.py b/litellm/main.py index da6db8e835..5d0662060d 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -438,6 +438,7 @@ async def acompletion( prompt_variables=kwargs.get("prompt_variables", None), tools=tools, prompt_label=kwargs.get("prompt_label", None), + prompt_version=kwargs.get("prompt_version", None), ) ######################################################### # if the chat completion logging hook removed all tools, @@ -1062,6 +1063,7 @@ def completion( # type: ignore # noqa: PLR0915 prompt_id=prompt_id, prompt_variables=prompt_variables, prompt_label=kwargs.get("prompt_label", None), + prompt_version=kwargs.get("prompt_version", None), ) try: @@ -1950,7 +1952,7 @@ def completion( # type: ignore # noqa: PLR0915 or get_secret("MISTRAL_API_BASE") or "https://api.mistral.ai/v1" ) - + response = base_llm_http_handler.completion( model=model, messages=messages, @@ -2615,9 +2617,7 @@ def completion( # type: ignore # noqa: PLR0915 api_base = api_base or litellm.api_base or get_secret("VERTEXAI_API_BASE") new_params = deepcopy(optional_params) - if ( - vertex_partner_models_chat_completion.is_vertex_partner_model(model) - ): + if vertex_partner_models_chat_completion.is_vertex_partner_model(model): model_response = vertex_partner_models_chat_completion.completion( model=model, messages=messages, diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html deleted file mode 100644 index be3a989197..0000000000 --- a/litellm/proxy/_experimental/out/onboarding.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 8be231a1fe..afaac02c08 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -3,11 +3,11 @@ model_list: litellm_params: model: openai/gpt-3.5-turbo api_key: os.environ/OPENAI_API_KEY - -guardrails: - - guardrail_name: azure-prompt-shield + - model_name: langfuse-model litellm_params: - guardrail: azure/prompt_shield - mode: pre_call # "During_call" is also available - api_key: os.environ/AZURE_GUARDRAIL_API_KEY - api_base: os.environ/AZURE_GUARDRAIL_API_BASE \ No newline at end of file + model: langfuse/langfuse-model + prompt_id: test-chat-prompt + prompt_version: 4 + +litellm_settings: + callbacks: ["langfuse_otel"] \ No newline at end of file diff --git a/litellm/proxy/custom_prompt_management.py b/litellm/proxy/custom_prompt_management.py index 8cf20da5e9..cae5890b6c 100644 --- a/litellm/proxy/custom_prompt_management.py +++ b/litellm/proxy/custom_prompt_management.py @@ -16,6 +16,7 @@ class X42PromptManagement(CustomPromptManagement): prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: """ Returns: diff --git a/litellm/router.py b/litellm/router.py index dc12b924ff..4ed8658058 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -1735,6 +1735,7 @@ class Router: kwargs: Dict[str, Any], ): litellm_logging_object = kwargs.get("litellm_logging_obj", None) + if litellm_logging_object is None: litellm_logging_object, kwargs = function_setup( **{ @@ -1770,6 +1771,10 @@ class Router: "litellm_params" ].get("prompt_label", None) + prompt_version = kwargs.get( + "prompt_version", None + ) or prompt_management_deployment["litellm_params"].get("prompt_version", None) + if prompt_id is None or not isinstance(prompt_id, str): raise ValueError( f"Prompt ID is not set or not a string. Got={prompt_id}, type={type(prompt_id)}" @@ -1790,6 +1795,7 @@ class Router: prompt_id=prompt_id, prompt_variables=prompt_variables, prompt_label=prompt_label, + prompt_version=prompt_version, ) kwargs = {**data, **kwargs, **optional_params} diff --git a/litellm/types/utils.py b/litellm/types/utils.py index b9d24d9455..3f5ea0785c 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2072,6 +2072,9 @@ class StandardCallbackDynamicParams(TypedDict, total=False): langfuse_secret_key: Optional[str] langfuse_host: Optional[str] + # Langfuse prompt version + langfuse_prompt_version: Optional[int] + # GCS dynamic params gcs_bucket_name: Optional[str] gcs_path_service_account: Optional[str] @@ -2112,6 +2115,7 @@ all_litellm_params = [ "prompt_id", "provider_specific_header", "prompt_variables", + "prompt_version", "api_base", "force_timeout", "logger_fn", diff --git a/tests/test_litellm/integrations/langfuse/test_langfuse_prompt_management.py b/tests/test_litellm/integrations/langfuse/test_langfuse_prompt_management.py new file mode 100644 index 0000000000..7a5b8bfb1c --- /dev/null +++ b/tests/test_litellm/integrations/langfuse/test_langfuse_prompt_management.py @@ -0,0 +1,32 @@ +import os +from unittest.mock import patch + +from litellm.integrations.langfuse.langfuse_prompt_management import ( + LangfusePromptManagement, +) + + +class TestLangfusePromptManagement: + def test_get_prompt_from_id(self): + langfuse_prompt_management = LangfusePromptManagement() + with patch.object( + langfuse_prompt_management, "should_run_prompt_management" + ) as mock_should_run_prompt_management, patch.object( + langfuse_prompt_management, "_get_prompt_from_id" + ) as mock_get_prompt_from_id: + mock_should_run_prompt_management.return_value = True + chat_completion_prompt = ( + langfuse_prompt_management.get_chat_completion_prompt( + model="langfuse/langfuse-model", + messages=[{"role": "user", "content": "Hello, how are you?"}], + non_default_params={}, + prompt_id="test-chat-prompt", + prompt_variables={}, + dynamic_callback_params={}, + prompt_version=4, + ) + ) + + mock_get_prompt_from_id.assert_called_once() + print(mock_get_prompt_from_id.call_args.kwargs) + assert mock_get_prompt_from_id.call_args.kwargs["prompt_version"] == 4 diff --git a/tests/test_litellm/integrations/test_custom_prompt_management.py b/tests/test_litellm/integrations/test_custom_prompt_management.py index f5855abf71..f01462070f 100644 --- a/tests/test_litellm/integrations/test_custom_prompt_management.py +++ b/tests/test_litellm/integrations/test_custom_prompt_management.py @@ -33,7 +33,8 @@ class TestCustomPromptManagement(CustomPromptManagement): prompt_id: Optional[str], prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, - prompt_label: Optional[str], + prompt_label: Optional[str] = None, + prompt_version: Optional[int] = None, ) -> Tuple[str, List[AllMessageValues], dict]: print( "TestCustomPromptManagement: running get_chat_completion_prompt for prompt_id: ",