ruby_decision_model 0.2.0 → 0.3.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 +20 -0
- data/README.md +59 -10
- data/lib/ruby_decision_model/client.rb +32 -12
- data/lib/ruby_decision_model/media.rb +36 -0
- data/lib/ruby_decision_model/providers/base.rb +18 -4
- data/lib/ruby_decision_model/providers/cloudflare.rb +17 -3
- data/lib/ruby_decision_model/version.rb +1 -1
- data/lib/ruby_decision_model.rb +1 -0
- metadata +8 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d92e5f4d5ae068f244d1adb69f59f536361046d28fe58923f0b347650122df1
|
|
4
|
+
data.tar.gz: 8e6a26811a717449fc52e4c53f076931fa1998fa28e4ec9937df27eff3bfd0f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c100363ee1b8f540916da3e32a051e3de427dcb5625a63254604cc5388f9059aa2df74497d3a1a449d4a6121f366be36de0b548b03966f95616b65d07e0d3723
|
|
7
|
+
data.tar.gz: efcc1c0abf9412978ca89dc8777c91950d9cf90600b1bc6444d51cf8fedde10b5b9399b9af16105821f3773d28ae2905fed30563558faefd8e50bdd9ee981146
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-10-12
|
|
4
|
+
|
|
5
|
+
Cloudflare's `clef-omni` reads audio and video.
|
|
6
|
+
|
|
7
|
+
- `Client#ask` takes `audio:` and `videos:`, arrays of base64 data URLs, for
|
|
8
|
+
`clef-omni`. Cloudflare reads WAV or MP3 audio and MP4 or WebM video. Added
|
|
9
|
+
`RubyDecisionModel::Media.data_url` and `.from_file`, which build data URLs
|
|
10
|
+
for images, audio, and video.
|
|
11
|
+
- When the provider or model doesn't read audio or video, including `clef`
|
|
12
|
+
and `clef-flash`, the client raises `RequestError` before sending.
|
|
13
|
+
- `cloudflare/clef-omni` and `@cf/cloudflare/clef-omni` resolve to
|
|
14
|
+
`clef-omni` on the Cloudflare provider. In 0.2.0 they raised
|
|
15
|
+
`ConfigurationError`. The bare `clef-omni` already worked.
|
|
16
|
+
- New provider hooks `supports_audio?(model)` and `supports_videos?(model)`,
|
|
17
|
+
both false by default. `Base#request_body` accepts `audio:` and `videos:`.
|
|
18
|
+
- `rake smoke` takes `SMOKE_AUDIO` and `SMOKE_VIDEO`.
|
|
19
|
+
- Hosted `clef-flash` now has a 24,576-token context window. Cloudflare cuts
|
|
20
|
+
a text state that runs past the window to fit instead of rejecting it. The
|
|
21
|
+
gem is unchanged.
|
|
22
|
+
|
|
3
23
|
## 0.2.0 - 2026-10-06
|
|
4
24
|
|
|
5
25
|
The decision models that shipped after Jev, behind the same `Client`.
|
data/README.md
CHANGED
|
@@ -127,7 +127,9 @@ client = RubyDecisionModel::Client.new(provider: provider)
|
|
|
127
127
|
|
|
128
128
|
Requests go to
|
|
129
129
|
`https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/run/@cf/cloudflare/<model>`.
|
|
130
|
-
The default model is `clef`; `clef-flash` is the smaller, faster one
|
|
130
|
+
The default model is `clef`; `clef-flash` is the smaller, faster one, and
|
|
131
|
+
`clef-omni` also reads audio and video (see
|
|
132
|
+
[Images, audio, and video](#images-audio-and-video)). The body
|
|
131
133
|
is System One. The provider unwraps Cloudflare's `{"result": ...}` envelope,
|
|
132
134
|
and a body with `"success": false` raises `InvalidResponse` carrying
|
|
133
135
|
Cloudflare's error text. The account id and model end up in the URL path, so
|
|
@@ -138,6 +140,11 @@ raises `ConfigurationError`.
|
|
|
138
140
|
Usage reports tokens with no cost, and `response.request_id` is the `cf-ray`
|
|
139
141
|
header.
|
|
140
142
|
|
|
143
|
+
Hosted `clef-flash` has a 24,576-token context window; `clef` and `clef-omni`
|
|
144
|
+
have about 64k. Cloudflare cuts a text state that runs past the window to
|
|
145
|
+
fit and returns no error. Images, audio, and video that don't fit fail the
|
|
146
|
+
request.
|
|
147
|
+
|
|
141
148
|
### Perplexity (pplx-decider)
|
|
142
149
|
|
|
143
150
|
```ruby
|
|
@@ -206,7 +213,9 @@ Databricks, and System One servers default to 30, since Perplexity documents
|
|
|
206
213
|
responses of up to 23 seconds on large inputs and a local server may load the
|
|
207
214
|
model on the first request. Connecting gets 5 seconds either way. A number
|
|
208
215
|
passed as `timeout:` sets both, as in 0.1.0. Through OpenRouter, pass a
|
|
209
|
-
longer `timeout:` yourself for large inputs to slower models.
|
|
216
|
+
longer `timeout:` yourself for large inputs to slower models. Audio and
|
|
217
|
+
video take longer than text: Cloudflare reports about 1.5 seconds for a
|
|
218
|
+
21-second video with sound. Pass a longer `timeout:` for long clips.
|
|
210
219
|
|
|
211
220
|
### Model aliases
|
|
212
221
|
|
|
@@ -225,26 +234,31 @@ whatever the provider returned.
|
|
|
225
234
|
| `"luna"`, `"gpt-6-luna"` | `openai/gpt-6-luna-decisions` | `gpt-6-luna` (OpenAI) |
|
|
226
235
|
| `"openai/gpt-6-luna-decisions"`, `"openai/gpt-6-luna"`, `"gpt-6-luna-decisions"` | as given | `gpt-6-luna` (OpenAI) |
|
|
227
236
|
| `"clef"`, `"clef-flash"` | `cloudflare/clef`, `cloudflare/clef-flash` | as given (Cloudflare) |
|
|
228
|
-
| `"cloudflare/clef"`, `"cloudflare/clef-flash"` | as given | `clef`, `clef-flash` (Cloudflare) |
|
|
229
|
-
| `"@cf/cloudflare/clef"`, `"@cf/cloudflare/clef-flash"` | as given | `clef`, `clef-flash` (Cloudflare) |
|
|
237
|
+
| `"cloudflare/clef"`, `"cloudflare/clef-flash"`, `"cloudflare/clef-omni"` | as given | `clef`, `clef-flash`, `clef-omni` (Cloudflare) |
|
|
238
|
+
| `"@cf/cloudflare/clef"`, `"@cf/cloudflare/clef-flash"`, `"@cf/cloudflare/clef-omni"` | as given | `clef`, `clef-flash`, `clef-omni` (Cloudflare) |
|
|
230
239
|
| `"pplx-decider"` | `perplexity/pplx-decider-v1-27b` | `pplx-decider-v1.1-27b` (Perplexity) |
|
|
231
240
|
| `"pplx-decider-v1-27b"` | `perplexity/pplx-decider-v1-27b` | as given (Perplexity) |
|
|
232
241
|
| `"perplexity/pplx-decider-v1-27b"` | as given | `pplx-decider-v1-27b` (Perplexity) |
|
|
233
242
|
| anything else | as given | as given |
|
|
234
243
|
|
|
235
244
|
OpenRouter carries pplx-decider v1 only, so `"pplx-decider"` means v1 there
|
|
236
|
-
and v1.1 on Perplexity's own API.
|
|
245
|
+
and v1.1 on Perplexity's own API. OpenRouter doesn't list `clef-omni` as of
|
|
246
|
+
October 2026, so it has no alias there; on Cloudflare pass `"clef-omni"`.
|
|
237
247
|
|
|
238
248
|
### Writing a provider
|
|
239
249
|
|
|
240
250
|
Subclass `RubyDecisionModel::Providers::Base` and define `name`, `env_var`,
|
|
241
251
|
`default_base_url`, `endpoint_path`, and `default_model`. Optional hooks:
|
|
242
|
-
`aliases`, `reports_cost?`, `supports_images?`, `
|
|
252
|
+
`aliases`, `reports_cost?`, `supports_images?`, `supports_audio?(model)`,
|
|
253
|
+
`supports_videos?(model)`, `request_id_header`,
|
|
243
254
|
`requires_api_key?`, `default_timeout`, `error_message`, and `validate!`. When the wire format differs from
|
|
244
255
|
System One, override `request_body` to encode and `normalize_response` to turn
|
|
245
256
|
the parsed body back into System One answers keyed by question id; the
|
|
246
257
|
OpenAI provider shows both directions. Override `url(model)` when the model
|
|
247
|
-
belongs in the path.
|
|
258
|
+
belongs in the path. A provider that answers true from `supports_audio?` or
|
|
259
|
+
`supports_videos?` gets `audio:` or `videos:` passed to `request_body`, so
|
|
260
|
+
an override of `request_body` has to accept them. Pass an instance as
|
|
261
|
+
`provider:`.
|
|
248
262
|
|
|
249
263
|
## Questions and answers
|
|
250
264
|
|
|
@@ -268,7 +282,7 @@ approximately 1; treat them as calibrated, not normalized. Noul
|
|
|
268
282
|
`probabilities` are always `{"true" => p, "false" => 1 - p}`. Most providers
|
|
269
283
|
send only the probability, and the client fills in the split.
|
|
270
284
|
|
|
271
|
-
## Images
|
|
285
|
+
## Images, audio, and video
|
|
272
286
|
|
|
273
287
|
OpenAI, Cloudflare, Perplexity, and System One servers read images. Pass them
|
|
274
288
|
as base64 data URLs; no provider fetches a remote URL.
|
|
@@ -290,6 +304,39 @@ OpenAI, the `images` field for Cloudflare and System One servers, and
|
|
|
290
304
|
Databricks don't take images, so the client raises `RequestError` before
|
|
291
305
|
sending. Size and count limits vary by vendor and are enforced server side.
|
|
292
306
|
|
|
307
|
+
Cloudflare's `clef-omni` also reads audio (WAV or MP3) and video (MP4 or
|
|
308
|
+
WebM), passed as `audio:` and `videos:`. `RubyDecisionModel::Media.from_file`
|
|
309
|
+
and `.data_url` build data URLs for all three kinds, and `Images` still works
|
|
310
|
+
for images.
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
client = RubyDecisionModel::Client.new(provider: :cloudflare, model: "clef-omni")
|
|
314
|
+
|
|
315
|
+
client.ask(
|
|
316
|
+
state: "Customer sent a photo, a voice note, and a short video of the fault.",
|
|
317
|
+
questions: { "faulty" => RubyDecisionModel::Questions.noul("Is the device faulty?") },
|
|
318
|
+
images: [RubyDecisionModel::Media.from_file("device.jpg")],
|
|
319
|
+
audio: [RubyDecisionModel::Media.from_file("note.mp3")],
|
|
320
|
+
videos: [RubyDecisionModel::Media.from_file("fault.mp4")]
|
|
321
|
+
)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
On Cloudflare they go in the `images`, `audio`, and `videos` fields. A
|
|
325
|
+
provider or model that doesn't read audio or video, including `clef` and
|
|
326
|
+
`clef-flash`, makes the client raise `RequestError` before sending.
|
|
327
|
+
Cloudflare documents up to 4 images, 4 audio clips, and 2 videos per request,
|
|
328
|
+
enforced server side.
|
|
329
|
+
|
|
330
|
+
The client checks only that each item is a data URL of the right kind
|
|
331
|
+
(`data:image/`, `data:audio/`, or `data:video/`). It does not decode the
|
|
332
|
+
payload or measure it, and `from_file` reads the whole file into memory.
|
|
333
|
+
Format, size, and count are the provider's to enforce, so check uploads you
|
|
334
|
+
don't control before you pass them on.
|
|
335
|
+
|
|
336
|
+
A retry sends the whole body again, so with the default policy a request
|
|
337
|
+
carrying large clips can upload up to three times. Build the client with
|
|
338
|
+
`retry: { max_retries: 0 }` to turn retries off for those calls.
|
|
339
|
+
|
|
293
340
|
## Retries
|
|
294
341
|
|
|
295
342
|
Retry behaviour follows the official Typesafe SDKs and lives in
|
|
@@ -338,7 +385,7 @@ return is still accepted and treated as having no headers, which means no
|
|
|
338
385
|
| Error | Meaning |
|
|
339
386
|
| --- | --- |
|
|
340
387
|
| `ConfigurationError` | No provider could be resolved, a missing key or setting (api_key, account_id, base_url), unknown provider, a `model:` Databricks can't take, or bad `retry:` value |
|
|
341
|
-
| `RequestError` | Questions hash was empty, images were not data URLs or went to a provider that doesn't read them, or state or questions could not be encoded as JSON (original error on `#cause`) |
|
|
388
|
+
| `RequestError` | Questions hash was empty, images, audio, or videos were not data URLs or went to a provider or model that doesn't read them, or state or questions could not be encoded as JSON (original error on `#cause`) |
|
|
342
389
|
| `TransportError` (`TimeoutError`) | Network or timeout failure after retries, carries `#cause_error` |
|
|
343
390
|
| `ApiError` | Non-2xx response, carries `#status`, `#body`, and `#headers`. The message ends with the vendor's own reason when the body has one |
|
|
344
391
|
| `Unauthorized` | 401 |
|
|
@@ -349,7 +396,7 @@ return is still accepted and treated as having no headers, which means no
|
|
|
349
396
|
| `InvalidResponse` | Body wasn't JSON, wasn't a Hash, or an answer was malformed (including a noul outside 0..1 or an id answered twice) |
|
|
350
397
|
| `MissingAnswers` | One or more question ids came back missing, wrong-typed, or refused. Carries `#missing`, `#refused` (the subset the provider declined), and `#answers` |
|
|
351
398
|
|
|
352
|
-
Status: 0.
|
|
399
|
+
Status: 0.3.0, API may change.
|
|
353
400
|
|
|
354
401
|
The companion gem `decide` builds decisions and verdicts on top of this client.
|
|
355
402
|
|
|
@@ -364,6 +411,8 @@ anywhere. To ship a version:
|
|
|
364
411
|
request per configured provider and prints the answers. CI never runs it.
|
|
365
412
|
`TARGETS=open_router:clef,open_router:luna` picks provider and model
|
|
366
413
|
pairs, and `SMOKE_IMAGE=photo.png` adds an image where supported.
|
|
414
|
+
`SMOKE_AUDIO=clip.mp3` and `SMOKE_VIDEO=clip.mp4` add audio and video
|
|
415
|
+
where the model reads them, such as `TARGETS=cloudflare:clef-omni`.
|
|
367
416
|
4. Merge to `main`. The Release workflow runs the suite, builds the gem with
|
|
368
417
|
`gem build --strict`, checks the built gem carries every file under
|
|
369
418
|
`lib/`, and pushes it. A version already on RubyGems is skipped, so the
|
|
@@ -60,12 +60,13 @@ module RubyDecisionModel
|
|
|
60
60
|
@provider.base_url
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
# images: data URLs (see Images) for providers
|
|
64
|
-
# provider places them where its API
|
|
65
|
-
|
|
63
|
+
# images:, audio:, videos: data URLs (see Images and Media) for providers
|
|
64
|
+
# and models that read them. Each provider places them where its API
|
|
65
|
+
# expects.
|
|
66
|
+
def ask(state:, questions:, images: nil, audio: nil, videos: nil)
|
|
66
67
|
raise RequestError, "questions must not be empty" if questions.nil? || questions.empty?
|
|
67
68
|
|
|
68
|
-
body = build_body(state, questions, images)
|
|
69
|
+
body = build_body(state, questions, images: images, audio: audio, videos: videos)
|
|
69
70
|
status, response_body, response_headers = perform_with_retry(
|
|
70
71
|
url: request_url, headers: @provider.headers, body: body
|
|
71
72
|
)
|
|
@@ -81,25 +82,44 @@ module RubyDecisionModel
|
|
|
81
82
|
takes_model ? @provider.url(@model) : @provider.url
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return @provider.request_body(model: @model, state: state, questions: questions) if images.empty?
|
|
85
|
+
MEDIA_PREFIXES = { images: "data:image/", audio: "data:audio/", videos: "data:video/" }.freeze
|
|
86
|
+
private_constant :MEDIA_PREFIXES
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
def build_body(state, questions, **media)
|
|
89
|
+
media = media.transform_values { |urls| Array(urls) }.reject { |_kind, urls| urls.empty? }
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
media.each do |kind, urls|
|
|
92
|
+
raise RequestError, unsupported_message(kind) unless accepts?(kind)
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
prefix = MEDIA_PREFIXES.fetch(kind)
|
|
95
|
+
next if urls.all? { |url| url.is_a?(String) && url.start_with?(prefix) }
|
|
96
|
+
|
|
97
|
+
raise RequestError, "#{kind} must be data URLs (#{prefix}...); see RubyDecisionModel::#{kind == :images ? 'Images' : 'Media'}"
|
|
94
98
|
end
|
|
95
99
|
|
|
96
|
-
@provider.request_body(model: @model, state: state, questions: questions,
|
|
100
|
+
@provider.request_body(model: @model, state: state, questions: questions, **media)
|
|
97
101
|
rescue JSON::GeneratorError, JSON::NestingError => e
|
|
98
102
|
# The generator's message can quote the offending value, so it stays
|
|
99
103
|
# on #cause rather than in a message that may be logged.
|
|
100
104
|
raise RequestError, "state or questions could not be encoded as JSON (#{e.class})"
|
|
101
105
|
end
|
|
102
106
|
|
|
107
|
+
# supports_images? takes no argument in 0.2.0 providers, so it is called
|
|
108
|
+
# bare; audio and videos can differ per model, so those get the model.
|
|
109
|
+
def accepts?(kind)
|
|
110
|
+
case kind
|
|
111
|
+
when :images then @provider.supports_images?
|
|
112
|
+
when :audio then @provider.supports_audio?(@model)
|
|
113
|
+
when :videos then @provider.supports_videos?(@model)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def unsupported_message(kind)
|
|
118
|
+
return "#{@provider.name} does not accept images" if kind == :images
|
|
119
|
+
|
|
120
|
+
"#{[@provider.name, @model].compact.join(' ')} does not accept #{kind}"
|
|
121
|
+
end
|
|
122
|
+
|
|
103
123
|
def resolve_provider(provider, api_key:, base_url:)
|
|
104
124
|
case provider
|
|
105
125
|
when Providers::Base
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "images"
|
|
4
|
+
|
|
5
|
+
module RubyDecisionModel
|
|
6
|
+
# Builds the base64 data URLs that Client#ask takes as images:, audio:, and
|
|
7
|
+
# videos:. Images has the image-only version of the same helpers.
|
|
8
|
+
module Media
|
|
9
|
+
CONTENT_TYPES = Images::CONTENT_TYPES.merge(
|
|
10
|
+
".wav" => "audio/wav",
|
|
11
|
+
".mp3" => "audio/mpeg",
|
|
12
|
+
".mp4" => "video/mp4",
|
|
13
|
+
".webm" => "video/webm"
|
|
14
|
+
).freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def data_url(bytes, content_type:)
|
|
19
|
+
unless content_type.to_s.start_with?("image/", "audio/", "video/")
|
|
20
|
+
raise ArgumentError, "content_type must be an image/*, audio/*, or video/* type, got #{content_type.inspect}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# pack("m0") is strict base64 without the base64 gem, which leaves the
|
|
24
|
+
# default gems in Ruby 3.4.
|
|
25
|
+
"data:#{content_type};base64,#{[bytes].pack('m0')}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Reads a file and infers its type from the extension.
|
|
29
|
+
def from_file(path, content_type: nil)
|
|
30
|
+
content_type ||= CONTENT_TYPES[File.extname(path.to_s).downcase]
|
|
31
|
+
raise ArgumentError, "cannot infer a media type for #{path}; pass content_type:" if content_type.nil?
|
|
32
|
+
|
|
33
|
+
data_url(File.binread(path), content_type: content_type)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -65,6 +65,18 @@ module RubyDecisionModel
|
|
|
65
65
|
false
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
# Whether requests may carry audio or videos. Client refuses audio: and
|
|
69
|
+
# videos: for a provider that says no, before anything goes over the
|
|
70
|
+
# wire. The model is passed because support can differ between one
|
|
71
|
+
# provider's models.
|
|
72
|
+
def supports_audio?(_model = nil)
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def supports_videos?(_model = nil)
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
|
|
68
80
|
# Response header carrying the provider's request id, or nil.
|
|
69
81
|
# Typesafe's, which 0.1.0 read for every provider and System One
|
|
70
82
|
# servers that follow Typesafe send too. Vendors with their own header
|
|
@@ -129,12 +141,14 @@ module RubyDecisionModel
|
|
|
129
141
|
headers
|
|
130
142
|
end
|
|
131
143
|
|
|
132
|
-
# Client passes images: only when the caller
|
|
133
|
-
#
|
|
134
|
-
# signature.
|
|
135
|
-
def request_body(model:, state:, questions:, images: nil)
|
|
144
|
+
# Client passes images:, audio:, and videos: only when the caller
|
|
145
|
+
# supplied some and the provider said it supports them, so a subclass
|
|
146
|
+
# that takes none can keep a three-keyword signature.
|
|
147
|
+
def request_body(model:, state:, questions:, images: nil, audio: nil, videos: nil)
|
|
136
148
|
body = { "model" => model, "state" => state, "questions" => questions }
|
|
137
149
|
body["images"] = images if images
|
|
150
|
+
body["audio"] = audio if audio
|
|
151
|
+
body["videos"] = videos if videos
|
|
138
152
|
JSON.generate(body)
|
|
139
153
|
end
|
|
140
154
|
|
|
@@ -4,8 +4,9 @@ require_relative "base"
|
|
|
4
4
|
|
|
5
5
|
module RubyDecisionModel
|
|
6
6
|
module Providers
|
|
7
|
-
# Cloudflare's Clef and Clef-
|
|
8
|
-
# with Cloudflare's images
|
|
7
|
+
# Cloudflare's Clef, Clef-flash, and Clef-omni on Workers AI. The body is
|
|
8
|
+
# System One with Cloudflare's images, audio, and videos extensions
|
|
9
|
+
# (audio and videos on Clef-omni only); the model also appears in the
|
|
9
10
|
# path, and the account id is required. Responses arrive inside the
|
|
10
11
|
# Workers AI envelope ({"result": ..., "success": true}), which is
|
|
11
12
|
# unwrapped here.
|
|
@@ -14,9 +15,14 @@ module RubyDecisionModel
|
|
|
14
15
|
"cloudflare/clef" => "clef",
|
|
15
16
|
"cloudflare/clef-flash" => "clef-flash",
|
|
16
17
|
"@cf/cloudflare/clef" => "clef",
|
|
17
|
-
"@cf/cloudflare/clef-flash" => "clef-flash"
|
|
18
|
+
"@cf/cloudflare/clef-flash" => "clef-flash",
|
|
19
|
+
"cloudflare/clef-omni" => "clef-omni",
|
|
20
|
+
"@cf/cloudflare/clef-omni" => "clef-omni"
|
|
18
21
|
}.freeze
|
|
19
22
|
|
|
23
|
+
# The models that read audio and videos as well as images.
|
|
24
|
+
OMNI_MODELS = %w[clef-omni].freeze
|
|
25
|
+
|
|
20
26
|
# Cloudflare's own tooling reads CLOUDFLARE_API_TOKEN; the Clef docs
|
|
21
27
|
# use CLOUDFLARE_AUTH_TOKEN. Either works, in that order.
|
|
22
28
|
FALLBACK_ENV_VAR = "CLOUDFLARE_AUTH_TOKEN"
|
|
@@ -76,6 +82,14 @@ module RubyDecisionModel
|
|
|
76
82
|
true
|
|
77
83
|
end
|
|
78
84
|
|
|
85
|
+
def supports_audio?(model = nil)
|
|
86
|
+
OMNI_MODELS.include?(model.to_s)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def supports_videos?(model = nil)
|
|
90
|
+
OMNI_MODELS.include?(model.to_s)
|
|
91
|
+
end
|
|
92
|
+
|
|
79
93
|
def request_id_header
|
|
80
94
|
"cf-ray"
|
|
81
95
|
end
|
data/lib/ruby_decision_model.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "ruby_decision_model/version"
|
|
|
7
7
|
require_relative "ruby_decision_model/errors"
|
|
8
8
|
require_relative "ruby_decision_model/questions"
|
|
9
9
|
require_relative "ruby_decision_model/images"
|
|
10
|
+
require_relative "ruby_decision_model/media"
|
|
10
11
|
require_relative "ruby_decision_model/answers"
|
|
11
12
|
require_relative "ruby_decision_model/response"
|
|
12
13
|
require_relative "ruby_decision_model/providers"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_decision_model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Obie Fernandez
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -40,11 +40,11 @@ dependencies:
|
|
|
40
40
|
version: '13.0'
|
|
41
41
|
description: Decision models answer typed questions about a state instead of generating
|
|
42
42
|
text. ruby_decision_model builds noul (yes/no probability), choice, and score questions,
|
|
43
|
-
posts them with a state and optional images to a decision-model
|
|
44
|
-
Typesafe, OpenAI, Cloudflare, Perplexity, Databricks, or any
|
|
45
|
-
as Ollama), and returns the same normalized answers with
|
|
46
|
-
legends, and usage whichever vendor served them. Retries
|
|
47
|
-
SDKs.
|
|
43
|
+
posts them with a state and optional images, audio, and video to a decision-model
|
|
44
|
+
provider (OpenRouter, Typesafe, OpenAI, Cloudflare, Perplexity, Databricks, or any
|
|
45
|
+
System One server such as Ollama), and returns the same normalized answers with
|
|
46
|
+
probabilities, confidence, legends, and usage whichever vendor served them. Retries
|
|
47
|
+
follow the official Typesafe SDKs.
|
|
48
48
|
email:
|
|
49
49
|
- obiefernandez@gmail.com
|
|
50
50
|
executables: []
|
|
@@ -59,6 +59,7 @@ files:
|
|
|
59
59
|
- lib/ruby_decision_model/client.rb
|
|
60
60
|
- lib/ruby_decision_model/errors.rb
|
|
61
61
|
- lib/ruby_decision_model/images.rb
|
|
62
|
+
- lib/ruby_decision_model/media.rb
|
|
62
63
|
- lib/ruby_decision_model/providers.rb
|
|
63
64
|
- lib/ruby_decision_model/providers/base.rb
|
|
64
65
|
- lib/ruby_decision_model/providers/cloudflare.rb
|