typesafe-sdk-ruby 0.6.0 → 0.7.0
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 +81 -1
- data/LICENSE +1 -0
- data/README.md +365 -94
- data/docs/COMPATIBILITY.md +119 -0
- data/docs/RELEASING.md +38 -0
- data/docs/UPSTREAM_ISSUES.md +74 -0
- data/lib/typesafe/sdk/assets.rb +326 -0
- data/lib/typesafe/sdk/client.rb +98 -30
- data/lib/typesafe/sdk/compression.rb +50 -0
- data/lib/typesafe/sdk/errors.rb +35 -6
- data/lib/typesafe/sdk/http.rb +58 -9
- data/lib/typesafe/sdk/logging.rb +19 -7
- data/lib/typesafe/sdk/questions.rb +74 -35
- data/lib/typesafe/sdk/resources/models.rb +6 -4
- data/lib/typesafe/sdk/retry.rb +4 -4
- data/lib/typesafe/sdk/types.rb +69 -11
- data/lib/typesafe/sdk/version.rb +3 -2
- data/lib/typesafe-sdk-ruby.rb +30 -0
- metadata +17 -7
data/README.md
CHANGED
|
@@ -4,37 +4,42 @@
|
|
|
4
4
|
[](https://github.com/afurm/typesafe-sdk-ruby/actions/workflows/ci.yml)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
score questions — with retries, timeouts, structured logging, and typed errors.
|
|
7
|
+
A community-maintained Ruby client for [TypeSafe AI](https://typesafe.ai), with typed answer
|
|
8
|
+
objects, image assets, retries, timeouts, cancellation, request compression, and configurable logging.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
TypeSafe's Jev model evaluates state against questions defined by your application. It returns
|
|
11
|
+
structured decisions that your code can use for classification, ranking, and routing.
|
|
12
|
+
Read the [official introduction](https://docs.typesafe.ai/introduction) for the product model.
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
**Unofficial:** this project is not affiliated with, endorsed by, or supported by TypeSafe AI.
|
|
15
|
+
It targets the official [JavaScript SDK](https://github.com/typesafe-ai/typesafe-sdk-js)
|
|
16
|
+
**0.7.0**. Official [JavaScript and Python SDKs](https://docs.typesafe.ai/sdk) are maintained
|
|
17
|
+
by TypeSafe AI.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
19
|
+
**Version:** this README documents Ruby **0.7.0**, targeting JS **0.7.0**.
|
|
20
|
+
Ruby-only corrections add a fourth version component. Check
|
|
21
|
+
[RubyGems](https://rubygems.org/gems/typesafe-sdk-ruby) and
|
|
22
|
+
[GitHub releases](https://github.com/afurm/typesafe-sdk-ruby/releases) for published versions;
|
|
23
|
+
merging changes into `main` does not publish a gem. See the [changelog](CHANGELOG.md)
|
|
24
|
+
for changes and migration notes.
|
|
22
25
|
|
|
23
26
|
## Installation
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
Requires Ruby **3.1 or newer**; CI tests Ruby 3.1, 3.2, 3.3, 3.4, and 4.0.
|
|
26
29
|
|
|
27
30
|
```sh
|
|
28
31
|
gem install typesafe-sdk-ruby
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
For Bundler, add the gem to your application's `Gemfile` and run `bundle install`:
|
|
32
35
|
|
|
33
36
|
```ruby
|
|
34
37
|
gem "typesafe-sdk-ruby"
|
|
35
38
|
```
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
Create an API key in the [TypeSafe console](https://console.typesafe.ai/), then set
|
|
41
|
+
`TYPESAFE_API_KEY` in your environment or application secret store. The SDK reads it
|
|
42
|
+
automatically. See the [official quick start](https://docs.typesafe.ai/introduction/quickstart).
|
|
38
43
|
|
|
39
44
|
## Quickstart
|
|
40
45
|
|
|
@@ -42,159 +47,425 @@ and run `bundle install`.
|
|
|
42
47
|
require "typesafe-sdk-ruby"
|
|
43
48
|
|
|
44
49
|
client = Typesafe::SDK::Client.new
|
|
45
|
-
|
|
46
50
|
response = client.system_one(
|
|
47
51
|
state: { document: "I was charged twice. Please fix this ASAP." },
|
|
48
52
|
questions: {
|
|
49
53
|
category: Typesafe::SDK.choice("What is this ticket about?", {
|
|
50
|
-
billing:
|
|
51
|
-
technical:
|
|
52
|
-
other:
|
|
54
|
+
billing: "Payments, invoices, or refunds",
|
|
55
|
+
technical: "A product error or technical problem",
|
|
56
|
+
other: "A different subject",
|
|
53
57
|
}),
|
|
54
58
|
},
|
|
55
59
|
)
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
category = response[:category]
|
|
62
|
+
puts category.choice
|
|
63
|
+
puts category.confidence
|
|
64
|
+
puts category.probabilities
|
|
65
|
+
puts response.model
|
|
66
|
+
puts response.usage.input_tokens
|
|
58
67
|
```
|
|
59
68
|
|
|
60
|
-
|
|
69
|
+
`response[:category]` and `response["category"]` work interchangeably.
|
|
70
|
+
`response.answers` is a regular Hash with string keys.
|
|
71
|
+
|
|
72
|
+
## Choosing a question type
|
|
61
73
|
|
|
62
|
-
|
|
|
74
|
+
| Builder | Use it for | Result |
|
|
63
75
|
| --- | --- | --- |
|
|
64
|
-
| `noul` |
|
|
65
|
-
| `choice` |
|
|
66
|
-
| `score` |
|
|
76
|
+
| `noul` | A yes/no judgment | `noul`: probability of yes, from 0 to 1 |
|
|
77
|
+
| `choice` | Selecting one named option | `choice`, `probabilities`, `confidence` |
|
|
78
|
+
| `score` | Rating against ordered descriptions | `score`, `legend`, `probabilities`, `confidence` |
|
|
79
|
+
| `box` | Locating something in an image ([Images](#images)) | `box` (`top`, `bottom`, `left`, `right`), `confidence` |
|
|
80
|
+
|
|
81
|
+
Mix question types in a single call. Each question is evaluated against the same state,
|
|
82
|
+
independently of the other questions. Put the meaning in the instructions and criteria;
|
|
83
|
+
question IDs identify answers and are not used for inference.
|
|
84
|
+
See [primitives](https://docs.typesafe.ai/primitives) and the [API reference](https://docs.typesafe.ai/api).
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
response = client.system_one(
|
|
88
|
+
state: "The export fails in Safari, but I can finish the task in Firefox.",
|
|
89
|
+
questions: {
|
|
90
|
+
workaround: Typesafe::SDK.noul("Does the user describe a working alternative?"),
|
|
91
|
+
category: Typesafe::SDK.choice("Which area is affected?", {
|
|
92
|
+
export: "Exporting application data",
|
|
93
|
+
login: "Signing in to the application",
|
|
94
|
+
other: "Another area",
|
|
95
|
+
}),
|
|
96
|
+
severity: Typesafe::SDK.score("How much does this issue affect the user's task?", [
|
|
97
|
+
"The task works; only its appearance is affected",
|
|
98
|
+
"The task needs an alternative method to complete",
|
|
99
|
+
"The task cannot be completed",
|
|
100
|
+
]),
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
puts response[:workaround].noul
|
|
105
|
+
puts response[:severity].score
|
|
106
|
+
puts response[:severity].probabilities["1"]
|
|
107
|
+
puts response[:severity].legend["1"]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Score criteria are an **ordered array**, with positions starting at zero. A returned score
|
|
111
|
+
is a probability-weighted mean and can be fractional. `legend` and `probabilities` retain
|
|
112
|
+
string keys such as `"0"` and `"1"`. See the [Score guide](https://docs.typesafe.ai/primitives/score).
|
|
113
|
+
|
|
114
|
+
State, instructions, and criterion descriptions can also contain JSON objects or arrays.
|
|
115
|
+
Optional noul criteria can describe either outcome:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
question = Typesafe::SDK.noul("Is this an explicit cancellation request?", criteria: {
|
|
119
|
+
true: "The customer asks to end the subscription",
|
|
120
|
+
false: "The customer only asks about cancellation terms",
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
See [structured questions](https://docs.typesafe.ai/primitives/advanced) for more examples.
|
|
125
|
+
|
|
126
|
+
From Ruby **0.6.0.1**, builders and raw question hashes are checked before sending:
|
|
127
|
+
|
|
128
|
+
- State must be a string, object, array, or image; `nil` is rejected, while `""`, `{}`, and `[]` are allowed.
|
|
129
|
+
- Noul needs instructions or at least one non-nil true/false outcome description; its criteria,
|
|
130
|
+
when given, must be a Hash (from 0.7.0).
|
|
131
|
+
- Score needs **2–10** levels. A nil level is rejected; use `""` explicitly to keep an
|
|
132
|
+
undescribed position. The SDK never drops or renumbers levels.
|
|
133
|
+
- Choice needs **1–255** options. Nil descriptions remain valid for choice labels.
|
|
134
|
+
- Question names must not be empty strings.
|
|
135
|
+
|
|
136
|
+
Failures raise `Typesafe::SDK::TypeSafeError` without an HTTP request. These checks address
|
|
137
|
+
request shapes reported as rejected by the API. JS 0.7.0 now rejects nil state and empty noul
|
|
138
|
+
questions too; the score and choice bounds, nil score levels, and empty names remain Ruby-only checks.
|
|
139
|
+
See the [upstream issue audit](docs/UPSTREAM_ISSUES.md) for evidence and limitations.
|
|
140
|
+
|
|
141
|
+
From Ruby **0.7.0**, as in JS 0.7.0, non-finite floats such as `Float::NAN` are sent as the
|
|
142
|
+
strings `"NaN"`, `"Infinity"`, and `"-Infinity"`, because JSON cannot represent them. Every answer
|
|
143
|
+
also responds to `to_h`, which returns all of its fields, including ones this SDK doesn't model.
|
|
144
|
+
Usage token counts are `nil` when the API doesn't report them.
|
|
145
|
+
|
|
146
|
+
## Images
|
|
147
|
+
|
|
148
|
+
Added in Ruby **0.7.0**. Create an image with `Typesafe::SDK.image` from file bytes (a binary
|
|
149
|
+
`String`), a `Pathname` (read when the request is sent), or an IO (read immediately), or with
|
|
150
|
+
`Typesafe::SDK::Image.from_base64` from base64 text. Images must be PNG or JPEG. The API detects
|
|
151
|
+
the format from the file bytes and rejects other formats, such as WebP and GIF, with a
|
|
152
|
+
`BadRequestError`. You can use an image in a request in three ways, and you can combine them.
|
|
153
|
+
|
|
154
|
+
Image support is an experimental API feature. Opt in by sending the
|
|
155
|
+
`TypeSafe-Danger-Zone-Images: true` header for a client, or per call with `headers:`:
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
client = Typesafe::SDK::Client.new(default_headers: { "TypeSafe-Danger-Zone-Images" => "true" })
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Place the image in the content.** Put it anywhere in `state`, question instructions, or criteria:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
require "pathname"
|
|
165
|
+
|
|
166
|
+
photo = Typesafe::SDK.image(Pathname("returns/RMA-1042.jpg"))
|
|
167
|
+
response = client.system_one(
|
|
168
|
+
state: { claim: "The mug arrived with a chipped rim.", photo: photo },
|
|
169
|
+
questions: {
|
|
170
|
+
damaged: Typesafe::SDK.noul("Is the product in the photo damaged?"),
|
|
171
|
+
severity: Typesafe::SDK.score("How severe is the damage?", %w[none cosmetic unusable]),
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
puts response[:damaged].noul, response[:severity].score
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Write the image into text with `with_assets`.** Ruby has no tagged template literals, so pass
|
|
178
|
+
the text and images as separate arguments, in order; other values are converted with `to_s`.
|
|
179
|
+
`join_assets` joins a list like `Array#join`, with `","` as the default separator, as in
|
|
180
|
+
JavaScript's `withAssets.join`:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
upload = Typesafe::SDK.image(params[:photo].read)
|
|
184
|
+
catalog = Typesafe::SDK.image(Pathname("catalog/mug.png"))
|
|
185
|
+
uploads = params[:photos].map { |file| Typesafe::SDK.image(file.read) }
|
|
186
|
+
|
|
187
|
+
response = client.system_one(
|
|
188
|
+
state: Typesafe::SDK.with_assets("The seller uploaded ", Typesafe::SDK.join_assets(uploads, ", "), "."),
|
|
189
|
+
questions: {
|
|
190
|
+
same_product: Typesafe::SDK.noul(Typesafe::SDK.with_assets("Does ", upload, " show the product in ", catalog, "?")),
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Each image is sent once, however many times it is used. Plain string interpolation, such as
|
|
196
|
+
`"Compare #{upload}"`, writes only the image's reference, so the call raises `TypeSafeError`
|
|
197
|
+
before the request is sent.
|
|
198
|
+
|
|
199
|
+
**Choose your own references with `assets:`.** Pass images keyed by names of your choice, then
|
|
200
|
+
use those names in plain strings, such as prompts stored in configuration:
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
response = client.system_one(
|
|
204
|
+
state: "A customer returned <delivered>. The catalog photo is <catalog>.",
|
|
205
|
+
questions: { matches: Typesafe::SDK.noul("Is <delivered> the product shown in <catalog>?") },
|
|
206
|
+
assets: {
|
|
207
|
+
"<delivered>" => Typesafe::SDK.image(File.binread("returns/RMA-1042.jpg")),
|
|
208
|
+
"<catalog>" => Typesafe::SDK.image(Pathname("catalog/mug.png")),
|
|
209
|
+
},
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Use distinct delimiters, such as `<delivered>`, so names don't match ordinary text. An image in
|
|
214
|
+
`assets:` that also appears in content or `with_assets` text is written there under its name.
|
|
215
|
+
Every name in `assets:` must appear in the request; otherwise the call raises `TypeSafeError`
|
|
216
|
+
before sending.
|
|
217
|
+
|
|
218
|
+
**Locate something with `box`.** A box question locates what its instructions describe. The
|
|
219
|
+
answer's `box` holds the edges as fractions of the image's height (`top`, `bottom`) and width
|
|
220
|
+
(`left`, `right`). Its `confidence` is `nil` when the API doesn't report one. Use the
|
|
221
|
+
`jev-preview` model for box questions:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
response = client.system_one(
|
|
225
|
+
state: Typesafe::SDK.image(Pathname("returns/RMA-1042.jpg")),
|
|
226
|
+
questions: { damage: Typesafe::SDK.box("The damaged area of the product") },
|
|
227
|
+
model: "jev-preview",
|
|
228
|
+
)
|
|
229
|
+
box = response[:damage].box
|
|
230
|
+
puts [box.top, box.bottom, box.left, box.right].inspect
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Requests with images are sent as `multipart/form-data`, with each image's file bytes in its own part.
|
|
234
|
+
|
|
235
|
+
## Using confidence
|
|
236
|
+
|
|
237
|
+
Choice and Score include confidence derived from their probability distributions. Confidence
|
|
238
|
+
is distinct from the probability of the selected option and is not a guarantee of correctness.
|
|
239
|
+
Noul returns the probability of yes and has no separate confidence field.
|
|
240
|
+
See the [official confidence guide](https://docs.typesafe.ai/confidence).
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
category = response[:category]
|
|
244
|
+
threshold = 0.8 # Illustrative: evaluate a suitable threshold on your own labeled examples.
|
|
245
|
+
puts(category.confidence >= threshold ? "Route to #{category.choice}" : "Needs review")
|
|
246
|
+
```
|
|
67
247
|
|
|
68
248
|
## Configuration
|
|
69
249
|
|
|
70
250
|
Explicit options take precedence over environment variables, then SDK defaults.
|
|
251
|
+
Blank environment values are ignored for optional settings. An API key is required.
|
|
252
|
+
From Ruby 0.6.0.1, outer spaces, tabs, and line endings are trimmed from keys; blank keys,
|
|
253
|
+
non-ASCII text, embedded whitespace, and control characters raise `TypeSafeError` at
|
|
254
|
+
construction. Error messages do not include the key.
|
|
71
255
|
|
|
72
256
|
| Option | Environment variable | Default |
|
|
73
257
|
| --- | --- | --- |
|
|
74
|
-
| `api_key:` | `TYPESAFE_API_KEY` |
|
|
258
|
+
| `api_key:` | `TYPESAFE_API_KEY` | Required |
|
|
75
259
|
| `base_url:` | `TYPESAFE_BASE_URL` | `https://api.typesafe.ai` |
|
|
76
260
|
| `default_model:` | `TYPESAFE_DEFAULT_MODEL` | `jev-latest` |
|
|
77
261
|
| `log_level:` | `TYPESAFE_LOG_LEVEL` | `warn` |
|
|
262
|
+
| `compression:` | None | `:zstd` when `zstd-ruby` is installed, otherwise `false` |
|
|
78
263
|
|
|
79
264
|
```ruby
|
|
80
265
|
client = Typesafe::SDK::Client.new(
|
|
81
|
-
|
|
82
|
-
base_url: "https://api.typesafe.ai",
|
|
83
|
-
default_model: "jev-latest",
|
|
84
|
-
log_level: :info, # :debug, :info, :warn, :error, :off
|
|
85
|
-
timeout: 10, # seconds per attempt
|
|
266
|
+
timeout: 10, # Seconds per attempt, including connection setup and response body receipt.
|
|
86
267
|
retry_policy: { max_retries: 2, backoff_initial_ms: 500 },
|
|
87
|
-
|
|
268
|
+
log_level: :info, # :debug, :info, :warn, :error, or :off
|
|
269
|
+
default_headers: { "X-My-App" => "support" },
|
|
88
270
|
)
|
|
89
271
|
```
|
|
90
272
|
|
|
273
|
+
### Request compression
|
|
274
|
+
|
|
275
|
+
Added in Ruby **0.7.0**. When the optional [`zstd-ruby`](https://rubygems.org/gems/zstd-ruby) gem
|
|
276
|
+
is available (add `gem "zstd-ruby"` to your Gemfile), the client compresses request bodies of
|
|
277
|
+
1 KiB or more with zstd, which reduces upload size and time, especially for large states and
|
|
278
|
+
images. Smaller bodies are sent uncompressed. Without the gem, all bodies are sent uncompressed;
|
|
279
|
+
`compression: :zstd` then raises `TypeSafeError`. Pass `compression: false` to send all bodies
|
|
280
|
+
uncompressed, for example to an API that doesn't accept compressed requests:
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
client = Typesafe::SDK::Client.new(compression: false)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`jev-latest` is a moving alias. For reproducible deployments, select an explicit model from
|
|
287
|
+
`client.models.list` and set `default_model:` or a per-call `model:`. See
|
|
288
|
+
[available models and aliases](https://docs.typesafe.ai/models).
|
|
289
|
+
|
|
91
290
|
## Retries and timeouts
|
|
92
291
|
|
|
93
|
-
|
|
94
|
-
with
|
|
95
|
-
|
|
292
|
+
By default, the SDK retries HTTP 408, 429, and 5xx responses, connection failures, and timeouts,
|
|
293
|
+
with up to two retries after the initial attempt. Backoff starts at 500 ms, doubles up to
|
|
294
|
+
5,000 ms, and uses up to 25% downward jitter. Server `Retry-After` and `retry-after-ms` delays
|
|
295
|
+
are honored up to 60,000 ms; larger delays fall back to backoff. From Ruby 0.6.0.1, blank
|
|
296
|
+
or malformed delay headers also fall back to backoff; an explicit zero remains valid.
|
|
297
|
+
A blank `retry-after-ms` still permits a valid `Retry-After` header to be used.
|
|
298
|
+
|
|
299
|
+
Each attempt receives a fresh timeout, so total call time can include several attempts and
|
|
300
|
+
backoff. Override settings per client or per call:
|
|
96
301
|
|
|
97
302
|
```ruby
|
|
98
303
|
client.system_one(
|
|
99
|
-
state: "
|
|
100
|
-
questions: {
|
|
304
|
+
state: "A refund request",
|
|
305
|
+
questions: { billing: Typesafe::SDK.noul("Is this about billing?") },
|
|
101
306
|
timeout: 30,
|
|
102
|
-
retry_policy: { max_retries: 0 },
|
|
307
|
+
retry_policy: { max_retries: 0 },
|
|
103
308
|
)
|
|
104
309
|
```
|
|
105
310
|
|
|
311
|
+
The full retry policy supports `max_retries`, `backoff_initial_ms`, `backoff_max_ms`,
|
|
312
|
+
`backoff_jitter`, `http_statuses`, `respect_retry_after`, `max_retry_after_ms`,
|
|
313
|
+
`api_connection_error`, and `api_timeout_error`. Overrides merge field by field.
|
|
314
|
+
Nil boolean flags inherit the existing setting; explicit `false` disables that behavior.
|
|
315
|
+
|
|
106
316
|
## Error handling
|
|
107
317
|
|
|
108
318
|
```ruby
|
|
109
319
|
begin
|
|
110
|
-
client.system_one(
|
|
320
|
+
client.system_one(
|
|
321
|
+
state: "A refund request",
|
|
322
|
+
questions: { billing: Typesafe::SDK.noul("Is this about billing?") },
|
|
323
|
+
)
|
|
111
324
|
rescue Typesafe::SDK::RateLimitError => e
|
|
112
|
-
|
|
325
|
+
warn "Rate limited; suggested retry delay: #{e.retry_after_ms.inspect}ms"
|
|
113
326
|
rescue Typesafe::SDK::APIError => e
|
|
114
|
-
warn "API error #{e.status}
|
|
327
|
+
warn "API error #{e.status}; request ID: #{e.request_id}"
|
|
328
|
+
rescue Typesafe::SDK::APIConnectionError => e
|
|
329
|
+
warn e.message
|
|
115
330
|
end
|
|
116
331
|
```
|
|
117
332
|
|
|
118
|
-
|
|
333
|
+
Errors reach your code after any configured retries. All SDK errors inherit from
|
|
334
|
+
`Typesafe::SDK::TypeSafeError`:
|
|
335
|
+
|
|
336
|
+
| Error | Meaning |
|
|
337
|
+
| --- | --- |
|
|
338
|
+
| `BadRequestError` | HTTP 400 |
|
|
339
|
+
| `AuthenticationError` | HTTP 401 |
|
|
340
|
+
| `PaymentRequiredError` | HTTP 402; added in 0.6.0.1 |
|
|
341
|
+
| `PermissionDeniedError` | HTTP 403 |
|
|
342
|
+
| `NotFoundError` | HTTP 404 |
|
|
343
|
+
| `ConflictError` | HTTP 409; added in 0.6.0.1 |
|
|
344
|
+
| `PayloadTooLargeError` | HTTP 413; added in 0.6.0.1 |
|
|
345
|
+
| `UnprocessableEntityError` | HTTP 422 |
|
|
346
|
+
| `RateLimitError` | HTTP 429; exposes `retry_after_ms` |
|
|
347
|
+
| `InternalServerError` | HTTP 5xx |
|
|
348
|
+
| `APIError` | Other non-2xx responses; exposes status, headers, body, and request ID |
|
|
349
|
+
| `APIConnectionError` | Connection or response-body delivery failure |
|
|
350
|
+
| `APITimeoutError` | Subclass of `APIConnectionError`; exposes `timeout_ms` |
|
|
351
|
+
| `APIUserAbortError` | Caller cancellation; never retried |
|
|
352
|
+
|
|
353
|
+
From Ruby 0.7.0, when an HTML page from Cloudflare answers instead of the API, for example because
|
|
354
|
+
its firewall matched text such as a shell command in `state`, the error message says so and
|
|
355
|
+
includes the `cf-ray` ID to report to TypeSafe support. The error class and `body` are unchanged.
|
|
356
|
+
|
|
357
|
+
## Response metadata and additional fields
|
|
358
|
+
|
|
359
|
+
Added for Ruby **0.6.0.1**:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
result = client.system_one(
|
|
363
|
+
state: "A refund request",
|
|
364
|
+
questions: { billing: Typesafe::SDK.noul("Is this about billing?") },
|
|
365
|
+
with_response: true,
|
|
366
|
+
)
|
|
367
|
+
puts result.data[:billing].noul
|
|
368
|
+
puts result.request_id
|
|
369
|
+
puts result.response.status
|
|
370
|
+
```
|
|
119
371
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`InternalServerError` (5xx)
|
|
125
|
-
- `Typesafe::SDK::APIConnectionError` — DNS, TLS, connection failures
|
|
126
|
-
- `Typesafe::SDK::APITimeoutError`
|
|
127
|
-
- `Typesafe::SDK::APIUserAbortError` — caller cancellation
|
|
372
|
+
`client.models.list(with_response: true)` wraps the model cards the same way. The response
|
|
373
|
+
exposes case-insensitive `headers` and a parsed `body`.
|
|
374
|
+
`extra_body: { new_option: nil }` forwards additional JSON fields; named `state`, `questions`,
|
|
375
|
+
and `model` arguments take precedence. The API decides whether a field is supported.
|
|
128
376
|
|
|
129
377
|
## Cancellation
|
|
130
378
|
|
|
131
379
|
```ruby
|
|
132
380
|
signal = Typesafe::SDK::Signal.new
|
|
133
|
-
Thread.new { sleep 5; signal.cancel }
|
|
134
|
-
|
|
135
|
-
client.system_one(
|
|
136
|
-
|
|
381
|
+
canceller = Thread.new { sleep 5; signal.cancel }
|
|
382
|
+
begin
|
|
383
|
+
client.system_one(
|
|
384
|
+
state: "A refund request",
|
|
385
|
+
questions: { billing: Typesafe::SDK.noul("Is this about billing?") },
|
|
386
|
+
signal: signal,
|
|
387
|
+
)
|
|
388
|
+
rescue Typesafe::SDK::APIUserAbortError
|
|
389
|
+
warn "Request canceled"
|
|
390
|
+
ensure
|
|
391
|
+
canceller.kill.join
|
|
392
|
+
end
|
|
137
393
|
```
|
|
138
394
|
|
|
395
|
+
From Ruby 0.6.0.1, cancellation interrupts active requests and retry backoff, and cleans up
|
|
396
|
+
the request worker and socket before returning.
|
|
397
|
+
|
|
139
398
|
## Listing models
|
|
140
399
|
|
|
141
400
|
```ruby
|
|
142
401
|
client.models.list.each do |model|
|
|
143
|
-
puts "#{model.name}: #{model.description}"
|
|
402
|
+
puts "#{model.name}: #{model.description} (#{model.release_date})"
|
|
144
403
|
end
|
|
145
404
|
```
|
|
146
405
|
|
|
147
|
-
## Logging
|
|
148
|
-
|
|
149
|
-
The default logger writes to `$stderr` with a `[typesafe-sdk-ruby]` prefix. `info` logs request
|
|
150
|
-
summaries; `debug` adds headers (credentials redacted) and bodies. Pass any object responding
|
|
151
|
-
to `debug`/`info`/`warn`/`error`:
|
|
152
|
-
|
|
153
|
-
```ruby
|
|
154
|
-
client = Typesafe::SDK::Client.new(logger: Rails.logger, log_level: :info)
|
|
155
|
-
```
|
|
406
|
+
## Logging and Rails
|
|
156
407
|
|
|
157
|
-
|
|
408
|
+
The default logger writes to `$stderr` with a `[typesafe-ai]` prefix. `info` includes request
|
|
409
|
+
summaries; `debug` adds redacted credential headers and request/response bodies. Bodies may
|
|
410
|
+
contain application data and are **not** redacted. Image bytes are not logged; requests show each
|
|
411
|
+
image's multipart part name. From Ruby 0.7.0, `x-api-key` values are always fully masked, and
|
|
412
|
+
authorization headers keep only an alphabetic scheme, such as `Bearer`, that a credential follows.
|
|
158
413
|
|
|
159
|
-
|
|
160
|
-
wrapped initializer:
|
|
414
|
+
From Ruby 0.6.0.1, standard Ruby `Logger` and `Rails.logger` also accept debug details:
|
|
161
415
|
|
|
162
416
|
```ruby
|
|
163
|
-
# config/initializers/typesafe.rb
|
|
164
|
-
TYPESAFE = Typesafe::SDK::Client.new(log_level: :info)
|
|
417
|
+
# config/initializers/typesafe.rb; configure TYPESAFE_API_KEY through your secret store.
|
|
418
|
+
TYPESAFE = Typesafe::SDK::Client.new(logger: Rails.logger, log_level: :info)
|
|
165
419
|
```
|
|
166
420
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
421
|
+
Call `TYPESAFE.system_one(...)` from application code. In plain Ruby, use
|
|
422
|
+
`require "logger"` and pass `logger: Logger.new($stderr)`; add the `logger` gem to your
|
|
423
|
+
application if your Ruby version does not provide it by default.
|
|
424
|
+
|
|
425
|
+
## Coming from JavaScript
|
|
426
|
+
|
|
427
|
+
| JavaScript | Ruby |
|
|
428
|
+
| --- | --- |
|
|
429
|
+
| `new TypeSafeClient()` | `Typesafe::SDK::Client.new` |
|
|
430
|
+
| `systemOne(request, options)` | `system_one(state:, questions:, **options)` |
|
|
431
|
+
| `apiKey`, `baseURL`, `defaultModel` | `api_key:`, `base_url:`, `default_model:` |
|
|
432
|
+
| `timeout` in milliseconds | `timeout:` in seconds |
|
|
433
|
+
| `retry: { maxRetries: 0 }` | `retry_policy: { max_retries: 0 }` |
|
|
434
|
+
| `.withResponse()` | `with_response: true` (Ruby 0.6.0.1+) |
|
|
435
|
+
| `AbortController` | `Typesafe::SDK::Signal` |
|
|
436
|
+
| `image(blob)`, `Image.fromBase64(text)` | `Typesafe::SDK.image(bytes_or_pathname)`, `Typesafe::SDK::Image.from_base64(text)` |
|
|
437
|
+
| `` withAssets`Compare ${a} with ${b}` `` | `Typesafe::SDK.with_assets("Compare ", a, " with ", b)` |
|
|
438
|
+
| `withAssets.join(images, ", ")` | `Typesafe::SDK.join_assets(images, ", ")` |
|
|
439
|
+
| `compression: false` | `compression: false` |
|
|
440
|
+
| `answers.category` | `response[:category]` or `response.answers["category"]` |
|
|
441
|
+
|
|
442
|
+
Ruby calls are synchronous and return Ruby answer objects. They do not provide TypeScript
|
|
443
|
+
compile-time inference, Fetch streams, connection pooling, or automatic redirect following.
|
|
444
|
+
See [tested compatibility and intentional differences](docs/COMPATIBILITY.md).
|
|
445
|
+
|
|
446
|
+
## Development and contributing
|
|
184
447
|
|
|
185
448
|
```sh
|
|
186
449
|
bundle install
|
|
187
|
-
bundle exec rake
|
|
188
|
-
bundle exec rspec # specs only
|
|
189
|
-
bundle exec rubocop # lint only
|
|
450
|
+
bundle exec rake # Tests and RuboCop; no API key or external API calls required.
|
|
190
451
|
```
|
|
191
452
|
|
|
192
|
-
|
|
453
|
+
Native transport tests bind loopback sockets. CI also builds and installs the packaged gem.
|
|
454
|
+
For an explicit live check, follow [live verification](docs/COMPATIBILITY.md#live-verification).
|
|
455
|
+
The [demo](examples/demo.rb) uses real API calls and can be run with
|
|
456
|
+
`bundle exec ruby examples/demo.rb` after configuring your key.
|
|
457
|
+
|
|
458
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md), the [PR template](.github/PULL_REQUEST_TEMPLATE.md),
|
|
459
|
+
and the [release process](docs/RELEASING.md). Changes are reviewed and merged by the repository owner.
|
|
460
|
+
|
|
461
|
+
## Support and security
|
|
193
462
|
|
|
194
|
-
|
|
195
|
-
[
|
|
196
|
-
[
|
|
463
|
+
Use [issues](https://github.com/afurm/typesafe-sdk-ruby/issues) for Ruby client bugs and
|
|
464
|
+
[discussions](https://github.com/afurm/typesafe-sdk-ruby/discussions) for usage questions.
|
|
465
|
+
See [SUPPORT.md](SUPPORT.md) for API/account questions and useful report details.
|
|
466
|
+
Report vulnerabilities privately using [SECURITY.md](SECURITY.md).
|
|
467
|
+
All participation follows our [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
197
468
|
|
|
198
469
|
## License
|
|
199
470
|
|
|
200
|
-
|
|
471
|
+
[MIT](LICENSE), including attribution to the upstream TypeSafe JavaScript SDK.
|