bitrouter 1.0.0-alpha.32

BitRouter: an LLM API router. CLI + assembly library.
Documentation
use std::path::Path;

use bitrouter_sdk::config::AdequacyConfig;
use bitrouter_sdk::{BitrouterError, Result};
use serde::Serialize;
use sha2::{Digest, Sha256};

use super::reliability::{
    ProviderReliabilityLedger, ReliabilityKey, ReliabilityObservation, ReliabilitySnapshot,
};
use super::store::PersistedReliabilityEvent;

const REPORT_SCHEMA_VERSION: u32 = 1;

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ReliabilityReportConfig {
    pub window_size: u32,
    pub consecutive_failure_threshold: u32,
    pub error_rate_percent: u32,
    pub cooldown_secs: u64,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ReliabilityReportEvent {
    pub sequence: i64,
    pub request_id: String,
    pub route_key: String,
    pub endpoint: ReliabilityKey,
    pub observation: ReliabilityObservation,
    pub half_open_probe: bool,
    pub observed_at_unix: u64,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ReliabilityRouteReport {
    pub route_key: String,
    pub snapshot: ReliabilitySnapshot,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ReliabilityEndpointReport {
    pub endpoint: ReliabilityKey,
    pub snapshot: ReliabilitySnapshot,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ReliabilityReport {
    pub schema_version: u32,
    pub event_count: usize,
    pub event_sha256: String,
    pub config: ReliabilityReportConfig,
    pub routes: Vec<ReliabilityRouteReport>,
    pub endpoints: Vec<ReliabilityEndpointReport>,
    pub events: Vec<ReliabilityReportEvent>,
}

impl ReliabilityReport {
    pub fn build(config: &AdequacyConfig, rows: &[PersistedReliabilityEvent]) -> Result<Self> {
        let mut ordered = rows.to_vec();
        ordered.sort_by_key(|row| row.sequence);
        if ordered
            .windows(2)
            .any(|pair| pair[0].sequence == pair[1].sequence)
        {
            return Err(BitrouterError::bad_request(
                "reliability event sequence must be unique",
            ));
        }
        let events = ordered
            .iter()
            .map(|row| ReliabilityReportEvent {
                sequence: row.sequence,
                request_id: row.event.request_id.clone(),
                route_key: row.event.route_key.clone(),
                endpoint: row.event.endpoint_key.clone(),
                observation: row.event.observation,
                half_open_probe: row.event.half_open_probe,
                observed_at_unix: row.event.observed_at_unix,
            })
            .collect::<Vec<_>>();
        let canonical_events = serde_json::to_vec(&events).map_err(|error| {
            BitrouterError::internal(format!("serialize reliability events: {error}"))
        })?;
        let event_sha256 = format!("sha256:{}", hex::encode(Sha256::digest(&canonical_events)));
        let replay_events = ordered
            .into_iter()
            .map(|persisted| persisted.event)
            .collect::<Vec<_>>();
        let ledger = ProviderReliabilityLedger::replay(
            config.reliability_window_size as usize,
            config.reliability_consecutive_failures,
            config.reliability_error_rate_percent,
            config.reliability_cooldown_secs,
            &replay_events,
        )?;
        let snapshots = ledger.snapshots();
        let routes = snapshots
            .routes
            .into_iter()
            .map(|(route_key, snapshot)| ReliabilityRouteReport {
                route_key,
                snapshot,
            })
            .collect();
        let endpoints = snapshots
            .endpoints
            .into_iter()
            .map(|(endpoint, snapshot)| ReliabilityEndpointReport { endpoint, snapshot })
            .collect();
        Ok(Self {
            schema_version: REPORT_SCHEMA_VERSION,
            event_count: events.len(),
            event_sha256,
            config: ReliabilityReportConfig {
                window_size: config.reliability_window_size,
                consecutive_failure_threshold: config.reliability_consecutive_failures,
                error_rate_percent: config.reliability_error_rate_percent,
                cooldown_secs: config.reliability_cooldown_secs,
            },
            routes,
            endpoints,
            events,
        })
    }

    pub fn to_pretty_json(&self) -> Result<String> {
        let mut json = serde_json::to_string_pretty(self).map_err(|error| {
            BitrouterError::internal(format!("serialize reliability report: {error}"))
        })?;
        json.push('\n');
        Ok(json)
    }

    pub fn write(&self, path: &Path) -> Result<()> {
        let json = self.to_pretty_json()?;
        std::fs::write(path, json).map_err(|error| {
            BitrouterError::internal(format!(
                "write reliability report {}: {error}",
                path.display()
            ))
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::adequacy::reliability::{ReliabilityEvent, ReliabilityKey, ReliabilityObservation};
    use crate::adequacy::store::PersistedReliabilityEvent;

    fn persisted_event(
        sequence: i64,
        request_id: &str,
        route_key: &str,
        provider: &str,
        observation: ReliabilityObservation,
    ) -> PersistedReliabilityEvent {
        PersistedReliabilityEvent {
            sequence,
            event: ReliabilityEvent {
                request_id: request_id.to_string(),
                route_key: route_key.to_string(),
                endpoint_key: ReliabilityKey {
                    provider: provider.to_string(),
                    model: "economy-model".to_string(),
                    credential_class: "default:oauth".to_string(),
                    endpoint_scope: "api.example.test:443".to_string(),
                    protocol: "responses".to_string(),
                },
                observation,
                half_open_probe: false,
                observed_at_unix: 100 + sequence as u64,
            },
        }
    }

    #[test]
    fn reliability_report_is_deterministic_sorted_and_content_free() {
        let config = AdequacyConfig {
            reliability_window_size: 23,
            reliability_consecutive_failures: 2,
            reliability_error_rate_percent: 35,
            reliability_cooldown_secs: 300,
            ..Default::default()
        };
        let rows = vec![
            persisted_event(
                2,
                "request-2",
                "z-provider:economy-model",
                "z-provider",
                ReliabilityObservation::Success,
            ),
            persisted_event(
                1,
                "request-1",
                "a-provider:economy-model",
                "a-provider",
                ReliabilityObservation::TransientFailure,
            ),
        ];

        let first = ReliabilityReport::build(&config, &rows).unwrap();
        let mut reversed = rows.clone();
        reversed.reverse();
        let second = ReliabilityReport::build(&config, &reversed).unwrap();
        let first_json = first.to_pretty_json().unwrap();
        let second_json = second.to_pretty_json().unwrap();

        assert_eq!(first_json, second_json);
        assert_eq!(first.event_sha256, second.event_sha256);
        assert_eq!(first.events[0].sequence, 1);
        assert_eq!(first.routes[0].route_key, "a-provider:economy-model");
        assert_eq!(first.endpoints[0].endpoint.provider, "a-provider");
        assert!(first.event_sha256.starts_with("sha256:"));
        assert!(first_json.ends_with('\n'));
        for forbidden in [
            "\"api_key\":",
            "\"prompt\":",
            "\"response\":",
            "\"command\":",
        ] {
            assert!(!first_json.contains(forbidden));
        }
    }
}