riffer 0.31.0 → 0.32.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.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/code-style.md +63 -4
  3. data/.agents/rbs-inline.md +1 -6
  4. data/.release-please-manifest.json +1 -1
  5. data/AGENTS.md +1 -2
  6. data/CHANGELOG.md +25 -0
  7. data/docs/08_MESSAGES.md +1 -1
  8. data/docs/13_SKILLS.md +6 -2
  9. data/docs/14_MCP.md +50 -5
  10. data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
  11. data/lib/riffer/agent/config.rb +42 -47
  12. data/lib/riffer/agent/context.rb +70 -50
  13. data/lib/riffer/agent/response.rb +4 -20
  14. data/lib/riffer/agent/run.rb +28 -67
  15. data/lib/riffer/agent/serializer.rb +22 -81
  16. data/lib/riffer/agent/session/repair.rb +14 -40
  17. data/lib/riffer/agent/session.rb +25 -67
  18. data/lib/riffer/agent/structured_output/result.rb +3 -11
  19. data/lib/riffer/agent/structured_output.rb +5 -13
  20. data/lib/riffer/agent.rb +78 -196
  21. data/lib/riffer/config.rb +34 -101
  22. data/lib/riffer/evals/evaluator.rb +7 -27
  23. data/lib/riffer/evals/evaluator_runner.rb +11 -19
  24. data/lib/riffer/evals/judge.rb +4 -25
  25. data/lib/riffer/evals/result.rb +1 -18
  26. data/lib/riffer/evals/run_result.rb +0 -11
  27. data/lib/riffer/evals/scenario_result.rb +0 -14
  28. data/lib/riffer/evals.rb +0 -6
  29. data/lib/riffer/guardrail.rb +4 -27
  30. data/lib/riffer/guardrails/modification.rb +0 -10
  31. data/lib/riffer/guardrails/result.rb +3 -30
  32. data/lib/riffer/guardrails/runner.rb +5 -22
  33. data/lib/riffer/guardrails/tripwire.rb +1 -19
  34. data/lib/riffer/guardrails.rb +2 -4
  35. data/lib/riffer/helpers/call_or_value.rb +4 -3
  36. data/lib/riffer/helpers/class_name_converter.rb +3 -1
  37. data/lib/riffer/helpers/dependencies.rb +5 -7
  38. data/lib/riffer/helpers.rb +0 -5
  39. data/lib/riffer/mcp/authenticated_tool.rb +9 -9
  40. data/lib/riffer/mcp/client.rb +12 -17
  41. data/lib/riffer/mcp/manifest.rb +13 -10
  42. data/lib/riffer/mcp/registration.rb +2 -11
  43. data/lib/riffer/mcp/registry.rb +44 -52
  44. data/lib/riffer/mcp/search_tool.rb +53 -0
  45. data/lib/riffer/mcp/tool_factory.rb +13 -18
  46. data/lib/riffer/mcp.rb +12 -17
  47. data/lib/riffer/messages/assistant.rb +2 -9
  48. data/lib/riffer/messages/base.rb +46 -16
  49. data/lib/riffer/messages/file_part.rb +32 -24
  50. data/lib/riffer/messages/system.rb +0 -5
  51. data/lib/riffer/messages/tool.rb +0 -10
  52. data/lib/riffer/messages/user.rb +0 -10
  53. data/lib/riffer/messages.rb +0 -7
  54. data/lib/riffer/params/boolean.rb +2 -4
  55. data/lib/riffer/params/param.rb +28 -39
  56. data/lib/riffer/params.rb +9 -21
  57. data/lib/riffer/providers/amazon_bedrock.rb +42 -28
  58. data/lib/riffer/providers/anthropic.rb +4 -9
  59. data/lib/riffer/providers/azure_open_ai.rb +3 -19
  60. data/lib/riffer/providers/base.rb +13 -26
  61. data/lib/riffer/providers/gemini.rb +4 -4
  62. data/lib/riffer/providers/mock.rb +6 -26
  63. data/lib/riffer/providers/open_ai.rb +6 -8
  64. data/lib/riffer/providers/open_router.rb +4 -10
  65. data/lib/riffer/providers/repository.rb +4 -3
  66. data/lib/riffer/providers/token_usage.rb +9 -20
  67. data/lib/riffer/providers.rb +0 -8
  68. data/lib/riffer/runner/fibers.rb +10 -16
  69. data/lib/riffer/runner/sequential.rb +1 -4
  70. data/lib/riffer/runner/threaded.rb +3 -14
  71. data/lib/riffer/runner.rb +2 -15
  72. data/lib/riffer/skills/activate_tool.rb +3 -11
  73. data/lib/riffer/skills/adapter.rb +4 -22
  74. data/lib/riffer/skills/backend.rb +7 -21
  75. data/lib/riffer/skills/config.rb +10 -31
  76. data/lib/riffer/skills/context.rb +21 -21
  77. data/lib/riffer/skills/filesystem_backend.rb +7 -25
  78. data/lib/riffer/skills/frontmatter.rb +22 -32
  79. data/lib/riffer/skills/markdown_adapter.rb +2 -9
  80. data/lib/riffer/skills/xml_adapter.rb +2 -8
  81. data/lib/riffer/stream_events/base.rb +1 -6
  82. data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
  83. data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
  84. data/lib/riffer/stream_events/interrupt.rb +4 -7
  85. data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
  86. data/lib/riffer/stream_events/reasoning_done.rb +2 -4
  87. data/lib/riffer/stream_events/skill_activation.rb +2 -4
  88. data/lib/riffer/stream_events/text_delta.rb +0 -2
  89. data/lib/riffer/stream_events/text_done.rb +1 -3
  90. data/lib/riffer/stream_events/token_usage_done.rb +1 -8
  91. data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
  92. data/lib/riffer/stream_events/tool_call_done.rb +1 -3
  93. data/lib/riffer/stream_events/web_search_done.rb +1 -3
  94. data/lib/riffer/stream_events/web_search_status.rb +2 -3
  95. data/lib/riffer/stream_events.rb +0 -10
  96. data/lib/riffer/tool.rb +6 -13
  97. data/lib/riffer/tools/response.rb +8 -4
  98. data/lib/riffer/tools/runtime/fibers.rb +0 -3
  99. data/lib/riffer/tools/runtime/inline.rb +1 -4
  100. data/lib/riffer/tools/runtime/threaded.rb +0 -2
  101. data/lib/riffer/tools/runtime.rb +5 -38
  102. data/lib/riffer/tools/toolable.rb +5 -16
  103. data/lib/riffer/tools.rb +0 -4
  104. data/lib/riffer/version.rb +1 -1
  105. data/lib/riffer.rb +7 -8
  106. data/sig/generated/riffer/agent/config.rbs +29 -46
  107. data/sig/generated/riffer/agent/context.rbs +40 -48
  108. data/sig/generated/riffer/agent/response.rbs +4 -20
  109. data/sig/generated/riffer/agent/run.rbs +12 -61
  110. data/sig/generated/riffer/agent/serializer.rbs +21 -80
  111. data/sig/generated/riffer/agent/session/repair.rbs +12 -40
  112. data/sig/generated/riffer/agent/session.rbs +25 -67
  113. data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
  114. data/sig/generated/riffer/agent/structured_output.rbs +5 -12
  115. data/sig/generated/riffer/agent.rbs +57 -186
  116. data/sig/generated/riffer/config.rbs +34 -100
  117. data/sig/generated/riffer/evals/evaluator.rbs +7 -27
  118. data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
  119. data/sig/generated/riffer/evals/judge.rbs +4 -24
  120. data/sig/generated/riffer/evals/result.rbs +1 -17
  121. data/sig/generated/riffer/evals/run_result.rbs +0 -10
  122. data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
  123. data/sig/generated/riffer/evals.rbs +0 -6
  124. data/sig/generated/riffer/guardrail.rbs +4 -27
  125. data/sig/generated/riffer/guardrails/modification.rbs +0 -10
  126. data/sig/generated/riffer/guardrails/result.rbs +3 -30
  127. data/sig/generated/riffer/guardrails/runner.rbs +5 -22
  128. data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
  129. data/sig/generated/riffer/guardrails.rbs +2 -4
  130. data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
  131. data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
  132. data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
  133. data/sig/generated/riffer/helpers.rbs +0 -5
  134. data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
  135. data/sig/generated/riffer/mcp/client.rbs +10 -16
  136. data/sig/generated/riffer/mcp/manifest.rbs +9 -9
  137. data/sig/generated/riffer/mcp/registration.rbs +2 -10
  138. data/sig/generated/riffer/mcp/registry.rbs +11 -18
  139. data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
  140. data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
  141. data/sig/generated/riffer/mcp.rbs +10 -17
  142. data/sig/generated/riffer/messages/assistant.rbs +2 -8
  143. data/sig/generated/riffer/messages/base.rbs +11 -16
  144. data/sig/generated/riffer/messages/file_part.rbs +13 -23
  145. data/sig/generated/riffer/messages/system.rbs +0 -4
  146. data/sig/generated/riffer/messages/tool.rbs +0 -9
  147. data/sig/generated/riffer/messages/user.rbs +0 -9
  148. data/sig/generated/riffer/messages.rbs +0 -7
  149. data/sig/generated/riffer/params/boolean.rbs +2 -4
  150. data/sig/generated/riffer/params/param.rbs +21 -39
  151. data/sig/generated/riffer/params.rbs +9 -21
  152. data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
  153. data/sig/generated/riffer/providers/anthropic.rbs +2 -7
  154. data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
  155. data/sig/generated/riffer/providers/base.rbs +9 -25
  156. data/sig/generated/riffer/providers/gemini.rbs +0 -2
  157. data/sig/generated/riffer/providers/mock.rbs +6 -26
  158. data/sig/generated/riffer/providers/open_ai.rbs +1 -5
  159. data/sig/generated/riffer/providers/open_router.rbs +4 -10
  160. data/sig/generated/riffer/providers/repository.rbs +2 -3
  161. data/sig/generated/riffer/providers/token_usage.rbs +6 -16
  162. data/sig/generated/riffer/providers.rbs +0 -8
  163. data/sig/generated/riffer/runner/fibers.rbs +8 -15
  164. data/sig/generated/riffer/runner/sequential.rbs +1 -3
  165. data/sig/generated/riffer/runner/threaded.rbs +3 -13
  166. data/sig/generated/riffer/runner.rbs +2 -14
  167. data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
  168. data/sig/generated/riffer/skills/adapter.rbs +4 -22
  169. data/sig/generated/riffer/skills/backend.rbs +7 -21
  170. data/sig/generated/riffer/skills/config.rbs +10 -31
  171. data/sig/generated/riffer/skills/context.rbs +15 -20
  172. data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
  173. data/sig/generated/riffer/skills/frontmatter.rbs +19 -29
  174. data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
  175. data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
  176. data/sig/generated/riffer/stream_events/base.rbs +1 -6
  177. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
  178. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
  179. data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
  180. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
  181. data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
  182. data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
  183. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  184. data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
  185. data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
  186. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
  187. data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
  188. data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
  189. data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
  190. data/sig/generated/riffer/stream_events.rbs +0 -10
  191. data/sig/generated/riffer/tool.rbs +5 -12
  192. data/sig/generated/riffer/tools/response.rbs +6 -4
  193. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
  194. data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
  195. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
  196. data/sig/generated/riffer/tools/runtime.rbs +5 -37
  197. data/sig/generated/riffer/tools/toolable.rbs +4 -14
  198. data/sig/generated/riffer/tools.rbs +0 -4
  199. data/sig/generated/riffer.rbs +5 -4
  200. data/sig/manual/riffer/agent/session/repair.rbs +5 -0
  201. data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
  202. data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
  203. data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
  204. data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
  205. data/sig/manual/riffer/mcp/registry.rbs +5 -0
  206. data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
  207. data/sig/manual/riffer/mcp.rbs +5 -0
  208. data/sig/manual/riffer/providers/repository.rbs +5 -0
  209. data/sig/manual/riffer.rbs +5 -0
  210. metadata +17 -9
  211. data/.agents/rdoc.md +0 -69
  212. data/lib/riffer/messages/converter.rb +0 -90
  213. data/sig/generated/riffer/messages/converter.rbs +0 -33
  214. data/sig/manual/riffer/tools/toolable.rbs +0 -6
@@ -3,10 +3,49 @@
3
3
 
4
4
  require "securerandom"
5
5
 
6
- # Base class for all message types in the Riffer framework.
7
- #
8
- # Subclasses must implement the +role+ method.
6
+ # Base class for all message types. Subclasses must implement +role+.
9
7
  class Riffer::Messages::Base
8
+ # Builds the matching message subclass from a hash, or returns +msg+ unchanged
9
+ # when it is already a message. Raises Riffer::ArgumentError on an invalid message.
10
+ #--
11
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::Base)) -> Riffer::Messages::Base
12
+ def self.from_hash(msg)
13
+ return msg if msg.is_a?(Riffer::Messages::Base)
14
+
15
+ unless msg.is_a?(Hash)
16
+ raise Riffer::ArgumentError, "Message must be a Hash or Message object, got #{msg.class}"
17
+ end
18
+
19
+ role = msg[:role]
20
+ content = msg[:content]
21
+
22
+ if role.nil? || role.empty?
23
+ raise Riffer::ArgumentError, "Message hash must include a 'role' key"
24
+ end
25
+
26
+ id = msg[:id]
27
+
28
+ case role.to_sym
29
+ when :user
30
+ files = (msg[:files] || []).map { |f| Riffer::Messages::FilePart.from_hash(f) }
31
+ Riffer::Messages::User.new(content, id: id, files: files)
32
+ when :assistant
33
+ tool_calls = (msg[:tool_calls] || []).map { |tc|
34
+ tc.is_a?(Riffer::Messages::Assistant::ToolCall) ? tc : Riffer::Messages::Assistant::ToolCall.new(**tc)
35
+ }
36
+ structured_output = msg[:structured_output]
37
+ Riffer::Messages::Assistant.new(content, id: id, tool_calls: tool_calls, structured_output: structured_output)
38
+ when :system
39
+ Riffer::Messages::System.new(content, id: id)
40
+ when :tool
41
+ tool_call_id = msg[:tool_call_id]
42
+ name = msg[:name]
43
+ Riffer::Messages::Tool.new(content, id: id, tool_call_id: tool_call_id, name: name)
44
+ else
45
+ raise Riffer::ArgumentError, "Unknown message role: #{role}"
46
+ end
47
+ end
48
+
10
49
  # The message content.
11
50
  attr_reader :content #: String
12
51
 
@@ -31,31 +70,22 @@ class Riffer::Messages::Base
31
70
  end
32
71
 
33
72
  # Returns the message role.
34
- #
35
- # Raises NotImplementedError if not implemented by subclass.
36
- #
37
73
  #--
38
74
  #: () -> Symbol
39
75
  def role
40
76
  raise NotImplementedError, "Subclasses must implement #role"
41
77
  end
42
78
 
43
- # Whether this message carries pending tool calls. Defaults to +false+;
44
- # +Riffer::Messages::Assistant+ overrides this when its +tool_calls+
45
- # array is non-empty.
46
- #
79
+ # Whether this message carries pending tool calls (overridden by
80
+ # +Riffer::Messages::Assistant+).
47
81
  #--
48
82
  #: () -> bool
49
83
  def has_tool_calls?
50
84
  false
51
85
  end
52
86
 
53
- # Merges another same-role message into this one.
54
- #
55
- # Raises NotImplementedError unless implemented by subclass. Mergeable
56
- # message types (+User+, +Assistant+, +System+) override this; +Tool+
57
- # messages are never merged.
58
- #
87
+ # Merges another same-role message into this one. +Tool+ messages are never
88
+ # merged.
59
89
  #--
60
90
  #: (untyped) -> Riffer::Messages::Base
61
91
  def +(other)
@@ -4,16 +4,8 @@
4
4
  require "base64"
5
5
  require "uri"
6
6
 
7
- # Represents a file attachment (image or document) in a conversation.
8
- #
9
- # Supports two input sources:
10
- # - URLs (stored and passed to providers that support them via +from_url+)
11
- # - Raw base64 data (via +new+)
12
- #
13
- # file = Riffer::Messages::FilePart.from_url("https://example.com/doc.pdf", media_type: "application/pdf")
14
- # file.url? # => true
15
- # file.document? # => true
16
- #
7
+ # Represents a file attachment (image or document) from a URL (+from_url+) or
8
+ # raw base64 data (+new+).
17
9
  class Riffer::Messages::FilePart
18
10
  # @rbs @url_string: String?
19
11
 
@@ -38,13 +30,8 @@ class Riffer::Messages::FilePart
38
30
  # The filename, if available.
39
31
  attr_reader :filename #: String?
40
32
 
41
- # Creates a FilePart from raw base64 data or a URL.
42
- #
43
- # At least one of +data+ or +url+ must be provided.
44
- #
45
- # Raises Riffer::ArgumentError if neither data nor url is provided,
46
- # or if media_type is not supported.
47
- #
33
+ # Raises Riffer::ArgumentError unless +data+ or +url+ is given and
34
+ # +media_type+ is supported.
48
35
  #--
49
36
  #: (media_type: String, ?data: String?, ?filename: String?, ?url: String?) -> void
50
37
  def initialize(media_type:, data: nil, filename: nil, url: nil)
@@ -57,13 +44,8 @@ class Riffer::Messages::FilePart
57
44
  @url_string = url
58
45
  end
59
46
 
60
- # Creates a FilePart from a URL.
61
- #
62
- # The URL is stored and passed directly to providers that support URL sources.
63
- # If +media_type+ is not provided, it is detected from the URL path extension.
64
- #
65
- # Raises Riffer::ArgumentError if media_type cannot be detected.
66
- #
47
+ # Creates a FilePart from a URL, detecting +media_type+ from the path
48
+ # extension when omitted. Raises Riffer::ArgumentError if it can't be detected.
67
49
  #--
68
50
  #: (String, ?media_type: String?) -> Riffer::Messages::FilePart
69
51
  def self.from_url(url, media_type: nil)
@@ -76,6 +58,32 @@ class Riffer::Messages::FilePart
76
58
  new(url: url, media_type: media_type)
77
59
  end
78
60
 
61
+ # Builds a FilePart from a +{url:, media_type:}+ or +{data:, media_type:}+ hash,
62
+ # or returns +file+ unchanged when it is already a FilePart. Raises
63
+ # Riffer::ArgumentError on an invalid hash.
64
+ #--
65
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::FilePart)) -> Riffer::Messages::FilePart
66
+ def self.from_hash(file)
67
+ return file if file.is_a?(Riffer::Messages::FilePart)
68
+
69
+ unless file.is_a?(Hash)
70
+ raise Riffer::ArgumentError, "File must be a Hash or FilePart object, got #{file.class}"
71
+ end
72
+
73
+ url = file[:url]
74
+ data = file[:data]
75
+ media_type = file[:media_type]
76
+ filename = file[:filename]
77
+
78
+ if url
79
+ from_url(url, media_type: media_type)
80
+ elsif data && media_type
81
+ new(data: data, media_type: media_type, filename: filename)
82
+ else
83
+ raise Riffer::ArgumentError, "File hash must include :url or :data with :media_type"
84
+ end
85
+ end
86
+
79
87
  # Returns the base64-encoded data, or nil for URL-only sources.
80
88
  attr_reader :data #: String?
81
89
 
@@ -2,11 +2,6 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  # Represents a system message (instructions) in a conversation.
5
- #
6
- # msg = Riffer::Messages::System.new("You are a helpful assistant.")
7
- # msg.role # => :system
8
- # msg.content # => "You are a helpful assistant."
9
- #
10
5
  class Riffer::Messages::System < Riffer::Messages::Base
11
6
  #--
12
7
  #: () -> Symbol
@@ -2,16 +2,6 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  # Represents a tool execution result in a conversation.
5
- #
6
- # msg = Riffer::Messages::Tool.new(
7
- # "The weather is sunny.",
8
- # tool_call_id: "call_123",
9
- # name: "weather_tool"
10
- # )
11
- # msg.role # => :tool
12
- # msg.tool_call_id # => "call_123"
13
- # msg.error? # => false
14
- #
15
5
  class Riffer::Messages::Tool < Riffer::Messages::Base
16
6
  # The ID of the tool call this result responds to.
17
7
  attr_reader :tool_call_id #: String
@@ -2,20 +2,10 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  # Represents a user message in a conversation.
5
- #
6
- # msg = Riffer::Messages::User.new("Hello!")
7
- # msg.role # => :user
8
- # msg.content # => "Hello!"
9
- #
10
- # msg = Riffer::Messages::User.new("Describe this image", files: [file_part])
11
- # msg.files # => [#<Riffer::Messages::FilePart ...>]
12
- #
13
5
  class Riffer::Messages::User < Riffer::Messages::Base
14
6
  # File attachments for this message.
15
7
  attr_reader :files #: Array[Riffer::Messages::FilePart]
16
8
 
17
- # Initializes a user message.
18
- #
19
9
  #--
20
10
  #: (String, ?id: String?, ?files: Array[Riffer::Messages::FilePart]) -> void
21
11
  def initialize(content, id: nil, files: [])
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Namespace for message types in the Riffer framework.
5
- #
6
- # Message objects represent the conversation between users and the assistant:
7
- # - Riffer::Messages::System - System instructions
8
- # - Riffer::Messages::User - User input
9
- # - Riffer::Messages::Assistant - LLM responses
10
- # - Riffer::Messages::Tool - Tool execution results
11
4
  module Riffer::Messages
12
5
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Riffer::Params::Boolean is a sentinel type for declaring boolean parameters.
5
- #
6
- # Ruby has no +Boolean+ class (+true+ is +TrueClass+, +false+ is +FalseClass+).
7
- # Use this module wherever you need a single type that means "boolean":
4
+ # Sentinel type for declaring boolean parameters — Ruby has no +Boolean+ class
5
+ # (+true+/+false+ are +TrueClass+/+FalseClass+).
8
6
  #
9
7
  # required :verbose, Riffer::Params::Boolean
10
8
  #
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Riffer::Params::Param represents a single parameter definition.
5
- #
6
- # Handles type validation and JSON Schema generation for individual parameters.
4
+ # A single parameter definition, handling type validation and JSON Schema
5
+ # generation.
7
6
  class Riffer::Params::Param
8
7
  # Maps Ruby types to JSON Schema type strings
9
8
  TYPE_MAPPINGS = {
@@ -20,9 +19,8 @@ class Riffer::Params::Param
20
19
  # Primitive types allowed for the <tt>of:</tt> keyword on Array params
21
20
  PRIMITIVE_TYPES = (TYPE_MAPPINGS.keys - [Array, Hash]).freeze #: Array[Module]
22
21
 
23
- # Maps JSON Schema type strings back to Ruby types. The inverse of
24
- # TYPE_MAPPINGS, collapsing the three boolean spellings onto
25
- # Riffer::Params::Boolean. Used by +from_json_schema+.
22
+ # Maps JSON Schema type strings back to Ruby types (inverse of TYPE_MAPPINGS),
23
+ # collapsing the three boolean spellings onto Riffer::Params::Boolean.
26
24
  JSON_TYPE_MAPPINGS = {
27
25
  "string" => String,
28
26
  "integer" => Integer,
@@ -32,24 +30,32 @@ class Riffer::Params::Param
32
30
  "object" => Hash
33
31
  }.freeze #: Hash[String, Module]
34
32
 
33
+ # The parameter name.
35
34
  attr_reader :name #: Symbol
35
+
36
+ # The Ruby type.
36
37
  attr_reader :type #: Module
38
+
39
+ # Whether the parameter is required.
37
40
  attr_reader :required #: bool
41
+
42
+ # The parameter description, if any.
38
43
  attr_reader :description #: String?
44
+
45
+ # Allowed values, if constrained.
39
46
  attr_reader :enum #: Array[untyped]?
47
+
48
+ # The default value, if any.
40
49
  attr_reader :default #: untyped
50
+
51
+ # Element type for a typed array (+of:+).
41
52
  attr_reader :item_type #: Module?
53
+
54
+ # Nested Params for object / array-of-object types.
42
55
  attr_reader :nested_params #: Riffer::Params?
43
56
 
44
- #--
45
- # Reconstructs a Param from a single JSON Schema property.
46
- #
47
- # [name] the parameter name (Symbol).
48
- # [schema] the property's JSON Schema (Symbol-keyed).
49
- # [required] whether the property appeared in the parent's +required+ list.
50
- #
51
- # Raises Riffer::ArgumentError on a type outside the Params-expressible subset.
52
- #
57
+ # Reconstructs a Param from a single JSON Schema property. Raises
58
+ # Riffer::ArgumentError on a type outside the Params-expressible subset.
53
59
  #--
54
60
  #: (Symbol, Hash[Symbol, untyped], required: bool) -> Riffer::Params::Param
55
61
  def self.from_json_schema(name, schema, required:)
@@ -68,10 +74,6 @@ class Riffer::Params::Param
68
74
  )
69
75
  end
70
76
 
71
- # Resolves the +[item_type, nested_params]+ pair for a reconstructed Param:
72
- # a nested Params for object / array-of-object schemas, an +item_type+ for
73
- # typed primitive arrays, and +nil+ for everything else.
74
- #
75
77
  #--
76
78
  #: (Module, Hash[Symbol, untyped]) -> [Module?, Riffer::Params?]
77
79
  def self.resolve_nesting(ruby_type, schema)
@@ -86,12 +88,9 @@ class Riffer::Params::Param
86
88
  end
87
89
  private_class_method :resolve_nesting
88
90
 
89
- # Resolves a JSON Schema +type+ (a String, or a <tt>[type, "null"]</tt>
90
- # union) back to its Ruby type. Returns a Module because
91
- # Riffer::Params::Boolean is a Module, not a Class — the same widening the
92
- # +type+ attribute uses. Raises Riffer::ArgumentError on a type outside the
93
- # Params-expressible subset (the block runs only for an unmapped type).
94
- #
91
+ # Resolves a JSON Schema +type+ (or a <tt>[type, "null"]</tt> union) to its
92
+ # Ruby type. Returns a Module — Riffer::Params::Boolean is a Module, not a
93
+ # Class. Raises Riffer::ArgumentError on an unsupported type.
95
94
  #--
96
95
  #: (untyped) -> Module
97
96
  def self.json_type_to_ruby(type)
@@ -135,21 +134,11 @@ class Riffer::Params::Param
135
134
  TYPE_MAPPINGS[type] || type.to_s.downcase
136
135
  end
137
136
 
138
- # Converts this parameter to JSON Schema format.
139
- #
140
- # When +strict+ is true, optional parameters are made nullable
141
- # (<tt>["type", "null"]</tt>) so that strict mode providers can distinguish
142
- # "absent" from "present" without rejecting the schema.
143
- #
144
- # Optional parameters with an +enum+ use +anyOf+ to separate the enum
145
- # constraint from the null type, since providers like Anthropic reject
137
+ # Converts this parameter to JSON Schema format. When +strict+, optional
138
+ # params are made nullable (<tt>["type", "null"]</tt>) so strict providers
139
+ # distinguish absent from present; optional params with an +enum+ use +anyOf+
140
+ # instead, since providers like Anthropic reject
146
141
  # <tt>{"type": ["string", "null"], "enum": [...]}</tt>.
147
- #
148
- # In non-strict mode a +default+ is emitted when set (a standard JSON
149
- # Schema keyword), making the schema a lossless source for
150
- # +Riffer::Params.from_json_schema+. Strict mode omits it, since strict
151
- # providers reject the keyword.
152
- #
153
142
  #--
154
143
  #: (?strict: bool) -> Hash[Symbol, untyped]
155
144
  def to_json_schema(strict: false)
data/lib/riffer/params.rb CHANGED
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Riffer::Params provides a DSL for defining parameters.
5
- #
6
- # Used within a Tool's +params+ block to define required and optional parameters,
7
- # and by StructuredOutput to define response schemas.
4
+ # A DSL for defining tool parameters and structured-output schemas, used within
5
+ # a Tool's +params+ block.
8
6
  #
9
7
  # params do
10
8
  # required :city, String, description: "The city name"
@@ -12,6 +10,7 @@
12
10
  # end
13
11
  #
14
12
  class Riffer::Params
13
+ # The defined parameters.
15
14
  attr_reader :parameters #: Array[Riffer::Params::Param]
16
15
 
17
16
  #--
@@ -20,17 +19,9 @@ class Riffer::Params
20
19
  @parameters = []
21
20
  end
22
21
 
23
- # Reconstructs a Params from a JSON Schema object (the inverse of
24
- # +to_json_schema(strict: false)+).
25
- #
26
- # Accepts the Symbol-keyed object schema produced by +to_json_schema+
27
- # (property-name keys may be String or Symbol — both are normalized).
28
- # Reconstructs types, +required+, +description+, +enum+, +default+,
29
- # typed-array +item_type+, and nested object/array Params recursively.
30
- #
31
- # Round-trips losslessly with +to_json_schema(strict: false)+ over the
32
- # Params-expressible subset of JSON Schema. Raises Riffer::ArgumentError
33
- # on a schema using features outside that subset.
22
+ # Reconstructs a Params from a JSON Schema object the inverse of
23
+ # +to_json_schema(strict: false)+. Raises Riffer::ArgumentError on features
24
+ # outside the Params-expressible subset of JSON Schema.
34
25
  #
35
26
  # schema = params.to_json_schema(strict: false)
36
27
  # Riffer::Params.from_json_schema(schema) # => equivalent Riffer::Params
@@ -129,12 +120,9 @@ class Riffer::Params
129
120
  validated
130
121
  end
131
122
 
132
- # Converts all parameters to JSON Schema format.
133
- #
134
- # When +strict+ is true, every property appears in +required+ and
135
- # optional properties are made nullable instead. This satisfies
136
- # providers that enforce strict structured output schemas.
137
- #
123
+ # Converts all parameters to JSON Schema format. When +strict+ is true, every
124
+ # property is listed in +required+ and optional ones are made nullable
125
+ # instead, satisfying providers that enforce strict structured output schemas.
138
126
  #--
139
127
  #: (?strict: bool) -> Hash[Symbol, untyped]
140
128
  def to_json_schema(strict: false)
@@ -3,23 +3,15 @@
3
3
 
4
4
  require "base64"
5
5
 
6
- # Amazon Bedrock provider for Claude and other foundation models.
7
- #
8
- # Requires the +aws-sdk-bedrockruntime+ gem to be installed.
9
- #
10
- # See https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/BedrockRuntime/Client.html
6
+ # Amazon Bedrock provider for Claude and other foundation models. Requires the
7
+ # +aws-sdk-bedrockruntime+ gem.
11
8
  class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
12
- # Matches Anthropic models on Bedrock: bare ids like
13
- # +anthropic.claude-3-5-sonnet-20241022-v2:0+ and cross-region prefixed
14
- # ids like +us.anthropic.claude-sonnet-4-6+.
9
+ # Matches Anthropic models on Bedrock bare (+anthropic.claude-...+) and
10
+ # cross-region (+us.anthropic.claude-...+) ids.
15
11
  ANTHROPIC_MODEL_PATTERN = /(?:^|\.)anthropic\./ #: Regexp
16
12
 
17
- # Returns the preferred skill adapter for the given Bedrock model.
18
- #
19
- # Bedrock hosts models from multiple vendors. Anthropic models prefer
20
- # XML-rendered catalogs; everything else falls back to the default
21
- # Markdown adapter.
22
- #
13
+ # Returns the skill adapter for the Bedrock model — XML for Anthropic models
14
+ # (which Bedrock hosts alongside other vendors'), else Markdown.
23
15
  #--
24
16
  #: (?String?) -> singleton(Riffer::Skills::Adapter)
25
17
  def self.skills_adapter(model = nil)
@@ -27,8 +19,6 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
27
19
  Riffer::Skills::MarkdownAdapter
28
20
  end
29
21
 
30
- # Initializes the Amazon Bedrock provider.
31
- #
32
22
  #--
33
23
  #: (?api_token: String?, ?region: String?, **untyped) -> void
34
24
  def initialize(api_token: nil, region: nil, **options)
@@ -57,12 +47,13 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
57
47
  partitioned_messages = partition_messages(messages)
58
48
  tools = options[:tools]
59
49
  structured_output = options[:structured_output]
50
+ cache_control = options[:cache_control]
60
51
 
61
52
  params = {
62
53
  model_id: model,
63
54
  system: partitioned_messages[:system],
64
55
  messages: partitioned_messages[:conversation],
65
- **options.except(:tools, :structured_output)
56
+ **options.except(:tools, :structured_output, :cache_control)
66
57
  } #: Hash[Symbol, untyped]
67
58
 
68
59
  if tools && !tools.empty?
@@ -88,9 +79,37 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
88
79
  }
89
80
  end
90
81
 
82
+ apply_cache_point(params, cache_control) if cache_control
83
+
91
84
  params
92
85
  end
93
86
 
87
+ # Converse chains +tools -> system -> messages+, so a single +cachePoint+ at
88
+ # the end of the system array (or the tools array, when there is no system
89
+ # prompt) also caches the preceding sections.
90
+ #--
91
+ #: (Hash[Symbol, untyped], untyped) -> void
92
+ def apply_cache_point(params, cache_control)
93
+ cache_point = {cache_point: build_cache_point(cache_control)}
94
+ system = params[:system]
95
+ tools = params.dig(:tool_config, :tools)
96
+
97
+ if system && !system.empty?
98
+ system << cache_point
99
+ elsif tools && !tools.empty?
100
+ tools << cache_point
101
+ end
102
+ end
103
+
104
+ #--
105
+ #: (untyped) -> Hash[Symbol, untyped]
106
+ def build_cache_point(cache_control)
107
+ point = {type: "default"} #: Hash[Symbol, untyped]
108
+ ttl = cache_control.is_a?(Hash) ? cache_control[:ttl] : nil
109
+ point[:ttl] = ttl if ttl
110
+ point
111
+ end
112
+
94
113
  #--
95
114
  #: (Hash[Symbol, untyped]) -> untyped
96
115
  def execute_generate(params)
@@ -106,7 +125,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
106
125
  Riffer::Providers::TokenUsage.new(
107
126
  input_tokens: usage.input_tokens,
108
127
  output_tokens: usage.output_tokens,
109
- cache_creation_tokens: usage.cache_write_input_tokens,
128
+ cache_write_tokens: usage.cache_write_input_tokens,
110
129
  cache_read_tokens: usage.cache_read_input_tokens
111
130
  )
112
131
  end
@@ -177,15 +196,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
177
196
  end
178
197
  end
179
198
 
180
- # Re-raises a Bedrock stream exception event as the matching
181
- # +Aws::BedrockRuntime::Errors+ service error. ConverseStream delivers API
182
- # errors on the same channel as content, so without this a mid-stream
183
- # failure would silently end the enumerator with no tokens or content.
184
- #
185
- # Detection is by class-name suffix: every Bedrock stream-exception struct
186
- # is named +*Exception+ and has a matching +Aws::BedrockRuntime::Errors+
187
- # class of the same name (generated via +DynamicErrors+ if not explicit).
188
- # Non-exception events (e.g. +MessageStartEvent+) pass through silently.
199
+ # Re-raises a Bedrock stream-exception event as the matching
200
+ # +Aws::BedrockRuntime::Errors+ class. ConverseStream delivers API errors on
201
+ # the same channel as content, so without this a mid-stream failure would
202
+ # silently end the stream with no content.
189
203
  #--
190
204
  #: (untyped) -> void
191
205
  def raise_if_stream_exception!(event)
@@ -261,7 +275,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
261
275
  token_usage: Riffer::Providers::TokenUsage.new(
262
276
  input_tokens: typed_event.usage.input_tokens,
263
277
  output_tokens: typed_event.usage.output_tokens,
264
- cache_creation_tokens: typed_event.usage.cache_write_input_tokens,
278
+ cache_write_tokens: typed_event.usage.cache_write_input_tokens,
265
279
  cache_read_tokens: typed_event.usage.cache_read_input_tokens
266
280
  )
267
281
  )
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Anthropic provider for Claude models via the Anthropic API.
5
- #
6
- # Requires the +anthropic+ gem to be installed.
7
- #
8
- # See https://github.com/anthropics/anthropic-sdk-ruby
4
+ # Anthropic provider for Claude models via the Anthropic API. Requires the
5
+ # +anthropic+ gem.
9
6
  class Riffer::Providers::Anthropic < Riffer::Providers::Base
10
7
  WEB_SEARCH_TOOL_TYPE = "web_search_20250305" #: String
11
8
 
@@ -17,8 +14,6 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
17
14
  Riffer::Skills::XmlAdapter
18
15
  end
19
16
 
20
- # Initializes the Anthropic provider.
21
- #
22
17
  #--
23
18
  #: (?api_key: String?, **untyped) -> void
24
19
  def initialize(api_key: nil, **options)
@@ -91,7 +86,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
91
86
  Riffer::Providers::TokenUsage.new(
92
87
  input_tokens: usage.input_tokens,
93
88
  output_tokens: usage.output_tokens,
94
- cache_creation_tokens: usage.cache_creation_input_tokens,
89
+ cache_write_tokens: usage.cache_creation_input_tokens,
95
90
  cache_read_tokens: usage.cache_read_input_tokens
96
91
  )
97
92
  end
@@ -298,7 +293,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
298
293
  token_usage: Riffer::Providers::TokenUsage.new(
299
294
  input_tokens: usage.input_tokens,
300
295
  output_tokens: usage.output_tokens,
301
- cache_creation_tokens: usage.cache_creation_input_tokens,
296
+ cache_write_tokens: usage.cache_creation_input_tokens,
302
297
  cache_read_tokens: usage.cache_read_input_tokens
303
298
  )
304
299
  )
@@ -1,26 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Azure OpenAI provider for GPT models hosted on Azure.
5
- #
6
- # Requires the +openai+ gem to be installed.
7
- #
8
- # Credentials are resolved in order:
9
- # 1. Keyword arguments (+api_key+, +base_url+)
10
- # 2. Config (<tt>Riffer.config.azure_openai.api_key</tt> / <tt>.endpoint</tt>)
11
- # 3. Environment variables (+AZURE_OPENAI_API_KEY+ / +AZURE_OPENAI_ENDPOINT+)
12
- #
13
- # Riffer::Providers::AzureOpenAI.new(
14
- # api_key: "key",
15
- # base_url: "https://my-resource.openai.azure.com"
16
- # )
17
- #
4
+ # Azure OpenAI provider for GPT models hosted on Azure. Requires the +openai+
5
+ # gem. Credentials resolve from kwargs, then config, then
6
+ # +AZURE_OPENAI_API_KEY+ / +AZURE_OPENAI_ENDPOINT+.
18
7
  class Riffer::Providers::AzureOpenAI < Riffer::Providers::OpenAI
19
- # Initializes the Azure OpenAI provider.
20
- #
21
- # [api_key] Azure OpenAI API key. Falls back to config, then +AZURE_OPENAI_API_KEY+.
22
- # [base_url] Azure OpenAI endpoint URL. Falls back to config, then +AZURE_OPENAI_ENDPOINT+.
23
- #
24
8
  #--
25
9
  #: (**untyped) -> void
26
10
  def initialize(**options)