riffer 0.52.1 → 0.54.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 +4 -4
- data/.claude/rules/rbs-inline.md +2 -2
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +22 -0
- data/Steepfile +19 -0
- data/docs/AGENTS.md +81 -4
- data/docs/CATALOG.md +205 -0
- data/docs/CONFIGURATION.md +35 -23
- data/docs/MESSAGES.md +1 -1
- data/docs/OVERVIEW.md +1 -1
- data/docs/SERIALIZATION.md +1 -0
- data/docs/TOOLS.md +15 -12
- data/docs/TRACING.md +2 -1
- data/docs/providers/AMAZON_BEDROCK.md +2 -0
- data/docs/providers/ANTHROPIC.md +2 -0
- data/docs/providers/AZURE_OPENAI.md +2 -0
- data/docs/providers/CUSTOM_PROVIDERS.md +15 -1
- data/docs/providers/GEMINI.md +11 -0
- data/docs/providers/MOCK_PROVIDER.md +2 -0
- data/docs/providers/OPENAI.md +2 -0
- data/docs/providers/OPENROUTER.md +2 -0
- data/docs-site/manifest.yml +3 -0
- data/lib/riffer/agent/config.rb +17 -0
- data/lib/riffer/agent/run.rb +2 -0
- data/lib/riffer/agent/serializer.rb +2 -0
- data/lib/riffer/agent/structured_output.rb +23 -3
- data/lib/riffer/agent.rb +7 -0
- data/lib/riffer/{config/pricing → catalog}/rates.rb +21 -6
- data/lib/riffer/catalog.rb +225 -0
- data/lib/riffer/config/pricing.rb +17 -27
- data/lib/riffer/config.rb +29 -0
- data/lib/riffer/params/any_of.rb +22 -0
- data/lib/riffer/params/format.rb +73 -0
- data/lib/riffer/params/param.rb +120 -17
- data/lib/riffer/params.rb +135 -24
- data/lib/riffer/providers/amazon_bedrock.rb +14 -0
- data/lib/riffer/providers/anthropic.rb +8 -0
- data/lib/riffer/providers/base.rb +67 -15
- data/lib/riffer/providers/gemini.rb +7 -0
- data/lib/riffer/providers/mock.rb +7 -0
- data/lib/riffer/providers/open_ai.rb +6 -0
- data/lib/riffer/providers/open_router.rb +6 -0
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -0
- data/rbs_collection.lock.yaml +8 -0
- data/rbs_collection.yaml +2 -0
- data/sig/generated/riffer/agent/config.rbs +10 -1
- data/sig/generated/riffer/agent/structured_output.rbs +6 -0
- data/sig/generated/riffer/agent.rbs +4 -0
- data/sig/generated/riffer/{config/pricing → catalog}/rates.rbs +10 -4
- data/sig/generated/riffer/catalog.rbs +77 -0
- data/sig/generated/riffer/config/pricing.rbs +12 -11
- data/sig/generated/riffer/config.rbs +17 -0
- data/sig/generated/riffer/params/any_of.rbs +13 -0
- data/sig/generated/riffer/params/format.rbs +39 -0
- data/sig/generated/riffer/params/param.rbs +22 -4
- data/sig/generated/riffer/params.rbs +34 -8
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +6 -0
- data/sig/generated/riffer/providers/anthropic.rbs +4 -0
- data/sig/generated/riffer/providers/base.rbs +22 -2
- data/sig/generated/riffer/providers/gemini.rbs +4 -0
- data/sig/generated/riffer/providers/mock.rbs +5 -0
- data/sig/generated/riffer/providers/open_ai.rbs +4 -0
- data/sig/generated/riffer/providers/open_router.rbs +4 -0
- data/sig/generated/riffer.rbs +1 -0
- data/sig/manifest.yaml +2 -1
- data/sig/manual/riffer/params/format.rbs +5 -0
- metadata +11 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da8c08b227ba3ddddaa025377b70f1dfb9acaa3c86b52ff9866446e8e908443c
|
|
4
|
+
data.tar.gz: 598cddab932d5f4e3c775847eafaba44d7d0fe21741539927fa1cad15cb1767e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d01ca1aa3fa2f183c93a7c783eb5d23040f8e76d403114abe4f53f2cb4a299b69046a0397c405380013467d0c035e9cb1b7e1fcc72b45e945a7a6c786159b274
|
|
7
|
+
data.tar.gz: 3f568d27f9b5cbd12d23be97acbe049d8e9dd70f817bc6375d273f2cae62d60cbc7ebf40d7588d516739107734a12f3b450863a13f327ed1e2d1d46b7f60ca1d
|
data/.claude/rules/rbs-inline.md
CHANGED
|
@@ -68,13 +68,13 @@ end
|
|
|
68
68
|
|
|
69
69
|
### Where stubs and stdlib deps live
|
|
70
70
|
|
|
71
|
-
Dependency signatures come from [`rbs collection`](https://github.com/ruby/gem_rbs_collection): `rbs_collection.yaml` pins the upstream repo to a commit SHA, the gem set is derived from `Gemfile.lock`, and `rbs_collection.lock.yaml` is committed (the installed `.gem_rbs_collection/` is gitignored). Steep auto-detects the config, so the `Steepfile` declares no `library` lines. `
|
|
71
|
+
Dependency signatures come from [`rbs collection`](https://github.com/ruby/gem_rbs_collection): `rbs_collection.yaml` pins the upstream repo to a commit SHA, the gem set is derived from `Gemfile.lock`, and `rbs_collection.lock.yaml` is committed (the installed `.gem_rbs_collection/` is gitignored). Steep auto-detects the config, so the `Steepfile` declares no `library` lines. Stdlib libraries that no gem in `Gemfile.lock` pulls in are hand-listed under `gems:` (`yaml`, `cgi`, `date`, `ipaddr`).
|
|
72
72
|
|
|
73
73
|
`bin/typecheck` runs `rbs collection update` before `steep check`, keeping the lock in sync with `Gemfile.lock`; CI fails on lock drift. So after a Dependabot gem bump, run `bin/typecheck` and commit the refreshed lock. To pull newer upstream stubs, bump `revision` in `rbs_collection.yaml` and run `bin/typecheck` — pinning is what makes `update` deterministic.
|
|
74
74
|
|
|
75
75
|
- `sig/_private/` — signatures that must **not** ship. RBS **skips** `_`-prefixed directories in library mode, so consumers never load them; riffer's own `steep check` does (via the `Steepfile`). Two kinds, by predictable path: external-gem signatures are named by gem at the top level — full stubs for the gems upstream has nothing for (`mcp.rbs`, `zeitwerk.rbs`, `opentelemetry.rbs`, `rspec.rbs`, `aws-sdk-core/*`) plus arity patches for the provider SDKs (`openai.rbs`, `anthropic.rbs`); riffer's own hidden stubs mirror `lib/` under `riffer/` (e.g. `riffer/providers/anthropic.rbs` narrows the private `client` method to the SDK-typed client). A stub must be **deleted** once the collection covers that gem, or RBS raises `DuplicatedMethodDefinitionError`.
|
|
76
76
|
- `sig/manual/` — hand-written riffer-only signatures that are **safe to ship**, for the few things rbs-inline can't generate _at all_ (mirroring `lib/`). In practice that's `extend self` modules (`riffer/agent/run.rbs`, `riffer/helpers/call_or_value.rbs`) and modeling an include applied dynamically (`riffer/tools/toolable.rbs`). SDK-free ivars are **not** hand-written here — declare them inline with `# @rbs` (see "Annotation Conventions"). SDK-typed signatures can't ship, so they go in `_private/riffer/providers/` (the narrowed `client`).
|
|
77
|
-
- `sig/manifest.yaml` — declares the **stdlib** RBS the shipped sigs reference (`uri`, `net-http`) so `rbs -r riffer` resolves them.
|
|
77
|
+
- `sig/manifest.yaml` — declares the **stdlib** RBS the shipped sigs reference (`uri`, `net-http`, `digest`, `ipaddr`) so `rbs -r riffer` resolves them.
|
|
78
78
|
|
|
79
79
|
## Workflow
|
|
80
80
|
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.54.0](https://github.com/janeapp/riffer/compare/riffer/v0.53.0...riffer/v0.54.0) (2026-10-20)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **params:** add `format:` for string formats ([#497](https://github.com/janeapp/riffer/issues/497)) ([3c2dec8](https://github.com/janeapp/riffer/commit/3c2dec8e70b28de2d7b9d655e8d17ce85dde6812))
|
|
14
|
+
* **params:** add `Riffer::Params::AnyOf` ([#491](https://github.com/janeapp/riffer/issues/491)) ([65493ca](https://github.com/janeapp/riffer/commit/65493caa8073cf86983ec27dc6fb84ede068b6b9))
|
|
15
|
+
* **params:** add arrays of arrays with nested `of:` ([#498](https://github.com/janeapp/riffer/issues/498)) ([2b3449c](https://github.com/janeapp/riffer/commit/2b3449c61474f5cc79643903c08668a8fd9db8b6))
|
|
16
|
+
* **structured_output:** append prompted schema to user messages ([#492](https://github.com/janeapp/riffer/issues/492)) ([332db21](https://github.com/janeapp/riffer/commit/332db216d2e7716904d5ff4a9c1d9d42e128ac83))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **params:** apply enum to each item of an Array of: param ([#493](https://github.com/janeapp/riffer/issues/493)) ([09da9b0](https://github.com/janeapp/riffer/commit/09da9b0ccb0e63b2ed3202a28546371b17118e8c))
|
|
22
|
+
|
|
23
|
+
## [0.53.0](https://github.com/janeapp/riffer/compare/riffer/v0.52.1...riffer/v0.53.0) (2026-10-13)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* **catalog:** add a model catalog and a cross-provider reasoning setting ([#480](https://github.com/janeapp/riffer/issues/480)) ([7144f54](https://github.com/janeapp/riffer/commit/7144f54022257217f4e9ebee3263b5627cb96544))
|
|
29
|
+
|
|
8
30
|
## [0.52.1](https://github.com/janeapp/riffer/compare/riffer/v0.52.0...riffer/v0.52.1) (2026-10-09)
|
|
9
31
|
|
|
10
32
|
|
data/Steepfile
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
D = Steep::Diagnostic
|
|
4
4
|
|
|
5
|
+
# RBS 4.2.0 re-validates every declaration of a module each time its type
|
|
6
|
+
# params are read, which is quadratic in how often a module is reopened. The
|
|
7
|
+
# openai and anthropic gems reopen their namespace ~1,600 times each, so
|
|
8
|
+
# loading signatures takes over a minute per Steep worker. Fixed upstream in
|
|
9
|
+
# ruby/rbs a5458a83; delete this once the bundle has an RBS release newer
|
|
10
|
+
# than 4.2.
|
|
11
|
+
if RBS::Environment::ModuleEntry.instance_method(:validate_type_params).owner == RBS::Environment::ModuleEntry
|
|
12
|
+
validate_once = Module.new do
|
|
13
|
+
def validate_type_params
|
|
14
|
+
return if @validated_decl_count == context_decls.size
|
|
15
|
+
|
|
16
|
+
super
|
|
17
|
+
@validated_decl_count = context_decls.size
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
RBS::Environment::ModuleEntry.prepend(validate_once)
|
|
21
|
+
RBS::Environment::ClassEntry.prepend(validate_once)
|
|
22
|
+
end
|
|
23
|
+
|
|
5
24
|
target :lib do
|
|
6
25
|
signature "sig/generated"
|
|
7
26
|
signature "sig/manual"
|
data/docs/AGENTS.md
CHANGED
|
@@ -139,6 +139,21 @@ class MyAgent < Riffer::Agent
|
|
|
139
139
|
end
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
### reasoning
|
|
143
|
+
|
|
144
|
+
Sets how much the model thinks before it answers: `:off`, `:low`, `:medium`, `:high`, `:xhigh`, or `:max` (a String works too). Anything else raises `Riffer::ArgumentError`. Leave it unset to send nothing and keep the model's default.
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
class MyAgent < Riffer::Agent
|
|
148
|
+
model 'anthropic/claude-sonnet-4-6'
|
|
149
|
+
reasoning :low
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Riffer turns the level into the provider's own params. It uses the model's [catalog](CATALOG.md#reasoning) entry when one defines the level, and otherwise the provider's default, which sends the level name as the provider's effort value (for example `output_config: { effort: "low" }` with adaptive thinking on Anthropic). Riffer doesn't check whether the model supports the level; a model that rejects it returns the provider's error.
|
|
154
|
+
|
|
155
|
+
`model_options` are deep-merged over those params, so they override single fields rather than whole blocks. For example, `model_options max_tokens: 8000` keeps the catalog's thinking budget and replaces only its `max_tokens`.
|
|
156
|
+
|
|
142
157
|
### max_steps
|
|
143
158
|
|
|
144
159
|
Sets the maximum number of LLM call steps in the tool-use loop. When the limit is reached, the loop interrupts with reason `:max_steps`. Defaults to `16`. Set to `nil` (`max_steps nil`) for unlimited steps:
|
|
@@ -197,12 +212,12 @@ class SentimentAgent < Riffer::Agent
|
|
|
197
212
|
end
|
|
198
213
|
```
|
|
199
214
|
|
|
200
|
-
With `strategy: :prompted`, the provider never receives its native structured-output field. Instead, an instruction
|
|
215
|
+
With `strategy: :prompted`, the provider never receives its native structured-output field. Instead, an instruction is appended to the end of every user message on every request of the run, after consecutive user messages are merged (it is not stored in `session.messages`, and the system instructions are left untouched). The instruction carries the JSON Schema, an example of the expected shape with placeholder values (`"<string>"`, enum values as `"a | b"`, an `AnyOf` field as its first variant), and a nudge to reply with the JSON right away unless the request needs a tool. The reply is parsed and validated exactly as above: `response.content` is the reply's JSON text, `response.structured_output` holds the validated object, and a reply that fails parsing or validation ends with `:invalid_structured_output`. Because nothing forces the model to comply, expect more `:invalid_structured_output` outcomes than with the native strategy.
|
|
201
216
|
|
|
202
217
|
| Strategy | Behavior |
|
|
203
218
|
| ------------------- | --------------------------------------------------------------- |
|
|
204
219
|
| `:native` (default) | Schema sent through the provider's structured-output field |
|
|
205
|
-
| `:prompted` | Schema
|
|
220
|
+
| `:prompted` | Schema appended to each user message; experimental, may change |
|
|
206
221
|
|
|
207
222
|
Riffer does not pick a strategy per model — the choice is always yours. The strategy is copied to subclasses and survives [serialization](SERIALIZATION.md). An unknown value raises `Riffer::ArgumentError`. `strategy:` can also be passed with a `Riffer::Params` instance: `structured_output params, strategy: :prompted`.
|
|
208
223
|
|
|
@@ -234,7 +249,46 @@ structured_output do
|
|
|
234
249
|
end
|
|
235
250
|
```
|
|
236
251
|
|
|
237
|
-
|
|
252
|
+
`of:` takes a primitive type: `String`, `Integer`, `Float`, `Riffer::Params::Boolean`, `TrueClass`, `FalseClass`.
|
|
253
|
+
|
|
254
|
+
Add `enum:` to limit each item to a list of values:
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
structured_output do
|
|
258
|
+
required :tags, Array, of: String, enum: %w[urgent billing technical]
|
|
259
|
+
end
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
An item outside the list fails validation with its index: `tags[1] must be one of: urgent, billing, technical`.
|
|
263
|
+
|
|
264
|
+
For an array of arrays, put the type in brackets. Each pair of brackets adds a level, and `enum:` and `format:` apply to the innermost items:
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
structured_output do
|
|
268
|
+
required :rows, Array, of: [Integer], description: "Seat numbers, one array per row"
|
|
269
|
+
required :board, Array, of: [String], enum: %w[x o]
|
|
270
|
+
end
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
A row that isn't an array fails validation with its index: `rows[1] must be an array`.
|
|
274
|
+
|
|
275
|
+
#### Formats
|
|
276
|
+
|
|
277
|
+
Use `format:` on a `String`, or on an `Array` whose innermost type is `String`, to require a standard string format:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
structured_output do
|
|
281
|
+
required :born_on, String, format: "date"
|
|
282
|
+
optional :email, String, format: "email"
|
|
283
|
+
required :visit_ids, Array, of: String, format: "uuid"
|
|
284
|
+
end
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Supported formats: `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `ipv4`, `ipv6`, `uuid`. Dates and times use RFC 3339 syntax, and a `time` may leave out its UTC offset. `email` and `hostname` are checked loosely, rejecting only values that are clearly not an address or a host name.
|
|
288
|
+
|
|
289
|
+
A value that doesn't match fails validation, with its index on an array: `born_on must be a valid date`, `visit_ids[1] must be a valid uuid`. Values stay strings.
|
|
290
|
+
|
|
291
|
+
A model that must match a format writes a matching value even when it has none, so it may invent an email. Make such a field `optional` so the model can return `null`.
|
|
238
292
|
|
|
239
293
|
#### Arrays of Objects
|
|
240
294
|
|
|
@@ -270,9 +324,32 @@ structured_output do
|
|
|
270
324
|
end
|
|
271
325
|
```
|
|
272
326
|
|
|
327
|
+
#### Either/Or Shapes
|
|
328
|
+
|
|
329
|
+
Use `Riffer::Params::AnyOf` when a field must be one of several object shapes. Each `variant` block defines one shape, the same way a `Hash` block does:
|
|
330
|
+
|
|
331
|
+
```ruby
|
|
332
|
+
structured_output do
|
|
333
|
+
required :result, Riffer::Params::AnyOf, description: "The address, or why none was found" do
|
|
334
|
+
variant do
|
|
335
|
+
required :street, String, description: "Street address"
|
|
336
|
+
required :city, String, description: "City"
|
|
337
|
+
end
|
|
338
|
+
variant do
|
|
339
|
+
required :error_code, String, enum: %w[no_address multiple_addresses]
|
|
340
|
+
required :error_message, String
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The schema sends the field as `anyOf`, with one object per variant. An answer must match one variant completely: every required field of that variant, and no field from outside it. Variants are tried in order and the first match is returned. When none match, the error lists each variant's problems: `result must match one of the variants: variant 1 (street is required, city is required), variant 2 (error_code is required, error_message is required)`.
|
|
347
|
+
|
|
348
|
+
An optional `AnyOf` field can also be `null`.
|
|
349
|
+
|
|
273
350
|
#### Limitations
|
|
274
351
|
|
|
275
|
-
A `Hash` param requires a block, and an `Array` param requires a block or `of:`. Using both `of:` and a block raises `Riffer::ArgumentError`. Using `of:` with a non-primitive type (e.g. `of: Hash`) also raises `Riffer::ArgumentError`.
|
|
352
|
+
A `Hash` param requires a block, and an `Array` param requires a block or `of:`. Using both `of:` and a block raises `Riffer::ArgumentError`. Using `of:` with a non-primitive type (e.g. `of: Hash` or `of: [Hash]`), or with more than one type (`of: [Integer, String]`), also raises `Riffer::ArgumentError`. An `AnyOf` param requires a block with at least two `variant`s, and can't be used with `of:` or `enum:`. Using `enum:` on a `Hash`, or on an `Array` without `of:`, also raises `Riffer::ArgumentError`. So does `format:` with an unsupported format, on a type other than `String` or an `Array` whose innermost type is `String`, or together with `enum:`.
|
|
276
353
|
|
|
277
354
|
Structured output is not compatible with streaming — calling `stream` on an agent with structured output configured raises `Riffer::ArgumentError`.
|
|
278
355
|
|
data/docs/CATALOG.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Model Catalog
|
|
2
|
+
|
|
3
|
+
The model catalog holds per-model data that riffer can't derive from the model name: how a [`reasoning`](AGENTS.md#reasoning) level maps to request params, and what the model costs. It lives in JSON files you list in your configuration, so a new model or a price change is a data edit, not a riffer release.
|
|
4
|
+
|
|
5
|
+
Riffer ships no catalog. Without one:
|
|
6
|
+
|
|
7
|
+
- `reasoning` still works through each provider's default mapping (see [Provider defaults](#provider-defaults)).
|
|
8
|
+
- Calls carry no cost (`token_usage.cost` is `nil`), unless the deprecated [`config.pricing`](CONFIGURATION.md#pricing-deprecated) prices the model.
|
|
9
|
+
|
|
10
|
+
## Loading catalog files
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
Riffer.configure do |config|
|
|
14
|
+
config.catalog_files = ["config/riffer/models.json"]
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- `catalog_files` takes an Array of paths (Strings or Pathnames). Files load in the order listed.
|
|
19
|
+
- `Riffer.configure` builds the catalog at the end of the block, so a missing file or invalid data raises `Riffer::ArgumentError` at boot.
|
|
20
|
+
- Calling `configure` again rebuilds the catalog from scratch.
|
|
21
|
+
- Files load at boot only. There is no API to add entries at runtime. The built catalog is frozen.
|
|
22
|
+
|
|
23
|
+
## File format
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"version": 1,
|
|
28
|
+
"models": {
|
|
29
|
+
"anthropic/claude-haiku-4-5-20251001": {
|
|
30
|
+
"aliases": ["anthropic/claude-haiku-4-5"],
|
|
31
|
+
"pricing": {
|
|
32
|
+
"input": 1.0,
|
|
33
|
+
"output": 5.0,
|
|
34
|
+
"cache_read": 0.1,
|
|
35
|
+
"cache_write": 1.25
|
|
36
|
+
},
|
|
37
|
+
"reasoning": {
|
|
38
|
+
"off": { "thinking": { "type": "disabled" } },
|
|
39
|
+
"low": {
|
|
40
|
+
"thinking": { "type": "enabled", "budget_tokens": 1024 },
|
|
41
|
+
"max_tokens": 5120
|
|
42
|
+
},
|
|
43
|
+
"high": {
|
|
44
|
+
"thinking": { "type": "enabled", "budget_tokens": 24576 },
|
|
45
|
+
"max_tokens": 28672
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"gemini/gemini-2.5-flash": {
|
|
50
|
+
"reasoning": {
|
|
51
|
+
"off": { "thinkingConfig": { "thinkingBudget": 0 } },
|
|
52
|
+
"low": { "thinkingConfig": { "thinkingBudget": 1024 } }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"amazon_bedrock/us.amazon.nova-2-lite-v1:0": {
|
|
56
|
+
"reasoning": {
|
|
57
|
+
"low": {
|
|
58
|
+
"additional_model_request_fields": {
|
|
59
|
+
"reasoningConfig": {
|
|
60
|
+
"type": "enabled",
|
|
61
|
+
"maxReasoningEffort": "low"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Key | Description |
|
|
72
|
+
| --------- | ------------------------------------------------------------------------------------------ |
|
|
73
|
+
| `version` | Required. The file format version. Only `1` is supported. |
|
|
74
|
+
| `models` | An object keyed by exact `provider/model` id: the same string you give an agent's `model`. |
|
|
75
|
+
|
|
76
|
+
Each model entry can have any of these keys:
|
|
77
|
+
|
|
78
|
+
| Key | Description |
|
|
79
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `aliases` | Other `provider/model` ids that share this entry, pricing included. An alias may name a different provider, e.g. `"azure_openai/my-gpt-prod"` on an OpenAI entry. |
|
|
81
|
+
| `pricing` | Per-million-token rates. See [Pricing](#pricing). |
|
|
82
|
+
| `reasoning` | An object keyed by level (`off`, `low`, `medium`, `high`, `xhigh`, `max`). Each value is a `model_options` fragment. See [Reasoning](#reasoning). |
|
|
83
|
+
|
|
84
|
+
### Lookup
|
|
85
|
+
|
|
86
|
+
A model id matches an entry by its exact key first, then by an alias. There are no patterns, families, or fallback entries.
|
|
87
|
+
|
|
88
|
+
The provider part of the key is the provider's [repository](providers/CUSTOM_PROVIDERS.md#registering-your-provider) identifier, so a custom provider registered as `:my_provider` reads entries under `my_provider/...`.
|
|
89
|
+
|
|
90
|
+
## Reasoning
|
|
91
|
+
|
|
92
|
+
A reasoning fragment is written exactly as you would write it in `model_options`, using the provider's own param names. Bedrock fragments spell out `additional_model_request_fields` and `inference_config` themselves.
|
|
93
|
+
|
|
94
|
+
When an agent sets `reasoning`, riffer picks the params for that level in this order:
|
|
95
|
+
|
|
96
|
+
1. The catalog entry's fragment for the level (by exact key, then alias).
|
|
97
|
+
2. The provider's default mapping, when the entry is missing or doesn't define the level.
|
|
98
|
+
3. If the provider has no default either, riffer raises `Riffer::ArgumentError` before the request:
|
|
99
|
+
`No reasoning mapping for my_provider/my-model at level :low. Add one in a catalog file (see docs/CATALOG.md).`
|
|
100
|
+
|
|
101
|
+
Riffer doesn't check whether a model supports a level. If the model rejects the params, you get the provider's error unchanged.
|
|
102
|
+
|
|
103
|
+
### Provider defaults
|
|
104
|
+
|
|
105
|
+
The defaults send the level name as the provider's effort value:
|
|
106
|
+
|
|
107
|
+
| Provider | `:off` | Other levels |
|
|
108
|
+
| -------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
109
|
+
| OpenAI, Azure OpenAI | `reasoning: "none"` | `reasoning: "<level>"` |
|
|
110
|
+
| OpenRouter | `reasoning: "none"` | `reasoning: "<level>"` |
|
|
111
|
+
| Anthropic | `thinking: { type: "disabled" }` | `thinking: { type: "adaptive" }, output_config: { effort: "<level>" }` |
|
|
112
|
+
| Amazon Bedrock | `additional_model_request_fields: { thinking: { type: "disabled" } }` | `additional_model_request_fields: { thinking: { type: "adaptive" }, output_config: { effort: "<level>" } }` |
|
|
113
|
+
| Gemini | `thinkingConfig: { thinkingLevel: "off" }` | `thinkingConfig: { thinkingLevel: "<level>" }` |
|
|
114
|
+
| Mock | nothing | nothing |
|
|
115
|
+
|
|
116
|
+
Add a catalog entry for models that need different params, for example:
|
|
117
|
+
|
|
118
|
+
- Claude 4.5 and earlier, which take a `budget_tokens` thinking budget instead of adaptive thinking.
|
|
119
|
+
- Gemini 2.5, which takes `thinkingBudget`.
|
|
120
|
+
- Non-Claude models on Amazon Bedrock (Nova, OpenAI), which use their own fields.
|
|
121
|
+
|
|
122
|
+
A [custom provider](providers/CUSTOM_PROVIDERS.md#reasoning-level) can supply its own default.
|
|
123
|
+
|
|
124
|
+
### Merging with model_options
|
|
125
|
+
|
|
126
|
+
The agent's `model_options` always win. Riffer deep-merges them over the fragment: nested objects merge key by key, and any other value (including arrays) replaces the fragment's. So you can override one field without repeating the rest:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
class SummaryAgent < Riffer::Agent
|
|
130
|
+
model "anthropic/claude-haiku-4-5-20251001"
|
|
131
|
+
reasoning :low
|
|
132
|
+
model_options max_tokens: 8000 # keeps the catalog's thinking budget, replaces its max_tokens
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Riffer doesn't check the merged result. If a partial override makes the request invalid, the provider rejects it.
|
|
137
|
+
|
|
138
|
+
## Pricing
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"pricing": {
|
|
143
|
+
"input": 3.0,
|
|
144
|
+
"output": 15.0,
|
|
145
|
+
"cache_read": 0.3,
|
|
146
|
+
"cache_write": 3.75
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| Key | Description |
|
|
152
|
+
| ------------- | ------------------------------------------------------------------------------------------------ |
|
|
153
|
+
| `input` | Required. Price per **million** input tokens. Applies to the uncached portion of `input_tokens`. |
|
|
154
|
+
| `output` | Required. Price per **million** output tokens. |
|
|
155
|
+
| `cache_read` | Price per million cache-read tokens. When omitted, cache reads bill at the `input` rate. |
|
|
156
|
+
| `cache_write` | Price per million cache-write tokens. When omitted, cache writes bill at the `input` rate. |
|
|
157
|
+
|
|
158
|
+
Because the cache buckets are subsets of `input_tokens`, the cost formula subtracts them before applying the input rate:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
cost = (input − cache_read − cache_write) × input_rate
|
|
162
|
+
+ cache_read × cache_read_rate
|
|
163
|
+
+ cache_write × cache_write_rate
|
|
164
|
+
+ output × output_rate
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
(all rates ÷ 1,000,000; an unset cache rate falls back to `input_rate`.) Cost is for observability, not billing: it's a `Float`, and sub-cent rounding can accumulate over a long run. See [Messages → Token Usage Semantics](MESSAGES.md#token-usage-semantics) for how cost surfaces and aggregates.
|
|
168
|
+
|
|
169
|
+
A model with no catalog pricing carries no cost. The deprecated [`config.pricing`](CONFIGURATION.md#pricing-deprecated) is still read as a fallback; catalog pricing wins when both price a model.
|
|
170
|
+
|
|
171
|
+
## Layering files
|
|
172
|
+
|
|
173
|
+
Later files override earlier ones, so you can keep shared data in one file and local overrides in another:
|
|
174
|
+
|
|
175
|
+
- For an entry with the same key, a later file replaces whole sections (`pricing`, `reasoning`) and leaves the others alone.
|
|
176
|
+
- `aliases` arrays are combined, so a later file can add aliases without repeating earlier ones.
|
|
177
|
+
- If a later file lists an alias an earlier file gave to a different entry, the later file wins.
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"version": 1,
|
|
182
|
+
"models": {
|
|
183
|
+
"anthropic/claude-haiku-4-5-20251001": {
|
|
184
|
+
"pricing": { "input": 0.8, "output": 4.0 }
|
|
185
|
+
},
|
|
186
|
+
"openai/gpt-5.1": {
|
|
187
|
+
"aliases": ["azure_openai/my-gpt-prod"]
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Listed after the first example, this file replaces Haiku's pricing but keeps its reasoning, and routes an Azure deployment to the `openai/gpt-5.1` entry.
|
|
194
|
+
|
|
195
|
+
## Validation
|
|
196
|
+
|
|
197
|
+
Loading raises `Riffer::ArgumentError`, naming the file and the model key, when:
|
|
198
|
+
|
|
199
|
+
- The file is missing or isn't valid JSON, or `version` is missing or unsupported.
|
|
200
|
+
- The file has keys other than `version` and `models`, or an entry has keys other than `aliases`, `pricing`, and `reasoning`.
|
|
201
|
+
- A model key or alias isn't in `provider/model` form, or `aliases` isn't an array of strings.
|
|
202
|
+
- `pricing` is missing `input` or `output`, has unknown keys, or has a rate that isn't a non-negative number.
|
|
203
|
+
- A reasoning level isn't one of `off`, `low`, `medium`, `high`, `xhigh`, `max`, or its value isn't an object.
|
|
204
|
+
- Two entries in the same file list the same alias.
|
|
205
|
+
- After all files are layered, an alias is also a model key.
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -224,39 +224,38 @@ Every download in step 3 or 4 is gated by `allow_downloads`; with it `false` (th
|
|
|
224
224
|
|
|
225
225
|
A file that fails resolution raises a `Riffer::FileError` subclass — `Riffer::FileUnsupportedError`, `Riffer::FileDownloadsDisabledError`, `Riffer::TooManyFilesError`, `Riffer::FileChecksumMismatchError`, `Riffer::FileTooLargeError`, `Riffer::FileDownloadError`, or `Riffer::FileEncodingError` — so callers can `rescue Riffer::FileError` for any attachment problem, or a specific subclass to handle one case.
|
|
226
226
|
|
|
227
|
-
###
|
|
227
|
+
### Model Catalog
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
List JSON catalog files that hold per-model reasoning mappings and pricing. Riffer ships no catalog, so without one, `reasoning` uses each provider's default mapping, and calls carry no cost unless the deprecated [`config.pricing`](#pricing-deprecated) prices the model.
|
|
230
230
|
|
|
231
231
|
```ruby
|
|
232
232
|
Riffer.configure do |config|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
config.catalog_files = ["config/riffer/models.json"]
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| Option | Description |
|
|
238
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
239
|
+
| `catalog_files` | Array of paths (Strings or Pathnames), loaded in order; later files override earlier ones. Built at the end of `Riffer.configure`. |
|
|
240
|
+
|
|
241
|
+
See [Model Catalog](CATALOG.md) for the file format, lookup, layering, and validation.
|
|
236
242
|
|
|
237
|
-
|
|
238
|
-
|
|
243
|
+
### Pricing (deprecated)
|
|
244
|
+
|
|
245
|
+
`config.pricing` is deprecated and will be removed in a future release. Move rates into a [catalog file](CATALOG.md#pricing); the first `set` call prints a deprecation warning. Riffer still reads these rates as a fallback, and catalog pricing wins when both price a model.
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# before
|
|
249
|
+
Riffer.configure do |config|
|
|
250
|
+
config.pricing.set("anthropic/claude-sonnet-4-6", input: 3.0, output: 15.0)
|
|
239
251
|
end
|
|
240
252
|
```
|
|
241
253
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
| `models` | A `provider/model` id (e.g. `"openai/gpt-4"`) — the same string you pass to `model` — or an array of ids that share one set of rates. No alias matching; raises on a malformed id. |
|
|
245
|
-
| `input:` | Price per **million** input tokens. Required. Applies to the uncached portion of `input_tokens`. |
|
|
246
|
-
| `output:` | Price per **million** output tokens. Required. |
|
|
247
|
-
| `cache_read:` | Price per million cache-read tokens. Optional — when omitted, cache reads bill at the `input:` rate. |
|
|
248
|
-
| `cache_write:` | Price per million cache-write tokens. Optional — when omitted, cache writes bill at the `input:` rate. |
|
|
249
|
-
|
|
250
|
-
Because the cache buckets are subsets of `input_tokens`, the cost formula subtracts them before applying the input rate:
|
|
251
|
-
|
|
252
|
-
```text
|
|
253
|
-
cost = (input − cache_read − cache_write) × input_rate
|
|
254
|
-
+ cache_read × cache_read_rate
|
|
255
|
-
+ cache_write × cache_write_rate
|
|
256
|
-
+ output × output_rate
|
|
254
|
+
```json
|
|
255
|
+
{ "version": 1, "models": { "anthropic/claude-sonnet-4-6": { "pricing": { "input": 3.0, "output": 15.0 } } } }
|
|
257
256
|
```
|
|
258
257
|
|
|
259
|
-
|
|
258
|
+
`set` takes a `provider/model` id or an array of ids, plus `input:`, `output:`, and optional `cache_read:` and `cache_write:` rates per million tokens. The rates and cost formula are the same as [catalog pricing](CATALOG.md#pricing).
|
|
260
259
|
|
|
261
260
|
### Message ID Strategy
|
|
262
261
|
|
|
@@ -297,6 +296,19 @@ class MyAgent < Riffer::Agent
|
|
|
297
296
|
end
|
|
298
297
|
```
|
|
299
298
|
|
|
299
|
+
### reasoning
|
|
300
|
+
|
|
301
|
+
Set how much the model thinks before it answers, with one setting that works on every provider:
|
|
302
|
+
|
|
303
|
+
```ruby
|
|
304
|
+
class MyAgent < Riffer::Agent
|
|
305
|
+
model 'anthropic/claude-sonnet-4-6'
|
|
306
|
+
reasoning :low # :off, :low, :medium, :high, :xhigh, or :max
|
|
307
|
+
end
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Riffer maps the level to the provider's own params, from a [catalog](CATALOG.md#reasoning) entry or the provider's default. `model_options` are deep-merged over those params and win. See [Agents — reasoning](AGENTS.md#reasoning).
|
|
311
|
+
|
|
300
312
|
## Common Model Options
|
|
301
313
|
|
|
302
314
|
### OpenAI
|
data/docs/MESSAGES.md
CHANGED
|
@@ -91,7 +91,7 @@ tool_call.to_h # => {call_id: "call_123", name: "weather_tool", argu
|
|
|
91
91
|
|
|
92
92
|
The cache buckets are subsets of `input_tokens`, never additions to it — summing `input_tokens + cache_read_tokens` double-counts. `total_tokens` (input + output) matches the totals providers report on their dashboards.
|
|
93
93
|
|
|
94
|
-
- `cost` — the computed cost of the call, set when pricing
|
|
94
|
+
- `cost` — the computed cost of the call, set when a [catalog](CATALOG.md#pricing) or the deprecated [`config.pricing`](CONFIGURATION.md#pricing-deprecated) prices the model in use; `nil` when the model is unpriced. It's for observability, not billing. Run-level usage sums per-call costs through `TokenUsage#+`, so `response.token_usage.cost` is the total spend across the run — but the sum is `nil` if any call in the run used an unpriced model, rather than silently under-reporting.
|
|
95
95
|
|
|
96
96
|
#### Finish Reasons
|
|
97
97
|
|
data/docs/OVERVIEW.md
CHANGED
|
@@ -39,7 +39,7 @@ See [Tools](TOOLS.md) for details.
|
|
|
39
39
|
|
|
40
40
|
### Structured Output
|
|
41
41
|
|
|
42
|
-
Agents can return structured JSON responses that conform to a schema. The response is automatically parsed and validated. Schemas support nested objects (`Hash`), typed arrays (`Array, of:`),
|
|
42
|
+
Agents can return structured JSON responses that conform to a schema. The response is automatically parsed and validated. Schemas support nested objects (`Hash`), typed arrays (`Array, of:`), arrays of arrays (`Array, of: [Integer]`), arrays of objects (`Array` with block), and either/or shapes (`Riffer::Params::AnyOf`):
|
|
43
43
|
|
|
44
44
|
```ruby
|
|
45
45
|
class SentimentAgent < Riffer::Agent
|
data/docs/SERIALIZATION.md
CHANGED
|
@@ -46,6 +46,7 @@ The session is used **as-is**: the rebuilt agent does not prepend anything to it
|
|
|
46
46
|
max_steps: 8, # integer; -1 = unlimited (see below)
|
|
47
47
|
structured_output: { type: "object", … }, # JSON Schema, or null
|
|
48
48
|
structured_output_strategy: :native, # :native or :prompted ("native"/"prompted" over JSON)
|
|
49
|
+
reasoning: :low, # reasoning level, or null
|
|
49
50
|
tools: [ { name:, description:, parameters_schema:, timeout: }, … ]
|
|
50
51
|
}
|
|
51
52
|
```
|
data/docs/TOOLS.md
CHANGED
|
@@ -82,7 +82,8 @@ end
|
|
|
82
82
|
Options:
|
|
83
83
|
|
|
84
84
|
- `description` - Human-readable description for the LLM
|
|
85
|
-
- `enum` - Array of allowed values
|
|
85
|
+
- `enum` - Array of allowed values (for the innermost items of an `Array` with `of:`)
|
|
86
|
+
- `format` - String format such as `"date"` or `"email"` (for the innermost `String` items of an `Array`; see [Formats](AGENTS.md#formats))
|
|
86
87
|
|
|
87
88
|
### optional
|
|
88
89
|
|
|
@@ -99,19 +100,21 @@ Options:
|
|
|
99
100
|
|
|
100
101
|
- `description` - Human-readable description
|
|
101
102
|
- `default` - Default value when not provided
|
|
102
|
-
- `enum` - Array of allowed values
|
|
103
|
+
- `enum` - Array of allowed values (for the innermost items of an `Array` with `of:`)
|
|
104
|
+
- `format` - String format such as `"date"` or `"email"` (for the innermost `String` items of an `Array`; see [Formats](AGENTS.md#formats))
|
|
103
105
|
|
|
104
106
|
### Supported Types
|
|
105
107
|
|
|
106
|
-
| Ruby Type | JSON Schema Type
|
|
107
|
-
| -------------------------- |
|
|
108
|
-
| `String` | `string`
|
|
109
|
-
| `Integer` | `integer`
|
|
110
|
-
| `Float` | `number`
|
|
111
|
-
| `Riffer::Params::Boolean` | `boolean`
|
|
112
|
-
| `TrueClass` / `FalseClass` | `boolean`
|
|
113
|
-
| `Array` | `array`
|
|
114
|
-
| `Hash` | `object`
|
|
108
|
+
| Ruby Type | JSON Schema Type |
|
|
109
|
+
| -------------------------- | ------------------ |
|
|
110
|
+
| `String` | `string` |
|
|
111
|
+
| `Integer` | `integer` |
|
|
112
|
+
| `Float` | `number` |
|
|
113
|
+
| `Riffer::Params::Boolean` | `boolean` |
|
|
114
|
+
| `TrueClass` / `FalseClass` | `boolean` |
|
|
115
|
+
| `Array` | `array` |
|
|
116
|
+
| `Hash` | `object` |
|
|
117
|
+
| `Riffer::Params::AnyOf` | `anyOf` of objects |
|
|
115
118
|
|
|
116
119
|
`Riffer::Params::Boolean` is the preferred way to declare boolean parameters. `TrueClass` and `FalseClass` continue to work for backwards compatibility.
|
|
117
120
|
|
|
@@ -119,7 +122,7 @@ A `Float` param accepts a whole number too, since JSON Schema's `number` covers
|
|
|
119
122
|
|
|
120
123
|
### Nested Parameters
|
|
121
124
|
|
|
122
|
-
Tool params support the same nested DSL as structured output — nested objects (`Hash` with block), typed arrays (`Array, of:`),
|
|
125
|
+
Tool params support the same nested DSL as structured output — nested objects (`Hash` with block), typed arrays (`Array, of:`), arrays of arrays (`Array, of: [Integer]`), arrays of objects (`Array` with block), and either/or shapes (`Riffer::Params::AnyOf` with `variant` blocks). See the [structured output section in Agents](AGENTS.md#nested-objects) for full syntax.
|
|
123
126
|
|
|
124
127
|
```ruby
|
|
125
128
|
class CreateOrderTool < Riffer::Tool
|
data/docs/TRACING.md
CHANGED
|
@@ -142,6 +142,7 @@ Usage on this span is the run total, aggregated across every step. See [Token us
|
|
|
142
142
|
| `gen_ai.request.presence_penalty` | float | When the caller set it |
|
|
143
143
|
| `gen_ai.request.seed` | int | When the caller set it |
|
|
144
144
|
| `gen_ai.request.stop_sequences` | string[] | When the caller set it |
|
|
145
|
+
| `riffer.request.reasoning_level` | string | When the agent sets [`reasoning`](AGENTS.md#reasoning) |
|
|
145
146
|
| `gen_ai.usage.input_tokens` | int | When the provider reported usage |
|
|
146
147
|
| `gen_ai.usage.output_tokens` | int | When the provider reported usage |
|
|
147
148
|
| `gen_ai.usage.cache_read.input_tokens` | int | When the provider reported cache reads |
|
|
@@ -252,7 +253,7 @@ Riffer normalizes this across providers, so the number may differ from a provide
|
|
|
252
253
|
|
|
253
254
|
### Cost
|
|
254
255
|
|
|
255
|
-
`riffer.cost` is the modeled cost of one call (on a `chat` span) or a whole run (on the `invoke_agent` span). It lives in Riffer's own namespace because the GenAI semantic conventions define no cost attribute by design — Riffer never squats `gen_ai.*` for it. The attribute appears only when
|
|
256
|
+
`riffer.cost` is the modeled cost of one call (on a `chat` span) or a whole run (on the `invoke_agent` span). It lives in Riffer's own namespace because the GenAI semantic conventions define no cost attribute by design — Riffer never squats `gen_ai.*` for it. The attribute appears only when a [catalog](CATALOG.md#pricing) or the deprecated [`config.pricing`](CONFIGURATION.md#pricing-deprecated) prices the model in use: Riffer ships no price table and never guesses, so an unpriced model simply carries no `riffer.cost`.
|
|
256
257
|
|
|
257
258
|
The value is **unitless on the wire** — Riffer attaches no currency. It is the sum of the per-token rates you configured, in whatever currency you expressed them, so a `riffer.cost` of `0.0123` means 0.0123 of that unit. The raw float is emitted unrounded; round for display in your backend, not before.
|
|
258
259
|
|
|
@@ -189,6 +189,8 @@ end
|
|
|
189
189
|
|
|
190
190
|
The reasoning is kept on the assistant message as [reasoning parts](../MESSAGES.md#reasoning), whether you call `generate` or `stream`. Read it with `response.reasoning`, or as plain text with `reasoning_text` on the message. Bedrock sometimes redacts part of Claude's reasoning. Those parts are stored and sent back like any other, but they have no readable text, so `reasoning_text` leaves them out.
|
|
191
191
|
|
|
192
|
+
The agent-level [`reasoning`](../AGENTS.md#reasoning) setting defaults to Claude's fields inside `additional_model_request_fields`: adaptive thinking at the level's effort, and `thinking: {type: "disabled"}` for `:off`. Other model families (Nova, OpenAI) and Claude 4.5 and earlier need a [catalog](../CATALOG.md#reasoning) entry with their own fields.
|
|
193
|
+
|
|
192
194
|
### Reasoning Replay
|
|
193
195
|
|
|
194
196
|
Riffer sends the reasoning back to Bedrock on every later turn. How much of it Claude uses depends on the model; some only use the reasoning from the current tool-calling turn. For tool calls, sending it back is required: when thinking is enabled, Claude needs its earlier reasoning back to carry on after a tool result. You don't have to do anything; it happens as long as the assistant messages stay in the history.
|
data/docs/providers/ANTHROPIC.md
CHANGED
|
@@ -170,6 +170,8 @@ class ReasoningAgent < Riffer::Agent
|
|
|
170
170
|
end
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
+
The agent-level [`reasoning`](../AGENTS.md#reasoning) setting defaults to adaptive thinking at the level's effort (`thinking: {type: "adaptive"}, output_config: {effort: "low"}`), and `:off` sends `thinking: {type: "disabled"}`. Claude 4.5 and earlier need a `budget_tokens` mapping in a [catalog](../CATALOG.md#reasoning) entry instead.
|
|
174
|
+
|
|
173
175
|
When streaming with extended thinking enabled, you'll receive `ReasoningDelta` events containing the model's thought process, followed by a `ReasoningDone` event when thinking completes:
|
|
174
176
|
|
|
175
177
|
```ruby
|