fino-solid 1.9.0 → 1.10.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: cd4440e5b282bcef8d8aeb2a56b98b75e31b2b34ff05dc57eb2d8b85a076f055
4
- data.tar.gz: fff6817b7f553c1a9482f31343f61b84b6e3fd60b6280cd558484f1281d11877
3
+ metadata.gz: d001f6bb38d131992464c9cdeab1e5927fd5a16623759459c7020b1c35d6664a
4
+ data.tar.gz: 339ab6d35c8dc40ecd553f835f536eb74a6978375ed29943cf5c8905c9c812e2
5
5
  SHA512:
6
- metadata.gz: 0f330e1353876cc19afafdea23beab7fedb48146a4a82e1b1feb86472164e27fd87408e3a325b6e192e6d14b318fb13bdf39d2f7205f5acd3464a765b5e138fa
7
- data.tar.gz: 42bdf1a8d7b5c102a8d8c846c372073318b2c90a25d4938913b87209c7d149f990cdce5588908a5a384ac9e401207adc76c9888e1fa8905238a32ba403dc664e
6
+ metadata.gz: 6f81641ee84473e0fb6262bfa41d6beefd0e2051eb8caf6fc15364198385324f162f395a959d6658791dd70b9dbc28de46259a561a7c08b7df90f7d04f7c0f54
7
+ data.tar.gz: 53eef4644ef38aaa358cf40d9f2cbf5ed587800a5acb0e47b94cf09b8f51371d5cb5a34678385f691a524d2beda08ed5814e029ebbf0180cf63a6055299585c8
data/README.md CHANGED
@@ -83,6 +83,126 @@ Fino.disable(:maintenance_mode, for: "qa")
83
83
  Fino.enabled?(:maintenance_mode, for: "qa") #=> false
84
84
  ```
85
85
 
86
+ ### Select setting
87
+
88
+ The simplest way to define select setting in fino is the following
89
+
90
+ ```ruby
91
+ Fino.configure do
92
+ # ...
93
+ section :storefront, label: "Storefront" do
94
+ setting :purchase_button_color,
95
+ :select,
96
+ options: [
97
+ Fino::Settings::Select::Option.new(label: "Red", value: "red"),
98
+ Fino::Settings::Select::Option.new(label: "Blue", value: "blue")
99
+ ],
100
+ default: "red",
101
+ description: "Color of the purchase button"
102
+ end
103
+ # ...
104
+ end
105
+ ```
106
+
107
+ Options must be an array of `Fino::Settings::Select::Option` instances
108
+
109
+ Then you can interact with the setting like that
110
+
111
+ ```ruby
112
+ # Read selected option
113
+ selected_option = Fino.value(:purchase_button_color, at: :storefront)
114
+ # => #<Fino::Settings::Select::Option:0x0000000124fecd90 @label="Red", @metadata={}, @value="red">
115
+
116
+ # Read setting value
117
+ selected_option.value
118
+ # => "red"
119
+
120
+ # Read options
121
+ Fino.setting(:purchase_button_color, at: :storefront).options
122
+ # => [#<Fino::Settings::Select::Option:0x0000000124fecd90 @label="Red", @metadata={}, @value="red">,
123
+ #<Fino::Settings::Select::Option:0x0000000124fecca0 @label="Blue", @metadata={}, @value="blue">]
124
+ ```
125
+
126
+ #### Dynamic option
127
+
128
+ Options can also be defined dynamically using any callable object. Let's take a look at dynamic options in an example
129
+ of LLM model setting using [RubyLLM](https://github.com/crmne/ruby_llm) by @crmne
130
+
131
+ To define dynamic select options, use `:select` as setting type and provide a callable to `options`. Your object's
132
+ `call` method might be called with `refresh` option which is true only when user initiates options refresh manually.
133
+ This is useful for updating models list in RubyLLM for example
134
+
135
+ ```ruby
136
+ section :llm, label: "LLM" do
137
+ setting :model,
138
+ :select,
139
+ options: proc { |refresh:|
140
+ RubyLLM.models.refresh! if refresh
141
+ models = RubyLLM.models.chat_models
142
+
143
+ openai_models = models.by_provider(:openai)
144
+ anthropic_models = models.by_provider(:anthropic)
145
+
146
+ build_pricing_label = proc do |model|
147
+ text_pricing = model.pricing&.text_tokens
148
+ next unless text_pricing && text_pricing.input && text_pricing.output
149
+
150
+ "$#{text_pricing.input} / $#{text_pricing.output} per 1M tokens"
151
+ end
152
+
153
+ [*openai_models, *anthropic_models].map do |model|
154
+ Fino::Settings::Select::Option.new(
155
+ label: model.name,
156
+ value: model.id,
157
+ metadata: {
158
+ provider: model.provider_class.name,
159
+ pricing: build_pricing_label.call(model)
160
+ }.compact
161
+ )
162
+ end
163
+ },
164
+ default: "gpt-5",
165
+ description: "Chat model for AI-powered features"
166
+ end
167
+ end
168
+ ```
169
+
170
+ ```ruby
171
+ # Read selected option
172
+ selected_option = Fino.setting(:model, at: :llm).value
173
+ # => #<Fino::Settings::Select::Option:0x0000000126257728
174
+ # @label="GPT-4",
175
+ # @metadata={provider: "OpenAI", pricing: "$30 / $60 per 1M tokens"},
176
+ # @value="gpt-4">
177
+
178
+ # Read setting value
179
+ selected_option.value
180
+ # => "gpt-4"
181
+
182
+ # Read options
183
+ Fino.setting(:model, at: :llm).options
184
+ # => [#<Fino::Settings::Select::Option:0x000000012629e448
185
+ # @label="GPT-5.3 Codex Spark",
186
+ # @metadata={provider: "OpenAI", pricing: "$1.75 / $14 per 1M tokens"},
187
+ # @value="gpt-5.3-codex-spark">,
188
+ # #<Fino::Settings::Select::Option:0x000000012629e1c8
189
+ # @label="GPT-5.4",
190
+ # @metadata={provider: "OpenAI", pricing: "$2.5 / $15 per 1M tokens"},
191
+ # @value="gpt-5.4">, ...]
192
+
193
+ # Refresh options
194
+ Fino.setting(:model, at: :llm).refresh!
195
+ # I, [2026-03-22T18:23:13.615270 #67293] INFO -- RubyLLM: Fetching models from providers:
196
+ # I, [2026-03-22T18:23:13.615934 #67293] INFO -- RubyLLM: Fetching models from models.dev API...
197
+ ```
198
+
199
+ #### Use with RubyLLM
200
+
201
+ ```ruby
202
+ chat = RubyLLM.chat(model: Fino.setting(:model, at: :llm).value)
203
+ chat.ask "Why Ruby?"
204
+ ```
205
+
86
206
  ### Overrides
87
207
 
88
208
  ```ruby
@@ -116,6 +236,44 @@ Fino.value(:model, at: :openai, for: "user_1") #=> "gpt-6"
116
236
  Fino.value(:model, at: :openai, for: "user_2") #=> "gpt-5"
117
237
  ```
118
238
 
239
+ #### Experiment analysis
240
+
241
+ Some Fino adapters support A/B testing analysis, e.g built-in Redis adapter
242
+
243
+ When you run an A/B test for a setting, fino automatically calculates variant based on a stable identifier you pass as
244
+ a `for` option
245
+
246
+ ```ruby
247
+ Fino.set(model: "gpt-5", at: :openai, variants: { 20.0 => "gpt-6" })
248
+
249
+ Fino.value(:model, at: :openai, for: "user_1") #=> "gpt-6"
250
+ Fino.value(:model, at: :openai, for: "user_1") #=> "gpt-6"
251
+
252
+ Fino.value(:model, at: :openai, for: "user_2") #=> "gpt-5"
253
+ ```
254
+
255
+ Later in your code, when user performs a "desired" action, simply call
256
+
257
+ ```ruby
258
+ Fino.convert!(:model, at: :openai, for: "user_2")
259
+ ```
260
+
261
+ to record a "convertion" for `user_2`. As Fino knows the right variant for `user_2`, conversion will be counted
262
+ todards it. Thanks to that later you'll be able to call
263
+
264
+ ```ruby
265
+ Fino.analyse(:model, at: :openai)
266
+ ```
267
+
268
+ to receive a detailed report over variants performance. Also bar charts comparing all variants and a chart displaying
269
+ amount of conversions over time per variant will be accessible on UI with `fino-rails`
270
+
271
+ To reset analysis data for an experiment:
272
+
273
+ ```ruby
274
+ Fino.reset_analysis!(:model, at: :openai)
275
+ ```
276
+
119
277
  ### Unit conversion
120
278
 
121
279
  Fino is able to convert numeric settings into various units
@@ -21,7 +21,7 @@ module Fino
21
21
  end
22
22
 
23
23
  def write(setting_definition, value, overrides, variants)
24
- serialize_value = ->(raw_value) { setting_definition.type_class.serialize(raw_value) }
24
+ serialize_value = ->(raw_value) { setting_definition.serialize(raw_value) }
25
25
 
26
26
  data = { VALUE_KEY => serialize_value.call(value) }
27
27
 
data/lib/fino/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fino
4
- VERSION = "1.9.0"
4
+ VERSION = "1.10.0"
5
5
  REQUIRED_RUBY_VERSION = ">= 3.2.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fino-solid
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Egor Iskrenkov
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 1.9.0
32
+ version: 1.10.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 1.9.0
39
+ version: 1.10.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: mysql2
42
42
  requirement: !ruby/object:Gem::Requirement