refactor(langfuse): extract _scope_prefix from _trace_key

The turn- and api-scoped branches each repeated the same
task/session/thread fallback ladder with only the infix differing. Extract
the shared prefix into _scope_prefix so a future scope dimension touches one
ladder instead of three. The legacy branch still returns a bare task_id (not
the task: prefix) for backward compatibility, so it stays separate.

Output key strings are unchanged; a new test pins them across every
task/session/turn/api combination since the keys are matched across hooks
and any drift would silently break trace finalization.
This commit is contained in:
kshitijk4poor 2026-06-18 12:58:24 +05:30
parent b4356135f2
commit e1d10ec1ed
2 changed files with 33 additions and 16 deletions

View file

@ -219,6 +219,15 @@ def _get_langfuse() -> Optional[Langfuse]:
return _LANGFUSE_CLIENT
def _scope_prefix(task_id: str, session_id: str) -> str:
"""The task/session/thread prefix shared by every trace-key shape."""
if task_id:
return f"task:{task_id}"
if session_id:
return f"session:{session_id}"
return f"thread:{threading.get_ident()}"
def _trace_key(
task_id: str,
session_id: str,
@ -231,27 +240,20 @@ def _trace_key(
Older Hermes paths only expose ``task_id``/``session_id``. Newer paths
pass ``turn_id`` and ``api_request_id`` in LLM/tool hooks; when present,
they must scope trace state so concurrent requests sharing one task/session
never collide.
never collide. ``turn_id`` is preferred over ``api_request_id`` so the
turn-level ``post_llm_call`` hook (which carries ``turn_id`` but no
``api_request_id``) resolves to the same key as the request-level hooks.
"""
if turn_id:
if task_id:
return f"task:{task_id}:turn:{turn_id}"
if session_id:
return f"session:{session_id}:turn:{turn_id}"
return f"thread:{threading.get_ident()}:turn:{turn_id}"
return f"{_scope_prefix(task_id, session_id)}:turn:{turn_id}"
if api_request_id:
if task_id:
return f"task:{task_id}:api:{api_request_id}"
if session_id:
return f"session:{session_id}:api:{api_request_id}"
return f"thread:{threading.get_ident()}:api:{api_request_id}"
return f"{_scope_prefix(task_id, session_id)}:api:{api_request_id}"
# Legacy shape: a bare ``task_id`` (NOT the ``task:`` prefix) when present,
# otherwise the session/thread prefix. Kept distinct for backward
# compatibility with keys minted before turn/request scoping existed.
if task_id:
return task_id
if session_id:
return f"session:{session_id}"
return f"thread:{threading.get_ident()}"
return _scope_prefix(task_id, session_id)
def _is_base64_data_uri(value: str) -> bool:

View file

@ -373,6 +373,21 @@ class TestTurnTraceIsolation:
assert k_pre_api == k_post_api == k_post_turn
def test_trace_key_strings_unchanged_by_refactor(self):
"""Pin the exact key strings across all task/session/turn/api
combinations so the _scope_prefix extraction can never silently change
a key (keys are matched across hooks; a drift breaks finalization)."""
mod = self._fresh_plugin()
tk = mod._trace_key
assert tk("t", "s", turn_id="u") == "task:t:turn:u"
assert tk("", "s", turn_id="u") == "session:s:turn:u"
assert tk("t", "s", api_request_id="r") == "task:t:api:r"
assert tk("", "s", api_request_id="r") == "session:s:api:r"
assert tk("t", "s") == "t" # legacy: bare task_id
assert tk("", "s") == "session:s"
# turn_id wins over api_request_id when both are present.
assert tk("t", "s", turn_id="u", api_request_id="r") == "task:t:turn:u"
# ---------------------------------------------------------------------------
# Placeholder-credential guard (#23823).