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
gen_ai.content.prompt/input.valueon thellm.*span. - Assistant response lands on
gen_ai.content.completion/output.value. - 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 — it's trace-only. The plugin auto-skips the metrics exporter when Langfuse is the sole backend. 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 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"
- The OTLP exporter's failures (a
405or401from Langfuse) are logged by theopentelemetryPython logger, not the plugin's debug log (#167). Run Hermes withPYTHONWARNINGS/logging at WARNING foropentelemetry.exporterto see them, or export one span with the SDK directly to confirm the URL and keys.
"Spans show up but without message content"
- Check
capture_previews— if it's false, the plugin is suppressinginput.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.