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
@@ -3,9 +3,6 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Handles tool call execution for an agent, composing with a Riffer::Runner for
7
- # concurrency. Subclass and override +dispatch_tool_call+ to customize dispatch
8
- # (e.g. HTTP, gRPC).
9
6
  class Riffer::Tools::Runtime
10
7
  # @rbs @runner: Riffer::Runner
11
8
 
@@ -20,17 +17,15 @@ class Riffer::Tools::Runtime
20
17
  @runner = runner
21
18
  end
22
19
 
23
- # Executes a batch of tool calls, returning <tt>[tool_call, response]</tt> pairs.
24
20
  #--
25
21
  #: (Array[Riffer::Messages::Assistant::ToolCall], tools: Array[singleton(Riffer::Tool)], context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?, ?tags: Hash[String, String]) -> Array[[Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]]
26
22
  def execute(tool_calls, tools:, context:, assistant_message: nil, tags: {})
27
23
  # Each Runner worker runs in its own thread/fiber, where the OTEL context
28
24
  # starts empty — capture here so the execute_tool span parents correctly.
29
- # tags are an ordinary local captured in the block, so they reach the
30
- # worker's span without re-propagation.
31
25
  trace_context = Riffer::Tracing.current_context
32
26
  @runner.map(tool_calls, context: context) do |tool_call|
33
27
  Riffer::Tracing.with_context(trace_context) do
28
+ # Outside around_tool_call so host enrichment spans nest beneath it.
34
29
  instrument_tool_call(tool_call, tags) do
35
30
  around_tool_call(tool_call, context: context, assistant_message: assistant_message) do
36
31
  dispatch_tool_call(tool_call, tools: tools, context: context, assistant_message: assistant_message)
@@ -40,20 +35,7 @@ class Riffer::Tools::Runtime
40
35
  end
41
36
  end
42
37
 
43
- # Hook wrapping each tool call; override in subclasses to instrument or
44
- # customize. Must +yield+ to continue.
45
- #
46
- # class InstrumentedRuntime < Riffer::Tools::Runtime::Inline
47
- # private
48
- #
49
- # def around_tool_call(tool_call, context:, assistant_message: nil)
50
- # start = Time.now
51
- # result = yield
52
- # Rails.logger.info("Tool #{tool_call.name} took #{Time.now - start}s")
53
- # result
54
- # end
55
- # end
56
- #
38
+ # Overrides must +yield+ and return its Response.
57
39
  #--
58
40
  #: (Riffer::Messages::Assistant::ToolCall, context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) { () -> Riffer::Tools::Response } -> Riffer::Tools::Response
59
41
  def around_tool_call(_tool_call, context:, assistant_message: nil)
@@ -73,6 +55,7 @@ class Riffer::Tools::Runtime
73
55
  [tool_call, result] #: [Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]
74
56
  end
75
57
 
58
+ # Subclasses override this to dispatch elsewhere (e.g. HTTP, gRPC).
76
59
  #--
77
60
  #: (Riffer::Messages::Assistant::ToolCall, tools: Array[singleton(Riffer::Tool)], context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) -> Riffer::Tools::Response
78
61
  def dispatch_tool_call(tool_call, tools:, context:, assistant_message: nil)
@@ -101,14 +84,13 @@ class Riffer::Tools::Runtime
101
84
  end
102
85
 
103
86
  #--
104
- #: (String?) -> untyped
87
+ #: (String) -> untyped
105
88
  def parse_arguments(arguments)
106
- return {} if arguments.nil? || arguments.empty?
89
+ return {} if arguments.empty?
107
90
 
108
91
  JSON.parse(arguments, symbolize_names: true)
109
92
  end
110
93
 
111
- # Emitted outside +around_tool_call+ so host enrichment spans nest beneath it.
112
94
  #--
113
95
  #: [R] (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> R } -> R
114
96
  def in_tool_span(tool_call, tags = {})
@@ -137,23 +119,20 @@ class Riffer::Tools::Runtime
137
119
  }.merge(tag_attributes(tags))
138
120
  end
139
121
 
140
- # Maps normalized tags to their namespaced span attribute form. An empty map
141
- # yields an empty hash, so merging it is a no-op.
142
122
  #--
143
123
  #: (Hash[String, String]) -> Hash[String, String]
144
124
  def tag_attributes(tags)
145
125
  tags.transform_keys { |key| "riffer.tag.#{key}" }
146
126
  end
147
127
 
148
- # A deliberate error Response is a handled outcome, so its status stays unset.
149
- # An error status is reserved for a Response carrying the exception it was
150
- # folded from — the tool failed for a reason nobody anticipated.
151
128
  #--
152
129
  #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
153
130
  def record_tool_outcome(span, result)
154
131
  error_type = result.error_type
155
132
  span.set_attribute("error.type", error_type.to_s) if error_type
156
133
 
134
+ # A deliberate error Response is a handled outcome, so only one carrying the
135
+ # exception it was folded from gets an error status.
157
136
  exception = result.exception
158
137
  if exception
159
138
  span.record_exception(exception)
@@ -168,8 +147,7 @@ class Riffer::Tools::Runtime
168
147
  def capture_tool_arguments(span, tool_call)
169
148
  return unless capture_tool_content?(span)
170
149
 
171
- arguments = tool_call.arguments
172
- span.set_attribute("gen_ai.tool.call.arguments", arguments) if arguments
150
+ span.set_attribute("gen_ai.tool.call.arguments", tool_call.arguments)
173
151
  end
174
152
 
175
153
  #--
@@ -1,20 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Shared class-level DSL for anything that presents as a tool to an LLM. Extend
5
- # it to make a class discoverable as a tool; instance-level execution (+call+,
6
- # +call_with_validation+) lives on Riffer::Tool instead.
7
- #
8
- # class MyTool
9
- # extend Riffer::Tools::Toolable
10
- #
11
- # description "Does something useful"
12
- #
13
- # params do
14
- # required :input, String
15
- # end
16
- # end
17
- #
18
4
  # @rbs module-self Module
19
5
  module Riffer::Tools::Toolable
20
6
  # @rbs self.@extenders: Array[Module]?
@@ -26,8 +12,6 @@ module Riffer::Tools::Toolable
26
12
 
27
13
  DEFAULT_TIMEOUT = 10 #: Integer
28
14
 
29
- # Tracks all classes that extend Toolable.
30
- #
31
15
  #--
32
16
  #: (Module) -> void
33
17
  def self.extended(base)
@@ -35,16 +19,12 @@ module Riffer::Tools::Toolable
35
19
  extenders << base
36
20
  end
37
21
 
38
- # Returns all classes that have extended Toolable.
39
- #
40
22
  #--
41
23
  #: () -> Array[Module]
42
24
  def self.all
43
25
  @extenders || []
44
26
  end
45
27
 
46
- # Gets or sets the tool description.
47
- #
48
28
  #--
49
29
  #: (?String?) -> String?
50
30
  def description(value = nil)
@@ -53,8 +33,6 @@ module Riffer::Tools::Toolable
53
33
  @description = value.to_s
54
34
  end
55
35
 
56
- # Gets or sets the tool identifier/name.
57
- #
58
36
  #--
59
37
  #: (?String?) -> String
60
38
  def identifier(value = nil)
@@ -63,8 +41,6 @@ module Riffer::Tools::Toolable
63
41
  @identifier = value.to_s
64
42
  end
65
43
 
66
- # Alias for identifier — used by providers.
67
- #
68
44
  #--
69
45
  #: (?String?) -> String
70
46
  def name(value = nil)
@@ -73,8 +49,6 @@ module Riffer::Tools::Toolable
73
49
  identifier
74
50
  end
75
51
 
76
- # Gets or sets the tool timeout in seconds.
77
- #
78
52
  #--
79
53
  #: (?(Integer | Float)?) -> (Integer | Float)
80
54
  def timeout(value = nil)
@@ -83,8 +57,6 @@ module Riffer::Tools::Toolable
83
57
  @timeout = value.to_f
84
58
  end
85
59
 
86
- # Defines parameters using the Params DSL.
87
- #
88
60
  #--
89
61
  #: () ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
90
62
  def params(&block)
@@ -95,15 +67,12 @@ module Riffer::Tools::Toolable
95
67
  @params_builder = builder
96
68
  end
97
69
 
98
- # Returns the JSON Schema for the tool's parameters.
99
- #
100
70
  #--
101
71
  #: (?strict: bool) -> Hash[Symbol, untyped]
102
72
  def parameters_schema(strict: false)
103
73
  @params_builder&.to_json_schema(strict: strict) || empty_schema
104
74
  end
105
75
 
106
- # Returns the kind of toolable entity; defaults to +:tool+.
107
76
  #--
108
77
  #: (?Symbol?) -> Symbol
109
78
  def kind(value = nil)
@@ -112,8 +81,6 @@ module Riffer::Tools::Toolable
112
81
  @kind = value.to_sym
113
82
  end
114
83
 
115
- # Returns a provider-agnostic tool schema hash.
116
- #
117
84
  #--
118
85
  #: (?strict: bool) -> Hash[Symbol, untyped]
119
86
  def to_tool_schema(strict: false)
@@ -124,10 +91,6 @@ module Riffer::Tools::Toolable
124
91
  }
125
92
  end
126
93
 
127
- # Validates that the minimum required metadata is present for LLM tool use.
128
- #
129
- # Raises Riffer::ArgumentError if validation fails.
130
- #
131
94
  #--
132
95
  #: () -> true
133
96
  def validate_as_tool!
@@ -3,9 +3,6 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Serializes riffer messages into the GenAI semconv JSON message structure
7
- # for opt-in span content capture. File parts become metadata-only stubs —
8
- # bytes and URLs never reach a span attribute.
9
6
  module Riffer::Tracing::Capture # :nodoc: all
10
7
  extend self
11
8
 
@@ -71,20 +68,19 @@ module Riffer::Tracing::Capture # :nodoc: all
71
68
  end
72
69
 
73
70
  #--
74
- #: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
71
+ #: (Riffer::Messages::User::FilePart) -> Hash[Symbol, untyped]
75
72
  def file_part(file)
73
+ # Metadata only: file bytes and URLs must never reach a span attribute.
76
74
  part = { type: "file", media_type: file.media_type } #: Hash[Symbol, untyped]
77
75
  part[:name] = file.filename if file.filename
78
76
  part
79
77
  end
80
78
 
81
- # Semconv's tool_call part carries arguments as a JSON object; riffer holds
82
- # them as a string — parse so the captured payload isn't double-encoded.
79
+ # Semconv's tool_call part carries arguments as a JSON object, so a string
80
+ # would be double-encoded.
83
81
  #--
84
- #: (untyped) -> untyped
82
+ #: (String) -> untyped
85
83
  def parse_arguments(arguments)
86
- return arguments unless arguments.is_a?(String)
87
-
88
84
  JSON.parse(arguments)
89
85
  rescue JSON::ParserError
90
86
  arguments
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # No-op tracing backend, used when OTEL is unavailable or tracing is
5
- # disabled.
6
4
  module Riffer::Tracing::NoOp # :nodoc: all
7
5
  extend self
8
6
 
9
- # No-op stand-in for a span; answers <tt>recording?</tt> with +false+ so
10
- # callers can skip expensive attribute serialization.
11
7
  class Span
12
8
  #--
13
9
  #: (String, untyped) -> void
@@ -34,21 +30,18 @@ module Riffer::Tracing::NoOp # :nodoc: all
34
30
 
35
31
  SPAN = Span.new.freeze #: Riffer::Tracing::NoOp::Span
36
32
 
37
- # Yields the no-op span, ignoring all span options.
38
33
  #--
39
34
  #: [R] (String, **untyped) { (Riffer::Tracing::NoOp::Span) -> R } -> R
40
35
  def in_span(_name, **)
41
36
  yield SPAN
42
37
  end
43
38
 
44
- # Returns +nil+; there is no trace context without OTEL.
45
39
  #--
46
40
  #: () -> nil
47
41
  def current_context
48
42
  nil
49
43
  end
50
44
 
51
- # Yields immediately; there is no context to attach.
52
45
  #--
53
46
  #: [R] (untyped) { () -> R } -> R
54
47
  def with_context(_context)
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # OTEL-backed tracing backend. <tt>::OpenTelemetry</tt> constants appear only
5
- # inside method bodies here, so the gem loads and eager-loads cleanly when the
6
- # OpenTelemetry API is absent.
4
+ # <tt>::OpenTelemetry</tt> constants must appear only inside method bodies, so
5
+ # the gem eager-loads cleanly when the OpenTelemetry API is absent.
7
6
  class Riffer::Tracing::Otel # :nodoc: all
8
7
  SUPPORTED_API_VERSIONS = Gem::Requirement.new(">= 1.1", "< 2") #: Gem::Requirement
9
8
 
10
- # Wraps a live OTEL span behind the port's span surface, so callers never
11
- # touch <tt>::OpenTelemetry</tt> constants (status objects in particular).
9
+ # Keeps callers from touching <tt>::OpenTelemetry</tt> constants (status
10
+ # objects in particular).
12
11
  class Span
13
12
  # @rbs @otel_span: untyped
14
13
 
@@ -36,7 +35,6 @@ class Riffer::Tracing::Otel # :nodoc: all
36
35
  @otel_span.record_exception(exception)
37
36
  end
38
37
 
39
- # Marks the span status as error.
40
38
  #--
41
39
  #: (?String) -> void
42
40
  def error!(description = "")
@@ -51,9 +49,7 @@ class Riffer::Tracing::Otel # :nodoc: all
51
49
  end
52
50
 
53
51
  class << self
54
- # Builds a backend when the OpenTelemetry API is loadable at a supported
55
- # version; returns +nil+ so resolution falls back to NoOp. +provider+
56
- # defaults to the global <tt>OpenTelemetry.tracer_provider</tt>.
52
+ # Returns +nil+ so resolution falls back to NoOp.
57
53
  #--
58
54
  #: (?provider: untyped) -> Riffer::Tracing::Otel?
59
55
  def build(provider: nil)
@@ -69,7 +65,6 @@ class Riffer::Tracing::Otel # :nodoc: all
69
65
  new(provider: provider || ::OpenTelemetry.tracer_provider)
70
66
  end
71
67
 
72
- # Whether the OpenTelemetry API gem is loadable at a supported version.
73
68
  #--
74
69
  #: () -> bool
75
70
  def available?
@@ -79,9 +74,8 @@ class Riffer::Tracing::Otel # :nodoc: all
79
74
  supported?(version)
80
75
  end
81
76
 
82
- # Whether the given opentelemetry-api version is one riffer codes
83
- # against. The gem is undeclared, so this guard is the only protection
84
- # against an incompatible API.
77
+ # opentelemetry-api is undeclared, so this is the only protection against
78
+ # an incompatible API.
85
79
  #--
86
80
  #: (Gem::Version) -> bool
87
81
  def supported?(version)
@@ -109,7 +103,6 @@ class Riffer::Tracing::Otel # :nodoc: all
109
103
  @tracer = provider.tracer("riffer", Riffer::VERSION)
110
104
  end
111
105
 
112
- # Opens an OTEL span around the block, yielding the wrapped span.
113
106
  #--
114
107
  #: [R] (String, attributes: Hash[String, untyped]?, kind: Symbol) { (Riffer::Tracing::Otel::Span) -> R } -> R
115
108
  def in_span(name, attributes:, kind:)
@@ -118,14 +111,12 @@ class Riffer::Tracing::Otel # :nodoc: all
118
111
  end
119
112
  end
120
113
 
121
- # Returns the active OTEL context.
122
114
  #--
123
115
  #: () -> untyped
124
116
  def current_context
125
117
  ::OpenTelemetry::Context.current
126
118
  end
127
119
 
128
- # Runs the block with the given OTEL context active.
129
120
  #--
130
121
  #: [R] (untyped) { () -> R } -> R
131
122
  def with_context(context, &)
@@ -1,23 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Wraps a stream yielder to observe terminal events for span stamping while
5
- # forwarding every event downstream untouched.
6
4
  class Riffer::Tracing::StreamRecorder # :nodoc: all
7
5
  # @rbs @yielder: Enumerator::Yielder
8
6
  # @rbs @clock: ^() -> Float
9
7
  # @rbs @started_at: Float
10
8
 
11
9
  attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
12
-
13
10
  attr_reader :time_to_first_chunk #: Float? # @dynamic time_to_first_chunk
14
-
15
11
  attr_reader :finish_reason #: Symbol? # @dynamic finish_reason
16
-
17
12
  attr_reader :raw_finish_reason #: String? # @dynamic raw_finish_reason
18
-
19
13
  attr_reader :content #: String? # @dynamic content
20
-
21
14
  attr_reader :tool_calls #: Array[Riffer::Messages::Assistant::ToolCall] # @dynamic tool_calls
22
15
 
23
16
  #--
@@ -1,21 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Internal tracing port — emits OTEL spans when the host bundles the
5
- # OpenTelemetry API and no-ops otherwise, so riffer never declares an OTEL
6
- # dependency.
4
+ # No-ops unless the host bundles the OpenTelemetry API, so riffer never
5
+ # declares an OTEL dependency.
7
6
  module Riffer::Tracing # :nodoc: all
8
7
  extend self
9
8
 
10
- # @rbs @backend: untyped
11
-
12
- MUTEX = Mutex.new #: Mutex
13
-
14
9
  # The Ruby API cannot attach a schema URL to a tracer, so the semconv pin
15
10
  # lives here as the documented contract version.
16
11
  SCHEMA_URL = "https://opentelemetry.io/schemas/1.37.0" #: String
17
12
 
18
- # Opens a span around the block, yielding the span.
19
13
  #--
20
14
  #: [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
21
15
  def in_span(name, attributes: nil, kind: :internal, &)
@@ -24,8 +18,7 @@ module Riffer::Tracing # :nodoc: all
24
18
  backend.in_span(name, attributes: attributes, kind: kind, &)
25
19
  end
26
20
 
27
- # Returns the active trace context, for re-attachment across fiber or
28
- # thread boundaries.
21
+ # For re-attachment across fiber or thread boundaries.
29
22
  #--
30
23
  #: () -> untyped
31
24
  def current_context
@@ -34,8 +27,7 @@ module Riffer::Tracing # :nodoc: all
34
27
  backend.current_context
35
28
  end
36
29
 
37
- # Runs the block with the given trace context active; +nil+ passes through
38
- # so captures taken while tracing was dark stay harmless.
30
+ # +nil+ passes through so captures taken while tracing was off stay harmless.
39
31
  #--
40
32
  #: [R] (untyped) { () -> R } -> R
41
33
  def with_context(context, &)
@@ -44,8 +36,6 @@ module Riffer::Tracing # :nodoc: all
44
36
  backend.with_context(context, &)
45
37
  end
46
38
 
47
- # Stamps token usage onto the span — the <tt>gen_ai.usage.*</tt> counts and,
48
- # when the model was priced, <tt>riffer.cost</tt>.
49
39
  #--
50
40
  #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Providers::TokenUsage?) -> void
51
41
  def record_usage(span, usage)
@@ -58,24 +48,11 @@ module Riffer::Tracing # :nodoc: all
58
48
  span.set_attribute("riffer.cost", usage.cost) if usage.cost
59
49
  end
60
50
 
61
- # Discards the resolved backend so the next span re-resolves it.
62
- #--
63
- #: () -> void
64
- def reset!
65
- MUTEX.synchronize { @backend = nil }
66
- end
67
-
68
51
  private
69
52
 
70
53
  #--
71
54
  #: () -> untyped
72
55
  def backend
73
- @backend || MUTEX.synchronize { @backend ||= resolve_backend }
74
- end
75
-
76
- #--
77
- #: () -> untyped
78
- def resolve_backend
79
56
  Riffer.config.tracing.backend || NoOp
80
57
  end
81
58
  end
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.47.2" #: String
5
+ VERSION = "0.49.0" #: String
6
6
  end
data/lib/riffer.rb CHANGED
@@ -3,7 +3,6 @@
3
3
 
4
4
  require "zeitwerk"
5
5
 
6
- # Riffer is the main module for the Riffer AI framework.
7
6
  loader = Zeitwerk::Loader.for_gem
8
7
  loader.inflector.inflect(
9
8
  "open_ai" => "OpenAI",
@@ -24,78 +23,51 @@ module Riffer
24
23
 
25
24
  # @rbs @config: Riffer::Config?
26
25
 
27
- # Base error class for Riffer.
28
26
  class Error < StandardError; end
29
27
 
30
- # Raised when invalid arguments are provided.
31
28
  class ArgumentError < ::ArgumentError; end
32
29
 
33
- # Raised when tool parameter validation fails.
34
30
  class ValidationError < Error; end
35
31
 
36
- # Raised inside a tool's +call+ when execution exceeds the configured
37
- # timeout. Rescue it in the tool to clean up; otherwise it becomes a
32
+ # Rescue it in the tool to clean up; otherwise it becomes a
38
33
  # +:timeout_error+ response.
39
34
  class TimeoutError < Error; end
40
35
 
41
- # Raised when a tool encounters an expected execution error.
42
36
  class ToolExecutionError < Error; end
43
37
 
44
- # Raised when a provider's stream ends before its terminal event (Bedrock's
45
- # messageStop, Anthropic's message_stop, OpenAI's response.completed /
46
- # incomplete / failed), so a truncated or empty response is never returned
47
- # as a complete message. Events already yielded were delivered; the
48
- # request is safe to retry.
38
+ # Events already yielded were delivered; the request is safe to retry.
49
39
  class IncompleteStreamError < Error; end
50
40
 
51
- # Base error for a file attachment that can't be resolved for the provider.
52
41
  class FileError < Error; end
53
42
 
54
- # Raised when the provider can't accept the file at all.
55
43
  class FileUnsupportedError < FileError; end
56
44
 
57
- # Raised when a download is required but Riffer.config.files.allow_downloads is false.
58
45
  class FileDownloadsDisabledError < FileError; end
59
46
 
60
- # Raised when a message carries more files than Riffer.config.files.max_per_message allows.
61
47
  class TooManyFilesError < FileError; end
62
48
 
63
- # Raised when there's an issue downloading a file
64
49
  class FileDownloadError < FileError; end
65
50
 
66
- # Raised when a file size exceeds Riffer.config.files.max_bytes
67
51
  class FileTooLargeError < FileError; end
68
52
 
69
- # Raised when a downloaded or inline file's sha256 doesn't match.
70
53
  class FileChecksumMismatchError < FileError; end
71
54
 
72
- # Raised when a FilePart's data isn't valid base64
73
55
  class FileEncodingError < FileError; end
74
56
 
75
- # Raised when two registered subclasses share the same identifier.
76
57
  class DuplicateIdentifierError < Error; end
77
58
 
78
- # Returns the Riffer configuration.
79
- #
80
59
  #--
81
60
  #: () -> Riffer::Config
82
61
  def config
83
62
  @config ||= Config.new
84
63
  end
85
64
 
86
- # Yields the configuration for block-based setup.
87
- #
88
- # Riffer.configure do |config|
89
- # config.openai.api_key = ENV['OPENAI_API_KEY']
90
- # end
91
- #
92
65
  #--
93
66
  #: () ?{ (Riffer::Config) -> void } -> void
94
67
  def configure(&)
95
68
  yield config if block_given?
96
69
  end
97
70
 
98
- # Returns the gem version.
99
71
  #--
100
72
  #: () -> String
101
73
  def version