riffer 0.44.0 → 0.46.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +24 -0
  4. data/docs/AGENTS.md +13 -0
  5. data/docs/AGENT_LIFECYCLE.md +39 -14
  6. data/docs/AGENT_LOOP.md +8 -9
  7. data/docs/CONFIGURATION.md +24 -17
  8. data/docs/GUARDRAILS.md +4 -4
  9. data/docs/MESSAGES.md +14 -11
  10. data/docs/STREAM_EVENTS.md +1 -1
  11. data/docs/TOOL_ADVANCED.md +2 -0
  12. data/docs/TRACING.md +4 -2
  13. data/docs/providers/CUSTOM_PROVIDERS.md +1 -1
  14. data/docs/providers/MOCK_PROVIDER.md +18 -11
  15. data/lib/riffer/agent/outcome.rb +52 -0
  16. data/lib/riffer/agent/response.rb +12 -33
  17. data/lib/riffer/agent/run.rb +54 -16
  18. data/lib/riffer/agent/session.rb +2 -0
  19. data/lib/riffer/messages/assistant.rb +24 -2
  20. data/lib/riffer/messages/base.rb +11 -19
  21. data/lib/riffer/providers/amazon_bedrock.rb +3 -0
  22. data/lib/riffer/providers/anthropic.rb +9 -0
  23. data/lib/riffer/providers/base.rb +1 -0
  24. data/lib/riffer/providers/finish_reason.rb +1 -1
  25. data/lib/riffer/providers/gemini.rb +10 -1
  26. data/lib/riffer/providers/open_ai.rb +30 -23
  27. data/lib/riffer/providers/open_router.rb +21 -7
  28. data/lib/riffer/runner/fibers.rb +12 -8
  29. data/lib/riffer/version.rb +1 -1
  30. data/sig/_private/async.rbs +4 -0
  31. data/sig/generated/riffer/agent/outcome.rbs +41 -0
  32. data/sig/generated/riffer/agent/response.rbs +11 -26
  33. data/sig/generated/riffer/agent/run.rbs +18 -7
  34. data/sig/generated/riffer/messages/assistant.rbs +14 -2
  35. data/sig/generated/riffer/providers/open_ai.rbs +8 -3
  36. data/sig/generated/riffer/providers/open_router.rbs +10 -2
  37. data/sig/generated/riffer/runner/fibers.rbs +2 -0
  38. metadata +3 -1
@@ -0,0 +1,41 @@
1
+ # Generated from lib/riffer/agent/outcome.rb with RBS::Inline
2
+
3
+ # How a run ended — the single place to read whether the agent completed
4
+ # normally and, if not, why. +detail+ carries the specifics when there are any:
5
+ # the tripwire reason, the interrupt reason, the provider's raw finish value,
6
+ # or the structured output parse/validation error.
7
+ #
8
+ # response = agent.generate("Analyze this")
9
+ # case response.outcome.reason
10
+ # when :completed then puts response.structured_output
11
+ # when :invalid_structured_output then warn response.outcome.detail
12
+ # end
13
+ class Riffer::Agent::Outcome
14
+ # Finish reasons that end a turn normally; every other finish reason means the
15
+ # provider cut the turn short and surfaces as the run's outcome verbatim.
16
+ NORMAL_FINISH_REASONS: Array[Symbol]
17
+
18
+ # Derived from the provider vocabulary so a new finish reason becomes an
19
+ # outcome without a second list to update.
20
+ PROVIDER_STOP_REASONS: Array[Symbol]
21
+
22
+ # The vocabulary every run ends in.
23
+ VALUES: Array[Symbol]
24
+
25
+ # Why the run ended.
26
+ attr_reader reason: Symbol
27
+
28
+ # Human-readable specifics for +reason+, when there are any.
29
+ attr_reader detail: String?
30
+
31
+ # Raises Riffer::ArgumentError when +reason+ is outside VALUES.
32
+ # --
33
+ # : (reason: Symbol, ?detail: String?) -> void
34
+ def initialize: (reason: Symbol, ?detail: String?) -> void
35
+
36
+ # Returns true when the run completed normally.
37
+ #
38
+ # --
39
+ # : () -> bool
40
+ def success?: () -> bool
41
+ end
@@ -1,29 +1,28 @@
1
1
  # Generated from lib/riffer/agent/response.rb with RBS::Inline
2
2
 
3
- # Wraps an agent generation response. When a guardrail blocks execution,
4
- # +content+ is empty and +tripwire+ carries the block details.
3
+ # Wraps an agent generation response. +outcome+ says how the run ended; when a
4
+ # guardrail blocks execution, +content+ is empty and +tripwire+ carries the
5
+ # block details.
5
6
  #
6
7
  # response = agent.generate("Hello")
7
- # if response.blocked?
8
- # puts "Blocked: #{response.tripwire.reason}"
9
- # else
8
+ # if response.outcome.success?
10
9
  # puts response.content
10
+ # else
11
+ # puts "#{response.outcome.reason}: #{response.outcome.detail}"
11
12
  # end
12
13
  class Riffer::Agent::Response
13
- @interrupted: bool
14
-
15
14
  # The response content.
16
15
  attr_reader content: String
17
16
 
17
+ # How the run ended.
18
+ attr_reader outcome: Riffer::Agent::Outcome
19
+
18
20
  # The tripwire if execution was blocked.
19
21
  attr_reader tripwire: Riffer::Guardrails::Tripwire?
20
22
 
21
23
  # The modifications made by guardrails during processing.
22
24
  attr_reader modifications: Array[Riffer::Guardrails::Modification]
23
25
 
24
- # The reason provided with the interrupt, if any.
25
- attr_reader interrupt_reason: (String | Symbol)?
26
-
27
26
  # The parsed structured output, if structured output was configured.
28
27
  attr_reader structured_output: Hash[Symbol, untyped]?
29
28
 
@@ -44,34 +43,20 @@ class Riffer::Agent::Response
44
43
  # --
45
44
  # : (
46
45
  # String,
46
+ # outcome: Riffer::Agent::Outcome,
47
47
  # ?tripwire: Riffer::Guardrails::Tripwire?,
48
48
  # ?modifications: Array[Riffer::Guardrails::Modification],
49
- # ?interrupted: bool,
50
- # ?interrupt_reason: (String | Symbol)?,
51
49
  # ?structured_output: Hash[Symbol, untyped]?,
52
50
  # ?messages: Array[Riffer::Messages::Base],
53
51
  # ?healed_tool_call_ids: Array[String],
54
52
  # ?token_usage: Riffer::Providers::TokenUsage?,
55
53
  # ?steps: Integer
56
54
  # ) -> void
57
- def initialize: (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> void
58
-
59
- # Returns true if the response was blocked by a guardrail.
60
- #
61
- # --
62
- # : () -> bool
63
- def blocked?: () -> bool
55
+ def initialize: (String, outcome: Riffer::Agent::Outcome, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> void
64
56
 
65
57
  # Returns true if any guardrail modified data during processing.
66
58
  #
67
59
  # --
68
60
  # : () -> bool
69
61
  def modified?: () -> bool
70
-
71
- # Returns true if the agent loop was interrupted by a callback
72
- # via <tt>throw :riffer_interrupt</tt>.
73
- #
74
- # --
75
- # : () -> bool
76
- def interrupted?: () -> bool
77
62
  end
@@ -45,8 +45,16 @@ module Riffer::Agent::Run
45
45
  def tripwire_response: (Riffer::Agent, Enumerator::Yielder?, Riffer::Guardrails::Tripwire, Array[Riffer::Guardrails::Modification], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> Riffer::Agent::Response
46
46
 
47
47
  # --
48
- # : (Riffer::Agent, Array[Riffer::Guardrails::Modification], **untyped) -> Riffer::Agent::Response
49
- def final_response: (Riffer::Agent, Array[Riffer::Guardrails::Modification], **untyped) -> Riffer::Agent::Response
48
+ # : (Riffer::Agent, Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, **untyped) -> Riffer::Agent::Response
49
+ def final_response: (Riffer::Agent, Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, **untyped) -> Riffer::Agent::Response
50
+
51
+ # Checked in the order things happened. The loop being stopped (max_steps or
52
+ # an interrupt) beats the provider's finish reason, which beats riffer's own
53
+ # validation of the content. A truncated response that also fails the schema
54
+ # therefore reports :length, not :invalid_structured_output.
55
+ # --
56
+ # : (Riffer::Messages::Assistant?, Riffer::Agent::StructuredOutput::Result?, interrupted: bool, interrupt_reason: (String | Symbol)?) -> Riffer::Agent::Outcome
57
+ def final_outcome: (Riffer::Messages::Assistant?, Riffer::Agent::StructuredOutput::Result?, interrupted: bool, interrupt_reason: (String | Symbol)?) -> Riffer::Agent::Outcome
50
58
 
51
59
  # --
52
60
  # : (Riffer::Agent, ?Hash[String, String]) -> Riffer::Messages::Assistant
@@ -77,8 +85,8 @@ module Riffer::Agent::Run
77
85
  def run_after_guardrails: (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], ?Hash[String, String]) { (Riffer::Guardrails::Tripwire) -> void } -> untyped
78
86
 
79
87
  # --
80
- # : (Riffer::Agent, Riffer::Messages::Assistant?) -> Hash[Symbol, untyped]?
81
- def validate_structured_output: (Riffer::Agent, Riffer::Messages::Assistant?) -> Hash[Symbol, untyped]?
88
+ # : (Riffer::Agent, Riffer::Messages::Assistant?) -> Riffer::Agent::StructuredOutput::Result?
89
+ def structured_output_result: (Riffer::Agent, Riffer::Messages::Assistant?) -> Riffer::Agent::StructuredOutput::Result?
82
90
 
83
91
  # --
84
92
  # : (Riffer::Agent) -> Array[singleton(Riffer::Tool)]
@@ -96,16 +104,15 @@ module Riffer::Agent::Run
96
104
  # : (
97
105
  # Riffer::Agent,
98
106
  # String,
107
+ # outcome: Riffer::Agent::Outcome,
99
108
  # ?tripwire: Riffer::Guardrails::Tripwire?,
100
109
  # ?modifications: Array[Riffer::Guardrails::Modification],
101
- # ?interrupted: bool,
102
- # ?interrupt_reason: (String | Symbol)?,
103
110
  # ?structured_output: Hash[Symbol, untyped]?,
104
111
  # ?healed_tool_call_ids: Array[String],
105
112
  # ?token_usage: Riffer::Providers::TokenUsage?,
106
113
  # ?steps: Integer
107
114
  # ) -> Riffer::Agent::Response
108
- def build_response: (Riffer::Agent, String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?healed_tool_call_ids: Array[String], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> Riffer::Agent::Response
115
+ def build_response: (Riffer::Agent, String, outcome: Riffer::Agent::Outcome, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?structured_output: Hash[Symbol, untyped]?, ?healed_tool_call_ids: Array[String], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> Riffer::Agent::Response
109
116
 
110
117
  # Raises when +files+ are supplied without a +prompt+ — the provider needs
111
118
  # text to anchor the attachments.
@@ -136,4 +143,8 @@ module Riffer::Agent::Run
136
143
  # --
137
144
  # : (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Agent::Response) -> void
138
145
  def record_run_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Agent::Response) -> void
146
+
147
+ # --
148
+ # : (Riffer::Agent::Outcome) -> String?
149
+ def interrupt_reason_attribute: (Riffer::Agent::Outcome) -> String?
139
150
  end
@@ -27,11 +27,23 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
27
27
  # <tt>Riffer::Providers::FinishReason::VALUES</tt>).
28
28
  attr_reader finish_reason: Symbol?
29
29
 
30
+ # The provider's raw finish-reason value behind +finish_reason+, when one
31
+ # exists on the wire.
32
+ attr_reader finish_reason_raw: String?
33
+
30
34
  # Raises Riffer::ArgumentError when +finish_reason+ is outside the
31
35
  # normalized vocabulary.
32
36
  # --
33
- # : (String, ?id: String?, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?token_usage: Riffer::Providers::TokenUsage?, ?structured_output: Hash[Symbol, untyped]?, ?finish_reason: Symbol?) -> void
34
- def initialize: (String, ?id: String?, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?token_usage: Riffer::Providers::TokenUsage?, ?structured_output: Hash[Symbol, untyped]?, ?finish_reason: Symbol?) -> void
37
+ # : (
38
+ # String,
39
+ # ?id: String?,
40
+ # ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall],
41
+ # ?token_usage: Riffer::Providers::TokenUsage?,
42
+ # ?structured_output: Hash[Symbol, untyped]?,
43
+ # ?finish_reason: Symbol?,
44
+ # ?finish_reason_raw: String?
45
+ # ) -> void
46
+ def initialize: (String, ?id: String?, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?token_usage: Riffer::Providers::TokenUsage?, ?structured_output: Hash[Symbol, untyped]?, ?finish_reason: Symbol?, ?finish_reason_raw: String?) -> void
35
47
 
36
48
  # --
37
49
  # : () -> Symbol
@@ -4,6 +4,11 @@
4
4
  class Riffer::Providers::OpenAI < Riffer::Providers::Base
5
5
  WEB_SEARCH_TOOL_TYPE: String
6
6
 
7
+ # The Responses API has no finish_reason field. The response +status+ is
8
+ # the primary signal; an +incomplete+ status is only meaningful together
9
+ # with <tt>incomplete_details.reason</tt>, so that branch nests one level.
10
+ FINISH_REASONS: Hash[String, Symbol | Hash[String, Symbol]]
11
+
7
12
  # The GenAI semconv well-known provider name.
8
13
  # --
9
14
  # : () -> String
@@ -50,14 +55,14 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
50
55
  # : (untyped) -> Riffer::Providers::FinishReason?
51
56
  def extract_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
52
57
 
53
- # The Responses API reports no finish_reason field, so one is derived.
54
58
  # --
55
59
  # : (untyped) -> Riffer::Providers::FinishReason?
56
60
  def build_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
57
61
 
62
+ # The nested field that names the cause behind an ambiguous status.
58
63
  # --
59
- # : (untyped) -> Riffer::Providers::FinishReason
60
- def incomplete_finish_reason: (untyped) -> Riffer::Providers::FinishReason
64
+ # : (untyped, String) -> String?
65
+ def finish_detail: (untyped, String) -> String?
61
66
 
62
67
  # --
63
68
  # : (untyped) -> String
@@ -57,9 +57,17 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
57
57
  # : (untyped) -> Riffer::Providers::FinishReason?
58
58
  def extract_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
59
59
 
60
+ # +native+ is the upstream model's own finish reason, which OpenRouter
61
+ # reports alongside its normalized one; it wins as +raw+ when present.
60
62
  # --
61
- # : (untyped) -> Riffer::Providers::FinishReason?
62
- def build_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
63
+ # : (untyped, ?native: untyped) -> Riffer::Providers::FinishReason?
64
+ def build_finish_reason: (untyped, ?native: untyped) -> Riffer::Providers::FinishReason?
65
+
66
+ # +native_finish_reason+ is outside the OpenAI schema, so it is only
67
+ # reachable through the SDK model's raw data hash.
68
+ # --
69
+ # : (untyped) -> untyped
70
+ def native_finish_reason: (untyped) -> untyped
63
71
 
64
72
  # --
65
73
  # : (untyped) -> String
@@ -4,6 +4,8 @@
4
4
  # +max_concurrency+ caps simultaneous fibers via an <tt>Async::Semaphore</tt>.
5
5
  # If multiple fibers raise, only the first exception is re-raised after all
6
6
  # finish.
7
+ # Joins the current reactor task when one is already running, and otherwise
8
+ # starts its own.
7
9
  class Riffer::Runner::Fibers < Riffer::Runner
8
10
  @max_concurrency: Integer?
9
11
 
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.44.0
4
+ version: 0.46.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall
@@ -110,6 +110,7 @@ files:
110
110
  - lib/riffer/agent.rb
111
111
  - lib/riffer/agent/config.rb
112
112
  - lib/riffer/agent/context.rb
113
+ - lib/riffer/agent/outcome.rb
113
114
  - lib/riffer/agent/response.rb
114
115
  - lib/riffer/agent/run.rb
115
116
  - lib/riffer/agent/serializer.rb
@@ -239,6 +240,7 @@ files:
239
240
  - sig/generated/riffer/agent.rbs
240
241
  - sig/generated/riffer/agent/config.rbs
241
242
  - sig/generated/riffer/agent/context.rbs
243
+ - sig/generated/riffer/agent/outcome.rbs
242
244
  - sig/generated/riffer/agent/response.rbs
243
245
  - sig/generated/riffer/agent/run.rbs
244
246
  - sig/generated/riffer/agent/serializer.rbs