Skip to main content

Phoenix

Arize Phoenix is an open-source LLM observability platform with a tracing UI built around the OpenInference span convention. It runs as a single container, takes ~5 seconds to start, and pretty-prints JSON previews.

Signals: traces only. Deployment: local (single container) or Arize AX cloud. Cost: OSS (self-host) / commercial (cloud).

Local (Docker)​

A ready-to-use compose file ships with the plugin:

cd ~/.hermes/plugins/hermes_otel
docker compose -f docker-compose/phoenix/docker-compose.yaml up -d

Then:

export OTEL_PHOENIX_ENDPOINT="http://localhost:6006/v1/traces"
export OTEL_PROJECT_NAME="hermes-agent"

Open the UI at http://localhost:6006. Traces show up in the hermes-agent project.

Arize AX Cloud​

Phoenix also runs as a hosted service. Point at your cloud endpoint and attach the ingest key:

export OTEL_PHOENIX_ENDPOINT="https://app.phoenix.arize.com/v1/traces"
# Headers can be passed via config.yaml:
# ~/.hermes/hermes_otel.yaml
backends:
- type: phoenix
endpoint: https://app.phoenix.arize.com/v1/traces
headers:
api_key: ${PHOENIX_API_KEY}

What you'll see​

Phoenix is built around LLM-specific spans, so the UI understands the plugin's span types natively:

  • agent / cron root spans appear as top-level traces with the turn summary on them (tool count, skills, final status).
  • llm.* spans show the user message in the Input panel and the assistant response in the Output panel (pretty-printed JSON when conversation capture is on).
  • api.* spans carry the token counts (llm.token_count.prompt, llm.token_count.completion, llm.token_count.total), the finish_reason, and the HTTP duration.
  • tool.* spans show the arguments (Input) and the result (Output). Errors map to StatusCode.ERROR so the Phoenix error filter works.

Attribute convention​

Phoenix uses OpenInference. hermes-otel emits that convention on every span (llm.token_count.*, input.value, output.value) alongside the gen_ai.* convention for other backends.

See Attribute conventions for the full dual-convention table.

Metrics and logs​

Phoenix ingests traces only: its server answers 405 on /v1/metrics and /v1/logs (checked against arizephoenix/phoenix:latest). The plugin therefore creates no metrics or logs exporter for a phoenix entry, and the startup banner reads ✓ Phoenix at … (traces only). Token, tool and cost metrics need a second backend that takes OTLP metrics (SigNoz, LGTM, OpenObserve) via multi-backend fan-out; Phoenix computes token totals and cost from the span attributes itself. If you front Phoenix with a collector that does accept metrics, set metrics: true on the entry explicitly.

Dashboard​

The bundled dashboard's Phoenix adapter shows the project named by project_name (on the backend entry, else the top-level key). If that project does not exist on the server the dashboard reports Phoenix project '…' not found; available: … instead of quietly showing another project's traces. With no project_name configured it shows the first project that has traces and says which one in its status (project_resolved, project_fallback: true).

Phoenix 20 removed the rootSpansOnly / orphanSpanAsRootSpan arguments of Project.spans; the adapter introspects the schema once per query URL and sends parent_id is None in the filter condition instead, so older and current builds both work (verified on 20.20.0, 2026-10-09).

Troubleshooting​

"No traces show up in Phoenix"

  • Check the endpoint includes /v1/traces — Phoenix doesn't redirect from the collector root.
  • Confirm the container is listening: curl -I http://localhost:6006 should return 200.
  • Turn on debug logging: export HERMES_OTEL_DEBUG=true, run a Hermes turn, check ~/.hermes/plugins/hermes_otel/debug.log for the export Phoenix: … -> SUCCESS|FAILURE line and, on failure, the [sdk] … Failed to export span batch code: <status> line with the reason.

"Spans are missing input/output previews"

  • If capture_previews: false or HERMES_OTEL_CAPTURE_PREVIEWS=false is set, previews are intentionally suppressed. Remove the setting, restart Hermes.

"Tokens show as zero"

  • Phoenix keys off llm.token_count.* — the plugin emits these on api.* spans (not llm.*). Check the child api.* span, not the parent.