riffer 0.51.0 → 0.52.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 +14 -0
- data/docs/AGENTS.md +24 -1
- data/docs/SERIALIZATION.md +3 -0
- data/lib/riffer/agent/config.rb +16 -0
- data/lib/riffer/agent/run.rb +1 -1
- data/lib/riffer/agent/serializer.rb +2 -0
- data/lib/riffer/agent/structured_output/parser.rb +14 -3
- data/lib/riffer/agent/structured_output/result.rb +5 -2
- data/lib/riffer/agent/structured_output.rb +23 -4
- data/lib/riffer/agent.rb +4 -3
- data/lib/riffer/providers/base.rb +24 -4
- data/lib/riffer/version.rb +1 -1
- data/sig/generated/riffer/agent/config.rbs +10 -1
- data/sig/generated/riffer/agent/structured_output/parser.rbs +6 -2
- data/sig/generated/riffer/agent/structured_output/result.rbs +4 -2
- data/sig/generated/riffer/agent/structured_output.rbs +13 -2
- data/sig/generated/riffer/agent.rbs +2 -2
- data/sig/generated/riffer/providers/base.rbs +8 -0
- 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: cdd49e9dd195fd8a90f82297b355f5a267fe79d15a909bb40219fcc5faedc1ea
|
|
4
|
+
data.tar.gz: c0aeeffd8b3aba99953c1efc2ac186a65d86611bd4c9e690cdd31c8834e8242d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc7d4287a7a94b1891d95d2c048e8ad3d5cd1cac9e7cae3047887316ed52224aa8f72a0c01eaba5d0ba1ebe1b4d78b48de42cc4a00114d5a8119fefe405f0e58
|
|
7
|
+
data.tar.gz: db472038a3c65f8643e792c987a837e1955c0699d3a2fd6297612e7f3bc8a8374f56751541663e9662f6da2dea0381a219d43b32b96eef30d6716d9662fdb540
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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.1](https://github.com/janeapp/riffer/compare/riffer/v0.52.0...riffer/v0.52.1) (2026-10-09)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **structured_output:** return the extracted JSON as response content ([#484](https://github.com/janeapp/riffer/issues/484)) ([cbea668](https://github.com/janeapp/riffer/commit/cbea66888690ec7fc0dccbde6198d2704fb165f0))
|
|
14
|
+
|
|
15
|
+
## [0.52.0](https://github.com/janeapp/riffer/compare/riffer/v0.51.0...riffer/v0.52.0) (2026-10-08)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **structured_output:** add experimental prompted strategy ([#477](https://github.com/janeapp/riffer/issues/477)) ([f517fe1](https://github.com/janeapp/riffer/commit/f517fe19788fe34167515998a7ac2870605b2228))
|
|
21
|
+
|
|
8
22
|
## [0.51.0](https://github.com/janeapp/riffer/compare/riffer/v0.50.1...riffer/v0.51.0) (2026-10-07)
|
|
9
23
|
|
|
10
24
|
|
data/docs/AGENTS.md
CHANGED
|
@@ -168,7 +168,7 @@ 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
|
-
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
|
|
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. When the recovered object validates, `response.content` holds only the extracted JSON text, without the fence or prose; the assistant message in `response.messages` keeps the raw reply. A reply that fails parsing or validation leaves the raw text in `response.content`.
|
|
172
172
|
|
|
173
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:
|
|
174
174
|
|
|
@@ -183,6 +183,29 @@ else
|
|
|
183
183
|
end
|
|
184
184
|
```
|
|
185
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'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
|
+
|
|
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
|
+
|
|
186
209
|
#### Nested Objects
|
|
187
210
|
|
|
188
211
|
Use `Hash` with a block to define nested object schemas:
|
data/docs/SERIALIZATION.md
CHANGED
|
@@ -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:
|
data/lib/riffer/agent/config.rb
CHANGED
|
@@ -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
|
data/lib/riffer/agent/run.rb
CHANGED
|
@@ -207,7 +207,7 @@ module Riffer::Agent::Run
|
|
|
207
207
|
result = agent.structured_output && structured_output_result(agent, message)
|
|
208
208
|
build_response(
|
|
209
209
|
agent,
|
|
210
|
-
message&.content || "",
|
|
210
|
+
result&.json || message&.content || "",
|
|
211
211
|
outcome: final_outcome(message, result, interrupted: interrupted, interrupt_reason: interrupt_reason),
|
|
212
212
|
modifications: all_modifications,
|
|
213
213
|
reasoning: message&.reasoning || [],
|
|
@@ -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]
|
|
@@ -9,19 +9,30 @@ module Riffer::Agent::StructuredOutput::Parser
|
|
|
9
9
|
#--
|
|
10
10
|
#: (String) -> Hash[Symbol, untyped]?
|
|
11
11
|
def parse(content)
|
|
12
|
-
|
|
12
|
+
extract(content)&.last
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#--
|
|
16
|
+
#: (String) -> [String, Hash[Symbol, untyped]]?
|
|
17
|
+
def extract(content)
|
|
18
|
+
parsed = parse_object(content)
|
|
19
|
+
return [content, parsed] if parsed
|
|
20
|
+
|
|
21
|
+
outermost_object(content)
|
|
13
22
|
end
|
|
14
23
|
|
|
15
24
|
private
|
|
16
25
|
|
|
17
26
|
#--
|
|
18
|
-
#: (String) -> Hash[Symbol, untyped]?
|
|
27
|
+
#: (String) -> [String, Hash[Symbol, untyped]]?
|
|
19
28
|
def outermost_object(content)
|
|
20
29
|
start = content.index("{")
|
|
21
30
|
stop = content.rindex("}")
|
|
22
31
|
return unless start && stop
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
json = content[start..stop].to_s
|
|
34
|
+
parsed = parse_object(json)
|
|
35
|
+
[json, parsed] if parsed
|
|
25
36
|
end
|
|
26
37
|
|
|
27
38
|
#--
|
|
@@ -5,11 +5,14 @@ class Riffer::Agent::StructuredOutput::Result
|
|
|
5
5
|
attr_reader :object #: Hash[Symbol, untyped]? # @dynamic object
|
|
6
6
|
attr_reader :error #: String? # @dynamic error
|
|
7
7
|
|
|
8
|
+
attr_reader :json #: String? # @dynamic json
|
|
9
|
+
|
|
8
10
|
#--
|
|
9
|
-
#: (?object: Hash[Symbol, untyped]?, ?error: String?) -> void
|
|
10
|
-
def initialize(object: nil, error: nil)
|
|
11
|
+
#: (?object: Hash[Symbol, untyped]?, ?error: String?, ?json: String?) -> void
|
|
12
|
+
def initialize(object: nil, error: nil, json: nil)
|
|
11
13
|
@object = object
|
|
12
14
|
@error = error
|
|
15
|
+
@json = json
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
#--
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
4
6
|
class Riffer::Agent::StructuredOutput
|
|
5
7
|
attr_reader :params #: Riffer::Params # @dynamic params
|
|
8
|
+
attr_reader :strategy #: Symbol # @dynamic strategy
|
|
6
9
|
|
|
7
10
|
#--
|
|
8
|
-
#: (Riffer::Params) -> void
|
|
9
|
-
def initialize(params)
|
|
11
|
+
#: (Riffer::Params, ?strategy: Symbol) -> void
|
|
12
|
+
def initialize(params, strategy: :native)
|
|
10
13
|
@params = params
|
|
14
|
+
@strategy = strategy
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#--
|
|
18
|
+
#: () -> bool
|
|
19
|
+
def prompted?
|
|
20
|
+
@strategy == :prompted
|
|
11
21
|
end
|
|
12
22
|
|
|
13
23
|
#--
|
|
@@ -16,14 +26,23 @@ class Riffer::Agent::StructuredOutput
|
|
|
16
26
|
@params.to_json_schema(strict: strict)
|
|
17
27
|
end
|
|
18
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
|
+
|
|
19
38
|
#--
|
|
20
39
|
#: (String) -> Riffer::Agent::StructuredOutput::Result
|
|
21
40
|
def parse_and_validate(json_string)
|
|
22
|
-
parsed = Riffer::Agent::StructuredOutput::Parser.
|
|
41
|
+
json, parsed = Riffer::Agent::StructuredOutput::Parser.extract(json_string)
|
|
23
42
|
return Result.new(error: "JSON parse error: no JSON object found in the response") if parsed.nil?
|
|
24
43
|
|
|
25
44
|
validated = @params.validate(parsed)
|
|
26
|
-
Result.new(object: validated)
|
|
45
|
+
Result.new(object: validated, json: json)
|
|
27
46
|
rescue Riffer::ValidationError => e
|
|
28
47
|
Result.new(error: "Validation error: #{e.message}")
|
|
29
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
|
|
@@ -371,6 +371,26 @@ class Riffer::Providers::Base
|
|
|
371
371
|
JSON.parse(arguments)
|
|
372
372
|
end
|
|
373
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
|
+
|
|
374
394
|
#--
|
|
375
395
|
#: (Array[Riffer::Messages::Base]) -> Array[Riffer::Messages::Base]
|
|
376
396
|
def merge_consecutive_messages(messages)
|
data/lib/riffer/version.rb
CHANGED
|
@@ -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)
|
|
@@ -5,11 +5,15 @@ module Riffer::Agent::StructuredOutput::Parser
|
|
|
5
5
|
# : (String) -> Hash[Symbol, untyped]?
|
|
6
6
|
def parse: (String) -> Hash[Symbol, untyped]?
|
|
7
7
|
|
|
8
|
+
# --
|
|
9
|
+
# : (String) -> [String, Hash[Symbol, untyped]]?
|
|
10
|
+
def extract: (String) -> [ String, Hash[Symbol, untyped] ]?
|
|
11
|
+
|
|
8
12
|
private
|
|
9
13
|
|
|
10
14
|
# --
|
|
11
|
-
# : (String) -> Hash[Symbol, untyped]?
|
|
12
|
-
def outermost_object: (String) -> Hash[Symbol, untyped]?
|
|
15
|
+
# : (String) -> [String, Hash[Symbol, untyped]]?
|
|
16
|
+
def outermost_object: (String) -> [ String, Hash[Symbol, untyped] ]?
|
|
13
17
|
|
|
14
18
|
# --
|
|
15
19
|
# : (String) -> Hash[Symbol, untyped]?
|
|
@@ -5,9 +5,11 @@ class Riffer::Agent::StructuredOutput::Result
|
|
|
5
5
|
|
|
6
6
|
attr_reader error: String?
|
|
7
7
|
|
|
8
|
+
attr_reader json: String?
|
|
9
|
+
|
|
8
10
|
# --
|
|
9
|
-
# : (?object: Hash[Symbol, untyped]?, ?error: String?) -> void
|
|
10
|
-
def initialize: (?object: Hash[Symbol, untyped]?, ?error: String?) -> void
|
|
11
|
+
# : (?object: Hash[Symbol, untyped]?, ?error: String?, ?json: String?) -> void
|
|
12
|
+
def initialize: (?object: Hash[Symbol, untyped]?, ?error: String?, ?json: String?) -> void
|
|
11
13
|
|
|
12
14
|
# --
|
|
13
15
|
# : () -> bool
|
|
@@ -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
|
-
# : (
|
|
8
|
-
def
|
|
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]
|