langfuse-haystack 0.0.4__tar.gz → 0.2.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-0.2.0/CHANGELOG.md +17 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/PKG-INFO +2 -2
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/pydoc/config.yml +2 -2
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/pyproject.toml +1 -1
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +25 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/src/haystack_integrations/tracing/langfuse/tracer.py +78 -17
- langfuse_haystack-0.2.0/tests/test_langfuse_span.py +65 -0
- langfuse_haystack-0.2.0/tests/test_tracer.py +114 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/.gitignore +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/LICENSE.txt +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/README.md +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/example/basic_rag.py +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/example/chat.py +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/example/requirements.txt +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/tests/__init__.py +0 -0
- {langfuse_haystack-0.0.4 → langfuse_haystack-0.2.0}/tests/test_tracing.py +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [integrations/langfuse-v0.1.0] - 2024-06-13
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Langfuse integration (#686)
|
|
8
|
+
|
|
9
|
+
### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Performance optimizations and value error when streaming in langfuse (#798)
|
|
12
|
+
|
|
13
|
+
### ⚙️ Miscellaneous Tasks
|
|
14
|
+
|
|
15
|
+
- Use ChatMessage to_openai_format, update unit tests, pydocs (#725)
|
|
16
|
+
|
|
17
|
+
<!-- generated by git-cliff -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: langfuse-haystack
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.2.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
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
18
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
19
19
|
Requires-Python: >=3.8
|
|
20
|
-
Requires-Dist: haystack-ai
|
|
20
|
+
Requires-Dist: haystack-ai>=2.1.0
|
|
21
21
|
Requires-Dist: langfuse
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
@@ -15,12 +15,12 @@ processors:
|
|
|
15
15
|
- type: smart
|
|
16
16
|
- type: crossref
|
|
17
17
|
renderer:
|
|
18
|
-
type: haystack_pydoc_tools.renderers.
|
|
18
|
+
type: haystack_pydoc_tools.renderers.ReadmeIntegrationRenderer
|
|
19
19
|
excerpt: Langfuse integration for Haystack
|
|
20
20
|
category_slug: integrations-api
|
|
21
21
|
title: langfuse
|
|
22
22
|
slug: integrations-langfuse
|
|
23
|
-
order:
|
|
23
|
+
order: 136
|
|
24
24
|
markdown:
|
|
25
25
|
descriptive_class_title: false
|
|
26
26
|
classdef_code_block: false
|
|
@@ -20,6 +20,31 @@ class LangfuseConnector:
|
|
|
20
20
|
In addition, you need to set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable to `true` in order to
|
|
21
21
|
enable Haystack tracing in your pipeline.
|
|
22
22
|
|
|
23
|
+
Lastly, you may disable flushing the data after each component by setting the `HAYSTACK_LANGFUSE_ENFORCE_FLUSH`
|
|
24
|
+
environent variable to `false`. By default, the data is flushed after each component and blocks the thread until
|
|
25
|
+
the data is sent to Langfuse. **Caution**: Disabling this feature may result in data loss if the program crashes
|
|
26
|
+
before the data is sent to Langfuse. Make sure you will call langfuse.flush() explicitly before the program exits.
|
|
27
|
+
E.g. by using tracer.actual_tracer.flush():
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from haystack.tracing import tracer
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# your code here
|
|
34
|
+
finally:
|
|
35
|
+
tracer.actual_tracer.flush()
|
|
36
|
+
```
|
|
37
|
+
or in FastAPI by defining a shutdown event handler:
|
|
38
|
+
```python
|
|
39
|
+
from haystack.tracing import tracer
|
|
40
|
+
|
|
41
|
+
# ...
|
|
42
|
+
|
|
43
|
+
@app.on_event("shutdown")
|
|
44
|
+
async def shutdown_event():
|
|
45
|
+
tracer.actual_tracer.flush()
|
|
46
|
+
```
|
|
47
|
+
|
|
23
48
|
Here is an example of how to use it:
|
|
24
49
|
|
|
25
50
|
```python
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
+
import os
|
|
2
3
|
from typing import Any, Dict, Iterator, Optional, Union
|
|
3
4
|
|
|
4
5
|
from haystack.dataclasses import ChatMessage
|
|
@@ -7,31 +8,57 @@ from haystack.tracing import utils as tracing_utils
|
|
|
7
8
|
|
|
8
9
|
import langfuse
|
|
9
10
|
|
|
11
|
+
HAYSTACK_LANGFUSE_ENFORCE_FLUSH_ENV_VAR = "HAYSTACK_LANGFUSE_ENFORCE_FLUSH"
|
|
12
|
+
_SUPPORTED_GENERATORS = ["AzureOpenAIGenerator", "OpenAIGenerator"]
|
|
13
|
+
_SUPPORTED_CHAT_GENERATORS = ["AzureOpenAIChatGenerator", "OpenAIChatGenerator"]
|
|
14
|
+
_ALL_SUPPORTED_GENERATORS = _SUPPORTED_GENERATORS + _SUPPORTED_CHAT_GENERATORS
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
class LangfuseSpan(Span):
|
|
18
|
+
"""
|
|
19
|
+
Internal class representing a bridge between the Haystack span tracing API and Langfuse.
|
|
20
|
+
"""
|
|
21
|
+
|
|
12
22
|
def __init__(self, span: "Union[langfuse.client.StatefulSpanClient, langfuse.client.StatefulTraceClient]") -> None:
|
|
23
|
+
"""
|
|
24
|
+
Initialize a LangfuseSpan instance.
|
|
25
|
+
|
|
26
|
+
:param span: The span instance managed by Langfuse.
|
|
27
|
+
"""
|
|
13
28
|
self._span = span
|
|
14
29
|
# locally cache tags
|
|
15
30
|
self._data: Dict[str, Any] = {}
|
|
16
31
|
|
|
17
32
|
def set_tag(self, key: str, value: Any) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Set a generic tag for this span.
|
|
35
|
+
|
|
36
|
+
:param key: The tag key.
|
|
37
|
+
:param value: The tag value.
|
|
38
|
+
"""
|
|
18
39
|
coerced_value = tracing_utils.coerce_tag_value(value)
|
|
19
40
|
self._span.update(metadata={key: coerced_value})
|
|
20
41
|
self._data[key] = value
|
|
21
42
|
|
|
22
43
|
def set_content_tag(self, key: str, value: Any) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Set a content-specific tag for this span.
|
|
46
|
+
|
|
47
|
+
:param key: The content tag key.
|
|
48
|
+
:param value: The content tag value.
|
|
49
|
+
"""
|
|
23
50
|
if not tracer.is_content_tracing_enabled:
|
|
24
51
|
return
|
|
25
52
|
if key.endswith(".input"):
|
|
26
53
|
if "messages" in value:
|
|
27
|
-
messages = [
|
|
54
|
+
messages = [m.to_openai_format() for m in value["messages"]]
|
|
28
55
|
self._span.update(input=messages)
|
|
29
56
|
else:
|
|
30
57
|
self._span.update(input=value)
|
|
31
58
|
elif key.endswith(".output"):
|
|
32
59
|
if "replies" in value:
|
|
33
60
|
if all(isinstance(r, ChatMessage) for r in value["replies"]):
|
|
34
|
-
replies = [
|
|
61
|
+
replies = [m.to_openai_format() for m in value["replies"]]
|
|
35
62
|
else:
|
|
36
63
|
replies = value["replies"]
|
|
37
64
|
self._span.update(output=replies)
|
|
@@ -41,35 +68,51 @@ class LangfuseSpan(Span):
|
|
|
41
68
|
self._data[key] = value
|
|
42
69
|
|
|
43
70
|
def raw_span(self) -> Any:
|
|
71
|
+
"""
|
|
72
|
+
Return the underlying span instance.
|
|
73
|
+
|
|
74
|
+
:return: The Langfuse span instance.
|
|
75
|
+
"""
|
|
44
76
|
return self._span
|
|
45
77
|
|
|
46
78
|
def get_correlation_data_for_logs(self) -> Dict[str, Any]:
|
|
47
79
|
return {}
|
|
48
80
|
|
|
49
|
-
def to_openai_format(self, m: ChatMessage) -> Dict[str, Any]:
|
|
50
|
-
"""
|
|
51
|
-
Remove after haystack 2.0.1 has been released and use the `to_openai_format` method from the ChatMessage class
|
|
52
|
-
"""
|
|
53
|
-
msg = {"role": m.role.value, "content": m.content}
|
|
54
|
-
if m.name:
|
|
55
|
-
msg["name"] = m.name
|
|
56
|
-
|
|
57
|
-
return msg
|
|
58
|
-
|
|
59
81
|
|
|
60
82
|
class LangfuseTracer(Tracer):
|
|
83
|
+
"""
|
|
84
|
+
Internal class representing a bridge between the Haystack tracer and Langfuse.
|
|
85
|
+
"""
|
|
86
|
+
|
|
61
87
|
def __init__(self, tracer: "langfuse.Langfuse", name: str = "Haystack", public: bool = False) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Initialize a LangfuseTracer instance.
|
|
90
|
+
|
|
91
|
+
:param tracer: The Langfuse tracer instance.
|
|
92
|
+
:param name: The name of the pipeline or component. This name will be used to identify the tracing run on the
|
|
93
|
+
Langfuse dashboard.
|
|
94
|
+
:param public: Whether the tracing data should be public or private. If set to `True`, the tracing data will
|
|
95
|
+
be publicly accessible to anyone with the tracing URL. If set to `False`, the tracing data will be private
|
|
96
|
+
and only accessible to the Langfuse account owner.
|
|
97
|
+
"""
|
|
62
98
|
self._tracer = tracer
|
|
63
99
|
self._context: list[LangfuseSpan] = []
|
|
64
100
|
self._name = name
|
|
65
101
|
self._public = public
|
|
102
|
+
self.enforce_flush = os.getenv(HAYSTACK_LANGFUSE_ENFORCE_FLUSH_ENV_VAR, "true").lower() == "true"
|
|
66
103
|
|
|
67
104
|
@contextlib.contextmanager
|
|
68
105
|
def trace(self, operation_name: str, tags: Optional[Dict[str, Any]] = None) -> Iterator[Span]:
|
|
106
|
+
"""
|
|
107
|
+
Start and manage a new trace span.
|
|
108
|
+
:param operation_name: The name of the operation.
|
|
109
|
+
:param tags: A dictionary of tags to attach to the span.
|
|
110
|
+
:return: A context manager yielding the span.
|
|
111
|
+
"""
|
|
69
112
|
tags = tags or {}
|
|
70
113
|
span_name = tags.get("haystack.component.name", operation_name)
|
|
71
114
|
|
|
72
|
-
if tags.get("haystack.component.type") in
|
|
115
|
+
if tags.get("haystack.component.type") in _ALL_SUPPORTED_GENERATORS:
|
|
73
116
|
span = LangfuseSpan(self.current_span().raw_span().generation(name=span_name))
|
|
74
117
|
else:
|
|
75
118
|
span = LangfuseSpan(self.current_span().raw_span().span(name=span_name))
|
|
@@ -79,18 +122,18 @@ class LangfuseTracer(Tracer):
|
|
|
79
122
|
|
|
80
123
|
yield span
|
|
81
124
|
|
|
82
|
-
if tags.get("haystack.component.type")
|
|
125
|
+
if tags.get("haystack.component.type") in _SUPPORTED_GENERATORS:
|
|
83
126
|
meta = span._data.get("haystack.component.output", {}).get("meta")
|
|
84
127
|
if meta:
|
|
85
128
|
# Haystack returns one meta dict for each message, but the 'usage' value
|
|
86
129
|
# is always the same, let's just pick the first item
|
|
87
130
|
m = meta[0]
|
|
88
|
-
span._span.update(usage=m.get("usage"), model=m.get("model"))
|
|
89
|
-
elif tags.get("haystack.component.type")
|
|
131
|
+
span._span.update(usage=m.get("usage") or None, model=m.get("model"))
|
|
132
|
+
elif tags.get("haystack.component.type") in _SUPPORTED_CHAT_GENERATORS:
|
|
90
133
|
replies = span._data.get("haystack.component.output", {}).get("replies")
|
|
91
134
|
if replies:
|
|
92
135
|
meta = replies[0].meta
|
|
93
|
-
span._span.update(usage=meta.get("usage"), model=meta.get("model"))
|
|
136
|
+
span._span.update(usage=meta.get("usage") or None, model=meta.get("model"))
|
|
94
137
|
|
|
95
138
|
pipeline_input = tags.get("haystack.pipeline.input_data", None)
|
|
96
139
|
if pipeline_input:
|
|
@@ -101,13 +144,31 @@ class LangfuseTracer(Tracer):
|
|
|
101
144
|
|
|
102
145
|
span.raw_span().end()
|
|
103
146
|
self._context.pop()
|
|
147
|
+
|
|
148
|
+
if len(self._context) == 1:
|
|
149
|
+
# The root span has to be a trace, which need to be removed from the context after the pipeline run
|
|
150
|
+
self._context.pop()
|
|
151
|
+
|
|
152
|
+
if self.enforce_flush:
|
|
153
|
+
self.flush()
|
|
154
|
+
|
|
155
|
+
def flush(self):
|
|
104
156
|
self._tracer.flush()
|
|
105
157
|
|
|
106
158
|
def current_span(self) -> Span:
|
|
159
|
+
"""
|
|
160
|
+
Return the currently active span.
|
|
161
|
+
|
|
162
|
+
:return: The currently active span.
|
|
163
|
+
"""
|
|
107
164
|
if not self._context:
|
|
108
165
|
# The root span has to be a trace
|
|
109
166
|
self._context.append(LangfuseSpan(self._tracer.trace(name=self._name, public=self._public)))
|
|
110
167
|
return self._context[-1]
|
|
111
168
|
|
|
112
169
|
def get_trace_url(self) -> str:
|
|
170
|
+
"""
|
|
171
|
+
Return the URL to the tracing data.
|
|
172
|
+
:return: The URL to the tracing data.
|
|
173
|
+
"""
|
|
113
174
|
return self._tracer.get_trace_url()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
|
|
4
|
+
|
|
5
|
+
from unittest.mock import Mock
|
|
6
|
+
from haystack.dataclasses import ChatMessage
|
|
7
|
+
from haystack_integrations.tracing.langfuse.tracer import LangfuseSpan
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestLangfuseSpan:
|
|
11
|
+
|
|
12
|
+
# LangfuseSpan can be initialized with a span object
|
|
13
|
+
def test_initialized_with_span_object(self):
|
|
14
|
+
mock_span = Mock()
|
|
15
|
+
span = LangfuseSpan(mock_span)
|
|
16
|
+
assert span.raw_span() == mock_span
|
|
17
|
+
|
|
18
|
+
# set_tag method can update metadata of the span object
|
|
19
|
+
def test_set_tag_updates_metadata(self):
|
|
20
|
+
mock_span = Mock()
|
|
21
|
+
span = LangfuseSpan(mock_span)
|
|
22
|
+
|
|
23
|
+
span.set_tag("key", "value")
|
|
24
|
+
mock_span.update.assert_called_once_with(metadata={"key": "value"})
|
|
25
|
+
assert span._data["key"] == "value"
|
|
26
|
+
|
|
27
|
+
# set_content_tag method can update input and output of the span object
|
|
28
|
+
def test_set_content_tag_updates_input_and_output(self):
|
|
29
|
+
mock_span = Mock()
|
|
30
|
+
|
|
31
|
+
span = LangfuseSpan(mock_span)
|
|
32
|
+
span.set_content_tag("input_key", "input_value")
|
|
33
|
+
assert span._data["input_key"] == "input_value"
|
|
34
|
+
|
|
35
|
+
mock_span.reset_mock()
|
|
36
|
+
span.set_content_tag("output_key", "output_value")
|
|
37
|
+
assert span._data["output_key"] == "output_value"
|
|
38
|
+
|
|
39
|
+
# set_content_tag method can update input and output of the span object with messages/replies
|
|
40
|
+
def test_set_content_tag_updates_input_and_output_with_messages(self):
|
|
41
|
+
mock_span = Mock()
|
|
42
|
+
|
|
43
|
+
# test message input
|
|
44
|
+
span = LangfuseSpan(mock_span)
|
|
45
|
+
span.set_content_tag("key.input", {"messages": [ChatMessage.from_user("message")]})
|
|
46
|
+
assert mock_span.update.call_count == 1
|
|
47
|
+
# check we converted ChatMessage to OpenAI format
|
|
48
|
+
assert mock_span.update.call_args_list[0][1] == {"input": [{"role": "user", "content": "message"}]}
|
|
49
|
+
assert span._data["key.input"] == {"messages": [ChatMessage.from_user("message")]}
|
|
50
|
+
|
|
51
|
+
# test replies ChatMessage list
|
|
52
|
+
mock_span.reset_mock()
|
|
53
|
+
span.set_content_tag("key.output", {"replies": [ChatMessage.from_system("reply")]})
|
|
54
|
+
assert mock_span.update.call_count == 1
|
|
55
|
+
# check we converted ChatMessage to OpenAI format
|
|
56
|
+
assert mock_span.update.call_args_list[0][1] == {"output": [{"role": "system", "content": "reply"}]}
|
|
57
|
+
assert span._data["key.output"] == {"replies": [ChatMessage.from_system("reply")]}
|
|
58
|
+
|
|
59
|
+
# test replies string list
|
|
60
|
+
mock_span.reset_mock()
|
|
61
|
+
span.set_content_tag("key.output", {"replies": ["reply1", "reply2"]})
|
|
62
|
+
assert mock_span.update.call_count == 1
|
|
63
|
+
# check we handle properly string list replies
|
|
64
|
+
assert mock_span.update.call_args_list[0][1] == {"output": ["reply1", "reply2"]}
|
|
65
|
+
assert span._data["key.output"] == {"replies": ["reply1", "reply2"]}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from unittest.mock import Mock, MagicMock, patch
|
|
3
|
+
|
|
4
|
+
from haystack_integrations.tracing.langfuse.tracer import LangfuseTracer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestLangfuseTracer:
|
|
8
|
+
|
|
9
|
+
# LangfuseTracer can be initialized with a Langfuse instance, a name and a boolean value for public.
|
|
10
|
+
def test_initialization(self):
|
|
11
|
+
langfuse_instance = Mock()
|
|
12
|
+
tracer = LangfuseTracer(tracer=langfuse_instance, name="Haystack", public=True)
|
|
13
|
+
assert tracer._tracer == langfuse_instance
|
|
14
|
+
assert tracer._context == []
|
|
15
|
+
assert tracer._name == "Haystack"
|
|
16
|
+
assert tracer._public
|
|
17
|
+
|
|
18
|
+
# check that the trace method is called on the tracer instance with the provided operation name and tags
|
|
19
|
+
# check that the span is added to the context and removed after the context manager exits
|
|
20
|
+
def test_create_new_span(self):
|
|
21
|
+
mock_raw_span = MagicMock()
|
|
22
|
+
mock_raw_span.operation_name = "operation_name"
|
|
23
|
+
mock_raw_span.metadata = {"tag1": "value1", "tag2": "value2"}
|
|
24
|
+
|
|
25
|
+
with patch("haystack_integrations.tracing.langfuse.tracer.LangfuseSpan") as MockLangfuseSpan:
|
|
26
|
+
mock_span_instance = MockLangfuseSpan.return_value
|
|
27
|
+
mock_span_instance.raw_span.return_value = mock_raw_span
|
|
28
|
+
|
|
29
|
+
mock_context_manager = MagicMock()
|
|
30
|
+
mock_context_manager.__enter__.return_value = mock_span_instance
|
|
31
|
+
|
|
32
|
+
mock_tracer = MagicMock()
|
|
33
|
+
mock_tracer.trace.return_value = mock_context_manager
|
|
34
|
+
|
|
35
|
+
tracer = LangfuseTracer(tracer=mock_tracer, name="Haystack", public=False)
|
|
36
|
+
|
|
37
|
+
with tracer.trace("operation_name", tags={"tag1": "value1", "tag2": "value2"}) as span:
|
|
38
|
+
assert len(tracer._context) == 2, "The trace span should have been added to the the root context span"
|
|
39
|
+
assert span.raw_span().operation_name == "operation_name"
|
|
40
|
+
assert span.raw_span().metadata == {"tag1": "value1", "tag2": "value2"}
|
|
41
|
+
|
|
42
|
+
assert (
|
|
43
|
+
len(tracer._context) == 0
|
|
44
|
+
), "The trace span should have been popped, and the root span is closed as well"
|
|
45
|
+
|
|
46
|
+
# check that update method is called on the span instance with the provided key value pairs
|
|
47
|
+
def test_update_span_with_pipeline_input_output_data(self):
|
|
48
|
+
class MockTracer:
|
|
49
|
+
|
|
50
|
+
def trace(self, name, **kwargs):
|
|
51
|
+
return MockSpan()
|
|
52
|
+
|
|
53
|
+
def flush(self):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
class MockSpan:
|
|
57
|
+
def __init__(self):
|
|
58
|
+
self._data = {}
|
|
59
|
+
self._span = self
|
|
60
|
+
self.operation_name = "operation_name"
|
|
61
|
+
|
|
62
|
+
def raw_span(self):
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def span(self, name=None):
|
|
66
|
+
# assert correct operation name passed to the span
|
|
67
|
+
assert name == "operation_name"
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def update(self, **kwargs):
|
|
71
|
+
self._data.update(kwargs)
|
|
72
|
+
|
|
73
|
+
def generation(self, name=None):
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def end(self):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
tracer = LangfuseTracer(tracer=MockTracer(), name="Haystack", public=False)
|
|
80
|
+
with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
|
|
81
|
+
assert span.raw_span()._data["metadata"] == {"haystack.pipeline.input_data": "hello"}
|
|
82
|
+
|
|
83
|
+
with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.output_data": "bye"}) as span:
|
|
84
|
+
assert span.raw_span()._data["metadata"] == {"haystack.pipeline.output_data": "bye"}
|
|
85
|
+
|
|
86
|
+
def test_update_span_gets_flushed_by_default(self):
|
|
87
|
+
tracer_mock = Mock()
|
|
88
|
+
|
|
89
|
+
tracer = LangfuseTracer(tracer=tracer_mock, name="Haystack", public=False)
|
|
90
|
+
with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
tracer_mock.flush.assert_called_once()
|
|
94
|
+
|
|
95
|
+
def test_update_span_flush_disable(self, monkeypatch):
|
|
96
|
+
monkeypatch.setenv("HAYSTACK_LANGFUSE_ENFORCE_FLUSH", "false")
|
|
97
|
+
tracer_mock = Mock()
|
|
98
|
+
|
|
99
|
+
from haystack_integrations.tracing.langfuse.tracer import LangfuseTracer
|
|
100
|
+
|
|
101
|
+
tracer = LangfuseTracer(tracer=tracer_mock, name="Haystack", public=False)
|
|
102
|
+
with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
tracer_mock.flush.assert_not_called()
|
|
106
|
+
|
|
107
|
+
def test_context_is_empty_after_tracing(self):
|
|
108
|
+
tracer_mock = Mock()
|
|
109
|
+
|
|
110
|
+
tracer = LangfuseTracer(tracer=tracer_mock, name="Haystack", public=False)
|
|
111
|
+
with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
assert tracer._context == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|