langfuse-haystack 0.6.3__tar.gz → 0.7.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.6.3 → langfuse_haystack-0.7.0}/CHANGELOG.md +13 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/PKG-INFO +1 -1
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/pyproject.toml +7 -1
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +54 -3
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/src/haystack_integrations/tracing/langfuse/tracer.py +5 -1
- langfuse_haystack-0.7.0/tests/test_tracing.py +171 -0
- langfuse_haystack-0.6.3/tests/test_tracing.py +0 -79
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/.gitignore +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/LICENSE.txt +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/README.md +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/example/basic_rag.py +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/example/chat.py +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/example/requirements.txt +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/pydoc/config.yml +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/tests/__init__.py +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/tests/test_langfuse_span.py +0 -0
- {langfuse_haystack-0.6.3 → langfuse_haystack-0.7.0}/tests/test_tracer.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/langfuse-v0.6.4] - 2025-01-17
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Add LangfuseConnector secure key management and serialization (#1287)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [integrations/langfuse-v0.6.3] - 2025-01-15
|
|
11
|
+
|
|
12
|
+
### 🌀 Miscellaneous
|
|
13
|
+
|
|
14
|
+
- Chore: Langfuse - pin `haystack-ai>=2.9.0` and simplify message conversion (#1292)
|
|
15
|
+
|
|
3
16
|
## [integrations/langfuse-v0.6.2] - 2025-01-02
|
|
4
17
|
|
|
5
18
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langfuse-haystack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -67,7 +67,12 @@ python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
|
67
67
|
[tool.hatch.envs.lint]
|
|
68
68
|
installer = "uv"
|
|
69
69
|
detached = true
|
|
70
|
-
dependencies = [
|
|
70
|
+
dependencies = [
|
|
71
|
+
"pip",
|
|
72
|
+
"black>=23.1.0",
|
|
73
|
+
"mypy>=1.0.0",
|
|
74
|
+
"ruff>=0.0.243",
|
|
75
|
+
]
|
|
71
76
|
|
|
72
77
|
[tool.hatch.envs.lint.scripts]
|
|
73
78
|
typing = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
|
|
@@ -167,6 +172,7 @@ module = [
|
|
|
167
172
|
"haystack_integrations.*",
|
|
168
173
|
"pytest.*",
|
|
169
174
|
"numpy.*",
|
|
175
|
+
"httpx.*",
|
|
170
176
|
]
|
|
171
177
|
ignore_missing_imports = true
|
|
172
178
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from typing import Any, Dict, Optional
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import httpx
|
|
4
|
+
from haystack import component, default_from_dict, default_to_dict, logging, tracing
|
|
5
|
+
from haystack.utils import Secret, deserialize_secrets_inplace
|
|
4
6
|
|
|
5
7
|
from haystack_integrations.tracing.langfuse import LangfuseTracer
|
|
6
8
|
from langfuse import Langfuse
|
|
@@ -94,7 +96,14 @@ class LangfuseConnector:
|
|
|
94
96
|
|
|
95
97
|
"""
|
|
96
98
|
|
|
97
|
-
def __init__(
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
name: str,
|
|
102
|
+
public: bool = False,
|
|
103
|
+
public_key: Optional[Secret] = Secret.from_env_var("LANGFUSE_PUBLIC_KEY"), # noqa: B008
|
|
104
|
+
secret_key: Optional[Secret] = Secret.from_env_var("LANGFUSE_SECRET_KEY"), # noqa: B008
|
|
105
|
+
httpx_client: Optional[httpx.Client] = None,
|
|
106
|
+
):
|
|
98
107
|
"""
|
|
99
108
|
Initialize the LangfuseConnector component.
|
|
100
109
|
|
|
@@ -103,9 +112,25 @@ class LangfuseConnector:
|
|
|
103
112
|
:param public: Whether the tracing data should be public or private. If set to `True`, the tracing data will be
|
|
104
113
|
publicly accessible to anyone with the tracing URL. If set to `False`, the tracing data will be private and
|
|
105
114
|
only accessible to the Langfuse account owner. The default is `False`.
|
|
115
|
+
:param public_key: The Langfuse public key. Defaults to reading from LANGFUSE_PUBLIC_KEY environment variable.
|
|
116
|
+
:param secret_key: The Langfuse secret key. Defaults to reading from LANGFUSE_SECRET_KEY environment variable.
|
|
117
|
+
:param httpx_client: Optional custom httpx.Client instance to use for Langfuse API calls. Note that when
|
|
118
|
+
deserializing a pipeline from YAML, any custom client is discarded and Langfuse will create its own default
|
|
119
|
+
client, since HTTPX clients cannot be serialized.
|
|
106
120
|
"""
|
|
107
121
|
self.name = name
|
|
108
|
-
self.
|
|
122
|
+
self.public = public
|
|
123
|
+
self.secret_key = secret_key
|
|
124
|
+
self.public_key = public_key
|
|
125
|
+
self.tracer = LangfuseTracer(
|
|
126
|
+
tracer=Langfuse(
|
|
127
|
+
secret_key=secret_key.resolve_value() if secret_key else None,
|
|
128
|
+
public_key=public_key.resolve_value() if public_key else None,
|
|
129
|
+
httpx_client=httpx_client,
|
|
130
|
+
),
|
|
131
|
+
name=name,
|
|
132
|
+
public=public,
|
|
133
|
+
)
|
|
109
134
|
tracing.enable_tracing(self.tracer)
|
|
110
135
|
|
|
111
136
|
@component.output_types(name=str, trace_url=str)
|
|
@@ -126,3 +151,29 @@ class LangfuseConnector:
|
|
|
126
151
|
invocation_context=invocation_context,
|
|
127
152
|
)
|
|
128
153
|
return {"name": self.name, "trace_url": self.tracer.get_trace_url()}
|
|
154
|
+
|
|
155
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
156
|
+
"""
|
|
157
|
+
Serialize this component to a dictionary.
|
|
158
|
+
|
|
159
|
+
:returns: The serialized component as a dictionary.
|
|
160
|
+
"""
|
|
161
|
+
return default_to_dict(
|
|
162
|
+
self,
|
|
163
|
+
name=self.name,
|
|
164
|
+
public=self.public,
|
|
165
|
+
secret_key=self.secret_key.to_dict() if self.secret_key else None,
|
|
166
|
+
public_key=self.public_key.to_dict() if self.public_key else None,
|
|
167
|
+
# Note: httpx_client is not serialized as it's not serializable
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_dict(cls, data: Dict[str, Any]) -> "LangfuseConnector":
|
|
172
|
+
"""
|
|
173
|
+
Deserialize this component from a dictionary.
|
|
174
|
+
|
|
175
|
+
:param data: The dictionary representation of this component.
|
|
176
|
+
:returns: The deserialized component instance.
|
|
177
|
+
"""
|
|
178
|
+
deserialize_secrets_inplace(data["init_parameters"], keys=["secret_key", "public_key"])
|
|
179
|
+
return default_from_dict(cls, data)
|
|
@@ -201,7 +201,11 @@ class LangfuseTracer(Tracer):
|
|
|
201
201
|
)
|
|
202
202
|
|
|
203
203
|
raw_span = span.raw_span()
|
|
204
|
-
|
|
204
|
+
|
|
205
|
+
# In this section, we finalize both regular spans and generation spans created using the LangfuseSpan class.
|
|
206
|
+
# It's important to end() these spans to ensure they are properly closed and all relevant data is recorded.
|
|
207
|
+
# Note that we do not call end() on the main trace span itself, as its lifecycle is managed differently.
|
|
208
|
+
if isinstance(raw_span, (langfuse.client.StatefulSpanClient, langfuse.client.StatefulGenerationClient)):
|
|
205
209
|
raw_span.end()
|
|
206
210
|
self._context.pop()
|
|
207
211
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import requests
|
|
7
|
+
from haystack import Pipeline
|
|
8
|
+
from haystack.components.builders import ChatPromptBuilder
|
|
9
|
+
from haystack.components.generators.chat import OpenAIChatGenerator
|
|
10
|
+
from haystack.dataclasses import ChatMessage
|
|
11
|
+
from haystack.utils import Secret
|
|
12
|
+
from requests.auth import HTTPBasicAuth
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from haystack_integrations.components.connectors.langfuse import LangfuseConnector
|
|
16
|
+
from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
|
|
17
|
+
from haystack_integrations.components.generators.cohere import CohereChatGenerator
|
|
18
|
+
|
|
19
|
+
# don't remove (or move) this env var setting from here, it's needed to turn tracing on
|
|
20
|
+
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def pipeline_with_env_vars(llm_class, expected_trace):
|
|
25
|
+
"""Pipeline factory using environment variables for Langfuse authentication"""
|
|
26
|
+
pipe = Pipeline()
|
|
27
|
+
pipe.add_component("tracer", LangfuseConnector(name=f"Chat example - {expected_trace}", public=True))
|
|
28
|
+
pipe.add_component("prompt_builder", ChatPromptBuilder())
|
|
29
|
+
pipe.add_component("llm", llm_class())
|
|
30
|
+
pipe.connect("prompt_builder.prompt", "llm.messages")
|
|
31
|
+
return pipe
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def pipeline_with_secrets(llm_class, expected_trace):
|
|
36
|
+
"""Pipeline factory using Secret objects for Langfuse authentication"""
|
|
37
|
+
pipe = Pipeline()
|
|
38
|
+
pipe.add_component(
|
|
39
|
+
"tracer",
|
|
40
|
+
LangfuseConnector(
|
|
41
|
+
name=f"Chat example - {expected_trace}",
|
|
42
|
+
public=True,
|
|
43
|
+
secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"),
|
|
44
|
+
public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"),
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
pipe.add_component("prompt_builder", ChatPromptBuilder())
|
|
48
|
+
pipe.add_component("llm", llm_class())
|
|
49
|
+
pipe.connect("prompt_builder.prompt", "llm.messages")
|
|
50
|
+
return pipe
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def pipeline_with_custom_client(llm_class, expected_trace):
|
|
55
|
+
"""Pipeline factory using custom httpx client for Langfuse"""
|
|
56
|
+
pipe = Pipeline()
|
|
57
|
+
custom_client = httpx.Client(timeout=30.0) # Custom timeout of 30 seconds
|
|
58
|
+
pipe.add_component(
|
|
59
|
+
"tracer",
|
|
60
|
+
LangfuseConnector(
|
|
61
|
+
name=f"Chat example - {expected_trace}",
|
|
62
|
+
public=True,
|
|
63
|
+
secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"),
|
|
64
|
+
public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"),
|
|
65
|
+
httpx_client=custom_client,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
pipe.add_component("prompt_builder", ChatPromptBuilder())
|
|
69
|
+
pipe.add_component("llm", llm_class())
|
|
70
|
+
pipe.connect("prompt_builder.prompt", "llm.messages")
|
|
71
|
+
return pipe
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.integration
|
|
75
|
+
@pytest.mark.parametrize(
|
|
76
|
+
"llm_class, env_var, expected_trace",
|
|
77
|
+
[
|
|
78
|
+
(OpenAIChatGenerator, "OPENAI_API_KEY", "OpenAI"),
|
|
79
|
+
(AnthropicChatGenerator, "ANTHROPIC_API_KEY", "Anthropic"),
|
|
80
|
+
(CohereChatGenerator, "COHERE_API_KEY", "Cohere"),
|
|
81
|
+
],
|
|
82
|
+
)
|
|
83
|
+
@pytest.mark.parametrize(
|
|
84
|
+
"pipeline_fixture", ["pipeline_with_env_vars", "pipeline_with_secrets", "pipeline_with_custom_client"]
|
|
85
|
+
)
|
|
86
|
+
def test_tracing_integration(llm_class, env_var, expected_trace, pipeline_fixture, request):
|
|
87
|
+
if not all([os.environ.get("LANGFUSE_SECRET_KEY"), os.environ.get("LANGFUSE_PUBLIC_KEY"), os.environ.get(env_var)]):
|
|
88
|
+
pytest.skip(f"Missing required environment variables: LANGFUSE_SECRET_KEY, LANGFUSE_PUBLIC_KEY, or {env_var}")
|
|
89
|
+
|
|
90
|
+
pipe = request.getfixturevalue(pipeline_fixture)
|
|
91
|
+
messages = [
|
|
92
|
+
ChatMessage.from_system("Always respond in German even if some input data is in other languages."),
|
|
93
|
+
ChatMessage.from_user("Tell me about {{location}}"),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
response = pipe.run(
|
|
97
|
+
data={
|
|
98
|
+
"prompt_builder": {"template_variables": {"location": "Berlin"}, "template": messages},
|
|
99
|
+
"tracer": {"invocation_context": {"user_id": "user_42"}},
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
assert "Berlin" in response["llm"]["replies"][0].text
|
|
103
|
+
assert response["tracer"]["trace_url"]
|
|
104
|
+
|
|
105
|
+
trace_url = response["tracer"]["trace_url"]
|
|
106
|
+
uuid = os.path.basename(urlparse(trace_url).path)
|
|
107
|
+
url = f"https://cloud.langfuse.com/api/public/traces/{uuid}"
|
|
108
|
+
|
|
109
|
+
# Poll the Langfuse API a bit as the trace might not be ready right away
|
|
110
|
+
attempts = 5
|
|
111
|
+
delay = 1
|
|
112
|
+
while attempts >= 0:
|
|
113
|
+
res = requests.get(
|
|
114
|
+
url, auth=HTTPBasicAuth(os.environ["LANGFUSE_PUBLIC_KEY"], os.environ["LANGFUSE_SECRET_KEY"])
|
|
115
|
+
)
|
|
116
|
+
if attempts > 0 and res.status_code != 200:
|
|
117
|
+
attempts -= 1
|
|
118
|
+
time.sleep(delay)
|
|
119
|
+
delay *= 2
|
|
120
|
+
continue
|
|
121
|
+
assert res.status_code == 200, f"Failed to retrieve data from Langfuse API: {res.status_code}"
|
|
122
|
+
|
|
123
|
+
# check if the trace contains the expected LLM name
|
|
124
|
+
assert expected_trace in str(res.content)
|
|
125
|
+
# check if the trace contains the expected generation span
|
|
126
|
+
assert "GENERATION" in str(res.content)
|
|
127
|
+
# check if the trace contains the expected user_id
|
|
128
|
+
assert "user_42" in str(res.content)
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_pipeline_serialization(monkeypatch):
|
|
133
|
+
"""Test that a pipeline with secrets can be properly serialized and deserialized"""
|
|
134
|
+
|
|
135
|
+
# Set test env vars
|
|
136
|
+
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "secret")
|
|
137
|
+
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "public")
|
|
138
|
+
monkeypatch.setenv("OPENAI_API_KEY", "openai_api_key")
|
|
139
|
+
|
|
140
|
+
# Create pipeline with OpenAI LLM
|
|
141
|
+
pipe = Pipeline()
|
|
142
|
+
pipe.add_component(
|
|
143
|
+
"tracer",
|
|
144
|
+
LangfuseConnector(
|
|
145
|
+
name="Chat example - OpenAI",
|
|
146
|
+
public=True,
|
|
147
|
+
secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"),
|
|
148
|
+
public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"),
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
pipe.add_component("prompt_builder", ChatPromptBuilder())
|
|
152
|
+
pipe.add_component("llm", OpenAIChatGenerator())
|
|
153
|
+
pipe.connect("prompt_builder.prompt", "llm.messages")
|
|
154
|
+
|
|
155
|
+
# Serialize
|
|
156
|
+
serialized = pipe.to_dict()
|
|
157
|
+
|
|
158
|
+
# Check serialized secrets
|
|
159
|
+
tracer_params = serialized["components"]["tracer"]["init_parameters"]
|
|
160
|
+
assert isinstance(tracer_params["secret_key"], dict)
|
|
161
|
+
assert tracer_params["secret_key"]["type"] == "env_var"
|
|
162
|
+
assert tracer_params["secret_key"]["env_vars"] == ["LANGFUSE_SECRET_KEY"]
|
|
163
|
+
assert isinstance(tracer_params["public_key"], dict)
|
|
164
|
+
assert tracer_params["public_key"]["type"] == "env_var"
|
|
165
|
+
assert tracer_params["public_key"]["env_vars"] == ["LANGFUSE_PUBLIC_KEY"]
|
|
166
|
+
|
|
167
|
+
# Deserialize
|
|
168
|
+
new_pipe = Pipeline.from_dict(serialized)
|
|
169
|
+
|
|
170
|
+
# Verify pipeline is the same
|
|
171
|
+
assert new_pipe == pipe
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import random
|
|
3
|
-
import time
|
|
4
|
-
from urllib.parse import urlparse
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
import requests
|
|
8
|
-
from haystack import Pipeline
|
|
9
|
-
from haystack.components.builders import ChatPromptBuilder
|
|
10
|
-
from haystack.components.generators.chat import OpenAIChatGenerator
|
|
11
|
-
from haystack.dataclasses import ChatMessage
|
|
12
|
-
from requests.auth import HTTPBasicAuth
|
|
13
|
-
|
|
14
|
-
from haystack_integrations.components.connectors.langfuse import LangfuseConnector
|
|
15
|
-
from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
|
|
16
|
-
from haystack_integrations.components.generators.cohere import CohereChatGenerator
|
|
17
|
-
|
|
18
|
-
# don't remove (or move) this env var setting from here, it's needed to turn tracing on
|
|
19
|
-
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@pytest.mark.integration
|
|
23
|
-
@pytest.mark.parametrize(
|
|
24
|
-
"llm_class, env_var, expected_trace",
|
|
25
|
-
[
|
|
26
|
-
(OpenAIChatGenerator, "OPENAI_API_KEY", "OpenAI"),
|
|
27
|
-
(AnthropicChatGenerator, "ANTHROPIC_API_KEY", "Anthropic"),
|
|
28
|
-
(CohereChatGenerator, "COHERE_API_KEY", "Cohere"),
|
|
29
|
-
],
|
|
30
|
-
)
|
|
31
|
-
def test_tracing_integration(llm_class, env_var, expected_trace):
|
|
32
|
-
if not all([os.environ.get("LANGFUSE_SECRET_KEY"), os.environ.get("LANGFUSE_PUBLIC_KEY"), os.environ.get(env_var)]):
|
|
33
|
-
pytest.skip(f"Missing required environment variables: LANGFUSE_SECRET_KEY, LANGFUSE_PUBLIC_KEY, or {env_var}")
|
|
34
|
-
|
|
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
|
-
|
|
41
|
-
messages = [
|
|
42
|
-
ChatMessage.from_system("Always respond in German even if some input data is in other languages."),
|
|
43
|
-
ChatMessage.from_user("Tell me about {{location}}"),
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
response = pipe.run(
|
|
47
|
-
data={
|
|
48
|
-
"prompt_builder": {"template_variables": {"location": "Berlin"}, "template": messages},
|
|
49
|
-
"tracer": {"invocation_context": {"user_id": "user_42"}},
|
|
50
|
-
}
|
|
51
|
-
)
|
|
52
|
-
assert "Berlin" in response["llm"]["replies"][0].text
|
|
53
|
-
assert response["tracer"]["trace_url"]
|
|
54
|
-
|
|
55
|
-
trace_url = response["tracer"]["trace_url"]
|
|
56
|
-
uuid = os.path.basename(urlparse(trace_url).path)
|
|
57
|
-
url = f"https://cloud.langfuse.com/api/public/traces/{uuid}"
|
|
58
|
-
|
|
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"])
|
|
65
|
-
)
|
|
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}"
|
|
72
|
-
|
|
73
|
-
# check if the trace contains the expected LLM name
|
|
74
|
-
assert expected_trace in str(res.content)
|
|
75
|
-
# check if the trace contains the expected generation span
|
|
76
|
-
assert "GENERATION" in str(res.content)
|
|
77
|
-
# check if the trace contains the expected user_id
|
|
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
|