use serde::{Deserialize, Serialize};
use super::accountant::TokenAccountantRef;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SystemSlotPolicy {
Many,
SingleConcatenated,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolCallConvention {
Native,
JsonInText,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReasoningTracePolicy {
None,
Requested,
Stripped,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DialectRef {
pub id: String,
pub version: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ModelDialect {
pub id: String,
pub version: u32,
pub system_slots: SystemSlotPolicy,
pub tool_calls: ToolCallConvention,
pub reasoning: ReasoningTracePolicy,
pub boundary_marker: String,
pub context_window_tokens: u64,
pub max_output_tokens: u64,
pub token_accountant: TokenAccountantRef,
pub max_prompt_bytes: u64,
}
impl ModelDialect {
pub fn dialect_ref(&self) -> DialectRef {
DialectRef {
id: self.id.clone(),
version: self.version,
}
}
pub fn native_tools_v1() -> Self {
Self {
id: "native_tools_v1".into(),
version: 1,
system_slots: SystemSlotPolicy::Many,
tool_calls: ToolCallConvention::Native,
reasoning: ReasoningTracePolicy::Stripped,
boundary_marker: String::new(),
context_window_tokens: 131_072,
max_output_tokens: 16_384,
token_accountant: TokenAccountantRef::approx_bytes_v1(),
max_prompt_bytes: 262_144,
}
}
pub fn genai_single_slot_v1() -> Self {
Self {
id: "genai_single_slot_v1".into(),
version: 1,
system_slots: SystemSlotPolicy::SingleConcatenated,
tool_calls: ToolCallConvention::Native,
reasoning: ReasoningTracePolicy::Stripped,
boundary_marker: String::new(),
context_window_tokens: 131_072,
max_output_tokens: 16_384,
token_accountant: TokenAccountantRef::approx_bytes_v1(),
max_prompt_bytes: 262_144,
}
}
pub fn with_context_window(mut self, tokens: u64) -> Self {
self.context_window_tokens = tokens.max(1);
self
}
pub fn json_single_slot_v1() -> Self {
Self {
id: "json_single_slot_v1".into(),
version: 1,
system_slots: SystemSlotPolicy::SingleConcatenated,
tool_calls: ToolCallConvention::JsonInText,
reasoning: ReasoningTracePolicy::None,
boundary_marker: "\n===== perspt:boundary =====\n".into(),
context_window_tokens: 65_536,
max_output_tokens: 8_192,
token_accountant: TokenAccountantRef::approx_bytes_v1(),
max_prompt_bytes: 131_072,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_two_contrasting_dialects_differ_structurally() {
let native = ModelDialect::native_tools_v1();
let json = ModelDialect::json_single_slot_v1();
assert_ne!(native.system_slots, json.system_slots);
assert_ne!(native.tool_calls, json.tool_calls);
assert!(json.boundary_marker.contains("perspt:boundary"));
assert_eq!(native.dialect_ref().id, "native_tools_v1");
}
}