NVIDIA-NeMo / NVIDIA-NeMo/Switchyard
Emit physical provider and classifier attempt lifecycles through Relay
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 3.2k
- Forks
- 291
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 182
Description
Problem
The NeMo Relay plugin exposes one logical LLM call plus high-level Switchyard marks and metrics. Those records show that routing happened, which model calls ran, how long routing took, and whether the selected model fell back. They do not create nested timed spans for the work Switchyard performs inside that call.
A routed request can include:
Relay LLM call
Switchyard algorithm
classifier or judge call
candidate model call
HTTP attempt and retry
answer candidate
fallback candidate
Today Relay exports the outer LLM span and the Switchyard summary marks. In a trace viewer, the internal routing work appears as flat records rather than a timed hierarchy. This makes it hard to see where routing time was spent or which retry or fallback caused a delay.
ATIF should continue to describe the normal agent trajectory without these internal calls. This issue is about the OpenTelemetry trace.
Existing instrumentation
Switchyard already emits these spans while running inside the plugin:
libsy.runfor the algorithm runlibsy.llm_callfor classifier and judge requestslibsy.client_callfor model candidates and fallbackslibsy.upstream_attemptfor HTTP attempts and retries
Together they cover most of the internal timeline, although they are not yet one complete hierarchy. Relay does not receive these spans through its plugin runtime, so its OpenTelemetry exporter cannot include them. The plugin currently reports summaries through Relay marks and metrics instead.
#604 and #612 improve those summaries but do not solve the missing span hierarchy.
Boundary to settle
There are two reasonable approaches:
- Switchyard exports its existing spans directly to the same collector, using the trace context Relay provides.
- Relay adds a native plugin API for explicitly parented child spans and remains responsible for exporting them.
Relay currently provides marks, metrics, and stack-based scopes. It does not provide an explicit child-span surface suited to overlapping work or spans that remain open while a response stream is consumed.
The second approach keeps one observability pipeline, but the boundary should be agreed with Relay maintainers before adding another lifecycle implementation to the plugin.
Desired outcome
- Keep the outer Relay LLM span.
- Show Switchyard routing work beneath it in the same trace.
- Distinguish classifier or judge calls from answer calls.
- Show model candidates, fallbacks, HTTP attempts, and retries with their own timing and outcome.
- Close streaming spans exactly once on completion, error, or cancellation.
- Preserve the existing routing marks and metrics.
- Avoid duplicate span export.
- Keep request content and unsafe provider error text out of span fields.
- Add tests for parent-child relationships, ordering, and balanced span lifecycles.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the existing libsy.run, libsy.llm_call, libsy.client_call, and libsy.upstream_attempt instrumentation, along with Relay's current marks, metrics, and stack-based scopes. Review PRs #604 and #612, then settle the export boundary with Relay maintainers. Done means one trace contains the nested routing, candidate, fallback, HTTP, retry, and streaming lifecycles without duplicate export or unsafe span fields.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- observability-sre
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100