Skip to content

Commit 39e7de9

Browse files
committed
push
1 parent b97fc2d commit 39e7de9

3 files changed

Lines changed: 191 additions & 6 deletions

File tree

langfuse/_client/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class Langfuse:
105105
sample_rate (Optional[float]): Sampling rate for traces (0.0 to 1.0). Defaults to 1.0 (100% of traces are sampled). Can also be set via LANGFUSE_SAMPLE_RATE environment variable.
106106
mask (Optional[MaskFunction]): Function to mask sensitive data in traces before sending to the API.
107107
blocked_instrumentation_scopes (Optional[List[str]]): List of instrumentation scope names to block from being exported to Langfuse. Spans from these scopes will be filtered out before being sent to the API. Useful for filtering out spans from specific libraries or frameworks. For exported spans, you can see the instrumentation scope name in the span metadata in Langfuse (`metadata.scope.name`)
108-
additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and OTLPSpanExporter requests. These headers will be merged with default headers.
108+
additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and OTLPSpanExporter requests. These headers will be merged with default headers. Note: If httpx_client is provided, additional_headers will be ignored and a warning will be logged. Configure headers directly on your custom httpx_client instead.
109109
110110
Example:
111111
```python

langfuse/_client/resource_manager.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,14 @@ def _initialize_instance(
184184
## could exhaust the OS's maximum number of available TCP sockets (file descriptors),
185185
## leading to connection errors.
186186
if httpx_client is not None:
187-
self.httpx_client = httpx_client
188-
# If additional_headers are provided and httpx_client is provided,
189-
# we merge the headers into the existing client
187+
# If additional_headers are provided with a custom httpx_client, log warning and ignore additional_headers
190188
if additional_headers:
191-
merged_headers = {**(httpx_client.headers or {}), **additional_headers}
192-
self.httpx_client.headers = merged_headers
189+
langfuse_logger.warning(
190+
"Configuration warning: Both httpx_client and additional_headers were provided. "
191+
"additional_headers will be ignored when using a custom httpx_client. "
192+
"To use additional_headers, either omit httpx_client or configure headers directly on your custom httpx_client."
193+
)
194+
self.httpx_client = httpx_client
193195
else:
194196
# Create a new httpx client with additional_headers if provided
195197
client_headers = additional_headers if additional_headers else {}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"""Simplified tests for additional_headers functionality in Langfuse client.
2+
3+
This module tests that additional headers are properly configured in the HTTP clients.
4+
"""
5+
6+
import httpx
7+
8+
from langfuse._client.client import Langfuse
9+
10+
11+
class TestAdditionalHeadersSimple:
12+
"""Simple test suite for additional_headers functionality."""
13+
14+
def teardown_method(self):
15+
"""Clean up after each test to avoid singleton interference."""
16+
from langfuse._client.resource_manager import LangfuseResourceManager
17+
18+
LangfuseResourceManager.reset()
19+
20+
def test_httpx_client_has_additional_headers_when_none_provided(self):
21+
"""Test that additional headers are set in httpx client when no custom client is provided."""
22+
additional_headers = {
23+
"X-Custom-Header": "custom-value",
24+
"X-Another-Header": "another-value",
25+
}
26+
27+
langfuse = Langfuse(
28+
public_key="test-public-key",
29+
secret_key="test-secret-key",
30+
host="https://mock-host.com",
31+
additional_headers=additional_headers,
32+
tracing_enabled=False, # Disable tracing to avoid OTEL setup
33+
)
34+
35+
# Verify the httpx client has the additional headers
36+
assert (
37+
langfuse._resources.httpx_client.headers["X-Custom-Header"]
38+
== "custom-value"
39+
)
40+
assert (
41+
langfuse._resources.httpx_client.headers["X-Another-Header"]
42+
== "another-value"
43+
)
44+
45+
def test_custom_httpx_client_with_additional_headers_ignores_additional_headers(
46+
self,
47+
):
48+
"""Test that when additional headers are provided with custom client, additional headers are ignored."""
49+
# Create a custom httpx client with headers
50+
existing_headers = {"X-Existing-Header": "existing-value"}
51+
custom_client = httpx.Client(headers=existing_headers)
52+
53+
additional_headers = {
54+
"X-Custom-Header": "custom-value",
55+
"X-Another-Header": "another-value",
56+
}
57+
58+
langfuse = Langfuse(
59+
public_key="test-public-key",
60+
secret_key="test-secret-key",
61+
host="https://mock-host.com",
62+
httpx_client=custom_client,
63+
additional_headers=additional_headers,
64+
tracing_enabled=False,
65+
)
66+
67+
# Verify the original client is used (same instance)
68+
assert langfuse._resources.httpx_client is custom_client
69+
70+
# Verify existing headers are preserved and additional headers are NOT added
71+
assert (
72+
langfuse._resources.httpx_client.headers["x-existing-header"]
73+
== "existing-value"
74+
)
75+
76+
# Additional headers should NOT be present
77+
assert "x-custom-header" not in langfuse._resources.httpx_client.headers
78+
assert "x-another-header" not in langfuse._resources.httpx_client.headers
79+
80+
def test_custom_httpx_client_without_additional_headers_preserves_client(self):
81+
"""Test that when no additional headers are provided, the custom client is preserved."""
82+
# Create a custom httpx client with headers
83+
existing_headers = {"X-Existing-Header": "existing-value"}
84+
custom_client = httpx.Client(headers=existing_headers)
85+
86+
langfuse = Langfuse(
87+
public_key="test-public-key",
88+
secret_key="test-secret-key",
89+
host="https://mock-host.com",
90+
httpx_client=custom_client,
91+
additional_headers=None, # No additional headers
92+
tracing_enabled=False,
93+
)
94+
95+
# Note: The client instance might be different due to Fern API wrapper behavior,
96+
# but the important thing is that the headers are preserved
97+
# Verify existing headers are preserved
98+
assert (
99+
langfuse._resources.httpx_client.headers["x-existing-header"]
100+
== "existing-value"
101+
)
102+
103+
def test_none_additional_headers_works(self):
104+
"""Test that passing None for additional_headers works without errors."""
105+
langfuse = Langfuse(
106+
public_key="test-public-key",
107+
secret_key="test-secret-key",
108+
host="https://mock-host.com",
109+
additional_headers=None,
110+
tracing_enabled=False,
111+
)
112+
113+
# Verify client was created successfully
114+
assert langfuse is not None
115+
assert langfuse._resources is not None
116+
assert langfuse._resources.httpx_client is not None
117+
118+
def test_empty_additional_headers_works(self):
119+
"""Test that passing an empty dict for additional_headers works."""
120+
langfuse = Langfuse(
121+
public_key="test-public-key",
122+
secret_key="test-secret-key",
123+
host="https://mock-host.com",
124+
additional_headers={},
125+
tracing_enabled=False,
126+
)
127+
128+
# Verify client was created successfully
129+
assert langfuse is not None
130+
assert langfuse._resources is not None
131+
assert langfuse._resources.httpx_client is not None
132+
133+
def test_span_processor_has_additional_headers_in_otel_exporter(self):
134+
"""Test that span processor includes additional headers in OTEL exporter."""
135+
from langfuse._client.span_processor import LangfuseSpanProcessor
136+
137+
additional_headers = {
138+
"X-Custom-Trace-Header": "trace-value",
139+
"X-Override-Default": "override-value",
140+
}
141+
142+
# Create span processor with additional headers
143+
processor = LangfuseSpanProcessor(
144+
public_key="test-public-key",
145+
secret_key="test-secret-key",
146+
host="https://mock-host.com",
147+
additional_headers=additional_headers,
148+
)
149+
150+
# Get the OTLP span exporter to check its headers
151+
exporter = processor.span_exporter
152+
153+
# Verify additional headers are in the exporter's headers
154+
assert exporter._headers["X-Custom-Trace-Header"] == "trace-value"
155+
assert exporter._headers["X-Override-Default"] == "override-value"
156+
157+
# Verify default headers are still present
158+
assert "Authorization" in exporter._headers
159+
assert "x_langfuse_sdk_name" in exporter._headers
160+
assert "x_langfuse_public_key" in exporter._headers
161+
162+
# Check that our override worked
163+
assert exporter._headers["X-Override-Default"] == "override-value"
164+
165+
def test_span_processor_none_additional_headers_works(self):
166+
"""Test that span processor works with None additional headers."""
167+
from langfuse._client.span_processor import LangfuseSpanProcessor
168+
169+
# Create span processor without additional headers
170+
processor = LangfuseSpanProcessor(
171+
public_key="test-public-key",
172+
secret_key="test-secret-key",
173+
host="https://mock-host.com",
174+
additional_headers=None,
175+
)
176+
177+
# Get the OTLP span exporter
178+
exporter = processor.span_exporter
179+
180+
# Verify default headers are present
181+
assert "Authorization" in exporter._headers
182+
assert "x_langfuse_sdk_name" in exporter._headers
183+
assert "x_langfuse_public_key" in exporter._headers

0 commit comments

Comments
 (0)