use crate::llm::capabilities::{
Capabilities, DecisionLimits, DecisionProtocol, DecisionQuestionKind, StructuredOutputStrategy,
};
use crate::llm_config::{self, ModelOperation};
#[derive(Debug, Clone, PartialEq)]
pub struct DecisionContract {
pub protocol: DecisionProtocol,
pub structured_output_strategy: Option<StructuredOutputStrategy>,
pub question_kinds: Vec<DecisionQuestionKind>,
pub limits: Option<DecisionLimits>,
pub input_price_per_mtok: Option<f64>,
pub output_price_per_mtok: Option<f64>,
pub served_model_id: String,
}
pub fn decision_contract_for_route(provider: &str, model: &str) -> Option<DecisionContract> {
let catalog_id = llm_config::model_catalog_id_for_route(provider, model)?;
let entry = llm_config::model_catalog_entry(&catalog_id)?;
let caps = crate::llm::capabilities::lookup(provider, model);
resolved_decision_contract(&catalog_id, &entry, &caps)
}
pub(super) fn resolved_decision_contract(
catalog_id: &str,
entry: &llm_config::ModelDef,
caps: &Capabilities,
) -> Option<DecisionContract> {
let (protocol, question_kinds, limits) = match caps.decision_protocol {
Some(protocol) if protocol.is_native() => {
if !entry.supports_operation(ModelOperation::Decision)
|| caps.decision_question_kinds.is_empty()
|| caps.decision_limits.is_none()
{
return None;
}
(
protocol,
caps.decision_question_kinds.clone(),
caps.decision_limits,
)
}
_ => {
if !entry.supports_operation(ModelOperation::TextGeneration)
|| caps.structured_output_strategy == StructuredOutputStrategy::Unsupported
{
return None;
}
(
DecisionProtocol::StructuredLlm,
vec![
DecisionQuestionKind::Boolean,
DecisionQuestionKind::Choice,
DecisionQuestionKind::Score,
],
None,
)
}
};
let pricing = entry.pricing.as_ref();
Some(DecisionContract {
protocol,
structured_output_strategy: (!protocol.is_native())
.then_some(caps.structured_output_strategy),
question_kinds,
limits,
input_price_per_mtok: pricing.map(|pricing| pricing.input_per_mtok),
output_price_per_mtok: pricing.map(|pricing| pricing.output_per_mtok),
served_model_id: entry
.wire_model
.clone()
.unwrap_or_else(|| catalog_id.to_string()),
})
}
pub(super) fn resolved_operations(
catalog_id: &str,
entry: &llm_config::ModelDef,
caps: &Capabilities,
) -> Vec<ModelOperation> {
let mut operations = entry.normalized_operations();
operations.retain(|operation| *operation != ModelOperation::Decision);
if resolved_decision_contract(catalog_id, entry, caps).is_some() {
operations.push(ModelOperation::Decision);
}
operations
}