1use std::hash::{DefaultHasher, Hash, Hasher};
8use std::io;
9use std::sync::Arc;
10
11use rig_core::completion::{Message, ToolDefinition, Usage};
12use rig_core::conversation::{Conversation, MessageId};
13use rig_core::wasm_compat::{WasmCompatSend, WasmCompatSync};
14use serde::{Deserialize, Serialize};
15
16pub trait TokenCounter: WasmCompatSend + WasmCompatSync {
35 fn count(&self, message: &Message) -> usize;
37}
38
39impl<F> TokenCounter for F
40where
41 F: Fn(&Message) -> usize + WasmCompatSend + WasmCompatSync,
42{
43 fn count(&self, message: &Message) -> usize {
44 (self)(message)
45 }
46}
47
48impl<C: TokenCounter + ?Sized> TokenCounter for Arc<C> {
49 fn count(&self, message: &Message) -> usize {
50 (**self).count(message)
51 }
52}
53
54impl TokenCounter for Box<dyn TokenCounter> {
55 fn count(&self, message: &Message) -> usize {
56 (**self).count(message)
57 }
58}
59
60impl TokenCounter for &dyn TokenCounter {
61 fn count(&self, message: &Message) -> usize {
62 (**self).count(message)
63 }
64}
65
66#[derive(Debug, Clone, Copy)]
78pub struct HeuristicTokenCounter {
79 bytes_per_token: f32,
80 per_message_overhead: usize,
81 per_attachment_tokens: usize,
82}
83
84impl HeuristicTokenCounter {
85 pub fn new(
90 bytes_per_token: f32,
91 per_message_overhead: usize,
92 per_attachment_tokens: usize,
93 ) -> Self {
94 let bytes_per_token = if bytes_per_token.is_finite() && bytes_per_token >= 1.0 {
95 bytes_per_token
96 } else {
97 1.0
98 };
99 Self {
100 bytes_per_token,
101 per_message_overhead,
102 per_attachment_tokens,
103 }
104 }
105
106 pub fn anthropic() -> Self {
109 Self::new(3.5, 4, ATTACHMENT_TOKENS)
110 }
111
112 fn bytes_to_tokens(&self, bytes: usize) -> usize {
113 let tokens = (bytes as f32) / self.bytes_per_token;
117 tokens.ceil() as usize
118 }
119
120 pub fn count_tool(&self, tool: &ToolDefinition) -> usize {
123 let bytes = tool.name.len().saturating_add(tool.description.len());
124 self.bytes_to_tokens(bytes.saturating_add(json_len(&tool.parameters)))
125 .saturating_add(self.per_message_overhead)
126 }
127
128 pub(crate) fn count_user(&self, content: &rig_core::message::UserContent) -> usize {
131 use rig_core::message::UserContent;
132 match content {
133 UserContent::Text(text) => self.bytes_to_tokens(text.text.len()),
134 UserContent::ToolResult(result) => result
135 .content
136 .iter()
137 .map(|c| match c {
138 rig_core::message::ToolResultContent::Text(t) => {
139 self.bytes_to_tokens(t.text.len())
140 }
141 rig_core::message::ToolResultContent::Json { value } => {
142 self.bytes_to_tokens(json_len(value))
143 }
144 rig_core::message::ToolResultContent::Image(_) => self.per_attachment_tokens,
145 })
146 .fold(0, usize::saturating_add),
147 UserContent::Image(_)
148 | UserContent::Audio(_)
149 | UserContent::Video(_)
150 | UserContent::Document(_) => self.per_attachment_tokens,
151 }
152 }
153
154 fn count_assistant(&self, content: &rig_core::message::AssistantContent) -> usize {
157 use rig_core::message::AssistantContent;
158 match content {
159 AssistantContent::Text(text) => self.bytes_to_tokens(text.text.len()),
160 AssistantContent::Reasoning(reasoning) => self.bytes_to_tokens(reasoning.text.len()),
161 AssistantContent::ToolCall(call) => {
162 let name_bytes = call.function.name.len();
163 let args_bytes = json_len(&call.function.arguments);
164 self.bytes_to_tokens(name_bytes + args_bytes)
165 }
166 AssistantContent::Image(_) => self.per_attachment_tokens,
167 AssistantContent::Opaque(opaque) => self.bytes_to_tokens(json_len(&opaque.item)),
169 other => self.bytes_to_tokens(json_len(other)),
170 }
171 }
172}
173
174fn json_len(value: &impl serde::Serialize) -> usize {
177 struct Len(usize);
178 impl io::Write for Len {
179 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
180 self.0 += bytes.len();
181 Ok(bytes.len())
182 }
183
184 fn flush(&mut self) -> io::Result<()> {
185 Ok(())
186 }
187 }
188 let mut len = Len(0);
189 serde_json::to_writer(&mut len, value).map_or(0, |()| len.0)
190}
191
192pub const ATTACHMENT_TOKENS: usize = 1_200;
194
195impl Default for HeuristicTokenCounter {
196 fn default() -> Self {
199 Self::new(4.0, 4, ATTACHMENT_TOKENS)
200 }
201}
202
203impl TokenCounter for HeuristicTokenCounter {
204 fn count(&self, message: &Message) -> usize {
205 let content_tokens: usize = match message {
206 Message::User { content } => content
207 .iter()
208 .map(|c| self.count_user(c))
209 .fold(0, usize::saturating_add),
210 Message::Assistant(turn) => turn
211 .content
212 .iter()
213 .map(|c| self.count_assistant(c))
214 .fold(0, usize::saturating_add),
215 Message::System { content } => self.bytes_to_tokens(content.len()),
216 };
217 content_tokens.saturating_add(self.per_message_overhead)
218 }
219}
220
221#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
226pub struct Anchor {
227 pub usage: Usage,
230 pub through: MessageId,
232}
233
234#[derive(Clone, Copy, Debug)]
236pub struct Prepared<'a> {
237 pub model: &'a str,
239 pub prompt: Option<&'a str>,
241 pub tools: &'a [Arc<ToolDefinition>],
243 pub history: &'a Conversation,
245}
246
247#[derive(Clone, Copy, Debug, PartialEq, Eq)]
254pub struct RequestStart {
255 fingerprint: u64,
256 messages: usize,
257}
258
259impl Prepared<'_> {
260 pub fn start(&self) -> RequestStart {
262 let messages = self.history.messages().len();
263 RequestStart {
264 fingerprint: self.fingerprint(messages),
265 messages,
266 }
267 }
268
269 fn fingerprint(&self, messages: usize) -> u64 {
272 let mut hasher = DefaultHasher::new();
273 (self.model, self.prompt).hash(&mut hasher);
274 for tool in self.tools {
275 let mut bytes = HashWriter(&mut hasher);
276 let _ = serde_json::to_writer(&mut bytes, &**tool);
277 }
278 for entry in self.history.entries().take(messages) {
279 (entry.key(), std::ptr::from_ref(entry.message).addr()).hash(&mut hasher);
280 }
281 hasher.finish()
282 }
283
284 pub fn whole_tokens(&self) -> u64 {
287 let counter = HeuristicTokenCounter::default();
288 let prompt = self
289 .prompt
290 .map_or(0, |prompt| counter.count(&Message::system(prompt)));
291 let tools = self.tools.iter().map(|tool| counter.count_tool(tool));
292 let tools = tools.fold(0, usize::saturating_add);
293 let messages = estimate_messages(self.history.messages());
294 (prompt.saturating_add(tools) as u64).saturating_add(messages)
295 }
296}
297
298struct HashWriter<'h>(&'h mut DefaultHasher);
300
301impl io::Write for HashWriter<'_> {
302 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
303 self.0.write(bytes);
304 Ok(bytes.len())
305 }
306
307 fn flush(&mut self) -> io::Result<()> {
308 Ok(())
309 }
310}
311
312fn estimate_messages(messages: &[Arc<Message>]) -> u64 {
315 let counter = HeuristicTokenCounter::default();
316 messages
317 .iter()
318 .map(|message| counter.count(message) as u64)
319 .fold(0, u64::saturating_add)
320}
321
322pub fn next_request_tokens(
332 anchor: Option<&Anchor>,
333 sent: Option<RequestStart>,
334 request: &Prepared<'_>,
335) -> u64 {
336 let anchored = anchor.zip(sent).and_then(|(anchor, sent)| {
337 let at = request.history.index_of(anchor.through)?;
338 let follows = at == sent.messages || at.saturating_add(1) == sent.messages;
339 let same = follows && request.fingerprint(sent.messages) == sent.fingerprint;
340 let reported = anchor.usage.context_tokens().filter(|_| same)?;
341 let after = request.history.messages().get(at.saturating_add(1)..);
342 Some(reported.saturating_add(estimate_messages(after.unwrap_or_default())))
343 });
344 anchored.unwrap_or_else(|| request.whole_tokens())
345}
346
347pub const OUTPUT_MARGIN: u64 = 4_096;
350
351pub fn output_room(window: Option<u64>, most: Option<u64>, estimate: u64) -> Option<u64> {
358 let room = window.map(|window| window.saturating_sub(estimate.saturating_add(OUTPUT_MARGIN)));
359 match (room, most) {
360 (Some(room), Some(most)) => Some(room.min(most)),
361 (room, most) => room.or(most),
362 }
363}
364
365#[cfg(test)]
366mod tests;