Skip to content

Emit physical provider and classifier attempt lifecycles through Relay #299

Description

@afourniernv

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.run for the algorithm run
  • libsy.llm_call for classifier and judge requests
  • libsy.client_call for model candidates and fallbacks
  • libsy.upstream_attempt for 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:

  1. Switchyard exports its existing spans directly to the same collector, using the trace context Relay provides.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions