riffer 0.52.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 +7 -0
- data/docs/AGENTS.md +2 -2
- data/lib/riffer/agent/run.rb +1 -1
- 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 +2 -2
- data/lib/riffer/version.rb +1 -1
- data/sig/generated/riffer/agent/structured_output/parser.rbs +6 -2
- data/sig/generated/riffer/agent/structured_output/result.rbs +4 -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: 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,13 @@ 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
|
+
|
|
8
15
|
## [0.52.0](https://github.com/janeapp/riffer/compare/riffer/v0.51.0...riffer/v0.52.0) (2026-10-08)
|
|
9
16
|
|
|
10
17
|
|
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
|
|
|
@@ -197,7 +197,7 @@ class SentimentAgent < Riffer::Agent
|
|
|
197
197
|
end
|
|
198
198
|
```
|
|
199
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.
|
|
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
201
|
|
|
202
202
|
| Strategy | Behavior |
|
|
203
203
|
| ------------------- | --------------------------------------------------------------- |
|
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 || [],
|
|
@@ -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
|
#--
|
|
@@ -38,11 +38,11 @@ class Riffer::Agent::StructuredOutput
|
|
|
38
38
|
#--
|
|
39
39
|
#: (String) -> Riffer::Agent::StructuredOutput::Result
|
|
40
40
|
def parse_and_validate(json_string)
|
|
41
|
-
parsed = Riffer::Agent::StructuredOutput::Parser.
|
|
41
|
+
json, parsed = Riffer::Agent::StructuredOutput::Parser.extract(json_string)
|
|
42
42
|
return Result.new(error: "JSON parse error: no JSON object found in the response") if parsed.nil?
|
|
43
43
|
|
|
44
44
|
validated = @params.validate(parsed)
|
|
45
|
-
Result.new(object: validated)
|
|
45
|
+
Result.new(object: validated, json: json)
|
|
46
46
|
rescue Riffer::ValidationError => e
|
|
47
47
|
Result.new(error: "Validation error: #{e.message}")
|
|
48
48
|
end
|
data/lib/riffer/version.rb
CHANGED
|
@@ -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
|