tiktoken_rs/patched_tiktoken.rs
1use super::vendor_tiktoken::*;
2use anyhow::Result;
3use anyhow::anyhow;
4use fancy_regex::Regex;
5use rustc_hash::FxHashMap as HashMap;
6use std::collections::HashSet;
7
8mod sealed {
9 pub trait Sealed {}
10 impl Sealed for super::Rank {}
11 impl Sealed for usize {}
12 impl Sealed for u64 {}
13 impl Sealed for i64 {}
14}
15
16/// Lossless conversion from [`Rank`] (`u32`) to a wider integer type.
17///
18/// This trait is used by the generic encoding methods
19/// ([`encode_ordinary_as`](CoreBPE::encode_ordinary_as), etc.) so callers
20/// can obtain tokens in whichever integer type their downstream code
21/// expects (e.g. `usize` for indexing, `u64` for ML frameworks).
22///
23/// This trait is sealed and cannot be implemented outside this crate.
24/// Implementations exist for `u32` (identity), `usize`, `u64`, and `i64`.
25pub trait FromRank: sealed::Sealed {
26 fn from_rank(rank: Rank) -> Self;
27}
28
29impl FromRank for Rank {
30 #[inline]
31 fn from_rank(rank: Rank) -> Self {
32 rank
33 }
34}
35
36impl FromRank for usize {
37 #[inline]
38 fn from_rank(rank: Rank) -> Self {
39 rank as usize
40 }
41}
42
43impl FromRank for u64 {
44 #[inline]
45 fn from_rank(rank: Rank) -> Self {
46 u64::from(rank)
47 }
48}
49
50impl FromRank for i64 {
51 #[inline]
52 fn from_rank(rank: Rank) -> Self {
53 i64::from(rank)
54 }
55}
56
57/// Rust API
58impl CoreBPE {
59 // ====================
60 // Encoding
61 // ====================
62
63 // This function a copy of the similar function in python API, but it return
64 // Rust's results and errors
65 pub fn new(
66 encoder: HashMap<Vec<u8>, Rank>,
67 special_tokens_encoder: HashMap<String, Rank>,
68 pattern: &str,
69 ) -> Result<Self> {
70 let regex = Regex::new(pattern)?;
71
72 let special_regex = {
73 let parts = special_tokens_encoder
74 .keys()
75 .map(|s| fancy_regex::escape(s))
76 .collect::<Vec<_>>();
77 Regex::new(&parts.join("|"))?
78 };
79
80 let decoder: HashMap<Rank, Vec<u8>> =
81 encoder.iter().map(|(k, v)| (*v, k.clone())).collect();
82
83 assert!(
84 encoder.len() == decoder.len(),
85 "Encoder and decoder must be of equal length; maybe you had duplicate token indices in your encoder?"
86 );
87
88 let special_tokens_decoder: HashMap<Rank, Vec<u8>> = special_tokens_encoder
89 .iter()
90 .map(|(k, v)| (*v, k.as_bytes().to_vec()))
91 .collect();
92
93 // Clone because I don't know how to tell Rust I'm not going to change the map
94 let mut sorted_token_bytes: Vec<Vec<u8>> = encoder.keys().cloned().collect();
95 sorted_token_bytes.sort();
96
97 Ok(Self {
98 encoder,
99 special_tokens_encoder,
100 decoder,
101 special_tokens_decoder,
102 regex_tls: (0..MAX_NUM_THREADS).map(|_| regex.clone()).collect(),
103 special_regex_tls: (0..MAX_NUM_THREADS)
104 .map(|_| special_regex.clone())
105 .collect(),
106 sorted_token_bytes,
107 })
108 }
109
110 // ====================
111 // Generic encoding
112 // ====================
113
114 /// Like [`encode_ordinary`](CoreBPE::encode_ordinary), but converts each
115 /// token from [`Rank`] (`u32`) into `T`.
116 ///
117 /// This is useful when you need tokens in a different integer type
118 /// (e.g. `usize` for indexing, or `u64` for ML frameworks).
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use tiktoken_rs::cl100k_base;
124 ///
125 /// let bpe = cl100k_base().unwrap();
126 /// let tokens: Vec<usize> = bpe.encode_ordinary_as("hello world");
127 /// ```
128 pub fn encode_ordinary_as<T: FromRank>(&self, text: &str) -> Vec<T> {
129 self.encode_ordinary(text)
130 .into_iter()
131 .map(T::from_rank)
132 .collect()
133 }
134
135 /// Like [`encode_with_special_tokens`](CoreBPE::encode_with_special_tokens),
136 /// but converts each token from [`Rank`] (`u32`) into `T`.
137 ///
138 /// # Examples
139 ///
140 /// ```
141 /// use tiktoken_rs::cl100k_base;
142 ///
143 /// let bpe = cl100k_base().unwrap();
144 /// let tokens: Vec<u64> = bpe.encode_with_special_tokens_as("hello <|endoftext|>");
145 /// ```
146 pub fn encode_with_special_tokens_as<T: FromRank>(&self, text: &str) -> Vec<T> {
147 self.encode_with_special_tokens(text)
148 .into_iter()
149 .map(T::from_rank)
150 .collect()
151 }
152
153 /// Like [`encode`](CoreBPE::encode), but converts each token from
154 /// [`Rank`] (`u32`) into `T`.
155 ///
156 /// Returns the same error as [`encode`](CoreBPE::encode) if tokenization
157 /// fails.
158 pub fn encode_as<T: FromRank>(
159 &self,
160 text: &str,
161 allowed_special: &HashSet<&str>,
162 ) -> Result<(Vec<T>, usize)> {
163 let (tokens, last_piece_token_len) = self.encode(text, allowed_special)?;
164 Ok((
165 tokens.into_iter().map(T::from_rank).collect(),
166 last_piece_token_len,
167 ))
168 }
169
170 // ====================
171 // Counting
172 // ====================
173
174 /// Returns the number of tokens that `encode_ordinary` would produce,
175 /// without returning the token list itself.
176 ///
177 /// Equivalent to `self.encode_ordinary(text).len()`.
178 pub fn count_ordinary(&self, text: &str) -> usize {
179 self.encode_ordinary(text).len()
180 }
181
182 /// Returns the number of tokens that `encode` would produce for the given
183 /// `allowed_special` set, without returning the token list itself.
184 ///
185 /// Equivalent to `self.encode(text, allowed_special)?.0.len()`.
186 pub fn count(&self, text: &str, allowed_special: &HashSet<&str>) -> Result<usize> {
187 Ok(self.encode(text, allowed_special)?.0.len())
188 }
189
190 /// Returns the number of tokens that `encode_with_special_tokens` would
191 /// produce, without returning the token list itself.
192 ///
193 /// Equivalent to `self.encode_with_special_tokens(text).len()`.
194 pub fn count_with_special_tokens(&self, text: &str) -> usize {
195 self.encode_with_special_tokens(text).len()
196 }
197
198 // ====================
199 // Decoding
200 // ====================
201
202 /// Decode a vector of tokens into a valid UTF-8 String
203 ///
204 /// If unicode validation is not wanted, see _decode_native.
205 pub fn decode(&self, tokens: &[Rank]) -> Result<String> {
206 match String::from_utf8(self.decode_bytes(tokens)?) {
207 Ok(text) => Ok(text),
208 Err(e) => Err(anyhow!("Unable to decode into a valid UTF-8 string: {}", e)),
209 }
210 }
211
212 pub fn _decode_native_and_split(
213 &self,
214 tokens: Vec<Rank>,
215 ) -> impl Iterator<Item = Vec<u8>> + '_ {
216 tokens.into_iter().map(|token| {
217 let token_bytes = self
218 .decoder
219 .get(&token)
220 .unwrap_or_else(|| &self.special_tokens_decoder[&token]);
221 token_bytes.clone()
222 })
223 }
224
225 /// Tokenize a string and return the decoded tokens using the correct BPE model.
226 ///
227 /// This method takes a string, encodes it using the BPE model, and decodes the encoded tokens into
228 /// a vector of strings. It can be used to tokenize a string and return the decoded tokens using the
229 /// correct BPE model.
230 ///
231 /// # Examples
232 ///
233 /// ```
234 /// use tiktoken_rs::cl100k_base;
235 /// let bpe = cl100k_base().unwrap();
236 /// let tokenized: Result<Vec<_>, _> = bpe
237 /// .split_by_token("This is a test with a lot of spaces", true);
238 /// let tokenized = tokenized.unwrap();
239 /// assert_eq!(
240 /// tokenized,
241 /// vec!["This", " is", " a", " test", " ", " with", " a", " lot", " of", " spaces"]
242 /// );
243 /// ```
244 ///
245 /// # Arguments
246 ///
247 /// * text: A string slice containing the text to be tokenized.
248 /// * use_special_tokens: A boolean indicating whether to use the special tokens in the BPE model.
249 ///
250 /// # Returns
251 ///
252 /// * `Result<Vec<String>>`: A Result containing a vector of decoded tokens as strings, or an error
253 /// if the string cannot be converted into a valid UTF-8 string.
254 ///
255 /// # Errors
256 ///
257 /// This function will return an error if:
258 ///
259 /// * The input text cannot be converted into a valid UTF-8 string during the decoding process.
260 ///
261 pub fn split_by_token<'a>(
262 &'a self,
263 text: &'a str,
264 use_special_tokens: bool,
265 ) -> Result<Vec<String>> {
266 self.split_by_token_iter(text, use_special_tokens).collect()
267 }
268
269 /// Iterator for decoding and splitting a String.
270 /// See `split_by_token` for more details.
271 pub fn split_by_token_iter<'a>(
272 &'a self,
273 text: &'a str,
274 use_special_tokens: bool,
275 ) -> impl Iterator<Item = Result<String>> + 'a {
276 // First, encode the text using the BPE model
277 let encoded = match use_special_tokens {
278 true => self.encode_with_special_tokens(text),
279 false => self.encode_ordinary(text),
280 };
281
282 self._decode_native_and_split(encoded).map(|token| {
283 // Map each token to a Result<String>
284 Ok(String::from_utf8_lossy(token.as_slice()).to_string())
285 })
286 }
287
288 /// Tokenize a string and return the decoded tokens using the correct BPE model.
289 /// This method is equivalent to `split_by_token(text, false)`.
290 pub fn split_by_token_ordinary<'a>(&'a self, text: &'a str) -> Result<Vec<String>> {
291 self.split_by_token(text, false)
292 }
293
294 /// Iterator for decoding and splitting a String.
295 /// This method is equivalent to `split_by_token_iter(text, false)`.
296 pub fn split_by_token_ordinary_iter<'a>(
297 &'a self,
298 text: &'a str,
299 ) -> impl Iterator<Item = Result<String>> + 'a {
300 self.split_by_token_iter(text, false)
301 }
302}