ruby_llm-providers-infomaniak 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 +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +21 -0
- data/README.md +217 -0
- data/lib/ruby_llm/providers/infomaniak/chat.rb +75 -0
- data/lib/ruby_llm/providers/infomaniak/models.rb +115 -0
- data/lib/ruby_llm/providers/infomaniak/version.rb +9 -0
- data/lib/ruby_llm/providers/infomaniak.rb +120 -0
- data/models.json +403 -0
- metadata +68 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 264a19f883d98cbfdfb7a49a0e1784d964e1289e224ae42dc46641c1e59e9ba5
|
|
4
|
+
data.tar.gz: 6cbc48e9ad718684ffb0609d10f2b6a4f88043ef6aeb22d19a99341089a2c27c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: dc84db9b0ff07fb6960b3a03344a1da8667a12758bb11d4e581bca04f67273c14debf118a5006c5b27dd93b6283763a6e3dc738e01279bca9e5ba9906f96dd21
|
|
7
|
+
data.tar.gz: d1940a9651d6ea0493903893129b86189a12f97ab5181456ab4057c9da44dbe92aa62bb785fd8908d26091f65a6e53bf58baae5130e92dd043c2e652ad95a017
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- First release: `infomaniak` provider for RubyLLM 2.x on Infomaniak AI Tools' OpenAI-compatible API, with
|
|
6
|
+
product id discovery, streaming, tools, structured output, thinking on/off, images, embeddings and a
|
|
7
|
+
model catalog built from the account's model list (`rake models`).
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andreas Idogawa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# ruby_llm-providers-infomaniak
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/ruby_llm-providers-infomaniak)
|
|
4
|
+
[](https://github.com/Largo/ruby_llm-providers-infomaniak/actions/workflows/tests.yml)
|
|
5
|
+
|
|
6
|
+
**Open-weight models hosted in Switzerland, through the RubyLLM API you already know.**
|
|
7
|
+
|
|
8
|
+
This gem adds [Infomaniak AI Tools](https://www.infomaniak.com/en/hosting/ai-tools) as a provider to
|
|
9
|
+
[RubyLLM](https://rubyllm.com). Kimi, Qwen, Gemma, Mistral and Apertus run in Infomaniak's Swiss data
|
|
10
|
+
centres; your Ruby code keeps using `RubyLLM.chat`, tools, schemas, streaming and embeddings as with any
|
|
11
|
+
other provider.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
chat = RubyLLM.chat(model: 'moonshotai/Kimi-K2.6', provider: :infomaniak)
|
|
15
|
+
chat.ask('Explain Ruby blocks in one sentence.').content
|
|
16
|
+
# => "A Ruby block is a chunk of code you pass to a method ..."
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- Chat, streaming, tool calling, structured output, image input, embeddings
|
|
20
|
+
- Thinking on/off per request, with the model's reasoning on `response.thinking`
|
|
21
|
+
- Only an API token to configure: the AI Tools product is looked up for you
|
|
22
|
+
- A model catalog with context sizes and capabilities, refreshable at runtime
|
|
23
|
+
- Irons out Infomaniak's differences from OpenAI's API, such as Kimi's broken JSON while thinking
|
|
24
|
+
|
|
25
|
+
## Contents
|
|
26
|
+
|
|
27
|
+
- [Installation](#installation)
|
|
28
|
+
- [Configuration](#configuration)
|
|
29
|
+
- [Models](#models)
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [How Infomaniak differs from OpenAI](#how-infomaniak-differs-from-openai)
|
|
32
|
+
- [Development](#development)
|
|
33
|
+
- [Releasing](#releasing)
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# Gemfile
|
|
39
|
+
gem 'ruby_llm-providers-infomaniak', require: 'ruby_llm/providers/infomaniak'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Requires Ruby 3.2+ and RubyLLM 2.x.
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
You need an Infomaniak API token with the **AI Tools** scope (Infomaniak Manager > API tokens).
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require 'ruby_llm/providers/infomaniak'
|
|
50
|
+
|
|
51
|
+
RubyLLM.configure do |config|
|
|
52
|
+
config.infomaniak_api_key = ENV['INFOMANIAK_API_KEY']
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
That is all. On first use the provider asks `GET /1/ai` which AI Tools product the token belongs to, and
|
|
57
|
+
caches the answer per token.
|
|
58
|
+
|
|
59
|
+
| Option | Default | |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `infomaniak_api_key` | required | API token with the AI Tools scope |
|
|
62
|
+
| `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
|
+
|
|
65
|
+
## Models
|
|
66
|
+
|
|
67
|
+
The chat and embedding models Infomaniak served on 2026-09-30, and how each behaved when probed.
|
|
68
|
+
Your product lists its own with `bundle exec rake models`, or live with
|
|
69
|
+
`RubyLLM::Providers::Infomaniak.refresh_models!`.
|
|
70
|
+
|
|
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 |
|
|
86
|
+
|
|
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.
|
|
90
|
+
|
|
91
|
+
### The model catalog
|
|
92
|
+
|
|
93
|
+
Any model id your product serves works as given, catalog or not. The gem ships a catalog, `models.json`,
|
|
94
|
+
so that ruby_llm also knows each model's context size and capabilities. That enables:
|
|
95
|
+
|
|
96
|
+
- `with_thinking` and `with_thinking(false)`, which need to know how a model switches thinking
|
|
97
|
+
- `RubyLLM.chat(model: 'moonshotai/Kimi-K2.6')` without `provider:`
|
|
98
|
+
- `RubyLLM.models.by_provider(:infomaniak)` without a network call
|
|
99
|
+
|
|
100
|
+
The catalog is a snapshot from the gem release. To know about models Infomaniak added since, load the
|
|
101
|
+
live list at boot (two API calls):
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
RubyLLM::Providers::Infomaniak.refresh_models!
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`RubyLLM.models.refresh!` does not do this: it skips providers that ship their own catalog.
|
|
108
|
+
|
|
109
|
+
## Usage
|
|
110
|
+
|
|
111
|
+
### Chat and streaming
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
chat = RubyLLM.chat(model: 'moonshotai/Kimi-K2.6', provider: :infomaniak)
|
|
115
|
+
|
|
116
|
+
chat.with_instructions('Answer like a Swiss train conductor.')
|
|
117
|
+
chat.ask('When does the next train to Bern leave?').content
|
|
118
|
+
|
|
119
|
+
chat.ask('Tell me a story about a marmot.') { |chunk| print chunk.content }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Tools
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
class Weather < RubyLLM::Tool
|
|
126
|
+
description 'Current weather for a city'
|
|
127
|
+
parameter :city, description: 'City name'
|
|
128
|
+
|
|
129
|
+
def execute(city:) = WeatherService.current(city)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
chat.with_tools(Weather).ask('Do I need an umbrella in Lausanne today?').content
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Structured output
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
schema = {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: { city: { type: 'string' }, population: { type: 'integer' } },
|
|
141
|
+
required: %w[city population],
|
|
142
|
+
additionalProperties: false
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
chat.with_schema(schema).ask('Largest city in Switzerland?').parsed
|
|
146
|
+
# => {"city" => "Zurich", "population" => 443000}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Thinking
|
|
150
|
+
|
|
151
|
+
Kimi and Qwen think before they answer, unless told not to. The reasoning comes back separately from the
|
|
152
|
+
answer:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
response = chat.ask('Is 1001 prime?')
|
|
156
|
+
response.thinking&.text # the model's reasoning
|
|
157
|
+
response.content # the answer
|
|
158
|
+
|
|
159
|
+
chat.with_thinking(effort: :none).ask('Quick: 17 * 23?') # thinking off: faster, cheaper
|
|
160
|
+
RubyLLM.chat(model: 'google/gemma-4-31B-it', provider: :infomaniak)
|
|
161
|
+
.with_thinking(effort: :high).ask('Plan a 3-day Ticino trip.') # thinking on
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
With the model in the catalog, `with_thinking` and `with_thinking(false)` work too.
|
|
165
|
+
|
|
166
|
+
### Images
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
chat.ask('What is on this receipt?', with: 'receipt.jpg').content
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Embeddings
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
RubyLLM.embed('Grüezi mitenand', model: 'Qwen/Qwen3-Embedding-8B', provider: :infomaniak).vectors
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## How Infomaniak differs from OpenAI
|
|
179
|
+
|
|
180
|
+
The gem handles these, so the RubyLLM API behaves as usual:
|
|
181
|
+
|
|
182
|
+
| | What Infomaniak does | What the gem does |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| Thinking | `reasoning_effort` is an on/off switch: `none` or `low`/`medium`/`high` | `:none` turns thinking off; `:minimal` is sent as `low`, `:xhigh` and `:max` as `high` |
|
|
185
|
+
| Kimi + schema | While thinking, Kimi answers `{{ ... }` instead of JSON | Kimi schema requests go out with thinking off, unless you ask for thinking, which logs a warning |
|
|
186
|
+
| System prompt | `system` role, not OpenAI's `developer` | Sends `system` |
|
|
187
|
+
| Output limit | `max_completion_tokens` | `with_max_output_tokens` maps to it |
|
|
188
|
+
| Attachments | Images inline; no audio, no PDF | Audio and PDFs raise `UnsupportedAttachmentError` before sending; text files are inlined |
|
|
189
|
+
| End user | `user` field | `with_end_user('id')` is sent as `user` |
|
|
190
|
+
| 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
|
+
| Errors | `{"error": {"code", "description"}}` on its own endpoints | The description ends up in the `RubyLLM::Error` message |
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
bundle install
|
|
197
|
+
cp test/.env.example test/.env # put your API token in test/.env (gitignored)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
| Command | |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `bundle exec ruby bin/console` | IRB with `chat`, `models` and a `Weather` tool, configured from `test/.env` |
|
|
203
|
+
| `bundle exec rake test` | Offline tests, all HTTP stubbed with WebMock. What CI runs |
|
|
204
|
+
| `bundle exec rake live` | The same features against the real API, with your token |
|
|
205
|
+
| `bundle exec rake models` | Rebuilds `models.json` from your product's model list |
|
|
206
|
+
|
|
207
|
+
`test/.env` also takes `INFOMANIAK_MODEL` (the console and live-test model, default Kimi-K2.6),
|
|
208
|
+
`INFOMANIAK_EMBEDDING_MODEL` (adds embeddings to `rake live`) and `RUBYLLM_DEBUG=1` (logs every request).
|
|
209
|
+
|
|
210
|
+
## Releasing
|
|
211
|
+
|
|
212
|
+
Tags publish to RubyGems through trusted publishing, without a stored API key. See
|
|
213
|
+
[RELEASING.md](RELEASING.md).
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT. Not affiliated with Infomaniak.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Infomaniak < Provider
|
|
6
|
+
# How Infomaniak's Chat Completions differ from OpenAI's: a plain system
|
|
7
|
+
# role, max_completion_tokens, reasoning_effort as an on/off switch, no
|
|
8
|
+
# OpenAI prompt-cache fields, and images as the only binary attachments.
|
|
9
|
+
module Chat
|
|
10
|
+
EFFORTS = %w[none low medium high].freeze
|
|
11
|
+
|
|
12
|
+
# Models whose schema-constrained output breaks while they think: Kimi
|
|
13
|
+
# answers "{{ ... }" instead of JSON (seen 2026-09-30).
|
|
14
|
+
SCHEMA_WITHOUT_THINKING = /kimi/i
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def render_payload(messages, tools:, temperature:, model:, stream: false, max_output_tokens: nil,
|
|
19
|
+
schema: nil, thinking: nil, citations: false, caching: nil, tool_prefs: nil)
|
|
20
|
+
payload = super
|
|
21
|
+
schema_without_thinking(payload, model) if schema && model.id.match?(SCHEMA_WITHOUT_THINKING)
|
|
22
|
+
payload
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Thinking goes off for structured output unless it was asked for.
|
|
26
|
+
def schema_without_thinking(payload, model)
|
|
27
|
+
effort = payload[:reasoning_effort]
|
|
28
|
+
return payload[:reasoning_effort] = 'none' if effort.nil?
|
|
29
|
+
return if effort == 'none'
|
|
30
|
+
|
|
31
|
+
RubyLLM.logger.warn(
|
|
32
|
+
"#{model.id} returns malformed JSON for a schema while thinking; use with_thinking(effort: :none)"
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def format_role(role)
|
|
37
|
+
role.to_s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def max_output_tokens_field(_model)
|
|
41
|
+
:max_completion_tokens
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Infomaniak reads reasoning_effort as a switch: "none" turns thinking
|
|
45
|
+
# off, any other value turns it on. Efforts outside its four values are
|
|
46
|
+
# clamped to the nearest one.
|
|
47
|
+
def resolve_effort(thinking)
|
|
48
|
+
return 'none' if thinking.respond_to?(:disabled?) && thinking.disabled?
|
|
49
|
+
|
|
50
|
+
effort = super
|
|
51
|
+
return effort if effort.nil? || EFFORTS.include?(effort)
|
|
52
|
+
|
|
53
|
+
effort == 'minimal' ? 'low' : 'high'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def openai_prompt_caching?
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def apply_end_user(payload, identifier)
|
|
61
|
+
payload.merge(user: identifier)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def format_content(content, attachments = [])
|
|
65
|
+
Protocols::ChatCompletions::Media.format_content(
|
|
66
|
+
content,
|
|
67
|
+
attachments,
|
|
68
|
+
document_attachments: :none,
|
|
69
|
+
audio_attachments: false
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Infomaniak < Provider
|
|
6
|
+
# Model listing. The OpenAI-style list names the ids this product can
|
|
7
|
+
# call; GET /1/ai/models adds the type, context size and beta flag.
|
|
8
|
+
# Neither reports vision or thinking, so those come from the model name,
|
|
9
|
+
# following what the served models did when probed (2026-09-30).
|
|
10
|
+
module Models
|
|
11
|
+
EFFORT_OPTION = { type: 'effort', values: Chat::EFFORTS }.freeze
|
|
12
|
+
|
|
13
|
+
# No reasoning output even with effort: :high. Mistral Small 4 does think.
|
|
14
|
+
NO_THINKING = /apertus|ministral|mistral-?3\b|mistral-small-3|llama|granite/i
|
|
15
|
+
VISION = /
|
|
16
|
+
\bvl\b|-vl-|vision|pixtral|qwen3\.5|kimi-k2\.[5-9]|gemma-?[34]|
|
|
17
|
+
ministral-3|mistral-small-4|apertus-v1\.5|llama-4
|
|
18
|
+
/ix
|
|
19
|
+
EMBEDDING = /embed|bge|\be5\b|gte|mini_?lm/i
|
|
20
|
+
|
|
21
|
+
def list_models
|
|
22
|
+
details = catalog_details
|
|
23
|
+
listed_models(@connection.get(models_url)).map do |entry|
|
|
24
|
+
build_model(entry, details_for(details, entry['id']))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def listed_models(response)
|
|
31
|
+
data = response.body['data']
|
|
32
|
+
data.is_a?(Hash) ? [data] : Array(data)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The catalog names a model either by its full id or without the
|
|
36
|
+
# organisation prefix.
|
|
37
|
+
def details_for(details, id)
|
|
38
|
+
key = id.to_s.downcase
|
|
39
|
+
details[key] || details[key.split('/').last] || {}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def catalog_details
|
|
43
|
+
data = Array(@provider.account_connection.get('1/ai/models').body['data'])
|
|
44
|
+
data.to_h { |entry| [entry['name'].to_s.downcase, entry] }
|
|
45
|
+
rescue RubyLLM::Error, Faraday::Error => e
|
|
46
|
+
RubyLLM.logger.warn("Infomaniak model details unavailable (#{e.message}); listing ids only")
|
|
47
|
+
{}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def build_model(entry, details)
|
|
51
|
+
id = entry['id']
|
|
52
|
+
type = model_type(id, details['type'])
|
|
53
|
+
vision = type == :chat && (id.match?(VISION) || details['description'].to_s.match?(/vision|image/i))
|
|
54
|
+
thinking = type == :chat && !id.match?(NO_THINKING)
|
|
55
|
+
|
|
56
|
+
Model.new(
|
|
57
|
+
id: id,
|
|
58
|
+
name: id,
|
|
59
|
+
provider: @provider.slug,
|
|
60
|
+
family: id.include?('/') ? id.split('/').first.downcase : nil,
|
|
61
|
+
created_at: entry['created'] ? Time.at(entry['created']) : nil,
|
|
62
|
+
context_window: details['max_token_input'],
|
|
63
|
+
capabilities: capabilities_for(type, vision:, thinking:),
|
|
64
|
+
modalities: modalities_for(type, vision:),
|
|
65
|
+
reasoning_options: thinking ? [EFFORT_OPTION] : [],
|
|
66
|
+
metadata: metadata_for(entry, details)
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def model_type(id, type)
|
|
71
|
+
case type.to_s.downcase
|
|
72
|
+
when /embed/ then :embedding
|
|
73
|
+
when /rerank/ then :rerank
|
|
74
|
+
when /stt|speech|audio|whisper/ then :transcription
|
|
75
|
+
when /image/ then :image
|
|
76
|
+
when '' then id.match?(EMBEDDING) ? :embedding : :chat
|
|
77
|
+
else :chat
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def capabilities_for(type, vision:, thinking:)
|
|
82
|
+
return [] unless type == :chat
|
|
83
|
+
|
|
84
|
+
capabilities = %w[streaming function_calling structured_output json_mode]
|
|
85
|
+
capabilities << 'vision' if vision
|
|
86
|
+
capabilities << 'reasoning' if thinking
|
|
87
|
+
capabilities
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def modalities_for(type, vision:)
|
|
91
|
+
case type
|
|
92
|
+
when :embedding then { input: %w[text], output: %w[embeddings] }
|
|
93
|
+
when :rerank then { input: %w[text], output: %w[rerank] }
|
|
94
|
+
when :transcription then { input: %w[audio], output: %w[text] }
|
|
95
|
+
when :image then { input: %w[text], output: %w[image] }
|
|
96
|
+
else { input: vision ? %w[text image] : %w[text], output: %w[text] }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def metadata_for(entry, details)
|
|
101
|
+
{
|
|
102
|
+
owned_by: entry['owned_by'],
|
|
103
|
+
type: details['type'],
|
|
104
|
+
description: details['description'],
|
|
105
|
+
version: details['version'],
|
|
106
|
+
status: details['info_status'],
|
|
107
|
+
beta: details.dig('meta', 'is_beta'),
|
|
108
|
+
coder: details.dig('meta', 'is_coder'),
|
|
109
|
+
documentation: details['documentation_link']
|
|
110
|
+
}.compact
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby_llm'
|
|
4
|
+
require_relative 'infomaniak/version'
|
|
5
|
+
require_relative 'infomaniak/chat'
|
|
6
|
+
require_relative 'infomaniak/models'
|
|
7
|
+
|
|
8
|
+
module RubyLLM
|
|
9
|
+
module Providers
|
|
10
|
+
# Infomaniak AI Tools: OpenAI-compatible chat completions, embeddings and
|
|
11
|
+
# model listing under https://api.infomaniak.com/2/ai/{product_id}/openai/v1.
|
|
12
|
+
class Infomaniak < Provider
|
|
13
|
+
API_HOST = 'https://api.infomaniak.com'
|
|
14
|
+
|
|
15
|
+
# Infomaniak's dialect of the Chat Completions API.
|
|
16
|
+
class ChatCompletions < Protocols::ChatCompletions
|
|
17
|
+
include Infomaniak::Chat
|
|
18
|
+
include Infomaniak::Models
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protocol :chat_completions, ChatCompletions
|
|
22
|
+
|
|
23
|
+
def api_base
|
|
24
|
+
@config.infomaniak_api_base || "#{API_HOST}/2/ai/#{product_id}/openai/v1"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def headers
|
|
28
|
+
{ 'Authorization' => "Bearer #{@config.infomaniak_api_key}" }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The AI Tools product requests are billed to: infomaniak_product_id, or
|
|
32
|
+
# the token's only product, looked up once per token with GET /1/ai.
|
|
33
|
+
def product_id
|
|
34
|
+
configured = @config.infomaniak_product_id.to_s.strip
|
|
35
|
+
return configured unless configured.empty?
|
|
36
|
+
|
|
37
|
+
self.class.product_ids[@config.infomaniak_api_key] ||= discover_product_id
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The account-level API, which serves product and model metadata
|
|
41
|
+
# outside the OpenAI base path.
|
|
42
|
+
def account_connection # :nodoc:
|
|
43
|
+
@account_connection ||= Transport::Connection.new(self, @config, api_base: API_HOST)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Infomaniak's own endpoints answer {"result":"error","error":{"code":..,"description":..}}.
|
|
47
|
+
def parse_error(response)
|
|
48
|
+
body = parse_error_body(response)
|
|
49
|
+
error = body['error'] if body.is_a?(Hash)
|
|
50
|
+
return super unless error.is_a?(Hash) && error['description']
|
|
51
|
+
|
|
52
|
+
[error['description'], error['code']].compact.uniq.join(' - ')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class << self
|
|
56
|
+
def configuration_options
|
|
57
|
+
%i[infomaniak_api_key infomaniak_product_id infomaniak_api_base]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def configuration_requirements
|
|
61
|
+
%i[infomaniak_api_key]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns +true+: any model id the product serves is accepted as given.
|
|
65
|
+
# `rake models` refreshes the bundled catalog with capabilities.
|
|
66
|
+
def assume_models_exist?
|
|
67
|
+
true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Loads the live model list into RubyLLM.models in place of the
|
|
71
|
+
# catalog bundled with the gem, so models Infomaniak added since the
|
|
72
|
+
# release are known with their capabilities. Returns those models.
|
|
73
|
+
#
|
|
74
|
+
# RubyLLM::Providers::Infomaniak.refresh_models!
|
|
75
|
+
#
|
|
76
|
+
def refresh_models!(config = RubyLLM.config)
|
|
77
|
+
live = new(config).list_models
|
|
78
|
+
registry = RubyLLM.models
|
|
79
|
+
others = registry.all_including_unlisted.reject { |model| model.provider == slug }
|
|
80
|
+
# RubyLLM has no public way to replace one provider's entries, and
|
|
81
|
+
# RubyLLM.models.refresh! skips providers that ship a catalog.
|
|
82
|
+
registry.instance_variable_set(:@models, others + live)
|
|
83
|
+
live
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def product_ids # :nodoc:
|
|
87
|
+
@product_ids ||= {}
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def discover_product_id
|
|
94
|
+
products = fetch_products
|
|
95
|
+
return products.first['product_id'].to_s if products.one?
|
|
96
|
+
|
|
97
|
+
raise ConfigurationError, product_error(products)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def fetch_products
|
|
101
|
+
Array(account_connection.get('1/ai').body['data'])
|
|
102
|
+
rescue RubyLLM::Error, Faraday::Error => e
|
|
103
|
+
raise ConfigurationError,
|
|
104
|
+
"Could not look up the Infomaniak AI Tools product (#{e.message}). " \
|
|
105
|
+
'Set config.infomaniak_product_id, or check that the token has the AI Tools scope.'
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def product_error(products)
|
|
109
|
+
return 'No Infomaniak AI Tools product is visible to this token. Set config.infomaniak_product_id.' if products.empty?
|
|
110
|
+
|
|
111
|
+
listed = products.map { |p| "#{p['product_id']} (#{p['product_name']}, #{p['account_name']})" }
|
|
112
|
+
"This token reaches #{products.size} AI Tools products: #{listed.join(', ')}. " \
|
|
113
|
+
'Set config.infomaniak_product_id to the one to use.'
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
RubyLLM::Provider.register :infomaniak, RubyLLM::Providers::Infomaniak,
|
|
120
|
+
models: File.expand_path('../../../models.json', __dir__)
|
data/models.json
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "bge_multilingual_gemma2",
|
|
4
|
+
"name": "bge_multilingual_gemma2",
|
|
5
|
+
"provider": "infomaniak",
|
|
6
|
+
"family": null,
|
|
7
|
+
"created_at": "2024-12-03 23:00:00 UTC",
|
|
8
|
+
"context_window": 8000,
|
|
9
|
+
"max_output_tokens": null,
|
|
10
|
+
"knowledge_cutoff": null,
|
|
11
|
+
"modalities": {
|
|
12
|
+
"input": [
|
|
13
|
+
"text"
|
|
14
|
+
],
|
|
15
|
+
"output": [
|
|
16
|
+
"embeddings"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"capabilities": [],
|
|
20
|
+
"pricing": {},
|
|
21
|
+
"metadata": {
|
|
22
|
+
"owned_by": "system",
|
|
23
|
+
"type": "embedding",
|
|
24
|
+
"description": "Bge Multilingual Gemma2",
|
|
25
|
+
"version": "1.0",
|
|
26
|
+
"status": "coming_soon",
|
|
27
|
+
"beta": false,
|
|
28
|
+
"coder": false,
|
|
29
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "mini_lm_l12_v2",
|
|
34
|
+
"name": "mini_lm_l12_v2",
|
|
35
|
+
"provider": "infomaniak",
|
|
36
|
+
"family": null,
|
|
37
|
+
"created_at": "2024-12-03 23:00:00 UTC",
|
|
38
|
+
"context_window": 128,
|
|
39
|
+
"max_output_tokens": null,
|
|
40
|
+
"knowledge_cutoff": null,
|
|
41
|
+
"modalities": {
|
|
42
|
+
"input": [
|
|
43
|
+
"text"
|
|
44
|
+
],
|
|
45
|
+
"output": [
|
|
46
|
+
"embeddings"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"capabilities": [],
|
|
50
|
+
"pricing": {},
|
|
51
|
+
"metadata": {
|
|
52
|
+
"owned_by": "system",
|
|
53
|
+
"type": "embedding",
|
|
54
|
+
"description": "All MiniLM L12 v2",
|
|
55
|
+
"version": "2.0",
|
|
56
|
+
"status": "coming_soon",
|
|
57
|
+
"beta": false,
|
|
58
|
+
"coder": false,
|
|
59
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "Qwen/Qwen3-Embedding-8B",
|
|
64
|
+
"name": "Qwen/Qwen3-Embedding-8B",
|
|
65
|
+
"provider": "infomaniak",
|
|
66
|
+
"family": "qwen",
|
|
67
|
+
"created_at": "2025-11-23 23:00:00 UTC",
|
|
68
|
+
"context_window": 8192,
|
|
69
|
+
"max_output_tokens": null,
|
|
70
|
+
"knowledge_cutoff": null,
|
|
71
|
+
"modalities": {
|
|
72
|
+
"input": [
|
|
73
|
+
"text"
|
|
74
|
+
],
|
|
75
|
+
"output": [
|
|
76
|
+
"embeddings"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
"capabilities": [],
|
|
80
|
+
"pricing": {},
|
|
81
|
+
"metadata": {
|
|
82
|
+
"owned_by": "system",
|
|
83
|
+
"type": "embedding",
|
|
84
|
+
"description": "Qwen/Qwen3-Embedding-8B",
|
|
85
|
+
"status": "coming_soon",
|
|
86
|
+
"beta": false,
|
|
87
|
+
"coder": false,
|
|
88
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/embeddings"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "mistralai/Ministral-3-14B-Instruct-2512",
|
|
93
|
+
"name": "mistralai/Ministral-3-14B-Instruct-2512",
|
|
94
|
+
"provider": "infomaniak",
|
|
95
|
+
"family": "mistralai",
|
|
96
|
+
"created_at": "2026-02-01 23:00:00 UTC",
|
|
97
|
+
"context_window": 100000,
|
|
98
|
+
"max_output_tokens": null,
|
|
99
|
+
"knowledge_cutoff": null,
|
|
100
|
+
"modalities": {
|
|
101
|
+
"input": [
|
|
102
|
+
"text",
|
|
103
|
+
"image"
|
|
104
|
+
],
|
|
105
|
+
"output": [
|
|
106
|
+
"text"
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"capabilities": [
|
|
110
|
+
"streaming",
|
|
111
|
+
"function_calling",
|
|
112
|
+
"structured_output",
|
|
113
|
+
"json_mode",
|
|
114
|
+
"vision"
|
|
115
|
+
],
|
|
116
|
+
"pricing": {},
|
|
117
|
+
"metadata": {
|
|
118
|
+
"owned_by": "system",
|
|
119
|
+
"type": "llm",
|
|
120
|
+
"description": "mistralai/Ministral-3-14B-Instruct-2512",
|
|
121
|
+
"status": "ready",
|
|
122
|
+
"beta": false,
|
|
123
|
+
"coder": false,
|
|
124
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "Qwen/Qwen3.5-122B-A10B-FP8",
|
|
129
|
+
"name": "Qwen/Qwen3.5-122B-A10B-FP8",
|
|
130
|
+
"provider": "infomaniak",
|
|
131
|
+
"family": "qwen",
|
|
132
|
+
"created_at": null,
|
|
133
|
+
"context_window": 200000,
|
|
134
|
+
"max_output_tokens": null,
|
|
135
|
+
"knowledge_cutoff": null,
|
|
136
|
+
"modalities": {
|
|
137
|
+
"input": [
|
|
138
|
+
"text",
|
|
139
|
+
"image"
|
|
140
|
+
],
|
|
141
|
+
"output": [
|
|
142
|
+
"text"
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
"capabilities": [
|
|
146
|
+
"streaming",
|
|
147
|
+
"function_calling",
|
|
148
|
+
"structured_output",
|
|
149
|
+
"json_mode",
|
|
150
|
+
"vision",
|
|
151
|
+
"reasoning"
|
|
152
|
+
],
|
|
153
|
+
"pricing": {},
|
|
154
|
+
"metadata": {
|
|
155
|
+
"owned_by": "system",
|
|
156
|
+
"type": "llm",
|
|
157
|
+
"description": "Qwen/Qwen3.5-122B-A10B-FP8",
|
|
158
|
+
"status": "coming_soon",
|
|
159
|
+
"beta": false,
|
|
160
|
+
"coder": false,
|
|
161
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
162
|
+
"reasoning_options": [
|
|
163
|
+
{
|
|
164
|
+
"type": "effort",
|
|
165
|
+
"values": [
|
|
166
|
+
"none",
|
|
167
|
+
"low",
|
|
168
|
+
"medium",
|
|
169
|
+
"high"
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "google/gemma-4-31B-it",
|
|
177
|
+
"name": "google/gemma-4-31B-it",
|
|
178
|
+
"provider": "infomaniak",
|
|
179
|
+
"family": "google",
|
|
180
|
+
"created_at": null,
|
|
181
|
+
"context_window": 100000,
|
|
182
|
+
"max_output_tokens": null,
|
|
183
|
+
"knowledge_cutoff": null,
|
|
184
|
+
"modalities": {
|
|
185
|
+
"input": [
|
|
186
|
+
"text",
|
|
187
|
+
"image"
|
|
188
|
+
],
|
|
189
|
+
"output": [
|
|
190
|
+
"text"
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
"capabilities": [
|
|
194
|
+
"streaming",
|
|
195
|
+
"function_calling",
|
|
196
|
+
"structured_output",
|
|
197
|
+
"json_mode",
|
|
198
|
+
"vision",
|
|
199
|
+
"reasoning"
|
|
200
|
+
],
|
|
201
|
+
"pricing": {},
|
|
202
|
+
"metadata": {
|
|
203
|
+
"owned_by": "system",
|
|
204
|
+
"type": "llm",
|
|
205
|
+
"description": "google/gemma-4-31B-it",
|
|
206
|
+
"status": "coming_soon",
|
|
207
|
+
"beta": false,
|
|
208
|
+
"coder": false,
|
|
209
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
210
|
+
"reasoning_options": [
|
|
211
|
+
{
|
|
212
|
+
"type": "effort",
|
|
213
|
+
"values": [
|
|
214
|
+
"none",
|
|
215
|
+
"low",
|
|
216
|
+
"medium",
|
|
217
|
+
"high"
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": "moonshotai/Kimi-K2.6",
|
|
225
|
+
"name": "moonshotai/Kimi-K2.6",
|
|
226
|
+
"provider": "infomaniak",
|
|
227
|
+
"family": "moonshotai",
|
|
228
|
+
"created_at": null,
|
|
229
|
+
"context_window": 256000,
|
|
230
|
+
"max_output_tokens": null,
|
|
231
|
+
"knowledge_cutoff": null,
|
|
232
|
+
"modalities": {
|
|
233
|
+
"input": [
|
|
234
|
+
"text",
|
|
235
|
+
"image"
|
|
236
|
+
],
|
|
237
|
+
"output": [
|
|
238
|
+
"text"
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
"capabilities": [
|
|
242
|
+
"streaming",
|
|
243
|
+
"function_calling",
|
|
244
|
+
"structured_output",
|
|
245
|
+
"json_mode",
|
|
246
|
+
"vision",
|
|
247
|
+
"reasoning"
|
|
248
|
+
],
|
|
249
|
+
"pricing": {},
|
|
250
|
+
"metadata": {
|
|
251
|
+
"owned_by": "system",
|
|
252
|
+
"type": "llm",
|
|
253
|
+
"description": "moonshotai/Kimi-K2.6",
|
|
254
|
+
"status": "coming_soon",
|
|
255
|
+
"beta": true,
|
|
256
|
+
"coder": false,
|
|
257
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
258
|
+
"reasoning_options": [
|
|
259
|
+
{
|
|
260
|
+
"type": "effort",
|
|
261
|
+
"values": [
|
|
262
|
+
"none",
|
|
263
|
+
"low",
|
|
264
|
+
"medium",
|
|
265
|
+
"high"
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"id": "mistralai/Mistral-Small-4-119B-2603",
|
|
273
|
+
"name": "mistralai/Mistral-Small-4-119B-2603",
|
|
274
|
+
"provider": "infomaniak",
|
|
275
|
+
"family": "mistralai",
|
|
276
|
+
"created_at": null,
|
|
277
|
+
"context_window": 256000,
|
|
278
|
+
"max_output_tokens": null,
|
|
279
|
+
"knowledge_cutoff": null,
|
|
280
|
+
"modalities": {
|
|
281
|
+
"input": [
|
|
282
|
+
"text",
|
|
283
|
+
"image"
|
|
284
|
+
],
|
|
285
|
+
"output": [
|
|
286
|
+
"text"
|
|
287
|
+
]
|
|
288
|
+
},
|
|
289
|
+
"capabilities": [
|
|
290
|
+
"streaming",
|
|
291
|
+
"function_calling",
|
|
292
|
+
"structured_output",
|
|
293
|
+
"json_mode",
|
|
294
|
+
"vision",
|
|
295
|
+
"reasoning"
|
|
296
|
+
],
|
|
297
|
+
"pricing": {},
|
|
298
|
+
"metadata": {
|
|
299
|
+
"owned_by": "system",
|
|
300
|
+
"type": "llm",
|
|
301
|
+
"description": "mistralai/Mistral-Small-4-119B-2603",
|
|
302
|
+
"status": "coming_soon",
|
|
303
|
+
"beta": false,
|
|
304
|
+
"coder": false,
|
|
305
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
306
|
+
"reasoning_options": [
|
|
307
|
+
{
|
|
308
|
+
"type": "effort",
|
|
309
|
+
"values": [
|
|
310
|
+
"none",
|
|
311
|
+
"low",
|
|
312
|
+
"medium",
|
|
313
|
+
"high"
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"id": "Qwen/Qwen3.5-397B-A17B-FP8",
|
|
321
|
+
"name": "Qwen/Qwen3.5-397B-A17B-FP8",
|
|
322
|
+
"provider": "infomaniak",
|
|
323
|
+
"family": "qwen",
|
|
324
|
+
"created_at": null,
|
|
325
|
+
"context_window": 200000,
|
|
326
|
+
"max_output_tokens": null,
|
|
327
|
+
"knowledge_cutoff": null,
|
|
328
|
+
"modalities": {
|
|
329
|
+
"input": [
|
|
330
|
+
"text",
|
|
331
|
+
"image"
|
|
332
|
+
],
|
|
333
|
+
"output": [
|
|
334
|
+
"text"
|
|
335
|
+
]
|
|
336
|
+
},
|
|
337
|
+
"capabilities": [
|
|
338
|
+
"streaming",
|
|
339
|
+
"function_calling",
|
|
340
|
+
"structured_output",
|
|
341
|
+
"json_mode",
|
|
342
|
+
"vision",
|
|
343
|
+
"reasoning"
|
|
344
|
+
],
|
|
345
|
+
"pricing": {},
|
|
346
|
+
"metadata": {
|
|
347
|
+
"owned_by": "system",
|
|
348
|
+
"type": "llm",
|
|
349
|
+
"description": "Qwen/Qwen3.5-397B-A17B-FP8",
|
|
350
|
+
"status": "coming_soon",
|
|
351
|
+
"beta": true,
|
|
352
|
+
"coder": false,
|
|
353
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions",
|
|
354
|
+
"reasoning_options": [
|
|
355
|
+
{
|
|
356
|
+
"type": "effort",
|
|
357
|
+
"values": [
|
|
358
|
+
"none",
|
|
359
|
+
"low",
|
|
360
|
+
"medium",
|
|
361
|
+
"high"
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"id": "swiss-ai/Apertus-v1.5-70B",
|
|
369
|
+
"name": "swiss-ai/Apertus-v1.5-70B",
|
|
370
|
+
"provider": "infomaniak",
|
|
371
|
+
"family": "swiss-ai",
|
|
372
|
+
"created_at": null,
|
|
373
|
+
"context_window": 100000,
|
|
374
|
+
"max_output_tokens": null,
|
|
375
|
+
"knowledge_cutoff": null,
|
|
376
|
+
"modalities": {
|
|
377
|
+
"input": [
|
|
378
|
+
"text",
|
|
379
|
+
"image"
|
|
380
|
+
],
|
|
381
|
+
"output": [
|
|
382
|
+
"text"
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
"capabilities": [
|
|
386
|
+
"streaming",
|
|
387
|
+
"function_calling",
|
|
388
|
+
"structured_output",
|
|
389
|
+
"json_mode",
|
|
390
|
+
"vision"
|
|
391
|
+
],
|
|
392
|
+
"pricing": {},
|
|
393
|
+
"metadata": {
|
|
394
|
+
"owned_by": "system",
|
|
395
|
+
"type": "llm",
|
|
396
|
+
"description": "swiss-ai/Apertus-v1.5-70B",
|
|
397
|
+
"status": "coming_soon",
|
|
398
|
+
"beta": true,
|
|
399
|
+
"coder": false,
|
|
400
|
+
"documentation": "https://developer.infomaniak.com/docs/api/post/2/ai/%7Bproduct_id%7D/openai/v1/chat/completions"
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
]
|
metadata
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby_llm-providers-infomaniak
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andreas Idogawa
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ruby_llm
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
description: 'Use the models hosted by Infomaniak AI Tools in Switzerland (Kimi, Qwen,
|
|
27
|
+
Apertus, Mistral, ...) through RubyLLM: chat, streaming, tools, structured output,
|
|
28
|
+
thinking, images and embeddings.'
|
|
29
|
+
email:
|
|
30
|
+
- web@idogawa.com
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- CHANGELOG.md
|
|
36
|
+
- LICENSE
|
|
37
|
+
- README.md
|
|
38
|
+
- lib/ruby_llm/providers/infomaniak.rb
|
|
39
|
+
- lib/ruby_llm/providers/infomaniak/chat.rb
|
|
40
|
+
- lib/ruby_llm/providers/infomaniak/models.rb
|
|
41
|
+
- lib/ruby_llm/providers/infomaniak/version.rb
|
|
42
|
+
- models.json
|
|
43
|
+
homepage: https://github.com/Largo/ruby_llm-providers-infomaniak
|
|
44
|
+
licenses:
|
|
45
|
+
- MIT
|
|
46
|
+
metadata:
|
|
47
|
+
source_code_uri: https://github.com/Largo/ruby_llm-providers-infomaniak
|
|
48
|
+
changelog_uri: https://github.com/Largo/ruby_llm-providers-infomaniak/blob/main/CHANGELOG.md
|
|
49
|
+
bug_tracker_uri: https://github.com/Largo/ruby_llm-providers-infomaniak/issues
|
|
50
|
+
rubygems_mfa_required: 'true'
|
|
51
|
+
rdoc_options: []
|
|
52
|
+
require_paths:
|
|
53
|
+
- lib
|
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '3.2'
|
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '0'
|
|
64
|
+
requirements: []
|
|
65
|
+
rubygems_version: 4.0.20
|
|
66
|
+
specification_version: 4
|
|
67
|
+
summary: RubyLLM provider for Infomaniak AI Tools
|
|
68
|
+
test_files: []
|