riffer 0.48.0 → 0.50.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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/rules/comments.md +2 -4
  3. data/.claude/rules/rbs-inline.md +2 -12
  4. data/.release-please-manifest.json +1 -1
  5. data/.rubocop.yml +5 -0
  6. data/CHANGELOG.md +32 -0
  7. data/docs/AGENTS.md +3 -5
  8. data/docs/AGENT_LIFECYCLE.md +13 -16
  9. data/docs/CONFIGURATION.md +22 -33
  10. data/docs/STREAM_EVENTS.md +3 -3
  11. data/docs/TOOL_ADVANCED.md +1 -3
  12. data/docs/TRACING.md +1 -1
  13. data/docs/providers/AMAZON_BEDROCK.md +31 -0
  14. data/docs/providers/ANTHROPIC.md +10 -0
  15. data/docs/providers/AZURE_OPENAI.md +2 -0
  16. data/docs/providers/OPENAI.md +22 -0
  17. data/docs/providers/OPENROUTER.md +18 -1
  18. data/docs-site/build.rb +0 -6
  19. data/docs-site/check.rb +1 -5
  20. data/lib/riffer/agent/config.rb +4 -45
  21. data/lib/riffer/agent/context.rb +2 -26
  22. data/lib/riffer/agent/outcome.rb +0 -20
  23. data/lib/riffer/agent/response.rb +0 -38
  24. data/lib/riffer/agent/run.rb +11 -43
  25. data/lib/riffer/agent/serializer.rb +10 -39
  26. data/lib/riffer/agent/session/repair.rb +3 -15
  27. data/lib/riffer/agent/session.rb +25 -38
  28. data/lib/riffer/agent/structured_output/result.rb +0 -8
  29. data/lib/riffer/agent/structured_output.rb +0 -7
  30. data/lib/riffer/agent.rb +10 -130
  31. data/lib/riffer/config/amazon_bedrock.rb +30 -0
  32. data/lib/riffer/config/anthropic.rb +21 -0
  33. data/lib/riffer/config/azure_open_ai.rb +30 -0
  34. data/lib/riffer/config/evals.rb +18 -0
  35. data/lib/riffer/config/files.rb +61 -0
  36. data/lib/riffer/config/gemini.rb +21 -0
  37. data/lib/riffer/config/mcp.rb +31 -0
  38. data/lib/riffer/config/open_ai.rb +30 -0
  39. data/lib/riffer/config/open_router.rb +21 -0
  40. data/lib/riffer/config/pricing/rates.rb +36 -0
  41. data/lib/riffer/config/pricing.rb +64 -0
  42. data/lib/riffer/config/skills.rb +38 -0
  43. data/lib/riffer/config/tracing.rb +43 -0
  44. data/lib/riffer/config.rb +3 -362
  45. data/lib/riffer/evals/evaluator.rb +1 -29
  46. data/lib/riffer/evals/evaluator_runner.rb +0 -15
  47. data/lib/riffer/evals/judge.rb +0 -8
  48. data/lib/riffer/evals/result.rb +1 -11
  49. data/lib/riffer/evals/run_result.rb +0 -12
  50. data/lib/riffer/evals/scenario_result.rb +0 -19
  51. data/lib/riffer/files/downloader.rb +2 -3
  52. data/lib/riffer/files/resolver.rb +2 -7
  53. data/lib/riffer/guardrail.rb +2 -22
  54. data/lib/riffer/guardrails/modification.rb +0 -8
  55. data/lib/riffer/guardrails/result.rb +0 -17
  56. data/lib/riffer/guardrails/runner.rb +2 -11
  57. data/lib/riffer/guardrails/tripwire.rb +0 -8
  58. data/lib/riffer/guardrails.rb +0 -2
  59. data/lib/riffer/helpers/boolean.rb +0 -4
  60. data/lib/riffer/helpers/call_or_value.rb +0 -3
  61. data/lib/riffer/helpers/deep_dup.rb +3 -8
  62. data/lib/riffer/helpers/dependencies.rb +0 -4
  63. data/lib/riffer/helpers/identifier.rb +3 -12
  64. data/lib/riffer/helpers/validate.rb +42 -0
  65. data/lib/riffer/mcp/authenticated_tool.rb +4 -12
  66. data/lib/riffer/mcp/client.rb +0 -7
  67. data/lib/riffer/mcp/manifest.rb +3 -7
  68. data/lib/riffer/mcp/registration.rb +0 -10
  69. data/lib/riffer/mcp/registry.rb +0 -9
  70. data/lib/riffer/mcp/search_tool.rb +0 -4
  71. data/lib/riffer/mcp/tool.rb +1 -4
  72. data/lib/riffer/mcp/tool_factory.rb +3 -6
  73. data/lib/riffer/mcp.rb +2 -23
  74. data/lib/riffer/messages/assistant/reasoning_part.rb +4 -21
  75. data/lib/riffer/messages/assistant/tool_call.rb +1 -9
  76. data/lib/riffer/messages/assistant.rb +1 -21
  77. data/lib/riffer/messages/base.rb +0 -12
  78. data/lib/riffer/messages/system.rb +0 -3
  79. data/lib/riffer/messages/tool.rb +0 -15
  80. data/lib/riffer/messages/user/file_part.rb +2 -30
  81. data/lib/riffer/messages/user.rb +0 -4
  82. data/lib/riffer/params/boolean.rb +1 -5
  83. data/lib/riffer/params/param.rb +3 -31
  84. data/lib/riffer/params.rb +8 -41
  85. data/lib/riffer/providers/amazon_bedrock.rb +98 -46
  86. data/lib/riffer/providers/anthropic.rb +65 -65
  87. data/lib/riffer/providers/azure_open_ai.rb +10 -8
  88. data/lib/riffer/providers/base.rb +7 -28
  89. data/lib/riffer/providers/finish_reason.rb +0 -6
  90. data/lib/riffer/providers/gemini/client.rb +4 -17
  91. data/lib/riffer/providers/gemini.rb +4 -10
  92. data/lib/riffer/providers/mock.rb +1 -26
  93. data/lib/riffer/providers/open_ai.rb +94 -46
  94. data/lib/riffer/providers/open_router.rb +105 -30
  95. data/lib/riffer/providers/repository.rb +2 -14
  96. data/lib/riffer/providers/token_usage.rb +5 -14
  97. data/lib/riffer/registrable.rb +11 -45
  98. data/lib/riffer/runner/fibers.rb +1 -6
  99. data/lib/riffer/runner/sequential.rb +0 -1
  100. data/lib/riffer/runner/threaded.rb +0 -3
  101. data/lib/riffer/runner.rb +0 -3
  102. data/lib/riffer/skills/activate_tool.rb +0 -3
  103. data/lib/riffer/skills/adapter.rb +0 -8
  104. data/lib/riffer/skills/backend.rb +2 -7
  105. data/lib/riffer/skills/config.rb +4 -20
  106. data/lib/riffer/skills/context.rb +0 -29
  107. data/lib/riffer/skills/filesystem_backend.rb +0 -7
  108. data/lib/riffer/skills/frontmatter.rb +2 -16
  109. data/lib/riffer/skills/markdown_adapter.rb +3 -6
  110. data/lib/riffer/skills/xml_adapter.rb +0 -3
  111. data/lib/riffer/stream_events/base.rb +0 -3
  112. data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
  113. data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
  114. data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
  115. data/lib/riffer/stream_events/interrupt.rb +2 -12
  116. data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
  117. data/lib/riffer/stream_events/reasoning_done.rb +1 -5
  118. data/lib/riffer/stream_events/skill_activation.rb +0 -3
  119. data/lib/riffer/stream_events/text_delta.rb +0 -2
  120. data/lib/riffer/stream_events/text_done.rb +0 -2
  121. data/lib/riffer/stream_events/token_usage_done.rb +0 -2
  122. data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
  123. data/lib/riffer/stream_events/tool_call_done.rb +0 -5
  124. data/lib/riffer/stream_events/web_search_done.rb +0 -3
  125. data/lib/riffer/stream_events/web_search_status.rb +1 -5
  126. data/lib/riffer/testing/minitest.rb +4 -5
  127. data/lib/riffer/testing.rb +5 -38
  128. data/lib/riffer/tool.rb +2 -28
  129. data/lib/riffer/tools/response.rb +3 -32
  130. data/lib/riffer/tools/runtime/fibers.rb +0 -6
  131. data/lib/riffer/tools/runtime/inline.rb +0 -1
  132. data/lib/riffer/tools/runtime/threaded.rb +0 -6
  133. data/lib/riffer/tools/runtime.rb +5 -26
  134. data/lib/riffer/tools/toolable.rb +0 -37
  135. data/lib/riffer/tracing/capture.rb +3 -5
  136. data/lib/riffer/tracing/no_op.rb +0 -7
  137. data/lib/riffer/tracing/otel.rb +7 -16
  138. data/lib/riffer/tracing/stream_recorder.rb +0 -7
  139. data/lib/riffer/tracing.rb +4 -27
  140. data/lib/riffer/version.rb +1 -1
  141. data/lib/riffer.rb +2 -30
  142. data/sig/generated/riffer/agent/config.rbs +12 -48
  143. data/sig/generated/riffer/agent/context.rbs +2 -26
  144. data/sig/generated/riffer/agent/outcome.rbs +0 -20
  145. data/sig/generated/riffer/agent/response.rbs +1 -29
  146. data/sig/generated/riffer/agent/run.rbs +1 -27
  147. data/sig/generated/riffer/agent/serializer.rbs +2 -27
  148. data/sig/generated/riffer/agent/session/repair.rbs +2 -10
  149. data/sig/generated/riffer/agent/session.rbs +10 -36
  150. data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
  151. data/sig/generated/riffer/agent/structured_output.rbs +0 -7
  152. data/sig/generated/riffer/agent.rbs +5 -121
  153. data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
  154. data/sig/generated/riffer/config/anthropic.rbs +15 -0
  155. data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
  156. data/sig/generated/riffer/config/evals.rbs +13 -0
  157. data/sig/generated/riffer/config/files.rbs +43 -0
  158. data/sig/generated/riffer/config/gemini.rbs +15 -0
  159. data/sig/generated/riffer/config/mcp.rbs +19 -0
  160. data/sig/generated/riffer/config/open_ai.rbs +21 -0
  161. data/sig/generated/riffer/config/open_router.rbs +15 -0
  162. data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
  163. data/sig/generated/riffer/config/pricing.rbs +31 -0
  164. data/sig/generated/riffer/config/skills.rbs +19 -0
  165. data/sig/generated/riffer/config/tracing.rbs +25 -0
  166. data/sig/generated/riffer/config.rbs +2 -307
  167. data/sig/generated/riffer/evals/evaluator.rbs +0 -28
  168. data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
  169. data/sig/generated/riffer/evals/judge.rbs +0 -7
  170. data/sig/generated/riffer/evals/result.rbs +1 -11
  171. data/sig/generated/riffer/evals/run_result.rbs +0 -12
  172. data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
  173. data/sig/generated/riffer/files/resolver.rbs +0 -7
  174. data/sig/generated/riffer/guardrail.rbs +2 -22
  175. data/sig/generated/riffer/guardrails/modification.rbs +0 -6
  176. data/sig/generated/riffer/guardrails/result.rbs +0 -15
  177. data/sig/generated/riffer/guardrails/runner.rbs +0 -11
  178. data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
  179. data/sig/generated/riffer/guardrails.rbs +0 -2
  180. data/sig/generated/riffer/helpers/boolean.rbs +0 -4
  181. data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
  182. data/sig/generated/riffer/helpers/deep_dup.rbs +0 -8
  183. data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
  184. data/sig/generated/riffer/helpers/identifier.rbs +0 -12
  185. data/sig/generated/riffer/helpers/validate.rbs +21 -0
  186. data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
  187. data/sig/generated/riffer/mcp/client.rbs +0 -7
  188. data/sig/generated/riffer/mcp/manifest.rbs +3 -7
  189. data/sig/generated/riffer/mcp/registration.rbs +0 -10
  190. data/sig/generated/riffer/mcp/registry.rbs +0 -9
  191. data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
  192. data/sig/generated/riffer/mcp/tool.rbs +0 -4
  193. data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
  194. data/sig/generated/riffer/mcp.rbs +2 -22
  195. data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +4 -17
  196. data/sig/generated/riffer/messages/assistant/tool_call.rbs +1 -9
  197. data/sig/generated/riffer/messages/assistant.rbs +10 -30
  198. data/sig/generated/riffer/messages/base.rbs +0 -12
  199. data/sig/generated/riffer/messages/system.rbs +0 -3
  200. data/sig/generated/riffer/messages/tool.rbs +0 -12
  201. data/sig/generated/riffer/messages/user/file_part.rbs +0 -30
  202. data/sig/generated/riffer/messages/user.rbs +0 -4
  203. data/sig/generated/riffer/params/boolean.rbs +1 -4
  204. data/sig/generated/riffer/params/param.rbs +0 -31
  205. data/sig/generated/riffer/params.rbs +4 -40
  206. data/sig/generated/riffer/providers/amazon_bedrock.rbs +26 -26
  207. data/sig/generated/riffer/providers/anthropic.rbs +23 -22
  208. data/sig/generated/riffer/providers/azure_open_ai.rbs +5 -8
  209. data/sig/generated/riffer/providers/base.rbs +0 -28
  210. data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
  211. data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
  212. data/sig/generated/riffer/providers/gemini.rbs +0 -6
  213. data/sig/generated/riffer/providers/mock.rbs +0 -26
  214. data/sig/generated/riffer/providers/open_ai.rbs +36 -19
  215. data/sig/generated/riffer/providers/open_router.rbs +33 -14
  216. data/sig/generated/riffer/providers/repository.rbs +2 -14
  217. data/sig/generated/riffer/providers/token_usage.rbs +5 -14
  218. data/sig/generated/riffer/registrable.rbs +0 -45
  219. data/sig/generated/riffer/runner/fibers.rbs +0 -6
  220. data/sig/generated/riffer/runner/sequential.rbs +0 -1
  221. data/sig/generated/riffer/runner/threaded.rbs +0 -3
  222. data/sig/generated/riffer/runner.rbs +0 -3
  223. data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
  224. data/sig/generated/riffer/skills/adapter.rbs +0 -8
  225. data/sig/generated/riffer/skills/backend.rbs +2 -7
  226. data/sig/generated/riffer/skills/config.rbs +0 -20
  227. data/sig/generated/riffer/skills/context.rbs +0 -29
  228. data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
  229. data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
  230. data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
  231. data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
  232. data/sig/generated/riffer/stream_events/base.rbs +0 -3
  233. data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
  234. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
  235. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
  236. data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
  237. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
  238. data/sig/generated/riffer/stream_events/reasoning_done.rbs +1 -5
  239. data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
  240. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  241. data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
  242. data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
  243. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
  244. data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
  245. data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
  246. data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
  247. data/sig/generated/riffer/testing.rbs +0 -38
  248. data/sig/generated/riffer/tool.rbs +0 -27
  249. data/sig/generated/riffer/tools/response.rbs +3 -29
  250. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
  251. data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
  252. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
  253. data/sig/generated/riffer/tools/runtime.rbs +2 -24
  254. data/sig/generated/riffer/tools/toolable.rbs +0 -37
  255. data/sig/generated/riffer/tracing/capture.rbs +2 -5
  256. data/sig/generated/riffer/tracing/no_op.rbs +0 -7
  257. data/sig/generated/riffer/tracing/otel.rbs +7 -16
  258. data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
  259. data/sig/generated/riffer/tracing.rbs +4 -23
  260. data/sig/generated/riffer.rbs +2 -29
  261. data/sig/manual/riffer/helpers/validate.rbs +5 -0
  262. metadata +30 -1
data/lib/riffer/config.rb CHANGED
@@ -1,365 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Configuration for the Riffer framework.
5
4
  class Riffer::Config
6
- AmazonBedrock = Struct.new(:api_token, :region, :client)
7
- Anthropic = Struct.new(:api_key, :client)
8
- AzureOpenAI = Struct.new(:api_key, :endpoint, :client)
9
- Gemini = Struct.new(:api_key, :client)
10
- OpenAI = Struct.new(:api_key, :base_url, :client)
11
- OpenRouter = Struct.new(:api_key, :client)
12
- Evals = Struct.new(:judge_model)
13
- Mcp = Struct.new(:credentials, :discovery_runner)
14
-
15
- # Skills-related global configuration.
16
- class Skills
17
- # The tool class the LLM calls to activate a skill; defaults to
18
- # <tt>Riffer::Skills::ActivateTool</tt>.
19
- attr_reader :default_activate_tool #: singleton(Riffer::Tool) # @dynamic default_activate_tool
20
-
21
- # Default skills backend for agents that declare a +skills+ block without
22
- # one; defaults to +nil+.
23
- attr_reader :default_backend #: (Riffer::Skills::Backend | Proc)? # @dynamic default_backend
24
-
25
- #--
26
- #: () -> void
27
- def initialize
28
- @default_activate_tool = Riffer::Skills::ActivateTool
29
- @default_backend = nil
30
- end
31
-
32
- # Sets the default skill activation tool class. Raises Riffer::ArgumentError
33
- # on an invalid value.
34
- #--
35
- #: (singleton(Riffer::Tool)) -> void
36
- def default_activate_tool=(value)
37
- unless value.is_a?(Class) && value < Riffer::Tool
38
- raise Riffer::ArgumentError,
39
- "default_activate_tool must be a Riffer::Tool subclass"
40
- end
41
-
42
- @default_activate_tool = value
43
- end
44
-
45
- # Sets the default skills backend. Raises Riffer::ArgumentError on an
46
- # invalid value.
47
- #--
48
- #: ((Riffer::Skills::Backend | Proc)?) -> void
49
- def default_backend=(value)
50
- valid = value.nil? || value.is_a?(Riffer::Skills::Backend) || value.is_a?(Proc)
51
- unless valid
52
- raise Riffer::ArgumentError,
53
- "default_backend must be a Riffer::Skills::Backend instance, Proc, or nil"
54
- end
55
-
56
- @default_backend = value
57
- end
58
- end
59
-
60
- # Tracing-related global configuration.
61
- class Tracing
62
- # Whether riffer emits OTEL spans; defaults to +true+, a no-op until a
63
- # host wires an OTEL SDK.
64
- attr_reader :enabled #: bool # @dynamic enabled
65
-
66
- # Whether LLM-call spans capture full message content
67
- # (<tt>gen_ai.input.messages</tt>, <tt>gen_ai.output.messages</tt>,
68
- # <tt>gen_ai.system_instructions</tt>); defaults to +false+ — message
69
- # content routinely carries sensitive data.
70
- attr_reader :capture_messages #: bool # @dynamic capture_messages
71
-
72
- # The backend riffer routes spans through; defaults to +nil+, a no-op.
73
- # Riffer auto-detects no backend; assigning one is opt-in.
74
- attr_reader :backend #: untyped # @dynamic backend
75
-
76
- #--
77
- #: () -> void
78
- def initialize
79
- @enabled = true
80
- @capture_messages = false
81
- @backend = nil
82
- end
83
-
84
- # Sets the enabled flag, coercing boolean-ish values so an env-var
85
- # +"false"+ (truthy in Ruby) doesn't silently keep tracing on. Raises
86
- # Riffer::ArgumentError on an unrecognized value.
87
- #--
88
- #: (untyped) -> void
89
- def enabled=(value)
90
- @enabled = Riffer::Helpers::Boolean.coerce(value, attribute: "enabled")
91
- end
92
-
93
- # Sets the capture_messages flag, coercing boolean-ish values so an
94
- # env-var +"false"+ (truthy in Ruby) doesn't silently enable content
95
- # capture. Raises Riffer::ArgumentError on an unrecognized value.
96
- #--
97
- #: (untyped) -> void
98
- def capture_messages=(value)
99
- @capture_messages = Riffer::Helpers::Boolean.coerce(value, attribute: "capture_messages")
100
- end
101
-
102
- # Sets the tracing backend riffer routes spans through. Raises
103
- # Riffer::ArgumentError unless the value is +nil+ or responds to the full
104
- # delegated contract: +in_span+, +current_context+, and +with_context+.
105
- #--
106
- #: (untyped) -> void
107
- def backend=(value)
108
- contract = %i[in_span current_context with_context]
109
- unless value.nil? || contract.all? { |method| value.respond_to?(method) }
110
- raise Riffer::ArgumentError, "tracing backend must respond to #in_span, #current_context, and #with_context"
111
- end
112
-
113
- @backend = value
114
- Riffer::Tracing.reset!
115
- end
116
- end
117
-
118
- # File-attachment-download policy for +Riffer::Messages::User::FilePart+ URL sources
119
- class Files
120
- # Allow file attachments to be downloaded to send to providers.
121
- attr_reader :allow_downloads #: bool # @dynamic allow_downloads
122
- # Maximum file size to download before failing.
123
- attr_reader :max_bytes #: Integer # @dynamic max_bytes
124
- # Maximum amount of time to spend downloading a file before failing.
125
- attr_reader :timeout #: Integer # @dynamic timeout
126
- # Maximum number of files to include in an individual message.
127
- attr_reader :max_per_message #: Integer? # @dynamic max_per_message
128
- # Execution pattern for downloading files.
129
- attr_reader :runner #: Riffer::Runner # @dynamic runner
130
- # The object used to fetch a URL source's bytes
131
- attr_reader :downloader #: untyped # @dynamic downloader
132
-
133
- #--
134
- #: () -> void
135
- def initialize
136
- @allow_downloads = false
137
- @max_bytes = 3_500_000
138
- @timeout = 60
139
- @max_per_message = nil
140
- @runner = Riffer::Runner::Sequential.new
141
- @downloader = Riffer::Files::Downloader.new
142
- end
143
-
144
- # Sets the allow_downloads flag, coercing boolean-ish values so an env-var
145
- # +"false"+ (truthy in Ruby) doesn't silently enable downloads. Raises
146
- # Riffer::ArgumentError on an unrecognized value.
147
- #--
148
- #: (untyped) -> void
149
- def allow_downloads=(value)
150
- @allow_downloads = Riffer::Helpers::Boolean.coerce(value, attribute: "allow_downloads")
151
- end
152
-
153
- # Sets max_bytes, provided value is a positive integer.
154
- # Raises Riffer::ArgumentError if value is not an Integer or less than or equal to 0.
155
- #--
156
- #: (untyped) -> void
157
- def max_bytes=(value)
158
- raise Riffer::ArgumentError, "max_bytes must be a positive integer" unless value.is_a?(Integer) && value.positive?
159
-
160
- @max_bytes = value
161
- end
162
-
163
- # Sets timeout, provided value is a positive integer.
164
- # Raises Riffer::ArgumentError if value is not an Integer or is less than or equal to 0.
165
- #--
166
- #: (untyped) -> void
167
- def timeout=(value)
168
- raise Riffer::ArgumentError, "timeout must be a positive integer" unless value.is_a?(Integer) && value.positive?
169
-
170
- @timeout = value
171
- end
172
-
173
- # Sets max_per_message, provided value is either nil or a positive integer.
174
- # Raises Riffer::ArgumentError if value is not an Integer or nil, or is less than or equal to 0.
175
- #--
176
- #: (untyped) -> void
177
- def max_per_message=(value)
178
- if value.is_a?(Integer) && value.positive?
179
- @max_per_message = value
180
- elsif value.nil?
181
- @max_per_message = nil
182
- else
183
- raise Riffer::ArgumentError, "max_per_message must be a positive integer or nil"
184
- end
185
- end
186
-
187
- # Sets the runner used to process file downloads, provided value is a Riffer::Runner.
188
- # Raises Riffer::ArgumentError if value is not a Riffer::Runner.
189
- #--
190
- #: (untyped) -> void
191
- def runner=(value)
192
- valid = value.is_a?(Riffer::Runner)
193
- raise Riffer::ArgumentError, "runner must be a Riffer::Runner instance" unless valid
194
-
195
- @runner = value
196
- end
197
-
198
- # Sets the object used to download bytes from a URL.
199
- # Raises Riffer::ArgumentError if value does not respond to +#call+.
200
- #--
201
- #: (untyped) -> void
202
- def downloader=(value)
203
- raise Riffer::ArgumentError, "downloader must respond to #call" unless value.respond_to?(:call)
204
-
205
- @downloader = value
206
- end
207
- end
208
-
209
- # Consumer-configured token pricing, keyed by +provider/model+ id. Riffer
210
- # ships no price table, so an unconfigured model carries no cost.
211
- class Pricing
212
- # @rbs @rates: Hash[String, Riffer::Config::Pricing::Rates]
213
-
214
- # Per-million-token rates for one model's four token buckets. +cache_read+
215
- # and +cache_write+ fall back to the +input+ rate when unset.
216
- class Rates
217
- # Input rate per million tokens.
218
- attr_reader :input #: Float # @dynamic input
219
-
220
- # Output rate per million tokens.
221
- attr_reader :output #: Float # @dynamic output
222
-
223
- # Cache-read rate per million tokens.
224
- attr_reader :cache_read #: Float? # @dynamic cache_read
225
-
226
- # Cache-write rate per million tokens.
227
- attr_reader :cache_write #: Float? # @dynamic cache_write
228
-
229
- #--
230
- #: (input: Float, output: Float, ?cache_read: Float?, ?cache_write: Float?) -> void
231
- def initialize(input:, output:, cache_read: nil, cache_write: nil)
232
- @input = input
233
- @output = output
234
- @cache_read = cache_read
235
- @cache_write = cache_write
236
- end
237
-
238
- # Returns the cost for the given token counts.
239
- #--
240
- #: (input_tokens: Integer, output_tokens: Integer, ?cache_read_tokens: Integer?, ?cache_write_tokens: Integer?) -> Float
241
- def cost_for(input_tokens:, output_tokens:, cache_read_tokens: nil, cache_write_tokens: nil)
242
- read = cache_read_tokens || 0
243
- write = cache_write_tokens || 0
244
- uncached = input_tokens - read - write
245
- uncached = 0 if uncached.negative?
246
-
247
- per_million = (uncached * input) +
248
- (read * (cache_read || input)) +
249
- (write * (cache_write || input)) +
250
- (output_tokens * output)
251
- per_million / 1_000_000.0
252
- end
253
- end
254
-
255
- #--
256
- #: () -> void
257
- def initialize
258
- @rates = {}
259
- end
260
-
261
- # Registers per-million-token rates for a +provider/model+ id/s. Raises
262
- # Riffer::ArgumentError on a malformed id or a negative/non-numeric rate.
263
- #--
264
- #: ((String | Array[String]), input: Numeric, output: Numeric, ?cache_read: Numeric?, ?cache_write: Numeric?) -> void
265
- def set(models, input:, output:, cache_read: nil, cache_write: nil)
266
- ids = models.is_a?(Array) ? models : [models]
267
- raise Riffer::ArgumentError, "at least one model id is required" if ids.empty?
268
-
269
- ids.each { |id| validate_model!(id) }
270
-
271
- rates = Rates.new(
272
- input: coerce_rate(input, "input"),
273
- output: coerce_rate(output, "output"),
274
- cache_read: coerce_optional_rate(cache_read, "cache_read"),
275
- cache_write: coerce_optional_rate(cache_write, "cache_write"),
276
- )
277
- ids.each { |id| @rates[id] = rates }
278
- end
279
-
280
- # Returns the rates registered for a +provider/model+ id.
281
- #--
282
- #: (String) -> Riffer::Config::Pricing::Rates?
283
- def rates_for(model)
284
- @rates[model]
285
- end
286
-
287
- # Returns true when no rates are registered.
288
- #--
289
- #: () -> bool
290
- def empty?
291
- @rates.empty?
292
- end
293
-
294
- private
295
-
296
- #--
297
- #: (String) -> void
298
- def validate_model!(model)
299
- segments = model.to_s.split("/", 2)
300
- valid = segments.length == 2 && segments.none? { |segment| segment.strip.empty? }
301
- return if valid
302
-
303
- raise Riffer::ArgumentError,
304
- "pricing model id must be in \"provider/model\" form, got #{model.inspect}"
305
- end
306
-
307
- #--
308
- #: (untyped, String) -> Float
309
- def coerce_rate(value, attribute)
310
- number = value
311
- float = value.is_a?(Numeric) ? number.to_f : nil #: Float?
312
- unless float&.finite? && float >= 0
313
- raise Riffer::ArgumentError,
314
- "#{attribute} rate must be a non-negative number, got #{value.inspect}"
315
- end
316
-
317
- float
318
- end
319
-
320
- #--
321
- #: (untyped, String) -> Float?
322
- def coerce_optional_rate(value, attribute)
323
- return nil if value.nil?
324
-
325
- coerce_rate(value, attribute)
326
- end
327
- end
328
-
329
5
  VALID_MESSAGE_ID_STRATEGIES = %i[none uuid uuidv7].freeze
330
6
 
331
- # Amazon Bedrock configuration.
332
7
  attr_reader :amazon_bedrock #: Riffer::Config::AmazonBedrock # @dynamic amazon_bedrock
333
8
 
334
- # Anthropic configuration.
335
9
  attr_reader :anthropic #: Riffer::Config::Anthropic # @dynamic anthropic
336
10
 
337
- # Azure OpenAI configuration.
338
11
  attr_reader :azure_openai #: Riffer::Config::AzureOpenAI # @dynamic azure_openai
339
12
 
340
- # Google Gemini configuration.
341
13
  attr_reader :gemini #: Riffer::Config::Gemini # @dynamic gemini
342
14
 
343
- # OpenAI configuration.
344
15
  attr_reader :openai #: Riffer::Config::OpenAI # @dynamic openai
345
16
 
346
- # OpenRouter configuration.
347
17
  attr_reader :openrouter #: Riffer::Config::OpenRouter # @dynamic openrouter
348
18
 
349
- # Evals configuration.
350
19
  attr_reader :evals #: Riffer::Config::Evals # @dynamic evals
351
20
 
352
- # MCP configuration. +credentials+ is an optional Proc returning per-run
353
- # +tools/call+ headers (or +nil+ to deny); +discovery_runner+ runs tool
354
- # discovery.
355
21
  attr_reader :mcp #: Riffer::Config::Mcp # @dynamic mcp
356
22
 
357
- # Global tool runtime configuration (experimental); defaults to
358
- # <tt>Riffer::Tools::Runtime::Inline.new</tt>.
359
23
  attr_reader :tool_runtime #: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc) # @dynamic tool_runtime
360
24
 
361
- # Sets the global tool runtime. Raises Riffer::ArgumentError on an invalid
362
- # value.
363
25
  #--
364
26
  #: ((singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)) -> void
365
27
  def tool_runtime=(value)
@@ -373,24 +35,18 @@ class Riffer::Config
373
35
  @tool_runtime = value
374
36
  end
375
37
 
376
- # Skills-related global configuration.
377
38
  attr_reader :skills #: Riffer::Config::Skills # @dynamic skills
378
39
 
379
- # Tracing-related global configuration.
380
40
  attr_reader :tracing #: Riffer::Config::Tracing # @dynamic tracing
381
41
 
382
42
  attr_reader :files #: Riffer::Config::Files # @dynamic files
383
43
 
384
- # Consumer-configured per-model token pricing.
385
44
  attr_reader :pricing #: Riffer::Config::Pricing # @dynamic pricing
386
45
 
387
- # Strategy for auto-generating message ids: +:none+ (default), +:uuid+, or
388
- # +:uuidv7+. When not +:none+, messages get an +id+ at construction, and
389
- # seeded messages passed to +Riffer::Agent#generate+ must carry their own.
46
+ # When not +:none+, seeded messages passed to +Riffer::Agent#generate+ must
47
+ # carry their own +id+.
390
48
  attr_reader :message_id_strategy #: Symbol # @dynamic message_id_strategy
391
49
 
392
- # Sets the message id strategy. Raises Riffer::ArgumentError unless the value
393
- # is +:none+, +:uuid+, or +:uuidv7+.
394
50
  #--
395
51
  #: (Symbol) -> void
396
52
  def message_id_strategy=(value)
@@ -401,20 +57,6 @@ class Riffer::Config
401
57
  @message_id_strategy = value
402
58
  end
403
59
 
404
- # Experimental: when +true+, riffer maintains the +tool_use+ ↔ +tool_result+
405
- # invariant itself — stripping orphaned exchanges and filling interrupted
406
- # ones. Defaults to +false+; the surface may change without notice.
407
- attr_reader :experimental_history_healing #: bool # @dynamic experimental_history_healing
408
-
409
- # Sets the +experimental_history_healing+ flag, coercing boolean-ish values so
410
- # an env-var +"false"+ (truthy in Ruby) doesn't silently enable healing.
411
- # Raises Riffer::ArgumentError on an unrecognized value.
412
- #--
413
- #: (untyped) -> void
414
- def experimental_history_healing=(value)
415
- @experimental_history_healing = Riffer::Helpers::Boolean.coerce(value, attribute: "experimental_history_healing")
416
- end
417
-
418
60
  #--
419
61
  #: () -> void
420
62
  def initialize
@@ -425,13 +67,12 @@ class Riffer::Config
425
67
  @openai = OpenAI.new
426
68
  @openrouter = OpenRouter.new
427
69
  @evals = Evals.new
428
- @mcp = Mcp.new(credentials: nil, discovery_runner: Riffer::Runner::Sequential.new)
70
+ @mcp = Mcp.new
429
71
  @tool_runtime = Riffer::Tools::Runtime::Inline.new
430
72
  @skills = Skills.new
431
73
  @tracing = Tracing.new
432
74
  @files = Files.new
433
75
  @pricing = Pricing.new
434
76
  @message_id_strategy = :none
435
- @experimental_history_healing = false
436
77
  end
437
78
  end
@@ -1,16 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Base class for all evaluators. Set +instructions+ and the base class calls
5
- # the judge automatically; override +#evaluate+ for custom logic. See
6
- # examples/evaluators/ for reference implementations.
7
- #
8
- # class MyEvaluator < Riffer::Evals::Evaluator
9
- # instructions "Assess medical accuracy of the response..."
10
- # higher_is_better true
11
- # judge_model "anthropic/claude-opus-4-5-20251101"
12
- # end
13
- #
14
4
  class Riffer::Evals::Evaluator
15
5
  # @rbs self.@instructions: String?
16
6
  # @rbs self.@higher_is_better: bool?
@@ -18,12 +8,9 @@ class Riffer::Evals::Evaluator
18
8
  # @rbs self.@identifier: String?
19
9
  # @rbs @judge: Riffer::Evals::Judge?
20
10
 
21
- # The identifier for an anonymous evaluator class.
22
11
  DEFAULT_IDENTIFIER = "riffer/judge" #: String
23
12
 
24
13
  class << self
25
- # Gets or sets the evaluation instructions (criteria and scoring rubric).
26
- #
27
14
  #--
28
15
  #: (?String?) -> String?
29
16
  def instructions(value = nil)
@@ -32,8 +19,6 @@ class Riffer::Evals::Evaluator
32
19
  @instructions = value.to_s
33
20
  end
34
21
 
35
- # Gets or sets whether higher scores are better.
36
- #
37
22
  #--
38
23
  #: (?bool?) -> bool
39
24
  def higher_is_better(value = nil)
@@ -46,8 +31,6 @@ class Riffer::Evals::Evaluator
46
31
  @higher_is_better = value
47
32
  end
48
33
 
49
- # Gets or sets the judge model for LLM-as-judge evaluations.
50
- #
51
34
  #--
52
35
  #: (?String?) -> String?
53
36
  def judge_model(value = nil)
@@ -56,10 +39,6 @@ class Riffer::Evals::Evaluator
56
39
  @judge_model = value.to_s
57
40
  end
58
41
 
59
- # Gets or sets the evaluator identifier, sent as the +agent+ tag on judge
60
- # calls. Defaults to the snake_cased class name, or DEFAULT_IDENTIFIER for
61
- # an anonymous class.
62
- #
63
42
  #--
64
43
  #: (?String?) -> String
65
44
  def identifier(value = nil)
@@ -70,18 +49,15 @@ class Riffer::Evals::Evaluator
70
49
 
71
50
  private
72
51
 
73
- # Anonymous classes derive an empty identifier.
74
52
  #--
75
53
  #: () -> String
76
54
  def derived_identifier
55
+ # Anonymous classes derive an empty identifier.
77
56
  derived = Riffer::Helpers::Identifier.for(self)
78
57
  derived.empty? ? DEFAULT_IDENTIFIER : derived
79
58
  end
80
59
  end
81
60
 
82
- # Evaluates an input/output pair. The default calls the judge with the
83
- # class-level +instructions+; override for custom logic (e.g. rule-based
84
- # evaluators).
85
61
  #--
86
62
  #: (input: String | Array[Hash[Symbol, untyped] | Riffer::Messages::Base], output: String, ?ground_truth: String?, ?messages: Array[Riffer::Messages::Base]) -> Riffer::Evals::Result
87
63
  def evaluate(input:, output:, ground_truth: nil, messages: [])
@@ -120,9 +96,6 @@ class Riffer::Evals::Evaluator
120
96
 
121
97
  protected
122
98
 
123
- # Returns a Judge instance configured for this evaluator. Its calls carry
124
- # the +kind+ (+"judge"+) and +agent+ (the evaluator identifier) tags.
125
- #
126
99
  #--
127
100
  #: () -> Riffer::Evals::Judge
128
101
  def judge
@@ -137,7 +110,6 @@ class Riffer::Evals::Evaluator
137
110
  end
138
111
  end
139
112
 
140
- # Builds a Result for this evaluator.
141
113
  #--
142
114
  #: (score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?token_usage: Riffer::Providers::TokenUsage?) -> Riffer::Evals::Result
143
115
  def result(score:, reason: nil, metadata: {}, token_usage: nil)
@@ -1,24 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Orchestrates running evaluators against an agent across multiple scenarios.
5
- #
6
- # result = Riffer::Evals::EvaluatorRunner.run(
7
- # agent: MyAgent,
8
- # scenarios: [
9
- # { input: "What is Ruby?", ground_truth: "A programming language" },
10
- # { input: "What is Python?" }
11
- # ],
12
- # evaluators: [AnswerRelevancyEvaluator]
13
- # )
14
- #
15
- # result.scores # => { AnswerRelevancyEvaluator => 0.85 }
16
- #
17
4
  module Riffer::Evals::EvaluatorRunner
18
5
  extend self
19
6
 
20
- # Runs evaluators against an agent for the given scenarios. Raises
21
- # Riffer::ArgumentError on an invalid agent or evaluator.
22
7
  #--
23
8
  #: (agent: singleton(Riffer::Agent), scenarios: Array[Hash[Symbol, untyped]], evaluators: Array[singleton(Riffer::Evals::Evaluator)], ?context: Hash[Symbol, untyped]?) -> Riffer::Evals::RunResult
24
9
  def run(agent:, scenarios:, evaluators:, context: nil)
@@ -3,14 +3,11 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Executes LLM-as-judge evaluations, using tool calling internally to get
7
- # structured output from the judge model.
8
6
  class Riffer::Evals::Judge
9
7
  # @rbs @provider_instance: Riffer::Providers::Base?
10
8
  # @rbs @provider_name: String?
11
9
  # @rbs @model_name: String?
12
10
 
13
- # Internal tool for structured evaluation output.
14
11
  class EvaluationTool < Riffer::Tool
15
12
  identifier "evaluation"
16
13
  description "Submit your evaluation score and reasoning"
@@ -27,13 +24,9 @@ class Riffer::Evals::Judge
27
24
  end
28
25
  end
29
26
 
30
- # The model string (provider/model format).
31
27
  attr_reader :model #: String # @dynamic model
32
-
33
- # The tags passed to the provider on every call.
34
28
  attr_reader :tags #: Hash[String, String] # @dynamic tags
35
29
 
36
- # Raises Riffer::ArgumentError unless +model+ is "provider/model" format.
37
30
  #--
38
31
  #: (model: String, ?tags: Hash[String, String]) -> void
39
32
  def initialize(model:, tags: {})
@@ -46,7 +39,6 @@ class Riffer::Evals::Judge
46
39
  @tags = tags
47
40
  end
48
41
 
49
- # Evaluates an input/output pair using the configured LLM.
50
42
  #--
51
43
  #: (instructions: String, input: String, output: String, ?ground_truth: String?) -> Hash[Symbol, untyped]
52
44
  def evaluate(instructions:, input:, output:, ground_truth: nil)
@@ -1,28 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents the result of a single evaluation.
5
4
  class Riffer::Evals::Result
6
- # The evaluator class that produced this result.
7
5
  attr_reader :evaluator #: singleton(Riffer::Evals::Evaluator) # @dynamic evaluator
8
6
 
9
- # The evaluation score (0.0 to 1.0).
10
7
  attr_reader :score #: Float # @dynamic score
11
8
 
12
- # Human-readable explanation of the score.
13
9
  attr_reader :reason #: String? # @dynamic reason
14
10
 
15
- # Additional metadata from the evaluation.
16
11
  attr_reader :metadata #: Hash[Symbol, untyped] # @dynamic metadata
17
12
 
18
- # Whether higher scores are better for this evaluator.
19
13
  attr_reader :higher_is_better #: bool # @dynamic higher_is_better
20
14
 
21
- # Token usage for the judge call that produced this result, when the
22
- # evaluator used an LLM. Nil for rule-based evaluators.
15
+ # Nil for rule-based evaluators.
23
16
  attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
24
17
 
25
- # Raises Riffer::ArgumentError if +score+ is not between 0.0 and 1.0.
26
18
  #--
27
19
  #: (evaluator: singleton(Riffer::Evals::Evaluator), score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?higher_is_better: bool, ?token_usage: Riffer::Providers::TokenUsage?) -> void
28
20
  def initialize(evaluator:, score:, reason: nil, metadata: {}, higher_is_better: true, token_usage: nil)
@@ -35,8 +27,6 @@ class Riffer::Evals::Result
35
27
  @token_usage = token_usage
36
28
  end
37
29
 
38
- # Returns a hash representation of the result.
39
- #
40
30
  #--
41
31
  #: () -> Hash[Symbol, untyped]
42
32
  def to_h
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents the complete result of an evaluation run across multiple scenarios.
5
4
  class Riffer::Evals::RunResult
6
- # Per-scenario evaluation results.
7
5
  attr_reader :scenario_results #: Array[Riffer::Evals::ScenarioResult] # @dynamic scenario_results
8
6
 
9
7
  #--
@@ -12,8 +10,6 @@ class Riffer::Evals::RunResult
12
10
  @scenario_results = scenario_results
13
11
  end
14
12
 
15
- # Returns average scores keyed by evaluator class across all scenarios.
16
- #
17
13
  #--
18
14
  #: () -> Hash[singleton(Riffer::Evals::Evaluator), Float]
19
15
  def scores
@@ -35,26 +31,18 @@ class Riffer::Evals::RunResult
35
31
  end
36
32
  end
37
33
 
38
- # Returns the summed token usage the agents under test spent across every
39
- # scenario, or nil when none reported usage.
40
- #
41
34
  #--
42
35
  #: () -> Riffer::Providers::TokenUsage?
43
36
  def token_usage
44
37
  scenario_results.filter_map(&:token_usage).reduce(:+)
45
38
  end
46
39
 
47
- # Returns the summed token usage across every scenario's LLM-as-judge
48
- # evaluators, or nil when none reported usage.
49
- #
50
40
  #--
51
41
  #: () -> Riffer::Providers::TokenUsage?
52
42
  def evaluator_token_usage
53
43
  scenario_results.filter_map(&:evaluator_token_usage).reduce(:+)
54
44
  end
55
45
 
56
- # Returns a hash representation of the run result.
57
- #
58
46
  #--
59
47
  #: () -> Hash[Symbol, untyped]
60
48
  def to_h