diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index c958219d5..9ec280087 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -117,6 +117,7 @@ MembershipsResponse, MethodNotAllowedError, MetricsResponse, + MetricsV2Response, Model, ModelPrice, ModelUsageUnit, @@ -128,6 +129,8 @@ ObservationLevel, ObservationType, Observations, + ObservationsV2Meta, + ObservationsV2Response, ObservationsView, ObservationsViews, OpenAiCompletionUsageSchema, @@ -234,8 +237,10 @@ llm_connections, media, metrics, + metrics_v_2, models, observations, + observations_v_2, opentelemetry, organizations, projects, @@ -367,6 +372,7 @@ "MembershipsResponse", "MethodNotAllowedError", "MetricsResponse", + "MetricsV2Response", "Model", "ModelPrice", "ModelUsageUnit", @@ -378,6 +384,8 @@ "ObservationLevel", "ObservationType", "Observations", + "ObservationsV2Meta", + "ObservationsV2Response", "ObservationsView", "ObservationsViews", "OpenAiCompletionUsageSchema", @@ -484,8 +492,10 @@ "llm_connections", "media", "metrics", + "metrics_v_2", "models", "observations", + "observations_v_2", "opentelemetry", "organizations", "projects", diff --git a/langfuse/api/client.py b/langfuse/api/client.py index 646279b5a..09674e979 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -28,8 +28,13 @@ ) from .resources.media.client import AsyncMediaClient, MediaClient from .resources.metrics.client import AsyncMetricsClient, MetricsClient +from .resources.metrics_v_2.client import AsyncMetricsV2Client, MetricsV2Client from .resources.models.client import AsyncModelsClient, ModelsClient from .resources.observations.client import AsyncObservationsClient, ObservationsClient +from .resources.observations_v_2.client import ( + AsyncObservationsV2Client, + ObservationsV2Client, +) from .resources.opentelemetry.client import ( AsyncOpentelemetryClient, OpentelemetryClient, @@ -137,8 +142,12 @@ def __init__( self.ingestion = IngestionClient(client_wrapper=self._client_wrapper) self.llm_connections = LlmConnectionsClient(client_wrapper=self._client_wrapper) self.media = MediaClient(client_wrapper=self._client_wrapper) + self.metrics_v_2 = MetricsV2Client(client_wrapper=self._client_wrapper) self.metrics = MetricsClient(client_wrapper=self._client_wrapper) self.models = ModelsClient(client_wrapper=self._client_wrapper) + self.observations_v_2 = ObservationsV2Client( + client_wrapper=self._client_wrapper + ) self.observations = ObservationsClient(client_wrapper=self._client_wrapper) self.opentelemetry = OpentelemetryClient(client_wrapper=self._client_wrapper) self.organizations = OrganizationsClient(client_wrapper=self._client_wrapper) @@ -242,8 +251,12 @@ def __init__( client_wrapper=self._client_wrapper ) self.media = AsyncMediaClient(client_wrapper=self._client_wrapper) + self.metrics_v_2 = AsyncMetricsV2Client(client_wrapper=self._client_wrapper) self.metrics = AsyncMetricsClient(client_wrapper=self._client_wrapper) self.models = AsyncModelsClient(client_wrapper=self._client_wrapper) + self.observations_v_2 = AsyncObservationsV2Client( + client_wrapper=self._client_wrapper + ) self.observations = AsyncObservationsClient(client_wrapper=self._client_wrapper) self.opentelemetry = AsyncOpentelemetryClient( client_wrapper=self._client_wrapper diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 66c008bb7..b84696be4 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -2946,6 +2946,232 @@ client.media.get_upload_url( + + + + +## MetricsV2 +
client.metrics_v_2.metrics(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +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 +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + 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_v_2.metrics( + query="query", +) + +``` +
+
+
+
+ +#### ⚙️ 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. + +
+
+
+
+ +
@@ -3377,6 +3603,318 @@ client.models.delete( + + + + +## ObservationsV2 +
client.observations_v_2.get_many(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +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 +- `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. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + 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_v_2.get_many() + +``` +
+
+
+
+ +#### ⚙️ 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" + +
+
+ +
+
+ +**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]` + +Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. +Defaults to `false` if not provided. + +
+
+ +
+
+ +**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]` + +
+
+ +
+
+ +**level:** `typing.Optional[ObservationLevel]` — Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + +
+
+ +
+
+ +**parent_observation_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**environment:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Optional filter for observations where the environment is one of the provided values. + +
+
+ +
+
+ +**from_start_time:** `typing.Optional[dt.datetime]` — Retrieve only observations with a start_time on or after this datetime (ISO 8601). + +
+
+ +
+
+ +**to_start_time:** `typing.Optional[dt.datetime]` — Retrieve only observations with a start_time before this datetime (ISO 8601). + +
+
+ +
+
+ +**version:** `typing.Optional[str]` — Optional filter to only include observations with a certain version. + +
+
+ +
+
+ +**filter:** `typing.Optional[str]` + +JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + +## Filter Structure +Each filter condition has the following structure: +```json +[ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } +] +``` + +## Available Columns + +### Core Observation Fields +- `id` (string) - Observation ID +- `type` (string) - Observation type (SPAN, GENERATION, EVENT) +- `name` (string) - Observation name +- `traceId` (string) - Associated trace ID +- `startTime` (datetime) - Observation start time +- `endTime` (datetime) - Observation end time +- `environment` (string) - Environment tag +- `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) +- `timeToFirstToken` (number) - Time to first token in seconds +- `tokensPerSecond` (number) - Output tokens per second + +### Token Usage +- `inputTokens` (number) - Number of input tokens +- `outputTokens` (number) - Number of output tokens +- `totalTokens` (number) - Total tokens (alias: `tokens`) + +### Cost Metrics +- `inputCost` (number) - Input cost in USD +- `outputCost` (number) - Output cost in USD +- `totalCost` (number) - Total cost in USD + +### Model Information +- `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. + +## Filter Examples +```json +[ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } +] +``` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index d91362a28..1095119a7 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -13,8 +13,10 @@ llm_connections, media, metrics, + metrics_v_2, models, observations, + observations_v_2, opentelemetry, organizations, projects, @@ -178,8 +180,10 @@ PatchMediaBody, ) from .metrics import MetricsResponse +from .metrics_v_2 import MetricsV2Response from .models import CreateModelRequest, PaginatedModels from .observations import Observations, ObservationsViews +from .observations_v_2 import ObservationsV2Meta, ObservationsV2Response from .opentelemetry import ( OtelAttribute, OtelAttributeValue, @@ -388,6 +392,7 @@ "MembershipsResponse", "MethodNotAllowedError", "MetricsResponse", + "MetricsV2Response", "Model", "ModelPrice", "ModelUsageUnit", @@ -399,6 +404,8 @@ "ObservationLevel", "ObservationType", "Observations", + "ObservationsV2Meta", + "ObservationsV2Response", "ObservationsView", "ObservationsViews", "OpenAiCompletionUsageSchema", @@ -505,8 +512,10 @@ "llm_connections", "media", "metrics", + "metrics_v_2", "models", "observations", + "observations_v_2", "opentelemetry", "organizations", "projects", diff --git a/langfuse/api/resources/metrics_v_2/__init__.py b/langfuse/api/resources/metrics_v_2/__init__.py new file mode 100644 index 000000000..a8c9304a6 --- /dev/null +++ b/langfuse/api/resources/metrics_v_2/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import MetricsV2Response + +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/resources/metrics_v_2/client.py b/langfuse/api/resources/metrics_v_2/client.py new file mode 100644 index 000000000..4628c4d61 --- /dev/null +++ b/langfuse/api/resources/metrics_v_2/client.py @@ -0,0 +1,461 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pydantic_utilities import pydantic_v1 +from ...core.request_options import RequestOptions +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 .types.metrics_v_2_response import MetricsV2Response + + +class MetricsV2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + 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.client import FernLangfuse + + client = FernLangfuse( + 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_v_2.metrics( + query="query", + ) + """ + _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: + return pydantic_v1.parse_obj_as(MetricsV2Response, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncMetricsV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + 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.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + 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_v_2.metrics( + query="query", + ) + + + asyncio.run(main()) + """ + _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: + return pydantic_v1.parse_obj_as(MetricsV2Response, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/metrics_v_2/types/__init__.py b/langfuse/api/resources/metrics_v_2/types/__init__.py new file mode 100644 index 000000000..b77cf3d4d --- /dev/null +++ b/langfuse/api/resources/metrics_v_2/types/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .metrics_v_2_response import MetricsV2Response + +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py b/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py new file mode 100644 index 000000000..ff0a475ea --- /dev/null +++ b/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class MetricsV2Response(pydantic_v1.BaseModel): + data: typing.List[typing.Dict[str, typing.Any]] = pydantic_v1.Field() + """ + The metrics data. Each item in the list contains the metric values and dimensions requested in the query. + Format varies based on the query parameters. + Histograms will return an array with [lower, upper, height] tuples. + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations_v_2/__init__.py b/langfuse/api/resources/observations_v_2/__init__.py new file mode 100644 index 000000000..a04697f31 --- /dev/null +++ b/langfuse/api/resources/observations_v_2/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import ObservationsV2Meta, ObservationsV2Response + +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/resources/observations_v_2/client.py b/langfuse/api/resources/observations_v_2/client.py new file mode 100644 index 000000000..e6796fd46 --- /dev/null +++ b/langfuse/api/resources/observations_v_2/client.py @@ -0,0 +1,558 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.datetime_utils import serialize_datetime +from ...core.pydantic_utilities import pydantic_v1 +from ...core.request_options import RequestOptions +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 .types.observations_v_2_response import ObservationsV2Response + + +class ObservationsV2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_many( + self, + *, + fields: 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, + trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, + parent_observation_id: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + from_start_time: typing.Optional[dt.datetime] = None, + to_start_time: typing.Optional[dt.datetime] = None, + version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsV2Response: + """ + 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 + - `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 + ---------- + 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" + + 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] + Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. + Defaults to `false` if not provided. + + 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] + + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + + parent_observation_id : typing.Optional[str] + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for observations where the environment is one of the provided values. + + from_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time on or after this datetime (ISO 8601). + + to_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time before this datetime (ISO 8601). + + version : typing.Optional[str] + Optional filter to only include observations with a certain version. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `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) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `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. + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsV2Response + + Examples + -------- + from langfuse.client import FernLangfuse + + client = FernLangfuse( + 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_v_2.get_many() + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v2/observations", + method="GET", + params={ + "fields": fields, + "limit": limit, + "cursor": cursor, + "parseIoAsJson": parse_io_as_json, + "name": name, + "userId": user_id, + "type": type, + "traceId": trace_id, + "level": level, + "parentObservationId": parent_observation_id, + "environment": environment, + "fromStartTime": serialize_datetime(from_start_time) + if from_start_time is not None + else None, + "toStartTime": serialize_datetime(to_start_time) + if to_start_time is not None + else None, + "version": version, + "filter": filter, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + ObservationsV2Response, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncObservationsV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_many( + self, + *, + fields: 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, + trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, + parent_observation_id: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + from_start_time: typing.Optional[dt.datetime] = None, + to_start_time: typing.Optional[dt.datetime] = None, + version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsV2Response: + """ + 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 + - `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 + ---------- + 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" + + 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] + Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. + Defaults to `false` if not provided. + + 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] + + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + + parent_observation_id : typing.Optional[str] + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for observations where the environment is one of the provided values. + + from_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time on or after this datetime (ISO 8601). + + to_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time before this datetime (ISO 8601). + + version : typing.Optional[str] + Optional filter to only include observations with a certain version. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `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) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `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. + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsV2Response + + Examples + -------- + import asyncio + + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + 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_v_2.get_many() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v2/observations", + method="GET", + params={ + "fields": fields, + "limit": limit, + "cursor": cursor, + "parseIoAsJson": parse_io_as_json, + "name": name, + "userId": user_id, + "type": type, + "traceId": trace_id, + "level": level, + "parentObservationId": parent_observation_id, + "environment": environment, + "fromStartTime": serialize_datetime(from_start_time) + if from_start_time is not None + else None, + "toStartTime": serialize_datetime(to_start_time) + if to_start_time is not None + else None, + "version": version, + "filter": filter, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + ObservationsV2Response, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/observations_v_2/types/__init__.py b/langfuse/api/resources/observations_v_2/types/__init__.py new file mode 100644 index 000000000..b62504c61 --- /dev/null +++ b/langfuse/api/resources/observations_v_2/types/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .observations_v_2_meta import ObservationsV2Meta +from .observations_v_2_response import ObservationsV2Response + +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py b/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py new file mode 100644 index 000000000..d720db59b --- /dev/null +++ b/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class ObservationsV2Meta(pydantic_v1.BaseModel): + """ + Metadata for cursor-based pagination + """ + + cursor: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Base64-encoded cursor to use for retrieving the next page. If not present, there are no more results. + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py b/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py new file mode 100644 index 000000000..fdea2c3c3 --- /dev/null +++ b/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .observations_v_2_meta import ObservationsV2Meta + + +class ObservationsV2Response(pydantic_v1.BaseModel): + """ + Response containing observations with field-group-based filtering and cursor-based pagination. + + The `data` array contains observation objects with only the requested field groups included. + Use the `cursor` in `meta` to retrieve the next page of results. + """ + + data: typing.List[typing.Dict[str, typing.Any]] = pydantic_v1.Field() + """ + Array of observation objects. Fields included depend on the `fields` parameter in the request. + """ + + meta: ObservationsV2Meta + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime}