Langfuse v4: up to 165× faster · Read more
DocsPython v4 → v5

Python v4 → v5

For most users, v5 is a small upgrade. If you trace with @observe, start_as_current_observation(), or an integration, and you create scores and fetch prompts, your code keeps working as is. v5 needs Python 3.11+ and Langfuse Cloud or self-hosted Langfuse v4.59.0 or later.

Most removals in v5 were already deprecated in v4. If your code runs on v4 without a DeprecationWarning from langfuse, you're most likely unaffected apart from the minimum versions, direct api.scores calls, and run_batched_evaluation(). Python hides DeprecationWarning by default outside __main__, so check with python -W default::DeprecationWarning or under pytest.

pip install --upgrade "langfuse>=5"

host and the LANGFUSE_HOST environment variable keep working as before.

Are you affected?

You only need to change something if one of these applies to you.

New in v5:

Previously deprecated in v4, now removed:

If none of these apply, upgrade and you're done.

Breaking changes

These are new in v5. Ordered by how many users they affect.

Minimum versions

Affects: self-hosted Langfuse v3 or older than v4.59.0, Python 3.10, and the OpenAI and LangChain integrations on older libraries.

Requirementv5If not met
LangfuseCloud, or self-hosted v4.59.0 or laterUnsupported. Upgrade Langfuse first, or stay on the previous SDK major
Python>=3.11pip install langfuse resolves to the latest v4 release
OpenAIopenai>=1.0The langfuse.openai integration no longer supports pre-1.0 SDKs
LangChainlangchain-core>=1ModuleNotFoundError when importing langfuse.langchain with an older langchain-core

If you self-host Langfuse v3, stay on Python SDK v4. See the self-hosted compatibility matrix.

pip install --upgrade "langfuse>=5" "langchain-core>=1"

The LangChain CallbackHandler imports only from langchain_core now, so the langchain meta-package is no longer needed. Its import path and usage are unchanged.

api.scores takes request objects

Affects: code that calls langfuse.api.scores.create(...) or langfuse.api.legacy.score_v1.* directly. create_score(), span.score(), and span.score_trace() are unchanged.

Breaks: TypeError for the old api.scores.create keyword arguments, and AttributeError for api.legacy.score_v1.

  • api.scores.create() now takes a list of CreateScoreRequest objects as request=, and returns CreateScoresResponse: CreateScoreBatchResponse when all scores are accepted, or CreateScoreBatchResults with accepted/rejected counts and errors.
  • api.legacy.score_v1.delete(score_id=...) moves to api.scores.delete(score_id=...).
  • api.legacy.score_v1.create(...) was already a deprecated alias of api.scores.create in v4. Use api.scores.create(request=[CreateScoreRequest(...)]).

v4:

langfuse.api.scores.create(name="accuracy", value=0.9, trace_id="trace-id")
langfuse.api.legacy.score_v1.delete(score_id="score-id")

v5:

from langfuse import get_client
from langfuse.api import CreateScoreRequest

langfuse = get_client()

langfuse.api.scores.create(
    request=[CreateScoreRequest(name="accuracy", value=0.9, trace_id="trace-id")]
)
langfuse.api.scores.delete(score_id="score-id")

Score reads move to api.scores.get_many

Affects: code that calls langfuse.api.scores_v3.get_many_v3(...) or langfuse.async_api.scores_v3.get_many_v3(...).

Breaks: AttributeError, because api.scores_v3 no longer exists.

Scores API v3 reads moved into the scores namespace, next to create and delete. The arguments and the GetScoresV3Response response are unchanged. The v4 api.scores.get_many(score_ids=...) read Scores API v2; in v5, the same method name reads Scores API v3 and filters by id= instead of score_ids=.

v4:

scores = langfuse.api.scores_v3.get_many_v3(trace_id="trace-id", fields="subject")

v5:

from langfuse import get_client

langfuse = get_client()

scores = langfuse.api.scores.get_many(trace_id="trace-id", fields="subject")

Batched evaluation reads observations only

Affects: code that calls langfuse.run_batched_evaluation().

Breaks (new in v5, no prior deprecation): TypeError for scope, fetch_trace_fields, and _additional_trace_tags, and ValueError when resuming with a v4 token or with a different filter. The function now reads from the v2 observations API and evaluates observations only.

  • scope is removed. To evaluate whole traces, filter for the observations that represent them, for example by name or type.
  • fetch_trace_fields is renamed to fields (default "core,basic,io,metadata"). Available field groups: core, basic, time, io, metadata, model, usage, prompt, metrics, trace_context.
  • The mapper receives item=ObservationV2. input and output are raw strings.
  • filter must be a v2 filter JSON array.
  • _additional_trace_tags is removed.
  • Resume tokens from v4 can't be reused, and resuming with a different filter raises a ValueError.

v4:

def trace_mapper(*, item):
    return EvaluatorInputs(input=item.input, output=item.output, expected_output=None)

langfuse.run_batched_evaluation(
    scope="traces",
    mapper=trace_mapper,
    filter='{"tags": ["production"]}',
    fetch_trace_fields="core,io",
    evaluators=[accuracy],
)

v5:

from langfuse import EvaluatorInputs, get_client

langfuse = get_client()

def observation_mapper(*, item, **kwargs):
    return EvaluatorInputs(
        input=item.input,
        output=item.output,
        expected_output=None,
        metadata={"trace_id": item.trace_id},
    )

langfuse.run_batched_evaluation(
    mapper=observation_mapper,
    filter='[{"type": "string", "column": "type", "operator": "=", "value": "GENERATION"}]',
    fields="core,basic,io,model",
    evaluators=[accuracy],
)

Previously deprecated, now removed

v4 already marked each of these as deprecated and named the replacement. If you moved to the replacement, nothing changes.

Deprecated Public API methods removed

Affects: code that calls the endpoints below through langfuse.api.*.

Previously deprecated in v4, now removed. Each of these methods raised a DeprecationWarning in v4 that named its replacement. In v5 they raise AttributeError, and there is no api.legacy namespace anymore. For full replacement guidance, see the SDK method quick reference.

RemovedReplacement
api.trace.get(...), api.trace.list(...)api.observations.get_many(trace_id=..., ...)
api.sessions.*api.observations.get_many(session_id=..., ...)
api.legacy.observations_v1.*api.observations.get_many(...)
api.legacy.metrics_v1.metrics(...)api.metrics.metrics(...)
api.scores.get_many(...) (v2), api.scores.get_by_id(...)api.scores.get_many(...) (Scores API v3)
api.dataset_run_items.*api.experiments.list_items(...) or the experiment runner
api.datasets.get_run(...), .get_runs(...), .delete_run(...)Dataset run helpers removed
api.ingestion.batch(...)OpenTelemetry for tracing (the SDK's tracing does this), api.scores.create(request=[...]) for scores

api.trace keeps delete and delete_multiple. api.datasets keeps list, get, and create.

api.scores.get_many keeps its name but now reads Scores API v3; see Score reads move to api.scores.get_many.

v4:

trace = langfuse.api.trace.get("trace-id")

v5:

observations = langfuse.api.observations.get_many(trace_id="trace-id", fields="core,basic,io")

Removed types include Trace, TraceWithDetails, TraceWithFullDetails, Traces, Observation, ObservationsView, ObservationsViews, Session, SessionWithTraces, PaginatedSessions, DatasetRun, DatasetRunItem, DatasetRunWithItems, PaginatedDatasetRuns, PaginatedDatasetRunItems, CreateDatasetRunItemRequest, DeleteDatasetRunResponse, the ScoreV1 family, GetScoresResponse*, the v1 MetricsResponse, and the ingestion request and event types such as IngestionEvent, TraceBody, and ObservationBody. langfuse.model no longer re-exports DatasetRun, Observation, or TraceWithFullDetails.

Trace input/output setters removed

Affects: code that calls set_current_trace_io() or span.set_trace_io(), or sets the trace-level input/output attributes.

Previously deprecated in v4, now removed. Both methods raised a DeprecationWarning in v4 that pointed to propagate_attributes() for trace attributes. In v5 they raise AttributeError. Trace input and output now come from the root observation.

v4:

from langfuse import get_client

langfuse = get_client()

with langfuse.start_as_current_observation(as_type="span", name="request") as span:
    result = call_llm(query)
    langfuse.set_current_trace_io(input=query, output=result)

v5:

from langfuse import get_client

langfuse = get_client()

with langfuse.start_as_current_observation(as_type="span", name="request") as span:
    result = call_llm(query)
    span.update(input=query, output=result)
    # or, inside the active root span:
    # langfuse.update_current_span(input=query, output=result)

set_current_trace_as_public() and span.set_trace_as_public() are unchanged. If you still use trace-level LLM-as-a-judge evaluators, migrate them to observation-level evaluators.

Dataset run helpers removed

Affects: code that calls get_dataset_runs(), get_dataset_run(), or delete_dataset_run().

Previously deprecated in v4, now removed. The endpoints behind these helpers were deprecated in v4, and the deprecated API migration guide lists the experiments API as their replacement. In v5 they raise AttributeError. "Dataset run" and "experiment" mean the same thing.

RemovedReplacement
langfuse.get_dataset_runs(dataset_name=...)langfuse.api.experiments.list(dataset_id=..., from_start_time=...)
langfuse.get_dataset_run(dataset_name=..., run_name=...)langfuse.api.experiments.list(...) and .list_items(...)
langfuse.delete_dataset_run(dataset_name=..., run_name=...)List the experiment items, then langfuse.api.trace.delete_multiple(...)

v4:

runs = langfuse.get_dataset_runs(dataset_name="qa-set")
run = langfuse.get_dataset_run(dataset_name="qa-set", run_name="baseline")
langfuse.delete_dataset_run(dataset_name="qa-set", run_name="baseline")

v5:

from langfuse import get_client

langfuse = get_client()
dataset = langfuse.get_dataset("qa-set")
# No experiment item starts before its dataset was created, so this covers every run.
since = dataset.created_at

# List experiments (dataset runs) for a dataset
experiments = langfuse.api.experiments.list(dataset_id=dataset.id, from_start_time=since)

# List the items of one experiment and follow cursor pagination
trace_ids = set()
cursor = None
while True:
    page = langfuse.api.experiments.list_items(
        dataset_id=dataset.id,
        experiment_name="baseline",
        from_start_time=since,
        cursor=cursor,
    )
    trace_ids.update(item.trace_id for item in page.data)
    cursor = page.meta.cursor
    if not cursor:
        break

# Delete the experiment's traces (also deletes their observations and scores)
ids = list(trace_ids)
for i in range(0, len(ids), 1000):
    langfuse.api.trace.delete_multiple(trace_ids=ids[i : i + 1000])

There is no experiment-delete API. Deleting the traces also deletes their observations and scores. See Dataset runs → Experiments for details.

blocked_instrumentation_scopes removed

Affects: code that passes blocked_instrumentation_scopes to Langfuse(...).

Previously deprecated in v4, now removed. v4 raised a DeprecationWarning that pointed to should_export_span. In v5 the parameter raises TypeError. Compose should_export_span with the default filter.

v4:

from langfuse import Langfuse

langfuse = Langfuse(blocked_instrumentation_scopes=["sqlite", "requests"])

v5:

from langfuse import Langfuse
from langfuse.span_filter import is_default_export_span

blocked = {"sqlite", "requests"}

langfuse = Langfuse(
    should_export_span=lambda span: (
        is_default_export_span(span)
        and (
            span.instrumentation_scope is None
            or span.instrumentation_scope.name not in blocked
        )
    )
)

New features

These need no code changes.

  • Experiment links. result.experiment_id and result.experiment_url link directly to the experiment comparison view.
  • Typed metadata filters. Numeric and boolean metadata keeps its type, and nested objects can be filtered by dotted path, such as a.b = 1.
  • Batch score creation. api.scores.create() accepts a list of CreateScoreRequest objects.
  • Experiment records. Run evaluations are stored for local-data runs too. Dataset versions are recorded on items as langfuse.experiment.item.version.
  • Lighter LangChain dependency. The CallbackHandler needs only langchain-core.

Under the hood

None of these need code changes. Each one has a setting if you need different behavior.

  • Scores are sent in batches to POST /api/public/scores instead of the legacy ingestion endpoint. create_score(), span.score(), span.score_trace(), flush_at, and flush_interval work as before. If the server accepts only part of a batch (HTTP 207), the SDK logs the accepted and rejected counts and the errors, and doesn't retry the rejected scores. Other 4xx responses are logged and not retried. Network errors, 429, and 5xx are retried with backoff.
  • Span batches are gzip-compressed. If a proxy rejects compressed requests, set Langfuse(otel_compression="none") or LANGFUSE_OTEL_COMPRESSION=none. The setting is read in this order: otel_compression, LANGFUSE_OTEL_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, OTEL_EXPORTER_OTLP_COMPRESSION, then the gzip default. Values are gzip or none (case-insensitive), and an invalid value falls through to the next setting. A custom span_exporter is not changed.
  • Spans carry x-langfuse-ingestion-version: 4, which enables real-time processing on Langfuse v4. To opt out, pass additional_headers={"x-langfuse-ingestion-version": "3"}. A custom span_exporter doesn't get this header automatically.
  • The default span filter's GenAI check requires the gen_ai. prefix, dot included, so attributes like gen_ai_custom no longer count.
  • Experiments are recorded through OpenTelemetry. run_experiment() sends experiment and item attributes on its spans instead of calling POST /api/public/dataset-run-items.

Was this page helpful?

Last updated on