use sha2::{Digest, Sha256};
use crate::runtime::ai::provider_capabilities::Capabilities;
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Overrides {
pub temperature: Option<f32>,
pub seed: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
pub default_temperature: f32,
}
impl Default for Settings {
fn default() -> Self {
Self {
default_temperature: 0.0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Inputs<'a> {
pub question: &'a str,
pub sources_fingerprint: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Applied {
pub temperature: Option<f32>,
pub seed: Option<u64>,
}
pub fn decide(
inputs: Inputs<'_>,
caps: Capabilities,
overrides: Overrides,
settings: Settings,
) -> Applied {
let temperature = resolve_temperature(caps, overrides.temperature, settings.default_temperature);
let seed = resolve_seed(caps, overrides.seed, inputs);
Applied { temperature, seed }
}
fn resolve_temperature(caps: Capabilities, override_t: Option<f32>, default_t: f32) -> Option<f32> {
if !caps.supports_temperature_zero {
return None;
}
Some(override_t.unwrap_or(default_t))
}
fn resolve_seed(caps: Capabilities, override_s: Option<u64>, inputs: Inputs<'_>) -> Option<u64> {
if !caps.supports_seed {
return None;
}
if let Some(s) = override_s {
return Some(s);
}
Some(derive_seed(inputs.question, inputs.sources_fingerprint))
}
pub fn derive_seed(question: &str, sources_fingerprint: &str) -> u64 {
let mut hasher = Sha256::new();
hasher.update(question.as_bytes());
hasher.update([0x1f]);
hasher.update(sources_fingerprint.as_bytes());
let digest = hasher.finalize();
let mut buf = [0u8; 8];
buf.copy_from_slice(&digest[..8]);
u64::from_le_bytes(buf)
}
#[cfg(test)]
mod tests {
use super::*;
fn full_caps() -> Capabilities {
Capabilities {
supports_citations: true,
supports_seed: true,
supports_temperature_zero: true,
supports_streaming: true,
}
}
fn no_seed_caps() -> Capabilities {
Capabilities {
supports_citations: true,
supports_seed: false,
supports_temperature_zero: true,
supports_streaming: true,
}
}
fn no_temp_caps() -> Capabilities {
Capabilities {
supports_citations: false,
supports_seed: false,
supports_temperature_zero: false,
supports_streaming: false,
}
}
fn inputs() -> Inputs<'static> {
Inputs {
question: "what is the meaning of life?",
sources_fingerprint: "abc123",
}
}
#[test]
fn default_temperature_is_zero() {
let out = decide(
inputs(),
full_caps(),
Overrides::default(),
Settings::default(),
);
assert_eq!(out.temperature, Some(0.0));
}
#[test]
fn default_seed_is_derived_from_question_and_fingerprint() {
let out = decide(
inputs(),
full_caps(),
Overrides::default(),
Settings::default(),
);
let expected = derive_seed(inputs().question, inputs().sources_fingerprint);
assert_eq!(out.seed, Some(expected));
}
#[test]
fn temperature_override_wins_over_default() {
let out = decide(
inputs(),
full_caps(),
Overrides {
temperature: Some(0.7),
seed: None,
},
Settings::default(),
);
assert_eq!(out.temperature, Some(0.7));
}
#[test]
fn seed_override_wins_over_derivation() {
let out = decide(
inputs(),
full_caps(),
Overrides {
temperature: None,
seed: Some(42),
},
Settings::default(),
);
assert_eq!(out.seed, Some(42));
}
#[test]
fn settings_default_temperature_honored_when_no_override() {
let out = decide(
inputs(),
full_caps(),
Overrides::default(),
Settings {
default_temperature: 0.3,
},
);
assert_eq!(out.temperature, Some(0.3));
}
#[test]
fn override_temperature_beats_settings_default() {
let out = decide(
inputs(),
full_caps(),
Overrides {
temperature: Some(0.9),
seed: None,
},
Settings {
default_temperature: 0.3,
},
);
assert_eq!(out.temperature, Some(0.9));
}
#[test]
fn no_seed_capability_drops_derived_seed() {
let out = decide(
inputs(),
no_seed_caps(),
Overrides::default(),
Settings::default(),
);
assert_eq!(out.seed, None);
assert_eq!(out.temperature, Some(0.0));
}
#[test]
fn no_seed_capability_drops_override_seed_too() {
let out = decide(
inputs(),
no_seed_caps(),
Overrides {
temperature: None,
seed: Some(42),
},
Settings::default(),
);
assert_eq!(out.seed, None);
}
#[test]
fn no_temperature_capability_drops_temperature() {
let out = decide(
inputs(),
no_temp_caps(),
Overrides::default(),
Settings::default(),
);
assert_eq!(out.temperature, None);
assert_eq!(out.seed, None);
}
#[test]
fn no_temperature_capability_drops_override_temperature() {
let out = decide(
inputs(),
no_temp_caps(),
Overrides {
temperature: Some(0.7),
seed: None,
},
Settings::default(),
);
assert_eq!(out.temperature, None);
}
#[test]
fn conservative_capabilities_drop_seed_keep_temperature() {
let out = decide(
inputs(),
Capabilities::conservative(),
Overrides::default(),
Settings::default(),
);
assert_eq!(out.temperature, Some(0.0));
assert_eq!(out.seed, None);
}
#[test]
fn derive_seed_is_deterministic_across_calls() {
let a = derive_seed("question", "fp");
let b = derive_seed("question", "fp");
assert_eq!(a, b);
}
#[test]
fn derive_seed_differs_on_question_change() {
let a = derive_seed("question A", "fp");
let b = derive_seed("question B", "fp");
assert_ne!(a, b);
}
#[test]
fn derive_seed_differs_on_fingerprint_change() {
let a = derive_seed("question", "fp1");
let b = derive_seed("question", "fp2");
assert_ne!(a, b);
}
#[test]
fn derive_seed_is_injective_across_field_boundary() {
let a = derive_seed("ab", "c");
let b = derive_seed("a", "bc");
assert_ne!(a, b);
}
#[test]
fn decide_is_deterministic_across_calls() {
let a = decide(
inputs(),
full_caps(),
Overrides::default(),
Settings::default(),
);
let b = decide(
inputs(),
full_caps(),
Overrides::default(),
Settings::default(),
);
assert_eq!(a, b);
}
#[test]
fn applied_carries_both_knobs_when_provider_supports_both() {
let out = decide(
inputs(),
full_caps(),
Overrides::default(),
Settings::default(),
);
assert!(out.temperature.is_some());
assert!(out.seed.is_some());
}
#[test]
fn override_zero_temperature_is_preserved_not_treated_as_missing() {
let out = decide(
inputs(),
full_caps(),
Overrides {
temperature: Some(0.0),
seed: None,
},
Settings {
default_temperature: 0.5,
},
);
assert_eq!(out.temperature, Some(0.0));
}
#[test]
fn override_zero_seed_is_preserved() {
let out = decide(
inputs(),
full_caps(),
Overrides {
temperature: None,
seed: Some(0),
},
Settings::default(),
);
assert_eq!(out.seed, Some(0));
}
}