diff --git a/langfuse/client.py b/langfuse/client.py index 72a49ffa..cbcab973 100644 --- a/langfuse/client.py +++ b/langfuse/client.py @@ -342,20 +342,28 @@ def _get_release_value(self, release: Optional[str] = None) -> Optional[str]: else: return get_common_release_envs() + def _get_project_id(self) -> Optional[str]: + """Fetch and return the current project id. Persisted across requests. Returns None if no project id is found for api keys.""" + if not self.project_id: + proj = self.client.projects.get() + if not proj.data or not proj.data[0].id: + return None + + self.project_id = proj.data[0].id + + return self.project_id + def get_trace_id(self) -> str: """Get the current trace id.""" return self.trace_id def get_trace_url(self) -> str: """Get the URL of the current trace to view it in the Langfuse UI.""" - if not self.project_id: - proj = self.client.projects.get() - if not proj.data or not proj.data[0].id: - return f"{self.base_url}/trace/{self.trace_id}" - - self.project_id = proj.data[0].id + project_id = self._get_project_id() + if not project_id: + return f"{self.base_url}/trace/{self.trace_id}" - return f"{self.base_url}/project/{self.project_id}/traces/{self.trace_id}" + return f"{self.base_url}/project/{project_id}/traces/{self.trace_id}" def get_dataset( self, name: str, *, fetch_items_page_size: Optional[int] = 50 diff --git a/langfuse/decorators/langfuse_decorator.py b/langfuse/decorators/langfuse_decorator.py index 137ba84b..b53427f3 100644 --- a/langfuse/decorators/langfuse_decorator.py +++ b/langfuse/decorators/langfuse_decorator.py @@ -713,7 +713,12 @@ def get_current_trace_url(self) -> Optional[str]: if not trace_id: raise ValueError("No trace found in the current context") - return f"{self.client_instance.client._client_wrapper._base_url}/trace/{trace_id}" + project_id = self.client_instance._get_project_id() + + if not project_id: + return f"{self.client_instance.client._client_wrapper._base_url}/trace/{trace_id}" + + return f"{self.client_instance.client._client_wrapper._base_url}/project/{project_id}/traces/{trace_id}" except Exception as e: self._log.error(f"Failed to get current trace URL: {e}") diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 3fca7415..99953ba2 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -1538,6 +1538,13 @@ def faulty_mask_func(data): assert fetched_trace["output"] == "" +def test_get_project_id(): + langfuse = Langfuse(debug=False) + res = langfuse._get_project_id() + assert res is not None + assert res == "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" + + def test_generate_trace_id(): langfuse = Langfuse(debug=False) trace_id = create_uuid() diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 1f08f583..0bd911c0 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -608,6 +608,30 @@ def level_1_function(*args, **kwargs): ) +def test_get_current_trace_url(): + mock_trace_id = create_uuid() + + @observe() + def level_3_function(): + return langfuse_context.get_current_trace_url() + + @observe() + def level_2_function(): + return level_3_function() + + @observe() + def level_1_function(*args, **kwargs): + return level_2_function() + + result = level_1_function( + *mock_args, **mock_kwargs, langfuse_observation_id=mock_trace_id + ) + langfuse_context.flush() + + expected_url = f"http://localhost:3000/project/7a88fb47-b4e2-43b8-a06c-a5ce950dc53a/traces/{mock_trace_id}" + assert result == expected_url + + def test_scoring_observations(): mock_name = "test_scoring_observations" mock_trace_id = create_uuid()