langfuse-haystack 0.8.0__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/CHANGELOG.md +35 -9
  2. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/PKG-INFO +3 -3
  3. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/pyproject.toml +2 -6
  4. langfuse_haystack-0.9.0/src/haystack_integrations/components/connectors/__init__.py +3 -0
  5. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/src/haystack_integrations/components/connectors/langfuse/__init__.py +1 -0
  6. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +8 -4
  7. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/src/haystack_integrations/tracing/langfuse/__init__.py +1 -0
  8. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/src/haystack_integrations/tracing/langfuse/tracer.py +37 -8
  9. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/tests/test_langfuse_span.py +4 -0
  10. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/tests/test_tracer.py +4 -0
  11. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/tests/test_tracing.py +43 -80
  12. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/.gitignore +0 -0
  13. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/LICENSE.txt +0 -0
  14. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/README.md +0 -0
  15. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/example/basic_rag.py +0 -0
  16. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/example/chat.py +0 -0
  17. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/example/requirements.txt +0 -0
  18. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/pydoc/config.yml +0 -0
  19. {langfuse_haystack-0.8.0 → langfuse_haystack-0.9.0}/tests/__init__.py +0 -0
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/langfuse-v0.8.0] - 2025-01-28
4
+
5
+ ### 🚀 Features
6
+
7
+ - Add custom Langfuse span handling support (#1313)
8
+
9
+
3
10
  ## [integrations/langfuse-v0.7.0] - 2025-01-21
4
11
 
5
12
  ### 🚀 Features
@@ -22,7 +29,7 @@
22
29
 
23
30
  ### 🌀 Miscellaneous
24
31
 
25
- - Chore: Langfuse - pin `haystack-ai>=2.9.0` and simplify message conversion (#1292)
32
+ - Pin haystack-ai>=2.9.0 and simplify (#1292)
26
33
 
27
34
  ## [integrations/langfuse-v0.6.2] - 2025-01-02
28
35
 
@@ -36,7 +43,7 @@
36
43
 
37
44
  ### 🌀 Miscellaneous
38
45
 
39
- - Chore: Fix tracing_context_var lint errors (#1220)
46
+ - Fix tracing_context_var lint errors (#1220)
40
47
  - Fix messages conversion to OpenAI format (#1272)
41
48
 
42
49
  ## [integrations/langfuse-v0.6.0] - 2024-11-18
@@ -53,6 +60,25 @@
53
60
 
54
61
  - Fixed TypeError in LangfuseTrace (#1184)
55
62
 
63
+ * Added parent_span functionality in trace method
64
+
65
+ * solved PR comments
66
+
67
+ * Readded "end()" for solving Latency issues
68
+
69
+ * chore: fix ruff linting
70
+
71
+ * Handle multiple runs
72
+
73
+ * Fix indentation and span closing
74
+
75
+ * Fix tests
76
+
77
+ ---------
78
+
79
+ Co-authored-by: Vladimir Blagojevic <dovlex@gmail.com>
80
+ Co-authored-by: Silvano Cerza <silvanocerza@gmail.com>
81
+
56
82
  ## [integrations/langfuse-v0.5.0] - 2024-10-01
57
83
 
58
84
  ### 🧹 Chores
@@ -61,7 +87,7 @@
61
87
 
62
88
  ### 🌀 Miscellaneous
63
89
 
64
- - Fix: Add delay to flush the Langfuse traces (#1091)
90
+ - Add delay to flush the Langfuse traces (#1091)
65
91
  - Add invocation_context to identify traces (#1089)
66
92
 
67
93
  ## [integrations/langfuse-v0.4.0] - 2024-09-17
@@ -93,15 +119,15 @@
93
119
 
94
120
  ### 🌀 Miscellaneous
95
121
 
96
- - Ci: install `pytest-rerunfailures` where needed; add retry config to `test-cov` script (#845)
97
- - Chore: Update Langfuse README to avoid common initialization issues (#952)
98
- - Chore: langfuse - ruff update, don't ruff tests (#992)
122
+ - Install pytest-rerunfailures; change test-cov script (#845)
123
+ - Update Langfuse README to avoid common initialization issues (#952)
124
+ - Ruff update, don't ruff tests (#992)
99
125
 
100
126
  ## [integrations/langfuse-v0.2.0] - 2024-06-18
101
127
 
102
128
  ### 🌀 Miscellaneous
103
129
 
104
- - Feat: add support for Azure generators (#815)
130
+ - Add support for Azure generators (#815)
105
131
 
106
132
  ## [integrations/langfuse-v0.1.0] - 2024-06-13
107
133
 
@@ -119,7 +145,7 @@
119
145
 
120
146
  ### 🌀 Miscellaneous
121
147
 
122
- - Chore: change the pydoc renderer class (#718)
123
- - Docs: add missing api references (#728)
148
+ - Change the pydoc renderer class (#718)
149
+ - Missing api references (#728)
124
150
 
125
151
  <!-- generated by git-cliff -->
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langfuse-haystack
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse#readme
5
5
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
6
6
  Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse
@@ -9,14 +9,14 @@ License-Expression: Apache-2.0
9
9
  License-File: LICENSE.txt
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Programming Language :: Python
12
- Classifier: Programming Language :: Python :: 3.8
13
12
  Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Programming Language :: Python :: Implementation :: CPython
18
18
  Classifier: Programming Language :: Python :: Implementation :: PyPy
19
- Requires-Python: >=3.8
19
+ Requires-Python: >=3.9
20
20
  Requires-Dist: haystack-ai>=2.9.0
21
21
  Requires-Dist: langfuse
22
22
  Description-Content-Type: text/markdown
@@ -7,18 +7,18 @@ name = "langfuse-haystack"
7
7
  dynamic = ["version"]
8
8
  description = ''
9
9
  readme = "README.md"
10
- requires-python = ">=3.8"
10
+ requires-python = ">=3.9"
11
11
  license = "Apache-2.0"
12
12
  keywords = []
13
13
  authors = [{ name = "deepset GmbH", email = "info@deepset.ai" }]
14
14
  classifiers = [
15
15
  "Development Status :: 4 - Beta",
16
16
  "Programming Language :: Python",
17
- "Programming Language :: Python :: 3.8",
18
17
  "Programming Language :: Python :: 3.9",
19
18
  "Programming Language :: Python :: 3.10",
20
19
  "Programming Language :: Python :: 3.11",
21
20
  "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
22
  "Programming Language :: Python :: Implementation :: CPython",
23
23
  "Programming Language :: Python :: Implementation :: PyPy",
24
24
  ]
@@ -60,10 +60,6 @@ cov = ["test-cov", "cov-report"]
60
60
  cov-retry = ["test-cov-retry", "cov-report"]
61
61
  docs = ["pydoc-markdown pydoc/config.yml"]
62
62
 
63
-
64
- [[tool.hatch.envs.all.matrix]]
65
- python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
66
-
67
63
  [tool.hatch.envs.lint]
68
64
  installer = "uv"
69
65
  detached = true
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+
4
5
  from .langfuse_connector import LangfuseConnector
5
6
 
6
7
  __all__ = ["LangfuseConnector"]
@@ -1,12 +1,16 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  from typing import Any, Dict, Optional
2
6
 
3
7
  import httpx
4
8
  from haystack import component, default_from_dict, default_to_dict, logging, tracing
5
9
  from haystack.utils import Secret, deserialize_secrets_inplace
6
10
  from haystack.utils.base_serialization import deserialize_class_instance, serialize_class_instance
11
+ from langfuse import Langfuse
7
12
 
8
13
  from haystack_integrations.tracing.langfuse import LangfuseTracer, SpanHandler
9
- from langfuse import Langfuse
10
14
 
11
15
  logger = logging.getLogger(__name__)
12
16
 
@@ -28,7 +32,7 @@ class LangfuseConnector:
28
32
  enable Haystack tracing in your pipeline.
29
33
 
30
34
  Lastly, you may disable flushing the data after each component by setting the `HAYSTACK_LANGFUSE_ENFORCE_FLUSH`
31
- environent variable to `false`. By default, the data is flushed after each component and blocks the thread until
35
+ environment variable to `false`. By default, the data is flushed after each component and blocks the thread until
32
36
  the data is sent to Langfuse. **Caution**: Disabling this feature may result in data loss if the program crashes
33
37
  before the data is sent to Langfuse. Make sure you will call langfuse.flush() explicitly before the program exits.
34
38
  E.g. by using tracer.actual_tracer.flush():
@@ -159,7 +163,7 @@ class LangfuseConnector:
159
163
  )
160
164
  tracing.enable_tracing(self.tracer)
161
165
 
162
- @component.output_types(name=str, trace_url=str)
166
+ @component.output_types(name=str, trace_url=str, trace_id=str)
163
167
  def run(self, invocation_context: Optional[Dict[str, Any]] = None):
164
168
  """
165
169
  Runs the LangfuseConnector component.
@@ -176,7 +180,7 @@ class LangfuseConnector:
176
180
  "Langfuse tracer invoked with the following context: '{invocation_context}'",
177
181
  invocation_context=invocation_context,
178
182
  )
179
- return {"name": self.name, "trace_url": self.tracer.get_trace_url()}
183
+ return {"name": self.name, "trace_url": self.tracer.get_trace_url(), "trace_id": self.tracer.get_trace_id()}
180
184
 
181
185
  def to_dict(self) -> Dict[str, Any]:
182
186
  """
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2024-present deepset GmbH <info@deepset.ai>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+
4
5
  from .tracer import DefaultSpanHandler, LangfuseSpan, LangfuseTracer, SpanContext, SpanHandler
5
6
 
6
7
  __all__ = ["DefaultSpanHandler", "LangfuseSpan", "LangfuseTracer", "SpanContext", "SpanHandler"]
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  import contextlib
2
6
  import os
3
7
  from abc import ABC, abstractmethod
@@ -43,6 +47,8 @@ _ALL_SUPPORTED_GENERATORS = _SUPPORTED_GENERATORS + _SUPPORTED_CHAT_GENERATORS
43
47
 
44
48
  # These are the keys used by Haystack for traces and span.
45
49
  # We keep them here to avoid making typos when using them.
50
+ _PIPELINE_INPUT_KEY = "haystack.pipeline.input_data"
51
+ _PIPELINE_OUTPUT_KEY = "haystack.pipeline.output_data"
46
52
  _PIPELINE_RUN_KEY = "haystack.pipeline.run"
47
53
  _COMPONENT_NAME_KEY = "haystack.component.name"
48
54
  _COMPONENT_TYPE_KEY = "haystack.component.type"
@@ -114,6 +120,14 @@ class LangfuseSpan(Span):
114
120
  """
115
121
  return self._span
116
122
 
123
+ def get_data(self) -> Dict[str, Any]:
124
+ """
125
+ Return the data associated with the span.
126
+
127
+ :return: The data associated with the span.
128
+ """
129
+ return self._data
130
+
117
131
  def get_correlation_data_for_logs(self) -> Dict[str, Any]:
118
132
  return {}
119
133
 
@@ -209,7 +223,7 @@ class SpanHandler(ABC):
209
223
  """
210
224
  Process a span after component execution by attaching metadata and metrics.
211
225
 
212
- This method is called after the component yields its span, allowing you to:
226
+ This method is called after the component or pipeline yields its span, allowing you to:
213
227
  - Extract and attach token usage statistics
214
228
  - Add model information
215
229
  - Record timing data (e.g., time-to-first-token)
@@ -234,9 +248,10 @@ class DefaultSpanHandler(SpanHandler):
234
248
  """DefaultSpanHandler provides the default Langfuse tracing behavior for Haystack."""
235
249
 
236
250
  def create_span(self, context: SpanContext) -> LangfuseSpan:
237
- message = "Tracer is not initialized"
238
251
  if self.tracer is None:
252
+ message = "Tracer is not initialized"
239
253
  raise RuntimeError(message)
254
+
240
255
  tracing_ctx = tracing_context_var.get({})
241
256
  if not context.parent_span:
242
257
  if context.operation_name != _PIPELINE_RUN_KEY:
@@ -262,13 +277,20 @@ class DefaultSpanHandler(SpanHandler):
262
277
  return LangfuseSpan(context.parent_span.raw_span().span(name=context.name))
263
278
 
264
279
  def handle(self, span: LangfuseSpan, component_type: Optional[str]) -> None:
280
+ # If the span is at the pipeline level, we add input and output keys to the span
281
+ at_pipeline_level = span.get_data().get(_PIPELINE_INPUT_KEY) is not None
282
+ if at_pipeline_level:
283
+ span.raw_span().update(
284
+ input=span.get_data().get(_PIPELINE_INPUT_KEY), output=span.get_data().get(_PIPELINE_OUTPUT_KEY)
285
+ )
286
+
265
287
  if component_type in _SUPPORTED_GENERATORS:
266
- meta = span._data.get(_COMPONENT_OUTPUT_KEY, {}).get("meta")
288
+ meta = span.get_data().get(_COMPONENT_OUTPUT_KEY, {}).get("meta")
267
289
  if meta:
268
- m = meta[0]
269
- span._span.update(usage=m.get("usage") or None, model=m.get("model"))
270
- elif component_type in _SUPPORTED_CHAT_GENERATORS:
271
- replies = span._data.get(_COMPONENT_OUTPUT_KEY, {}).get("replies")
290
+ span.raw_span().update(usage=meta[0].get("usage") or None, model=meta[0].get("model"))
291
+
292
+ if component_type in _SUPPORTED_CHAT_GENERATORS:
293
+ replies = span.get_data().get(_COMPONENT_OUTPUT_KEY, {}).get("replies")
272
294
  if replies:
273
295
  meta = replies[0].meta
274
296
  completion_start_time = meta.get("completion_start_time")
@@ -278,7 +300,7 @@ class DefaultSpanHandler(SpanHandler):
278
300
  except ValueError:
279
301
  logger.error(f"Failed to parse completion_start_time: {completion_start_time}")
280
302
  completion_start_time = None
281
- span._span.update(
303
+ span.raw_span().update(
282
304
  usage=meta.get("usage") or None,
283
305
  model=meta.get("model"),
284
306
  completion_start_time=completion_start_time,
@@ -380,3 +402,10 @@ class LangfuseTracer(Tracer):
380
402
  :return: The URL to the tracing data.
381
403
  """
382
404
  return self._tracer.get_trace_url()
405
+
406
+ def get_trace_id(self) -> str:
407
+ """
408
+ Return the trace ID.
409
+ :return: The trace ID.
410
+ """
411
+ return self._tracer.get_trace_id()
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  import os
2
6
 
3
7
  os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  import datetime
2
6
  import logging
3
7
  import sys
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  import os
2
6
  import time
3
7
  from urllib.parse import urlparse
@@ -11,7 +15,6 @@ from haystack.components.generators.chat import OpenAIChatGenerator
11
15
  from haystack.dataclasses import ChatMessage
12
16
  from haystack.utils import Secret
13
17
  from requests.auth import HTTPBasicAuth
14
- import httpx
15
18
 
16
19
  from haystack_integrations.components.connectors.langfuse import LangfuseConnector
17
20
  from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
@@ -23,51 +26,34 @@ from haystack_integrations.tracing.langfuse.tracer import _COMPONENT_OUTPUT_KEY
23
26
  os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
24
27
 
25
28
 
26
- @pytest.fixture
27
- def pipeline_with_env_vars(llm_class, expected_trace):
28
- """Pipeline factory using environment variables for Langfuse authentication"""
29
- pipe = Pipeline()
30
- pipe.add_component("tracer", LangfuseConnector(name=f"Chat example - {expected_trace}", public=True))
31
- pipe.add_component("prompt_builder", ChatPromptBuilder())
32
- pipe.add_component("llm", llm_class())
33
- pipe.connect("prompt_builder.prompt", "llm.messages")
34
- return pipe
29
+ def poll_langfuse(url: str):
30
+ """Utility function to poll Langfuse API until the trace is ready"""
31
+ # Initial wait for trace creation
32
+ time.sleep(10)
35
33
 
34
+ auth = HTTPBasicAuth(os.environ["LANGFUSE_PUBLIC_KEY"], os.environ["LANGFUSE_SECRET_KEY"])
36
35
 
37
- @pytest.fixture
38
- def pipeline_with_secrets(llm_class, expected_trace):
39
- """Pipeline factory using Secret objects for Langfuse authentication"""
40
- pipe = Pipeline()
41
- pipe.add_component(
42
- "tracer",
43
- LangfuseConnector(
44
- name=f"Chat example - {expected_trace}",
45
- public=True,
46
- secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"),
47
- public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"),
48
- ),
49
- )
50
- pipe.add_component("prompt_builder", ChatPromptBuilder())
51
- pipe.add_component("llm", llm_class())
52
- pipe.connect("prompt_builder.prompt", "llm.messages")
53
- return pipe
36
+ attempts = 5
37
+ delay = 1
38
+
39
+ res = None
40
+ while attempts > 0:
41
+ res = requests.get(url, auth=auth)
42
+ if res.status_code == 200:
43
+ return res
44
+
45
+ attempts -= 1
46
+ if attempts > 0:
47
+ time.sleep(delay)
48
+ delay *= 2
49
+
50
+ return res
54
51
 
55
52
 
56
53
  @pytest.fixture
57
- def pipeline_with_custom_client(llm_class, expected_trace):
58
- """Pipeline factory using custom httpx client for Langfuse"""
54
+ def pipeline_with_env_vars(llm_class, expected_trace):
59
55
  pipe = Pipeline()
60
- custom_client = httpx.Client(timeout=30.0) # Custom timeout of 30 seconds
61
- pipe.add_component(
62
- "tracer",
63
- LangfuseConnector(
64
- name=f"Chat example - {expected_trace}",
65
- public=True,
66
- secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"),
67
- public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"),
68
- httpx_client=custom_client,
69
- ),
70
- )
56
+ pipe.add_component("tracer", LangfuseConnector(name=f"Chat example - {expected_trace}", public=True))
71
57
  pipe.add_component("prompt_builder", ChatPromptBuilder())
72
58
  pipe.add_component("llm", llm_class())
73
59
  pipe.connect("prompt_builder.prompt", "llm.messages")
@@ -83,9 +69,7 @@ def pipeline_with_custom_client(llm_class, expected_trace):
83
69
  (CohereChatGenerator, "COHERE_API_KEY", "Cohere"),
84
70
  ],
85
71
  )
86
- @pytest.mark.parametrize(
87
- "pipeline_fixture", ["pipeline_with_env_vars", "pipeline_with_secrets", "pipeline_with_custom_client"]
88
- )
72
+ @pytest.mark.parametrize("pipeline_fixture", ["pipeline_with_env_vars"])
89
73
  def test_tracing_integration(llm_class, env_var, expected_trace, pipeline_fixture, request):
90
74
  if not all([os.environ.get("LANGFUSE_SECRET_KEY"), os.environ.get("LANGFUSE_PUBLIC_KEY"), os.environ.get(env_var)]):
91
75
  pytest.skip(f"Missing required environment variables: LANGFUSE_SECRET_KEY, LANGFUSE_PUBLIC_KEY, or {env_var}")
@@ -104,32 +88,23 @@ def test_tracing_integration(llm_class, env_var, expected_trace, pipeline_fixtur
104
88
  )
105
89
  assert "Berlin" in response["llm"]["replies"][0].text
106
90
  assert response["tracer"]["trace_url"]
91
+ assert response["tracer"]["trace_id"]
107
92
 
108
93
  trace_url = response["tracer"]["trace_url"]
109
94
  uuid = os.path.basename(urlparse(trace_url).path)
110
95
  url = f"https://cloud.langfuse.com/api/public/traces/{uuid}"
111
96
 
112
- # Poll the Langfuse API a bit as the trace might not be ready right away
113
- attempts = 5
114
- delay = 1
115
- while attempts >= 0:
116
- res = requests.get(
117
- url, auth=HTTPBasicAuth(os.environ["LANGFUSE_PUBLIC_KEY"], os.environ["LANGFUSE_SECRET_KEY"])
118
- )
119
- if attempts > 0 and res.status_code != 200:
120
- attempts -= 1
121
- time.sleep(delay)
122
- delay *= 2
123
- continue
124
- assert res.status_code == 200, f"Failed to retrieve data from Langfuse API: {res.status_code}"
97
+ res = poll_langfuse(url)
98
+ assert res.status_code == 200, f"Failed to retrieve data from Langfuse API: {res.status_code}"
125
99
 
126
- # check if the trace contains the expected LLM name
127
- assert expected_trace in str(res.content)
128
- # check if the trace contains the expected generation span
129
- assert "GENERATION" in str(res.content)
130
- # check if the trace contains the expected user_id
131
- assert "user_42" in str(res.content)
132
- break
100
+ res_json = res.json()
101
+ assert res_json["name"] == f"Chat example - {expected_trace}"
102
+ assert isinstance(res_json["input"], dict)
103
+ assert res_json["input"]["tracer"]["invocation_context"]["user_id"] == "user_42"
104
+ assert isinstance(res_json["output"], dict)
105
+ assert isinstance(res_json["metadata"], dict)
106
+ assert isinstance(res_json["observations"], list)
107
+ assert res_json["observations"][0]["type"] == "GENERATION"
133
108
 
134
109
 
135
110
  def test_pipeline_serialization(monkeypatch):
@@ -237,21 +212,9 @@ def test_custom_span_handler():
237
212
  uuid = os.path.basename(urlparse(trace_url).path)
238
213
  url = f"https://cloud.langfuse.com/api/public/traces/{uuid}"
239
214
 
240
- # Poll the Langfuse API
241
- attempts = 5
242
- delay = 1
243
- while attempts >= 0:
244
- res = requests.get(
245
- url, auth=HTTPBasicAuth(os.environ["LANGFUSE_PUBLIC_KEY"], os.environ["LANGFUSE_SECRET_KEY"])
246
- )
247
- if attempts > 0 and res.status_code != 200:
248
- attempts -= 1
249
- time.sleep(delay)
250
- delay *= 2
251
- continue
215
+ res = poll_langfuse(url)
216
+ assert res.status_code == 200, f"Failed to retrieve data from Langfuse API: {res.status_code}"
252
217
 
253
- assert res.status_code == 200
254
- content = str(res.content)
255
- assert "WARNING" in content
256
- assert "Response too long" in content
257
- break
218
+ content = str(res.content)
219
+ assert "WARNING" in content
220
+ assert "Response too long" in content