langfuse-haystack 4.0.0__tar.gz → 4.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.
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/CHANGELOG.md +15 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/PKG-INFO +1 -1
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/example/basic_rag.py +2 -2
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/example/chat.py +2 -2
- langfuse_haystack-4.1.0/pydoc/config_docusaurus.yml +14 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/pyproject.toml +14 -5
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +1 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/src/haystack_integrations/tracing/langfuse/tracer.py +18 -2
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/tests/test_tracer.py +55 -3
- langfuse_haystack-4.0.0/pydoc/config_docusaurus.yml +0 -29
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/.gitignore +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/LICENSE.txt +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/README.md +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/example/requirements.txt +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/src/haystack_integrations/components/connectors/__init__.py +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/src/haystack_integrations/components/connectors/py.typed +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/src/haystack_integrations/tracing/py.typed +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/tests/__init__.py +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/tests/test_langfuse_connector.py +0 -0
- {langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/tests/test_tracing.py +0 -0
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/langfuse-v4.0.1] - 2026-01-27
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- *(langfuse)* Apply trace_id from tracing context to Langfuse traces (#2781)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [integrations/langfuse-v4.0.0] - 2026-01-12
|
|
11
|
+
|
|
12
|
+
### 🧹 Chores
|
|
13
|
+
|
|
14
|
+
- Make fmt command more forgiving (#2671)
|
|
15
|
+
- [**breaking**] Langfuse - drop Python 3.9 and use X|Y typing (#2709)
|
|
16
|
+
|
|
17
|
+
|
|
3
18
|
## [integrations/langfuse-v3.3.1] - 2025-12-10
|
|
4
19
|
|
|
5
20
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langfuse-haystack
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.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
|
|
@@ -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])
|
|
66
|
+
print(response["tracer"]["trace_url"])
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
loaders:
|
|
2
|
+
- modules:
|
|
3
|
+
- haystack_integrations.components.connectors.langfuse.langfuse_connector
|
|
4
|
+
- haystack_integrations.tracing.langfuse.tracer
|
|
5
|
+
search_path: [../src]
|
|
6
|
+
processors:
|
|
7
|
+
- type: filter
|
|
8
|
+
documented_only: true
|
|
9
|
+
skip_empty_modules: true
|
|
10
|
+
renderer:
|
|
11
|
+
description: Langfuse integration for Haystack
|
|
12
|
+
id: integrations-langfuse
|
|
13
|
+
filename: langfuse.md
|
|
14
|
+
title: langfuse
|
|
@@ -45,7 +45,7 @@ installer = "uv"
|
|
|
45
45
|
dependencies = ["haystack-pydoc-tools", "ruff"]
|
|
46
46
|
|
|
47
47
|
[tool.hatch.envs.default.scripts]
|
|
48
|
-
docs = ["pydoc
|
|
48
|
+
docs = ["haystack-pydoc pydoc/config_docusaurus.yml"]
|
|
49
49
|
fmt = "ruff check --fix {args}; ruff format {args}"
|
|
50
50
|
fmt-check = "ruff check {args} && ruff format --check {args}"
|
|
51
51
|
|
|
@@ -76,8 +76,6 @@ non_interactive = true
|
|
|
76
76
|
check_untyped_defs = true
|
|
77
77
|
disallow_incomplete_defs = true
|
|
78
78
|
|
|
79
|
-
[tool.hatch.metadata]
|
|
80
|
-
allow-direct-references = true
|
|
81
79
|
|
|
82
80
|
|
|
83
81
|
[tool.ruff]
|
|
@@ -86,9 +84,17 @@ line-length = 120
|
|
|
86
84
|
[tool.ruff.lint]
|
|
87
85
|
select = [
|
|
88
86
|
"A",
|
|
87
|
+
"ANN",
|
|
89
88
|
"ARG",
|
|
90
89
|
"B",
|
|
91
90
|
"C",
|
|
91
|
+
"D102", # Missing docstring in public method
|
|
92
|
+
"D103", # Missing docstring in public function
|
|
93
|
+
"D205", # 1 blank line required between summary line and description
|
|
94
|
+
"D209", # Closing triple quotes go to new line
|
|
95
|
+
"D213", # summary lines must be positioned on the second physical line of the docstring
|
|
96
|
+
"D417", # Missing argument descriptions in the docstring
|
|
97
|
+
"D419", # Docstring is empty
|
|
92
98
|
"DTZ",
|
|
93
99
|
"E",
|
|
94
100
|
"EM",
|
|
@@ -125,6 +131,8 @@ ignore = [
|
|
|
125
131
|
"PLR0915",
|
|
126
132
|
# Asserts
|
|
127
133
|
"S101",
|
|
134
|
+
# Allow `Any` - used legitimately for dynamic types and SDK boundaries
|
|
135
|
+
"ANN401",
|
|
128
136
|
]
|
|
129
137
|
|
|
130
138
|
[tool.ruff.lint.isort]
|
|
@@ -135,9 +143,10 @@ ban-relative-imports = "parents"
|
|
|
135
143
|
|
|
136
144
|
[tool.ruff.lint.per-file-ignores]
|
|
137
145
|
# Tests can use magic values, assertions, and relative imports
|
|
138
|
-
"tests/**/*" = ["PLR2004", "S101", "TID252"]
|
|
146
|
+
"tests/**/*" = ["D", "PLR2004", "S101", "TID252", "ANN"]
|
|
139
147
|
# Examples can print their output
|
|
140
|
-
"examples/**" = ["T201"]
|
|
148
|
+
"examples/**" = ["T201", "ANN", "D"]
|
|
149
|
+
"example/**" = ["T201", "ANN", "D"]
|
|
141
150
|
"tests/**" = ["T201"]
|
|
142
151
|
|
|
143
152
|
[tool.coverage.run]
|
|
@@ -19,6 +19,7 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
class LangfuseConnector:
|
|
20
20
|
"""
|
|
21
21
|
LangfuseConnector connects Haystack LLM framework with [Langfuse](https://langfuse.com) in order to enable the
|
|
22
|
+
|
|
22
23
|
tracing of operations and data flow within various components of a pipeline.
|
|
23
24
|
|
|
24
25
|
To use LangfuseConnector, add it to your pipeline without connecting it to any other components.
|
|
@@ -22,7 +22,7 @@ from haystack.tracing import utils as tracing_utils
|
|
|
22
22
|
|
|
23
23
|
import langfuse
|
|
24
24
|
from langfuse import LangfuseSpan as LangfuseClientSpan
|
|
25
|
-
from langfuse.types import TraceMetadata
|
|
25
|
+
from langfuse.types import TraceContext, TraceMetadata
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
28
28
|
|
|
@@ -91,7 +91,10 @@ class LangfuseSpan(Span):
|
|
|
91
91
|
if key.endswith(".input"):
|
|
92
92
|
if "messages" in value:
|
|
93
93
|
messages = [m.to_openai_dict_format(require_tool_call_ids=False) for m in value["messages"]]
|
|
94
|
-
|
|
94
|
+
if isinstance(gen_kwargs := value.get("generation_kwargs"), dict):
|
|
95
|
+
self._span.update(input={"messages": messages, "generation_kwargs": gen_kwargs})
|
|
96
|
+
else:
|
|
97
|
+
self._span.update(input=messages)
|
|
95
98
|
else:
|
|
96
99
|
coerced_value = tracing_utils.coerce_tag_value(value)
|
|
97
100
|
self._span.update(input=coerced_value)
|
|
@@ -125,6 +128,7 @@ class LangfuseSpan(Span):
|
|
|
125
128
|
return self._data
|
|
126
129
|
|
|
127
130
|
def get_correlation_data_for_logs(self) -> dict[str, Any]:
|
|
131
|
+
"""Return correlation data for log enrichment."""
|
|
128
132
|
return {}
|
|
129
133
|
|
|
130
134
|
|
|
@@ -234,9 +238,11 @@ class SpanHandler(ABC):
|
|
|
234
238
|
|
|
235
239
|
@classmethod
|
|
236
240
|
def from_dict(cls, data: dict[str, Any]) -> "SpanHandler":
|
|
241
|
+
"""Deserialize a SpanHandler from a dictionary."""
|
|
237
242
|
return default_from_dict(cls, data)
|
|
238
243
|
|
|
239
244
|
def to_dict(self) -> dict[str, Any]:
|
|
245
|
+
"""Serialize this SpanHandler to a dictionary."""
|
|
240
246
|
return default_to_dict(self)
|
|
241
247
|
|
|
242
248
|
|
|
@@ -273,6 +279,7 @@ class DefaultSpanHandler(SpanHandler):
|
|
|
273
279
|
"""DefaultSpanHandler provides the default Langfuse tracing behavior for Haystack."""
|
|
274
280
|
|
|
275
281
|
def create_span(self, context: SpanContext) -> LangfuseSpan:
|
|
282
|
+
"""Create a Langfuse span based on the given context."""
|
|
276
283
|
if self.tracer is None:
|
|
277
284
|
message = (
|
|
278
285
|
"Tracer is not initialized. "
|
|
@@ -287,8 +294,12 @@ class DefaultSpanHandler(SpanHandler):
|
|
|
287
294
|
root_span_type: Literal["agent", "span"] = (
|
|
288
295
|
"agent" if context.operation_name == "haystack.agent.run" else "span"
|
|
289
296
|
)
|
|
297
|
+
trace_context: TraceContext | None = cast(
|
|
298
|
+
TraceContext, {"trace_id": tracing_ctx.get("trace_id")} if tracing_ctx.get("trace_id") else None
|
|
299
|
+
)
|
|
290
300
|
# Create a new trace when there's no parent span
|
|
291
301
|
span_context_manager = self.tracer.start_as_current_observation(
|
|
302
|
+
trace_context=trace_context,
|
|
292
303
|
name=context.trace_name,
|
|
293
304
|
version=tracing_ctx.get("version"),
|
|
294
305
|
as_type=root_span_type,
|
|
@@ -339,6 +350,7 @@ class DefaultSpanHandler(SpanHandler):
|
|
|
339
350
|
return LangfuseSpan(self.tracer.start_as_current_span(name=context.name))
|
|
340
351
|
|
|
341
352
|
def handle(self, span: LangfuseSpan, component_type: str | None) -> None:
|
|
353
|
+
"""Process and enrich a span after component execution."""
|
|
342
354
|
# If the span is at the pipeline level, we add input and output keys to the span
|
|
343
355
|
at_pipeline_level = span.get_data().get(_PIPELINE_INPUT_KEY) is not None
|
|
344
356
|
if at_pipeline_level:
|
|
@@ -452,6 +464,7 @@ class LangfuseTracer(Tracer):
|
|
|
452
464
|
def trace(
|
|
453
465
|
self, operation_name: str, tags: dict[str, Any] | None = None, parent_span: Span | None = None
|
|
454
466
|
) -> Iterator[Span]:
|
|
467
|
+
"""Create and manage a tracing span as a context manager."""
|
|
455
468
|
tags = tags or {}
|
|
456
469
|
span_name = tags.get(_COMPONENT_NAME_KEY, operation_name)
|
|
457
470
|
component_type = tags.get(_COMPONENT_TYPE_KEY)
|
|
@@ -539,6 +552,7 @@ class LangfuseTracer(Tracer):
|
|
|
539
552
|
self.flush()
|
|
540
553
|
|
|
541
554
|
def flush(self) -> None:
|
|
555
|
+
"""Flush all pending spans to Langfuse."""
|
|
542
556
|
self._tracer.flush()
|
|
543
557
|
|
|
544
558
|
def current_span(self) -> Span | None:
|
|
@@ -554,6 +568,7 @@ class LangfuseTracer(Tracer):
|
|
|
554
568
|
def get_trace_url(self) -> str:
|
|
555
569
|
"""
|
|
556
570
|
Return the URL to the tracing data.
|
|
571
|
+
|
|
557
572
|
:return: The URL to the tracing data.
|
|
558
573
|
"""
|
|
559
574
|
return self._tracer.get_trace_url() or ""
|
|
@@ -561,6 +576,7 @@ class LangfuseTracer(Tracer):
|
|
|
561
576
|
def get_trace_id(self) -> str:
|
|
562
577
|
"""
|
|
563
578
|
Return the trace ID.
|
|
579
|
+
|
|
564
580
|
:return: The trace ID.
|
|
565
581
|
"""
|
|
566
582
|
return self._tracer.get_current_trace_id() or ""
|
|
@@ -18,6 +18,7 @@ from haystack_integrations.tracing.langfuse.tracer import (
|
|
|
18
18
|
LangfuseTracer,
|
|
19
19
|
SpanContext,
|
|
20
20
|
_sanitize_usage_data,
|
|
21
|
+
tracing_context_var,
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
|
|
@@ -148,7 +149,6 @@ class TestLangfuseSpan:
|
|
|
148
149
|
def test_set_content_tag_updates_input_and_output_with_messages(self):
|
|
149
150
|
mock_context_manager = MockContextManager()
|
|
150
151
|
|
|
151
|
-
# test message input
|
|
152
152
|
span = LangfuseSpan(mock_context_manager)
|
|
153
153
|
span.set_content_tag("key.input", {"messages": [ChatMessage.from_user("message")]})
|
|
154
154
|
assert mock_context_manager._span.update.call_count == 1
|
|
@@ -156,6 +156,20 @@ class TestLangfuseSpan:
|
|
|
156
156
|
assert mock_context_manager._span.update.call_args_list[0][1] == {
|
|
157
157
|
"input": [{"role": "user", "content": "message"}]
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
# test generation_kwargs are captured
|
|
161
|
+
mock_context_manager._span.update.reset_mock()
|
|
162
|
+
span.set_content_tag(
|
|
163
|
+
"key.input",
|
|
164
|
+
{"messages": [ChatMessage.from_user("message")], "generation_kwargs": {"n": 2, "temperature": 0.7}},
|
|
165
|
+
)
|
|
166
|
+
assert mock_context_manager._span.update.call_count == 1
|
|
167
|
+
assert mock_context_manager._span.update.call_args_list[0][1] == {
|
|
168
|
+
"input": {
|
|
169
|
+
"messages": [{"role": "user", "content": "message"}],
|
|
170
|
+
"generation_kwargs": {"n": 2, "temperature": 0.7},
|
|
171
|
+
}
|
|
172
|
+
}
|
|
159
173
|
# test replies ChatMessage list
|
|
160
174
|
mock_context_manager._span.update.reset_mock()
|
|
161
175
|
span.set_content_tag("key.output", {"replies": [ChatMessage.from_system("reply")]})
|
|
@@ -222,6 +236,44 @@ class TestSanitizeUsageData:
|
|
|
222
236
|
|
|
223
237
|
|
|
224
238
|
class TestDefaultSpanHandler:
|
|
239
|
+
def test_create_span_root_trace(self):
|
|
240
|
+
"""Test creating a root span (no parent)."""
|
|
241
|
+
mock_client = Mock()
|
|
242
|
+
mock_client.start_as_current_span = Mock(return_value=MockContextManager())
|
|
243
|
+
mock_client.start_as_current_observation = Mock(return_value=MockContextManager())
|
|
244
|
+
|
|
245
|
+
handler = DefaultSpanHandler()
|
|
246
|
+
handler.init_tracer(mock_client)
|
|
247
|
+
|
|
248
|
+
context = MagicMock(spec=SpanContext)
|
|
249
|
+
context.parent_span = None
|
|
250
|
+
context.trace_name = "test_trace"
|
|
251
|
+
context.operation_name = "haystack.pipeline.run"
|
|
252
|
+
context.public = False
|
|
253
|
+
|
|
254
|
+
# Set up tracing context
|
|
255
|
+
token = tracing_context_var.set(
|
|
256
|
+
{"user_id": "test_user", "session_id": "test_session", "trace_id": "test_trace_id"}
|
|
257
|
+
)
|
|
258
|
+
try:
|
|
259
|
+
result = handler.create_span(context)
|
|
260
|
+
|
|
261
|
+
# Assert tracer was called with correct parameters
|
|
262
|
+
handler.tracer.start_as_current_observation.assert_called_once_with(
|
|
263
|
+
trace_context={"trace_id": "test_trace_id"},
|
|
264
|
+
name="test_trace",
|
|
265
|
+
version=None,
|
|
266
|
+
as_type="span",
|
|
267
|
+
)
|
|
268
|
+
call_kwargs = handler.tracer.start_as_current_observation.call_args[1]
|
|
269
|
+
assert "test_trace" == call_kwargs["name"]
|
|
270
|
+
assert "span" == call_kwargs["as_type"]
|
|
271
|
+
|
|
272
|
+
# Assert result is a LangfuseSpan
|
|
273
|
+
assert isinstance(result, LangfuseSpan)
|
|
274
|
+
finally:
|
|
275
|
+
tracing_context_var.reset(token)
|
|
276
|
+
|
|
225
277
|
def test_handle_generator(self):
|
|
226
278
|
mock_span = Mock()
|
|
227
279
|
mock_span.raw_span.return_value = mock_span
|
|
@@ -529,7 +581,7 @@ class TestLangfuseTracer:
|
|
|
529
581
|
assert tracer._public
|
|
530
582
|
|
|
531
583
|
def test_create_new_span(self):
|
|
532
|
-
mock_raw_span =
|
|
584
|
+
mock_raw_span = Mock()
|
|
533
585
|
mock_raw_span.operation_name = "operation_name"
|
|
534
586
|
mock_raw_span.metadata = {"tag1": "value1", "tag2": "value2"}
|
|
535
587
|
|
|
@@ -547,7 +599,7 @@ class TestLangfuseTracer:
|
|
|
547
599
|
mock_span_instance.get_data.return_value = {}
|
|
548
600
|
mock_span_instance._context_manager = mock_context_manager
|
|
549
601
|
|
|
550
|
-
mock_tracer =
|
|
602
|
+
mock_tracer = Mock()
|
|
551
603
|
mock_tracer.start_as_current_span.return_value = mock_context_manager
|
|
552
604
|
mock_tracer.start_as_current_observation.return_value = mock_context_manager
|
|
553
605
|
|
|
@@ -1,29 +0,0 @@
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langfuse_haystack-4.0.0 → langfuse_haystack-4.1.0}/src/haystack_integrations/tracing/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|