"""Observability via Langfuse (v4). Initializes the Langfuse client from our settings and re-exports the pieces the rest of the app uses to trace itself: - ``observe`` : decorator that turns a function into a traced span (use as_type="generation" for model calls, as_type="tool" for tool calls). - ``trace_attributes`` : context manager that stamps trace-level fields (session_id, tags, metadata) onto every span created inside it. - ``annotate_span`` : add metadata to the currently-running span. - ``flush`` / ``current_trace_url`` : flush buffered events; fetch the URL of the trace currently in scope. If Langfuse keys are not configured, every export degrades to a safe no-op so the app and eval still run untraced. """ from __future__ import annotations from contextlib import contextmanager, nullcontext from src.config import settings # Tracing is only active when both Langfuse keys are present. enabled = bool(settings.langfuse_public_key and settings.langfuse_secret_key) if enabled: from langfuse import Langfuse, get_client from langfuse import observe as observe # re-export from langfuse import propagate_attributes as _propagate # Configure the singleton client explicitly. We pass keys from settings # because they live in .env (read by pydantic) and are NOT exported to # os.environ, which is the only place the Langfuse SDK would look otherwise. Langfuse( public_key=settings.langfuse_public_key, secret_key=settings.langfuse_secret_key, host=settings.langfuse_host, ) _client = get_client() def trace_attributes(**kwargs): """Stamp trace-level attributes (session_id, tags, metadata, ...).""" return _propagate(**kwargs) def annotate_span(**kwargs) -> None: """Attach metadata/name/level to the current span.""" _client.update_current_span(**kwargs) def flush() -> None: _client.flush() def current_trace_url() -> str | None: try: return _client.get_trace_url() except Exception: # noqa: BLE001 return None else: def observe(func=None, **_kwargs): # type: ignore[misc] """No-op stand-in for langfuse.observe (supports @observe and @observe(...)).""" if callable(func): return func def _decorator(f): return f return _decorator def trace_attributes(**_kwargs): return nullcontext() def annotate_span(**_kwargs) -> None: pass def flush() -> None: pass def current_trace_url() -> str | None: return None