langfuse-haystack 3.0.0__tar.gz → 3.1.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 (22) hide show
  1. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/CHANGELOG.md +6 -0
  2. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/PKG-INFO +3 -3
  3. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/example/basic_rag.py +2 -2
  4. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/example/chat.py +2 -3
  5. langfuse_haystack-3.1.0/pydoc/config_docusaurus.yml +29 -0
  6. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/pyproject.toml +7 -28
  7. langfuse_haystack-3.1.0/src/haystack_integrations/components/connectors/py.typed +0 -0
  8. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/src/haystack_integrations/tracing/langfuse/tracer.py +3 -2
  9. langfuse_haystack-3.1.0/src/haystack_integrations/tracing/py.typed +0 -0
  10. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/tests/test_tracer.py +21 -30
  11. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/tests/test_tracing.py +15 -6
  12. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/.gitignore +0 -0
  13. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/LICENSE.txt +0 -0
  14. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/README.md +0 -0
  15. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/example/requirements.txt +0 -0
  16. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/pydoc/config.yml +0 -0
  17. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/src/haystack_integrations/components/connectors/__init__.py +0 -0
  18. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
  19. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +0 -0
  20. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
  21. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/tests/__init__.py +0 -0
  22. {langfuse_haystack-3.0.0 → langfuse_haystack-3.1.0}/tests/test_langfuse_connector.py +0 -0
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/langfuse-v3.0.0] - 2025-09-19
4
+
5
+ ### 🌀 Miscellaneous
6
+
7
+ - Migrate langfuse to v3 (#2247)
8
+
3
9
  ## [integrations/langfuse-v2.3.0] - 2025-08-25
4
10
 
5
11
  ### 🐛 Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langfuse-haystack
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: Langfuse integration for Haystack
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -18,8 +18,8 @@ Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: Implementation :: CPython
19
19
  Classifier: Programming Language :: Python :: Implementation :: PyPy
20
20
  Requires-Python: >=3.9
21
- Requires-Dist: haystack-ai>=2.15.1
22
- Requires-Dist: langfuse<4.0.0,>=3.0.0
21
+ Requires-Dist: haystack-ai>=2.17.1
22
+ Requires-Dist: langfuse<4.0.0,>=3.3.0
23
23
  Description-Content-Type: text/markdown
24
24
 
25
25
  # langfuse-haystack
@@ -62,5 +62,5 @@ if __name__ == "__main__":
62
62
  question = "What does Rhodes Statue look like?"
63
63
  response = pipeline.run({"text_embedder": {"text": question}, "prompt_builder": {"question": question}})
64
64
 
65
- print(response["llm"]["replies"][0])
66
- print(response["tracer"]["trace_url"])
65
+ print(response["llm"]["replies"][0]) # noqa: T201
66
+ print(response["tracer"]["trace_url"]) # noqa: T201
@@ -36,7 +36,6 @@ generators = {
36
36
  selected_chat_generator = generators[selected_chat_generator]()
37
37
 
38
38
  if __name__ == "__main__":
39
-
40
39
  pipe = Pipeline()
41
40
  pipe.add_component("tracer", LangfuseConnector("Chat example"))
42
41
  pipe.add_component("prompt_builder", ChatPromptBuilder())
@@ -60,5 +59,5 @@ if __name__ == "__main__":
60
59
  },
61
60
  }
62
61
  )
63
- print(response["llm"]["replies"][0])
64
- print(response["tracer"]["trace_url"])
62
+ print(response["llm"]["replies"][0]) # noqa: T201
63
+ print(response["tracer"]["trace_url"]) # noqa: T201
@@ -0,0 +1,29 @@
1
+ loaders:
2
+ - ignore_when_discovered:
3
+ - __init__
4
+ modules:
5
+ - haystack_integrations.components.connectors.langfuse.langfuse_connector
6
+ - haystack_integrations.tracing.langfuse.tracer
7
+ search_path:
8
+ - ../src
9
+ type: haystack_pydoc_tools.loaders.CustomPythonLoader
10
+ processors:
11
+ - do_not_filter_modules: false
12
+ documented_only: true
13
+ expression: null
14
+ skip_empty_modules: true
15
+ type: filter
16
+ - type: smart
17
+ - type: crossref
18
+ renderer:
19
+ description: Langfuse integration for Haystack
20
+ id: integrations-langfuse
21
+ markdown:
22
+ add_member_class_prefix: false
23
+ add_method_class_prefix: true
24
+ classdef_code_block: false
25
+ descriptive_class_title: false
26
+ descriptive_module_title: true
27
+ filename: langfuse.md
28
+ title: langfuse
29
+ type: haystack_pydoc_tools.renderers.DocusaurusRenderer
@@ -22,7 +22,7 @@ classifiers = [
22
22
  "Programming Language :: Python :: Implementation :: CPython",
23
23
  "Programming Language :: Python :: Implementation :: PyPy",
24
24
  ]
25
- dependencies = ["haystack-ai>=2.15.1", "langfuse>=3.0.0, <4.0.0"]
25
+ dependencies = ["haystack-ai>=2.17.1", "langfuse>=3.3.0, <4.0.0"]
26
26
 
27
27
  [project.urls]
28
28
  Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse#readme"
@@ -69,22 +69,13 @@ integration = 'pytest -m "integration" {args:tests}'
69
69
  all = 'pytest {args:tests}'
70
70
  cov-retry = 'all --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x'
71
71
 
72
- types = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
72
+ types = "mypy -p haystack_integrations.components.connectors.langfuse -p haystack_integrations.tracing.langfuse {args}"
73
73
 
74
- # TODO: remove lint environment once this integration is properly typed
75
- # test environment should be used instead
76
- # https://github.com/deepset-ai/haystack-core-integrations/issues/1771
77
- [tool.hatch.envs.lint]
78
- installer = "uv"
79
- detached = true
80
- dependencies = [
81
- "pip",
82
- "mypy>=1.0.0",
83
- "ruff>=0.0.243",
84
- ]
85
-
86
- [tool.hatch.envs.lint.scripts]
87
- typing = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
74
+ [tool.mypy]
75
+ install_types = true
76
+ non_interactive = true
77
+ check_untyped_defs = true
78
+ disallow_incomplete_defs = true
88
79
 
89
80
  [tool.hatch.metadata]
90
81
  allow-direct-references = true
@@ -93,7 +84,6 @@ allow-direct-references = true
93
84
  [tool.ruff]
94
85
  target-version = "py38"
95
86
  line-length = 120
96
- exclude = ["example", "tests"]
97
87
 
98
88
  [tool.ruff.lint]
99
89
  select = [
@@ -167,17 +157,6 @@ omit = ["*/tests/*", "*/__init__.py"]
167
157
  show_missing = true
168
158
  exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
169
159
 
170
- [[tool.mypy.overrides]]
171
- module = [
172
- "langfuse.*",
173
- "haystack.*",
174
- "haystack_integrations.*",
175
- "pytest.*",
176
- "numpy.*",
177
- "httpx.*",
178
- ]
179
- ignore_missing_imports = true
180
-
181
160
  [tool.pytest.ini_options]
182
161
  addopts = "--strict-markers"
183
162
  markers = ["integration: integration tests"]
@@ -289,6 +289,7 @@ class DefaultSpanHandler(SpanHandler):
289
289
  "metadata": None,
290
290
  "tags": tracing_ctx.get("tags"),
291
291
  "public": context.public,
292
+ "release": None,
292
293
  }
293
294
 
294
295
  # Filter out None values and apply trace attributes
@@ -465,11 +466,11 @@ class LangfuseTracer(Tracer):
465
466
  Return the URL to the tracing data.
466
467
  :return: The URL to the tracing data.
467
468
  """
468
- return self._tracer.get_trace_url()
469
+ return self._tracer.get_trace_url() or ""
469
470
 
470
471
  def get_trace_id(self) -> str:
471
472
  """
472
473
  Return the trace ID.
473
474
  :return: The trace ID.
474
475
  """
475
- return self._tracer.get_current_trace_id()
476
+ return self._tracer.get_current_trace_id() or ""
@@ -6,12 +6,10 @@ import asyncio
6
6
  import datetime
7
7
  import logging
8
8
  import sys
9
- import json
10
9
  from typing import Optional
11
10
  from unittest.mock import MagicMock, Mock, patch
12
11
 
13
12
  import pytest
14
- from haystack import Pipeline, component
15
13
  from haystack.dataclasses import ChatMessage, ToolCall
16
14
 
17
15
  from haystack_integrations.tracing.langfuse.tracer import (
@@ -21,7 +19,6 @@ from haystack_integrations.tracing.langfuse.tracer import (
21
19
  LangfuseTracer,
22
20
  SpanContext,
23
21
  )
24
- from haystack_integrations.components.connectors.langfuse import LangfuseConnector
25
22
 
26
23
 
27
24
  # Mock functions for Langfuse v3 API
@@ -78,7 +75,7 @@ class MockSpan:
78
75
 
79
76
 
80
77
  class MockTracer:
81
- def trace(self, name, **kwargs):
78
+ def trace(self, name, **kwargs): # noqa: ARG002
82
79
  # Return a unique mock span for each trace call
83
80
  return MockSpan(name=name)
84
81
 
@@ -92,10 +89,10 @@ class MockLangfuseClient:
92
89
  def __init__(self):
93
90
  self._mock_context_manager = MockContextManager()
94
91
 
95
- def start_as_current_span(self, name=None, **kwargs):
92
+ def start_as_current_span(self, _name=None, **_kwargs):
96
93
  return self._mock_context_manager
97
94
 
98
- def start_as_current_observation(self, name=None, as_type=None, **kwargs):
95
+ def start_as_current_observation(self, _name=None, _as_type=None, **_kwargs):
99
96
  return self._mock_context_manager
100
97
 
101
98
  def get_current_trace_id(self):
@@ -230,7 +227,9 @@ class TestDefaultSpanHandler:
230
227
  assert mock_span.update.call_args_list[0][1] == {
231
228
  "usage": None,
232
229
  "model": "test_model",
233
- "completion_start_time": datetime.datetime(2021, 7, 27, 16, 2, 8, 12345),
230
+ "completion_start_time": datetime.datetime( # noqa: DTZ001
231
+ 2021, 7, 27, 16, 2, 8, 12345
232
+ ),
234
233
  }
235
234
 
236
235
  def test_handle_bad_completion_start_time(self, caplog):
@@ -302,18 +301,15 @@ class TestLangfuseTracer:
302
301
  mock_raw_span.operation_name = "operation_name"
303
302
  mock_raw_span.metadata = {"tag1": "value1", "tag2": "value2"}
304
303
 
305
- with patch("haystack_integrations.tracing.langfuse.tracer.LangfuseSpan") as MockLangfuseSpan, patch(
306
- "haystack_integrations.tracing.langfuse.tracer.langfuse.get_client"
307
- ) as mock_get_client:
308
- mock_span_instance = MockLangfuseSpan.return_value
304
+ with patch("haystack_integrations.tracing.langfuse.tracer.LangfuseSpan") as mock_langfuse_span:
305
+ mock_span_instance = mock_langfuse_span.return_value
309
306
  mock_span_instance.raw_span.return_value = mock_raw_span
310
307
 
311
- mock_client = mock_get_client()
312
308
  mock_context_manager = MockContextManager()
313
309
  mock_context_manager._span = mock_raw_span
314
- mock_client.start_as_current_span.return_value = mock_context_manager
315
-
316
310
  mock_tracer = MagicMock()
311
+ mock_tracer.start_as_current_span.return_value = mock_context_manager
312
+
317
313
  tracer = LangfuseTracer(tracer=mock_tracer, name="Haystack", public=False)
318
314
 
319
315
  # check that the trace method is called on the tracer instance with the provided operation name and tags
@@ -329,9 +325,7 @@ class TestLangfuseTracer:
329
325
 
330
326
  # check that update method is called on the span instance with the provided key value pairs
331
327
  def test_update_span_with_pipeline_input_output_data(self):
332
- with patch("haystack_integrations.tracing.langfuse.tracer.langfuse.get_client") as mock_get_client:
333
- mock_client = mock_get_client()
334
-
328
+ with patch("haystack_integrations.tracing.langfuse.tracer.langfuse.get_client"):
335
329
  tracer = LangfuseTracer(tracer=MockLangfuseClient(), name="Haystack", public=False)
336
330
  with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
337
331
  assert span.raw_span()._data["metadata"] == {"haystack.pipeline.input_data": "hello"}
@@ -340,9 +334,7 @@ class TestLangfuseTracer:
340
334
  assert span.raw_span()._data["metadata"] == {"haystack.pipeline.output_data": "bye"}
341
335
 
342
336
  def test_trace_generation(self):
343
- with patch("haystack_integrations.tracing.langfuse.tracer.langfuse.get_client") as mock_get_client:
344
- mock_client = mock_get_client()
345
-
337
+ with patch("haystack_integrations.tracing.langfuse.tracer.langfuse.get_client"):
346
338
  tracer = LangfuseTracer(tracer=MockLangfuseClient(), name="Haystack", public=False)
347
339
  tags = {
348
340
  "haystack.component.type": "OpenAIChatGenerator",
@@ -358,7 +350,7 @@ class TestLangfuseTracer:
358
350
  ...
359
351
  assert span.raw_span()._data["usage"] is None
360
352
  assert span.raw_span()._data["model"] == "test_model"
361
- assert span.raw_span()._data["completion_start_time"] == datetime.datetime(2021, 7, 27, 16, 2, 8, 12345)
353
+ assert span.raw_span()._data["completion_start_time"] == datetime.datetime(2021, 7, 27, 16, 2, 8, 12345) # noqa: DTZ001
362
354
 
363
355
  def test_handle_tool_invoker(self):
364
356
  """
@@ -402,7 +394,7 @@ class TestLangfuseTracer:
402
394
  updated_name = name_update_call[1]["name"]
403
395
 
404
396
  # verify the format of the updated span name to be: `original_component_name - [list_of_tool_names]`
405
- assert updated_name != "tool_invoker", f"Expected 'tool_invoker` to be upddated with tool names"
397
+ assert updated_name != "tool_invoker", "Expected 'tool_invoker` to be upddated with tool names"
406
398
  assert " - " in updated_name, f"Expected ' - ' in {updated_name}"
407
399
  assert "[" in updated_name, f"Expected '[' in {updated_name}"
408
400
  assert "]" in updated_name, f"Expected ']' in {updated_name}"
@@ -411,9 +403,7 @@ class TestLangfuseTracer:
411
403
  assert "weather_tool" in updated_name, f"Expected 'weather_tool' in {updated_name}"
412
404
 
413
405
  def test_trace_generation_invalid_start_time(self):
414
- with patch("haystack_integrations.tracing.langfuse.tracer.langfuse.get_client") as mock_get_client:
415
- mock_client = mock_get_client()
416
-
406
+ with patch("haystack_integrations.tracing.langfuse.tracer.langfuse.get_client"):
417
407
  tracer = LangfuseTracer(tracer=MockLangfuseClient(), name="Haystack", public=False)
418
408
  tags = {
419
409
  "haystack.component.type": "OpenAIChatGenerator",
@@ -434,7 +424,7 @@ class TestLangfuseTracer:
434
424
  tracer_mock.flush = Mock() # Make flush a mock for assertions
435
425
 
436
426
  tracer = LangfuseTracer(tracer=tracer_mock, name="Haystack", public=False)
437
- with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
427
+ with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}):
438
428
  pass
439
429
 
440
430
  tracer_mock.flush.assert_called_once()
@@ -444,10 +434,11 @@ class TestLangfuseTracer:
444
434
  tracer_mock = MockLangfuseClient()
445
435
  tracer_mock.flush = Mock() # Make flush a mock for assertions
446
436
 
447
- from haystack_integrations.tracing.langfuse.tracer import LangfuseTracer
437
+ # Re-import LangfuseTracer to ensure it picks up the new environment variable
438
+ from haystack_integrations.tracing.langfuse.tracer import LangfuseTracer # noqa: PLC0415
448
439
 
449
440
  tracer = LangfuseTracer(tracer=tracer_mock, name="Haystack", public=False)
450
- with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
441
+ with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}):
451
442
  pass
452
443
 
453
444
  tracer_mock.flush.assert_not_called()
@@ -456,7 +447,7 @@ class TestLangfuseTracer:
456
447
  tracer_mock = MockLangfuseClient()
457
448
 
458
449
  tracer = LangfuseTracer(tracer=tracer_mock, name="Haystack", public=False)
459
- with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
450
+ with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}):
460
451
  pass
461
452
 
462
453
  # Check behavioral state instead of internal _context list
@@ -471,7 +462,7 @@ class TestLangfuseTracer:
471
462
  # Re-import LangfuseTracer and instantiate it with tracing disabled
472
463
  with caplog.at_level(logging.WARNING):
473
464
  monkeypatch.setenv("HAYSTACK_CONTENT_TRACING_ENABLED", "false")
474
- from haystack_integrations.tracing.langfuse import LangfuseTracer
465
+ from haystack_integrations.tracing.langfuse import LangfuseTracer # noqa: PLC0415
475
466
 
476
467
  LangfuseTracer(tracer=MockLangfuseClient(), name="Haystack", public=False)
477
468
  assert "tracing is disabled" in caplog.text
@@ -2,11 +2,11 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ import json
5
6
  import os
6
7
  import time
7
8
  from typing import Any, Dict, List
8
9
  from urllib.parse import urlparse
9
- import json
10
10
 
11
11
  import pytest
12
12
  import requests
@@ -17,8 +17,17 @@ from haystack.dataclasses import ChatMessage
17
17
  from requests.auth import HTTPBasicAuth
18
18
 
19
19
  from haystack_integrations.components.connectors.langfuse import LangfuseConnector
20
- from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
21
- from haystack_integrations.components.generators.cohere import CohereChatGenerator
20
+
21
+ # We use the try/except block to prevent import errors when running unit tests with lowest direct dependencies,
22
+ # where the subdependencies of LLM integrations might not be respected.
23
+ try:
24
+ from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
25
+ except ImportError:
26
+ AnthropicChatGenerator = None
27
+ try:
28
+ from haystack_integrations.components.generators.cohere import CohereChatGenerator
29
+ except ImportError:
30
+ CohereChatGenerator = None
22
31
 
23
32
  # don't remove (or move) this env var setting from here, it's needed to turn tracing on
24
33
  os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
@@ -38,7 +47,7 @@ def poll_langfuse(url: str):
38
47
 
39
48
  res = None
40
49
  while attempts > 0:
41
- res = requests.get(url, auth=auth)
50
+ res = requests.get(url, auth=auth, timeout=60.0)
42
51
  if res.status_code == 200:
43
52
  return res
44
53
 
@@ -73,7 +82,7 @@ def basic_pipeline(llm_class, expected_trace):
73
82
  (CohereChatGenerator, "COHERE_API_KEY", "Cohere"),
74
83
  ],
75
84
  )
76
- def test_tracing_integration(llm_class, env_var, expected_trace, basic_pipeline):
85
+ def test_tracing_integration(env_var, expected_trace, basic_pipeline):
77
86
  if not all([os.environ.get("LANGFUSE_SECRET_KEY"), os.environ.get("LANGFUSE_PUBLIC_KEY"), os.environ.get(env_var)]):
78
87
  pytest.skip(f"Missing required environment variable: {env_var}")
79
88
 
@@ -242,7 +251,7 @@ def test_context_cleanup_after_nested_failures():
242
251
  # Test 1: First run will fail and should clean up context
243
252
  try:
244
253
  main_pipeline.run({"nested_component": {"input_data": "invalid json"}})
245
- except Exception:
254
+ except Exception: # noqa: S110
246
255
  pass # Expected to fail
247
256
 
248
257
  # Critical assertion: context should be empty after failed operation