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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +3 -3
- data/lib/savvy_openrouter/resources/models.rb +6 -4
- data/lib/savvy_openrouter/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8137e975dade1520d62e20932114c3001f00f068ef088a58e1ffb461c1ba7473
|
|
4
|
+
data.tar.gz: 6aedce626d20af2e52b5b109c595a7b36a87c1861275d6bcce5d64cf52dfbd7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 `
|
|
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
|
|
17
|
-
# OpenRouter returns models in curated rank order
|
|
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
|