Skip to main content

tokenmiser_router/
policy.rs

1//! Maps a `Difficulty` to a concrete (provider, model) target.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use crate::Difficulty;
7
8/// A provider name and the model id to send it.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct RoutingTarget {
11    pub provider: String,
12    pub model: String,
13}
14
15impl RoutingTarget {
16    /// Pass the requested model through with an empty provider, leaving
17    /// resolution to the registry.
18    pub fn passthrough(model: &str) -> Self {
19        Self {
20            provider: String::new(),
21            model: model.to_string(),
22        }
23    }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct RoutingPolicy {
28    pub tiers: HashMap<Difficulty, RoutingTarget>,
29    /// Frontier model used for counterfactual savings accounting.
30    pub frontier_model: String,
31}
32
33impl Default for RoutingPolicy {
34    fn default() -> Self {
35        let mut tiers = HashMap::new();
36        tiers.insert(
37            Difficulty::Easy,
38            RoutingTarget {
39                provider: "ollama".into(),
40                // Overridable via the YAML policy; startup auto-detection may
41                // replace this with a model that is actually installed.
42                model: "ollama:qwen2.5:7b".into(),
43            },
44        );
45        tiers.insert(
46            Difficulty::Medium,
47            RoutingTarget {
48                provider: "anthropic".into(),
49                model: "claude-haiku-4-5".into(),
50            },
51        );
52        tiers.insert(
53            Difficulty::Hard,
54            RoutingTarget {
55                provider: "anthropic".into(),
56                model: "claude-opus-4-7".into(),
57            },
58        );
59        Self {
60            tiers,
61            frontier_model: "claude-opus-4-7".into(),
62        }
63    }
64}
65
66impl RoutingPolicy {
67    pub fn choose(&self, d: Difficulty) -> RoutingTarget {
68        self.tiers
69            .get(&d)
70            .cloned()
71            .unwrap_or_else(|| RoutingTarget {
72                provider: "anthropic".into(),
73                model: self.frontier_model.clone(),
74            })
75    }
76
77    pub fn frontier_for(&self, _d: Difficulty) -> Option<String> {
78        Some(self.frontier_model.clone())
79    }
80}