ruby_llm-providers-infomaniak 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 264a19f883d98cbfdfb7a49a0e1784d964e1289e224ae42dc46641c1e59e9ba5
4
- data.tar.gz: 6cbc48e9ad718684ffb0609d10f2b6a4f88043ef6aeb22d19a99341089a2c27c
3
+ metadata.gz: 8a7d8b0d086efaa9c17802b1a0fad9b129627e42014334458219956e553ea49d
4
+ data.tar.gz: 874abaf93d029ac7c471d9c0c1a7a7c1f78ff603159b67b60bf91f213111a321
5
5
  SHA512:
6
- metadata.gz: dc84db9b0ff07fb6960b3a03344a1da8667a12758bb11d4e581bca04f67273c14debf118a5006c5b27dd93b6283763a6e3dc738e01279bca9e5ba9906f96dd21
7
- data.tar.gz: d1940a9651d6ea0493903893129b86189a12f97ab5181456ab4057c9da44dbe92aa62bb785fd8908d26091f65a6e53bf58baae5130e92dd043c2e652ad95a017
6
+ metadata.gz: 991cb79aae76ef6e1b7a8df3b78468c855d7f9f6b7bcaeaf3c86a8d386f529794b95beacb15320162f492df8df997b78d83a13390b5d51a90fde9c6d7a2c002e
7
+ data.tar.gz: d70228140d4b2b2eb504f71d2637817355dc18ccee36f9a6eb4d1da736c7788cf7d03e27ce8ad529c4f4c00247bf97263c2637fd277d676575237a9f1d5e7f53
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ - Reranking (`RubyLLM.rerank`) with `BAAI/bge-reranker-v2-m3` and `Qwen/Qwen3-Reranker-0.6B`, on the
6
+ Cohere-compatible endpoint.
7
+ - Image generation (`RubyLLM.paint`) with Flux; the image type is read from the returned bytes (JPEG).
8
+ - Transcription (`RubyLLM.transcribe`) with Whisper, polling Infomaniak's asynchronous results
9
+ (`infomaniak_poll_interval`).
10
+ - The model catalog now also lists the rerankers, Flux and Whisper.
11
+ - Pricing: the catalog carries Infomaniak's CHF list prices, so `response.cost` works for chat,
12
+ embeddings and rerank (in CHF). Flux and Whisper per-minute prices are in the model metadata.
13
+ - Author name: Andi Idogawa.
14
+
3
15
  ## 0.1.0
4
16
 
5
17
  - First release: `infomaniak` provider for RubyLLM 2.x on Infomaniak AI Tools' OpenAI-compatible API, with
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Andreas Idogawa
3
+ Copyright (c) 2026 Andi Idogawa
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -17,6 +17,7 @@ chat.ask('Explain Ruby blocks in one sentence.').content
17
17
  ```
18
18
 
19
19
  - Chat, streaming, tool calling, structured output, image input, embeddings
20
+ - Reranking, image generation (Flux) and transcription (Whisper)
20
21
  - Thinking on/off per request, with the model's reasoning on `response.thinking`
21
22
  - Only an API token to configure: the AI Tools product is looked up for you
22
23
  - A model catalog with context sizes and capabilities, refreshable at runtime
@@ -60,7 +61,8 @@ caches the answer per token.
60
61
  |---|---|---|
61
62
  | `infomaniak_api_key` | required | API token with the AI Tools scope |
62
63
  | `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 |
64
+ | `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 |
65
+ | `infomaniak_poll_interval` | `2` | Seconds between checks while a transcription runs (bounded by `request_timeout`) |
64
66
 
65
67
  ## Models
66
68
 
@@ -68,25 +70,30 @@ The chat and embedding models Infomaniak served on 2026-09-30, and how each beha
68
70
  Your product lists its own with `bundle exec rake models`, or live with
69
71
  `RubyLLM::Providers::Infomaniak.refresh_models!`.
70
72
 
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 |
73
+ | Model | Context | Images | Thinking | CHF per 1M tokens, in / out |
74
+ |---|---|---|---|---|
75
+ | `moonshotai/Kimi-K2.6` (beta) | 256K | yes | on by default | 0.60 / 3.00 |
76
+ | `Qwen/Qwen3.5-397B-A17B-FP8` (beta) | 200K | yes | on by default | 0.80 / 3.60 |
77
+ | `Qwen/Qwen3.5-122B-A10B-FP8` | 200K | yes | on by default | 0.40 / 3.20 |
78
+ | `google/gemma-4-31B-it` | 100K | yes | off, opt in with an effort | 0.20 / 0.40 |
79
+ | `mistralai/Mistral-Small-4-119B-2603` | 256K | yes | off, opt in with an effort | 0.20 / 0.75 |
80
+ | `mistralai/Ministral-3-14B-Instruct-2512` | 100K | yes | none | 0.30 / 0.40 |
81
+ | `swiss-ai/Apertus-v1.5-70B` (beta) | 100K | yes | none | 0.70 / 2.50 |
82
+
83
+ | Embedding model | Input tokens | CHF per 1M tokens |
84
+ |---|---|---|
85
+ | `Qwen/Qwen3-Embedding-8B` | 8192 | 0.07 |
86
+ | `bge_multilingual_gemma2` | 8000 | 0.065 |
87
+ | `mini_lm_l12_v2` | 128 | free |
88
+
89
+ | Other models | For | CHF |
90
+ |---|---|---|
91
+ | `BAAI/bge-reranker-v2-m3` | `RubyLLM.rerank` | 0.01 per 1M tokens |
92
+ | `Qwen/Qwen3-Reranker-0.6B` | `RubyLLM.rerank` | 0.009 per 1M tokens |
93
+ | `flux` | `RubyLLM.paint`, returns JPEG | 0.30 per minute of compute |
94
+ | `whisper` | `RubyLLM.transcribe` | 0.006 per audio minute |
86
95
 
87
- Infomaniak also lists image generation (Flux, Photomaker), transcription (Whisper) and rerankers
88
- (`BAAI/bge-reranker-v2-m3`, `Qwen/Qwen3-Reranker-0.6B`). They live on other endpoints this gem does not
89
- cover yet.
96
+ Photomaker, which needs reference photos on its own route, is not covered.
90
97
 
91
98
  ### The model catalog
92
99
 
@@ -175,6 +182,53 @@ chat.ask('What is on this receipt?', with: 'receipt.jpg').content
175
182
  RubyLLM.embed('Grüezi mitenand', model: 'Qwen/Qwen3-Embedding-8B', provider: :infomaniak).vectors
176
183
  ```
177
184
 
185
+ ### Reranking
186
+
187
+ Sort retrieved passages by relevance before handing them to a chat model:
188
+
189
+ ```ruby
190
+ rerank = RubyLLM.rerank('Which metal is the densest?', passages,
191
+ model: 'BAAI/bge-reranker-v2-m3', provider: :infomaniak, top_n: 3)
192
+ rerank.results.map { |result| [result.score, result.document] }
193
+ ```
194
+
195
+ ### Image generation
196
+
197
+ ```ruby
198
+ image = RubyLLM.paint('A red Swiss train crossing a stone viaduct, watercolor',
199
+ model: 'flux', provider: :infomaniak, size: '1024x1024') # or 1024x1792, 1792x1024
200
+ image.save('train.jpg')
201
+ ```
202
+
203
+ Prompts work best in English, and are limited to 77 tokens. Infomaniak's extra options pass through
204
+ `provider_options:`, e.g. `provider_options: { style: 'photographic' }`.
205
+
206
+ ### Transcription
207
+
208
+ ```ruby
209
+ RubyLLM.transcribe('meeting.m4a', model: 'whisper', provider: :infomaniak, language: 'de').text
210
+ ```
211
+
212
+ Infomaniak runs transcriptions as background jobs: the gem uploads the file, then polls until the
213
+ transcript is ready, so the call blocks for about as long as the job takes. Streaming (passing a block)
214
+ is not possible. mp3, mp4, m4a, wav, flac, ogg, opus, aac, wma and webm are accepted.
215
+
216
+ ### Costs
217
+
218
+ The catalog carries Infomaniak's list prices, so ruby_llm computes costs from the token counts:
219
+
220
+ ```ruby
221
+ response = chat.ask('Summarise this contract.', with: 'contract.txt')
222
+ response.cost.total # => 0.0021 (CHF)
223
+ chat.cost.total # the whole conversation
224
+ ```
225
+
226
+ **Costs from this provider are in CHF**, exactly as Infomaniak bills them, although ruby_llm documents its
227
+ prices as USD. Don't add them to costs from other providers without converting. Chat, embedding and
228
+ rerank costs are computed; Flux and Whisper bill per minute, which ruby_llm cannot compute, so their
229
+ prices are only in `model.metadata[:price_per_minute]`. The prices date from 2026-09-30 and live in
230
+ `lib/ruby_llm/providers/infomaniak/pricing.rb`.
231
+
178
232
  ## How Infomaniak differs from OpenAI
179
233
 
180
234
  The gem handles these, so the RubyLLM API behaves as usual:
@@ -189,6 +243,10 @@ The gem handles these, so the RubyLLM API behaves as usual:
189
243
  | End user | `user` field | `with_end_user('id')` is sent as `user` |
190
244
  | 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
245
  | Errors | `{"error": {"code", "description"}}` on its own endpoints | The description ends up in the `RubyLLM::Error` message |
246
+ | 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 |
247
+ | Reranking | Cohere v2 format, usage as `usage.total_tokens` | Read into `rerank.tokens.input` |
248
+ | Images | Only base64 JPEG, no edits | The image type is read from the bytes; `with:` raises `ArgumentError` |
249
+ | Transcription | Asynchronous: upload returns a batch id, results are polled | Polls every `infomaniak_poll_interval` seconds until done, failed or `request_timeout` |
192
250
 
193
251
  ## Development
194
252
 
@@ -0,0 +1,95 @@
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
+ def validate_paint_inputs!(with:, mask:)
23
+ raise ArgumentError, 'Infomaniak generates images from a prompt only; it cannot edit images' if editing?(with, mask)
24
+ end
25
+
26
+ # ruby_llm assumes PNG for OpenAI-style images; read the real type from the bytes.
27
+ def parse_image_responses(response, model:)
28
+ entries = Array(response.body['data'])
29
+ raise Error.new('Infomaniak returned no image', response: response) if entries.empty?
30
+
31
+ entries.map do |entry|
32
+ Image.new(data: entry['b64_json'], mime_type: image_mime_type(entry['b64_json']),
33
+ revised_prompt: entry['revised_prompt'], model: model, usage: {})
34
+ end
35
+ end
36
+
37
+ def transcription_url
38
+ @provider.product_url(1, 'openai/audio/transcriptions')
39
+ end
40
+
41
+ def stream_transcription(*)
42
+ raise Error, 'Infomaniak transcribes asynchronously and cannot stream; call transcribe without a block'
43
+ end
44
+
45
+ def parse_transcription_response(response, model:)
46
+ batch_id = unwrap(response.body)['batch_id']
47
+ raise Error.new('Infomaniak returned no transcription batch id', response: response) unless batch_id
48
+
49
+ super(Body.new(transcript(await_batch(batch_id))), model:)
50
+ end
51
+
52
+ private
53
+
54
+ def image_mime_type(base64)
55
+ head = base64.to_s[0, 24].unpack1('m')
56
+ IMAGE_SIGNATURES.find { |signature, _| head.start_with?(signature) }&.last || 'image/png'
57
+ end
58
+
59
+ def await_batch(batch_id)
60
+ deadline = monotonic_now + @config.request_timeout
61
+ loop do
62
+ result = unwrap(@connection.get(@provider.product_url(1, "results/#{batch_id}")).body)
63
+ case result['status']
64
+ when 'success' then return result
65
+ when 'failed', 'cancelled' then raise Error, "Infomaniak transcription #{result['status']} (batch #{batch_id})"
66
+ end
67
+ raise Error, "Infomaniak transcription not done after #{@config.request_timeout}s (batch #{batch_id})" if monotonic_now > deadline
68
+
69
+ sleep(@config.infomaniak_poll_interval || 2)
70
+ end
71
+ end
72
+
73
+ # The finished batch carries the output inline, or only a download URL.
74
+ def transcript(result)
75
+ data = result['data'] || @connection.get(result['url']).body
76
+ return data unless data.is_a?(String)
77
+
78
+ parsed = JSON.parse(data)
79
+ parsed.is_a?(Hash) ? parsed : data
80
+ rescue JSON::ParserError
81
+ data
82
+ end
83
+
84
+ # Infomaniak's own routes wrap payloads as {"result": "success", "data": {...}}.
85
+ def unwrap(body)
86
+ body.is_a?(Hash) && body.key?('result') && body['data'].is_a?(Hash) ? body['data'] : body
87
+ end
88
+
89
+ def monotonic_now
90
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ 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)).map do |entry|
24
- build_model(entry, details_for(details, entry['id']))
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
- metadata: metadata_for(entry, details)
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,7 +3,7 @@
3
3
  module RubyLLM
4
4
  module Providers
5
5
  class Infomaniak < Provider
6
- VERSION = '0.1.0'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
9
9
  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"
@@ -54,7 +76,7 @@ module RubyLLM
54
76
 
55
77
  class << self
56
78
  def configuration_options
57
- %i[infomaniak_api_key infomaniak_product_id infomaniak_api_base]
79
+ %i[infomaniak_api_key infomaniak_product_id infomaniak_api_base infomaniak_poll_interval]
58
80
  end
59
81
 
60
82
  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.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - Andreas Idogawa
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