riffer 0.46.1 → 0.47.1

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: 569f1c5929847037c97cc5552eb38a8609db6a1e67ce99b8135961d50aa7a0b5
4
- data.tar.gz: c8e03583eb04ff2c8922b8d554c10f5a47fb4421b15600905ba21b0d69031aed
3
+ metadata.gz: 4ac69c8c20b892c53242b0fb792aee2ee1ce5da1ab02b1c53536fa8d1c006f08
4
+ data.tar.gz: 0fa02ebc739e1968167e517a41d4f0a2cabff5a5845862ac78980d207638c6cd
5
5
  SHA512:
6
- metadata.gz: 6a0380696a959b4c291a5bf52c03eb7a1e06971a60ded5d058311bbdfe9efd99e58bf94506980e2d4b7b0e21257773894d11be4d1a896ea1e7e3ba8e2ce3359e
7
- data.tar.gz: 965f4d1fe6b97f4bd887a1fddd310c7a4cb81b5a0789cf8b91c58c49796abe79f396ec410b6008895275bd16281593a29f53d870e591124ade85b38c62a8fb22
6
+ metadata.gz: 5c0363ce9dad27ccfc899d6c7f4e71123eaa9ae5ceec8e50387575f0642c7696f07de37cb987c45099aee999fe5ac39c8635f07f36efb784c3341bd8a4c6a6aa
7
+ data.tar.gz: 4b5d6209255a46d9abd52275021709740bf0e87676c20c3bd9240f12acddc7768a050391530970c2a2ed407dbdd27fb50a1b03e43caff8361ad6f28cc67884db
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.46.1"
2
+ ".": "0.47.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ 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.47.1](https://github.com/janeapp/riffer/compare/riffer/v0.47.0...riffer/v0.47.1) (2026-09-11)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **params:** keep optional Hash params nullable in strict JSON Schema ([#433](https://github.com/janeapp/riffer/issues/433)) ([c7308fd](https://github.com/janeapp/riffer/commit/c7308fd15a3293877510e74c41662a68fac942df))
14
+ * **params:** raise for bare Hash and Array params under strict schemas ([#434](https://github.com/janeapp/riffer/issues/434)) ([174ad92](https://github.com/janeapp/riffer/commit/174ad92e7c765dc8833695ded9d2920f1ab27337))
15
+
16
+ ## [0.47.0](https://github.com/janeapp/riffer/compare/riffer/v0.46.1...riffer/v0.47.0) (2026-09-11)
17
+
18
+
19
+ ### Features
20
+
21
+ * **bedrock:** moving cachePoint so the conversation tail is cached ([#431](https://github.com/janeapp/riffer/issues/431)) ([4fa44b8](https://github.com/janeapp/riffer/commit/4fa44b817c00642303ac30002c33dd77c6d502ab))
22
+
8
23
  ## [0.46.1](https://github.com/janeapp/riffer/compare/riffer/v0.46.0...riffer/v0.46.1) (2026-09-11)
9
24
 
10
25
 
data/docs/AGENTS.md CHANGED
@@ -247,7 +247,7 @@ end
247
247
 
248
248
  #### Limitations
249
249
 
250
- Using both `of:` and a block raises `Riffer::ArgumentError`. Using `of:` with a non-primitive type (e.g. `of: Hash`) also raises `Riffer::ArgumentError`.
250
+ 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`.
251
251
 
252
252
  Structured output is not compatible with streaming — calling `stream` on an agent with structured output configured raises `Riffer::ArgumentError`.
253
253
 
@@ -94,7 +94,10 @@ model_options additional_model_request_fields: {
94
94
 
95
95
  ### cache_control
96
96
 
97
- Enable prompt caching for models that support it (Claude, Nova). Riffer appends a single Converse `cachePoint` to the stable prefix — after the system array, or after the tools when there is no system prompt — so system instructions and tool definitions are reused across the calls in an agent loop and across conversation turns. The volatile message tail is never cached.
97
+ Enable prompt caching for models that support it (Claude, Nova). Riffer mirrors Anthropic's automatic caching with two Converse `cachePoint` blocks:
98
+
99
+ - A **static** checkpoint at the end of the stable prefix — after the system array, or after the tools when there is no system prompt — so system instructions and tool definitions are reused across every call.
100
+ - A **moving** checkpoint after the last content block of the final message (a user turn or a batch of tool results). Bedrock looks back roughly 20 content blocks from a checkpoint for the longest cached prefix, so the second and later calls in a tool loop, and later turns in a conversation, read the accumulated conversation from cache instead of re-billing it at the full input rate.
98
101
 
99
102
  ```ruby
100
103
  # 5-minute TTL (default)
@@ -104,7 +107,7 @@ model_options cache_control: {type: "ephemeral"}
104
107
  model_options cache_control: {type: "ephemeral", ttl: "1h"}
105
108
  ```
106
109
 
107
- Caching is opt-in: omit `cache_control` and no cachePoint is sent. The breakpoint is only honored once the prefix clears the model's minimum token count; on models that don't support `cachePoint`, the Converse request errors. Verify hits via `response.token_usage.cache_read_tokens`.
110
+ Both checkpoints share the same `ttl`. Caching is opt-in: omit `cache_control` and no cachePoint is sent. A checkpoint is only honored once the content before it clears the model's minimum token count (see the per-model limits in the [AWS prompt caching guide](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html)) and is silently ignored below it, so short conversations may see no cache reads at first. On models that don't support `cachePoint`, the Converse request errors. Verify hits via `response.token_usage.cache_read_tokens`, which should grow with each step of an agent loop as the conversation accumulates.
108
111
 
109
112
  ## Example
110
113
 
@@ -140,10 +140,14 @@ class Riffer::Params::Param
140
140
  # params are made nullable (<tt>["type", "null"]</tt>) so strict providers
141
141
  # distinguish absent from present; optional params with an +enum+ use +anyOf+
142
142
  # instead, since providers like Anthropic reject
143
- # <tt>{"type": ["string", "null"], "enum": [...]}</tt>.
143
+ # <tt>{"type": ["string", "null"], "enum": [...]}</tt>. Raises
144
+ # Riffer::ArgumentError when +strict+ and a Hash param has no block or an
145
+ # Array param has neither a block nor <tt>of:</tt>, since strict providers
146
+ # reject objects without +properties+ and arrays without +items+.
144
147
  #--
145
148
  #: (?strict: bool) -> Hash[Symbol, untyped]
146
149
  def to_json_schema(strict: false)
150
+ validate_strict_shape! if strict
147
151
  nullable = strict && !required
148
152
 
149
153
  if nullable && enum
@@ -168,9 +172,24 @@ class Riffer::Params::Param
168
172
  elsif self.type == Array && item_type
169
173
  schema[:items] = { type: TYPE_MAPPINGS[item_type] }
170
174
  elsif self.type == Hash && nested_params
171
- schema.merge!(nested_params.to_json_schema(strict: strict))
175
+ # The nested schema carries its own type: "object", which would clobber a nullable union.
176
+ schema.merge!(nested_params.to_json_schema(strict: strict).except(:type))
172
177
  end
173
178
 
174
179
  schema
175
180
  end
181
+
182
+ private
183
+
184
+ #--
185
+ #: () -> void
186
+ def validate_strict_shape!
187
+ if type == Hash && nested_params.nil?
188
+ raise Riffer::ArgumentError,
189
+ "#{name}: a Hash param requires a block defining its properties under strict schemas"
190
+ elsif type == Array && nested_params.nil? && item_type.nil?
191
+ raise Riffer::ArgumentError,
192
+ "#{name}: an Array param requires a block or of: defining its items under strict schemas"
193
+ end
194
+ end
176
195
  end
data/lib/riffer/params.rb CHANGED
@@ -128,6 +128,8 @@ class Riffer::Params
128
128
  # Converts all parameters to JSON Schema format. When +strict+ is true, every
129
129
  # property is listed in +required+ and optional ones are made nullable
130
130
  # instead, satisfying providers that enforce strict structured output schemas.
131
+ # Raises Riffer::ArgumentError when +strict+ and a Hash param (at any depth)
132
+ # has no block or an Array param has neither a block nor <tt>of:</tt>.
131
133
  #--
132
134
  #: (?strict: bool) -> Hash[Symbol, untyped]
133
135
  def to_json_schema(strict: false)
@@ -129,21 +129,25 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
129
129
  params
130
130
  end
131
131
 
132
- # Converse chains +tools -> system -> messages+, so a single +cachePoint+ at
133
- # the end of the system array (or the tools array, when there is no system
134
- # prompt) also caches the preceding sections.
132
+ # Converse treats +tools -> system -> messages+ as one prefix and looks back
133
+ # from a +cachePoint+ for the longest cached run, so the point on the final
134
+ # message reuses the previous step's cache wherever that point sat. Mixed
135
+ # ttls must be ordered 1h before 5m, so both points share one.
135
136
  #--
136
137
  #: (Hash[Symbol, untyped], untyped) -> void
137
138
  def apply_cache_point(params, cache_control)
138
139
  cache_point = { cache_point: build_cache_point(cache_control) }
139
140
  system = params[:system]
140
141
  tools = params.dig(:tool_config, :tools)
142
+ last_message = params[:messages].last
141
143
 
142
144
  if system && !system.empty?
143
145
  system << cache_point
144
146
  elsif tools && !tools.empty?
145
147
  tools << cache_point
146
148
  end
149
+
150
+ last_message[:content] << cache_point if last_message
147
151
  end
148
152
 
149
153
  #--
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.46.1" #: String
5
+ VERSION = "0.47.1" #: String
6
6
  end
@@ -74,8 +74,17 @@ class Riffer::Params::Param
74
74
  # params are made nullable (<tt>["type", "null"]</tt>) so strict providers
75
75
  # distinguish absent from present; optional params with an +enum+ use +anyOf+
76
76
  # instead, since providers like Anthropic reject
77
- # <tt>{"type": ["string", "null"], "enum": [...]}</tt>.
77
+ # <tt>{"type": ["string", "null"], "enum": [...]}</tt>. Raises
78
+ # Riffer::ArgumentError when +strict+ and a Hash param has no block or an
79
+ # Array param has neither a block nor <tt>of:</tt>, since strict providers
80
+ # reject objects without +properties+ and arrays without +items+.
78
81
  # --
79
82
  # : (?strict: bool) -> Hash[Symbol, untyped]
80
83
  def to_json_schema: (?strict: bool) -> Hash[Symbol, untyped]
84
+
85
+ private
86
+
87
+ # --
88
+ # : () -> void
89
+ def validate_strict_shape!: () -> void
81
90
  end
@@ -54,6 +54,8 @@ class Riffer::Params
54
54
  # Converts all parameters to JSON Schema format. When +strict+ is true, every
55
55
  # property is listed in +required+ and optional ones are made nullable
56
56
  # instead, satisfying providers that enforce strict structured output schemas.
57
+ # Raises Riffer::ArgumentError when +strict+ and a Hash param (at any depth)
58
+ # has no block or an Array param has neither a block nor <tt>of:</tt>.
57
59
  # --
58
60
  # : (?strict: bool) -> Hash[Symbol, untyped]
59
61
  def to_json_schema: (?strict: bool) -> Hash[Symbol, untyped]
@@ -45,9 +45,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
45
45
  # : (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
46
46
  def build_request_params: (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
47
47
 
48
- # Converse chains +tools -> system -> messages+, so a single +cachePoint+ at
49
- # the end of the system array (or the tools array, when there is no system
50
- # prompt) also caches the preceding sections.
48
+ # Converse treats +tools -> system -> messages+ as one prefix and looks back
49
+ # from a +cachePoint+ for the longest cached run, so the point on the final
50
+ # message reuses the previous step's cache wherever that point sat. Mixed
51
+ # ttls must be ordered 1h before 5m, so both points share one.
51
52
  # --
52
53
  # : (Hash[Symbol, untyped], untyped) -> void
53
54
  def apply_cache_point: (Hash[Symbol, untyped], untyped) -> void
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.46.1
4
+ version: 0.47.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall