tiktoken_ruby 0.0.15.1 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/Cargo.lock +20 -28
- data/Gemfile.lock +21 -21
- data/README.md +23 -4
- data/ext/tiktoken_ruby/Cargo.toml +2 -2
- data/ext/tiktoken_ruby/src/core_bpe_wrapper.rs +59 -7
- data/ext/tiktoken_ruby/src/lib.rs +6 -1
- data/lib/tiktoken_ruby/encoding.rb +34 -4
- data/lib/tiktoken_ruby/version.rb +1 -1
- data/lib/tiktoken_ruby.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b04168b5f23957815c4f9a7139d0629c29236befaaeab12deaa1e0fee2c3b814
|
|
4
|
+
data.tar.gz: aa8af07c52ba013e0e7d7ad0aa569db6419a3330eaace2d3401963c7f4f9d2cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0bf109e1ab9191f06bf7450cfa32589c5dc4fcff41084c3682694b9ddd03f5595151e3ba2e376916c85f5ea1ce8e0866743556ec2c0f778309ac3aad216caf8a
|
|
7
|
+
data.tar.gz: b105c5871dde75b05274e830f1b40c0a4a05364c2cb6947f1223c128af4c1564dfc573c8c6e3b521ccf11b4eb6eb0592882af734deeba39251f8a1eab619e4a5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
# [v0.0.17] - 18-07-2026
|
|
2
|
+
## What's Changed
|
|
3
|
+
* Bump the github-actions group with 2 updates by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/119
|
|
4
|
+
* Bump the bundler-dependencies group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/121
|
|
5
|
+
* Bump the cargo group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/120
|
|
6
|
+
* feat(decode): Add opt-in lossy mode and decode_bytes by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/123
|
|
7
|
+
* Bump version to 0.0.17 by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/124
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
**Full Changelog**: https://github.com/IAPark/tiktoken_ruby/compare/v0.0.16...v0.0.17
|
|
11
|
+
# [v0.0.16] - 02-05-2026
|
|
12
|
+
## What's Changed
|
|
13
|
+
* Bump rb-sys from 0.9.123 to 0.9.124 in the cargo group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/107
|
|
14
|
+
* Bump the bundler-dependencies group with 2 updates by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/108
|
|
15
|
+
* Bump standard from 1.53.0 to 1.54.0 in the bundler-dependencies group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/109
|
|
16
|
+
* Bump the cargo group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/113
|
|
17
|
+
* Bump the bundler-dependencies group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/114
|
|
18
|
+
* bump version to 0.0.16 by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/115
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
**Full Changelog**: https://github.com/IAPark/tiktoken_ruby/compare/v0.0.15.1...v0.0.16
|
|
1
22
|
# [v0.0.15.1] - 07-01-2026
|
|
2
23
|
## What's Changed
|
|
3
24
|
* Re-release 0.0.15 for mistakenly yanked gems by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/105
|
data/Cargo.lock
CHANGED
|
@@ -13,9 +13,9 @@ dependencies = [
|
|
|
13
13
|
|
|
14
14
|
[[package]]
|
|
15
15
|
name = "anyhow"
|
|
16
|
-
version = "1.0.
|
|
16
|
+
version = "1.0.102"
|
|
17
17
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
18
|
-
checksum = "
|
|
18
|
+
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
|
19
19
|
|
|
20
20
|
[[package]]
|
|
21
21
|
name = "base64"
|
|
@@ -25,16 +25,14 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|
|
25
25
|
|
|
26
26
|
[[package]]
|
|
27
27
|
name = "bindgen"
|
|
28
|
-
version = "0.
|
|
28
|
+
version = "0.72.1"
|
|
29
29
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
30
|
-
checksum = "
|
|
30
|
+
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
|
31
31
|
dependencies = [
|
|
32
32
|
"bitflags",
|
|
33
33
|
"cexpr",
|
|
34
34
|
"clang-sys",
|
|
35
35
|
"itertools",
|
|
36
|
-
"lazy_static",
|
|
37
|
-
"lazycell",
|
|
38
36
|
"proc-macro2",
|
|
39
37
|
"quote",
|
|
40
38
|
"regex",
|
|
@@ -45,18 +43,18 @@ dependencies = [
|
|
|
45
43
|
|
|
46
44
|
[[package]]
|
|
47
45
|
name = "bit-set"
|
|
48
|
-
version = "0.
|
|
46
|
+
version = "0.8.0"
|
|
49
47
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
50
|
-
checksum = "
|
|
48
|
+
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
|
51
49
|
dependencies = [
|
|
52
50
|
"bit-vec",
|
|
53
51
|
]
|
|
54
52
|
|
|
55
53
|
[[package]]
|
|
56
54
|
name = "bit-vec"
|
|
57
|
-
version = "0.
|
|
55
|
+
version = "0.8.0"
|
|
58
56
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
59
|
-
checksum = "
|
|
57
|
+
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
|
60
58
|
|
|
61
59
|
[[package]]
|
|
62
60
|
name = "bitflags"
|
|
@@ -109,9 +107,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
|
|
109
107
|
|
|
110
108
|
[[package]]
|
|
111
109
|
name = "fancy-regex"
|
|
112
|
-
version = "0.
|
|
110
|
+
version = "0.17.0"
|
|
113
111
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
114
|
-
checksum = "
|
|
112
|
+
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
|
|
115
113
|
dependencies = [
|
|
116
114
|
"bit-set",
|
|
117
115
|
"regex-automata",
|
|
@@ -139,12 +137,6 @@ version = "1.5.0"
|
|
|
139
137
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
140
138
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
141
139
|
|
|
142
|
-
[[package]]
|
|
143
|
-
name = "lazycell"
|
|
144
|
-
version = "1.3.0"
|
|
145
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
146
|
-
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|
147
|
-
|
|
148
140
|
[[package]]
|
|
149
141
|
name = "libc"
|
|
150
142
|
version = "0.2.177"
|
|
@@ -226,18 +218,18 @@ dependencies = [
|
|
|
226
218
|
|
|
227
219
|
[[package]]
|
|
228
220
|
name = "rb-sys"
|
|
229
|
-
version = "0.9.
|
|
221
|
+
version = "0.9.128"
|
|
230
222
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
231
|
-
checksum = "
|
|
223
|
+
checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
|
|
232
224
|
dependencies = [
|
|
233
225
|
"rb-sys-build",
|
|
234
226
|
]
|
|
235
227
|
|
|
236
228
|
[[package]]
|
|
237
229
|
name = "rb-sys-build"
|
|
238
|
-
version = "0.9.
|
|
230
|
+
version = "0.9.128"
|
|
239
231
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
240
|
-
checksum = "
|
|
232
|
+
checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
|
|
241
233
|
dependencies = [
|
|
242
234
|
"bindgen",
|
|
243
235
|
"lazy_static",
|
|
@@ -256,9 +248,9 @@ checksum = "08f8d2924cf136a1315e2b4c7460a39f62ef11ee5d522df9b2750fab55b868b6"
|
|
|
256
248
|
|
|
257
249
|
[[package]]
|
|
258
250
|
name = "regex"
|
|
259
|
-
version = "1.12.
|
|
251
|
+
version = "1.12.3"
|
|
260
252
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
261
|
-
checksum = "
|
|
253
|
+
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
|
262
254
|
dependencies = [
|
|
263
255
|
"aho-corasick",
|
|
264
256
|
"memchr",
|
|
@@ -285,9 +277,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
|
|
285
277
|
|
|
286
278
|
[[package]]
|
|
287
279
|
name = "rustc-hash"
|
|
288
|
-
version = "
|
|
280
|
+
version = "2.1.2"
|
|
289
281
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
290
|
-
checksum = "
|
|
282
|
+
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
|
291
283
|
|
|
292
284
|
[[package]]
|
|
293
285
|
name = "seq-macro"
|
|
@@ -349,9 +341,9 @@ dependencies = [
|
|
|
349
341
|
|
|
350
342
|
[[package]]
|
|
351
343
|
name = "tiktoken-rs"
|
|
352
|
-
version = "0.
|
|
344
|
+
version = "0.12.0"
|
|
353
345
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
354
|
-
checksum = "
|
|
346
|
+
checksum = "027853bbf8c7763b77c5c595f1c271c7d536ced7d6f83452911b944621e57fc2"
|
|
355
347
|
dependencies = [
|
|
356
348
|
"anyhow",
|
|
357
349
|
"base64",
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
tiktoken_ruby (0.0.
|
|
4
|
+
tiktoken_ruby (0.0.17)
|
|
5
5
|
rb_sys (~> 0.9)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -9,25 +9,25 @@ GEM
|
|
|
9
9
|
specs:
|
|
10
10
|
ast (2.4.3)
|
|
11
11
|
diff-lcs (1.6.2)
|
|
12
|
-
json (2.
|
|
12
|
+
json (2.20.0)
|
|
13
13
|
language_server-protocol (3.17.0.5)
|
|
14
14
|
lint_roller (1.1.0)
|
|
15
15
|
minitest (6.0.1)
|
|
16
16
|
prism (~> 1.5)
|
|
17
|
-
parallel (1.
|
|
18
|
-
parser (3.3.
|
|
17
|
+
parallel (1.28.0)
|
|
18
|
+
parser (3.3.11.1)
|
|
19
19
|
ast (~> 2.4.1)
|
|
20
20
|
racc
|
|
21
|
-
prism (1.
|
|
21
|
+
prism (1.9.0)
|
|
22
22
|
racc (1.8.1)
|
|
23
23
|
rainbow (3.1.1)
|
|
24
|
-
rake (13.
|
|
24
|
+
rake (13.4.2)
|
|
25
25
|
rake-compiler (1.3.1)
|
|
26
26
|
rake
|
|
27
|
-
rake-compiler-dock (1.
|
|
28
|
-
rb_sys (0.9.
|
|
29
|
-
rake-compiler-dock (= 1.
|
|
30
|
-
regexp_parser (2.
|
|
27
|
+
rake-compiler-dock (1.12.0)
|
|
28
|
+
rb_sys (0.9.128)
|
|
29
|
+
rake-compiler-dock (= 1.12.0)
|
|
30
|
+
regexp_parser (2.12.0)
|
|
31
31
|
rspec (3.13.2)
|
|
32
32
|
rspec-core (~> 3.13.0)
|
|
33
33
|
rspec-expectations (~> 3.13.0)
|
|
@@ -41,37 +41,37 @@ GEM
|
|
|
41
41
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
42
42
|
rspec-support (~> 3.13.0)
|
|
43
43
|
rspec-support (3.13.6)
|
|
44
|
-
rubocop (1.
|
|
44
|
+
rubocop (1.87.0)
|
|
45
45
|
json (~> 2.3)
|
|
46
46
|
language_server-protocol (~> 3.17.0.2)
|
|
47
47
|
lint_roller (~> 1.1.0)
|
|
48
|
-
parallel (
|
|
48
|
+
parallel (>= 1.10)
|
|
49
49
|
parser (>= 3.3.0.2)
|
|
50
50
|
rainbow (>= 2.2.2, < 4.0)
|
|
51
51
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
52
|
-
rubocop-ast (>= 1.
|
|
52
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
53
53
|
ruby-progressbar (~> 1.7)
|
|
54
54
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
55
|
-
rubocop-ast (1.
|
|
55
|
+
rubocop-ast (1.49.1)
|
|
56
56
|
parser (>= 3.3.7.2)
|
|
57
|
-
prism (~> 1.
|
|
58
|
-
rubocop-performance (1.
|
|
57
|
+
prism (~> 1.7)
|
|
58
|
+
rubocop-performance (1.26.1)
|
|
59
59
|
lint_roller (~> 1.1)
|
|
60
60
|
rubocop (>= 1.75.0, < 2.0)
|
|
61
|
-
rubocop-ast (>= 1.
|
|
61
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
62
62
|
ruby-progressbar (1.13.0)
|
|
63
|
-
standard (1.
|
|
63
|
+
standard (1.55.0)
|
|
64
64
|
language_server-protocol (~> 3.17.0.2)
|
|
65
65
|
lint_roller (~> 1.0)
|
|
66
|
-
rubocop (~> 1.
|
|
66
|
+
rubocop (~> 1.87.0)
|
|
67
67
|
standard-custom (~> 1.0.0)
|
|
68
68
|
standard-performance (~> 1.8)
|
|
69
69
|
standard-custom (1.0.2)
|
|
70
70
|
lint_roller (~> 1.0)
|
|
71
71
|
rubocop (~> 1.50)
|
|
72
|
-
standard-performance (1.
|
|
72
|
+
standard-performance (1.9.0)
|
|
73
73
|
lint_roller (~> 1.1)
|
|
74
|
-
rubocop-performance (~> 1.
|
|
74
|
+
rubocop-performance (~> 1.26.0)
|
|
75
75
|
unicode-display_width (3.2.0)
|
|
76
76
|
unicode-emoji (~> 4.1)
|
|
77
77
|
unicode-emoji (4.2.0)
|
data/README.md
CHANGED
|
@@ -17,9 +17,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Encode and decode text
|
|
20
|
+
Encode and decode text:
|
|
23
21
|
|
|
24
22
|
```ruby
|
|
25
23
|
require 'tiktoken_ruby'
|
|
@@ -27,7 +25,7 @@ enc = Tiktoken.get_encoding("cl100k_base")
|
|
|
27
25
|
enc.decode(enc.encode("hello world")) #=> "hello world"
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
Encoders can also be retrieved by model name
|
|
28
|
+
Encoders can also be retrieved by model name:
|
|
31
29
|
|
|
32
30
|
```ruby
|
|
33
31
|
require 'tiktoken_ruby'
|
|
@@ -69,6 +67,27 @@ enc.encode_with_special_tokens(text)
|
|
|
69
67
|
|
|
70
68
|
All methods round-trip correctly through `decode`.
|
|
71
69
|
|
|
70
|
+
### Decoding methods
|
|
71
|
+
|
|
72
|
+
- `decode(tokens, errors: :strict)` - Decodes tokens back into a UTF-8 string
|
|
73
|
+
- `decode_bytes(tokens)` - Decodes tokens into their raw bytes (an `ASCII-8BIT` string), without UTF-8 validation
|
|
74
|
+
|
|
75
|
+
Because BPE tokens are byte-level, a single character (an emoji, or non-Latin scripts) can span multiple tokens. Truncating a token array (like, "trim text to N tokens") can leave a prefix that is not valid UTF-8. The `errors:` option controls how those invalid sequences are handled.
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
enc = Tiktoken.encoding_for_model("gpt-4o")
|
|
79
|
+
tokens = enc.encode("🦄") # the emoji spans multiple tokens
|
|
80
|
+
|
|
81
|
+
# :strict (default) - raise Tiktoken::UnicodeError on invalid UTF-8
|
|
82
|
+
enc.decode(tokens.first(2)) #=> raises Tiktoken::UnicodeError
|
|
83
|
+
|
|
84
|
+
# :replace - substitute invalid sequences with "�" (matches Python tiktoken's default)
|
|
85
|
+
enc.decode(tokens.first(2), errors: :replace) #=> "�"
|
|
86
|
+
|
|
87
|
+
# decode_bytes - get the raw bytes and handle them yourself
|
|
88
|
+
enc.decode_bytes(tokens.first(2)) #=> "\xF0\x9F\xA6" (ASCII-8BIT)
|
|
89
|
+
```
|
|
90
|
+
|
|
72
91
|
## Development
|
|
73
92
|
|
|
74
93
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
@@ -11,5 +11,5 @@ crate-type = ["cdylib"]
|
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
13
|
magnus = { version = "0.8.2" }
|
|
14
|
-
rb-sys = { version = "0.9.
|
|
15
|
-
tiktoken-rs = { version = "0.
|
|
14
|
+
rb-sys = { version = "0.9.127", features = ["stable-api-compiled-fallback"] }
|
|
15
|
+
tiktoken-rs = { version = "0.12.0" }
|
|
@@ -3,7 +3,7 @@ use std::ffi::c_void;
|
|
|
3
3
|
|
|
4
4
|
use tiktoken_rs::Rank;
|
|
5
5
|
|
|
6
|
-
use crate::
|
|
6
|
+
use crate::unicode_error;
|
|
7
7
|
|
|
8
8
|
#[magnus::wrap(class = "Tiktoken::Ext::CoreBPE")]
|
|
9
9
|
pub struct CoreBPEWrapper {
|
|
@@ -20,7 +20,7 @@ struct EncodeData {
|
|
|
20
20
|
core_bpe: *const tiktoken_rs::CoreBPE,
|
|
21
21
|
text: String,
|
|
22
22
|
allowed_special: HashSet<String>,
|
|
23
|
-
result: Vec<Rank>,
|
|
23
|
+
result: Result<Vec<Rank>, String>,
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
struct EncodeSpecialData {
|
|
@@ -35,6 +35,12 @@ struct DecodeData {
|
|
|
35
35
|
result: Result<String, String>,
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
struct DecodeBytesData {
|
|
39
|
+
core_bpe: *const tiktoken_rs::CoreBPE,
|
|
40
|
+
ids: Vec<Rank>,
|
|
41
|
+
result: Result<Vec<u8>, String>,
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
unsafe extern "C" fn encode_ordinary_without_gvl(data: *mut c_void) -> *mut c_void {
|
|
39
45
|
let data = &mut *(data as *mut EncodeOrdinaryData);
|
|
40
46
|
let core_bpe = &*data.core_bpe;
|
|
@@ -46,7 +52,11 @@ unsafe extern "C" fn encode_without_gvl(data: *mut c_void) -> *mut c_void {
|
|
|
46
52
|
let data = &mut *(data as *mut EncodeData);
|
|
47
53
|
let core_bpe = &*data.core_bpe;
|
|
48
54
|
let allowed_special: HashSet<&str> = data.allowed_special.iter().map(|s| s.as_str()).collect();
|
|
49
|
-
|
|
55
|
+
// Keep only the token vector and surface any tokenization failure to Ruby.
|
|
56
|
+
data.result = core_bpe
|
|
57
|
+
.encode(&data.text, &allowed_special)
|
|
58
|
+
.map(|(tokens, _last_piece_token_len)| tokens)
|
|
59
|
+
.map_err(|e| e.to_string());
|
|
50
60
|
std::ptr::null_mut()
|
|
51
61
|
}
|
|
52
62
|
|
|
@@ -60,7 +70,14 @@ unsafe extern "C" fn encode_special_without_gvl(data: *mut c_void) -> *mut c_voi
|
|
|
60
70
|
unsafe extern "C" fn decode_without_gvl(data: *mut c_void) -> *mut c_void {
|
|
61
71
|
let data = &mut *(data as *mut DecodeData);
|
|
62
72
|
let core_bpe = &*data.core_bpe;
|
|
63
|
-
data.result = core_bpe.decode(
|
|
73
|
+
data.result = core_bpe.decode(&data.ids).map_err(|e| e.to_string());
|
|
74
|
+
std::ptr::null_mut()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
unsafe extern "C" fn decode_bytes_without_gvl(data: *mut c_void) -> *mut c_void {
|
|
78
|
+
let data = &mut *(data as *mut DecodeBytesData);
|
|
79
|
+
let core_bpe = &*data.core_bpe;
|
|
80
|
+
data.result = core_bpe.decode_bytes(&data.ids).map_err(|e| e.to_string());
|
|
64
81
|
std::ptr::null_mut()
|
|
65
82
|
}
|
|
66
83
|
|
|
@@ -99,7 +116,7 @@ impl CoreBPEWrapper {
|
|
|
99
116
|
core_bpe: &self.core_bpe as *const _,
|
|
100
117
|
text,
|
|
101
118
|
allowed_special: HashSet::from_iter(allowed_special),
|
|
102
|
-
result: Vec::new(),
|
|
119
|
+
result: Ok(Vec::new()),
|
|
103
120
|
};
|
|
104
121
|
|
|
105
122
|
unsafe {
|
|
@@ -111,7 +128,9 @@ impl CoreBPEWrapper {
|
|
|
111
128
|
);
|
|
112
129
|
}
|
|
113
130
|
|
|
114
|
-
|
|
131
|
+
data.result.map_err(|e| {
|
|
132
|
+
magnus::Error::new(magnus::Ruby::get().unwrap().exception_runtime_error(), e)
|
|
133
|
+
})
|
|
115
134
|
}
|
|
116
135
|
|
|
117
136
|
pub fn encode_with_special_tokens(&self, text: String) -> Vec<Rank> {
|
|
@@ -150,7 +169,7 @@ impl CoreBPEWrapper {
|
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
data.result.map_err(|e| {
|
|
153
|
-
let error = match
|
|
172
|
+
let error = match unicode_error() {
|
|
154
173
|
Ok(error) => error,
|
|
155
174
|
Err(e) => return e,
|
|
156
175
|
};
|
|
@@ -158,4 +177,37 @@ impl CoreBPEWrapper {
|
|
|
158
177
|
magnus::Error::new(error, e)
|
|
159
178
|
})
|
|
160
179
|
}
|
|
180
|
+
|
|
181
|
+
pub fn decode_bytes(&self, ids: Vec<Rank>) -> Result<magnus::RString, magnus::Error> {
|
|
182
|
+
let mut data = DecodeBytesData {
|
|
183
|
+
core_bpe: &self.core_bpe as *const _,
|
|
184
|
+
ids,
|
|
185
|
+
result: Err(String::new()),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
unsafe {
|
|
189
|
+
rb_sys::rb_thread_call_without_gvl(
|
|
190
|
+
Some(decode_bytes_without_gvl),
|
|
191
|
+
&mut data as *mut _ as *mut c_void,
|
|
192
|
+
None,
|
|
193
|
+
std::ptr::null_mut(),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Build the Ruby string only after the GVL has been re-acquired.
|
|
198
|
+
// `str_from_slice` yields an ASCII-8BIT (binary) string, so the bytes
|
|
199
|
+
// are handed back verbatim without any UTF-8 validation. We hold the
|
|
200
|
+
// GVL here (this is called from Ruby), so `Ruby::get` cannot fail.
|
|
201
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
202
|
+
data.result
|
|
203
|
+
.map(|bytes| ruby.str_from_slice(&bytes))
|
|
204
|
+
.map_err(|e| {
|
|
205
|
+
let error = match unicode_error() {
|
|
206
|
+
Ok(error) => error,
|
|
207
|
+
Err(e) => return e,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
magnus::Error::new(error, e)
|
|
211
|
+
})
|
|
212
|
+
}
|
|
161
213
|
}
|
|
@@ -34,7 +34,7 @@ fn module() -> Result<RModule, magnus::Error> {
|
|
|
34
34
|
Ruby::get().unwrap().define_module("Tiktoken")
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
fn
|
|
37
|
+
fn unicode_error() -> Result<ExceptionClass, magnus::Error> {
|
|
38
38
|
module()?.define_error(
|
|
39
39
|
"UnicodeError",
|
|
40
40
|
Ruby::get().unwrap().exception_standard_error(),
|
|
@@ -45,6 +45,10 @@ fn uncicode_error() -> Result<ExceptionClass, magnus::Error> {
|
|
|
45
45
|
fn init() -> Result<(), Error> {
|
|
46
46
|
let module = module()?;
|
|
47
47
|
|
|
48
|
+
// Define Tiktoken::UnicodeError eagerly so the constant exists as soon as
|
|
49
|
+
// the extension is loaded, rather than only after the first decode error.
|
|
50
|
+
unicode_error()?;
|
|
51
|
+
|
|
48
52
|
let factory_module = module.define_module("BpeFactory")?;
|
|
49
53
|
factory_module.define_singleton_method("r50k_base", function!(r50k_base, 0))?;
|
|
50
54
|
factory_module.define_singleton_method("p50k_base", function!(p50k_base, 0))?;
|
|
@@ -67,5 +71,6 @@ fn init() -> Result<(), Error> {
|
|
|
67
71
|
)?;
|
|
68
72
|
|
|
69
73
|
bpe_class.define_method("decode", method!(CoreBPEWrapper::decode, 1))?;
|
|
74
|
+
bpe_class.define_method("decode_bytes", method!(CoreBPEWrapper::decode_bytes, 1))?;
|
|
70
75
|
Ok(())
|
|
71
76
|
}
|
|
@@ -47,11 +47,41 @@ class Tiktoken::Encoding
|
|
|
47
47
|
@ext_base_bpe.encode_with_special_tokens(text)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
# Decodes the tokens back into text
|
|
50
|
+
# Decodes the tokens back into text.
|
|
51
|
+
#
|
|
52
|
+
# BPE tokens are byte-level, so a single character (an emoji, non-Latin
|
|
53
|
+
# scripts) can span multiple tokens. Truncating a token array to a limit can
|
|
54
|
+
# therefore leave a prefix that is not valid UTF-8. The +errors+ option
|
|
55
|
+
# controls how those invalid byte sequences are handled:
|
|
56
|
+
#
|
|
57
|
+
# * +:strict+ (default) - raise Tiktoken::UnicodeError on invalid UTF-8.
|
|
58
|
+
# * +:replace+ - substitute each invalid sequence with the Unicode replacement
|
|
59
|
+
# character (U+FFFD, +�+), matching Python tiktoken's default behavior.
|
|
60
|
+
#
|
|
51
61
|
# @param tokens [Array<Integer>] The tokens to decode
|
|
52
|
-
# @
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
# @param errors [Symbol] How to handle invalid UTF-8 (:strict or :replace)
|
|
63
|
+
# @return [String] The decoded text (UTF-8)
|
|
64
|
+
def decode(tokens, errors: :strict)
|
|
65
|
+
case errors
|
|
66
|
+
when :strict
|
|
67
|
+
@ext_base_bpe.decode(tokens)
|
|
68
|
+
when :replace
|
|
69
|
+
decode_bytes(tokens).force_encoding(Encoding::UTF_8).scrub("\u{FFFD}")
|
|
70
|
+
else
|
|
71
|
+
raise ArgumentError, "errors must be :strict or :replace, got #{errors.inspect}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Decodes the tokens back into their raw bytes, without any UTF-8 validation.
|
|
76
|
+
#
|
|
77
|
+
# The returned string has ASCII-8BIT (binary) encoding and is not guaranteed
|
|
78
|
+
# to be valid UTF-8 — useful when you want to handle invalid sequences
|
|
79
|
+
# yourself. Matches Python tiktoken's +decode_bytes+.
|
|
80
|
+
#
|
|
81
|
+
# @param tokens [Array<Integer>] The tokens to decode
|
|
82
|
+
# @return [String] The decoded bytes (ASCII-8BIT)
|
|
83
|
+
def decode_bytes(tokens)
|
|
84
|
+
@ext_base_bpe.decode_bytes(tokens)
|
|
55
85
|
end
|
|
56
86
|
|
|
57
87
|
private
|
data/lib/tiktoken_ruby.rb
CHANGED
|
@@ -79,6 +79,7 @@ module Tiktoken
|
|
|
79
79
|
o3: "o200k_base",
|
|
80
80
|
"o4-mini": "o200k_base",
|
|
81
81
|
# chat
|
|
82
|
+
"gpt-5": "o200k_base",
|
|
82
83
|
"gpt-4.1": "o200k_base",
|
|
83
84
|
"chatgpt-4o": "o200k_base",
|
|
84
85
|
"gpt-4o": "o200k_base",
|
|
@@ -136,7 +137,9 @@ module Tiktoken
|
|
|
136
137
|
"o3-": "o200k_base",
|
|
137
138
|
"o4-": "o200k_base",
|
|
138
139
|
# chat
|
|
140
|
+
"gpt-5.": "o200k_base", # e.g., gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, gpt-5.2-pro
|
|
139
141
|
"gpt-5-": "o200k_base",
|
|
142
|
+
"codex-mini": "o200k_base", # e.g., codex-mini, codex-mini-latest
|
|
140
143
|
"gpt-4.5-": "o200k_base",
|
|
141
144
|
"gpt-4.1-": "o200k_base",
|
|
142
145
|
"chatgpt-4o-": "o200k_base",
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tiktoken_ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.17
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- IAPark
|
|
@@ -74,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
75
|
version: 3.4.0
|
|
76
76
|
requirements: []
|
|
77
|
-
rubygems_version: 4.0.
|
|
77
|
+
rubygems_version: 4.0.10
|
|
78
78
|
specification_version: 4
|
|
79
79
|
summary: Ruby wrapper for Tiktoken
|
|
80
80
|
test_files: []
|