feat(langfuse): propagate user_id and session_id to all traces
- Add hash_user_id() to SHA-256 hash user IDs before sending to Langfuse - Add langfuse_context() helper wrapping propagate_attributes() - deep_agent: extract session_id from _debug context, wrap all agent runs and classifier with langfuse_context(user_id, session_id) - agent_runner: add session_id param, pass run_id as session for batch - agent_setup: wrap journey LLM calls with langfuse_context - Remove redundant metadata dicts (now handled by propagate_attributes)
This commit is contained in:
@@ -16,7 +16,7 @@ from app.agents.note_agent import NOTE_TOOLS
|
||||
from app.agents.project_agent import PROJECT_TOOLS
|
||||
from app.agents.task_agent import TASK_TOOLS
|
||||
from app.agents.timeline_agent import TIMELINE_TOOLS
|
||||
from app.core.langfuse_client import extract_usage, get_langfuse, get_prompt_or_fallback
|
||||
from app.core.langfuse_client import extract_usage, get_langfuse, get_prompt_or_fallback, langfuse_context
|
||||
from app.core.llm import get_agent_llm, model_for_agent
|
||||
from app.core.memory_middleware import MemoryMiddleware
|
||||
from app.core.ws_context import clear_tool_result_collector, execute_on_client, set_tool_result_collector
|
||||
@@ -148,6 +148,15 @@ def _trace_id_from_context(context: dict[str, Any]) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _session_id_from_context(context: dict[str, Any]) -> str | None:
|
||||
debug = context.get("_debug")
|
||||
if isinstance(debug, dict):
|
||||
session_id = debug.get("session_id")
|
||||
if isinstance(session_id, str) and session_id:
|
||||
return session_id
|
||||
return None
|
||||
|
||||
|
||||
def _context_for_model(context: dict[str, Any]) -> dict[str, Any]:
|
||||
sanitized = dict(context)
|
||||
sanitized.pop("_debug", None)
|
||||
@@ -550,18 +559,25 @@ async def _infer_floating_domain(message: str, context: dict[str, Any]) -> dict[
|
||||
_, classifier_prompt_obj = get_prompt_or_fallback(
|
||||
"floating_domain_classifier", _FLOATING_DOMAIN_CLASSIFIER_PROMPT
|
||||
)
|
||||
if lf:
|
||||
with lf.start_as_current_observation(
|
||||
as_type="generation",
|
||||
name="floating-classifier",
|
||||
model=model_for_agent("classifier"),
|
||||
prompt=classifier_prompt_obj,
|
||||
input=classifier_messages,
|
||||
) as gen:
|
||||
|
||||
# Extract user/session from context for Langfuse attribution
|
||||
_debug = context.get("_debug") if isinstance(context, dict) else None
|
||||
_lf_user = (_debug or {}).get("user_id") if isinstance(_debug, dict) else None
|
||||
_lf_session = (_debug or {}).get("session_id") if isinstance(_debug, dict) else None
|
||||
|
||||
with langfuse_context(user_id=_lf_user, session_id=_lf_session):
|
||||
if lf:
|
||||
with lf.start_as_current_observation(
|
||||
as_type="generation",
|
||||
name="floating-classifier",
|
||||
model=model_for_agent("classifier"),
|
||||
prompt=classifier_prompt_obj,
|
||||
input=classifier_messages,
|
||||
) as gen:
|
||||
response = await llm.ainvoke(classifier_messages)
|
||||
gen.update(output=_as_text(response.content), usage_details=extract_usage(response))
|
||||
else:
|
||||
response = await llm.ainvoke(classifier_messages)
|
||||
gen.update(output=_as_text(response.content), usage=extract_usage(response))
|
||||
else:
|
||||
response = await llm.ainvoke(classifier_messages)
|
||||
parsed = _parse_json_object(_as_text(response.content))
|
||||
if parsed is not None:
|
||||
domain = _normalize_domain_payload(parsed, project_id)
|
||||
@@ -590,6 +606,7 @@ async def _run_single_agent(
|
||||
agent_name: str = "agent",
|
||||
) -> str:
|
||||
trace_id = _trace_id_from_context(context)
|
||||
session_id = _session_id_from_context(context)
|
||||
lf = get_langfuse()
|
||||
llm = get_agent_llm(agent_name)
|
||||
tools = _all_tools_for_user(user_id, trace_id)
|
||||
@@ -610,6 +627,9 @@ async def _run_single_agent(
|
||||
collected: list[dict[str, Any]] = []
|
||||
set_tool_result_collector(collected)
|
||||
|
||||
_lf_ctx = langfuse_context(user_id=user_id, session_id=session_id)
|
||||
_lf_ctx.__enter__()
|
||||
|
||||
_span_ctx = (
|
||||
lf.start_as_current_observation(
|
||||
as_type="span",
|
||||
@@ -636,7 +656,7 @@ async def _run_single_agent(
|
||||
_gen = _gen_ctx.__enter__() if _gen_ctx else None
|
||||
response: AIMessage = await llm_with_tools.ainvoke(messages)
|
||||
if _gen_ctx:
|
||||
_gen.update(output=_as_text(response.content), usage=extract_usage(response))
|
||||
_gen.update(output=_as_text(response.content), usage_details=extract_usage(response))
|
||||
_gen_ctx.__exit__(None, None, None)
|
||||
|
||||
messages.append(response)
|
||||
@@ -698,6 +718,7 @@ async def _run_single_agent(
|
||||
clear_tool_result_collector()
|
||||
if _span_ctx:
|
||||
_span_ctx.__exit__(None, None, None)
|
||||
_lf_ctx.__exit__(None, None, None)
|
||||
if lf:
|
||||
lf.flush()
|
||||
|
||||
@@ -713,6 +734,7 @@ async def _run_single_agent_stream(
|
||||
agent_name: str = "agent",
|
||||
) -> AsyncGenerator[tuple[str, Any], None]:
|
||||
trace_id = _trace_id_from_context(context)
|
||||
session_id = _session_id_from_context(context)
|
||||
lf = get_langfuse()
|
||||
llm = get_agent_llm(agent_name)
|
||||
tools = _all_tools_for_user(user_id, trace_id)
|
||||
@@ -734,6 +756,9 @@ async def _run_single_agent_stream(
|
||||
collected: list[dict[str, Any]] = []
|
||||
set_tool_result_collector(collected)
|
||||
|
||||
_lf_ctx = langfuse_context(user_id=user_id, session_id=session_id)
|
||||
_lf_ctx.__enter__()
|
||||
|
||||
_span_ctx = (
|
||||
lf.start_as_current_observation(
|
||||
as_type="span",
|
||||
@@ -761,7 +786,7 @@ async def _run_single_agent_stream(
|
||||
_gen = _gen_ctx.__enter__() if _gen_ctx else None
|
||||
response: AIMessage = await llm_with_tools.ainvoke(messages)
|
||||
if _gen_ctx:
|
||||
_gen.update(output=_as_text(response.content), usage=extract_usage(response))
|
||||
_gen.update(output=_as_text(response.content), usage_details=extract_usage(response))
|
||||
_gen_ctx.__exit__(None, None, None)
|
||||
|
||||
messages.append(response)
|
||||
@@ -841,6 +866,7 @@ async def _run_single_agent_stream(
|
||||
clear_tool_result_collector()
|
||||
if _span_ctx:
|
||||
_span_ctx.__exit__(None, None, None)
|
||||
_lf_ctx.__exit__(None, None, None)
|
||||
if lf:
|
||||
lf.flush()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user