tiktoken_ruby 0.0.13 → 0.0.15

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6eb1bf0a15715fcd42136ff3378a0b2b5676a06dd817fbbe59e83287c9a7e996
4
- data.tar.gz: 30172f2328b4c98c62963c5ddc5afda840eb0f938e632d40e5aafc1eb7521693
3
+ metadata.gz: 6ad1b0b7629c92e355ac26b13f4c9ecb00cf96a047e8a50ca88bac5b2efc05c8
4
+ data.tar.gz: 99aa54177dfcfa435c9539b2210eb3b1af3c84ba76c1b5e1e31728f1b4ca041b
5
5
  SHA512:
6
- metadata.gz: 2db1488ee96bcfc94cbe48617c1f37f54ef74e532dafd50d4c89fc56940dcd3ca83cc3bd4c49ebc8edae71998e37046af62c8e67a8c2a97b3d316ec534acec62
7
- data.tar.gz: 9ce2ca75ba2ba65c375c562717cdd19c6a3e41c127964775ce49eb1e3664954c530348eba71888b037d7b3ae2568f6fe1b52bd887b27e72294e5530ab54f8f8f
6
+ metadata.gz: 5cfaaa543821c6bc80cc39b19bb71d4e3b0dbec058de5027b47ecbcaacbeef1436532a52fe63a4407436dfa7d506252150ae4b07656fbdb969ce7eee694f50c2
7
+ data.tar.gz: 15c5f6aa872daf9900ce5c10b049292ae80d48e44ace0251643749a8b925b9dc89cfa34ccc1706f36db7bf6a832466fc530e502e280adaacd234044849432ff7
@@ -4,5 +4,8 @@
4
4
  },
5
5
  "[markdown]": {
6
6
  "editor.defaultFormatter": "esbenp.prettier-vscode"
7
+ },
8
+ "[github-actions-workflow]": {
9
+ "editor.defaultFormatter": "redhat.vscode-yaml"
7
10
  }
8
11
  }
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # [v0.0.15] - 06-01-2026
2
+ ## What's Changed
3
+ * No longer true by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/96
4
+ * Test build on version change by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/97
5
+ * Bump actions/cache from 4 to 5 in the github-actions group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/98
6
+ * Bump rb-sys from 0.9.117 to 0.9.123 in the cargo group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/99
7
+ * Bump the bundler-dependencies group with 2 updates by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/100
8
+ * Add Ruby 4.0 to build matrix by @chubchenko in https://github.com/IAPark/tiktoken_ruby/pull/101
9
+ * Support Ruby 4 by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/102
10
+
11
+ ## New Contributors
12
+ * @chubchenko made their first contribution in https://github.com/IAPark/tiktoken_ruby/pull/101
13
+
14
+ **Full Changelog**: https://github.com/IAPark/tiktoken_ruby/compare/v0.0.14.1...v0.0.15
15
+ # [v0.0.14.1] - 20-12-2025
16
+ ## What's Changed
17
+ * Cut v0.0.12 by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/78
18
+ * Bump magnus from 0.8.0 to 0.8.1 in the cargo group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/83
19
+ * Bump actions/checkout from 4 to 5 in the github-actions group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/82
20
+ * Bump standard from 1.50.0 to 1.51.1 in the bundler-dependencies group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/81
21
+ * Bump actions/upload-artifact from 4 to 5 in the github-actions group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/84
22
+ * Bump magnus from 0.8.1 to 0.8.2 in the cargo group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/85
23
+ * Bump the bundler-dependencies group with 2 updates by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/86
24
+ * Support by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/87
25
+ * Bump actions/checkout from 5 to 6 in the github-actions group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/89
26
+ * Bump standard from 1.51.1 to 1.52.0 in the bundler-dependencies group by @dependabot[bot] in https://github.com/IAPark/tiktoken_ruby/pull/88
27
+ * release GVL while encoding / decoding tokens by @tenderworks in https://github.com/IAPark/tiktoken_ruby/pull/90
28
+ * Drop Ruby 3.1 support; automate release process by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/92
29
+ * Rewrite history by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/93
30
+ * Force workflow rebuild by @gjtorikian in https://github.com/IAPark/tiktoken_ruby/pull/94
31
+
32
+ ## New Contributors
33
+ * @tenderworks made their first contribution in https://github.com/IAPark/tiktoken_ruby/pull/90
34
+
35
+ **Full Changelog**: https://github.com/IAPark/tiktoken_ruby/compare/v0.0.12...v0.0.14.1
data/Cargo.lock CHANGED
@@ -226,18 +226,18 @@ dependencies = [
226
226
 
227
227
  [[package]]
228
228
  name = "rb-sys"
229
- version = "0.9.117"
229
+ version = "0.9.123"
230
230
  source = "registry+https://github.com/rust-lang/crates.io-index"
231
- checksum = "f900d1ce4629a2ebffaf5de74bd8f9c1188d4c5ed406df02f97e22f77a006f44"
231
+ checksum = "45fb1a185af97ee456f1c9e56dbe6e2e662bec4fdeaf83c4c28e0e6adfb18816"
232
232
  dependencies = [
233
233
  "rb-sys-build",
234
234
  ]
235
235
 
236
236
  [[package]]
237
237
  name = "rb-sys-build"
238
- version = "0.9.117"
238
+ version = "0.9.123"
239
239
  source = "registry+https://github.com/rust-lang/crates.io-index"
240
- checksum = "ef1e9c857028f631056bcd6d88cec390c751e343ce2223ddb26d23eb4a151d59"
240
+ checksum = "a58ebd02d7a6033e6a5f6f8d150c1e9f16506039092b84a73e6bedce6d3adf41"
241
241
  dependencies = [
242
242
  "bindgen",
243
243
  "lazy_static",
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tiktoken_ruby (0.0.13)
4
+ tiktoken_ruby (0.0.15)
5
5
  rb_sys (~> 0.9)
6
6
 
7
7
  GEM
@@ -9,23 +9,24 @@ GEM
9
9
  specs:
10
10
  ast (2.4.3)
11
11
  diff-lcs (1.6.2)
12
- json (2.15.0)
12
+ json (2.16.0)
13
13
  language_server-protocol (3.17.0.5)
14
14
  lint_roller (1.1.0)
15
- minitest (5.25.5)
15
+ minitest (6.0.1)
16
+ prism (~> 1.5)
16
17
  parallel (1.27.0)
17
- parser (3.3.9.0)
18
+ parser (3.3.10.0)
18
19
  ast (~> 2.4.1)
19
20
  racc
20
- prism (1.5.1)
21
+ prism (1.6.0)
21
22
  racc (1.8.1)
22
23
  rainbow (3.1.1)
23
24
  rake (13.3.1)
24
- rake-compiler (1.3.0)
25
+ rake-compiler (1.3.1)
25
26
  rake
26
- rake-compiler-dock (1.9.1)
27
- rb_sys (0.9.117)
28
- rake-compiler-dock (= 1.9.1)
27
+ rake-compiler-dock (1.10.0)
28
+ rb_sys (0.9.123)
29
+ rake-compiler-dock (= 1.10.0)
29
30
  regexp_parser (2.11.3)
30
31
  rspec (3.13.2)
31
32
  rspec-core (~> 3.13.0)
@@ -40,7 +41,7 @@ GEM
40
41
  diff-lcs (>= 1.2.0, < 2.0)
41
42
  rspec-support (~> 3.13.0)
42
43
  rspec-support (3.13.6)
43
- rubocop (1.80.2)
44
+ rubocop (1.81.7)
44
45
  json (~> 2.3)
45
46
  language_server-protocol (~> 3.17.0.2)
46
47
  lint_roller (~> 1.1.0)
@@ -48,10 +49,10 @@ GEM
48
49
  parser (>= 3.3.0.2)
49
50
  rainbow (>= 2.2.2, < 4.0)
50
51
  regexp_parser (>= 2.9.3, < 3.0)
51
- rubocop-ast (>= 1.46.0, < 2.0)
52
+ rubocop-ast (>= 1.47.1, < 2.0)
52
53
  ruby-progressbar (~> 1.7)
53
54
  unicode-display_width (>= 2.4.0, < 4.0)
54
- rubocop-ast (1.47.1)
55
+ rubocop-ast (1.48.0)
55
56
  parser (>= 3.3.7.2)
56
57
  prism (~> 1.4)
57
58
  rubocop-performance (1.25.0)
@@ -59,10 +60,10 @@ GEM
59
60
  rubocop (>= 1.75.0, < 2.0)
60
61
  rubocop-ast (>= 1.38.0, < 2.0)
61
62
  ruby-progressbar (1.13.0)
62
- standard (1.51.1)
63
+ standard (1.52.0)
63
64
  language_server-protocol (~> 3.17.0.2)
64
65
  lint_roller (~> 1.0)
65
- rubocop (~> 1.80.2)
66
+ rubocop (~> 1.81.7)
66
67
  standard-custom (~> 1.0.0)
67
68
  standard-performance (~> 1.8)
68
69
  standard-custom (1.0.2)
@@ -73,7 +74,7 @@ GEM
73
74
  rubocop-performance (~> 1.25.0)
74
75
  unicode-display_width (3.2.0)
75
76
  unicode-emoji (~> 4.1)
76
- unicode-emoji (4.1.0)
77
+ unicode-emoji (4.2.0)
77
78
  yard (0.9.37)
78
79
  yard-doctest (0.1.17)
79
80
  minitest
data/README.md CHANGED
@@ -36,11 +36,44 @@ enc = Tiktoken.encoding_for_model("gpt-4")
36
36
  enc.encode("hello world").length #=> 2
37
37
  ```
38
38
 
39
+ ### Encoding methods
40
+
41
+ There are three methods for encoding text:
42
+
43
+ - `encode_ordinary(text)` - Encodes text, always treating special tokens as ordinary text
44
+ - `encode(text, allowed_special: [])` - Encodes text, treating special tokens as text unless listed in `allowed_special`
45
+ - `encode_with_special_tokens(text)` - Encodes text, recognizing and parsing all special tokens
46
+
47
+ **Special tokens** are control sequences used by OpenAI models, such as `<|endoftext|>`, `<|fim_prefix|>`, `<|fim_middle|>`, and `<|fim_suffix|>`. The encoding methods differ in how they handle these sequences:
48
+
49
+ ```ruby
50
+ enc = Tiktoken.get_encoding("cl100k_base")
51
+ text = "Hello<|endoftext|>World"
52
+
53
+ # encode_ordinary: treats <|endoftext|> as literal characters (9 tokens)
54
+ enc.encode_ordinary(text)
55
+ #=> [9906, 27, 91, 8862, 728, 428, 91, 29, 10343]
56
+
57
+ # encode: same as encode_ordinary by default
58
+ enc.encode(text)
59
+ #=> [9906, 27, 91, 8862, 728, 428, 91, 29, 10343]
60
+
61
+ # encode with allowed_special: recognizes the specified special token (3 tokens)
62
+ enc.encode(text, allowed_special: ["<|endoftext|>"])
63
+ #=> [9906, 100257, 10343]
64
+
65
+ # encode_with_special_tokens: recognizes ALL special tokens (3 tokens)
66
+ enc.encode_with_special_tokens(text)
67
+ #=> [9906, 100257, 10343]
68
+ ```
69
+
70
+ All methods round-trip correctly through `decode`.
71
+
39
72
  ## Development
40
73
 
41
74
  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.
42
75
 
43
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
76
+ To install this gem onto your local machine, run `bundle exec rake install`.
44
77
 
45
78
  ## Contributing
46
79
 
@@ -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.117", features = ["stable-api-compiled-fallback"] }
14
+ rb-sys = { version = "0.9.123", features = ["stable-api-compiled-fallback"] }
15
15
  tiktoken-rs = { version = "0.9.0" }
@@ -1,4 +1,5 @@
1
1
  use std::collections::HashSet;
2
+ use std::ffi::c_void;
2
3
 
3
4
  use tiktoken_rs::Rank;
4
5
 
@@ -9,13 +10,82 @@ pub struct CoreBPEWrapper {
9
10
  core_bpe: tiktoken_rs::CoreBPE,
10
11
  }
11
12
 
13
+ struct EncodeOrdinaryData {
14
+ core_bpe: *const tiktoken_rs::CoreBPE,
15
+ text: String,
16
+ result: Vec<Rank>,
17
+ }
18
+
19
+ struct EncodeData {
20
+ core_bpe: *const tiktoken_rs::CoreBPE,
21
+ text: String,
22
+ allowed_special: HashSet<String>,
23
+ result: Vec<Rank>,
24
+ }
25
+
26
+ struct EncodeSpecialData {
27
+ core_bpe: *const tiktoken_rs::CoreBPE,
28
+ text: String,
29
+ result: Vec<Rank>,
30
+ }
31
+
32
+ struct DecodeData {
33
+ core_bpe: *const tiktoken_rs::CoreBPE,
34
+ ids: Vec<Rank>,
35
+ result: Result<String, String>,
36
+ }
37
+
38
+ unsafe extern "C" fn encode_ordinary_without_gvl(data: *mut c_void) -> *mut c_void {
39
+ let data = &mut *(data as *mut EncodeOrdinaryData);
40
+ let core_bpe = &*data.core_bpe;
41
+ data.result = core_bpe.encode_ordinary(&data.text);
42
+ std::ptr::null_mut()
43
+ }
44
+
45
+ unsafe extern "C" fn encode_without_gvl(data: *mut c_void) -> *mut c_void {
46
+ let data = &mut *(data as *mut EncodeData);
47
+ let core_bpe = &*data.core_bpe;
48
+ let allowed_special: HashSet<&str> = data.allowed_special.iter().map(|s| s.as_str()).collect();
49
+ data.result = core_bpe.encode(&data.text, &allowed_special).0;
50
+ std::ptr::null_mut()
51
+ }
52
+
53
+ unsafe extern "C" fn encode_special_without_gvl(data: *mut c_void) -> *mut c_void {
54
+ let data = &mut *(data as *mut EncodeSpecialData);
55
+ let core_bpe = &*data.core_bpe;
56
+ data.result = core_bpe.encode_with_special_tokens(&data.text);
57
+ std::ptr::null_mut()
58
+ }
59
+
60
+ unsafe extern "C" fn decode_without_gvl(data: *mut c_void) -> *mut c_void {
61
+ let data = &mut *(data as *mut DecodeData);
62
+ let core_bpe = &*data.core_bpe;
63
+ data.result = core_bpe.decode(std::mem::take(&mut data.ids)).map_err(|e| e.to_string());
64
+ std::ptr::null_mut()
65
+ }
66
+
12
67
  impl CoreBPEWrapper {
13
68
  pub fn new(core_bpe: tiktoken_rs::CoreBPE) -> Self {
14
69
  Self { core_bpe }
15
70
  }
16
71
 
17
72
  pub fn encode_ordinary(&self, text: String) -> Vec<Rank> {
18
- self.core_bpe.encode_ordinary(text.as_str())
73
+ let mut data = EncodeOrdinaryData {
74
+ core_bpe: &self.core_bpe as *const _,
75
+ text,
76
+ result: Vec::new(),
77
+ };
78
+
79
+ unsafe {
80
+ rb_sys::rb_thread_call_without_gvl(
81
+ Some(encode_ordinary_without_gvl),
82
+ &mut data as *mut _ as *mut c_void,
83
+ None,
84
+ std::ptr::null_mut(),
85
+ );
86
+ }
87
+
88
+ data.result
19
89
  }
20
90
 
21
91
  pub fn encode(
@@ -24,24 +94,68 @@ impl CoreBPEWrapper {
24
94
  allowed_special: magnus::RArray,
25
95
  ) -> Result<Vec<Rank>, magnus::Error> {
26
96
  let allowed_special: Vec<String> = allowed_special.to_vec()?;
27
- let allowed_special: Vec<&str> = allowed_special.iter().map(|s| s.as_str()).collect();
28
- let allowed_special: HashSet<&str> = HashSet::from_iter(allowed_special.iter().cloned());
29
97
 
30
- Ok(self.core_bpe.encode(text.as_str(), &allowed_special).0)
98
+ let mut data = EncodeData {
99
+ core_bpe: &self.core_bpe as *const _,
100
+ text,
101
+ allowed_special: HashSet::from_iter(allowed_special),
102
+ result: Vec::new(),
103
+ };
104
+
105
+ unsafe {
106
+ rb_sys::rb_thread_call_without_gvl(
107
+ Some(encode_without_gvl),
108
+ &mut data as *mut _ as *mut c_void,
109
+ None,
110
+ std::ptr::null_mut(),
111
+ );
112
+ }
113
+
114
+ Ok(data.result)
31
115
  }
32
116
 
33
117
  pub fn encode_with_special_tokens(&self, text: String) -> Vec<Rank> {
34
- self.core_bpe.encode_with_special_tokens(text.as_str())
118
+ let mut data = EncodeSpecialData {
119
+ core_bpe: &self.core_bpe as *const _,
120
+ text,
121
+ result: Vec::new(),
122
+ };
123
+
124
+ unsafe {
125
+ rb_sys::rb_thread_call_without_gvl(
126
+ Some(encode_special_without_gvl),
127
+ &mut data as *mut _ as *mut c_void,
128
+ None,
129
+ std::ptr::null_mut(),
130
+ );
131
+ }
132
+
133
+ data.result
35
134
  }
36
135
 
37
136
  pub fn decode(&self, ids: Vec<Rank>) -> Result<String, magnus::Error> {
38
- self.core_bpe.decode(ids).map_err(|e| {
137
+ let mut data = DecodeData {
138
+ core_bpe: &self.core_bpe as *const _,
139
+ ids,
140
+ result: Err(String::new()),
141
+ };
142
+
143
+ unsafe {
144
+ rb_sys::rb_thread_call_without_gvl(
145
+ Some(decode_without_gvl),
146
+ &mut data as *mut _ as *mut c_void,
147
+ None,
148
+ std::ptr::null_mut(),
149
+ );
150
+ }
151
+
152
+ data.result.map_err(|e| {
39
153
  let error = match uncicode_error() {
40
154
  Ok(error) => error,
41
155
  Err(e) => return e,
42
156
  };
43
157
 
44
- magnus::Error::new(error, e.to_string())
158
+ magnus::Error::new(error, e)
45
159
  })
46
160
  }
47
161
  }
@@ -40,6 +40,13 @@ class Tiktoken::Encoding
40
40
  @ext_base_bpe.encode(text, allowed_special)
41
41
  end
42
42
 
43
+ # Encodes the text as a list of integer tokens, including special tokens.
44
+ # @param text [String] The text to encode
45
+ # @return [Array<Integer>] The encoded tokens
46
+ def encode_with_special_tokens(text)
47
+ @ext_base_bpe.encode_with_special_tokens(text)
48
+ end
49
+
43
50
  # Decodes the tokens back into text
44
51
  # @param tokens [Array<Integer>] The tokens to decode
45
52
  # @return [String] The decoded text
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tiktoken
4
- VERSION = "0.0.13"
4
+ VERSION = "0.0.15"
5
5
  end
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.13
4
+ version: 0.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - IAPark
@@ -36,6 +36,7 @@ files:
36
36
  - ".rspec"
37
37
  - ".standard.yml"
38
38
  - ".vscode/settings.json"
39
+ - CHANGELOG.md
39
40
  - Cargo.lock
40
41
  - Cargo.toml
41
42
  - Gemfile
@@ -51,7 +52,6 @@ files:
51
52
  - lib/tiktoken_ruby.rb
52
53
  - lib/tiktoken_ruby/encoding.rb
53
54
  - lib/tiktoken_ruby/version.rb
54
- - script/release
55
55
  - sig/tiktoken_ruby.rbs
56
56
  homepage: https://github.com/IAPark/tiktoken_ruby
57
57
  licenses:
@@ -67,14 +67,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
67
  requirements:
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- version: 3.1.0
70
+ version: 3.2.0
71
71
  required_rubygems_version: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: 3.4.0
76
76
  requirements: []
77
- rubygems_version: 3.6.9
77
+ rubygems_version: 4.0.3
78
78
  specification_version: 4
79
79
  summary: Ruby wrapper for Tiktoken
80
80
  test_files: []
data/script/release DELETED
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- set -e
4
-
5
- if [ -z "${TIKTOKEN_PUBLISH_KEY}" ]; then
6
- echo "Error: TIKTOKEN_PUBLISH_KEY is not set. This is the RubyGems API key to push the gem."
7
- exit 1
8
- fi
9
-
10
- run_id=""
11
- # Parse arguments
12
- while [[ "$#" -gt 0 ]]; do
13
- case $1 in
14
- --run-id)
15
- run_id="$2"
16
- shift 2
17
- ;;
18
- *)
19
- echo "Unknown parameter passed: $1"
20
- exit 1
21
- ;;
22
- esac
23
- done
24
-
25
- if [ -z "${run_id}" ]; then
26
- echo "Error: --run-id is not provided. Please provide the GitHub Action run id for the cross-compile workflow."
27
- exit 1
28
- fi
29
-
30
- version=$(grep VERSION lib/tiktoken_ruby/version.rb | head -n 1 | cut -d'"' -f2)
31
- echo "Building tiktoken_ruby v$version, using artifacts from run $run_id"
32
-
33
- rm -rf pkg/cross-compiled
34
- gh run download "$run_id" -D pkg/cross-compiled
35
-
36
- for gem in pkg/cross-compiled/cross-gem-*/tiktoken_ruby-"$version"*.gem ; do
37
- echo "Publishing $gem"
38
- GEM_HOST_API_KEY="${TIKTOKEN_PUBLISH_KEY}" gem push "$gem" --host https://rubygems.org
39
- done
40
-
41
- # last but not least, the uncompiled gem
42
- bundle exec rake package
43
- GEM_HOST_API_KEY="${TIKTOKEN_PUBLISH_KEY}" gem push "pkg/tiktoken_ruby-$version.gem" --host https://rubygems.org