riffer 0.47.2 → 0.49.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 (267) 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 +42 -0
  7. data/docs/AGENTS.md +39 -7
  8. data/docs/AGENT_LIFECYCLE.md +14 -16
  9. data/docs/CONFIGURATION.md +23 -34
  10. data/docs/EVALS.md +2 -1
  11. data/docs/MCP.md +0 -4
  12. data/docs/MESSAGES.md +85 -17
  13. data/docs/STREAM_EVENTS.md +8 -4
  14. data/docs/TOOL_ADVANCED.md +1 -3
  15. data/docs/TRACING.md +2 -2
  16. data/docs/providers/AMAZON_BEDROCK.md +32 -1
  17. data/docs/providers/CUSTOM_PROVIDERS.md +56 -4
  18. data/docs/providers/GEMINI.md +1 -1
  19. data/docs/providers/MOCK_PROVIDER.md +17 -0
  20. data/docs/providers/OPENROUTER.md +18 -1
  21. data/docs-site/build.rb +0 -6
  22. data/docs-site/check.rb +1 -5
  23. data/lib/riffer/agent/config.rb +27 -44
  24. data/lib/riffer/agent/context.rb +2 -26
  25. data/lib/riffer/agent/outcome.rb +0 -20
  26. data/lib/riffer/agent/response.rb +4 -35
  27. data/lib/riffer/agent/run.rb +32 -48
  28. data/lib/riffer/agent/serializer.rb +10 -39
  29. data/lib/riffer/agent/session/repair.rb +3 -15
  30. data/lib/riffer/agent/session.rb +26 -38
  31. data/lib/riffer/agent/structured_output/result.rb +0 -8
  32. data/lib/riffer/agent/structured_output.rb +0 -7
  33. data/lib/riffer/agent.rb +31 -127
  34. data/lib/riffer/config/amazon_bedrock.rb +30 -0
  35. data/lib/riffer/config/anthropic.rb +21 -0
  36. data/lib/riffer/config/azure_open_ai.rb +30 -0
  37. data/lib/riffer/config/evals.rb +18 -0
  38. data/lib/riffer/config/files.rb +61 -0
  39. data/lib/riffer/config/gemini.rb +21 -0
  40. data/lib/riffer/config/mcp.rb +31 -0
  41. data/lib/riffer/config/open_ai.rb +30 -0
  42. data/lib/riffer/config/open_router.rb +21 -0
  43. data/lib/riffer/config/pricing/rates.rb +36 -0
  44. data/lib/riffer/config/pricing.rb +64 -0
  45. data/lib/riffer/config/skills.rb +38 -0
  46. data/lib/riffer/config/tracing.rb +43 -0
  47. data/lib/riffer/config.rb +3 -362
  48. data/lib/riffer/evals/evaluator.rb +22 -23
  49. data/lib/riffer/evals/evaluator_runner.rb +0 -15
  50. data/lib/riffer/evals/judge.rb +9 -11
  51. data/lib/riffer/evals/result.rb +1 -11
  52. data/lib/riffer/evals/run_result.rb +0 -12
  53. data/lib/riffer/evals/scenario_result.rb +0 -19
  54. data/lib/riffer/files/downloader.rb +2 -3
  55. data/lib/riffer/files/resolver.rb +5 -10
  56. data/lib/riffer/guardrail.rb +2 -22
  57. data/lib/riffer/guardrails/modification.rb +0 -8
  58. data/lib/riffer/guardrails/result.rb +0 -17
  59. data/lib/riffer/guardrails/runner.rb +2 -11
  60. data/lib/riffer/guardrails/tripwire.rb +0 -8
  61. data/lib/riffer/guardrails.rb +0 -2
  62. data/lib/riffer/helpers/boolean.rb +0 -4
  63. data/lib/riffer/helpers/call_or_value.rb +0 -3
  64. data/lib/riffer/helpers/deep_dup.rb +37 -0
  65. data/lib/riffer/helpers/dependencies.rb +0 -4
  66. data/lib/riffer/helpers/identifier.rb +3 -12
  67. data/lib/riffer/helpers/validate.rb +42 -0
  68. data/lib/riffer/mcp/authenticated_tool.rb +4 -12
  69. data/lib/riffer/mcp/client.rb +0 -7
  70. data/lib/riffer/mcp/manifest.rb +3 -7
  71. data/lib/riffer/mcp/registration.rb +0 -10
  72. data/lib/riffer/mcp/registry.rb +0 -9
  73. data/lib/riffer/mcp/search_tool.rb +0 -4
  74. data/lib/riffer/mcp/tool.rb +1 -4
  75. data/lib/riffer/mcp/tool_factory.rb +3 -6
  76. data/lib/riffer/mcp.rb +2 -23
  77. data/lib/riffer/messages/assistant/reasoning_part.rb +73 -0
  78. data/lib/riffer/messages/assistant/tool_call.rb +55 -0
  79. data/lib/riffer/messages/assistant.rb +43 -15
  80. data/lib/riffer/messages/base.rb +5 -33
  81. data/lib/riffer/messages/system.rb +8 -1
  82. data/lib/riffer/messages/tool.rb +15 -13
  83. data/lib/riffer/messages/{file_part.rb → user/file_part.rb} +16 -40
  84. data/lib/riffer/messages/user.rb +11 -4
  85. data/lib/riffer/params/boolean.rb +1 -5
  86. data/lib/riffer/params/param.rb +15 -31
  87. data/lib/riffer/params.rb +15 -39
  88. data/lib/riffer/providers/amazon_bedrock.rb +104 -44
  89. data/lib/riffer/providers/anthropic.rb +11 -26
  90. data/lib/riffer/providers/azure_open_ai.rb +3 -8
  91. data/lib/riffer/providers/base.rb +30 -32
  92. data/lib/riffer/providers/finish_reason.rb +0 -6
  93. data/lib/riffer/providers/gemini/client.rb +4 -17
  94. data/lib/riffer/providers/gemini.rb +6 -12
  95. data/lib/riffer/providers/mock.rb +18 -27
  96. data/lib/riffer/providers/open_ai.rb +12 -21
  97. data/lib/riffer/providers/open_router.rb +109 -33
  98. data/lib/riffer/providers/repository.rb +2 -14
  99. data/lib/riffer/providers/token_usage.rb +19 -12
  100. data/lib/riffer/registrable.rb +11 -45
  101. data/lib/riffer/runner/fibers.rb +1 -6
  102. data/lib/riffer/runner/sequential.rb +0 -1
  103. data/lib/riffer/runner/threaded.rb +0 -3
  104. data/lib/riffer/runner.rb +0 -3
  105. data/lib/riffer/skills/activate_tool.rb +0 -3
  106. data/lib/riffer/skills/adapter.rb +0 -8
  107. data/lib/riffer/skills/backend.rb +2 -7
  108. data/lib/riffer/skills/config.rb +13 -17
  109. data/lib/riffer/skills/context.rb +0 -29
  110. data/lib/riffer/skills/filesystem_backend.rb +0 -7
  111. data/lib/riffer/skills/frontmatter.rb +2 -16
  112. data/lib/riffer/skills/markdown_adapter.rb +3 -6
  113. data/lib/riffer/skills/xml_adapter.rb +0 -3
  114. data/lib/riffer/stream_events/base.rb +0 -3
  115. data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
  116. data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
  117. data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
  118. data/lib/riffer/stream_events/interrupt.rb +2 -12
  119. data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
  120. data/lib/riffer/stream_events/reasoning_done.rb +6 -8
  121. data/lib/riffer/stream_events/skill_activation.rb +0 -3
  122. data/lib/riffer/stream_events/text_delta.rb +0 -2
  123. data/lib/riffer/stream_events/text_done.rb +0 -2
  124. data/lib/riffer/stream_events/token_usage_done.rb +0 -2
  125. data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
  126. data/lib/riffer/stream_events/tool_call_done.rb +0 -5
  127. data/lib/riffer/stream_events/web_search_done.rb +0 -3
  128. data/lib/riffer/stream_events/web_search_status.rb +1 -5
  129. data/lib/riffer/testing/minitest.rb +4 -5
  130. data/lib/riffer/testing.rb +5 -38
  131. data/lib/riffer/tool.rb +2 -28
  132. data/lib/riffer/tools/response.rb +3 -32
  133. data/lib/riffer/tools/runtime/fibers.rb +0 -6
  134. data/lib/riffer/tools/runtime/inline.rb +0 -1
  135. data/lib/riffer/tools/runtime/threaded.rb +0 -6
  136. data/lib/riffer/tools/runtime.rb +8 -30
  137. data/lib/riffer/tools/toolable.rb +0 -37
  138. data/lib/riffer/tracing/capture.rb +5 -9
  139. data/lib/riffer/tracing/no_op.rb +0 -7
  140. data/lib/riffer/tracing/otel.rb +7 -16
  141. data/lib/riffer/tracing/stream_recorder.rb +0 -7
  142. data/lib/riffer/tracing.rb +4 -27
  143. data/lib/riffer/version.rb +1 -1
  144. data/lib/riffer.rb +2 -30
  145. data/sig/generated/riffer/agent/config.rbs +24 -46
  146. data/sig/generated/riffer/agent/context.rbs +2 -26
  147. data/sig/generated/riffer/agent/outcome.rbs +0 -20
  148. data/sig/generated/riffer/agent/response.rbs +4 -27
  149. data/sig/generated/riffer/agent/run.rbs +12 -31
  150. data/sig/generated/riffer/agent/serializer.rbs +2 -27
  151. data/sig/generated/riffer/agent/session/repair.rbs +2 -10
  152. data/sig/generated/riffer/agent/session.rbs +10 -36
  153. data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
  154. data/sig/generated/riffer/agent/structured_output.rbs +0 -7
  155. data/sig/generated/riffer/agent.rbs +25 -125
  156. data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
  157. data/sig/generated/riffer/config/anthropic.rbs +15 -0
  158. data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
  159. data/sig/generated/riffer/config/evals.rbs +13 -0
  160. data/sig/generated/riffer/config/files.rbs +43 -0
  161. data/sig/generated/riffer/config/gemini.rbs +15 -0
  162. data/sig/generated/riffer/config/mcp.rbs +19 -0
  163. data/sig/generated/riffer/config/open_ai.rbs +21 -0
  164. data/sig/generated/riffer/config/open_router.rbs +15 -0
  165. data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
  166. data/sig/generated/riffer/config/pricing.rbs +31 -0
  167. data/sig/generated/riffer/config/skills.rbs +19 -0
  168. data/sig/generated/riffer/config/tracing.rbs +25 -0
  169. data/sig/generated/riffer/config.rbs +2 -307
  170. data/sig/generated/riffer/evals/evaluator.rbs +12 -21
  171. data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
  172. data/sig/generated/riffer/evals/judge.rbs +4 -8
  173. data/sig/generated/riffer/evals/result.rbs +1 -11
  174. data/sig/generated/riffer/evals/run_result.rbs +0 -12
  175. data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
  176. data/sig/generated/riffer/files/resolver.rbs +6 -13
  177. data/sig/generated/riffer/guardrail.rbs +2 -22
  178. data/sig/generated/riffer/guardrails/modification.rbs +0 -6
  179. data/sig/generated/riffer/guardrails/result.rbs +0 -15
  180. data/sig/generated/riffer/guardrails/runner.rbs +0 -11
  181. data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
  182. data/sig/generated/riffer/guardrails.rbs +0 -2
  183. data/sig/generated/riffer/helpers/boolean.rbs +0 -4
  184. data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
  185. data/sig/generated/riffer/helpers/deep_dup.rbs +13 -0
  186. data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
  187. data/sig/generated/riffer/helpers/identifier.rbs +0 -12
  188. data/sig/generated/riffer/helpers/validate.rbs +21 -0
  189. data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
  190. data/sig/generated/riffer/mcp/client.rbs +0 -7
  191. data/sig/generated/riffer/mcp/manifest.rbs +3 -7
  192. data/sig/generated/riffer/mcp/registration.rbs +0 -10
  193. data/sig/generated/riffer/mcp/registry.rbs +0 -9
  194. data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
  195. data/sig/generated/riffer/mcp/tool.rbs +0 -4
  196. data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
  197. data/sig/generated/riffer/mcp.rbs +2 -22
  198. data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +45 -0
  199. data/sig/generated/riffer/messages/assistant/tool_call.rbs +34 -0
  200. data/sig/generated/riffer/messages/assistant.rbs +25 -31
  201. data/sig/generated/riffer/messages/base.rbs +0 -12
  202. data/sig/generated/riffer/messages/system.rbs +4 -1
  203. data/sig/generated/riffer/messages/tool.rbs +4 -10
  204. data/sig/generated/riffer/messages/user/file_part.rbs +76 -0
  205. data/sig/generated/riffer/messages/user.rbs +7 -5
  206. data/sig/generated/riffer/params/boolean.rbs +1 -4
  207. data/sig/generated/riffer/params/param.rbs +7 -31
  208. data/sig/generated/riffer/params.rbs +8 -38
  209. data/sig/generated/riffer/providers/amazon_bedrock.rbs +33 -29
  210. data/sig/generated/riffer/providers/anthropic.rbs +2 -12
  211. data/sig/generated/riffer/providers/azure_open_ai.rbs +0 -8
  212. data/sig/generated/riffer/providers/base.rbs +18 -34
  213. data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
  214. data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
  215. data/sig/generated/riffer/providers/gemini.rbs +4 -10
  216. data/sig/generated/riffer/providers/mock.rbs +6 -27
  217. data/sig/generated/riffer/providers/open_ai.rbs +6 -13
  218. data/sig/generated/riffer/providers/open_router.rbs +37 -18
  219. data/sig/generated/riffer/providers/repository.rbs +2 -14
  220. data/sig/generated/riffer/providers/token_usage.rbs +9 -12
  221. data/sig/generated/riffer/registrable.rbs +0 -45
  222. data/sig/generated/riffer/runner/fibers.rbs +0 -6
  223. data/sig/generated/riffer/runner/sequential.rbs +0 -1
  224. data/sig/generated/riffer/runner/threaded.rbs +0 -3
  225. data/sig/generated/riffer/runner.rbs +0 -3
  226. data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
  227. data/sig/generated/riffer/skills/adapter.rbs +0 -8
  228. data/sig/generated/riffer/skills/backend.rbs +2 -7
  229. data/sig/generated/riffer/skills/config.rbs +6 -17
  230. data/sig/generated/riffer/skills/context.rbs +0 -29
  231. data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
  232. data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
  233. data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
  234. data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
  235. data/sig/generated/riffer/stream_events/base.rbs +0 -3
  236. data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
  237. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
  238. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
  239. data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
  240. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
  241. data/sig/generated/riffer/stream_events/reasoning_done.rbs +4 -6
  242. data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
  243. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  244. data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
  245. data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
  246. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
  247. data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
  248. data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
  249. data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
  250. data/sig/generated/riffer/testing.rbs +0 -38
  251. data/sig/generated/riffer/tool.rbs +0 -27
  252. data/sig/generated/riffer/tools/response.rbs +3 -29
  253. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
  254. data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
  255. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
  256. data/sig/generated/riffer/tools/runtime.rbs +4 -26
  257. data/sig/generated/riffer/tools/toolable.rbs +0 -37
  258. data/sig/generated/riffer/tracing/capture.rbs +6 -9
  259. data/sig/generated/riffer/tracing/no_op.rbs +0 -7
  260. data/sig/generated/riffer/tracing/otel.rbs +7 -16
  261. data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
  262. data/sig/generated/riffer/tracing.rbs +4 -23
  263. data/sig/generated/riffer.rbs +2 -29
  264. data/sig/manual/riffer/helpers/deep_dup.rbs +5 -0
  265. data/sig/manual/riffer/helpers/validate.rbs +5 -0
  266. metadata +39 -3
  267. data/sig/generated/riffer/messages/file_part.rbs +0 -101
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::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,25 +1,16 @@
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?
17
7
  # @rbs self.@judge_model: String?
8
+ # @rbs self.@identifier: String?
18
9
  # @rbs @judge: Riffer::Evals::Judge?
19
10
 
11
+ DEFAULT_IDENTIFIER = "riffer/judge" #: String
12
+
20
13
  class << self
21
- # Gets or sets the evaluation instructions (criteria and scoring rubric).
22
- #
23
14
  #--
24
15
  #: (?String?) -> String?
25
16
  def instructions(value = nil)
@@ -28,8 +19,6 @@ class Riffer::Evals::Evaluator
28
19
  @instructions = value.to_s
29
20
  end
30
21
 
31
- # Gets or sets whether higher scores are better.
32
- #
33
22
  #--
34
23
  #: (?bool?) -> bool
35
24
  def higher_is_better(value = nil)
@@ -42,8 +31,6 @@ class Riffer::Evals::Evaluator
42
31
  @higher_is_better = value
43
32
  end
44
33
 
45
- # Gets or sets the judge model for LLM-as-judge evaluations.
46
- #
47
34
  #--
48
35
  #: (?String?) -> String?
49
36
  def judge_model(value = nil)
@@ -51,11 +38,26 @@ class Riffer::Evals::Evaluator
51
38
 
52
39
  @judge_model = value.to_s
53
40
  end
41
+
42
+ #--
43
+ #: (?String?) -> String
44
+ def identifier(value = nil)
45
+ return @identifier = value.to_s if value
46
+
47
+ @identifier || derived_identifier
48
+ end
49
+
50
+ private
51
+
52
+ #--
53
+ #: () -> String
54
+ def derived_identifier
55
+ # Anonymous classes derive an empty identifier.
56
+ derived = Riffer::Helpers::Identifier.for(self)
57
+ derived.empty? ? DEFAULT_IDENTIFIER : derived
58
+ end
54
59
  end
55
60
 
56
- # Evaluates an input/output pair. The default calls the judge with the
57
- # class-level +instructions+; override for custom logic (e.g. rule-based
58
- # evaluators).
59
61
  #--
60
62
  #: (input: String | Array[Hash[Symbol, untyped] | Riffer::Messages::Base], output: String, ?ground_truth: String?, ?messages: Array[Riffer::Messages::Base]) -> Riffer::Evals::Result
61
63
  def evaluate(input:, output:, ground_truth: nil, messages: [])
@@ -94,8 +96,6 @@ class Riffer::Evals::Evaluator
94
96
 
95
97
  protected
96
98
 
97
- # Returns a Judge instance configured for this evaluator.
98
- #
99
99
  #--
100
100
  #: () -> Riffer::Evals::Judge
101
101
  def judge
@@ -106,11 +106,10 @@ class Riffer::Evals::Evaluator
106
106
  "No judge model configured. Set judge_model on the evaluator or Riffer.config.evals.judge_model"
107
107
  end
108
108
 
109
- Riffer::Evals::Judge.new(model: model)
109
+ Riffer::Evals::Judge.new(model: model, tags: { "kind" => "judge", "agent" => self.class.identifier })
110
110
  end
111
111
  end
112
112
 
113
- # Builds a Result for this evaluator.
114
113
  #--
115
114
  #: (score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?token_usage: Riffer::Providers::TokenUsage?) -> Riffer::Evals::Result
116
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,34 +24,35 @@ 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
28
+ attr_reader :tags #: Hash[String, String] # @dynamic tags
32
29
 
33
- # Raises Riffer::ArgumentError unless +model+ is "provider/model" format.
34
30
  #--
35
- #: (model: String) -> void
36
- def initialize(model:)
31
+ #: (model: String, ?tags: Hash[String, String]) -> void
32
+ def initialize(model:, tags: {})
37
33
  provider_name, model_name = model.split("/", 2)
38
34
  unless [provider_name, model_name].all? { |part| part.is_a?(String) && !part.strip.empty? }
39
35
  raise Riffer::ArgumentError, "Invalid model string: #{model}"
40
36
  end
41
37
 
42
38
  @model = model
39
+ @tags = tags
43
40
  end
44
41
 
45
- # Evaluates an input/output pair using the configured LLM.
46
42
  #--
47
43
  #: (instructions: String, input: String, output: String, ?ground_truth: String?) -> Hash[Symbol, untyped]
48
44
  def evaluate(instructions:, input:, output:, ground_truth: nil)
49
45
  system_message = build_system_message(instructions)
50
46
  user_message = build_user_message(input: input, output: output, ground_truth: ground_truth)
51
47
 
52
- response = provider_instance.generate_text(
48
+ options = {
53
49
  system: system_message,
54
50
  prompt: user_message,
55
51
  model: model_name,
56
52
  tools: [EvaluationTool],
57
- )
53
+ } #: Hash[Symbol, untyped]
54
+ options[:tags] = tags unless tags.empty?
55
+ response = provider_instance.generate_text(**options)
58
56
 
59
57
  parse_tool_response(response)
60
58
  end
@@ -111,7 +109,7 @@ class Riffer::Evals::Judge
111
109
  def parse_tool_response(response)
112
110
  tool_call = response.tool_calls.fetch(0) { raise Riffer::Error, "Invalid judge response: no tool call found" }
113
111
 
114
- parsed = JSON.parse(tool_call[:arguments], symbolize_names: true)
112
+ parsed = JSON.parse(tool_call.arguments, symbolize_names: true)
115
113
  score = parsed[:score]
116
114
  reason = parsed[:reason]
117
115
 
@@ -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