fal 0.0.3 → 0.1.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/.rubocop.yml +8 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +88 -26
- data/README.md +115 -8
- data/lib/fal/client.rb +43 -0
- data/lib/fal/model.rb +166 -0
- data/lib/fal/price.rb +92 -0
- data/lib/fal/price_estimate.rb +89 -0
- data/lib/fal/request.rb +29 -27
- data/lib/fal/version.rb +1 -1
- data/lib/fal/webhook_request.rb +4 -4
- data/lib/fal.rb +9 -0
- data/rbi/fal/client.rbi +59 -0
- data/rbi/fal/fal.rbi +68 -0
- data/rbi/fal/model.rbi +130 -0
- data/rbi/fal/price.rbi +49 -0
- data/rbi/fal/price_estimate.rbi +61 -0
- data/rbi/fal/request.rbi +88 -0
- data/rbi/fal/stream.rbi +37 -0
- data/rbi/fal/version.rbi +6 -0
- data/rbi/fal/webhook_request.rbi +61 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/.gitignore +2 -0
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ff4bd4724ee6b7ea39f7c72f51503e4e918b50f019972a08836efd6d22fdffc
|
|
4
|
+
data.tar.gz: 4f9020293f0b639487e836668116fadfcd51768ef0401e335a18394be155c930
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7646551a9cffa3a58b9ce0ac08f48e258cedd9e19f7ad18e9ae8fc3fe0aa85c774e5a21a283026ef5a22e0126c8c3db52d2667bc7cca285f8d9b6d74c4ac3b7
|
|
7
|
+
data.tar.gz: 0f1f6137e731796d859ff4bd51eb92078453530498dfd72d436e8c9999dd4f1f26087a99c010f533fb231bb2012186d10fee972811ff44d268e77a71d1180e3a
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,57 +1,115 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
fal (0.0
|
|
4
|
+
fal (0.1.0)
|
|
5
5
|
faraday (>= 1)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
|
|
10
|
+
addressable (2.8.7)
|
|
11
|
+
public_suffix (>= 2.0.2, < 7.0)
|
|
12
|
+
ast (2.4.3)
|
|
13
|
+
base64 (0.3.0)
|
|
14
|
+
benchmark (0.5.0)
|
|
15
|
+
bigdecimal (3.3.1)
|
|
16
|
+
crack (1.0.1)
|
|
17
|
+
bigdecimal
|
|
18
|
+
rexml
|
|
11
19
|
dotenv (3.1.8)
|
|
20
|
+
erubi (1.13.1)
|
|
12
21
|
faraday (2.14.0)
|
|
13
22
|
faraday-net_http (>= 2.0, < 3.5)
|
|
14
23
|
json
|
|
15
24
|
logger
|
|
16
25
|
faraday-net_http (3.4.1)
|
|
17
26
|
net-http (>= 0.5.0)
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
hashdiff (1.2.1)
|
|
28
|
+
json (2.15.2)
|
|
29
|
+
language_server-protocol (3.17.0.5)
|
|
30
|
+
lint_roller (1.1.0)
|
|
20
31
|
logger (1.7.0)
|
|
21
|
-
minitest (5.
|
|
22
|
-
net-http (0.
|
|
32
|
+
minitest (5.26.0)
|
|
33
|
+
net-http (0.7.0)
|
|
23
34
|
uri
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
netrc (0.11.0)
|
|
36
|
+
parallel (1.27.0)
|
|
37
|
+
parser (3.3.10.0)
|
|
26
38
|
ast (~> 2.4.1)
|
|
27
39
|
racc
|
|
28
|
-
|
|
40
|
+
prism (1.6.0)
|
|
41
|
+
public_suffix (6.0.2)
|
|
42
|
+
racc (1.8.1)
|
|
29
43
|
rainbow (3.1.1)
|
|
30
|
-
rake (13.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
rake (13.3.1)
|
|
45
|
+
rbi (0.3.7)
|
|
46
|
+
prism (~> 1.0)
|
|
47
|
+
rbs (>= 3.4.4)
|
|
48
|
+
rbs (3.9.5)
|
|
49
|
+
logger
|
|
50
|
+
regexp_parser (2.11.3)
|
|
51
|
+
rexml (3.4.4)
|
|
52
|
+
rubocop (1.81.7)
|
|
35
53
|
json (~> 2.3)
|
|
36
|
-
language_server-protocol (
|
|
54
|
+
language_server-protocol (~> 3.17.0.2)
|
|
55
|
+
lint_roller (~> 1.1.0)
|
|
37
56
|
parallel (~> 1.10)
|
|
38
57
|
parser (>= 3.3.0.2)
|
|
39
58
|
rainbow (>= 2.2.2, < 4.0)
|
|
40
|
-
regexp_parser (>=
|
|
41
|
-
|
|
42
|
-
rubocop-ast (>= 1.31.1, < 2.0)
|
|
59
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
60
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
43
61
|
ruby-progressbar (~> 1.7)
|
|
44
|
-
unicode-display_width (>= 2.4.0, <
|
|
45
|
-
rubocop-ast (1.
|
|
46
|
-
parser (>= 3.3.
|
|
62
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
63
|
+
rubocop-ast (1.47.1)
|
|
64
|
+
parser (>= 3.3.7.2)
|
|
65
|
+
prism (~> 1.4)
|
|
47
66
|
ruby-progressbar (1.13.0)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
sorbet (0.6.12689)
|
|
68
|
+
sorbet-static (= 0.6.12689)
|
|
69
|
+
sorbet-runtime (0.6.12689)
|
|
70
|
+
sorbet-static (0.6.12689-aarch64-linux)
|
|
71
|
+
sorbet-static (0.6.12689-universal-darwin)
|
|
72
|
+
sorbet-static (0.6.12689-x86_64-linux)
|
|
73
|
+
sorbet-static-and-runtime (0.6.12689)
|
|
74
|
+
sorbet (= 0.6.12689)
|
|
75
|
+
sorbet-runtime (= 0.6.12689)
|
|
76
|
+
spoom (1.6.3)
|
|
77
|
+
erubi (>= 1.10.0)
|
|
78
|
+
prism (>= 0.28.0)
|
|
79
|
+
rbi (>= 0.3.3)
|
|
80
|
+
rexml (>= 3.2.6)
|
|
81
|
+
sorbet-static-and-runtime (>= 0.5.10187)
|
|
82
|
+
thor (>= 0.19.2)
|
|
83
|
+
tapioca (0.16.11)
|
|
84
|
+
benchmark
|
|
85
|
+
bundler (>= 2.2.25)
|
|
86
|
+
netrc (>= 0.11.0)
|
|
87
|
+
parallel (>= 1.21.0)
|
|
88
|
+
rbi (~> 0.2)
|
|
89
|
+
sorbet-static-and-runtime (>= 0.5.11087)
|
|
90
|
+
spoom (>= 1.2.0)
|
|
91
|
+
thor (>= 1.2.0)
|
|
92
|
+
yard-sorbet
|
|
93
|
+
thor (1.4.0)
|
|
94
|
+
unicode-display_width (3.2.0)
|
|
95
|
+
unicode-emoji (~> 4.1)
|
|
96
|
+
unicode-emoji (4.1.0)
|
|
97
|
+
uri (1.1.0)
|
|
98
|
+
vcr (6.3.1)
|
|
99
|
+
base64
|
|
100
|
+
webmock (3.26.1)
|
|
101
|
+
addressable (>= 2.8.0)
|
|
102
|
+
crack (>= 0.3.2)
|
|
103
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
104
|
+
yard (0.9.37)
|
|
105
|
+
yard-sorbet (0.9.0)
|
|
106
|
+
sorbet-runtime
|
|
107
|
+
yard
|
|
51
108
|
|
|
52
109
|
PLATFORMS
|
|
53
|
-
|
|
54
|
-
|
|
110
|
+
aarch64-linux
|
|
111
|
+
universal-darwin
|
|
112
|
+
x86_64-linux
|
|
55
113
|
|
|
56
114
|
DEPENDENCIES
|
|
57
115
|
dotenv
|
|
@@ -59,6 +117,10 @@ DEPENDENCIES
|
|
|
59
117
|
minitest (~> 5.0)
|
|
60
118
|
rake (~> 13.0)
|
|
61
119
|
rubocop (~> 1.21)
|
|
120
|
+
sorbet
|
|
121
|
+
tapioca
|
|
122
|
+
vcr
|
|
123
|
+
webmock
|
|
62
124
|
|
|
63
125
|
BUNDLED WITH
|
|
64
126
|
2.5.7
|
data/README.md
CHANGED
|
@@ -21,19 +21,20 @@ Fal.configure do |config|
|
|
|
21
21
|
config.api_key = "your-key" # Optional. Defaults to ENV["FAL_KEY"] if not set.
|
|
22
22
|
config.queue_base = "https://queue.fal.run" # Optional (default: https://queue.fal.run)
|
|
23
23
|
config.sync_base = "https://fal.run" # Optional (default: https://fal.run)
|
|
24
|
+
config.api_base = "https://api.fal.ai/v1" # Optional (default: https://api.fal.ai/v1)
|
|
24
25
|
config.request_timeout = 120 # Optional (default: 120)
|
|
25
26
|
end
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
### Create a queued request
|
|
29
30
|
|
|
30
|
-
The Queue API is the recommended way to call models on fal. Provide a `
|
|
31
|
+
The Queue API is the recommended way to call models on fal. Provide a `endpoint_id` in "namespace/name" format and an input payload.
|
|
31
32
|
|
|
32
33
|
```ruby
|
|
33
|
-
|
|
34
|
+
endpoint_id = "fal-ai/fast-sdxl"
|
|
34
35
|
|
|
35
36
|
request = Fal::Request.create!(
|
|
36
|
-
|
|
37
|
+
endpoint_id: endpoint_id,
|
|
37
38
|
input: { prompt: "a cat" }
|
|
38
39
|
)
|
|
39
40
|
|
|
@@ -45,7 +46,7 @@ You can also specify a webhook URL to be notified when the request is finished.
|
|
|
45
46
|
|
|
46
47
|
```ruby
|
|
47
48
|
request = Fal::Request.create!(
|
|
48
|
-
|
|
49
|
+
endpoint_id: endpoint_id,
|
|
49
50
|
input: { prompt: "a cat playing piano" },
|
|
50
51
|
webhook_url: "https://example.com/fal/webhook"
|
|
51
52
|
)
|
|
@@ -56,7 +57,7 @@ request = Fal::Request.create!(
|
|
|
56
57
|
Fetch the current status by id:
|
|
57
58
|
|
|
58
59
|
```ruby
|
|
59
|
-
status = Fal::Request.find_by!(id: request.id,
|
|
60
|
+
status = Fal::Request.find_by!(id: request.id, endpoint_id: endpoint_id)
|
|
60
61
|
status.in_queue? # => true/false
|
|
61
62
|
status.in_progress? # => true/false
|
|
62
63
|
status.completed? # => true/false
|
|
@@ -138,7 +139,7 @@ Rescue them as needed:
|
|
|
138
139
|
|
|
139
140
|
```ruby
|
|
140
141
|
begin
|
|
141
|
-
Fal::Request.create!(
|
|
142
|
+
Fal::Request.create!(endpoint_id: endpoint_id, input: { prompt: "hi" })
|
|
142
143
|
rescue Fal::UnauthorizedError
|
|
143
144
|
# handle invalid/missing FAL_KEY
|
|
144
145
|
end
|
|
@@ -149,9 +150,9 @@ end
|
|
|
149
150
|
Use `stream!` for SSE streaming from synchronous endpoints. It yields each chunk’s data Hash and returns a `Fal::Request` whose `response` contains the last chunk’s payload.
|
|
150
151
|
|
|
151
152
|
```ruby
|
|
152
|
-
|
|
153
|
+
endpoint_id = "fal-ai/flux/dev"
|
|
153
154
|
|
|
154
|
-
last = Fal::Request.stream!(
|
|
155
|
+
last = Fal::Request.stream!(endpoint_id: endpoint_id, input: { prompt: "a cat" }) do |chunk|
|
|
155
156
|
# chunk is a Hash, e.g. { images: [...] }
|
|
156
157
|
puts chunk
|
|
157
158
|
end
|
|
@@ -160,6 +161,112 @@ last.completed? # => true/false
|
|
|
160
161
|
last.response # => last streamed data hash (e.g., { "response" => { ... } } or final payload)
|
|
161
162
|
```
|
|
162
163
|
|
|
164
|
+
### Pricing
|
|
165
|
+
|
|
166
|
+
Use the Platform API to fetch per-endpoint pricing.
|
|
167
|
+
|
|
168
|
+
Find pricing for a single endpoint:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
price = Fal::Price.find_by(endpoint_id: "fal-ai/flux/dev")
|
|
172
|
+
price.unit_price # => e.g., 0.025
|
|
173
|
+
price.unit # => e.g., "image"
|
|
174
|
+
price.currency # => e.g., "USD"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Iterate through all prices (auto-paginates):
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
Fal::Price.each do |p|
|
|
181
|
+
puts "#{p.endpoint_id} => #{p.unit_price} #{p.currency} per #{p.unit}"
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Collect all prices as an array:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
prices = Fal::Price.all
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Estimate cost
|
|
192
|
+
|
|
193
|
+
Compute a total cost estimate across endpoints using historical API price or unit price.
|
|
194
|
+
|
|
195
|
+
Unit price (uses billing units like images/videos):
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
estimate = Fal::PriceEstimate.create(
|
|
199
|
+
estimate_type: Fal::PriceEstimate::EstimateType::UNIT_PRICE,
|
|
200
|
+
endpoints: [
|
|
201
|
+
# You can pass unit_quantity directly
|
|
202
|
+
Fal::PriceEstimate::Endpoint.new(endpoint_id: "fal-ai/flux/dev", unit_quantity: 50),
|
|
203
|
+
# Or use call_quantity as a convenience alias for units
|
|
204
|
+
Fal::PriceEstimate::Endpoint.new(endpoint_id: "fal-ai/flux-pro", call_quantity: 25)
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
estimate.estimate_type # => "unit_price"
|
|
209
|
+
estimate.total_cost # => e.g., 1.88
|
|
210
|
+
estimate.currency # => "USD"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Historical API price (uses calls per endpoint):
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
estimate = Fal::PriceEstimate.create(
|
|
217
|
+
estimate_type: Fal::PriceEstimate::EstimateType::HISTORICAL_API_PRICE,
|
|
218
|
+
endpoints: [
|
|
219
|
+
Fal::PriceEstimate::Endpoint.new(endpoint_id: "fal-ai/flux/dev", call_quantity: 100)
|
|
220
|
+
]
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Models
|
|
225
|
+
|
|
226
|
+
List, search, and find models via the Models API.
|
|
227
|
+
|
|
228
|
+
Find a model by endpoint ID:
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
model = Fal::Model.find_by(endpoint_id: "fal-ai/flux/dev")
|
|
232
|
+
model.endpoint_id # => "fal-ai/flux/dev"
|
|
233
|
+
model.display_name # => e.g., "FLUX.1 [dev]"
|
|
234
|
+
model.category # => e.g., "text-to-image"
|
|
235
|
+
model.status # => "active" | "deprecated"
|
|
236
|
+
model.tags # => ["fast", "pro"]
|
|
237
|
+
model.model_url # => "https://fal.run/..."
|
|
238
|
+
model.thumbnail_url # => "https://..."
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Iterate or collect all models (auto-paginates):
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
Fal::Model.each do |m|
|
|
245
|
+
puts m.endpoint_id
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
all_models = Fal::Model.all
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Search with filters:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
results = Fal::Model.search(query: "text to image", status: "active")
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Get a model’s price (memoized):
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
price = model.price
|
|
261
|
+
price.unit_price # => e.g., 0.025
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Run a request for a model (uses the model's `endpoint_id` as `endpoint_id`):
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
request = model.run(input: { prompt: "a cat" })
|
|
268
|
+
```
|
|
269
|
+
|
|
163
270
|
### Development
|
|
164
271
|
|
|
165
272
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/fal/client.rb
CHANGED
|
@@ -22,6 +22,25 @@ module Fal
|
|
|
22
22
|
parse_json(response.body)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Perform a POST against the platform API base (api.fal.ai/v1)
|
|
26
|
+
# @param path [String]
|
|
27
|
+
# @param payload [Hash]
|
|
28
|
+
# @param headers [Hash]
|
|
29
|
+
# @return [Hash, nil]
|
|
30
|
+
def post_api(path, payload = {}, headers: {})
|
|
31
|
+
response = connection.post(build_api_url(path)) do |request|
|
|
32
|
+
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
33
|
+
request.headers["Content-Type"] = "application/json"
|
|
34
|
+
request.headers["Accept"] = "application/json"
|
|
35
|
+
request.headers.merge!(headers)
|
|
36
|
+
request.body = payload.compact.to_json
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
handle_error(response) unless response.success?
|
|
40
|
+
|
|
41
|
+
parse_json(response.body)
|
|
42
|
+
end
|
|
43
|
+
|
|
25
44
|
# Perform a POST to the streaming (sync) base with SSE/text-event-stream handling.
|
|
26
45
|
# The provided on_data Proc will be used to receive chunked data.
|
|
27
46
|
# @param path [String]
|
|
@@ -55,6 +74,26 @@ module Fal
|
|
|
55
74
|
parse_json(response.body)
|
|
56
75
|
end
|
|
57
76
|
|
|
77
|
+
# Perform a GET against the platform API base (api.fal.ai/v1)
|
|
78
|
+
# @param path [String]
|
|
79
|
+
# @param query [Hash, nil]
|
|
80
|
+
# @param headers [Hash]
|
|
81
|
+
# @return [Hash, nil]
|
|
82
|
+
def get_api(path, query: nil, headers: {})
|
|
83
|
+
url = build_api_url(path)
|
|
84
|
+
url = "#{url}?#{URI.encode_www_form(query)}" if query && !query.empty?
|
|
85
|
+
|
|
86
|
+
response = connection.get(url) do |request|
|
|
87
|
+
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
88
|
+
request.headers["Accept"] = "application/json"
|
|
89
|
+
request.headers.merge!(headers)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
handle_error(response) unless response.success?
|
|
93
|
+
|
|
94
|
+
parse_json(response.body)
|
|
95
|
+
end
|
|
96
|
+
|
|
58
97
|
def put(path)
|
|
59
98
|
response = connection.put(build_url(path)) do |request|
|
|
60
99
|
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
@@ -97,6 +136,10 @@ module Fal
|
|
|
97
136
|
"#{@configuration.sync_base}#{path}"
|
|
98
137
|
end
|
|
99
138
|
|
|
139
|
+
def build_api_url(path)
|
|
140
|
+
"#{@configuration.api_base}#{path}"
|
|
141
|
+
end
|
|
142
|
+
|
|
100
143
|
def connection
|
|
101
144
|
Faraday.new do |faraday|
|
|
102
145
|
faraday.request :url_encoded
|
data/lib/fal/model.rb
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fal
|
|
4
|
+
# Represents a model endpoint discoverable via the Models API.
|
|
5
|
+
# Provides helpers to list, search, and fetch pricing and to run requests.
|
|
6
|
+
class Model
|
|
7
|
+
MODELS_PATH = "/models"
|
|
8
|
+
|
|
9
|
+
# @return [String]
|
|
10
|
+
attr_reader :endpoint_id
|
|
11
|
+
# Flattened metadata fields
|
|
12
|
+
# @return [String, nil]
|
|
13
|
+
attr_reader :display_name
|
|
14
|
+
# @return [String, nil]
|
|
15
|
+
attr_reader :category
|
|
16
|
+
# @return [String, nil]
|
|
17
|
+
attr_reader :description
|
|
18
|
+
# @return [String, nil]
|
|
19
|
+
attr_reader :status
|
|
20
|
+
# @return [Array<String>, nil]
|
|
21
|
+
attr_reader :tags
|
|
22
|
+
# @return [String, nil]
|
|
23
|
+
attr_reader :updated_at
|
|
24
|
+
# @return [Boolean, nil]
|
|
25
|
+
attr_reader :is_favorited
|
|
26
|
+
# @return [String, nil]
|
|
27
|
+
attr_reader :thumbnail_url
|
|
28
|
+
# @return [String, nil]
|
|
29
|
+
attr_reader :thumbnail_animated_url
|
|
30
|
+
# @return [String, nil]
|
|
31
|
+
attr_reader :model_url
|
|
32
|
+
# @return [String, nil]
|
|
33
|
+
attr_reader :github_url
|
|
34
|
+
# @return [String, nil]
|
|
35
|
+
attr_reader :license_type
|
|
36
|
+
# @return [String, nil]
|
|
37
|
+
attr_reader :date
|
|
38
|
+
# @return [Hash, nil]
|
|
39
|
+
attr_reader :group
|
|
40
|
+
# @return [Boolean, nil]
|
|
41
|
+
attr_reader :highlighted
|
|
42
|
+
# @return [String, nil]
|
|
43
|
+
attr_reader :kind
|
|
44
|
+
# @return [Array<String>, nil]
|
|
45
|
+
attr_reader :training_endpoint_ids
|
|
46
|
+
# @return [Array<String>, nil]
|
|
47
|
+
attr_reader :inference_endpoint_ids
|
|
48
|
+
# @return [String, nil]
|
|
49
|
+
attr_reader :stream_url
|
|
50
|
+
# @return [Float, nil]
|
|
51
|
+
attr_reader :duration_estimate
|
|
52
|
+
# @return [Boolean, nil]
|
|
53
|
+
attr_reader :pinned
|
|
54
|
+
# @return [Hash, nil]
|
|
55
|
+
attr_reader :openapi
|
|
56
|
+
|
|
57
|
+
# @param attributes [Hash]
|
|
58
|
+
# @param client [Fal::Client]
|
|
59
|
+
def initialize(attributes, client: Fal.client)
|
|
60
|
+
@client = client
|
|
61
|
+
reset_attributes(attributes)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Fetch and memoize the price object for this model's endpoint.
|
|
65
|
+
# @return [Fal::Price, nil]
|
|
66
|
+
def price
|
|
67
|
+
@price ||= Fal::Price.find_by(endpoint_id: @endpoint_id, client: @client)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Run a queued request for this model endpoint.
|
|
71
|
+
# @param input [Hash]
|
|
72
|
+
# @param webhook_url [String, nil]
|
|
73
|
+
# @return [Fal::Request]
|
|
74
|
+
def run(input:, webhook_url: nil)
|
|
75
|
+
Fal::Request.create!(endpoint_id: @endpoint_id, input: input, webhook_url: webhook_url, client: @client)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class << self
|
|
79
|
+
# Find a specific model by endpoint_id.
|
|
80
|
+
# @param endpoint_id [String]
|
|
81
|
+
# @param client [Fal::Client]
|
|
82
|
+
# @return [Fal::Model, nil]
|
|
83
|
+
def find_by(endpoint_id:, client: Fal.client)
|
|
84
|
+
response = client.get_api(MODELS_PATH, query: { endpoint_id: endpoint_id })
|
|
85
|
+
entry = Array(response && response["models"]).find { |m| m["endpoint_id"] == endpoint_id }
|
|
86
|
+
entry ? new(entry, client: client) : nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Iterate through models with optional search filters.
|
|
90
|
+
# @param client [Fal::Client]
|
|
91
|
+
# @param query [String, nil] Free-text search query
|
|
92
|
+
# @param category [String, nil]
|
|
93
|
+
# @param status [String, nil]
|
|
94
|
+
# @param expand [Array<String>, String, nil]
|
|
95
|
+
# @yield [Fal::Model]
|
|
96
|
+
# @return [void]
|
|
97
|
+
def each(client: Fal.client, query: nil, category: nil, status: nil, expand: nil, &block)
|
|
98
|
+
cursor = nil
|
|
99
|
+
loop do
|
|
100
|
+
query_hash = { limit: 50, cursor: cursor, q: query, category: category, status: status }.compact
|
|
101
|
+
query_hash[:expand] = expand if expand
|
|
102
|
+
response = client.get_api(MODELS_PATH, query: query_hash)
|
|
103
|
+
models = Array(response && response["models"])
|
|
104
|
+
models.each { |attributes| block.call(new(attributes, client: client)) }
|
|
105
|
+
cursor = response && response["next_cursor"]
|
|
106
|
+
break if cursor.nil?
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Return an array of models for the given filters (or all models).
|
|
111
|
+
# @param client [Fal::Client]
|
|
112
|
+
# @param query [String, nil]
|
|
113
|
+
# @param category [String, nil]
|
|
114
|
+
# @param status [String, nil]
|
|
115
|
+
# @param expand [Array<String>, String, nil]
|
|
116
|
+
# @return [Array<Fal::Model>]
|
|
117
|
+
def all(client: Fal.client, query: nil, category: nil, status: nil, expand: nil)
|
|
118
|
+
results = []
|
|
119
|
+
each(client: client, query: query, category: category, status: status, expand: expand) { |m| results << m }
|
|
120
|
+
results
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Convenience search wrapper that returns all matching models.
|
|
124
|
+
# @param client [Fal::Client]
|
|
125
|
+
# @param query [String, nil]
|
|
126
|
+
# @param category [String, nil]
|
|
127
|
+
# @param status [String, nil]
|
|
128
|
+
# @param expand [Array<String>, String, nil]
|
|
129
|
+
# @return [Array<Fal::Model>]
|
|
130
|
+
def search(query: nil, category: nil, status: nil, expand: nil, client: Fal.client)
|
|
131
|
+
all(client: client, query: query, category: category, status: status, expand: expand)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def reset_attributes(attributes)
|
|
138
|
+
@endpoint_id = attributes["endpoint_id"]
|
|
139
|
+
|
|
140
|
+
meta = attributes["metadata"] || {}
|
|
141
|
+
@display_name = meta["display_name"]
|
|
142
|
+
@category = meta["category"]
|
|
143
|
+
@description = meta["description"]
|
|
144
|
+
@status = meta["status"]
|
|
145
|
+
@tags = meta["tags"]
|
|
146
|
+
@updated_at = meta["updated_at"]
|
|
147
|
+
@is_favorited = meta["is_favorited"]
|
|
148
|
+
@thumbnail_url = meta["thumbnail_url"]
|
|
149
|
+
@thumbnail_animated_url = meta["thumbnail_animated_url"]
|
|
150
|
+
@model_url = meta["model_url"]
|
|
151
|
+
@github_url = meta["github_url"]
|
|
152
|
+
@license_type = meta["license_type"]
|
|
153
|
+
@date = meta["date"]
|
|
154
|
+
@group = meta["group"]
|
|
155
|
+
@highlighted = meta["highlighted"]
|
|
156
|
+
@kind = meta["kind"]
|
|
157
|
+
@training_endpoint_ids = meta["training_endpoint_ids"]
|
|
158
|
+
@inference_endpoint_ids = meta["inference_endpoint_ids"]
|
|
159
|
+
@stream_url = meta["stream_url"]
|
|
160
|
+
@duration_estimate = meta["duration_estimate"]
|
|
161
|
+
@pinned = meta["pinned"]
|
|
162
|
+
|
|
163
|
+
@openapi = attributes["openapi"]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
data/lib/fal/price.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fal
|
|
4
|
+
# Represents pricing information for a model endpoint.
|
|
5
|
+
# Fetches data from the Platform API at /models and /models/pricing.
|
|
6
|
+
class Price
|
|
7
|
+
MODELS_PATH = "/models"
|
|
8
|
+
PRICING_PATH = "/models/pricing"
|
|
9
|
+
|
|
10
|
+
# Billing units returned by the pricing service.
|
|
11
|
+
module Unit
|
|
12
|
+
# Output-based units
|
|
13
|
+
IMAGES = "image"
|
|
14
|
+
VIDEOS = "video"
|
|
15
|
+
MEGAPIXELS = "megapixels"
|
|
16
|
+
|
|
17
|
+
# Compute-based units (provider-specific)
|
|
18
|
+
GPU_SECONDS = "gpu_second"
|
|
19
|
+
GPU_MINUTES = "gpu_minute"
|
|
20
|
+
GPU_HOURS = "gpu_hour"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String]
|
|
24
|
+
attr_reader :endpoint_id
|
|
25
|
+
# @return [Float]
|
|
26
|
+
attr_reader :unit_price
|
|
27
|
+
# @return [String]
|
|
28
|
+
attr_reader :unit
|
|
29
|
+
# @return [String]
|
|
30
|
+
attr_reader :currency
|
|
31
|
+
|
|
32
|
+
# @param attributes [Hash] Raw attributes from pricing API
|
|
33
|
+
# @param client [Fal::Client]
|
|
34
|
+
def initialize(attributes, client: Fal.client)
|
|
35
|
+
@client = client
|
|
36
|
+
reset_attributes(attributes)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Find pricing for a specific model endpoint.
|
|
41
|
+
# @param endpoint_id [String]
|
|
42
|
+
# @param client [Fal::Client]
|
|
43
|
+
# @return [Fal::Price, nil]
|
|
44
|
+
def find_by(endpoint_id:, client: Fal.client)
|
|
45
|
+
response = client.get_api(PRICING_PATH, query: { endpoint_id: endpoint_id })
|
|
46
|
+
entry = Array(response && response["prices"]).find { |p| p["endpoint_id"] == endpoint_id }
|
|
47
|
+
entry ? new(entry, client: client) : nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Iterate over all prices by paging through models and fetching pricing in batches.
|
|
51
|
+
# @param client [Fal::Client]
|
|
52
|
+
# @yield [Fal::Price]
|
|
53
|
+
# @return [void]
|
|
54
|
+
def each(client: Fal.client, &block)
|
|
55
|
+
cursor = nil
|
|
56
|
+
loop do
|
|
57
|
+
models_response = client.get_api(MODELS_PATH, query: { limit: 50, cursor: cursor }.compact)
|
|
58
|
+
models = Array(models_response && models_response["models"])
|
|
59
|
+
endpoint_ids = models.map { |m| m["endpoint_id"] }.compact
|
|
60
|
+
|
|
61
|
+
if endpoint_ids.any?
|
|
62
|
+
pricing_response = client.get_api(PRICING_PATH, query: { endpoint_id: endpoint_ids })
|
|
63
|
+
Array(pricing_response && pricing_response["prices"]).each do |attributes|
|
|
64
|
+
block.call(new(attributes, client: client))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
cursor = models_response && models_response["next_cursor"]
|
|
69
|
+
break if cursor.nil?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Return an array of all prices.
|
|
74
|
+
# @param client [Fal::Client]
|
|
75
|
+
# @return [Array<Fal::Price>]
|
|
76
|
+
def all(client: Fal.client)
|
|
77
|
+
results = []
|
|
78
|
+
each(client: client) { |price| results << price }
|
|
79
|
+
results
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def reset_attributes(attributes)
|
|
86
|
+
@endpoint_id = attributes["endpoint_id"]
|
|
87
|
+
@unit_price = attributes["unit_price"]
|
|
88
|
+
@unit = attributes["unit"]
|
|
89
|
+
@currency = attributes["currency"]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|