Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions langfuse/api/observations/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ def get_many(
## Cursor-based Pagination
This endpoint uses cursor-based pagination for efficient traversal of large datasets.
The cursor is returned in the response metadata and should be passed in subsequent requests
to retrieve the next page of results.
to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain
fewer items than the requested limit, including zero items, while `meta.cursor` is present.
Continue requesting pages until `meta.cursor` is absent.
Requests without a lower start-time bound may search only from January 1, 2020.

## Field Selection
Use the `fields` parameter to control which observation fields are returned:
Expand Down Expand Up @@ -92,7 +95,7 @@ def get_many(
Example: "key1,key2"

limit : typing.Optional[int]
Number of items to return per page. Maximum 1000, default 50.
Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50.

cursor : typing.Optional[str]
Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page.
Expand Down Expand Up @@ -342,7 +345,10 @@ async def get_many(
## Cursor-based Pagination
This endpoint uses cursor-based pagination for efficient traversal of large datasets.
The cursor is returned in the response metadata and should be passed in subsequent requests
to retrieve the next page of results.
to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain
fewer items than the requested limit, including zero items, while `meta.cursor` is present.
Continue requesting pages until `meta.cursor` is absent.
Requests without a lower start-time bound may search only from January 1, 2020.

## Field Selection
Use the `fields` parameter to control which observation fields are returned:
Expand Down Expand Up @@ -378,7 +384,7 @@ async def get_many(
Example: "key1,key2"

limit : typing.Optional[int]
Number of items to return per page. Maximum 1000, default 50.
Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50.

cursor : typing.Optional[str]
Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page.
Expand Down
14 changes: 10 additions & 4 deletions langfuse/api/observations/raw_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ def get_many(
## Cursor-based Pagination
This endpoint uses cursor-based pagination for efficient traversal of large datasets.
The cursor is returned in the response metadata and should be passed in subsequent requests
to retrieve the next page of results.
to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain
fewer items than the requested limit, including zero items, while `meta.cursor` is present.
Continue requesting pages until `meta.cursor` is absent.
Requests without a lower start-time bound may search only from January 1, 2020.

## Field Selection
Use the `fields` parameter to control which observation fields are returned:
Expand Down Expand Up @@ -90,7 +93,7 @@ def get_many(
Example: "key1,key2"

limit : typing.Optional[int]
Number of items to return per page. Maximum 1000, default 50.
Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50.

cursor : typing.Optional[str]
Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page.
Expand Down Expand Up @@ -399,7 +402,10 @@ async def get_many(
## Cursor-based Pagination
This endpoint uses cursor-based pagination for efficient traversal of large datasets.
The cursor is returned in the response metadata and should be passed in subsequent requests
to retrieve the next page of results.
to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain
fewer items than the requested limit, including zero items, while `meta.cursor` is present.
Continue requesting pages until `meta.cursor` is absent.
Requests without a lower start-time bound may search only from January 1, 2020.

## Field Selection
Use the `fields` parameter to control which observation fields are returned:
Expand Down Expand Up @@ -435,7 +441,7 @@ async def get_many(
Example: "key1,key2"

limit : typing.Optional[int]
Number of items to return per page. Maximum 1000, default 50.
Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50.

cursor : typing.Optional[str]
Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page.
Expand Down
5 changes: 5 additions & 0 deletions langfuse/api/observations/types/observations_v2meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ class ObservationsV2Meta(CursorMetaResponse):
Metadata for cursor-based pagination.
"""

comment: typing.Optional[str] = pydantic.Field(default=None)
"""
Present when an empty page has a cursor; continue requesting pages until the cursor is absent.
Comment on lines +14 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟣 pre-existing, not blocking: Batch evaluation over observations can stop early and report itself complete with no resume token when the server returns an empty page that still carries a cursor. This change documents that contract but does not update the SDK's own pagination loop. langfuse/batch_evaluation.py:1057 breaks on cursor is None or not items, so an empty page ends the run as finished. Fix: end observation pagination only when meta.cursor is absent, and keep looping on empty pages. Same pattern at 2 sites (langfuse/batch_evaluation.py:1057, tests/support/utils.py:189).
A small fix can ride a push you are already making; otherwise a short reply is enough.

Why this was flagged

The trigger is the API contract this change documents: get_many can return data=[] while meta.cursor is set, with comment filled in (observations_v2meta.py:14-16). One example is a filter whose next matches fall outside the server's scan window. run_batched_evaluation on observations calls _fetch_batch_with_retry (batch_evaluation.py:1114-1123) and then runs if cursor is None or not items: has_more = False; break at batch_evaluation.py:1057. The result therefore has completed=True, has_more_items=False and resume_token=None (lines 1080-1085), even though more matching observations exist. They are never scored, and the caller gets no token to resume from. The e2e helper get_observations in tests/support/utils.py:189 stops the same way on not response.data, so it can return a partial list. The base client did not document this, but the loop ignores the cursor whenever a page is empty.

Verification: pre-existing. Trigger: the v2 observations endpoint returns data=[] while meta.cursor is still set. The loop then runs cursor = next_cursor; if cursor is None or not items: has_more = False; break (batch_evaluation.py:1056-1059). An empty page that still has a cursor therefore ends the run. The base commit has the same loop at 1056-1059.

"""

model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(
extra="allow", frozen=True
)
4 changes: 3 additions & 1 deletion langfuse/api/observations/types/observations_v2response.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ class ObservationsV2Response(UniversalBaseModel):
Response containing observations with field-group-based filtering and cursor-based pagination.

The `data` array contains observation objects with only the requested field groups included.
Use the `cursor` in `meta` to retrieve the next page of results.
Use the `cursor` in `meta` to retrieve the next page of results. The array may contain fewer
than the requested `limit` items, or be empty, while a cursor is present. Continue until the
cursor is absent.
"""

data: typing.List[ObservationV2] = pydantic.Field()
Expand Down
Loading