Skip to content

Commit c167ca8

Browse files
authored
Merge branch 'main' into add-tool-call-parsing
2 parents 22e75e7 + c514628 commit c167ca8

7 files changed

Lines changed: 300 additions & 10 deletions

File tree

langfuse/_client/client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2996,3 +2996,14 @@ def _url_encode(self, url: str, *, is_url_param: Optional[bool] = False) -> str:
29962996
# we need add safe="" to force escaping of slashes
29972997
# This is necessary for prompts in prompt folders
29982998
return urllib.parse.quote(url, safe="")
2999+
3000+
def clear_prompt_cache(self):
3001+
"""
3002+
Clear the entire prompt cache, removing all cached prompts.
3003+
3004+
This method is useful when you want to force a complete refresh of all
3005+
cached prompts, for example after major updates or when you need to
3006+
ensure the latest versions are fetched from the server.
3007+
"""
3008+
if self._resources is not None:
3009+
self._resources.prompt_cache.clear()

langfuse/_client/environment_variables.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,13 @@
128128
129129
**Default value**: ``5``
130130
"""
131+
132+
LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS = "LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS"
133+
"""
134+
.. envvar: LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS
135+
136+
Controls the default time-to-live (TTL) in seconds for cached prompts.
137+
This setting determines how long prompt responses are cached before they expire.
138+
139+
**Default value**: ``60``
140+
"""

langfuse/_utils/prompt_cache.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
from queue import Empty, Queue
77
from threading import Thread
88
from typing import Callable, Dict, List, Optional, Set
9+
import os
910

1011
from langfuse.model import PromptClient
12+
from langfuse._client.environment_variables import (
13+
LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS
14+
)
1115

12-
DEFAULT_PROMPT_CACHE_TTL_SECONDS = 60
16+
DEFAULT_PROMPT_CACHE_TTL_SECONDS = int(os.getenv(LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS, 60))
1317

1418
DEFAULT_PROMPT_CACHE_REFRESH_WORKERS = 1
1519

@@ -162,6 +166,10 @@ def add_refresh_prompt_task(self, key: str, fetch_func: Callable[[], None]) -> N
162166
self._log.debug(f"Submitting refresh task for key: {key}")
163167
self._task_manager.add_task(key, fetch_func)
164168

169+
def clear(self) -> None:
170+
"""Clear the entire prompt cache, removing all cached prompts."""
171+
self._cache.clear()
172+
165173
@staticmethod
166174
def generate_cache_key(
167175
name: str, *, version: Optional[int], label: Optional[str]

langfuse/langchain/CallbackHandler.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
import pydantic
55
from opentelemetry import context, trace
6+
from opentelemetry.context import _RUNTIME_CONTEXT
67

78
from langfuse._client.attributes import LangfuseOtelSpanAttributes
9+
from langfuse._client.client import Langfuse
810
from langfuse._client.get_client import get_client
911
from langfuse._client.span import (
1012
LangfuseAgent,
@@ -272,7 +274,7 @@ def on_chain_start(
272274
serialized, "chain", **kwargs
273275
)
274276

275-
span = self.client.start_observation(
277+
span = self._get_parent_observation(parent_run_id).start_observation(
276278
name=span_name,
277279
as_type=observation_type,
278280
metadata=span_metadata,
@@ -336,6 +338,22 @@ def _deregister_langfuse_prompt(self, run_id: Optional[UUID]) -> None:
336338
if run_id is not None and run_id in self.prompt_to_parent_run_map:
337339
del self.prompt_to_parent_run_map[run_id]
338340

341+
def _get_parent_observation(
342+
self, parent_run_id: Optional[UUID]
343+
) -> Union[
344+
Langfuse,
345+
LangfuseAgent,
346+
LangfuseChain,
347+
LangfuseGeneration,
348+
LangfuseRetriever,
349+
LangfuseSpan,
350+
LangfuseTool,
351+
]:
352+
if parent_run_id and parent_run_id in self.runs:
353+
return self.runs[parent_run_id]
354+
355+
return self.client
356+
339357
def _attach_observation(
340358
self,
341359
run_id: UUID,
@@ -369,7 +387,18 @@ def _detach_observation(
369387
token = self.context_tokens.pop(run_id, None)
370388

371389
if token:
372-
context.detach(token)
390+
try:
391+
# Directly detach from runtime context to avoid error logging
392+
_RUNTIME_CONTEXT.detach(token)
393+
except Exception:
394+
# Context detach can fail in async scenarios - this is expected and safe to ignore
395+
# The span itself was properly ended and tracing data is correctly captured
396+
#
397+
# Examples:
398+
# 1. Token created in one async task/thread, detached in another
399+
# 2. Context already detached by framework or other handlers
400+
# 3. Runtime context state mismatch in concurrent execution
401+
pass
373402

374403
return cast(
375404
Union[
@@ -591,7 +620,7 @@ def on_tool_start(
591620
serialized, "tool", **kwargs
592621
)
593622

594-
span = self.client.start_observation(
623+
span = self._get_parent_observation(parent_run_id).start_observation(
595624
name=self.get_langchain_run_name(serialized, **kwargs),
596625
as_type=observation_type,
597626
input=input_str,
@@ -626,8 +655,7 @@ def on_retriever_start(
626655
observation_type = self._get_observation_type_from_serialized(
627656
serialized, "retriever", **kwargs
628657
)
629-
630-
span = self.client.start_observation(
658+
span = self._get_parent_observation(parent_run_id).start_observation(
631659
name=span_name,
632660
as_type=observation_type,
633661
metadata=span_metadata,
@@ -753,7 +781,9 @@ def __on_llm_action(
753781
"prompt": registered_prompt,
754782
}
755783

756-
generation = self.client.start_observation(as_type="generation", **content) # type: ignore
784+
generation = self._get_parent_observation(parent_run_id).start_observation(
785+
as_type="generation", **content
786+
) # type: ignore
757787
self._attach_observation(run_id, generation)
758788

759789
self.last_trace_id = self.runs[run_id].trace_id

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.2"
3+
__version__ = "3.3.3"

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.2"
4+
version = "3.3.3"
55
description = "A client library for accessing langfuse"
66
authors = ["langfuse <developers@langfuse.com>"]
77
license = "MIT"

0 commit comments

Comments
 (0)