Skip to main content

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}