langfuse

Langfuse Python SDK — observability, evaluation, and prompt management for LLM applications.

Capabilities:

Quickstart:

# env: LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_BASE_URL
from langfuse import get_client

langfuse = get_client()

# Create a span using a context manager
with langfuse.start_as_current_observation(as_type="span", name="process-request") as span:
    # Your processing logic here
    span.update(output="Processing complete")

    # Create a nested generation for an LLM call
    with langfuse.start_as_current_observation(as_type="generation", name="llm-response", model="gpt-3.5-turbo") as generation:
        # Your LLM call logic here
        generation.update(output="Generated response")

# All spans are automatically closed when exiting their context blocks

# Flush events in short-lived applications
langfuse.flush()

Configuration is via constructor args or environment variables: LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_BASE_URL (defaults to https://cloud.langfuse.com). See langfuse._client.environment_variables for the full list.

Docs: https://langfuse.com/docs — machine-readable index: https://langfuse.com/llms.txt

  1"""Langfuse Python SDK — observability, evaluation, and prompt management for LLM applications.
  2
  3Capabilities:
  4
  5- **Tracing / observability**: `@observe` decorator, `Langfuse.start_observation` /
  6  `start_as_current_observation` context managers, OpenTelemetry-based; integrations
  7  for OpenAI (`langfuse.openai`) and LangChain (`langfuse.langchain.CallbackHandler`).
  8- **Trace attributes**: `propagate_attributes` (top-level function) sets user_id,
  9  session_id, tags, and metadata on all spans in a context.
 10- **Datasets & experiments**: `Langfuse.get_dataset`, `Langfuse.run_experiment` for
 11  offline evaluation and regression testing of prompt/model changes (CI support via
 12  https://github.com/langfuse/experiment-action and `RegressionError`).
 13- **Evaluation / LLM-as-a-judge**: `Evaluation` results from custom or model-based
 14  evaluators; scores via `Langfuse.create_score` / `span.score`.
 15- **Prompt management**: `Langfuse.get_prompt`, `Langfuse.create_prompt` with
 16  client-side caching and version/label control.
 17- **Full REST API**: `Langfuse.api` (sync) / `Langfuse.async_api` (async) clients.
 18
 19Quickstart:
 20
 21```python
 22# env: LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_BASE_URL
 23from langfuse import get_client
 24
 25langfuse = get_client()
 26
 27# Create a span using a context manager
 28with langfuse.start_as_current_observation(as_type="span", name="process-request") as span:
 29    # Your processing logic here
 30    span.update(output="Processing complete")
 31
 32    # Create a nested generation for an LLM call
 33    with langfuse.start_as_current_observation(as_type="generation", name="llm-response", model="gpt-3.5-turbo") as generation:
 34        # Your LLM call logic here
 35        generation.update(output="Generated response")
 36
 37# All spans are automatically closed when exiting their context blocks
 38
 39# Flush events in short-lived applications
 40langfuse.flush()
 41```
 42
 43Configuration is via constructor args or environment variables: `LANGFUSE_PUBLIC_KEY`,
 44`LANGFUSE_SECRET_KEY`, `LANGFUSE_BASE_URL` (defaults to https://cloud.langfuse.com). See `langfuse._client.environment_variables`
 45for the full list.
 46
 47Docs: https://langfuse.com/docs — machine-readable index: https://langfuse.com/llms.txt
 48
 49.. include:: ../README.md
 50"""
 51
 52from langfuse.batch_evaluation import (
 53    BatchEvaluationResult,
 54    BatchEvaluationResumeToken,
 55    CompositeEvaluatorFunction,
 56    EvaluatorInputs,
 57    EvaluatorStats,
 58    MapperFunction,
 59)
 60from langfuse.experiment import Evaluation, RegressionError, RunnerContext
 61
 62from ._client import client as _client_module
 63from ._client.attributes import LangfuseOtelSpanAttributes
 64from ._client.constants import ObservationTypeLiteral
 65from ._client.get_client import get_client
 66from ._client.observe import observe
 67from ._client.propagation import propagate_attributes
 68from ._client.span import (
 69    LangfuseAgent,
 70    LangfuseChain,
 71    LangfuseEmbedding,
 72    LangfuseEvaluator,
 73    LangfuseEvent,
 74    LangfuseGeneration,
 75    LangfuseGuardrail,
 76    LangfuseRetriever,
 77    LangfuseSpan,
 78    LangfuseTool,
 79)
 80from ._version import __version__
 81from .media import LangfuseMedia, LangfuseMediaReference
 82from .span_filter import (
 83    KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES,
 84    is_default_export_span,
 85    is_genai_span,
 86    is_known_llm_instrumentor,
 87    is_langfuse_span,
 88)
 89from .types import (
 90    MaskOtelSpansFunction,
 91    MaskOtelSpansParams,
 92    MaskOtelSpansResult,
 93    OtelSpanData,
 94    OtelSpanIdentifier,
 95    OtelSpanPatch,
 96)
 97
 98Langfuse = _client_module.Langfuse
 99
100__all__ = [
101    "Langfuse",
102    "LangfuseMedia",
103    "LangfuseMediaReference",
104    "get_client",
105    "observe",
106    "propagate_attributes",
107    "ObservationTypeLiteral",
108    "LangfuseSpan",
109    "LangfuseGeneration",
110    "LangfuseEvent",
111    "LangfuseOtelSpanAttributes",
112    "LangfuseAgent",
113    "LangfuseTool",
114    "LangfuseChain",
115    "LangfuseEmbedding",
116    "LangfuseEvaluator",
117    "LangfuseRetriever",
118    "LangfuseGuardrail",
119    "Evaluation",
120    "EvaluatorInputs",
121    "MapperFunction",
122    "CompositeEvaluatorFunction",
123    "EvaluatorStats",
124    "BatchEvaluationResumeToken",
125    "BatchEvaluationResult",
126    "RunnerContext",
127    "RegressionError",
128    "__version__",
129    "is_default_export_span",
130    "is_langfuse_span",
131    "is_genai_span",
132    "is_known_llm_instrumentor",
133    "KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES",
134    "MaskOtelSpansFunction",
135    "MaskOtelSpansParams",
136    "MaskOtelSpansResult",
137    "OtelSpanData",
138    "OtelSpanIdentifier",
139    "OtelSpanPatch",
140    "experiment",
141    "api",
142]
class Langfuse:
 180class Langfuse:
 181    """Main client for Langfuse tracing and platform features.
 182
 183    This class provides an interface for creating and managing traces, spans,
 184    and generations in Langfuse as well as interacting with the Langfuse API.
 185
 186    The client features a thread-safe singleton pattern for each unique public API key,
 187    ensuring consistent trace context propagation across your application. It implements
 188    efficient batching of spans with configurable flush settings and includes background
 189    thread management for media uploads and score ingestion.
 190
 191    Configuration is flexible through either direct parameters or environment variables,
 192    with graceful fallbacks and runtime configuration updates.
 193
 194    Attributes:
 195        api: Synchronous API client for Langfuse backend communication
 196        async_api: Asynchronous API client for Langfuse backend communication
 197        _otel_tracer: Internal LangfuseTracer instance managing OpenTelemetry components
 198
 199    Parameters:
 200        public_key (Optional[str]): Your Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable.
 201        secret_key (Optional[str]): Your Langfuse secret API key. Can also be set via LANGFUSE_SECRET_KEY environment variable.
 202        base_url (Optional[str]): The Langfuse API base URL. Defaults to "https://cloud.langfuse.com". Can also be set via LANGFUSE_BASE_URL environment variable.
 203        host (Optional[str]): Deprecated. Use base_url instead. The Langfuse API host URL. Defaults to "https://cloud.langfuse.com".
 204        timeout (Optional[int]): Timeout in seconds for API requests. Defaults to 5 seconds.
 205        httpx_client (Optional[httpx.Client]): Custom httpx client for making non-tracing HTTP requests. If not provided, a default client will be created.
 206            **Fork safety**: ``httpx.Client`` is thread-safe but not process-safe. When using
 207            ``fork()``-based servers (e.g. Gunicorn with ``--preload``), the SDK automatically
 208            recreates its internally-managed HTTP client in child processes after fork. A custom
 209            ``httpx_client`` is intentionally left as-is (the fork-inherited copy is reused), so
 210            you retain the opportunity to handle process-safety yourself — for example by
 211            registering your own ``os.register_at_fork(after_in_child=...)`` handler to close and
 212            reopen connections on the custom client.
 213        debug (bool): Enable debug logging. Defaults to False. Can also be set via LANGFUSE_DEBUG environment variable.
 214        tracing_enabled (Optional[bool]): Enable or disable tracing. Defaults to True. Can also be set via LANGFUSE_TRACING_ENABLED environment variable.
 215        flush_at (Optional[int]): Number of spans to batch before sending to the API. Defaults to 512. Can also be set via LANGFUSE_FLUSH_AT environment variable.
 216        flush_interval (Optional[float]): Time in seconds between batch flushes. Defaults to 5 seconds. Can also be set via LANGFUSE_FLUSH_INTERVAL environment variable.
 217        environment (Optional[str]): Environment name for tracing. Default is 'default'. Can also be set via LANGFUSE_TRACING_ENVIRONMENT environment variable. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'.
 218        release (Optional[str]): Release version/hash of your application. Used for grouping analytics by release.
 219        media_upload_thread_count (Optional[int]): Number of background threads for handling media uploads. Defaults to 1. Can also be set via LANGFUSE_MEDIA_UPLOAD_THREAD_COUNT environment variable.
 220        sample_rate (Optional[float]): Sampling rate for traces (0.0 to 1.0). Defaults to 1.0 (100% of traces are sampled). Can also be set via LANGFUSE_SAMPLE_RATE environment variable.
 221        mask (Optional[MaskFunction]): Function to mask sensitive data synchronously when Langfuse SDK attributes are created. This applies only to data set through Langfuse SDK APIs such as `start_observation()`, `update()`, and `set_trace_io()`.
 222        mask_otel_spans (Optional[MaskOtelSpansFunction]): Synchronous export-stage hook for masking raw OpenTelemetry span attributes before this Langfuse client sends them to Langfuse. Use this for spans created by third-party OpenTelemetry instrumentations, or when you need to inspect final span attributes after export filtering and Langfuse media handling. It does not modify spans already exported through other OpenTelemetry exporters.
 223
 224            The hook receives one OpenTelemetry export batch. A batch is not guaranteed to contain a complete trace, request, or Langfuse observation tree. The hook usually runs on the OpenTelemetry batch span processor worker thread; during `flush()` and shutdown it may run on the caller thread. Keep it synchronous, deterministic, and fast.
 225
 226            Return `None` to leave the batch unchanged. Return `MaskOtelSpansResult` with `OtelSpanPatch` values to delete or replace attributes on selected spans. If a batch contains duplicate trace and span identifiers, Langfuse keeps only the last matching span. If the hook raises or returns an invalid batch result, Langfuse drops the whole export batch. If one returned span patch is invalid, Langfuse drops only that span from the Langfuse export.
 227
 228            Example:
 229                ```python
 230                from typing import Optional
 231
 232                from langfuse import Langfuse
 233                from langfuse.types import (
 234                    MaskOtelSpansParams,
 235                    MaskOtelSpansResult,
 236                    OtelSpanPatch,
 237                )
 238
 239                def mask_otel_spans(
 240                    *, params: MaskOtelSpansParams
 241                ) -> Optional[MaskOtelSpansResult]:
 242                    patches = {}
 243
 244                    for identifier, span in params.spans.items():
 245                        if "gen_ai.prompt.0.content" in span.attributes:
 246                            patches[identifier] = OtelSpanPatch(
 247                                delete_attributes=("gen_ai.prompt.0.content",),
 248                                set_attributes={"masking.applied": True},
 249                            )
 250
 251                    return MaskOtelSpansResult(span_patches=patches)
 252
 253                langfuse = Langfuse(mask_otel_spans=mask_otel_spans)
 254                ```
 255        blocked_instrumentation_scopes (Optional[List[str]]): Deprecated. Use `should_export_span` instead. Equivalent behavior:
 256            ```python
 257            from langfuse.span_filter import is_default_export_span
 258            blocked = {"sqlite", "requests"}
 259
 260            should_export_span = lambda span: (
 261                is_default_export_span(span)
 262                and (
 263                    span.instrumentation_scope is None
 264                    or span.instrumentation_scope.name not in blocked
 265                )
 266            )
 267            ```
 268        should_export_span (Optional[Callable[[ReadableSpan], bool]]): Callback to decide whether to export a span. If omitted, Langfuse uses the default filter (Langfuse SDK spans, spans with `gen_ai.*` attributes, and known LLM instrumentation scopes).
 269        additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and in the default OTLPSpanExporter requests. These headers will be merged with default headers. Note: If httpx_client is provided, additional_headers must be set directly on your custom httpx_client as well. If `span_exporter` is provided, these headers are not wired into that exporter and must be configured on the exporter instance directly.
 270        tracer_provider(Optional[TracerProvider]): OpenTelemetry TracerProvider to use for Langfuse. This can be useful to set to have disconnected tracing between Langfuse and other OpenTelemetry-span emitting libraries. Note: To track active spans, the context is still shared between TracerProviders. This may lead to broken trace trees.
 271        id_generator (Optional[IdGenerator]): OpenTelemetry ID generator to use when Langfuse creates its own TracerProvider. If omitted, the OpenTelemetry SDK default is used. If `tracer_provider` is provided, or an OpenTelemetry TracerProvider is already registered globally, configure the ID generator on that provider instead.
 272        span_exporter (Optional[SpanExporter]): Custom OpenTelemetry span exporter for the Langfuse span processor. If omitted, Langfuse creates an OTLPSpanExporter pointed at the Langfuse OTLP endpoint. If provided, Langfuse does not wire `base_url`, exporter headers, exporter auth, or exporter timeout into it. Configure endpoint, headers, and timeout on the exporter instance directly. If you are sending spans to Langfuse v4 or using Langfuse Cloud Fast Preview, include `x-langfuse-ingestion-version=4` on the exporter to enable real time processing of exported spans.
 273
 274    Example:
 275        ```python
 276        from langfuse import Langfuse
 277
 278        # Initialize the client (reads from env vars if not provided)
 279        langfuse = Langfuse(
 280            public_key="your-public-key",
 281            secret_key="your-secret-key",
 282            base_url="https://cloud.langfuse.com",  # Optional, default shown
 283        )
 284
 285        # Create a trace span
 286        with langfuse.start_as_current_observation(name="process-query") as span:
 287            # Your application code here
 288
 289            # Create a nested generation span for an LLM call
 290            with span.start_as_current_generation(
 291                name="generate-response",
 292                model="gpt-4",
 293                input={"query": "Tell me about AI"},
 294                model_parameters={"temperature": 0.7, "max_tokens": 500}
 295            ) as generation:
 296                # Generate response here
 297                response = "AI is a field of computer science..."
 298
 299                generation.update(
 300                    output=response,
 301                    usage_details={"prompt_tokens": 10, "completion_tokens": 50},
 302                    cost_details={"total_cost": 0.0023}
 303                )
 304
 305                # Score the generation (supports NUMERIC, BOOLEAN, CATEGORICAL)
 306                generation.score(name="relevance", value=0.95, data_type="NUMERIC")
 307        ```
 308    """
 309
 310    _resources: Optional[LangfuseResourceManager] = None
 311    _mask: Optional[MaskFunction] = None
 312    _otel_tracer: otel_trace_api.Tracer
 313
 314    def __init__(
 315        self,
 316        *,
 317        public_key: Optional[str] = None,
 318        secret_key: Optional[str] = None,
 319        base_url: Optional[str] = None,
 320        host: Optional[str] = None,
 321        timeout: Optional[int] = None,
 322        httpx_client: Optional[httpx.Client] = None,
 323        debug: bool = False,
 324        tracing_enabled: Optional[bool] = True,
 325        flush_at: Optional[int] = None,
 326        flush_interval: Optional[float] = None,
 327        environment: Optional[str] = None,
 328        release: Optional[str] = None,
 329        media_upload_thread_count: Optional[int] = None,
 330        sample_rate: Optional[float] = None,
 331        mask: Optional[MaskFunction] = None,
 332        mask_otel_spans: Optional[MaskOtelSpansFunction] = None,
 333        blocked_instrumentation_scopes: Optional[List[str]] = None,
 334        should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
 335        additional_headers: Optional[Dict[str, str]] = None,
 336        tracer_provider: Optional[TracerProvider] = None,
 337        id_generator: Optional[IdGenerator] = None,
 338        span_exporter: Optional[SpanExporter] = None,
 339    ):
 340        self._base_url = (
 341            base_url
 342            or os.environ.get(LANGFUSE_BASE_URL)
 343            or host
 344            or os.environ.get(LANGFUSE_HOST, "https://cloud.langfuse.com")
 345        )
 346        self._environment = environment or cast(
 347            str, os.environ.get(LANGFUSE_TRACING_ENVIRONMENT)
 348        )
 349        self._release = (
 350            release
 351            or os.environ.get(LANGFUSE_RELEASE, None)
 352            or get_common_release_envs()
 353        )
 354        self._project_id: Optional[str] = None
 355        if sample_rate is None:
 356            sample_rate = float(os.environ.get(LANGFUSE_SAMPLE_RATE, 1.0))
 357        if not 0.0 <= sample_rate <= 1.0:
 358            raise ValueError(
 359                f"Sample rate must be between 0.0 and 1.0, got {sample_rate}"
 360            )
 361
 362        timeout = timeout or int(os.environ.get(LANGFUSE_TIMEOUT, 5))
 363
 364        self._tracing_enabled = (
 365            tracing_enabled
 366            and os.environ.get(LANGFUSE_TRACING_ENABLED, "true").lower() != "false"
 367        )
 368        if not self._tracing_enabled:
 369            langfuse_logger.info(
 370                "Configuration: Langfuse tracing is explicitly disabled. No data will be sent to the Langfuse API."
 371            )
 372
 373        debug = (
 374            debug if debug else (os.getenv(LANGFUSE_DEBUG, "false").lower() == "true")
 375        )
 376        if debug:
 377            logging.basicConfig(
 378                format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
 379            )
 380            langfuse_logger.setLevel(logging.DEBUG)
 381
 382        public_key = public_key or os.environ.get(LANGFUSE_PUBLIC_KEY)
 383        if public_key is None:
 384            langfuse_logger.warning(
 385                "Authentication error: Langfuse client initialized without public_key. Client will be disabled. "
 386                "Provide a public_key parameter or set LANGFUSE_PUBLIC_KEY environment variable. "
 387            )
 388            self._otel_tracer = otel_trace_api.NoOpTracer()
 389            return
 390
 391        secret_key = secret_key or os.environ.get(LANGFUSE_SECRET_KEY)
 392        if secret_key is None:
 393            langfuse_logger.warning(
 394                "Authentication error: Langfuse client initialized without secret_key. Client will be disabled. "
 395                "Provide a secret_key parameter or set LANGFUSE_SECRET_KEY environment variable. "
 396            )
 397            self._otel_tracer = otel_trace_api.NoOpTracer()
 398            return
 399
 400        if os.environ.get("OTEL_SDK_DISABLED", "false").lower() == "true":
 401            langfuse_logger.warning(
 402                "OTEL_SDK_DISABLED is set. Langfuse tracing will be disabled and no traces will appear in the UI."
 403            )
 404
 405        if blocked_instrumentation_scopes is not None:
 406            warnings.warn(
 407                "`blocked_instrumentation_scopes` is deprecated and will be removed in a future release. "
 408                "Use `should_export_span` instead. Example: "
 409                "from langfuse.span_filter import is_default_export_span; "
 410                'blocked={"scope"}; should_export_span=lambda span: '
 411                "is_default_export_span(span) and (span.instrumentation_scope is None or "
 412                "span.instrumentation_scope.name not in blocked).",
 413                DeprecationWarning,
 414                stacklevel=2,
 415            )
 416
 417        # Initialize api and tracer if requirements are met
 418        self._resources = LangfuseResourceManager(
 419            public_key=public_key,
 420            secret_key=secret_key,
 421            base_url=self._base_url,
 422            timeout=timeout,
 423            environment=self._environment,
 424            release=release,
 425            flush_at=flush_at,
 426            flush_interval=flush_interval,
 427            httpx_client=httpx_client,
 428            media_upload_thread_count=media_upload_thread_count,
 429            sample_rate=sample_rate,
 430            mask=mask,
 431            mask_otel_spans=mask_otel_spans,
 432            tracing_enabled=self._tracing_enabled,
 433            blocked_instrumentation_scopes=blocked_instrumentation_scopes,
 434            should_export_span=should_export_span,
 435            additional_headers=additional_headers,
 436            tracer_provider=tracer_provider,
 437            id_generator=id_generator,
 438            span_exporter=span_exporter,
 439        )
 440        self._mask = self._resources.mask
 441
 442        self._otel_tracer = (
 443            self._resources.tracer
 444            if self._tracing_enabled and self._resources.tracer is not None
 445            else otel_trace_api.NoOpTracer()
 446        )
 447
 448    @property
 449    def api(self) -> LangfuseAPI:
 450        """Synchronous client for the full Langfuse REST API (traces, observations, scores, datasets, prompts, ...).
 451
 452        Use this to read or manage data on the Langfuse server; use the tracing methods
 453        (`start_observation`, `@observe`) to create traces. Use `async_api` for the
 454        asyncio variant.
 455
 456        Semantics that are easy to miss:
 457
 458        - **Ingestion is asynchronous.** `langfuse.flush()` only guarantees delivery to
 459          the API, not read visibility: reads such as `api.trace.get(trace_id)` may
 460          raise `langfuse.api.NotFoundError` until processing completes (typically
 461          within 15-30 seconds; longer under load). The same applies to scores and
 462          dataset run reads. Instead of a fixed sleep, retry with a deadline:
 463
 464        - **List endpoints return lightweight views.** `api.trace.list(...)` returns
 465          `TraceWithDetails`, where `observations` and `scores` are lists of ID strings.
 466          Fetch the full objects with `api.trace.get(trace_id)` (`TraceWithFullDetails`),
 467          or prefer `api.observations.get_many(trace_id=...)` for row-level observation
 468          queries. The same list-view vs. get-detail pattern applies to other resources.
 469
 470        - **Prefer the v2 data APIs — they are the defaults since SDK v4.**
 471          `api.observations` and `api.metrics` map to the high-performance
 472          `/api/public/v2/...` endpoints and are the recommended read path. Their v1
 473          equivalents remain available under `api.legacy.observations_v1` /
 474          `api.legacy.metrics_v1` but are less performant at scale, not recommended
 475          for new workflows, and will be deprecated.
 476
 477        - For large-scale aggregation (usage/cost by model, user, etc.), prefer the
 478        v2 Metrics API (`api.metrics.metrics(...)`) over paginating row-level data.
 479
 480
 481        See also: `async_api`,
 482        https://langfuse.com/docs/api-and-data-platform/features/query-via-sdk
 483        (ingestion lag: #ingestion-lag, list vs. get: #traces-list-vs-get),
 484        https://langfuse.com/docs/api-and-data-platform/features/observations-api,
 485        https://langfuse.com/docs/metrics/features/metrics-api
 486        """
 487        if self._resources is None:
 488            raise AttributeError("Langfuse client is not initialized")
 489
 490        return self._resources.api
 491
 492    @api.setter
 493    def api(self, value: LangfuseAPI) -> None:
 494        if self._resources is None:
 495            raise AttributeError("Langfuse client is not initialized")
 496
 497        self._resources.api = value
 498
 499    @property
 500    def async_api(self) -> AsyncLangfuseAPI:
 501        if self._resources is None:
 502            raise AttributeError("Langfuse client is not initialized")
 503
 504        return self._resources.async_api
 505
 506    @async_api.setter
 507    def async_api(self, value: AsyncLangfuseAPI) -> None:
 508        if self._resources is None:
 509            raise AttributeError("Langfuse client is not initialized")
 510
 511        self._resources.async_api = value
 512
 513    @overload
 514    def start_observation(
 515        self,
 516        *,
 517        trace_context: Optional[TraceContext] = None,
 518        name: str,
 519        as_type: Literal["generation"],
 520        input: Optional[Any] = None,
 521        output: Optional[Any] = None,
 522        metadata: Optional[Any] = None,
 523        version: Optional[str] = None,
 524        level: Optional[SpanLevel] = None,
 525        status_message: Optional[str] = None,
 526        completion_start_time: Optional[datetime] = None,
 527        model: Optional[str] = None,
 528        model_parameters: Optional[Dict[str, MapValue]] = None,
 529        usage_details: Optional[Dict[str, int]] = None,
 530        cost_details: Optional[Dict[str, float]] = None,
 531        prompt: Optional[PromptClient] = None,
 532    ) -> LangfuseGeneration: ...
 533
 534    @overload
 535    def start_observation(
 536        self,
 537        *,
 538        trace_context: Optional[TraceContext] = None,
 539        name: str,
 540        as_type: Literal["span"] = "span",
 541        input: Optional[Any] = None,
 542        output: Optional[Any] = None,
 543        metadata: Optional[Any] = None,
 544        version: Optional[str] = None,
 545        level: Optional[SpanLevel] = None,
 546        status_message: Optional[str] = None,
 547    ) -> LangfuseSpan: ...
 548
 549    @overload
 550    def start_observation(
 551        self,
 552        *,
 553        trace_context: Optional[TraceContext] = None,
 554        name: str,
 555        as_type: Literal["agent"],
 556        input: Optional[Any] = None,
 557        output: Optional[Any] = None,
 558        metadata: Optional[Any] = None,
 559        version: Optional[str] = None,
 560        level: Optional[SpanLevel] = None,
 561        status_message: Optional[str] = None,
 562    ) -> LangfuseAgent: ...
 563
 564    @overload
 565    def start_observation(
 566        self,
 567        *,
 568        trace_context: Optional[TraceContext] = None,
 569        name: str,
 570        as_type: Literal["tool"],
 571        input: Optional[Any] = None,
 572        output: Optional[Any] = None,
 573        metadata: Optional[Any] = None,
 574        version: Optional[str] = None,
 575        level: Optional[SpanLevel] = None,
 576        status_message: Optional[str] = None,
 577    ) -> LangfuseTool: ...
 578
 579    @overload
 580    def start_observation(
 581        self,
 582        *,
 583        trace_context: Optional[TraceContext] = None,
 584        name: str,
 585        as_type: Literal["chain"],
 586        input: Optional[Any] = None,
 587        output: Optional[Any] = None,
 588        metadata: Optional[Any] = None,
 589        version: Optional[str] = None,
 590        level: Optional[SpanLevel] = None,
 591        status_message: Optional[str] = None,
 592    ) -> LangfuseChain: ...
 593
 594    @overload
 595    def start_observation(
 596        self,
 597        *,
 598        trace_context: Optional[TraceContext] = None,
 599        name: str,
 600        as_type: Literal["retriever"],
 601        input: Optional[Any] = None,
 602        output: Optional[Any] = None,
 603        metadata: Optional[Any] = None,
 604        version: Optional[str] = None,
 605        level: Optional[SpanLevel] = None,
 606        status_message: Optional[str] = None,
 607    ) -> LangfuseRetriever: ...
 608
 609    @overload
 610    def start_observation(
 611        self,
 612        *,
 613        trace_context: Optional[TraceContext] = None,
 614        name: str,
 615        as_type: Literal["evaluator"],
 616        input: Optional[Any] = None,
 617        output: Optional[Any] = None,
 618        metadata: Optional[Any] = None,
 619        version: Optional[str] = None,
 620        level: Optional[SpanLevel] = None,
 621        status_message: Optional[str] = None,
 622    ) -> LangfuseEvaluator: ...
 623
 624    @overload
 625    def start_observation(
 626        self,
 627        *,
 628        trace_context: Optional[TraceContext] = None,
 629        name: str,
 630        as_type: Literal["embedding"],
 631        input: Optional[Any] = None,
 632        output: Optional[Any] = None,
 633        metadata: Optional[Any] = None,
 634        version: Optional[str] = None,
 635        level: Optional[SpanLevel] = None,
 636        status_message: Optional[str] = None,
 637        completion_start_time: Optional[datetime] = None,
 638        model: Optional[str] = None,
 639        model_parameters: Optional[Dict[str, MapValue]] = None,
 640        usage_details: Optional[Dict[str, int]] = None,
 641        cost_details: Optional[Dict[str, float]] = None,
 642        prompt: Optional[PromptClient] = None,
 643    ) -> LangfuseEmbedding: ...
 644
 645    @overload
 646    def start_observation(
 647        self,
 648        *,
 649        trace_context: Optional[TraceContext] = None,
 650        name: str,
 651        as_type: Literal["guardrail"],
 652        input: Optional[Any] = None,
 653        output: Optional[Any] = None,
 654        metadata: Optional[Any] = None,
 655        version: Optional[str] = None,
 656        level: Optional[SpanLevel] = None,
 657        status_message: Optional[str] = None,
 658    ) -> LangfuseGuardrail: ...
 659
 660    def start_observation(
 661        self,
 662        *,
 663        trace_context: Optional[TraceContext] = None,
 664        name: str,
 665        as_type: ObservationTypeLiteralNoEvent = "span",
 666        input: Optional[Any] = None,
 667        output: Optional[Any] = None,
 668        metadata: Optional[Any] = None,
 669        version: Optional[str] = None,
 670        level: Optional[SpanLevel] = None,
 671        status_message: Optional[str] = None,
 672        completion_start_time: Optional[datetime] = None,
 673        model: Optional[str] = None,
 674        model_parameters: Optional[Dict[str, MapValue]] = None,
 675        usage_details: Optional[Dict[str, int]] = None,
 676        cost_details: Optional[Dict[str, float]] = None,
 677        prompt: Optional[PromptClient] = None,
 678    ) -> Union[
 679        LangfuseSpan,
 680        LangfuseGeneration,
 681        LangfuseAgent,
 682        LangfuseTool,
 683        LangfuseChain,
 684        LangfuseRetriever,
 685        LangfuseEvaluator,
 686        LangfuseEmbedding,
 687        LangfuseGuardrail,
 688    ]:
 689        """Create a new observation of the specified type.
 690
 691        This method creates a new observation but does not set it as the current span in the
 692        context. To create and use an observation within a context, use start_as_current_observation().
 693
 694        Args:
 695            trace_context: Optional context for connecting to an existing trace
 696            name: Name of the observation
 697            as_type: Type of observation to create (defaults to "span")
 698            input: Input data for the operation
 699            output: Output data from the operation
 700            metadata: Additional metadata to associate with the observation
 701            version: Version identifier for the code or component
 702            level: Importance level of the observation
 703            status_message: Optional status message for the observation
 704            completion_start_time: When the model started generating (for generation types)
 705            model: Name/identifier of the AI model used (for generation types)
 706            model_parameters: Parameters used for the model (for generation types)
 707            usage_details: Token usage information (for generation types)
 708            cost_details: Cost information (for generation types)
 709            prompt: Associated prompt template (for generation types)
 710
 711        Returns:
 712            An observation object of the appropriate type that must be ended with .end()
 713        """
 714        if trace_context:
 715            trace_id = trace_context.get("trace_id", None)
 716            parent_span_id = trace_context.get("parent_span_id", None)
 717
 718            if trace_id:
 719                remote_parent_span = self._create_remote_parent_span(
 720                    trace_id=trace_id, parent_span_id=parent_span_id
 721                )
 722
 723                with otel_trace_api.use_span(
 724                    cast(otel_trace_api.Span, remote_parent_span)
 725                ):
 726                    otel_span = self._otel_tracer.start_span(name=name)
 727                    otel_span.set_attribute(LangfuseOtelSpanAttributes.AS_ROOT, True)
 728
 729                    return self._create_observation_from_otel_span(
 730                        otel_span=otel_span,
 731                        as_type=as_type,
 732                        input=input,
 733                        output=output,
 734                        metadata=metadata,
 735                        version=version,
 736                        level=level,
 737                        status_message=status_message,
 738                        completion_start_time=completion_start_time,
 739                        model=model,
 740                        model_parameters=model_parameters,
 741                        usage_details=usage_details,
 742                        cost_details=cost_details,
 743                        prompt=prompt,
 744                    )
 745
 746        otel_span = self._otel_tracer.start_span(name=name)
 747
 748        return self._create_observation_from_otel_span(
 749            otel_span=otel_span,
 750            as_type=as_type,
 751            input=input,
 752            output=output,
 753            metadata=metadata,
 754            version=version,
 755            level=level,
 756            status_message=status_message,
 757            completion_start_time=completion_start_time,
 758            model=model,
 759            model_parameters=model_parameters,
 760            usage_details=usage_details,
 761            cost_details=cost_details,
 762            prompt=prompt,
 763        )
 764
 765    def _create_observation_from_otel_span(
 766        self,
 767        *,
 768        otel_span: otel_trace_api.Span,
 769        as_type: ObservationTypeLiteralNoEvent,
 770        input: Optional[Any] = None,
 771        output: Optional[Any] = None,
 772        metadata: Optional[Any] = None,
 773        version: Optional[str] = None,
 774        level: Optional[SpanLevel] = None,
 775        status_message: Optional[str] = None,
 776        completion_start_time: Optional[datetime] = None,
 777        model: Optional[str] = None,
 778        model_parameters: Optional[Dict[str, MapValue]] = None,
 779        usage_details: Optional[Dict[str, int]] = None,
 780        cost_details: Optional[Dict[str, float]] = None,
 781        prompt: Optional[PromptClient] = None,
 782    ) -> Union[
 783        LangfuseSpan,
 784        LangfuseGeneration,
 785        LangfuseAgent,
 786        LangfuseTool,
 787        LangfuseChain,
 788        LangfuseRetriever,
 789        LangfuseEvaluator,
 790        LangfuseEmbedding,
 791        LangfuseGuardrail,
 792    ]:
 793        """Create the appropriate observation type from an OTEL span."""
 794        if as_type in get_observation_types_list(ObservationTypeGenerationLike):
 795            observation_class = self._get_span_class(as_type)
 796            # Type ignore to prevent overloads of internal _get_span_class function,
 797            # issue is that LangfuseEvent could be returned and that classes have diff. args
 798            return observation_class(  # type: ignore[return-value,call-arg]
 799                otel_span=otel_span,
 800                langfuse_client=self,
 801                environment=self._environment,
 802                release=self._release,
 803                input=input,
 804                output=output,
 805                metadata=metadata,
 806                version=version,
 807                level=level,
 808                status_message=status_message,
 809                completion_start_time=completion_start_time,
 810                model=model,
 811                model_parameters=model_parameters,
 812                usage_details=usage_details,
 813                cost_details=cost_details,
 814                prompt=prompt,
 815            )
 816        else:
 817            # For other types (e.g. span, guardrail), create appropriate class without generation properties
 818            observation_class = self._get_span_class(as_type)
 819            # Type ignore to prevent overloads of internal _get_span_class function,
 820            # issue is that LangfuseEvent could be returned and that classes have diff. args
 821            return observation_class(  # type: ignore[return-value,call-arg]
 822                otel_span=otel_span,
 823                langfuse_client=self,
 824                environment=self._environment,
 825                release=self._release,
 826                input=input,
 827                output=output,
 828                metadata=metadata,
 829                version=version,
 830                level=level,
 831                status_message=status_message,
 832            )
 833            # span._observation_type = as_type
 834            # span._otel_span.set_attribute("langfuse.observation.type", as_type)
 835            # return span
 836
 837    @overload
 838    def start_as_current_observation(
 839        self,
 840        *,
 841        trace_context: Optional[TraceContext] = None,
 842        name: str,
 843        as_type: Literal["generation"],
 844        input: Optional[Any] = None,
 845        output: Optional[Any] = None,
 846        metadata: Optional[Any] = None,
 847        version: Optional[str] = None,
 848        level: Optional[SpanLevel] = None,
 849        status_message: Optional[str] = None,
 850        completion_start_time: Optional[datetime] = None,
 851        model: Optional[str] = None,
 852        model_parameters: Optional[Dict[str, MapValue]] = None,
 853        usage_details: Optional[Dict[str, int]] = None,
 854        cost_details: Optional[Dict[str, float]] = None,
 855        prompt: Optional[PromptClient] = None,
 856        end_on_exit: Optional[bool] = None,
 857    ) -> _AgnosticContextManager[LangfuseGeneration]: ...
 858
 859    @overload
 860    def start_as_current_observation(
 861        self,
 862        *,
 863        trace_context: Optional[TraceContext] = None,
 864        name: str,
 865        as_type: Literal["span"] = "span",
 866        input: Optional[Any] = None,
 867        output: Optional[Any] = None,
 868        metadata: Optional[Any] = None,
 869        version: Optional[str] = None,
 870        level: Optional[SpanLevel] = None,
 871        status_message: Optional[str] = None,
 872        end_on_exit: Optional[bool] = None,
 873    ) -> _AgnosticContextManager[LangfuseSpan]: ...
 874
 875    @overload
 876    def start_as_current_observation(
 877        self,
 878        *,
 879        trace_context: Optional[TraceContext] = None,
 880        name: str,
 881        as_type: Literal["agent"],
 882        input: Optional[Any] = None,
 883        output: Optional[Any] = None,
 884        metadata: Optional[Any] = None,
 885        version: Optional[str] = None,
 886        level: Optional[SpanLevel] = None,
 887        status_message: Optional[str] = None,
 888        end_on_exit: Optional[bool] = None,
 889    ) -> _AgnosticContextManager[LangfuseAgent]: ...
 890
 891    @overload
 892    def start_as_current_observation(
 893        self,
 894        *,
 895        trace_context: Optional[TraceContext] = None,
 896        name: str,
 897        as_type: Literal["tool"],
 898        input: Optional[Any] = None,
 899        output: Optional[Any] = None,
 900        metadata: Optional[Any] = None,
 901        version: Optional[str] = None,
 902        level: Optional[SpanLevel] = None,
 903        status_message: Optional[str] = None,
 904        end_on_exit: Optional[bool] = None,
 905    ) -> _AgnosticContextManager[LangfuseTool]: ...
 906
 907    @overload
 908    def start_as_current_observation(
 909        self,
 910        *,
 911        trace_context: Optional[TraceContext] = None,
 912        name: str,
 913        as_type: Literal["chain"],
 914        input: Optional[Any] = None,
 915        output: Optional[Any] = None,
 916        metadata: Optional[Any] = None,
 917        version: Optional[str] = None,
 918        level: Optional[SpanLevel] = None,
 919        status_message: Optional[str] = None,
 920        end_on_exit: Optional[bool] = None,
 921    ) -> _AgnosticContextManager[LangfuseChain]: ...
 922
 923    @overload
 924    def start_as_current_observation(
 925        self,
 926        *,
 927        trace_context: Optional[TraceContext] = None,
 928        name: str,
 929        as_type: Literal["retriever"],
 930        input: Optional[Any] = None,
 931        output: Optional[Any] = None,
 932        metadata: Optional[Any] = None,
 933        version: Optional[str] = None,
 934        level: Optional[SpanLevel] = None,
 935        status_message: Optional[str] = None,
 936        end_on_exit: Optional[bool] = None,
 937    ) -> _AgnosticContextManager[LangfuseRetriever]: ...
 938
 939    @overload
 940    def start_as_current_observation(
 941        self,
 942        *,
 943        trace_context: Optional[TraceContext] = None,
 944        name: str,
 945        as_type: Literal["evaluator"],
 946        input: Optional[Any] = None,
 947        output: Optional[Any] = None,
 948        metadata: Optional[Any] = None,
 949        version: Optional[str] = None,
 950        level: Optional[SpanLevel] = None,
 951        status_message: Optional[str] = None,
 952        end_on_exit: Optional[bool] = None,
 953    ) -> _AgnosticContextManager[LangfuseEvaluator]: ...
 954
 955    @overload
 956    def start_as_current_observation(
 957        self,
 958        *,
 959        trace_context: Optional[TraceContext] = None,
 960        name: str,
 961        as_type: Literal["embedding"],
 962        input: Optional[Any] = None,
 963        output: Optional[Any] = None,
 964        metadata: Optional[Any] = None,
 965        version: Optional[str] = None,
 966        level: Optional[SpanLevel] = None,
 967        status_message: Optional[str] = None,
 968        completion_start_time: Optional[datetime] = None,
 969        model: Optional[str] = None,
 970        model_parameters: Optional[Dict[str, MapValue]] = None,
 971        usage_details: Optional[Dict[str, int]] = None,
 972        cost_details: Optional[Dict[str, float]] = None,
 973        prompt: Optional[PromptClient] = None,
 974        end_on_exit: Optional[bool] = None,
 975    ) -> _AgnosticContextManager[LangfuseEmbedding]: ...
 976
 977    @overload
 978    def start_as_current_observation(
 979        self,
 980        *,
 981        trace_context: Optional[TraceContext] = None,
 982        name: str,
 983        as_type: Literal["guardrail"],
 984        input: Optional[Any] = None,
 985        output: Optional[Any] = None,
 986        metadata: Optional[Any] = None,
 987        version: Optional[str] = None,
 988        level: Optional[SpanLevel] = None,
 989        status_message: Optional[str] = None,
 990        end_on_exit: Optional[bool] = None,
 991    ) -> _AgnosticContextManager[LangfuseGuardrail]: ...
 992
 993    def start_as_current_observation(
 994        self,
 995        *,
 996        trace_context: Optional[TraceContext] = None,
 997        name: str,
 998        as_type: ObservationTypeLiteralNoEvent = "span",
 999        input: Optional[Any] = None,
1000        output: Optional[Any] = None,
1001        metadata: Optional[Any] = None,
1002        version: Optional[str] = None,
1003        level: Optional[SpanLevel] = None,
1004        status_message: Optional[str] = None,
1005        completion_start_time: Optional[datetime] = None,
1006        model: Optional[str] = None,
1007        model_parameters: Optional[Dict[str, MapValue]] = None,
1008        usage_details: Optional[Dict[str, int]] = None,
1009        cost_details: Optional[Dict[str, float]] = None,
1010        prompt: Optional[PromptClient] = None,
1011        end_on_exit: Optional[bool] = None,
1012    ) -> Union[
1013        _AgnosticContextManager[LangfuseGeneration],
1014        _AgnosticContextManager[LangfuseSpan],
1015        _AgnosticContextManager[LangfuseAgent],
1016        _AgnosticContextManager[LangfuseTool],
1017        _AgnosticContextManager[LangfuseChain],
1018        _AgnosticContextManager[LangfuseRetriever],
1019        _AgnosticContextManager[LangfuseEvaluator],
1020        _AgnosticContextManager[LangfuseEmbedding],
1021        _AgnosticContextManager[LangfuseGuardrail],
1022    ]:
1023        """Create a new observation and set it as the current span in a context manager.
1024
1025        This method creates a new observation of the specified type and sets it as the
1026        current span within a context manager. Use this method with a 'with' statement to
1027        automatically handle the observation lifecycle within a code block.
1028
1029        The created observation will be the child of the current span in the context.
1030
1031        Args:
1032            trace_context: Optional context for connecting to an existing trace
1033            name: Name of the observation (e.g., function or operation name)
1034            as_type: Type of observation to create (defaults to "span")
1035            input: Input data for the operation (can be any JSON-serializable object)
1036            output: Output data from the operation (can be any JSON-serializable object)
1037            metadata: Additional metadata to associate with the observation
1038            version: Version identifier for the code or component
1039            level: Importance level of the observation (info, warning, error)
1040            status_message: Optional status message for the observation
1041            end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks.
1042
1043            The following parameters are available when as_type is: "generation" or "embedding".
1044            completion_start_time: When the model started generating the response
1045            model: Name/identifier of the AI model used (e.g., "gpt-4")
1046            model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
1047            usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
1048            cost_details: Cost information for the model call
1049            prompt: Associated prompt template from Langfuse prompt management
1050
1051        Returns:
1052            A context manager that yields the appropriate observation type based on as_type
1053
1054        Example:
1055            ```python
1056            # Create a span
1057            with langfuse.start_as_current_observation(name="process-query", as_type="span") as span:
1058                # Do work
1059                result = process_data()
1060                span.update(output=result)
1061
1062                # Create a child span automatically
1063                with span.start_as_current_observation(name="sub-operation") as child_span:
1064                    # Do sub-operation work
1065                    child_span.update(output="sub-result")
1066
1067            # Create a tool observation
1068            with langfuse.start_as_current_observation(name="web-search", as_type="tool") as tool:
1069                # Do tool work
1070                results = search_web(query)
1071                tool.update(output=results)
1072
1073            # Create a generation observation
1074            with langfuse.start_as_current_observation(
1075                name="answer-generation",
1076                as_type="generation",
1077                model="gpt-4"
1078            ) as generation:
1079                # Generate answer
1080                response = llm.generate(...)
1081                generation.update(output=response)
1082            ```
1083        """
1084        if as_type in get_observation_types_list(ObservationTypeGenerationLike):
1085            if trace_context:
1086                trace_id = trace_context.get("trace_id", None)
1087                parent_span_id = trace_context.get("parent_span_id", None)
1088
1089                if trace_id:
1090                    remote_parent_span = self._create_remote_parent_span(
1091                        trace_id=trace_id, parent_span_id=parent_span_id
1092                    )
1093
1094                    return cast(
1095                        Union[
1096                            _AgnosticContextManager[LangfuseGeneration],
1097                            _AgnosticContextManager[LangfuseEmbedding],
1098                        ],
1099                        self._create_span_with_parent_context(
1100                            as_type=as_type,
1101                            name=name,
1102                            remote_parent_span=remote_parent_span,
1103                            parent=None,
1104                            end_on_exit=end_on_exit,
1105                            input=input,
1106                            output=output,
1107                            metadata=metadata,
1108                            version=version,
1109                            level=level,
1110                            status_message=status_message,
1111                            completion_start_time=completion_start_time,
1112                            model=model,
1113                            model_parameters=model_parameters,
1114                            usage_details=usage_details,
1115                            cost_details=cost_details,
1116                            prompt=prompt,
1117                        ),
1118                    )
1119
1120            return cast(
1121                Union[
1122                    _AgnosticContextManager[LangfuseGeneration],
1123                    _AgnosticContextManager[LangfuseEmbedding],
1124                ],
1125                self._start_as_current_otel_span_with_processed_media(
1126                    as_type=as_type,
1127                    name=name,
1128                    end_on_exit=end_on_exit,
1129                    input=input,
1130                    output=output,
1131                    metadata=metadata,
1132                    version=version,
1133                    level=level,
1134                    status_message=status_message,
1135                    completion_start_time=completion_start_time,
1136                    model=model,
1137                    model_parameters=model_parameters,
1138                    usage_details=usage_details,
1139                    cost_details=cost_details,
1140                    prompt=prompt,
1141                ),
1142            )
1143
1144        if as_type in get_observation_types_list(ObservationTypeSpanLike):
1145            if trace_context:
1146                trace_id = trace_context.get("trace_id", None)
1147                parent_span_id = trace_context.get("parent_span_id", None)
1148
1149                if trace_id:
1150                    remote_parent_span = self._create_remote_parent_span(
1151                        trace_id=trace_id, parent_span_id=parent_span_id
1152                    )
1153
1154                    return cast(
1155                        Union[
1156                            _AgnosticContextManager[LangfuseSpan],
1157                            _AgnosticContextManager[LangfuseAgent],
1158                            _AgnosticContextManager[LangfuseTool],
1159                            _AgnosticContextManager[LangfuseChain],
1160                            _AgnosticContextManager[LangfuseRetriever],
1161                            _AgnosticContextManager[LangfuseEvaluator],
1162                            _AgnosticContextManager[LangfuseGuardrail],
1163                        ],
1164                        self._create_span_with_parent_context(
1165                            as_type=as_type,
1166                            name=name,
1167                            remote_parent_span=remote_parent_span,
1168                            parent=None,
1169                            end_on_exit=end_on_exit,
1170                            input=input,
1171                            output=output,
1172                            metadata=metadata,
1173                            version=version,
1174                            level=level,
1175                            status_message=status_message,
1176                        ),
1177                    )
1178
1179            return cast(
1180                Union[
1181                    _AgnosticContextManager[LangfuseSpan],
1182                    _AgnosticContextManager[LangfuseAgent],
1183                    _AgnosticContextManager[LangfuseTool],
1184                    _AgnosticContextManager[LangfuseChain],
1185                    _AgnosticContextManager[LangfuseRetriever],
1186                    _AgnosticContextManager[LangfuseEvaluator],
1187                    _AgnosticContextManager[LangfuseGuardrail],
1188                ],
1189                self._start_as_current_otel_span_with_processed_media(
1190                    as_type=as_type,
1191                    name=name,
1192                    end_on_exit=end_on_exit,
1193                    input=input,
1194                    output=output,
1195                    metadata=metadata,
1196                    version=version,
1197                    level=level,
1198                    status_message=status_message,
1199                ),
1200            )
1201
1202        # This should never be reached since all valid types are handled above
1203        langfuse_logger.warning(
1204            "Unknown observation type: %s, falling back to span", as_type
1205        )
1206        return self._start_as_current_otel_span_with_processed_media(
1207            as_type="span",
1208            name=name,
1209            end_on_exit=end_on_exit,
1210            input=input,
1211            output=output,
1212            metadata=metadata,
1213            version=version,
1214            level=level,
1215            status_message=status_message,
1216        )
1217
1218    def _get_span_class(
1219        self,
1220        as_type: str,
1221    ) -> Union[
1222        Type[LangfuseAgent],
1223        Type[LangfuseTool],
1224        Type[LangfuseChain],
1225        Type[LangfuseRetriever],
1226        Type[LangfuseEvaluator],
1227        Type[LangfuseEmbedding],
1228        Type[LangfuseGuardrail],
1229        Type[LangfuseGeneration],
1230        Type[LangfuseEvent],
1231        Type[LangfuseSpan],
1232    ]:
1233        """Get the appropriate span class based on as_type."""
1234        normalized_type = as_type.lower()
1235
1236        if normalized_type == "agent":
1237            return LangfuseAgent
1238        elif normalized_type == "tool":
1239            return LangfuseTool
1240        elif normalized_type == "chain":
1241            return LangfuseChain
1242        elif normalized_type == "retriever":
1243            return LangfuseRetriever
1244        elif normalized_type == "evaluator":
1245            return LangfuseEvaluator
1246        elif normalized_type == "embedding":
1247            return LangfuseEmbedding
1248        elif normalized_type == "guardrail":
1249            return LangfuseGuardrail
1250        elif normalized_type == "generation":
1251            return LangfuseGeneration
1252        elif normalized_type == "event":
1253            return LangfuseEvent
1254        elif normalized_type == "span":
1255            return LangfuseSpan
1256        else:
1257            return LangfuseSpan
1258
1259    @staticmethod
1260    def _get_observation_type_from_otel_span(otel_span: otel_trace_api.Span) -> str:
1261        if not otel_span.is_recording():
1262            return "span"
1263
1264        attributes = getattr(otel_span, "attributes", None)
1265        if attributes is None or not hasattr(attributes, "get"):
1266            return "span"
1267
1268        observation_type = attributes.get(
1269            LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "span"
1270        )
1271
1272        return observation_type if isinstance(observation_type, str) else "span"
1273
1274    @_agnosticcontextmanager
1275    def _create_span_with_parent_context(
1276        self,
1277        *,
1278        name: str,
1279        parent: Optional[otel_trace_api.Span] = None,
1280        remote_parent_span: Optional[otel_trace_api.Span] = None,
1281        as_type: ObservationTypeLiteralNoEvent,
1282        end_on_exit: Optional[bool] = None,
1283        input: Optional[Any] = None,
1284        output: Optional[Any] = None,
1285        metadata: Optional[Any] = None,
1286        version: Optional[str] = None,
1287        level: Optional[SpanLevel] = None,
1288        status_message: Optional[str] = None,
1289        completion_start_time: Optional[datetime] = None,
1290        model: Optional[str] = None,
1291        model_parameters: Optional[Dict[str, MapValue]] = None,
1292        usage_details: Optional[Dict[str, int]] = None,
1293        cost_details: Optional[Dict[str, float]] = None,
1294        prompt: Optional[PromptClient] = None,
1295    ) -> Any:
1296        parent_span = parent or cast(otel_trace_api.Span, remote_parent_span)
1297
1298        with otel_trace_api.use_span(parent_span):
1299            with self._start_as_current_otel_span_with_processed_media(
1300                name=name,
1301                as_type=as_type,
1302                end_on_exit=end_on_exit,
1303                input=input,
1304                output=output,
1305                metadata=metadata,
1306                version=version,
1307                level=level,
1308                status_message=status_message,
1309                completion_start_time=completion_start_time,
1310                model=model,
1311                model_parameters=model_parameters,
1312                usage_details=usage_details,
1313                cost_details=cost_details,
1314                prompt=prompt,
1315            ) as langfuse_span:
1316                if remote_parent_span is not None:
1317                    langfuse_span._otel_span.set_attribute(
1318                        LangfuseOtelSpanAttributes.AS_ROOT, True
1319                    )
1320
1321                yield langfuse_span
1322
1323    @_agnosticcontextmanager
1324    def _start_as_current_otel_span_with_processed_media(
1325        self,
1326        *,
1327        name: str,
1328        as_type: Optional[ObservationTypeLiteralNoEvent] = None,
1329        end_on_exit: Optional[bool] = None,
1330        input: Optional[Any] = None,
1331        output: Optional[Any] = None,
1332        metadata: Optional[Any] = None,
1333        version: Optional[str] = None,
1334        level: Optional[SpanLevel] = None,
1335        status_message: Optional[str] = None,
1336        completion_start_time: Optional[datetime] = None,
1337        model: Optional[str] = None,
1338        model_parameters: Optional[Dict[str, MapValue]] = None,
1339        usage_details: Optional[Dict[str, int]] = None,
1340        cost_details: Optional[Dict[str, float]] = None,
1341        prompt: Optional[PromptClient] = None,
1342    ) -> Any:
1343        with self._otel_tracer.start_as_current_span(
1344            name=name,
1345            end_on_exit=end_on_exit if end_on_exit is not None else True,
1346        ) as otel_span:
1347            baggage_token = None
1348
1349            if otel_span.is_recording():
1350                context_with_app_root_claim = _set_langfuse_trace_id_in_baggage(
1351                    trace_id=self._get_otel_trace_id(otel_span),
1352                    context=otel_context_api.get_current(),
1353                )
1354                baggage_token = otel_context_api.attach(context_with_app_root_claim)
1355
1356            span_class = self._get_span_class(
1357                as_type or "generation"
1358            )  # default was "generation"
1359
1360            try:
1361                common_args = {
1362                    "otel_span": otel_span,
1363                    "langfuse_client": self,
1364                    "environment": self._environment,
1365                    "release": self._release,
1366                    "input": input,
1367                    "output": output,
1368                    "metadata": metadata,
1369                    "version": version,
1370                    "level": level,
1371                    "status_message": status_message,
1372                }
1373
1374                if span_class in [
1375                    LangfuseGeneration,
1376                    LangfuseEmbedding,
1377                ]:
1378                    common_args.update(
1379                        {
1380                            "completion_start_time": completion_start_time,
1381                            "model": model,
1382                            "model_parameters": model_parameters,
1383                            "usage_details": usage_details,
1384                            "cost_details": cost_details,
1385                            "prompt": prompt,
1386                        }
1387                    )
1388                # For span-like types (span, agent, tool, chain, retriever, evaluator, guardrail), no generation properties needed
1389
1390                yield span_class(**common_args)  # type: ignore[arg-type]
1391
1392            finally:
1393                if baggage_token is not None:
1394                    _detach_context_token_safely(baggage_token)
1395
1396    def _get_current_otel_span(self) -> Optional[otel_trace_api.Span]:
1397        current_span = otel_trace_api.get_current_span()
1398
1399        if current_span is otel_trace_api.INVALID_SPAN:
1400            langfuse_logger.warning(
1401                "Context error: No active span in current context. Operations that depend on an active span will be skipped. "
1402                "Ensure spans are created with start_as_current_observation() or that you're operating within an active span context."
1403            )
1404            return None
1405
1406        return current_span
1407
1408    def update_current_generation(
1409        self,
1410        *,
1411        name: Optional[str] = None,
1412        input: Optional[Any] = None,
1413        output: Optional[Any] = None,
1414        metadata: Optional[Any] = None,
1415        version: Optional[str] = None,
1416        level: Optional[SpanLevel] = None,
1417        status_message: Optional[str] = None,
1418        completion_start_time: Optional[datetime] = None,
1419        model: Optional[str] = None,
1420        model_parameters: Optional[Dict[str, MapValue]] = None,
1421        usage_details: Optional[Dict[str, int]] = None,
1422        cost_details: Optional[Dict[str, float]] = None,
1423        prompt: Optional[PromptClient] = None,
1424    ) -> None:
1425        """Update the current active generation span with new information.
1426
1427        This method updates the current generation span in the active context with
1428        additional information. It's useful for adding output, usage stats, or other
1429        details that become available during or after model generation.
1430
1431        Args:
1432            name: The generation name
1433            input: Updated input data for the model
1434            output: Output from the model (e.g., completions)
1435            metadata: Additional metadata to associate with the generation
1436            version: Version identifier for the model or component
1437            level: Importance level of the generation (info, warning, error)
1438            status_message: Optional status message for the generation
1439            completion_start_time: When the model started generating the response
1440            model: Name/identifier of the AI model used (e.g., "gpt-4")
1441            model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
1442            usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
1443            cost_details: Cost information for the model call
1444            prompt: Associated prompt template from Langfuse prompt management
1445
1446        Example:
1447            ```python
1448            with langfuse.start_as_current_generation(name="answer-query") as generation:
1449                # Initial setup and API call
1450                response = llm.generate(...)
1451
1452                # Update with results that weren't available at creation time
1453                langfuse.update_current_generation(
1454                    output=response.text,
1455                    usage_details={
1456                        "prompt_tokens": response.usage.prompt_tokens,
1457                        "completion_tokens": response.usage.completion_tokens
1458                    }
1459                )
1460            ```
1461        """
1462        if not self._tracing_enabled:
1463            langfuse_logger.debug(
1464                "Operation skipped: update_current_generation - Tracing is disabled or client is in no-op mode."
1465            )
1466            return
1467
1468        current_otel_span = self._get_current_otel_span()
1469
1470        if current_otel_span is not None:
1471            generation = LangfuseGeneration(
1472                otel_span=current_otel_span, langfuse_client=self
1473            )
1474
1475            if name:
1476                current_otel_span.update_name(name)
1477
1478            generation.update(
1479                input=input,
1480                output=output,
1481                metadata=metadata,
1482                version=version,
1483                level=level,
1484                status_message=status_message,
1485                completion_start_time=completion_start_time,
1486                model=model,
1487                model_parameters=model_parameters,
1488                usage_details=usage_details,
1489                cost_details=cost_details,
1490                prompt=prompt,
1491            )
1492
1493    def update_current_span(
1494        self,
1495        *,
1496        name: Optional[str] = None,
1497        input: Optional[Any] = None,
1498        output: Optional[Any] = None,
1499        metadata: Optional[Any] = None,
1500        version: Optional[str] = None,
1501        level: Optional[SpanLevel] = None,
1502        status_message: Optional[str] = None,
1503    ) -> None:
1504        """Update the current active span with new information.
1505
1506        This method updates the current span in the active context with
1507        additional information. It's useful for adding outputs or metadata
1508        that become available during execution.
1509
1510        Args:
1511            name: The span name
1512            input: Updated input data for the operation
1513            output: Output data from the operation
1514            metadata: Additional metadata to associate with the span
1515            version: Version identifier for the code or component
1516            level: Importance level of the span (info, warning, error)
1517            status_message: Optional status message for the span
1518
1519        Example:
1520            ```python
1521            with langfuse.start_as_current_observation(name="process-data") as span:
1522                # Initial processing
1523                result = process_first_part()
1524
1525                # Update with intermediate results
1526                langfuse.update_current_span(metadata={"intermediate_result": result})
1527
1528                # Continue processing
1529                final_result = process_second_part(result)
1530
1531                # Final update
1532                langfuse.update_current_span(output=final_result)
1533            ```
1534        """
1535        if not self._tracing_enabled:
1536            langfuse_logger.debug(
1537                "Operation skipped: update_current_span - Tracing is disabled or client is in no-op mode."
1538            )
1539            return
1540
1541        current_otel_span = self._get_current_otel_span()
1542
1543        if current_otel_span is not None:
1544            span_class = self._get_span_class(
1545                self._get_observation_type_from_otel_span(current_otel_span)
1546            )
1547            span = span_class(
1548                otel_span=current_otel_span,
1549                langfuse_client=self,
1550                environment=self._environment,
1551                release=self._release,
1552            )
1553
1554            if name:
1555                current_otel_span.update_name(name)
1556
1557            span.update(
1558                input=input,
1559                output=output,
1560                metadata=metadata,
1561                version=version,
1562                level=level,
1563                status_message=status_message,
1564            )
1565
1566    @deprecated(
1567        "Trace-level input/output is deprecated. "
1568        "For trace attributes (user_id, session_id, tags, etc.), use propagate_attributes() instead. "
1569        "This method will be removed in a future major version."
1570    )
1571    def set_current_trace_io(
1572        self,
1573        *,
1574        input: Optional[Any] = None,
1575        output: Optional[Any] = None,
1576    ) -> None:
1577        """Set trace-level input and output for the current span's trace.
1578
1579        .. deprecated::
1580            This is a legacy method for backward compatibility with Langfuse platform
1581            features that still rely on trace-level input/output (e.g., legacy LLM-as-a-judge
1582            evaluators). It will be removed in a future major version.
1583
1584            For setting other trace attributes (user_id, session_id, metadata, tags, version),
1585            use :func:`langfuse.propagate_attributes` (top-level import) instead.
1586
1587        Args:
1588            input: Input data to associate with the trace.
1589            output: Output data to associate with the trace.
1590        """
1591        if not self._tracing_enabled:
1592            langfuse_logger.debug(
1593                "Operation skipped: set_current_trace_io - Tracing is disabled or client is in no-op mode."
1594            )
1595            return
1596
1597        current_otel_span = self._get_current_otel_span()
1598
1599        if current_otel_span is not None and current_otel_span.is_recording():
1600            span_class = self._get_span_class(
1601                self._get_observation_type_from_otel_span(current_otel_span)
1602            )
1603            span = span_class(
1604                otel_span=current_otel_span,
1605                langfuse_client=self,
1606                environment=self._environment,
1607                release=self._release,
1608            )
1609
1610            span.set_trace_io(
1611                input=input,
1612                output=output,
1613            )
1614
1615    def set_current_trace_as_public(self) -> None:
1616        """Make the current trace publicly accessible via its URL.
1617
1618        When a trace is published, anyone with the trace link can view the full trace
1619        without needing to be logged in to Langfuse. This action cannot be undone
1620        programmatically - once published, the entire trace becomes public.
1621
1622        This is a convenience method that publishes the trace from the currently
1623        active span context. Use this when you want to make a trace public from
1624        within a traced function without needing direct access to the span object.
1625        """
1626        if not self._tracing_enabled:
1627            langfuse_logger.debug(
1628                "Operation skipped: set_current_trace_as_public - Tracing is disabled or client is in no-op mode."
1629            )
1630            return
1631
1632        current_otel_span = self._get_current_otel_span()
1633
1634        if current_otel_span is not None and current_otel_span.is_recording():
1635            span_class = self._get_span_class(
1636                self._get_observation_type_from_otel_span(current_otel_span)
1637            )
1638            span = span_class(
1639                otel_span=current_otel_span,
1640                langfuse_client=self,
1641                environment=self._environment,
1642            )
1643
1644            span.set_trace_as_public()
1645
1646    def create_event(
1647        self,
1648        *,
1649        trace_context: Optional[TraceContext] = None,
1650        name: str,
1651        input: Optional[Any] = None,
1652        output: Optional[Any] = None,
1653        metadata: Optional[Any] = None,
1654        version: Optional[str] = None,
1655        level: Optional[SpanLevel] = None,
1656        status_message: Optional[str] = None,
1657    ) -> LangfuseEvent:
1658        """Create a new Langfuse observation of type 'EVENT'.
1659
1660        The created Langfuse Event observation will be the child of the current span in the context.
1661
1662        Args:
1663            trace_context: Optional context for connecting to an existing trace
1664            name: Name of the span (e.g., function or operation name)
1665            input: Input data for the operation (can be any JSON-serializable object)
1666            output: Output data from the operation (can be any JSON-serializable object)
1667            metadata: Additional metadata to associate with the span
1668            version: Version identifier for the code or component
1669            level: Importance level of the span (info, warning, error)
1670            status_message: Optional status message for the span
1671
1672        Returns:
1673            The Langfuse Event object
1674
1675        Example:
1676            ```python
1677            event = langfuse.create_event(name="process-event")
1678            ```
1679        """
1680        timestamp = time_ns()
1681
1682        if trace_context:
1683            trace_id = trace_context.get("trace_id", None)
1684            parent_span_id = trace_context.get("parent_span_id", None)
1685
1686            if trace_id:
1687                remote_parent_span = self._create_remote_parent_span(
1688                    trace_id=trace_id, parent_span_id=parent_span_id
1689                )
1690
1691                with otel_trace_api.use_span(
1692                    cast(otel_trace_api.Span, remote_parent_span)
1693                ):
1694                    otel_span = self._otel_tracer.start_span(
1695                        name=name, start_time=timestamp
1696                    )
1697                    otel_span.set_attribute(LangfuseOtelSpanAttributes.AS_ROOT, True)
1698
1699                    return cast(
1700                        LangfuseEvent,
1701                        LangfuseEvent(
1702                            otel_span=otel_span,
1703                            langfuse_client=self,
1704                            environment=self._environment,
1705                            release=self._release,
1706                            input=input,
1707                            output=output,
1708                            metadata=metadata,
1709                            version=version,
1710                            level=level,
1711                            status_message=status_message,
1712                        ).end(end_time=timestamp),
1713                    )
1714
1715        otel_span = self._otel_tracer.start_span(name=name, start_time=timestamp)
1716
1717        return cast(
1718            LangfuseEvent,
1719            LangfuseEvent(
1720                otel_span=otel_span,
1721                langfuse_client=self,
1722                environment=self._environment,
1723                release=self._release,
1724                input=input,
1725                output=output,
1726                metadata=metadata,
1727                version=version,
1728                level=level,
1729                status_message=status_message,
1730            ).end(end_time=timestamp),
1731        )
1732
1733    def _create_remote_parent_span(
1734        self, *, trace_id: str, parent_span_id: Optional[str]
1735    ) -> Any:
1736        if not self._is_valid_trace_id(trace_id):
1737            langfuse_logger.warning(
1738                "Passed trace ID '%s' is not a valid 32 lowercase hex char Langfuse trace "
1739                "id. Ignoring trace ID.",
1740                trace_id,
1741            )
1742
1743        if parent_span_id and not self._is_valid_span_id(parent_span_id):
1744            langfuse_logger.warning(
1745                "Passed span ID '%s' is not a valid 16 lowercase hex char Langfuse span "
1746                "id. Ignoring parent span ID.",
1747                parent_span_id,
1748            )
1749
1750        int_trace_id = int(trace_id, 16)
1751        int_parent_span_id = (
1752            int(parent_span_id, 16)
1753            if parent_span_id
1754            else RandomIdGenerator().generate_span_id()
1755        )
1756
1757        span_context = otel_trace_api.SpanContext(
1758            trace_id=int_trace_id,
1759            span_id=int_parent_span_id,
1760            trace_flags=otel_trace_api.TraceFlags(0x01),  # mark span as sampled
1761            is_remote=False,
1762        )
1763
1764        return otel_trace_api.NonRecordingSpan(span_context)
1765
1766    def _is_valid_trace_id(self, trace_id: str) -> bool:
1767        pattern = r"^[0-9a-f]{32}$"
1768
1769        return bool(re.match(pattern, trace_id))
1770
1771    def _is_valid_span_id(self, span_id: str) -> bool:
1772        pattern = r"^[0-9a-f]{16}$"
1773
1774        return bool(re.match(pattern, span_id))
1775
1776    def _create_observation_id(self, *, seed: Optional[str] = None) -> str:
1777        """Create a unique observation ID for use with Langfuse.
1778
1779        This method generates a unique observation ID (span ID in OpenTelemetry terms)
1780        for use with various Langfuse APIs. It can either generate a random ID or
1781        create a deterministic ID based on a seed string.
1782
1783        Observation IDs must be 16 lowercase hexadecimal characters, representing 8 bytes.
1784        This method ensures the generated ID meets this requirement. If you need to
1785        correlate an external ID with a Langfuse observation ID, use the external ID as
1786        the seed to get a valid, deterministic observation ID.
1787
1788        Args:
1789            seed: Optional string to use as a seed for deterministic ID generation.
1790                 If provided, the same seed will always produce the same ID.
1791                 If not provided, a random ID will be generated.
1792
1793        Returns:
1794            A 16-character lowercase hexadecimal string representing the observation ID.
1795
1796        Example:
1797            ```python
1798            # Generate a random observation ID
1799            obs_id = langfuse.create_observation_id()
1800
1801            # Generate a deterministic ID based on a seed
1802            user_obs_id = langfuse.create_observation_id(seed="user-123-feedback")
1803
1804            # Correlate an external item ID with a Langfuse observation ID
1805            item_id = "item-789012"
1806            correlated_obs_id = langfuse.create_observation_id(seed=item_id)
1807
1808            # Use the ID with Langfuse APIs
1809            langfuse.create_score(
1810                name="relevance",
1811                value=0.95,
1812                trace_id=trace_id,
1813                observation_id=obs_id
1814            )
1815            ```
1816        """
1817        if not seed:
1818            span_id_int = RandomIdGenerator().generate_span_id()
1819
1820            return self._format_otel_span_id(span_id_int)
1821
1822        return sha256(seed.encode("utf-8")).digest()[:8].hex()
1823
1824    @staticmethod
1825    def create_trace_id(*, seed: Optional[str] = None) -> str:
1826        """Create a unique trace ID for use with Langfuse.
1827
1828        This method generates a unique trace ID for use with various Langfuse APIs.
1829        It can either generate a random ID or create a deterministic ID based on
1830        a seed string.
1831
1832        Trace IDs must be 32 lowercase hexadecimal characters, representing 16 bytes.
1833        This method ensures the generated ID meets this requirement. If you need to
1834        correlate an external ID with a Langfuse trace ID, use the external ID as the
1835        seed to get a valid, deterministic Langfuse trace ID.
1836
1837        Args:
1838            seed: Optional string to use as a seed for deterministic ID generation.
1839                 If provided, the same seed will always produce the same ID.
1840                 If not provided, a random ID will be generated.
1841
1842        Returns:
1843            A 32-character lowercase hexadecimal string representing the Langfuse trace ID.
1844
1845        Example:
1846            ```python
1847            # Generate a random trace ID
1848            trace_id = langfuse.create_trace_id()
1849
1850            # Generate a deterministic ID based on a seed
1851            session_trace_id = langfuse.create_trace_id(seed="session-456")
1852
1853            # Correlate an external ID with a Langfuse trace ID
1854            external_id = "external-system-123456"
1855            correlated_trace_id = langfuse.create_trace_id(seed=external_id)
1856
1857            # Use the ID with trace context
1858            with langfuse.start_as_current_observation(
1859                name="process-request",
1860                trace_context={"trace_id": trace_id}
1861            ) as span:
1862                # Operation will be part of the specific trace
1863                pass
1864            ```
1865        """
1866        if not seed:
1867            trace_id_int = RandomIdGenerator().generate_trace_id()
1868
1869            return Langfuse._format_otel_trace_id(trace_id_int)
1870
1871        return sha256(seed.encode("utf-8")).digest()[:16].hex()
1872
1873    def _get_otel_trace_id(self, otel_span: otel_trace_api.Span) -> str:
1874        span_context = otel_span.get_span_context()
1875
1876        return self._format_otel_trace_id(span_context.trace_id)
1877
1878    def _get_otel_span_id(self, otel_span: otel_trace_api.Span) -> str:
1879        span_context = otel_span.get_span_context()
1880
1881        return self._format_otel_span_id(span_context.span_id)
1882
1883    @staticmethod
1884    def _format_otel_span_id(span_id_int: int) -> str:
1885        """Format an integer span ID to a 16-character lowercase hex string.
1886
1887        Internal method to convert an OpenTelemetry integer span ID to the standard
1888        W3C Trace Context format (16-character lowercase hex string).
1889
1890        Args:
1891            span_id_int: 64-bit integer representing a span ID
1892
1893        Returns:
1894            A 16-character lowercase hexadecimal string
1895        """
1896        return format(span_id_int, "016x")
1897
1898    @staticmethod
1899    def _format_otel_trace_id(trace_id_int: int) -> str:
1900        """Format an integer trace ID to a 32-character lowercase hex string.
1901
1902        Internal method to convert an OpenTelemetry integer trace ID to the standard
1903        W3C Trace Context format (32-character lowercase hex string).
1904
1905        Args:
1906            trace_id_int: 128-bit integer representing a trace ID
1907
1908        Returns:
1909            A 32-character lowercase hexadecimal string
1910        """
1911        return format(trace_id_int, "032x")
1912
1913    @overload
1914    def create_score(
1915        self,
1916        *,
1917        name: str,
1918        value: float,
1919        session_id: Optional[str] = None,
1920        dataset_run_id: Optional[str] = None,
1921        trace_id: Optional[str] = None,
1922        observation_id: Optional[str] = None,
1923        score_id: Optional[str] = None,
1924        data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None,
1925        comment: Optional[str] = None,
1926        config_id: Optional[str] = None,
1927        metadata: Optional[Any] = None,
1928        timestamp: Optional[datetime] = None,
1929        environment: Optional[str] = None,
1930    ) -> None: ...
1931
1932    @overload
1933    def create_score(
1934        self,
1935        *,
1936        name: str,
1937        value: str,
1938        session_id: Optional[str] = None,
1939        dataset_run_id: Optional[str] = None,
1940        trace_id: Optional[str] = None,
1941        score_id: Optional[str] = None,
1942        observation_id: Optional[str] = None,
1943        data_type: Optional[
1944            Literal["CATEGORICAL", "TEXT", "CORRECTION"]
1945        ] = "CATEGORICAL",
1946        comment: Optional[str] = None,
1947        config_id: Optional[str] = None,
1948        metadata: Optional[Any] = None,
1949        timestamp: Optional[datetime] = None,
1950        environment: Optional[str] = None,
1951    ) -> None: ...
1952
1953    def create_score(
1954        self,
1955        *,
1956        name: str,
1957        value: Union[float, str],
1958        session_id: Optional[str] = None,
1959        dataset_run_id: Optional[str] = None,
1960        trace_id: Optional[str] = None,
1961        observation_id: Optional[str] = None,
1962        score_id: Optional[str] = None,
1963        data_type: Optional[ScoreDataType] = None,
1964        comment: Optional[str] = None,
1965        config_id: Optional[str] = None,
1966        metadata: Optional[Any] = None,
1967        timestamp: Optional[datetime] = None,
1968        environment: Optional[str] = None,
1969    ) -> None:
1970        """Create a score for a specific trace or observation.
1971
1972        This method creates a score for evaluating a Langfuse trace or observation. Scores can be
1973        used to track quality metrics, user feedback, or automated evaluations.
1974
1975        Args:
1976            name: Name of the score (e.g., "relevance", "accuracy")
1977            value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
1978            session_id: ID of the Langfuse session to associate the score with
1979            dataset_run_id: ID of the Langfuse dataset run to associate the score with
1980            trace_id: ID of the Langfuse trace to associate the score with
1981            observation_id: Optional ID of the specific observation to score. Trace ID must be provided too.
1982            score_id: Optional custom ID for the score (auto-generated if not provided)
1983            data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
1984            comment: Optional comment or explanation for the score
1985            config_id: Optional ID of a score config defined in Langfuse
1986            metadata: Optional metadata to be attached to the score
1987            timestamp: Optional timestamp for the score (defaults to current UTC time)
1988            environment: Optional environment override for this score. If omitted,
1989                the score uses the client-level environment from
1990                `Langfuse(environment=...)` or `LANGFUSE_TRACING_ENVIRONMENT`.
1991                Langfuse observation wrapper methods pass their resolved span
1992                environment here so scores created via `span.score()` or
1993                `span.score_trace()` stay grouped with the scored observation or
1994                trace, including request-scoped environments propagated with
1995                `propagate_attributes(environment=...)`.
1996
1997        Example:
1998            ```python
1999            # Create a numeric score for accuracy
2000            langfuse.create_score(
2001                name="accuracy",
2002                value=0.92,
2003                trace_id="abcdef1234567890abcdef1234567890",
2004                data_type="NUMERIC",
2005                comment="High accuracy with minor irrelevant details"
2006            )
2007
2008            # Create a categorical score for sentiment
2009            langfuse.create_score(
2010                name="sentiment",
2011                value="positive",
2012                trace_id="abcdef1234567890abcdef1234567890",
2013                observation_id="abcdef1234567890",
2014                data_type="CATEGORICAL"
2015            )
2016            ```
2017        """
2018        if not self._tracing_enabled:
2019            return
2020
2021        score_id = score_id or self._create_observation_id()
2022
2023        try:
2024            new_body = ScoreBody(
2025                id=score_id,
2026                sessionId=session_id,
2027                datasetRunId=dataset_run_id,
2028                traceId=trace_id,
2029                observationId=observation_id,
2030                name=name,
2031                value=value,
2032                dataType=data_type,  # type: ignore
2033                comment=comment,
2034                configId=config_id,
2035                environment=environment or self._environment,
2036                metadata=metadata,
2037            )
2038
2039            event = {
2040                "id": self.create_trace_id(),
2041                "type": "score-create",
2042                "timestamp": timestamp or _get_timestamp(),
2043                "body": new_body,
2044            }
2045
2046            if self._resources is not None:
2047                # Force the score to be in sample if it was for a legacy trace ID, i.e. non-32 hexchar
2048                force_sample = (
2049                    not self._is_valid_trace_id(trace_id) if trace_id else True
2050                )
2051
2052                self._resources.add_score_task(
2053                    event,
2054                    force_sample=force_sample,
2055                )
2056
2057        except Exception as e:
2058            langfuse_logger.exception(
2059                "Error creating score: Failed to process score event for trace_id=%s, "
2060                "name=%s. Error: %s",
2061                trace_id,
2062                name,
2063                e,
2064            )
2065
2066    def _create_trace_tags_via_ingestion(
2067        self,
2068        *,
2069        trace_id: str,
2070        tags: List[str],
2071    ) -> None:
2072        """Private helper to enqueue trace tag updates via ingestion API events."""
2073        if not self._tracing_enabled:
2074            return
2075
2076        if len(tags) == 0:
2077            return
2078
2079        try:
2080            new_body = TraceBody(
2081                id=trace_id,
2082                tags=tags,
2083            )
2084
2085            event = {
2086                "id": self.create_trace_id(),
2087                "type": "trace-create",
2088                "timestamp": _get_timestamp(),
2089                "body": new_body,
2090            }
2091
2092            if self._resources is not None:
2093                self._resources.add_trace_task(event)
2094        except Exception as e:
2095            langfuse_logger.exception(
2096                "Error updating trace tags: Failed to process trace update event for "
2097                "trace_id=%s. Error: %s",
2098                trace_id,
2099                e,
2100            )
2101
2102    @overload
2103    def score_current_span(
2104        self,
2105        *,
2106        name: str,
2107        value: float,
2108        score_id: Optional[str] = None,
2109        data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None,
2110        comment: Optional[str] = None,
2111        config_id: Optional[str] = None,
2112        metadata: Optional[Any] = None,
2113    ) -> None: ...
2114
2115    @overload
2116    def score_current_span(
2117        self,
2118        *,
2119        name: str,
2120        value: str,
2121        score_id: Optional[str] = None,
2122        data_type: Optional[
2123            Literal["CATEGORICAL", "TEXT", "CORRECTION"]
2124        ] = "CATEGORICAL",
2125        comment: Optional[str] = None,
2126        config_id: Optional[str] = None,
2127        metadata: Optional[Any] = None,
2128    ) -> None: ...
2129
2130    def score_current_span(
2131        self,
2132        *,
2133        name: str,
2134        value: Union[float, str],
2135        score_id: Optional[str] = None,
2136        data_type: Optional[ScoreDataType] = None,
2137        comment: Optional[str] = None,
2138        config_id: Optional[str] = None,
2139        metadata: Optional[Any] = None,
2140    ) -> None:
2141        """Create a score for the current active span.
2142
2143        This method scores the currently active span in the context. It's a convenient
2144        way to score the current operation without needing to know its trace and span IDs.
2145        If the active span has a `langfuse.environment` attribute, including one
2146        set by `propagate_attributes(environment=...)`, the score uses that
2147        environment. Otherwise it uses the client-level environment.
2148
2149        Args:
2150            name: Name of the score (e.g., "relevance", "accuracy")
2151            value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
2152            score_id: Optional custom ID for the score (auto-generated if not provided)
2153            data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
2154            comment: Optional comment or explanation for the score
2155            config_id: Optional ID of a score config defined in Langfuse
2156            metadata: Optional metadata to be attached to the score
2157
2158        Example:
2159            ```python
2160            with langfuse.start_as_current_generation(name="answer-query") as generation:
2161                # Generate answer
2162                response = generate_answer(...)
2163                generation.update(output=response)
2164
2165                # Score the generation
2166                langfuse.score_current_span(
2167                    name="relevance",
2168                    value=0.85,
2169                    data_type="NUMERIC",
2170                    comment="Mostly relevant but contains some tangential information",
2171                    metadata={"model": "gpt-4", "prompt_version": "v2"}
2172                )
2173            ```
2174        """
2175        current_span = self._get_current_otel_span()
2176
2177        if current_span is not None:
2178            trace_id = self._get_otel_trace_id(current_span)
2179            observation_id = self._get_otel_span_id(current_span)
2180
2181            langfuse_logger.info(
2182                "Score: Creating score name='%s' value=%s for current span (%s) in trace "
2183                "%s",
2184                name,
2185                value,
2186                observation_id,
2187                trace_id,
2188            )
2189
2190            self.create_score(
2191                trace_id=trace_id,
2192                observation_id=observation_id,
2193                name=name,
2194                value=cast(str, value),
2195                score_id=score_id,
2196                data_type=cast(Literal["CATEGORICAL", "TEXT", "CORRECTION"], data_type),
2197                comment=comment,
2198                config_id=config_id,
2199                metadata=metadata,
2200                environment=get_string_span_attribute(
2201                    current_span, LangfuseOtelSpanAttributes.ENVIRONMENT
2202                ),
2203            )
2204
2205    @overload
2206    def score_current_trace(
2207        self,
2208        *,
2209        name: str,
2210        value: float,
2211        score_id: Optional[str] = None,
2212        data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None,
2213        comment: Optional[str] = None,
2214        config_id: Optional[str] = None,
2215        metadata: Optional[Any] = None,
2216    ) -> None: ...
2217
2218    @overload
2219    def score_current_trace(
2220        self,
2221        *,
2222        name: str,
2223        value: str,
2224        score_id: Optional[str] = None,
2225        data_type: Optional[
2226            Literal["CATEGORICAL", "TEXT", "CORRECTION"]
2227        ] = "CATEGORICAL",
2228        comment: Optional[str] = None,
2229        config_id: Optional[str] = None,
2230        metadata: Optional[Any] = None,
2231    ) -> None: ...
2232
2233    def score_current_trace(
2234        self,
2235        *,
2236        name: str,
2237        value: Union[float, str],
2238        score_id: Optional[str] = None,
2239        data_type: Optional[ScoreDataType] = None,
2240        comment: Optional[str] = None,
2241        config_id: Optional[str] = None,
2242        metadata: Optional[Any] = None,
2243    ) -> None:
2244        """Create a score for the current trace.
2245
2246        This method scores the trace of the currently active span. Unlike score_current_span,
2247        this method associates the score with the entire trace rather than a specific span.
2248        It's useful for scoring overall performance or quality of the entire operation.
2249        If the active span has a `langfuse.environment` attribute, including one
2250        set by `propagate_attributes(environment=...)`, the score uses that
2251        environment. Otherwise it uses the client-level environment.
2252
2253        Args:
2254            name: Name of the score (e.g., "user_satisfaction", "overall_quality")
2255            value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
2256            score_id: Optional custom ID for the score (auto-generated if not provided)
2257            data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
2258            comment: Optional comment or explanation for the score
2259            config_id: Optional ID of a score config defined in Langfuse
2260            metadata: Optional metadata to be attached to the score
2261
2262        Example:
2263            ```python
2264            with langfuse.start_as_current_observation(name="process-user-request") as span:
2265                # Process request
2266                result = process_complete_request()
2267                span.update(output=result)
2268
2269                # Score the overall trace
2270                langfuse.score_current_trace(
2271                    name="overall_quality",
2272                    value=0.95,
2273                    data_type="NUMERIC",
2274                    comment="High quality end-to-end response",
2275                    metadata={"evaluator": "gpt-4", "criteria": "comprehensive"}
2276                )
2277            ```
2278        """
2279        current_span = self._get_current_otel_span()
2280
2281        if current_span is not None:
2282            trace_id = self._get_otel_trace_id(current_span)
2283
2284            langfuse_logger.info(
2285                "Score: Creating score name='%s' value=%s for entire trace %s",
2286                name,
2287                value,
2288                trace_id,
2289            )
2290
2291            self.create_score(
2292                trace_id=trace_id,
2293                name=name,
2294                value=cast(str, value),
2295                score_id=score_id,
2296                data_type=cast(Literal["CATEGORICAL", "TEXT", "CORRECTION"], data_type),
2297                comment=comment,
2298                config_id=config_id,
2299                metadata=metadata,
2300                environment=get_string_span_attribute(
2301                    current_span, LangfuseOtelSpanAttributes.ENVIRONMENT
2302                ),
2303            )
2304
2305    def flush(self) -> None:
2306        """Force flush all pending spans and events to the Langfuse API.
2307
2308        This method manually flushes any pending spans, scores, and other events to the
2309        Langfuse API. It's useful in scenarios where you want to ensure all data is sent
2310        before proceeding, without waiting for the automatic flush interval.
2311
2312        Example:
2313            ```python
2314            # Record some spans and scores
2315            with langfuse.start_as_current_observation(name="operation") as span:
2316                # Do work...
2317                pass
2318
2319            # Ensure all data is sent to Langfuse before proceeding
2320            langfuse.flush()
2321
2322            # Continue with other work
2323            ```
2324
2325        Note:
2326            `flush()` guarantees data was *delivered* to the API, not that it is
2327            *readable* yet: server-side ingestion is asynchronous, so flushed data
2328            may not be queryable for 15-30 seconds —
2329            `api.observations.get_many(trace_id=...)` may return empty results and
2330            `api.trace.get()` may raise `langfuse.api.NotFoundError` right after a
2331            successful flush. See the `api` property docs for a bounded retry
2332            pattern, or
2333            https://langfuse.com/docs/api-and-data-platform/features/query-via-sdk#ingestion-lag
2334        """
2335        if self._resources is not None:
2336            self._resources.flush()
2337
2338    def shutdown(self) -> None:
2339        """Shut down the Langfuse client and flush all pending data.
2340
2341        This method cleanly shuts down the Langfuse client, ensuring all pending data
2342        is flushed to the API and all background threads are properly terminated.
2343
2344        It's important to call this method when your application is shutting down to
2345        prevent data loss and resource leaks. For most applications, using the client
2346        as a context manager or relying on the automatic shutdown via atexit is sufficient.
2347
2348        Example:
2349            ```python
2350            # Initialize Langfuse
2351            langfuse = Langfuse(public_key="...", secret_key="...")
2352
2353            # Use Langfuse throughout your application
2354            # ...
2355
2356            # When application is shutting down
2357            langfuse.shutdown()
2358            ```
2359        """
2360        if self._resources is not None:
2361            self._resources.shutdown()
2362
2363    def get_current_trace_id(self) -> Optional[str]:
2364        """Get the trace ID of the current active span.
2365
2366        This method retrieves the trace ID from the currently active span in the context.
2367        It can be used to get the trace ID for referencing in logs, external systems,
2368        or for creating related operations.
2369
2370        Returns:
2371            The current trace ID as a 32-character lowercase hexadecimal string,
2372            or None if there is no active span.
2373
2374        Example:
2375            ```python
2376            with langfuse.start_as_current_observation(name="process-request") as span:
2377                # Get the current trace ID for reference
2378                trace_id = langfuse.get_current_trace_id()
2379
2380                # Use it for external correlation
2381                log.info(f"Processing request with trace_id: {trace_id}")
2382
2383                # Or pass to another system
2384                external_system.process(data, trace_id=trace_id)
2385            ```
2386        """
2387        if not self._tracing_enabled:
2388            langfuse_logger.debug(
2389                "Operation skipped: get_current_trace_id - Tracing is disabled or client is in no-op mode."
2390            )
2391            return None
2392
2393        current_otel_span = self._get_current_otel_span()
2394
2395        return self._get_otel_trace_id(current_otel_span) if current_otel_span else None
2396
2397    def get_current_observation_id(self) -> Optional[str]:
2398        """Get the observation ID (span ID) of the current active span.
2399
2400        This method retrieves the observation ID from the currently active span in the context.
2401        It can be used to get the observation ID for referencing in logs, external systems,
2402        or for creating scores or other related operations.
2403
2404        Returns:
2405            The current observation ID as a 16-character lowercase hexadecimal string,
2406            or None if there is no active span.
2407
2408        Example:
2409            ```python
2410            with langfuse.start_as_current_observation(name="process-user-query") as span:
2411                # Get the current observation ID
2412                observation_id = langfuse.get_current_observation_id()
2413
2414                # Store it for later reference
2415                cache.set(f"query_{query_id}_observation", observation_id)
2416
2417                # Process the query...
2418            ```
2419        """
2420        if not self._tracing_enabled:
2421            langfuse_logger.debug(
2422                "Operation skipped: get_current_observation_id - Tracing is disabled or client is in no-op mode."
2423            )
2424            return None
2425
2426        current_otel_span = self._get_current_otel_span()
2427
2428        return self._get_otel_span_id(current_otel_span) if current_otel_span else None
2429
2430    def _get_project_id(self) -> Optional[str]:
2431        """Fetch and return the current project id. Persisted across requests. Returns None if no project id is found for api keys."""
2432        if not self._project_id:
2433            proj = self.api.projects.get()
2434            if not proj.data or not proj.data[0].id:
2435                return None
2436
2437            self._project_id = proj.data[0].id
2438
2439        return self._project_id
2440
2441    def get_trace_url(self, *, trace_id: Optional[str] = None) -> Optional[str]:
2442        """Get the URL to view a trace in the Langfuse UI.
2443
2444        This method generates a URL that links directly to a trace in the Langfuse UI.
2445        It's useful for providing links in logs, notifications, or debugging tools.
2446
2447        Args:
2448            trace_id: Optional trace ID to generate a URL for. If not provided,
2449                     the trace ID of the current active span will be used.
2450
2451        Returns:
2452            A URL string pointing to the trace in the Langfuse UI,
2453            or None if the project ID couldn't be retrieved or no trace ID is available.
2454
2455        Example:
2456            ```python
2457            # Get URL for the current trace
2458            with langfuse.start_as_current_observation(name="process-request") as span:
2459                trace_url = langfuse.get_trace_url()
2460                log.info(f"Processing trace: {trace_url}")
2461
2462            # Get URL for a specific trace
2463            specific_trace_url = langfuse.get_trace_url(trace_id="1234567890abcdef1234567890abcdef")
2464            send_notification(f"Review needed for trace: {specific_trace_url}")
2465            ```
2466        """
2467        final_trace_id = trace_id or self.get_current_trace_id()
2468        if not final_trace_id:
2469            return None
2470
2471        project_id = self._get_project_id()
2472
2473        return (
2474            f"{self._base_url}/project/{project_id}/traces/{final_trace_id}"
2475            if project_id and final_trace_id
2476            else None
2477        )
2478
2479    def get_dataset(
2480        self,
2481        name: str,
2482        *,
2483        fetch_items_page_size: Optional[int] = 50,
2484        version: Optional[datetime] = None,
2485    ) -> "DatasetClient":
2486        """Fetch a dataset by its name.
2487
2488        Args:
2489            name: The name of the dataset to fetch.
2490            fetch_items_page_size: All items of the dataset will be fetched in chunks of this size. Defaults to 50.
2491            version: Retrieve dataset items as they existed at this specific point in time (UTC).
2492                If provided, returns the state of items at the specified UTC timestamp.
2493                If not provided, returns the latest version. Must be a timezone-aware datetime object in UTC.
2494
2495        Returns:
2496            DatasetClient: The dataset with the given name.
2497        """
2498        try:
2499            langfuse_logger.debug("Getting datasets %s", name)
2500            dataset = self.api.datasets.get(dataset_name=self._url_encode(name))
2501
2502            dataset_items: List[DatasetItem] = []
2503            page = 1
2504
2505            while True:
2506                new_items = self.api.dataset_items.list(
2507                    dataset_name=self._url_encode(name, is_url_param=True),
2508                    page=page,
2509                    limit=fetch_items_page_size,
2510                    version=version,
2511                )
2512                dataset_items.extend(
2513                    self._hydrate_dataset_item_media_references(item)
2514                    for item in new_items.data
2515                )
2516
2517                if new_items.meta.total_pages <= page:
2518                    break
2519
2520                page += 1
2521
2522            return DatasetClient(
2523                dataset=dataset,
2524                items=dataset_items,
2525                version=version,
2526                langfuse_client=self,
2527            )
2528
2529        except Error as e:
2530            handle_fern_exception(e)
2531            raise e
2532
2533    def get_dataset_run(
2534        self, *, dataset_name: str, run_name: str
2535    ) -> DatasetRunWithItems:
2536        """Fetch a dataset run by dataset name and run name.
2537
2538        Args:
2539            dataset_name (str): The name of the dataset.
2540            run_name (str): The name of the run.
2541
2542        Returns:
2543            DatasetRunWithItems: The dataset run with its items.
2544        """
2545        try:
2546            return cast(
2547                DatasetRunWithItems,
2548                self.api.datasets.get_run(
2549                    dataset_name=self._url_encode(dataset_name),
2550                    run_name=self._url_encode(run_name),
2551                    request_options=None,
2552                ),
2553            )
2554        except Error as e:
2555            handle_fern_exception(e)
2556            raise e
2557
2558    def get_dataset_runs(
2559        self,
2560        *,
2561        dataset_name: str,
2562        page: Optional[int] = None,
2563        limit: Optional[int] = None,
2564    ) -> PaginatedDatasetRuns:
2565        """Fetch all runs for a dataset.
2566
2567        Args:
2568            dataset_name (str): The name of the dataset.
2569            page (Optional[int]): Page number, starts at 1.
2570            limit (Optional[int]): Limit of items per page.
2571
2572        Returns:
2573            PaginatedDatasetRuns: Paginated list of dataset runs.
2574        """
2575        try:
2576            return cast(
2577                PaginatedDatasetRuns,
2578                self.api.datasets.get_runs(
2579                    dataset_name=self._url_encode(dataset_name),
2580                    page=page,
2581                    limit=limit,
2582                    request_options=None,
2583                ),
2584            )
2585        except Error as e:
2586            handle_fern_exception(e)
2587            raise e
2588
2589    def delete_dataset_run(
2590        self, *, dataset_name: str, run_name: str
2591    ) -> DeleteDatasetRunResponse:
2592        """Delete a dataset run and all its run items. This action is irreversible.
2593
2594        Args:
2595            dataset_name (str): The name of the dataset.
2596            run_name (str): The name of the run.
2597
2598        Returns:
2599            DeleteDatasetRunResponse: Confirmation of deletion.
2600        """
2601        try:
2602            return cast(
2603                DeleteDatasetRunResponse,
2604                self.api.datasets.delete_run(
2605                    dataset_name=self._url_encode(dataset_name),
2606                    run_name=self._url_encode(run_name),
2607                    request_options=None,
2608                ),
2609            )
2610        except Error as e:
2611            handle_fern_exception(e)
2612            raise e
2613
2614    def run_experiment(
2615        self,
2616        *,
2617        name: str,
2618        run_name: Optional[str] = None,
2619        description: Optional[str] = None,
2620        data: ExperimentData,
2621        task: TaskFunction,
2622        evaluators: List[EvaluatorFunction] = [],
2623        composite_evaluator: Optional[CompositeEvaluatorFunction] = None,
2624        run_evaluators: List[RunEvaluatorFunction] = [],
2625        max_concurrency: int = 50,
2626        metadata: Optional[Dict[str, str]] = None,
2627        _dataset_version: Optional[datetime] = None,
2628    ) -> ExperimentResult:
2629        """Run an experiment on a dataset with automatic tracing and evaluation.
2630
2631        This method executes a task function on each item in the provided dataset,
2632        automatically traces all executions with Langfuse for observability, runs
2633        item-level and run-level evaluators on the outputs, and returns comprehensive
2634        results with evaluation metrics.
2635
2636        The experiment system provides:
2637        - Automatic tracing of all task executions
2638        - Concurrent processing with configurable limits
2639        - Comprehensive error handling that isolates failures
2640        - Integration with Langfuse datasets for experiment tracking
2641        - Flexible evaluation framework supporting both sync and async evaluators
2642
2643        Args:
2644            name: Human-readable name for the experiment. Used for identification
2645                in the Langfuse UI.
2646            run_name: Optional exact name for the experiment run. If provided, this will be
2647                used as the exact dataset run name if the `data` contains Langfuse dataset items.
2648                If not provided, this will default to the experiment name appended with an ISO timestamp.
2649            description: Optional description explaining the experiment's purpose,
2650                methodology, or expected outcomes.
2651            data: Array of data items to process. Can be either:
2652                - List of dict-like items with 'input', 'expected_output', 'metadata' keys
2653                - List of Langfuse DatasetItem objects from dataset.items
2654            task: Function that processes each data item and returns output.
2655                Must accept 'item' as keyword argument and can return sync or async results.
2656                The task function signature should be: task(*, item, **kwargs) -> Any
2657            evaluators: List of functions to evaluate each item's output individually.
2658                Each evaluator receives input, output, expected_output, and metadata.
2659                Can return single Evaluation dict or list of Evaluation dicts.
2660            composite_evaluator: Optional function that creates composite scores from item-level evaluations.
2661                Receives the same inputs as item-level evaluators (input, output, expected_output, metadata)
2662                plus the list of evaluations from item-level evaluators. Useful for weighted averages,
2663                pass/fail decisions based on multiple criteria, or custom scoring logic combining multiple metrics.
2664            run_evaluators: List of functions to evaluate the entire experiment run.
2665                Each run evaluator receives all item_results and can compute aggregate metrics.
2666                Useful for calculating averages, distributions, or cross-item comparisons.
2667            max_concurrency: Maximum number of concurrent task executions (default: 50).
2668                Controls the number of items processed simultaneously. Adjust based on
2669                API rate limits and system resources.
2670            metadata: Optional metadata dictionary to attach to all experiment traces.
2671                This metadata will be included in every trace created during the experiment.
2672                If `data` are Langfuse dataset items, the metadata will be attached to the dataset run, too.
2673
2674        Returns:
2675            ExperimentResult containing:
2676            - run_name: The experiment run name. This is equal to the dataset run name if experiment was on Langfuse dataset.
2677            - item_results: List of results for each processed item with outputs and evaluations
2678            - run_evaluations: List of aggregate evaluation results for the entire run
2679            - experiment_id: Stable identifier for the experiment run across all items
2680            - dataset_run_id: ID of the dataset run (if using Langfuse datasets)
2681            - dataset_run_url: Direct URL to view results in Langfuse UI (if applicable)
2682
2683        Raises:
2684            ValueError: If required parameters are missing or invalid
2685            Exception: If experiment setup fails (individual item failures are handled gracefully)
2686
2687        Examples:
2688            Basic experiment with local data:
2689            ```python
2690            def summarize_text(*, item, **kwargs):
2691                return f"Summary: {item['input'][:50]}..."
2692
2693            def length_evaluator(*, input, output, expected_output=None, **kwargs):
2694                return {
2695                    "name": "output_length",
2696                    "value": len(output),
2697                    "comment": f"Output contains {len(output)} characters"
2698                }
2699
2700            result = langfuse.run_experiment(
2701                name="Text Summarization Test",
2702                description="Evaluate summarization quality and length",
2703                data=[
2704                    {"input": "Long article text...", "expected_output": "Expected summary"},
2705                    {"input": "Another article...", "expected_output": "Another summary"}
2706                ],
2707                task=summarize_text,
2708                evaluators=[length_evaluator]
2709            )
2710
2711            print(f"Processed {len(result.item_results)} items")
2712            for item_result in result.item_results:
2713                print(f"Input: {item_result.item['input']}")
2714                print(f"Output: {item_result.output}")
2715                print(f"Evaluations: {item_result.evaluations}")
2716            ```
2717
2718            Advanced experiment with async task and multiple evaluators:
2719            ```python
2720            async def llm_task(*, item, **kwargs):
2721                # Simulate async LLM call
2722                response = await openai_client.chat.completions.create(
2723                    model="gpt-4",
2724                    messages=[{"role": "user", "content": item["input"]}]
2725                )
2726                return response.choices[0].message.content
2727
2728            def accuracy_evaluator(*, input, output, expected_output=None, **kwargs):
2729                if expected_output and expected_output.lower() in output.lower():
2730                    return {"name": "accuracy", "value": 1.0, "comment": "Correct answer"}
2731                return {"name": "accuracy", "value": 0.0, "comment": "Incorrect answer"}
2732
2733            def toxicity_evaluator(*, input, output, expected_output=None, **kwargs):
2734                # Simulate toxicity check
2735                toxicity_score = check_toxicity(output)  # Your toxicity checker
2736                return {
2737                    "name": "toxicity",
2738                    "value": toxicity_score,
2739                    "comment": f"Toxicity level: {'high' if toxicity_score > 0.7 else 'low'}"
2740                }
2741
2742            def average_accuracy(*, item_results, **kwargs):
2743                accuracies = [
2744                    eval.value for result in item_results
2745                    for eval in result.evaluations
2746                    if eval.name == "accuracy"
2747                ]
2748                return {
2749                    "name": "average_accuracy",
2750                    "value": sum(accuracies) / len(accuracies) if accuracies else 0,
2751                    "comment": f"Average accuracy across {len(accuracies)} items"
2752                }
2753
2754            result = langfuse.run_experiment(
2755                name="LLM Safety and Accuracy Test",
2756                description="Evaluate model accuracy and safety across diverse prompts",
2757                data=test_dataset,  # Your dataset items
2758                task=llm_task,
2759                evaluators=[accuracy_evaluator, toxicity_evaluator],
2760                run_evaluators=[average_accuracy],
2761                max_concurrency=5,  # Limit concurrent API calls
2762                metadata={"model": "gpt-4", "temperature": 0.7}
2763            )
2764            ```
2765
2766            Using with Langfuse datasets:
2767            ```python
2768            # Get dataset from Langfuse
2769            dataset = langfuse.get_dataset("my-eval-dataset")
2770
2771            result = dataset.run_experiment(
2772                name="Production Model Evaluation",
2773                description="Monthly evaluation of production model performance",
2774                task=my_production_task,
2775                evaluators=[accuracy_evaluator, latency_evaluator]
2776            )
2777
2778            # Results automatically linked to dataset in Langfuse UI
2779            print(f"View results: {result['dataset_run_url']}")
2780            ```
2781
2782        Note:
2783            - Task and evaluator functions can be either synchronous or asynchronous
2784            - Individual item failures are logged but don't stop the experiment
2785            - All executions are automatically traced and visible in Langfuse UI
2786            - When using Langfuse datasets, results are automatically linked for easy comparison
2787            - This method works in both sync and async contexts (Jupyter notebooks, web apps, etc.)
2788            - Async execution is handled automatically with smart event loop detection
2789        """
2790        return cast(
2791            ExperimentResult,
2792            run_async_safely(
2793                self._run_experiment_async(
2794                    name=name,
2795                    run_name=self._create_experiment_run_name(
2796                        name=name, run_name=run_name
2797                    ),
2798                    description=description,
2799                    data=data,
2800                    task=task,
2801                    evaluators=evaluators or [],
2802                    composite_evaluator=composite_evaluator,
2803                    run_evaluators=run_evaluators or [],
2804                    max_concurrency=max_concurrency,
2805                    metadata=metadata,
2806                    dataset_version=_dataset_version,
2807                ),
2808            ),
2809        )
2810
2811    async def _run_experiment_async(
2812        self,
2813        *,
2814        name: str,
2815        run_name: str,
2816        description: Optional[str],
2817        data: ExperimentData,
2818        task: TaskFunction,
2819        evaluators: List[EvaluatorFunction],
2820        composite_evaluator: Optional[CompositeEvaluatorFunction],
2821        run_evaluators: List[RunEvaluatorFunction],
2822        max_concurrency: int,
2823        metadata: Optional[Dict[str, Any]] = None,
2824        dataset_version: Optional[datetime] = None,
2825    ) -> ExperimentResult:
2826        langfuse_logger.debug(
2827            "Starting experiment '%s' run '%s' with %s items", name, run_name, len(data)
2828        )
2829
2830        shared_fallback_experiment_id = self._create_observation_id()
2831
2832        # Set up concurrency control
2833        semaphore = asyncio.Semaphore(max_concurrency)
2834
2835        # Process all items
2836        async def process_item(item: ExperimentItem) -> ExperimentItemResult:
2837            async with semaphore:
2838                return await self._process_experiment_item(
2839                    item,
2840                    task,
2841                    evaluators,
2842                    composite_evaluator,
2843                    shared_fallback_experiment_id,
2844                    name,
2845                    run_name,
2846                    description,
2847                    metadata,
2848                    dataset_version,
2849                )
2850
2851        # Run all items concurrently
2852        tasks = [process_item(item) for item in data]
2853        item_results = await asyncio.gather(*tasks, return_exceptions=True)
2854
2855        # Filter out any exceptions and log errors
2856        valid_results: List[ExperimentItemResult] = []
2857        for i, result in enumerate(item_results):
2858            if isinstance(result, Exception):
2859                langfuse_logger.error("Item %s failed: %s", i, result)
2860            elif isinstance(result, ExperimentItemResult):
2861                valid_results.append(result)  # type: ignore
2862
2863        # Run experiment-level evaluators
2864        run_evaluations: List[Evaluation] = []
2865        for run_evaluator in run_evaluators:
2866            try:
2867                evaluations = await _run_evaluator(
2868                    run_evaluator, item_results=valid_results
2869                )
2870                run_evaluations.extend(evaluations)
2871            except Exception as e:
2872                langfuse_logger.error("Run evaluator failed: %s", e)
2873
2874        # Generate dataset run URL if applicable
2875        dataset_run_id = next(
2876            (
2877                result.dataset_run_id
2878                for result in valid_results
2879                if result.dataset_run_id
2880            ),
2881            None,
2882        )
2883        dataset_run_url = None
2884        if dataset_run_id and data:
2885            try:
2886                # Check if the first item has dataset_id (for DatasetItem objects)
2887                first_item = data[0]
2888                dataset_id = None
2889
2890                if hasattr(first_item, "dataset_id"):
2891                    dataset_id = getattr(first_item, "dataset_id", None)
2892
2893                if dataset_id:
2894                    project_id = self._get_project_id()
2895
2896                    if project_id:
2897                        dataset_run_url = f"{self._base_url}/project/{project_id}/datasets/{dataset_id}/runs/{dataset_run_id}"
2898
2899            except Exception:
2900                pass  # URL generation is optional
2901
2902        # Store run-level evaluations as scores
2903        for evaluation in run_evaluations:
2904            try:
2905                if dataset_run_id:
2906                    self.create_score(
2907                        dataset_run_id=dataset_run_id,
2908                        name=evaluation.name or "<unknown>",
2909                        value=evaluation.value,  # type: ignore
2910                        comment=evaluation.comment,
2911                        metadata=evaluation.metadata,
2912                        data_type=evaluation.data_type,  # type: ignore
2913                        config_id=evaluation.config_id,
2914                    )
2915
2916            except Exception as e:
2917                langfuse_logger.error("Failed to store run evaluation: %s", e)
2918
2919        # Flush scores and traces
2920        self.flush()
2921
2922        return ExperimentResult(
2923            name=name,
2924            run_name=run_name,
2925            description=description,
2926            item_results=valid_results,
2927            run_evaluations=run_evaluations,
2928            experiment_id=dataset_run_id or shared_fallback_experiment_id,
2929            dataset_run_id=dataset_run_id,
2930            dataset_run_url=dataset_run_url,
2931        )
2932
2933    async def _process_experiment_item(
2934        self,
2935        item: ExperimentItem,
2936        task: Callable,
2937        evaluators: List[Callable],
2938        composite_evaluator: Optional[CompositeEvaluatorFunction],
2939        fallback_experiment_id: str,
2940        experiment_name: str,
2941        experiment_run_name: str,
2942        experiment_description: Optional[str],
2943        experiment_metadata: Optional[Dict[str, Any]] = None,
2944        dataset_version: Optional[datetime] = None,
2945    ) -> ExperimentItemResult:
2946        with self.start_as_current_observation(name="experiment-item-run") as span:
2947            try:
2948                input_data = (
2949                    item.get("input")
2950                    if isinstance(item, dict)
2951                    else getattr(item, "input", None)
2952                )
2953
2954                if input_data is None:
2955                    raise ValueError("Experiment Item is missing input. Skipping item.")
2956
2957                expected_output = (
2958                    item.get("expected_output")
2959                    if isinstance(item, dict)
2960                    else getattr(item, "expected_output", None)
2961                )
2962
2963                item_metadata = (
2964                    item.get("metadata")
2965                    if isinstance(item, dict)
2966                    else getattr(item, "metadata", None)
2967                )
2968
2969                final_observation_metadata = {
2970                    **(item_metadata if isinstance(item_metadata, dict) else {}),
2971                    **(experiment_metadata or {}),
2972                    "experiment_name": experiment_name,
2973                    "experiment_run_name": experiment_run_name,
2974                }
2975
2976                trace_id = span.trace_id
2977                dataset_id = None
2978                dataset_item_id = None
2979                dataset_run_id = None
2980
2981                if (
2982                    not isinstance(item, dict)
2983                    and hasattr(item, "dataset_id")
2984                    and hasattr(item, "id")
2985                ):
2986                    dataset_id = item.dataset_id
2987                    dataset_item_id = item.id
2988
2989                    final_observation_metadata.update(
2990                        {"dataset_id": dataset_id, "dataset_item_id": dataset_item_id}
2991                    )
2992
2993                experiment_item_id = (
2994                    dataset_item_id or get_sha256_hash_hex(_serialize(input_data))[:16]
2995                )
2996                experiment_span_attributes = {
2997                    k: v
2998                    for k, v in {
2999                        LangfuseOtelSpanAttributes.ENVIRONMENT: LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT,
3000                        LangfuseOtelSpanAttributes.EXPERIMENT_DESCRIPTION: experiment_description,
3001                        LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_EXPECTED_OUTPUT: _serialize(
3002                            expected_output
3003                        ),
3004                    }.items()
3005                    if v is not None
3006                }
3007                span._otel_span.set_attributes(experiment_span_attributes)
3008
3009                with span.start_as_current_observation(
3010                    name="experiment-item-task",
3011                    as_type="span",
3012                    input=input_data,
3013                    metadata=final_observation_metadata,
3014                ) as task_span:
3015                    task_span._otel_span.set_attributes(experiment_span_attributes)
3016
3017                    # Link dataset runs to the canonical task observation so their
3018                    # latency excludes the subsequent evaluator subtree.
3019                    if hasattr(item, "id") and hasattr(item, "dataset_id"):
3020                        try:
3021                            # Use sync API to avoid event loop issues when
3022                            # run_async_safely creates multiple event loops across
3023                            # different threads.
3024                            dataset_run_item = await asyncio.to_thread(
3025                                self.api.dataset_run_items.create,
3026                                run_name=experiment_run_name,
3027                                run_description=experiment_description,
3028                                metadata=experiment_metadata,
3029                                dataset_item_id=item.id,  # type: ignore
3030                                trace_id=trace_id,
3031                                observation_id=task_span.id,
3032                                dataset_version=dataset_version,
3033                            )
3034
3035                            dataset_run_id = dataset_run_item.dataset_run_id
3036
3037                        except Exception as e:
3038                            langfuse_logger.error(
3039                                "Failed to create dataset run item: %s", e
3040                            )
3041
3042                    experiment_id = dataset_run_id or fallback_experiment_id
3043                    propagated_experiment_attributes = PropagatedExperimentAttributes(
3044                        experiment_id=experiment_id,
3045                        experiment_name=experiment_run_name,
3046                        experiment_metadata=_flatten_and_serialize_metadata_values(
3047                            experiment_metadata
3048                        ),
3049                        experiment_dataset_id=dataset_id,
3050                        experiment_item_id=experiment_item_id,
3051                        experiment_item_metadata=_flatten_and_serialize_metadata_values(
3052                            item_metadata if isinstance(item_metadata, dict) else None
3053                        ),
3054                        experiment_item_root_observation_id=task_span.id,
3055                    )
3056
3057                    with _propagate_attributes(
3058                        experiment=propagated_experiment_attributes
3059                    ):
3060                        # _propagate_attributes updates the current task span and future children.
3061                        # Explicitly backfill the parent item-run span to preserve experiment association.
3062                        span._otel_span.set_attributes(
3063                            _get_propagated_attributes_from_context(
3064                                otel_context_api.get_current()
3065                            )
3066                        )
3067                        try:
3068                            output = await _run_task(task, item)
3069                        except Exception as e:
3070                            task_span.update(
3071                                output=f"Error: {str(e)}",
3072                                level="ERROR",
3073                                status_message=str(e),
3074                            )
3075                            raise
3076
3077                    task_span.update(output=output)
3078
3079                span.update(
3080                    input=input_data,
3081                    output=output,
3082                    metadata=final_observation_metadata,
3083                )
3084
3085            except Exception as e:
3086                span.update(
3087                    output=f"Error: {str(e)}", level="ERROR", status_message=str(e)
3088                )
3089                raise e
3090
3091            evaluations: List[Evaluation] = []
3092            failed_evaluator_count = 0
3093            eval_metadata = (
3094                item.get("metadata")
3095                if isinstance(item, dict)
3096                else getattr(item, "metadata", None)
3097            )
3098
3099            if len(evaluators) > 0:
3100                with _propagate_attributes(experiment=propagated_experiment_attributes):
3101                    with span.start_as_current_observation(
3102                        name="experiment-item-evaluation", as_type="span"
3103                    ) as evaluation_span:
3104                        for evaluator_index, evaluator in enumerate(evaluators):
3105                            evaluator_name = _get_evaluator_name(evaluator)
3106                            evaluator_input = {
3107                                "input": input_data,
3108                                "output": output,
3109                                "expected_output": expected_output,
3110                                "metadata": eval_metadata,
3111                            }
3112
3113                            with evaluation_span.start_as_current_observation(
3114                                name=evaluator_name,
3115                                as_type="evaluator",
3116                                input=evaluator_input,
3117                                metadata={
3118                                    "evaluator_kind": "item",
3119                                    "evaluator_index": evaluator_index,
3120                                },
3121                            ) as evaluator_span:
3122                                try:
3123                                    eval_results = await _run_evaluator(
3124                                        evaluator,
3125                                        _raise_on_error=True,
3126                                        **evaluator_input,
3127                                    )
3128                                    evaluator_span.update(
3129                                        output=_serialize_evaluations(eval_results)
3130                                    )
3131                                except Exception as e:
3132                                    failed_evaluator_count += 1
3133                                    evaluator_span.update(
3134                                        output={"error": str(e)},
3135                                        level="ERROR",
3136                                        status_message=str(e),
3137                                    )
3138                                    langfuse_logger.error("Evaluator failed: %s", e)
3139                                    continue
3140
3141                            evaluations.extend(eval_results)
3142
3143                            for evaluation in eval_results:
3144                                try:
3145                                    self.create_score(
3146                                        trace_id=trace_id,
3147                                        observation_id=task_span.id,
3148                                        name=evaluation.name,
3149                                        value=evaluation.value,  # type: ignore
3150                                        comment=evaluation.comment,
3151                                        metadata=evaluation.metadata,
3152                                        config_id=evaluation.config_id,
3153                                        data_type=evaluation.data_type,  # type: ignore
3154                                    )
3155                                except Exception as e:
3156                                    langfuse_logger.error(
3157                                        "Failed to store evaluation: %s", e
3158                                    )
3159
3160                        if composite_evaluator and evaluations:
3161                            composite_evaluator_name = _get_evaluator_name(
3162                                composite_evaluator
3163                            )
3164                            composite_input = {
3165                                "input": input_data,
3166                                "output": output,
3167                                "expected_output": expected_output,
3168                                "metadata": eval_metadata,
3169                                "evaluations": _serialize_evaluations(evaluations),
3170                            }
3171
3172                            with evaluation_span.start_as_current_observation(
3173                                name=composite_evaluator_name,
3174                                as_type="evaluator",
3175                                input=composite_input,
3176                                metadata={"evaluator_kind": "composite"},
3177                            ) as composite_evaluator_span:
3178                                try:
3179                                    result = composite_evaluator(
3180                                        input=input_data,
3181                                        output=output,
3182                                        expected_output=expected_output,
3183                                        metadata=eval_metadata,
3184                                        evaluations=evaluations,
3185                                    )
3186
3187                                    if asyncio.iscoroutine(result):
3188                                        result = await result
3189
3190                                    composite_evals = _normalize_evaluator_result(
3191                                        result
3192                                    )
3193
3194                                    composite_evaluator_span.update(
3195                                        output=_serialize_evaluations(composite_evals)
3196                                    )
3197                                except Exception as e:
3198                                    failed_evaluator_count += 1
3199                                    composite_evaluator_span.update(
3200                                        output={"error": str(e)},
3201                                        level="ERROR",
3202                                        status_message=str(e),
3203                                    )
3204                                    langfuse_logger.error(
3205                                        "Composite evaluator failed: %s", e
3206                                    )
3207                                    composite_evals = []
3208
3209                            for composite_evaluation in composite_evals:
3210                                evaluations.append(composite_evaluation)
3211                                try:
3212                                    self.create_score(
3213                                        trace_id=trace_id,
3214                                        observation_id=task_span.id,
3215                                        name=composite_evaluation.name,
3216                                        value=composite_evaluation.value,  # type: ignore
3217                                        comment=composite_evaluation.comment,
3218                                        metadata=composite_evaluation.metadata,
3219                                        config_id=composite_evaluation.config_id,
3220                                        data_type=composite_evaluation.data_type,  # type: ignore
3221                                    )
3222                                except Exception as e:
3223                                    langfuse_logger.error(
3224                                        "Failed to store composite evaluation: %s", e
3225                                    )
3226
3227                        evaluation_span.update(
3228                            output={
3229                                "evaluator_count": len(evaluators)
3230                                + (1 if composite_evaluator else 0),
3231                                "evaluation_count": len(evaluations),
3232                                "failed_evaluator_count": failed_evaluator_count,
3233                                "skipped_evaluator_count": (
3234                                    1 if composite_evaluator and not evaluations else 0
3235                                ),
3236                            }
3237                        )
3238
3239            return ExperimentItemResult(
3240                item=item,
3241                output=output,
3242                evaluations=evaluations,
3243                trace_id=trace_id,
3244                dataset_run_id=dataset_run_id,
3245            )
3246
3247    def _create_experiment_run_name(
3248        self, *, name: Optional[str] = None, run_name: Optional[str] = None
3249    ) -> str:
3250        if run_name:
3251            return run_name
3252
3253        iso_timestamp = _get_timestamp().isoformat().replace("+00:00", "Z")
3254
3255        return f"{name} - {iso_timestamp}"
3256
3257    def run_batched_evaluation(
3258        self,
3259        *,
3260        scope: Literal["traces", "observations"],
3261        mapper: MapperFunction,
3262        filter: Optional[str] = None,
3263        fetch_batch_size: int = 50,
3264        fetch_trace_fields: Optional[str] = None,
3265        max_items: Optional[int] = None,
3266        max_retries: int = 3,
3267        evaluators: List[EvaluatorFunction],
3268        composite_evaluator: Optional[CompositeEvaluatorFunction] = None,
3269        max_concurrency: int = 5,
3270        metadata: Optional[Dict[str, Any]] = None,
3271        _add_observation_scores_to_trace: bool = False,
3272        _additional_trace_tags: Optional[List[str]] = None,
3273        resume_from: Optional[BatchEvaluationResumeToken] = None,
3274        verbose: bool = False,
3275    ) -> BatchEvaluationResult:
3276        """Fetch traces or observations and run evaluations on each item.
3277
3278        This method provides a powerful way to evaluate existing data in Langfuse at scale.
3279        It fetches items based on filters, transforms them using a mapper function, runs
3280        evaluators on each item, and creates scores that are linked back to the original
3281        entities. This is ideal for:
3282
3283        - Running evaluations on production traces after deployment
3284        - Backtesting new evaluation metrics on historical data
3285        - Batch scoring of observations for quality monitoring
3286        - Periodic evaluation runs on recent data
3287
3288        The method uses a streaming/pipeline approach to process items in batches, making
3289        it memory-efficient for large datasets. It includes comprehensive error handling,
3290        retry logic, and resume capability for long-running evaluations.
3291
3292        Args:
3293            scope: The type of items to evaluate. Must be one of:
3294                - "traces": Evaluate complete traces with all their observations
3295                - "observations": Evaluate individual observations (spans, generations, events)
3296            mapper: Function that transforms API response objects into evaluator inputs.
3297                Receives a trace/observation object and returns an EvaluatorInputs
3298                instance with input, output, expected_output, and metadata fields.
3299                Can be sync or async.
3300            evaluators: List of evaluation functions to run on each item. Each evaluator
3301                receives the mapped inputs and returns Evaluation object(s). Evaluator
3302                failures are logged but don't stop the batch evaluation.
3303            filter: Optional JSON filter string for querying items (same format as Langfuse API). Examples:
3304                - '{"tags": ["production"]}'
3305                - '{"user_id": "user123", "timestamp": {"operator": ">", "value": "2024-01-01"}}'
3306                Default: None (fetches all items).
3307            fetch_batch_size: Number of items to fetch per API call and hold in memory.
3308                Larger values may be faster but use more memory. Default: 50.
3309            fetch_trace_fields: Comma-separated list of fields to include when fetching traces. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. Only relevant if scope is 'traces'.
3310            max_items: Maximum total number of items to process. If None, processes all
3311                items matching the filter. Useful for testing or limiting evaluation runs.
3312                Default: None (process all).
3313            max_concurrency: Maximum number of items to evaluate concurrently. Controls
3314                parallelism and resource usage. Default: 5.
3315            composite_evaluator: Optional function that creates a composite score from
3316                item-level evaluations. Receives the original item and its evaluations,
3317                returns a single Evaluation. Useful for weighted averages or combined metrics.
3318                Default: None.
3319            metadata: Optional metadata dict to add to all created scores. Useful for
3320                tracking evaluation runs, versions, or other context. Default: None.
3321            max_retries: Maximum number of retry attempts for failed batch fetches.
3322                Uses exponential backoff (1s, 2s, 4s). Default: 3.
3323            verbose: If True, logs progress information to console. Useful for monitoring
3324                long-running evaluations. Default: False.
3325            resume_from: Optional resume token from a previous incomplete run. Allows
3326                continuing evaluation after interruption or failure. Default: None.
3327
3328
3329        Returns:
3330            BatchEvaluationResult containing:
3331                - total_items_fetched: Number of items fetched from API
3332                - total_items_processed: Number of items successfully evaluated
3333                - total_items_failed: Number of items that failed evaluation
3334                - total_scores_created: Scores created by item-level evaluators
3335                - total_composite_scores_created: Scores created by composite evaluator
3336                - total_evaluations_failed: Individual evaluator failures
3337                - evaluator_stats: Per-evaluator statistics (success rate, scores created)
3338                - resume_token: Token for resuming if incomplete (None if completed)
3339                - completed: True if all items processed
3340                - duration_seconds: Total execution time
3341                - failed_item_ids: IDs of items that failed
3342                - error_summary: Error types and counts
3343                - has_more_items: True if max_items reached but more exist
3344
3345        Raises:
3346            ValueError: If invalid scope is provided.
3347
3348        Examples:
3349            Basic trace evaluation:
3350            ```python
3351            from langfuse import Langfuse, EvaluatorInputs, Evaluation
3352
3353            client = Langfuse()
3354
3355            # Define mapper to extract fields from traces
3356            def trace_mapper(trace):
3357                return EvaluatorInputs(
3358                    input=trace.input,
3359                    output=trace.output,
3360                    expected_output=None,
3361                    metadata={"trace_id": trace.id}
3362                )
3363
3364            # Define evaluator
3365            def length_evaluator(*, input, output, expected_output, metadata):
3366                return Evaluation(
3367                    name="output_length",
3368                    value=len(output) if output else 0
3369                )
3370
3371            # Run batch evaluation
3372            result = client.run_batched_evaluation(
3373                scope="traces",
3374                mapper=trace_mapper,
3375                evaluators=[length_evaluator],
3376                filter='{"tags": ["production"]}',
3377                max_items=1000,
3378                verbose=True
3379            )
3380
3381            print(f"Processed {result.total_items_processed} traces")
3382            print(f"Created {result.total_scores_created} scores")
3383            ```
3384
3385            Evaluation with composite scorer:
3386            ```python
3387            def accuracy_evaluator(*, input, output, expected_output, metadata):
3388                # ... evaluation logic
3389                return Evaluation(name="accuracy", value=0.85)
3390
3391            def relevance_evaluator(*, input, output, expected_output, metadata):
3392                # ... evaluation logic
3393                return Evaluation(name="relevance", value=0.92)
3394
3395            def composite_evaluator(*, item, evaluations):
3396                # Weighted average of evaluations
3397                weights = {"accuracy": 0.6, "relevance": 0.4}
3398                total = sum(
3399                    e.value * weights.get(e.name, 0)
3400                    for e in evaluations
3401                    if isinstance(e.value, (int, float))
3402                )
3403                return Evaluation(
3404                    name="composite_score",
3405                    value=total,
3406                    comment=f"Weighted average of {len(evaluations)} metrics"
3407                )
3408
3409            result = client.run_batched_evaluation(
3410                scope="traces",
3411                mapper=trace_mapper,
3412                evaluators=[accuracy_evaluator, relevance_evaluator],
3413                composite_evaluator=composite_evaluator,
3414                filter='{"user_id": "important_user"}',
3415                verbose=True
3416            )
3417            ```
3418
3419            Handling incomplete runs with resume:
3420            ```python
3421            # Initial run that may fail or timeout
3422            result = client.run_batched_evaluation(
3423                scope="observations",
3424                mapper=obs_mapper,
3425                evaluators=[my_evaluator],
3426                max_items=10000,
3427                verbose=True
3428            )
3429
3430            # Check if incomplete
3431            if not result.completed and result.resume_token:
3432                print(f"Processed {result.resume_token.items_processed} items before interruption")
3433
3434                # Resume from where it left off
3435                result = client.run_batched_evaluation(
3436                    scope="observations",
3437                    mapper=obs_mapper,
3438                    evaluators=[my_evaluator],
3439                    resume_from=result.resume_token,
3440                    verbose=True
3441                )
3442
3443            print(f"Total items processed: {result.total_items_processed}")
3444            ```
3445
3446            Monitoring evaluator performance:
3447            ```python
3448            result = client.run_batched_evaluation(...)
3449
3450            for stats in result.evaluator_stats:
3451                success_rate = stats.successful_runs / stats.total_runs
3452                print(f"{stats.name}:")
3453                print(f"  Success rate: {success_rate:.1%}")
3454                print(f"  Scores created: {stats.total_scores_created}")
3455
3456                if stats.failed_runs > 0:
3457                    print(f"  ⚠️  Failed {stats.failed_runs} times")
3458            ```
3459
3460        Note:
3461            - Evaluator failures are logged but don't stop the batch evaluation
3462            - Individual item failures are tracked but don't stop processing
3463            - Fetch failures are retried with exponential backoff
3464            - All scores are automatically flushed to Langfuse at the end
3465            - The resume mechanism uses timestamp-based filtering to avoid duplicates
3466        """
3467        runner = BatchEvaluationRunner(self)
3468
3469        return cast(
3470            BatchEvaluationResult,
3471            run_async_safely(
3472                runner.run_async(
3473                    scope=scope,
3474                    mapper=mapper,
3475                    evaluators=evaluators,
3476                    filter=filter,
3477                    fetch_batch_size=fetch_batch_size,
3478                    fetch_trace_fields=fetch_trace_fields,
3479                    max_items=max_items,
3480                    max_concurrency=max_concurrency,
3481                    composite_evaluator=composite_evaluator,
3482                    metadata=metadata,
3483                    _add_observation_scores_to_trace=_add_observation_scores_to_trace,
3484                    _additional_trace_tags=_additional_trace_tags,
3485                    max_retries=max_retries,
3486                    verbose=verbose,
3487                    resume_from=resume_from,
3488                )
3489            ),
3490        )
3491
3492    def auth_check(self) -> bool:
3493        """Check if the provided credentials (public and secret key) are valid.
3494
3495        Raises:
3496            Exception: If no projects were found for the provided credentials.
3497
3498        Note:
3499            This method is blocking. It is discouraged to use it in production code.
3500        """
3501        try:
3502            projects = self.api.projects.get()
3503            langfuse_logger.debug(
3504                "Auth check successful, found %s projects", len(projects.data)
3505            )
3506            if len(projects.data) == 0:
3507                raise Exception(
3508                    "Auth check failed, no project found for the keys provided."
3509                )
3510            return True
3511
3512        except AttributeError as e:
3513            langfuse_logger.warning(
3514                "Auth check failed: Client not properly initialized. Error: %s", e
3515            )
3516            return False
3517
3518        except Error as e:
3519            handle_fern_exception(e)
3520            raise e
3521
3522    def create_dataset(
3523        self,
3524        *,
3525        name: str,
3526        description: Optional[str] = None,
3527        metadata: Optional[Any] = None,
3528        input_schema: Optional[Any] = None,
3529        expected_output_schema: Optional[Any] = None,
3530    ) -> Dataset:
3531        """Create a dataset with the given name on Langfuse.
3532
3533        Args:
3534            name: Name of the dataset to create.
3535            description: Description of the dataset. Defaults to None.
3536            metadata: Additional metadata. Defaults to None.
3537            input_schema: JSON Schema for validating dataset item inputs. When set, all new items will be validated against this schema.
3538            expected_output_schema: JSON Schema for validating dataset item expected outputs. When set, all new items will be validated against this schema.
3539
3540        Returns:
3541            Dataset: The created dataset as returned by the Langfuse API.
3542        """
3543        try:
3544            langfuse_logger.debug("Creating datasets %s", name)
3545
3546            result = self.api.datasets.create(
3547                name=name,
3548                description=description,
3549                metadata=metadata,
3550                input_schema=input_schema,
3551                expected_output_schema=expected_output_schema,
3552            )
3553
3554            return cast(Dataset, result)
3555
3556        except Error as e:
3557            handle_fern_exception(e)
3558            raise e
3559
3560    def create_dataset_item(
3561        self,
3562        *,
3563        dataset_name: str,
3564        input: Optional[Any] = None,
3565        expected_output: Optional[Any] = None,
3566        metadata: Optional[Any] = None,
3567        source_trace_id: Optional[str] = None,
3568        source_observation_id: Optional[str] = None,
3569        status: Optional[DatasetStatus] = None,
3570        id: Optional[str] = None,
3571    ) -> DatasetItem:
3572        """Create a dataset item.
3573
3574        Upserts if an item with id already exists.
3575
3576        Args:
3577            dataset_name: Name of the dataset in which the dataset item should be created.
3578            input: Input data. Defaults to None. Can contain any dict, list or scalar.
3579            expected_output: Expected output data. Defaults to None. Can contain any dict, list or scalar.
3580            metadata: Additional metadata. Defaults to None. Can contain any dict, list or scalar.
3581            source_trace_id: Id of the source trace. Defaults to None.
3582            source_observation_id: Id of the source observation. Defaults to None.
3583            status: Status of the dataset item. Defaults to ACTIVE for newly created items.
3584            id: Id of the dataset item. Defaults to None. Provide your own id if you want to dedupe dataset items. Id needs to be globally unique and cannot be reused across datasets.
3585
3586        Returns:
3587            DatasetItem: The created dataset item as returned by the Langfuse API.
3588
3589        Example:
3590            ```python
3591            from langfuse import Langfuse
3592
3593            langfuse = Langfuse()
3594
3595            # Uploading items to the Langfuse dataset named "capital_cities"
3596            langfuse.create_dataset_item(
3597                dataset_name="capital_cities",
3598                input={"input": {"country": "Italy"}},
3599                expected_output={"expected_output": "Rome"},
3600                metadata={"foo": "bar"}
3601            )
3602            ```
3603        """
3604        try:
3605            langfuse_logger.debug("Creating dataset item for dataset %s", dataset_name)
3606
3607            # Media uploads must reference the (dataset, item) they belong to, and
3608            # the item need not exist yet — so settle on the item id up front and
3609            # reuse it for the create call below.
3610            item_id = id if id is not None else str(uuid.uuid4())
3611
3612            # Single pass per field: swap each LangfuseMedia for its reference
3613            # string (derived from content, not the upload) and collect the media
3614            # still to upload, deduped by media id and tagged with its field.
3615            pending_media: Dict[str, Tuple[LangfuseMedia, str]] = {}
3616            input = self._process_dataset_item_media(
3617                data=input,
3618                pending_media=pending_media,
3619                field=DatasetItemMediaReferenceField.INPUT.value,
3620            )
3621            expected_output = self._process_dataset_item_media(
3622                data=expected_output,
3623                pending_media=pending_media,
3624                field=DatasetItemMediaReferenceField.EXPECTED_OUTPUT.value,
3625            )
3626            metadata = self._process_dataset_item_media(
3627                data=metadata,
3628                pending_media=pending_media,
3629                field=DatasetItemMediaReferenceField.METADATA.value,
3630            )
3631
3632            # The upload needs the dataset id, but the create API only takes the
3633            # name. Resolve it once, and only when there is actually media to
3634            # upload — a plain item pays no extra datasets.get round-trip.
3635            if pending_media:
3636                assert self._resources is not None
3637                dataset_id = self.api.datasets.get(self._url_encode(dataset_name)).id
3638                for media, field in pending_media.values():
3639                    self._resources._media_manager._upload_media_sync(
3640                        media=media,
3641                        dataset_id=dataset_id,
3642                        dataset_item_id=item_id,
3643                        field=field,
3644                    )
3645
3646            result = self.api.dataset_items.create(
3647                dataset_name=dataset_name,
3648                input=input,
3649                expected_output=expected_output,
3650                metadata=metadata,
3651                source_trace_id=source_trace_id,
3652                source_observation_id=source_observation_id,
3653                status=status,
3654                id=item_id,
3655            )
3656
3657            return cast(DatasetItem, result)
3658        except Error as e:
3659            handle_fern_exception(e)
3660            raise e
3661
3662    def _process_dataset_item_media(
3663        self,
3664        *,
3665        data: Any,
3666        pending_media: Dict[str, Tuple[LangfuseMedia, str]],
3667        field: str,
3668    ) -> Any:
3669        """Swap each ``LangfuseMedia`` for its reference string in ``data``.
3670
3671        Each replaced media is recorded in ``pending_media`` (keyed by media id,
3672        so the same media across fields uploads once) for the caller to upload
3673        after the dataset id has been resolved.
3674        """
3675        if self._resources is None:
3676            return data
3677
3678        max_levels = 10
3679
3680        def _process_data_recursively(
3681            data: Any, level: int, ancestor_container_ids: set[int]
3682        ) -> Any:
3683            if isinstance(data, LangfuseMedia):
3684                reference_string = data._reference_string
3685                media_id = data._media_id
3686                if reference_string is None or media_id is None:
3687                    raise ValueError(
3688                        "Cannot create dataset item with invalid LangfuseMedia."
3689                    )
3690                # First field a media appears in wins; later duplicates dedupe.
3691                pending_media.setdefault(media_id, (data, field))
3692                return reference_string
3693
3694            if isinstance(data, LangfuseMediaReference):
3695                return data.reference_string if data.reference_string else data
3696
3697            # Tuples are intentionally excluded: namedtuple subclasses can't be
3698            # rebuilt from an iterable, so media inside them is left untouched.
3699            if not isinstance(data, (list, set, frozenset, dict)):
3700                return data
3701
3702            # Container ids only protect against recursive cycles.
3703            data_id = id(data)
3704            if data_id in ancestor_container_ids or level > max_levels:
3705                return data
3706
3707            next_ancestor_container_ids = ancestor_container_ids | {data_id}
3708
3709            if isinstance(data, (list, set, frozenset)):
3710                processed = (
3711                    _process_data_recursively(
3712                        item, level + 1, next_ancestor_container_ids
3713                    )
3714                    for item in data
3715                )
3716                return type(data)(processed)
3717
3718            return {
3719                key: _process_data_recursively(
3720                    value, level + 1, next_ancestor_container_ids
3721                )
3722                for key, value in data.items()
3723            }
3724
3725        return _process_data_recursively(data, 1, set())
3726
3727    def _hydrate_dataset_item_media_references(self, item: DatasetItem) -> DatasetItem:
3728        media_references = item.media_references or []
3729        if not media_references:
3730            return item
3731
3732        # Map the API enum member to the snake_case model attribute so this keeps
3733        # working regardless of the enum's wire value (e.g. "expectedOutput").
3734        attr_by_field = {
3735            DatasetItemMediaReferenceField.INPUT: "input",
3736            DatasetItemMediaReferenceField.EXPECTED_OUTPUT: "expected_output",
3737            DatasetItemMediaReferenceField.METADATA: "metadata",
3738        }
3739        hydrated_fields = {
3740            "input": item.input,
3741            "expected_output": item.expected_output,
3742            "metadata": item.metadata,
3743        }
3744
3745        for media_reference in media_references:
3746            media = media_reference.media
3747            field = attr_by_field.get(media_reference.field)
3748            if field is None:
3749                continue
3750
3751            replacement = LangfuseMediaReference(
3752                media_id=media.media_id,
3753                content_type=media.content_type,
3754                url=media.url,
3755                url_expiry=media.url_expiry,
3756                content_length=media.content_length,
3757                reference_string=media_reference.reference_string,
3758            )
3759            hydrated_fields[field] = self._replace_json_path_value(
3760                value=hydrated_fields[field],
3761                path=media_reference.json_path,
3762                replacement=replacement,
3763            )
3764
3765        return item.model_copy(
3766            update={
3767                "input": hydrated_fields["input"],
3768                "expected_output": hydrated_fields["expected_output"],
3769                "metadata": hydrated_fields["metadata"],
3770            }
3771        )
3772
3773    def _replace_json_path_value(
3774        self, *, value: Any, path: str, replacement: LangfuseMediaReference
3775    ) -> Any:
3776        try:
3777            return json_path.set_value_at_path(value, path, replacement)
3778        except Exception as e:
3779            langfuse_logger.warning(
3780                "Failed to hydrate dataset media reference at JSONPath %s",
3781                path,
3782                exc_info=e,
3783            )
3784
3785            return value
3786
3787    def resolve_media_references(
3788        self,
3789        *,
3790        obj: Any,
3791        resolve_with: Literal["base64_data_uri"],
3792        max_depth: int = 10,
3793        content_fetch_timeout_seconds: int = 5,
3794    ) -> Any:
3795        """Replace media reference strings in an object with base64 data URIs.
3796
3797        This method recursively traverses an object (up to max_depth) looking for media reference strings
3798        in the format "@@@langfuseMedia:...@@@". When found, it (synchronously) fetches the actual media content using
3799        the provided Langfuse client and replaces the reference string with a base64 data URI.
3800
3801        If fetching media content fails for a reference string, a warning is logged and the reference
3802        string is left unchanged.
3803
3804        Args:
3805            obj: The object to process. Can be a primitive value, array, or nested object.
3806                If the object has a __dict__ attribute, a dict will be returned instead of the original object type.
3807            resolve_with: The representation of the media content to replace the media reference string with.
3808                Currently only "base64_data_uri" is supported.
3809            max_depth: int: The maximum depth to traverse the object. Default is 10.
3810            content_fetch_timeout_seconds: int: The timeout in seconds for fetching media content. Default is 5.
3811
3812        Returns:
3813            A deep copy of the input object with all media references replaced with base64 data URIs where possible.
3814            If the input object has a __dict__ attribute, a dict will be returned instead of the original object type.
3815
3816        Example:
3817            obj = {
3818                "image": "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@",
3819                "nested": {
3820                    "pdf": "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@"
3821                }
3822            }
3823
3824            result = await LangfuseMedia.resolve_media_references(obj, langfuse_client)
3825
3826            # Result:
3827            # {
3828            #     "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
3829            #     "nested": {
3830            #         "pdf": "data:application/pdf;base64,JVBERi0xLjcK..."
3831            #     }
3832            # }
3833        """
3834        return LangfuseMedia.resolve_media_references(
3835            langfuse_client=self,
3836            obj=obj,
3837            resolve_with=resolve_with,
3838            max_depth=max_depth,
3839            content_fetch_timeout_seconds=content_fetch_timeout_seconds,
3840        )
3841
3842    @overload
3843    def get_prompt(
3844        self,
3845        name: str,
3846        *,
3847        version: Optional[int] = None,
3848        label: Optional[str] = None,
3849        type: Literal["chat"],
3850        cache_ttl_seconds: Optional[int] = None,
3851        fallback: Optional[List[ChatMessageDict]] = None,
3852        max_retries: Optional[int] = None,
3853        fetch_timeout_seconds: Optional[int] = None,
3854    ) -> ChatPromptClient: ...
3855
3856    @overload
3857    def get_prompt(
3858        self,
3859        name: str,
3860        *,
3861        version: Optional[int] = None,
3862        label: Optional[str] = None,
3863        type: Literal["text"] = "text",
3864        cache_ttl_seconds: Optional[int] = None,
3865        fallback: Optional[str] = None,
3866        max_retries: Optional[int] = None,
3867        fetch_timeout_seconds: Optional[int] = None,
3868    ) -> TextPromptClient: ...
3869
3870    def get_prompt(
3871        self,
3872        name: str,
3873        *,
3874        version: Optional[int] = None,
3875        label: Optional[str] = None,
3876        type: Literal["chat", "text"] = "text",
3877        cache_ttl_seconds: Optional[int] = None,
3878        fallback: Union[Optional[List[ChatMessageDict]], Optional[str]] = None,
3879        max_retries: Optional[int] = None,
3880        fetch_timeout_seconds: Optional[int] = None,
3881    ) -> PromptClient:
3882        """Get a prompt.
3883
3884        This method attempts to fetch the requested prompt from the local cache. If the prompt is not found
3885        in the cache or if the cached prompt has expired, it will try to fetch the prompt from the server again
3886        and update the cache. If fetching the new prompt fails, and there is an expired prompt in the cache, it will
3887        return the expired prompt as a fallback.
3888
3889        Args:
3890            name (str): The name of the prompt to retrieve.
3891
3892        Keyword Args:
3893            version (Optional[int]): The version of the prompt to retrieve. If no label and version is specified, the `production` label is returned. Specify either version or label, not both.
3894            label: Optional[str]: The label of the prompt to retrieve. If no label and version is specified, the `production` label is returned. Specify either version or label, not both.
3895            cache_ttl_seconds: Optional[int]: Time-to-live in seconds for caching the prompt. Must be specified as a
3896            keyword argument. If not set, defaults to 60 seconds. Disables caching if set to 0.
3897            type: Literal["chat", "text"]: The type of the prompt to retrieve. Defaults to "text".
3898            fallback: Union[Optional[List[ChatMessageDict]], Optional[str]]: The prompt string to return if fetching the prompt fails. Important on the first call where no cached prompt is available. Follows Langfuse prompt formatting with double curly braces for variables. Defaults to None.
3899            max_retries: Optional[int]: The maximum number of retries in case of API/network errors. Defaults to 2. The maximum value is 4. Retries have an exponential backoff with a maximum delay of 10 seconds.
3900            fetch_timeout_seconds: Optional[int]: The timeout in milliseconds for fetching the prompt. Defaults to the default timeout set on the SDK, which is 5 seconds per default.
3901
3902        Returns:
3903            The prompt object retrieved from the cache or directly fetched if not cached or expired of type
3904            - TextPromptClient, if type argument is 'text'.
3905            - ChatPromptClient, if type argument is 'chat'.
3906
3907        Raises:
3908            Exception: Propagates any exceptions raised during the fetching of a new prompt, unless there is an
3909            expired prompt in the cache, in which case it logs a warning and returns the expired prompt.
3910        """
3911        if self._resources is None:
3912            raise Error(
3913                "SDK is not correctly initialized. Check the init logs for more details."
3914            )
3915        if version is not None and label is not None:
3916            raise ValueError("Cannot specify both version and label at the same time.")
3917
3918        if not name:
3919            raise ValueError("Prompt name cannot be empty.")
3920
3921        cache_key = PromptCache.generate_cache_key(name, version=version, label=label)
3922        bounded_max_retries = self._get_bounded_max_retries(
3923            max_retries, default_max_retries=2, max_retries_upper_bound=4
3924        )
3925
3926        langfuse_logger.debug("Getting prompt '%s'", cache_key)
3927        cached_prompt = self._resources.prompt_cache.get(cache_key)
3928
3929        if cached_prompt is None or cache_ttl_seconds == 0:
3930            langfuse_logger.debug(
3931                "Prompt '%s' not found in cache or caching disabled.", cache_key
3932            )
3933            try:
3934                return self._fetch_prompt_and_update_cache(
3935                    name,
3936                    version=version,
3937                    label=label,
3938                    ttl_seconds=cache_ttl_seconds,
3939                    max_retries=bounded_max_retries,
3940                    fetch_timeout_seconds=fetch_timeout_seconds,
3941                )
3942            except Exception as e:
3943                if fallback:
3944                    langfuse_logger.warning(
3945                        "Returning fallback prompt for '%s' due to fetch error: %s",
3946                        cache_key,
3947                        e,
3948                    )
3949
3950                    fallback_client_args: Dict[str, Any] = {
3951                        "name": name,
3952                        "prompt": fallback,
3953                        "type": type,
3954                        "version": version or 0,
3955                        "config": {},
3956                        "labels": [label] if label else [],
3957                        "tags": [],
3958                    }
3959
3960                    if type == "text":
3961                        return TextPromptClient(
3962                            prompt=Prompt_Text(**fallback_client_args),
3963                            is_fallback=True,
3964                        )
3965
3966                    if type == "chat":
3967                        return ChatPromptClient(
3968                            prompt=Prompt_Chat(**fallback_client_args),
3969                            is_fallback=True,
3970                        )
3971
3972                raise e
3973
3974        if cached_prompt.is_expired():
3975            langfuse_logger.debug("Stale prompt '%s' found in cache.", cache_key)
3976            try:
3977                # refresh prompt in background thread, refresh_prompt deduplicates tasks
3978                langfuse_logger.debug(
3979                    "Refreshing prompt '%s' in background.", cache_key
3980                )
3981
3982                def refresh_task() -> None:
3983                    self._fetch_prompt_and_update_cache(
3984                        name,
3985                        version=version,
3986                        label=label,
3987                        ttl_seconds=cache_ttl_seconds,
3988                        max_retries=bounded_max_retries,
3989                        fetch_timeout_seconds=fetch_timeout_seconds,
3990                    )
3991
3992                self._resources.prompt_cache.add_refresh_prompt_task_if_current(
3993                    cache_key,
3994                    cached_prompt,
3995                    refresh_task,
3996                )
3997                langfuse_logger.debug(
3998                    "Returning stale prompt '%s' from cache.", cache_key
3999                )
4000                # return stale prompt
4001                return cached_prompt.value
4002
4003            except Exception as e:
4004                langfuse_logger.warning(
4005                    "Error when refreshing cached prompt '%s', returning cached version. "
4006                    "Error: %s",
4007                    cache_key,
4008                    e,
4009                )
4010                # creation of refresh prompt task failed, return stale prompt
4011                return cached_prompt.value
4012
4013        return cached_prompt.value
4014
4015    def _fetch_prompt_and_update_cache(
4016        self,
4017        name: str,
4018        *,
4019        version: Optional[int] = None,
4020        label: Optional[str] = None,
4021        ttl_seconds: Optional[int] = None,
4022        max_retries: int,
4023        fetch_timeout_seconds: Optional[int],
4024    ) -> PromptClient:
4025        cache_key = PromptCache.generate_cache_key(name, version=version, label=label)
4026        langfuse_logger.debug("Fetching prompt '%s' from server...", cache_key)
4027
4028        try:
4029
4030            @backoff.on_exception(
4031                backoff.constant, Exception, max_tries=max_retries + 1, logger=None
4032            )
4033            def fetch_prompts() -> Any:
4034                return self.api.prompts.get(
4035                    self._url_encode(name),
4036                    version=version,
4037                    label=label,
4038                    request_options={
4039                        "timeout_in_seconds": fetch_timeout_seconds,
4040                    }
4041                    if fetch_timeout_seconds is not None
4042                    else None,
4043                )
4044
4045            prompt_response = fetch_prompts()
4046
4047            prompt: PromptClient
4048            if prompt_response.type == "chat":
4049                prompt = ChatPromptClient(prompt_response)
4050            else:
4051                prompt = TextPromptClient(prompt_response)
4052
4053            if self._resources is not None:
4054                self._resources.prompt_cache.set(cache_key, prompt, ttl_seconds)
4055
4056            return prompt
4057
4058        except NotFoundError as not_found_error:
4059            langfuse_logger.warning(
4060                "Prompt '%s' not found during refresh, evicting from cache.", cache_key
4061            )
4062            if self._resources is not None:
4063                self._resources.prompt_cache.delete(cache_key)
4064            raise not_found_error
4065
4066        except Exception as e:
4067            langfuse_logger.error(
4068                "Error while fetching prompt '%s': %s", cache_key, str(e)
4069            )
4070            raise e
4071
4072    def _get_bounded_max_retries(
4073        self,
4074        max_retries: Optional[int],
4075        *,
4076        default_max_retries: int = 2,
4077        max_retries_upper_bound: int = 4,
4078    ) -> int:
4079        if max_retries is None:
4080            return default_max_retries
4081
4082        bounded_max_retries = min(
4083            max(max_retries, 0),
4084            max_retries_upper_bound,
4085        )
4086
4087        return bounded_max_retries
4088
4089    @overload
4090    def create_prompt(
4091        self,
4092        *,
4093        name: str,
4094        prompt: List[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict]],
4095        labels: List[str] = [],
4096        tags: Optional[List[str]] = None,
4097        type: Optional[Literal["chat"]],
4098        config: Optional[Any] = None,
4099        commit_message: Optional[str] = None,
4100    ) -> ChatPromptClient: ...
4101
4102    @overload
4103    def create_prompt(
4104        self,
4105        *,
4106        name: str,
4107        prompt: str,
4108        labels: List[str] = [],
4109        tags: Optional[List[str]] = None,
4110        type: Optional[Literal["text"]] = "text",
4111        config: Optional[Any] = None,
4112        commit_message: Optional[str] = None,
4113    ) -> TextPromptClient: ...
4114
4115    def create_prompt(
4116        self,
4117        *,
4118        name: str,
4119        prompt: Union[
4120            str, List[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict]]
4121        ],
4122        labels: List[str] = [],
4123        tags: Optional[List[str]] = None,
4124        type: Optional[Literal["chat", "text"]] = "text",
4125        config: Optional[Any] = None,
4126        commit_message: Optional[str] = None,
4127    ) -> PromptClient:
4128        """Create a new prompt in Langfuse.
4129
4130        Keyword Args:
4131            name : The name of the prompt to be created.
4132            prompt : The content of the prompt to be created.
4133            is_active [DEPRECATED] : A flag indicating whether the prompt is active or not. This is deprecated and will be removed in a future release. Please use the 'production' label instead.
4134            labels: The labels of the prompt. Defaults to None. To create a default-served prompt, add the 'production' label.
4135            tags: The tags of the prompt. Defaults to None. Will be applied to all versions of the prompt.
4136            config: Additional structured data to be saved with the prompt. Defaults to None.
4137            type: The type of the prompt to be created. "chat" vs. "text". Defaults to "text".
4138            commit_message: Optional string describing the change.
4139
4140        Returns:
4141            TextPromptClient: The prompt if type argument is 'text'.
4142            ChatPromptClient: The prompt if type argument is 'chat'.
4143        """
4144        try:
4145            langfuse_logger.debug("Creating prompt name=%r, labels=%r", name, labels)
4146
4147            if type == "chat":
4148                if not isinstance(prompt, list):
4149                    raise ValueError(
4150                        "For 'chat' type, 'prompt' must be a list of chat messages with role and content attributes."
4151                    )
4152                request: Union[CreateChatPromptRequest, CreateTextPromptRequest] = (
4153                    CreateChatPromptRequest(
4154                        name=name,
4155                        prompt=cast(Any, prompt),
4156                        labels=labels,
4157                        tags=tags,
4158                        config=config or {},
4159                        commit_message=commit_message,
4160                        type=CreateChatPromptType.CHAT,
4161                    )
4162                )
4163                server_prompt = self.api.prompts.create(request=request)
4164
4165                if self._resources is not None:
4166                    self._resources.prompt_cache.invalidate(name)
4167
4168                return ChatPromptClient(prompt=cast(Prompt_Chat, server_prompt))
4169
4170            if not isinstance(prompt, str):
4171                raise ValueError("For 'text' type, 'prompt' must be a string.")
4172
4173            request = CreateTextPromptRequest(
4174                name=name,
4175                prompt=prompt,
4176                labels=labels,
4177                tags=tags,
4178                config=config or {},
4179                commit_message=commit_message,
4180            )
4181
4182            server_prompt = self.api.prompts.create(request=request)
4183
4184            if self._resources is not None:
4185                self._resources.prompt_cache.invalidate(name)
4186
4187            return TextPromptClient(prompt=cast(Prompt_Text, server_prompt))
4188
4189        except Error as e:
4190            handle_fern_exception(e)
4191            raise e
4192
4193    def update_prompt(
4194        self,
4195        *,
4196        name: str,
4197        version: int,
4198        new_labels: List[str] = [],
4199    ) -> Any:
4200        """Update an existing prompt version in Langfuse. The Langfuse SDK prompt cache is invalidated for all prompts witht he specified name.
4201
4202        Args:
4203            name (str): The name of the prompt to update.
4204            version (int): The version number of the prompt to update.
4205            new_labels (List[str], optional): New labels to assign to the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. Defaults to [].
4206
4207        Returns:
4208            Prompt: The updated prompt from the Langfuse API.
4209
4210        """
4211        updated_prompt = self.api.prompt_version.update(
4212            name=self._url_encode(name),
4213            version=version,
4214            new_labels=new_labels,
4215        )
4216
4217        if self._resources is not None:
4218            self._resources.prompt_cache.invalidate(name)
4219
4220        return updated_prompt
4221
4222    def _url_encode(self, url: str, *, is_url_param: Optional[bool] = False) -> str:
4223        # httpx ≥ 0.28 does its own WHATWG-compliant quoting (eg. encodes bare
4224        # “%”, “?”, “#”, “|”, … in query/path parts).  Re-quoting here would
4225        # double-encode, so we skip when the value is about to be sent straight
4226        # to httpx (`is_url_param=True`) and the installed version is ≥ 0.28.
4227        if is_url_param and Version(httpx.__version__) >= Version("0.28.0"):
4228            return url
4229
4230        # urllib.parse.quote does not escape slashes "/" by default; we need to add safe="" to force escaping
4231        # we need add safe="" to force escaping of slashes
4232        # This is necessary for prompts in prompt folders
4233        return urllib.parse.quote(url, safe="")
4234
4235    def clear_prompt_cache(self) -> None:
4236        """Clear the entire prompt cache, removing all cached prompts.
4237
4238        This method is useful when you want to force a complete refresh of all
4239        cached prompts, for example after major updates or when you need to
4240        ensure the latest versions are fetched from the server.
4241        """
4242        if self._resources is not None:
4243            self._resources.prompt_cache.clear()

Main client for Langfuse tracing and platform features.

This class provides an interface for creating and managing traces, spans, and generations in Langfuse as well as interacting with the Langfuse API.

The client features a thread-safe singleton pattern for each unique public API key, ensuring consistent trace context propagation across your application. It implements efficient batching of spans with configurable flush settings and includes background thread management for media uploads and score ingestion.

Configuration is flexible through either direct parameters or environment variables, with graceful fallbacks and runtime configuration updates.

Attributes:
  • api: Synchronous API client for Langfuse backend communication
  • async_api: Asynchronous API client for Langfuse backend communication
  • _otel_tracer: Internal LangfuseTracer instance managing OpenTelemetry components
Arguments:
  • public_key (Optional[str]): Your Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable.
  • secret_key (Optional[str]): Your Langfuse secret API key. Can also be set via LANGFUSE_SECRET_KEY environment variable.
  • base_url (Optional[str]): The Langfuse API base URL. Defaults to "https://cloud.langfuse.com". Can also be set via LANGFUSE_BASE_URL environment variable.
  • host (Optional[str]): Deprecated. Use base_url instead. The Langfuse API host URL. Defaults to "https://cloud.langfuse.com".
  • timeout (Optional[int]): Timeout in seconds for API requests. Defaults to 5 seconds.
  • httpx_client (Optional[httpx.Client]): Custom httpx client for making non-tracing HTTP requests. If not provided, a default client will be created. Fork safety: httpx.Client is thread-safe but not process-safe. When using fork()-based servers (e.g. Gunicorn with --preload), the SDK automatically recreates its internally-managed HTTP client in child processes after fork. A custom httpx_client is intentionally left as-is (the fork-inherited copy is reused), so you retain the opportunity to handle process-safety yourself — for example by registering your own os.register_at_fork(after_in_child=...) handler to close and reopen connections on the custom client.
  • debug (bool): Enable debug logging. Defaults to False. Can also be set via LANGFUSE_DEBUG environment variable.
  • tracing_enabled (Optional[bool]): Enable or disable tracing. Defaults to True. Can also be set via LANGFUSE_TRACING_ENABLED environment variable.
  • flush_at (Optional[int]): Number of spans to batch before sending to the API. Defaults to 512. Can also be set via LANGFUSE_FLUSH_AT environment variable.
  • flush_interval (Optional[float]): Time in seconds between batch flushes. Defaults to 5 seconds. Can also be set via LANGFUSE_FLUSH_INTERVAL environment variable.
  • environment (Optional[str]): Environment name for tracing. Default is 'default'. Can also be set via LANGFUSE_TRACING_ENVIRONMENT environment variable. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'.
  • release (Optional[str]): Release version/hash of your application. Used for grouping analytics by release.
  • media_upload_thread_count (Optional[int]): Number of background threads for handling media uploads. Defaults to 1. Can also be set via LANGFUSE_MEDIA_UPLOAD_THREAD_COUNT environment variable.
  • sample_rate (Optional[float]): Sampling rate for traces (0.0 to 1.0). Defaults to 1.0 (100% of traces are sampled). Can also be set via LANGFUSE_SAMPLE_RATE environment variable.
  • mask (Optional[MaskFunction]): Function to mask sensitive data synchronously when Langfuse SDK attributes are created. This applies only to data set through Langfuse SDK APIs such as start_observation(), update(), and set_trace_io().
  • mask_otel_spans (Optional[MaskOtelSpansFunction]): Synchronous export-stage hook for masking raw OpenTelemetry span attributes before this Langfuse client sends them to Langfuse. Use this for spans created by third-party OpenTelemetry instrumentations, or when you need to inspect final span attributes after export filtering and Langfuse media handling. It does not modify spans already exported through other OpenTelemetry exporters.

    The hook receives one OpenTelemetry export batch. A batch is not guaranteed to contain a complete trace, request, or Langfuse observation tree. The hook usually runs on the OpenTelemetry batch span processor worker thread; during flush() and shutdown it may run on the caller thread. Keep it synchronous, deterministic, and fast.

    Return None to leave the batch unchanged. Return MaskOtelSpansResult with OtelSpanPatch values to delete or replace attributes on selected spans. If a batch contains duplicate trace and span identifiers, Langfuse keeps only the last matching span. If the hook raises or returns an invalid batch result, Langfuse drops the whole export batch. If one returned span patch is invalid, Langfuse drops only that span from the Langfuse export.

    Example:

    from typing import Optional
    
    from langfuse import Langfuse
    from langfuse.types import (
        MaskOtelSpansParams,
        MaskOtelSpansResult,
        OtelSpanPatch,
    )
    
    def mask_otel_spans(
        *, params: MaskOtelSpansParams
    ) -> Optional[MaskOtelSpansResult]:
        patches = {}
    
        for identifier, span in params.spans.items():
            if "gen_ai.prompt.0.content" in span.attributes:
                patches[identifier] = OtelSpanPatch(
                    delete_attributes=("gen_ai.prompt.0.content",),
                    set_attributes={"masking.applied": True},
                )
    
        return MaskOtelSpansResult(span_patches=patches)
    
    langfuse = Langfuse(mask_otel_spans=mask_otel_spans)
    
  • blocked_instrumentation_scopes (Optional[List[str]]): Deprecated. Use should_export_span instead. Equivalent behavior:

    from langfuse.span_filter import is_default_export_span
    blocked = {"sqlite", "requests"}
    
    should_export_span = lambda span: (
        is_default_export_span(span)
        and (
            span.instrumentation_scope is None
            or span.instrumentation_scope.name not in blocked
        )
    )
    
  • should_export_span (Optional[Callable[[ReadableSpan], bool]]): Callback to decide whether to export a span. If omitted, Langfuse uses the default filter (Langfuse SDK spans, spans with gen_ai.* attributes, and known LLM instrumentation scopes).

  • additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and in the default OTLPSpanExporter requests. These headers will be merged with default headers. Note: If httpx_client is provided, additional_headers must be set directly on your custom httpx_client as well. If span_exporter is provided, these headers are not wired into that exporter and must be configured on the exporter instance directly.
  • tracer_provider(Optional[TracerProvider]): OpenTelemetry TracerProvider to use for Langfuse. This can be useful to set to have disconnected tracing between Langfuse and other OpenTelemetry-span emitting libraries. Note: To track active spans, the context is still shared between TracerProviders. This may lead to broken trace trees.
  • id_generator (Optional[IdGenerator]): OpenTelemetry ID generator to use when Langfuse creates its own TracerProvider. If omitted, the OpenTelemetry SDK default is used. If tracer_provider is provided, or an OpenTelemetry TracerProvider is already registered globally, configure the ID generator on that provider instead.
  • span_exporter (Optional[SpanExporter]): Custom OpenTelemetry span exporter for the Langfuse span processor. If omitted, Langfuse creates an OTLPSpanExporter pointed at the Langfuse OTLP endpoint. If provided, Langfuse does not wire base_url, exporter headers, exporter auth, or exporter timeout into it. Configure endpoint, headers, and timeout on the exporter instance directly. If you are sending spans to Langfuse v4 or using Langfuse Cloud Fast Preview, include x-langfuse-ingestion-version=4 on the exporter to enable real time processing of exported spans.
Example:
from langfuse import Langfuse

# Initialize the client (reads from env vars if not provided)
langfuse = Langfuse(
    public_key="your-public-key",
    secret_key="your-secret-key",
    base_url="https://cloud.langfuse.com",  # Optional, default shown
)

# Create a trace span
with langfuse.start_as_current_observation(name="process-query") as span:
    # Your application code here

    # Create a nested generation span for an LLM call
    with span.start_as_current_generation(
        name="generate-response",
        model="gpt-4",
        input={"query": "Tell me about AI"},
        model_parameters={"temperature": 0.7, "max_tokens": 500}
    ) as generation:
        # Generate response here
        response = "AI is a field of computer science..."

        generation.update(
            output=response,
            usage_details={"prompt_tokens": 10, "completion_tokens": 50},
            cost_details={"total_cost": 0.0023}
        )

        # Score the generation (supports NUMERIC, BOOLEAN, CATEGORICAL)
        generation.score(name="relevance", value=0.95, data_type="NUMERIC")
Langfuse( *, public_key: Optional[str] = None, secret_key: Optional[str] = None, base_url: Optional[str] = None, host: Optional[str] = None, timeout: Optional[int] = None, httpx_client: Optional[httpx.Client] = None, debug: bool = False, tracing_enabled: Optional[bool] = True, flush_at: Optional[int] = None, flush_interval: Optional[float] = None, environment: Optional[str] = None, release: Optional[str] = None, media_upload_thread_count: Optional[int] = None, sample_rate: Optional[float] = None, mask: Optional[langfuse.types.MaskFunction] = None, mask_otel_spans: Optional[MaskOtelSpansFunction] = None, blocked_instrumentation_scopes: Optional[List[str]] = None, should_export_span: Optional[Callable[[opentelemetry.sdk.trace.ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[opentelemetry.sdk.trace.TracerProvider] = None, id_generator: Optional[opentelemetry.sdk.trace.id_generator.IdGenerator] = None, span_exporter: Optional[opentelemetry.sdk.trace.export.SpanExporter] = None)
314    def __init__(
315        self,
316        *,
317        public_key: Optional[str] = None,
318        secret_key: Optional[str] = None,
319        base_url: Optional[str] = None,
320        host: Optional[str] = None,
321        timeout: Optional[int] = None,
322        httpx_client: Optional[httpx.Client] = None,
323        debug: bool = False,
324        tracing_enabled: Optional[bool] = True,
325        flush_at: Optional[int] = None,
326        flush_interval: Optional[float] = None,
327        environment: Optional[str] = None,
328        release: Optional[str] = None,
329        media_upload_thread_count: Optional[int] = None,
330        sample_rate: Optional[float] = None,
331        mask: Optional[MaskFunction] = None,
332        mask_otel_spans: Optional[MaskOtelSpansFunction] = None,
333        blocked_instrumentation_scopes: Optional[List[str]] = None,
334        should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
335        additional_headers: Optional[Dict[str, str]] = None,
336        tracer_provider: Optional[TracerProvider] = None,
337        id_generator: Optional[IdGenerator] = None,
338        span_exporter: Optional[SpanExporter] = None,
339    ):
340        self._base_url = (
341            base_url
342            or os.environ.get(LANGFUSE_BASE_URL)
343            or host
344            or os.environ.get(LANGFUSE_HOST, "https://cloud.langfuse.com")
345        )
346        self._environment = environment or cast(
347            str, os.environ.get(LANGFUSE_TRACING_ENVIRONMENT)
348        )
349        self._release = (
350            release
351            or os.environ.get(LANGFUSE_RELEASE, None)
352            or get_common_release_envs()
353        )
354        self._project_id: Optional[str] = None
355        if sample_rate is None:
356            sample_rate = float(os.environ.get(LANGFUSE_SAMPLE_RATE, 1.0))
357        if not 0.0 <= sample_rate <= 1.0:
358            raise ValueError(
359                f"Sample rate must be between 0.0 and 1.0, got {sample_rate}"
360            )
361
362        timeout = timeout or int(os.environ.get(LANGFUSE_TIMEOUT, 5))
363
364        self._tracing_enabled = (
365            tracing_enabled
366            and os.environ.get(LANGFUSE_TRACING_ENABLED, "true").lower() != "false"
367        )
368        if not self._tracing_enabled:
369            langfuse_logger.info(
370                "Configuration: Langfuse tracing is explicitly disabled. No data will be sent to the Langfuse API."
371            )
372
373        debug = (
374            debug if debug else (os.getenv(LANGFUSE_DEBUG, "false").lower() == "true")
375        )
376        if debug:
377            logging.basicConfig(
378                format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
379            )
380            langfuse_logger.setLevel(logging.DEBUG)
381
382        public_key = public_key or os.environ.get(LANGFUSE_PUBLIC_KEY)
383        if public_key is None:
384            langfuse_logger.warning(
385                "Authentication error: Langfuse client initialized without public_key. Client will be disabled. "
386                "Provide a public_key parameter or set LANGFUSE_PUBLIC_KEY environment variable. "
387            )
388            self._otel_tracer = otel_trace_api.NoOpTracer()
389            return
390
391        secret_key = secret_key or os.environ.get(LANGFUSE_SECRET_KEY)
392        if secret_key is None:
393            langfuse_logger.warning(
394                "Authentication error: Langfuse client initialized without secret_key. Client will be disabled. "
395                "Provide a secret_key parameter or set LANGFUSE_SECRET_KEY environment variable. "
396            )
397            self._otel_tracer = otel_trace_api.NoOpTracer()
398            return
399
400        if os.environ.get("OTEL_SDK_DISABLED", "false").lower() == "true":
401            langfuse_logger.warning(
402                "OTEL_SDK_DISABLED is set. Langfuse tracing will be disabled and no traces will appear in the UI."
403            )
404
405        if blocked_instrumentation_scopes is not None:
406            warnings.warn(
407                "`blocked_instrumentation_scopes` is deprecated and will be removed in a future release. "
408                "Use `should_export_span` instead. Example: "
409                "from langfuse.span_filter import is_default_export_span; "
410                'blocked={"scope"}; should_export_span=lambda span: '
411                "is_default_export_span(span) and (span.instrumentation_scope is None or "
412                "span.instrumentation_scope.name not in blocked).",
413                DeprecationWarning,
414                stacklevel=2,
415            )
416
417        # Initialize api and tracer if requirements are met
418        self._resources = LangfuseResourceManager(
419            public_key=public_key,
420            secret_key=secret_key,
421            base_url=self._base_url,
422            timeout=timeout,
423            environment=self._environment,
424            release=release,
425            flush_at=flush_at,
426            flush_interval=flush_interval,
427            httpx_client=httpx_client,
428            media_upload_thread_count=media_upload_thread_count,
429            sample_rate=sample_rate,
430            mask=mask,
431            mask_otel_spans=mask_otel_spans,
432            tracing_enabled=self._tracing_enabled,
433            blocked_instrumentation_scopes=blocked_instrumentation_scopes,
434            should_export_span=should_export_span,
435            additional_headers=additional_headers,
436            tracer_provider=tracer_provider,
437            id_generator=id_generator,
438            span_exporter=span_exporter,
439        )
440        self._mask = self._resources.mask
441
442        self._otel_tracer = (
443            self._resources.tracer
444            if self._tracing_enabled and self._resources.tracer is not None
445            else otel_trace_api.NoOpTracer()
446        )
api: langfuse.api.LangfuseAPI
448    @property
449    def api(self) -> LangfuseAPI:
450        """Synchronous client for the full Langfuse REST API (traces, observations, scores, datasets, prompts, ...).
451
452        Use this to read or manage data on the Langfuse server; use the tracing methods
453        (`start_observation`, `@observe`) to create traces. Use `async_api` for the
454        asyncio variant.
455
456        Semantics that are easy to miss:
457
458        - **Ingestion is asynchronous.** `langfuse.flush()` only guarantees delivery to
459          the API, not read visibility: reads such as `api.trace.get(trace_id)` may
460          raise `langfuse.api.NotFoundError` until processing completes (typically
461          within 15-30 seconds; longer under load). The same applies to scores and
462          dataset run reads. Instead of a fixed sleep, retry with a deadline:
463
464        - **List endpoints return lightweight views.** `api.trace.list(...)` returns
465          `TraceWithDetails`, where `observations` and `scores` are lists of ID strings.
466          Fetch the full objects with `api.trace.get(trace_id)` (`TraceWithFullDetails`),
467          or prefer `api.observations.get_many(trace_id=...)` for row-level observation
468          queries. The same list-view vs. get-detail pattern applies to other resources.
469
470        - **Prefer the v2 data APIs — they are the defaults since SDK v4.**
471          `api.observations` and `api.metrics` map to the high-performance
472          `/api/public/v2/...` endpoints and are the recommended read path. Their v1
473          equivalents remain available under `api.legacy.observations_v1` /
474          `api.legacy.metrics_v1` but are less performant at scale, not recommended
475          for new workflows, and will be deprecated.
476
477        - For large-scale aggregation (usage/cost by model, user, etc.), prefer the
478        v2 Metrics API (`api.metrics.metrics(...)`) over paginating row-level data.
479
480
481        See also: `async_api`,
482        https://langfuse.com/docs/api-and-data-platform/features/query-via-sdk
483        (ingestion lag: #ingestion-lag, list vs. get: #traces-list-vs-get),
484        https://langfuse.com/docs/api-and-data-platform/features/observations-api,
485        https://langfuse.com/docs/metrics/features/metrics-api
486        """
487        if self._resources is None:
488            raise AttributeError("Langfuse client is not initialized")
489
490        return self._resources.api

Synchronous client for the full Langfuse REST API (traces, observations, scores, datasets, prompts, ...).

Use this to read or manage data on the Langfuse server; use the tracing methods (start_observation, @observe) to create traces. Use async_api for the asyncio variant.

Semantics that are easy to miss:

  • Ingestion is asynchronous. langfuse.flush() only guarantees delivery to the API, not read visibility: reads such as api.trace.get(trace_id) may raise langfuse.api.NotFoundError until processing completes (typically within 15-30 seconds; longer under load). The same applies to scores and dataset run reads. Instead of a fixed sleep, retry with a deadline:

  • List endpoints return lightweight views. api.trace.list(...) returns TraceWithDetails, where observations and scores are lists of ID strings. Fetch the full objects with api.trace.get(trace_id) (TraceWithFullDetails), or prefer api.observations.get_many(trace_id=...) for row-level observation queries. The same list-view vs. get-detail pattern applies to other resources.

  • Prefer the v2 data APIs — they are the defaults since SDK v4. api.observations and api.metrics map to the high-performance /api/public/v2/... endpoints and are the recommended read path. Their v1 equivalents remain available under api.legacy.observations_v1 / api.legacy.metrics_v1 but are less performant at scale, not recommended for new workflows, and will be deprecated.

  • For large-scale aggregation (usage/cost by model, user, etc.), prefer the v2 Metrics API (api.metrics.metrics(...)) over paginating row-level data.

See also: async_api, https://langfuse.com/docs/api-and-data-platform/features/query-via-sdk (ingestion lag: #ingestion-lag, list vs. get: #traces-list-vs-get), https://langfuse.com/docs/api-and-data-platform/features/observations-api, https://langfuse.com/docs/metrics/features/metrics-api

async_api: langfuse.api.AsyncLangfuseAPI
499    @property
500    def async_api(self) -> AsyncLangfuseAPI:
501        if self._resources is None:
502            raise AttributeError("Langfuse client is not initialized")
503
504        return self._resources.async_api
def start_observation( self, *, trace_context: Optional[langfuse.types.TraceContext] = None, name: str, as_type: Union[Literal['generation', 'embedding'], Literal['span', 'agent', 'tool', 'chain', 'retriever', 'evaluator', 'guardrail']] = 'span', input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None, completion_start_time: Optional[datetime.datetime] = None, model: Optional[str] = None, model_parameters: Optional[Dict[str, Union[str, NoneType, int, float, bool, List[str]]]] = None, usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Union[langfuse.model.TextPromptClient, langfuse.model.ChatPromptClient, NoneType] = None) -> Union[LangfuseSpan, LangfuseGeneration, LangfuseAgent, LangfuseTool, LangfuseChain, LangfuseRetriever, LangfuseEvaluator, LangfuseEmbedding, LangfuseGuardrail]:
660    def start_observation(
661        self,
662        *,
663        trace_context: Optional[TraceContext] = None,
664        name: str,
665        as_type: ObservationTypeLiteralNoEvent = "span",
666        input: Optional[Any] = None,
667        output: Optional[Any] = None,
668        metadata: Optional[Any] = None,
669        version: Optional[str] = None,
670        level: Optional[SpanLevel] = None,
671        status_message: Optional[str] = None,
672        completion_start_time: Optional[datetime] = None,
673        model: Optional[str] = None,
674        model_parameters: Optional[Dict[str, MapValue]] = None,
675        usage_details: Optional[Dict[str, int]] = None,
676        cost_details: Optional[Dict[str, float]] = None,
677        prompt: Optional[PromptClient] = None,
678    ) -> Union[
679        LangfuseSpan,
680        LangfuseGeneration,
681        LangfuseAgent,
682        LangfuseTool,
683        LangfuseChain,
684        LangfuseRetriever,
685        LangfuseEvaluator,
686        LangfuseEmbedding,
687        LangfuseGuardrail,
688    ]:
689        """Create a new observation of the specified type.
690
691        This method creates a new observation but does not set it as the current span in the
692        context. To create and use an observation within a context, use start_as_current_observation().
693
694        Args:
695            trace_context: Optional context for connecting to an existing trace
696            name: Name of the observation
697            as_type: Type of observation to create (defaults to "span")
698            input: Input data for the operation
699            output: Output data from the operation
700            metadata: Additional metadata to associate with the observation
701            version: Version identifier for the code or component
702            level: Importance level of the observation
703            status_message: Optional status message for the observation
704            completion_start_time: When the model started generating (for generation types)
705            model: Name/identifier of the AI model used (for generation types)
706            model_parameters: Parameters used for the model (for generation types)
707            usage_details: Token usage information (for generation types)
708            cost_details: Cost information (for generation types)
709            prompt: Associated prompt template (for generation types)
710
711        Returns:
712            An observation object of the appropriate type that must be ended with .end()
713        """
714        if trace_context:
715            trace_id = trace_context.get("trace_id", None)
716            parent_span_id = trace_context.get("parent_span_id", None)
717
718            if trace_id:
719                remote_parent_span = self._create_remote_parent_span(
720                    trace_id=trace_id, parent_span_id=parent_span_id
721                )
722
723                with otel_trace_api.use_span(
724                    cast(otel_trace_api.Span, remote_parent_span)
725                ):
726                    otel_span = self._otel_tracer.start_span(name=name)
727                    otel_span.set_attribute(LangfuseOtelSpanAttributes.AS_ROOT, True)
728
729                    return self._create_observation_from_otel_span(
730                        otel_span=otel_span,
731                        as_type=as_type,
732                        input=input,
733                        output=output,
734                        metadata=metadata,
735                        version=version,
736                        level=level,
737                        status_message=status_message,
738                        completion_start_time=completion_start_time,
739                        model=model,
740                        model_parameters=model_parameters,
741                        usage_details=usage_details,
742                        cost_details=cost_details,
743                        prompt=prompt,
744                    )
745
746        otel_span = self._otel_tracer.start_span(name=name)
747
748        return self._create_observation_from_otel_span(
749            otel_span=otel_span,
750            as_type=as_type,
751            input=input,
752            output=output,
753            metadata=metadata,
754            version=version,
755            level=level,
756            status_message=status_message,
757            completion_start_time=completion_start_time,
758            model=model,
759            model_parameters=model_parameters,
760            usage_details=usage_details,
761            cost_details=cost_details,
762            prompt=prompt,
763        )

Create a new observation of the specified type.

This method creates a new observation but does not set it as the current span in the context. To create and use an observation within a context, use start_as_current_observation().

Arguments:
  • trace_context: Optional context for connecting to an existing trace
  • name: Name of the observation
  • as_type: Type of observation to create (defaults to "span")
  • input: Input data for the operation
  • output: Output data from the operation
  • metadata: Additional metadata to associate with the observation
  • version: Version identifier for the code or component
  • level: Importance level of the observation
  • status_message: Optional status message for the observation
  • completion_start_time: When the model started generating (for generation types)
  • model: Name/identifier of the AI model used (for generation types)
  • model_parameters: Parameters used for the model (for generation types)
  • usage_details: Token usage information (for generation types)
  • cost_details: Cost information (for generation types)
  • prompt: Associated prompt template (for generation types)
Returns:

An observation object of the appropriate type that must be ended with .end()

def start_as_current_observation( self, *, trace_context: Optional[langfuse.types.TraceContext] = None, name: str, as_type: Union[Literal['generation', 'embedding'], Literal['span', 'agent', 'tool', 'chain', 'retriever', 'evaluator', 'guardrail']] = 'span', input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None, completion_start_time: Optional[datetime.datetime] = None, model: Optional[str] = None, model_parameters: Optional[Dict[str, Union[str, NoneType, int, float, bool, List[str]]]] = None, usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Union[langfuse.model.TextPromptClient, langfuse.model.ChatPromptClient, NoneType] = None, end_on_exit: Optional[bool] = None) -> Union[opentelemetry.util._decorator._AgnosticContextManager[LangfuseGeneration], opentelemetry.util._decorator._AgnosticContextManager[LangfuseSpan], opentelemetry.util._decorator._AgnosticContextManager[LangfuseAgent], opentelemetry.util._decorator._AgnosticContextManager[LangfuseTool], opentelemetry.util._decorator._AgnosticContextManager[LangfuseChain], opentelemetry.util._decorator._AgnosticContextManager[LangfuseRetriever], opentelemetry.util._decorator._AgnosticContextManager[LangfuseEvaluator], opentelemetry.util._decorator._AgnosticContextManager[LangfuseEmbedding], opentelemetry.util._decorator._AgnosticContextManager[LangfuseGuardrail]]:
 993    def start_as_current_observation(
 994        self,
 995        *,
 996        trace_context: Optional[TraceContext] = None,
 997        name: str,
 998        as_type: ObservationTypeLiteralNoEvent = "span",
 999        input: Optional[Any] = None,
1000        output: Optional[Any] = None,
1001        metadata: Optional[Any] = None,
1002        version: Optional[str] = None,
1003        level: Optional[SpanLevel] = None,
1004        status_message: Optional[str] = None,
1005        completion_start_time: Optional[datetime] = None,
1006        model: Optional[str] = None,
1007        model_parameters: Optional[Dict[str, MapValue]] = None,
1008        usage_details: Optional[Dict[str, int]] = None,
1009        cost_details: Optional[Dict[str, float]] = None,
1010        prompt: Optional[PromptClient] = None,
1011        end_on_exit: Optional[bool] = None,
1012    ) -> Union[
1013        _AgnosticContextManager[LangfuseGeneration],
1014        _AgnosticContextManager[LangfuseSpan],
1015        _AgnosticContextManager[LangfuseAgent],
1016        _AgnosticContextManager[LangfuseTool],
1017        _AgnosticContextManager[LangfuseChain],
1018        _AgnosticContextManager[LangfuseRetriever],
1019        _AgnosticContextManager[LangfuseEvaluator],
1020        _AgnosticContextManager[LangfuseEmbedding],
1021        _AgnosticContextManager[LangfuseGuardrail],
1022    ]:
1023        """Create a new observation and set it as the current span in a context manager.
1024
1025        This method creates a new observation of the specified type and sets it as the
1026        current span within a context manager. Use this method with a 'with' statement to
1027        automatically handle the observation lifecycle within a code block.
1028
1029        The created observation will be the child of the current span in the context.
1030
1031        Args:
1032            trace_context: Optional context for connecting to an existing trace
1033            name: Name of the observation (e.g., function or operation name)
1034            as_type: Type of observation to create (defaults to "span")
1035            input: Input data for the operation (can be any JSON-serializable object)
1036            output: Output data from the operation (can be any JSON-serializable object)
1037            metadata: Additional metadata to associate with the observation
1038            version: Version identifier for the code or component
1039            level: Importance level of the observation (info, warning, error)
1040            status_message: Optional status message for the observation
1041            end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks.
1042
1043            The following parameters are available when as_type is: "generation" or "embedding".
1044            completion_start_time: When the model started generating the response
1045            model: Name/identifier of the AI model used (e.g., "gpt-4")
1046            model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
1047            usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
1048            cost_details: Cost information for the model call
1049            prompt: Associated prompt template from Langfuse prompt management
1050
1051        Returns:
1052            A context manager that yields the appropriate observation type based on as_type
1053
1054        Example:
1055            ```python
1056            # Create a span
1057            with langfuse.start_as_current_observation(name="process-query", as_type="span") as span:
1058                # Do work
1059                result = process_data()
1060                span.update(output=result)
1061
1062                # Create a child span automatically
1063                with span.start_as_current_observation(name="sub-operation") as child_span:
1064                    # Do sub-operation work
1065                    child_span.update(output="sub-result")
1066
1067            # Create a tool observation
1068            with langfuse.start_as_current_observation(name="web-search", as_type="tool") as tool:
1069                # Do tool work
1070                results = search_web(query)
1071                tool.update(output=results)
1072
1073            # Create a generation observation
1074            with langfuse.start_as_current_observation(
1075                name="answer-generation",
1076                as_type="generation",
1077                model="gpt-4"
1078            ) as generation:
1079                # Generate answer
1080                response = llm.generate(...)
1081                generation.update(output=response)
1082            ```
1083        """
1084        if as_type in get_observation_types_list(ObservationTypeGenerationLike):
1085            if trace_context:
1086                trace_id = trace_context.get("trace_id", None)
1087                parent_span_id = trace_context.get("parent_span_id", None)
1088
1089                if trace_id:
1090                    remote_parent_span = self._create_remote_parent_span(
1091                        trace_id=trace_id, parent_span_id=parent_span_id
1092                    )
1093
1094                    return cast(
1095                        Union[
1096                            _AgnosticContextManager[LangfuseGeneration],
1097                            _AgnosticContextManager[LangfuseEmbedding],
1098                        ],
1099                        self._create_span_with_parent_context(
1100                            as_type=as_type,
1101                            name=name,
1102                            remote_parent_span=remote_parent_span,
1103                            parent=None,
1104                            end_on_exit=end_on_exit,
1105                            input=input,
1106                            output=output,
1107                            metadata=metadata,
1108                            version=version,
1109                            level=level,
1110                            status_message=status_message,
1111                            completion_start_time=completion_start_time,
1112                            model=model,
1113                            model_parameters=model_parameters,
1114                            usage_details=usage_details,
1115                            cost_details=cost_details,
1116                            prompt=prompt,
1117                        ),
1118                    )
1119
1120            return cast(
1121                Union[
1122                    _AgnosticContextManager[LangfuseGeneration],
1123                    _AgnosticContextManager[LangfuseEmbedding],
1124                ],
1125                self._start_as_current_otel_span_with_processed_media(
1126                    as_type=as_type,
1127                    name=name,
1128                    end_on_exit=end_on_exit,
1129                    input=input,
1130                    output=output,
1131                    metadata=metadata,
1132                    version=version,
1133                    level=level,
1134                    status_message=status_message,
1135                    completion_start_time=completion_start_time,
1136                    model=model,
1137                    model_parameters=model_parameters,
1138                    usage_details=usage_details,
1139                    cost_details=cost_details,
1140                    prompt=prompt,
1141                ),
1142            )
1143
1144        if as_type in get_observation_types_list(ObservationTypeSpanLike):
1145            if trace_context:
1146                trace_id = trace_context.get("trace_id", None)
1147                parent_span_id = trace_context.get("parent_span_id", None)
1148
1149                if trace_id:
1150                    remote_parent_span = self._create_remote_parent_span(
1151                        trace_id=trace_id, parent_span_id=parent_span_id
1152                    )
1153
1154                    return cast(
1155                        Union[
1156                            _AgnosticContextManager[LangfuseSpan],
1157                            _AgnosticContextManager[LangfuseAgent],
1158                            _AgnosticContextManager[LangfuseTool],
1159                            _AgnosticContextManager[LangfuseChain],
1160                            _AgnosticContextManager[LangfuseRetriever],
1161                            _AgnosticContextManager[LangfuseEvaluator],
1162                            _AgnosticContextManager[LangfuseGuardrail],
1163                        ],
1164                        self._create_span_with_parent_context(
1165                            as_type=as_type,
1166                            name=name,
1167                            remote_parent_span=remote_parent_span,
1168                            parent=None,
1169                            end_on_exit=end_on_exit,
1170                            input=input,
1171                            output=output,
1172                            metadata=metadata,
1173                            version=version,
1174                            level=level,
1175                            status_message=status_message,
1176                        ),
1177                    )
1178
1179            return cast(
1180                Union[
1181                    _AgnosticContextManager[LangfuseSpan],
1182                    _AgnosticContextManager[LangfuseAgent],
1183                    _AgnosticContextManager[LangfuseTool],
1184                    _AgnosticContextManager[LangfuseChain],
1185                    _AgnosticContextManager[LangfuseRetriever],
1186                    _AgnosticContextManager[LangfuseEvaluator],
1187                    _AgnosticContextManager[LangfuseGuardrail],
1188                ],
1189                self._start_as_current_otel_span_with_processed_media(
1190                    as_type=as_type,
1191                    name=name,
1192                    end_on_exit=end_on_exit,
1193                    input=input,
1194                    output=output,
1195                    metadata=metadata,
1196                    version=version,
1197                    level=level,
1198                    status_message=status_message,
1199                ),
1200            )
1201
1202        # This should never be reached since all valid types are handled above
1203        langfuse_logger.warning(
1204            "Unknown observation type: %s, falling back to span", as_type
1205        )
1206        return self._start_as_current_otel_span_with_processed_media(
1207            as_type="span",
1208            name=name,
1209            end_on_exit=end_on_exit,
1210            input=input,
1211            output=output,
1212            metadata=metadata,
1213            version=version,
1214            level=level,
1215            status_message=status_message,
1216        )

Create a new observation and set it as the current span in a context manager.

This method creates a new observation of the specified type and sets it as the current span within a context manager. Use this method with a 'with' statement to automatically handle the observation lifecycle within a code block.

The created observation will be the child of the current span in the context.

Arguments:
  • trace_context: Optional context for connecting to an existing trace
  • name: Name of the observation (e.g., function or operation name)
  • as_type: Type of observation to create (defaults to "span")
  • input: Input data for the operation (can be any JSON-serializable object)
  • output: Output data from the operation (can be any JSON-serializable object)
  • metadata: Additional metadata to associate with the observation
  • version: Version identifier for the code or component
  • level: Importance level of the observation (info, warning, error)
  • status_message: Optional status message for the observation
  • end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks.
  • The following parameters are available when as_type is: "generation" or "embedding".
  • completion_start_time: When the model started generating the response
  • model: Name/identifier of the AI model used (e.g., "gpt-4")
  • model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
  • usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
  • cost_details: Cost information for the model call
  • prompt: Associated prompt template from Langfuse prompt management
Returns:

A context manager that yields the appropriate observation type based on as_type

Example:
# Create a span
with langfuse.start_as_current_observation(name="process-query", as_type="span") as span:
    # Do work
    result = process_data()
    span.update(output=result)

    # Create a child span automatically
    with span.start_as_current_observation(name="sub-operation") as child_span:
        # Do sub-operation work
        child_span.update(output="sub-result")

# Create a tool observation
with langfuse.start_as_current_observation(name="web-search", as_type="tool") as tool:
    # Do tool work
    results = search_web(query)
    tool.update(output=results)

# Create a generation observation
with langfuse.start_as_current_observation(
    name="answer-generation",
    as_type="generation",
    model="gpt-4"
) as generation:
    # Generate answer
    response = llm.generate(...)
    generation.update(output=response)
def update_current_generation( self, *, name: Optional[str] = None, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None, completion_start_time: Optional[datetime.datetime] = None, model: Optional[str] = None, model_parameters: Optional[Dict[str, Union[str, NoneType, int, float, bool, List[str]]]] = None, usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Union[langfuse.model.TextPromptClient, langfuse.model.ChatPromptClient, NoneType] = None) -> None:
1408    def update_current_generation(
1409        self,
1410        *,
1411        name: Optional[str] = None,
1412        input: Optional[Any] = None,
1413        output: Optional[Any] = None,
1414        metadata: Optional[Any] = None,
1415        version: Optional[str] = None,
1416        level: Optional[SpanLevel] = None,
1417        status_message: Optional[str] = None,
1418        completion_start_time: Optional[datetime] = None,
1419        model: Optional[str] = None,
1420        model_parameters: Optional[Dict[str, MapValue]] = None,
1421        usage_details: Optional[Dict[str, int]] = None,
1422        cost_details: Optional[Dict[str, float]] = None,
1423        prompt: Optional[PromptClient] = None,
1424    ) -> None:
1425        """Update the current active generation span with new information.
1426
1427        This method updates the current generation span in the active context with
1428        additional information. It's useful for adding output, usage stats, or other
1429        details that become available during or after model generation.
1430
1431        Args:
1432            name: The generation name
1433            input: Updated input data for the model
1434            output: Output from the model (e.g., completions)
1435            metadata: Additional metadata to associate with the generation
1436            version: Version identifier for the model or component
1437            level: Importance level of the generation (info, warning, error)
1438            status_message: Optional status message for the generation
1439            completion_start_time: When the model started generating the response
1440            model: Name/identifier of the AI model used (e.g., "gpt-4")
1441            model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
1442            usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
1443            cost_details: Cost information for the model call
1444            prompt: Associated prompt template from Langfuse prompt management
1445
1446        Example:
1447            ```python
1448            with langfuse.start_as_current_generation(name="answer-query") as generation:
1449                # Initial setup and API call
1450                response = llm.generate(...)
1451
1452                # Update with results that weren't available at creation time
1453                langfuse.update_current_generation(
1454                    output=response.text,
1455                    usage_details={
1456                        "prompt_tokens": response.usage.prompt_tokens,
1457                        "completion_tokens": response.usage.completion_tokens
1458                    }
1459                )
1460            ```
1461        """
1462        if not self._tracing_enabled:
1463            langfuse_logger.debug(
1464                "Operation skipped: update_current_generation - Tracing is disabled or client is in no-op mode."
1465            )
1466            return
1467
1468        current_otel_span = self._get_current_otel_span()
1469
1470        if current_otel_span is not None:
1471            generation = LangfuseGeneration(
1472                otel_span=current_otel_span, langfuse_client=self
1473            )
1474
1475            if name:
1476                current_otel_span.update_name(name)
1477
1478            generation.update(
1479                input=input,
1480                output=output,
1481                metadata=metadata,
1482                version=version,
1483                level=level,
1484                status_message=status_message,
1485                completion_start_time=completion_start_time,
1486                model=model,
1487                model_parameters=model_parameters,
1488                usage_details=usage_details,
1489                cost_details=cost_details,
1490                prompt=prompt,
1491            )

Update the current active generation span with new information.

This method updates the current generation span in the active context with additional information. It's useful for adding output, usage stats, or other details that become available during or after model generation.

Arguments:
  • name: The generation name
  • input: Updated input data for the model
  • output: Output from the model (e.g., completions)
  • metadata: Additional metadata to associate with the generation
  • version: Version identifier for the model or component
  • level: Importance level of the generation (info, warning, error)
  • status_message: Optional status message for the generation
  • completion_start_time: When the model started generating the response
  • model: Name/identifier of the AI model used (e.g., "gpt-4")
  • model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
  • usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
  • cost_details: Cost information for the model call
  • prompt: Associated prompt template from Langfuse prompt management
Example:
with langfuse.start_as_current_generation(name="answer-query") as generation:
    # Initial setup and API call
    response = llm.generate(...)

    # Update with results that weren't available at creation time
    langfuse.update_current_generation(
        output=response.text,
        usage_details={
            "prompt_tokens": response.usage.prompt_tokens,
            "completion_tokens": response.usage.completion_tokens
        }
    )
def update_current_span( self, *, name: Optional[str] = None, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None) -> None:
1493    def update_current_span(
1494        self,
1495        *,
1496        name: Optional[str] = None,
1497        input: Optional[Any] = None,
1498        output: Optional[Any] = None,
1499        metadata: Optional[Any] = None,
1500        version: Optional[str] = None,
1501        level: Optional[SpanLevel] = None,
1502        status_message: Optional[str] = None,
1503    ) -> None:
1504        """Update the current active span with new information.
1505
1506        This method updates the current span in the active context with
1507        additional information. It's useful for adding outputs or metadata
1508        that become available during execution.
1509
1510        Args:
1511            name: The span name
1512            input: Updated input data for the operation
1513            output: Output data from the operation
1514            metadata: Additional metadata to associate with the span
1515            version: Version identifier for the code or component
1516            level: Importance level of the span (info, warning, error)
1517            status_message: Optional status message for the span
1518
1519        Example:
1520            ```python
1521            with langfuse.start_as_current_observation(name="process-data") as span:
1522                # Initial processing
1523                result = process_first_part()
1524
1525                # Update with intermediate results
1526                langfuse.update_current_span(metadata={"intermediate_result": result})
1527
1528                # Continue processing
1529                final_result = process_second_part(result)
1530
1531                # Final update
1532                langfuse.update_current_span(output=final_result)
1533            ```
1534        """
1535        if not self._tracing_enabled:
1536            langfuse_logger.debug(
1537                "Operation skipped: update_current_span - Tracing is disabled or client is in no-op mode."
1538            )
1539            return
1540
1541        current_otel_span = self._get_current_otel_span()
1542
1543        if current_otel_span is not None:
1544            span_class = self._get_span_class(
1545                self._get_observation_type_from_otel_span(current_otel_span)
1546            )
1547            span = span_class(
1548                otel_span=current_otel_span,
1549                langfuse_client=self,
1550                environment=self._environment,
1551                release=self._release,
1552            )
1553
1554            if name:
1555                current_otel_span.update_name(name)
1556
1557            span.update(
1558                input=input,
1559                output=output,
1560                metadata=metadata,
1561                version=version,
1562                level=level,
1563                status_message=status_message,
1564            )

Update the current active span with new information.

This method updates the current span in the active context with additional information. It's useful for adding outputs or metadata that become available during execution.

Arguments:
  • name: The span name
  • input: Updated input data for the operation
  • output: Output data from the operation
  • metadata: Additional metadata to associate with the span
  • version: Version identifier for the code or component
  • level: Importance level of the span (info, warning, error)
  • status_message: Optional status message for the span
Example:
with langfuse.start_as_current_observation(name="process-data") as span:
    # Initial processing
    result = process_first_part()

    # Update with intermediate results
    langfuse.update_current_span(metadata={"intermediate_result": result})

    # Continue processing
    final_result = process_second_part(result)

    # Final update
    langfuse.update_current_span(output=final_result)
@deprecated('Trace-level input/output is deprecated. For trace attributes (user_id, session_id, tags, etc.), use propagate_attributes() instead. This method will be removed in a future major version.')
def set_current_trace_io( self, *, input: Optional[Any] = None, output: Optional[Any] = None) -> None:
1566    @deprecated(
1567        "Trace-level input/output is deprecated. "
1568        "For trace attributes (user_id, session_id, tags, etc.), use propagate_attributes() instead. "
1569        "This method will be removed in a future major version."
1570    )
1571    def set_current_trace_io(
1572        self,
1573        *,
1574        input: Optional[Any] = None,
1575        output: Optional[Any] = None,
1576    ) -> None:
1577        """Set trace-level input and output for the current span's trace.
1578
1579        .. deprecated::
1580            This is a legacy method for backward compatibility with Langfuse platform
1581            features that still rely on trace-level input/output (e.g., legacy LLM-as-a-judge
1582            evaluators). It will be removed in a future major version.
1583
1584            For setting other trace attributes (user_id, session_id, metadata, tags, version),
1585            use :func:`langfuse.propagate_attributes` (top-level import) instead.
1586
1587        Args:
1588            input: Input data to associate with the trace.
1589            output: Output data to associate with the trace.
1590        """
1591        if not self._tracing_enabled:
1592            langfuse_logger.debug(
1593                "Operation skipped: set_current_trace_io - Tracing is disabled or client is in no-op mode."
1594            )
1595            return
1596
1597        current_otel_span = self._get_current_otel_span()
1598
1599        if current_otel_span is not None and current_otel_span.is_recording():
1600            span_class = self._get_span_class(
1601                self._get_observation_type_from_otel_span(current_otel_span)
1602            )
1603            span = span_class(
1604                otel_span=current_otel_span,
1605                langfuse_client=self,
1606                environment=self._environment,
1607                release=self._release,
1608            )
1609
1610            span.set_trace_io(
1611                input=input,
1612                output=output,
1613            )

Set trace-level input and output for the current span's trace.

Deprecated since version : This is a legacy method for backward compatibility with Langfuse platform features that still rely on trace-level input/output (e.g., legacy LLM-as-a-judge evaluators). It will be removed in a future major version.

For setting other trace attributes (user_id, session_id, metadata, tags, version), use langfuse.propagate_attributes() (top-level import) instead.

Arguments:
  • input: Input data to associate with the trace.
  • output: Output data to associate with the trace.
def set_current_trace_as_public(self) -> None:
1615    def set_current_trace_as_public(self) -> None:
1616        """Make the current trace publicly accessible via its URL.
1617
1618        When a trace is published, anyone with the trace link can view the full trace
1619        without needing to be logged in to Langfuse. This action cannot be undone
1620        programmatically - once published, the entire trace becomes public.
1621
1622        This is a convenience method that publishes the trace from the currently
1623        active span context. Use this when you want to make a trace public from
1624        within a traced function without needing direct access to the span object.
1625        """
1626        if not self._tracing_enabled:
1627            langfuse_logger.debug(
1628                "Operation skipped: set_current_trace_as_public - Tracing is disabled or client is in no-op mode."
1629            )
1630            return
1631
1632        current_otel_span = self._get_current_otel_span()
1633
1634        if current_otel_span is not None and current_otel_span.is_recording():
1635            span_class = self._get_span_class(
1636                self._get_observation_type_from_otel_span(current_otel_span)
1637            )
1638            span = span_class(
1639                otel_span=current_otel_span,
1640                langfuse_client=self,
1641                environment=self._environment,
1642            )
1643
1644            span.set_trace_as_public()

Make the current trace publicly accessible via its URL.

When a trace is published, anyone with the trace link can view the full trace without needing to be logged in to Langfuse. This action cannot be undone programmatically - once published, the entire trace becomes public.

This is a convenience method that publishes the trace from the currently active span context. Use this when you want to make a trace public from within a traced function without needing direct access to the span object.

def create_event( self, *, trace_context: Optional[langfuse.types.TraceContext] = None, name: str, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None) -> LangfuseEvent:
1646    def create_event(
1647        self,
1648        *,
1649        trace_context: Optional[TraceContext] = None,
1650        name: str,
1651        input: Optional[Any] = None,
1652        output: Optional[Any] = None,
1653        metadata: Optional[Any] = None,
1654        version: Optional[str] = None,
1655        level: Optional[SpanLevel] = None,
1656        status_message: Optional[str] = None,
1657    ) -> LangfuseEvent:
1658        """Create a new Langfuse observation of type 'EVENT'.
1659
1660        The created Langfuse Event observation will be the child of the current span in the context.
1661
1662        Args:
1663            trace_context: Optional context for connecting to an existing trace
1664            name: Name of the span (e.g., function or operation name)
1665            input: Input data for the operation (can be any JSON-serializable object)
1666            output: Output data from the operation (can be any JSON-serializable object)
1667            metadata: Additional metadata to associate with the span
1668            version: Version identifier for the code or component
1669            level: Importance level of the span (info, warning, error)
1670            status_message: Optional status message for the span
1671
1672        Returns:
1673            The Langfuse Event object
1674
1675        Example:
1676            ```python
1677            event = langfuse.create_event(name="process-event")
1678            ```
1679        """
1680        timestamp = time_ns()
1681
1682        if trace_context:
1683            trace_id = trace_context.get("trace_id", None)
1684            parent_span_id = trace_context.get("parent_span_id", None)
1685
1686            if trace_id:
1687                remote_parent_span = self._create_remote_parent_span(
1688                    trace_id=trace_id, parent_span_id=parent_span_id
1689                )
1690
1691                with otel_trace_api.use_span(
1692                    cast(otel_trace_api.Span, remote_parent_span)
1693                ):
1694                    otel_span = self._otel_tracer.start_span(
1695                        name=name, start_time=timestamp
1696                    )
1697                    otel_span.set_attribute(LangfuseOtelSpanAttributes.AS_ROOT, True)
1698
1699                    return cast(
1700                        LangfuseEvent,
1701                        LangfuseEvent(
1702                            otel_span=otel_span,
1703                            langfuse_client=self,
1704                            environment=self._environment,
1705                            release=self._release,
1706                            input=input,
1707                            output=output,
1708                            metadata=metadata,
1709                            version=version,
1710                            level=level,
1711                            status_message=status_message,
1712                        ).end(end_time=timestamp),
1713                    )
1714
1715        otel_span = self._otel_tracer.start_span(name=name, start_time=timestamp)
1716
1717        return cast(
1718            LangfuseEvent,
1719            LangfuseEvent(
1720                otel_span=otel_span,
1721                langfuse_client=self,
1722                environment=self._environment,
1723                release=self._release,
1724                input=input,
1725                output=output,
1726                metadata=metadata,
1727                version=version,
1728                level=level,
1729                status_message=status_message,
1730            ).end(end_time=timestamp),
1731        )

Create a new Langfuse observation of type 'EVENT'.

The created Langfuse Event observation will be the child of the current span in the context.

Arguments:
  • trace_context: Optional context for connecting to an existing trace
  • name: Name of the span (e.g., function or operation name)
  • input: Input data for the operation (can be any JSON-serializable object)
  • output: Output data from the operation (can be any JSON-serializable object)
  • metadata: Additional metadata to associate with the span
  • version: Version identifier for the code or component
  • level: Importance level of the span (info, warning, error)
  • status_message: Optional status message for the span
Returns:

The Langfuse Event object

Example:
event = langfuse.create_event(name="process-event")
@staticmethod
def create_trace_id(*, seed: Optional[str] = None) -> str:
1824    @staticmethod
1825    def create_trace_id(*, seed: Optional[str] = None) -> str:
1826        """Create a unique trace ID for use with Langfuse.
1827
1828        This method generates a unique trace ID for use with various Langfuse APIs.
1829        It can either generate a random ID or create a deterministic ID based on
1830        a seed string.
1831
1832        Trace IDs must be 32 lowercase hexadecimal characters, representing 16 bytes.
1833        This method ensures the generated ID meets this requirement. If you need to
1834        correlate an external ID with a Langfuse trace ID, use the external ID as the
1835        seed to get a valid, deterministic Langfuse trace ID.
1836
1837        Args:
1838            seed: Optional string to use as a seed for deterministic ID generation.
1839                 If provided, the same seed will always produce the same ID.
1840                 If not provided, a random ID will be generated.
1841
1842        Returns:
1843            A 32-character lowercase hexadecimal string representing the Langfuse trace ID.
1844
1845        Example:
1846            ```python
1847            # Generate a random trace ID
1848            trace_id = langfuse.create_trace_id()
1849
1850            # Generate a deterministic ID based on a seed
1851            session_trace_id = langfuse.create_trace_id(seed="session-456")
1852
1853            # Correlate an external ID with a Langfuse trace ID
1854            external_id = "external-system-123456"
1855            correlated_trace_id = langfuse.create_trace_id(seed=external_id)
1856
1857            # Use the ID with trace context
1858            with langfuse.start_as_current_observation(
1859                name="process-request",
1860                trace_context={"trace_id": trace_id}
1861            ) as span:
1862                # Operation will be part of the specific trace
1863                pass
1864            ```
1865        """
1866        if not seed:
1867            trace_id_int = RandomIdGenerator().generate_trace_id()
1868
1869            return Langfuse._format_otel_trace_id(trace_id_int)
1870
1871        return sha256(seed.encode("utf-8")).digest()[:16].hex()

Create a unique trace ID for use with Langfuse.

This method generates a unique trace ID for use with various Langfuse APIs. It can either generate a random ID or create a deterministic ID based on a seed string.

Trace IDs must be 32 lowercase hexadecimal characters, representing 16 bytes. This method ensures the generated ID meets this requirement. If you need to correlate an external ID with a Langfuse trace ID, use the external ID as the seed to get a valid, deterministic Langfuse trace ID.

Arguments:
  • seed: Optional string to use as a seed for deterministic ID generation. If provided, the same seed will always produce the same ID. If not provided, a random ID will be generated.
Returns:

A 32-character lowercase hexadecimal string representing the Langfuse trace ID.

Example:
# Generate a random trace ID
trace_id = langfuse.create_trace_id()

# Generate a deterministic ID based on a seed
session_trace_id = langfuse.create_trace_id(seed="session-456")

# Correlate an external ID with a Langfuse trace ID
external_id = "external-system-123456"
correlated_trace_id = langfuse.create_trace_id(seed=external_id)

# Use the ID with trace context
with langfuse.start_as_current_observation(
    name="process-request",
    trace_context={"trace_id": trace_id}
) as span:
    # Operation will be part of the specific trace
    pass
def create_score( self, *, name: str, value: Union[float, str], session_id: Optional[str] = None, dataset_run_id: Optional[str] = None, trace_id: Optional[str] = None, observation_id: Optional[str] = None, score_id: Optional[str] = None, data_type: Optional[Literal['NUMERIC', 'CATEGORICAL', 'BOOLEAN', 'TEXT', 'CORRECTION']] = None, comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None, timestamp: Optional[datetime.datetime] = None, environment: Optional[str] = None) -> None:
1953    def create_score(
1954        self,
1955        *,
1956        name: str,
1957        value: Union[float, str],
1958        session_id: Optional[str] = None,
1959        dataset_run_id: Optional[str] = None,
1960        trace_id: Optional[str] = None,
1961        observation_id: Optional[str] = None,
1962        score_id: Optional[str] = None,
1963        data_type: Optional[ScoreDataType] = None,
1964        comment: Optional[str] = None,
1965        config_id: Optional[str] = None,
1966        metadata: Optional[Any] = None,
1967        timestamp: Optional[datetime] = None,
1968        environment: Optional[str] = None,
1969    ) -> None:
1970        """Create a score for a specific trace or observation.
1971
1972        This method creates a score for evaluating a Langfuse trace or observation. Scores can be
1973        used to track quality metrics, user feedback, or automated evaluations.
1974
1975        Args:
1976            name: Name of the score (e.g., "relevance", "accuracy")
1977            value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
1978            session_id: ID of the Langfuse session to associate the score with
1979            dataset_run_id: ID of the Langfuse dataset run to associate the score with
1980            trace_id: ID of the Langfuse trace to associate the score with
1981            observation_id: Optional ID of the specific observation to score. Trace ID must be provided too.
1982            score_id: Optional custom ID for the score (auto-generated if not provided)
1983            data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
1984            comment: Optional comment or explanation for the score
1985            config_id: Optional ID of a score config defined in Langfuse
1986            metadata: Optional metadata to be attached to the score
1987            timestamp: Optional timestamp for the score (defaults to current UTC time)
1988            environment: Optional environment override for this score. If omitted,
1989                the score uses the client-level environment from
1990                `Langfuse(environment=...)` or `LANGFUSE_TRACING_ENVIRONMENT`.
1991                Langfuse observation wrapper methods pass their resolved span
1992                environment here so scores created via `span.score()` or
1993                `span.score_trace()` stay grouped with the scored observation or
1994                trace, including request-scoped environments propagated with
1995                `propagate_attributes(environment=...)`.
1996
1997        Example:
1998            ```python
1999            # Create a numeric score for accuracy
2000            langfuse.create_score(
2001                name="accuracy",
2002                value=0.92,
2003                trace_id="abcdef1234567890abcdef1234567890",
2004                data_type="NUMERIC",
2005                comment="High accuracy with minor irrelevant details"
2006            )
2007
2008            # Create a categorical score for sentiment
2009            langfuse.create_score(
2010                name="sentiment",
2011                value="positive",
2012                trace_id="abcdef1234567890abcdef1234567890",
2013                observation_id="abcdef1234567890",
2014                data_type="CATEGORICAL"
2015            )
2016            ```
2017        """
2018        if not self._tracing_enabled:
2019            return
2020
2021        score_id = score_id or self._create_observation_id()
2022
2023        try:
2024            new_body = ScoreBody(
2025                id=score_id,
2026                sessionId=session_id,
2027                datasetRunId=dataset_run_id,
2028                traceId=trace_id,
2029                observationId=observation_id,
2030                name=name,
2031                value=value,
2032                dataType=data_type,  # type: ignore
2033                comment=comment,
2034                configId=config_id,
2035                environment=environment or self._environment,
2036                metadata=metadata,
2037            )
2038
2039            event = {
2040                "id": self.create_trace_id(),
2041                "type": "score-create",
2042                "timestamp": timestamp or _get_timestamp(),
2043                "body": new_body,
2044            }
2045
2046            if self._resources is not None:
2047                # Force the score to be in sample if it was for a legacy trace ID, i.e. non-32 hexchar
2048                force_sample = (
2049                    not self._is_valid_trace_id(trace_id) if trace_id else True
2050                )
2051
2052                self._resources.add_score_task(
2053                    event,
2054                    force_sample=force_sample,
2055                )
2056
2057        except Exception as e:
2058            langfuse_logger.exception(
2059                "Error creating score: Failed to process score event for trace_id=%s, "
2060                "name=%s. Error: %s",
2061                trace_id,
2062                name,
2063                e,
2064            )

Create a score for a specific trace or observation.

This method creates a score for evaluating a Langfuse trace or observation. Scores can be used to track quality metrics, user feedback, or automated evaluations.

Arguments:
  • name: Name of the score (e.g., "relevance", "accuracy")
  • value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
  • session_id: ID of the Langfuse session to associate the score with
  • dataset_run_id: ID of the Langfuse dataset run to associate the score with
  • trace_id: ID of the Langfuse trace to associate the score with
  • observation_id: Optional ID of the specific observation to score. Trace ID must be provided too.
  • score_id: Optional custom ID for the score (auto-generated if not provided)
  • data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
  • comment: Optional comment or explanation for the score
  • config_id: Optional ID of a score config defined in Langfuse
  • metadata: Optional metadata to be attached to the score
  • timestamp: Optional timestamp for the score (defaults to current UTC time)
  • environment: Optional environment override for this score. If omitted, the score uses the client-level environment from Langfuse(environment=...) or LANGFUSE_TRACING_ENVIRONMENT. Langfuse observation wrapper methods pass their resolved span environment here so scores created via span.score() or span.score_trace() stay grouped with the scored observation or trace, including request-scoped environments propagated with propagate_attributes(environment=...).
Example:
# Create a numeric score for accuracy
langfuse.create_score(
    name="accuracy",
    value=0.92,
    trace_id="abcdef1234567890abcdef1234567890",
    data_type="NUMERIC",
    comment="High accuracy with minor irrelevant details"
)

# Create a categorical score for sentiment
langfuse.create_score(
    name="sentiment",
    value="positive",
    trace_id="abcdef1234567890abcdef1234567890",
    observation_id="abcdef1234567890",
    data_type="CATEGORICAL"
)
def score_current_span( self, *, name: str, value: Union[float, str], score_id: Optional[str] = None, data_type: Optional[Literal['NUMERIC', 'CATEGORICAL', 'BOOLEAN', 'TEXT', 'CORRECTION']] = None, comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None) -> None:
2130    def score_current_span(
2131        self,
2132        *,
2133        name: str,
2134        value: Union[float, str],
2135        score_id: Optional[str] = None,
2136        data_type: Optional[ScoreDataType] = None,
2137        comment: Optional[str] = None,
2138        config_id: Optional[str] = None,
2139        metadata: Optional[Any] = None,
2140    ) -> None:
2141        """Create a score for the current active span.
2142
2143        This method scores the currently active span in the context. It's a convenient
2144        way to score the current operation without needing to know its trace and span IDs.
2145        If the active span has a `langfuse.environment` attribute, including one
2146        set by `propagate_attributes(environment=...)`, the score uses that
2147        environment. Otherwise it uses the client-level environment.
2148
2149        Args:
2150            name: Name of the score (e.g., "relevance", "accuracy")
2151            value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
2152            score_id: Optional custom ID for the score (auto-generated if not provided)
2153            data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
2154            comment: Optional comment or explanation for the score
2155            config_id: Optional ID of a score config defined in Langfuse
2156            metadata: Optional metadata to be attached to the score
2157
2158        Example:
2159            ```python
2160            with langfuse.start_as_current_generation(name="answer-query") as generation:
2161                # Generate answer
2162                response = generate_answer(...)
2163                generation.update(output=response)
2164
2165                # Score the generation
2166                langfuse.score_current_span(
2167                    name="relevance",
2168                    value=0.85,
2169                    data_type="NUMERIC",
2170                    comment="Mostly relevant but contains some tangential information",
2171                    metadata={"model": "gpt-4", "prompt_version": "v2"}
2172                )
2173            ```
2174        """
2175        current_span = self._get_current_otel_span()
2176
2177        if current_span is not None:
2178            trace_id = self._get_otel_trace_id(current_span)
2179            observation_id = self._get_otel_span_id(current_span)
2180
2181            langfuse_logger.info(
2182                "Score: Creating score name='%s' value=%s for current span (%s) in trace "
2183                "%s",
2184                name,
2185                value,
2186                observation_id,
2187                trace_id,
2188            )
2189
2190            self.create_score(
2191                trace_id=trace_id,
2192                observation_id=observation_id,
2193                name=name,
2194                value=cast(str, value),
2195                score_id=score_id,
2196                data_type=cast(Literal["CATEGORICAL", "TEXT", "CORRECTION"], data_type),
2197                comment=comment,
2198                config_id=config_id,
2199                metadata=metadata,
2200                environment=get_string_span_attribute(
2201                    current_span, LangfuseOtelSpanAttributes.ENVIRONMENT
2202                ),
2203            )

Create a score for the current active span.

This method scores the currently active span in the context. It's a convenient way to score the current operation without needing to know its trace and span IDs. If the active span has a langfuse.environment attribute, including one set by propagate_attributes(environment=...), the score uses that environment. Otherwise it uses the client-level environment.

Arguments:
  • name: Name of the score (e.g., "relevance", "accuracy")
  • value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
  • score_id: Optional custom ID for the score (auto-generated if not provided)
  • data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
  • comment: Optional comment or explanation for the score
  • config_id: Optional ID of a score config defined in Langfuse
  • metadata: Optional metadata to be attached to the score
Example:
with langfuse.start_as_current_generation(name="answer-query") as generation:
    # Generate answer
    response = generate_answer(...)
    generation.update(output=response)

    # Score the generation
    langfuse.score_current_span(
        name="relevance",
        value=0.85,
        data_type="NUMERIC",
        comment="Mostly relevant but contains some tangential information",
        metadata={"model": "gpt-4", "prompt_version": "v2"}
    )
def score_current_trace( self, *, name: str, value: Union[float, str], score_id: Optional[str] = None, data_type: Optional[Literal['NUMERIC', 'CATEGORICAL', 'BOOLEAN', 'TEXT', 'CORRECTION']] = None, comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None) -> None:
2233    def score_current_trace(
2234        self,
2235        *,
2236        name: str,
2237        value: Union[float, str],
2238        score_id: Optional[str] = None,
2239        data_type: Optional[ScoreDataType] = None,
2240        comment: Optional[str] = None,
2241        config_id: Optional[str] = None,
2242        metadata: Optional[Any] = None,
2243    ) -> None:
2244        """Create a score for the current trace.
2245
2246        This method scores the trace of the currently active span. Unlike score_current_span,
2247        this method associates the score with the entire trace rather than a specific span.
2248        It's useful for scoring overall performance or quality of the entire operation.
2249        If the active span has a `langfuse.environment` attribute, including one
2250        set by `propagate_attributes(environment=...)`, the score uses that
2251        environment. Otherwise it uses the client-level environment.
2252
2253        Args:
2254            name: Name of the score (e.g., "user_satisfaction", "overall_quality")
2255            value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
2256            score_id: Optional custom ID for the score (auto-generated if not provided)
2257            data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
2258            comment: Optional comment or explanation for the score
2259            config_id: Optional ID of a score config defined in Langfuse
2260            metadata: Optional metadata to be attached to the score
2261
2262        Example:
2263            ```python
2264            with langfuse.start_as_current_observation(name="process-user-request") as span:
2265                # Process request
2266                result = process_complete_request()
2267                span.update(output=result)
2268
2269                # Score the overall trace
2270                langfuse.score_current_trace(
2271                    name="overall_quality",
2272                    value=0.95,
2273                    data_type="NUMERIC",
2274                    comment="High quality end-to-end response",
2275                    metadata={"evaluator": "gpt-4", "criteria": "comprehensive"}
2276                )
2277            ```
2278        """
2279        current_span = self._get_current_otel_span()
2280
2281        if current_span is not None:
2282            trace_id = self._get_otel_trace_id(current_span)
2283
2284            langfuse_logger.info(
2285                "Score: Creating score name='%s' value=%s for entire trace %s",
2286                name,
2287                value,
2288                trace_id,
2289            )
2290
2291            self.create_score(
2292                trace_id=trace_id,
2293                name=name,
2294                value=cast(str, value),
2295                score_id=score_id,
2296                data_type=cast(Literal["CATEGORICAL", "TEXT", "CORRECTION"], data_type),
2297                comment=comment,
2298                config_id=config_id,
2299                metadata=metadata,
2300                environment=get_string_span_attribute(
2301                    current_span, LangfuseOtelSpanAttributes.ENVIRONMENT
2302                ),
2303            )

Create a score for the current trace.

This method scores the trace of the currently active span. Unlike score_current_span, this method associates the score with the entire trace rather than a specific span. It's useful for scoring overall performance or quality of the entire operation. If the active span has a langfuse.environment attribute, including one set by propagate_attributes(environment=...), the score uses that environment. Otherwise it uses the client-level environment.

Arguments:
  • name: Name of the score (e.g., "user_satisfaction", "overall_quality")
  • value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT/CORRECTION)
  • score_id: Optional custom ID for the score (auto-generated if not provided)
  • data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, or CORRECTION)
  • comment: Optional comment or explanation for the score
  • config_id: Optional ID of a score config defined in Langfuse
  • metadata: Optional metadata to be attached to the score
Example:
with langfuse.start_as_current_observation(name="process-user-request") as span:
    # Process request
    result = process_complete_request()
    span.update(output=result)

    # Score the overall trace
    langfuse.score_current_trace(
        name="overall_quality",
        value=0.95,
        data_type="NUMERIC",
        comment="High quality end-to-end response",
        metadata={"evaluator": "gpt-4", "criteria": "comprehensive"}
    )
def flush(self) -> None:
2305    def flush(self) -> None:
2306        """Force flush all pending spans and events to the Langfuse API.
2307
2308        This method manually flushes any pending spans, scores, and other events to the
2309        Langfuse API. It's useful in scenarios where you want to ensure all data is sent
2310        before proceeding, without waiting for the automatic flush interval.
2311
2312        Example:
2313            ```python
2314            # Record some spans and scores
2315            with langfuse.start_as_current_observation(name="operation") as span:
2316                # Do work...
2317                pass
2318
2319            # Ensure all data is sent to Langfuse before proceeding
2320            langfuse.flush()
2321
2322            # Continue with other work
2323            ```
2324
2325        Note:
2326            `flush()` guarantees data was *delivered* to the API, not that it is
2327            *readable* yet: server-side ingestion is asynchronous, so flushed data
2328            may not be queryable for 15-30 seconds —
2329            `api.observations.get_many(trace_id=...)` may return empty results and
2330            `api.trace.get()` may raise `langfuse.api.NotFoundError` right after a
2331            successful flush. See the `api` property docs for a bounded retry
2332            pattern, or
2333            https://langfuse.com/docs/api-and-data-platform/features/query-via-sdk#ingestion-lag
2334        """
2335        if self._resources is not None:
2336            self._resources.flush()

Force flush all pending spans and events to the Langfuse API.

This method manually flushes any pending spans, scores, and other events to the Langfuse API. It's useful in scenarios where you want to ensure all data is sent before proceeding, without waiting for the automatic flush interval.

Example:
# Record some spans and scores
with langfuse.start_as_current_observation(name="operation") as span:
    # Do work...
    pass

# Ensure all data is sent to Langfuse before proceeding
langfuse.flush()

# Continue with other work
Note:

flush() guarantees data was delivered to the API, not that it is readable yet: server-side ingestion is asynchronous, so flushed data may not be queryable for 15-30 seconds — api.observations.get_many(trace_id=...) may return empty results and api.trace.get() may raise langfuse.api.NotFoundError right after a successful flush. See the api property docs for a bounded retry pattern, or https://langfuse.com/docs/api-and-data-platform/features/query-via-sdk#ingestion-lag

def shutdown(self) -> None:
2338    def shutdown(self) -> None:
2339        """Shut down the Langfuse client and flush all pending data.
2340
2341        This method cleanly shuts down the Langfuse client, ensuring all pending data
2342        is flushed to the API and all background threads are properly terminated.
2343
2344        It's important to call this method when your application is shutting down to
2345        prevent data loss and resource leaks. For most applications, using the client
2346        as a context manager or relying on the automatic shutdown via atexit is sufficient.
2347
2348        Example:
2349            ```python
2350            # Initialize Langfuse
2351            langfuse = Langfuse(public_key="...", secret_key="...")
2352
2353            # Use Langfuse throughout your application
2354            # ...
2355
2356            # When application is shutting down
2357            langfuse.shutdown()
2358            ```
2359        """
2360        if self._resources is not None:
2361            self._resources.shutdown()

Shut down the Langfuse client and flush all pending data.

This method cleanly shuts down the Langfuse client, ensuring all pending data is flushed to the API and all background threads are properly terminated.

It's important to call this method when your application is shutting down to prevent data loss and resource leaks. For most applications, using the client as a context manager or relying on the automatic shutdown via atexit is sufficient.

Example:
# Initialize Langfuse
langfuse = Langfuse(public_key="...", secret_key="...")

# Use Langfuse throughout your application
# ...

# When application is shutting down
langfuse.shutdown()
def get_current_trace_id(self) -> Optional[str]:
2363    def get_current_trace_id(self) -> Optional[str]:
2364        """Get the trace ID of the current active span.
2365
2366        This method retrieves the trace ID from the currently active span in the context.
2367        It can be used to get the trace ID for referencing in logs, external systems,
2368        or for creating related operations.
2369
2370        Returns:
2371            The current trace ID as a 32-character lowercase hexadecimal string,
2372            or None if there is no active span.
2373
2374        Example:
2375            ```python
2376            with langfuse.start_as_current_observation(name="process-request") as span:
2377                # Get the current trace ID for reference
2378                trace_id = langfuse.get_current_trace_id()
2379
2380                # Use it for external correlation
2381                log.info(f"Processing request with trace_id: {trace_id}")
2382
2383                # Or pass to another system
2384                external_system.process(data, trace_id=trace_id)
2385            ```
2386        """
2387        if not self._tracing_enabled:
2388            langfuse_logger.debug(
2389                "Operation skipped: get_current_trace_id - Tracing is disabled or client is in no-op mode."
2390            )
2391            return None
2392
2393        current_otel_span = self._get_current_otel_span()
2394
2395        return self._get_otel_trace_id(current_otel_span) if current_otel_span else None

Get the trace ID of the current active span.

This method retrieves the trace ID from the currently active span in the context. It can be used to get the trace ID for referencing in logs, external systems, or for creating related operations.

Returns:

The current trace ID as a 32-character lowercase hexadecimal string, or None if there is no active span.

Example:
with langfuse.start_as_current_observation(name="process-request") as span:
    # Get the current trace ID for reference
    trace_id = langfuse.get_current_trace_id()

    # Use it for external correlation
    log.info(f"Processing request with trace_id: {trace_id}")

    # Or pass to another system
    external_system.process(data, trace_id=trace_id)
def get_current_observation_id(self) -> Optional[str]:
2397    def get_current_observation_id(self) -> Optional[str]:
2398        """Get the observation ID (span ID) of the current active span.
2399
2400        This method retrieves the observation ID from the currently active span in the context.
2401        It can be used to get the observation ID for referencing in logs, external systems,
2402        or for creating scores or other related operations.
2403
2404        Returns:
2405            The current observation ID as a 16-character lowercase hexadecimal string,
2406            or None if there is no active span.
2407
2408        Example:
2409            ```python
2410            with langfuse.start_as_current_observation(name="process-user-query") as span:
2411                # Get the current observation ID
2412                observation_id = langfuse.get_current_observation_id()
2413
2414                # Store it for later reference
2415                cache.set(f"query_{query_id}_observation", observation_id)
2416
2417                # Process the query...
2418            ```
2419        """
2420        if not self._tracing_enabled:
2421            langfuse_logger.debug(
2422                "Operation skipped: get_current_observation_id - Tracing is disabled or client is in no-op mode."
2423            )
2424            return None
2425
2426        current_otel_span = self._get_current_otel_span()
2427
2428        return self._get_otel_span_id(current_otel_span) if current_otel_span else None

Get the observation ID (span ID) of the current active span.

This method retrieves the observation ID from the currently active span in the context. It can be used to get the observation ID for referencing in logs, external systems, or for creating scores or other related operations.

Returns:

The current observation ID as a 16-character lowercase hexadecimal string, or None if there is no active span.

Example:
with langfuse.start_as_current_observation(name="process-user-query") as span:
    # Get the current observation ID
    observation_id = langfuse.get_current_observation_id()

    # Store it for later reference
    cache.set(f"query_{query_id}_observation", observation_id)

    # Process the query...
def get_trace_url(self, *, trace_id: Optional[str] = None) -> Optional[str]:
2441    def get_trace_url(self, *, trace_id: Optional[str] = None) -> Optional[str]:
2442        """Get the URL to view a trace in the Langfuse UI.
2443
2444        This method generates a URL that links directly to a trace in the Langfuse UI.
2445        It's useful for providing links in logs, notifications, or debugging tools.
2446
2447        Args:
2448            trace_id: Optional trace ID to generate a URL for. If not provided,
2449                     the trace ID of the current active span will be used.
2450
2451        Returns:
2452            A URL string pointing to the trace in the Langfuse UI,
2453            or None if the project ID couldn't be retrieved or no trace ID is available.
2454
2455        Example:
2456            ```python
2457            # Get URL for the current trace
2458            with langfuse.start_as_current_observation(name="process-request") as span:
2459                trace_url = langfuse.get_trace_url()
2460                log.info(f"Processing trace: {trace_url}")
2461
2462            # Get URL for a specific trace
2463            specific_trace_url = langfuse.get_trace_url(trace_id="1234567890abcdef1234567890abcdef")
2464            send_notification(f"Review needed for trace: {specific_trace_url}")
2465            ```
2466        """
2467        final_trace_id = trace_id or self.get_current_trace_id()
2468        if not final_trace_id:
2469            return None
2470
2471        project_id = self._get_project_id()
2472
2473        return (
2474            f"{self._base_url}/project/{project_id}/traces/{final_trace_id}"
2475            if project_id and final_trace_id
2476            else None
2477        )

Get the URL to view a trace in the Langfuse UI.

This method generates a URL that links directly to a trace in the Langfuse UI. It's useful for providing links in logs, notifications, or debugging tools.

Arguments:
  • trace_id: Optional trace ID to generate a URL for. If not provided, the trace ID of the current active span will be used.
Returns:

A URL string pointing to the trace in the Langfuse UI, or None if the project ID couldn't be retrieved or no trace ID is available.

Example:
# Get URL for the current trace
with langfuse.start_as_current_observation(name="process-request") as span:
    trace_url = langfuse.get_trace_url()
    log.info(f"Processing trace: {trace_url}")

# Get URL for a specific trace
specific_trace_url = langfuse.get_trace_url(trace_id="1234567890abcdef1234567890abcdef")
send_notification(f"Review needed for trace: {specific_trace_url}")
def get_dataset( self, name: str, *, fetch_items_page_size: Optional[int] = 50, version: Optional[datetime.datetime] = None) -> langfuse._client.datasets.DatasetClient:
2479    def get_dataset(
2480        self,
2481        name: str,
2482        *,
2483        fetch_items_page_size: Optional[int] = 50,
2484        version: Optional[datetime] = None,
2485    ) -> "DatasetClient":
2486        """Fetch a dataset by its name.
2487
2488        Args:
2489            name: The name of the dataset to fetch.
2490            fetch_items_page_size: All items of the dataset will be fetched in chunks of this size. Defaults to 50.
2491            version: Retrieve dataset items as they existed at this specific point in time (UTC).
2492                If provided, returns the state of items at the specified UTC timestamp.
2493                If not provided, returns the latest version. Must be a timezone-aware datetime object in UTC.
2494
2495        Returns:
2496            DatasetClient: The dataset with the given name.
2497        """
2498        try:
2499            langfuse_logger.debug("Getting datasets %s", name)
2500            dataset = self.api.datasets.get(dataset_name=self._url_encode(name))
2501
2502            dataset_items: List[DatasetItem] = []
2503            page = 1
2504
2505            while True:
2506                new_items = self.api.dataset_items.list(
2507                    dataset_name=self._url_encode(name, is_url_param=True),
2508                    page=page,
2509                    limit=fetch_items_page_size,
2510                    version=version,
2511                )
2512                dataset_items.extend(
2513                    self._hydrate_dataset_item_media_references(item)
2514                    for item in new_items.data
2515                )
2516
2517                if new_items.meta.total_pages <= page:
2518                    break
2519
2520                page += 1
2521
2522            return DatasetClient(
2523                dataset=dataset,
2524                items=dataset_items,
2525                version=version,
2526                langfuse_client=self,
2527            )
2528
2529        except Error as e:
2530            handle_fern_exception(e)
2531            raise e

Fetch a dataset by its name.

Arguments:
  • name: The name of the dataset to fetch.
  • fetch_items_page_size: All items of the dataset will be fetched in chunks of this size. Defaults to 50.
  • version: Retrieve dataset items as they existed at this specific point in time (UTC). If provided, returns the state of items at the specified UTC timestamp. If not provided, returns the latest version. Must be a timezone-aware datetime object in UTC.
Returns:

DatasetClient: The dataset with the given name.

def get_dataset_run( self, *, dataset_name: str, run_name: str) -> langfuse.api.DatasetRunWithItems:
2533    def get_dataset_run(
2534        self, *, dataset_name: str, run_name: str
2535    ) -> DatasetRunWithItems:
2536        """Fetch a dataset run by dataset name and run name.
2537
2538        Args:
2539            dataset_name (str): The name of the dataset.
2540            run_name (str): The name of the run.
2541
2542        Returns:
2543            DatasetRunWithItems: The dataset run with its items.
2544        """
2545        try:
2546            return cast(
2547                DatasetRunWithItems,
2548                self.api.datasets.get_run(
2549                    dataset_name=self._url_encode(dataset_name),
2550                    run_name=self._url_encode(run_name),
2551                    request_options=None,
2552                ),
2553            )
2554        except Error as e:
2555            handle_fern_exception(e)
2556            raise e

Fetch a dataset run by dataset name and run name.

Arguments:
  • dataset_name (str): The name of the dataset.
  • run_name (str): The name of the run.
Returns:

DatasetRunWithItems: The dataset run with its items.

def get_dataset_runs( self, *, dataset_name: str, page: Optional[int] = None, limit: Optional[int] = None) -> langfuse.api.PaginatedDatasetRuns:
2558    def get_dataset_runs(
2559        self,
2560        *,
2561        dataset_name: str,
2562        page: Optional[int] = None,
2563        limit: Optional[int] = None,
2564    ) -> PaginatedDatasetRuns:
2565        """Fetch all runs for a dataset.
2566
2567        Args:
2568            dataset_name (str): The name of the dataset.
2569            page (Optional[int]): Page number, starts at 1.
2570            limit (Optional[int]): Limit of items per page.
2571
2572        Returns:
2573            PaginatedDatasetRuns: Paginated list of dataset runs.
2574        """
2575        try:
2576            return cast(
2577                PaginatedDatasetRuns,
2578                self.api.datasets.get_runs(
2579                    dataset_name=self._url_encode(dataset_name),
2580                    page=page,
2581                    limit=limit,
2582                    request_options=None,
2583                ),
2584            )
2585        except Error as e:
2586            handle_fern_exception(e)
2587            raise e

Fetch all runs for a dataset.

Arguments:
  • dataset_name (str): The name of the dataset.
  • page (Optional[int]): Page number, starts at 1.
  • limit (Optional[int]): Limit of items per page.
Returns:

PaginatedDatasetRuns: Paginated list of dataset runs.

def delete_dataset_run( self, *, dataset_name: str, run_name: str) -> langfuse.api.DeleteDatasetRunResponse:
2589    def delete_dataset_run(
2590        self, *, dataset_name: str, run_name: str
2591    ) -> DeleteDatasetRunResponse:
2592        """Delete a dataset run and all its run items. This action is irreversible.
2593
2594        Args:
2595            dataset_name (str): The name of the dataset.
2596            run_name (str): The name of the run.
2597
2598        Returns:
2599            DeleteDatasetRunResponse: Confirmation of deletion.
2600        """
2601        try:
2602            return cast(
2603                DeleteDatasetRunResponse,
2604                self.api.datasets.delete_run(
2605                    dataset_name=self._url_encode(dataset_name),
2606                    run_name=self._url_encode(run_name),
2607                    request_options=None,
2608                ),
2609            )
2610        except Error as e:
2611            handle_fern_exception(e)
2612            raise e

Delete a dataset run and all its run items. This action is irreversible.

Arguments:
  • dataset_name (str): The name of the dataset.
  • run_name (str): The name of the run.
Returns:

DeleteDatasetRunResponse: Confirmation of deletion.

def run_experiment( self, *, name: str, run_name: Optional[str] = None, description: Optional[str] = None, data: Union[List[langfuse.experiment.LocalExperimentItem], List[langfuse.api.DatasetItem]], task: langfuse.experiment.TaskFunction, evaluators: List[langfuse.experiment.EvaluatorFunction] = [], composite_evaluator: Optional[CompositeEvaluatorFunction] = None, run_evaluators: List[langfuse.experiment.RunEvaluatorFunction] = [], max_concurrency: int = 50, metadata: Optional[Dict[str, str]] = None, _dataset_version: Optional[datetime.datetime] = None) -> langfuse.experiment.ExperimentResult:
2614    def run_experiment(
2615        self,
2616        *,
2617        name: str,
2618        run_name: Optional[str] = None,
2619        description: Optional[str] = None,
2620        data: ExperimentData,
2621        task: TaskFunction,
2622        evaluators: List[EvaluatorFunction] = [],
2623        composite_evaluator: Optional[CompositeEvaluatorFunction] = None,
2624        run_evaluators: List[RunEvaluatorFunction] = [],
2625        max_concurrency: int = 50,
2626        metadata: Optional[Dict[str, str]] = None,
2627        _dataset_version: Optional[datetime] = None,
2628    ) -> ExperimentResult:
2629        """Run an experiment on a dataset with automatic tracing and evaluation.
2630
2631        This method executes a task function on each item in the provided dataset,
2632        automatically traces all executions with Langfuse for observability, runs
2633        item-level and run-level evaluators on the outputs, and returns comprehensive
2634        results with evaluation metrics.
2635
2636        The experiment system provides:
2637        - Automatic tracing of all task executions
2638        - Concurrent processing with configurable limits
2639        - Comprehensive error handling that isolates failures
2640        - Integration with Langfuse datasets for experiment tracking
2641        - Flexible evaluation framework supporting both sync and async evaluators
2642
2643        Args:
2644            name: Human-readable name for the experiment. Used for identification
2645                in the Langfuse UI.
2646            run_name: Optional exact name for the experiment run. If provided, this will be
2647                used as the exact dataset run name if the `data` contains Langfuse dataset items.
2648                If not provided, this will default to the experiment name appended with an ISO timestamp.
2649            description: Optional description explaining the experiment's purpose,
2650                methodology, or expected outcomes.
2651            data: Array of data items to process. Can be either:
2652                - List of dict-like items with 'input', 'expected_output', 'metadata' keys
2653                - List of Langfuse DatasetItem objects from dataset.items
2654            task: Function that processes each data item and returns output.
2655                Must accept 'item' as keyword argument and can return sync or async results.
2656                The task function signature should be: task(*, item, **kwargs) -> Any
2657            evaluators: List of functions to evaluate each item's output individually.
2658                Each evaluator receives input, output, expected_output, and metadata.
2659                Can return single Evaluation dict or list of Evaluation dicts.
2660            composite_evaluator: Optional function that creates composite scores from item-level evaluations.
2661                Receives the same inputs as item-level evaluators (input, output, expected_output, metadata)
2662                plus the list of evaluations from item-level evaluators. Useful for weighted averages,
2663                pass/fail decisions based on multiple criteria, or custom scoring logic combining multiple metrics.
2664            run_evaluators: List of functions to evaluate the entire experiment run.
2665                Each run evaluator receives all item_results and can compute aggregate metrics.
2666                Useful for calculating averages, distributions, or cross-item comparisons.
2667            max_concurrency: Maximum number of concurrent task executions (default: 50).
2668                Controls the number of items processed simultaneously. Adjust based on
2669                API rate limits and system resources.
2670            metadata: Optional metadata dictionary to attach to all experiment traces.
2671                This metadata will be included in every trace created during the experiment.
2672                If `data` are Langfuse dataset items, the metadata will be attached to the dataset run, too.
2673
2674        Returns:
2675            ExperimentResult containing:
2676            - run_name: The experiment run name. This is equal to the dataset run name if experiment was on Langfuse dataset.
2677            - item_results: List of results for each processed item with outputs and evaluations
2678            - run_evaluations: List of aggregate evaluation results for the entire run
2679            - experiment_id: Stable identifier for the experiment run across all items
2680            - dataset_run_id: ID of the dataset run (if using Langfuse datasets)
2681            - dataset_run_url: Direct URL to view results in Langfuse UI (if applicable)
2682
2683        Raises:
2684            ValueError: If required parameters are missing or invalid
2685            Exception: If experiment setup fails (individual item failures are handled gracefully)
2686
2687        Examples:
2688            Basic experiment with local data:
2689            ```python
2690            def summarize_text(*, item, **kwargs):
2691                return f"Summary: {item['input'][:50]}..."
2692
2693            def length_evaluator(*, input, output, expected_output=None, **kwargs):
2694                return {
2695                    "name": "output_length",
2696                    "value": len(output),
2697                    "comment": f"Output contains {len(output)} characters"
2698                }
2699
2700            result = langfuse.run_experiment(
2701                name="Text Summarization Test",
2702                description="Evaluate summarization quality and length",
2703                data=[
2704                    {"input": "Long article text...", "expected_output": "Expected summary"},
2705                    {"input": "Another article...", "expected_output": "Another summary"}
2706                ],
2707                task=summarize_text,
2708                evaluators=[length_evaluator]
2709            )
2710
2711            print(f"Processed {len(result.item_results)} items")
2712            for item_result in result.item_results:
2713                print(f"Input: {item_result.item['input']}")
2714                print(f"Output: {item_result.output}")
2715                print(f"Evaluations: {item_result.evaluations}")
2716            ```
2717
2718            Advanced experiment with async task and multiple evaluators:
2719            ```python
2720            async def llm_task(*, item, **kwargs):
2721                # Simulate async LLM call
2722                response = await openai_client.chat.completions.create(
2723                    model="gpt-4",
2724                    messages=[{"role": "user", "content": item["input"]}]
2725                )
2726                return response.choices[0].message.content
2727
2728            def accuracy_evaluator(*, input, output, expected_output=None, **kwargs):
2729                if expected_output and expected_output.lower() in output.lower():
2730                    return {"name": "accuracy", "value": 1.0, "comment": "Correct answer"}
2731                return {"name": "accuracy", "value": 0.0, "comment": "Incorrect answer"}
2732
2733            def toxicity_evaluator(*, input, output, expected_output=None, **kwargs):
2734                # Simulate toxicity check
2735                toxicity_score = check_toxicity(output)  # Your toxicity checker
2736                return {
2737                    "name": "toxicity",
2738                    "value": toxicity_score,
2739                    "comment": f"Toxicity level: {'high' if toxicity_score > 0.7 else 'low'}"
2740                }
2741
2742            def average_accuracy(*, item_results, **kwargs):
2743                accuracies = [
2744                    eval.value for result in item_results
2745                    for eval in result.evaluations
2746                    if eval.name == "accuracy"
2747                ]
2748                return {
2749                    "name": "average_accuracy",
2750                    "value": sum(accuracies) / len(accuracies) if accuracies else 0,
2751                    "comment": f"Average accuracy across {len(accuracies)} items"
2752                }
2753
2754            result = langfuse.run_experiment(
2755                name="LLM Safety and Accuracy Test",
2756                description="Evaluate model accuracy and safety across diverse prompts",
2757                data=test_dataset,  # Your dataset items
2758                task=llm_task,
2759                evaluators=[accuracy_evaluator, toxicity_evaluator],
2760                run_evaluators=[average_accuracy],
2761                max_concurrency=5,  # Limit concurrent API calls
2762                metadata={"model": "gpt-4", "temperature": 0.7}
2763            )
2764            ```
2765
2766            Using with Langfuse datasets:
2767            ```python
2768            # Get dataset from Langfuse
2769            dataset = langfuse.get_dataset("my-eval-dataset")
2770
2771            result = dataset.run_experiment(
2772                name="Production Model Evaluation",
2773                description="Monthly evaluation of production model performance",
2774                task=my_production_task,
2775                evaluators=[accuracy_evaluator, latency_evaluator]
2776            )
2777
2778            # Results automatically linked to dataset in Langfuse UI
2779            print(f"View results: {result['dataset_run_url']}")
2780            ```
2781
2782        Note:
2783            - Task and evaluator functions can be either synchronous or asynchronous
2784            - Individual item failures are logged but don't stop the experiment
2785            - All executions are automatically traced and visible in Langfuse UI
2786            - When using Langfuse datasets, results are automatically linked for easy comparison
2787            - This method works in both sync and async contexts (Jupyter notebooks, web apps, etc.)
2788            - Async execution is handled automatically with smart event loop detection
2789        """
2790        return cast(
2791            ExperimentResult,
2792            run_async_safely(
2793                self._run_experiment_async(
2794                    name=name,
2795                    run_name=self._create_experiment_run_name(
2796                        name=name, run_name=run_name
2797                    ),
2798                    description=description,
2799                    data=data,
2800                    task=task,
2801                    evaluators=evaluators or [],
2802                    composite_evaluator=composite_evaluator,
2803                    run_evaluators=run_evaluators or [],
2804                    max_concurrency=max_concurrency,
2805                    metadata=metadata,
2806                    dataset_version=_dataset_version,
2807                ),
2808            ),
2809        )

Run an experiment on a dataset with automatic tracing and evaluation.

This method executes a task function on each item in the provided dataset, automatically traces all executions with Langfuse for observability, runs item-level and run-level evaluators on the outputs, and returns comprehensive results with evaluation metrics.

The experiment system provides:

  • Automatic tracing of all task executions
  • Concurrent processing with configurable limits
  • Comprehensive error handling that isolates failures
  • Integration with Langfuse datasets for experiment tracking
  • Flexible evaluation framework supporting both sync and async evaluators
Arguments:
  • name: Human-readable name for the experiment. Used for identification in the Langfuse UI.
  • run_name: Optional exact name for the experiment run. If provided, this will be used as the exact dataset run name if the data contains Langfuse dataset items. If not provided, this will default to the experiment name appended with an ISO timestamp.
  • description: Optional description explaining the experiment's purpose, methodology, or expected outcomes.
  • data: Array of data items to process. Can be either:
    • List of dict-like items with 'input', 'expected_output', 'metadata' keys
    • List of Langfuse DatasetItem objects from dataset.items
  • task: Function that processes each data item and returns output. Must accept 'item' as keyword argument and can return sync or async results. The task function signature should be: task(*, item, **kwargs) -> Any
  • evaluators: List of functions to evaluate each item's output individually. Each evaluator receives input, output, expected_output, and metadata. Can return single Evaluation dict or list of Evaluation dicts.
  • composite_evaluator: Optional function that creates composite scores from item-level evaluations. Receives the same inputs as item-level evaluators (input, output, expected_output, metadata) plus the list of evaluations from item-level evaluators. Useful for weighted averages, pass/fail decisions based on multiple criteria, or custom scoring logic combining multiple metrics.
  • run_evaluators: List of functions to evaluate the entire experiment run. Each run evaluator receives all item_results and can compute aggregate metrics. Useful for calculating averages, distributions, or cross-item comparisons.
  • max_concurrency: Maximum number of concurrent task executions (default: 50). Controls the number of items processed simultaneously. Adjust based on API rate limits and system resources.
  • metadata: Optional metadata dictionary to attach to all experiment traces. This metadata will be included in every trace created during the experiment. If data are Langfuse dataset items, the metadata will be attached to the dataset run, too.
Returns:

ExperimentResult containing:

  • run_name: The experiment run name. This is equal to the dataset run name if experiment was on Langfuse dataset.
  • item_results: List of results for each processed item with outputs and evaluations
  • run_evaluations: List of aggregate evaluation results for the entire run
  • experiment_id: Stable identifier for the experiment run across all items
  • dataset_run_id: ID of the dataset run (if using Langfuse datasets)
  • dataset_run_url: Direct URL to view results in Langfuse UI (if applicable)
Raises:
  • ValueError: If required parameters are missing or invalid
  • Exception: If experiment setup fails (individual item failures are handled gracefully)
Examples:

Basic experiment with local data:

def summarize_text(*, item, **kwargs):
    return f"Summary: {item['input'][:50]}..."

def length_evaluator(*, input, output, expected_output=None, **kwargs):
    return {
        "name": "output_length",
        "value": len(output),
        "comment": f"Output contains {len(output)} characters"
    }

result = langfuse.run_experiment(
    name="Text Summarization Test",
    description="Evaluate summarization quality and length",
    data=[
        {"input": "Long article text...", "expected_output": "Expected summary"},
        {"input": "Another article...", "expected_output": "Another summary"}
    ],
    task=summarize_text,
    evaluators=[length_evaluator]
)

print(f"Processed {len(result.item_results)} items")
for item_result in result.item_results:
    print(f"Input: {item_result.item['input']}")
    print(f"Output: {item_result.output}")
    print(f"Evaluations: {item_result.evaluations}")

Advanced experiment with async task and multiple evaluators:

async def llm_task(*, item, **kwargs):
    # Simulate async LLM call
    response = await openai_client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": item["input"]}]
    )
    return response.choices[0].message.content

def accuracy_evaluator(*, input, output, expected_output=None, **kwargs):
    if expected_output and expected_output.lower() in output.lower():
        return {"name": "accuracy", "value": 1.0, "comment": "Correct answer"}
    return {"name": "accuracy", "value": 0.0, "comment": "Incorrect answer"}

def toxicity_evaluator(*, input, output, expected_output=None, **kwargs):
    # Simulate toxicity check
    toxicity_score = check_toxicity(output)  # Your toxicity checker
    return {
        "name": "toxicity",
        "value": toxicity_score,
        "comment": f"Toxicity level: {'high' if toxicity_score > 0.7 else 'low'}"
    }

def average_accuracy(*, item_results, **kwargs):
    accuracies = [
        eval.value for result in item_results
        for eval in result.evaluations
        if eval.name == "accuracy"
    ]
    return {
        "name": "average_accuracy",
        "value": sum(accuracies) / len(accuracies) if accuracies else 0,
        "comment": f"Average accuracy across {len(accuracies)} items"
    }

result = langfuse.run_experiment(
    name="LLM Safety and Accuracy Test",
    description="Evaluate model accuracy and safety across diverse prompts",
    data=test_dataset,  # Your dataset items
    task=llm_task,
    evaluators=[accuracy_evaluator, toxicity_evaluator],
    run_evaluators=[average_accuracy],
    max_concurrency=5,  # Limit concurrent API calls
    metadata={"model": "gpt-4", "temperature": 0.7}
)

Using with Langfuse datasets:

# Get dataset from Langfuse
dataset = langfuse.get_dataset("my-eval-dataset")

result = dataset.run_experiment(
    name="Production Model Evaluation",
    description="Monthly evaluation of production model performance",
    task=my_production_task,
    evaluators=[accuracy_evaluator, latency_evaluator]
)

# Results automatically linked to dataset in Langfuse UI
print(f"View results: {result['dataset_run_url']}")
Note:
  • Task and evaluator functions can be either synchronous or asynchronous
  • Individual item failures are logged but don't stop the experiment
  • All executions are automatically traced and visible in Langfuse UI
  • When using Langfuse datasets, results are automatically linked for easy comparison
  • This method works in both sync and async contexts (Jupyter notebooks, web apps, etc.)
  • Async execution is handled automatically with smart event loop detection
def run_batched_evaluation( self, *, scope: Literal['traces', 'observations'], mapper: MapperFunction, filter: Optional[str] = None, fetch_batch_size: int = 50, fetch_trace_fields: Optional[str] = None, max_items: Optional[int] = None, max_retries: int = 3, evaluators: List[langfuse.experiment.EvaluatorFunction], composite_evaluator: Optional[CompositeEvaluatorFunction] = None, max_concurrency: int = 5, metadata: Optional[Dict[str, Any]] = None, _add_observation_scores_to_trace: bool = False, _additional_trace_tags: Optional[List[str]] = None, resume_from: Optional[BatchEvaluationResumeToken] = None, verbose: bool = False) -> BatchEvaluationResult:
3257    def run_batched_evaluation(
3258        self,
3259        *,
3260        scope: Literal["traces", "observations"],
3261        mapper: MapperFunction,
3262        filter: Optional[str] = None,
3263        fetch_batch_size: int = 50,
3264        fetch_trace_fields: Optional[str] = None,
3265        max_items: Optional[int] = None,
3266        max_retries: int = 3,
3267        evaluators: List[EvaluatorFunction],
3268        composite_evaluator: Optional[CompositeEvaluatorFunction] = None,
3269        max_concurrency: int = 5,
3270        metadata: Optional[Dict[str, Any]] = None,
3271        _add_observation_scores_to_trace: bool = False,
3272        _additional_trace_tags: Optional[List[str]] = None,
3273        resume_from: Optional[BatchEvaluationResumeToken] = None,
3274        verbose: bool = False,
3275    ) -> BatchEvaluationResult:
3276        """Fetch traces or observations and run evaluations on each item.
3277
3278        This method provides a powerful way to evaluate existing data in Langfuse at scale.
3279        It fetches items based on filters, transforms them using a mapper function, runs
3280        evaluators on each item, and creates scores that are linked back to the original
3281        entities. This is ideal for:
3282
3283        - Running evaluations on production traces after deployment
3284        - Backtesting new evaluation metrics on historical data
3285        - Batch scoring of observations for quality monitoring
3286        - Periodic evaluation runs on recent data
3287
3288        The method uses a streaming/pipeline approach to process items in batches, making
3289        it memory-efficient for large datasets. It includes comprehensive error handling,
3290        retry logic, and resume capability for long-running evaluations.
3291
3292        Args:
3293            scope: The type of items to evaluate. Must be one of:
3294                - "traces": Evaluate complete traces with all their observations
3295                - "observations": Evaluate individual observations (spans, generations, events)
3296            mapper: Function that transforms API response objects into evaluator inputs.
3297                Receives a trace/observation object and returns an EvaluatorInputs
3298                instance with input, output, expected_output, and metadata fields.
3299                Can be sync or async.
3300            evaluators: List of evaluation functions to run on each item. Each evaluator
3301                receives the mapped inputs and returns Evaluation object(s). Evaluator
3302                failures are logged but don't stop the batch evaluation.
3303            filter: Optional JSON filter string for querying items (same format as Langfuse API). Examples:
3304                - '{"tags": ["production"]}'
3305                - '{"user_id": "user123", "timestamp": {"operator": ">", "value": "2024-01-01"}}'
3306                Default: None (fetches all items).
3307            fetch_batch_size: Number of items to fetch per API call and hold in memory.
3308                Larger values may be faster but use more memory. Default: 50.
3309            fetch_trace_fields: Comma-separated list of fields to include when fetching traces. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. Only relevant if scope is 'traces'.
3310            max_items: Maximum total number of items to process. If None, processes all
3311                items matching the filter. Useful for testing or limiting evaluation runs.
3312                Default: None (process all).
3313            max_concurrency: Maximum number of items to evaluate concurrently. Controls
3314                parallelism and resource usage. Default: 5.
3315            composite_evaluator: Optional function that creates a composite score from
3316                item-level evaluations. Receives the original item and its evaluations,
3317                returns a single Evaluation. Useful for weighted averages or combined metrics.
3318                Default: None.
3319            metadata: Optional metadata dict to add to all created scores. Useful for
3320                tracking evaluation runs, versions, or other context. Default: None.
3321            max_retries: Maximum number of retry attempts for failed batch fetches.
3322                Uses exponential backoff (1s, 2s, 4s). Default: 3.
3323            verbose: If True, logs progress information to console. Useful for monitoring
3324                long-running evaluations. Default: False.
3325            resume_from: Optional resume token from a previous incomplete run. Allows
3326                continuing evaluation after interruption or failure. Default: None.
3327
3328
3329        Returns:
3330            BatchEvaluationResult containing:
3331                - total_items_fetched: Number of items fetched from API
3332                - total_items_processed: Number of items successfully evaluated
3333                - total_items_failed: Number of items that failed evaluation
3334                - total_scores_created: Scores created by item-level evaluators
3335                - total_composite_scores_created: Scores created by composite evaluator
3336                - total_evaluations_failed: Individual evaluator failures
3337                - evaluator_stats: Per-evaluator statistics (success rate, scores created)
3338                - resume_token: Token for resuming if incomplete (None if completed)
3339                - completed: True if all items processed
3340                - duration_seconds: Total execution time
3341                - failed_item_ids: IDs of items that failed
3342                - error_summary: Error types and counts
3343                - has_more_items: True if max_items reached but more exist
3344
3345        Raises:
3346            ValueError: If invalid scope is provided.
3347
3348        Examples:
3349            Basic trace evaluation:
3350            ```python
3351            from langfuse import Langfuse, EvaluatorInputs, Evaluation
3352
3353            client = Langfuse()
3354
3355            # Define mapper to extract fields from traces
3356            def trace_mapper(trace):
3357                return EvaluatorInputs(
3358                    input=trace.input,
3359                    output=trace.output,
3360                    expected_output=None,
3361                    metadata={"trace_id": trace.id}
3362                )
3363
3364            # Define evaluator
3365            def length_evaluator(*, input, output, expected_output, metadata):
3366                return Evaluation(
3367                    name="output_length",
3368                    value=len(output) if output else 0
3369                )
3370
3371            # Run batch evaluation
3372            result = client.run_batched_evaluation(
3373                scope="traces",
3374                mapper=trace_mapper,
3375                evaluators=[length_evaluator],
3376                filter='{"tags": ["production"]}',
3377                max_items=1000,
3378                verbose=True
3379            )
3380
3381            print(f"Processed {result.total_items_processed} traces")
3382            print(f"Created {result.total_scores_created} scores")
3383            ```
3384
3385            Evaluation with composite scorer:
3386            ```python
3387            def accuracy_evaluator(*, input, output, expected_output, metadata):
3388                # ... evaluation logic
3389                return Evaluation(name="accuracy", value=0.85)
3390
3391            def relevance_evaluator(*, input, output, expected_output, metadata):
3392                # ... evaluation logic
3393                return Evaluation(name="relevance", value=0.92)
3394
3395            def composite_evaluator(*, item, evaluations):
3396                # Weighted average of evaluations
3397                weights = {"accuracy": 0.6, "relevance": 0.4}
3398                total = sum(
3399                    e.value * weights.get(e.name, 0)
3400                    for e in evaluations
3401                    if isinstance(e.value, (int, float))
3402                )
3403                return Evaluation(
3404                    name="composite_score",
3405                    value=total,
3406                    comment=f"Weighted average of {len(evaluations)} metrics"
3407                )
3408
3409            result = client.run_batched_evaluation(
3410                scope="traces",
3411                mapper=trace_mapper,
3412                evaluators=[accuracy_evaluator, relevance_evaluator],
3413                composite_evaluator=composite_evaluator,
3414                filter='{"user_id": "important_user"}',
3415                verbose=True
3416            )
3417            ```
3418
3419            Handling incomplete runs with resume:
3420            ```python
3421            # Initial run that may fail or timeout
3422            result = client.run_batched_evaluation(
3423                scope="observations",
3424                mapper=obs_mapper,
3425                evaluators=[my_evaluator],
3426                max_items=10000,
3427                verbose=True
3428            )
3429
3430            # Check if incomplete
3431            if not result.completed and result.resume_token:
3432                print(f"Processed {result.resume_token.items_processed} items before interruption")
3433
3434                # Resume from where it left off
3435                result = client.run_batched_evaluation(
3436                    scope="observations",
3437                    mapper=obs_mapper,
3438                    evaluators=[my_evaluator],
3439                    resume_from=result.resume_token,
3440                    verbose=True
3441                )
3442
3443            print(f"Total items processed: {result.total_items_processed}")
3444            ```
3445
3446            Monitoring evaluator performance:
3447            ```python
3448            result = client.run_batched_evaluation(...)
3449
3450            for stats in result.evaluator_stats:
3451                success_rate = stats.successful_runs / stats.total_runs
3452                print(f"{stats.name}:")
3453                print(f"  Success rate: {success_rate:.1%}")
3454                print(f"  Scores created: {stats.total_scores_created}")
3455
3456                if stats.failed_runs > 0:
3457                    print(f"  ⚠️  Failed {stats.failed_runs} times")
3458            ```
3459
3460        Note:
3461            - Evaluator failures are logged but don't stop the batch evaluation
3462            - Individual item failures are tracked but don't stop processing
3463            - Fetch failures are retried with exponential backoff
3464            - All scores are automatically flushed to Langfuse at the end
3465            - The resume mechanism uses timestamp-based filtering to avoid duplicates
3466        """
3467        runner = BatchEvaluationRunner(self)
3468
3469        return cast(
3470            BatchEvaluationResult,
3471            run_async_safely(
3472                runner.run_async(
3473                    scope=scope,
3474                    mapper=mapper,
3475                    evaluators=evaluators,
3476                    filter=filter,
3477                    fetch_batch_size=fetch_batch_size,
3478                    fetch_trace_fields=fetch_trace_fields,
3479                    max_items=max_items,
3480                    max_concurrency=max_concurrency,
3481                    composite_evaluator=composite_evaluator,
3482                    metadata=metadata,
3483                    _add_observation_scores_to_trace=_add_observation_scores_to_trace,
3484                    _additional_trace_tags=_additional_trace_tags,
3485                    max_retries=max_retries,
3486                    verbose=verbose,
3487                    resume_from=resume_from,
3488                )
3489            ),
3490        )

Fetch traces or observations and run evaluations on each item.

This method provides a powerful way to evaluate existing data in Langfuse at scale. It fetches items based on filters, transforms them using a mapper function, runs evaluators on each item, and creates scores that are linked back to the original entities. This is ideal for:

  • Running evaluations on production traces after deployment
  • Backtesting new evaluation metrics on historical data
  • Batch scoring of observations for quality monitoring
  • Periodic evaluation runs on recent data

The method uses a streaming/pipeline approach to process items in batches, making it memory-efficient for large datasets. It includes comprehensive error handling, retry logic, and resume capability for long-running evaluations.

Arguments:
  • scope: The type of items to evaluate. Must be one of:
    • "traces": Evaluate complete traces with all their observations
    • "observations": Evaluate individual observations (spans, generations, events)
  • mapper: Function that transforms API response objects into evaluator inputs. Receives a trace/observation object and returns an EvaluatorInputs instance with input, output, expected_output, and metadata fields. Can be sync or async.
  • evaluators: List of evaluation functions to run on each item. Each evaluator receives the mapped inputs and returns Evaluation object(s). Evaluator failures are logged but don't stop the batch evaluation.
  • filter: Optional JSON filter string for querying items (same format as Langfuse API). Examples:
    • '{"tags": ["production"]}'
    • '{"user_id": "user123", "timestamp": {"operator": ">", "value": "2024-01-01"}}' Default: None (fetches all items).
  • fetch_batch_size: Number of items to fetch per API call and hold in memory. Larger values may be faster but use more memory. Default: 50.
  • fetch_trace_fields: Comma-separated list of fields to include when fetching traces. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. Only relevant if scope is 'traces'.
  • max_items: Maximum total number of items to process. If None, processes all items matching the filter. Useful for testing or limiting evaluation runs. Default: None (process all).
  • max_concurrency: Maximum number of items to evaluate concurrently. Controls parallelism and resource usage. Default: 5.
  • composite_evaluator: Optional function that creates a composite score from item-level evaluations. Receives the original item and its evaluations, returns a single Evaluation. Useful for weighted averages or combined metrics. Default: None.
  • metadata: Optional metadata dict to add to all created scores. Useful for tracking evaluation runs, versions, or other context. Default: None.
  • max_retries: Maximum number of retry attempts for failed batch fetches. Uses exponential backoff (1s, 2s, 4s). Default: 3.
  • verbose: If True, logs progress information to console. Useful for monitoring long-running evaluations. Default: False.
  • resume_from: Optional resume token from a previous incomplete run. Allows continuing evaluation after interruption or failure. Default: None.
Returns:

BatchEvaluationResult containing: - total_items_fetched: Number of items fetched from API - total_items_processed: Number of items successfully evaluated - total_items_failed: Number of items that failed evaluation - total_scores_created: Scores created by item-level evaluators - total_composite_scores_created: Scores created by composite evaluator - total_evaluations_failed: Individual evaluator failures - evaluator_stats: Per-evaluator statistics (success rate, scores created) - resume_token: Token for resuming if incomplete (None if completed) - completed: True if all items processed - duration_seconds: Total execution time - failed_item_ids: IDs of items that failed - error_summary: Error types and counts - has_more_items: True if max_items reached but more exist

Raises:
  • ValueError: If invalid scope is provided.
Examples:

Basic trace evaluation:

from langfuse import Langfuse, EvaluatorInputs, Evaluation

client = Langfuse()

# Define mapper to extract fields from traces
def trace_mapper(trace):
    return EvaluatorInputs(
        input=trace.input,
        output=trace.output,
        expected_output=None,
        metadata={"trace_id": trace.id}
    )

# Define evaluator
def length_evaluator(*, input, output, expected_output, metadata):
    return Evaluation(
        name="output_length",
        value=len(output) if output else 0
    )

# Run batch evaluation
result = client.run_batched_evaluation(
    scope="traces",
    mapper=trace_mapper,
    evaluators=[length_evaluator],
    filter='{"tags": ["production"]}',
    max_items=1000,
    verbose=True
)

print(f"Processed {result.total_items_processed} traces")
print(f"Created {result.total_scores_created} scores")

Evaluation with composite scorer:

def accuracy_evaluator(*, input, output, expected_output, metadata):
    # ... evaluation logic
    return Evaluation(name="accuracy", value=0.85)

def relevance_evaluator(*, input, output, expected_output, metadata):
    # ... evaluation logic
    return Evaluation(name="relevance", value=0.92)

def composite_evaluator(*, item, evaluations):
    # Weighted average of evaluations
    weights = {"accuracy": 0.6, "relevance": 0.4}
    total = sum(
        e.value * weights.get(e.name, 0)
        for e in evaluations
        if isinstance(e.value, (int, float))
    )
    return Evaluation(
        name="composite_score",
        value=total,
        comment=f"Weighted average of {len(evaluations)} metrics"
    )

result = client.run_batched_evaluation(
    scope="traces",
    mapper=trace_mapper,
    evaluators=[accuracy_evaluator, relevance_evaluator],
    composite_evaluator=composite_evaluator,
    filter='{"user_id": "important_user"}',
    verbose=True
)

Handling incomplete runs with resume:

# Initial run that may fail or timeout
result = client.run_batched_evaluation(
    scope="observations",
    mapper=obs_mapper,
    evaluators=[my_evaluator],
    max_items=10000,
    verbose=True
)

# Check if incomplete
if not result.completed and result.resume_token:
    print(f"Processed {result.resume_token.items_processed} items before interruption")

    # Resume from where it left off
    result = client.run_batched_evaluation(
        scope="observations",
        mapper=obs_mapper,
        evaluators=[my_evaluator],
        resume_from=result.resume_token,
        verbose=True
    )

print(f"Total items processed: {result.total_items_processed}")

Monitoring evaluator performance:

result = client.run_batched_evaluation(...)

for stats in result.evaluator_stats:
    success_rate = stats.successful_runs / stats.total_runs
    print(f"{stats.name}:")
    print(f"  Success rate: {success_rate:.1%}")
    print(f"  Scores created: {stats.total_scores_created}")

    if stats.failed_runs > 0:
        print(f"  ⚠️  Failed {stats.failed_runs} times")
Note:
  • Evaluator failures are logged but don't stop the batch evaluation
  • Individual item failures are tracked but don't stop processing
  • Fetch failures are retried with exponential backoff
  • All scores are automatically flushed to Langfuse at the end
  • The resume mechanism uses timestamp-based filtering to avoid duplicates
def auth_check(self) -> bool:
3492    def auth_check(self) -> bool:
3493        """Check if the provided credentials (public and secret key) are valid.
3494
3495        Raises:
3496            Exception: If no projects were found for the provided credentials.
3497
3498        Note:
3499            This method is blocking. It is discouraged to use it in production code.
3500        """
3501        try:
3502            projects = self.api.projects.get()
3503            langfuse_logger.debug(
3504                "Auth check successful, found %s projects", len(projects.data)
3505            )
3506            if len(projects.data) == 0:
3507                raise Exception(
3508                    "Auth check failed, no project found for the keys provided."
3509                )
3510            return True
3511
3512        except AttributeError as e:
3513            langfuse_logger.warning(
3514                "Auth check failed: Client not properly initialized. Error: %s", e
3515            )
3516            return False
3517
3518        except Error as e:
3519            handle_fern_exception(e)
3520            raise e

Check if the provided credentials (public and secret key) are valid.

Raises:
  • Exception: If no projects were found for the provided credentials.
Note:

This method is blocking. It is discouraged to use it in production code.

def create_dataset( self, *, name: str, description: Optional[str] = None, metadata: Optional[Any] = None, input_schema: Optional[Any] = None, expected_output_schema: Optional[Any] = None) -> langfuse.api.Dataset:
3522    def create_dataset(
3523        self,
3524        *,
3525        name: str,
3526        description: Optional[str] = None,
3527        metadata: Optional[Any] = None,
3528        input_schema: Optional[Any] = None,
3529        expected_output_schema: Optional[Any] = None,
3530    ) -> Dataset:
3531        """Create a dataset with the given name on Langfuse.
3532
3533        Args:
3534            name: Name of the dataset to create.
3535            description: Description of the dataset. Defaults to None.
3536            metadata: Additional metadata. Defaults to None.
3537            input_schema: JSON Schema for validating dataset item inputs. When set, all new items will be validated against this schema.
3538            expected_output_schema: JSON Schema for validating dataset item expected outputs. When set, all new items will be validated against this schema.
3539
3540        Returns:
3541            Dataset: The created dataset as returned by the Langfuse API.
3542        """
3543        try:
3544            langfuse_logger.debug("Creating datasets %s", name)
3545
3546            result = self.api.datasets.create(
3547                name=name,
3548                description=description,
3549                metadata=metadata,
3550                input_schema=input_schema,
3551                expected_output_schema=expected_output_schema,
3552            )
3553
3554            return cast(Dataset, result)
3555
3556        except Error as e:
3557            handle_fern_exception(e)
3558            raise e

Create a dataset with the given name on Langfuse.

Arguments:
  • name: Name of the dataset to create.
  • description: Description of the dataset. Defaults to None.
  • metadata: Additional metadata. Defaults to None.
  • input_schema: JSON Schema for validating dataset item inputs. When set, all new items will be validated against this schema.
  • expected_output_schema: JSON Schema for validating dataset item expected outputs. When set, all new items will be validated against this schema.
Returns:

Dataset: The created dataset as returned by the Langfuse API.

def create_dataset_item( self, *, dataset_name: str, input: Optional[Any] = None, expected_output: Optional[Any] = None, metadata: Optional[Any] = None, source_trace_id: Optional[str] = None, source_observation_id: Optional[str] = None, status: Optional[langfuse.api.DatasetStatus] = None, id: Optional[str] = None) -> langfuse.api.DatasetItem:
3560    def create_dataset_item(
3561        self,
3562        *,
3563        dataset_name: str,
3564        input: Optional[Any] = None,
3565        expected_output: Optional[Any] = None,
3566        metadata: Optional[Any] = None,
3567        source_trace_id: Optional[str] = None,
3568        source_observation_id: Optional[str] = None,
3569        status: Optional[DatasetStatus] = None,
3570        id: Optional[str] = None,
3571    ) -> DatasetItem:
3572        """Create a dataset item.
3573
3574        Upserts if an item with id already exists.
3575
3576        Args:
3577            dataset_name: Name of the dataset in which the dataset item should be created.
3578            input: Input data. Defaults to None. Can contain any dict, list or scalar.
3579            expected_output: Expected output data. Defaults to None. Can contain any dict, list or scalar.
3580            metadata: Additional metadata. Defaults to None. Can contain any dict, list or scalar.
3581            source_trace_id: Id of the source trace. Defaults to None.
3582            source_observation_id: Id of the source observation. Defaults to None.
3583            status: Status of the dataset item. Defaults to ACTIVE for newly created items.
3584            id: Id of the dataset item. Defaults to None. Provide your own id if you want to dedupe dataset items. Id needs to be globally unique and cannot be reused across datasets.
3585
3586        Returns:
3587            DatasetItem: The created dataset item as returned by the Langfuse API.
3588
3589        Example:
3590            ```python
3591            from langfuse import Langfuse
3592
3593            langfuse = Langfuse()
3594
3595            # Uploading items to the Langfuse dataset named "capital_cities"
3596            langfuse.create_dataset_item(
3597                dataset_name="capital_cities",
3598                input={"input": {"country": "Italy"}},
3599                expected_output={"expected_output": "Rome"},
3600                metadata={"foo": "bar"}
3601            )
3602            ```
3603        """
3604        try:
3605            langfuse_logger.debug("Creating dataset item for dataset %s", dataset_name)
3606
3607            # Media uploads must reference the (dataset, item) they belong to, and
3608            # the item need not exist yet — so settle on the item id up front and
3609            # reuse it for the create call below.
3610            item_id = id if id is not None else str(uuid.uuid4())
3611
3612            # Single pass per field: swap each LangfuseMedia for its reference
3613            # string (derived from content, not the upload) and collect the media
3614            # still to upload, deduped by media id and tagged with its field.
3615            pending_media: Dict[str, Tuple[LangfuseMedia, str]] = {}
3616            input = self._process_dataset_item_media(
3617                data=input,
3618                pending_media=pending_media,
3619                field=DatasetItemMediaReferenceField.INPUT.value,
3620            )
3621            expected_output = self._process_dataset_item_media(
3622                data=expected_output,
3623                pending_media=pending_media,
3624                field=DatasetItemMediaReferenceField.EXPECTED_OUTPUT.value,
3625            )
3626            metadata = self._process_dataset_item_media(
3627                data=metadata,
3628                pending_media=pending_media,
3629                field=DatasetItemMediaReferenceField.METADATA.value,
3630            )
3631
3632            # The upload needs the dataset id, but the create API only takes the
3633            # name. Resolve it once, and only when there is actually media to
3634            # upload — a plain item pays no extra datasets.get round-trip.
3635            if pending_media:
3636                assert self._resources is not None
3637                dataset_id = self.api.datasets.get(self._url_encode(dataset_name)).id
3638                for media, field in pending_media.values():
3639                    self._resources._media_manager._upload_media_sync(
3640                        media=media,
3641                        dataset_id=dataset_id,
3642                        dataset_item_id=item_id,
3643                        field=field,
3644                    )
3645
3646            result = self.api.dataset_items.create(
3647                dataset_name=dataset_name,
3648                input=input,
3649                expected_output=expected_output,
3650                metadata=metadata,
3651                source_trace_id=source_trace_id,
3652                source_observation_id=source_observation_id,
3653                status=status,
3654                id=item_id,
3655            )
3656
3657            return cast(DatasetItem, result)
3658        except Error as e:
3659            handle_fern_exception(e)
3660            raise e

Create a dataset item.

Upserts if an item with id already exists.

Arguments:
  • dataset_name: Name of the dataset in which the dataset item should be created.
  • input: Input data. Defaults to None. Can contain any dict, list or scalar.
  • expected_output: Expected output data. Defaults to None. Can contain any dict, list or scalar.
  • metadata: Additional metadata. Defaults to None. Can contain any dict, list or scalar.
  • source_trace_id: Id of the source trace. Defaults to None.
  • source_observation_id: Id of the source observation. Defaults to None.
  • status: Status of the dataset item. Defaults to ACTIVE for newly created items.
  • id: Id of the dataset item. Defaults to None. Provide your own id if you want to dedupe dataset items. Id needs to be globally unique and cannot be reused across datasets.
Returns:

DatasetItem: The created dataset item as returned by the Langfuse API.

Example:
from langfuse import Langfuse

langfuse = Langfuse()

# Uploading items to the Langfuse dataset named "capital_cities"
langfuse.create_dataset_item(
    dataset_name="capital_cities",
    input={"input": {"country": "Italy"}},
    expected_output={"expected_output": "Rome"},
    metadata={"foo": "bar"}
)
def resolve_media_references( self, *, obj: Any, resolve_with: Literal['base64_data_uri'], max_depth: int = 10, content_fetch_timeout_seconds: int = 5) -> Any:
3787    def resolve_media_references(
3788        self,
3789        *,
3790        obj: Any,
3791        resolve_with: Literal["base64_data_uri"],
3792        max_depth: int = 10,
3793        content_fetch_timeout_seconds: int = 5,
3794    ) -> Any:
3795        """Replace media reference strings in an object with base64 data URIs.
3796
3797        This method recursively traverses an object (up to max_depth) looking for media reference strings
3798        in the format "@@@langfuseMedia:...@@@". When found, it (synchronously) fetches the actual media content using
3799        the provided Langfuse client and replaces the reference string with a base64 data URI.
3800
3801        If fetching media content fails for a reference string, a warning is logged and the reference
3802        string is left unchanged.
3803
3804        Args:
3805            obj: The object to process. Can be a primitive value, array, or nested object.
3806                If the object has a __dict__ attribute, a dict will be returned instead of the original object type.
3807            resolve_with: The representation of the media content to replace the media reference string with.
3808                Currently only "base64_data_uri" is supported.
3809            max_depth: int: The maximum depth to traverse the object. Default is 10.
3810            content_fetch_timeout_seconds: int: The timeout in seconds for fetching media content. Default is 5.
3811
3812        Returns:
3813            A deep copy of the input object with all media references replaced with base64 data URIs where possible.
3814            If the input object has a __dict__ attribute, a dict will be returned instead of the original object type.
3815
3816        Example:
3817            obj = {
3818                "image": "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@",
3819                "nested": {
3820                    "pdf": "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@"
3821                }
3822            }
3823
3824            result = await LangfuseMedia.resolve_media_references(obj, langfuse_client)
3825
3826            # Result:
3827            # {
3828            #     "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
3829            #     "nested": {
3830            #         "pdf": "data:application/pdf;base64,JVBERi0xLjcK..."
3831            #     }
3832            # }
3833        """
3834        return LangfuseMedia.resolve_media_references(
3835            langfuse_client=self,
3836            obj=obj,
3837            resolve_with=resolve_with,
3838            max_depth=max_depth,
3839            content_fetch_timeout_seconds=content_fetch_timeout_seconds,
3840        )

Replace media reference strings in an object with base64 data URIs.

This method recursively traverses an object (up to max_depth) looking for media reference strings in the format "@@@langfuseMedia:...@@@". When found, it (synchronously) fetches the actual media content using the provided Langfuse client and replaces the reference string with a base64 data URI.

If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged.

Arguments:
  • obj: The object to process. Can be a primitive value, array, or nested object. If the object has a __dict__ attribute, a dict will be returned instead of the original object type.
  • resolve_with: The representation of the media content to replace the media reference string with. Currently only "base64_data_uri" is supported.
  • max_depth: int: The maximum depth to traverse the object. Default is 10.
  • content_fetch_timeout_seconds: int: The timeout in seconds for fetching media content. Default is 5.
Returns:

A deep copy of the input object with all media references replaced with base64 data URIs where possible. If the input object has a __dict__ attribute, a dict will be returned instead of the original object type.

Example:

obj = { "image": "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@", "nested": { "pdf": "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@" } }

result = await LangfuseMedia.resolve_media_references(obj, langfuse_client)

Result:

{

"image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",

"nested": {

"pdf": "data:application/pdf;base64,JVBERi0xLjcK..."

}

}

def get_prompt( self, name: str, *, version: Optional[int] = None, label: Optional[str] = None, type: Literal['chat', 'text'] = 'text', cache_ttl_seconds: Optional[int] = None, fallback: Union[List[langfuse.model.ChatMessageDict], NoneType, str] = None, max_retries: Optional[int] = None, fetch_timeout_seconds: Optional[int] = None) -> Union[langfuse.model.TextPromptClient, langfuse.model.ChatPromptClient]:
3870    def get_prompt(
3871        self,
3872        name: str,
3873        *,
3874        version: Optional[int] = None,
3875        label: Optional[str] = None,
3876        type: Literal["chat", "text"] = "text",
3877        cache_ttl_seconds: Optional[int] = None,
3878        fallback: Union[Optional[List[ChatMessageDict]], Optional[str]] = None,
3879        max_retries: Optional[int] = None,
3880        fetch_timeout_seconds: Optional[int] = None,
3881    ) -> PromptClient:
3882        """Get a prompt.
3883
3884        This method attempts to fetch the requested prompt from the local cache. If the prompt is not found
3885        in the cache or if the cached prompt has expired, it will try to fetch the prompt from the server again
3886        and update the cache. If fetching the new prompt fails, and there is an expired prompt in the cache, it will
3887        return the expired prompt as a fallback.
3888
3889        Args:
3890            name (str): The name of the prompt to retrieve.
3891
3892        Keyword Args:
3893            version (Optional[int]): The version of the prompt to retrieve. If no label and version is specified, the `production` label is returned. Specify either version or label, not both.
3894            label: Optional[str]: The label of the prompt to retrieve. If no label and version is specified, the `production` label is returned. Specify either version or label, not both.
3895            cache_ttl_seconds: Optional[int]: Time-to-live in seconds for caching the prompt. Must be specified as a
3896            keyword argument. If not set, defaults to 60 seconds. Disables caching if set to 0.
3897            type: Literal["chat", "text"]: The type of the prompt to retrieve. Defaults to "text".
3898            fallback: Union[Optional[List[ChatMessageDict]], Optional[str]]: The prompt string to return if fetching the prompt fails. Important on the first call where no cached prompt is available. Follows Langfuse prompt formatting with double curly braces for variables. Defaults to None.
3899            max_retries: Optional[int]: The maximum number of retries in case of API/network errors. Defaults to 2. The maximum value is 4. Retries have an exponential backoff with a maximum delay of 10 seconds.
3900            fetch_timeout_seconds: Optional[int]: The timeout in milliseconds for fetching the prompt. Defaults to the default timeout set on the SDK, which is 5 seconds per default.
3901
3902        Returns:
3903            The prompt object retrieved from the cache or directly fetched if not cached or expired of type
3904            - TextPromptClient, if type argument is 'text'.
3905            - ChatPromptClient, if type argument is 'chat'.
3906
3907        Raises:
3908            Exception: Propagates any exceptions raised during the fetching of a new prompt, unless there is an
3909            expired prompt in the cache, in which case it logs a warning and returns the expired prompt.
3910        """
3911        if self._resources is None:
3912            raise Error(
3913                "SDK is not correctly initialized. Check the init logs for more details."
3914            )
3915        if version is not None and label is not None:
3916            raise ValueError("Cannot specify both version and label at the same time.")
3917
3918        if not name:
3919            raise ValueError("Prompt name cannot be empty.")
3920
3921        cache_key = PromptCache.generate_cache_key(name, version=version, label=label)
3922        bounded_max_retries = self._get_bounded_max_retries(
3923            max_retries, default_max_retries=2, max_retries_upper_bound=4
3924        )
3925
3926        langfuse_logger.debug("Getting prompt '%s'", cache_key)
3927        cached_prompt = self._resources.prompt_cache.get(cache_key)
3928
3929        if cached_prompt is None or cache_ttl_seconds == 0:
3930            langfuse_logger.debug(
3931                "Prompt '%s' not found in cache or caching disabled.", cache_key
3932            )
3933            try:
3934                return self._fetch_prompt_and_update_cache(
3935                    name,
3936                    version=version,
3937                    label=label,
3938                    ttl_seconds=cache_ttl_seconds,
3939                    max_retries=bounded_max_retries,
3940                    fetch_timeout_seconds=fetch_timeout_seconds,
3941                )
3942            except Exception as e:
3943                if fallback:
3944                    langfuse_logger.warning(
3945                        "Returning fallback prompt for '%s' due to fetch error: %s",
3946                        cache_key,
3947                        e,
3948                    )
3949
3950                    fallback_client_args: Dict[str, Any] = {
3951                        "name": name,
3952                        "prompt": fallback,
3953                        "type": type,
3954                        "version": version or 0,
3955                        "config": {},
3956                        "labels": [label] if label else [],
3957                        "tags": [],
3958                    }
3959
3960                    if type == "text":
3961                        return TextPromptClient(
3962                            prompt=Prompt_Text(**fallback_client_args),
3963                            is_fallback=True,
3964                        )
3965
3966                    if type == "chat":
3967                        return ChatPromptClient(
3968                            prompt=Prompt_Chat(**fallback_client_args),
3969                            is_fallback=True,
3970                        )
3971
3972                raise e
3973
3974        if cached_prompt.is_expired():
3975            langfuse_logger.debug("Stale prompt '%s' found in cache.", cache_key)
3976            try:
3977                # refresh prompt in background thread, refresh_prompt deduplicates tasks
3978                langfuse_logger.debug(
3979                    "Refreshing prompt '%s' in background.", cache_key
3980                )
3981
3982                def refresh_task() -> None:
3983                    self._fetch_prompt_and_update_cache(
3984                        name,
3985                        version=version,
3986                        label=label,
3987                        ttl_seconds=cache_ttl_seconds,
3988                        max_retries=bounded_max_retries,
3989                        fetch_timeout_seconds=fetch_timeout_seconds,
3990                    )
3991
3992                self._resources.prompt_cache.add_refresh_prompt_task_if_current(
3993                    cache_key,
3994                    cached_prompt,
3995                    refresh_task,
3996                )
3997                langfuse_logger.debug(
3998                    "Returning stale prompt '%s' from cache.", cache_key
3999                )
4000                # return stale prompt
4001                return cached_prompt.value
4002
4003            except Exception as e:
4004                langfuse_logger.warning(
4005                    "Error when refreshing cached prompt '%s', returning cached version. "
4006                    "Error: %s",
4007                    cache_key,
4008                    e,
4009                )
4010                # creation of refresh prompt task failed, return stale prompt
4011                return cached_prompt.value
4012
4013        return cached_prompt.value

Get a prompt.

This method attempts to fetch the requested prompt from the local cache. If the prompt is not found in the cache or if the cached prompt has expired, it will try to fetch the prompt from the server again and update the cache. If fetching the new prompt fails, and there is an expired prompt in the cache, it will return the expired prompt as a fallback.

Arguments:
  • name (str): The name of the prompt to retrieve.
Keyword Args:
  • version (Optional[int]): The version of the prompt to retrieve. If no label and version is specified, the production label is returned. Specify either version or label, not both.
  • label: Optional[str]: The label of the prompt to retrieve. If no label and version is specified, the production label is returned. Specify either version or label, not both.
  • cache_ttl_seconds: Optional[int]: Time-to-live in seconds for caching the prompt. Must be specified as a
  • keyword argument. If not set, defaults to 60 seconds. Disables caching if set to 0.
  • type: Literal["chat", "text"]: The type of the prompt to retrieve. Defaults to "text".
  • fallback: Union[Optional[List[ChatMessageDict]], Optional[str]]: The prompt string to return if fetching the prompt fails. Important on the first call where no cached prompt is available. Follows Langfuse prompt formatting with double curly braces for variables. Defaults to None.
  • max_retries: Optional[int]: The maximum number of retries in case of API/network errors. Defaults to 2. The maximum value is 4. Retries have an exponential backoff with a maximum delay of 10 seconds.
  • fetch_timeout_seconds: Optional[int]: The timeout in milliseconds for fetching the prompt. Defaults to the default timeout set on the SDK, which is 5 seconds per default.
Returns:

The prompt object retrieved from the cache or directly fetched if not cached or expired of type

  • TextPromptClient, if type argument is 'text'.
  • ChatPromptClient, if type argument is 'chat'.
Raises:
  • Exception: Propagates any exceptions raised during the fetching of a new prompt, unless there is an
  • expired prompt in the cache, in which case it logs a warning and returns the expired prompt.
def create_prompt( self, *, name: str, prompt: Union[str, List[Union[langfuse.model.ChatMessageDict, langfuse.model.ChatMessageWithPlaceholdersDict_Message, langfuse.model.ChatMessageWithPlaceholdersDict_Placeholder]]], labels: List[str] = [], tags: Optional[List[str]] = None, type: Optional[Literal['chat', 'text']] = 'text', config: Optional[Any] = None, commit_message: Optional[str] = None) -> Union[langfuse.model.TextPromptClient, langfuse.model.ChatPromptClient]:
4115    def create_prompt(
4116        self,
4117        *,
4118        name: str,
4119        prompt: Union[
4120            str, List[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict]]
4121        ],
4122        labels: List[str] = [],
4123        tags: Optional[List[str]] = None,
4124        type: Optional[Literal["chat", "text"]] = "text",
4125        config: Optional[Any] = None,
4126        commit_message: Optional[str] = None,
4127    ) -> PromptClient:
4128        """Create a new prompt in Langfuse.
4129
4130        Keyword Args:
4131            name : The name of the prompt to be created.
4132            prompt : The content of the prompt to be created.
4133            is_active [DEPRECATED] : A flag indicating whether the prompt is active or not. This is deprecated and will be removed in a future release. Please use the 'production' label instead.
4134            labels: The labels of the prompt. Defaults to None. To create a default-served prompt, add the 'production' label.
4135            tags: The tags of the prompt. Defaults to None. Will be applied to all versions of the prompt.
4136            config: Additional structured data to be saved with the prompt. Defaults to None.
4137            type: The type of the prompt to be created. "chat" vs. "text". Defaults to "text".
4138            commit_message: Optional string describing the change.
4139
4140        Returns:
4141            TextPromptClient: The prompt if type argument is 'text'.
4142            ChatPromptClient: The prompt if type argument is 'chat'.
4143        """
4144        try:
4145            langfuse_logger.debug("Creating prompt name=%r, labels=%r", name, labels)
4146
4147            if type == "chat":
4148                if not isinstance(prompt, list):
4149                    raise ValueError(
4150                        "For 'chat' type, 'prompt' must be a list of chat messages with role and content attributes."
4151                    )
4152                request: Union[CreateChatPromptRequest, CreateTextPromptRequest] = (
4153                    CreateChatPromptRequest(
4154                        name=name,
4155                        prompt=cast(Any, prompt),
4156                        labels=labels,
4157                        tags=tags,
4158                        config=config or {},
4159                        commit_message=commit_message,
4160                        type=CreateChatPromptType.CHAT,
4161                    )
4162                )
4163                server_prompt = self.api.prompts.create(request=request)
4164
4165                if self._resources is not None:
4166                    self._resources.prompt_cache.invalidate(name)
4167
4168                return ChatPromptClient(prompt=cast(Prompt_Chat, server_prompt))
4169
4170            if not isinstance(prompt, str):
4171                raise ValueError("For 'text' type, 'prompt' must be a string.")
4172
4173            request = CreateTextPromptRequest(
4174                name=name,
4175                prompt=prompt,
4176                labels=labels,
4177                tags=tags,
4178                config=config or {},
4179                commit_message=commit_message,
4180            )
4181
4182            server_prompt = self.api.prompts.create(request=request)
4183
4184            if self._resources is not None:
4185                self._resources.prompt_cache.invalidate(name)
4186
4187            return TextPromptClient(prompt=cast(Prompt_Text, server_prompt))
4188
4189        except Error as e:
4190            handle_fern_exception(e)
4191            raise e

Create a new prompt in Langfuse.

Keyword Args:
  • name : The name of the prompt to be created.
  • prompt : The content of the prompt to be created.
  • is_active [DEPRECATED] : A flag indicating whether the prompt is active or not. This is deprecated and will be removed in a future release. Please use the 'production' label instead.
  • labels: The labels of the prompt. Defaults to None. To create a default-served prompt, add the 'production' label.
  • tags: The tags of the prompt. Defaults to None. Will be applied to all versions of the prompt.
  • config: Additional structured data to be saved with the prompt. Defaults to None.
  • type: The type of the prompt to be created. "chat" vs. "text". Defaults to "text".
  • commit_message: Optional string describing the change.
Returns:

TextPromptClient: The prompt if type argument is 'text'. ChatPromptClient: The prompt if type argument is 'chat'.

def update_prompt(self, *, name: str, version: int, new_labels: List[str] = []) -> Any:
4193    def update_prompt(
4194        self,
4195        *,
4196        name: str,
4197        version: int,
4198        new_labels: List[str] = [],
4199    ) -> Any:
4200        """Update an existing prompt version in Langfuse. The Langfuse SDK prompt cache is invalidated for all prompts witht he specified name.
4201
4202        Args:
4203            name (str): The name of the prompt to update.
4204            version (int): The version number of the prompt to update.
4205            new_labels (List[str], optional): New labels to assign to the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. Defaults to [].
4206
4207        Returns:
4208            Prompt: The updated prompt from the Langfuse API.
4209
4210        """
4211        updated_prompt = self.api.prompt_version.update(
4212            name=self._url_encode(name),
4213            version=version,
4214            new_labels=new_labels,
4215        )
4216
4217        if self._resources is not None:
4218            self._resources.prompt_cache.invalidate(name)
4219
4220        return updated_prompt

Update an existing prompt version in Langfuse. The Langfuse SDK prompt cache is invalidated for all prompts witht he specified name.

Arguments:
  • name (str): The name of the prompt to update.
  • version (int): The version number of the prompt to update.
  • new_labels (List[str], optional): New labels to assign to the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. Defaults to [].
Returns:

Prompt: The updated prompt from the Langfuse API.

def clear_prompt_cache(self) -> None:
4235    def clear_prompt_cache(self) -> None:
4236        """Clear the entire prompt cache, removing all cached prompts.
4237
4238        This method is useful when you want to force a complete refresh of all
4239        cached prompts, for example after major updates or when you need to
4240        ensure the latest versions are fetched from the server.
4241        """
4242        if self._resources is not None:
4243            self._resources.prompt_cache.clear()

Clear the entire prompt cache, removing all cached prompts.

This method is useful when you want to force a complete refresh of all cached prompts, for example after major updates or when you need to ensure the latest versions are fetched from the server.

class LangfuseMedia:
 99class LangfuseMedia:
100    """A class for wrapping media objects for upload to Langfuse.
101
102    This class handles the preparation and formatting of media content for Langfuse,
103    supporting both base64 data URIs and raw content bytes.
104
105    Args:
106        obj (Optional[object]): The source object to be wrapped. Can be accessed via the `obj` attribute.
107        base64_data_uri (Optional[str]): A base64-encoded data URI containing the media content
108            and content type (e.g., "data:image/jpeg;base64,/9j/4AAQ...").
109        content_type (Optional[str]): The MIME type of the media content when providing raw bytes.
110        content_bytes (Optional[bytes]): Raw bytes of the media content.
111        file_path (Optional[str]): The path to the file containing the media content. For relative paths,
112            the current working directory is used.
113
114    Raises:
115        ValueError: If neither base64_data_uri or the combination of content_bytes
116            and content_type is provided.
117    """
118
119    obj: object
120
121    _content_bytes: Optional[bytes]
122    _content_type: Optional[MediaContentType]
123    _source: Optional[str]
124    _media_id: Optional[str]
125
126    def __init__(
127        self,
128        *,
129        obj: Optional[object] = None,
130        base64_data_uri: Optional[str] = None,
131        content_type: Optional[MediaContentType] = None,
132        content_bytes: Optional[bytes] = None,
133        file_path: Optional[str] = None,
134    ):
135        """Initialize a LangfuseMedia object.
136
137        Args:
138            obj: The object to wrap.
139
140            base64_data_uri: A base64-encoded data URI containing the media content
141                and content type (e.g., "data:image/jpeg;base64,/9j/4AAQ...").
142            content_type: The MIME type of the media content when providing raw bytes or reading from a file.
143            content_bytes: Raw bytes of the media content.
144            file_path: The path to the file containing the media content. For relative paths,
145                the current working directory is used.
146        """
147        self.obj = obj
148
149        if base64_data_uri is not None:
150            parsed_data = self._parse_base64_data_uri(base64_data_uri)
151            self._content_bytes, self._content_type = parsed_data
152            self._source = "base64_data_uri"
153
154        elif content_bytes is not None and content_type is not None:
155            self._content_type = content_type
156            self._content_bytes = content_bytes
157            self._source = "bytes"
158        elif (
159            file_path is not None
160            and content_type is not None
161            and os.path.exists(file_path)
162        ):
163            self._content_bytes = self._read_file(file_path)
164            self._content_type = content_type if self._content_bytes else None
165            self._source = "file" if self._content_bytes else None
166        else:
167            logger.error(
168                "base64_data_uri, or content_bytes and content_type, or file_path must be provided to LangfuseMedia"
169            )
170
171            self._content_bytes = None
172            self._content_type = None
173            self._source = None
174
175        self._media_id = self._get_media_id()
176
177    def _read_file(self, file_path: str) -> Optional[bytes]:
178        try:
179            with open(file_path, "rb") as file:
180                return file.read()
181        except Exception as e:
182            logger.error("Error reading file at path %s", file_path, exc_info=e)
183
184            return None
185
186    def _get_media_id(self) -> Optional[str]:
187        content_hash = self._content_sha256_hash
188
189        if content_hash is None:
190            return None
191
192        # Convert hash to base64Url
193        url_safe_content_hash = content_hash.replace("+", "-").replace("/", "_")
194
195        return url_safe_content_hash[:22]
196
197    @property
198    def _content_length(self) -> Optional[int]:
199        return len(self._content_bytes) if self._content_bytes else None
200
201    @property
202    def _content_sha256_hash(self) -> Optional[str]:
203        if self._content_bytes is None:
204            return None
205
206        sha256_hash_bytes = hashlib.sha256(self._content_bytes).digest()
207
208        return base64.b64encode(sha256_hash_bytes).decode("utf-8")
209
210    @property
211    def _reference_string(self) -> Optional[str]:
212        if self._content_type is None or self._source is None or self._media_id is None:
213            return None
214
215        return f"@@@langfuseMedia:type={self._content_type}|id={self._media_id}|source={self._source}@@@"
216
217    @staticmethod
218    def parse_reference_string(reference_string: str) -> ParsedMediaReference:
219        """Parse a media reference string into a ParsedMediaReference.
220
221        Example reference string:
222            "@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64_data_uri@@@"
223
224        Args:
225            reference_string: The reference string to parse.
226
227        Returns:
228            A TypedDict with the media_id, source, and content_type.
229
230        Raises:
231            ValueError: If the reference string is empty or not a string.
232            ValueError: If the reference string does not start with "@@@langfuseMedia:type=".
233            ValueError: If the reference string does not end with "@@@".
234            ValueError: If the reference string is missing required fields.
235        """
236        if not reference_string:
237            raise ValueError("Reference string is empty")
238
239        if not isinstance(reference_string, str):
240            raise ValueError("Reference string is not a string")
241
242        if not reference_string.startswith("@@@langfuseMedia:type="):
243            raise ValueError(
244                "Reference string does not start with '@@@langfuseMedia:type='"
245            )
246
247        if not reference_string.endswith("@@@"):
248            raise ValueError("Reference string does not end with '@@@'")
249
250        content = reference_string[len("@@@langfuseMedia:") :].rstrip("@@@")
251
252        # Split into key-value pairs
253        pairs = content.split("|")
254        parsed_data = {}
255
256        for pair in pairs:
257            key, value = pair.split("=", 1)
258            parsed_data[key] = value
259
260        # Verify all required fields are present
261        if not all(key in parsed_data for key in ["type", "id", "source"]):
262            raise ValueError("Missing required fields in reference string")
263
264        return ParsedMediaReference(
265            media_id=parsed_data["id"],
266            source=parsed_data["source"],
267            content_type=cast(MediaContentType, parsed_data["type"]),
268        )
269
270    def _parse_base64_data_uri(
271        self, data: str
272    ) -> Tuple[Optional[bytes], Optional[MediaContentType]]:
273        # Example data URI: data:image/jpeg;base64,/9j/4AAQ...
274        try:
275            if not data or not isinstance(data, str):
276                raise ValueError("Data URI is not a string")
277
278            if not data.startswith("data:"):
279                raise ValueError("Data URI does not start with 'data:'")
280
281            header, actual_data = data[5:].split(",", 1)
282            if not header or not actual_data:
283                raise ValueError("Invalid URI")
284
285            # Split header into parts and check for base64
286            header_parts = header.split(";")
287            if "base64" not in header_parts:
288                raise ValueError("Data is not base64 encoded")
289
290            # Content type is the first part
291            content_type = header_parts[0]
292            if not content_type:
293                raise ValueError("Content type is empty")
294
295            translation_table = str.maketrans("-_", "+/")
296            decoded_data = base64.b64decode(actual_data.translate(translation_table))
297
298            return decoded_data, cast(MediaContentType, content_type)
299
300        except Exception as e:
301            logger.error("Error parsing base64 data URI", exc_info=e)
302
303            return None, None
304
305    @staticmethod
306    def resolve_media_references(
307        *,
308        obj: T,
309        langfuse_client: "Langfuse",
310        resolve_with: Literal["base64_data_uri"],
311        max_depth: int = 10,
312        content_fetch_timeout_seconds: int = 10,
313    ) -> T:
314        """Replace media reference strings in an object with base64 data URIs.
315
316        This method recursively traverses an object (up to max_depth) looking for media reference strings
317        in the format "@@@langfuseMedia:...@@@". When found, it (synchronously) fetches the actual media content using
318        the provided Langfuse client and replaces the reference string with a base64 data URI.
319
320        If fetching media content fails for a reference string, a warning is logged and the reference
321        string is left unchanged.
322
323        Args:
324            obj: The object to process. Can be a primitive value, array, or nested object.
325                If the object has a __dict__ attribute, a dict will be returned instead of the original object type.
326            langfuse_client: Langfuse client instance used to fetch media content.
327            resolve_with: The representation of the media content to replace the media reference string with.
328                Currently only "base64_data_uri" is supported.
329            max_depth: Optional. Default is 10. The maximum depth to traverse the object.
330
331        Returns:
332            A deep copy of the input object with all media references replaced with base64 data URIs where possible.
333            If the input object has a __dict__ attribute, a dict will be returned instead of the original object type.
334
335        Example:
336            obj = {
337                "image": "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@",
338                "nested": {
339                    "pdf": "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@"
340                }
341            }
342
343            result = await LangfuseMedia.resolve_media_references(obj, langfuse_client)
344
345            # Result:
346            # {
347            #     "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
348            #     "nested": {
349            #         "pdf": "data:application/pdf;base64,JVBERi0xLjcK..."
350            #     }
351            # }
352        """
353
354        def traverse(obj: Any, depth: int) -> Any:
355            if depth > max_depth:
356                return obj
357
358            # Handle string
359            if isinstance(obj, str):
360                regex = r"@@@langfuseMedia:.+?@@@"
361                reference_string_matches = re.findall(regex, obj)
362                if len(reference_string_matches) == 0:
363                    return obj
364
365                result = obj
366                reference_string_to_media_content = {}
367                httpx_client = (
368                    langfuse_client._resources.httpx_client
369                    if langfuse_client._resources is not None
370                    else None
371                )
372
373                for reference_string in reference_string_matches:
374                    try:
375                        parsed_media_reference = LangfuseMedia.parse_reference_string(
376                            reference_string
377                        )
378                        media_data = langfuse_client.api.media.get(
379                            parsed_media_reference["media_id"]
380                        )
381                        media_content = (
382                            httpx_client.get(
383                                media_data.url,
384                                timeout=content_fetch_timeout_seconds,
385                            )
386                            if httpx_client is not None
387                            else httpx.get(
388                                media_data.url, timeout=content_fetch_timeout_seconds
389                            )
390                        )
391                        media_content.raise_for_status()
392
393                        base64_media_content = base64.b64encode(
394                            media_content.content
395                        ).decode()
396                        base64_data_uri = f"data:{media_data.content_type};base64,{base64_media_content}"
397
398                        reference_string_to_media_content[reference_string] = (
399                            base64_data_uri
400                        )
401                    except Exception as e:
402                        logger.warning(
403                            "Error fetching media content for reference string %s: %s",
404                            reference_string,
405                            e,
406                        )
407                        # Do not replace the reference string if there's an error
408                        continue
409
410                for (
411                    ref_str,
412                    media_content_str,
413                ) in reference_string_to_media_content.items():
414                    result = result.replace(ref_str, media_content_str)
415
416                return result
417
418            # Handle arrays
419            if isinstance(obj, list):
420                return [traverse(item, depth + 1) for item in obj]
421
422            # Handle dictionaries
423            if isinstance(obj, dict):
424                return {key: traverse(value, depth + 1) for key, value in obj.items()}
425
426            # Handle objects:
427            if hasattr(obj, "__dict__"):
428                return {
429                    key: traverse(value, depth + 1)
430                    for key, value in obj.__dict__.items()
431                }
432
433            return obj
434
435        return cast(T, traverse(obj, 0))

A class for wrapping media objects for upload to Langfuse.

This class handles the preparation and formatting of media content for Langfuse, supporting both base64 data URIs and raw content bytes.

Arguments:
  • obj (Optional[object]): The source object to be wrapped. Can be accessed via the obj attribute.
  • base64_data_uri (Optional[str]): A base64-encoded data URI containing the media content and content type (e.g., "data:image/jpeg;base64,/9j/4AAQ...").
  • content_type (Optional[str]): The MIME type of the media content when providing raw bytes.
  • content_bytes (Optional[bytes]): Raw bytes of the media content.
  • file_path (Optional[str]): The path to the file containing the media content. For relative paths, the current working directory is used.
Raises:
  • ValueError: If neither base64_data_uri or the combination of content_bytes and content_type is provided.
LangfuseMedia( *, obj: Optional[object] = None, base64_data_uri: Optional[str] = None, content_type: Optional[langfuse.api.MediaContentType] = None, content_bytes: Optional[bytes] = None, file_path: Optional[str] = None)
126    def __init__(
127        self,
128        *,
129        obj: Optional[object] = None,
130        base64_data_uri: Optional[str] = None,
131        content_type: Optional[MediaContentType] = None,
132        content_bytes: Optional[bytes] = None,
133        file_path: Optional[str] = None,
134    ):
135        """Initialize a LangfuseMedia object.
136
137        Args:
138            obj: The object to wrap.
139
140            base64_data_uri: A base64-encoded data URI containing the media content
141                and content type (e.g., "data:image/jpeg;base64,/9j/4AAQ...").
142            content_type: The MIME type of the media content when providing raw bytes or reading from a file.
143            content_bytes: Raw bytes of the media content.
144            file_path: The path to the file containing the media content. For relative paths,
145                the current working directory is used.
146        """
147        self.obj = obj
148
149        if base64_data_uri is not None:
150            parsed_data = self._parse_base64_data_uri(base64_data_uri)
151            self._content_bytes, self._content_type = parsed_data
152            self._source = "base64_data_uri"
153
154        elif content_bytes is not None and content_type is not None:
155            self._content_type = content_type
156            self._content_bytes = content_bytes
157            self._source = "bytes"
158        elif (
159            file_path is not None
160            and content_type is not None
161            and os.path.exists(file_path)
162        ):
163            self._content_bytes = self._read_file(file_path)
164            self._content_type = content_type if self._content_bytes else None
165            self._source = "file" if self._content_bytes else None
166        else:
167            logger.error(
168                "base64_data_uri, or content_bytes and content_type, or file_path must be provided to LangfuseMedia"
169            )
170
171            self._content_bytes = None
172            self._content_type = None
173            self._source = None
174
175        self._media_id = self._get_media_id()

Initialize a LangfuseMedia object.

Arguments:
  • obj: The object to wrap.
  • base64_data_uri: A base64-encoded data URI containing the media content and content type (e.g., "data:image/jpeg;base64,/9j/4AAQ...").
  • content_type: The MIME type of the media content when providing raw bytes or reading from a file.
  • content_bytes: Raw bytes of the media content.
  • file_path: The path to the file containing the media content. For relative paths, the current working directory is used.
obj: object
@staticmethod
def parse_reference_string(reference_string: str) -> langfuse.types.ParsedMediaReference:
217    @staticmethod
218    def parse_reference_string(reference_string: str) -> ParsedMediaReference:
219        """Parse a media reference string into a ParsedMediaReference.
220
221        Example reference string:
222            "@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64_data_uri@@@"
223
224        Args:
225            reference_string: The reference string to parse.
226
227        Returns:
228            A TypedDict with the media_id, source, and content_type.
229
230        Raises:
231            ValueError: If the reference string is empty or not a string.
232            ValueError: If the reference string does not start with "@@@langfuseMedia:type=".
233            ValueError: If the reference string does not end with "@@@".
234            ValueError: If the reference string is missing required fields.
235        """
236        if not reference_string:
237            raise ValueError("Reference string is empty")
238
239        if not isinstance(reference_string, str):
240            raise ValueError("Reference string is not a string")
241
242        if not reference_string.startswith("@@@langfuseMedia:type="):
243            raise ValueError(
244                "Reference string does not start with '@@@langfuseMedia:type='"
245            )
246
247        if not reference_string.endswith("@@@"):
248            raise ValueError("Reference string does not end with '@@@'")
249
250        content = reference_string[len("@@@langfuseMedia:") :].rstrip("@@@")
251
252        # Split into key-value pairs
253        pairs = content.split("|")
254        parsed_data = {}
255
256        for pair in pairs:
257            key, value = pair.split("=", 1)
258            parsed_data[key] = value
259
260        # Verify all required fields are present
261        if not all(key in parsed_data for key in ["type", "id", "source"]):
262            raise ValueError("Missing required fields in reference string")
263
264        return ParsedMediaReference(
265            media_id=parsed_data["id"],
266            source=parsed_data["source"],
267            content_type=cast(MediaContentType, parsed_data["type"]),
268        )

Parse a media reference string into a ParsedMediaReference.

Example reference string:

"@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64_data_uri@@@"

Arguments:
  • reference_string: The reference string to parse.
Returns:

A TypedDict with the media_id, source, and content_type.

Raises:
  • ValueError: If the reference string is empty or not a string.
  • ValueError: If the reference string does not start with "@@@langfuseMedia:type=".
  • ValueError: If the reference string does not end with "@@@".
  • ValueError: If the reference string is missing required fields.
@staticmethod
def resolve_media_references( *, obj: ~T, langfuse_client: Langfuse, resolve_with: Literal['base64_data_uri'], max_depth: int = 10, content_fetch_timeout_seconds: int = 10) -> ~T:
305    @staticmethod
306    def resolve_media_references(
307        *,
308        obj: T,
309        langfuse_client: "Langfuse",
310        resolve_with: Literal["base64_data_uri"],
311        max_depth: int = 10,
312        content_fetch_timeout_seconds: int = 10,
313    ) -> T:
314        """Replace media reference strings in an object with base64 data URIs.
315
316        This method recursively traverses an object (up to max_depth) looking for media reference strings
317        in the format "@@@langfuseMedia:...@@@". When found, it (synchronously) fetches the actual media content using
318        the provided Langfuse client and replaces the reference string with a base64 data URI.
319
320        If fetching media content fails for a reference string, a warning is logged and the reference
321        string is left unchanged.
322
323        Args:
324            obj: The object to process. Can be a primitive value, array, or nested object.
325                If the object has a __dict__ attribute, a dict will be returned instead of the original object type.
326            langfuse_client: Langfuse client instance used to fetch media content.
327            resolve_with: The representation of the media content to replace the media reference string with.
328                Currently only "base64_data_uri" is supported.
329            max_depth: Optional. Default is 10. The maximum depth to traverse the object.
330
331        Returns:
332            A deep copy of the input object with all media references replaced with base64 data URIs where possible.
333            If the input object has a __dict__ attribute, a dict will be returned instead of the original object type.
334
335        Example:
336            obj = {
337                "image": "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@",
338                "nested": {
339                    "pdf": "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@"
340                }
341            }
342
343            result = await LangfuseMedia.resolve_media_references(obj, langfuse_client)
344
345            # Result:
346            # {
347            #     "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
348            #     "nested": {
349            #         "pdf": "data:application/pdf;base64,JVBERi0xLjcK..."
350            #     }
351            # }
352        """
353
354        def traverse(obj: Any, depth: int) -> Any:
355            if depth > max_depth:
356                return obj
357
358            # Handle string
359            if isinstance(obj, str):
360                regex = r"@@@langfuseMedia:.+?@@@"
361                reference_string_matches = re.findall(regex, obj)
362                if len(reference_string_matches) == 0:
363                    return obj
364
365                result = obj
366                reference_string_to_media_content = {}
367                httpx_client = (
368                    langfuse_client._resources.httpx_client
369                    if langfuse_client._resources is not None
370                    else None
371                )
372
373                for reference_string in reference_string_matches:
374                    try:
375                        parsed_media_reference = LangfuseMedia.parse_reference_string(
376                            reference_string
377                        )
378                        media_data = langfuse_client.api.media.get(
379                            parsed_media_reference["media_id"]
380                        )
381                        media_content = (
382                            httpx_client.get(
383                                media_data.url,
384                                timeout=content_fetch_timeout_seconds,
385                            )
386                            if httpx_client is not None
387                            else httpx.get(
388                                media_data.url, timeout=content_fetch_timeout_seconds
389                            )
390                        )
391                        media_content.raise_for_status()
392
393                        base64_media_content = base64.b64encode(
394                            media_content.content
395                        ).decode()
396                        base64_data_uri = f"data:{media_data.content_type};base64,{base64_media_content}"
397
398                        reference_string_to_media_content[reference_string] = (
399                            base64_data_uri
400                        )
401                    except Exception as e:
402                        logger.warning(
403                            "Error fetching media content for reference string %s: %s",
404                            reference_string,
405                            e,
406                        )
407                        # Do not replace the reference string if there's an error
408                        continue
409
410                for (
411                    ref_str,
412                    media_content_str,
413                ) in reference_string_to_media_content.items():
414                    result = result.replace(ref_str, media_content_str)
415
416                return result
417
418            # Handle arrays
419            if isinstance(obj, list):
420                return [traverse(item, depth + 1) for item in obj]
421
422            # Handle dictionaries
423            if isinstance(obj, dict):
424                return {key: traverse(value, depth + 1) for key, value in obj.items()}
425
426            # Handle objects:
427            if hasattr(obj, "__dict__"):
428                return {
429                    key: traverse(value, depth + 1)
430                    for key, value in obj.__dict__.items()
431                }
432
433            return obj
434
435        return cast(T, traverse(obj, 0))

Replace media reference strings in an object with base64 data URIs.

This method recursively traverses an object (up to max_depth) looking for media reference strings in the format "@@@langfuseMedia:...@@@". When found, it (synchronously) fetches the actual media content using the provided Langfuse client and replaces the reference string with a base64 data URI.

If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged.

Arguments:
  • obj: The object to process. Can be a primitive value, array, or nested object. If the object has a __dict__ attribute, a dict will be returned instead of the original object type.
  • langfuse_client: Langfuse client instance used to fetch media content.
  • resolve_with: The representation of the media content to replace the media reference string with. Currently only "base64_data_uri" is supported.
  • max_depth: Optional. Default is 10. The maximum depth to traverse the object.
Returns:

A deep copy of the input object with all media references replaced with base64 data URIs where possible. If the input object has a __dict__ attribute, a dict will be returned instead of the original object type.

Example:

obj = { "image": "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@", "nested": { "pdf": "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@" } }

result = await LangfuseMedia.resolve_media_references(obj, langfuse_client)

Result:

{

"image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",

"nested": {

"pdf": "data:application/pdf;base64,JVBERi0xLjcK..."

}

}

@dataclass(frozen=True)
class LangfuseMediaReference:
24@dataclass(frozen=True)
25class LangfuseMediaReference:
26    """Resolved reference to media stored in Langfuse."""
27
28    media_id: str
29    content_type: str
30    url: str
31    url_expiry: Optional[str] = None
32    content_length: Optional[int] = None
33    reference_string: Optional[str] = None
34
35    def is_url_expired(self) -> bool:
36        """Return whether the signed URL is already expired."""
37        if self.url_expiry is None:
38            return False
39
40        expiry = self.url_expiry.replace("Z", "+00:00")
41
42        try:
43            expiry_datetime = datetime.fromisoformat(expiry)
44        except ValueError:
45            return False
46
47        if expiry_datetime.tzinfo is None:
48            expiry_datetime = expiry_datetime.replace(tzinfo=timezone.utc)
49
50        return expiry_datetime <= datetime.now(timezone.utc)
51
52    def fetch_bytes(
53        self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None
54    ) -> bytes:
55        """Fetch the media content from the signed URL.
56
57        Args:
58            timeout: Request timeout in seconds.
59            client: Optional httpx client to use for the request. Pass this to
60                honor custom transport settings (proxy, CA bundle, mTLS) — in
61                particular when multiple Langfuse clients are configured, since
62                the SDK cannot otherwise tell which client produced this
63                reference. When omitted, the single configured client is used,
64                falling back to a default httpx client.
65        """
66        from langfuse._client.resource_manager import LangfuseResourceManager
67
68        httpx_client = client or LangfuseResourceManager.get_singleton_httpx_client()
69        response = (
70            httpx_client.get(self.url, timeout=timeout)
71            if httpx_client is not None
72            else httpx.get(self.url, timeout=timeout)
73        )
74        response.raise_for_status()
75
76        return response.content
77
78    def fetch_base64(
79        self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None
80    ) -> str:
81        """Fetch media and return raw base64 without a data URI prefix.
82
83        See :meth:`fetch_bytes` for the ``client`` argument.
84        """
85        return base64.b64encode(
86            self.fetch_bytes(timeout=timeout, client=client)
87        ).decode()
88
89    def fetch_data_uri(
90        self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None
91    ) -> str:
92        """Fetch media and return it as a data URI.
93
94        See :meth:`fetch_bytes` for the ``client`` argument.
95        """
96        return f"data:{self.content_type};base64,{self.fetch_base64(timeout=timeout, client=client)}"

Resolved reference to media stored in Langfuse.

LangfuseMediaReference( media_id: str, content_type: str, url: str, url_expiry: Optional[str] = None, content_length: Optional[int] = None, reference_string: Optional[str] = None)
media_id: str
content_type: str
url: str
url_expiry: Optional[str] = None
content_length: Optional[int] = None
reference_string: Optional[str] = None
def is_url_expired(self) -> bool:
35    def is_url_expired(self) -> bool:
36        """Return whether the signed URL is already expired."""
37        if self.url_expiry is None:
38            return False
39
40        expiry = self.url_expiry.replace("Z", "+00:00")
41
42        try:
43            expiry_datetime = datetime.fromisoformat(expiry)
44        except ValueError:
45            return False
46
47        if expiry_datetime.tzinfo is None:
48            expiry_datetime = expiry_datetime.replace(tzinfo=timezone.utc)
49
50        return expiry_datetime <= datetime.now(timezone.utc)

Return whether the signed URL is already expired.

def fetch_bytes( self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None) -> bytes:
52    def fetch_bytes(
53        self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None
54    ) -> bytes:
55        """Fetch the media content from the signed URL.
56
57        Args:
58            timeout: Request timeout in seconds.
59            client: Optional httpx client to use for the request. Pass this to
60                honor custom transport settings (proxy, CA bundle, mTLS) — in
61                particular when multiple Langfuse clients are configured, since
62                the SDK cannot otherwise tell which client produced this
63                reference. When omitted, the single configured client is used,
64                falling back to a default httpx client.
65        """
66        from langfuse._client.resource_manager import LangfuseResourceManager
67
68        httpx_client = client or LangfuseResourceManager.get_singleton_httpx_client()
69        response = (
70            httpx_client.get(self.url, timeout=timeout)
71            if httpx_client is not None
72            else httpx.get(self.url, timeout=timeout)
73        )
74        response.raise_for_status()
75
76        return response.content

Fetch the media content from the signed URL.

Arguments:
  • timeout: Request timeout in seconds.
  • client: Optional httpx client to use for the request. Pass this to honor custom transport settings (proxy, CA bundle, mTLS) — in particular when multiple Langfuse clients are configured, since the SDK cannot otherwise tell which client produced this reference. When omitted, the single configured client is used, falling back to a default httpx client.
def fetch_base64( self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None) -> str:
78    def fetch_base64(
79        self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None
80    ) -> str:
81        """Fetch media and return raw base64 without a data URI prefix.
82
83        See :meth:`fetch_bytes` for the ``client`` argument.
84        """
85        return base64.b64encode(
86            self.fetch_bytes(timeout=timeout, client=client)
87        ).decode()

Fetch media and return raw base64 without a data URI prefix.

See fetch_bytes() for the client argument.

def fetch_data_uri( self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None) -> str:
89    def fetch_data_uri(
90        self, *, timeout: float = 30.0, client: Optional[httpx.Client] = None
91    ) -> str:
92        """Fetch media and return it as a data URI.
93
94        See :meth:`fetch_bytes` for the ``client`` argument.
95        """
96        return f"data:{self.content_type};base64,{self.fetch_base64(timeout=timeout, client=client)}"

Fetch media and return it as a data URI.

See fetch_bytes() for the client argument.

def get_client(*, public_key: Optional[str] = None) -> Langfuse:
 65def get_client(*, public_key: Optional[str] = None) -> Langfuse:
 66    """Get or create a Langfuse client instance.
 67
 68    Returns an existing Langfuse client or creates a new one if none exists. In multi-project setups,
 69    providing a public_key is required. Multi-project support is experimental - see Langfuse docs.
 70
 71    Behavior:
 72    - Single project: Returns existing client or creates new one
 73    - Multi-project: Requires public_key to return specific client
 74    - No public_key in multi-project: Returns disabled client to prevent data leakage
 75
 76    The function uses a singleton pattern per public_key to conserve resources and maintain state.
 77
 78    Args:
 79        public_key (Optional[str]): Project identifier
 80            - With key: Returns client for that project
 81            - Without key: Returns single client or disabled client if multiple exist
 82
 83    Returns:
 84        Langfuse: Client instance in one of three states:
 85            1. Client for specified public_key
 86            2. Default client for single-project setup
 87            3. Disabled client when multiple projects exist without key
 88
 89    Security:
 90        Disables tracing when multiple projects exist without explicit key to prevent
 91        cross-project data leakage. Multi-project setups are experimental.
 92
 93    Example:
 94        ```python
 95        # Single project
 96        client = get_client()  # Default client
 97
 98        # In multi-project usage:
 99        client_a = get_client(public_key="project_a_key")  # Returns project A's client
100        client_b = get_client(public_key="project_b_key")  # Returns project B's client
101
102        # Without specific key in multi-project setup:
103        client = get_client()  # Returns disabled client for safety
104        ```
105    """
106    with LangfuseResourceManager._lock:
107        active_instances = LangfuseResourceManager._instances
108
109        # If no explicit public_key provided, check execution context
110        if not public_key:
111            public_key = _current_public_key.get(None)
112
113        if not public_key:
114            if len(active_instances) == 0:
115                # No clients initialized yet, create default instance
116                return Langfuse()
117
118            if len(active_instances) == 1:
119                # Only one client exists, safe to use without specifying key
120                instance = list(active_instances.values())[0]
121
122                # Initialize with the credentials bound to the instance
123                # This is important if the original instance was instantiated
124                # via constructor arguments
125                return _create_client_from_instance(instance)
126
127            else:
128                # Multiple clients exist but no key specified - disable tracing
129                # to prevent cross-project data leakage
130                langfuse_logger.warning(
131                    "No 'langfuse_public_key' passed to decorated function, but multiple langfuse clients are instantiated in current process. Skipping tracing for this function to avoid cross-project leakage."
132                )
133                return Langfuse(
134                    tracing_enabled=False, public_key="fake", secret_key="fake"
135                )
136
137        else:
138            # Specific key provided, look up existing instance
139            target_instance: Optional[LangfuseResourceManager] = active_instances.get(
140                public_key, None
141            )
142
143            if target_instance is None:
144                # No instance found with this key - client not initialized properly
145                langfuse_logger.warning(
146                    "No Langfuse client with public key %s has been initialized. Skipping "
147                    "tracing for decorated function.",
148                    public_key,
149                )
150                return Langfuse(
151                    tracing_enabled=False, public_key="fake", secret_key="fake"
152                )
153
154            # target_instance is guaranteed to be not None at this point
155            return _create_client_from_instance(target_instance, public_key)

Get or create a Langfuse client instance.

Returns an existing Langfuse client or creates a new one if none exists. In multi-project setups, providing a public_key is required. Multi-project support is experimental - see Langfuse docs.

Behavior:

  • Single project: Returns existing client or creates new one
  • Multi-project: Requires public_key to return specific client
  • No public_key in multi-project: Returns disabled client to prevent data leakage

The function uses a singleton pattern per public_key to conserve resources and maintain state.

Arguments:
  • public_key (Optional[str]): Project identifier
    • With key: Returns client for that project
    • Without key: Returns single client or disabled client if multiple exist
Returns:

Langfuse: Client instance in one of three states: 1. Client for specified public_key 2. Default client for single-project setup 3. Disabled client when multiple projects exist without key

Security:

Disables tracing when multiple projects exist without explicit key to prevent cross-project data leakage. Multi-project setups are experimental.

Example:
# Single project
client = get_client()  # Default client

# In multi-project usage:
client_a = get_client(public_key="project_a_key")  # Returns project A's client
client_b = get_client(public_key="project_b_key")  # Returns project B's client

# Without specific key in multi-project setup:
client = get_client()  # Returns disabled client for safety
def observe( func: Optional[~F] = None, *, name: Optional[str] = None, as_type: Union[Literal['generation', 'embedding'], Literal['span', 'agent', 'tool', 'chain', 'retriever', 'evaluator', 'guardrail'], NoneType] = None, capture_input: Optional[bool] = None, capture_output: Optional[bool] = None, transform_to_string: Optional[Callable[[Iterable], str]] = None) -> Union[~F, Callable[[~F], ~F]]:
 91    def observe(
 92        self,
 93        func: Optional[F] = None,
 94        *,
 95        name: Optional[str] = None,
 96        as_type: Optional[ObservationTypeLiteralNoEvent] = None,
 97        capture_input: Optional[bool] = None,
 98        capture_output: Optional[bool] = None,
 99        transform_to_string: Optional[Callable[[Iterable], str]] = None,
100    ) -> Union[F, Callable[[F], F]]:
101        """Wrap a function to create and manage Langfuse tracing around its execution, supporting both synchronous and asynchronous functions.
102
103        This decorator provides seamless integration of Langfuse observability into your codebase. It automatically creates
104        spans or generations around function execution, capturing timing, inputs/outputs, and error states. The decorator
105        intelligently handles both synchronous and asynchronous functions, preserving function signatures and type hints.
106
107        Using OpenTelemetry's distributed tracing system, it maintains proper trace context propagation throughout your application,
108        enabling you to see hierarchical traces of function calls with detailed performance metrics and function-specific details.
109
110        Args:
111            func (Optional[Callable]): The function to decorate. When used with parentheses @observe(), this will be None.
112            name (Optional[str]): Custom name for the created trace or span. If not provided, the function name is used.
113            as_type (Optional[Literal]): Set the observation type. Supported values:
114                    "generation", "span", "agent", "tool", "chain", "retriever", "embedding", "evaluator", "guardrail".
115                    Observation types are highlighted in the Langfuse UI for filtering and visualization.
116                    The types "generation" and "embedding" create a span on which additional attributes such as model,
117                    usage_details, and cost_details can be set — use `as_type="generation"` for LLM calls and update the
118                    observation via `langfuse.update_current_generation(...)` inside the function.
119            capture_input (Optional[bool]): Whether to capture the function's arguments as the observation's input.
120                    Defaults to the LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED environment variable (True if unset).
121                    Set to False for sensitive or very large inputs, then set input explicitly via
122                    `langfuse.update_current_span(input=...)` if needed.
123            capture_output (Optional[bool]): Whether to capture the function's return value as the observation's output.
124                    Same default and override mechanism as capture_input.
125            transform_to_string (Optional[Callable[[Iterable], str]]): For functions returning generators, joins the
126                    yielded chunks into the string stored as output. Without it, chunks are concatenated if all are
127                    strings, otherwise stored as a list.
128
129        Returns:
130            Callable: A wrapped version of the original function that automatically creates and manages Langfuse spans.
131
132        Example:
133            For general function tracing with automatic naming:
134            ```python
135            @observe()
136            def process_user_request(user_id, query):
137                # Function is automatically traced with name "process_user_request"
138                return get_response(query)
139            ```
140
141            For language model generation tracking:
142            ```python
143            from langfuse import get_client, observe
144
145            @observe(name="answer-generation", as_type="generation")
146            async def generate_answer(query):
147                # Creates a generation-type observation with extended LLM metrics
148                response = await openai.chat.completions.create(
149                    model="gpt-4",
150                    messages=[{"role": "user", "content": query}]
151                )
152                return response.choices[0].message.content
153            ```
154
155            Disabling input/output capture (e.g. for sensitive or large payloads):
156            ```python
157            @observe(capture_input=False, capture_output=False)
158            def handle_pii(user_record):
159                return process(user_record)
160            ```
161
162            For trace context propagation between functions:
163            ```python
164            @observe()
165            def main_process():
166                # Parent span is created
167                return sub_process()  # Child span automatically connected to parent
168
169            @observe()
170            def sub_process():
171                # Automatically becomes a child span of main_process
172                return "result"
173            ```
174
175        Raises:
176            Exception: Propagates any exceptions from the wrapped function after logging them in the trace.
177
178        Notes:
179            - The decorator preserves the original function's signature, docstring, and return type.
180            - Proper parent-child relationships between spans are automatically maintained.
181            - Special keyword arguments can be passed to control tracing:
182              - langfuse_trace_id: Explicitly set the trace ID for this function call
183              - langfuse_parent_observation_id: Explicitly set the parent span ID
184              - langfuse_public_key: Use a specific Langfuse project (when multiple clients exist)
185            - For async functions, the decorator returns an async function wrapper.
186            - For sync functions, the decorator returns a synchronous wrapper.
187        """
188        valid_types = set(get_observation_types_list(ObservationTypeLiteralNoEvent))
189        if as_type is not None and as_type not in valid_types:
190            logger.warning(
191                "Invalid as_type '%s'. Valid types are: %s. Defaulting to 'span'.",
192                as_type,
193                ", ".join(sorted(valid_types)),
194            )
195            as_type = "span"
196
197        function_io_capture_enabled = os.environ.get(
198            LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED, "True"
199        ).lower() not in ("false", "0")
200
201        should_capture_input = (
202            capture_input if capture_input is not None else function_io_capture_enabled
203        )
204
205        should_capture_output = (
206            capture_output
207            if capture_output is not None
208            else function_io_capture_enabled
209        )
210
211        def decorator(func: F) -> F:
212            return (
213                self._async_observe(
214                    func,
215                    name=name,
216                    as_type=as_type,
217                    capture_input=should_capture_input,
218                    capture_output=should_capture_output,
219                    transform_to_string=transform_to_string,
220                )
221                if asyncio.iscoroutinefunction(func)
222                else self._sync_observe(
223                    func,
224                    name=name,
225                    as_type=as_type,
226                    capture_input=should_capture_input,
227                    capture_output=should_capture_output,
228                    transform_to_string=transform_to_string,
229                )
230            )
231
232        """Handle decorator with or without parentheses.
233
234        This logic enables the decorator to work both with and without parentheses:
235        - @observe - Python passes the function directly to the decorator
236        - @observe() - Python calls the decorator first, which must return a function decorator
237
238        When called without arguments (@observe), the func parameter contains the function to decorate,
239        so we directly apply the decorator to it. When called with parentheses (@observe()),
240        func is None, so we return the decorator function itself for Python to apply in the next step.
241        """
242        if func is None:
243            return decorator
244        else:
245            return decorator(func)

Wrap a function to create and manage Langfuse tracing around its execution, supporting both synchronous and asynchronous functions.

This decorator provides seamless integration of Langfuse observability into your codebase. It automatically creates spans or generations around function execution, capturing timing, inputs/outputs, and error states. The decorator intelligently handles both synchronous and asynchronous functions, preserving function signatures and type hints.

Using OpenTelemetry's distributed tracing system, it maintains proper trace context propagation throughout your application, enabling you to see hierarchical traces of function calls with detailed performance metrics and function-specific details.

Arguments:
  • func (Optional[Callable]): The function to decorate. When used with parentheses @observe(), this will be None.
  • name (Optional[str]): Custom name for the created trace or span. If not provided, the function name is used.
  • as_type (Optional[Literal]): Set the observation type. Supported values: "generation", "span", "agent", "tool", "chain", "retriever", "embedding", "evaluator", "guardrail". Observation types are highlighted in the Langfuse UI for filtering and visualization. The types "generation" and "embedding" create a span on which additional attributes such as model, usage_details, and cost_details can be set — use as_type="generation" for LLM calls and update the observation via langfuse.update_current_generation(...) inside the function.
  • capture_input (Optional[bool]): Whether to capture the function's arguments as the observation's input. Defaults to the LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED environment variable (True if unset). Set to False for sensitive or very large inputs, then set input explicitly via langfuse.update_current_span(input=...) if needed.
  • capture_output (Optional[bool]): Whether to capture the function's return value as the observation's output. Same default and override mechanism as capture_input.
  • transform_to_string (Optional[Callable[[Iterable], str]]): For functions returning generators, joins the yielded chunks into the string stored as output. Without it, chunks are concatenated if all are strings, otherwise stored as a list.
Returns:

Callable: A wrapped version of the original function that automatically creates and manages Langfuse spans.

Example:

For general function tracing with automatic naming:

@observe()
def process_user_request(user_id, query):
    # Function is automatically traced with name "process_user_request"
    return get_response(query)

For language model generation tracking:

from langfuse import get_client, observe

@observe(name="answer-generation", as_type="generation")
async def generate_answer(query):
    # Creates a generation-type observation with extended LLM metrics
    response = await openai.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": query}]
    )
    return response.choices[0].message.content

Disabling input/output capture (e.g. for sensitive or large payloads):

@observe(capture_input=False, capture_output=False)
def handle_pii(user_record):
    return process(user_record)

For trace context propagation between functions:

@observe()
def main_process():
    # Parent span is created
    return sub_process()  # Child span automatically connected to parent

@observe()
def sub_process():
    # Automatically becomes a child span of main_process
    return "result"
Raises:
  • Exception: Propagates any exceptions from the wrapped function after logging them in the trace.
Notes:
  • The decorator preserves the original function's signature, docstring, and return type.
  • Proper parent-child relationships between spans are automatically maintained.
  • Special keyword arguments can be passed to control tracing:
    • langfuse_trace_id: Explicitly set the trace ID for this function call
    • langfuse_parent_observation_id: Explicitly set the parent span ID
    • langfuse_public_key: Use a specific Langfuse project (when multiple clients exist)
  • For async functions, the decorator returns an async function wrapper.
  • For sync functions, the decorator returns a synchronous wrapper.
def propagate_attributes( *, user_id: Optional[str] = None, session_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, version: Optional[str] = None, tags: Optional[List[str]] = None, trace_name: Optional[str] = None, environment: Optional[str] = None, prompt: Union[langfuse.model.TextPromptClient, langfuse.model.ChatPromptClient, Mapping[str, Any], NoneType] = None, as_baggage: bool = False) -> opentelemetry.util._decorator._AgnosticContextManager[typing.Any]:
115def propagate_attributes(
116    *,
117    user_id: Optional[str] = None,
118    session_id: Optional[str] = None,
119    metadata: Optional[Dict[str, Any]] = None,
120    version: Optional[str] = None,
121    tags: Optional[List[str]] = None,
122    trace_name: Optional[str] = None,
123    environment: Optional[str] = None,
124    prompt: Optional[Union[PromptClient, Mapping[str, Any]]] = None,
125    as_baggage: bool = False,
126) -> _AgnosticContextManager[Any]:
127    """Propagate trace-level attributes to all spans created within this context.
128
129    This context manager sets attributes on the currently active span AND automatically
130    propagates them to all new child spans created within the context. This is the
131    recommended way to set trace-level attributes like user_id, session_id,
132    environment, and metadata dimensions that should be consistently applied across
133    all observations in a trace.
134
135    This is a module-level function, not a method on the Langfuse client:
136    import it with `from langfuse import propagate_attributes`.
137
138    **IMPORTANT**: Call this as early as possible within your trace/workflow —
139    ideally wrapping the creation of your root span, or immediately inside it. Only
140    the currently active span and spans created after entering this context will have
141    these attributes. Pre-existing spans will NOT be retroactively updated.
142
143    **Why this matters**: Langfuse aggregation queries (e.g., total cost by user_id,
144    filtering by session_id) only include observations that have the attribute set.
145    If you call `propagate_attributes` late in your workflow, earlier spans won't be
146    included in aggregations for that attribute.
147
148    Args:
149        user_id: User identifier to associate with all spans in this context.
150            Must be US-ASCII string, ≤200 characters. Use this to track which user
151            generated each trace and enable e.g. per-user cost/performance analysis.
152        session_id: Session identifier to associate with all spans in this context.
153            Must be US-ASCII string, ≤200 characters. Use this to group related traces
154            within a user session (e.g., a conversation thread, multi-turn interaction).
155        metadata: Additional key-value metadata to propagate to all spans.
156            - Keys must be US-ASCII strings
157            - Values are coerced to strings
158            - Coerced values must be ≤200 characters
159            - Use for dimensions like internal correlating identifiers
160            - AVOID: large payloads or sensitive data
161        version: Version identfier for parts of your application that are independently versioned, e.g. agents
162        tags: List of tags to categorize the group of observations
163        trace_name: Name to assign to the trace. Must be US-ASCII string, ≤200 characters.
164            Use this to set a consistent trace name for all spans created within this context.
165        prompt: Langfuse prompt to link to generations created within this context.
166            Accepts a `PromptClient` returned by `langfuse.get_prompt(...)` or any
167            object/dict exposing `name` (string) and `version` (integer) — e.g.
168            `{"name": "my-prompt", "version": 3}`. This is the recommended way to
169            link prompts to generations emitted by auto-instrumentation libraries
170            (e.g. LiteLLM's `langfuse_otel`, OpenAI Agents SDK, OpenInference)
171            where you don't create the generation via the Langfuse SDK yourself.
172            The prompt link is only applied to generation-type observations by the
173            Langfuse backend. Fallback prompts are never linked. An explicit
174            `prompt` passed to `start_observation` / `update_current_generation`
175            takes precedence over the propagated one.
176        environment: Langfuse environment to assign to spans created in this context.
177            Must be a lowercase alphanumeric string with optional hyphens or underscores,
178            must be ≤40 characters, and must not start with "langfuse". This maps to
179            the first-class `langfuse.environment` attribute, not to trace metadata.
180            Use it for request-scoped environments, for example when one shared proxy
181            handles calls from dev, staging, qa, and prod. A propagated environment
182            takes precedence over the local client default configured via
183            `Langfuse(environment=...)` or `LANGFUSE_TRACING_ENVIRONMENT` for spans
184            created while this propagation context is active.
185        as_baggage: If True, propagates attributes using OpenTelemetry baggage for
186            cross-process/service propagation. **Security warning**: When enabled,
187            attribute values are added to HTTP headers on ALL outbound requests.
188            This includes `environment` as the `langfuse_environment` baggage entry.
189            Only enable if values are safe to transmit via HTTP headers and you need
190            cross-service tracing. Default: False.
191
192    Returns:
193        Context manager that propagates attributes to all child spans.
194
195    Example:
196        Basic usage with user and session tracking (note: `propagate_attributes` is a
197        top-level import, not a client method):
198
199        ```python
200        from langfuse import Langfuse, propagate_attributes
201
202        langfuse = Langfuse()
203
204        # Set attributes early: wrap everything inside the root span
205        with langfuse.start_as_current_observation(name="user_workflow") as span:
206            with propagate_attributes(
207                user_id="user_123",
208                session_id="session_abc",
209                environment="production",
210                metadata={"experiment": "variant_a"}
211            ):
212                # All spans created here will have user_id, session_id, environment, and metadata
213                with langfuse.start_as_current_observation(name="llm_call") as llm_span:
214                    # This span inherits user_id, session_id, environment, and experiment metadata
215                    ...
216
217                with langfuse.start_as_current_observation(
218                    name="completion", as_type="generation"
219                ) as gen:
220                    # This span also inherits all attributes
221                    ...
222        ```
223
224        Prompt linking with auto-instrumented libraries:
225
226        ```python
227        from langfuse import Langfuse, propagate_attributes
228
229        langfuse = Langfuse()
230        prompt = langfuse.get_prompt("my-prompt")
231
232        with propagate_attributes(prompt=prompt):
233            # Generations emitted by auto-instrumentation (LiteLLM langfuse_otel,
234            # OpenAI Agents SDK, OpenInference, ...) within this context are
235            # linked to the prompt version.
236            completion = litellm.completion(
237                model="gpt-4o",
238                messages=prompt.compile(topic="chickens"),
239            )
240        ```
241
242        Late propagation (anti-pattern):
243
244        ```python
245        with langfuse.start_as_current_observation(name="workflow") as span:
246            # These spans WON'T have user_id
247            early_span = langfuse.start_observation(name="early_work")
248            early_span.end()
249
250            # Set attributes in the middle
251            with propagate_attributes(user_id="user_123"):
252                # Only spans created AFTER this point will have user_id
253                late_span = langfuse.start_observation(name="late_work")
254                late_span.end()
255
256            # Result: Aggregations by user_id will miss "early_work" span
257        ```
258
259        Cross-service propagation with baggage (advanced):
260
261        ```python
262        # Service A - originating service
263        with langfuse.start_as_current_observation(name="api_request"):
264            with propagate_attributes(
265                user_id="user_123",
266                session_id="session_abc",
267                environment="staging",
268                as_baggage=True  # Propagate via HTTP headers
269            ):
270                # Make HTTP request to Service B
271                response = requests.get("https://service-b.example.com/api")
272                # user_id, session_id, and environment are now in HTTP headers
273
274        # Service B - downstream service
275        # OpenTelemetry will automatically extract baggage from HTTP headers
276        # and propagate attributes to spans in Service B. If Service B has a local
277        # Langfuse environment configured, the propagated environment wins for
278        # spans created within this context.
279        ```
280
281    Note:
282        - **Validation**: Attribute values (user_id, session_id, version, tags,
283          trace_name) must be strings ≤200 characters. Environment must also match
284          Langfuse's environment format: lowercase alphanumeric with optional
285          hyphens or underscores, must be ≤40 characters, and it must not start with "langfuse". Metadata
286          values are coerced to strings before the 200 character limit is applied.
287          Invalid values will be dropped with a warning logged.
288        - **OpenTelemetry**: This uses OpenTelemetry context propagation under the hood,
289          making it compatible with other OTel-instrumented libraries.
290
291    Raises:
292        No exceptions are raised. Invalid values are logged as warnings and dropped.
293
294    See also:
295        `Langfuse.start_as_current_observation` (create the root span this wraps),
296        https://langfuse.com/docs/observability/features/sessions,
297        https://langfuse.com/docs/observability/features/users,
298        https://langfuse.com/docs/observability/features/environments
299    """
300    return _propagate_attributes(
301        user_id=user_id,
302        session_id=session_id,
303        metadata=metadata,
304        version=version,
305        tags=tags,
306        trace_name=trace_name,
307        environment=environment,
308        prompt=prompt,
309        as_baggage=as_baggage,
310    )

Propagate trace-level attributes to all spans created within this context.

This context manager sets attributes on the currently active span AND automatically propagates them to all new child spans created within the context. This is the recommended way to set trace-level attributes like user_id, session_id, environment, and metadata dimensions that should be consistently applied across all observations in a trace.

This is a module-level function, not a method on the Langfuse client: import it with from langfuse import propagate_attributes.

IMPORTANT: Call this as early as possible within your trace/workflow — ideally wrapping the creation of your root span, or immediately inside it. Only the currently active span and spans created after entering this context will have these attributes. Pre-existing spans will NOT be retroactively updated.

Why this matters: Langfuse aggregation queries (e.g., total cost by user_id, filtering by session_id) only include observations that have the attribute set. If you call propagate_attributes late in your workflow, earlier spans won't be included in aggregations for that attribute.

Arguments:
  • user_id: User identifier to associate with all spans in this context. Must be US-ASCII string, ≤200 characters. Use this to track which user generated each trace and enable e.g. per-user cost/performance analysis.
  • session_id: Session identifier to associate with all spans in this context. Must be US-ASCII string, ≤200 characters. Use this to group related traces within a user session (e.g., a conversation thread, multi-turn interaction).
  • metadata: Additional key-value metadata to propagate to all spans.
    • Keys must be US-ASCII strings
    • Values are coerced to strings
    • Coerced values must be ≤200 characters
    • Use for dimensions like internal correlating identifiers
    • AVOID: large payloads or sensitive data
  • version: Version identfier for parts of your application that are independently versioned, e.g. agents
  • tags: List of tags to categorize the group of observations
  • trace_name: Name to assign to the trace. Must be US-ASCII string, ≤200 characters. Use this to set a consistent trace name for all spans created within this context.
  • prompt: Langfuse prompt to link to generations created within this context. Accepts a PromptClient returned by langfuse.get_prompt(...) or any object/dict exposing name (string) and version (integer) — e.g. {"name": "my-prompt", "version": 3}. This is the recommended way to link prompts to generations emitted by auto-instrumentation libraries (e.g. LiteLLM's langfuse_otel, OpenAI Agents SDK, OpenInference) where you don't create the generation via the Langfuse SDK yourself. The prompt link is only applied to generation-type observations by the Langfuse backend. Fallback prompts are never linked. An explicit prompt passed to start_observation / update_current_generation takes precedence over the propagated one.
  • environment: Langfuse environment to assign to spans created in this context. Must be a lowercase alphanumeric string with optional hyphens or underscores, must be ≤40 characters, and must not start with "langfuse". This maps to the first-class langfuse.environment attribute, not to trace metadata. Use it for request-scoped environments, for example when one shared proxy handles calls from dev, staging, qa, and prod. A propagated environment takes precedence over the local client default configured via Langfuse(environment=...) or LANGFUSE_TRACING_ENVIRONMENT for spans created while this propagation context is active.
  • as_baggage: If True, propagates attributes using OpenTelemetry baggage for cross-process/service propagation. Security warning: When enabled, attribute values are added to HTTP headers on ALL outbound requests. This includes environment as the langfuse_environment baggage entry. Only enable if values are safe to transmit via HTTP headers and you need cross-service tracing. Default: False.
Returns:

Context manager that propagates attributes to all child spans.

Example:

Basic usage with user and session tracking (note: propagate_attributes is a top-level import, not a client method):

from langfuse import Langfuse, propagate_attributes

langfuse = Langfuse()

# Set attributes early: wrap everything inside the root span
with langfuse.start_as_current_observation(name="user_workflow") as span:
    with propagate_attributes(
        user_id="user_123",
        session_id="session_abc",
        environment="production",
        metadata={"experiment": "variant_a"}
    ):
        # All spans created here will have user_id, session_id, environment, and metadata
        with langfuse.start_as_current_observation(name="llm_call") as llm_span:
            # This span inherits user_id, session_id, environment, and experiment metadata
            ...

        with langfuse.start_as_current_observation(
            name="completion", as_type="generation"
        ) as gen:
            # This span also inherits all attributes
            ...

Prompt linking with auto-instrumented libraries:

from langfuse import Langfuse, propagate_attributes

langfuse = Langfuse()
prompt = langfuse.get_prompt("my-prompt")

with propagate_attributes(prompt=prompt):
    # Generations emitted by auto-instrumentation (LiteLLM langfuse_otel,
    # OpenAI Agents SDK, OpenInference, ...) within this context are
    # linked to the prompt version.
    completion = litellm.completion(
        model="gpt-4o",
        messages=prompt.compile(topic="chickens"),
    )

Late propagation (anti-pattern):

with langfuse.start_as_current_observation(name="workflow") as span:
    # These spans WON'T have user_id
    early_span = langfuse.start_observation(name="early_work")
    early_span.end()

    # Set attributes in the middle
    with propagate_attributes(user_id="user_123"):
        # Only spans created AFTER this point will have user_id
        late_span = langfuse.start_observation(name="late_work")
        late_span.end()

    # Result: Aggregations by user_id will miss "early_work" span

Cross-service propagation with baggage (advanced):

# Service A - originating service
with langfuse.start_as_current_observation(name="api_request"):
    with propagate_attributes(
        user_id="user_123",
        session_id="session_abc",
        environment="staging",
        as_baggage=True  # Propagate via HTTP headers
    ):
        # Make HTTP request to Service B
        response = requests.get("https://service-b.example.com/api")
        # user_id, session_id, and environment are now in HTTP headers

# Service B - downstream service
# OpenTelemetry will automatically extract baggage from HTTP headers
# and propagate attributes to spans in Service B. If Service B has a local
# Langfuse environment configured, the propagated environment wins for
# spans created within this context.
Note:
  • Validation: Attribute values (user_id, session_id, version, tags, trace_name) must be strings ≤200 characters. Environment must also match Langfuse's environment format: lowercase alphanumeric with optional hyphens or underscores, must be ≤40 characters, and it must not start with "langfuse". Metadata values are coerced to strings before the 200 character limit is applied. Invalid values will be dropped with a warning logged.
  • OpenTelemetry: This uses OpenTelemetry context propagation under the hood, making it compatible with other OTel-instrumented libraries.
Raises:
  • No exceptions are raised. Invalid values are logged as warnings and dropped.
See also:

Langfuse.start_as_current_observation (create the root span this wraps), https://langfuse.com/docs/observability/features/sessions, https://langfuse.com/docs/observability/features/users, https://langfuse.com/docs/observability/features/environments

ObservationTypeLiteral = typing.Union[typing.Literal['generation', 'embedding'], typing.Literal['span', 'agent', 'tool', 'chain', 'retriever', 'evaluator', 'guardrail'], typing.Literal['event']]
class LangfuseSpan(langfuse._client.span.LangfuseObservationWrapper):
1269class LangfuseSpan(LangfuseObservationWrapper):
1270    """Standard span implementation for general operations in Langfuse.
1271
1272    This class represents a general-purpose span that can be used to trace
1273    any operation in your application. It extends the base LangfuseObservationWrapper
1274    with specific methods for creating child spans, generations, and updating
1275    span-specific attributes. If possible, use a more specific type for
1276    better observability and insights.
1277    """
1278
1279    def __init__(
1280        self,
1281        *,
1282        otel_span: otel_trace_api.Span,
1283        langfuse_client: "Langfuse",
1284        input: Optional[Any] = None,
1285        output: Optional[Any] = None,
1286        metadata: Optional[Any] = None,
1287        environment: Optional[str] = None,
1288        release: Optional[str] = None,
1289        version: Optional[str] = None,
1290        level: Optional[SpanLevel] = None,
1291        status_message: Optional[str] = None,
1292    ):
1293        """Initialize a new LangfuseSpan.
1294
1295        Args:
1296            otel_span: The OpenTelemetry span to wrap
1297            langfuse_client: Reference to the parent Langfuse client
1298            input: Input data for the span (any JSON-serializable object)
1299            output: Output data from the span (any JSON-serializable object)
1300            metadata: Additional metadata to associate with the span
1301            environment: The tracing environment
1302            release: Release identifier for the application
1303            version: Version identifier for the code or component
1304            level: Importance level of the span (info, warning, error)
1305            status_message: Optional status message for the span
1306        """
1307        super().__init__(
1308            otel_span=otel_span,
1309            as_type="span",
1310            langfuse_client=langfuse_client,
1311            input=input,
1312            output=output,
1313            metadata=metadata,
1314            environment=environment,
1315            release=release,
1316            version=version,
1317            level=level,
1318            status_message=status_message,
1319        )

Standard span implementation for general operations in Langfuse.

This class represents a general-purpose span that can be used to trace any operation in your application. It extends the base LangfuseObservationWrapper with specific methods for creating child spans, generations, and updating span-specific attributes. If possible, use a more specific type for better observability and insights.

LangfuseSpan( *, otel_span: opentelemetry.trace.span.Span, langfuse_client: Langfuse, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, environment: Optional[str] = None, release: Optional[str] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None)
1279    def __init__(
1280        self,
1281        *,
1282        otel_span: otel_trace_api.Span,
1283        langfuse_client: "Langfuse",
1284        input: Optional[Any] = None,
1285        output: Optional[Any] = None,
1286        metadata: Optional[Any] = None,
1287        environment: Optional[str] = None,
1288        release: Optional[str] = None,
1289        version: Optional[str] = None,
1290        level: Optional[SpanLevel] = None,
1291        status_message: Optional[str] = None,
1292    ):
1293        """Initialize a new LangfuseSpan.
1294
1295        Args:
1296            otel_span: The OpenTelemetry span to wrap
1297            langfuse_client: Reference to the parent Langfuse client
1298            input: Input data for the span (any JSON-serializable object)
1299            output: Output data from the span (any JSON-serializable object)
1300            metadata: Additional metadata to associate with the span
1301            environment: The tracing environment
1302            release: Release identifier for the application
1303            version: Version identifier for the code or component
1304            level: Importance level of the span (info, warning, error)
1305            status_message: Optional status message for the span
1306        """
1307        super().__init__(
1308            otel_span=otel_span,
1309            as_type="span",
1310            langfuse_client=langfuse_client,
1311            input=input,
1312            output=output,
1313            metadata=metadata,
1314            environment=environment,
1315            release=release,
1316            version=version,
1317            level=level,
1318            status_message=status_message,
1319        )

Initialize a new LangfuseSpan.

Arguments:
  • otel_span: The OpenTelemetry span to wrap
  • langfuse_client: Reference to the parent Langfuse client
  • input: Input data for the span (any JSON-serializable object)
  • output: Output data from the span (any JSON-serializable object)
  • metadata: Additional metadata to associate with the span
  • environment: The tracing environment
  • release: Release identifier for the application
  • version: Version identifier for the code or component
  • level: Importance level of the span (info, warning, error)
  • status_message: Optional status message for the span
class LangfuseGeneration(langfuse._client.span.LangfuseObservationWrapper):
1322class LangfuseGeneration(LangfuseObservationWrapper):
1323    """Specialized span implementation for AI model generations in Langfuse.
1324
1325    This class represents a generation span specifically designed for tracking
1326    AI/LLM operations. It extends the base LangfuseObservationWrapper with specialized
1327    attributes for model details, token usage, and costs.
1328    """
1329
1330    def __init__(
1331        self,
1332        *,
1333        otel_span: otel_trace_api.Span,
1334        langfuse_client: "Langfuse",
1335        input: Optional[Any] = None,
1336        output: Optional[Any] = None,
1337        metadata: Optional[Any] = None,
1338        environment: Optional[str] = None,
1339        release: Optional[str] = None,
1340        version: Optional[str] = None,
1341        level: Optional[SpanLevel] = None,
1342        status_message: Optional[str] = None,
1343        completion_start_time: Optional[datetime] = None,
1344        model: Optional[str] = None,
1345        model_parameters: Optional[Dict[str, MapValue]] = None,
1346        usage_details: Optional[Dict[str, int]] = None,
1347        cost_details: Optional[Dict[str, float]] = None,
1348        prompt: Optional[PromptClient] = None,
1349    ):
1350        """Initialize a new LangfuseGeneration span.
1351
1352        Args:
1353            otel_span: The OpenTelemetry span to wrap
1354            langfuse_client: Reference to the parent Langfuse client
1355            input: Input data for the generation (e.g., prompts)
1356            output: Output from the generation (e.g., completions)
1357            metadata: Additional metadata to associate with the generation
1358            environment: The tracing environment
1359            release: Release identifier for the application
1360            version: Version identifier for the model or component
1361            level: Importance level of the generation (info, warning, error)
1362            status_message: Optional status message for the generation
1363            completion_start_time: When the model started generating the response
1364            model: Name/identifier of the AI model used (e.g., "gpt-4")
1365            model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
1366            usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
1367            cost_details: Cost information for the model call
1368            prompt: Associated prompt template from Langfuse prompt management
1369        """
1370        super().__init__(
1371            as_type="generation",
1372            otel_span=otel_span,
1373            langfuse_client=langfuse_client,
1374            input=input,
1375            output=output,
1376            metadata=metadata,
1377            environment=environment,
1378            release=release,
1379            version=version,
1380            level=level,
1381            status_message=status_message,
1382            completion_start_time=completion_start_time,
1383            model=model,
1384            model_parameters=model_parameters,
1385            usage_details=usage_details,
1386            cost_details=cost_details,
1387            prompt=prompt,
1388        )

Specialized span implementation for AI model generations in Langfuse.

This class represents a generation span specifically designed for tracking AI/LLM operations. It extends the base LangfuseObservationWrapper with specialized attributes for model details, token usage, and costs.

LangfuseGeneration( *, otel_span: opentelemetry.trace.span.Span, langfuse_client: Langfuse, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, environment: Optional[str] = None, release: Optional[str] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None, completion_start_time: Optional[datetime.datetime] = None, model: Optional[str] = None, model_parameters: Optional[Dict[str, Union[str, NoneType, int, float, bool, List[str]]]] = None, usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Union[langfuse.model.TextPromptClient, langfuse.model.ChatPromptClient, NoneType] = None)
1330    def __init__(
1331        self,
1332        *,
1333        otel_span: otel_trace_api.Span,
1334        langfuse_client: "Langfuse",
1335        input: Optional[Any] = None,
1336        output: Optional[Any] = None,
1337        metadata: Optional[Any] = None,
1338        environment: Optional[str] = None,
1339        release: Optional[str] = None,
1340        version: Optional[str] = None,
1341        level: Optional[SpanLevel] = None,
1342        status_message: Optional[str] = None,
1343        completion_start_time: Optional[datetime] = None,
1344        model: Optional[str] = None,
1345        model_parameters: Optional[Dict[str, MapValue]] = None,
1346        usage_details: Optional[Dict[str, int]] = None,
1347        cost_details: Optional[Dict[str, float]] = None,
1348        prompt: Optional[PromptClient] = None,
1349    ):
1350        """Initialize a new LangfuseGeneration span.
1351
1352        Args:
1353            otel_span: The OpenTelemetry span to wrap
1354            langfuse_client: Reference to the parent Langfuse client
1355            input: Input data for the generation (e.g., prompts)
1356            output: Output from the generation (e.g., completions)
1357            metadata: Additional metadata to associate with the generation
1358            environment: The tracing environment
1359            release: Release identifier for the application
1360            version: Version identifier for the model or component
1361            level: Importance level of the generation (info, warning, error)
1362            status_message: Optional status message for the generation
1363            completion_start_time: When the model started generating the response
1364            model: Name/identifier of the AI model used (e.g., "gpt-4")
1365            model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
1366            usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
1367            cost_details: Cost information for the model call
1368            prompt: Associated prompt template from Langfuse prompt management
1369        """
1370        super().__init__(
1371            as_type="generation",
1372            otel_span=otel_span,
1373            langfuse_client=langfuse_client,
1374            input=input,
1375            output=output,
1376            metadata=metadata,
1377            environment=environment,
1378            release=release,
1379            version=version,
1380            level=level,
1381            status_message=status_message,
1382            completion_start_time=completion_start_time,
1383            model=model,
1384            model_parameters=model_parameters,
1385            usage_details=usage_details,
1386            cost_details=cost_details,
1387            prompt=prompt,
1388        )

Initialize a new LangfuseGeneration span.

Arguments:
  • otel_span: The OpenTelemetry span to wrap
  • langfuse_client: Reference to the parent Langfuse client
  • input: Input data for the generation (e.g., prompts)
  • output: Output from the generation (e.g., completions)
  • metadata: Additional metadata to associate with the generation
  • environment: The tracing environment
  • release: Release identifier for the application
  • version: Version identifier for the model or component
  • level: Importance level of the generation (info, warning, error)
  • status_message: Optional status message for the generation
  • completion_start_time: When the model started generating the response
  • model: Name/identifier of the AI model used (e.g., "gpt-4")
  • model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
  • usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
  • cost_details: Cost information for the model call
  • prompt: Associated prompt template from Langfuse prompt management
class LangfuseEvent(langfuse._client.span.LangfuseObservationWrapper):
1391class LangfuseEvent(LangfuseObservationWrapper):
1392    """Specialized span implementation for Langfuse Events."""
1393
1394    def __init__(
1395        self,
1396        *,
1397        otel_span: otel_trace_api.Span,
1398        langfuse_client: "Langfuse",
1399        input: Optional[Any] = None,
1400        output: Optional[Any] = None,
1401        metadata: Optional[Any] = None,
1402        environment: Optional[str] = None,
1403        release: Optional[str] = None,
1404        version: Optional[str] = None,
1405        level: Optional[SpanLevel] = None,
1406        status_message: Optional[str] = None,
1407    ):
1408        """Initialize a new LangfuseEvent span.
1409
1410        Args:
1411            otel_span: The OpenTelemetry span to wrap
1412            langfuse_client: Reference to the parent Langfuse client
1413            input: Input data for the event
1414            output: Output from the event
1415            metadata: Additional metadata to associate with the generation
1416            environment: The tracing environment
1417            release: Release identifier for the application
1418            version: Version identifier for the model or component
1419            level: Importance level of the generation (info, warning, error)
1420            status_message: Optional status message for the generation
1421        """
1422        super().__init__(
1423            otel_span=otel_span,
1424            as_type="event",
1425            langfuse_client=langfuse_client,
1426            input=input,
1427            output=output,
1428            metadata=metadata,
1429            environment=environment,
1430            release=release,
1431            version=version,
1432            level=level,
1433            status_message=status_message,
1434        )
1435
1436    def update(
1437        self,
1438        *,
1439        name: Optional[str] = None,
1440        input: Optional[Any] = None,
1441        output: Optional[Any] = None,
1442        metadata: Optional[Any] = None,
1443        version: Optional[str] = None,
1444        level: Optional[SpanLevel] = None,
1445        status_message: Optional[str] = None,
1446        completion_start_time: Optional[datetime] = None,
1447        model: Optional[str] = None,
1448        model_parameters: Optional[Dict[str, MapValue]] = None,
1449        usage_details: Optional[Dict[str, int]] = None,
1450        cost_details: Optional[Dict[str, float]] = None,
1451        prompt: Optional[PromptClient] = None,
1452        **kwargs: Any,
1453    ) -> "LangfuseEvent":
1454        """Update is not allowed for LangfuseEvent because events cannot be updated.
1455
1456        This method logs a warning and returns self without making changes.
1457
1458        Returns:
1459            self: Returns the unchanged LangfuseEvent instance
1460        """
1461        langfuse_logger.warning(
1462            "Attempted to update LangfuseEvent observation. Events cannot be updated after creation."
1463        )
1464        return self

Specialized span implementation for Langfuse Events.

LangfuseEvent( *, otel_span: opentelemetry.trace.span.Span, langfuse_client: Langfuse, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, environment: Optional[str] = None, release: Optional[str] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None)
1394    def __init__(
1395        self,
1396        *,
1397        otel_span: otel_trace_api.Span,
1398        langfuse_client: "Langfuse",
1399        input: Optional[Any] = None,
1400        output: Optional[Any] = None,
1401        metadata: Optional[Any] = None,
1402        environment: Optional[str] = None,
1403        release: Optional[str] = None,
1404        version: Optional[str] = None,
1405        level: Optional[SpanLevel] = None,
1406        status_message: Optional[str] = None,
1407    ):
1408        """Initialize a new LangfuseEvent span.
1409
1410        Args:
1411            otel_span: The OpenTelemetry span to wrap
1412            langfuse_client: Reference to the parent Langfuse client
1413            input: Input data for the event
1414            output: Output from the event
1415            metadata: Additional metadata to associate with the generation
1416            environment: The tracing environment
1417            release: Release identifier for the application
1418            version: Version identifier for the model or component
1419            level: Importance level of the generation (info, warning, error)
1420            status_message: Optional status message for the generation
1421        """
1422        super().__init__(
1423            otel_span=otel_span,
1424            as_type="event",
1425            langfuse_client=langfuse_client,
1426            input=input,
1427            output=output,
1428            metadata=metadata,
1429            environment=environment,
1430            release=release,
1431            version=version,
1432            level=level,
1433            status_message=status_message,
1434        )

Initialize a new LangfuseEvent span.

Arguments:
  • otel_span: The OpenTelemetry span to wrap
  • langfuse_client: Reference to the parent Langfuse client
  • input: Input data for the event
  • output: Output from the event
  • metadata: Additional metadata to associate with the generation
  • environment: The tracing environment
  • release: Release identifier for the application
  • version: Version identifier for the model or component
  • level: Importance level of the generation (info, warning, error)
  • status_message: Optional status message for the generation
def update( self, *, name: Optional[str] = None, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, version: Optional[str] = None, level: Optional[Literal['DEBUG', 'DEFAULT', 'WARNING', 'ERROR']] = None, status_message: Optional[str] = None, completion_start_time: Optional[datetime.datetime] = None, model: Optional[str] = None, model_parameters: Optional[Dict[str, Union[str, NoneType, int, float, bool, List[str]]]] = None, usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Union[langfuse.model.TextPromptClient, langfuse.model.ChatPromptClient, NoneType] = None, **kwargs: Any) -> LangfuseEvent:
1436    def update(
1437        self,
1438        *,
1439        name: Optional[str] = None,
1440        input: Optional[Any] = None,
1441        output: Optional[Any] = None,
1442        metadata: Optional[Any] = None,
1443        version: Optional[str] = None,
1444        level: Optional[SpanLevel] = None,
1445        status_message: Optional[str] = None,
1446        completion_start_time: Optional[datetime] = None,
1447        model: Optional[str] = None,
1448        model_parameters: Optional[Dict[str, MapValue]] = None,
1449        usage_details: Optional[Dict[str, int]] = None,
1450        cost_details: Optional[Dict[str, float]] = None,
1451        prompt: Optional[PromptClient] = None,
1452        **kwargs: Any,
1453    ) -> "LangfuseEvent":
1454        """Update is not allowed for LangfuseEvent because events cannot be updated.
1455
1456        This method logs a warning and returns self without making changes.
1457
1458        Returns:
1459            self: Returns the unchanged LangfuseEvent instance
1460        """
1461        langfuse_logger.warning(
1462            "Attempted to update LangfuseEvent observation. Events cannot be updated after creation."
1463        )
1464        return self

Update is not allowed for LangfuseEvent because events cannot be updated.

This method logs a warning and returns self without making changes.

Returns:

self: Returns the unchanged LangfuseEvent instance

class LangfuseOtelSpanAttributes:
28class LangfuseOtelSpanAttributes:
29    # Langfuse-Trace attributes
30    TRACE_NAME = "langfuse.trace.name"
31    TRACE_USER_ID = "user.id"
32    TRACE_SESSION_ID = "session.id"
33    TRACE_TAGS = "langfuse.trace.tags"
34    TRACE_PUBLIC = "langfuse.trace.public"
35    TRACE_METADATA = "langfuse.trace.metadata"
36    TRACE_INPUT = "langfuse.trace.input"
37    TRACE_OUTPUT = "langfuse.trace.output"
38
39    # Langfuse-observation attributes
40    OBSERVATION_TYPE = "langfuse.observation.type"
41    OBSERVATION_METADATA = "langfuse.observation.metadata"
42    OBSERVATION_LEVEL = "langfuse.observation.level"
43    OBSERVATION_STATUS_MESSAGE = "langfuse.observation.status_message"
44    OBSERVATION_INPUT = "langfuse.observation.input"
45    OBSERVATION_OUTPUT = "langfuse.observation.output"
46
47    # Langfuse-observation of type Generation attributes
48    OBSERVATION_COMPLETION_START_TIME = "langfuse.observation.completion_start_time"
49    OBSERVATION_MODEL = "langfuse.observation.model.name"
50    OBSERVATION_MODEL_PARAMETERS = "langfuse.observation.model.parameters"
51    OBSERVATION_USAGE_DETAILS = "langfuse.observation.usage_details"
52    OBSERVATION_COST_DETAILS = "langfuse.observation.cost_details"
53    OBSERVATION_PROMPT_NAME = "langfuse.observation.prompt.name"
54    OBSERVATION_PROMPT_VERSION = "langfuse.observation.prompt.version"
55
56    # General
57    ENVIRONMENT = "langfuse.environment"
58    RELEASE = "langfuse.release"
59    VERSION = "langfuse.version"
60
61    # Internal
62    AS_ROOT = "langfuse.internal.as_root"
63    IS_APP_ROOT = "langfuse.internal.is_app_root"
64
65    # Experiments
66    EXPERIMENT_ID = "langfuse.experiment.id"
67    EXPERIMENT_NAME = "langfuse.experiment.name"
68    EXPERIMENT_DESCRIPTION = "langfuse.experiment.description"
69    EXPERIMENT_METADATA = "langfuse.experiment.metadata"
70    EXPERIMENT_DATASET_ID = "langfuse.experiment.dataset.id"
71    EXPERIMENT_ITEM_ID = "langfuse.experiment.item.id"
72    EXPERIMENT_ITEM_EXPECTED_OUTPUT = "langfuse.experiment.item.expected_output"
73    EXPERIMENT_ITEM_METADATA = "langfuse.experiment.item.metadata"
74    EXPERIMENT_ITEM_ROOT_OBSERVATION_ID = "langfuse.experiment.item.root_observation_id"
TRACE_NAME = 'langfuse.trace.name'
TRACE_USER_ID = 'user.id'
TRACE_SESSION_ID = 'session.id'
TRACE_TAGS = 'langfuse.trace.tags'
TRACE_PUBLIC = 'langfuse.trace.public'
TRACE_METADATA = 'langfuse.trace.metadata'
TRACE_INPUT = 'langfuse.trace.input'
TRACE_OUTPUT = 'langfuse.trace.output'
OBSERVATION_TYPE = 'langfuse.observation.type'
OBSERVATION_METADATA = 'langfuse.observation.metadata'
OBSERVATION_LEVEL = 'langfuse.observation.level'
OBSERVATION_STATUS_MESSAGE = 'langfuse.observation.status_message'
OBSERVATION_INPUT = 'langfuse.observation.input'
OBSERVATION_OUTPUT = 'langfuse.observation.output'
OBSERVATION_COMPLETION_START_TIME = 'langfuse.observation.completion_start_time'
OBSERVATION_MODEL = 'langfuse.observation.model.name'
OBSERVATION_MODEL_PARAMETERS = 'langfuse.observation.model.parameters'
OBSERVATION_USAGE_DETAILS = 'langfuse.observation.usage_details'
OBSERVATION_COST_DETAILS = 'langfuse.observation.cost_details'
OBSERVATION_PROMPT_NAME = 'langfuse.observation.prompt.name'
OBSERVATION_PROMPT_VERSION = 'langfuse.observation.prompt.version'
ENVIRONMENT = 'langfuse.environment'
RELEASE = 'langfuse.release'
VERSION = 'langfuse.version'
AS_ROOT = 'langfuse.internal.as_root'
IS_APP_ROOT = 'langfuse.internal.is_app_root'
EXPERIMENT_ID = 'langfuse.experiment.id'
EXPERIMENT_NAME = 'langfuse.experiment.name'
EXPERIMENT_DESCRIPTION = 'langfuse.experiment.description'
EXPERIMENT_METADATA = 'langfuse.experiment.metadata'
EXPERIMENT_DATASET_ID = 'langfuse.experiment.dataset.id'
EXPERIMENT_ITEM_ID = 'langfuse.experiment.item.id'
EXPERIMENT_ITEM_EXPECTED_OUTPUT = 'langfuse.experiment.item.expected_output'
EXPERIMENT_ITEM_METADATA = 'langfuse.experiment.item.metadata'
EXPERIMENT_ITEM_ROOT_OBSERVATION_ID = 'langfuse.experiment.item.root_observation_id'
class LangfuseAgent(langfuse._client.span.LangfuseObservationWrapper):
1467class LangfuseAgent(LangfuseObservationWrapper):
1468    """Agent observation for reasoning blocks that act on tools using LLM guidance."""
1469
1470    def __init__(self, **kwargs: Any) -> None:
1471        """Initialize a new LangfuseAgent span."""
1472        kwargs["as_type"] = "agent"
1473        super().__init__(**kwargs)

Agent observation for reasoning blocks that act on tools using LLM guidance.

LangfuseAgent(**kwargs: Any)
1470    def __init__(self, **kwargs: Any) -> None:
1471        """Initialize a new LangfuseAgent span."""
1472        kwargs["as_type"] = "agent"
1473        super().__init__(**kwargs)

Initialize a new LangfuseAgent span.

class LangfuseTool(langfuse._client.span.LangfuseObservationWrapper):
1476class LangfuseTool(LangfuseObservationWrapper):
1477    """Tool observation representing external tool calls, e.g., calling a weather API."""
1478
1479    def __init__(self, **kwargs: Any) -> None:
1480        """Initialize a new LangfuseTool span."""
1481        kwargs["as_type"] = "tool"
1482        super().__init__(**kwargs)

Tool observation representing external tool calls, e.g., calling a weather API.

LangfuseTool(**kwargs: Any)
1479    def __init__(self, **kwargs: Any) -> None:
1480        """Initialize a new LangfuseTool span."""
1481        kwargs["as_type"] = "tool"
1482        super().__init__(**kwargs)

Initialize a new LangfuseTool span.

class LangfuseChain(langfuse._client.span.LangfuseObservationWrapper):
1485class LangfuseChain(LangfuseObservationWrapper):
1486    """Chain observation for connecting LLM application steps, e.g. passing context from retriever to LLM."""
1487
1488    def __init__(self, **kwargs: Any) -> None:
1489        """Initialize a new LangfuseChain span."""
1490        kwargs["as_type"] = "chain"
1491        super().__init__(**kwargs)

Chain observation for connecting LLM application steps, e.g. passing context from retriever to LLM.

LangfuseChain(**kwargs: Any)
1488    def __init__(self, **kwargs: Any) -> None:
1489        """Initialize a new LangfuseChain span."""
1490        kwargs["as_type"] = "chain"
1491        super().__init__(**kwargs)

Initialize a new LangfuseChain span.

class LangfuseEmbedding(langfuse._client.span.LangfuseObservationWrapper):
1503class LangfuseEmbedding(LangfuseObservationWrapper):
1504    """Embedding observation for LLM embedding calls, typically used before retrieval."""
1505
1506    def __init__(self, **kwargs: Any) -> None:
1507        """Initialize a new LangfuseEmbedding span."""
1508        kwargs["as_type"] = "embedding"
1509        super().__init__(**kwargs)

Embedding observation for LLM embedding calls, typically used before retrieval.

LangfuseEmbedding(**kwargs: Any)
1506    def __init__(self, **kwargs: Any) -> None:
1507        """Initialize a new LangfuseEmbedding span."""
1508        kwargs["as_type"] = "embedding"
1509        super().__init__(**kwargs)

Initialize a new LangfuseEmbedding span.

class LangfuseEvaluator(langfuse._client.span.LangfuseObservationWrapper):
1512class LangfuseEvaluator(LangfuseObservationWrapper):
1513    """Evaluator observation for assessing relevance, correctness, or helpfulness of LLM outputs."""
1514
1515    def __init__(self, **kwargs: Any) -> None:
1516        """Initialize a new LangfuseEvaluator span."""
1517        kwargs["as_type"] = "evaluator"
1518        super().__init__(**kwargs)

Evaluator observation for assessing relevance, correctness, or helpfulness of LLM outputs.

LangfuseEvaluator(**kwargs: Any)
1515    def __init__(self, **kwargs: Any) -> None:
1516        """Initialize a new LangfuseEvaluator span."""
1517        kwargs["as_type"] = "evaluator"
1518        super().__init__(**kwargs)

Initialize a new LangfuseEvaluator span.

class LangfuseRetriever(langfuse._client.span.LangfuseObservationWrapper):
1494class LangfuseRetriever(LangfuseObservationWrapper):
1495    """Retriever observation for data retrieval steps, e.g. vector store or database queries."""
1496
1497    def __init__(self, **kwargs: Any) -> None:
1498        """Initialize a new LangfuseRetriever span."""
1499        kwargs["as_type"] = "retriever"
1500        super().__init__(**kwargs)

Retriever observation for data retrieval steps, e.g. vector store or database queries.

LangfuseRetriever(**kwargs: Any)
1497    def __init__(self, **kwargs: Any) -> None:
1498        """Initialize a new LangfuseRetriever span."""
1499        kwargs["as_type"] = "retriever"
1500        super().__init__(**kwargs)

Initialize a new LangfuseRetriever span.

class LangfuseGuardrail(langfuse._client.span.LangfuseObservationWrapper):
1521class LangfuseGuardrail(LangfuseObservationWrapper):
1522    """Guardrail observation for protection e.g. against jailbreaks or offensive content."""
1523
1524    def __init__(self, **kwargs: Any) -> None:
1525        """Initialize a new LangfuseGuardrail span."""
1526        kwargs["as_type"] = "guardrail"
1527        super().__init__(**kwargs)

Guardrail observation for protection e.g. against jailbreaks or offensive content.

LangfuseGuardrail(**kwargs: Any)
1524    def __init__(self, **kwargs: Any) -> None:
1525        """Initialize a new LangfuseGuardrail span."""
1526        kwargs["as_type"] = "guardrail"
1527        super().__init__(**kwargs)

Initialize a new LangfuseGuardrail span.

class Evaluation:
101class Evaluation:
102    """Represents an evaluation result for an experiment item or an entire experiment run.
103
104    This class provides a strongly-typed way to create evaluation results in evaluator functions.
105    Users must use keyword arguments when instantiating this class.
106
107    Attributes:
108        name: Unique identifier for the evaluation metric. Should be descriptive
109            and consistent across runs (e.g., "accuracy", "bleu_score", "toxicity").
110            Used for aggregation and comparison across experiment runs.
111        value: The evaluation score or result. Can be:
112            - Numeric (int/float): For quantitative metrics like accuracy (0.85), BLEU (0.42)
113            - String: For categorical results like "positive", "negative", "neutral"
114            - Boolean: For binary assessments like "passes_safety_check"
115        comment: Optional human-readable explanation of the evaluation result.
116            Useful for providing context, explaining scoring rationale, or noting
117            special conditions. Displayed in Langfuse UI for interpretability.
118        metadata: Optional structured metadata about the evaluation process.
119            Can include confidence scores, intermediate calculations, model versions,
120            or any other relevant technical details.
121        data_type: Optional score data type. Required if value is not NUMERIC.
122            One of NUMERIC, CATEGORICAL, or BOOLEAN. Defaults to NUMERIC.
123        config_id: Optional Langfuse score config ID.
124
125    Examples:
126        Basic accuracy evaluation:
127        ```python
128        from langfuse import Evaluation
129
130        def accuracy_evaluator(*, input, output, expected_output=None, **kwargs):
131            if not expected_output:
132                return Evaluation(name="accuracy", value=0, comment="No expected output")
133
134            is_correct = output.strip().lower() == expected_output.strip().lower()
135            return Evaluation(
136                name="accuracy",
137                value=1.0 if is_correct else 0.0,
138                comment="Correct answer" if is_correct else "Incorrect answer"
139            )
140        ```
141
142        Multi-metric evaluator:
143        ```python
144        def comprehensive_evaluator(*, input, output, expected_output=None, **kwargs):
145            return [
146                Evaluation(name="length", value=len(output), comment=f"Output length: {len(output)} chars"),
147                Evaluation(name="has_greeting", value="hello" in output.lower(), comment="Contains greeting"),
148                Evaluation(
149                    name="quality",
150                    value=0.85,
151                    comment="High quality response",
152                    metadata={"confidence": 0.92, "model": "gpt-4"}
153                )
154            ]
155        ```
156
157        Categorical evaluation:
158        ```python
159        def sentiment_evaluator(*, input, output, **kwargs):
160            sentiment = analyze_sentiment(output)  # Returns "positive", "negative", or "neutral"
161            return Evaluation(
162                name="sentiment",
163                value=sentiment,
164                comment=f"Response expresses {sentiment} sentiment",
165                data_type="CATEGORICAL"
166            )
167        ```
168
169        Failed evaluation with error handling:
170        ```python
171        def external_api_evaluator(*, input, output, **kwargs):
172            try:
173                score = external_api.evaluate(output)
174                return Evaluation(name="external_score", value=score)
175            except Exception as e:
176                return Evaluation(
177                    name="external_score",
178                    value=0,
179                    comment=f"API unavailable: {e}",
180                    metadata={"error": str(e), "retry_count": 3}
181                )
182        ```
183
184    Note:
185        All arguments must be passed as keywords. Positional arguments are not allowed
186        to ensure code clarity and prevent errors from argument reordering.
187    """
188
189    def __init__(
190        self,
191        *,
192        name: str,
193        value: Union[int, float, str, bool],
194        comment: Optional[str] = None,
195        metadata: Optional[Dict[str, Any]] = None,
196        data_type: Optional[ExperimentScoreType] = None,
197        config_id: Optional[str] = None,
198    ):
199        """Initialize an Evaluation with the provided data.
200
201        Args:
202            name: Unique identifier for the evaluation metric.
203            value: The evaluation score or result.
204            comment: Optional human-readable explanation of the result.
205            metadata: Optional structured metadata about the evaluation process.
206            data_type: Optional score data type (NUMERIC, CATEGORICAL, or BOOLEAN).
207            config_id: Optional Langfuse score config ID.
208
209        Note:
210            All arguments must be provided as keywords. Positional arguments will raise a TypeError.
211        """
212        self.name = name
213        self.value = value
214        self.comment = comment
215        self.metadata = metadata
216        self.data_type = data_type
217        self.config_id = config_id

Represents an evaluation result for an experiment item or an entire experiment run.

This class provides a strongly-typed way to create evaluation results in evaluator functions. Users must use keyword arguments when instantiating this class.

Attributes:
  • name: Unique identifier for the evaluation metric. Should be descriptive and consistent across runs (e.g., "accuracy", "bleu_score", "toxicity"). Used for aggregation and comparison across experiment runs.
  • value: The evaluation score or result. Can be:
    • Numeric (int/float): For quantitative metrics like accuracy (0.85), BLEU (0.42)
    • String: For categorical results like "positive", "negative", "neutral"
    • Boolean: For binary assessments like "passes_safety_check"
  • comment: Optional human-readable explanation of the evaluation result. Useful for providing context, explaining scoring rationale, or noting special conditions. Displayed in Langfuse UI for interpretability.
  • metadata: Optional structured metadata about the evaluation process. Can include confidence scores, intermediate calculations, model versions, or any other relevant technical details.
  • data_type: Optional score data type. Required if value is not NUMERIC. One of NUMERIC, CATEGORICAL, or BOOLEAN. Defaults to NUMERIC.
  • config_id: Optional Langfuse score config ID.
Examples:

Basic accuracy evaluation:

from langfuse import Evaluation

def accuracy_evaluator(*, input, output, expected_output=None, **kwargs):
    if not expected_output:
        return Evaluation(name="accuracy", value=0, comment="No expected output")

    is_correct = output.strip().lower() == expected_output.strip().lower()
    return Evaluation(
        name="accuracy",
        value=1.0 if is_correct else 0.0,
        comment="Correct answer" if is_correct else "Incorrect answer"
    )

Multi-metric evaluator:

def comprehensive_evaluator(*, input, output, expected_output=None, **kwargs):
    return [
        Evaluation(name="length", value=len(output), comment=f"Output length: {len(output)} chars"),
        Evaluation(name="has_greeting", value="hello" in output.lower(), comment="Contains greeting"),
        Evaluation(
            name="quality",
            value=0.85,
            comment="High quality response",
            metadata={"confidence": 0.92, "model": "gpt-4"}
        )
    ]

Categorical evaluation:

def sentiment_evaluator(*, input, output, **kwargs):
    sentiment = analyze_sentiment(output)  # Returns "positive", "negative", or "neutral"
    return Evaluation(
        name="sentiment",
        value=sentiment,
        comment=f"Response expresses {sentiment} sentiment",
        data_type="CATEGORICAL"
    )

Failed evaluation with error handling:

def external_api_evaluator(*, input, output, **kwargs):
    try:
        score = external_api.evaluate(output)
        return Evaluation(name="external_score", value=score)
    except Exception as e:
        return Evaluation(
            name="external_score",
            value=0,
            comment=f"API unavailable: {e}",
            metadata={"error": str(e), "retry_count": 3}
        )
Note:

All arguments must be passed as keywords. Positional arguments are not allowed to ensure code clarity and prevent errors from argument reordering.

Evaluation( *, name: str, value: Union[int, float, str, bool], comment: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, data_type: Optional[Literal['NUMERIC', 'CATEGORICAL', 'BOOLEAN']] = None, config_id: Optional[str] = None)
189    def __init__(
190        self,
191        *,
192        name: str,
193        value: Union[int, float, str, bool],
194        comment: Optional[str] = None,
195        metadata: Optional[Dict[str, Any]] = None,
196        data_type: Optional[ExperimentScoreType] = None,
197        config_id: Optional[str] = None,
198    ):
199        """Initialize an Evaluation with the provided data.
200
201        Args:
202            name: Unique identifier for the evaluation metric.
203            value: The evaluation score or result.
204            comment: Optional human-readable explanation of the result.
205            metadata: Optional structured metadata about the evaluation process.
206            data_type: Optional score data type (NUMERIC, CATEGORICAL, or BOOLEAN).
207            config_id: Optional Langfuse score config ID.
208
209        Note:
210            All arguments must be provided as keywords. Positional arguments will raise a TypeError.
211        """
212        self.name = name
213        self.value = value
214        self.comment = comment
215        self.metadata = metadata
216        self.data_type = data_type
217        self.config_id = config_id

Initialize an Evaluation with the provided data.

Arguments:
  • name: Unique identifier for the evaluation metric.
  • value: The evaluation score or result.
  • comment: Optional human-readable explanation of the result.
  • metadata: Optional structured metadata about the evaluation process.
  • data_type: Optional score data type (NUMERIC, CATEGORICAL, or BOOLEAN).
  • config_id: Optional Langfuse score config ID.
Note:

All arguments must be provided as keywords. Positional arguments will raise a TypeError.

name
value
comment
metadata
data_type
config_id
class EvaluatorInputs:
 38class EvaluatorInputs:
 39    """Input data structure for evaluators, returned by mapper functions.
 40
 41    This class provides a strongly-typed container for transforming API response
 42    objects (traces, observations) into the standardized format expected
 43    by evaluator functions. It ensures consistent access to input, output, expected
 44    output, and metadata regardless of the source entity type.
 45
 46    Attributes:
 47        input: The input data that was provided to generate the output being evaluated.
 48            For traces, this might be the initial prompt or request. For observations,
 49            this could be the span's input. The exact meaning depends on your use case.
 50        output: The actual output that was produced and needs to be evaluated.
 51            For traces, this is typically the final response. For observations,
 52            this might be the generation output or span result.
 53        expected_output: Optional ground truth or expected result for comparison.
 54            Used by evaluators to assess correctness. May be None if no ground truth
 55            is available for the entity being evaluated.
 56        metadata: Optional structured metadata providing additional context for evaluation.
 57            Can include information about the entity, execution context, user attributes,
 58            or any other relevant data that evaluators might use.
 59
 60    Examples:
 61        Simple mapper for traces:
 62        ```python
 63        from langfuse import EvaluatorInputs
 64
 65        def trace_mapper(trace):
 66            return EvaluatorInputs(
 67                input=trace.input,
 68                output=trace.output,
 69                expected_output=None,  # No ground truth available
 70                metadata={"user_id": trace.user_id, "tags": trace.tags}
 71            )
 72        ```
 73
 74        Mapper for observations extracting specific fields:
 75        ```python
 76        def observation_mapper(observation):
 77            # Extract input/output from observation's data
 78            input_data = observation.input if hasattr(observation, 'input') else None
 79            output_data = observation.output if hasattr(observation, 'output') else None
 80
 81            return EvaluatorInputs(
 82                input=input_data,
 83                output=output_data,
 84                expected_output=None,
 85                metadata={
 86                    "observation_type": observation.type,
 87                    "model": observation.model,
 88                    "latency_ms": observation.end_time - observation.start_time
 89                }
 90            )
 91        ```
 92        ```
 93
 94    Note:
 95        All arguments must be passed as keywords when instantiating this class.
 96    """
 97
 98    def __init__(
 99        self,
100        *,
101        input: Any,
102        output: Any,
103        expected_output: Any = None,
104        metadata: Optional[Dict[str, Any]] = None,
105    ):
106        """Initialize EvaluatorInputs with the provided data.
107
108        Args:
109            input: The input data for evaluation.
110            output: The output data to be evaluated.
111            expected_output: Optional ground truth for comparison.
112            metadata: Optional additional context for evaluation.
113
114        Note:
115            All arguments must be provided as keywords.
116        """
117        self.input = input
118        self.output = output
119        self.expected_output = expected_output
120        self.metadata = metadata

Input data structure for evaluators, returned by mapper functions.

This class provides a strongly-typed container for transforming API response objects (traces, observations) into the standardized format expected by evaluator functions. It ensures consistent access to input, output, expected output, and metadata regardless of the source entity type.

Attributes:
  • input: The input data that was provided to generate the output being evaluated. For traces, this might be the initial prompt or request. For observations, this could be the span's input. The exact meaning depends on your use case.
  • output: The actual output that was produced and needs to be evaluated. For traces, this is typically the final response. For observations, this might be the generation output or span result.
  • expected_output: Optional ground truth or expected result for comparison. Used by evaluators to assess correctness. May be None if no ground truth is available for the entity being evaluated.
  • metadata: Optional structured metadata providing additional context for evaluation. Can include information about the entity, execution context, user attributes, or any other relevant data that evaluators might use.
Examples:

Simple mapper for traces:

from langfuse import EvaluatorInputs

def trace_mapper(trace):
    return EvaluatorInputs(
        input=trace.input,
        output=trace.output,
        expected_output=None,  # No ground truth available
        metadata={"user_id": trace.user_id, "tags": trace.tags}
    )

Mapper for observations extracting specific fields:

def observation_mapper(observation):
    # Extract input/output from observation's data
    input_data = observation.input if hasattr(observation, 'input') else None
    output_data = observation.output if hasattr(observation, 'output') else None

    return EvaluatorInputs(
        input=input_data,
        output=output_data,
        expected_output=None,
        metadata={
            "observation_type": observation.type,
            "model": observation.model,
            "latency_ms": observation.end_time - observation.start_time
        }
    )

```

Note:

All arguments must be passed as keywords when instantiating this class.

EvaluatorInputs( *, input: Any, output: Any, expected_output: Any = None, metadata: Optional[Dict[str, Any]] = None)
 98    def __init__(
 99        self,
100        *,
101        input: Any,
102        output: Any,
103        expected_output: Any = None,
104        metadata: Optional[Dict[str, Any]] = None,
105    ):
106        """Initialize EvaluatorInputs with the provided data.
107
108        Args:
109            input: The input data for evaluation.
110            output: The output data to be evaluated.
111            expected_output: Optional ground truth for comparison.
112            metadata: Optional additional context for evaluation.
113
114        Note:
115            All arguments must be provided as keywords.
116        """
117        self.input = input
118        self.output = output
119        self.expected_output = expected_output
120        self.metadata = metadata

Initialize EvaluatorInputs with the provided data.

Arguments:
  • input: The input data for evaluation.
  • output: The output data to be evaluated.
  • expected_output: Optional ground truth for comparison.
  • metadata: Optional additional context for evaluation.
Note:

All arguments must be provided as keywords.

input
output
expected_output
metadata
class MapperFunction(typing.Protocol):
123class MapperFunction(Protocol):
124    """Protocol defining the interface for mapper functions in batch evaluation.
125
126    Mapper functions transform API response objects (traces or observations)
127    into the standardized EvaluatorInputs format that evaluators expect. This abstraction
128    allows you to define how to extract and structure evaluation data from different
129    entity types.
130
131    Mapper functions must:
132    - Accept a single item parameter (trace, observation)
133    - Return an EvaluatorInputs instance with input, output, expected_output, metadata
134    - Can be either synchronous or asynchronous
135    - Should handle missing or malformed data gracefully
136    """
137
138    def __call__(
139        self,
140        *,
141        item: Union["TraceWithFullDetails", "ObservationsView"],
142        **kwargs: Dict[str, Any],
143    ) -> Union[EvaluatorInputs, Awaitable[EvaluatorInputs]]:
144        """Transform an API response object into evaluator inputs.
145
146        This method defines how to extract evaluation-relevant data from the raw
147        API response object. The implementation should map entity-specific fields
148        to the standardized input/output/expected_output/metadata structure.
149
150        Args:
151            item: The API response object to transform. The type depends on the scope:
152                - TraceWithFullDetails: When evaluating traces
153                - ObservationsView: When evaluating observations
154
155        Returns:
156            EvaluatorInputs: A structured container with:
157                - input: The input data that generated the output
158                - output: The output to be evaluated
159                - expected_output: Optional ground truth for comparison
160                - metadata: Optional additional context
161
162            Can return either a direct EvaluatorInputs instance or an awaitable
163            (for async mappers that need to fetch additional data).
164
165        Examples:
166            Basic trace mapper:
167            ```python
168            def map_trace(trace):
169                return EvaluatorInputs(
170                    input=trace.input,
171                    output=trace.output,
172                    expected_output=None,
173                    metadata={"trace_id": trace.id, "user": trace.user_id}
174                )
175            ```
176
177            Observation mapper with conditional logic:
178            ```python
179            def map_observation(observation):
180                # Extract fields based on observation type
181                if observation.type == "GENERATION":
182                    input_data = observation.input
183                    output_data = observation.output
184                else:
185                    # For other types, use different fields
186                    input_data = observation.metadata.get("input")
187                    output_data = observation.metadata.get("output")
188
189                return EvaluatorInputs(
190                    input=input_data,
191                    output=output_data,
192                    expected_output=None,
193                    metadata={"obs_id": observation.id, "type": observation.type}
194                )
195            ```
196
197            Async mapper (if additional processing needed):
198            ```python
199            async def map_trace_async(trace):
200                # Could do async processing here if needed
201                processed_output = await some_async_transformation(trace.output)
202
203                return EvaluatorInputs(
204                    input=trace.input,
205                    output=processed_output,
206                    expected_output=None,
207                    metadata={"trace_id": trace.id}
208                )
209            ```
210        """
211        ...

Protocol defining the interface for mapper functions in batch evaluation.

Mapper functions transform API response objects (traces or observations) into the standardized EvaluatorInputs format that evaluators expect. This abstraction allows you to define how to extract and structure evaluation data from different entity types.

Mapper functions must:

  • Accept a single item parameter (trace, observation)
  • Return an EvaluatorInputs instance with input, output, expected_output, metadata
  • Can be either synchronous or asynchronous
  • Should handle missing or malformed data gracefully
MapperFunction(*args, **kwargs)
1927def _no_init_or_replace_init(self, *args, **kwargs):
1928    cls = type(self)
1929
1930    if cls._is_protocol:
1931        raise TypeError('Protocols cannot be instantiated')
1932
1933    # Already using a custom `__init__`. No need to calculate correct
1934    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1935    if cls.__init__ is not _no_init_or_replace_init:
1936        return
1937
1938    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1939    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1940    # searches for a proper new `__init__` in the MRO. The new `__init__`
1941    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1942    # instantiation of the protocol subclass will thus use the new
1943    # `__init__` and no longer call `_no_init_or_replace_init`.
1944    for base in cls.__mro__:
1945        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1946        if init is not _no_init_or_replace_init:
1947            cls.__init__ = init
1948            break
1949    else:
1950        # should not happen
1951        cls.__init__ = object.__init__
1952
1953    cls.__init__(self, *args, **kwargs)
class CompositeEvaluatorFunction(typing.Protocol):
214class CompositeEvaluatorFunction(Protocol):
215    """Protocol defining the interface for composite evaluator functions.
216
217    Composite evaluators create aggregate scores from multiple item-level evaluations.
218    This is commonly used to compute weighted averages, combined metrics, or other
219    composite assessments based on individual evaluation results.
220
221    Composite evaluators:
222    - Accept the same inputs as item-level evaluators (input, output, expected_output, metadata)
223      plus the list of evaluations
224    - Return either a single Evaluation, a list of Evaluations, or a dict
225    - Can be either synchronous or asynchronous
226    - Have access to both raw item data and evaluation results
227    """
228
229    def __call__(
230        self,
231        *,
232        input: Optional[Any] = None,
233        output: Optional[Any] = None,
234        expected_output: Optional[Any] = None,
235        metadata: Optional[Dict[str, Any]] = None,
236        evaluations: List[Evaluation],
237        **kwargs: Dict[str, Any],
238    ) -> Union[
239        Evaluation,
240        List[Evaluation],
241        Dict[str, Any],
242        Awaitable[Evaluation],
243        Awaitable[List[Evaluation]],
244        Awaitable[Dict[str, Any]],
245    ]:
246        r"""Create a composite evaluation from item-level evaluation results.
247
248        This method combines multiple evaluation scores into a single composite metric.
249        Common use cases include weighted averages, pass/fail decisions based on multiple
250        criteria, or custom scoring logic that considers multiple dimensions.
251
252        Args:
253            input: The input data that was provided to the system being evaluated.
254            output: The output generated by the system being evaluated.
255            expected_output: The expected/reference output for comparison (if available).
256            metadata: Additional metadata about the evaluation context.
257            evaluations: List of evaluation results from item-level evaluators.
258                Each evaluation contains name, value, comment, and metadata.
259
260        Returns:
261            Can return any of:
262            - Evaluation: A single composite evaluation result
263            - List[Evaluation]: Multiple composite evaluations
264            - Dict: A dict that will be converted to an Evaluation
265                - name: Identifier for the composite metric (e.g., "composite_score")
266                - value: The computed composite value
267                - comment: Optional explanation of how the score was computed
268                - metadata: Optional details about the composition logic
269
270            Can return either a direct Evaluation instance or an awaitable
271            (for async composite evaluators).
272
273        Examples:
274            Simple weighted average:
275            ```python
276            def weighted_composite(*, input, output, expected_output, metadata, evaluations):
277                weights = {
278                    "accuracy": 0.5,
279                    "relevance": 0.3,
280                    "safety": 0.2
281                }
282
283                total_score = 0.0
284                total_weight = 0.0
285
286                for eval in evaluations:
287                    if eval.name in weights and isinstance(eval.value, (int, float)):
288                        total_score += eval.value * weights[eval.name]
289                        total_weight += weights[eval.name]
290
291                final_score = total_score / total_weight if total_weight > 0 else 0.0
292
293                return Evaluation(
294                    name="composite_score",
295                    value=final_score,
296                    comment=f"Weighted average of {len(evaluations)} metrics"
297                )
298            ```
299
300            Pass/fail composite based on thresholds:
301            ```python
302            def pass_fail_composite(*, input, output, expected_output, metadata, evaluations):
303                # Must pass all criteria
304                thresholds = {
305                    "accuracy": 0.7,
306                    "safety": 0.9,
307                    "relevance": 0.6
308                }
309
310                passes = True
311                failing_metrics = []
312
313                for metric, threshold in thresholds.items():
314                    eval_result = next((e for e in evaluations if e.name == metric), None)
315                    if eval_result and isinstance(eval_result.value, (int, float)):
316                        if eval_result.value < threshold:
317                            passes = False
318                            failing_metrics.append(metric)
319
320                return Evaluation(
321                    name="passes_all_checks",
322                    value=passes,
323                    comment=f"Failed: {', '.join(failing_metrics)}" if failing_metrics else "All checks passed",
324                    data_type="BOOLEAN"
325                )
326            ```
327
328            Async composite with external scoring:
329            ```python
330            async def llm_composite(*, input, output, expected_output, metadata, evaluations):
331                # Use LLM to synthesize multiple evaluation results
332                eval_summary = "\n".join(
333                    f"- {e.name}: {e.value}" for e in evaluations
334                )
335
336                prompt = f"Given these evaluation scores:\n{eval_summary}\n"
337                prompt += f"For the output: {output}\n"
338                prompt += "Provide an overall quality score from 0-1."
339
340                response = await openai.chat.completions.create(
341                    model="gpt-4",
342                    messages=[{"role": "user", "content": prompt}]
343                )
344
345                score = float(response.choices[0].message.content.strip())
346
347                return Evaluation(
348                    name="llm_composite_score",
349                    value=score,
350                    comment="LLM-synthesized composite score"
351                )
352            ```
353
354            Context-aware composite:
355            ```python
356            def context_composite(*, input, output, expected_output, metadata, evaluations):
357                # Adjust weighting based on metadata
358                base_weights = {"accuracy": 0.5, "speed": 0.3, "cost": 0.2}
359
360                # If metadata indicates high importance, prioritize accuracy
361                if metadata and metadata.get('importance') == 'high':
362                    weights = {"accuracy": 0.7, "speed": 0.2, "cost": 0.1}
363                else:
364                    weights = base_weights
365
366                total = sum(
367                    e.value * weights.get(e.name, 0)
368                    for e in evaluations
369                    if isinstance(e.value, (int, float))
370                )
371
372                return Evaluation(
373                    name="weighted_composite",
374                    value=total,
375                    comment="Context-aware weighted composite"
376                )
377            ```
378        """
379        ...

Protocol defining the interface for composite evaluator functions.

Composite evaluators create aggregate scores from multiple item-level evaluations. This is commonly used to compute weighted averages, combined metrics, or other composite assessments based on individual evaluation results.

Composite evaluators:

  • Accept the same inputs as item-level evaluators (input, output, expected_output, metadata) plus the list of evaluations
  • Return either a single Evaluation, a list of Evaluations, or a dict
  • Can be either synchronous or asynchronous
  • Have access to both raw item data and evaluation results
CompositeEvaluatorFunction(*args, **kwargs)
1927def _no_init_or_replace_init(self, *args, **kwargs):
1928    cls = type(self)
1929
1930    if cls._is_protocol:
1931        raise TypeError('Protocols cannot be instantiated')
1932
1933    # Already using a custom `__init__`. No need to calculate correct
1934    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1935    if cls.__init__ is not _no_init_or_replace_init:
1936        return
1937
1938    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1939    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1940    # searches for a proper new `__init__` in the MRO. The new `__init__`
1941    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1942    # instantiation of the protocol subclass will thus use the new
1943    # `__init__` and no longer call `_no_init_or_replace_init`.
1944    for base in cls.__mro__:
1945        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1946        if init is not _no_init_or_replace_init:
1947            cls.__init__ = init
1948            break
1949    else:
1950        # should not happen
1951        cls.__init__ = object.__init__
1952
1953    cls.__init__(self, *args, **kwargs)
class EvaluatorStats:
382class EvaluatorStats:
383    """Statistics for a single evaluator's performance during batch evaluation.
384
385    This class tracks detailed metrics about how a specific evaluator performed
386    across all items in a batch evaluation run. It helps identify evaluator issues,
387    understand reliability, and optimize evaluation pipelines.
388
389    Attributes:
390        name: The name of the evaluator function (extracted from __name__).
391        total_runs: Total number of times the evaluator was invoked.
392        successful_runs: Number of times the evaluator completed successfully.
393        failed_runs: Number of times the evaluator raised an exception or failed.
394        total_scores_created: Total number of evaluation scores created by this evaluator.
395            Can be higher than successful_runs if the evaluator returns multiple scores.
396
397    Examples:
398        Accessing evaluator stats from batch evaluation result:
399        ```python
400        result = client.run_batched_evaluation(...)
401
402        for stats in result.evaluator_stats:
403            print(f"Evaluator: {stats.name}")
404            print(f"  Success rate: {stats.successful_runs / stats.total_runs:.1%}")
405            print(f"  Scores created: {stats.total_scores_created}")
406
407            if stats.failed_runs > 0:
408                print(f"  ⚠️  Failed {stats.failed_runs} times")
409        ```
410
411        Identifying problematic evaluators:
412        ```python
413        result = client.run_batched_evaluation(...)
414
415        # Find evaluators with high failure rates
416        for stats in result.evaluator_stats:
417            failure_rate = stats.failed_runs / stats.total_runs
418            if failure_rate > 0.1:  # More than 10% failures
419                print(f"⚠️  {stats.name} has {failure_rate:.1%} failure rate")
420                print(f"    Consider debugging or removing this evaluator")
421        ```
422
423    Note:
424        All arguments must be passed as keywords when instantiating this class.
425    """
426
427    def __init__(
428        self,
429        *,
430        name: str,
431        total_runs: int = 0,
432        successful_runs: int = 0,
433        failed_runs: int = 0,
434        total_scores_created: int = 0,
435    ):
436        """Initialize EvaluatorStats with the provided metrics.
437
438        Args:
439            name: The evaluator function name.
440            total_runs: Total number of evaluator invocations.
441            successful_runs: Number of successful completions.
442            failed_runs: Number of failures.
443            total_scores_created: Total scores created by this evaluator.
444
445        Note:
446            All arguments must be provided as keywords.
447        """
448        self.name = name
449        self.total_runs = total_runs
450        self.successful_runs = successful_runs
451        self.failed_runs = failed_runs
452        self.total_scores_created = total_scores_created

Statistics for a single evaluator's performance during batch evaluation.

This class tracks detailed metrics about how a specific evaluator performed across all items in a batch evaluation run. It helps identify evaluator issues, understand reliability, and optimize evaluation pipelines.

Attributes:
  • name: The name of the evaluator function (extracted from __name__).
  • total_runs: Total number of times the evaluator was invoked.
  • successful_runs: Number of times the evaluator completed successfully.
  • failed_runs: Number of times the evaluator raised an exception or failed.
  • total_scores_created: Total number of evaluation scores created by this evaluator. Can be higher than successful_runs if the evaluator returns multiple scores.
Examples:

Accessing evaluator stats from batch evaluation result:

result = client.run_batched_evaluation(...)

for stats in result.evaluator_stats:
    print(f"Evaluator: {stats.name}")
    print(f"  Success rate: {stats.successful_runs / stats.total_runs:.1%}")
    print(f"  Scores created: {stats.total_scores_created}")

    if stats.failed_runs > 0:
        print(f"  ⚠️  Failed {stats.failed_runs} times")

Identifying problematic evaluators:

result = client.run_batched_evaluation(...)

# Find evaluators with high failure rates
for stats in result.evaluator_stats:
    failure_rate = stats.failed_runs / stats.total_runs
    if failure_rate > 0.1:  # More than 10% failures
        print(f"⚠️  {stats.name} has {failure_rate:.1%} failure rate")
        print(f"    Consider debugging or removing this evaluator")
Note:

All arguments must be passed as keywords when instantiating this class.

EvaluatorStats( *, name: str, total_runs: int = 0, successful_runs: int = 0, failed_runs: int = 0, total_scores_created: int = 0)
427    def __init__(
428        self,
429        *,
430        name: str,
431        total_runs: int = 0,
432        successful_runs: int = 0,
433        failed_runs: int = 0,
434        total_scores_created: int = 0,
435    ):
436        """Initialize EvaluatorStats with the provided metrics.
437
438        Args:
439            name: The evaluator function name.
440            total_runs: Total number of evaluator invocations.
441            successful_runs: Number of successful completions.
442            failed_runs: Number of failures.
443            total_scores_created: Total scores created by this evaluator.
444
445        Note:
446            All arguments must be provided as keywords.
447        """
448        self.name = name
449        self.total_runs = total_runs
450        self.successful_runs = successful_runs
451        self.failed_runs = failed_runs
452        self.total_scores_created = total_scores_created

Initialize EvaluatorStats with the provided metrics.

Arguments:
  • name: The evaluator function name.
  • total_runs: Total number of evaluator invocations.
  • successful_runs: Number of successful completions.
  • failed_runs: Number of failures.
  • total_scores_created: Total scores created by this evaluator.
Note:

All arguments must be provided as keywords.

name
total_runs
successful_runs
failed_runs
total_scores_created
class BatchEvaluationResumeToken:
455class BatchEvaluationResumeToken:
456    """Token for resuming a failed batch evaluation run.
457
458    This class encapsulates all the information needed to resume a batch evaluation
459    that was interrupted or failed partway through. It uses timestamp-based filtering
460    to avoid re-processing items that were already evaluated, even if the underlying
461    dataset changed between runs.
462
463    Attributes:
464        scope: The type of items being evaluated ("traces", "observations").
465        filter: The original JSON filter string used to query items.
466        last_processed_timestamp: ISO 8601 timestamp of the last successfully processed item.
467            Used to construct a filter that only fetches items after this timestamp.
468        last_processed_id: The ID of the last successfully processed item, for reference.
469        items_processed: Count of items successfully processed before interruption.
470
471    Examples:
472        Resuming a failed batch evaluation:
473        ```python
474        # Initial run that fails partway through
475        try:
476            result = client.run_batched_evaluation(
477                scope="traces",
478                mapper=my_mapper,
479                evaluators=[evaluator1, evaluator2],
480                filter='{"tags": ["production"]}',
481                max_items=10000
482            )
483        except Exception as e:
484            print(f"Evaluation failed: {e}")
485
486            # Save the resume token
487            if result.resume_token:
488                # Store resume token for later (e.g., in a file or database)
489                import json
490                with open("resume_token.json", "w") as f:
491                    json.dump({
492                        "scope": result.resume_token.scope,
493                        "filter": result.resume_token.filter,
494                        "last_timestamp": result.resume_token.last_processed_timestamp,
495                        "last_id": result.resume_token.last_processed_id,
496                        "items_done": result.resume_token.items_processed
497                    }, f)
498
499        # Later, resume from where it left off
500        with open("resume_token.json") as f:
501            token_data = json.load(f)
502
503        resume_token = BatchEvaluationResumeToken(
504            scope=token_data["scope"],
505            filter=token_data["filter"],
506            last_processed_timestamp=token_data["last_timestamp"],
507            last_processed_id=token_data["last_id"],
508            items_processed=token_data["items_done"]
509        )
510
511        # Resume the evaluation
512        result = client.run_batched_evaluation(
513            scope="traces",
514            mapper=my_mapper,
515            evaluators=[evaluator1, evaluator2],
516            resume_from=resume_token
517        )
518
519        print(f"Processed {result.total_items_processed} additional items")
520        ```
521
522        Handling partial completion:
523        ```python
524        result = client.run_batched_evaluation(...)
525
526        if not result.completed:
527            print(f"Evaluation incomplete. Processed {result.resume_token.items_processed} items")
528            print(f"Last item: {result.resume_token.last_processed_id}")
529            print(f"Resume from: {result.resume_token.last_processed_timestamp}")
530
531            # Optionally retry automatically
532            if result.resume_token:
533                print("Retrying...")
534                result = client.run_batched_evaluation(
535                    scope=result.resume_token.scope,
536                    mapper=my_mapper,
537                    evaluators=my_evaluators,
538                    resume_from=result.resume_token
539                )
540        ```
541
542    Note:
543        All arguments must be passed as keywords when instantiating this class.
544        The timestamp-based approach means that items created after the initial run
545        but before the timestamp will be skipped. This is intentional to avoid
546        duplicates and ensure consistent evaluation.
547    """
548
549    def __init__(
550        self,
551        *,
552        scope: str,
553        filter: Optional[str],
554        last_processed_timestamp: str,
555        last_processed_id: str,
556        items_processed: int,
557    ):
558        """Initialize BatchEvaluationResumeToken with the provided state.
559
560        Args:
561            scope: The scope type ("traces", "observations").
562            filter: The original JSON filter string.
563            last_processed_timestamp: ISO 8601 timestamp of last processed item.
564            last_processed_id: ID of last processed item.
565            items_processed: Count of items processed before interruption.
566
567        Note:
568            All arguments must be provided as keywords.
569        """
570        self.scope = scope
571        self.filter = filter
572        self.last_processed_timestamp = last_processed_timestamp
573        self.last_processed_id = last_processed_id
574        self.items_processed = items_processed

Token for resuming a failed batch evaluation run.

This class encapsulates all the information needed to resume a batch evaluation that was interrupted or failed partway through. It uses timestamp-based filtering to avoid re-processing items that were already evaluated, even if the underlying dataset changed between runs.

Attributes:
  • scope: The type of items being evaluated ("traces", "observations").
  • filter: The original JSON filter string used to query items.
  • last_processed_timestamp: ISO 8601 timestamp of the last successfully processed item. Used to construct a filter that only fetches items after this timestamp.
  • last_processed_id: The ID of the last successfully processed item, for reference.
  • items_processed: Count of items successfully processed before interruption.
Examples:

Resuming a failed batch evaluation:

# Initial run that fails partway through
try:
    result = client.run_batched_evaluation(
        scope="traces",
        mapper=my_mapper,
        evaluators=[evaluator1, evaluator2],
        filter='{"tags": ["production"]}',
        max_items=10000
    )
except Exception as e:
    print(f"Evaluation failed: {e}")

    # Save the resume token
    if result.resume_token:
        # Store resume token for later (e.g., in a file or database)
        import json
        with open("resume_token.json", "w") as f:
            json.dump({
                "scope": result.resume_token.scope,
                "filter": result.resume_token.filter,
                "last_timestamp": result.resume_token.last_processed_timestamp,
                "last_id": result.resume_token.last_processed_id,
                "items_done": result.resume_token.items_processed
            }, f)

# Later, resume from where it left off
with open("resume_token.json") as f:
    token_data = json.load(f)

resume_token = BatchEvaluationResumeToken(
    scope=token_data["scope"],
    filter=token_data["filter"],
    last_processed_timestamp=token_data["last_timestamp"],
    last_processed_id=token_data["last_id"],
    items_processed=token_data["items_done"]
)

# Resume the evaluation
result = client.run_batched_evaluation(
    scope="traces",
    mapper=my_mapper,
    evaluators=[evaluator1, evaluator2],
    resume_from=resume_token
)

print(f"Processed {result.total_items_processed} additional items")

Handling partial completion:

result = client.run_batched_evaluation(...)

if not result.completed:
    print(f"Evaluation incomplete. Processed {result.resume_token.items_processed} items")
    print(f"Last item: {result.resume_token.last_processed_id}")
    print(f"Resume from: {result.resume_token.last_processed_timestamp}")

    # Optionally retry automatically
    if result.resume_token:
        print("Retrying...")
        result = client.run_batched_evaluation(
            scope=result.resume_token.scope,
            mapper=my_mapper,
            evaluators=my_evaluators,
            resume_from=result.resume_token
        )
Note:

All arguments must be passed as keywords when instantiating this class. The timestamp-based approach means that items created after the initial run but before the timestamp will be skipped. This is intentional to avoid duplicates and ensure consistent evaluation.

BatchEvaluationResumeToken( *, scope: str, filter: Optional[str], last_processed_timestamp: str, last_processed_id: str, items_processed: int)
549    def __init__(
550        self,
551        *,
552        scope: str,
553        filter: Optional[str],
554        last_processed_timestamp: str,
555        last_processed_id: str,
556        items_processed: int,
557    ):
558        """Initialize BatchEvaluationResumeToken with the provided state.
559
560        Args:
561            scope: The scope type ("traces", "observations").
562            filter: The original JSON filter string.
563            last_processed_timestamp: ISO 8601 timestamp of last processed item.
564            last_processed_id: ID of last processed item.
565            items_processed: Count of items processed before interruption.
566
567        Note:
568            All arguments must be provided as keywords.
569        """
570        self.scope = scope
571        self.filter = filter
572        self.last_processed_timestamp = last_processed_timestamp
573        self.last_processed_id = last_processed_id
574        self.items_processed = items_processed

Initialize BatchEvaluationResumeToken with the provided state.

Arguments:
  • scope: The scope type ("traces", "observations").
  • filter: The original JSON filter string.
  • last_processed_timestamp: ISO 8601 timestamp of last processed item.
  • last_processed_id: ID of last processed item.
  • items_processed: Count of items processed before interruption.
Note:

All arguments must be provided as keywords.

scope
filter
last_processed_timestamp
last_processed_id
items_processed
class BatchEvaluationResult:
577class BatchEvaluationResult:
578    r"""Complete result structure for batch evaluation execution.
579
580    This class encapsulates comprehensive statistics and metadata about a batch
581    evaluation run, including counts, evaluator-specific metrics, timing information,
582    error details, and resume capability.
583
584    Attributes:
585        total_items_fetched: Total number of items fetched from the API.
586        total_items_processed: Number of items successfully evaluated.
587        total_items_failed: Number of items that failed during evaluation.
588        total_scores_created: Total scores created by all item-level evaluators.
589        total_composite_scores_created: Scores created by the composite evaluator.
590        total_evaluations_failed: Number of individual evaluator failures across all items.
591        evaluator_stats: List of per-evaluator statistics (success/failure rates, scores created).
592        resume_token: Token for resuming if evaluation was interrupted (None if completed).
593        completed: True if all items were processed, False if stopped early or failed.
594        duration_seconds: Total time taken to execute the batch evaluation.
595        failed_item_ids: List of IDs for items that failed evaluation.
596        error_summary: Dictionary mapping error types to occurrence counts.
597        has_more_items: True if max_items limit was reached but more items exist.
598        item_evaluations: Dictionary mapping item IDs to their evaluation results (both regular and composite).
599
600    Examples:
601        Basic result inspection:
602        ```python
603        result = client.run_batched_evaluation(...)
604
605        print(f"Processed: {result.total_items_processed}/{result.total_items_fetched}")
606        print(f"Scores created: {result.total_scores_created}")
607        print(f"Duration: {result.duration_seconds:.2f}s")
608        print(f"Success rate: {result.total_items_processed / result.total_items_fetched:.1%}")
609        ```
610
611        Detailed analysis with evaluator stats:
612        ```python
613        result = client.run_batched_evaluation(...)
614
615        print(f"\n📊 Batch Evaluation Results")
616        print(f"{'='*50}")
617        print(f"Items processed: {result.total_items_processed}")
618        print(f"Items failed: {result.total_items_failed}")
619        print(f"Scores created: {result.total_scores_created}")
620
621        if result.total_composite_scores_created > 0:
622            print(f"Composite scores: {result.total_composite_scores_created}")
623
624        print(f"\n📈 Evaluator Performance:")
625        for stats in result.evaluator_stats:
626            success_rate = stats.successful_runs / stats.total_runs if stats.total_runs > 0 else 0
627            print(f"\n  {stats.name}:")
628            print(f"    Success rate: {success_rate:.1%}")
629            print(f"    Scores created: {stats.total_scores_created}")
630            if stats.failed_runs > 0:
631                print(f"    ⚠️  Failures: {stats.failed_runs}")
632
633        if result.error_summary:
634            print(f"\n⚠️  Errors encountered:")
635            for error_type, count in result.error_summary.items():
636                print(f"    {error_type}: {count}")
637        ```
638
639        Handling incomplete runs:
640        ```python
641        result = client.run_batched_evaluation(...)
642
643        if not result.completed:
644            print("⚠️  Evaluation incomplete!")
645
646            if result.resume_token:
647                print(f"Processed {result.resume_token.items_processed} items before failure")
648                print(f"Use resume_from parameter to continue from:")
649                print(f"  Timestamp: {result.resume_token.last_processed_timestamp}")
650                print(f"  Last ID: {result.resume_token.last_processed_id}")
651
652        if result.has_more_items:
653            print(f"ℹ️  More items available beyond max_items limit")
654        ```
655
656        Performance monitoring:
657        ```python
658        result = client.run_batched_evaluation(...)
659
660        items_per_second = result.total_items_processed / result.duration_seconds
661        avg_scores_per_item = result.total_scores_created / result.total_items_processed
662
663        print(f"Performance metrics:")
664        print(f"  Throughput: {items_per_second:.2f} items/second")
665        print(f"  Avg scores/item: {avg_scores_per_item:.2f}")
666        print(f"  Total duration: {result.duration_seconds:.2f}s")
667
668        if result.total_evaluations_failed > 0:
669            failure_rate = result.total_evaluations_failed / (
670                result.total_items_processed * len(result.evaluator_stats)
671            )
672            print(f"  Evaluation failure rate: {failure_rate:.1%}")
673        ```
674
675    Note:
676        All arguments must be passed as keywords when instantiating this class.
677    """
678
679    def __init__(
680        self,
681        *,
682        total_items_fetched: int,
683        total_items_processed: int,
684        total_items_failed: int,
685        total_scores_created: int,
686        total_composite_scores_created: int,
687        total_evaluations_failed: int,
688        evaluator_stats: List[EvaluatorStats],
689        resume_token: Optional[BatchEvaluationResumeToken],
690        completed: bool,
691        duration_seconds: float,
692        failed_item_ids: List[str],
693        error_summary: Dict[str, int],
694        has_more_items: bool,
695        item_evaluations: Dict[str, List["Evaluation"]],
696    ):
697        """Initialize BatchEvaluationResult with comprehensive statistics.
698
699        Args:
700            total_items_fetched: Total items fetched from API.
701            total_items_processed: Items successfully evaluated.
702            total_items_failed: Items that failed evaluation.
703            total_scores_created: Scores from item-level evaluators.
704            total_composite_scores_created: Scores from composite evaluator.
705            total_evaluations_failed: Individual evaluator failures.
706            evaluator_stats: Per-evaluator statistics.
707            resume_token: Token for resuming (None if completed).
708            completed: Whether all items were processed.
709            duration_seconds: Total execution time.
710            failed_item_ids: IDs of failed items.
711            error_summary: Error types and counts.
712            has_more_items: Whether more items exist beyond max_items.
713            item_evaluations: Dictionary mapping item IDs to their evaluation results.
714
715        Note:
716            All arguments must be provided as keywords.
717        """
718        self.total_items_fetched = total_items_fetched
719        self.total_items_processed = total_items_processed
720        self.total_items_failed = total_items_failed
721        self.total_scores_created = total_scores_created
722        self.total_composite_scores_created = total_composite_scores_created
723        self.total_evaluations_failed = total_evaluations_failed
724        self.evaluator_stats = evaluator_stats
725        self.resume_token = resume_token
726        self.completed = completed
727        self.duration_seconds = duration_seconds
728        self.failed_item_ids = failed_item_ids
729        self.error_summary = error_summary
730        self.has_more_items = has_more_items
731        self.item_evaluations = item_evaluations
732
733    def __str__(self) -> str:
734        """Return a formatted string representation of the batch evaluation results.
735
736        Returns:
737            A multi-line string with a summary of the evaluation results.
738        """
739        lines = []
740        lines.append("=" * 60)
741        lines.append("Batch Evaluation Results")
742        lines.append("=" * 60)
743
744        # Summary statistics
745        lines.append(f"\nStatus: {'Completed' if self.completed else 'Incomplete'}")
746        lines.append(f"Duration: {self.duration_seconds:.2f}s")
747        lines.append(f"\nItems fetched: {self.total_items_fetched}")
748        lines.append(f"Items processed: {self.total_items_processed}")
749
750        if self.total_items_failed > 0:
751            lines.append(f"Items failed: {self.total_items_failed}")
752
753        # Success rate
754        if self.total_items_fetched > 0:
755            success_rate = self.total_items_processed / self.total_items_fetched * 100
756            lines.append(f"Success rate: {success_rate:.1f}%")
757
758        # Scores created
759        lines.append(f"\nScores created: {self.total_scores_created}")
760        if self.total_composite_scores_created > 0:
761            lines.append(f"Composite scores: {self.total_composite_scores_created}")
762
763        total_scores = self.total_scores_created + self.total_composite_scores_created
764        lines.append(f"Total scores: {total_scores}")
765
766        # Evaluator statistics
767        if self.evaluator_stats:
768            lines.append("\nEvaluator Performance:")
769            for stats in self.evaluator_stats:
770                lines.append(f"  {stats.name}:")
771                if stats.total_runs > 0:
772                    success_rate = (
773                        stats.successful_runs / stats.total_runs * 100
774                        if stats.total_runs > 0
775                        else 0
776                    )
777                    lines.append(
778                        f"    Runs: {stats.successful_runs}/{stats.total_runs} "
779                        f"({success_rate:.1f}% success)"
780                    )
781                    lines.append(f"    Scores created: {stats.total_scores_created}")
782                    if stats.failed_runs > 0:
783                        lines.append(f"    Failed runs: {stats.failed_runs}")
784
785        # Performance metrics
786        if self.total_items_processed > 0 and self.duration_seconds > 0:
787            items_per_sec = self.total_items_processed / self.duration_seconds
788            lines.append("\nPerformance:")
789            lines.append(f"  Throughput: {items_per_sec:.2f} items/second")
790            if self.total_scores_created > 0:
791                avg_scores = self.total_scores_created / self.total_items_processed
792                lines.append(f"  Avg scores per item: {avg_scores:.2f}")
793
794        # Errors and warnings
795        if self.error_summary:
796            lines.append("\nErrors encountered:")
797            for error_type, count in self.error_summary.items():
798                lines.append(f"  {error_type}: {count}")
799
800        # Incomplete run information
801        if not self.completed:
802            lines.append("\nWarning: Evaluation incomplete")
803            if self.resume_token:
804                lines.append(
805                    f"  Last processed: {self.resume_token.last_processed_timestamp}"
806                )
807                lines.append(f"  Items processed: {self.resume_token.items_processed}")
808                lines.append("  Use resume_from parameter to continue")
809
810        if self.has_more_items:
811            lines.append("\nNote: More items available beyond max_items limit")
812
813        lines.append("=" * 60)
814        return "\n".join(lines)

Complete result structure for batch evaluation execution.

This class encapsulates comprehensive statistics and metadata about a batch evaluation run, including counts, evaluator-specific metrics, timing information, error details, and resume capability.

Attributes:
  • total_items_fetched: Total number of items fetched from the API.
  • total_items_processed: Number of items successfully evaluated.
  • total_items_failed: Number of items that failed during evaluation.
  • total_scores_created: Total scores created by all item-level evaluators.
  • total_composite_scores_created: Scores created by the composite evaluator.
  • total_evaluations_failed: Number of individual evaluator failures across all items.
  • evaluator_stats: List of per-evaluator statistics (success/failure rates, scores created).
  • resume_token: Token for resuming if evaluation was interrupted (None if completed).
  • completed: True if all items were processed, False if stopped early or failed.
  • duration_seconds: Total time taken to execute the batch evaluation.
  • failed_item_ids: List of IDs for items that failed evaluation.
  • error_summary: Dictionary mapping error types to occurrence counts.
  • has_more_items: True if max_items limit was reached but more items exist.
  • item_evaluations: Dictionary mapping item IDs to their evaluation results (both regular and composite).
Examples:

Basic result inspection:

result = client.run_batched_evaluation(...)

print(f"Processed: {result.total_items_processed}/{result.total_items_fetched}")
print(f"Scores created: {result.total_scores_created}")
print(f"Duration: {result.duration_seconds:.2f}s")
print(f"Success rate: {result.total_items_processed / result.total_items_fetched:.1%}")

Detailed analysis with evaluator stats:

result = client.run_batched_evaluation(...)

print(f"\n📊 Batch Evaluation Results")
print(f"{'='*50}")
print(f"Items processed: {result.total_items_processed}")
print(f"Items failed: {result.total_items_failed}")
print(f"Scores created: {result.total_scores_created}")

if result.total_composite_scores_created > 0:
    print(f"Composite scores: {result.total_composite_scores_created}")

print(f"\n📈 Evaluator Performance:")
for stats in result.evaluator_stats:
    success_rate = stats.successful_runs / stats.total_runs if stats.total_runs > 0 else 0
    print(f"\n  {stats.name}:")
    print(f"    Success rate: {success_rate:.1%}")
    print(f"    Scores created: {stats.total_scores_created}")
    if stats.failed_runs > 0:
        print(f"    ⚠️  Failures: {stats.failed_runs}")

if result.error_summary:
    print(f"\n⚠️  Errors encountered:")
    for error_type, count in result.error_summary.items():
        print(f"    {error_type}: {count}")

Handling incomplete runs:

result = client.run_batched_evaluation(...)

if not result.completed:
    print("⚠️  Evaluation incomplete!")

    if result.resume_token:
        print(f"Processed {result.resume_token.items_processed} items before failure")
        print(f"Use resume_from parameter to continue from:")
        print(f"  Timestamp: {result.resume_token.last_processed_timestamp}")
        print(f"  Last ID: {result.resume_token.last_processed_id}")

if result.has_more_items:
    print(f"ℹ️  More items available beyond max_items limit")

Performance monitoring:

result = client.run_batched_evaluation(...)

items_per_second = result.total_items_processed / result.duration_seconds
avg_scores_per_item = result.total_scores_created / result.total_items_processed

print(f"Performance metrics:")
print(f"  Throughput: {items_per_second:.2f} items/second")
print(f"  Avg scores/item: {avg_scores_per_item:.2f}")
print(f"  Total duration: {result.duration_seconds:.2f}s")

if result.total_evaluations_failed > 0:
    failure_rate = result.total_evaluations_failed / (
        result.total_items_processed * len(result.evaluator_stats)
    )
    print(f"  Evaluation failure rate: {failure_rate:.1%}")
Note:

All arguments must be passed as keywords when instantiating this class.

BatchEvaluationResult( *, total_items_fetched: int, total_items_processed: int, total_items_failed: int, total_scores_created: int, total_composite_scores_created: int, total_evaluations_failed: int, evaluator_stats: List[EvaluatorStats], resume_token: Optional[BatchEvaluationResumeToken], completed: bool, duration_seconds: float, failed_item_ids: List[str], error_summary: Dict[str, int], has_more_items: bool, item_evaluations: Dict[str, List[Evaluation]])
679    def __init__(
680        self,
681        *,
682        total_items_fetched: int,
683        total_items_processed: int,
684        total_items_failed: int,
685        total_scores_created: int,
686        total_composite_scores_created: int,
687        total_evaluations_failed: int,
688        evaluator_stats: List[EvaluatorStats],
689        resume_token: Optional[BatchEvaluationResumeToken],
690        completed: bool,
691        duration_seconds: float,
692        failed_item_ids: List[str],
693        error_summary: Dict[str, int],
694        has_more_items: bool,
695        item_evaluations: Dict[str, List["Evaluation"]],
696    ):
697        """Initialize BatchEvaluationResult with comprehensive statistics.
698
699        Args:
700            total_items_fetched: Total items fetched from API.
701            total_items_processed: Items successfully evaluated.
702            total_items_failed: Items that failed evaluation.
703            total_scores_created: Scores from item-level evaluators.
704            total_composite_scores_created: Scores from composite evaluator.
705            total_evaluations_failed: Individual evaluator failures.
706            evaluator_stats: Per-evaluator statistics.
707            resume_token: Token for resuming (None if completed).
708            completed: Whether all items were processed.
709            duration_seconds: Total execution time.
710            failed_item_ids: IDs of failed items.
711            error_summary: Error types and counts.
712            has_more_items: Whether more items exist beyond max_items.
713            item_evaluations: Dictionary mapping item IDs to their evaluation results.
714
715        Note:
716            All arguments must be provided as keywords.
717        """
718        self.total_items_fetched = total_items_fetched
719        self.total_items_processed = total_items_processed
720        self.total_items_failed = total_items_failed
721        self.total_scores_created = total_scores_created
722        self.total_composite_scores_created = total_composite_scores_created
723        self.total_evaluations_failed = total_evaluations_failed
724        self.evaluator_stats = evaluator_stats
725        self.resume_token = resume_token
726        self.completed = completed
727        self.duration_seconds = duration_seconds
728        self.failed_item_ids = failed_item_ids
729        self.error_summary = error_summary
730        self.has_more_items = has_more_items
731        self.item_evaluations = item_evaluations

Initialize BatchEvaluationResult with comprehensive statistics.

Arguments:
  • total_items_fetched: Total items fetched from API.
  • total_items_processed: Items successfully evaluated.
  • total_items_failed: Items that failed evaluation.
  • total_scores_created: Scores from item-level evaluators.
  • total_composite_scores_created: Scores from composite evaluator.
  • total_evaluations_failed: Individual evaluator failures.
  • evaluator_stats: Per-evaluator statistics.
  • resume_token: Token for resuming (None if completed).
  • completed: Whether all items were processed.
  • duration_seconds: Total execution time.
  • failed_item_ids: IDs of failed items.
  • error_summary: Error types and counts.
  • has_more_items: Whether more items exist beyond max_items.
  • item_evaluations: Dictionary mapping item IDs to their evaluation results.
Note:

All arguments must be provided as keywords.

total_items_fetched
total_items_processed
total_items_failed
total_scores_created
total_composite_scores_created
total_evaluations_failed
evaluator_stats
resume_token
completed
duration_seconds
failed_item_ids
error_summary
has_more_items
item_evaluations
class RunnerContext:
1071class RunnerContext:
1072    """Wraps :meth:`Langfuse.run_experiment` with CI-injected defaults.
1073
1074    Intended for use with the ``langfuse/experiment-action`` GitHub Action
1075    (https://github.com/langfuse/experiment-action). The action builds a
1076    ``RunnerContext`` before invoking the user's ``experiment(context)``
1077    function. Defaults set here (dataset, metadata tags) are applied when
1078    the user omits them on the :meth:`run_experiment` call; users can
1079    override any default by passing the corresponding argument explicitly.
1080    """
1081
1082    def __init__(
1083        self,
1084        *,
1085        client: "Langfuse",
1086        data: Optional[ExperimentData] = None,
1087        dataset_version: Optional[datetime] = None,
1088        metadata: Optional[Dict[str, str]] = None,
1089    ):
1090        """Build a ``RunnerContext`` populated with defaults for ``run_experiment``.
1091
1092        Typically called by the ``langfuse/experiment-action`` GitHub Action,
1093        not by end users directly. Every field except ``client`` is optional:
1094        fields left as ``None`` simply mean the corresponding argument must be
1095        supplied on the :meth:`run_experiment` call.
1096
1097        Args:
1098            client: Initialized Langfuse SDK client used to execute the
1099                experiment. The action creates this from the
1100                ``langfuse_public_key`` / ``langfuse_secret_key`` /
1101                ``langfuse_base_url`` inputs.
1102            data: Default dataset items to run the experiment on. Accepts
1103                either ``List[LocalExperimentItem]`` or ``List[DatasetItem]``.
1104                Injected by the action when ``dataset_name`` is configured.
1105                If ``None``, the user must pass ``data=`` to
1106                :meth:`run_experiment`.
1107            dataset_version: Optional pinned dataset version. Injected by the
1108                action when ``dataset_version`` is configured.
1109            metadata: Default metadata attached to every experiment trace and
1110                the dataset run. The action injects GitHub-sourced tags (SHA,
1111                PR link, workflow run link, branch, GH user, etc.). Merged
1112                with any ``metadata`` passed to :meth:`run_experiment`, with
1113                user-supplied keys winning on collision.
1114        """
1115        self.client = client
1116        self.data = data
1117        self.dataset_version = dataset_version
1118        self.metadata = metadata
1119
1120    def run_experiment(
1121        self,
1122        *,
1123        name: str,
1124        run_name: Optional[str] = None,
1125        description: Optional[str] = None,
1126        data: Optional[ExperimentData] = None,
1127        task: TaskFunction,
1128        evaluators: List[EvaluatorFunction] = [],
1129        composite_evaluator: Optional["CompositeEvaluatorFunction"] = None,
1130        run_evaluators: List[RunEvaluatorFunction] = [],
1131        max_concurrency: int = 50,
1132        metadata: Optional[Dict[str, str]] = None,
1133        _dataset_version: Optional[datetime] = None,
1134    ) -> ExperimentResult:
1135        resolved_data = data if data is not None else self.data
1136        if resolved_data is None:
1137            raise ValueError(
1138                "`data` must be provided either on the RunnerContext or the run_experiment call"
1139            )
1140
1141        resolved_dataset_version = (
1142            _dataset_version if _dataset_version is not None else self.dataset_version
1143        )
1144
1145        merged_metadata: Optional[Dict[str, str]]
1146        if self.metadata is None and metadata is None:
1147            merged_metadata = None
1148        else:
1149            merged_metadata = {**(self.metadata or {}), **(metadata or {})}
1150
1151        return self.client.run_experiment(
1152            name=name,
1153            run_name=run_name,
1154            description=description,
1155            data=resolved_data,
1156            task=task,
1157            evaluators=evaluators,
1158            composite_evaluator=composite_evaluator,
1159            run_evaluators=run_evaluators,
1160            max_concurrency=max_concurrency,
1161            metadata=merged_metadata,
1162            _dataset_version=resolved_dataset_version,
1163        )

Wraps Langfuse.run_experiment() with CI-injected defaults.

Intended for use with the langfuse/experiment-action GitHub Action (https://github.com/langfuse/experiment-action). The action builds a RunnerContext before invoking the user's experiment(context) function. Defaults set here (dataset, metadata tags) are applied when the user omits them on the run_experiment() call; users can override any default by passing the corresponding argument explicitly.

RunnerContext( *, client: Langfuse, data: Union[List[langfuse.experiment.LocalExperimentItem], List[langfuse.api.DatasetItem], NoneType] = None, dataset_version: Optional[datetime.datetime] = None, metadata: Optional[Dict[str, str]] = None)
1082    def __init__(
1083        self,
1084        *,
1085        client: "Langfuse",
1086        data: Optional[ExperimentData] = None,
1087        dataset_version: Optional[datetime] = None,
1088        metadata: Optional[Dict[str, str]] = None,
1089    ):
1090        """Build a ``RunnerContext`` populated with defaults for ``run_experiment``.
1091
1092        Typically called by the ``langfuse/experiment-action`` GitHub Action,
1093        not by end users directly. Every field except ``client`` is optional:
1094        fields left as ``None`` simply mean the corresponding argument must be
1095        supplied on the :meth:`run_experiment` call.
1096
1097        Args:
1098            client: Initialized Langfuse SDK client used to execute the
1099                experiment. The action creates this from the
1100                ``langfuse_public_key`` / ``langfuse_secret_key`` /
1101                ``langfuse_base_url`` inputs.
1102            data: Default dataset items to run the experiment on. Accepts
1103                either ``List[LocalExperimentItem]`` or ``List[DatasetItem]``.
1104                Injected by the action when ``dataset_name`` is configured.
1105                If ``None``, the user must pass ``data=`` to
1106                :meth:`run_experiment`.
1107            dataset_version: Optional pinned dataset version. Injected by the
1108                action when ``dataset_version`` is configured.
1109            metadata: Default metadata attached to every experiment trace and
1110                the dataset run. The action injects GitHub-sourced tags (SHA,
1111                PR link, workflow run link, branch, GH user, etc.). Merged
1112                with any ``metadata`` passed to :meth:`run_experiment`, with
1113                user-supplied keys winning on collision.
1114        """
1115        self.client = client
1116        self.data = data
1117        self.dataset_version = dataset_version
1118        self.metadata = metadata

Build a RunnerContext populated with defaults for run_experiment.

Typically called by the langfuse/experiment-action GitHub Action, not by end users directly. Every field except client is optional: fields left as None simply mean the corresponding argument must be supplied on the run_experiment() call.

Arguments:
  • client: Initialized Langfuse SDK client used to execute the experiment. The action creates this from the langfuse_public_key / langfuse_secret_key / langfuse_base_url inputs.
  • data: Default dataset items to run the experiment on. Accepts either List[LocalExperimentItem] or List[DatasetItem]. Injected by the action when dataset_name is configured. If None, the user must pass data= to run_experiment().
  • dataset_version: Optional pinned dataset version. Injected by the action when dataset_version is configured.
  • metadata: Default metadata attached to every experiment trace and the dataset run. The action injects GitHub-sourced tags (SHA, PR link, workflow run link, branch, GH user, etc.). Merged with any metadata passed to run_experiment(), with user-supplied keys winning on collision.
client
data
dataset_version
metadata
def run_experiment( self, *, name: str, run_name: Optional[str] = None, description: Optional[str] = None, data: Union[List[langfuse.experiment.LocalExperimentItem], List[langfuse.api.DatasetItem], NoneType] = None, task: langfuse.experiment.TaskFunction, evaluators: List[langfuse.experiment.EvaluatorFunction] = [], composite_evaluator: Optional[CompositeEvaluatorFunction] = None, run_evaluators: List[langfuse.experiment.RunEvaluatorFunction] = [], max_concurrency: int = 50, metadata: Optional[Dict[str, str]] = None, _dataset_version: Optional[datetime.datetime] = None) -> langfuse.experiment.ExperimentResult:
1120    def run_experiment(
1121        self,
1122        *,
1123        name: str,
1124        run_name: Optional[str] = None,
1125        description: Optional[str] = None,
1126        data: Optional[ExperimentData] = None,
1127        task: TaskFunction,
1128        evaluators: List[EvaluatorFunction] = [],
1129        composite_evaluator: Optional["CompositeEvaluatorFunction"] = None,
1130        run_evaluators: List[RunEvaluatorFunction] = [],
1131        max_concurrency: int = 50,
1132        metadata: Optional[Dict[str, str]] = None,
1133        _dataset_version: Optional[datetime] = None,
1134    ) -> ExperimentResult:
1135        resolved_data = data if data is not None else self.data
1136        if resolved_data is None:
1137            raise ValueError(
1138                "`data` must be provided either on the RunnerContext or the run_experiment call"
1139            )
1140
1141        resolved_dataset_version = (
1142            _dataset_version if _dataset_version is not None else self.dataset_version
1143        )
1144
1145        merged_metadata: Optional[Dict[str, str]]
1146        if self.metadata is None and metadata is None:
1147            merged_metadata = None
1148        else:
1149            merged_metadata = {**(self.metadata or {}), **(metadata or {})}
1150
1151        return self.client.run_experiment(
1152            name=name,
1153            run_name=run_name,
1154            description=description,
1155            data=resolved_data,
1156            task=task,
1157            evaluators=evaluators,
1158            composite_evaluator=composite_evaluator,
1159            run_evaluators=run_evaluators,
1160            max_concurrency=max_concurrency,
1161            metadata=merged_metadata,
1162            _dataset_version=resolved_dataset_version,
1163        )
class RegressionError(builtins.Exception):
1166class RegressionError(Exception):
1167    """Raised by a user's ``experiment`` function to signal a CI gate failure.
1168
1169    Intended for use with the ``langfuse/experiment-action`` GitHub Action
1170    (https://github.com/langfuse/experiment-action). The action catches this
1171    exception and, when ``should_fail_on_error`` is enabled, fails the
1172    workflow run and renders a callout in the PR comment using
1173    ``metric``/``value``/``threshold`` if supplied, otherwise ``str(exc)``.
1174
1175    Callers choose one of three forms:
1176
1177    - ``RegressionError(result=r)`` — minimal, generic message.
1178    - ``RegressionError(result=r, message="...")`` — free-form message.
1179    - ``RegressionError(result=r, metric="acc", value=0.7, threshold=0.9)`` —
1180      structured; ``metric`` and ``value`` must be provided together so the
1181      action can render a targeted callout without ``None`` placeholders.
1182    """
1183
1184    @overload
1185    def __init__(self, *, result: ExperimentResult) -> None: ...
1186    @overload
1187    def __init__(self, *, result: ExperimentResult, message: str) -> None: ...
1188    @overload
1189    def __init__(
1190        self,
1191        *,
1192        result: ExperimentResult,
1193        metric: str,
1194        value: float,
1195        threshold: Optional[float] = None,
1196        message: Optional[str] = None,
1197    ) -> None: ...
1198    def __init__(
1199        self,
1200        *,
1201        result: ExperimentResult,
1202        metric: Optional[str] = None,
1203        value: Optional[float] = None,
1204        threshold: Optional[float] = None,
1205        message: Optional[str] = None,
1206    ):
1207        self.result = result
1208        self.metric = metric
1209        self.value = value
1210        self.threshold = threshold
1211        if message is not None:
1212            formatted = message
1213        elif metric is not None and value is not None:
1214            formatted = f"Regression on `{metric}`: {value} (threshold {threshold})"
1215        else:
1216            formatted = "Experiment regression detected"
1217        super().__init__(formatted)

Raised by a user's experiment function to signal a CI gate failure.

Intended for use with the langfuse/experiment-action GitHub Action (https://github.com/langfuse/experiment-action). The action catches this exception and, when should_fail_on_error is enabled, fails the workflow run and renders a callout in the PR comment using metric/value/threshold if supplied, otherwise str(exc).

Callers choose one of three forms:

  • RegressionError(result=r) — minimal, generic message.
  • RegressionError(result=r, message="...") — free-form message.
  • RegressionError(result=r, metric="acc", value=0.7, threshold=0.9) — structured; metric and value must be provided together so the action can render a targeted callout without None placeholders.
RegressionError( *, result: langfuse.experiment.ExperimentResult, metric: Optional[str] = None, value: Optional[float] = None, threshold: Optional[float] = None, message: Optional[str] = None)
1198    def __init__(
1199        self,
1200        *,
1201        result: ExperimentResult,
1202        metric: Optional[str] = None,
1203        value: Optional[float] = None,
1204        threshold: Optional[float] = None,
1205        message: Optional[str] = None,
1206    ):
1207        self.result = result
1208        self.metric = metric
1209        self.value = value
1210        self.threshold = threshold
1211        if message is not None:
1212            formatted = message
1213        elif metric is not None and value is not None:
1214            formatted = f"Regression on `{metric}`: {value} (threshold {threshold})"
1215        else:
1216            formatted = "Experiment regression detected"
1217        super().__init__(formatted)
result
metric
value
threshold
__version__ = '4.15.1'
def is_default_export_span(span: opentelemetry.sdk.trace.ReadableSpan) -> bool:
105def is_default_export_span(span: ReadableSpan) -> bool:
106    """Return whether a span should be exported by default."""
107    return (
108        is_langfuse_span(span) or is_genai_span(span) or is_known_llm_instrumentor(span)
109    )

Return whether a span should be exported by default.

def is_langfuse_span(span: opentelemetry.sdk.trace.ReadableSpan) -> bool:
68def is_langfuse_span(span: ReadableSpan) -> bool:
69    """Return whether the span was created by the Langfuse SDK tracer."""
70    return (
71        span.instrumentation_scope is not None
72        and span.instrumentation_scope.name == LANGFUSE_TRACER_NAME
73    )

Return whether the span was created by the Langfuse SDK tracer.

def is_genai_span(span: opentelemetry.sdk.trace.ReadableSpan) -> bool:
76def is_genai_span(span: ReadableSpan) -> bool:
77    """Return whether the span has any ``gen_ai.*`` semantic convention attribute."""
78    if span.attributes is None:
79        return False
80
81    return any(
82        isinstance(key, str) and key.startswith("gen_ai")
83        for key in span.attributes.keys()
84    )

Return whether the span has any gen_ai.* semantic convention attribute.

def is_known_llm_instrumentor(span: opentelemetry.sdk.trace.ReadableSpan) -> bool:
 92def is_known_llm_instrumentor(span: ReadableSpan) -> bool:
 93    """Return whether the span comes from a known LLM instrumentation scope."""
 94    if span.instrumentation_scope is None:
 95        return False
 96
 97    scope_name = span.instrumentation_scope.name
 98
 99    return any(
100        _matches_scope_prefix(scope_name, prefix)
101        for prefix in KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES
102    )

Return whether the span comes from a known LLM instrumentation scope.

KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = frozenset({'opentelemetry.instrumentation.bedrock', 'langsmith', 'opentelemetry.instrumentation.alephalpha', 'opentelemetry.instrumentation.openai_agents', 'opentelemetry.instrumentation.haystack', 'opentelemetry.instrumentation.llamaindex', 'opentelemetry.instrumentation.together', 'opentelemetry.instrumentation.google_generativeai', 'vllm', 'opentelemetry.instrumentation.anthropic', 'litellm', 'opentelemetry.instrumentation.replicate', 'autogen-core', 'opentelemetry.instrumentation.mistralai', 'opentelemetry.instrumentation.cohere', 'agent_framework', 'pydantic-ai', 'langfuse-sdk', 'opentelemetry.instrumentation.groq', 'opentelemetry.instrumentation.writer', 'opentelemetry.instrumentation.crewai', 'opentelemetry.instrumentation.langchain', 'openinference', 'ai', 'opentelemetry.instrumentation.openai_v2', 'opentelemetry.instrumentation.ollama', 'opentelemetry.instrumentation.sagemaker', 'opentelemetry.instrumentation.openai', 'opentelemetry.instrumentation.agno', 'haystack', 'opentelemetry.instrumentation.voyageai', 'opentelemetry.instrumentation.vertexai', 'opentelemetry.instrumentation.transformers', 'opentelemetry.instrumentation.watsonx', 'strands-agents'})
class MaskOtelSpansFunction(typing.Protocol):
226class MaskOtelSpansFunction(Protocol):
227    """Function protocol for export-stage OpenTelemetry span masking.
228
229    `mask_otel_spans` runs after Langfuse decides which spans this client should
230    export and after export-stage media handling has converted supported media
231    payloads into Langfuse media references. It affects only the spans exported
232    by this Langfuse client. If the same OpenTelemetry spans are sent to another
233    exporter, that exporter receives its own unmodified copy.
234
235    The function is synchronous. It usually runs on the OpenTelemetry batch span
236    processor worker thread; during `flush()` and shutdown it may run on the
237    caller thread. Keep it deterministic and fast, and avoid relying on request
238    locals, the current active span, or async I/O.
239
240    Return `None` to leave the whole batch unchanged, or return
241    `MaskOtelSpansResult` with sparse patches for the spans that should change.
242
243    Example:
244        ```python
245        from typing import Optional
246
247        from langfuse import Langfuse
248        from langfuse.types import (
249            MaskOtelSpansParams,
250            MaskOtelSpansResult,
251            OtelSpanPatch,
252        )
253
254        def mask_otel_spans(
255            *, params: MaskOtelSpansParams
256        ) -> Optional[MaskOtelSpansResult]:
257            patches = {}
258
259            for identifier, span in params.spans.items():
260                if span.instrumentation_scope_name == "openai":
261                    patches[identifier] = OtelSpanPatch(
262                        delete_attributes=(
263                            "gen_ai.prompt.0.content",
264                            "gen_ai.completion.0.content",
265                        ),
266                        set_attributes={"masking.applied": True},
267                    )
268
269            return MaskOtelSpansResult(span_patches=patches)
270
271        langfuse = Langfuse(mask_otel_spans=mask_otel_spans)
272        ```
273    """
274
275    def __call__(
276        self, *, params: MaskOtelSpansParams
277    ) -> Optional[MaskOtelSpansResult]: ...

Function protocol for export-stage OpenTelemetry span masking.

mask_otel_spans runs after Langfuse decides which spans this client should export and after export-stage media handling has converted supported media payloads into Langfuse media references. It affects only the spans exported by this Langfuse client. If the same OpenTelemetry spans are sent to another exporter, that exporter receives its own unmodified copy.

The function is synchronous. It usually runs on the OpenTelemetry batch span processor worker thread; during flush() and shutdown it may run on the caller thread. Keep it deterministic and fast, and avoid relying on request locals, the current active span, or async I/O.

Return None to leave the whole batch unchanged, or return MaskOtelSpansResult with sparse patches for the spans that should change.

Example:
from typing import Optional

from langfuse import Langfuse
from langfuse.types import (
    MaskOtelSpansParams,
    MaskOtelSpansResult,
    OtelSpanPatch,
)

def mask_otel_spans(
    *, params: MaskOtelSpansParams
) -> Optional[MaskOtelSpansResult]:
    patches = {}

    for identifier, span in params.spans.items():
        if span.instrumentation_scope_name == "openai":
            patches[identifier] = OtelSpanPatch(
                delete_attributes=(
                    "gen_ai.prompt.0.content",
                    "gen_ai.completion.0.content",
                ),
                set_attributes={"masking.applied": True},
            )

    return MaskOtelSpansResult(span_patches=patches)

langfuse = Langfuse(mask_otel_spans=mask_otel_spans)
MaskOtelSpansFunction(*args, **kwargs)
1927def _no_init_or_replace_init(self, *args, **kwargs):
1928    cls = type(self)
1929
1930    if cls._is_protocol:
1931        raise TypeError('Protocols cannot be instantiated')
1932
1933    # Already using a custom `__init__`. No need to calculate correct
1934    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1935    if cls.__init__ is not _no_init_or_replace_init:
1936        return
1937
1938    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1939    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1940    # searches for a proper new `__init__` in the MRO. The new `__init__`
1941    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1942    # instantiation of the protocol subclass will thus use the new
1943    # `__init__` and no longer call `_no_init_or_replace_init`.
1944    for base in cls.__mro__:
1945        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1946        if init is not _no_init_or_replace_init:
1947            cls.__init__ = init
1948            break
1949    else:
1950        # should not happen
1951        cls.__init__ = object.__init__
1952
1953    cls.__init__(self, *args, **kwargs)
@dataclass(frozen=True)
class MaskOtelSpansParams:
123@dataclass(frozen=True)
124class MaskOtelSpansParams:
125    """Input passed to an export-stage OpenTelemetry span masking function.
126
127    A single call receives one OpenTelemetry export batch, not necessarily a
128    complete trace, request, or Langfuse observation tree. Batch contents depend
129    on OpenTelemetry span processor settings such as `flush_at`,
130    `flush_interval`, explicit `flush()`, and shutdown. If a batch contains
131    duplicate trace and span identifiers, Langfuse keeps only the last matching
132    span before calling the masking function.
133
134    Example:
135        ```python
136        from typing import Optional
137
138        from langfuse.types import (
139            MaskOtelSpansParams,
140            MaskOtelSpansResult,
141            OtelSpanPatch,
142        )
143
144        def mask_otel_spans(
145            *, params: MaskOtelSpansParams
146        ) -> Optional[MaskOtelSpansResult]:
147            patches = {}
148
149            for identifier, span in params.spans.items():
150                if "http.request.header.authorization" in span.attributes:
151                    patches[identifier] = OtelSpanPatch(
152                        delete_attributes=("http.request.header.authorization",),
153                        set_attributes={"security.redacted": True},
154                    )
155
156            return MaskOtelSpansResult(span_patches=patches)
157        ```
158
159    Attributes:
160        spans: Read-only mapping from stable span identifiers to span snapshots.
161            Return patches using keys from this mapping.
162    """
163
164    spans: Mapping[OtelSpanIdentifier, OtelSpanData]

Input passed to an export-stage OpenTelemetry span masking function.

A single call receives one OpenTelemetry export batch, not necessarily a complete trace, request, or Langfuse observation tree. Batch contents depend on OpenTelemetry span processor settings such as flush_at, flush_interval, explicit flush(), and shutdown. If a batch contains duplicate trace and span identifiers, Langfuse keeps only the last matching span before calling the masking function.

Example:
from typing import Optional

from langfuse.types import (
    MaskOtelSpansParams,
    MaskOtelSpansResult,
    OtelSpanPatch,
)

def mask_otel_spans(
    *, params: MaskOtelSpansParams
) -> Optional[MaskOtelSpansResult]:
    patches = {}

    for identifier, span in params.spans.items():
        if "http.request.header.authorization" in span.attributes:
            patches[identifier] = OtelSpanPatch(
                delete_attributes=("http.request.header.authorization",),
                set_attributes={"security.redacted": True},
            )

    return MaskOtelSpansResult(span_patches=patches)
Attributes:
  • spans: Read-only mapping from stable span identifiers to span snapshots. Return patches using keys from this mapping.
MaskOtelSpansParams( spans: Mapping[OtelSpanIdentifier, OtelSpanData])
spans: Mapping[OtelSpanIdentifier, OtelSpanData]
@dataclass(frozen=True)
class MaskOtelSpansResult:
202@dataclass(frozen=True)
203class MaskOtelSpansResult:
204    """Patches returned by a `mask_otel_spans` function.
205
206    Omit spans that do not need changes. A mapping value of `None` also leaves
207    that span unchanged. Returning an invalid patch to drop a span is not a
208    supported API; use `should_export_span` when you need span-level export
209    filtering.
210
211    If `mask_otel_spans` raises or returns an object that is not a
212    `MaskOtelSpansResult`, Langfuse drops the whole export batch. If one
213    individual `OtelSpanPatch` is invalid, Langfuse drops only that span from
214    the export batch.
215
216    Attributes:
217        span_patches: Mapping from identifiers in `MaskOtelSpansParams.spans` to
218            sparse attribute patches.
219    """
220
221    span_patches: Mapping[OtelSpanIdentifier, Optional[OtelSpanPatch]] = field(
222        default_factory=lambda: MappingProxyType({})
223    )

Patches returned by a mask_otel_spans function.

Omit spans that do not need changes. A mapping value of None also leaves that span unchanged. Returning an invalid patch to drop a span is not a supported API; use should_export_span when you need span-level export filtering.

If mask_otel_spans raises or returns an object that is not a MaskOtelSpansResult, Langfuse drops the whole export batch. If one individual OtelSpanPatch is invalid, Langfuse drops only that span from the export batch.

Attributes:
MaskOtelSpansResult( span_patches: Mapping[OtelSpanIdentifier, Optional[OtelSpanPatch]] = <factory>)
span_patches: Mapping[OtelSpanIdentifier, Optional[OtelSpanPatch]]
@dataclass(frozen=True)
class OtelSpanData:
 82@dataclass(frozen=True)
 83class OtelSpanData:
 84    """Read-only OpenTelemetry span snapshot passed to `mask_otel_spans`.
 85
 86    The snapshot contains the span data that Langfuse is about to export after
 87    the SDK has applied `should_export_span` filtering and export-stage media
 88    processing. The mappings are immutable views and mutating them is not
 89    supported; return an `OtelSpanPatch` to change exported attributes.
 90
 91    `mask_otel_spans` can only change span attributes. It cannot change the
 92    span name, IDs, parent relationship, resource attributes, events, links, or
 93    instrumentation scope.
 94
 95    Attributes:
 96        trace_id: Lowercase 32-character hexadecimal OpenTelemetry trace ID.
 97        span_id: Lowercase 16-character hexadecimal OpenTelemetry span ID.
 98        parent_span_id: Lowercase hexadecimal parent span ID, or `None` for a
 99            root span or when the parent is not available.
100        name: OpenTelemetry span name.
101        instrumentation_scope_name: Name of the instrumentation scope that
102            emitted the span, for example `openai` or `langfuse`.
103        instrumentation_scope_version: Version of the instrumentation scope, if
104            the instrumentation library provided one.
105        attributes: Read-only attributes that will be exported unless patched.
106            Values use OpenTelemetry `AttributeValue` types: strings, booleans,
107            numbers, or homogeneous sequences of those scalar values.
108        resource_attributes: Read-only resource attributes from the span's
109            OpenTelemetry resource. These are available for decisions only and
110            cannot be patched through `mask_otel_spans`.
111    """
112
113    trace_id: str
114    span_id: str
115    parent_span_id: Optional[str]
116    name: str
117    instrumentation_scope_name: Optional[str]
118    instrumentation_scope_version: Optional[str]
119    attributes: Mapping[str, AttributeValue]
120    resource_attributes: Mapping[str, AttributeValue]

Read-only OpenTelemetry span snapshot passed to mask_otel_spans.

The snapshot contains the span data that Langfuse is about to export after the SDK has applied should_export_span filtering and export-stage media processing. The mappings are immutable views and mutating them is not supported; return an OtelSpanPatch to change exported attributes.

mask_otel_spans can only change span attributes. It cannot change the span name, IDs, parent relationship, resource attributes, events, links, or instrumentation scope.

Attributes:
  • trace_id: Lowercase 32-character hexadecimal OpenTelemetry trace ID.
  • span_id: Lowercase 16-character hexadecimal OpenTelemetry span ID.
  • parent_span_id: Lowercase hexadecimal parent span ID, or None for a root span or when the parent is not available.
  • name: OpenTelemetry span name.
  • instrumentation_scope_name: Name of the instrumentation scope that emitted the span, for example openai or langfuse.
  • instrumentation_scope_version: Version of the instrumentation scope, if the instrumentation library provided one.
  • attributes: Read-only attributes that will be exported unless patched. Values use OpenTelemetry AttributeValue types: strings, booleans, numbers, or homogeneous sequences of those scalar values.
  • resource_attributes: Read-only resource attributes from the span's OpenTelemetry resource. These are available for decisions only and cannot be patched through mask_otel_spans.
OtelSpanData( trace_id: str, span_id: str, parent_span_id: Optional[str], name: str, instrumentation_scope_name: Optional[str], instrumentation_scope_version: Optional[str], attributes: Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]], resource_attributes: Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]])
trace_id: str
span_id: str
parent_span_id: Optional[str]
name: str
instrumentation_scope_name: Optional[str]
instrumentation_scope_version: Optional[str]
attributes: Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]]
resource_attributes: Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]]
@dataclass(frozen=True)
class OtelSpanIdentifier:
65@dataclass(frozen=True)
66class OtelSpanIdentifier:
67    """Stable key for one OpenTelemetry span in a masking batch.
68
69    Use this object as the key when returning a patch for a span. It is a
70    frozen, hashable dataclass, so the safest pattern is to reuse the exact
71    identifier object from `MaskOtelSpansParams.spans` instead of rebuilding it.
72
73    Attributes:
74        trace_id: Lowercase 32-character hexadecimal OpenTelemetry trace ID.
75        span_id: Lowercase 16-character hexadecimal OpenTelemetry span ID.
76    """
77
78    trace_id: str
79    span_id: str

Stable key for one OpenTelemetry span in a masking batch.

Use this object as the key when returning a patch for a span. It is a frozen, hashable dataclass, so the safest pattern is to reuse the exact identifier object from MaskOtelSpansParams.spans instead of rebuilding it.

Attributes:
  • trace_id: Lowercase 32-character hexadecimal OpenTelemetry trace ID.
  • span_id: Lowercase 16-character hexadecimal OpenTelemetry span ID.
OtelSpanIdentifier(trace_id: str, span_id: str)
trace_id: str
span_id: str
@dataclass(frozen=True)
class OtelSpanPatch:
167@dataclass(frozen=True)
168class OtelSpanPatch:
169    """Attribute changes to apply to one OpenTelemetry span before export.
170
171    Patches are sparse: include only the attributes that should change. Langfuse
172    deletes `delete_attributes` first and then applies `set_attributes`, so a key
173    present in both fields is exported with the value from `set_attributes`.
174
175    Attribute values must be valid OpenTelemetry attributes: strings, booleans,
176    integers, floats, or homogeneous sequences of those scalar types. If one
177    value is not valid for OpenTelemetry, Langfuse removes that attribute from
178    the export rather than sending an invalid span.
179
180    Example:
181        ```python
182        OtelSpanPatch(
183            delete_attributes=("gen_ai.prompt.0.content",),
184            set_attributes={
185                "gen_ai.prompt.redacted": True,
186                "app.masking.rule": "drop_prompt_text",
187            },
188        )
189        ```
190
191    Attributes:
192        set_attributes: Attribute values to add or replace on the exported span.
193        delete_attributes: Attribute keys to remove from the exported span.
194    """
195
196    set_attributes: Mapping[str, AttributeValue] = field(
197        default_factory=lambda: MappingProxyType({})
198    )
199    delete_attributes: Sequence[str] = field(default_factory=tuple)

Attribute changes to apply to one OpenTelemetry span before export.

Patches are sparse: include only the attributes that should change. Langfuse deletes delete_attributes first and then applies set_attributes, so a key present in both fields is exported with the value from set_attributes.

Attribute values must be valid OpenTelemetry attributes: strings, booleans, integers, floats, or homogeneous sequences of those scalar types. If one value is not valid for OpenTelemetry, Langfuse removes that attribute from the export rather than sending an invalid span.

Example:
OtelSpanPatch(
    delete_attributes=("gen_ai.prompt.0.content",),
    set_attributes={
        "gen_ai.prompt.redacted": True,
        "app.masking.rule": "drop_prompt_text",
    },
)
Attributes:
  • set_attributes: Attribute values to add or replace on the exported span.
  • delete_attributes: Attribute keys to remove from the exported span.
OtelSpanPatch( set_attributes: Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]] = <factory>, delete_attributes: Sequence[str] = <factory>)
set_attributes: Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]]
delete_attributes: Sequence[str]