perspt_sdk/prompt/
dialect.rs1use serde::{Deserialize, Serialize};
9
10use super::accountant::TokenAccountantRef;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum SystemSlotPolicy {
16 Many,
18 SingleConcatenated,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum ToolCallConvention {
26 Native,
28 JsonInText,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum ReasoningTracePolicy {
36 None,
37 Requested,
38 Stripped,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub struct DialectRef {
44 pub id: String,
45 pub version: u32,
46}
47
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct ModelDialect {
51 pub id: String,
52 pub version: u32,
53 pub system_slots: SystemSlotPolicy,
54 pub tool_calls: ToolCallConvention,
55 pub reasoning: ReasoningTracePolicy,
56 pub boundary_marker: String,
58 pub context_window_tokens: u64,
59 pub max_output_tokens: u64,
60 pub token_accountant: TokenAccountantRef,
61 pub max_prompt_bytes: u64,
63}
64
65impl ModelDialect {
66 pub fn dialect_ref(&self) -> DialectRef {
67 DialectRef {
68 id: self.id.clone(),
69 version: self.version,
70 }
71 }
72
73 pub fn native_tools_v1() -> Self {
75 Self {
76 id: "native_tools_v1".into(),
77 version: 1,
78 system_slots: SystemSlotPolicy::Many,
79 tool_calls: ToolCallConvention::Native,
80 reasoning: ReasoningTracePolicy::Stripped,
81 boundary_marker: String::new(),
82 context_window_tokens: 131_072,
83 max_output_tokens: 16_384,
84 token_accountant: TokenAccountantRef::approx_bytes_v1(),
85 max_prompt_bytes: 262_144,
86 }
87 }
88
89 pub fn genai_single_slot_v1() -> Self {
94 Self {
95 id: "genai_single_slot_v1".into(),
96 version: 1,
97 system_slots: SystemSlotPolicy::SingleConcatenated,
98 tool_calls: ToolCallConvention::Native,
99 reasoning: ReasoningTracePolicy::Stripped,
100 boundary_marker: String::new(),
101 context_window_tokens: 131_072,
102 max_output_tokens: 16_384,
103 token_accountant: TokenAccountantRef::approx_bytes_v1(),
104 max_prompt_bytes: 262_144,
105 }
106 }
107
108 pub fn with_context_window(mut self, tokens: u64) -> Self {
112 self.context_window_tokens = tokens.max(1);
113 self
114 }
115
116 pub fn json_single_slot_v1() -> Self {
119 Self {
120 id: "json_single_slot_v1".into(),
121 version: 1,
122 system_slots: SystemSlotPolicy::SingleConcatenated,
123 tool_calls: ToolCallConvention::JsonInText,
124 reasoning: ReasoningTracePolicy::None,
125 boundary_marker: "\n===== perspt:boundary =====\n".into(),
126 context_window_tokens: 65_536,
127 max_output_tokens: 8_192,
128 token_accountant: TokenAccountantRef::approx_bytes_v1(),
129 max_prompt_bytes: 131_072,
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn the_two_contrasting_dialects_differ_structurally() {
140 let native = ModelDialect::native_tools_v1();
141 let json = ModelDialect::json_single_slot_v1();
142 assert_ne!(native.system_slots, json.system_slots);
143 assert_ne!(native.tool_calls, json.tool_calls);
144 assert!(json.boundary_marker.contains("perspt:boundary"));
145 assert_eq!(native.dialect_ref().id, "native_tools_v1");
146 }
147}