savvy_openrouter 0.4.1 → 0.4.2

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: 0b72086a1800026ac12dae2c055b06051b61dec6ea35704d9ebe3a96aac9dee5
4
- data.tar.gz: f2d1a708927e1b8fbaf57c10c648a4928cf19714bbae20ee0b4865b918c852b2
3
+ metadata.gz: 8137e975dade1520d62e20932114c3001f00f068ef088a58e1ffb461c1ba7473
4
+ data.tar.gz: 6aedce626d20af2e52b5b109c595a7b36a87c1861275d6bcce5d64cf52dfbd7c
5
5
  SHA512:
6
- metadata.gz: e6cae64c814dd9a1a8162638f28740c05f617085081c32982fce542a1eff413a2010d00977d7a4433886ac2a3d022bc8edeab48693132e53be5ae95b071b03fe
7
- data.tar.gz: b50221b09dfce3abbf179bd76d6d8ac951e1382d3bf899cd43008f7cd1f6f2e0d8aa9cc6c08b8deba11e721bb929db33ddabc4cb76c151a1c88c6f87ca9629a0
6
+ metadata.gz: ca74a1076e38ef38c008d8562744cc88625ca409c93e3ce3474c8413c04cb10fc830a7fc69811578e6d2dee4f1b659b2339319cd151abf2062f446d4df02b0c0
7
+ data.tar.gz: 30a6e7c41a40710944ad41a2ea9d3886961d4f6ef9e2f6ef7f902c339cb1b7d4e63705a63e2f74163ee06f173cb7ba66f5c2e48c09d20829838f24894e752d9d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.2] - 2026-07-14
4
+
5
+ ### Changed
6
+
7
+ - **`Models#first_ranked_free_text_model`:** passes **`max_price: 0`** to **`GET /models`** (OpenRouter’s free-price filter) while still requiring zero prompt and completion pricing. Temporary free models with an **`expiration_date`** remain eligible when they rank first. Integration expectations updated for current top free ids; smoke chat model switched off the removed `inclusionai/ring-2.6-1t:free` id.
8
+
3
9
  ## [0.4.1] - 2026-05-15
4
10
 
5
11
  ### Added
data/README.md CHANGED
@@ -214,14 +214,14 @@ OpenRouter documents the legacy `plugins: [{ id: "web" }]` approach as deprecate
214
214
  client.models.list(category: "programming", output_modalities: "text")
215
215
  ```
216
216
 
217
- OpenRouter returns models in **curated rank order** within each filtered result set—that order is not “highest `context_length` wins.” To approximate the **top free text model** for a category, call **`first_ranked_free_text_model`**, which keeps API order and returns the **first** model whose **`pricing.prompt`** and **`pricing.completion`** both parse to zero:
217
+ OpenRouter returns models in **curated rank order** within each filtered result set—that order is not “highest `context_length` wins.” To approximate the **top free text model** for a category, call **`first_ranked_free_text_model`**, which calls **`GET /models`** with **`max_price=0`** (and text modality), keeps API order, and returns the **first** model whose **`pricing.prompt`** and **`pricing.completion`** both parse to zero. Temporary free models (those with an **`expiration_date` / “going away” badge) are **included** on purpose when they rank first:
218
218
 
219
219
  ```ruby
220
220
  client.models.first_ranked_free_text_model(category: "programming")
221
221
  client.models.first_ranked_free_text_model(category: "roleplay")
222
222
  ```
223
223
 
224
- **Use a stored model id for normal traffic.** Resolving the free model calls **`GET /models`** (one request). Each chat turn is **`POST /chat/completions`** (another). If you call `first_ranked_free_text_model` (or `list` and pick) on **every** user message, you pay **two** API calls per turn—list plus chat. Instead, resolve **once** (at deploy, in a Rake task, or on a long TTL), **remember** the returned **`id`** (environment variable, database, cache, or `default_model` in `savvy_openrouter.yml`), and pass that string to **`client.chat.completions(model: ...)`** for ongoing requests. Refresh the stored id when you want to pick up a new “top free” model after OpenRouter changes their list.
224
+ **Use a stored model id for normal traffic.** Resolving the free model calls **`GET /models`** (one request). Each chat turn is **`POST /chat/completions`** (another). If you call `first_ranked_free_text_model` (or `list` and pick) on **every** user message, you pay **two** API calls per turn—list plus chat. Instead, resolve **once** (at deploy, in a Rake task, or on a long TTL), **remember** the returned **`id`** (environment variable, database, cache, or `default_model` in `savvy_openrouter.yml`), and pass that string to **`client.chat.completions(model: ...)`** for ongoing requests. Refresh the stored id when you want to pick up a new “top free” model after OpenRouter changes their list—especially if the chosen model lists an expiration date.
225
225
 
226
226
  That heuristic stays aligned with OpenRouter’s listing **as long as their ranking and pricing rows stay as they are**; it is not a separate benchmark score from the JSON (there is no `rating` field on each model). For chat-specific knobs such as **`tools`**, **`tool_choice`**, and **`response_format`** (including JSON schema), pass them on **`client.chat.completions`**; global YAML **`defaults`** also merge into embeddings and other resources, so prefer per-call args for tool and schema defaults unless you only use chat endpoints.
227
227
 
@@ -282,7 +282,7 @@ bin/setup
282
282
  bundle exec rake # RSpec + RuboCop
283
283
  ```
284
284
 
285
- Integration tests live in `spec/integration/` and are tagged `:integration`. When `OPENROUTER_API_KEY` is set, they call the live API (WebMock allows net connect only for those examples). Smoke chat examples use the free model `inclusionai/ring-2.6-1t:free`; **`spec/integration/free_model_rank_spec.rb`** asserts curated “first free” model ids for `programming` and `roleplay` against the live models list (these examples can fail if OpenRouter changes ordering or pricing).
285
+ Integration tests live in `spec/integration/` and are tagged `:integration`. When `OPENROUTER_API_KEY` is set, they call the live API (WebMock allows net connect only for those examples). Smoke chat examples use the free model `nvidia/nemotron-3-super-120b-a12b:free`; **`spec/integration/free_model_rank_spec.rb`** asserts curated “first free” model ids for `programming` and `roleplay` against the live models list (these examples can fail if OpenRouter changes ordering or pricing).
286
286
 
287
287
  ## Contributing
288
288
 
@@ -13,11 +13,13 @@ module SavvyOpenrouter
13
13
  end
14
14
  end
15
15
 
16
- # Uses GET /models with filters, then returns the first model whose prompt + completion pricing are zero.
17
- # OpenRouter returns models in curated rank order within a category; first matching free model aligns with
18
- # site “top free” picks when combined with output_modalities=text.
16
+ # Uses GET /models with category + free-price filters, then returns the first model whose prompt and
17
+ # completion pricing are both zero. OpenRouter returns models in curated rank order; the first free
18
+ # match aligns with site “top free” picks when combined with output_modalities=text.
19
+ # Models scheduled for removal (expiration_date) are still eligible — callers often want the current
20
+ # top free pick even when it is temporary.
19
21
  def first_ranked_free_text_model(category:, output_modalities: "text")
20
- res = list(category: category, output_modalities: output_modalities)
22
+ res = list(category: category, output_modalities: output_modalities, max_price: 0)
21
23
  data = res[:data] || []
22
24
  data.find { |m| free_pricing?(m) }
23
25
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SavvyOpenrouter
4
- VERSION = "0.4.1"
4
+ VERSION = "0.4.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: savvy_openrouter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Feller