Skip to content

docs: update Langfuse page for --observe langfuse CLI + lifecycle spans + richer llm_response (PR #1461) #179

@MervinPraison

Description

@MervinPraison

Summary

PraisonAI PR #1461 ("feat: implement langextract/langfuse observability follow-ups") merged into main on 2026-04-18 (head SHA a7efb680d33f48955257fbf59f7817bfb166a9f6, closes #1460) ships two user-visible Langfuse observability changes that are not reflected in the existing docs/observability/langfuse.mdx page.

This is the Langfuse twin of the still-open Langextract issue #173 — same architectural fix (_ContextToActionBridge + context_sink()), same trace-quality fix (_extract_llm_response_content), but applied to the Langfuse sink + CLI wiring instead.

Decision: Update existing content (docs/observability/langfuse.mdx), plus a small entry in docs/observability/overview.mdx. Confirmed by:

  • grep -rn "context_sink\|_ContextToActionBridge\|--observe.*langfuse" docs/zero matches in user-facing docs.
  • grep -rn "_setup_langfuse_observability" docs/zero matches.
  • docs/observability/langfuse.mdx only documents the obs.langfuse() global-OpenAI-instrumentation path; it does not mention praisonai --observe langfuse, LangfuseSink, lifecycle spans, or the trace-content improvement.
  • docs/observability/overview.mdx line 51 still lists Langfuse with the wrong install (pip install opentelemetry-sdk opentelemetry-exporter-otlp) — should be pip install langfuse (this was already flagged in the now-closed Docs: Rewrite Langfuse integration page with correct Langfuse v4 API, obs.langfuse(), and CLI commands #64 but the overview row was never updated).

What PR #1461 actually changes (SDK ground truth)

Follow-up 1 — Richer llm_response trace content (Core SDK)

praisonaiagents/agent/chat_mixin.py previously emitted response_content=str(final_response), which serialised the entire ChatCompletion(...) object into the trace. Trace UIs (Langfuse, Langextract HTML) showed the verbose Python repr instead of the actual assistant text.

The fix — new helper _extract_llm_response_content(response):

  • If response.choices[0].message.content exists → return the assistant text.
  • Else if response.choices[0].message.tool_calls exists → return "[tool_calls: name1, name2]".
  • Fallback → str(response) (preserves old behaviour for unknown shapes).

Used at chat_mixin.py:592 to populate the llm_response ContextEvent data["response_content"] field.

User-visible effect: Langfuse spans (and any other trace sink) now show the readable assistant message — not ChatCompletion(id='chatcmpl-...', choices=[Choice(...)], ...).

Follow-up 2 — LangfuseSink context-emitter bridge (Wrapper + CLI)

This is the same architectural gap that was fixed for LangextractSink in #1420. The core runtime emits lifecycle events exclusively via ContextTraceEmitter / ContextTraceSinkProtocol, but LangfuseSink only consumes ActionEvents — so before #1461, praisonai --observe langfuse only captured RouterAgent token-usage and PlanningAgent.plan_created events. Real Agent.start() flows produced almost-empty Langfuse traces.

Three changes ship together:

  1. praisonai/observability/langfuse.py — new _ContextToActionBridge class implementing ContextTraceSinkProtocol. Maps:

    ContextEventType ActionEventType
    AGENT_START AGENT_START
    AGENT_END AGENT_END
    TOOL_CALL_START TOOL_START (carries tool_name, tool_args)
    TOOL_CALL_END TOOL_END (carries tool_name, tool_result_summary)
    LLM_REQUEST TOOL_START
    LLM_RESPONSE TOOL_END (carries response_content — see Follow-up 1)
    MEMORY_*, KNOWLEDGE_*, etc. (skipped — not mappable)
  2. praisonai/observability/langfuse.py — new LangfuseSink.context_sink() returning the bridge.

  3. praisonai/cli/app.py::_setup_langfuse_observability — now also installs ContextTraceEmitter(sink=sink.context_sink(), enabled=True) via set_context_emitter, and registers atexit.register(sink.close) so spans flush even if the user forgets provider.flush().

User-visible effect: running praisonai --observe langfuse <command> now produces full agent_start / agent_end / tool_call_* / llm_* spans in Langfuse, mirroring what users already get from obs.langfuse() (which traces via the langfuse.openai drop-in).

Two paths to enable Langfuse — current docs only cover one

After #1461 there are now two complete, supported paths to wire Langfuse into PraisonAI. The current page only explains Path A.

Path A — obs.langfuse() Path B — praisonai --observe langfuse (NEW)
Where the user calls it Python script CLI flag (also PRAISONAI_OBSERVE=langfuse)
Mechanism Instruments OpenAI client globally via langfuse.openai drop-in (Langfuse v4 SDK) LangfuseSink (TraceSinkProtocol) + ContextTraceEmitter bridge
Span coverage Per-LLM-call generations (input/output, tokens, model) Full lifecycle: agent_start, agent_end, tool_call_*, llm_*
Manual flush() needed? Yes — provider.flush() No — atexit registers it (since #1461)
Best for Programmatic agents, any Python flow YAML / CLI workflows, multi-agent pipelines
Documented today? ✅ Yes (docs/observability/langfuse.mdx) No — the focus of this issue

Both paths can co-exist: Path A traces the LLM HTTP call; Path B traces the agent's lifecycle around it.

SDK ground truth — files to read before editing the page

Per AGENTS.md §1.1 / §1.3 (SDK-first), the docs author must read these files in the daily-synced source trees before authoring:

Concern File (in this repo's synced tree)
_extract_llm_response_content helper + emit site praisonaiagents/agent/chat_mixin.py (helper at ~line 460, emit at ~line 592)
LangfuseSink + LangfuseSinkConfig dataclass praisonai/observability/langfuse.py
_ContextToActionBridge + LangfuseSink.context_sink() praisonai/observability/langfuse.py (added by #1461 — will appear after the next daily update_repos.sh run; until then read the PR #1461 diff directly)
--observe langfuse CLI wiring praisonai/cli/app.py::_setup_langfuse_observability (lines 16–40 after #1461)
Global --observe flag declaration praisonai/cli/app.py (lines 180–214)
Context-event protocol the bridge implements praisonaiagents/trace/context_events.py (ContextTraceSinkProtocol, ContextEvent, ContextEventType)
obs.langfuse() factory (already-documented Path A) praisonaiagents/obs/__init__.py
LangfuseProvider.init() (already-documented Path A) praisonai_tools/observability/providers/langfuse_provider.py

LangfuseSinkConfig — extract verbatim, do not guess

From @dataclass LangfuseSinkConfig in praisonai/observability/langfuse.py:

Option Type Default Description
public_key str "" (then os.getenv("LANGFUSE_PUBLIC_KEY", "")) Langfuse public key (pk-lf-...)
secret_key str "" (then os.getenv("LANGFUSE_SECRET_KEY", "")) Langfuse secret key (sk-lf-...)
host str "" (then LANGFUSE_HOSTLANGFUSE_BASE_URLhttps://cloud.langfuse.com) Langfuse server URL
flush_at int 20 Number of events that triggers a flush
flush_interval float 10.0 Seconds between background flushes
enabled bool True Master switch

Files to create / modify

1. UPDATE docs/observability/langfuse.mdx (primary change)

Add two new top-level sections (plus minor wording tweaks). Existing obs.langfuse() content stays — it's correct and covers Path A.

New section: ## CLI Observability — --observe langfuse``

Insert after the existing ## CLI Commands section. Document Path B end-to-end:

  • Hero one-liner: praisonai --observe langfuse run agents.yaml
  • Env-var alias: PRAISONAI_OBSERVE=langfuse
  • Required env vars: LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_HOST (or LANGFUSE_BASE_URL) — same table as the existing Environment Variables section can be referenced.
  • What spans appear in Langfuse (mermaid sequence diagram showing agent_start → llm_request → llm_response → tool_call_start → tool_call_end → agent_end).
  • A small <Note>: as of PR #1461, atexit auto-closes the sink — no manual flush required for CLI runs.
  • Cross-link to /observability/custom-tracing for the underlying ContextTraceSinkProtocol.

New section: ## Programmatic — LangfuseSink + context bridge

Insert after the new CLI section. Agent-centric example first (per AGENTS.md §1.1.9):

from praisonaiagents import Agent
from praisonaiagents.trace.protocol import TraceEmitter, set_default_emitter
from praisonaiagents.trace.context_events import ContextTraceEmitter, set_context_emitter
from praisonai.observability import LangfuseSink, LangfuseSinkConfig
import atexit

sink = LangfuseSink(LangfuseSinkConfig())  # reads env vars

# Action-level events (RouterAgent / PlanningAgent)
set_default_emitter(TraceEmitter(sink=sink, enabled=True))

# Context-level events (Agent.start lifecycle, tool calls, LLM I/O) — required for full coverage
set_context_emitter(ContextTraceEmitter(sink=sink.context_sink(), enabled=True))

atexit.register(sink.close)

agent = Agent(name="Writer", instructions="Write a haiku about code.")
agent.start("Write a haiku about code.")

⚠️ The set_context_emitter(... sink=sink.context_sink() ...) call is required for typical single-agent flows. Without it, only RouterAgent token-usage and PlanningAgent.plan_created events appear in Langfuse — Agent.start() lifecycle is silent. (This is the exact gap fixed by #1461.)

Follow with the LangfuseSinkConfig table above.

New <AccordionGroup> entry: "Trace content quality (PR #1461)"

Inside the existing ## Best Practices section, add an accordion explaining that as of #1461, llm_response spans contain the assistant message text (or [tool_calls: ...] summary), not the raw ChatCompletion(...) repr — so the Langfuse "Output" panel is now human-readable.

Minor tweaks

  • Reword the existing ## How It Works section to make clear it describes Path A specifically; add a one-line note pointing to the new CLI / programmatic-sink sections for Path B.
  • Add an "Always Flush Before Exit" caveat: still best practice for obs.langfuse() (Path A), but --observe langfuse (Path B) auto-registers atexit.close since #1461.
  • Add a "Path comparison" mermaid diagram near the top of the page (the table above, in mermaid form) so readers know which path to pick.

2. UPDATE docs/observability/overview.mdx

Two small fixes on the supported-providers table (line ~51):

3. docs.jsonno change required

The Langfuse page is already registered. CLI flag docs live inside the existing page.

Placement rules (AGENTS.md §1.8)

  • docs/observability/ — agent-writable. Both files in scope live here.
  • ❌ Do not touch docs/concepts/ — observability is a feature integration, not a core concept.
  • ❌ Do not touch docs/js/ or docs/rust/LangfuseSink + the bridge are Python-only; those trees are auto-generated by the parity system.

Acceptance checklist (per AGENTS.md §9)

  • docs/observability/langfuse.mdx updated with the two new sections (CLI --observe langfuse; programmatic LangfuseSink + bridge).
  • Path A vs Path B comparison present (table or mermaid) so users can choose.
  • Agent-centric code example leads the programmatic section (AGENTS.md §1.1.9), and includes the set_context_emitter(... sink=sink.context_sink() ...) call.
  • Every LangfuseSinkConfig field documented with the exact type + default from source.
  • _extract_llm_response_content improvement (Follow-up 1) called out in Best Practices accordion ("trace output is now readable").
  • atexit auto-close behaviour noted; explicit provider.flush() still recommended for Path A.
  • All Mermaid diagrams use the AGENTS.md §3.1 colour scheme.
  • <Steps>, <AccordionGroup>, <CardGroup> components used per template (AGENTS.md §2).
  • docs/observability/overview.mdx Langfuse install column fixed to pip install langfuse.
  • No edits to docs/concepts/, docs/js/, or docs/rust/.
  • All code examples are runnable copy-paste (AGENTS.md §5.1) — including the imports.
  • Cross-link added between docs/observability/langfuse.mdx (Path B section) and docs/observability/custom-tracing.mdx (the underlying protocol).

References

  • PraisonAI PR #1461 — merged 2026-04-18, head SHA a7efb680d33f48955257fbf59f7817bfb166a9f6 (the source change driving this issue).
  • PraisonAI Issue #1460 — the spec PR #1461 implements (closed as completed).
  • PraisonAI PR #1420 — the prior Langextract context-bridge fix (identical pattern; reference for prose).
  • PraisonAIDocs Issue #173 — sibling Langextract docs issue (still open, same architectural template).
  • PraisonAIDocs PR/Issue #64 — prior Langfuse rewrite (closed); explains Path A; Path B is the gap this issue fills.

Metadata

Metadata

Assignees

No one assigned

    Labels

    claudeTrigger Claude Code analysisdocumentationImprovements or additions to documentationobservability

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions