1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//! LLM routing: model selection strategies, cost metrics, and RoRF.
//!
//! The router selects which LLM to call for a given query from a configured
//! pool of models. Five strategies are provided:
//!
//! - [`strategies::RegexStrategy`] — rule-based routing by content patterns
//! - [`strategies::RoundRobin`] — even load distribution (thread-safe)
//! - [`strategies::WeightedRandom`] — probabilistic selection by weight
//! - [`strategies::EmbeddingThreshold`] — complexity routing via embedding centroids
//! - [`strategies::RoRFStrategy`] — learned routing via a trained Random Forest
//!
//! # RoRF
//!
//! RoRF trains a binary Random Forest classifier on `(embedding, label)` pairs
//! where label 0 = model_a preferred and label 1 = model_b preferred. At
//! inference time it embeds the query, runs it through the forest, and routes
//! to model_a when `P(model_a) ≥ threshold`.
//!
//! ```rust,no_run
//! use fornix::router::{
//! strategies::{RoRFStrategy, RoutingStrategy},
//! forest::ForestParams,
//! };
//!
//! // Training data: embeddings + labels (0 = use strong model, 1 = use weak model)
//! let features: Vec<Vec<f32>> = vec![vec![0.1], vec![0.9]];
//! let labels: Vec<u8> = vec![0, 1];
//!
//! let router = RoRFStrategy::train(
//! &features,
//! &labels,
//! 0.5, // threshold
//! "claude-opus-4", // model_a (strong)
//! "anthropic",
//! "claude-sonnet-4", // model_b (weak)
//! "anthropic",
//! ForestParams::default(),
//! ).unwrap();
//!
//! let decision = router.route("explain Rust lifetimes", Some(&[0.15]), &[]).unwrap();
//! assert_eq!(decision.model, "claude-opus-4");
//! ```
//!
//! # Cost metrics
//!
//! [`metrics::MetricsCalculator`] estimates per-request cost by tier (1–5)
//! and [`metrics::MetricsCollector`] records decisions for aggregate reporting.
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;