langfuse-haystack 0.6.2__tar.gz → 0.6.4__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.6.2 → langfuse_haystack-0.6.4}/CHANGELOG.md +8 -1
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/PKG-INFO +2 -2
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/pyproject.toml +1 -1
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +47 -3
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/src/haystack_integrations/tracing/langfuse/tracer.py +2 -16
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/tests/test_tracing.py +76 -8
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/.gitignore +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/LICENSE.txt +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/README.md +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/example/basic_rag.py +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/example/chat.py +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/example/requirements.txt +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/pydoc/config.yml +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/tests/__init__.py +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/tests/test_langfuse_span.py +0 -0
- {langfuse_haystack-0.6.2 → langfuse_haystack-0.6.4}/tests/test_tracer.py +0 -0
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [integrations/langfuse-v0.6.3] - 2025-01-15
|
|
4
|
+
|
|
5
|
+
### 🌀 Miscellaneous
|
|
6
|
+
|
|
7
|
+
- Chore: Langfuse - pin `haystack-ai>=2.9.0` and simplify message conversion (#1292)
|
|
8
|
+
|
|
9
|
+
## [integrations/langfuse-v0.6.2] - 2025-01-02
|
|
4
10
|
|
|
5
11
|
### 🚀 Features
|
|
6
12
|
|
|
@@ -13,6 +19,7 @@
|
|
|
13
19
|
### 🌀 Miscellaneous
|
|
14
20
|
|
|
15
21
|
- Chore: Fix tracing_context_var lint errors (#1220)
|
|
22
|
+
- Fix messages conversion to OpenAI format (#1272)
|
|
16
23
|
|
|
17
24
|
## [integrations/langfuse-v0.6.0] - 2024-11-18
|
|
18
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langfuse-haystack
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
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>=2.
|
|
20
|
+
Requires-Dist: haystack-ai>=2.9.0
|
|
21
21
|
Requires-Dist: langfuse
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
@@ -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.
|
|
25
|
+
dependencies = ["haystack-ai>=2.9.0", "langfuse"]
|
|
26
26
|
|
|
27
27
|
[project.urls]
|
|
28
28
|
Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse#readme"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Any, Dict, Optional
|
|
2
2
|
|
|
3
|
-
from haystack import component, logging, tracing
|
|
3
|
+
from haystack import component, default_from_dict, default_to_dict, logging, tracing
|
|
4
|
+
from haystack.utils import Secret, deserialize_secrets_inplace
|
|
4
5
|
|
|
5
6
|
from haystack_integrations.tracing.langfuse import LangfuseTracer
|
|
6
7
|
from langfuse import Langfuse
|
|
@@ -94,7 +95,13 @@ class LangfuseConnector:
|
|
|
94
95
|
|
|
95
96
|
"""
|
|
96
97
|
|
|
97
|
-
def __init__(
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
name: str,
|
|
101
|
+
public: bool = False,
|
|
102
|
+
public_key: Optional[Secret] = Secret.from_env_var("LANGFUSE_PUBLIC_KEY"), # noqa: B008
|
|
103
|
+
secret_key: Optional[Secret] = Secret.from_env_var("LANGFUSE_SECRET_KEY"), # noqa: B008
|
|
104
|
+
):
|
|
98
105
|
"""
|
|
99
106
|
Initialize the LangfuseConnector component.
|
|
100
107
|
|
|
@@ -103,9 +110,21 @@ class LangfuseConnector:
|
|
|
103
110
|
:param public: Whether the tracing data should be public or private. If set to `True`, the tracing data will be
|
|
104
111
|
publicly accessible to anyone with the tracing URL. If set to `False`, the tracing data will be private and
|
|
105
112
|
only accessible to the Langfuse account owner. The default is `False`.
|
|
113
|
+
:param public_key: The Langfuse public key. Defaults to reading from LANGFUSE_PUBLIC_KEY environment variable.
|
|
114
|
+
:param secret_key: The Langfuse secret key. Defaults to reading from LANGFUSE_SECRET_KEY environment variable.
|
|
106
115
|
"""
|
|
107
116
|
self.name = name
|
|
108
|
-
self.
|
|
117
|
+
self.public = public
|
|
118
|
+
self.secret_key = secret_key
|
|
119
|
+
self.public_key = public_key
|
|
120
|
+
self.tracer = LangfuseTracer(
|
|
121
|
+
tracer=Langfuse(
|
|
122
|
+
secret_key=secret_key.resolve_value() if secret_key else None,
|
|
123
|
+
public_key=public_key.resolve_value() if public_key else None,
|
|
124
|
+
),
|
|
125
|
+
name=name,
|
|
126
|
+
public=public,
|
|
127
|
+
)
|
|
109
128
|
tracing.enable_tracing(self.tracer)
|
|
110
129
|
|
|
111
130
|
@component.output_types(name=str, trace_url=str)
|
|
@@ -126,3 +145,28 @@ class LangfuseConnector:
|
|
|
126
145
|
invocation_context=invocation_context,
|
|
127
146
|
)
|
|
128
147
|
return {"name": self.name, "trace_url": self.tracer.get_trace_url()}
|
|
148
|
+
|
|
149
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
150
|
+
"""
|
|
151
|
+
Serialize this component to a dictionary.
|
|
152
|
+
|
|
153
|
+
:returns: The serialized component as a dictionary.
|
|
154
|
+
"""
|
|
155
|
+
return default_to_dict(
|
|
156
|
+
self,
|
|
157
|
+
name=self.name,
|
|
158
|
+
public=self.public,
|
|
159
|
+
secret_key=self.secret_key.to_dict() if self.secret_key else None,
|
|
160
|
+
public_key=self.public_key.to_dict() if self.public_key else None,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def from_dict(cls, data: Dict[str, Any]) -> "LangfuseConnector":
|
|
165
|
+
"""
|
|
166
|
+
Deserialize this component from a dictionary.
|
|
167
|
+
|
|
168
|
+
:param data: The dictionary representation of this component.
|
|
169
|
+
:returns: The deserialized component instance.
|
|
170
|
+
"""
|
|
171
|
+
deserialize_secrets_inplace(data["init_parameters"], keys=["secret_key", "public_key"])
|
|
172
|
+
return default_from_dict(cls, data)
|
|
@@ -6,16 +6,12 @@ from typing import Any, Dict, Iterator, List, Optional, Union
|
|
|
6
6
|
|
|
7
7
|
from haystack import logging
|
|
8
8
|
from haystack.dataclasses import ChatMessage
|
|
9
|
-
from haystack.lazy_imports import LazyImport
|
|
10
9
|
from haystack.tracing import Span, Tracer
|
|
11
10
|
from haystack.tracing import tracer as proxy_tracer
|
|
12
11
|
from haystack.tracing import utils as tracing_utils
|
|
13
12
|
|
|
14
13
|
import langfuse
|
|
15
14
|
|
|
16
|
-
with LazyImport("") as openai_utils_import:
|
|
17
|
-
from haystack.components.generators.openai_utils import _convert_message_to_openai_format
|
|
18
|
-
|
|
19
15
|
logger = logging.getLogger(__name__)
|
|
20
16
|
|
|
21
17
|
HAYSTACK_LANGFUSE_ENFORCE_FLUSH_ENV_VAR = "HAYSTACK_LANGFUSE_ENFORCE_FLUSH"
|
|
@@ -86,24 +82,14 @@ class LangfuseSpan(Span):
|
|
|
86
82
|
return
|
|
87
83
|
if key.endswith(".input"):
|
|
88
84
|
if "messages" in value:
|
|
89
|
-
|
|
90
|
-
# Haystack < 2.9.0
|
|
91
|
-
messages = [_convert_message_to_openai_format(m) for m in value["messages"]]
|
|
92
|
-
else:
|
|
93
|
-
# Haystack >= 2.9.0
|
|
94
|
-
messages = [m.to_openai_dict_format() for m in value["messages"]]
|
|
85
|
+
messages = [m.to_openai_dict_format() for m in value["messages"]]
|
|
95
86
|
self._span.update(input=messages)
|
|
96
87
|
else:
|
|
97
88
|
self._span.update(input=value)
|
|
98
89
|
elif key.endswith(".output"):
|
|
99
90
|
if "replies" in value:
|
|
100
91
|
if all(isinstance(r, ChatMessage) for r in value["replies"]):
|
|
101
|
-
|
|
102
|
-
# Haystack < 2.9.0
|
|
103
|
-
replies = [_convert_message_to_openai_format(m) for m in value["replies"]]
|
|
104
|
-
else:
|
|
105
|
-
# Haystack >= 2.9.0
|
|
106
|
-
replies = [m.to_openai_dict_format() for m in value["replies"]]
|
|
92
|
+
replies = [m.to_openai_dict_format() for m in value["replies"]]
|
|
107
93
|
else:
|
|
108
94
|
replies = value["replies"]
|
|
109
95
|
self._span.update(output=replies)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import random
|
|
3
2
|
import time
|
|
4
3
|
from urllib.parse import urlparse
|
|
5
4
|
|
|
@@ -9,6 +8,7 @@ from haystack import Pipeline
|
|
|
9
8
|
from haystack.components.builders import ChatPromptBuilder
|
|
10
9
|
from haystack.components.generators.chat import OpenAIChatGenerator
|
|
11
10
|
from haystack.dataclasses import ChatMessage
|
|
11
|
+
from haystack.utils import Secret
|
|
12
12
|
from requests.auth import HTTPBasicAuth
|
|
13
13
|
|
|
14
14
|
from haystack_integrations.components.connectors.langfuse import LangfuseConnector
|
|
@@ -19,6 +19,36 @@ from haystack_integrations.components.generators.cohere import CohereChatGenerat
|
|
|
19
19
|
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def pipeline_with_env_vars(llm_class, expected_trace):
|
|
24
|
+
"""Pipeline factory using environment variables for Langfuse authentication"""
|
|
25
|
+
pipe = Pipeline()
|
|
26
|
+
pipe.add_component("tracer", LangfuseConnector(name=f"Chat example - {expected_trace}", public=True))
|
|
27
|
+
pipe.add_component("prompt_builder", ChatPromptBuilder())
|
|
28
|
+
pipe.add_component("llm", llm_class())
|
|
29
|
+
pipe.connect("prompt_builder.prompt", "llm.messages")
|
|
30
|
+
return pipe
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def pipeline_with_secrets(llm_class, expected_trace):
|
|
35
|
+
"""Pipeline factory using Secret objects for Langfuse authentication"""
|
|
36
|
+
pipe = Pipeline()
|
|
37
|
+
pipe.add_component(
|
|
38
|
+
"tracer",
|
|
39
|
+
LangfuseConnector(
|
|
40
|
+
name=f"Chat example - {expected_trace}",
|
|
41
|
+
public=True,
|
|
42
|
+
secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"),
|
|
43
|
+
public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"),
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
pipe.add_component("prompt_builder", ChatPromptBuilder())
|
|
47
|
+
pipe.add_component("llm", llm_class())
|
|
48
|
+
pipe.connect("prompt_builder.prompt", "llm.messages")
|
|
49
|
+
return pipe
|
|
50
|
+
|
|
51
|
+
|
|
22
52
|
@pytest.mark.integration
|
|
23
53
|
@pytest.mark.parametrize(
|
|
24
54
|
"llm_class, env_var, expected_trace",
|
|
@@ -28,16 +58,12 @@ os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
|
|
|
28
58
|
(CohereChatGenerator, "COHERE_API_KEY", "Cohere"),
|
|
29
59
|
],
|
|
30
60
|
)
|
|
31
|
-
|
|
61
|
+
@pytest.mark.parametrize("pipeline_fixture", ["pipeline_with_env_vars", "pipeline_with_secrets"])
|
|
62
|
+
def test_tracing_integration(llm_class, env_var, expected_trace, pipeline_fixture, request):
|
|
32
63
|
if not all([os.environ.get("LANGFUSE_SECRET_KEY"), os.environ.get("LANGFUSE_PUBLIC_KEY"), os.environ.get(env_var)]):
|
|
33
64
|
pytest.skip(f"Missing required environment variables: LANGFUSE_SECRET_KEY, LANGFUSE_PUBLIC_KEY, or {env_var}")
|
|
34
65
|
|
|
35
|
-
pipe =
|
|
36
|
-
pipe.add_component("tracer", LangfuseConnector(name=f"Chat example - {expected_trace}", public=True))
|
|
37
|
-
pipe.add_component("prompt_builder", ChatPromptBuilder())
|
|
38
|
-
pipe.add_component("llm", llm_class())
|
|
39
|
-
pipe.connect("prompt_builder.prompt", "llm.messages")
|
|
40
|
-
|
|
66
|
+
pipe = request.getfixturevalue(pipeline_fixture)
|
|
41
67
|
messages = [
|
|
42
68
|
ChatMessage.from_system("Always respond in German even if some input data is in other languages."),
|
|
43
69
|
ChatMessage.from_user("Tell me about {{location}}"),
|
|
@@ -77,3 +103,45 @@ def test_tracing_integration(llm_class, env_var, expected_trace):
|
|
|
77
103
|
# check if the trace contains the expected user_id
|
|
78
104
|
assert "user_42" in str(res.content)
|
|
79
105
|
break
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_pipeline_serialization(monkeypatch):
|
|
109
|
+
"""Test that a pipeline with secrets can be properly serialized and deserialized"""
|
|
110
|
+
|
|
111
|
+
# Set test env vars
|
|
112
|
+
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "secret")
|
|
113
|
+
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "public")
|
|
114
|
+
monkeypatch.setenv("OPENAI_API_KEY", "openai_api_key")
|
|
115
|
+
|
|
116
|
+
# Create pipeline with OpenAI LLM
|
|
117
|
+
pipe = Pipeline()
|
|
118
|
+
pipe.add_component(
|
|
119
|
+
"tracer",
|
|
120
|
+
LangfuseConnector(
|
|
121
|
+
name="Chat example - OpenAI",
|
|
122
|
+
public=True,
|
|
123
|
+
secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"),
|
|
124
|
+
public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"),
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
pipe.add_component("prompt_builder", ChatPromptBuilder())
|
|
128
|
+
pipe.add_component("llm", OpenAIChatGenerator())
|
|
129
|
+
pipe.connect("prompt_builder.prompt", "llm.messages")
|
|
130
|
+
|
|
131
|
+
# Serialize
|
|
132
|
+
serialized = pipe.to_dict()
|
|
133
|
+
|
|
134
|
+
# Check serialized secrets
|
|
135
|
+
tracer_params = serialized["components"]["tracer"]["init_parameters"]
|
|
136
|
+
assert isinstance(tracer_params["secret_key"], dict)
|
|
137
|
+
assert tracer_params["secret_key"]["type"] == "env_var"
|
|
138
|
+
assert tracer_params["secret_key"]["env_vars"] == ["LANGFUSE_SECRET_KEY"]
|
|
139
|
+
assert isinstance(tracer_params["public_key"], dict)
|
|
140
|
+
assert tracer_params["public_key"]["type"] == "env_var"
|
|
141
|
+
assert tracer_params["public_key"]["env_vars"] == ["LANGFUSE_PUBLIC_KEY"]
|
|
142
|
+
|
|
143
|
+
# Deserialize
|
|
144
|
+
new_pipe = Pipeline.from_dict(serialized)
|
|
145
|
+
|
|
146
|
+
# Verify pipeline is the same
|
|
147
|
+
assert new_pipe == pipe
|
|
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
|