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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a69720c69304a2b2ac26a5b03a3fa0866a58434687fc5745563a6a4dd0afc7c
4
- data.tar.gz: aa9b4937c096c1e152b941f426b7f6b119d6b90d45935a57a42e9fd0f19ac0bd
3
+ metadata.gz: cdd49e9dd195fd8a90f82297b355f5a267fe79d15a909bb40219fcc5faedc1ea
4
+ data.tar.gz: c0aeeffd8b3aba99953c1efc2ac186a65d86611bd4c9e690cdd31c8834e8242d
5
5
  SHA512:
6
- metadata.gz: 169697ebceedccc7be95b4f98ddee84a93f48b330b1fe71a1207ad98d79822298e0432e7bb9ab4696ffa2e2709c8df4f795fbd2e6fd33a1643147be2f2cf8870
7
- data.tar.gz: eb1eab78a9d50a63ea156b53c951e500944d5ef8ec622811dead87c0427198de9d979a31ec60b0de006d8c6409424d73b605ff7ddf997a88de55bd37eea3d3b1
6
+ metadata.gz: fc7d4287a7a94b1891d95d2c048e8ad3d5cd1cac9e7cae3047887316ed52224aa8f72a0c01eaba5d0ba1ebe1b4d78b48de42cc4a00114d5a8119fefe405f0e58
7
+ data.tar.gz: db472038a3c65f8643e792c987a837e1955c0699d3a2fd6297612e7f3bc8a8374f56751541663e9662f6da2dea0381a219d43b32b96eef30d6716d9662fdb540
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.52.0"
2
+ ".": "0.52.1"
3
3
  }
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; `response.content` keeps the raw text.
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
  | ------------------- | --------------------------------------------------------------- |
@@ -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
- parse_object(content) || outermost_object(content)
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
- parse_object(content[start..stop].to_s)
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.parse(json_string)
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
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.52.0" #: String
5
+ VERSION = "0.52.1" #: String
6
6
  end
@@ -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
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.52.0
4
+ version: 0.52.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall