langfuse-haystack 3.3.1__tar.gz → 4.0.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-3.3.1 → langfuse_haystack-4.0.1}/CHANGELOG.md +23 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/PKG-INFO +3 -4
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/pyproject.toml +3 -9
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/src/haystack_integrations/components/connectors/langfuse/langfuse_connector.py +8 -8
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/src/haystack_integrations/tracing/langfuse/tracer.py +15 -11
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/tests/test_tracer.py +40 -2
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/.gitignore +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/LICENSE.txt +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/README.md +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/example/basic_rag.py +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/example/chat.py +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/example/requirements.txt +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/pydoc/config_docusaurus.yml +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/src/haystack_integrations/components/connectors/__init__.py +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/src/haystack_integrations/components/connectors/langfuse/__init__.py +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/src/haystack_integrations/components/connectors/py.typed +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/src/haystack_integrations/tracing/langfuse/__init__.py +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/src/haystack_integrations/tracing/py.typed +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/tests/__init__.py +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/tests/test_langfuse_connector.py +0 -0
- {langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/tests/test_tracing.py +0 -0
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/langfuse-v4.0.0] - 2026-01-12
|
|
4
|
+
|
|
5
|
+
### 🧹 Chores
|
|
6
|
+
|
|
7
|
+
- Make fmt command more forgiving (#2671)
|
|
8
|
+
- [**breaking**] Langfuse - drop Python 3.9 and use X|Y typing (#2709)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [integrations/langfuse-v3.3.1] - 2025-12-10
|
|
12
|
+
|
|
13
|
+
### 🚀 Features
|
|
14
|
+
|
|
15
|
+
- *(langfuse)* Add embedder usage metrics for langfuse (#2542)
|
|
16
|
+
|
|
17
|
+
### 🐛 Bug Fixes
|
|
18
|
+
|
|
19
|
+
- Correct token usage accounting, fix trace polling in tests (#2594)
|
|
20
|
+
|
|
21
|
+
### 🧹 Chores
|
|
22
|
+
|
|
23
|
+
- Remove Readme API CI workflow and configs (#2573)
|
|
24
|
+
|
|
25
|
+
|
|
3
26
|
## [integrations/langfuse-v3.3.0] - 2025-11-21
|
|
4
27
|
|
|
5
28
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langfuse-haystack
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.1
|
|
4
4
|
Summary: Langfuse integration for Haystack
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -10,15 +10,14 @@ License-Expression: Apache-2.0
|
|
|
10
10
|
License-File: LICENSE.txt
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Programming Language :: Python
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
17
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
18
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
|
-
Requires-Python: >=3.
|
|
21
|
-
Requires-Dist: haystack-ai>=2.
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: haystack-ai>=2.22.0
|
|
22
21
|
Requires-Dist: langfuse<4.0.0,>=3.3.1
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
|
|
@@ -7,14 +7,13 @@ name = "langfuse-haystack"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Langfuse integration for Haystack"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
keywords = []
|
|
13
13
|
authors = [{ name = "deepset GmbH", email = "info@deepset.ai" }]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 4 - Beta",
|
|
16
16
|
"Programming Language :: Python",
|
|
17
|
-
"Programming Language :: Python :: 3.9",
|
|
18
17
|
"Programming Language :: Python :: 3.10",
|
|
19
18
|
"Programming Language :: Python :: 3.11",
|
|
20
19
|
"Programming Language :: Python :: 3.12",
|
|
@@ -22,7 +21,7 @@ classifiers = [
|
|
|
22
21
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
23
22
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
24
23
|
]
|
|
25
|
-
dependencies = ["haystack-ai>=2.
|
|
24
|
+
dependencies = ["haystack-ai>=2.22.0", "langfuse>=3.3.1, <4.0.0"]
|
|
26
25
|
|
|
27
26
|
[project.urls]
|
|
28
27
|
Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse#readme"
|
|
@@ -47,7 +46,7 @@ dependencies = ["haystack-pydoc-tools", "ruff"]
|
|
|
47
46
|
|
|
48
47
|
[tool.hatch.envs.default.scripts]
|
|
49
48
|
docs = ["pydoc-markdown pydoc/config_docusaurus.yml"]
|
|
50
|
-
fmt = "ruff check --fix {args}
|
|
49
|
+
fmt = "ruff check --fix {args}; ruff format {args}"
|
|
51
50
|
fmt-check = "ruff check {args} && ruff format --check {args}"
|
|
52
51
|
|
|
53
52
|
[tool.hatch.envs.test]
|
|
@@ -82,7 +81,6 @@ allow-direct-references = true
|
|
|
82
81
|
|
|
83
82
|
|
|
84
83
|
[tool.ruff]
|
|
85
|
-
target-version = "py39"
|
|
86
84
|
line-length = 120
|
|
87
85
|
|
|
88
86
|
[tool.ruff.lint]
|
|
@@ -128,10 +126,6 @@ ignore = [
|
|
|
128
126
|
# Asserts
|
|
129
127
|
"S101",
|
|
130
128
|
]
|
|
131
|
-
unfixable = [
|
|
132
|
-
# Don't touch unused imports
|
|
133
|
-
"F401",
|
|
134
|
-
]
|
|
135
129
|
|
|
136
130
|
[tool.ruff.lint.isort]
|
|
137
131
|
known-first-party = ["haystack_integrations"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
from haystack import component, default_from_dict, default_to_dict, logging, tracing
|
|
@@ -118,13 +118,13 @@ class LangfuseConnector:
|
|
|
118
118
|
self,
|
|
119
119
|
name: str,
|
|
120
120
|
public: bool = False,
|
|
121
|
-
public_key:
|
|
122
|
-
secret_key:
|
|
123
|
-
httpx_client:
|
|
124
|
-
span_handler:
|
|
121
|
+
public_key: Secret | None = Secret.from_env_var("LANGFUSE_PUBLIC_KEY"), # noqa: B008
|
|
122
|
+
secret_key: Secret | None = Secret.from_env_var("LANGFUSE_SECRET_KEY"), # noqa: B008
|
|
123
|
+
httpx_client: httpx.Client | None = None,
|
|
124
|
+
span_handler: SpanHandler | None = None,
|
|
125
125
|
*,
|
|
126
|
-
host:
|
|
127
|
-
langfuse_client_kwargs:
|
|
126
|
+
host: str | None = None,
|
|
127
|
+
langfuse_client_kwargs: dict[str, Any] | None = None,
|
|
128
128
|
) -> None:
|
|
129
129
|
"""
|
|
130
130
|
Initialize the LangfuseConnector component.
|
|
@@ -172,7 +172,7 @@ class LangfuseConnector:
|
|
|
172
172
|
tracing.enable_tracing(self.tracer)
|
|
173
173
|
|
|
174
174
|
@component.output_types(name=str, trace_url=str, trace_id=str)
|
|
175
|
-
def run(self, invocation_context:
|
|
175
|
+
def run(self, invocation_context: dict[str, Any] | None = None) -> dict[str, str]:
|
|
176
176
|
"""
|
|
177
177
|
Runs the LangfuseConnector component.
|
|
178
178
|
|
|
@@ -12,7 +12,7 @@ from contextlib import AbstractContextManager
|
|
|
12
12
|
from contextvars import ContextVar
|
|
13
13
|
from dataclasses import dataclass
|
|
14
14
|
from datetime import datetime
|
|
15
|
-
from typing import Any, Literal,
|
|
15
|
+
from typing import Any, Literal, cast
|
|
16
16
|
|
|
17
17
|
from haystack import default_from_dict, default_to_dict, logging
|
|
18
18
|
from haystack.dataclasses import ChatMessage
|
|
@@ -22,7 +22,7 @@ from haystack.tracing import utils as tracing_utils
|
|
|
22
22
|
|
|
23
23
|
import langfuse
|
|
24
24
|
from langfuse import LangfuseSpan as LangfuseClientSpan
|
|
25
|
-
from langfuse.types import TraceMetadata
|
|
25
|
+
from langfuse.types import TraceContext, TraceMetadata
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
28
28
|
|
|
@@ -48,7 +48,7 @@ tracing_context_var: ContextVar[dict[Any, Any]] = ContextVar("tracing_context")
|
|
|
48
48
|
|
|
49
49
|
# Internal span execution hierarchy for our tracer
|
|
50
50
|
# Manages parent-child relationships and prevents cross-request span interleaving
|
|
51
|
-
span_stack_var: ContextVar[
|
|
51
|
+
span_stack_var: ContextVar[list["LangfuseSpan"] | None] = ContextVar("span_stack", default=None)
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class LangfuseSpan(Span):
|
|
@@ -150,9 +150,9 @@ class SpanContext:
|
|
|
150
150
|
|
|
151
151
|
name: str
|
|
152
152
|
operation_name: str
|
|
153
|
-
component_type:
|
|
153
|
+
component_type: str | None
|
|
154
154
|
tags: dict[str, Any]
|
|
155
|
-
parent_span:
|
|
155
|
+
parent_span: Span | None
|
|
156
156
|
trace_name: str = "Haystack"
|
|
157
157
|
public: bool = False
|
|
158
158
|
|
|
@@ -189,7 +189,7 @@ class SpanHandler(ABC):
|
|
|
189
189
|
"""
|
|
190
190
|
|
|
191
191
|
def __init__(self) -> None:
|
|
192
|
-
self.tracer:
|
|
192
|
+
self.tracer: langfuse.Langfuse | None = None
|
|
193
193
|
|
|
194
194
|
def init_tracer(self, tracer: langfuse.Langfuse) -> None:
|
|
195
195
|
"""
|
|
@@ -215,7 +215,7 @@ class SpanHandler(ABC):
|
|
|
215
215
|
pass
|
|
216
216
|
|
|
217
217
|
@abstractmethod
|
|
218
|
-
def handle(self, span: LangfuseSpan, component_type:
|
|
218
|
+
def handle(self, span: LangfuseSpan, component_type: str | None) -> None:
|
|
219
219
|
"""
|
|
220
220
|
Process a span after component execution by attaching metadata and metrics.
|
|
221
221
|
|
|
@@ -287,8 +287,12 @@ class DefaultSpanHandler(SpanHandler):
|
|
|
287
287
|
root_span_type: Literal["agent", "span"] = (
|
|
288
288
|
"agent" if context.operation_name == "haystack.agent.run" else "span"
|
|
289
289
|
)
|
|
290
|
+
trace_context: TraceContext | None = cast(
|
|
291
|
+
TraceContext, {"trace_id": tracing_ctx.get("trace_id")} if tracing_ctx.get("trace_id") else None
|
|
292
|
+
)
|
|
290
293
|
# Create a new trace when there's no parent span
|
|
291
294
|
span_context_manager = self.tracer.start_as_current_observation(
|
|
295
|
+
trace_context=trace_context,
|
|
292
296
|
name=context.trace_name,
|
|
293
297
|
version=tracing_ctx.get("version"),
|
|
294
298
|
as_type=root_span_type,
|
|
@@ -338,7 +342,7 @@ class DefaultSpanHandler(SpanHandler):
|
|
|
338
342
|
else:
|
|
339
343
|
return LangfuseSpan(self.tracer.start_as_current_span(name=context.name))
|
|
340
344
|
|
|
341
|
-
def handle(self, span: LangfuseSpan, component_type:
|
|
345
|
+
def handle(self, span: LangfuseSpan, component_type: str | None) -> None:
|
|
342
346
|
# If the span is at the pipeline level, we add input and output keys to the span
|
|
343
347
|
at_pipeline_level = span.get_data().get(_PIPELINE_INPUT_KEY) is not None
|
|
344
348
|
if at_pipeline_level:
|
|
@@ -420,7 +424,7 @@ class LangfuseTracer(Tracer):
|
|
|
420
424
|
tracer: langfuse.Langfuse,
|
|
421
425
|
name: str = "Haystack",
|
|
422
426
|
public: bool = False,
|
|
423
|
-
span_handler:
|
|
427
|
+
span_handler: SpanHandler | None = None,
|
|
424
428
|
) -> None:
|
|
425
429
|
"""
|
|
426
430
|
Initialize a LangfuseTracer instance.
|
|
@@ -450,7 +454,7 @@ class LangfuseTracer(Tracer):
|
|
|
450
454
|
|
|
451
455
|
@contextlib.contextmanager
|
|
452
456
|
def trace(
|
|
453
|
-
self, operation_name: str, tags:
|
|
457
|
+
self, operation_name: str, tags: dict[str, Any] | None = None, parent_span: Span | None = None
|
|
454
458
|
) -> Iterator[Span]:
|
|
455
459
|
tags = tags or {}
|
|
456
460
|
span_name = tags.get(_COMPONENT_NAME_KEY, operation_name)
|
|
@@ -541,7 +545,7 @@ class LangfuseTracer(Tracer):
|
|
|
541
545
|
def flush(self) -> None:
|
|
542
546
|
self._tracer.flush()
|
|
543
547
|
|
|
544
|
-
def current_span(self) ->
|
|
548
|
+
def current_span(self) -> Span | None:
|
|
545
549
|
"""
|
|
546
550
|
Return the current active span.
|
|
547
551
|
|
|
@@ -6,7 +6,6 @@ import asyncio
|
|
|
6
6
|
import datetime
|
|
7
7
|
import logging
|
|
8
8
|
import sys
|
|
9
|
-
from typing import Optional
|
|
10
9
|
from unittest.mock import MagicMock, Mock, patch
|
|
11
10
|
|
|
12
11
|
import pytest
|
|
@@ -19,6 +18,7 @@ from haystack_integrations.tracing.langfuse.tracer import (
|
|
|
19
18
|
LangfuseTracer,
|
|
20
19
|
SpanContext,
|
|
21
20
|
_sanitize_usage_data,
|
|
21
|
+
tracing_context_var,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
@@ -107,7 +107,7 @@ class MockLangfuseClient:
|
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
class CustomSpanHandler(DefaultSpanHandler):
|
|
110
|
-
def handle(self, span: LangfuseSpan, component_type:
|
|
110
|
+
def handle(self, span: LangfuseSpan, component_type: str | None) -> None:
|
|
111
111
|
if component_type == "OpenAIChatGenerator":
|
|
112
112
|
output = span.get_data().get(_COMPONENT_OUTPUT_KEY, {})
|
|
113
113
|
replies = output.get("replies", [])
|
|
@@ -223,6 +223,44 @@ class TestSanitizeUsageData:
|
|
|
223
223
|
|
|
224
224
|
|
|
225
225
|
class TestDefaultSpanHandler:
|
|
226
|
+
def test_create_span_root_trace(self):
|
|
227
|
+
"""Test creating a root span (no parent)."""
|
|
228
|
+
mock_client = Mock()
|
|
229
|
+
mock_client.start_as_current_span = Mock(return_value=MockContextManager())
|
|
230
|
+
mock_client.start_as_current_observation = Mock(return_value=MockContextManager())
|
|
231
|
+
|
|
232
|
+
handler = DefaultSpanHandler()
|
|
233
|
+
handler.init_tracer(mock_client)
|
|
234
|
+
|
|
235
|
+
context = MagicMock(spec=SpanContext)
|
|
236
|
+
context.parent_span = None
|
|
237
|
+
context.trace_name = "test_trace"
|
|
238
|
+
context.operation_name = "haystack.pipeline.run"
|
|
239
|
+
context.public = False
|
|
240
|
+
|
|
241
|
+
# Set up tracing context
|
|
242
|
+
token = tracing_context_var.set(
|
|
243
|
+
{"user_id": "test_user", "session_id": "test_session", "trace_id": "test_trace_id"}
|
|
244
|
+
)
|
|
245
|
+
try:
|
|
246
|
+
result = handler.create_span(context)
|
|
247
|
+
|
|
248
|
+
# Assert tracer was called with correct parameters
|
|
249
|
+
handler.tracer.start_as_current_observation.assert_called_once_with(
|
|
250
|
+
trace_context={"trace_id": "test_trace_id"},
|
|
251
|
+
name="test_trace",
|
|
252
|
+
version=None,
|
|
253
|
+
as_type="span",
|
|
254
|
+
)
|
|
255
|
+
call_kwargs = handler.tracer.start_as_current_observation.call_args[1]
|
|
256
|
+
assert "test_trace" == call_kwargs["name"]
|
|
257
|
+
assert "span" == call_kwargs["as_type"]
|
|
258
|
+
|
|
259
|
+
# Assert result is a LangfuseSpan
|
|
260
|
+
assert isinstance(result, LangfuseSpan)
|
|
261
|
+
finally:
|
|
262
|
+
tracing_context_var.reset(token)
|
|
263
|
+
|
|
226
264
|
def test_handle_generator(self):
|
|
227
265
|
mock_span = Mock()
|
|
228
266
|
mock_span.raw_span.return_value = mock_span
|
|
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
|
{langfuse_haystack-3.3.1 → langfuse_haystack-4.0.1}/src/haystack_integrations/tracing/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|