Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
18a7f21
chore!: require Python 3.11 or newer
cursoragent Oct 6, 2026
ee38a2b
feat(openai)!: drop support for OpenAI SDK versions below 1.0
cursoragent Oct 6, 2026
95b341d
feat(otel)!: send x-langfuse-ingestion-version: 4 by default
cursoragent Oct 6, 2026
466808b
feat(client)!: remove blocked_instrumentation_scopes
cursoragent Oct 6, 2026
4cffec5
fix(observe): keep detecting legacy-marked coroutine functions
cursoragent Oct 7, 2026
aa14c25
Merge branch 'lfe-17080-python-sdk-v5-drop-openai-sdk-10-support' int…
cursoragent Oct 7, 2026
90102f8
Merge branch 'lfe-17074-python-sdk-v5-send-x-langfuse-ingestion-versi…
cursoragent Oct 7, 2026
d1cb7a1
Merge branch 'lfe-16917-python-sdk-add-python-315-support-and-drop-31…
cursoragent Oct 7, 2026
846cc7f
test(otel): pin that a mixed-case ingestion-version override wins
cursoragent Oct 7, 2026
ab0e49d
Merge branch 'lfe-17074-python-sdk-v5-send-x-langfuse-ingestion-versi…
cursoragent Oct 7, 2026
5c4bb1f
Merge branch 'prepare-v5-release' into lfe-17080-python-sdk-v5-drop-o…
cursoragent Oct 7, 2026
3eea389
Merge branch 'lfe-17080-python-sdk-v5-drop-openai-sdk-10-support' int…
cursoragent Oct 7, 2026
6c0165d
Merge branch 'lfe-17074-python-sdk-v5-send-x-langfuse-ingestion-versi…
cursoragent Oct 7, 2026
ac1eeb0
Merge branch 'prepare-v5-release' into lfe-17078-python-sdk-v5-remove…
cursoragent Oct 7, 2026
c7c709a
feat(client): keep host and LANGFUSE_HOST working without a warning
cursoragent Oct 7, 2026
06f4965
chore: re-run CI
cursoragent Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions langfuse/_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import re
import urllib.parse
import uuid
import warnings
from datetime import datetime
from hashlib import sha256
from time import time_ns
Expand Down Expand Up @@ -251,19 +250,6 @@ def mask_otel_spans(

langfuse = Langfuse(mask_otel_spans=mask_otel_spans)
```
blocked_instrumentation_scopes (Optional[List[str]]): Deprecated. Use `should_export_span` instead. Equivalent behavior:
```python
from langfuse.span_filter import is_default_export_span
blocked = {"sqlite", "requests"}

should_export_span = lambda span: (
is_default_export_span(span)
and (
span.instrumentation_scope is None
or span.instrumentation_scope.name not in blocked
)
)
```
should_export_span (Optional[Callable[[ReadableSpan], bool]]): Callback to decide whether to export a span. If omitted, Langfuse uses the default filter (Langfuse SDK spans, spans with `gen_ai.*` attributes, and known LLM instrumentation scopes).
additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and in the default OTLPSpanExporter requests. These headers will be merged with default headers. Note: If httpx_client is provided, additional_headers must be set directly on your custom httpx_client as well. If `span_exporter` is provided, these headers are not wired into that exporter and must be configured on the exporter instance directly.
tracer_provider(Optional[TracerProvider]): OpenTelemetry TracerProvider to use for Langfuse. This can be useful to set to have disconnected tracing between Langfuse and other OpenTelemetry-span emitting libraries. Note: To track active spans, the context is still shared between TracerProviders. This may lead to broken trace trees.
Expand Down Expand Up @@ -330,7 +316,6 @@ def __init__(
sample_rate: Optional[float] = None,
mask: Optional[MaskFunction] = None,
mask_otel_spans: Optional[MaskOtelSpansFunction] = None,
blocked_instrumentation_scopes: Optional[List[str]] = None,
should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
additional_headers: Optional[Dict[str, str]] = None,
tracer_provider: Optional[TracerProvider] = None,
Expand Down Expand Up @@ -403,18 +388,6 @@ def __init__(
"OTEL_SDK_DISABLED is set. Langfuse tracing will be disabled and no traces will appear in the UI."
)

if blocked_instrumentation_scopes is not None:
warnings.warn(
"`blocked_instrumentation_scopes` is deprecated and will be removed in a future release. "
"Use `should_export_span` instead. Example: "
"from langfuse.span_filter import is_default_export_span; "
'blocked={"scope"}; should_export_span=lambda span: '
"is_default_export_span(span) and (span.instrumentation_scope is None or "
"span.instrumentation_scope.name not in blocked).",
DeprecationWarning,
stacklevel=2,
)

# Initialize api and tracer if requirements are met
self._resources = LangfuseResourceManager(
public_key=public_key,
Expand All @@ -431,7 +404,6 @@ def __init__(
mask=mask,
mask_otel_spans=mask_otel_spans,
tracing_enabled=self._tracing_enabled,
blocked_instrumentation_scopes=blocked_instrumentation_scopes,
should_export_span=should_export_span,
additional_headers=additional_headers,
tracer_provider=tracer_provider,
Expand Down Expand Up @@ -4137,7 +4109,6 @@ def create_prompt(
Keyword Args:
name : The name of the prompt to be created.
prompt : The content of the prompt to be created.
is_active [DEPRECATED] : A flag indicating whether the prompt is active or not. This is deprecated and will be removed in a future release. Please use the 'production' label instead.
labels: The labels of the prompt. Defaults to None. To create a default-served prompt, add the 'production' label.
tags: The tags of the prompt. Defaults to None. Will be applied to all versions of the prompt.
config: Additional structured data to be saved with the prompt. Defaults to None.
Expand Down
1 change: 0 additions & 1 deletion langfuse/_client/get_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ def _create_client_from_instance(
sample_rate=instance.sample_rate,
mask=instance.mask,
mask_otel_spans=instance.mask_otel_spans,
blocked_instrumentation_scopes=instance.blocked_instrumentation_scopes,
should_export_span=instance.should_export_span,
additional_headers=instance.additional_headers,
tracer_provider=instance.tracer_provider,
Expand Down
5 changes: 0 additions & 5 deletions langfuse/_client/resource_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ def __new__(
mask: Optional[MaskFunction] = None,
mask_otel_spans: Optional[MaskOtelSpansFunction] = None,
tracing_enabled: Optional[bool] = None,
blocked_instrumentation_scopes: Optional[List[str]] = None,
should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
additional_headers: Optional[Dict[str, str]] = None,
tracer_provider: Optional[TracerProvider] = None,
Expand Down Expand Up @@ -166,7 +165,6 @@ def __new__(
tracing_enabled=tracing_enabled
if tracing_enabled is not None
else True,
blocked_instrumentation_scopes=blocked_instrumentation_scopes,
should_export_span=should_export_span,
additional_headers=additional_headers,
tracer_provider=tracer_provider,
Expand Down Expand Up @@ -196,7 +194,6 @@ def _initialize_instance(
mask: Optional[MaskFunction] = None,
mask_otel_spans: Optional[MaskOtelSpansFunction] = None,
tracing_enabled: bool = True,
blocked_instrumentation_scopes: Optional[List[str]] = None,
should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
additional_headers: Optional[Dict[str, str]] = None,
tracer_provider: Optional[TracerProvider] = None,
Expand All @@ -220,7 +217,6 @@ def _initialize_instance(
self.release = release
self.media_upload_thread_count = media_upload_thread_count
self.sample_rate = sample_rate
self.blocked_instrumentation_scopes = blocked_instrumentation_scopes
self.should_export_span = should_export_span
self.additional_headers = additional_headers
self.id_generator = id_generator
Expand Down Expand Up @@ -259,7 +255,6 @@ def _initialize_instance(
timeout=timeout,
flush_at=flush_at,
flush_interval=flush_interval,
blocked_instrumentation_scopes=blocked_instrumentation_scopes,
should_export_span=should_export_span,
additional_headers=additional_headers,
span_exporter=span_exporter,
Expand Down
27 changes: 1 addition & 26 deletions langfuse/_client/span_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import logging
import os
import threading
from typing import Callable, Dict, List, Literal, Optional, cast
from typing import Callable, Dict, Literal, Optional, cast

from opentelemetry import context as context_api
from opentelemetry.context import Context
Expand Down Expand Up @@ -121,7 +121,6 @@ def __init__(
timeout: Optional[int] = None,
flush_at: Optional[int] = None,
flush_interval: Optional[float] = None,
blocked_instrumentation_scopes: Optional[List[str]] = None,
should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
additional_headers: Optional[Dict[str, str]] = None,
span_exporter: Optional[SpanExporter] = None,
Expand All @@ -130,11 +129,6 @@ def __init__(
otel_compression: Optional[Literal["gzip", "none"]] = None,
):
self.public_key = public_key
self.blocked_instrumentation_scopes = (
blocked_instrumentation_scopes
if blocked_instrumentation_scopes is not None
else []
)
self._should_export_span = should_export_span or is_default_export_span

self._app_root_lock = threading.Lock()
Expand Down Expand Up @@ -258,16 +252,6 @@ def on_end(self, span: ReadableSpan) -> None:
)
return

# Do not export spans from blocked instrumentation scopes
if self._is_blocked_instrumentation_scope(span):
langfuse_logger.debug(
"Trace: Dropping span due to blocked instrumentation scope | "
"span_name='%s' | instrumentation_scope='%s'",
span.name,
self._get_scope_name(span),
)
return

# Apply custom or default span filter
try:
should_export = self._should_export_span(span)
Expand Down Expand Up @@ -342,9 +326,6 @@ def _is_expected_exported_at_start(self, span: Span) -> bool:
):
return False

if self._is_blocked_instrumentation_scope(readable_span):
return False

try:
return bool(self._should_export_span(readable_span))
except Exception as error:
Expand All @@ -359,12 +340,6 @@ def _is_expected_exported_at_start(self, span: Span) -> bool:

return False

def _is_blocked_instrumentation_scope(self, span: ReadableSpan) -> bool:
return (
span.instrumentation_scope is not None
and span.instrumentation_scope.name in self.blocked_instrumentation_scopes
)

def _is_langfuse_project_span(self, span: ReadableSpan) -> bool:
if not is_langfuse_span(span):
return False
Expand Down
4 changes: 0 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,6 @@ def mock_init(self: Any, **kwargs: Any) -> None:
from langfuse._client.span_filter import is_default_export_span

self.public_key = kwargs.get("public_key", "test-public-key")
blocked_scopes = kwargs.get("blocked_instrumentation_scopes")
self.blocked_instrumentation_scopes = (
blocked_scopes if blocked_scopes is not None else []
)
self._should_export_span = (
kwargs.get("should_export_span") or is_default_export_span
)
Expand Down
7 changes: 5 additions & 2 deletions tests/unit/test_app_root_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,15 @@ def test_active_langfuse_scope_sets_baggage_after_root_start(
assert "langfuse.trace.metadata.trace_id" not in spans["child"].attributes


def test_blocked_instrumentation_scope_parent_marks_child_as_app_root(
def test_filtered_scope_parent_marks_child_as_app_root(
memory_exporter,
):
tracer_provider, processor = _create_processor(
memory_exporter,
blocked_instrumentation_scopes=["blocked.scope"],
should_export_span=lambda span: (
span.instrumentation_scope is None
or span.instrumentation_scope.name != "blocked.scope"
),
)
blocked_tracer = tracer_provider.get_tracer("blocked.scope")
langfuse_tracer = _langfuse_tracer(tracer_provider)
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/test_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ def test_env_host_fallback(self, cleanup_env_vars):

assert client._base_url == "http://env-host.com"

def test_env_host_fallback_logs_no_warning(self, cleanup_env_vars, caplog):
"""Test that LANGFUSE_HOST keeps working without a deprecation warning."""
caplog.set_level("WARNING", logger="langfuse")
os.environ["LANGFUSE_HOST"] = "http://env-host.com"

client = Langfuse(public_key="test_pk", secret_key="test_sk")

assert client._base_url == "http://env-host.com"
assert not any("deprecated" in record.message for record in caplog.records)

def test_default_base_url(self, cleanup_env_vars):
"""Test that default base_url is used when nothing is set."""
client = Langfuse(
Expand Down
73 changes: 1 addition & 72 deletions tests/unit/test_otel.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,6 @@ def mock_init(self, **kwargs):
from langfuse._client.span_filter import is_default_export_span

self.public_key = kwargs.get("public_key", "test-key")
blocked_scopes = kwargs.get("blocked_instrumentation_scopes")
self.blocked_instrumentation_scopes = (
blocked_scopes if blocked_scopes is not None else []
)
self._should_export_span = (
kwargs.get("should_export_span") or is_default_export_span
)
Expand Down Expand Up @@ -2408,10 +2404,6 @@ def mock_processor_init(self, **kwargs):
from langfuse._client.span_filter import is_default_export_span

self.public_key = kwargs.get("public_key", "test-key")
blocked_scopes = kwargs.get("blocked_instrumentation_scopes")
self.blocked_instrumentation_scopes = (
blocked_scopes if blocked_scopes is not None else []
)
self._should_export_span = (
kwargs.get("should_export_span") or is_default_export_span
)
Expand Down Expand Up @@ -2446,16 +2438,13 @@ def mock_initialize(self, **kwargs):
# Call original_initialize to set up all the necessary attributes
original_initialize(self, **kwargs)

# Now create our custom LangfuseSpanProcessor with the actual blocked_instrumentation_scopes
# Now create our custom LangfuseSpanProcessor with the actual should_export_span
from langfuse._client.span_processor import LangfuseSpanProcessor

processor = LangfuseSpanProcessor(
public_key=self.public_key,
secret_key=self.secret_key,
base_url=self.base_url,
blocked_instrumentation_scopes=kwargs.get(
"blocked_instrumentation_scopes"
),
should_export_span=kwargs.get("should_export_span"),
)
# Replace its exporter with our test exporter
Expand Down Expand Up @@ -2661,38 +2650,6 @@ def test_custom_should_export_span_with_composition(
assert "known-span" in exported_span_names
assert "unknown-span" not in exported_span_names

def test_blocked_scopes_override_should_export(
self, instrumentation_filtering_setup
):
"""Test that blocked scopes are dropped even when callback allows all."""
with pytest.warns(DeprecationWarning, match="blocked_instrumentation_scopes"):
Langfuse(
public_key=instrumentation_filtering_setup["test_key"],
secret_key="test-secret-key",
base_url="http://localhost:3000",
blocked_instrumentation_scopes=["my-framework.worker"],
should_export_span=lambda span: True,
)

tracer_provider = instrumentation_filtering_setup["test_tracer_provider"]
blocked_tracer = tracer_provider.get_tracer("my-framework.worker")
allowed_tracer = tracer_provider.get_tracer("custom.allowed")

blocked_span = blocked_tracer.start_span("blocked-span")
blocked_span.end()
allowed_span = allowed_tracer.start_span("allowed-span")
allowed_span.end()
tracer_provider.force_flush()

exported_span_names = [
span.name
for span in instrumentation_filtering_setup[
"blocked_exporter"
].get_finished_spans()
]
assert "blocked-span" not in exported_span_names
assert "allowed-span" in exported_span_names

def test_should_export_span_with_none_uses_default(
self, instrumentation_filtering_setup
):
Expand Down Expand Up @@ -2758,34 +2715,6 @@ def _failing_filter(_span):
for record in caplog.records
)

def test_blocked_scope_drop_logs_scope_name(
self, instrumentation_filtering_setup, caplog
):
"""Test that blocked scope drops include scope names in debug logs."""
caplog.set_level("DEBUG", logger="langfuse")

with pytest.warns(DeprecationWarning, match="blocked_instrumentation_scopes"):
Langfuse(
public_key=instrumentation_filtering_setup["test_key"],
secret_key="test-secret-key",
base_url="http://localhost:3000",
blocked_instrumentation_scopes=["my.blocked.scope"],
should_export_span=lambda span: True,
)

tracer_provider = instrumentation_filtering_setup["test_tracer_provider"]
blocked_tracer = tracer_provider.get_tracer("my.blocked.scope")

span = blocked_tracer.start_span("blocked-debug-span")
span.end()
tracer_provider.force_flush()

assert any(
"Dropping span due to blocked instrumentation scope" in record.message
and "my.blocked.scope" in record.message
for record in caplog.records
)


class TestConcurrencyAndAsync(TestOTelBase):
"""Tests for asynchronous and concurrent span operations."""
Expand Down
Loading