feat(langfuse): support langfuse_environment as a per-key dynamic callback param

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
milan 2026-08-25 21:36:34 +00:00
parent f005afa146
commit 902e8dd576
7 changed files with 67 additions and 1 deletions

View file

@ -140,6 +140,7 @@ class LangFuseLogger:
langfuse_public_key=None,
langfuse_secret=None,
langfuse_host=None,
langfuse_environment=None,
flush_interval=1,
allow_env_credentials: bool = True,
):
@ -159,6 +160,7 @@ class LangFuseLogger:
if not (self.langfuse_host.startswith("http://") or self.langfuse_host.startswith("https://")):
# add http:// if unset, assume communicating over private network - e.g. render
self.langfuse_host = "http://" + self.langfuse_host
self.langfuse_environment = langfuse_environment or os.getenv("LANGFUSE_TRACING_ENVIRONMENT")
self.langfuse_release = os.getenv("LANGFUSE_RELEASE")
self.langfuse_debug = os.getenv("LANGFUSE_DEBUG")
self.langfuse_flush_interval = LangFuseLogger._get_langfuse_flush_interval(flush_interval)
@ -179,6 +181,7 @@ class LangFuseLogger:
"debug": self.langfuse_debug,
"flush_interval": self.langfuse_flush_interval, # flush interval in seconds
"httpx_client": self.langfuse_client,
"environment": self.langfuse_environment,
}
self.langfuse_sdk_version: str = langfuse.version.__version__

View file

@ -108,6 +108,7 @@ class LangFuseHandler:
langfuse_public_key=credentials.get("langfuse_public_key"),
langfuse_secret=credentials.get("langfuse_secret") or credentials.get("langfuse_secret_key"),
langfuse_host=credentials.get("langfuse_host"),
langfuse_environment=credentials.get("langfuse_environment"),
allow_env_credentials=credentials.get("langfuse_host") is None,
)
in_memory_dynamic_logger_cache.set_cache(
@ -135,6 +136,7 @@ class LangFuseHandler:
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"),
langfuse_environment=standard_callback_dynamic_params.get("langfuse_environment"),
)
@staticmethod
@ -153,6 +155,7 @@ class LangFuseHandler:
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
or standard_callback_dynamic_params.get("langfuse_environment") is not None
):
return True
return False

View file

@ -51,6 +51,7 @@ _supported_callback_params: Final[tuple[str, ...]] = (
"langfuse_secret",
"langfuse_secret_key",
"langfuse_host",
"langfuse_environment",
"langfuse_prompt_version",
"langsmith_api_key",
"langsmith_project",

View file

@ -1,10 +1,11 @@
from typing_extensions import TypedDict
from typing_extensions import ReadOnly, TypedDict
class LangfuseLoggingConfig(TypedDict):
langfuse_secret: str | None
langfuse_public_key: str | None
langfuse_host: str | None
langfuse_environment: ReadOnly[str | None]
class LangfuseUsageDetails(TypedDict):

View file

@ -3263,6 +3263,7 @@ class StandardCallbackDynamicParams(TypedDict, total=False):
langfuse_secret: str | None
langfuse_secret_key: str | None
langfuse_host: str | None
langfuse_environment: ReadOnly[str | None]
# Langfuse prompt version
langfuse_prompt_version: int | None

View file

@ -1195,6 +1195,49 @@ def _build_langfuse_logger(monkeypatch) -> LangFuseLogger:
)
def test_langfuse_environment_is_passed_to_sdk_client(monkeypatch):
monkeypatch.setenv("LANGFUSE_MOCK", "false")
monkeypatch.delenv("LANGFUSE_TRACING_ENVIRONMENT", raising=False)
monkeypatch.setattr(litellm, "initialized_langfuse_clients", 0)
with patch("langfuse.Langfuse", _RecordingLangfuse):
logger = LangFuseLogger(
langfuse_public_key="pk-env",
langfuse_secret="sk-env",
langfuse_host="https://test.langfuse.com",
langfuse_environment="staging",
)
assert logger.langfuse_environment == "staging"
assert _RecordingLangfuse.last_parameters["environment"] == "staging"
def test_langfuse_environment_falls_back_to_deployment_env_var(monkeypatch):
monkeypatch.setenv("LANGFUSE_MOCK", "false")
monkeypatch.setenv("LANGFUSE_TRACING_ENVIRONMENT", "deployment-wide")
monkeypatch.setattr(litellm, "initialized_langfuse_clients", 0)
with patch("langfuse.Langfuse", _RecordingLangfuse):
logger = LangFuseLogger(
langfuse_public_key="pk-env",
langfuse_secret="sk-env",
langfuse_host="https://test.langfuse.com",
)
assert logger.langfuse_environment == "deployment-wide"
assert _RecordingLangfuse.last_parameters["environment"] == "deployment-wide"
def test_dynamic_langfuse_environment_triggers_dynamic_logger():
from litellm.integrations.langfuse.langfuse_handler import LangFuseHandler
from litellm.types.utils import StandardCallbackDynamicParams
params = StandardCallbackDynamicParams(langfuse_environment="team-a-env")
assert LangFuseHandler._dynamic_langfuse_credentials_are_passed(params) is True
config = LangFuseHandler.get_dynamic_langfuse_logging_config(
standard_callback_dynamic_params=params
)
assert config["langfuse_environment"] == "team-a-env"
def test_langfuse_sdk_client_survives_httpx_cache_eviction(monkeypatch):
import gc
import weakref

View file

@ -77,6 +77,20 @@ def test_resolves_plain_values_from_metadata():
assert params.get("langfuse_host") == "https://test.langfuse.com"
def test_resolves_langfuse_environment_from_metadata():
kwargs = {
"litellm_params": {
"metadata": {
"langfuse_environment": "team-a-env",
}
}
}
params = initialize_standard_callback_dynamic_params(kwargs)
assert params.get("langfuse_environment") == "team-a-env"
def test_litellm_params_metadata_overrides_metadata():
kwargs = {
"metadata": {