langfuse-haystack 4.0.1__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.1 → langfuse_haystack-4.1.0}/CHANGELOG.md +7 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/PKG-INFO +1 -1
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/example/basic_rag.py +2 -2
- {langfuse_haystack-4.0.1 → 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.1 → langfuse_haystack-4.1.0}/pyproject.toml +14 -5
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +1 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/src/haystack_integrations/tracing/langfuse/tracer.py +13 -1
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/tests/test_tracer.py +16 -3
- langfuse_haystack-4.0.1/pydoc/config_docusaurus.yml +0 -29
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/.gitignore +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/LICENSE.txt +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/README.md +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/example/requirements.txt +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/src/haystack_integrations/components/connectors/__init__.py +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/src/haystack_integrations/components/connectors/py.typed +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/src/haystack_integrations/tracing/py.typed +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/tests/__init__.py +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/tests/test_langfuse_connector.py +0 -0
- {langfuse_haystack-4.0.1 → langfuse_haystack-4.1.0}/tests/test_tracing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langfuse-haystack
|
|
3
|
-
Version: 4.0
|
|
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.
|
|
@@ -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. "
|
|
@@ -343,6 +350,7 @@ class DefaultSpanHandler(SpanHandler):
|
|
|
343
350
|
return LangfuseSpan(self.tracer.start_as_current_span(name=context.name))
|
|
344
351
|
|
|
345
352
|
def handle(self, span: LangfuseSpan, component_type: str | None) -> None:
|
|
353
|
+
"""Process and enrich a span after component execution."""
|
|
346
354
|
# If the span is at the pipeline level, we add input and output keys to the span
|
|
347
355
|
at_pipeline_level = span.get_data().get(_PIPELINE_INPUT_KEY) is not None
|
|
348
356
|
if at_pipeline_level:
|
|
@@ -456,6 +464,7 @@ class LangfuseTracer(Tracer):
|
|
|
456
464
|
def trace(
|
|
457
465
|
self, operation_name: str, tags: dict[str, Any] | None = None, parent_span: Span | None = None
|
|
458
466
|
) -> Iterator[Span]:
|
|
467
|
+
"""Create and manage a tracing span as a context manager."""
|
|
459
468
|
tags = tags or {}
|
|
460
469
|
span_name = tags.get(_COMPONENT_NAME_KEY, operation_name)
|
|
461
470
|
component_type = tags.get(_COMPONENT_TYPE_KEY)
|
|
@@ -543,6 +552,7 @@ class LangfuseTracer(Tracer):
|
|
|
543
552
|
self.flush()
|
|
544
553
|
|
|
545
554
|
def flush(self) -> None:
|
|
555
|
+
"""Flush all pending spans to Langfuse."""
|
|
546
556
|
self._tracer.flush()
|
|
547
557
|
|
|
548
558
|
def current_span(self) -> Span | None:
|
|
@@ -558,6 +568,7 @@ class LangfuseTracer(Tracer):
|
|
|
558
568
|
def get_trace_url(self) -> str:
|
|
559
569
|
"""
|
|
560
570
|
Return the URL to the tracing data.
|
|
571
|
+
|
|
561
572
|
:return: The URL to the tracing data.
|
|
562
573
|
"""
|
|
563
574
|
return self._tracer.get_trace_url() or ""
|
|
@@ -565,6 +576,7 @@ class LangfuseTracer(Tracer):
|
|
|
565
576
|
def get_trace_id(self) -> str:
|
|
566
577
|
"""
|
|
567
578
|
Return the trace ID.
|
|
579
|
+
|
|
568
580
|
:return: The trace ID.
|
|
569
581
|
"""
|
|
570
582
|
return self._tracer.get_current_trace_id() or ""
|
|
@@ -149,7 +149,6 @@ class TestLangfuseSpan:
|
|
|
149
149
|
def test_set_content_tag_updates_input_and_output_with_messages(self):
|
|
150
150
|
mock_context_manager = MockContextManager()
|
|
151
151
|
|
|
152
|
-
# test message input
|
|
153
152
|
span = LangfuseSpan(mock_context_manager)
|
|
154
153
|
span.set_content_tag("key.input", {"messages": [ChatMessage.from_user("message")]})
|
|
155
154
|
assert mock_context_manager._span.update.call_count == 1
|
|
@@ -157,6 +156,20 @@ class TestLangfuseSpan:
|
|
|
157
156
|
assert mock_context_manager._span.update.call_args_list[0][1] == {
|
|
158
157
|
"input": [{"role": "user", "content": "message"}]
|
|
159
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
|
+
}
|
|
160
173
|
# test replies ChatMessage list
|
|
161
174
|
mock_context_manager._span.update.reset_mock()
|
|
162
175
|
span.set_content_tag("key.output", {"replies": [ChatMessage.from_system("reply")]})
|
|
@@ -568,7 +581,7 @@ class TestLangfuseTracer:
|
|
|
568
581
|
assert tracer._public
|
|
569
582
|
|
|
570
583
|
def test_create_new_span(self):
|
|
571
|
-
mock_raw_span =
|
|
584
|
+
mock_raw_span = Mock()
|
|
572
585
|
mock_raw_span.operation_name = "operation_name"
|
|
573
586
|
mock_raw_span.metadata = {"tag1": "value1", "tag2": "value2"}
|
|
574
587
|
|
|
@@ -586,7 +599,7 @@ class TestLangfuseTracer:
|
|
|
586
599
|
mock_span_instance.get_data.return_value = {}
|
|
587
600
|
mock_span_instance._context_manager = mock_context_manager
|
|
588
601
|
|
|
589
|
-
mock_tracer =
|
|
602
|
+
mock_tracer = Mock()
|
|
590
603
|
mock_tracer.start_as_current_span.return_value = mock_context_manager
|
|
591
604
|
mock_tracer.start_as_current_observation.return_value = mock_context_manager
|
|
592
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.1 → langfuse_haystack-4.1.0}/src/haystack_integrations/tracing/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|