Skip to content

Commit f47f6fb

Browse files
committed
feat(client): allow passing async httpx client
1 parent 63a3b62 commit f47f6fb

File tree

5 files changed

+74
-2
lines changed

5 files changed

+74
-2
lines changed

langfuse/_client/client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ class Langfuse:
155155
base_url (Optional[str]): The Langfuse API base URL. Defaults to "https://cloud.langfuse.com". Can also be set via LANGFUSE_BASE_URL environment variable.
156156
host (Optional[str]): Deprecated. Use base_url instead. The Langfuse API host URL. Defaults to "https://cloud.langfuse.com".
157157
timeout (Optional[int]): Timeout in seconds for API requests. Defaults to 5 seconds.
158-
httpx_client (Optional[httpx.Client]): Custom httpx client for making non-tracing HTTP requests. If not provided, a default client will be created.
158+
httpx_client (Optional[httpx.Client]): Custom synchronous httpx client for making non-tracing HTTP requests. If not provided, a default client will be created.
159+
async_httpx_client (Optional[httpx.AsyncClient]): Custom asynchronous httpx client for `client.async_api`. If not provided, a default async client will be created.
159160
debug (bool): Enable debug logging. Defaults to False. Can also be set via LANGFUSE_DEBUG environment variable.
160161
tracing_enabled (Optional[bool]): Enable or disable tracing. Defaults to True. Can also be set via LANGFUSE_TRACING_ENABLED environment variable.
161162
flush_at (Optional[int]): Number of spans to batch before sending to the API. Defaults to 512. Can also be set via LANGFUSE_FLUSH_AT environment variable.
@@ -179,7 +180,7 @@ class Langfuse:
179180
)
180181
```
181182
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).
182-
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 must be set directly on your custom httpx_client as well.
183+
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` or `async_httpx_client` is provided, `additional_headers` must be set directly on your custom client as well.
183184
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.
184185
185186
Example:
@@ -231,6 +232,7 @@ def __init__(
231232
host: Optional[str] = None,
232233
timeout: Optional[int] = None,
233234
httpx_client: Optional[httpx.Client] = None,
235+
async_httpx_client: Optional[httpx.AsyncClient] = None,
234236
debug: bool = False,
235237
tracing_enabled: Optional[bool] = True,
236238
flush_at: Optional[int] = None,
@@ -332,6 +334,7 @@ def __init__(
332334
flush_at=flush_at,
333335
flush_interval=flush_interval,
334336
httpx_client=httpx_client,
337+
async_httpx_client=async_httpx_client,
335338
media_upload_thread_count=media_upload_thread_count,
336339
sample_rate=sample_rate,
337340
mask=mask,

langfuse/_client/get_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def _create_client_from_instance(
5555
additional_headers=instance.additional_headers,
5656
tracer_provider=instance.tracer_provider,
5757
httpx_client=instance.httpx_client,
58+
async_httpx_client=instance.async_httpx_client,
5859
)
5960

6061

langfuse/_client/resource_manager.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def __new__(
9090
flush_at: Optional[int] = None,
9191
flush_interval: Optional[float] = None,
9292
httpx_client: Optional[httpx.Client] = None,
93+
async_httpx_client: Optional[httpx.AsyncClient] = None,
9394
media_upload_thread_count: Optional[int] = None,
9495
sample_rate: Optional[float] = None,
9596
mask: Optional[MaskFunction] = None,
@@ -123,6 +124,7 @@ def __new__(
123124
flush_at=flush_at,
124125
flush_interval=flush_interval,
125126
httpx_client=httpx_client,
127+
async_httpx_client=async_httpx_client,
126128
media_upload_thread_count=media_upload_thread_count,
127129
sample_rate=sample_rate,
128130
mask=mask,
@@ -152,6 +154,7 @@ def _initialize_instance(
152154
flush_interval: Optional[float] = None,
153155
media_upload_thread_count: Optional[int] = None,
154156
httpx_client: Optional[httpx.Client] = None,
157+
async_httpx_client: Optional[httpx.AsyncClient] = None,
155158
sample_rate: Optional[float] = None,
156159
mask: Optional[MaskFunction] = None,
157160
tracing_enabled: bool = True,
@@ -218,6 +221,15 @@ def _initialize_instance(
218221
client_headers = additional_headers if additional_headers else {}
219222
self.httpx_client = httpx.Client(timeout=timeout, headers=client_headers)
220223

224+
if async_httpx_client is not None:
225+
self.async_httpx_client = async_httpx_client
226+
else:
227+
async_client_headers = additional_headers if additional_headers else {}
228+
self.async_httpx_client = httpx.AsyncClient(
229+
timeout=timeout,
230+
headers=async_client_headers,
231+
)
232+
221233
self.api = LangfuseAPI(
222234
base_url=base_url,
223235
username=self.public_key,
@@ -235,6 +247,7 @@ def _initialize_instance(
235247
x_langfuse_sdk_name="python",
236248
x_langfuse_sdk_version=langfuse_version,
237249
x_langfuse_public_key=self.public_key,
250+
httpx_client=self.async_httpx_client,
238251
timeout=timeout,
239252
)
240253
score_ingestion_client = LangfuseClient(

tests/test_additional_headers_simple.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
This module tests that additional headers are properly configured in the HTTP clients.
44
"""
55

6+
import asyncio
7+
68
import httpx
79

810
from langfuse._client.client import Langfuse
@@ -115,6 +117,28 @@ def test_media_manager_uses_custom_httpx_client(self):
115117
assert langfuse._resources is not None
116118
assert langfuse._resources._media_manager._httpx_client is custom_client
117119

120+
def test_async_api_uses_custom_async_httpx_client(self):
121+
"""Test that async_api reuses the configured custom async httpx client."""
122+
custom_async_client = httpx.AsyncClient()
123+
124+
try:
125+
langfuse = Langfuse(
126+
public_key="test-public-key",
127+
secret_key="test-secret-key",
128+
host="https://mock-host.com",
129+
async_httpx_client=custom_async_client,
130+
tracing_enabled=False,
131+
)
132+
133+
assert langfuse._resources is not None
134+
assert langfuse._resources.async_httpx_client is custom_async_client
135+
assert (
136+
langfuse.async_api._client_wrapper.httpx_client.httpx_client
137+
is custom_async_client
138+
)
139+
finally:
140+
asyncio.run(custom_async_client.aclose())
141+
118142
def test_none_additional_headers_works(self):
119143
"""Test that passing None for additional_headers works without errors."""
120144
langfuse = Langfuse(

tests/test_resource_manager.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""Test the LangfuseResourceManager and get_client() function."""
22

3+
import asyncio
4+
5+
import httpx
6+
37
from langfuse import Langfuse
48
from langfuse._client.get_client import get_client
59
from langfuse._client.resource_manager import LangfuseResourceManager
@@ -94,3 +98,30 @@ def should_export_b(span):
9498

9599
client_a.shutdown()
96100
client_b.shutdown()
101+
102+
103+
def test_get_client_preserves_custom_async_httpx_client():
104+
"""Test that get_client() preserves the custom async httpx client."""
105+
with LangfuseResourceManager._lock:
106+
LangfuseResourceManager._instances.clear()
107+
108+
custom_async_client = httpx.AsyncClient()
109+
110+
try:
111+
Langfuse(
112+
public_key="pk-async-client",
113+
secret_key="sk-async-client",
114+
async_httpx_client=custom_async_client,
115+
tracing_enabled=False,
116+
)
117+
retrieved_client = get_client()
118+
119+
assert retrieved_client._resources is not None
120+
assert retrieved_client._resources.async_httpx_client is custom_async_client
121+
assert (
122+
retrieved_client.async_api._client_wrapper.httpx_client.httpx_client
123+
is custom_async_client
124+
)
125+
finally:
126+
LangfuseResourceManager.reset()
127+
asyncio.run(custom_async_client.aclose())

0 commit comments

Comments
 (0)