Langfuse
Langfuse is an open-source LLM engineering platform with a polished tracing UI, session grouping, and cost attribution. It has a generous free cloud tier and a self-host option via docker-compose.
Signals: traces only. Deployment: local (docker compose) or cloud. Cost: OSS (self-host) / free tier + paid (cloud).
Cloud (fastest)
Sign up at cloud.langfuse.com, create a project, and grab the public + secret keys from Settings → API Keys.
Option A — plugin-specific env vars:
export OTEL_LANGFUSE_PUBLIC_API_KEY="pk-lf-..."
export OTEL_LANGFUSE_SECRET_API_KEY="sk-lf-..."
# Optional — defaults to EU cloud. A root URL, ".../api/public/otel" or the
# full ".../api/public/otel/v1/traces" all work; the plugin completes the path.
export OTEL_LANGFUSE_ENDPOINT="https://cloud.langfuse.com/api/public/otel/v1/traces"
# US region:
# export OTEL_LANGFUSE_ENDPOINT="https://us.cloud.langfuse.com/api/public/otel/v1/traces"
Option B — Langfuse-standard env vars from their docs:
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_BASE_URL="https://cloud.langfuse.com"
Both forms work for the credentials. In env-var mode (no backends: list), Langfuse is only selected when at least one OTEL_LANGFUSE_* variable is set, so Option B alone does not turn export on; add OTEL_LANGFUSE_ENDPOINT (or use a backends: entry). The plugin automatically constructs the Authorization: Basic ... header from the two keys — you don't need to base64-encode anything yourself.
Self-hosted
Langfuse self-host is a full stack (Langfuse + Postgres + Redis + ClickHouse + MinIO). The plugin ships with a ready-to-go compose file:
cd ~/.hermes/plugins/hermes_otel
docker compose -f docker-compose/langfuse/docker-compose.yaml up -d
# Wait ~60s for ClickHouse to start
Pre-seeded test keys:
export OTEL_LANGFUSE_PUBLIC_API_KEY="lf_pk_hermes_dev"
export OTEL_LANGFUSE_SECRET_API_KEY="lf_sk_hermes_dev"
export OTEL_LANGFUSE_ENDPOINT="http://localhost:3000" # completed to /api/public/otel/v1/traces
UI at http://localhost:3000.
The same file runs Langfuse v4 with LANGFUSE_VERSION=4 docker compose -f docker-compose/langfuse/docker-compose.yaml up -d (use down -v when switching major versions). Export
works unchanged on v4. Its default events_only mode removes /api/public/traces,
/api/public/observations and /api/public/sessions (all 404, #246); the dashboard's Langfuse
adapter detects that once per query URL and reads v4 through /api/public/v2/observations
and /api/public/v2/metrics instead (see Dashboard). Verified on 4.56.0 and on
3.225.11 on 2026-10-09; query_api: v3 or v4 on the entry pins the API.
Multi-backend config
# ~/.hermes/hermes_otel.yaml
backends:
- type: langfuse
public_key_env: LANGFUSE_PUBLIC_KEY
secret_key_env: LANGFUSE_SECRET_KEY
base_url: https://cloud.langfuse.com
# Or override the full OTLP path:
# endpoint: https://cloud.langfuse.com/api/public/otel/v1/traces
Secrets should live in env vars (*_env: keys). Plaintext public_key: / secret_key: also work but are discouraged.
What you'll see
Langfuse groups traces into sessions automatically. hermes-otel's agent / cron root spans show up as top-level traces; nested llm.* / api.* / tool.* appear as observations within.
- User message lands on
input.value/gen_ai.input.messageson thellm.*span; the full request messages are on eachapi.*span (with the defaultcontent_capture: full). - Assistant response lands on
output.value/gen_ai.output.messages. - Token counts use
gen_ai.usage.input_tokensandgen_ai.usage.output_tokensonapi.*spans. - Tool calls appear as child spans with inputs/outputs.
Attribute convention
Langfuse keys off gen_ai.*. The plugin emits that alongside the OpenInference convention so the same span serves both UIs — see Attribute conventions.
Metrics
Langfuse doesn't accept OTLP metrics or logs — it's trace-only. The langfuse type turns metrics and logs off by default, so the plugin builds no metrics exporter for this entry. If you want token/tool/cost metrics too, fan out to a metrics-capable backend in parallel; see Multi-backend.
Dashboard
A trace ingested through OTLP keeps its real root observation (the plugin's agent span); only when Langfuse holds no single parentless observation does the bundled dashboard build one top-level node from the trace record to hold the tree together. That node carries synthetic: true and a synthetic.reason, so it is never mistaken for a span the agent emitted.
Langfuse v3 keeps an OTLP-ingested observation's span attributes verbatim under metadata.attributes; the trace detail lifts them onto each span, so the attribute table shows what the plugin exported and the header reads the model and the turn's token roll-up from the agent root exactly as on every other backend. Langfuse's own derived fields (model, usage, input, output) are only the fallback for observations written through its SDKs, which carry no metadata.attributes; its usage object is skipped for OTLP observations because Langfuse zero-fills it on every non-GENERATION type, and a zero on the root used to make the header show 0 tokens while the api.* rows below carried the counts.
Langfuse v4 (events_only, the default): the trace list is built from the AGENT root observations of /api/public/v2/observations (one per Hermes turn; session id, an ERROR level and the native k=v parameters filter server-side, name prefix and minimum duration on the rows), and each page's token totals, cost, observation count and model join from one /api/public/v2/metrics query grouped by trace. The detail is the trace's observations with their parent links and status; the v4 public API carries no input, output or per-observation usage, so the previews and per-span tokens are absent and the trace's totals sit on the root. Langfuse Cloud retires the v3 endpoints on 2026-11-16, so cloud projects read through this path from then on.
Troubleshooting
"Auth failed / 401 from Langfuse"
- You need both keys (public + secret). Langfuse won't authenticate with only one.
LANGFUSE_BASE_URL/base_urltake the site root;OTEL_LANGFUSE_ENDPOINT/endpointaccept the root,…/api/public/otelor the full…/api/public/otel/v1/traces— every form is completed to the traces URL (a root URL used to be posted to as-is and answered405).
"Nothing arrives and there is no error"
- Turn on the plugin's debug log (
HERMES_OTEL_DEBUG=true; it writes$HERMES_HOME/plugins/hermes_otel/debug.log). Every batch gets one line,export Langfuse: N span(s) -> SUCCESSor-> FAILURE(#167). The HTTP status behind a failure (a405or401from Langfuse) is logged by theopentelemetryPython logger.
"Spans show up but without message content"
- Check
content_capture(or the oldercapture_previews) — withcontent_capture: offthe plugin suppressesinput.value/output.valueat the source. - Remember: by default
input.valueis just the latest user turn. To capture the full conversation history, enable conversation capture.
"Self-hosted Langfuse won't start"
- ClickHouse needs ~60 seconds to come up. The plugin will show connection refused errors until it's ready. Wait and retry.