fix(observability)!: attribute ATIF steps to effective response model#399
Conversation
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Enterprise Run ID: 📒 Files selected for processing (1)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (13)**/*.rs📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)
Files:
{crates/core,crates/adaptive}/**/*📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)
Files:
crates/core/**/*.rs📄 CodeRabbit inference engine (.agents/skills/test-go-binding/SKILL.md)
Files:
**/*.{rs,py}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{rs,py,js,mjs,cjs,ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{rs,py,go,js,ts,c,h}📄 CodeRabbit inference engine (CONTRIBUTING.md)
Files:
**/*.{rs,go,js,ts}📄 CodeRabbit inference engine (CONTRIBUTING.md)
Files:
{crates/**/src/**/*.rs,python/**/*.py}📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)
Files:
crates/core/src/observability/{atif,otel,openinference}.rs📄 CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)
Files:
**/*📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
Files:
crates/{core,adaptive}/**/*📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
Files:
**/*.{rs,py,go,js,ts}📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
Files:
crates/{core,adaptive}/**/*.rs⚙️ CodeRabbit configuration file
Files:
🔇 Additional comments (2)
WalkthroughATIF model selection now prefers the normalized LLM response model for emitted steps and span candidates, while retaining event-based fallbacks. Unit tests cover effective-model preference and Start-event fallback. ChangesATIF model selection
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
> [!WARNING] > **BREAKING CHANGE:** **[Rust source compatibility]** This PR adds `FlowError::Upstream` and new fields to the public `AtofEndpointConfig`, `AtofEndpointSectionConfig`, and `EmitMarkEventParams` types. Downstream exhaustive `FlowError` matches must handle the new variant, and direct struct literals must initialize the new fields or migrate to the provided constructor/builder APIs. Builder-based mark emission and default Relay configurations remain compatible. #### Overview This adds Relay's first-party integration with the [Switchyard](https://github.com/NVIDIA-NeMo/Switchyard) Decision API. The plugin routes buffered and streaming LLM requests through an external Switchyard service, executes the selected Relay-owned backend, and records causal model-routing optimization evidence. Switchyard owns provider-protocol translation through its `switchyard-translation` Rust library. Relay consumes that library only when the native Switchyard plugin is enabled; default Relay builds do not include either the plugin or the translation dependency. - [x] I confirm this contribution is my own work, or I have the right to submit it under this project's license. - [x] I searched existing issues and open pull requests, and this does not duplicate existing work. #### Details ##### Plugin and execution behavior - Add the standalone `nemo-relay-switchyard` first-party plugin. - Normalize stable Relay identity and build `switchyard.routing_request.v1` requests. - Validate selected routes and optional counterfactual baselines against exact Relay-owned backend bindings. - Support enforce and observe-only modes, six request-materialization modes, bounded retry-aware dispatch, protocol-specific trusted fallbacks, and no retry after the first upstream stream item. - Record one model-routing optimization contribution only after the selected route commits. Failed decisions, failed provider attempts, and fallback dispatches do not claim routing savings. - Preserve Switchyard routing metadata, including snapshot freshness, decision source, event count, and turn depth. - Continue using Relay's existing ATOF exporter and lifecycle; the plugin does not install local routers, feature accumulators, or a duplicate event subscriber. ##### Translation ownership and optional dependency Request, buffered-response, and SSE translation across OpenAI Chat Completions, OpenAI Responses, and Anthropic Messages use Switchyard's public `switchyard-translation` library instead of a Relay-local translation engine. The dependency is pinned to the immutable topic-branch commit [`8f9db9a6a47f848cdff1d262276ba25a8ae9cbc8`](NVIDIA-NeMo/Switchyard@8f9db9a), which includes the Decision API series, StageRouter baseline/freshness behavior, and [Switchyard #46](NVIDIA-NeMo/Switchyard#46). The Relay CLI exposes this through `--features switchyard`: - Without that feature, `nemo-relay-cli` contains neither `nemo-relay-switchyard` nor `switchyard-translation`. - With that feature, the plugin and its pinned Switchyard translation library are compiled and registered. - Relay continues to own target bindings, credentials, dispatch, retries, fallbacks, lifecycle, and observability. - Relay retains narrow fail-open policy checks for provider extensions that cannot be translated without data loss. ##### Runtime requirements The current experimental deployment requires a separately running Switchyard service from the pinned topic commit. The plugin does not start or supervise that service. Switchyard availability is not a Relay startup requirement. It is a request-time dependency: connection failures, timeouts, malformed decisions, selected-route drift, unsupported extensions, and missing required identity fail open to the configured same-protocol default. ATOF-backed profiles additionally require Relay's ATOF HTTP exporter to target Switchyard's `/v1/atof/events` endpoint with preserved field names and environment-referenced authentication. The public examples document this service boundary, verify the exact Switchyard revision before startup, and intentionally remain manual compatibility workflows rather than production orchestration. ##### Optimization accounting and observability This PR builds on the plugin-neutral accounting contract merged in [NeMo Relay #389](#389). Applied routing contributions carry the selected and baseline model transition, decision identity, backend and tier, attempt, rollout mode, reason, and router metadata. Observe-only decisions remain visible as non-applied evidence. Relay core combines routing and other plugin contributions at LLM close while retaining explicit token evidence for downstream repricing. Existing requested, decision, retry, fallback, and error routing marks remain available alongside the canonical optimization contribution. The generic ATIF change that attributes `step.model_name` to the effective response model is intentionally separated into [NeMo Relay #399](#399). This PR still ensures translated provider responses carry the actual selected model; #399 independently controls how ATIF presents it. ##### Testing summary - `cargo fmt --all -- --check` - `cargo clippy -p nemo-relay-switchyard --all-targets -- -D warnings` - `cargo clippy -p nemo-relay-cli --features switchyard --all-targets -- -D warnings` - `cargo check -p nemo-relay-cli` — proves the default CLI builds without Switchyard. - `cargo check -p nemo-relay-cli --features switchyard` — proves the opt-in dependency path. - `cargo test -p nemo-relay-switchyard` — 31 passed. - `cargo test -p nemo-relay-cli gateway::tests` — 33 passed without the feature. - `cargo test -p nemo-relay-cli --features switchyard gateway::tests` — 33 passed with the feature. - `cargo test -p nemo-relay-cli --features switchyard --test switchyard_process_e2e` — buffered, streaming, retry, and fail-open process boundary passed after the split. - `examples/switchyard/run-real-e2e.sh` against the exact pinned Switchyard service — passed with the expected StageRouter route sequence `provider/weak`, `provider/strong`, `provider/strong`. - The pinned Switchyard translation crate separately passes 61 request, response, streaming, extension, and round-trip tests under Rust 1.93. ##### Previous feedback addressed - Split the implementation at repository ownership boundaries. - Replaced Relay's custom translation engine with the Switchyard-owned reusable library. - Kept the dependency compile-time optional for users who do not enable the Relay-native Switchyard plugin. - Removed in-process Random, LLM, and Cascade routers, Relay-side feature accumulation, duplicate Switchyard subscriptions, and the global fallback target. - Replaced literal credentials with environment-referenced headers and activation-time validation. - Hardened internal dispatch headers and structured upstream-failure metadata. - Standardized routing terminology on StageRouter. - Standardized model-routing evidence on Relay's shared optimization contract instead of adding a second post-call savings mark. - Moved generic ATIF effective-model attribution into focused PR #399. - Moved Fern documentation into focused experimental docs PR #400 so this functional PR remains code-focused. - Kept public examples free of internal-only providers, credentials, Visor source references, and private infrastructure assumptions. #### Where should the reviewer start? 1. `crates/switchyard/src/component.rs` — Decision API lifecycle, exact target and baseline validation, retries, fallback, and terminal route commitment. 2. `crates/switchyard/src/translation.rs` and `stream_translation.rs` — thin policy adapters over the Switchyard-owned translation library. 3. `crates/cli/Cargo.toml` and the `cfg(feature = "switchyard")` registration paths — compile-time optionality. 4. `crates/cli/src/gateway.rs` — reserved dispatch headers and structured retry-aware upstream failures. 5. `crates/cli/tests/switchyard_process_e2e.rs` — process-boundary buffered, streaming, and fail-open coverage. 6. `examples/switchyard/README.md` and `run-real-e2e.sh` — pinned experimental service workflow. #### Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to) - Relates to [NVIDIA/NeMo-Relay #389](#389). - Relates to [NVIDIA/NeMo-Relay #399](#399). - Relates to [NVIDIA/NeMo-Relay #400](#400). - Relates to [NVIDIA-NeMo/Switchyard #46](NVIDIA-NeMo/Switchyard#46). - Paired with the Switchyard Decision API topic series: - [NVIDIA-NeMo/Switchyard #8](NVIDIA-NeMo/Switchyard#8) - [NVIDIA-NeMo/Switchyard #9](NVIDIA-NeMo/Switchyard#9) - [NVIDIA-NeMo/Switchyard #11](NVIDIA-NeMo/Switchyard#11) - [NVIDIA-NeMo/Switchyard #35](NVIDIA-NeMo/Switchyard#35) Authors: - Bryan Bednarski (https://github.com/bbednarski9) Approvers: - Will Killian (https://github.com/willkill07) - Maryam Najafian (https://github.com/mnajafian-nv) - https://github.com/Salonijain27 URL: #369
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/core/src/observability/atif.rs`:
- Around line 1515-1516: Update the paired-span model attribution around
effective_response_model_name so it distinguishes the response-only normalized
lookup from the fallback to end’s event model, preserving the existing order
before consulting model_name_for_llm_event(start). Add a regression case
covering different start and end models with no normalized response, asserting
the intended paired-span fallback attribution.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 726c31ea-b2df-4b93-bfcc-00aedcf806f6
📒 Files selected for processing (2)
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (14)
**/*.rs
📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)
**/*.rs: Any Rust change must runjust test-rust
Any Rust change must runcargo fmt --all
Any Rust change must runcargo clippy --workspace --all-targets -- -D warnings
**/*.rs: Runcargo fmt --allfor all FFI work since it is Rust work
Runjust test-rustto validate FFI changes
Runcargo clippy --workspace --all-targets -- -D warningsto enforce strict linting on FFI workWhen Rust files changed as part of Go work, also run
cargo fmt --all,just test-rust, andcargo clippy --workspace --all-targets -- -D warnings
**/*.rs: Runcargo fmt --allwhen Rust files are changed as part of Node work
Runcargo clippy --workspace --all-targets -- -D warningswhen Rust files are changed as part of Node work
Runjust test-rustwhen Rust files are changed as part of Node workWhen changing the core Rust runtime or Rust-facing API surface, format Rust code with
cargo fmt(rustfmt defaults), keepcargo clippy -- -D warningsclean, and satisfycargo deny checkperdeny.toml.
**/*.rs: If any Rust code changed, always runjust test-rust.
If any Rust code changed, also runcargo fmt --all.
If any Rust code changed, also runcargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, runcargo fmt --allandcargo clippy --workspace --all-targets -- -D warningseven if relying on pre-commit.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
{crates/core,crates/adaptive}/**/*
📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)
Changes to
crates/coreorcrates/adaptivemust run the full language matrix
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
crates/core/**/*.rs
📄 CodeRabbit inference engine (.agents/skills/test-go-binding/SKILL.md)
If the change touched
crates/coreor shared runtime semantics, also usevalidate-changefor broader validation
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
**/*.{rs,py}
📄 CodeRabbit inference engine (AGENTS.md)
Follow binding naming conventions in Rust and Python: use
snake_case.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{rs,py,js,mjs,cjs,ts,tsx}: UseJson = serde_json::Valuein Rust-facing runtime APIs where the existing code expects JSON payloads.
UseResult<T>withFlowErrorin core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
**/*.{rs,py,go,js,ts,c,h}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Use language-appropriate naming conventions: Rust
snake_case, C FFI exports prefixednemo_relay_, GoPascalCase, Node.jscamelCase, and Pythonsnake_case.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
**/*.{rs,go,js,ts}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding
//comment form.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
**/*
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, usemaintain-dynamic-pluginsand include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, preferuv run pre-commit run --files <changed files...>.
Before review or handoff, runuv run pre-commit run --all-files.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
crates/{core,adaptive}/**/*
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
If
crates/coreorcrates/adaptivechanged, run the full validation matrix across Rust, Python, Go, and Node.js.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
**/*.{rs,py,go,js,ts}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
If a language surface changed, always run that language's test target even when Rust core did not change.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
crates/{core,adaptive}/**/*.rs
⚙️ CodeRabbit configuration file
crates/{core,adaptive}/**/*.rs: Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.
Pay close attention to task-local/thread-local scope propagation, callback lifetimes, stream finalization, and root_uuid isolation.
Public API changes should preserve existing behavior unless tests and docs show the intended migration path.
Files:
crates/core/tests/unit/atif_tests.rscrates/core/src/observability/atif.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}
⚙️ CodeRabbit configuration file
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.
Files:
crates/core/tests/unit/atif_tests.rs
{crates/**/src/**/*.rs,python/**/*.py}
📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)
Do not add tests under
src; Rust tests belong in cratetests/trees, and Python SDK tests belong underpython/tests.
Files:
crates/core/src/observability/atif.rs
crates/core/src/observability/{atif,otel,openinference}.rs
📄 CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)
When changing the core event model, emitted fields, exporter behavior, subscriber config, or lifecycle, keep the ATIF, OpenTelemetry, and OpenInference implementations in sync.
Files:
crates/core/src/observability/atif.rs
🔇 Additional comments (3)
crates/core/src/observability/atif.rs (2)
2376-2376: 📐 Maintainability & Code QualityProvide the required core-Rust validation results.
The PR summary mentions targeted tests, formatting, and Clippy, but this
crates/corebehavior change also requires evidence forjust test-rust,cargo fmt --all, strict Clippy,cargo deny check,validate-change, the Rust/Python/Go/Node matrix, anduv run pre-commit run --all-files.As per coding guidelines, “If the change touched
crates/coreor shared runtime semantics, also usevalidate-changefor broader validation” and “Ifcrates/coreorcrates/adaptivechanged, run the full validation matrix across Rust, Python, Go, and Node.js.”Source: Coding guidelines
2701-2706: LGTM!crates/core/tests/unit/atif_tests.rs (1)
882-933: LGTM!
|
/ok to test 79905e8 |
willkill07
left a comment
There was a problem hiding this comment.
May want to address given Coderabbit feedback. Non-blocking.
LGTM
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/core/src/observability/atif.rs`:
- Around line 1515-1523: Extract the shared response-model fallback into a
helper such as effective_model_for_pair, preserving the order
normalized_response_model_name → explicit model_name() →
model_name_for_llm_event(start) → model_name_for_llm_event(end). Update both
LlmSpanCandidate::from_events and effective_response_model_name, including the
handle_llm_end path, to use this helper instead of maintaining separate fallback
logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: eb0624e1-f419-483a-8a29-b456ac7b5d85
📒 Files selected for processing (2)
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (14)
**/*.rs
📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)
**/*.rs: Any Rust change must runjust test-rust
Any Rust change must runcargo fmt --all
Any Rust change must runcargo clippy --workspace --all-targets -- -D warnings
**/*.rs: Runcargo fmt --allfor all FFI work since it is Rust work
Runjust test-rustto validate FFI changes
Runcargo clippy --workspace --all-targets -- -D warningsto enforce strict linting on FFI workWhen Rust files changed as part of Go work, also run
cargo fmt --all,just test-rust, andcargo clippy --workspace --all-targets -- -D warnings
**/*.rs: Runcargo fmt --allwhen Rust files are changed as part of Node work
Runcargo clippy --workspace --all-targets -- -D warningswhen Rust files are changed as part of Node work
Runjust test-rustwhen Rust files are changed as part of Node workWhen changing the core Rust runtime or Rust-facing API surface, format Rust code with
cargo fmt(rustfmt defaults), keepcargo clippy -- -D warningsclean, and satisfycargo deny checkperdeny.toml.
**/*.rs: If any Rust code changed, always runjust test-rust.
If any Rust code changed, also runcargo fmt --all.
If any Rust code changed, also runcargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, runcargo fmt --allandcargo clippy --workspace --all-targets -- -D warningseven if relying on pre-commit.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
{crates/core,crates/adaptive}/**/*
📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)
Changes to
crates/coreorcrates/adaptivemust run the full language matrix
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
crates/core/**/*.rs
📄 CodeRabbit inference engine (.agents/skills/test-go-binding/SKILL.md)
If the change touched
crates/coreor shared runtime semantics, also usevalidate-changefor broader validation
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
**/*.{rs,py}
📄 CodeRabbit inference engine (AGENTS.md)
Follow binding naming conventions in Rust and Python: use
snake_case.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{rs,py,js,mjs,cjs,ts,tsx}: UseJson = serde_json::Valuein Rust-facing runtime APIs where the existing code expects JSON payloads.
UseResult<T>withFlowErrorin core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
**/*.{rs,py,go,js,ts,c,h}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Use language-appropriate naming conventions: Rust
snake_case, C FFI exports prefixednemo_relay_, GoPascalCase, Node.jscamelCase, and Pythonsnake_case.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
**/*.{rs,go,js,ts}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding
//comment form.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
{crates/**/src/**/*.rs,python/**/*.py}
📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)
Do not add tests under
src; Rust tests belong in cratetests/trees, and Python SDK tests belong underpython/tests.
Files:
crates/core/src/observability/atif.rs
crates/core/src/observability/{atif,otel,openinference}.rs
📄 CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)
When changing the core event model, emitted fields, exporter behavior, subscriber config, or lifecycle, keep the ATIF, OpenTelemetry, and OpenInference implementations in sync.
Files:
crates/core/src/observability/atif.rs
**/*
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, usemaintain-dynamic-pluginsand include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, preferuv run pre-commit run --files <changed files...>.
Before review or handoff, runuv run pre-commit run --all-files.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
crates/{core,adaptive}/**/*
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
If
crates/coreorcrates/adaptivechanged, run the full validation matrix across Rust, Python, Go, and Node.js.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
**/*.{rs,py,go,js,ts}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
If a language surface changed, always run that language's test target even when Rust core did not change.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
crates/{core,adaptive}/**/*.rs
⚙️ CodeRabbit configuration file
crates/{core,adaptive}/**/*.rs: Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.
Pay close attention to task-local/thread-local scope propagation, callback lifetimes, stream finalization, and root_uuid isolation.
Public API changes should preserve existing behavior unless tests and docs show the intended migration path.
Files:
crates/core/src/observability/atif.rscrates/core/tests/unit/atif_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}
⚙️ CodeRabbit configuration file
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.
Files:
crates/core/tests/unit/atif_tests.rs
🔇 Additional comments (4)
crates/core/src/observability/atif.rs (3)
1515-1523: Past fallback-order concern is now fixed.The previously flagged issue (missing start-model fallback before
model_name_for_llm_event) is resolved — the chain now falls back throughstart.model_name()/end.model_name()before hittingmodel_name_for_llm_event, and the new regression test (atif_tests.rs882-903) covers exactly this scenario.
2383-2383: LGTM!
2708-2716: LGTM!crates/core/tests/unit/atif_tests.rs (1)
882-903: LGTM!Also applies to: 906-956
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
|
/ok to test a6c42e4 |
|
/merge |
Warning
BREAKING CHANGE: [Behavioral] ATIF LLM steps now prefer the model reported by the normalized provider response over the requested or profile model. Consumers that group, compare, or price trajectories by
step.model_namemay observe a different model for routed or cross-protocol calls. When no effective response model is available, the existing attribution fallback remains unchanged.Overview
Correct ATIF model attribution for LLM calls whose effective provider model differs from the model requested at the beginning of the Relay lifecycle.
This generic exporter fix is intentionally independent of the Switchyard plugin. Any router, proxy, or provider translation layer that records the effective model in
AnnotatedLlmResponsereceives the same ATIF behavior.Details
ATIF previously preferred the model recorded on the LLM start event. For routed requests, that value can be a client alias, profile model, or originally requested model rather than the backend that actually produced the response. The normalized end response already contains the effective model, but ATIF did not use it for
step.model_name.This PR:
qwen3:4bbut the normalized response is produced byqwen3.6:35b.No ATOF event, provider response, routing decision, cost calculation, or plugin behavior changes. Only exported ATIF
step.model_nameattribution changes.Validation:
cargo fmt --all -- --checkcargo test -p nemo-relay test_exporter_prefers_effective_response_model_over_requested_profile_modelcargo test -p nemo-relay observability::atif::tests— 88 passed.cargo clippy -p nemo-relay --all-targets -- -D warningsgit diff --checkWhere should the reviewer start?
crates/core/src/observability/atif.rs— model selection and fallback behavior.crates/core/tests/unit/atif_tests.rs— routed-model regression fixture.The central design decision is that ATIF describes the model that produced the response, while the requested model remains available in the original ATOF lifecycle input and optimization metadata.
Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)
Summary by CodeRabbit