langfuse-haystack 0.6.3__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.
Files changed (18) hide show
  1. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/CHANGELOG.md +6 -0
  2. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/PKG-INFO +1 -1
  3. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +47 -3
  4. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/tests/test_tracing.py +76 -8
  5. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/.gitignore +0 -0
  6. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/LICENSE.txt +0 -0
  7. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/README.md +0 -0
  8. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/example/basic_rag.py +0 -0
  9. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/example/chat.py +0 -0
  10. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/example/requirements.txt +0 -0
  11. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/pydoc/config.yml +0 -0
  12. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/pyproject.toml +0 -0
  13. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
  14. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
  15. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/src/haystack_integrations/tracing/langfuse/tracer.py +0 -0
  16. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/tests/__init__.py +0 -0
  17. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/tests/test_langfuse_span.py +0 -0
  18. {langfuse_haystack-0.6.3 → langfuse_haystack-0.6.4}/tests/test_tracer.py +0 -0
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
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
+
3
9
  ## [integrations/langfuse-v0.6.2] - 2025-01-02
4
10
 
5
11
  ### 🚀 Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langfuse-haystack
3
- Version: 0.6.3
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
@@ -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__(self, name: str, public: bool = False):
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.tracer = LangfuseTracer(tracer=Langfuse(), name=name, public=public)
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)
@@ -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
- def test_tracing_integration(llm_class, env_var, expected_trace):
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 = Pipeline()
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