langfuse-haystack 0.5.0__tar.gz → 0.6.1__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.5.0 → langfuse_haystack-0.6.1}/CHANGELOG.md +20 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/PKG-INFO +2 -3
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/pyproject.toml +3 -1
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/src/haystack_integrations/tracing/langfuse/tracer.py +85 -46
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/tests/test_tracer.py +86 -33
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/tests/test_tracing.py +18 -15
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/.gitignore +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/LICENSE.txt +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/README.md +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/example/basic_rag.py +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/example/chat.py +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/example/requirements.txt +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/pydoc/config.yml +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/tests/__init__.py +0 -0
- {langfuse_haystack-0.5.0 → langfuse_haystack-0.6.1}/tests/test_langfuse_span.py +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/langfuse-v0.6.0] - 2024-11-18
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Add support for ttft (#1161)
|
|
8
|
+
|
|
9
|
+
### ⚙️ Miscellaneous Tasks
|
|
10
|
+
|
|
11
|
+
- Adopt uv as installer (#1142)
|
|
12
|
+
|
|
13
|
+
## [integrations/langfuse-v0.5.0] - 2024-10-01
|
|
14
|
+
|
|
15
|
+
### ⚙️ Miscellaneous Tasks
|
|
16
|
+
|
|
17
|
+
- Update ruff linting scripts and settings (#1105)
|
|
18
|
+
|
|
19
|
+
### Langfuse
|
|
20
|
+
|
|
21
|
+
- Add invocation_context to identify traces (#1089)
|
|
22
|
+
|
|
3
23
|
## [integrations/langfuse-v0.4.0] - 2024-09-17
|
|
4
24
|
|
|
5
25
|
### 🚀 Features
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: langfuse-haystack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
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
|
|
7
7
|
Author-email: deepset GmbH <info@deepset.ai>
|
|
8
|
-
License
|
|
9
|
-
License-File: LICENSE.txt
|
|
8
|
+
License: Apache-2.0
|
|
10
9
|
Classifier: Development Status :: 4 - Beta
|
|
11
10
|
Classifier: Programming Language :: Python
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.8
|
|
@@ -42,6 +42,7 @@ root = "../.."
|
|
|
42
42
|
git_describe_command = 'git describe --tags --match="integrations/langfuse-v[0-9]*"'
|
|
43
43
|
|
|
44
44
|
[tool.hatch.envs.default]
|
|
45
|
+
installer = "uv"
|
|
45
46
|
dependencies = [
|
|
46
47
|
"coverage[toml]>=6.5",
|
|
47
48
|
"pytest",
|
|
@@ -64,8 +65,9 @@ docs = ["pydoc-markdown pydoc/config.yml"]
|
|
|
64
65
|
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
65
66
|
|
|
66
67
|
[tool.hatch.envs.lint]
|
|
68
|
+
installer = "uv"
|
|
67
69
|
detached = true
|
|
68
|
-
dependencies = ["black>=23.1.0", "mypy>=1.0.0", "ruff>=0.0.243"]
|
|
70
|
+
dependencies = ["pip", "black>=23.1.0", "mypy>=1.0.0", "ruff>=0.0.243"]
|
|
69
71
|
|
|
70
72
|
[tool.hatch.envs.lint.scripts]
|
|
71
73
|
typing = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import os
|
|
3
|
-
from
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, Dict, Iterator, List, Optional, Union
|
|
4
6
|
|
|
7
|
+
from haystack import logging
|
|
5
8
|
from haystack.components.generators.openai_utils import _convert_message_to_openai_format
|
|
6
9
|
from haystack.dataclasses import ChatMessage
|
|
7
|
-
from haystack.tracing import Span, Tracer
|
|
10
|
+
from haystack.tracing import Span, Tracer
|
|
11
|
+
from haystack.tracing import tracer as proxy_tracer
|
|
8
12
|
from haystack.tracing import utils as tracing_utils
|
|
9
13
|
|
|
10
14
|
import langfuse
|
|
11
15
|
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
12
18
|
HAYSTACK_LANGFUSE_ENFORCE_FLUSH_ENV_VAR = "HAYSTACK_LANGFUSE_ENFORCE_FLUSH"
|
|
13
19
|
_SUPPORTED_GENERATORS = [
|
|
14
20
|
"AzureOpenAIGenerator",
|
|
@@ -28,6 +34,17 @@ _SUPPORTED_CHAT_GENERATORS = [
|
|
|
28
34
|
]
|
|
29
35
|
_ALL_SUPPORTED_GENERATORS = _SUPPORTED_GENERATORS + _SUPPORTED_CHAT_GENERATORS
|
|
30
36
|
|
|
37
|
+
# These are the keys used by Haystack for traces and span.
|
|
38
|
+
# We keep them here to avoid making typos when using them.
|
|
39
|
+
_PIPELINE_RUN_KEY = "haystack.pipeline.run"
|
|
40
|
+
_COMPONENT_NAME_KEY = "haystack.component.name"
|
|
41
|
+
_COMPONENT_TYPE_KEY = "haystack.component.type"
|
|
42
|
+
_COMPONENT_OUTPUT_KEY = "haystack.component.output"
|
|
43
|
+
|
|
44
|
+
# Context var used to keep track of tracing related info.
|
|
45
|
+
# This mainly useful for parents spans.
|
|
46
|
+
tracing_context_var: ContextVar[Dict[Any, Any]] = ContextVar("tracing_context")
|
|
47
|
+
|
|
31
48
|
|
|
32
49
|
class LangfuseSpan(Span):
|
|
33
50
|
"""
|
|
@@ -62,7 +79,7 @@ class LangfuseSpan(Span):
|
|
|
62
79
|
:param key: The content tag key.
|
|
63
80
|
:param value: The content tag value.
|
|
64
81
|
"""
|
|
65
|
-
if not
|
|
82
|
+
if not proxy_tracer.is_content_tracing_enabled:
|
|
66
83
|
return
|
|
67
84
|
if key.endswith(".input"):
|
|
68
85
|
if "messages" in value:
|
|
@@ -82,7 +99,7 @@ class LangfuseSpan(Span):
|
|
|
82
99
|
|
|
83
100
|
self._data[key] = value
|
|
84
101
|
|
|
85
|
-
def raw_span(self) ->
|
|
102
|
+
def raw_span(self) -> "Union[langfuse.client.StatefulSpanClient, langfuse.client.StatefulTraceClient]":
|
|
86
103
|
"""
|
|
87
104
|
Return the underlying span instance.
|
|
88
105
|
|
|
@@ -110,76 +127,98 @@ class LangfuseTracer(Tracer):
|
|
|
110
127
|
be publicly accessible to anyone with the tracing URL. If set to `False`, the tracing data will be private
|
|
111
128
|
and only accessible to the Langfuse account owner.
|
|
112
129
|
"""
|
|
130
|
+
if not proxy_tracer.is_content_tracing_enabled:
|
|
131
|
+
logger.warning(
|
|
132
|
+
"Traces will not be logged to Langfuse because Haystack tracing is disabled. "
|
|
133
|
+
"To enable, set the HAYSTACK_CONTENT_TRACING_ENABLED environment variable to true "
|
|
134
|
+
"before importing Haystack."
|
|
135
|
+
)
|
|
113
136
|
self._tracer = tracer
|
|
114
|
-
self._context:
|
|
137
|
+
self._context: List[LangfuseSpan] = []
|
|
115
138
|
self._name = name
|
|
116
139
|
self._public = public
|
|
117
140
|
self.enforce_flush = os.getenv(HAYSTACK_LANGFUSE_ENFORCE_FLUSH_ENV_VAR, "true").lower() == "true"
|
|
118
141
|
|
|
119
142
|
@contextlib.contextmanager
|
|
120
|
-
def trace(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
:param operation_name: The name of the operation.
|
|
124
|
-
:param tags: A dictionary of tags to attach to the span.
|
|
125
|
-
:return: A context manager yielding the span.
|
|
126
|
-
"""
|
|
143
|
+
def trace(
|
|
144
|
+
self, operation_name: str, tags: Optional[Dict[str, Any]] = None, parent_span: Optional[Span] = None
|
|
145
|
+
) -> Iterator[Span]:
|
|
127
146
|
tags = tags or {}
|
|
128
|
-
span_name = tags.get(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
147
|
+
span_name = tags.get(_COMPONENT_NAME_KEY, operation_name)
|
|
148
|
+
|
|
149
|
+
# Create new span depending whether there's a parent span or not
|
|
150
|
+
if not parent_span:
|
|
151
|
+
if operation_name != _PIPELINE_RUN_KEY:
|
|
152
|
+
logger.warning(
|
|
153
|
+
"Creating a new trace without a parent span is not recommended for operation '{operation_name}'.",
|
|
154
|
+
operation_name=operation_name,
|
|
155
|
+
)
|
|
156
|
+
# Create a new trace if no parent span is provided
|
|
157
|
+
context = tracing_context_var.get({})
|
|
158
|
+
span = LangfuseSpan(
|
|
159
|
+
self._tracer.trace(
|
|
160
|
+
name=self._name,
|
|
161
|
+
public=self._public,
|
|
162
|
+
id=context.get("trace_id"),
|
|
163
|
+
user_id=context.get("user_id"),
|
|
164
|
+
session_id=context.get("session_id"),
|
|
165
|
+
tags=context.get("tags"),
|
|
166
|
+
version=context.get("version"),
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
elif tags.get(_COMPONENT_TYPE_KEY) in _ALL_SUPPORTED_GENERATORS:
|
|
170
|
+
span = LangfuseSpan(parent_span.raw_span().generation(name=span_name))
|
|
132
171
|
else:
|
|
133
|
-
span = LangfuseSpan(
|
|
172
|
+
span = LangfuseSpan(parent_span.raw_span().span(name=span_name))
|
|
134
173
|
|
|
135
174
|
self._context.append(span)
|
|
136
175
|
span.set_tags(tags)
|
|
137
176
|
|
|
138
177
|
yield span
|
|
139
178
|
|
|
140
|
-
|
|
141
|
-
|
|
179
|
+
# Update span metadata based on component type
|
|
180
|
+
if tags.get(_COMPONENT_TYPE_KEY) in _SUPPORTED_GENERATORS:
|
|
181
|
+
# Haystack returns one meta dict for each message, but the 'usage' value
|
|
182
|
+
# is always the same, let's just pick the first item
|
|
183
|
+
meta = span._data.get(_COMPONENT_OUTPUT_KEY, {}).get("meta")
|
|
142
184
|
if meta:
|
|
143
|
-
# Haystack returns one meta dict for each message, but the 'usage' value
|
|
144
|
-
# is always the same, let's just pick the first item
|
|
145
185
|
m = meta[0]
|
|
146
186
|
span._span.update(usage=m.get("usage") or None, model=m.get("model"))
|
|
147
|
-
elif tags.get(
|
|
148
|
-
replies = span._data.get(
|
|
187
|
+
elif tags.get(_COMPONENT_TYPE_KEY) in _SUPPORTED_CHAT_GENERATORS:
|
|
188
|
+
replies = span._data.get(_COMPONENT_OUTPUT_KEY, {}).get("replies")
|
|
149
189
|
if replies:
|
|
150
190
|
meta = replies[0].meta
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
191
|
+
completion_start_time = meta.get("completion_start_time")
|
|
192
|
+
if completion_start_time:
|
|
193
|
+
try:
|
|
194
|
+
completion_start_time = datetime.fromisoformat(completion_start_time)
|
|
195
|
+
except ValueError:
|
|
196
|
+
logger.error(f"Failed to parse completion_start_time: {completion_start_time}")
|
|
197
|
+
completion_start_time = None
|
|
198
|
+
span._span.update(
|
|
199
|
+
usage=meta.get("usage") or None,
|
|
200
|
+
model=meta.get("model"),
|
|
201
|
+
completion_start_time=completion_start_time,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
raw_span = span.raw_span()
|
|
205
|
+
if isinstance(raw_span, langfuse.client.StatefulSpanClient):
|
|
206
|
+
raw_span.end()
|
|
161
207
|
self._context.pop()
|
|
162
208
|
|
|
163
|
-
if
|
|
164
|
-
|
|
165
|
-
self._context.pop()
|
|
166
|
-
|
|
167
|
-
if self.enforce_flush:
|
|
168
|
-
self.flush()
|
|
209
|
+
if self.enforce_flush:
|
|
210
|
+
self.flush()
|
|
169
211
|
|
|
170
212
|
def flush(self):
|
|
171
213
|
self._tracer.flush()
|
|
172
214
|
|
|
173
|
-
def current_span(self) -> Span:
|
|
215
|
+
def current_span(self) -> Optional[Span]:
|
|
174
216
|
"""
|
|
175
|
-
Return the
|
|
217
|
+
Return the current active span.
|
|
176
218
|
|
|
177
|
-
:return: The
|
|
219
|
+
:return: The current span if available, else None.
|
|
178
220
|
"""
|
|
179
|
-
if
|
|
180
|
-
# The root span has to be a trace
|
|
181
|
-
self._context.append(LangfuseSpan(self._tracer.trace(name=self._name, public=self._public)))
|
|
182
|
-
return self._context[-1]
|
|
221
|
+
return self._context[-1] if self._context else None
|
|
183
222
|
|
|
184
223
|
def get_trace_url(self) -> str:
|
|
185
224
|
"""
|
|
@@ -1,9 +1,45 @@
|
|
|
1
|
-
import
|
|
1
|
+
import datetime
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
2
4
|
from unittest.mock import MagicMock, Mock, patch
|
|
3
5
|
|
|
6
|
+
from haystack.dataclasses import ChatMessage
|
|
4
7
|
from haystack_integrations.tracing.langfuse.tracer import LangfuseTracer
|
|
5
8
|
|
|
6
9
|
|
|
10
|
+
class MockSpan:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self._data = {}
|
|
13
|
+
self._span = self
|
|
14
|
+
self.operation_name = "operation_name"
|
|
15
|
+
|
|
16
|
+
def raw_span(self):
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
def span(self, name=None):
|
|
20
|
+
# assert correct operation name passed to the span
|
|
21
|
+
assert name == "operation_name"
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def update(self, **kwargs):
|
|
25
|
+
self._data.update(kwargs)
|
|
26
|
+
|
|
27
|
+
def generation(self, name=None):
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def end(self):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MockTracer:
|
|
35
|
+
|
|
36
|
+
def trace(self, name, **kwargs):
|
|
37
|
+
return MockSpan()
|
|
38
|
+
|
|
39
|
+
def flush(self):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
7
43
|
class TestLangfuseTracer:
|
|
8
44
|
|
|
9
45
|
# LangfuseTracer can be initialized with a Langfuse instance, a name and a boolean value for public.
|
|
@@ -35,7 +71,7 @@ class TestLangfuseTracer:
|
|
|
35
71
|
tracer = LangfuseTracer(tracer=mock_tracer, name="Haystack", public=False)
|
|
36
72
|
|
|
37
73
|
with tracer.trace("operation_name", tags={"tag1": "value1", "tag2": "value2"}) as span:
|
|
38
|
-
assert len(tracer._context) ==
|
|
74
|
+
assert len(tracer._context) == 1, "The trace span should have been added to the the root context span"
|
|
39
75
|
assert span.raw_span().operation_name == "operation_name"
|
|
40
76
|
assert span.raw_span().metadata == {"tag1": "value1", "tag2": "value2"}
|
|
41
77
|
|
|
@@ -45,37 +81,6 @@ class TestLangfuseTracer:
|
|
|
45
81
|
|
|
46
82
|
# check that update method is called on the span instance with the provided key value pairs
|
|
47
83
|
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
84
|
tracer = LangfuseTracer(tracer=MockTracer(), name="Haystack", public=False)
|
|
80
85
|
with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.input_data": "hello"}) as span:
|
|
81
86
|
assert span.raw_span()._data["metadata"] == {"haystack.pipeline.input_data": "hello"}
|
|
@@ -83,6 +88,40 @@ class TestLangfuseTracer:
|
|
|
83
88
|
with tracer.trace(operation_name="operation_name", tags={"haystack.pipeline.output_data": "bye"}) as span:
|
|
84
89
|
assert span.raw_span()._data["metadata"] == {"haystack.pipeline.output_data": "bye"}
|
|
85
90
|
|
|
91
|
+
def test_trace_generation(self):
|
|
92
|
+
tracer = LangfuseTracer(tracer=MockTracer(), name="Haystack", public=False)
|
|
93
|
+
tags = {
|
|
94
|
+
"haystack.component.type": "OpenAIChatGenerator",
|
|
95
|
+
"haystack.component.output": {
|
|
96
|
+
"replies": [
|
|
97
|
+
ChatMessage.from_assistant(
|
|
98
|
+
"", meta={"completion_start_time": "2021-07-27T16:02:08.012345", "model": "test_model"}
|
|
99
|
+
)
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
with tracer.trace(operation_name="operation_name", tags=tags) as span:
|
|
104
|
+
...
|
|
105
|
+
assert span.raw_span()._data["usage"] is None
|
|
106
|
+
assert span.raw_span()._data["model"] == "test_model"
|
|
107
|
+
assert span.raw_span()._data["completion_start_time"] == datetime.datetime(2021, 7, 27, 16, 2, 8, 12345)
|
|
108
|
+
|
|
109
|
+
def test_trace_generation_invalid_start_time(self):
|
|
110
|
+
tracer = LangfuseTracer(tracer=MockTracer(), name="Haystack", public=False)
|
|
111
|
+
tags = {
|
|
112
|
+
"haystack.component.type": "OpenAIChatGenerator",
|
|
113
|
+
"haystack.component.output": {
|
|
114
|
+
"replies": [
|
|
115
|
+
ChatMessage.from_assistant("", meta={"completion_start_time": "foobar", "model": "test_model"}),
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
with tracer.trace(operation_name="operation_name", tags=tags) as span:
|
|
120
|
+
...
|
|
121
|
+
assert span.raw_span()._data["usage"] is None
|
|
122
|
+
assert span.raw_span()._data["model"] == "test_model"
|
|
123
|
+
assert span.raw_span()._data["completion_start_time"] is None
|
|
124
|
+
|
|
86
125
|
def test_update_span_gets_flushed_by_default(self):
|
|
87
126
|
tracer_mock = Mock()
|
|
88
127
|
|
|
@@ -112,3 +151,17 @@ class TestLangfuseTracer:
|
|
|
112
151
|
pass
|
|
113
152
|
|
|
114
153
|
assert tracer._context == []
|
|
154
|
+
|
|
155
|
+
def test_init_with_tracing_disabled(self, monkeypatch, caplog):
|
|
156
|
+
# Clear haystack modules because ProxyTracer is initialized whenever haystack is imported
|
|
157
|
+
modules_to_clear = [name for name in sys.modules if name.startswith('haystack')]
|
|
158
|
+
for name in modules_to_clear:
|
|
159
|
+
sys.modules.pop(name, None)
|
|
160
|
+
|
|
161
|
+
# Re-import LangfuseTracer and instantiate it with tracing disabled
|
|
162
|
+
with caplog.at_level(logging.WARNING):
|
|
163
|
+
monkeypatch.setenv("HAYSTACK_CONTENT_TRACING_ENABLED", "false")
|
|
164
|
+
from haystack_integrations.tracing.langfuse import LangfuseTracer
|
|
165
|
+
|
|
166
|
+
LangfuseTracer(tracer=MockTracer(), name="Haystack", public=False)
|
|
167
|
+
assert "tracing is disabled" in caplog.text
|
|
@@ -49,28 +49,31 @@ def test_tracing_integration(llm_class, env_var, expected_trace):
|
|
|
49
49
|
"tracer": {"invocation_context": {"user_id": "user_42"}},
|
|
50
50
|
}
|
|
51
51
|
)
|
|
52
|
-
assert "Berlin" in response["llm"]["replies"][0].
|
|
52
|
+
assert "Berlin" in response["llm"]["replies"][0].text
|
|
53
53
|
assert response["tracer"]["trace_url"]
|
|
54
54
|
|
|
55
|
-
# add a random delay between 1 and 3 seconds to make sure the trace is flushed
|
|
56
|
-
# and that the trace is available in Langfuse when we fetch it below
|
|
57
|
-
time.sleep(random.uniform(1, 3))
|
|
58
|
-
|
|
59
|
-
url = "https://cloud.langfuse.com/api/public/traces/"
|
|
60
55
|
trace_url = response["tracer"]["trace_url"]
|
|
61
56
|
uuid = os.path.basename(urlparse(trace_url).path)
|
|
57
|
+
url = f"https://cloud.langfuse.com/api/public/traces/{uuid}"
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
# Poll the Langfuse API a bit as the trace might not be ready right away
|
|
60
|
+
attempts = 5
|
|
61
|
+
delay = 1
|
|
62
|
+
while attempts >= 0:
|
|
63
|
+
res = requests.get(
|
|
64
|
+
url, auth=HTTPBasicAuth(os.environ["LANGFUSE_PUBLIC_KEY"], os.environ["LANGFUSE_SECRET_KEY"])
|
|
66
65
|
)
|
|
67
|
-
|
|
66
|
+
if attempts > 0 and res.status_code != 200:
|
|
67
|
+
attempts -= 1
|
|
68
|
+
time.sleep(delay)
|
|
69
|
+
delay *= 2
|
|
70
|
+
continue
|
|
71
|
+
assert res.status_code == 200, f"Failed to retrieve data from Langfuse API: {res.status_code}"
|
|
68
72
|
|
|
69
73
|
# check if the trace contains the expected LLM name
|
|
70
|
-
assert expected_trace in str(
|
|
74
|
+
assert expected_trace in str(res.content)
|
|
71
75
|
# check if the trace contains the expected generation span
|
|
72
|
-
assert "GENERATION" in str(
|
|
76
|
+
assert "GENERATION" in str(res.content)
|
|
73
77
|
# check if the trace contains the expected user_id
|
|
74
|
-
assert "user_42" in str(
|
|
75
|
-
|
|
76
|
-
pytest.fail(f"Failed to retrieve data from Langfuse API: {e}")
|
|
78
|
+
assert "user_42" in str(res.content)
|
|
79
|
+
break
|
|
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
|
|
File without changes
|
|
File without changes
|