perspt_sdk/prompt/
accountant.rs1use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum AccountingMode {
18 ExactTokenizer,
19 ConservativeUpperBound,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct TokenAccountantRef {
25 pub id: String,
27 pub mode: AccountingMode,
28}
29
30const MESSAGE_FRAMING_TOKENS: u64 = 8;
33
34impl TokenAccountantRef {
35 pub fn approx_bytes_v1() -> Self {
38 Self {
39 id: "approx_bytes_v1".into(),
40 mode: AccountingMode::ConservativeUpperBound,
41 }
42 }
43
44 pub fn count_text(&self, text: &str) -> u64 {
46 (text.len() as u64).div_ceil(3)
47 }
48
49 pub fn count_message(&self, content: &str) -> u64 {
51 self.count_text(content) + MESSAGE_FRAMING_TOKENS
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn counting_is_a_conservative_upper_bound() {
61 let accountant = TokenAccountantRef::approx_bytes_v1();
62 assert_eq!(accountant.count_text(""), 0);
63 assert_eq!(accountant.count_text("abc"), 1);
64 assert_eq!(accountant.count_text("abcd"), 2);
65 let text = "let value = compute(input); // typical code density";
67 assert!(accountant.count_text(text) * 4 > text.len() as u64);
68 assert_eq!(accountant.count_message("abc"), 1 + MESSAGE_FRAMING_TOKENS);
69 }
70}