riffer 0.50.1 → 0.52.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6bfdc8b4676c2d368fd17ff5b709e802549a01580845e9c12c9f352d395e128
4
- data.tar.gz: 1567b5498abacc5fe2c820dee04d18bee016465108eea6c1172f03954359bdc6
3
+ metadata.gz: 3a69720c69304a2b2ac26a5b03a3fa0866a58434687fc5745563a6a4dd0afc7c
4
+ data.tar.gz: aa9b4937c096c1e152b941f426b7f6b119d6b90d45935a57a42e9fd0f19ac0bd
5
5
  SHA512:
6
- metadata.gz: 78c30c4798848f219b2c10f756d2c0caca5a95f2ab705660789a5e557284aa275e9d203a18e37427be63d8cee6858d90a88cb3da7aaf59abff2271a532ed65ec
7
- data.tar.gz: a8e8b4a891b42b3b1f83ba50a4fe6affe3f9ecf78a158de8d6696c89ed71c88a2549b5a6f72c134743914abb69e0a34d88e953b700d166be6626d8b92b6a139d
6
+ metadata.gz: 169697ebceedccc7be95b4f98ddee84a93f48b330b1fe71a1207ad98d79822298e0432e7bb9ab4696ffa2e2709c8df4f795fbd2e6fd33a1643147be2f2cf8870
7
+ data.tar.gz: eb1eab78a9d50a63ea156b53c951e500944d5ef8ec622811dead87c0427198de9d979a31ec60b0de006d8c6409424d73b605ff7ddf997a88de55bd37eea3d3b1
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.50.1"
2
+ ".": "0.52.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ 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.52.0](https://github.com/janeapp/riffer/compare/riffer/v0.51.0...riffer/v0.52.0) (2026-10-08)
9
+
10
+
11
+ ### Features
12
+
13
+ * **structured_output:** add experimental prompted strategy ([#477](https://github.com/janeapp/riffer/issues/477)) ([f517fe1](https://github.com/janeapp/riffer/commit/f517fe19788fe34167515998a7ac2870605b2228))
14
+
15
+ ## [0.51.0](https://github.com/janeapp/riffer/compare/riffer/v0.50.1...riffer/v0.51.0) (2026-10-07)
16
+
17
+
18
+ ### Features
19
+
20
+ * **structured_output:** recover objects wrapped in code fences or prose ([#473](https://github.com/janeapp/riffer/issues/473)) ([59a6d40](https://github.com/janeapp/riffer/commit/59a6d40b08b9f18fcb4ebac24598925f94a9acea))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **agent:** apply the max_steps cap per run, not per conversation ([#478](https://github.com/janeapp/riffer/issues/478)) ([4948e6b](https://github.com/janeapp/riffer/commit/4948e6b9769df084bed8c8a29862067560f800bc))
26
+
8
27
  ## [0.50.1](https://github.com/janeapp/riffer/compare/riffer/v0.50.0...riffer/v0.50.1) (2026-10-02)
9
28
 
10
29
 
data/docs/AGENTS.md CHANGED
@@ -168,7 +168,9 @@ end
168
168
 
169
169
  The LLM response is automatically parsed and validated against the schema. Access the result via `response.structured_output`.
170
170
 
171
- When the response is not valid JSON or does not satisfy the schema, `response.structured_output` is `nil`, `response.outcome.reason` is `:invalid_structured_output`, and `response.outcome.detail` carries the parse or validation message:
171
+ If the response is not valid JSON on its own, Riffer recovers a JSON object wrapped in a code fence (```` ```json ````), surrounded by prose (`Here is the data: {...}`), or wrapped in formatting (`**{...}**`). It parses the span from the first `{` to the last `}`, so a reply whose surrounding text contains other braces, or that holds more than one object, is still rejected; `response.content` keeps the raw text.
172
+
173
+ When the response contains no well-formed JSON object or does not satisfy the schema, `response.structured_output` is `nil`, `response.outcome.reason` is `:invalid_structured_output`, and `response.outcome.detail` carries the parse or validation message:
172
174
 
173
175
  ```ruby
174
176
  response = SentimentAgent.generate('Analyze: "I love this!"')
@@ -181,6 +183,29 @@ else
181
183
  end
182
184
  ```
183
185
 
186
+ #### Structured output strategy (experimental)
187
+
188
+ By default Riffer sends the schema through each provider's native structured-output field. Some models reject that field, accept it without enforcing it, or behave differently with it enabled (for example, skipping tool calls). For those, opt the agent into the **experimental** prompted strategy:
189
+
190
+ ```ruby
191
+ class SentimentAgent < Riffer::Agent
192
+ model 'amazon_bedrock/my-model'
193
+ instructions 'Analyze the sentiment of the given text.'
194
+ structured_output strategy: :prompted do
195
+ required :sentiment, String, description: "positive, negative, or neutral"
196
+ end
197
+ end
198
+ ```
199
+
200
+ With `strategy: :prompted`, the provider never receives its native structured-output field. Instead, an instruction carrying the JSON Schema is appended to the system instructions on every request of the run (it is not stored in `session.messages`), and the reply is parsed and validated exactly as above: `response.content` is the reply 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
+
202
+ | Strategy | Behavior |
203
+ | ------------------- | --------------------------------------------------------------- |
204
+ | `:native` (default) | Schema sent through the provider's structured-output field |
205
+ | `:prompted` | Schema sent in the system prompt; experimental, may change |
206
+
207
+ 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
+
184
209
  #### Nested Objects
185
210
 
186
211
  Use `Hash` with a block to define nested object schemas:
@@ -351,7 +351,7 @@ end
351
351
 
352
352
  ### response.structured_output
353
353
 
354
- When structured output is configured, the LLM response is parsed as JSON and validated against the schema. The validated result is available as `response.structured_output`:
354
+ When structured output is configured, the LLM response is parsed as JSON and validated against the schema. A JSON object wrapped in a code fence, prose, or formatting is recovered (see [Agents — structured_output](AGENTS.md#structured_output)). The validated result is available as `response.structured_output`:
355
355
 
356
356
  ```ruby
357
357
  response = SentimentAgent.generate('Analyze: "I love this!"')
data/docs/AGENT_LOOP.md CHANGED
@@ -72,12 +72,12 @@ response = agent.generate('Approved, continue') # continues where it left off
72
72
 
73
73
  ### Max Steps Limit
74
74
 
75
- The `max_steps` class method caps the number of LLM call steps in the tool-use loop. When the step count reaches the limit, the loop interrupts automatically with reason `:max_steps`.
75
+ The `max_steps` class method caps the number of LLM call steps in the tool-use loop of a single run. Steps are counted from the last user message in the session, so each new user message starts a fresh budget — prior turns' assistant messages in the history do not count against it. When the step count reaches the limit, the loop interrupts automatically with reason `:max_steps`.
76
76
 
77
77
  - **When to use:** Safety net to prevent runaway tool-use loops — useful when agents have access to many tools or operate autonomously.
78
78
  - **Response:** `response.outcome.reason` is `:max_steps`.
79
79
  - **Streaming:** Yields an `Interrupt` event with `reason: :max_steps`.
80
- - **Resumable:** Yes. Call `generate('Continue')` or `stream('Continue')` on the same agent instance to resume. For cross-process resume, pass persisted messages as an array to a new agent. Pending tool calls are automatically executed before the LLM loop resumes.
80
+ - **Resumable:** Yes. Call `generate('Continue')` or `stream('Continue')` on the same agent instance to resume with a fresh budget, or call `generate` / `stream` with no prompt to resume against the remaining budget. For cross-process resume, pass persisted messages as an array to a new agent; the same rule applies. Pending tool calls are automatically executed before the LLM loop resumes.
81
81
 
82
82
  ```ruby
83
83
  class MyAgent < Riffer::Agent
@@ -45,6 +45,7 @@ The session is used **as-is**: the rebuilt agent does not prepend anything to it
45
45
  model_options: { temperature: 0.2 },
46
46
  max_steps: 8, # integer; -1 = unlimited (see below)
47
47
  structured_output: { type: "object", … }, # JSON Schema, or null
48
+ structured_output_strategy: :native, # :native or :prompted ("native"/"prompted" over JSON)
48
49
  tools: [ { name:, description:, parameters_schema:, timeout: }, … ]
49
50
  }
50
51
  ```
@@ -57,6 +58,8 @@ The session is used **as-is**: the rebuilt agent does not prepend anything to it
57
58
 
58
59
  Structured output crosses as **provider-neutral JSON Schema** (`Riffer::Params#to_json_schema`), never a provider-rendered schema. `from_h` rebuilds a validating `Riffer::Params` via `Riffer::Params.from_json_schema`, so the rebuilt agent both constrains the model and can `parse_and_validate` responses — rendering provider-correct bytes at call time, the same way an in-code agent does.
59
60
 
61
+ `structured_output_strategy` carries the [strategy](AGENTS.md#structured-output-strategy-experimental) the agent was configured with, so a `:prompted` agent rebuilds as `:prompted`. A hash without the key decodes as `:native`.
62
+
60
63
  ## Reconstructing tools
61
64
 
62
65
  Tools cross as `{name, description, parameters_schema, timeout}` descriptors — never code. How a descriptor becomes a runnable tool is controlled by the `tool_resolver:` you pass to `from_h`. The two common shapes:
@@ -3,6 +3,7 @@
3
3
 
4
4
  class Riffer::Agent::Config
5
5
  DEFAULT_MAX_STEPS = 16 #: Integer
6
+ STRUCTURED_OUTPUT_STRATEGIES = %i[native prompted].freeze #: Array[Symbol]
6
7
 
7
8
  # @rbs @tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?
8
9
 
@@ -11,6 +12,7 @@ class Riffer::Agent::Config
11
12
  attr_reader :instructions #: (String | Proc)? # @dynamic instructions
12
13
  attr_accessor :model_options #: Hash[Symbol, untyped] # @dynamic model_options, model_options=
13
14
  attr_reader :structured_output #: Riffer::Params? # @dynamic structured_output
15
+ attr_reader :structured_output_strategy #: Symbol # @dynamic structured_output_strategy
14
16
  attr_accessor :max_steps #: Numeric? # @dynamic max_steps, max_steps=
15
17
  attr_accessor :tools_config #: (Array[singleton(Riffer::Tool)] | Proc)? # @dynamic tools_config, tools_config=
16
18
  attr_reader :mcp_configs #: Array[Hash[Symbol, untyped]] # @dynamic mcp_configs
@@ -24,6 +26,7 @@ class Riffer::Agent::Config
24
26
  # ?instructions: (String | Proc)?,
25
27
  # ?model_options: Hash[Symbol, untyped],
26
28
  # ?structured_output: Riffer::Params?,
29
+ # ?structured_output_strategy: Symbol,
27
30
  # ?max_steps: Numeric?,
28
31
  # ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?,
29
32
  # ?mcp_configs: Array[Hash[Symbol, untyped]],
@@ -37,6 +40,7 @@ class Riffer::Agent::Config
37
40
  instructions: nil,
38
41
  model_options: {},
39
42
  structured_output: nil,
43
+ structured_output_strategy: :native,
40
44
  max_steps: DEFAULT_MAX_STEPS,
41
45
  tools_config: nil,
42
46
  mcp_configs: [],
@@ -54,6 +58,7 @@ class Riffer::Agent::Config
54
58
  self.model = model
55
59
  self.instructions = instructions
56
60
  self.structured_output = structured_output
61
+ self.structured_output_strategy = structured_output_strategy
57
62
  self.tool_runtime = tool_runtime if tool_runtime
58
63
  end
59
64
 
@@ -73,6 +78,17 @@ class Riffer::Agent::Config
73
78
  @structured_output = value
74
79
  end
75
80
 
81
+ #--
82
+ #: (Symbol) -> Symbol
83
+ def structured_output_strategy=(value)
84
+ unless STRUCTURED_OUTPUT_STRATEGIES.include?(value)
85
+ raise Riffer::ArgumentError,
86
+ "structured_output_strategy must be one of #{STRUCTURED_OUTPUT_STRATEGIES.inspect}, got #{value.inspect}"
87
+ end
88
+
89
+ @structured_output_strategy = value
90
+ end
91
+
76
92
  #--
77
93
  #: () -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
78
94
  def tool_runtime
@@ -68,7 +68,7 @@ module Riffer::Agent::Run
68
68
  end
69
69
 
70
70
  begin
71
- step = agent.session.steps
71
+ step = agent.session.steps_since_last_user_message
72
72
 
73
73
  reason = catch(:riffer_interrupt) do
74
74
  execute_pending_tool_calls(agent, tags)
@@ -27,6 +27,7 @@ module Riffer::Agent::Serializer
27
27
  model_options: config.model_options,
28
28
  max_steps: encode_max_steps(config.max_steps),
29
29
  structured_output: config.structured_output&.to_json_schema(strict: false),
30
+ structured_output_strategy: config.structured_output_strategy,
30
31
  tools: agent.tools.map { |tool_class| tool_descriptor(tool_class) },
31
32
  }
32
33
  end
@@ -76,6 +77,7 @@ module Riffer::Agent::Serializer
76
77
  instructions: hash[:instructions],
77
78
  model_options: hash[:model_options] || {},
78
79
  structured_output: decode_structured_output(hash[:structured_output]),
80
+ structured_output_strategy: (hash[:structured_output_strategy] || :native).to_sym,
79
81
  max_steps: decode_max_steps(hash),
80
82
  tools_config: tools,
81
83
  } #: Hash[Symbol, untyped]
@@ -143,6 +143,13 @@ class Riffer::Agent::Session
143
143
  @messages.count { |m| m.is_a?(Riffer::Messages::Assistant) }
144
144
  end
145
145
 
146
+ #--
147
+ #: () -> Integer
148
+ def steps_since_last_user_message
149
+ since_last_user = @messages.reverse_each.take_while { |m| !m.is_a?(Riffer::Messages::User) }
150
+ since_last_user.count { |m| m.is_a?(Riffer::Messages::Assistant) }
151
+ end
152
+
146
153
  #--
147
154
  #: () -> Riffer::Messages::Assistant?
148
155
  def final_assistant_message
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+
6
+ module Riffer::Agent::StructuredOutput::Parser
7
+ extend self
8
+
9
+ #--
10
+ #: (String) -> Hash[Symbol, untyped]?
11
+ def parse(content)
12
+ parse_object(content) || outermost_object(content)
13
+ end
14
+
15
+ private
16
+
17
+ #--
18
+ #: (String) -> Hash[Symbol, untyped]?
19
+ def outermost_object(content)
20
+ start = content.index("{")
21
+ stop = content.rindex("}")
22
+ return unless start && stop
23
+
24
+ parse_object(content[start..stop].to_s)
25
+ end
26
+
27
+ #--
28
+ #: (String) -> Hash[Symbol, untyped]?
29
+ def parse_object(json)
30
+ parsed = JSON.parse(json, symbolize_names: true)
31
+ parsed if parsed.is_a?(Hash)
32
+ rescue JSON::ParserError
33
+ nil
34
+ end
35
+ end
@@ -5,11 +5,19 @@ require "json"
5
5
 
6
6
  class Riffer::Agent::StructuredOutput
7
7
  attr_reader :params #: Riffer::Params # @dynamic params
8
+ attr_reader :strategy #: Symbol # @dynamic strategy
8
9
 
9
10
  #--
10
- #: (Riffer::Params) -> void
11
- def initialize(params)
11
+ #: (Riffer::Params, ?strategy: Symbol) -> void
12
+ def initialize(params, strategy: :native)
12
13
  @params = params
14
+ @strategy = strategy
15
+ end
16
+
17
+ #--
18
+ #: () -> bool
19
+ def prompted?
20
+ @strategy == :prompted
13
21
  end
14
22
 
15
23
  #--
@@ -18,14 +26,23 @@ class Riffer::Agent::StructuredOutput
18
26
  @params.to_json_schema(strict: strict)
19
27
  end
20
28
 
29
+ # Worded for the final answer only, so the model can still call tools first.
30
+ #--
31
+ #: () -> String
32
+ def prompt_instructions
33
+ "When you give your final answer, respond with only a JSON object that conforms to " \
34
+ "the following JSON Schema, with no other text before or after it:\n\n" \
35
+ "#{JSON.generate(json_schema)}"
36
+ end
37
+
21
38
  #--
22
39
  #: (String) -> Riffer::Agent::StructuredOutput::Result
23
40
  def parse_and_validate(json_string)
24
- parsed = JSON.parse(json_string, symbolize_names: true)
41
+ parsed = Riffer::Agent::StructuredOutput::Parser.parse(json_string)
42
+ return Result.new(error: "JSON parse error: no JSON object found in the response") if parsed.nil?
43
+
25
44
  validated = @params.validate(parsed)
26
45
  Result.new(object: validated)
27
- rescue JSON::ParserError => e
28
- Result.new(error: "JSON parse error: #{e.message}")
29
46
  rescue Riffer::ValidationError => e
30
47
  Result.new(error: "Validation error: #{e.message}")
31
48
  end
data/lib/riffer/agent.rb CHANGED
@@ -53,13 +53,14 @@ class Riffer::Agent
53
53
  end
54
54
 
55
55
  #--
56
- #: (?Riffer::Params?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
57
- def self.structured_output(params = nil, &block)
56
+ #: (?Riffer::Params?, ?strategy: Symbol?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
57
+ def self.structured_output(params = nil, strategy: nil, &block)
58
58
  if block
59
59
  params = Riffer::Params.new
60
60
  params.instance_eval(&block)
61
61
  end
62
62
  config.structured_output = params if params
63
+ config.structured_output_strategy = strategy if strategy
63
64
  config.structured_output
64
65
  end
65
66
 
@@ -328,7 +329,7 @@ class Riffer::Agent
328
329
  #: () -> Riffer::Agent::StructuredOutput?
329
330
  def resolve_structured_output
330
331
  params = @config.structured_output
331
- params ? Riffer::Agent::StructuredOutput.new(params) : nil
332
+ params ? Riffer::Agent::StructuredOutput.new(params, strategy: @config.structured_output_strategy) : nil
332
333
  end
333
334
 
334
335
  #--
@@ -38,8 +38,8 @@ class Riffer::Providers::Base
38
38
  messages = normalize_messages(prompt: prompt, system: system, messages: messages, files: files)
39
39
  validate_normalized_messages!(messages)
40
40
  Riffer::Files::Resolver.new(provider: self).resolve!(messages)
41
- messages = merge_consecutive_messages(messages)
42
- params = build_request_params(messages, model, options)
41
+ messages = merge_consecutive_messages(with_structured_output_prompt(messages, options[:structured_output]))
42
+ params = build_request_params(messages, model, request_options(options))
43
43
 
44
44
  in_chat_span(model, messages, options) do |span|
45
45
  response = execute_generate(params)
@@ -76,8 +76,8 @@ class Riffer::Providers::Base
76
76
  messages = normalize_messages(prompt: prompt, system: system, messages: messages, files: files)
77
77
  validate_normalized_messages!(messages)
78
78
  Riffer::Files::Resolver.new(provider: self).resolve!(messages)
79
- messages = merge_consecutive_messages(messages)
80
- params = build_request_params(messages, model, options)
79
+ messages = merge_consecutive_messages(with_structured_output_prompt(messages, options[:structured_output]))
80
+ params = build_request_params(messages, model, request_options(options))
81
81
 
82
82
  # The enumerator body runs in its own fiber, where the fiber-local OTEL
83
83
  # context is empty — capture here so the chat span parents to the caller's
@@ -360,9 +360,7 @@ class Riffer::Providers::Base
360
360
  #--
361
361
  #: (String) -> Hash[Symbol, untyped]?
362
362
  def parse_structured_output(content)
363
- JSON.parse(content, symbolize_names: true)
364
- rescue JSON::ParserError
365
- nil
363
+ Riffer::Agent::StructuredOutput::Parser.parse(content)
366
364
  end
367
365
 
368
366
  #--
@@ -373,6 +371,26 @@ class Riffer::Providers::Base
373
371
  JSON.parse(arguments)
374
372
  end
375
373
 
374
+ #--
375
+ #: (Array[Riffer::Messages::Base], Riffer::Agent::StructuredOutput?) -> Array[Riffer::Messages::Base]
376
+ def with_structured_output_prompt(messages, structured_output)
377
+ return messages unless structured_output&.prompted?
378
+
379
+ # After the leading system messages, so merge_consecutive_messages folds it into the system prompt.
380
+ leading_system = messages.take_while { |m| m.is_a?(Riffer::Messages::System) }
381
+ [
382
+ *leading_system,
383
+ Riffer::Messages::System.new(structured_output.prompt_instructions),
384
+ *messages.drop(leading_system.length),
385
+ ]
386
+ end
387
+
388
+ #--
389
+ #: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
390
+ def request_options(options)
391
+ options[:structured_output]&.prompted? ? options.except(:structured_output) : options
392
+ end
393
+
376
394
  #--
377
395
  #: (Array[Riffer::Messages::Base]) -> Array[Riffer::Messages::Base]
378
396
  def merge_consecutive_messages(messages)
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.50.1" #: String
5
+ VERSION = "0.52.0" #: String
6
6
  end
@@ -10,7 +10,7 @@ gems:
10
10
  remote: https://github.com/ruby/gem_rbs_collection.git
11
11
  repo_dir: gems
12
12
  - name: anthropic
13
- version: 1.71.0
13
+ version: 1.76.0
14
14
  source:
15
15
  type: rubygems
16
16
  - name: ast
@@ -170,7 +170,7 @@ gems:
170
170
  source:
171
171
  type: stdlib
172
172
  - name: openai
173
- version: 0.91.0
173
+ version: 0.97.0
174
174
  source:
175
175
  type: rubygems
176
176
  - name: openssl
@@ -3,6 +3,8 @@
3
3
  class Riffer::Agent::Config
4
4
  DEFAULT_MAX_STEPS: Integer
5
5
 
6
+ STRUCTURED_OUTPUT_STRATEGIES: Array[Symbol]
7
+
6
8
  @tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?
7
9
 
8
10
  attr_reader identifier: String?
@@ -15,6 +17,8 @@ class Riffer::Agent::Config
15
17
 
16
18
  attr_reader structured_output: Riffer::Params?
17
19
 
20
+ attr_reader structured_output_strategy: Symbol
21
+
18
22
  attr_accessor max_steps: Numeric?
19
23
 
20
24
  attr_accessor tools_config: (Array[singleton(Riffer::Tool)] | Proc)?
@@ -32,6 +36,7 @@ class Riffer::Agent::Config
32
36
  # ?instructions: (String | Proc)?,
33
37
  # ?model_options: Hash[Symbol, untyped],
34
38
  # ?structured_output: Riffer::Params?,
39
+ # ?structured_output_strategy: Symbol,
35
40
  # ?max_steps: Numeric?,
36
41
  # ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?,
37
42
  # ?mcp_configs: Array[Hash[Symbol, untyped]],
@@ -39,7 +44,7 @@ class Riffer::Agent::Config
39
44
  # ?skills_config: Riffer::Skills::Config?,
40
45
  # ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]
41
46
  # ) -> void
42
- def initialize: (?identifier: String?, ?model: (String | Proc)?, ?instructions: (String | Proc)?, ?model_options: Hash[Symbol, untyped], ?structured_output: Riffer::Params?, ?max_steps: Numeric?, ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?, ?mcp_configs: Array[Hash[Symbol, untyped]], ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?, ?skills_config: Riffer::Skills::Config?, ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]) -> void
47
+ def initialize: (?identifier: String?, ?model: (String | Proc)?, ?instructions: (String | Proc)?, ?model_options: Hash[Symbol, untyped], ?structured_output: Riffer::Params?, ?structured_output_strategy: Symbol, ?max_steps: Numeric?, ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?, ?mcp_configs: Array[Hash[Symbol, untyped]], ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?, ?skills_config: Riffer::Skills::Config?, ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]) -> void
43
48
 
44
49
  # --
45
50
  # : (untyped) -> String?
@@ -49,6 +54,10 @@ class Riffer::Agent::Config
49
54
  # : (Riffer::Params?) -> Riffer::Params?
50
55
  def structured_output=: (Riffer::Params?) -> Riffer::Params?
51
56
 
57
+ # --
58
+ # : (Symbol) -> Symbol
59
+ def structured_output_strategy=: (Symbol) -> Symbol
60
+
52
61
  # --
53
62
  # : () -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
54
63
  def tool_runtime: () -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
@@ -57,6 +57,10 @@ class Riffer::Agent::Session
57
57
  # : () -> Integer
58
58
  def steps: () -> Integer
59
59
 
60
+ # --
61
+ # : () -> Integer
62
+ def steps_since_last_user_message: () -> Integer
63
+
60
64
  # --
61
65
  # : () -> Riffer::Messages::Assistant?
62
66
  def final_assistant_message: () -> Riffer::Messages::Assistant?
@@ -0,0 +1,17 @@
1
+ # Generated from lib/riffer/agent/structured_output/parser.rb with RBS::Inline
2
+
3
+ module Riffer::Agent::StructuredOutput::Parser
4
+ # --
5
+ # : (String) -> Hash[Symbol, untyped]?
6
+ def parse: (String) -> Hash[Symbol, untyped]?
7
+
8
+ private
9
+
10
+ # --
11
+ # : (String) -> Hash[Symbol, untyped]?
12
+ def outermost_object: (String) -> Hash[Symbol, untyped]?
13
+
14
+ # --
15
+ # : (String) -> Hash[Symbol, untyped]?
16
+ def parse_object: (String) -> Hash[Symbol, untyped]?
17
+ end
@@ -3,14 +3,25 @@
3
3
  class Riffer::Agent::StructuredOutput
4
4
  attr_reader params: Riffer::Params
5
5
 
6
+ attr_reader strategy: Symbol
7
+
8
+ # --
9
+ # : (Riffer::Params, ?strategy: Symbol) -> void
10
+ def initialize: (Riffer::Params, ?strategy: Symbol) -> void
11
+
6
12
  # --
7
- # : (Riffer::Params) -> void
8
- def initialize: (Riffer::Params) -> void
13
+ # : () -> bool
14
+ def prompted?: () -> bool
9
15
 
10
16
  # --
11
17
  # : (?strict: bool) -> Hash[Symbol, untyped]
12
18
  def json_schema: (?strict: bool) -> Hash[Symbol, untyped]
13
19
 
20
+ # Worded for the final answer only, so the model can still call tools first.
21
+ # --
22
+ # : () -> String
23
+ def prompt_instructions: () -> String
24
+
14
25
  # --
15
26
  # : (String) -> Riffer::Agent::StructuredOutput::Result
16
27
  def parse_and_validate: (String) -> Riffer::Agent::StructuredOutput::Result
@@ -32,8 +32,8 @@ class Riffer::Agent
32
32
  def self.model_options: (?Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]
33
33
 
34
34
  # --
35
- # : (?Riffer::Params?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
36
- def self.structured_output: (?Riffer::Params?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
35
+ # : (?Riffer::Params?, ?strategy: Symbol?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
36
+ def self.structured_output: (?Riffer::Params?, ?strategy: Symbol?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
37
37
 
38
38
  # --
39
39
  # : (*Numeric?) -> Numeric?
@@ -146,6 +146,14 @@ class Riffer::Providers::Base
146
146
  # : (String) -> Hash[String, untyped]
147
147
  def parse_tool_arguments: (String) -> Hash[String, untyped]
148
148
 
149
+ # --
150
+ # : (Array[Riffer::Messages::Base], Riffer::Agent::StructuredOutput?) -> Array[Riffer::Messages::Base]
151
+ def with_structured_output_prompt: (Array[Riffer::Messages::Base], Riffer::Agent::StructuredOutput?) -> Array[Riffer::Messages::Base]
152
+
153
+ # --
154
+ # : (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
155
+ def request_options: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
156
+
149
157
  # --
150
158
  # : (Array[Riffer::Messages::Base]) -> Array[Riffer::Messages::Base]
151
159
  def merge_consecutive_messages: (Array[Riffer::Messages::Base]) -> Array[Riffer::Messages::Base]
@@ -0,0 +1,5 @@
1
+ # `Riffer::Agent::StructuredOutput::Parser` uses `extend self`; rbs-inline doesn't emit that,
2
+ # so re-extend here to expose its instance methods as singleton methods.
3
+ module Riffer::Agent::StructuredOutput::Parser
4
+ extend ::Riffer::Agent::StructuredOutput::Parser
5
+ end
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.50.1
4
+ version: 0.52.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall
@@ -117,6 +117,7 @@ files:
117
117
  - lib/riffer/agent/session.rb
118
118
  - lib/riffer/agent/session/repair.rb
119
119
  - lib/riffer/agent/structured_output.rb
120
+ - lib/riffer/agent/structured_output/parser.rb
120
121
  - lib/riffer/agent/structured_output/result.rb
121
122
  - lib/riffer/config.rb
122
123
  - lib/riffer/config/amazon_bedrock.rb
@@ -265,6 +266,7 @@ files:
265
266
  - sig/generated/riffer/agent/session.rbs
266
267
  - sig/generated/riffer/agent/session/repair.rbs
267
268
  - sig/generated/riffer/agent/structured_output.rbs
269
+ - sig/generated/riffer/agent/structured_output/parser.rbs
268
270
  - sig/generated/riffer/agent/structured_output/result.rbs
269
271
  - sig/generated/riffer/config.rbs
270
272
  - sig/generated/riffer/config/amazon_bedrock.rbs
@@ -390,6 +392,7 @@ files:
390
392
  - sig/manual/riffer/agent/run.rbs
391
393
  - sig/manual/riffer/agent/serializer.rbs
392
394
  - sig/manual/riffer/agent/session/repair.rbs
395
+ - sig/manual/riffer/agent/structured_output/parser.rbs
393
396
  - sig/manual/riffer/evals/evaluator_runner.rbs
394
397
  - sig/manual/riffer/helpers/boolean.rbs
395
398
  - sig/manual/riffer/helpers/call_or_value.rbs