lc_core/token_counter/
tracker.rs1use std::sync::Arc;
4
5use crate::language_models::LLMResult;
6use crate::{BaseChatModel, RunnableConfig};
7use lc_schema::Message;
8use tokio::sync::Mutex;
9
10use super::counter::{TokenCounter, TrackerTokenUsage};
11use super::tiktoken::TiktokenCounter;
12
13pub struct TokenTrackingLLM<L: BaseChatModel> {
18 llm: L,
19 counter: Arc<dyn TokenCounter>,
20 usage: Arc<Mutex<TrackerTokenUsage>>,
21}
22
23impl<L: BaseChatModel> TokenTrackingLLM<L> {
24 pub fn new(llm: L, counter: Arc<dyn TokenCounter>) -> Self {
25 Self {
26 llm,
27 counter,
28 usage: Arc::new(Mutex::new(TrackerTokenUsage::new())),
29 }
30 }
31
32 pub fn for_openai(llm: L) -> Result<Self, String> {
34 let counter = TiktokenCounter::new()?;
35 Ok(Self::new(llm, Arc::new(counter)))
36 }
37
38 pub async fn chat(
40 &self,
41 messages: Vec<Message>,
42 config: Option<RunnableConfig>,
43 ) -> Result<LLMResult, L::Error> {
44 let estimated_prompt = self.counter.count_messages(&messages);
45 let result = self.llm.chat(messages, config).await?;
46
47 let (prompt, completion) = result
51 .token_usage
52 .as_ref()
53 .map(|u| (u.prompt_tokens, u.completion_tokens))
54 .unwrap_or((
55 estimated_prompt as usize,
56 self.counter.count_tokens(&result.content) as usize,
57 ));
58
59 self.usage.lock().await.add(prompt, completion);
60 Ok(result)
61 }
62
63 pub async fn get_usage(&self) -> TrackerTokenUsage {
65 self.usage.lock().await.clone()
66 }
67
68 pub async fn reset(&self) {
70 self.usage.lock().await.reset();
71 }
72
73 pub async fn estimate_cost(&self, pricing: &ModelPricing) -> f64 {
75 let usage = self.get_usage().await;
76 pricing.calculate(usage.prompt_tokens, usage.completion_tokens)
77 }
78}
79
80pub struct ModelPricing {
82 pub prompt_price_per_1k: f64,
83 pub completion_price_per_1k: f64,
84}
85
86impl ModelPricing {
87 pub fn new(prompt: f64, completion: f64) -> Self {
88 Self {
89 prompt_price_per_1k: prompt,
90 completion_price_per_1k: completion,
91 }
92 }
93
94 pub fn gpt4o_mini() -> Self {
96 Self::new(0.15, 0.60)
97 }
98
99 pub fn gpt4o() -> Self {
101 Self::new(2.50, 10.00)
102 }
103
104 pub fn calculate(&self, prompt: usize, completion: usize) -> f64 {
106 (prompt as f64 / 1000.0) * self.prompt_price_per_1k
107 + (completion as f64 / 1000.0) * self.completion_price_per_1k
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_model_pricing_gpt4o_mini() {
117 let p = ModelPricing::gpt4o_mini();
118 let cost = p.calculate(1000, 1000);
120 assert!((cost - 0.75).abs() < 0.001);
121 }
122
123 #[test]
124 fn test_model_pricing_zero() {
125 let p = ModelPricing::gpt4o_mini();
126 assert_eq!(p.calculate(0, 0), 0.0);
127 }
128
129 #[test]
130 fn test_model_pricing_custom() {
131 let p = ModelPricing::new(1.0, 2.0);
132 let cost = p.calculate(500, 250);
134 assert!((cost - 1.0).abs() < 0.001);
135 }
136
137 }