diff --git a/crates/switchyard-nemo-relay-plugin/README.md b/crates/switchyard-nemo-relay-plugin/README.md index d1359096a..782ac774f 100644 --- a/crates/switchyard-nemo-relay-plugin/README.md +++ b/crates/switchyard-nemo-relay-plugin/README.md @@ -110,6 +110,19 @@ attributes. `target_model` comes from the configured Switchyard target set, rather than arbitrary caller input, keeping the metric cardinality bounded by the deployment. +Every non-metric mark sets `data_schema.name` to the mark name and +`data_schema.version` to `1`. Consumers should tolerate unknown fields and +values. Removing or renaming fields, changing their type, or changing their +meaning requires a new schema version. + +| Mark | Data fields | +| --- | --- | +| `switchyard.routing.requested` | `algorithm` | +| `switchyard.routing.llm_call` | `call_index`, `selected_model`, `call_role`, `outcome`, `latency_ms` | +| `switchyard.routing.overhead` | `latency_ms` | +| `switchyard.routing.decision` | `algorithm`, `selected_model` | +| `switchyard.routing.error` | `failure_kind`; optional `category`, `phase`, `upstream_status`, and `target` | + ## Failure policy `switchyard-llm-client` owns provider retry and route-candidate fallback diff --git a/crates/switchyard-nemo-relay-plugin/src/runtime.rs b/crates/switchyard-nemo-relay-plugin/src/runtime.rs index 7b5a12fb9..136ec2a88 100644 --- a/crates/switchyard-nemo-relay-plugin/src/runtime.rs +++ b/crates/switchyard-nemo-relay-plugin/src/runtime.rs @@ -7,8 +7,8 @@ use std::sync::{Arc, Mutex}; use futures_util::{Stream, StreamExt}; use http::StatusCode; use nemo_relay_plugin::{ - Json, LlmRequest as RelayRequest, LogSeverity, MetricKind, MetricMeasurement, MetricValueType, - PluginRuntime, + DataSchema, Json, LlmRequest as RelayRequest, LogSeverity, MetricKind, MetricMeasurement, + MetricValueType, PluginRuntime, }; use serde_json::{Map, json}; use switchyard_llm_client::{LlmCallObservation, RunObservation, RunObserver}; @@ -22,6 +22,8 @@ use switchyard_translation::{TranslationEngine, encode_stream_with_extensions}; use crate::config::SwitchyardConfig; use crate::translation; +const ROUTING_MARK_SCHEMA_VERSION: &str = "1"; + #[derive(Debug)] pub(crate) struct RoutingMark { pub(crate) name: String, @@ -30,6 +32,15 @@ pub(crate) struct RoutingMark { pub(crate) severity: Option, } +impl RoutingMark { + fn data_schema(&self) -> DataSchema { + DataSchema { + name: self.name.clone(), + version: ROUTING_MARK_SCHEMA_VERSION.into(), + } + } +} + #[derive(Debug)] pub(crate) struct RoutingMetric { pub(crate) name: String, @@ -324,15 +335,18 @@ pub(crate) fn emit_events(runtime: &PluginRuntime, events: Vec) { pub(crate) fn emit_event(runtime: &PluginRuntime, event: RoutingEvent) { let result = match event { - RoutingEvent::Mark(mark) => runtime - .emit_mark_with_options( - &mark.name, - Some(&mark.data), - Some(&mark.metadata), - None, - mark.severity, - ) - .map_err(|error| ("routing mark", mark.name, error)), + RoutingEvent::Mark(mark) => { + let data_schema = mark.data_schema(); + runtime + .emit_mark_with_options( + &mark.name, + Some(&mark.data), + Some(&mark.metadata), + Some(&data_schema), + mark.severity, + ) + .map_err(|error| ("routing mark", mark.name, error)) + } RoutingEvent::Metric(metric) => runtime .emit_metric(&metric.name, metric.measurements, Some(&metric.metadata)) .map_err(|error| ("routing metric", metric.name, error)), @@ -980,6 +994,8 @@ mod tests { assert_eq!(mark.data["target"], "weak"); assert_eq!(mark.data["upstream_status"], Json::Null); assert_eq!(mark.severity, Some(LogSeverity::Error)); + assert_eq!(mark.data_schema().name, "switchyard.routing.error"); + assert_eq!(mark.data_schema().version, "1"); assert!(!mark.data.to_string().contains(secret)); }