Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ tokio = { version = "1", features = ["full"] }
tracing = { version = "0.1", default-features = false, features = ["attributes", "std"] }
tracing-opentelemetry = "0.33"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1", features = ["v7"] }
1 change: 1 addition & 0 deletions crates/libsy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ tokio.workspace = true
tokio-stream = "0.1"
tracing.workspace = true
tracing-opentelemetry.workspace = true
uuid.workspace = true

[dev-dependencies]
# SDK + in-memory exporter to assert what the observability layer records.
Expand Down
1 change: 1 addition & 0 deletions crates/libsy/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ pub(crate) mod testing;

pub mod algorithm;
pub mod classifier;
pub mod outcome_metadata;
pub mod processor;
pub mod state;
27 changes: 27 additions & 0 deletions crates/libsy/src/core/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ pub struct RoutingOutcome {
pub request: Request,
/// A response produced while routing, or `None` when the client must make the answer call.
pub response: Option<Response>,
/// Outcome identity and optional algorithm evidence.
///
/// Constructors leave this empty; [`Algorithm::run_stream`] fills it before publishing a
/// successful outcome.
pub metadata: Option<crate::OutcomeMetadata>,
}

impl RoutingOutcome {
Expand All @@ -93,6 +98,7 @@ impl RoutingOutcome {
selected_model_ids,
request,
response: None,
metadata: None,
}
}

Expand All @@ -104,6 +110,7 @@ impl RoutingOutcome {
selected_model_ids: vec![selected_model_id],
request,
response: Some(response),
metadata: None,
}
}
}
Expand Down Expand Up @@ -196,6 +203,13 @@ impl Driver {
/// item on failure. Internal: called once by [`run_stream`](Algorithm::run_stream)
/// when the algorithm finishes.
pub(crate) async fn finish(&self, result: Result<RoutingOutcome>) -> Result<()> {
let result = result.map(|mut outcome| {
let metadata = outcome
.metadata
.get_or_insert_with(|| crate::OutcomeMetadata::new(self.algorithm.clone(), None));
tracing::Span::current().record("outcome_id", metadata.outcome_id());
outcome
});
let selected_model = result
.as_ref()
.ok()
Expand Down Expand Up @@ -483,6 +497,7 @@ mod tests {
);
assert_eq!(outcome.request.model_id().as_deref(), Some("selected"));
assert!(outcome.response.is_none());
assert!(outcome.metadata.is_none());

let outcome = RoutingOutcome::route_to("only".into(), Vec::new(), request());
assert_eq!(outcome.selected_model_ids, target_set(&["only"]));
Expand Down Expand Up @@ -717,6 +732,18 @@ mod tests {
}))?;
}
Step::Done(outcome) => {
let metadata = outcome
.metadata
.as_ref()
.expect("run_stream should attach outcome metadata");
assert_eq!(metadata.algorithm, "test");
assert_eq!(
uuid::Uuid::parse_str(metadata.outcome_id())
.expect("outcome id should be a UUID")
.get_version_num(),
7
);
assert!(metadata.evidence.is_none());
let response = outcome
.response
.ok_or_else(|| test_error("expected an answered outcome"))?;
Expand Down
30 changes: 30 additions & 0 deletions crates/libsy/src/core/outcome_metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Metadata describing a routing outcome.

/// Identity and optional algorithm evidence attached to a successful routing outcome.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OutcomeMetadata {
outcome_id: String,
/// Stable name of the algorithm that produced the outcome.
pub algorithm: String,
/// Optional bounded, machine-readable evidence produced by the algorithm.
pub evidence: Option<String>,
}

impl OutcomeMetadata {
/// Creates outcome metadata with a new UUIDv7 identifier.
pub fn new(algorithm: String, evidence: Option<String>) -> Self {
Self {
outcome_id: uuid::Uuid::now_v7().to_string(),
algorithm,
evidence,
}
}

/// Returns the unique identifier for this outcome.
pub fn outcome_id(&self) -> &str {
&self.outcome_id
}
}
1 change: 1 addition & 0 deletions crates/libsy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
mod core;
pub use core::algorithm::{Algorithm, CallModel, Driver, RoutingOutcome, Step, StepStream, drive};
pub use core::classifier::{Classification, Classifier, Score};
pub use core::outcome_metadata::OutcomeMetadata;
pub use core::processor::{Event, Processor};
pub use core::state::{State, StateValue};

Expand Down
1 change: 1 addition & 0 deletions crates/libsy/src/observability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub(crate) fn run_span(algorithm: &str, request: &Request) -> Span {
target: TRACING_TARGET,
"libsy.run",
algorithm,
outcome_id = tracing::field::Empty,
switchyard.algorithm = algorithm,
openinference.span.kind = "CHAIN",
switchyard.route = tracing::field::Empty,
Expand Down
1 change: 1 addition & 0 deletions crates/switchyard-py/src/libsy_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ fn step_to_python(step: RustStep) -> PyResult<PyStep> {
selected_model_ids,
request,
response,
metadata: _,
} = *outcome;
Python::attach(|py| {
Ok(PyStep::Done {
Expand Down
Loading