Observability¶
Neurosurfer can ship every agent run to an external monitoring backend so you can see and debug what your agents do — the LLM turns, the tool calls, token usage and cost — in a real UI. Two backends are supported:
- Langfuse — batteries-included LLM observability (traces, token cost, sessions, evals). The best out-of-box experience.
- OpenTelemetry — the vendor-neutral standard. Emits GenAI-semantic-convention spans over OTLP, so any OTel backend ingests them: Arize Phoenix, Grafana Tempo, Datadog, Honeycomb — or Langfuse's own OTLP endpoint.
Install the extra:
How it works¶
Every agent already yields a stream of typed events (ToolStarted, ToolFinished, TurnCompleted, …). Tracing attaches a side-channel observer to that stream — it observes, never consumes — and translates it into backend calls. Nothing about how you consume agent.run(...) changes; the mapping is:
| Agent activity | Trace observation |
|---|---|
| a run | a trace (the root) |
| one LLM turn | a generation — model + input/output tokens ⇒ cost |
| a tool call (start→finish) | a span |
| a spawned sub-agent | a nested span under the parent run (same trace) |
| mode change / context compaction | a trace event |
Nesting. A run started inside another run (a spawned sub-agent) automatically nests under it — one trace shows parent → sub-agent → tool, not disconnected top-level traces. This works because the active run publishes its trace context, which sub-agents inherit (across await and parallel asyncio.gather spawns alike).
Sessions. Pass session_id= when constructing an agent and every run of that agent groups under one Langfuse session — the CLI does this per conversation, so a multi-message chat is one session (reset on /clear) instead of N stray traces.
Turn it on (zero code)¶
Tracing is auto-on from the environment — set the backend's connection vars and it activates on the next run. No code change.
Langfuse¶
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_HOST="https://cloud.langfuse.com" # or your self-hosted URL
Pick the right region
Langfuse Cloud runs EU (https://cloud.langfuse.com) and US (https://us.cloud.langfuse.com) as separate instances. Keys from one region return 401 Unauthorized against the other — set LANGFUSE_HOST to match where your project lives.
OpenTelemetry¶
Both can be active at once. Now just run an agent — the trace appears in the UI:
Force everything off (even when keys are present) with NEUROSURFER_EXPORTERS=none, or pin an explicit set with NEUROSURFER_EXPORTERS=langfuse,otel.
Turn it on (in code)¶
For explicit control, configure the registry before your first run:
from neurosurfer.observability.exporters import configure_exporters
configure_exporters(["langfuse"], service_name="my-app")
Or register your own exporter instance — subclass neurosurfer.observability.exporters.base.TraceExporter and override the hooks you need (on_run_start, on_turn, on_tool_start, on_tool_finish, on_run_finish, …):
from neurosurfer.observability.exporters import register_exporter
from neurosurfer.observability.exporters.base import MemoryExporter
mem = MemoryExporter() # captures the lifecycle in-memory (great for tests)
register_exporter(mem)
Guarantees¶
- Zero overhead when off. With no backend configured, no observer is created.
- Never breaks a run. A misbehaving or unreachable exporter is isolated — its errors are swallowed, the agent run is unaffected.
- Optional dependency. A base install (without the
observabilityextra) simply resolves to no exporters; nothing to import, nothing to fail.
Graph & workflow nesting
Agent runs — including spawned sub-agents — nest into a single trace today. Nesting a multi-node workflow (graph node → agent → tool) under one trace is the remaining step on the roadmap; the same trace-context mechanism will carry it.