Skip to content

fix(client): point the dataset-run helpers at the v4 read path in docs and logs - #1912

Open
passionworkeer wants to merge 1 commit into
langfuse:mainfrom
passionworkeer:oss/langfuse-dataset-run-v4-20261001T000023-morning-9f27fc43
Open

passionworkeer wants to merge 1 commit into
langfuse:mainfrom
passionworkeer:oss/langfuse-dataset-run-v4-20261001T000023-morning-9f27fc43

Conversation

@passionworkeer

@passionworkeer passionworkeer commented Sep 30, 2026 •

Copy link
Copy Markdown

fix(client): point the dataset-run helpers at the v4 read path in docs and logs

Fixes #1906.

What is wrong

On a Langfuse v4 deployment the three dataset-run read helpers cannot work: the
endpoints behind them are rejected with 404 in events_only mode. Two things made
that a bad experience rather than a merely inconvenient one.

The docstrings said nothing about v4. So the only signal a caller got was a
404 whose Python str() begins with the response headers — the server's own
explanation is only reachable through exc.body.

The error handlers were dead code. All three helpers were written as:

except Error as e:
    handle_fern_exception(e)
    raise e

where Error is langfuse.api.Error, the fern-generated base class. The errors
the SDK actually raises derive from langfuse.api.core.api_error.ApiError:

>>> from langfuse.api import Error, NotFoundError
>>> from langfuse.api.core.api_error import ApiError
>>> issubclass(NotFoundError, ApiError)
True
>>> issubclass(NotFoundError, Error)
False

So the clause never fired for a server rejection, and handle_fern_exception —
which only logs — was never called either.

What this changes

  1. Each of the three docstrings now states that the endpoint is unavailable on v4,
    names client.api.experiments.list() / list_items() as the replacement, notes
    that the dataset_run_id returned by run_experiment() is the same value those
    return, and links the migration guide.

  2. The three clauses become except (Error, ApiError), which also lets the
    pre-existing handle_fern_exception call do the job it was written for.

  3. _warn_if_v4_dataset_run_rejected(e) adds one WARNING naming the replacement —
    only when the server's body actually carries the v4 rejection. The exception type
    is unchanged, so anything already catching NotFoundError keeps working.

Routing the helpers themselves onto the v4 read path is deliberately not part of
this change. It is not a drop-in: DatasetRunItem.id has no experiment counterpart,
dataset_name needs a datasets.list() lookup, metadata needs fields=metadata,
startTime/endTime are clipped to the requested from_start_time window, and
pagination is cursor-based instead of page-based. That mapping is a product decision
and belongs to a maintainer.

Scope note

client.py contains eight except Error clauses and all eight have this latent
bug. The five left alone are get_dataset, auth_check, create_dataset,
create_dataset_item and create_prompt. Widening them alters error-logging
behaviour across unrelated methods, so it is left for a separate decision.

404 is not routed through the error logger

handle_fern_exception maps a bare status onto a generic message, and its 404 entry
reads "Internal error occurred. This is an unusual occurrence and we are monitoring it
closely."
These three helpers raise NotFoundError routinely — asking for a run that
does not exist is an ordinary outcome — so routing 404 through it would ship a false
claim at ERROR level, which is also what error alerting keys on. A v4 refusal and a 404
therefore log the actionable warning (or nothing), while every other status still goes
through handle_fern_exception as originally intended.

Verification

Against a real self-hosted Langfuse v4 events_only deployment (langfuse-web
4.42.0) on base 0bc5897b:

  • run_experiment() returns a populated dataset_run_id with item_count=2 and a
    dataset_run_url; the same client then fails to read that run back through
    get_dataset_run() with the v4 404.
  • Before: get_dataset_run, get_dataset_runs and delete_dataset_run all raised
    NotFoundError with no guidance reachable, and the error-log handler never ran.
  • After: all three emit the v4 guidance, and client.api.experiments.list(...) — the
    replacement the guidance names — returns ExperimentsResponse on the same
    deployment.

New unit tests in tests/unit/test_dataset_run_v4_guidance.py:

  • the three docstrings name the replacement and the migration guide;
  • the v4 guidance fires on the real server body (pinned verbatim) and stays quiet on an
    ordinary 404, on missing bodies and on non-dict bodies;
  • a refused delete does not receive the read hint — there is no delete counterpart on
    client.api.experiments, so pointing the caller at the read path could lead them to
    conclude the run had been removed;
  • the ApiError / Error class relationship that caused the dead handlers;
  • behavioural tests that drive each helper through a stand-in generated client and assert
    a v4 refusal logs the guidance and never "Internal error", an ordinary 404 logs neither,
    and a non-404 API error still reaches the original logger.

The dataset_run_id == experiment id claim was checked against the live deployment:
the experiment returned by experiments.list() carries the same id, and it resolves both
by id and by name.

ruff check and ruff format --check are clean on the new test file. client.py has
563 pre-existing ruff findings, identical before and after this change.

RetriggerConfidence Score: 4/5

The runtime change appears safe to merge, but the repository’s import-placement requirement must be satisfied first.

Summary

The PR adds v4 migration guidance to three legacy dataset-run helpers and catches generated API errors so a recognized v4 rejection produces an actionable warning. It also adds focused regression tests.

Reviews (1) · Last reviewed commit: "fix(client): point the dataset-run helpe..."

…s and logs

Fixes langfuse-python#1906.

A Langfuse v4 deployment rejects the legacy dataset-run read endpoints with
404, so `get_dataset_run()`, `get_dataset_runs()` and `delete_dataset_run()`
cannot work there. Two things made that worse than merely inconvenient.

The docstrings said nothing about v4, and the raised error's `str()` starts
with the response headers, so the only signal a caller got was a bare 404
whose explanation sits in `exc.body`.

The error handlers were also dead code. All three were written as
`except Error as e:`, and `Error` here is the fern-generated
`langfuse.api.Error` -- a *subclass* of `langfuse.api.core.api_error.ApiError`.
The errors the SDK actually raises (`NotFoundError` and the rest of the
exported set) are `ApiError` subclasses that are not `Error`, so the clause
never fired and `handle_fern_exception` was never called either:

    >>> issubclass(NotFoundError, ApiError)
    True
    >>> issubclass(NotFoundError, Error)
    False

This change:

- adds a v4 note to each docstring naming the replacement read path, notes
  that the `dataset_run_id` from `run_experiment()` is the same value those
  return, and links the migration guide. `from_start_time` is called out as
  required, since a literal follower otherwise gets a TypeError;
- makes the three clauses catch `ApiError`;
- routes all three failures through one `_handle_dataset_run_error()`, which
  emits the v4 guidance and otherwise leaves the exception alone.

`handle_fern_exception` and `generate_error_message_fern` now take `ApiError`
instead of the narrower `Error`. Widening a parameter is safe for every
existing caller, `generate_error_message_fern` already dispatched on
`isinstance(..., ApiError)`, and leaving the annotations alone made mypy fail
with three `arg-type` errors on the newly reachable calls.

Two deliberate omissions in `_handle_dataset_run_error`:

- 404 is not routed to `handle_fern_exception`. Its 404 entry reads
  "Internal error occurred. This is an unusual occurrence and we are monitoring
  it closely" -- these helpers raise `NotFoundError` routinely, because asking
  for a run that does not exist is an ordinary outcome, and a false claim at
  ERROR level is also what error alerting keys on.
- a refused delete gets its own hint, not the read hint. There is no delete
  counterpart on `client.api.experiments`, so telling a caller whose delete
  was refused to "read the run" could lead them to conclude it was removed. It
  was not.

Routing the helpers themselves onto the v4 read path is not part of this
change and is not a drop-in: `DatasetRunItem.id` has no experiment
counterpart, `dataset_name` needs a `datasets.list()` lookup, `metadata`
needs `fields=metadata`, `startTime`/`endTime` are clipped to the requested
`from_start_time` window, and pagination is cursor-based rather than
page-based. That mapping is a product decision.

Five other `except Error` clauses in this file share the latent bug --
`get_dataset`, `auth_check`, `create_dataset`, `create_dataset_item` and
`create_prompt` -- but they are left alone here: widening them changes
error-logging behaviour across unrelated methods.

Verified against a real v4 events_only deployment (langfuse 4.42.0): before,
all three helpers raised with no guidance and the error logger never ran;
after, the two read helpers log the read guidance, the delete helper logs the
delete-specific guidance, nothing is reported as an internal error, a
non-404 API error still reaches the original logger, and
`client.api.experiments.list(...)` returns ExperimentsResponse.
Copilot AI balanced review requested due to automatic review settings September 30, 2026 17:09

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment on lines +2608 to +2611
``client.api.experiments.list(from_start_time=...)`` and match on ``id``
or ``name`` instead -- ``from_start_time`` is required, and
``run_experiment()`` returns that same value as ``dataset_run_id``. See
https://langfuse.com/docs/v4.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Run name lacks dataset scope The old lookup uses both dataset_name and run_name, but this guidance suggests matching an experiment by name alone. If two datasets use the same run name, that match is ambiguous and a caller could select the wrong run. Advise callers to include dataset_id when matching by name, or to use the run ID.

Knowledge Base Used: Dataset and run management

Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/_client/client.py
Line: 2608-2611

Comment:
**Run name lacks dataset scope** The old lookup uses both `dataset_name` and `run_name`, but this guidance suggests matching an experiment by name alone. If two datasets use the same run name, that match is ambiguous and a caller could select the wrong run. Advise callers to include `dataset_id` when matching by name, or to use the run ID.

**Knowledge Base Used:** [Dataset and run management](https://app.greptile.com/personal-org-4986/-/custom-context/knowledge-base/langfuse/langfuse-python/-/docs/dataset-and-run-management.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: get_dataset_run / get_dataset_runs / delete_dataset_run are unusable on Langfuse v4

2 participants