diff --git a/postflight/adapters/langfuse.py b/postflight/adapters/langfuse.py index ca9d761..1451141 100644 --- a/postflight/adapters/langfuse.py +++ b/postflight/adapters/langfuse.py @@ -273,6 +273,10 @@ def observations( pages += 1 if on_page: on_page(pages, len(rows)) - cursor = (data.get("meta") or {}).get("cursor") - if not cursor: + nxt = (data.get("meta") or {}).get("cursor") + # A cursor that echoes the one just sent is not a next page — it is the + # same page again. Ending on falsy alone turns that into an unbounded + # loop that grows `rows` forever and never reports anything. + if not nxt or nxt == cursor: return rows + cursor = nxt diff --git a/tests/test_langfuse_adapter.py b/tests/test_langfuse_adapter.py index 5ef0e41..b77b32d 100644 --- a/tests/test_langfuse_adapter.py +++ b/tests/test_langfuse_adapter.py @@ -7,7 +7,7 @@ from datetime import UTC from postflight import Config, run -from postflight.adapters.langfuse import LangfuseAdapter, text_of +from postflight.adapters.langfuse import LangfuseAdapter, LangfuseClient, text_of ADAPTER = LangfuseAdapter() @@ -138,6 +138,30 @@ def test_first_non_empty_user_id_wins(): assert built.user_id == "u1" +def test_paging_stops_when_the_cursor_stops_advancing(): + """A repeated cursor means the same page again, not a next one. + + Pages past the last one raise, so a client that keeps walking fails loudly here + instead of looping until the suite is killed. + """ + pages = [ + {"data": [span(name="a.turn")], "meta": {"cursor": "same"}}, + {"data": [span(name="b.turn")], "meta": {"cursor": "same"}}, + ] + asked: list[str] = [] + + class Client(LangfuseClient): + def get(self, path, retries=5): + asked.append(path) + return pages[len(asked) - 1] # IndexError past the last page + + rows = Client("https://h", "pk", "sk").observations(hours=1) + + assert len(asked) == 2 + assert len(rows) == 2 + assert "cursor=same" in asked[1] + + def test_text_of_handles_the_three_output_shapes(): assert text_of('[{"type": "text", "text": "hi"}]') == "hi" assert text_of([{"type": "text", "text": "hi"}]) == "hi"