From 2bd5eb4dfd65d8ed969e4fc33397aaf30cc4937a Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 6 Mar 2026 14:46:51 +0000 Subject: [PATCH 1/6] feat(api): update API spec from langfuse/langfuse 2eaf041 --- langfuse/api/__init__.py | 65 +-- langfuse/api/client.py | 106 ++-- langfuse/api/legacy/__init__.py | 61 ++ langfuse/api/legacy/client.py | 105 ++++ .../metrics_v1}/__init__.py | 6 +- langfuse/api/legacy/metrics_v1/client.py | 214 +++++++ langfuse/api/legacy/metrics_v1/raw_client.py | 322 +++++++++++ .../metrics_v1}/types/__init__.py | 6 +- .../metrics_v1}/types/metrics_response.py | 2 +- .../observations_v1}/__init__.py | 8 +- .../observations_v1}/client.py | 283 +++++----- .../observations_v1}/raw_client.py | 416 +++++++++----- .../observations_v1}/types/__init__.py | 10 +- .../observations_v1}/types/observations.py | 6 +- .../types/observations_views.py | 6 +- langfuse/api/legacy/raw_client.py | 13 + .../{score => legacy/score_v1}/__init__.py | 0 .../api/{score => legacy/score_v1}/client.py | 34 +- .../{score => legacy/score_v1}/raw_client.py | 32 +- .../score_v1}/types/__init__.py | 0 .../score_v1}/types/create_score_request.py | 10 +- .../score_v1}/types/create_score_response.py | 2 +- langfuse/api/metrics/__init__.py | 6 +- langfuse/api/metrics/client.py | 278 +++++++-- langfuse/api/metrics/raw_client.py | 290 ++++++++-- langfuse/api/metrics/types/__init__.py | 6 +- .../types/metrics_v2response.py | 0 langfuse/api/metrics_v2/client.py | 422 -------------- langfuse/api/metrics_v2/raw_client.py | 530 ------------------ langfuse/api/observations/__init__.py | 8 +- langfuse/api/observations/client.py | 255 +++++---- langfuse/api/observations/raw_client.py | 384 +++++-------- langfuse/api/observations/types/__init__.py | 10 +- .../types/observations_v2meta.py | 0 .../types/observations_v2response.py | 0 langfuse/api/{score_v2 => scores}/__init__.py | 0 langfuse/api/{score_v2 => scores}/client.py | 34 +- .../api/{score_v2 => scores}/raw_client.py | 8 +- .../{score_v2 => scores}/types/__init__.py | 0 .../types/get_scores_response.py | 0 .../types/get_scores_response_data.py | 0 .../types/get_scores_response_data_boolean.py | 0 .../get_scores_response_data_categorical.py | 0 .../get_scores_response_data_correction.py | 0 .../types/get_scores_response_data_numeric.py | 0 .../types/get_scores_response_trace_data.py | 0 46 files changed, 2028 insertions(+), 1910 deletions(-) create mode 100644 langfuse/api/legacy/__init__.py create mode 100644 langfuse/api/legacy/client.py rename langfuse/api/{metrics_v2 => legacy/metrics_v1}/__init__.py (87%) create mode 100644 langfuse/api/legacy/metrics_v1/client.py create mode 100644 langfuse/api/legacy/metrics_v1/raw_client.py rename langfuse/api/{metrics_v2 => legacy/metrics_v1}/types/__init__.py (85%) rename langfuse/api/{metrics => legacy/metrics_v1}/types/metrics_response.py (90%) rename langfuse/api/{observations_v2 => legacy/observations_v1}/__init__.py (83%) rename langfuse/api/{observations_v2 => legacy/observations_v1}/client.py (66%) rename langfuse/api/{observations_v2 => legacy/observations_v1}/raw_client.py (65%) rename langfuse/api/{observations_v2 => legacy/observations_v1}/types/__init__.py (78%) rename langfuse/api/{observations => legacy/observations_v1}/types/observations.py (63%) rename langfuse/api/{observations => legacy/observations_v1}/types/observations_views.py (63%) create mode 100644 langfuse/api/legacy/raw_client.py rename langfuse/api/{score => legacy/score_v1}/__init__.py (100%) rename langfuse/api/{score => legacy/score_v1}/client.py (92%) rename langfuse/api/{score => legacy/score_v1}/raw_client.py (95%) rename langfuse/api/{score => legacy/score_v1}/types/__init__.py (100%) rename langfuse/api/{score => legacy/score_v1}/types/create_score_request.py (89%) rename langfuse/api/{score => legacy/score_v1}/types/create_score_response.py (85%) rename langfuse/api/{metrics_v2 => metrics}/types/metrics_v2response.py (100%) delete mode 100644 langfuse/api/metrics_v2/client.py delete mode 100644 langfuse/api/metrics_v2/raw_client.py rename langfuse/api/{observations_v2 => observations}/types/observations_v2meta.py (100%) rename langfuse/api/{observations_v2 => observations}/types/observations_v2response.py (100%) rename langfuse/api/{score_v2 => scores}/__init__.py (100%) rename langfuse/api/{score_v2 => scores}/client.py (95%) rename langfuse/api/{score_v2 => scores}/raw_client.py (99%) rename langfuse/api/{score_v2 => scores}/types/__init__.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data_boolean.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data_categorical.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data_correction.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data_numeric.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_trace_data.py (100%) diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 8b70c0807..7322f3e71 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -16,22 +16,20 @@ datasets, health, ingestion, + legacy, llm_connections, media, metrics, - metrics_v2, models, observations, - observations_v2, opentelemetry, organizations, projects, prompt_version, prompts, scim, - score, score_configs, - score_v2, + scores, sessions, trace, utils, @@ -194,11 +192,9 @@ MediaContentType, PatchMediaBody, ) - from .metrics import MetricsResponse - from .metrics_v2 import MetricsV2Response + from .metrics import MetricsV2Response from .models import CreateModelRequest, PaginatedModels - from .observations import Observations, ObservationsViews - from .observations_v2 import ObservationsV2Meta, ObservationsV2Response + from .observations import ObservationsV2Meta, ObservationsV2Response from .opentelemetry import ( OtelAttribute, OtelAttributeValue, @@ -271,13 +267,12 @@ ServiceProviderConfig, UserMeta, ) - from .score import CreateScoreRequest, CreateScoreResponse from .score_configs import ( CreateScoreConfigRequest, ScoreConfigs, UpdateScoreConfigRequest, ) - from .score_v2 import ( + from .scores import ( GetScoresResponse, GetScoresResponseData, GetScoresResponseDataBoolean, @@ -348,8 +343,6 @@ "CreateObservationEvent": ".ingestion", "CreatePromptRequest": ".prompts", "CreateScoreConfigRequest": ".score_configs", - "CreateScoreRequest": ".score", - "CreateScoreResponse": ".score", "CreateScoreValue": ".commons", "CreateSpanBody": ".ingestion", "CreateSpanEvent": ".ingestion", @@ -374,17 +367,17 @@ "GetMediaResponse": ".media", "GetMediaUploadUrlRequest": ".media", "GetMediaUploadUrlResponse": ".media", - "GetScoresResponse": ".score_v2", - "GetScoresResponseData": ".score_v2", - "GetScoresResponseDataBoolean": ".score_v2", - "GetScoresResponseDataCategorical": ".score_v2", - "GetScoresResponseDataCorrection": ".score_v2", - "GetScoresResponseDataNumeric": ".score_v2", - "GetScoresResponseData_Boolean": ".score_v2", - "GetScoresResponseData_Categorical": ".score_v2", - "GetScoresResponseData_Correction": ".score_v2", - "GetScoresResponseData_Numeric": ".score_v2", - "GetScoresResponseTraceData": ".score_v2", + "GetScoresResponse": ".scores", + "GetScoresResponseData": ".scores", + "GetScoresResponseDataBoolean": ".scores", + "GetScoresResponseDataCategorical": ".scores", + "GetScoresResponseDataCorrection": ".scores", + "GetScoresResponseDataNumeric": ".scores", + "GetScoresResponseData_Boolean": ".scores", + "GetScoresResponseData_Categorical": ".scores", + "GetScoresResponseData_Correction": ".scores", + "GetScoresResponseData_Numeric": ".scores", + "GetScoresResponseTraceData": ".scores", "HealthResponse": ".health", "IngestionError": ".ingestion", "IngestionEvent": ".ingestion", @@ -412,8 +405,7 @@ "MembershipRole": ".organizations", "MembershipsResponse": ".organizations", "MethodNotAllowedError": ".commons", - "MetricsResponse": ".metrics", - "MetricsV2Response": ".metrics_v2", + "MetricsV2Response": ".metrics", "Model": ".commons", "ModelPrice": ".commons", "ModelUsageUnit": ".commons", @@ -425,11 +417,9 @@ "ObservationLevel": ".commons", "ObservationType": ".ingestion", "ObservationV2": ".commons", - "Observations": ".observations", - "ObservationsV2Meta": ".observations_v2", - "ObservationsV2Response": ".observations_v2", + "ObservationsV2Meta": ".observations", + "ObservationsV2Response": ".observations", "ObservationsView": ".commons", - "ObservationsViews": ".observations", "OpenAiCompletionUsageSchema": ".ingestion", "OpenAiResponseUsageSchema": ".ingestion", "OpenAiUsage": ".ingestion", @@ -535,22 +525,20 @@ "datasets": ".datasets", "health": ".health", "ingestion": ".ingestion", + "legacy": ".legacy", "llm_connections": ".llm_connections", "media": ".media", "metrics": ".metrics", - "metrics_v2": ".metrics_v2", "models": ".models", "observations": ".observations", - "observations_v2": ".observations_v2", "opentelemetry": ".opentelemetry", "organizations": ".organizations", "projects": ".projects", "prompt_version": ".prompt_version", "prompts": ".prompts", "scim": ".scim", - "score": ".score", "score_configs": ".score_configs", - "score_v2": ".score_v2", + "scores": ".scores", "sessions": ".sessions", "trace": ".trace", "utils": ".utils", @@ -640,8 +628,6 @@ def __dir__(): "CreateObservationEvent", "CreatePromptRequest", "CreateScoreConfigRequest", - "CreateScoreRequest", - "CreateScoreResponse", "CreateScoreValue", "CreateSpanBody", "CreateSpanEvent", @@ -704,7 +690,6 @@ def __dir__(): "MembershipRole", "MembershipsResponse", "MethodNotAllowedError", - "MetricsResponse", "MetricsV2Response", "Model", "ModelPrice", @@ -717,11 +702,9 @@ def __dir__(): "ObservationLevel", "ObservationType", "ObservationV2", - "Observations", "ObservationsV2Meta", "ObservationsV2Response", "ObservationsView", - "ObservationsViews", "OpenAiCompletionUsageSchema", "OpenAiResponseUsageSchema", "OpenAiUsage", @@ -827,22 +810,20 @@ def __dir__(): "datasets", "health", "ingestion", + "legacy", "llm_connections", "media", "metrics", - "metrics_v2", "models", "observations", - "observations_v2", "opentelemetry", "organizations", "projects", "prompt_version", "prompts", "scim", - "score", "score_configs", - "score_v2", + "scores", "sessions", "trace", "utils", diff --git a/langfuse/api/client.py b/langfuse/api/client.py index 041f214b9..3f656cdcd 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -25,22 +25,20 @@ from .datasets.client import AsyncDatasetsClient, DatasetsClient from .health.client import AsyncHealthClient, HealthClient from .ingestion.client import AsyncIngestionClient, IngestionClient + from .legacy.client import AsyncLegacyClient, LegacyClient from .llm_connections.client import AsyncLlmConnectionsClient, LlmConnectionsClient from .media.client import AsyncMediaClient, MediaClient from .metrics.client import AsyncMetricsClient, MetricsClient - from .metrics_v2.client import AsyncMetricsV2Client, MetricsV2Client from .models.client import AsyncModelsClient, ModelsClient from .observations.client import AsyncObservationsClient, ObservationsClient - from .observations_v2.client import AsyncObservationsV2Client, ObservationsV2Client from .opentelemetry.client import AsyncOpentelemetryClient, OpentelemetryClient from .organizations.client import AsyncOrganizationsClient, OrganizationsClient from .projects.client import AsyncProjectsClient, ProjectsClient from .prompt_version.client import AsyncPromptVersionClient, PromptVersionClient from .prompts.client import AsyncPromptsClient, PromptsClient from .scim.client import AsyncScimClient, ScimClient - from .score.client import AsyncScoreClient, ScoreClient from .score_configs.client import AsyncScoreConfigsClient, ScoreConfigsClient - from .score_v2.client import AsyncScoreV2Client, ScoreV2Client + from .scores.client import AsyncScoresClient, ScoresClient from .sessions.client import AsyncSessionsClient, SessionsClient from .trace.client import AsyncTraceClient, TraceClient @@ -133,12 +131,11 @@ def __init__( self._datasets: typing.Optional[DatasetsClient] = None self._health: typing.Optional[HealthClient] = None self._ingestion: typing.Optional[IngestionClient] = None + self._legacy: typing.Optional[LegacyClient] = None self._llm_connections: typing.Optional[LlmConnectionsClient] = None self._media: typing.Optional[MediaClient] = None - self._metrics_v2: typing.Optional[MetricsV2Client] = None self._metrics: typing.Optional[MetricsClient] = None self._models: typing.Optional[ModelsClient] = None - self._observations_v2: typing.Optional[ObservationsV2Client] = None self._observations: typing.Optional[ObservationsClient] = None self._opentelemetry: typing.Optional[OpentelemetryClient] = None self._organizations: typing.Optional[OrganizationsClient] = None @@ -147,8 +144,7 @@ def __init__( self._prompts: typing.Optional[PromptsClient] = None self._scim: typing.Optional[ScimClient] = None self._score_configs: typing.Optional[ScoreConfigsClient] = None - self._score_v2: typing.Optional[ScoreV2Client] = None - self._score: typing.Optional[ScoreClient] = None + self._scores: typing.Optional[ScoresClient] = None self._sessions: typing.Optional[SessionsClient] = None self._trace: typing.Optional[TraceClient] = None @@ -224,6 +220,14 @@ def ingestion(self): self._ingestion = IngestionClient(client_wrapper=self._client_wrapper) return self._ingestion + @property + def legacy(self): + if self._legacy is None: + from .legacy.client import LegacyClient # noqa: E402 + + self._legacy = LegacyClient(client_wrapper=self._client_wrapper) + return self._legacy + @property def llm_connections(self): if self._llm_connections is None: @@ -242,14 +246,6 @@ def media(self): self._media = MediaClient(client_wrapper=self._client_wrapper) return self._media - @property - def metrics_v2(self): - if self._metrics_v2 is None: - from .metrics_v2.client import MetricsV2Client # noqa: E402 - - self._metrics_v2 = MetricsV2Client(client_wrapper=self._client_wrapper) - return self._metrics_v2 - @property def metrics(self): if self._metrics is None: @@ -266,16 +262,6 @@ def models(self): self._models = ModelsClient(client_wrapper=self._client_wrapper) return self._models - @property - def observations_v2(self): - if self._observations_v2 is None: - from .observations_v2.client import ObservationsV2Client # noqa: E402 - - self._observations_v2 = ObservationsV2Client( - client_wrapper=self._client_wrapper - ) - return self._observations_v2 - @property def observations(self): if self._observations is None: @@ -349,20 +335,12 @@ def score_configs(self): return self._score_configs @property - def score_v2(self): - if self._score_v2 is None: - from .score_v2.client import ScoreV2Client # noqa: E402 - - self._score_v2 = ScoreV2Client(client_wrapper=self._client_wrapper) - return self._score_v2 - - @property - def score(self): - if self._score is None: - from .score.client import ScoreClient # noqa: E402 + def scores(self): + if self._scores is None: + from .scores.client import ScoresClient # noqa: E402 - self._score = ScoreClient(client_wrapper=self._client_wrapper) - return self._score + self._scores = ScoresClient(client_wrapper=self._client_wrapper) + return self._scores @property def sessions(self): @@ -469,12 +447,11 @@ def __init__( self._datasets: typing.Optional[AsyncDatasetsClient] = None self._health: typing.Optional[AsyncHealthClient] = None self._ingestion: typing.Optional[AsyncIngestionClient] = None + self._legacy: typing.Optional[AsyncLegacyClient] = None self._llm_connections: typing.Optional[AsyncLlmConnectionsClient] = None self._media: typing.Optional[AsyncMediaClient] = None - self._metrics_v2: typing.Optional[AsyncMetricsV2Client] = None self._metrics: typing.Optional[AsyncMetricsClient] = None self._models: typing.Optional[AsyncModelsClient] = None - self._observations_v2: typing.Optional[AsyncObservationsV2Client] = None self._observations: typing.Optional[AsyncObservationsClient] = None self._opentelemetry: typing.Optional[AsyncOpentelemetryClient] = None self._organizations: typing.Optional[AsyncOrganizationsClient] = None @@ -483,8 +460,7 @@ def __init__( self._prompts: typing.Optional[AsyncPromptsClient] = None self._scim: typing.Optional[AsyncScimClient] = None self._score_configs: typing.Optional[AsyncScoreConfigsClient] = None - self._score_v2: typing.Optional[AsyncScoreV2Client] = None - self._score: typing.Optional[AsyncScoreClient] = None + self._scores: typing.Optional[AsyncScoresClient] = None self._sessions: typing.Optional[AsyncSessionsClient] = None self._trace: typing.Optional[AsyncTraceClient] = None @@ -562,6 +538,14 @@ def ingestion(self): self._ingestion = AsyncIngestionClient(client_wrapper=self._client_wrapper) return self._ingestion + @property + def legacy(self): + if self._legacy is None: + from .legacy.client import AsyncLegacyClient # noqa: E402 + + self._legacy = AsyncLegacyClient(client_wrapper=self._client_wrapper) + return self._legacy + @property def llm_connections(self): if self._llm_connections is None: @@ -580,14 +564,6 @@ def media(self): self._media = AsyncMediaClient(client_wrapper=self._client_wrapper) return self._media - @property - def metrics_v2(self): - if self._metrics_v2 is None: - from .metrics_v2.client import AsyncMetricsV2Client # noqa: E402 - - self._metrics_v2 = AsyncMetricsV2Client(client_wrapper=self._client_wrapper) - return self._metrics_v2 - @property def metrics(self): if self._metrics is None: @@ -604,16 +580,6 @@ def models(self): self._models = AsyncModelsClient(client_wrapper=self._client_wrapper) return self._models - @property - def observations_v2(self): - if self._observations_v2 is None: - from .observations_v2.client import AsyncObservationsV2Client # noqa: E402 - - self._observations_v2 = AsyncObservationsV2Client( - client_wrapper=self._client_wrapper - ) - return self._observations_v2 - @property def observations(self): if self._observations is None: @@ -689,20 +655,12 @@ def score_configs(self): return self._score_configs @property - def score_v2(self): - if self._score_v2 is None: - from .score_v2.client import AsyncScoreV2Client # noqa: E402 - - self._score_v2 = AsyncScoreV2Client(client_wrapper=self._client_wrapper) - return self._score_v2 - - @property - def score(self): - if self._score is None: - from .score.client import AsyncScoreClient # noqa: E402 + def scores(self): + if self._scores is None: + from .scores.client import AsyncScoresClient # noqa: E402 - self._score = AsyncScoreClient(client_wrapper=self._client_wrapper) - return self._score + self._scores = AsyncScoresClient(client_wrapper=self._client_wrapper) + return self._scores @property def sessions(self): diff --git a/langfuse/api/legacy/__init__.py b/langfuse/api/legacy/__init__.py new file mode 100644 index 000000000..d91b42c2b --- /dev/null +++ b/langfuse/api/legacy/__init__.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import metrics_v1, observations_v1, score_v1 + from .metrics_v1 import MetricsResponse + from .observations_v1 import Observations, ObservationsViews + from .score_v1 import CreateScoreRequest, CreateScoreResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateScoreRequest": ".score_v1", + "CreateScoreResponse": ".score_v1", + "MetricsResponse": ".metrics_v1", + "Observations": ".observations_v1", + "ObservationsViews": ".observations_v1", + "metrics_v1": ".metrics_v1", + "observations_v1": ".observations_v1", + "score_v1": ".score_v1", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateScoreRequest", + "CreateScoreResponse", + "MetricsResponse", + "Observations", + "ObservationsViews", + "metrics_v1", + "observations_v1", + "score_v1", +] diff --git a/langfuse/api/legacy/client.py b/langfuse/api/legacy/client.py new file mode 100644 index 000000000..3886b9eac --- /dev/null +++ b/langfuse/api/legacy/client.py @@ -0,0 +1,105 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawLegacyClient, RawLegacyClient + +if typing.TYPE_CHECKING: + from .metrics_v1.client import AsyncMetricsV1Client, MetricsV1Client + from .observations_v1.client import AsyncObservationsV1Client, ObservationsV1Client + from .score_v1.client import AsyncScoreV1Client, ScoreV1Client + + +class LegacyClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawLegacyClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._metrics_v1: typing.Optional[MetricsV1Client] = None + self._observations_v1: typing.Optional[ObservationsV1Client] = None + self._score_v1: typing.Optional[ScoreV1Client] = None + + @property + def with_raw_response(self) -> RawLegacyClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawLegacyClient + """ + return self._raw_client + + @property + def metrics_v1(self): + if self._metrics_v1 is None: + from .metrics_v1.client import MetricsV1Client # noqa: E402 + + self._metrics_v1 = MetricsV1Client(client_wrapper=self._client_wrapper) + return self._metrics_v1 + + @property + def observations_v1(self): + if self._observations_v1 is None: + from .observations_v1.client import ObservationsV1Client # noqa: E402 + + self._observations_v1 = ObservationsV1Client( + client_wrapper=self._client_wrapper + ) + return self._observations_v1 + + @property + def score_v1(self): + if self._score_v1 is None: + from .score_v1.client import ScoreV1Client # noqa: E402 + + self._score_v1 = ScoreV1Client(client_wrapper=self._client_wrapper) + return self._score_v1 + + +class AsyncLegacyClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawLegacyClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._metrics_v1: typing.Optional[AsyncMetricsV1Client] = None + self._observations_v1: typing.Optional[AsyncObservationsV1Client] = None + self._score_v1: typing.Optional[AsyncScoreV1Client] = None + + @property + def with_raw_response(self) -> AsyncRawLegacyClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawLegacyClient + """ + return self._raw_client + + @property + def metrics_v1(self): + if self._metrics_v1 is None: + from .metrics_v1.client import AsyncMetricsV1Client # noqa: E402 + + self._metrics_v1 = AsyncMetricsV1Client(client_wrapper=self._client_wrapper) + return self._metrics_v1 + + @property + def observations_v1(self): + if self._observations_v1 is None: + from .observations_v1.client import AsyncObservationsV1Client # noqa: E402 + + self._observations_v1 = AsyncObservationsV1Client( + client_wrapper=self._client_wrapper + ) + return self._observations_v1 + + @property + def score_v1(self): + if self._score_v1 is None: + from .score_v1.client import AsyncScoreV1Client # noqa: E402 + + self._score_v1 = AsyncScoreV1Client(client_wrapper=self._client_wrapper) + return self._score_v1 diff --git a/langfuse/api/metrics_v2/__init__.py b/langfuse/api/legacy/metrics_v1/__init__.py similarity index 87% rename from langfuse/api/metrics_v2/__init__.py rename to langfuse/api/legacy/metrics_v1/__init__.py index 0421785de..fb47bc976 100644 --- a/langfuse/api/metrics_v2/__init__.py +++ b/langfuse/api/legacy/metrics_v1/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import MetricsV2Response -_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".types"} + from .types import MetricsResponse +_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -37,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["MetricsV2Response"] +__all__ = ["MetricsResponse"] diff --git a/langfuse/api/legacy/metrics_v1/client.py b/langfuse/api/legacy/metrics_v1/client.py new file mode 100644 index 000000000..bece982b3 --- /dev/null +++ b/langfuse/api/legacy/metrics_v1/client.py @@ -0,0 +1,214 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawMetricsV1Client, RawMetricsV1Client +from .types.metrics_response import MetricsResponse + + +class MetricsV1Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMetricsV1Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMetricsV1Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMetricsV1Client + """ + return self._raw_client + + def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> MetricsResponse: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MetricsResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.legacy.metrics_v1.metrics( + query="query", + ) + """ + _response = self._raw_client.metrics( + query=query, request_options=request_options + ) + return _response.data + + +class AsyncMetricsV1Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMetricsV1Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMetricsV1Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMetricsV1Client + """ + return self._raw_client + + async def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> MetricsResponse: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MetricsResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.legacy.metrics_v1.metrics( + query="query", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.metrics( + query=query, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/legacy/metrics_v1/raw_client.py b/langfuse/api/legacy/metrics_v1/raw_client.py new file mode 100644 index 000000000..61f03e541 --- /dev/null +++ b/langfuse/api/legacy/metrics_v1/raw_client.py @@ -0,0 +1,322 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...commons.errors.access_denied_error import AccessDeniedError +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import MethodNotAllowedError +from ...commons.errors.not_found_error import NotFoundError +from ...commons.errors.unauthorized_error import UnauthorizedError +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from .types.metrics_response import MetricsResponse + + +class RawMetricsV1Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[MetricsResponse]: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MetricsResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/metrics", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MetricsResponse, + parse_obj_as( + type_=MetricsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawMetricsV1Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[MetricsResponse]: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MetricsResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/metrics", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MetricsResponse, + parse_obj_as( + type_=MetricsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/metrics_v2/types/__init__.py b/langfuse/api/legacy/metrics_v1/types/__init__.py similarity index 85% rename from langfuse/api/metrics_v2/types/__init__.py rename to langfuse/api/legacy/metrics_v1/types/__init__.py index b9510d24f..308847504 100644 --- a/langfuse/api/metrics_v2/types/__init__.py +++ b/langfuse/api/legacy/metrics_v1/types/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .metrics_v2response import MetricsV2Response -_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".metrics_v2response"} + from .metrics_response import MetricsResponse +_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".metrics_response"} def __getattr__(attr_name: str) -> typing.Any: @@ -37,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["MetricsV2Response"] +__all__ = ["MetricsResponse"] diff --git a/langfuse/api/metrics/types/metrics_response.py b/langfuse/api/legacy/metrics_v1/types/metrics_response.py similarity index 90% rename from langfuse/api/metrics/types/metrics_response.py rename to langfuse/api/legacy/metrics_v1/types/metrics_response.py index ffbbfaa59..734c47ffd 100644 --- a/langfuse/api/metrics/types/metrics_response.py +++ b/langfuse/api/legacy/metrics_v1/types/metrics_response.py @@ -3,7 +3,7 @@ import typing import pydantic -from ...core.pydantic_utilities import UniversalBaseModel +from ....core.pydantic_utilities import UniversalBaseModel class MetricsResponse(UniversalBaseModel): diff --git a/langfuse/api/observations_v2/__init__.py b/langfuse/api/legacy/observations_v1/__init__.py similarity index 83% rename from langfuse/api/observations_v2/__init__.py rename to langfuse/api/legacy/observations_v1/__init__.py index 66816e540..22b445984 100644 --- a/langfuse/api/observations_v2/__init__.py +++ b/langfuse/api/legacy/observations_v1/__init__.py @@ -6,10 +6,10 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ObservationsV2Meta, ObservationsV2Response + from .types import Observations, ObservationsViews _dynamic_imports: typing.Dict[str, str] = { - "ObservationsV2Meta": ".types", - "ObservationsV2Response": ".types", + "Observations": ".types", + "ObservationsViews": ".types", } @@ -40,4 +40,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] +__all__ = ["Observations", "ObservationsViews"] diff --git a/langfuse/api/observations_v2/client.py b/langfuse/api/legacy/observations_v1/client.py similarity index 66% rename from langfuse/api/observations_v2/client.py rename to langfuse/api/legacy/observations_v1/client.py index 923a4db4b..40d5df6b8 100644 --- a/langfuse/api/observations_v2/client.py +++ b/langfuse/api/legacy/observations_v1/client.py @@ -3,36 +3,76 @@ import datetime as dt import typing -from ..commons.types.observation_level import ObservationLevel -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.request_options import RequestOptions -from .raw_client import AsyncRawObservationsV2Client, RawObservationsV2Client -from .types.observations_v2response import ObservationsV2Response +from ...commons.types.observation_level import ObservationLevel +from ...commons.types.observations_view import ObservationsView +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawObservationsV1Client, RawObservationsV1Client +from .types.observations_views import ObservationsViews -class ObservationsV2Client: +class ObservationsV1Client: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawObservationsV2Client(client_wrapper=client_wrapper) + self._raw_client = RawObservationsV1Client(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> RawObservationsV2Client: + def with_raw_response(self) -> RawObservationsV1Client: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - RawObservationsV2Client + RawObservationsV1Client """ return self._raw_client + def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsView: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsView + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.legacy.observations_v1.get( + observation_id="observationId", + ) + """ + _response = self._raw_client.get( + observation_id, request_options=request_options + ) + return _response.data + def get_many( self, *, - fields: typing.Optional[str] = None, - expand_metadata: typing.Optional[str] = None, + page: typing.Optional[int] = None, limit: typing.Optional[int] = None, - cursor: typing.Optional[str] = None, - parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -45,64 +85,25 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsV2Response: + ) -> ObservationsViews: """ - Get a list of observations with cursor-based pagination and flexible field selection. - - ## Cursor-based Pagination - This endpoint uses cursor-based pagination for efficient traversal of large datasets. - The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. - - ## Field Selection - Use the `fields` parameter to control which observation fields are returned: - - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type - - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - - `time` - completionStartTime, createdAt, updatedAt - - `io` - input, output - - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost - - `prompt` - promptId, promptName, promptVersion - - `metrics` - latency, timeToFirstToken - - If not specified, `core` and `basic` field groups are returned. - - ## Filters - Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- - fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. - If not specified, `core` and `basic` field groups are returned. - Example: "basic,usage,model" - - expand_metadata : typing.Optional[str] - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" + page : typing.Optional[int] + Page number, starts at 1. limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. - - cursor : typing.Optional[str] - Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - - parse_io_as_json : typing.Optional[bool] - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -163,13 +164,6 @@ def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag - - `userId` (string) - User ID - - `sessionId` (string) - Session ID - - ### Trace-Related Fields - - `traceName` (string) - Name of the parent trace - - `traceTags` (arrayOptions) - Tags from the parent trace - - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -187,13 +181,19 @@ def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name (alias: `providedModelName`) + - `model` (string) - Provided model name - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + ## Filter Examples ```json [ @@ -224,7 +224,7 @@ def get_many( Returns ------- - ObservationsV2Response + ObservationsViews Examples -------- @@ -238,14 +238,11 @@ def get_many( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.observations_v2.get_many() + client.legacy.observations_v1.get_many() """ _response = self._raw_client.get_many( - fields=fields, - expand_metadata=expand_metadata, + page=page, limit=limit, - cursor=cursor, - parse_io_as_json=parse_io_as_json, name=name, user_id=user_id, type=type, @@ -262,29 +259,76 @@ def get_many( return _response.data -class AsyncObservationsV2Client: +class AsyncObservationsV1Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawObservationsV2Client(client_wrapper=client_wrapper) + self._raw_client = AsyncRawObservationsV1Client(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> AsyncRawObservationsV2Client: + def with_raw_response(self) -> AsyncRawObservationsV1Client: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - AsyncRawObservationsV2Client + AsyncRawObservationsV1Client """ return self._raw_client + async def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsView: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsView + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.legacy.observations_v1.get( + observation_id="observationId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + observation_id, request_options=request_options + ) + return _response.data + async def get_many( self, *, - fields: typing.Optional[str] = None, - expand_metadata: typing.Optional[str] = None, + page: typing.Optional[int] = None, limit: typing.Optional[int] = None, - cursor: typing.Optional[str] = None, - parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -297,64 +341,25 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsV2Response: + ) -> ObservationsViews: """ - Get a list of observations with cursor-based pagination and flexible field selection. - - ## Cursor-based Pagination - This endpoint uses cursor-based pagination for efficient traversal of large datasets. - The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. - - ## Field Selection - Use the `fields` parameter to control which observation fields are returned: - - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type - - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - - `time` - completionStartTime, createdAt, updatedAt - - `io` - input, output - - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost - - `prompt` - promptId, promptName, promptVersion - - `metrics` - latency, timeToFirstToken - - If not specified, `core` and `basic` field groups are returned. - - ## Filters - Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- - fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. - If not specified, `core` and `basic` field groups are returned. - Example: "basic,usage,model" - - expand_metadata : typing.Optional[str] - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" + page : typing.Optional[int] + Page number, starts at 1. limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. - - cursor : typing.Optional[str] - Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - - parse_io_as_json : typing.Optional[bool] - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -415,13 +420,6 @@ async def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag - - `userId` (string) - User ID - - `sessionId` (string) - Session ID - - ### Trace-Related Fields - - `traceName` (string) - Name of the parent trace - - `traceTags` (arrayOptions) - Tags from the parent trace - - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -439,13 +437,19 @@ async def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name (alias: `providedModelName`) + - `model` (string) - Provided model name - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + ## Filter Examples ```json [ @@ -476,7 +480,7 @@ async def get_many( Returns ------- - ObservationsV2Response + ObservationsViews Examples -------- @@ -495,17 +499,14 @@ async def get_many( async def main() -> None: - await client.observations_v2.get_many() + await client.legacy.observations_v1.get_many() asyncio.run(main()) """ _response = await self._raw_client.get_many( - fields=fields, - expand_metadata=expand_metadata, + page=page, limit=limit, - cursor=cursor, - parse_io_as_json=parse_io_as_json, name=name, user_id=user_id, type=type, diff --git a/langfuse/api/observations_v2/raw_client.py b/langfuse/api/legacy/observations_v1/raw_client.py similarity index 65% rename from langfuse/api/observations_v2/raw_client.py rename to langfuse/api/legacy/observations_v1/raw_client.py index f65a45502..61ecf409d 100644 --- a/langfuse/api/observations_v2/raw_client.py +++ b/langfuse/api/legacy/observations_v1/raw_client.py @@ -4,33 +4,136 @@ import typing from json.decoder import JSONDecodeError -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.observation_level import ObservationLevel -from ..core.api_error import ApiError -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.datetime_utils import serialize_datetime -from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.pydantic_utilities import parse_obj_as -from ..core.request_options import RequestOptions -from .types.observations_v2response import ObservationsV2Response - - -class RawObservationsV2Client: +from ...commons.errors.access_denied_error import AccessDeniedError +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import MethodNotAllowedError +from ...commons.errors.not_found_error import NotFoundError +from ...commons.errors.unauthorized_error import UnauthorizedError +from ...commons.types.observation_level import ObservationLevel +from ...commons.types.observations_view import ObservationsView +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.datetime_utils import serialize_datetime +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import jsonable_encoder +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from .types.observations_views import ObservationsViews + + +class RawObservationsV1Client: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper + def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ObservationsView]: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ObservationsView] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/observations/{jsonable_encoder(observation_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ObservationsView, + parse_obj_as( + type_=ObservationsView, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + def get_many( self, *, - fields: typing.Optional[str] = None, - expand_metadata: typing.Optional[str] = None, + page: typing.Optional[int] = None, limit: typing.Optional[int] = None, - cursor: typing.Optional[str] = None, - parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -43,64 +146,25 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ObservationsV2Response]: + ) -> HttpResponse[ObservationsViews]: """ - Get a list of observations with cursor-based pagination and flexible field selection. - - ## Cursor-based Pagination - This endpoint uses cursor-based pagination for efficient traversal of large datasets. - The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. - - ## Field Selection - Use the `fields` parameter to control which observation fields are returned: - - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type - - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - - `time` - completionStartTime, createdAt, updatedAt - - `io` - input, output - - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost - - `prompt` - promptId, promptName, promptVersion - - `metrics` - latency, timeToFirstToken - - If not specified, `core` and `basic` field groups are returned. - - ## Filters - Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- - fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. - If not specified, `core` and `basic` field groups are returned. - Example: "basic,usage,model" - - expand_metadata : typing.Optional[str] - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" + page : typing.Optional[int] + Page number, starts at 1. limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. - - cursor : typing.Optional[str] - Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - - parse_io_as_json : typing.Optional[bool] - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -161,13 +225,6 @@ def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag - - `userId` (string) - User ID - - `sessionId` (string) - Session ID - - ### Trace-Related Fields - - `traceName` (string) - Name of the parent trace - - `traceTags` (arrayOptions) - Tags from the parent trace - - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -185,13 +242,19 @@ def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name (alias: `providedModelName`) + - `model` (string) - Provided model name - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + ## Filter Examples ```json [ @@ -222,17 +285,14 @@ def get_many( Returns ------- - HttpResponse[ObservationsV2Response] + HttpResponse[ObservationsViews] """ _response = self._client_wrapper.httpx_client.request( - "api/public/v2/observations", + "api/public/observations", method="GET", params={ - "fields": fields, - "expandMetadata": expand_metadata, + "page": page, "limit": limit, - "cursor": cursor, - "parseIoAsJson": parse_io_as_json, "name": name, "userId": user_id, "type": type, @@ -254,9 +314,9 @@ def get_many( try: if 200 <= _response.status_code < 300: _data = typing.cast( - ObservationsV2Response, + ObservationsViews, parse_obj_as( - type_=ObservationsV2Response, # type: ignore + type_=ObservationsViews, # type: ignore object_=_response.json(), ), ) @@ -330,18 +390,119 @@ def get_many( ) -class AsyncRawObservationsV2Client: +class AsyncRawObservationsV1Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper + async def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ObservationsView]: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ObservationsView] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/observations/{jsonable_encoder(observation_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ObservationsView, + parse_obj_as( + type_=ObservationsView, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + async def get_many( self, *, - fields: typing.Optional[str] = None, - expand_metadata: typing.Optional[str] = None, + page: typing.Optional[int] = None, limit: typing.Optional[int] = None, - cursor: typing.Optional[str] = None, - parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -354,64 +515,25 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ObservationsV2Response]: + ) -> AsyncHttpResponse[ObservationsViews]: """ - Get a list of observations with cursor-based pagination and flexible field selection. - - ## Cursor-based Pagination - This endpoint uses cursor-based pagination for efficient traversal of large datasets. - The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. - - ## Field Selection - Use the `fields` parameter to control which observation fields are returned: - - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type - - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - - `time` - completionStartTime, createdAt, updatedAt - - `io` - input, output - - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost - - `prompt` - promptId, promptName, promptVersion - - `metrics` - latency, timeToFirstToken - - If not specified, `core` and `basic` field groups are returned. - - ## Filters - Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- - fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. - If not specified, `core` and `basic` field groups are returned. - Example: "basic,usage,model" - - expand_metadata : typing.Optional[str] - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" + page : typing.Optional[int] + Page number, starts at 1. limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. - - cursor : typing.Optional[str] - Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - - parse_io_as_json : typing.Optional[bool] - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -472,13 +594,6 @@ async def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag - - `userId` (string) - User ID - - `sessionId` (string) - Session ID - - ### Trace-Related Fields - - `traceName` (string) - Name of the parent trace - - `traceTags` (arrayOptions) - Tags from the parent trace - - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -496,13 +611,19 @@ async def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name (alias: `providedModelName`) + - `model` (string) - Provided model name - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + ## Filter Examples ```json [ @@ -533,17 +654,14 @@ async def get_many( Returns ------- - AsyncHttpResponse[ObservationsV2Response] + AsyncHttpResponse[ObservationsViews] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/observations", + "api/public/observations", method="GET", params={ - "fields": fields, - "expandMetadata": expand_metadata, + "page": page, "limit": limit, - "cursor": cursor, - "parseIoAsJson": parse_io_as_json, "name": name, "userId": user_id, "type": type, @@ -565,9 +683,9 @@ async def get_many( try: if 200 <= _response.status_code < 300: _data = typing.cast( - ObservationsV2Response, + ObservationsViews, parse_obj_as( - type_=ObservationsV2Response, # type: ignore + type_=ObservationsViews, # type: ignore object_=_response.json(), ), ) diff --git a/langfuse/api/observations_v2/types/__init__.py b/langfuse/api/legacy/observations_v1/types/__init__.py similarity index 78% rename from langfuse/api/observations_v2/types/__init__.py rename to langfuse/api/legacy/observations_v1/types/__init__.py index 6e132aba6..247b674a1 100644 --- a/langfuse/api/observations_v2/types/__init__.py +++ b/langfuse/api/legacy/observations_v1/types/__init__.py @@ -6,11 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .observations_v2meta import ObservationsV2Meta - from .observations_v2response import ObservationsV2Response + from .observations import Observations + from .observations_views import ObservationsViews _dynamic_imports: typing.Dict[str, str] = { - "ObservationsV2Meta": ".observations_v2meta", - "ObservationsV2Response": ".observations_v2response", + "Observations": ".observations", + "ObservationsViews": ".observations_views", } @@ -41,4 +41,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] +__all__ = ["Observations", "ObservationsViews"] diff --git a/langfuse/api/observations/types/observations.py b/langfuse/api/legacy/observations_v1/types/observations.py similarity index 63% rename from langfuse/api/observations/types/observations.py rename to langfuse/api/legacy/observations_v1/types/observations.py index 61a47cdc5..860047ef2 100644 --- a/langfuse/api/observations/types/observations.py +++ b/langfuse/api/legacy/observations_v1/types/observations.py @@ -3,9 +3,9 @@ import typing import pydantic -from ...commons.types.observation import Observation -from ...core.pydantic_utilities import UniversalBaseModel -from ...utils.pagination.types.meta_response import MetaResponse +from ....commons.types.observation import Observation +from ....core.pydantic_utilities import UniversalBaseModel +from ....utils.pagination.types.meta_response import MetaResponse class Observations(UniversalBaseModel): diff --git a/langfuse/api/observations/types/observations_views.py b/langfuse/api/legacy/observations_v1/types/observations_views.py similarity index 63% rename from langfuse/api/observations/types/observations_views.py rename to langfuse/api/legacy/observations_v1/types/observations_views.py index ee682ed39..b6d294c7d 100644 --- a/langfuse/api/observations/types/observations_views.py +++ b/langfuse/api/legacy/observations_v1/types/observations_views.py @@ -3,9 +3,9 @@ import typing import pydantic -from ...commons.types.observations_view import ObservationsView -from ...core.pydantic_utilities import UniversalBaseModel -from ...utils.pagination.types.meta_response import MetaResponse +from ....commons.types.observations_view import ObservationsView +from ....core.pydantic_utilities import UniversalBaseModel +from ....utils.pagination.types.meta_response import MetaResponse class ObservationsViews(UniversalBaseModel): diff --git a/langfuse/api/legacy/raw_client.py b/langfuse/api/legacy/raw_client.py new file mode 100644 index 000000000..0672ed01d --- /dev/null +++ b/langfuse/api/legacy/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawLegacyClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawLegacyClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper diff --git a/langfuse/api/score/__init__.py b/langfuse/api/legacy/score_v1/__init__.py similarity index 100% rename from langfuse/api/score/__init__.py rename to langfuse/api/legacy/score_v1/__init__.py diff --git a/langfuse/api/score/client.py b/langfuse/api/legacy/score_v1/client.py similarity index 92% rename from langfuse/api/score/client.py rename to langfuse/api/legacy/score_v1/client.py index 7a2ba1b83..7c7a214e3 100644 --- a/langfuse/api/score/client.py +++ b/langfuse/api/legacy/score_v1/client.py @@ -2,29 +2,29 @@ import typing -from ..commons.types.create_score_value import CreateScoreValue -from ..commons.types.score_data_type import ScoreDataType -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.request_options import RequestOptions -from .raw_client import AsyncRawScoreClient, RawScoreClient +from ...commons.types.create_score_value import CreateScoreValue +from ...commons.types.score_data_type import ScoreDataType +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawScoreV1Client, RawScoreV1Client from .types.create_score_response import CreateScoreResponse # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) -class ScoreClient: +class ScoreV1Client: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawScoreClient(client_wrapper=client_wrapper) + self._raw_client = RawScoreV1Client(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> RawScoreClient: + def with_raw_response(self) -> RawScoreV1Client: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - RawScoreClient + RawScoreV1Client """ return self._raw_client @@ -101,7 +101,7 @@ def create( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.score.create( + client.legacy.score_v1.create( name="name", value=1.1, ) @@ -154,7 +154,7 @@ def delete( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.score.delete( + client.legacy.score_v1.delete( score_id="scoreId", ) """ @@ -162,18 +162,18 @@ def delete( return _response.data -class AsyncScoreClient: +class AsyncScoreV1Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawScoreClient(client_wrapper=client_wrapper) + self._raw_client = AsyncRawScoreV1Client(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> AsyncRawScoreClient: + def with_raw_response(self) -> AsyncRawScoreV1Client: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - AsyncRawScoreClient + AsyncRawScoreV1Client """ return self._raw_client @@ -255,7 +255,7 @@ async def create( async def main() -> None: - await client.score.create( + await client.legacy.score_v1.create( name="name", value=1.1, ) @@ -316,7 +316,7 @@ async def delete( async def main() -> None: - await client.score.delete( + await client.legacy.score_v1.delete( score_id="scoreId", ) diff --git a/langfuse/api/score/raw_client.py b/langfuse/api/legacy/score_v1/raw_client.py similarity index 95% rename from langfuse/api/score/raw_client.py rename to langfuse/api/legacy/score_v1/raw_client.py index 7c20eee08..9bcbe082d 100644 --- a/langfuse/api/score/raw_client.py +++ b/langfuse/api/legacy/score_v1/raw_client.py @@ -3,27 +3,27 @@ import typing from json.decoder import JSONDecodeError -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.create_score_value import CreateScoreValue -from ..commons.types.score_data_type import ScoreDataType -from ..core.api_error import ApiError -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.jsonable_encoder import jsonable_encoder -from ..core.pydantic_utilities import parse_obj_as -from ..core.request_options import RequestOptions -from ..core.serialization import convert_and_respect_annotation_metadata +from ...commons.errors.access_denied_error import AccessDeniedError +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import MethodNotAllowedError +from ...commons.errors.not_found_error import NotFoundError +from ...commons.errors.unauthorized_error import UnauthorizedError +from ...commons.types.create_score_value import CreateScoreValue +from ...commons.types.score_data_type import ScoreDataType +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import jsonable_encoder +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata from .types.create_score_response import CreateScoreResponse # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) -class RawScoreClient: +class RawScoreV1Client: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper @@ -284,7 +284,7 @@ def delete( ) -class AsyncRawScoreClient: +class AsyncRawScoreV1Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper diff --git a/langfuse/api/score/types/__init__.py b/langfuse/api/legacy/score_v1/types/__init__.py similarity index 100% rename from langfuse/api/score/types/__init__.py rename to langfuse/api/legacy/score_v1/types/__init__.py diff --git a/langfuse/api/score/types/create_score_request.py b/langfuse/api/legacy/score_v1/types/create_score_request.py similarity index 89% rename from langfuse/api/score/types/create_score_request.py rename to langfuse/api/legacy/score_v1/types/create_score_request.py index 5491031ca..d54333ac3 100644 --- a/langfuse/api/score/types/create_score_request.py +++ b/langfuse/api/legacy/score_v1/types/create_score_request.py @@ -4,17 +4,17 @@ import pydantic import typing_extensions -from ...commons.types.create_score_value import CreateScoreValue -from ...commons.types.score_data_type import ScoreDataType -from ...core.pydantic_utilities import UniversalBaseModel -from ...core.serialization import FieldMetadata +from ....commons.types.create_score_value import CreateScoreValue +from ....commons.types.score_data_type import ScoreDataType +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata class CreateScoreRequest(UniversalBaseModel): """ Examples -------- - from langfuse.score import CreateScoreRequest + from langfuse.legacy.score_v1 import CreateScoreRequest CreateScoreRequest( name="novelty", diff --git a/langfuse/api/score/types/create_score_response.py b/langfuse/api/legacy/score_v1/types/create_score_response.py similarity index 85% rename from langfuse/api/score/types/create_score_response.py rename to langfuse/api/legacy/score_v1/types/create_score_response.py index 1c20c0f3a..ff1d27c2c 100644 --- a/langfuse/api/score/types/create_score_response.py +++ b/langfuse/api/legacy/score_v1/types/create_score_response.py @@ -3,7 +3,7 @@ import typing import pydantic -from ...core.pydantic_utilities import UniversalBaseModel +from ....core.pydantic_utilities import UniversalBaseModel class CreateScoreResponse(UniversalBaseModel): diff --git a/langfuse/api/metrics/__init__.py b/langfuse/api/metrics/__init__.py index fb47bc976..0421785de 100644 --- a/langfuse/api/metrics/__init__.py +++ b/langfuse/api/metrics/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import MetricsResponse -_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".types"} + from .types import MetricsV2Response +_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -37,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["MetricsResponse"] +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/metrics/client.py b/langfuse/api/metrics/client.py index a7a3fe359..ed584d99b 100644 --- a/langfuse/api/metrics/client.py +++ b/langfuse/api/metrics/client.py @@ -5,7 +5,7 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawMetricsClient, RawMetricsClient -from .types.metrics_response import MetricsResponse +from .types.metrics_v2response import MetricsV2Response class MetricsClient: @@ -25,55 +25,159 @@ def with_raw_response(self) -> RawMetricsClient: def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> MetricsResponse: + ) -> MetricsV2Response: """ - Get metrics from the Langfuse project using a query object. + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + Parameters ---------- query : str JSON string containing the query parameters with the following structure: ```json { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" "dimensions": [ // Optional. Default: [] { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" + "field": string // Field to group by (see available dimensions above) } ], "metrics": [ // Required. At least one metric must be provided { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" } ], "filters": [ // Optional. Default: [] { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) } ], "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" }, "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) "orderBy": [ // Optional. Default: null { - "field": string, // Field to order by + "field": string, // Field to order by (dimension or metric alias) "direction": string // "asc" or "desc" } ], "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 } } ``` @@ -83,7 +187,7 @@ def metrics( Returns ------- - MetricsResponse + MetricsV2Response Examples -------- @@ -124,55 +228,159 @@ def with_raw_response(self) -> AsyncRawMetricsClient: async def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> MetricsResponse: + ) -> MetricsV2Response: """ - Get metrics from the Langfuse project using a query object. + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + Parameters ---------- query : str JSON string containing the query parameters with the following structure: ```json { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" "dimensions": [ // Optional. Default: [] { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" + "field": string // Field to group by (see available dimensions above) } ], "metrics": [ // Required. At least one metric must be provided { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" } ], "filters": [ // Optional. Default: [] { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) } ], "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" }, "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) "orderBy": [ // Optional. Default: null { - "field": string, // Field to order by + "field": string, // Field to order by (dimension or metric alias) "direction": string // "asc" or "desc" } ], "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 } } ``` @@ -182,7 +390,7 @@ async def metrics( Returns ------- - MetricsResponse + MetricsV2Response Examples -------- diff --git a/langfuse/api/metrics/raw_client.py b/langfuse/api/metrics/raw_client.py index 605b095db..69c976bcc 100644 --- a/langfuse/api/metrics/raw_client.py +++ b/langfuse/api/metrics/raw_client.py @@ -13,7 +13,7 @@ from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions -from .types.metrics_response import MetricsResponse +from .types.metrics_v2response import MetricsV2Response class RawMetricsClient: @@ -22,55 +22,159 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[MetricsResponse]: + ) -> HttpResponse[MetricsV2Response]: """ - Get metrics from the Langfuse project using a query object. + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + Parameters ---------- query : str JSON string containing the query parameters with the following structure: ```json { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" "dimensions": [ // Optional. Default: [] { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" + "field": string // Field to group by (see available dimensions above) } ], "metrics": [ // Required. At least one metric must be provided { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" } ], "filters": [ // Optional. Default: [] { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) } ], "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" }, "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) "orderBy": [ // Optional. Default: null { - "field": string, // Field to order by + "field": string, // Field to order by (dimension or metric alias) "direction": string // "asc" or "desc" } ], "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 } } ``` @@ -80,10 +184,10 @@ def metrics( Returns ------- - HttpResponse[MetricsResponse] + HttpResponse[MetricsV2Response] """ _response = self._client_wrapper.httpx_client.request( - "api/public/metrics", + "api/public/v2/metrics", method="GET", params={ "query": query, @@ -93,9 +197,9 @@ def metrics( try: if 200 <= _response.status_code < 300: _data = typing.cast( - MetricsResponse, + MetricsV2Response, parse_obj_as( - type_=MetricsResponse, # type: ignore + type_=MetricsV2Response, # type: ignore object_=_response.json(), ), ) @@ -175,55 +279,159 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[MetricsResponse]: + ) -> AsyncHttpResponse[MetricsV2Response]: """ - Get metrics from the Langfuse project using a query object. + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + Parameters ---------- query : str JSON string containing the query parameters with the following structure: ```json { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" "dimensions": [ // Optional. Default: [] { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" + "field": string // Field to group by (see available dimensions above) } ], "metrics": [ // Required. At least one metric must be provided { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" } ], "filters": [ // Optional. Default: [] { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) } ], "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" }, "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) "orderBy": [ // Optional. Default: null { - "field": string, // Field to order by + "field": string, // Field to order by (dimension or metric alias) "direction": string // "asc" or "desc" } ], "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 } } ``` @@ -233,10 +441,10 @@ async def metrics( Returns ------- - AsyncHttpResponse[MetricsResponse] + AsyncHttpResponse[MetricsV2Response] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/metrics", + "api/public/v2/metrics", method="GET", params={ "query": query, @@ -246,9 +454,9 @@ async def metrics( try: if 200 <= _response.status_code < 300: _data = typing.cast( - MetricsResponse, + MetricsV2Response, parse_obj_as( - type_=MetricsResponse, # type: ignore + type_=MetricsV2Response, # type: ignore object_=_response.json(), ), ) diff --git a/langfuse/api/metrics/types/__init__.py b/langfuse/api/metrics/types/__init__.py index 308847504..b9510d24f 100644 --- a/langfuse/api/metrics/types/__init__.py +++ b/langfuse/api/metrics/types/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .metrics_response import MetricsResponse -_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".metrics_response"} + from .metrics_v2response import MetricsV2Response +_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".metrics_v2response"} def __getattr__(attr_name: str) -> typing.Any: @@ -37,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["MetricsResponse"] +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/metrics_v2/types/metrics_v2response.py b/langfuse/api/metrics/types/metrics_v2response.py similarity index 100% rename from langfuse/api/metrics_v2/types/metrics_v2response.py rename to langfuse/api/metrics/types/metrics_v2response.py diff --git a/langfuse/api/metrics_v2/client.py b/langfuse/api/metrics_v2/client.py deleted file mode 100644 index d6c05914c..000000000 --- a/langfuse/api/metrics_v2/client.py +++ /dev/null @@ -1,422 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.request_options import RequestOptions -from .raw_client import AsyncRawMetricsV2Client, RawMetricsV2Client -from .types.metrics_v2response import MetricsV2Response - - -class MetricsV2Client: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawMetricsV2Client(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> RawMetricsV2Client: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - RawMetricsV2Client - """ - return self._raw_client - - def metrics( - self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> MetricsV2Response: - """ - Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - - ## V2 Differences - - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) - - Direct access to tags and release fields on observations - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view - - High cardinality dimensions are not supported and will return a 400 error (see below) - - For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - ## Available Views - - ### observations - Query observation-level data (spans, generations, events). - - **Dimensions:** - - `environment` - Deployment environment (e.g., production, staging) - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - `name` - Name of the observation - - `level` - Logging level of the observation - - `version` - Version of the observation - - `tags` - User-defined tags - - `release` - Release version - - `traceName` - Name of the parent trace (backwards-compatible) - - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) - - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) - - `providedModelName` - Name of the model used - - `promptName` - Name of the prompt used - - `promptVersion` - Version of the prompt used - - `startTimeMonth` - Month of start_time in YYYY-MM format - - **Measures:** - - `count` - Total number of observations - - `latency` - Observation latency (milliseconds) - - `streamingLatency` - Generation latency from completion start to end (milliseconds) - - `inputTokens` - Sum of input tokens consumed - - `outputTokens` - Sum of output tokens produced - - `totalTokens` - Sum of all tokens consumed - - `outputTokensPerSecond` - Output tokens per second - - `tokensPerSecond` - Total tokens per second - - `inputCost` - Input cost (USD) - - `outputCost` - Output cost (USD) - - `totalCost` - Total cost (USD) - - `timeToFirstToken` - Time to first token (milliseconds) - - `countScores` - Number of scores attached to the observation - - ### scores-numeric - Query numeric and boolean score data. - - **Dimensions:** - - `environment` - Deployment environment - - `name` - Name of the score (e.g., accuracy, toxicity) - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - `dataType` - Data type (NUMERIC, BOOLEAN) - - `configId` - Identifier of the score config - - `timestampMonth` - Month in YYYY-MM format - - `timestampDay` - Day in YYYY-MM-DD format - - `value` - Numeric value of the score - - `traceName` - Name of the parent trace - - `tags` - Tags - - `traceRelease` - Release version - - `traceVersion` - Version - - `observationName` - Name of the associated observation - - `observationModelName` - Model name of the associated observation - - `observationPromptName` - Prompt name of the associated observation - - `observationPromptVersion` - Prompt version of the associated observation - - **Measures:** - - `count` - Total number of scores - - `value` - Score value (for aggregations) - - ### scores-categorical - Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - - **Measures:** - - `count` - Total number of scores - - ## High Cardinality Dimensions - The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. - Use them in filters instead. - - **observations view:** - - `id` - Use traceId filter to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `parentObservationId` - Use parentObservationId filter instead - - **scores-numeric / scores-categorical views:** - - `id` - Use specific filters to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `observationId` - Use observationId filter instead - - ## Aggregations - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - ## Time Granularities - Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` - - `auto` bins the data into approximately 50 buckets based on the time range - - Parameters - ---------- - query : str - JSON string containing the query parameters with the following structure: - ```json - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - ``` - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MetricsV2Response - - Examples - -------- - from langfuse import LangfuseAPI - - client = LangfuseAPI( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.metrics_v2.metrics( - query="query", - ) - """ - _response = self._raw_client.metrics( - query=query, request_options=request_options - ) - return _response.data - - -class AsyncMetricsV2Client: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawMetricsV2Client(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> AsyncRawMetricsV2Client: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - AsyncRawMetricsV2Client - """ - return self._raw_client - - async def metrics( - self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> MetricsV2Response: - """ - Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - - ## V2 Differences - - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) - - Direct access to tags and release fields on observations - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view - - High cardinality dimensions are not supported and will return a 400 error (see below) - - For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - ## Available Views - - ### observations - Query observation-level data (spans, generations, events). - - **Dimensions:** - - `environment` - Deployment environment (e.g., production, staging) - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - `name` - Name of the observation - - `level` - Logging level of the observation - - `version` - Version of the observation - - `tags` - User-defined tags - - `release` - Release version - - `traceName` - Name of the parent trace (backwards-compatible) - - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) - - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) - - `providedModelName` - Name of the model used - - `promptName` - Name of the prompt used - - `promptVersion` - Version of the prompt used - - `startTimeMonth` - Month of start_time in YYYY-MM format - - **Measures:** - - `count` - Total number of observations - - `latency` - Observation latency (milliseconds) - - `streamingLatency` - Generation latency from completion start to end (milliseconds) - - `inputTokens` - Sum of input tokens consumed - - `outputTokens` - Sum of output tokens produced - - `totalTokens` - Sum of all tokens consumed - - `outputTokensPerSecond` - Output tokens per second - - `tokensPerSecond` - Total tokens per second - - `inputCost` - Input cost (USD) - - `outputCost` - Output cost (USD) - - `totalCost` - Total cost (USD) - - `timeToFirstToken` - Time to first token (milliseconds) - - `countScores` - Number of scores attached to the observation - - ### scores-numeric - Query numeric and boolean score data. - - **Dimensions:** - - `environment` - Deployment environment - - `name` - Name of the score (e.g., accuracy, toxicity) - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - `dataType` - Data type (NUMERIC, BOOLEAN) - - `configId` - Identifier of the score config - - `timestampMonth` - Month in YYYY-MM format - - `timestampDay` - Day in YYYY-MM-DD format - - `value` - Numeric value of the score - - `traceName` - Name of the parent trace - - `tags` - Tags - - `traceRelease` - Release version - - `traceVersion` - Version - - `observationName` - Name of the associated observation - - `observationModelName` - Model name of the associated observation - - `observationPromptName` - Prompt name of the associated observation - - `observationPromptVersion` - Prompt version of the associated observation - - **Measures:** - - `count` - Total number of scores - - `value` - Score value (for aggregations) - - ### scores-categorical - Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - - **Measures:** - - `count` - Total number of scores - - ## High Cardinality Dimensions - The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. - Use them in filters instead. - - **observations view:** - - `id` - Use traceId filter to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `parentObservationId` - Use parentObservationId filter instead - - **scores-numeric / scores-categorical views:** - - `id` - Use specific filters to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `observationId` - Use observationId filter instead - - ## Aggregations - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - ## Time Granularities - Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` - - `auto` bins the data into approximately 50 buckets based on the time range - - Parameters - ---------- - query : str - JSON string containing the query parameters with the following structure: - ```json - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - ``` - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MetricsV2Response - - Examples - -------- - import asyncio - - from langfuse import AsyncLangfuseAPI - - client = AsyncLangfuseAPI( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.metrics_v2.metrics( - query="query", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.metrics( - query=query, request_options=request_options - ) - return _response.data diff --git a/langfuse/api/metrics_v2/raw_client.py b/langfuse/api/metrics_v2/raw_client.py deleted file mode 100644 index b79d55713..000000000 --- a/langfuse/api/metrics_v2/raw_client.py +++ /dev/null @@ -1,530 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..core.api_error import ApiError -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.pydantic_utilities import parse_obj_as -from ..core.request_options import RequestOptions -from .types.metrics_v2response import MetricsV2Response - - -class RawMetricsV2Client: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def metrics( - self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[MetricsV2Response]: - """ - Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - - ## V2 Differences - - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) - - Direct access to tags and release fields on observations - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view - - High cardinality dimensions are not supported and will return a 400 error (see below) - - For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - ## Available Views - - ### observations - Query observation-level data (spans, generations, events). - - **Dimensions:** - - `environment` - Deployment environment (e.g., production, staging) - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - `name` - Name of the observation - - `level` - Logging level of the observation - - `version` - Version of the observation - - `tags` - User-defined tags - - `release` - Release version - - `traceName` - Name of the parent trace (backwards-compatible) - - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) - - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) - - `providedModelName` - Name of the model used - - `promptName` - Name of the prompt used - - `promptVersion` - Version of the prompt used - - `startTimeMonth` - Month of start_time in YYYY-MM format - - **Measures:** - - `count` - Total number of observations - - `latency` - Observation latency (milliseconds) - - `streamingLatency` - Generation latency from completion start to end (milliseconds) - - `inputTokens` - Sum of input tokens consumed - - `outputTokens` - Sum of output tokens produced - - `totalTokens` - Sum of all tokens consumed - - `outputTokensPerSecond` - Output tokens per second - - `tokensPerSecond` - Total tokens per second - - `inputCost` - Input cost (USD) - - `outputCost` - Output cost (USD) - - `totalCost` - Total cost (USD) - - `timeToFirstToken` - Time to first token (milliseconds) - - `countScores` - Number of scores attached to the observation - - ### scores-numeric - Query numeric and boolean score data. - - **Dimensions:** - - `environment` - Deployment environment - - `name` - Name of the score (e.g., accuracy, toxicity) - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - `dataType` - Data type (NUMERIC, BOOLEAN) - - `configId` - Identifier of the score config - - `timestampMonth` - Month in YYYY-MM format - - `timestampDay` - Day in YYYY-MM-DD format - - `value` - Numeric value of the score - - `traceName` - Name of the parent trace - - `tags` - Tags - - `traceRelease` - Release version - - `traceVersion` - Version - - `observationName` - Name of the associated observation - - `observationModelName` - Model name of the associated observation - - `observationPromptName` - Prompt name of the associated observation - - `observationPromptVersion` - Prompt version of the associated observation - - **Measures:** - - `count` - Total number of scores - - `value` - Score value (for aggregations) - - ### scores-categorical - Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - - **Measures:** - - `count` - Total number of scores - - ## High Cardinality Dimensions - The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. - Use them in filters instead. - - **observations view:** - - `id` - Use traceId filter to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `parentObservationId` - Use parentObservationId filter instead - - **scores-numeric / scores-categorical views:** - - `id` - Use specific filters to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `observationId` - Use observationId filter instead - - ## Aggregations - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - ## Time Granularities - Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` - - `auto` bins the data into approximately 50 buckets based on the time range - - Parameters - ---------- - query : str - JSON string containing the query parameters with the following structure: - ```json - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - ``` - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[MetricsV2Response] - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/v2/metrics", - method="GET", - params={ - "query": query, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - MetricsV2Response, - parse_obj_as( - type_=MetricsV2Response, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 400: - raise Error( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise AccessDeniedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 405: - raise MethodNotAllowedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response.text, - ) - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response_json, - ) - - -class AsyncRawMetricsV2Client: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def metrics( - self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[MetricsV2Response]: - """ - Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - - ## V2 Differences - - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) - - Direct access to tags and release fields on observations - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view - - High cardinality dimensions are not supported and will return a 400 error (see below) - - For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - ## Available Views - - ### observations - Query observation-level data (spans, generations, events). - - **Dimensions:** - - `environment` - Deployment environment (e.g., production, staging) - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - `name` - Name of the observation - - `level` - Logging level of the observation - - `version` - Version of the observation - - `tags` - User-defined tags - - `release` - Release version - - `traceName` - Name of the parent trace (backwards-compatible) - - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) - - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) - - `providedModelName` - Name of the model used - - `promptName` - Name of the prompt used - - `promptVersion` - Version of the prompt used - - `startTimeMonth` - Month of start_time in YYYY-MM format - - **Measures:** - - `count` - Total number of observations - - `latency` - Observation latency (milliseconds) - - `streamingLatency` - Generation latency from completion start to end (milliseconds) - - `inputTokens` - Sum of input tokens consumed - - `outputTokens` - Sum of output tokens produced - - `totalTokens` - Sum of all tokens consumed - - `outputTokensPerSecond` - Output tokens per second - - `tokensPerSecond` - Total tokens per second - - `inputCost` - Input cost (USD) - - `outputCost` - Output cost (USD) - - `totalCost` - Total cost (USD) - - `timeToFirstToken` - Time to first token (milliseconds) - - `countScores` - Number of scores attached to the observation - - ### scores-numeric - Query numeric and boolean score data. - - **Dimensions:** - - `environment` - Deployment environment - - `name` - Name of the score (e.g., accuracy, toxicity) - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - `dataType` - Data type (NUMERIC, BOOLEAN) - - `configId` - Identifier of the score config - - `timestampMonth` - Month in YYYY-MM format - - `timestampDay` - Day in YYYY-MM-DD format - - `value` - Numeric value of the score - - `traceName` - Name of the parent trace - - `tags` - Tags - - `traceRelease` - Release version - - `traceVersion` - Version - - `observationName` - Name of the associated observation - - `observationModelName` - Model name of the associated observation - - `observationPromptName` - Prompt name of the associated observation - - `observationPromptVersion` - Prompt version of the associated observation - - **Measures:** - - `count` - Total number of scores - - `value` - Score value (for aggregations) - - ### scores-categorical - Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - - **Measures:** - - `count` - Total number of scores - - ## High Cardinality Dimensions - The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. - Use them in filters instead. - - **observations view:** - - `id` - Use traceId filter to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `parentObservationId` - Use parentObservationId filter instead - - **scores-numeric / scores-categorical views:** - - `id` - Use specific filters to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `observationId` - Use observationId filter instead - - ## Aggregations - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - ## Time Granularities - Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` - - `auto` bins the data into approximately 50 buckets based on the time range - - Parameters - ---------- - query : str - JSON string containing the query parameters with the following structure: - ```json - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - ``` - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[MetricsV2Response] - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/metrics", - method="GET", - params={ - "query": query, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - MetricsV2Response, - parse_obj_as( - type_=MetricsV2Response, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 400: - raise Error( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise AccessDeniedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 405: - raise MethodNotAllowedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response.text, - ) - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response_json, - ) diff --git a/langfuse/api/observations/__init__.py b/langfuse/api/observations/__init__.py index 22b445984..66816e540 100644 --- a/langfuse/api/observations/__init__.py +++ b/langfuse/api/observations/__init__.py @@ -6,10 +6,10 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import Observations, ObservationsViews + from .types import ObservationsV2Meta, ObservationsV2Response _dynamic_imports: typing.Dict[str, str] = { - "Observations": ".types", - "ObservationsViews": ".types", + "ObservationsV2Meta": ".types", + "ObservationsV2Response": ".types", } @@ -40,4 +40,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["Observations", "ObservationsViews"] +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/observations/client.py b/langfuse/api/observations/client.py index 6be2a71f4..ce0de0cf2 100644 --- a/langfuse/api/observations/client.py +++ b/langfuse/api/observations/client.py @@ -4,11 +4,10 @@ import typing from ..commons.types.observation_level import ObservationLevel -from ..commons.types.observations_view import ObservationsView from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawObservationsClient, RawObservationsClient -from .types.observations_views import ObservationsViews +from .types.observations_v2response import ObservationsV2Response class ObservationsClient: @@ -26,53 +25,14 @@ def with_raw_response(self) -> RawObservationsClient: """ return self._raw_client - def get( - self, - observation_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsView: - """ - Get a observation - - Parameters - ---------- - observation_id : str - The unique langfuse identifier of an observation, can be an event, span or generation - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ObservationsView - - Examples - -------- - from langfuse import LangfuseAPI - - client = LangfuseAPI( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.observations.get( - observation_id="observationId", - ) - """ - _response = self._raw_client.get( - observation_id, request_options=request_options - ) - return _response.data - def get_many( self, *, - page: typing.Optional[int] = None, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -85,25 +45,64 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsViews: + ) -> ObservationsV2Response: """ - Get a list of observations. - - Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. Parameters ---------- - page : typing.Optional[int] - Page number, starts at 1. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -164,6 +163,13 @@ def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -181,19 +187,13 @@ def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name + - `model` (string) - Provided model name (alias: `providedModelName`) - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Associated Trace Fields (requires join with traces table) - - `userId` (string) - User ID from associated trace - - `traceName` (string) - Name from associated trace - - `traceEnvironment` (string) - Environment from associated trace - - `traceTags` (arrayOptions) - Tags from associated trace - ## Filter Examples ```json [ @@ -224,7 +224,7 @@ def get_many( Returns ------- - ObservationsViews + ObservationsV2Response Examples -------- @@ -241,8 +241,11 @@ def get_many( client.observations.get_many() """ _response = self._raw_client.get_many( - page=page, + fields=fields, + expand_metadata=expand_metadata, limit=limit, + cursor=cursor, + parse_io_as_json=parse_io_as_json, name=name, user_id=user_id, type=type, @@ -274,61 +277,14 @@ def with_raw_response(self) -> AsyncRawObservationsClient: """ return self._raw_client - async def get( - self, - observation_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsView: - """ - Get a observation - - Parameters - ---------- - observation_id : str - The unique langfuse identifier of an observation, can be an event, span or generation - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ObservationsView - - Examples - -------- - import asyncio - - from langfuse import AsyncLangfuseAPI - - client = AsyncLangfuseAPI( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.observations.get( - observation_id="observationId", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.get( - observation_id, request_options=request_options - ) - return _response.data - async def get_many( self, *, - page: typing.Optional[int] = None, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -341,25 +297,64 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsViews: + ) -> ObservationsV2Response: """ - Get a list of observations. - - Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. Parameters ---------- - page : typing.Optional[int] - Page number, starts at 1. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -420,6 +415,13 @@ async def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -437,19 +439,13 @@ async def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name + - `model` (string) - Provided model name (alias: `providedModelName`) - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Associated Trace Fields (requires join with traces table) - - `userId` (string) - User ID from associated trace - - `traceName` (string) - Name from associated trace - - `traceEnvironment` (string) - Environment from associated trace - - `traceTags` (arrayOptions) - Tags from associated trace - ## Filter Examples ```json [ @@ -480,7 +476,7 @@ async def get_many( Returns ------- - ObservationsViews + ObservationsV2Response Examples -------- @@ -505,8 +501,11 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.get_many( - page=page, + fields=fields, + expand_metadata=expand_metadata, limit=limit, + cursor=cursor, + parse_io_as_json=parse_io_as_json, name=name, user_id=user_id, type=type, diff --git a/langfuse/api/observations/raw_client.py b/langfuse/api/observations/raw_client.py index 508f8b082..3ae8eab15 100644 --- a/langfuse/api/observations/raw_client.py +++ b/langfuse/api/observations/raw_client.py @@ -10,130 +10,27 @@ from ..commons.errors.not_found_error import NotFoundError from ..commons.errors.unauthorized_error import UnauthorizedError from ..commons.types.observation_level import ObservationLevel -from ..commons.types.observations_view import ObservationsView from ..core.api_error import ApiError from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.datetime_utils import serialize_datetime from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.jsonable_encoder import jsonable_encoder from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions -from .types.observations_views import ObservationsViews +from .types.observations_v2response import ObservationsV2Response class RawObservationsClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - def get( - self, - observation_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ObservationsView]: - """ - Get a observation - - Parameters - ---------- - observation_id : str - The unique langfuse identifier of an observation, can be an event, span or generation - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[ObservationsView] - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/observations/{jsonable_encoder(observation_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - ObservationsView, - parse_obj_as( - type_=ObservationsView, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 400: - raise Error( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise AccessDeniedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 405: - raise MethodNotAllowedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response.text, - ) - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response_json, - ) - def get_many( self, *, - page: typing.Optional[int] = None, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -146,25 +43,64 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ObservationsViews]: + ) -> HttpResponse[ObservationsV2Response]: """ - Get a list of observations. - - Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. Parameters ---------- - page : typing.Optional[int] - Page number, starts at 1. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -225,6 +161,13 @@ def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -242,19 +185,13 @@ def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name + - `model` (string) - Provided model name (alias: `providedModelName`) - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Associated Trace Fields (requires join with traces table) - - `userId` (string) - User ID from associated trace - - `traceName` (string) - Name from associated trace - - `traceEnvironment` (string) - Environment from associated trace - - `traceTags` (arrayOptions) - Tags from associated trace - ## Filter Examples ```json [ @@ -285,14 +222,17 @@ def get_many( Returns ------- - HttpResponse[ObservationsViews] + HttpResponse[ObservationsV2Response] """ _response = self._client_wrapper.httpx_client.request( - "api/public/observations", + "api/public/v2/observations", method="GET", params={ - "page": page, + "fields": fields, + "expandMetadata": expand_metadata, "limit": limit, + "cursor": cursor, + "parseIoAsJson": parse_io_as_json, "name": name, "userId": user_id, "type": type, @@ -314,9 +254,9 @@ def get_many( try: if 200 <= _response.status_code < 300: _data = typing.cast( - ObservationsViews, + ObservationsV2Response, parse_obj_as( - type_=ObservationsViews, # type: ignore + type_=ObservationsV2Response, # type: ignore object_=_response.json(), ), ) @@ -394,115 +334,14 @@ class AsyncRawObservationsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper - async def get( - self, - observation_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ObservationsView]: - """ - Get a observation - - Parameters - ---------- - observation_id : str - The unique langfuse identifier of an observation, can be an event, span or generation - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[ObservationsView] - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/observations/{jsonable_encoder(observation_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - ObservationsView, - parse_obj_as( - type_=ObservationsView, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 400: - raise Error( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise AccessDeniedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 405: - raise MethodNotAllowedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response.text, - ) - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response_json, - ) - async def get_many( self, *, - page: typing.Optional[int] = None, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -515,25 +354,64 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ObservationsViews]: + ) -> AsyncHttpResponse[ObservationsV2Response]: """ - Get a list of observations. - - Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. Parameters ---------- - page : typing.Optional[int] - Page number, starts at 1. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -594,6 +472,13 @@ async def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -611,19 +496,13 @@ async def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name + - `model` (string) - Provided model name (alias: `providedModelName`) - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Associated Trace Fields (requires join with traces table) - - `userId` (string) - User ID from associated trace - - `traceName` (string) - Name from associated trace - - `traceEnvironment` (string) - Environment from associated trace - - `traceTags` (arrayOptions) - Tags from associated trace - ## Filter Examples ```json [ @@ -654,14 +533,17 @@ async def get_many( Returns ------- - AsyncHttpResponse[ObservationsViews] + AsyncHttpResponse[ObservationsV2Response] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/observations", + "api/public/v2/observations", method="GET", params={ - "page": page, + "fields": fields, + "expandMetadata": expand_metadata, "limit": limit, + "cursor": cursor, + "parseIoAsJson": parse_io_as_json, "name": name, "userId": user_id, "type": type, @@ -683,9 +565,9 @@ async def get_many( try: if 200 <= _response.status_code < 300: _data = typing.cast( - ObservationsViews, + ObservationsV2Response, parse_obj_as( - type_=ObservationsViews, # type: ignore + type_=ObservationsV2Response, # type: ignore object_=_response.json(), ), ) diff --git a/langfuse/api/observations/types/__init__.py b/langfuse/api/observations/types/__init__.py index 247b674a1..6e132aba6 100644 --- a/langfuse/api/observations/types/__init__.py +++ b/langfuse/api/observations/types/__init__.py @@ -6,11 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .observations import Observations - from .observations_views import ObservationsViews + from .observations_v2meta import ObservationsV2Meta + from .observations_v2response import ObservationsV2Response _dynamic_imports: typing.Dict[str, str] = { - "Observations": ".observations", - "ObservationsViews": ".observations_views", + "ObservationsV2Meta": ".observations_v2meta", + "ObservationsV2Response": ".observations_v2response", } @@ -41,4 +41,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["Observations", "ObservationsViews"] +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/observations_v2/types/observations_v2meta.py b/langfuse/api/observations/types/observations_v2meta.py similarity index 100% rename from langfuse/api/observations_v2/types/observations_v2meta.py rename to langfuse/api/observations/types/observations_v2meta.py diff --git a/langfuse/api/observations_v2/types/observations_v2response.py b/langfuse/api/observations/types/observations_v2response.py similarity index 100% rename from langfuse/api/observations_v2/types/observations_v2response.py rename to langfuse/api/observations/types/observations_v2response.py diff --git a/langfuse/api/score_v2/__init__.py b/langfuse/api/scores/__init__.py similarity index 100% rename from langfuse/api/score_v2/__init__.py rename to langfuse/api/scores/__init__.py diff --git a/langfuse/api/score_v2/client.py b/langfuse/api/scores/client.py similarity index 95% rename from langfuse/api/score_v2/client.py rename to langfuse/api/scores/client.py index 4ee5cf372..91db2c416 100644 --- a/langfuse/api/score_v2/client.py +++ b/langfuse/api/scores/client.py @@ -8,26 +8,26 @@ from ..commons.types.score_source import ScoreSource from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions -from .raw_client import AsyncRawScoreV2Client, RawScoreV2Client +from .raw_client import AsyncRawScoresClient, RawScoresClient from .types.get_scores_response import GetScoresResponse -class ScoreV2Client: +class ScoresClient: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawScoreV2Client(client_wrapper=client_wrapper) + self._raw_client = RawScoresClient(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> RawScoreV2Client: + def with_raw_response(self) -> RawScoresClient: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - RawScoreV2Client + RawScoresClient """ return self._raw_client - def get( + def get_many( self, *, page: typing.Optional[int] = None, @@ -140,9 +140,9 @@ def get( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.score_v2.get() + client.scores.get_many() """ - _response = self._raw_client.get( + _response = self._raw_client.get_many( page=page, limit=limit, user_id=user_id, @@ -198,7 +198,7 @@ def get_by_id( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.score_v2.get_by_id( + client.scores.get_by_id( score_id="scoreId", ) """ @@ -208,22 +208,22 @@ def get_by_id( return _response.data -class AsyncScoreV2Client: +class AsyncScoresClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawScoreV2Client(client_wrapper=client_wrapper) + self._raw_client = AsyncRawScoresClient(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> AsyncRawScoreV2Client: + def with_raw_response(self) -> AsyncRawScoresClient: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - AsyncRawScoreV2Client + AsyncRawScoresClient """ return self._raw_client - async def get( + async def get_many( self, *, page: typing.Optional[int] = None, @@ -341,12 +341,12 @@ async def get( async def main() -> None: - await client.score_v2.get() + await client.scores.get_many() asyncio.run(main()) """ - _response = await self._raw_client.get( + _response = await self._raw_client.get_many( page=page, limit=limit, user_id=user_id, @@ -407,7 +407,7 @@ async def get_by_id( async def main() -> None: - await client.score_v2.get_by_id( + await client.scores.get_by_id( score_id="scoreId", ) diff --git a/langfuse/api/score_v2/raw_client.py b/langfuse/api/scores/raw_client.py similarity index 99% rename from langfuse/api/score_v2/raw_client.py rename to langfuse/api/scores/raw_client.py index 2062b5bd9..2dc16e688 100644 --- a/langfuse/api/score_v2/raw_client.py +++ b/langfuse/api/scores/raw_client.py @@ -22,11 +22,11 @@ from .types.get_scores_response import GetScoresResponse -class RawScoreV2Client: +class RawScoresClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - def get( + def get_many( self, *, page: typing.Optional[int] = None, @@ -339,11 +339,11 @@ def get_by_id( ) -class AsyncRawScoreV2Client: +class AsyncRawScoresClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper - async def get( + async def get_many( self, *, page: typing.Optional[int] = None, diff --git a/langfuse/api/score_v2/types/__init__.py b/langfuse/api/scores/types/__init__.py similarity index 100% rename from langfuse/api/score_v2/types/__init__.py rename to langfuse/api/scores/types/__init__.py diff --git a/langfuse/api/score_v2/types/get_scores_response.py b/langfuse/api/scores/types/get_scores_response.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response.py rename to langfuse/api/scores/types/get_scores_response.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data.py b/langfuse/api/scores/types/get_scores_response_data.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data.py rename to langfuse/api/scores/types/get_scores_response_data.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data_boolean.py b/langfuse/api/scores/types/get_scores_response_data_boolean.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data_boolean.py rename to langfuse/api/scores/types/get_scores_response_data_boolean.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data_categorical.py b/langfuse/api/scores/types/get_scores_response_data_categorical.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data_categorical.py rename to langfuse/api/scores/types/get_scores_response_data_categorical.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data_correction.py b/langfuse/api/scores/types/get_scores_response_data_correction.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data_correction.py rename to langfuse/api/scores/types/get_scores_response_data_correction.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data_numeric.py b/langfuse/api/scores/types/get_scores_response_data_numeric.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data_numeric.py rename to langfuse/api/scores/types/get_scores_response_data_numeric.py diff --git a/langfuse/api/score_v2/types/get_scores_response_trace_data.py b/langfuse/api/scores/types/get_scores_response_trace_data.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_trace_data.py rename to langfuse/api/scores/types/get_scores_response_trace_data.py From cd89409b428da91468da4f0be14b3905d7174613 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:14:12 +0100 Subject: [PATCH 2/6] push --- langfuse/batch_evaluation.py | 2 +- tests/test_core_sdk.py | 2 +- tests/test_prompt.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py index 0ed8a7458..d28fd085d 100644 --- a/langfuse/batch_evaluation.py +++ b/langfuse/batch_evaluation.py @@ -1173,7 +1173,7 @@ async def _fetch_batch_with_retry( ) # type: ignore return list(response.data) # type: ignore elif scope == "observations": - response = self.client.api.observations.get_many( + response = self.client.api.legacy.observations_v1.get_many( page=page, limit=limit, filter=filter, diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index adfa003df..5aa293368 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -50,7 +50,7 @@ async def update_generation(i, langfuse: Langfuse): api = get_api() for i in range(100): # Find the observations with the expected name - observations = api.observations.get_many(name=str(i)).data + observations = api.legacy.observations_v1.get_many(name=str(i)).data # Find generation observations (there should be at least one) generation_obs = [obs for obs in observations if obs.type == "GENERATION"] diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 6aabb1a96..3c4c5c013 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -676,7 +676,7 @@ def test_prompt_end_to_end(): generation = trace.observations[0] assert generation.prompt_id is not None - observation = api.observations.get(generation.id) + observation = api.legacy.observations_v1.get(generation.id) assert observation.prompt_id is not None From 54a2e434e7f290f69a30e39f590dba5fc433eaf6 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:59:54 +0100 Subject: [PATCH 3/6] push --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9908e5caa..113733a84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,6 +126,10 @@ jobs: LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 \ LANGFUSE_INGESTION_QUEUE_DELAY_MS=10 \ LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=10 \ + LANGFUSE_EXPERIMENT_INSERT_INTO_EVENTS_TABLE=true \ + QUEUE_CONSUMER_EVENT_PROPAGATION_QUEUE_IS_ENABLED=true \ + LANGFUSE_ENABLE_EVENTS_TABLE_V2_APIS=true \ + LANGFUSE_ENABLE_EVENTS_TABLE_OBSERVATIONS=true \ docker compose up -d echo "::endgroup::" From 65e547357c8abe319dd37478994c32bbf026f011 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:29:19 +0100 Subject: [PATCH 4/6] push --- tests/test_openai.py | 76 ++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/tests/test_openai.py b/tests/test_openai.py index f24bf93cf..8706bae13 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -39,7 +39,7 @@ def test_openai_chat_completion(openai): sleep(1) - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -96,7 +96,7 @@ def test_openai_chat_completion_stream(openai): langfuse.flush() sleep(3) - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -156,7 +156,7 @@ def test_openai_chat_completion_stream_with_next_iteration(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -205,7 +205,7 @@ def test_openai_chat_completion_stream_fail(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -255,7 +255,7 @@ def test_openai_chat_completion_with_langfuse_prompt(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -278,7 +278,7 @@ def test_openai_chat_completion_fail(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -338,7 +338,7 @@ def test_openai_chat_completion_two_calls(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -348,7 +348,7 @@ def test_openai_chat_completion_two_calls(openai): assert generation.data[0].input == [{"content": "1 + 1 = ", "role": "user"}] - generation_2 = get_api().observations.get_many( + generation_2 = get_api().legacy.observations_v1.get_many( name=generation_name_2, type="GENERATION" ) @@ -372,7 +372,7 @@ def test_openai_chat_completion_with_seed(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -399,7 +399,7 @@ def test_openai_completion(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -447,7 +447,7 @@ def test_openai_completion_stream(openai): assert len(content) > 0 - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -496,7 +496,7 @@ def test_openai_completion_fail(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -539,7 +539,7 @@ def test_openai_completion_stream_fail(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -588,7 +588,7 @@ def test_openai_completion_with_langfuse_prompt(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -630,7 +630,7 @@ async def test_async_chat(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -676,7 +676,7 @@ async def test_async_chat_stream(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -734,7 +734,7 @@ async def test_async_chat_stream_with_anext(openai): print(result) - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -796,7 +796,7 @@ class StepByStepAIResponse(BaseModel): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -840,7 +840,7 @@ class StepByStepAIResponse(BaseModel): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -884,7 +884,7 @@ def test_openai_tool_call(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -940,7 +940,7 @@ def test_openai_tool_call_streamed(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1018,7 +1018,7 @@ def test_structured_output_response_format_kwarg(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1087,7 +1087,7 @@ class CalendarEvent(BaseModel): if Version(openai.__version__) >= Version("1.50.0"): # Check the trace and observation properties - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1133,7 +1133,7 @@ async def test_close_async_stream(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1194,7 +1194,7 @@ def test_base_64_image_input(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1245,7 +1245,7 @@ def test_audio_input_and_output(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1285,7 +1285,7 @@ def test_response_api_text_input(openai): ) langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1331,7 +1331,7 @@ def test_response_api_image_input(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1363,7 +1363,7 @@ def test_response_api_web_search(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1400,7 +1400,7 @@ def test_response_api_streaming(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1454,7 +1454,7 @@ def test_response_api_functions(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1486,7 +1486,7 @@ def test_response_api_reasoning(openai): ) langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1518,7 +1518,9 @@ def test_openai_embeddings(openai): langfuse.flush() sleep(1) - embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + embedding = get_api().legacy.observations_v1.get_many( + name=embedding_name, type="EMBEDDING" + ) assert len(embedding.data) != 0 embedding_data = embedding.data[0] @@ -1552,7 +1554,9 @@ def test_openai_embeddings_multiple_inputs(openai): langfuse.flush() sleep(1) - embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + embedding = get_api().legacy.observations_v1.get_many( + name=embedding_name, type="EMBEDDING" + ) assert len(embedding.data) != 0 embedding_data = embedding.data[0] @@ -1583,7 +1587,9 @@ async def test_async_openai_embeddings(openai): langfuse.flush() sleep(1) - embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + embedding = get_api().legacy.observations_v1.get_many( + name=embedding_name, type="EMBEDDING" + ) assert len(embedding.data) != 0 embedding_data = embedding.data[0] From 60a18b51ca63bec43f3c2ad4e71e9431abe38036 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:29:51 +0100 Subject: [PATCH 5/6] push --- tests/test_core_sdk.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 5aa293368..8b2966474 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -1397,7 +1397,7 @@ def test_get_generations(): sleep(3) # Fetch generations using API - generations = get_api().observations.get_many(name=generation_name) + generations = get_api().legacy.observations_v1.get_many(name=generation_name) # Verify fetched generation matches what we created assert len(generations.data) == 1 @@ -1436,7 +1436,9 @@ def test_get_generations_by_user(): sleep(3) # Fetch generations by user ID using the API - generations = get_api().observations.get_many(user_id=user_id, type="GENERATION") + generations = get_api().legacy.observations_v1.get_many( + user_id=user_id, type="GENERATION" + ) # Verify fetched generation matches what we created assert len(generations.data) == 1 @@ -1770,7 +1772,7 @@ def test_get_observations(): sleep(2) # Fetch observations using the API - observations = get_api().observations.get_many(name=name, limit=10) + observations = get_api().legacy.observations_v1.get_many(name=name, limit=10) # Verify fetched observations assert len(observations.data) == 2 @@ -1785,7 +1787,9 @@ def test_get_observations(): assert gen2_id in gen_ids # Test pagination - paginated_response = get_api().observations.get_many(name=name, limit=1, page=2) + paginated_response = get_api().legacy.observations_v1.get_many( + name=name, limit=1, page=2 + ) assert len(paginated_response.data) == 1 assert paginated_response.meta.total_items == 2 # Parent span + 2 generations assert paginated_response.meta.total_pages == 2 @@ -1813,7 +1817,7 @@ def test_get_traces_empty(): def test_get_observations_empty(): # Fetch observations with a filter that should return no results - response = get_api().observations.get_many(name=create_uuid()) + response = get_api().legacy.observations_v1.get_many(name=create_uuid()) assert len(response.data) == 0 assert response.meta.total_items == 0 From 11dfa9c4ff3334d7348bcd0d6e7b90a85061ab1b Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:38:28 +0100 Subject: [PATCH 6/6] push --- tests/test_core_sdk.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 8b2966474..10a6b0d80 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -962,7 +962,7 @@ def test_create_span_and_get_observation(): sleep(2) # Use API to fetch the observation by ID - observation = get_api().observations.get(span_id) + observation = get_api().legacy.observations_v1.get(span_id) # Verify observation properties assert observation.name == "span" @@ -1476,7 +1476,7 @@ def test_kwargs(): sleep(2) # Retrieve and verify - observation = get_api().observations.get(span_id) + observation = get_api().legacy.observations_v1.get(span_id) # Verify kwargs were properly set as attributes assert observation.start_time is not None @@ -1740,7 +1740,7 @@ def test_get_observation(): sleep(2) # Fetch the observation using the API - observation = get_api().observations.get(generation_id) + observation = get_api().legacy.observations_v1.get(generation_id) # Verify observation properties assert observation.id == generation_id @@ -1804,7 +1804,7 @@ def test_get_trace_not_found(): def test_get_observation_not_found(): # Attempt to fetch a non-existent observation using the API with pytest.raises(Exception): - get_api().observations.get(create_uuid()) + get_api().legacy.observations_v1.get(create_uuid()) def test_get_traces_empty():