ruby_llm-providers-infomaniak 0.1.0 → 0.2.1
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 +19 -0
- data/LICENSE +1 -1
- data/README.md +79 -19
- data/lib/ruby_llm/providers/infomaniak/media.rb +100 -0
- data/lib/ruby_llm/providers/infomaniak/models.rb +16 -3
- data/lib/ruby_llm/providers/infomaniak/pricing.rb +53 -0
- data/lib/ruby_llm/providers/infomaniak/rerank.rb +27 -0
- data/lib/ruby_llm/providers/infomaniak/version.rb +1 -1
- data/lib/ruby_llm/providers/infomaniak.rb +29 -4
- data/models.json +238 -15
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 313aafbac43b95af0726c18ae23550a1d5af92ce8f2fd37df2d278a10e9ac38f
|
|
4
|
+
data.tar.gz: 1176ac3099d709cab842735916ee51de3276e1fb2e27ac8a4bb86309add7b2db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc6651ace176ee4dcdfb2966a3935c316d0b9b3ab61b8a3b4e0e015a1b29455a510522a3ee375fa5da1aa72d4640d2c1e8414e94661cd6eb1d023fbea8dba421
|
|
7
|
+
data.tar.gz: 8d9527b0e60fbb7462fa10bb2375305cac960599d4394cb72c7ecc0c52a024b220450c020fb96efe0406e883b6fa42014a9328369ebcb4e9a5aeb3938415ede0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- `RubyLLM.paint` without `size:` works: the size is left out instead of being sent as null,
|
|
6
|
+
which Infomaniak rejected.
|
|
7
|
+
- Error messages include Infomaniak's validation details, e.g.
|
|
8
|
+
"Validation failed: The size field must have a value. (validation_failed)".
|
|
9
|
+
|
|
10
|
+
## 0.2.0
|
|
11
|
+
|
|
12
|
+
- Reranking (`RubyLLM.rerank`) with `BAAI/bge-reranker-v2-m3` and `Qwen/Qwen3-Reranker-0.6B`, on the
|
|
13
|
+
Cohere-compatible endpoint.
|
|
14
|
+
- Image generation (`RubyLLM.paint`) with Flux; the image type is read from the returned bytes (JPEG).
|
|
15
|
+
- Transcription (`RubyLLM.transcribe`) with Whisper, polling Infomaniak's asynchronous results
|
|
16
|
+
(`infomaniak_poll_interval`).
|
|
17
|
+
- The model catalog now also lists the rerankers, Flux and Whisper.
|
|
18
|
+
- Pricing: the catalog carries Infomaniak's CHF list prices, so `response.cost` works for chat,
|
|
19
|
+
embeddings and rerank (in CHF). Flux and Whisper per-minute prices are in the model metadata.
|
|
20
|
+
- Author name: Andi Idogawa.
|
|
21
|
+
|
|
3
22
|
## 0.1.0
|
|
4
23
|
|
|
5
24
|
- First release: `infomaniak` provider for RubyLLM 2.x on Infomaniak AI Tools' OpenAI-compatible API, with
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[](https://rubygems.org/gems/ruby_llm-providers-infomaniak)
|
|
4
4
|
[](https://github.com/Largo/ruby_llm-providers-infomaniak/actions/workflows/tests.yml)
|
|
5
5
|
|
|
6
|
+

|
|
7
|
+
|
|
6
8
|
**Open-weight models hosted in Switzerland, through the RubyLLM API you already know.**
|
|
7
9
|
|
|
8
10
|
This gem adds [Infomaniak AI Tools](https://www.infomaniak.com/en/hosting/ai-tools) as a provider to
|
|
@@ -17,6 +19,7 @@ chat.ask('Explain Ruby blocks in one sentence.').content
|
|
|
17
19
|
```
|
|
18
20
|
|
|
19
21
|
- Chat, streaming, tool calling, structured output, image input, embeddings
|
|
22
|
+
- Reranking, image generation (Flux) and transcription (Whisper)
|
|
20
23
|
- Thinking on/off per request, with the model's reasoning on `response.thinking`
|
|
21
24
|
- Only an API token to configure: the AI Tools product is looked up for you
|
|
22
25
|
- A model catalog with context sizes and capabilities, refreshable at runtime
|
|
@@ -60,7 +63,8 @@ caches the answer per token.
|
|
|
60
63
|
|---|---|---|
|
|
61
64
|
| `infomaniak_api_key` | required | API token with the AI Tools scope |
|
|
62
65
|
| `infomaniak_product_id` | looked up | The product to bill. Set it when the token reaches several products (the error lists them) or to skip the lookup |
|
|
63
|
-
| `infomaniak_api_base` | `https://api.infomaniak.com/2/ai/{product_id}/openai/v1` | Override for a proxy or gateway |
|
|
66
|
+
| `infomaniak_api_base` | `https://api.infomaniak.com/2/ai/{product_id}/openai/v1` | Override for a proxy or gateway. Rerank, image and transcription routes are derived from it |
|
|
67
|
+
| `infomaniak_poll_interval` | `2` | Seconds between checks while a transcription runs (bounded by `request_timeout`) |
|
|
64
68
|
|
|
65
69
|
## Models
|
|
66
70
|
|
|
@@ -68,25 +72,30 @@ The chat and embedding models Infomaniak served on 2026-09-30, and how each beha
|
|
|
68
72
|
Your product lists its own with `bundle exec rake models`, or live with
|
|
69
73
|
`RubyLLM::Providers::Infomaniak.refresh_models!`.
|
|
70
74
|
|
|
71
|
-
| Model | Context | Images | Thinking |
|
|
72
|
-
|
|
73
|
-
| `moonshotai/Kimi-K2.6` (beta) | 256K | yes | on by default |
|
|
74
|
-
| `Qwen/Qwen3.5-397B-A17B-FP8` (beta) | 200K | yes | on by default |
|
|
75
|
-
| `Qwen/Qwen3.5-122B-A10B-FP8` | 200K | yes | on by default |
|
|
76
|
-
| `google/gemma-4-31B-it` | 100K | yes | off, opt in with an effort |
|
|
77
|
-
| `mistralai/Mistral-Small-4-119B-2603` | 256K | yes | off, opt in with an effort |
|
|
78
|
-
| `mistralai/Ministral-3-14B-Instruct-2512` | 100K | yes | none |
|
|
79
|
-
| `swiss-ai/Apertus-v1.5-70B` (beta) | 100K | yes | none |
|
|
80
|
-
|
|
81
|
-
| Embedding model | Input tokens |
|
|
82
|
-
|
|
83
|
-
| `Qwen/Qwen3-Embedding-8B` | 8192 |
|
|
84
|
-
| `bge_multilingual_gemma2` | 8000 |
|
|
85
|
-
| `mini_lm_l12_v2` | 128 |
|
|
75
|
+
| Model | Context | Images | Thinking | CHF per 1M tokens, in / out |
|
|
76
|
+
|---|---|---|---|---|
|
|
77
|
+
| `moonshotai/Kimi-K2.6` (beta) | 256K | yes | on by default | 0.60 / 3.00 |
|
|
78
|
+
| `Qwen/Qwen3.5-397B-A17B-FP8` (beta) | 200K | yes | on by default | 0.80 / 3.60 |
|
|
79
|
+
| `Qwen/Qwen3.5-122B-A10B-FP8` | 200K | yes | on by default | 0.40 / 3.20 |
|
|
80
|
+
| `google/gemma-4-31B-it` | 100K | yes | off, opt in with an effort | 0.20 / 0.40 |
|
|
81
|
+
| `mistralai/Mistral-Small-4-119B-2603` | 256K | yes | off, opt in with an effort | 0.20 / 0.75 |
|
|
82
|
+
| `mistralai/Ministral-3-14B-Instruct-2512` | 100K | yes | none | 0.30 / 0.40 |
|
|
83
|
+
| `swiss-ai/Apertus-v1.5-70B` (beta) | 100K | yes | none | 0.70 / 2.50 |
|
|
84
|
+
|
|
85
|
+
| Embedding model | Input tokens | CHF per 1M tokens |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `Qwen/Qwen3-Embedding-8B` | 8192 | 0.07 |
|
|
88
|
+
| `bge_multilingual_gemma2` | 8000 | 0.065 |
|
|
89
|
+
| `mini_lm_l12_v2` | 128 | free |
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
| Other models | For | CHF |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `BAAI/bge-reranker-v2-m3` | `RubyLLM.rerank` | 0.01 per 1M tokens |
|
|
94
|
+
| `Qwen/Qwen3-Reranker-0.6B` | `RubyLLM.rerank` | 0.009 per 1M tokens |
|
|
95
|
+
| `flux` | `RubyLLM.paint`, returns JPEG | 0.30 per minute of compute |
|
|
96
|
+
| `whisper` | `RubyLLM.transcribe` | 0.006 per audio minute |
|
|
97
|
+
|
|
98
|
+
Photomaker, which needs reference photos on its own route, is not covered.
|
|
90
99
|
|
|
91
100
|
### The model catalog
|
|
92
101
|
|
|
@@ -175,6 +184,53 @@ chat.ask('What is on this receipt?', with: 'receipt.jpg').content
|
|
|
175
184
|
RubyLLM.embed('Grüezi mitenand', model: 'Qwen/Qwen3-Embedding-8B', provider: :infomaniak).vectors
|
|
176
185
|
```
|
|
177
186
|
|
|
187
|
+
### Reranking
|
|
188
|
+
|
|
189
|
+
Sort retrieved passages by relevance before handing them to a chat model:
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
rerank = RubyLLM.rerank('Which metal is the densest?', passages,
|
|
193
|
+
model: 'BAAI/bge-reranker-v2-m3', provider: :infomaniak, top_n: 3)
|
|
194
|
+
rerank.results.map { |result| [result.score, result.document] }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Image generation
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
image = RubyLLM.paint('A red Swiss train crossing a stone viaduct, watercolor',
|
|
201
|
+
model: 'flux', provider: :infomaniak, size: '1024x1024') # or 1024x1792, 1792x1024
|
|
202
|
+
image.save('train.jpg')
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Prompts work best in English, and are limited to 77 tokens. Infomaniak's extra options pass through
|
|
206
|
+
`provider_options:`, e.g. `provider_options: { style: 'photographic' }`.
|
|
207
|
+
|
|
208
|
+
### Transcription
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
RubyLLM.transcribe('meeting.m4a', model: 'whisper', provider: :infomaniak, language: 'de').text
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Infomaniak runs transcriptions as background jobs: the gem uploads the file, then polls until the
|
|
215
|
+
transcript is ready, so the call blocks for about as long as the job takes. Streaming (passing a block)
|
|
216
|
+
is not possible. mp3, mp4, m4a, wav, flac, ogg, opus, aac, wma and webm are accepted.
|
|
217
|
+
|
|
218
|
+
### Costs
|
|
219
|
+
|
|
220
|
+
The catalog carries Infomaniak's list prices, so ruby_llm computes costs from the token counts:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
response = chat.ask('Summarise this contract.', with: 'contract.txt')
|
|
224
|
+
response.cost.total # => 0.0021 (CHF)
|
|
225
|
+
chat.cost.total # the whole conversation
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Costs from this provider are in CHF**, exactly as Infomaniak bills them, although ruby_llm documents its
|
|
229
|
+
prices as USD. Don't add them to costs from other providers without converting. Chat, embedding and
|
|
230
|
+
rerank costs are computed; Flux and Whisper bill per minute, which ruby_llm cannot compute, so their
|
|
231
|
+
prices are only in `model.metadata[:price_per_minute]`. The prices date from 2026-09-30 and live in
|
|
232
|
+
`lib/ruby_llm/providers/infomaniak/pricing.rb`.
|
|
233
|
+
|
|
178
234
|
## How Infomaniak differs from OpenAI
|
|
179
235
|
|
|
180
236
|
The gem handles these, so the RubyLLM API behaves as usual:
|
|
@@ -189,6 +245,10 @@ The gem handles these, so the RubyLLM API behaves as usual:
|
|
|
189
245
|
| End user | `user` field | `with_end_user('id')` is sent as `user` |
|
|
190
246
|
| Prompt caching | Repeated prompt prefixes answer faster, but no cached-token counts come back and `prompt_cache_key` has no visible effect | `with_caching` options are not sent; keep long shared context (instructions, documents) at the start of the conversation to benefit |
|
|
191
247
|
| Errors | `{"error": {"code", "description"}}` on its own endpoints | The description ends up in the `RubyLLM::Error` message |
|
|
248
|
+
| Routes | Chat and embeddings on `/2/.../openai/v1`, rerank on `/2/.../cohere/v2`, images and transcription on `/1/.../openai` | Each call goes to its route |
|
|
249
|
+
| Reranking | Cohere v2 format, usage as `usage.total_tokens` | Read into `rerank.tokens.input` |
|
|
250
|
+
| Images | Only base64 JPEG, no edits | The image type is read from the bytes; `with:` raises `ArgumentError` |
|
|
251
|
+
| Transcription | Asynchronous: upload returns a batch id, results are polled | Polls every `infomaniak_poll_interval` seconds until done, failed or `request_timeout` |
|
|
192
252
|
|
|
193
253
|
## Development
|
|
194
254
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Infomaniak < Provider
|
|
6
|
+
# Image generation (Flux) and transcription (Whisper) on Infomaniak's
|
|
7
|
+
# OpenAI-style v1 routes. Transcription is asynchronous there: the upload
|
|
8
|
+
# returns a batch id, and the transcript is polled from /results.
|
|
9
|
+
class Media < Protocol
|
|
10
|
+
include Protocols::ChatCompletions::Images
|
|
11
|
+
include Protocols::ChatCompletions::Transcription
|
|
12
|
+
|
|
13
|
+
public :render_transcription_options
|
|
14
|
+
|
|
15
|
+
IMAGE_SIGNATURES = { "\x89PNG".b => 'image/png', "\xFF\xD8\xFF".b => 'image/jpeg', 'RIFF'.b => 'image/webp' }.freeze
|
|
16
|
+
Body = Struct.new(:body)
|
|
17
|
+
|
|
18
|
+
def images_url(**)
|
|
19
|
+
@provider.product_url(1, 'openai/images/generations')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Infomaniak rejects "size": null, so a paint call without size: leaves it out.
|
|
23
|
+
def render_image_payload(prompt, model:, size:, **)
|
|
24
|
+
super.compact
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def validate_paint_inputs!(with:, mask:)
|
|
28
|
+
raise ArgumentError, 'Infomaniak generates images from a prompt only; it cannot edit images' if editing?(with, mask)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# ruby_llm assumes PNG for OpenAI-style images; read the real type from the bytes.
|
|
32
|
+
def parse_image_responses(response, model:)
|
|
33
|
+
entries = Array(response.body['data'])
|
|
34
|
+
raise Error.new('Infomaniak returned no image', response: response) if entries.empty?
|
|
35
|
+
|
|
36
|
+
entries.map do |entry|
|
|
37
|
+
Image.new(data: entry['b64_json'], mime_type: image_mime_type(entry['b64_json']),
|
|
38
|
+
revised_prompt: entry['revised_prompt'], model: model, usage: {})
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def transcription_url
|
|
43
|
+
@provider.product_url(1, 'openai/audio/transcriptions')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def stream_transcription(*)
|
|
47
|
+
raise Error, 'Infomaniak transcribes asynchronously and cannot stream; call transcribe without a block'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_transcription_response(response, model:)
|
|
51
|
+
batch_id = unwrap(response.body)['batch_id']
|
|
52
|
+
raise Error.new('Infomaniak returned no transcription batch id', response: response) unless batch_id
|
|
53
|
+
|
|
54
|
+
super(Body.new(transcript(await_batch(batch_id))), model:)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def image_mime_type(base64)
|
|
60
|
+
head = base64.to_s[0, 24].unpack1('m')
|
|
61
|
+
IMAGE_SIGNATURES.find { |signature, _| head.start_with?(signature) }&.last || 'image/png'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def await_batch(batch_id)
|
|
65
|
+
deadline = monotonic_now + @config.request_timeout
|
|
66
|
+
loop do
|
|
67
|
+
result = unwrap(@connection.get(@provider.product_url(1, "results/#{batch_id}")).body)
|
|
68
|
+
case result['status']
|
|
69
|
+
when 'success' then return result
|
|
70
|
+
when 'failed', 'cancelled' then raise Error, "Infomaniak transcription #{result['status']} (batch #{batch_id})"
|
|
71
|
+
end
|
|
72
|
+
raise Error, "Infomaniak transcription not done after #{@config.request_timeout}s (batch #{batch_id})" if monotonic_now > deadline
|
|
73
|
+
|
|
74
|
+
sleep(@config.infomaniak_poll_interval || 2)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The finished batch carries the output inline, or only a download URL.
|
|
79
|
+
def transcript(result)
|
|
80
|
+
data = result['data'] || @connection.get(result['url']).body
|
|
81
|
+
return data unless data.is_a?(String)
|
|
82
|
+
|
|
83
|
+
parsed = JSON.parse(data)
|
|
84
|
+
parsed.is_a?(Hash) ? parsed : data
|
|
85
|
+
rescue JSON::ParserError
|
|
86
|
+
data
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Infomaniak's own routes wrap payloads as {"result": "success", "data": {...}}.
|
|
90
|
+
def unwrap(body)
|
|
91
|
+
body.is_a?(Hash) && body.key?('result') && body['data'].is_a?(Hash) ? body['data'] : body
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def monotonic_now
|
|
95
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -18,11 +18,23 @@ module RubyLLM
|
|
|
18
18
|
/ix
|
|
19
19
|
EMBEDDING = /embed|bge|\be5\b|gte|mini_?lm/i
|
|
20
20
|
|
|
21
|
+
# Catalog types served outside the OpenAI endpoint, so missing from its list.
|
|
22
|
+
OTHER_ENDPOINT_TYPES = %w[reranker image stt].freeze
|
|
23
|
+
# Photomaker needs its own route with reference photos, which the gem does not cover.
|
|
24
|
+
UNSUPPORTED = /photo_?maker/i
|
|
25
|
+
|
|
21
26
|
def list_models
|
|
22
27
|
details = catalog_details
|
|
23
|
-
listed_models(@connection.get(models_url))
|
|
24
|
-
|
|
28
|
+
listed = listed_models(@connection.get(models_url))
|
|
29
|
+
listed_ids = listed.map { |entry| entry['id'].to_s.downcase }
|
|
30
|
+
elsewhere = details.values.select do |entry|
|
|
31
|
+
name = entry['name'].to_s
|
|
32
|
+
OTHER_ENDPOINT_TYPES.include?(entry['type']) && !name.match?(UNSUPPORTED) &&
|
|
33
|
+
!listed_ids.include?(name.downcase)
|
|
25
34
|
end
|
|
35
|
+
|
|
36
|
+
listed.map { |entry| build_model(entry, details_for(details, entry['id'])) } +
|
|
37
|
+
elsewhere.map { |entry| build_model({ 'id' => entry['name'] }, entry) }
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
private
|
|
@@ -63,7 +75,8 @@ module RubyLLM
|
|
|
63
75
|
capabilities: capabilities_for(type, vision:, thinking:),
|
|
64
76
|
modalities: modalities_for(type, vision:),
|
|
65
77
|
reasoning_options: thinking ? [EFFORT_OPTION] : [],
|
|
66
|
-
|
|
78
|
+
pricing: Pricing.pricing_for(id),
|
|
79
|
+
metadata: metadata_for(entry, details).merge(Pricing.metadata_for(id))
|
|
67
80
|
)
|
|
68
81
|
end
|
|
69
82
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Infomaniak < Provider
|
|
6
|
+
# Infomaniak's list prices in CHF, from
|
|
7
|
+
# https://www.infomaniak.com/en/hosting/ai-services/prices. The API does
|
|
8
|
+
# not report prices, so they are kept here and written into the catalog
|
|
9
|
+
# by `rake models`. They go in as CHF: ruby_llm calls its prices USD,
|
|
10
|
+
# but response.cost for this provider is in CHF, matching the invoice.
|
|
11
|
+
module Pricing
|
|
12
|
+
AS_OF = '2026-09-30'
|
|
13
|
+
CURRENCY = 'CHF'
|
|
14
|
+
|
|
15
|
+
# Per million tokens: [input, output]. Rerankers and embeddings bill input only.
|
|
16
|
+
PER_MILLION_TOKENS = {
|
|
17
|
+
'Qwen/Qwen3.5-122B-A10B-FP8' => [0.40, 3.20],
|
|
18
|
+
'Qwen/Qwen3.5-397B-A17B-FP8' => [0.80, 3.60],
|
|
19
|
+
'mistralai/Ministral-3-14B-Instruct-2512' => [0.30, 0.40],
|
|
20
|
+
'mistralai/Mistral-Small-4-119B-2603' => [0.20, 0.75],
|
|
21
|
+
'google/gemma-4-31B-it' => [0.20, 0.40],
|
|
22
|
+
'moonshotai/Kimi-K2.6' => [0.60, 3.00],
|
|
23
|
+
'swiss-ai/Apertus-v1.5-70B' => [0.70, 2.50],
|
|
24
|
+
'BAAI/bge-reranker-v2-m3' => [0.01],
|
|
25
|
+
'Qwen/Qwen3-Reranker-0.6B' => [0.009],
|
|
26
|
+
'bge_multilingual_gemma2' => [0.065],
|
|
27
|
+
'Qwen/Qwen3-Embedding-8B' => [0.07],
|
|
28
|
+
'mini_lm_l12_v2' => [0]
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
# Billed per minute (audio length for Whisper, compute time for Flux),
|
|
32
|
+
# which ruby_llm's per-token pricing cannot express. Metadata only.
|
|
33
|
+
PER_MINUTE = { 'whisper' => 0.006, 'flux' => 0.30 }.freeze
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
def pricing_for(id)
|
|
38
|
+
input, output = PER_MILLION_TOKENS[id]
|
|
39
|
+
return {} unless input
|
|
40
|
+
|
|
41
|
+
{ text_tokens: { standard: { input_per_million: input, output_per_million: output }.compact } }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def metadata_for(id)
|
|
45
|
+
per_minute = PER_MINUTE[id]
|
|
46
|
+
return {} unless per_minute || PER_MILLION_TOKENS.key?(id)
|
|
47
|
+
|
|
48
|
+
{ currency: CURRENCY, prices_as_of: AS_OF, price_per_minute: per_minute }.compact
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Infomaniak < Provider
|
|
6
|
+
# Reranking on Infomaniak's Cohere v2 compatible endpoint, which sits
|
|
7
|
+
# next to the OpenAI one and reports usage OpenAI-style instead of in meta.
|
|
8
|
+
class CohereRerank < Protocol
|
|
9
|
+
include Protocols::Cohere::Rerank
|
|
10
|
+
|
|
11
|
+
def rerank_url
|
|
12
|
+
@provider.product_url(2, 'cohere/v2/rerank')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parse_rerank_response(response, model:, documents: [])
|
|
16
|
+
data = response.body
|
|
17
|
+
RubyLLM::Rerank.new(
|
|
18
|
+
results: parse_rerank_results(data, documents),
|
|
19
|
+
model: data['model'] || model,
|
|
20
|
+
raw: data,
|
|
21
|
+
input_tokens: data.dig('usage', 'total_tokens')
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
require 'ruby_llm'
|
|
4
4
|
require_relative 'infomaniak/version'
|
|
5
5
|
require_relative 'infomaniak/chat'
|
|
6
|
+
require_relative 'infomaniak/pricing'
|
|
6
7
|
require_relative 'infomaniak/models'
|
|
8
|
+
require_relative 'infomaniak/rerank'
|
|
9
|
+
require_relative 'infomaniak/media'
|
|
7
10
|
|
|
8
11
|
module RubyLLM
|
|
9
12
|
module Providers
|
|
10
13
|
# Infomaniak AI Tools: OpenAI-compatible chat completions, embeddings and
|
|
11
|
-
# model listing under https://api.infomaniak.com/2/ai/{product_id}/openai/v1
|
|
14
|
+
# model listing under https://api.infomaniak.com/2/ai/{product_id}/openai/v1,
|
|
15
|
+
# reranking under /2/ai/{product_id}/cohere/v2, and image generation and
|
|
16
|
+
# transcription under /1/ai/{product_id}/openai.
|
|
12
17
|
class Infomaniak < Provider
|
|
13
18
|
API_HOST = 'https://api.infomaniak.com'
|
|
14
19
|
|
|
@@ -19,6 +24,23 @@ module RubyLLM
|
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
protocol :chat_completions, ChatCompletions
|
|
27
|
+
protocol :rerank, CohereRerank
|
|
28
|
+
protocol :media, Media
|
|
29
|
+
|
|
30
|
+
def protocol_for(model, operation: nil, **)
|
|
31
|
+
case operation
|
|
32
|
+
when :rerank then protocols[:rerank]
|
|
33
|
+
when :paint, :transcribe then protocols[:media]
|
|
34
|
+
else super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The URL of +path+ under the product on API +version+ (1 or 2), derived
|
|
39
|
+
# from api_base so an override moves every route along.
|
|
40
|
+
def product_url(version, path)
|
|
41
|
+
base = api_base.delete_suffix('/').delete_suffix('/openai/v1')
|
|
42
|
+
"#{base.sub(%r{/2/ai/}, "/#{version}/ai/")}/#{path}"
|
|
43
|
+
end
|
|
22
44
|
|
|
23
45
|
def api_base
|
|
24
46
|
@config.infomaniak_api_base || "#{API_HOST}/2/ai/#{product_id}/openai/v1"
|
|
@@ -43,18 +65,21 @@ module RubyLLM
|
|
|
43
65
|
@account_connection ||= Transport::Connection.new(self, @config, api_base: API_HOST)
|
|
44
66
|
end
|
|
45
67
|
|
|
46
|
-
# Infomaniak's own endpoints answer {"result":"error","error":{"code":..,"description":..}}
|
|
68
|
+
# Infomaniak's own endpoints answer {"result":"error","error":{"code":..,"description":..}},
|
|
69
|
+
# with the specifics of a validation failure in error.errors[].description.
|
|
47
70
|
def parse_error(response)
|
|
48
71
|
body = parse_error_body(response)
|
|
49
72
|
error = body['error'] if body.is_a?(Hash)
|
|
50
73
|
return super unless error.is_a?(Hash) && error['description']
|
|
51
74
|
|
|
52
|
-
|
|
75
|
+
details = Array(error['errors']).filter_map { |item| item['description'] if item.is_a?(Hash) }
|
|
76
|
+
message = details.any? ? "#{error['description']}: #{details.join(' ')}" : error['description']
|
|
77
|
+
error['code'] ? "#{message} (#{error['code']})" : message
|
|
53
78
|
end
|
|
54
79
|
|
|
55
80
|
class << self
|
|
56
81
|
def configuration_options
|
|
57
|
-
%i[infomaniak_api_key infomaniak_product_id infomaniak_api_base]
|
|
82
|
+
%i[infomaniak_api_key infomaniak_product_id infomaniak_api_base infomaniak_poll_interval]
|
|
58
83
|
end
|
|
59
84
|
|
|
60
85
|
def configuration_requirements
|
data/models.json
CHANGED
|
@@ -17,7 +17,13 @@
|
|
|
17
17
|
]
|
|
18
18
|
},
|
|
19
19
|
"capabilities": [],
|
|
20
|
-
"pricing": {
|
|
20
|
+
"pricing": {
|
|
21
|
+
"text_tokens": {
|
|
22
|
+
"standard": {
|
|
23
|
+
"input_per_million": 0.065
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
21
27
|
"metadata": {
|
|
22
28
|
"owned_by": "system",
|
|
23
29
|
"type": "embedding",
|
|
@@ -26,7 +32,9 @@
|
|
|
26
32
|
"status": "coming_soon",
|
|
27
33
|
"beta": false,
|
|
28
34
|
"coder": false,
|
|
29
|
-
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings"
|
|
35
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings",
|
|
36
|
+
"currency": "CHF",
|
|
37
|
+
"prices_as_of": "2026-09-30"
|
|
30
38
|
}
|
|
31
39
|
},
|
|
32
40
|
{
|
|
@@ -47,7 +55,13 @@
|
|
|
47
55
|
]
|
|
48
56
|
},
|
|
49
57
|
"capabilities": [],
|
|
50
|
-
"pricing": {
|
|
58
|
+
"pricing": {
|
|
59
|
+
"text_tokens": {
|
|
60
|
+
"standard": {
|
|
61
|
+
"input_per_million": 0
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
51
65
|
"metadata": {
|
|
52
66
|
"owned_by": "system",
|
|
53
67
|
"type": "embedding",
|
|
@@ -56,7 +70,9 @@
|
|
|
56
70
|
"status": "coming_soon",
|
|
57
71
|
"beta": false,
|
|
58
72
|
"coder": false,
|
|
59
|
-
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings"
|
|
73
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings",
|
|
74
|
+
"currency": "CHF",
|
|
75
|
+
"prices_as_of": "2026-09-30"
|
|
60
76
|
}
|
|
61
77
|
},
|
|
62
78
|
{
|
|
@@ -77,7 +93,13 @@
|
|
|
77
93
|
]
|
|
78
94
|
},
|
|
79
95
|
"capabilities": [],
|
|
80
|
-
"pricing": {
|
|
96
|
+
"pricing": {
|
|
97
|
+
"text_tokens": {
|
|
98
|
+
"standard": {
|
|
99
|
+
"input_per_million": 0.07
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
81
103
|
"metadata": {
|
|
82
104
|
"owned_by": "system",
|
|
83
105
|
"type": "embedding",
|
|
@@ -85,7 +107,9 @@
|
|
|
85
107
|
"status": "coming_soon",
|
|
86
108
|
"beta": false,
|
|
87
109
|
"coder": false,
|
|
88
|
-
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings"
|
|
110
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings",
|
|
111
|
+
"currency": "CHF",
|
|
112
|
+
"prices_as_of": "2026-09-30"
|
|
89
113
|
}
|
|
90
114
|
},
|
|
91
115
|
{
|
|
@@ -113,7 +137,14 @@
|
|
|
113
137
|
"json_mode",
|
|
114
138
|
"vision"
|
|
115
139
|
],
|
|
116
|
-
"pricing": {
|
|
140
|
+
"pricing": {
|
|
141
|
+
"text_tokens": {
|
|
142
|
+
"standard": {
|
|
143
|
+
"input_per_million": 0.3,
|
|
144
|
+
"output_per_million": 0.4
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
117
148
|
"metadata": {
|
|
118
149
|
"owned_by": "system",
|
|
119
150
|
"type": "llm",
|
|
@@ -121,7 +152,9 @@
|
|
|
121
152
|
"status": "ready",
|
|
122
153
|
"beta": false,
|
|
123
154
|
"coder": false,
|
|
124
|
-
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions"
|
|
155
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
156
|
+
"currency": "CHF",
|
|
157
|
+
"prices_as_of": "2026-09-30"
|
|
125
158
|
}
|
|
126
159
|
},
|
|
127
160
|
{
|
|
@@ -150,7 +183,14 @@
|
|
|
150
183
|
"vision",
|
|
151
184
|
"reasoning"
|
|
152
185
|
],
|
|
153
|
-
"pricing": {
|
|
186
|
+
"pricing": {
|
|
187
|
+
"text_tokens": {
|
|
188
|
+
"standard": {
|
|
189
|
+
"input_per_million": 0.4,
|
|
190
|
+
"output_per_million": 3.2
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
},
|
|
154
194
|
"metadata": {
|
|
155
195
|
"owned_by": "system",
|
|
156
196
|
"type": "llm",
|
|
@@ -159,6 +199,8 @@
|
|
|
159
199
|
"beta": false,
|
|
160
200
|
"coder": false,
|
|
161
201
|
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
202
|
+
"currency": "CHF",
|
|
203
|
+
"prices_as_of": "2026-09-30",
|
|
162
204
|
"reasoning_options": [
|
|
163
205
|
{
|
|
164
206
|
"type": "effort",
|
|
@@ -198,7 +240,14 @@
|
|
|
198
240
|
"vision",
|
|
199
241
|
"reasoning"
|
|
200
242
|
],
|
|
201
|
-
"pricing": {
|
|
243
|
+
"pricing": {
|
|
244
|
+
"text_tokens": {
|
|
245
|
+
"standard": {
|
|
246
|
+
"input_per_million": 0.2,
|
|
247
|
+
"output_per_million": 0.4
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
202
251
|
"metadata": {
|
|
203
252
|
"owned_by": "system",
|
|
204
253
|
"type": "llm",
|
|
@@ -207,6 +256,8 @@
|
|
|
207
256
|
"beta": false,
|
|
208
257
|
"coder": false,
|
|
209
258
|
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
259
|
+
"currency": "CHF",
|
|
260
|
+
"prices_as_of": "2026-09-30",
|
|
210
261
|
"reasoning_options": [
|
|
211
262
|
{
|
|
212
263
|
"type": "effort",
|
|
@@ -246,7 +297,14 @@
|
|
|
246
297
|
"vision",
|
|
247
298
|
"reasoning"
|
|
248
299
|
],
|
|
249
|
-
"pricing": {
|
|
300
|
+
"pricing": {
|
|
301
|
+
"text_tokens": {
|
|
302
|
+
"standard": {
|
|
303
|
+
"input_per_million": 0.6,
|
|
304
|
+
"output_per_million": 3.0
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
},
|
|
250
308
|
"metadata": {
|
|
251
309
|
"owned_by": "system",
|
|
252
310
|
"type": "llm",
|
|
@@ -255,6 +313,8 @@
|
|
|
255
313
|
"beta": true,
|
|
256
314
|
"coder": false,
|
|
257
315
|
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
316
|
+
"currency": "CHF",
|
|
317
|
+
"prices_as_of": "2026-09-30",
|
|
258
318
|
"reasoning_options": [
|
|
259
319
|
{
|
|
260
320
|
"type": "effort",
|
|
@@ -294,7 +354,14 @@
|
|
|
294
354
|
"vision",
|
|
295
355
|
"reasoning"
|
|
296
356
|
],
|
|
297
|
-
"pricing": {
|
|
357
|
+
"pricing": {
|
|
358
|
+
"text_tokens": {
|
|
359
|
+
"standard": {
|
|
360
|
+
"input_per_million": 0.2,
|
|
361
|
+
"output_per_million": 0.75
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
},
|
|
298
365
|
"metadata": {
|
|
299
366
|
"owned_by": "system",
|
|
300
367
|
"type": "llm",
|
|
@@ -303,6 +370,8 @@
|
|
|
303
370
|
"beta": false,
|
|
304
371
|
"coder": false,
|
|
305
372
|
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
373
|
+
"currency": "CHF",
|
|
374
|
+
"prices_as_of": "2026-09-30",
|
|
306
375
|
"reasoning_options": [
|
|
307
376
|
{
|
|
308
377
|
"type": "effort",
|
|
@@ -342,7 +411,14 @@
|
|
|
342
411
|
"vision",
|
|
343
412
|
"reasoning"
|
|
344
413
|
],
|
|
345
|
-
"pricing": {
|
|
414
|
+
"pricing": {
|
|
415
|
+
"text_tokens": {
|
|
416
|
+
"standard": {
|
|
417
|
+
"input_per_million": 0.8,
|
|
418
|
+
"output_per_million": 3.6
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
},
|
|
346
422
|
"metadata": {
|
|
347
423
|
"owned_by": "system",
|
|
348
424
|
"type": "llm",
|
|
@@ -351,6 +427,8 @@
|
|
|
351
427
|
"beta": true,
|
|
352
428
|
"coder": false,
|
|
353
429
|
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
430
|
+
"currency": "CHF",
|
|
431
|
+
"prices_as_of": "2026-09-30",
|
|
354
432
|
"reasoning_options": [
|
|
355
433
|
{
|
|
356
434
|
"type": "effort",
|
|
@@ -389,7 +467,14 @@
|
|
|
389
467
|
"json_mode",
|
|
390
468
|
"vision"
|
|
391
469
|
],
|
|
392
|
-
"pricing": {
|
|
470
|
+
"pricing": {
|
|
471
|
+
"text_tokens": {
|
|
472
|
+
"standard": {
|
|
473
|
+
"input_per_million": 0.7,
|
|
474
|
+
"output_per_million": 2.5
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
},
|
|
393
478
|
"metadata": {
|
|
394
479
|
"owned_by": "system",
|
|
395
480
|
"type": "llm",
|
|
@@ -397,7 +482,145 @@
|
|
|
397
482
|
"status": "coming_soon",
|
|
398
483
|
"beta": true,
|
|
399
484
|
"coder": false,
|
|
400
|
-
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions"
|
|
485
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
486
|
+
"currency": "CHF",
|
|
487
|
+
"prices_as_of": "2026-09-30"
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"id": "whisper",
|
|
492
|
+
"name": "whisper",
|
|
493
|
+
"provider": "infomaniak",
|
|
494
|
+
"family": null,
|
|
495
|
+
"created_at": null,
|
|
496
|
+
"context_window": null,
|
|
497
|
+
"max_output_tokens": null,
|
|
498
|
+
"knowledge_cutoff": null,
|
|
499
|
+
"modalities": {
|
|
500
|
+
"input": [
|
|
501
|
+
"audio"
|
|
502
|
+
],
|
|
503
|
+
"output": [
|
|
504
|
+
"text"
|
|
505
|
+
]
|
|
506
|
+
},
|
|
507
|
+
"capabilities": [],
|
|
508
|
+
"pricing": {},
|
|
509
|
+
"metadata": {
|
|
510
|
+
"type": "stt",
|
|
511
|
+
"description": "Whisper V3",
|
|
512
|
+
"version": "3.0",
|
|
513
|
+
"status": "ready",
|
|
514
|
+
"beta": false,
|
|
515
|
+
"coder": false,
|
|
516
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/1/ai/%7Bproduct_id%7D/openai/audio/transcriptions",
|
|
517
|
+
"currency": "CHF",
|
|
518
|
+
"prices_as_of": "2026-09-30",
|
|
519
|
+
"price_per_minute": 0.006
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"id": "flux",
|
|
524
|
+
"name": "flux",
|
|
525
|
+
"provider": "infomaniak",
|
|
526
|
+
"family": null,
|
|
527
|
+
"created_at": null,
|
|
528
|
+
"context_window": null,
|
|
529
|
+
"max_output_tokens": null,
|
|
530
|
+
"knowledge_cutoff": null,
|
|
531
|
+
"modalities": {
|
|
532
|
+
"input": [
|
|
533
|
+
"text"
|
|
534
|
+
],
|
|
535
|
+
"output": [
|
|
536
|
+
"image"
|
|
537
|
+
]
|
|
538
|
+
},
|
|
539
|
+
"capabilities": [],
|
|
540
|
+
"pricing": {},
|
|
541
|
+
"metadata": {
|
|
542
|
+
"type": "image",
|
|
543
|
+
"description": "Flux schnell",
|
|
544
|
+
"version": "0.1",
|
|
545
|
+
"status": "ready",
|
|
546
|
+
"beta": false,
|
|
547
|
+
"coder": false,
|
|
548
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/1/ai/%7Bproduct_id%7D/openai/images/generations",
|
|
549
|
+
"currency": "CHF",
|
|
550
|
+
"prices_as_of": "2026-09-30",
|
|
551
|
+
"price_per_minute": 0.3
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
"id": "BAAI/bge-reranker-v2-m3",
|
|
556
|
+
"name": "BAAI/bge-reranker-v2-m3",
|
|
557
|
+
"provider": "infomaniak",
|
|
558
|
+
"family": "baai",
|
|
559
|
+
"created_at": null,
|
|
560
|
+
"context_window": null,
|
|
561
|
+
"max_output_tokens": null,
|
|
562
|
+
"knowledge_cutoff": null,
|
|
563
|
+
"modalities": {
|
|
564
|
+
"input": [
|
|
565
|
+
"text"
|
|
566
|
+
],
|
|
567
|
+
"output": [
|
|
568
|
+
"rerank"
|
|
569
|
+
]
|
|
570
|
+
},
|
|
571
|
+
"capabilities": [],
|
|
572
|
+
"pricing": {
|
|
573
|
+
"text_tokens": {
|
|
574
|
+
"standard": {
|
|
575
|
+
"input_per_million": 0.01
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
"metadata": {
|
|
580
|
+
"type": "reranker",
|
|
581
|
+
"description": "BAAI/bge-reranker-v2-m3",
|
|
582
|
+
"status": "coming_soon",
|
|
583
|
+
"beta": false,
|
|
584
|
+
"coder": false,
|
|
585
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/cohere/v2/rerank",
|
|
586
|
+
"currency": "CHF",
|
|
587
|
+
"prices_as_of": "2026-09-30"
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
"id": "Qwen/Qwen3-Reranker-0.6B",
|
|
592
|
+
"name": "Qwen/Qwen3-Reranker-0.6B",
|
|
593
|
+
"provider": "infomaniak",
|
|
594
|
+
"family": "qwen",
|
|
595
|
+
"created_at": null,
|
|
596
|
+
"context_window": null,
|
|
597
|
+
"max_output_tokens": null,
|
|
598
|
+
"knowledge_cutoff": null,
|
|
599
|
+
"modalities": {
|
|
600
|
+
"input": [
|
|
601
|
+
"text"
|
|
602
|
+
],
|
|
603
|
+
"output": [
|
|
604
|
+
"rerank"
|
|
605
|
+
]
|
|
606
|
+
},
|
|
607
|
+
"capabilities": [],
|
|
608
|
+
"pricing": {
|
|
609
|
+
"text_tokens": {
|
|
610
|
+
"standard": {
|
|
611
|
+
"input_per_million": 0.009
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
"metadata": {
|
|
616
|
+
"type": "reranker",
|
|
617
|
+
"description": "Qwen/Qwen3-Reranker-0.6B",
|
|
618
|
+
"status": "coming_soon",
|
|
619
|
+
"beta": false,
|
|
620
|
+
"coder": false,
|
|
621
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/cohere/v2/rerank",
|
|
622
|
+
"currency": "CHF",
|
|
623
|
+
"prices_as_of": "2026-09-30"
|
|
401
624
|
}
|
|
402
625
|
}
|
|
403
626
|
]
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm-providers-infomaniak
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Andi Idogawa
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -37,7 +37,10 @@ files:
|
|
|
37
37
|
- README.md
|
|
38
38
|
- lib/ruby_llm/providers/infomaniak.rb
|
|
39
39
|
- lib/ruby_llm/providers/infomaniak/chat.rb
|
|
40
|
+
- lib/ruby_llm/providers/infomaniak/media.rb
|
|
40
41
|
- lib/ruby_llm/providers/infomaniak/models.rb
|
|
42
|
+
- lib/ruby_llm/providers/infomaniak/pricing.rb
|
|
43
|
+
- lib/ruby_llm/providers/infomaniak/rerank.rb
|
|
41
44
|
- lib/ruby_llm/providers/infomaniak/version.rb
|
|
42
45
|
- models.json
|
|
43
46
|
homepage: https://github.com/Largo/ruby_llm-providers-infomaniak
|