Skip to content

feat(api): update API spec from langfuse/langfuse e5c1a45#1699

Closed
langfuse-bot wants to merge 1 commit into
mainfrom
api-spec-bot-e5c1a45
Closed

feat(api): update API spec from langfuse/langfuse e5c1a45#1699
langfuse-bot wants to merge 1 commit into
mainfrom
api-spec-bot-e5c1a45

Conversation

@langfuse-bot

@langfuse-bot langfuse-bot commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Greptile Summary

This PR is a Fern-generated API spec update that restructures the scores API modules: the v3 scores types and client (previously under scores_v3) are merged into the scores module, the old v2 scores endpoint is moved into a new legacy/scores_v2 sub-client, and scores.get_many() now calls GET /api/public/v3/scores with cursor-based pagination and a revised filter set.

  • The scores_v3 module is deleted and its types/clients are folded into scores, with renames dropping the V3 suffix from subject and meta types (e.g. ScoreSubjectV3ScoreSubject, GetScoresV3MetaGetScoresMeta).
  • scores.get_many() gains new filters (cursor, id, author_user_id, value_min, value_max, experiment_id) and drops v2-only params (page, user_id, score_ids, dataset_run_id, operator, trace_tags, filter); scores.get_by_id() is removed with no v3 equivalent.
  • The old v2 behavior is preserved under client.legacy.scores_v2, which introduces a new ScoresV2Client / AsyncScoresV2Client wrapping GET /api/public/v2/scores.

Confidence Score: 3/5

The migration from scores to the v3 endpoint is intentional, but the silent removal of get_by_id and the incompatible get_many signature are breaking changes for any caller that hasn't been updated alongside this PR.

The scores.get_by_id() method is deleted with no v3 drop-in: callers must switch to get_many(id=...) which returns a list, not a single object. Simultaneously, get_many loses several parameters (page, user_id, score_ids, trace_tags, filter) that existing callers may depend on. If all call-sites have been audited and updated, this is safe; if not, runtime errors or silently wrong behavior will occur.

langfuse/api/scores/client.py and langfuse/api/scores/raw_client.py — the removed get_by_id and the changed get_many parameter set are the most likely source of breakage for downstream code.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant ScoresClient as scores.get_many() (v3)
    participant LegacyScoresV2 as legacy.scores_v2.get_many() (v2)
    participant API_v3 as GET /api/public/v3/scores
    participant API_v2 as GET /api/public/v2/scores

    Caller->>ScoresClient: get_many(cursor, id, value_min, ...)
    ScoresClient->>API_v3: HTTP GET with cursor pagination
    API_v3-->>ScoresClient: GetScoresResponse (ScoreV3 list + GetScoresMeta)
    ScoresClient-->>Caller: GetScoresResponse

    Caller->>LegacyScoresV2: get_many(page, user_id, score_ids, ...)
    LegacyScoresV2->>API_v2: HTTP GET with page pagination
    API_v2-->>LegacyScoresV2: GetScoresResponse (v2 data + MetaResponse)
    LegacyScoresV2-->>Caller: GetScoresResponse (v2)
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
langfuse/api/legacy/client.py:66
**Lazy imports inside property methods**

The `from .scores_v2.client import ScoresV2Client` import on line 66 (and the async equivalent on line 121) are placed inside property bodies rather than at the top of the module. This violates the project's import style rule. The same pattern appears for the other properties in this file (`metrics_v1`, `observations_v1`, `score_v1`), so the new `scores_v2` property continues that existing pattern — but it is worth consolidating these to top-level imports to stay consistent with the rule.

### Issue 2 of 2
langfuse/api/scores/client.py:27
**Breaking removal of `get_by_id` without a v3 equivalent**

The `get_by_id(score_id)` method has been removed from `ScoresClient` and `AsyncScoresClient`. The v3 API has no `GET /api/public/v3/scores/:id` endpoint, so the nearest workaround is `get_many(id=score_id)` — but that returns a `GetScoresResponse` (a list) rather than a single `Score`. Any caller that previously used `client.scores.get_by_id()` must be updated; there is no drop-in replacement. The old v2 equivalent is preserved under `client.legacy.scores_v2.get_by_id()`, but callers still need to be migrated manually.

Reviews (1): Last reviewed commit: "feat(api): update API spec from langfuse..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

  • Rule used - Move imports to the top of the module instead of p... (source)

Learned From
langfuse/langfuse-python#1387

@github-actions

Copy link
Copy Markdown

@claude review

@@ -30,96 +27,109 @@ def with_raw_response(self) -> RawScoresClient:
def get_many(

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.

P1 Breaking removal of get_by_id without a v3 equivalent

The get_by_id(score_id) method has been removed from ScoresClient and AsyncScoresClient. The v3 API has no GET /api/public/v3/scores/:id endpoint, so the nearest workaround is get_many(id=score_id) — but that returns a GetScoresResponse (a list) rather than a single Score. Any caller that previously used client.scores.get_by_id() must be updated; there is no drop-in replacement. The old v2 equivalent is preserved under client.legacy.scores_v2.get_by_id(), but callers still need to be migrated manually.

Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/api/scores/client.py
Line: 27

Comment:
**Breaking removal of `get_by_id` without a v3 equivalent**

The `get_by_id(score_id)` method has been removed from `ScoresClient` and `AsyncScoresClient`. The v3 API has no `GET /api/public/v3/scores/:id` endpoint, so the nearest workaround is `get_many(id=score_id)` — but that returns a `GetScoresResponse` (a list) rather than a single `Score`. Any caller that previously used `client.scores.get_by_id()` must be updated; there is no drop-in replacement. The old v2 equivalent is preserved under `client.legacy.scores_v2.get_by_id()`, but callers still need to be migrated manually.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 27 to 30
def get_many(
self,
*,
page: typing.Optional[int] = None,
limit: typing.Optional[int] = None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 The autogenerated scores.get_by_id method is removed by this regen (only get_many remains on the new v3 ScoresClient), but tests/e2e/test_core_sdk.py:158 (test_create_session_score) still calls get_api().scores.get_by_id(score_id). Once merged the e2e CI shard will fail immediately with AttributeError: 'ScoresClient' object has no attribute 'get_by_id' — please migrate that one call to get_api().legacy.scores_v2.get_by_id(score_id) (which this same PR adds and which still returns the v2 Score with the flat session_id the test asserts on) in this PR.

Extended reasoning...

What the bug is

This PR is an auto-generated regen of the Fern client that swaps ScoresClient.get_many to the new v3 /api/public/v3/scores endpoint and drops the old ScoresClient.get_by_id method entirely. The replacement is moved to client.legacy.scores_v2.get_by_id (hitting /api/public/v2/scores/{score_id}). The PR does not touch the in-repo test suite.

The triggering code path

tests/e2e/test_core_sdk.py:158 in test_create_session_score does:

score = get_api().scores.get_by_id(score_id)
assert score.value == 1
assert score.data_type == "NUMERIC"
assert score.session_id == session_id

get_api() in tests/support/utils.py:68 returns a LangfuseAPI (the generated client) wrapped in a thin retry proxy that forwards attribute access via getattr(self._target, name). So get_api().scores is the new ScoresClient, whose only public method is now get_many. Attribute access for get_by_id therefore raises AttributeError before any HTTP call is ever made.

Why existing code doesn't prevent it

There is no shim or backward-compat alias re-exporting get_by_id on the new ScoresClient. grep -n get_by_id langfuse/api/scores/client.py returns nothing after this PR; the only sibling get_by_id lives at langfuse/api/legacy/scores_v2/client.py:207, which the test does not reference. The synthesis description and all four verifiers independently confirmed both halves (call site exists, method removed).

Impact

tests/e2e/test_core_sdk.py is included in the e2e shards (it is explicitly weighted in scripts/select_e2e_shard.py, and the e2e job in .github/workflows/ci.yml runs pytest against tests/e2e/* on every PR). The failure is deterministic — AttributeError on the first call — so the shard that picks up test_create_session_score will go red the moment this PR is merged, blocking subsequent merges.

Step-by-step proof

  1. Pytest loads tests/e2e/test_core_sdk.py and dispatches test_create_session_score.
  2. The test calls get_api() → returns a _RetryingApiProxy around a LangfuseAPI instance (tests/support/utils.py:68).
  3. .scores → proxy forwards to LangfuseAPI.scores → lazy-instantiates ScoresClient (langfuse/api/client.py, post-PR).
  4. .get_by_id(score_id)ScoresClient only defines get_many (see langfuse/api/scores/client.py:27). Python raises AttributeError: 'ScoresClient' object has no attribute 'get_by_id'.
  5. Test fails before flush/assert; e2e shard fails.

How to fix

One-line change in the same PR:

score = get_api().legacy.scores_v2.get_by_id(score_id)

The new ScoresV2Client.get_by_id (added by this PR at langfuse/api/legacy/scores_v2/client.py:166) returns the v2 Score type from commons.types.score, which still has the flat session_id, value, and data_type fields that the test's assertions read — so no other test changes are needed. Migrating to v3 get_many(id=score_id) would work for fetching but the assertion on score.session_id would need updating because v3 ScoreV3 puts session_id on a nested optional subject object that is only populated when fields=subject is requested. The legacy route is the cheaper, safer fix.

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.

1 participant