riffer 0.46.0 → 0.46.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 +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +8 -0
- data/docs/AGENT_LIFECYCLE.md +8 -0
- data/docs/TOOLS.md +2 -0
- data/lib/riffer/agent/run.rb +1 -1
- data/lib/riffer/agent.rb +5 -4
- data/lib/riffer/params/param.rb +2 -0
- data/lib/riffer/params.rb +30 -6
- data/lib/riffer/version.rb +1 -1
- data/sig/generated/riffer/agent/run.rbs +2 -2
- data/sig/generated/riffer/agent.rbs +7 -6
- data/sig/generated/riffer/params.rbs +16 -2
- 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: 569f1c5929847037c97cc5552eb38a8609db6a1e67ce99b8135961d50aa7a0b5
|
|
4
|
+
data.tar.gz: c8e03583eb04ff2c8922b8d554c10f5a47fb4421b15600905ba21b0d69031aed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a0380696a959b4c291a5bf52c03eb7a1e06971a60ded5d058311bbdfe9efd99e58bf94506980e2d4b7b0e21257773894d11be4d1a896ea1e7e3ba8e2ce3359e
|
|
7
|
+
data.tar.gz: 965f4d1fe6b97f4bd887a1fddd310c7a4cb81b5a0789cf8b91c58c49796abe79f396ec410b6008895275bd16281593a29f53d870e591124ade85b38c62a8fb22
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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.46.1](https://github.com/janeapp/riffer/compare/riffer/v0.46.0...riffer/v0.46.1) (2026-09-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* accept JSON integers for Float params ([#429](https://github.com/janeapp/riffer/issues/429)) ([928c33b](https://github.com/janeapp/riffer/commit/928c33b1125bf013f66c8b736191bc70eb313ccd))
|
|
14
|
+
* **agent:** type stream's enumerator as returning Response ([#428](https://github.com/janeapp/riffer/issues/428)) ([4d2b2d2](https://github.com/janeapp/riffer/commit/4d2b2d2aa08d1d95b97226e84180284349fff441))
|
|
15
|
+
|
|
8
16
|
## [0.46.0](https://github.com/janeapp/riffer/compare/riffer/v0.45.0...riffer/v0.46.0) (2026-09-08)
|
|
9
17
|
|
|
10
18
|
|
data/docs/AGENT_LIFECYCLE.md
CHANGED
|
@@ -59,6 +59,14 @@ response = MyAgent.generate('What is in this image?', files: [
|
|
|
59
59
|
|
|
60
60
|
Streams a response as an Enumerator. Same prompt/files semantics as `generate`.
|
|
61
61
|
|
|
62
|
+
Consuming the enumerator with a block returns the same `Riffer::Agent::Response` that `generate` would, so you can stream events to the user and still inspect the final outcome:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
response = MyAgent.stream('Tell me a story').each { |event| handle(event) }
|
|
66
|
+
response.outcome.reason # => :completed
|
|
67
|
+
response.content
|
|
68
|
+
```
|
|
69
|
+
|
|
62
70
|
```ruby
|
|
63
71
|
# New conversation (class method — recommended for simple calls)
|
|
64
72
|
MyAgent.stream('Tell me a story').each do |event|
|
data/docs/TOOLS.md
CHANGED
|
@@ -115,6 +115,8 @@ Options:
|
|
|
115
115
|
|
|
116
116
|
`Riffer::Params::Boolean` is the preferred way to declare boolean parameters. `TrueClass` and `FalseClass` continue to work for backwards compatibility.
|
|
117
117
|
|
|
118
|
+
A `Float` param accepts a whole number too, since JSON Schema's `number` covers integers — a model returning `120` for a `Float` is valid, and the validated value is coerced to `120.0`. `Integer` stays strict: `1.0` is rejected, matching JSON Schema's `integer`.
|
|
119
|
+
|
|
118
120
|
### Nested Parameters
|
|
119
121
|
|
|
120
122
|
Tool params support the same nested DSL as structured output — nested objects (`Hash` with block), typed arrays (`Array, of:`), and arrays of objects (`Array` with block). See the [structured output section in Agents](AGENTS.md#nested-objects) for full syntax.
|
data/lib/riffer/agent/run.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Riffer::Agent::Run
|
|
|
20
20
|
# for prompt/files semantics.
|
|
21
21
|
#
|
|
22
22
|
#--
|
|
23
|
-
#: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
23
|
+
#: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
24
24
|
def stream(agent:, prompt: nil, files: nil, tags: {})
|
|
25
25
|
append_user_message(agent, prompt, files: files)
|
|
26
26
|
# The enumerator body runs in its own fiber, where the fiber-local OTEL
|
data/lib/riffer/agent.rb
CHANGED
|
@@ -155,7 +155,7 @@ class Riffer::Agent
|
|
|
155
155
|
|
|
156
156
|
# Streams a response using a new agent instance.
|
|
157
157
|
#--
|
|
158
|
-
#: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
158
|
+
#: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
159
159
|
def self.stream(prompt = nil, files: nil, context: nil, tags: {})
|
|
160
160
|
new(context: context).stream(prompt, files: files, tags: tags)
|
|
161
161
|
end
|
|
@@ -304,13 +304,14 @@ class Riffer::Agent
|
|
|
304
304
|
Riffer::Agent::Run.generate(agent: self, prompt: prompt, files: files, tags: tags)
|
|
305
305
|
end
|
|
306
306
|
|
|
307
|
-
# Streams a response from the agent
|
|
308
|
-
# +Riffer::StreamEvents
|
|
307
|
+
# Streams a response from the agent as an +Enumerator+ of
|
|
308
|
+
# +Riffer::StreamEvents+ whose block-form +each+ returns the final
|
|
309
|
+
# Riffer::Agent::Response. See +#generate+ for prompt/files/tags semantics.
|
|
309
310
|
#
|
|
310
311
|
# Raises Riffer::ArgumentError if structured output is configured.
|
|
311
312
|
#
|
|
312
313
|
#--
|
|
313
|
-
#: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
314
|
+
#: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
314
315
|
def stream(prompt = nil, files: nil, tags: {})
|
|
315
316
|
if @structured_output
|
|
316
317
|
raise Riffer::ArgumentError,
|
data/lib/riffer/params/param.rb
CHANGED
data/lib/riffer/params.rb
CHANGED
|
@@ -79,6 +79,11 @@ class Riffer::Params
|
|
|
79
79
|
|
|
80
80
|
# Validates arguments against parameter definitions.
|
|
81
81
|
#
|
|
82
|
+
# A Float param accepts an Integer (JSON Schema <tt>"number"</tt> covers
|
|
83
|
+
# integers) and its value is coerced with +to_f+, so callers always get a
|
|
84
|
+
# Float. The same holds for the items of an <tt>of: Float</tt> array. No other
|
|
85
|
+
# type is coerced.
|
|
86
|
+
#
|
|
82
87
|
# Raises Riffer::ValidationError if validation fails.
|
|
83
88
|
#
|
|
84
89
|
#--
|
|
@@ -112,7 +117,7 @@ class Riffer::Params
|
|
|
112
117
|
|
|
113
118
|
value = validate_nested(param, value, errors)
|
|
114
119
|
|
|
115
|
-
validated[param.name] = value
|
|
120
|
+
validated[param.name] = coerce_value(param.type, value)
|
|
116
121
|
end
|
|
117
122
|
|
|
118
123
|
raise Riffer::ValidationError, errors.join("; ") if errors.any?
|
|
@@ -178,7 +183,6 @@ class Riffer::Params
|
|
|
178
183
|
validate_nested_array_of_objects(param, value, errors)
|
|
179
184
|
elsif param.type == Array && param.item_type
|
|
180
185
|
validate_typed_array(param, value, errors)
|
|
181
|
-
value
|
|
182
186
|
else
|
|
183
187
|
value
|
|
184
188
|
end
|
|
@@ -218,20 +222,40 @@ class Riffer::Params
|
|
|
218
222
|
end
|
|
219
223
|
end
|
|
220
224
|
|
|
225
|
+
# Returns the array with its valid items coerced by +coerce_value+.
|
|
221
226
|
#--
|
|
222
|
-
#: (Riffer::Params::Param, Array[untyped], Array[String]) ->
|
|
227
|
+
#: (Riffer::Params::Param, Array[untyped], Array[String]) -> Array[untyped]
|
|
223
228
|
def validate_typed_array(param, value, errors)
|
|
224
229
|
item_type = param.item_type
|
|
225
|
-
return unless item_type
|
|
230
|
+
return value unless item_type
|
|
226
231
|
|
|
227
232
|
type_name = Riffer::Params::Param::TYPE_MAPPINGS[item_type]
|
|
228
233
|
valid_item = if [Riffer::Params::Boolean, TrueClass, FalseClass].include?(item_type)
|
|
229
234
|
->(item) { [true, false].include?(item) }
|
|
235
|
+
elsif item_type == Float
|
|
236
|
+
->(item) { item.is_a?(Numeric) }
|
|
230
237
|
else
|
|
231
238
|
->(item) { item.is_a?(item_type) }
|
|
232
239
|
end
|
|
233
|
-
value.
|
|
234
|
-
|
|
240
|
+
value.map.with_index do |item, i|
|
|
241
|
+
unless valid_item.call(item)
|
|
242
|
+
errors << "#{param.name}[#{i}] must be a #{type_name}"
|
|
243
|
+
next item
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
coerce_value(item_type, item)
|
|
235
247
|
end
|
|
236
248
|
end
|
|
249
|
+
|
|
250
|
+
# Coerces an already-validated value to the Ruby type its param declares.
|
|
251
|
+
# Only Float coerces today, because JSON Schema "number" accepts integers and
|
|
252
|
+
# callers should not get a type that depends on whether the model wrote a
|
|
253
|
+
# decimal point. Add a branch here rather than inline at a call site.
|
|
254
|
+
#--
|
|
255
|
+
#: (Module, untyped) -> untyped
|
|
256
|
+
def coerce_value(type, value)
|
|
257
|
+
return value.to_f if type == Float
|
|
258
|
+
|
|
259
|
+
value
|
|
260
|
+
end
|
|
237
261
|
end
|
data/lib/riffer/version.rb
CHANGED
|
@@ -14,8 +14,8 @@ module Riffer::Agent::Run
|
|
|
14
14
|
# for prompt/files semantics.
|
|
15
15
|
#
|
|
16
16
|
# --
|
|
17
|
-
# : (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
18
|
-
def stream: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
17
|
+
# : (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
18
|
+
def stream: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
@@ -113,8 +113,8 @@ class Riffer::Agent
|
|
|
113
113
|
|
|
114
114
|
# Streams a response using a new agent instance.
|
|
115
115
|
# --
|
|
116
|
-
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
117
|
-
def self.stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
116
|
+
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
117
|
+
def self.stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
118
118
|
|
|
119
119
|
# Reconstructs a runnable agent from a wire hash produced by +#to_h+.
|
|
120
120
|
# --
|
|
@@ -208,14 +208,15 @@ class Riffer::Agent
|
|
|
208
208
|
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
|
|
209
209
|
def generate: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Riffer::Agent::Response
|
|
210
210
|
|
|
211
|
-
# Streams a response from the agent
|
|
212
|
-
# +Riffer::StreamEvents
|
|
211
|
+
# Streams a response from the agent as an +Enumerator+ of
|
|
212
|
+
# +Riffer::StreamEvents+ whose block-form +each+ returns the final
|
|
213
|
+
# Riffer::Agent::Response. See +#generate+ for prompt/files/tags semantics.
|
|
213
214
|
#
|
|
214
215
|
# Raises Riffer::ArgumentError if structured output is configured.
|
|
215
216
|
#
|
|
216
217
|
# --
|
|
217
|
-
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
218
|
-
def stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base,
|
|
218
|
+
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
219
|
+
def stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
219
220
|
|
|
220
221
|
# Interrupts the agent loop from an +on_message+ callback. Equivalent to
|
|
221
222
|
# <tt>throw :riffer_interrupt, reason</tt>.
|
|
@@ -40,6 +40,11 @@ class Riffer::Params
|
|
|
40
40
|
|
|
41
41
|
# Validates arguments against parameter definitions.
|
|
42
42
|
#
|
|
43
|
+
# A Float param accepts an Integer (JSON Schema <tt>"number"</tt> covers
|
|
44
|
+
# integers) and its value is coerced with +to_f+, so callers always get a
|
|
45
|
+
# Float. The same holds for the items of an <tt>of: Float</tt> array. No other
|
|
46
|
+
# type is coerced.
|
|
47
|
+
#
|
|
43
48
|
# Raises Riffer::ValidationError if validation fails.
|
|
44
49
|
#
|
|
45
50
|
# --
|
|
@@ -71,7 +76,16 @@ class Riffer::Params
|
|
|
71
76
|
# : (Riffer::Params::Param, Array[untyped], Array[String]) -> Array[untyped]
|
|
72
77
|
def validate_nested_array_of_objects: (Riffer::Params::Param, Array[untyped], Array[String]) -> Array[untyped]
|
|
73
78
|
|
|
79
|
+
# Returns the array with its valid items coerced by +coerce_value+.
|
|
80
|
+
# --
|
|
81
|
+
# : (Riffer::Params::Param, Array[untyped], Array[String]) -> Array[untyped]
|
|
82
|
+
def validate_typed_array: (Riffer::Params::Param, Array[untyped], Array[String]) -> Array[untyped]
|
|
83
|
+
|
|
84
|
+
# Coerces an already-validated value to the Ruby type its param declares.
|
|
85
|
+
# Only Float coerces today, because JSON Schema "number" accepts integers and
|
|
86
|
+
# callers should not get a type that depends on whether the model wrote a
|
|
87
|
+
# decimal point. Add a branch here rather than inline at a call site.
|
|
74
88
|
# --
|
|
75
|
-
# : (
|
|
76
|
-
def
|
|
89
|
+
# : (Module, untyped) -> untyped
|
|
90
|
+
def coerce_value: (Module, untyped) -> untyped
|
|
77
91
|
end
|