Skip to content

Commit 13c42d9

Browse files
authored
Merge branch 'main' into add-experiments
2 parents 36ca2c2 + acd0e0d commit 13c42d9

9 files changed

Lines changed: 158 additions & 18 deletions

File tree

langfuse/_client/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3394,7 +3394,7 @@ def update_prompt(
33943394
33953395
"""
33963396
updated_prompt = self.api.prompt_version.update(
3397-
name=name,
3397+
name=self._url_encode(name),
33983398
version=version,
33993399
new_labels=new_labels,
34003400
)

langfuse/_client/get_client.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,28 @@ def _set_current_public_key(public_key: Optional[str]) -> Iterator[None]:
3333
_current_public_key.reset(token)
3434

3535

36+
def _create_client_from_instance(
37+
instance: "LangfuseResourceManager", public_key: Optional[str] = None
38+
) -> Langfuse:
39+
"""Create a Langfuse client from a resource manager instance with all settings preserved."""
40+
return Langfuse(
41+
public_key=public_key or instance.public_key,
42+
secret_key=instance.secret_key,
43+
host=instance.host,
44+
tracing_enabled=instance.tracing_enabled,
45+
environment=instance.environment,
46+
timeout=instance.timeout,
47+
flush_at=instance.flush_at,
48+
flush_interval=instance.flush_interval,
49+
release=instance.release,
50+
media_upload_thread_count=instance.media_upload_thread_count,
51+
sample_rate=instance.sample_rate,
52+
mask=instance.mask,
53+
blocked_instrumentation_scopes=instance.blocked_instrumentation_scopes,
54+
additional_headers=instance.additional_headers,
55+
)
56+
57+
3658
def get_client(*, public_key: Optional[str] = None) -> Langfuse:
3759
"""Get or create a Langfuse client instance.
3860
@@ -93,12 +115,7 @@ def get_client(*, public_key: Optional[str] = None) -> Langfuse:
93115
# Initialize with the credentials bound to the instance
94116
# This is important if the original instance was instantiated
95117
# via constructor arguments
96-
return Langfuse(
97-
public_key=instance.public_key,
98-
secret_key=instance.secret_key,
99-
host=instance.host,
100-
tracing_enabled=instance.tracing_enabled,
101-
)
118+
return _create_client_from_instance(instance)
102119

103120
else:
104121
# Multiple clients exist but no key specified - disable tracing
@@ -126,9 +143,4 @@ def get_client(*, public_key: Optional[str] = None) -> Langfuse:
126143
)
127144

128145
# target_instance is guaranteed to be not None at this point
129-
return Langfuse(
130-
public_key=public_key,
131-
secret_key=target_instance.secret_key,
132-
host=target_instance.host,
133-
tracing_enabled=target_instance.tracing_enabled,
134-
)
146+
return _create_client_from_instance(target_instance, public_key)

langfuse/_client/resource_manager.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@ def _initialize_instance(
162162
self.tracing_enabled = tracing_enabled
163163
self.host = host
164164
self.mask = mask
165+
self.environment = environment
166+
167+
# Store additional client settings for get_client() to use
168+
self.timeout = timeout
169+
self.flush_at = flush_at
170+
self.flush_interval = flush_interval
171+
self.release = release
172+
self.media_upload_thread_count = media_upload_thread_count
173+
self.sample_rate = sample_rate
174+
self.blocked_instrumentation_scopes = blocked_instrumentation_scopes
175+
self.additional_headers = additional_headers
165176

166177
# OTEL Tracer
167178
if tracing_enabled:

langfuse/openai.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,10 @@ def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any:
641641
curr[-1]["name"] = curr[-1]["name"] or getattr(
642642
tool_call_chunk, "name", None
643643
)
644+
645+
if curr[-1]["arguments"] is None:
646+
curr[-1]["arguments"] = ""
647+
644648
curr[-1]["arguments"] += getattr(
645649
tool_call_chunk, "arguments", None
646650
)

langfuse/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""@private"""
22

3-
__version__ = "3.3.4"
3+
__version__ = "3.3.5"

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
22
name = "langfuse"
33

4-
version = "3.3.4"
4+
version = "3.3.5"
55
description = "A client library for accessing langfuse"
66
authors = ["langfuse <developers@langfuse.com>"]
77
license = "MIT"

tests/test_prompt.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,3 +1410,37 @@ def test_update_prompt():
14101410
expected_labels = sorted(["latest", "doe", "production", "john"])
14111411
assert sorted(fetched_prompt.labels) == expected_labels
14121412
assert sorted(updated_prompt.labels) == expected_labels
1413+
1414+
1415+
def test_update_prompt_in_folder():
1416+
langfuse = Langfuse()
1417+
prompt_name = f"some-folder/{create_uuid()}"
1418+
1419+
# Create initial prompt
1420+
langfuse.create_prompt(
1421+
name=prompt_name,
1422+
prompt="test prompt",
1423+
labels=["production"],
1424+
)
1425+
1426+
old_prompt_obj = langfuse.get_prompt(prompt_name)
1427+
1428+
updated_prompt = langfuse.update_prompt(
1429+
name=old_prompt_obj.name,
1430+
version=old_prompt_obj.version,
1431+
new_labels=["john", "doe"],
1432+
)
1433+
1434+
# Fetch prompt after update (should be invalidated)
1435+
fetched_prompt = langfuse.get_prompt(prompt_name)
1436+
1437+
# Verify the fetched prompt matches the updated values
1438+
assert fetched_prompt.name == prompt_name
1439+
assert fetched_prompt.version == 1
1440+
print(f"Fetched prompt labels: {fetched_prompt.labels}")
1441+
print(f"Updated prompt labels: {updated_prompt.labels}")
1442+
1443+
# production was set by the first call, latest is managed and set by Langfuse
1444+
expected_labels = sorted(["latest", "doe", "production", "john"])
1445+
assert sorted(fetched_prompt.labels) == expected_labels
1446+
assert sorted(updated_prompt.labels) == expected_labels

tests/test_resource_manager.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""Test the LangfuseResourceManager and get_client() function."""
2+
3+
from langfuse import Langfuse
4+
from langfuse._client.get_client import get_client
5+
from langfuse._client.resource_manager import LangfuseResourceManager
6+
7+
8+
def test_get_client_preserves_all_settings():
9+
"""Test that get_client() preserves environment and all client settings."""
10+
with LangfuseResourceManager._lock:
11+
LangfuseResourceManager._instances.clear()
12+
13+
settings = {
14+
"environment": "test-env",
15+
"release": "v1.2.3",
16+
"timeout": 30,
17+
"flush_at": 100,
18+
"sample_rate": 0.8,
19+
"additional_headers": {"X-Custom": "value"},
20+
}
21+
22+
original_client = Langfuse(**settings)
23+
retrieved_client = get_client()
24+
25+
assert retrieved_client._environment == settings["environment"]
26+
27+
assert retrieved_client._resources is not None
28+
rm = retrieved_client._resources
29+
assert rm.environment == settings["environment"]
30+
assert rm.timeout == settings["timeout"]
31+
assert rm.sample_rate == settings["sample_rate"]
32+
assert rm.additional_headers == settings["additional_headers"]
33+
34+
original_client.shutdown()
35+
36+
37+
def test_get_client_multiple_clients_preserve_different_settings():
38+
"""Test that get_client() preserves different settings for multiple clients."""
39+
# Settings for client A
40+
settings_a = {
41+
"public_key": "pk-comprehensive-a",
42+
"secret_key": "sk-comprehensive-a",
43+
"environment": "env-a",
44+
"release": "release-a",
45+
"timeout": 10,
46+
"sample_rate": 0.5,
47+
}
48+
49+
# Settings for client B
50+
settings_b = {
51+
"public_key": "pk-comprehensive-b",
52+
"secret_key": "sk-comprehensive-b",
53+
"environment": "env-b",
54+
"release": "release-b",
55+
"timeout": 20,
56+
"sample_rate": 0.9,
57+
}
58+
59+
client_a = Langfuse(**settings_a)
60+
client_b = Langfuse(**settings_b)
61+
62+
# Get clients via get_client()
63+
retrieved_a = get_client(public_key="pk-comprehensive-a")
64+
retrieved_b = get_client(public_key="pk-comprehensive-b")
65+
66+
# Verify each client preserves its own settings
67+
assert retrieved_a._environment == settings_a["environment"]
68+
assert retrieved_b._environment == settings_b["environment"]
69+
70+
if retrieved_a._resources and retrieved_b._resources:
71+
assert retrieved_a._resources.timeout == settings_a["timeout"]
72+
assert retrieved_b._resources.timeout == settings_b["timeout"]
73+
assert retrieved_a._resources.sample_rate == settings_a["sample_rate"]
74+
assert retrieved_b._resources.sample_rate == settings_b["sample_rate"]
75+
assert retrieved_a._resources.release == settings_a["release"]
76+
assert retrieved_b._resources.release == settings_b["release"]
77+
78+
client_a.shutdown()
79+
client_b.shutdown()

0 commit comments

Comments
 (0)