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.
- checksums.yaml +4 -4
- data/.claude/rules/comments.md +2 -4
- data/.claude/rules/rbs-inline.md +2 -12
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +42 -0
- data/docs/AGENTS.md +39 -7
- data/docs/AGENT_LIFECYCLE.md +14 -16
- data/docs/CONFIGURATION.md +23 -34
- data/docs/EVALS.md +2 -1
- data/docs/MCP.md +0 -4
- data/docs/MESSAGES.md +85 -17
- data/docs/STREAM_EVENTS.md +8 -4
- data/docs/TOOL_ADVANCED.md +1 -3
- data/docs/TRACING.md +2 -2
- data/docs/providers/AMAZON_BEDROCK.md +32 -1
- data/docs/providers/CUSTOM_PROVIDERS.md +56 -4
- data/docs/providers/GEMINI.md +1 -1
- data/docs/providers/MOCK_PROVIDER.md +17 -0
- data/docs/providers/OPENROUTER.md +18 -1
- data/docs-site/build.rb +0 -6
- data/docs-site/check.rb +1 -5
- data/lib/riffer/agent/config.rb +27 -44
- data/lib/riffer/agent/context.rb +2 -26
- data/lib/riffer/agent/outcome.rb +0 -20
- data/lib/riffer/agent/response.rb +4 -35
- data/lib/riffer/agent/run.rb +32 -48
- data/lib/riffer/agent/serializer.rb +10 -39
- data/lib/riffer/agent/session/repair.rb +3 -15
- data/lib/riffer/agent/session.rb +26 -38
- data/lib/riffer/agent/structured_output/result.rb +0 -8
- data/lib/riffer/agent/structured_output.rb +0 -7
- data/lib/riffer/agent.rb +31 -127
- data/lib/riffer/config/amazon_bedrock.rb +30 -0
- data/lib/riffer/config/anthropic.rb +21 -0
- data/lib/riffer/config/azure_open_ai.rb +30 -0
- data/lib/riffer/config/evals.rb +18 -0
- data/lib/riffer/config/files.rb +61 -0
- data/lib/riffer/config/gemini.rb +21 -0
- data/lib/riffer/config/mcp.rb +31 -0
- data/lib/riffer/config/open_ai.rb +30 -0
- data/lib/riffer/config/open_router.rb +21 -0
- data/lib/riffer/config/pricing/rates.rb +36 -0
- data/lib/riffer/config/pricing.rb +64 -0
- data/lib/riffer/config/skills.rb +38 -0
- data/lib/riffer/config/tracing.rb +43 -0
- data/lib/riffer/config.rb +3 -362
- data/lib/riffer/evals/evaluator.rb +22 -23
- data/lib/riffer/evals/evaluator_runner.rb +0 -15
- data/lib/riffer/evals/judge.rb +9 -11
- data/lib/riffer/evals/result.rb +1 -11
- data/lib/riffer/evals/run_result.rb +0 -12
- data/lib/riffer/evals/scenario_result.rb +0 -19
- data/lib/riffer/files/downloader.rb +2 -3
- data/lib/riffer/files/resolver.rb +5 -10
- data/lib/riffer/guardrail.rb +2 -22
- data/lib/riffer/guardrails/modification.rb +0 -8
- data/lib/riffer/guardrails/result.rb +0 -17
- data/lib/riffer/guardrails/runner.rb +2 -11
- data/lib/riffer/guardrails/tripwire.rb +0 -8
- data/lib/riffer/guardrails.rb +0 -2
- data/lib/riffer/helpers/boolean.rb +0 -4
- data/lib/riffer/helpers/call_or_value.rb +0 -3
- data/lib/riffer/helpers/deep_dup.rb +37 -0
- data/lib/riffer/helpers/dependencies.rb +0 -4
- data/lib/riffer/helpers/identifier.rb +3 -12
- data/lib/riffer/helpers/validate.rb +42 -0
- data/lib/riffer/mcp/authenticated_tool.rb +4 -12
- data/lib/riffer/mcp/client.rb +0 -7
- data/lib/riffer/mcp/manifest.rb +3 -7
- data/lib/riffer/mcp/registration.rb +0 -10
- data/lib/riffer/mcp/registry.rb +0 -9
- data/lib/riffer/mcp/search_tool.rb +0 -4
- data/lib/riffer/mcp/tool.rb +1 -4
- data/lib/riffer/mcp/tool_factory.rb +3 -6
- data/lib/riffer/mcp.rb +2 -23
- data/lib/riffer/messages/assistant/reasoning_part.rb +73 -0
- data/lib/riffer/messages/assistant/tool_call.rb +55 -0
- data/lib/riffer/messages/assistant.rb +43 -15
- data/lib/riffer/messages/base.rb +5 -33
- data/lib/riffer/messages/system.rb +8 -1
- data/lib/riffer/messages/tool.rb +15 -13
- data/lib/riffer/messages/{file_part.rb → user/file_part.rb} +16 -40
- data/lib/riffer/messages/user.rb +11 -4
- data/lib/riffer/params/boolean.rb +1 -5
- data/lib/riffer/params/param.rb +15 -31
- data/lib/riffer/params.rb +15 -39
- data/lib/riffer/providers/amazon_bedrock.rb +104 -44
- data/lib/riffer/providers/anthropic.rb +11 -26
- data/lib/riffer/providers/azure_open_ai.rb +3 -8
- data/lib/riffer/providers/base.rb +30 -32
- data/lib/riffer/providers/finish_reason.rb +0 -6
- data/lib/riffer/providers/gemini/client.rb +4 -17
- data/lib/riffer/providers/gemini.rb +6 -12
- data/lib/riffer/providers/mock.rb +18 -27
- data/lib/riffer/providers/open_ai.rb +12 -21
- data/lib/riffer/providers/open_router.rb +109 -33
- data/lib/riffer/providers/repository.rb +2 -14
- data/lib/riffer/providers/token_usage.rb +19 -12
- data/lib/riffer/registrable.rb +11 -45
- data/lib/riffer/runner/fibers.rb +1 -6
- data/lib/riffer/runner/sequential.rb +0 -1
- data/lib/riffer/runner/threaded.rb +0 -3
- data/lib/riffer/runner.rb +0 -3
- data/lib/riffer/skills/activate_tool.rb +0 -3
- data/lib/riffer/skills/adapter.rb +0 -8
- data/lib/riffer/skills/backend.rb +2 -7
- data/lib/riffer/skills/config.rb +13 -17
- data/lib/riffer/skills/context.rb +0 -29
- data/lib/riffer/skills/filesystem_backend.rb +0 -7
- data/lib/riffer/skills/frontmatter.rb +2 -16
- data/lib/riffer/skills/markdown_adapter.rb +3 -6
- data/lib/riffer/skills/xml_adapter.rb +0 -3
- data/lib/riffer/stream_events/base.rb +0 -3
- data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
- data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
- data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
- data/lib/riffer/stream_events/interrupt.rb +2 -12
- data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
- data/lib/riffer/stream_events/reasoning_done.rb +6 -8
- data/lib/riffer/stream_events/skill_activation.rb +0 -3
- data/lib/riffer/stream_events/text_delta.rb +0 -2
- data/lib/riffer/stream_events/text_done.rb +0 -2
- data/lib/riffer/stream_events/token_usage_done.rb +0 -2
- data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
- data/lib/riffer/stream_events/tool_call_done.rb +0 -5
- data/lib/riffer/stream_events/web_search_done.rb +0 -3
- data/lib/riffer/stream_events/web_search_status.rb +1 -5
- data/lib/riffer/testing/minitest.rb +4 -5
- data/lib/riffer/testing.rb +5 -38
- data/lib/riffer/tool.rb +2 -28
- data/lib/riffer/tools/response.rb +3 -32
- data/lib/riffer/tools/runtime/fibers.rb +0 -6
- data/lib/riffer/tools/runtime/inline.rb +0 -1
- data/lib/riffer/tools/runtime/threaded.rb +0 -6
- data/lib/riffer/tools/runtime.rb +8 -30
- data/lib/riffer/tools/toolable.rb +0 -37
- data/lib/riffer/tracing/capture.rb +5 -9
- data/lib/riffer/tracing/no_op.rb +0 -7
- data/lib/riffer/tracing/otel.rb +7 -16
- data/lib/riffer/tracing/stream_recorder.rb +0 -7
- data/lib/riffer/tracing.rb +4 -27
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -30
- data/sig/generated/riffer/agent/config.rbs +24 -46
- data/sig/generated/riffer/agent/context.rbs +2 -26
- data/sig/generated/riffer/agent/outcome.rbs +0 -20
- data/sig/generated/riffer/agent/response.rbs +4 -27
- data/sig/generated/riffer/agent/run.rbs +12 -31
- data/sig/generated/riffer/agent/serializer.rbs +2 -27
- data/sig/generated/riffer/agent/session/repair.rbs +2 -10
- data/sig/generated/riffer/agent/session.rbs +10 -36
- data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
- data/sig/generated/riffer/agent/structured_output.rbs +0 -7
- data/sig/generated/riffer/agent.rbs +25 -125
- data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
- data/sig/generated/riffer/config/anthropic.rbs +15 -0
- data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
- data/sig/generated/riffer/config/evals.rbs +13 -0
- data/sig/generated/riffer/config/files.rbs +43 -0
- data/sig/generated/riffer/config/gemini.rbs +15 -0
- data/sig/generated/riffer/config/mcp.rbs +19 -0
- data/sig/generated/riffer/config/open_ai.rbs +21 -0
- data/sig/generated/riffer/config/open_router.rbs +15 -0
- data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
- data/sig/generated/riffer/config/pricing.rbs +31 -0
- data/sig/generated/riffer/config/skills.rbs +19 -0
- data/sig/generated/riffer/config/tracing.rbs +25 -0
- data/sig/generated/riffer/config.rbs +2 -307
- data/sig/generated/riffer/evals/evaluator.rbs +12 -21
- data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
- data/sig/generated/riffer/evals/judge.rbs +4 -8
- data/sig/generated/riffer/evals/result.rbs +1 -11
- data/sig/generated/riffer/evals/run_result.rbs +0 -12
- data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
- data/sig/generated/riffer/files/resolver.rbs +6 -13
- data/sig/generated/riffer/guardrail.rbs +2 -22
- data/sig/generated/riffer/guardrails/modification.rbs +0 -6
- data/sig/generated/riffer/guardrails/result.rbs +0 -15
- data/sig/generated/riffer/guardrails/runner.rbs +0 -11
- data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
- data/sig/generated/riffer/guardrails.rbs +0 -2
- data/sig/generated/riffer/helpers/boolean.rbs +0 -4
- data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
- data/sig/generated/riffer/helpers/deep_dup.rbs +13 -0
- data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
- data/sig/generated/riffer/helpers/identifier.rbs +0 -12
- data/sig/generated/riffer/helpers/validate.rbs +21 -0
- data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
- data/sig/generated/riffer/mcp/client.rbs +0 -7
- data/sig/generated/riffer/mcp/manifest.rbs +3 -7
- data/sig/generated/riffer/mcp/registration.rbs +0 -10
- data/sig/generated/riffer/mcp/registry.rbs +0 -9
- data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
- data/sig/generated/riffer/mcp/tool.rbs +0 -4
- data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
- data/sig/generated/riffer/mcp.rbs +2 -22
- data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +45 -0
- data/sig/generated/riffer/messages/assistant/tool_call.rbs +34 -0
- data/sig/generated/riffer/messages/assistant.rbs +25 -31
- data/sig/generated/riffer/messages/base.rbs +0 -12
- data/sig/generated/riffer/messages/system.rbs +4 -1
- data/sig/generated/riffer/messages/tool.rbs +4 -10
- data/sig/generated/riffer/messages/user/file_part.rbs +76 -0
- data/sig/generated/riffer/messages/user.rbs +7 -5
- data/sig/generated/riffer/params/boolean.rbs +1 -4
- data/sig/generated/riffer/params/param.rbs +7 -31
- data/sig/generated/riffer/params.rbs +8 -38
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +33 -29
- data/sig/generated/riffer/providers/anthropic.rbs +2 -12
- data/sig/generated/riffer/providers/azure_open_ai.rbs +0 -8
- data/sig/generated/riffer/providers/base.rbs +18 -34
- data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
- data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
- data/sig/generated/riffer/providers/gemini.rbs +4 -10
- data/sig/generated/riffer/providers/mock.rbs +6 -27
- data/sig/generated/riffer/providers/open_ai.rbs +6 -13
- data/sig/generated/riffer/providers/open_router.rbs +37 -18
- data/sig/generated/riffer/providers/repository.rbs +2 -14
- data/sig/generated/riffer/providers/token_usage.rbs +9 -12
- data/sig/generated/riffer/registrable.rbs +0 -45
- data/sig/generated/riffer/runner/fibers.rbs +0 -6
- data/sig/generated/riffer/runner/sequential.rbs +0 -1
- data/sig/generated/riffer/runner/threaded.rbs +0 -3
- data/sig/generated/riffer/runner.rbs +0 -3
- data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
- data/sig/generated/riffer/skills/adapter.rbs +0 -8
- data/sig/generated/riffer/skills/backend.rbs +2 -7
- data/sig/generated/riffer/skills/config.rbs +6 -17
- data/sig/generated/riffer/skills/context.rbs +0 -29
- data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
- data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
- data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
- data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
- data/sig/generated/riffer/stream_events/base.rbs +0 -3
- data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
- data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
- data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
- data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
- data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +4 -6
- data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
- data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
- data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
- data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
- data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
- data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
- data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
- data/sig/generated/riffer/testing.rbs +0 -38
- data/sig/generated/riffer/tool.rbs +0 -27
- data/sig/generated/riffer/tools/response.rbs +3 -29
- data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
- data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
- data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
- data/sig/generated/riffer/tools/runtime.rbs +4 -26
- data/sig/generated/riffer/tools/toolable.rbs +0 -37
- data/sig/generated/riffer/tracing/capture.rbs +6 -9
- data/sig/generated/riffer/tracing/no_op.rbs +0 -7
- data/sig/generated/riffer/tracing/otel.rbs +7 -16
- data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
- data/sig/generated/riffer/tracing.rbs +4 -23
- data/sig/generated/riffer.rbs +2 -29
- data/sig/manual/riffer/helpers/deep_dup.rbs +5 -0
- data/sig/manual/riffer/helpers/validate.rbs +5 -0
- metadata +39 -3
- data/sig/generated/riffer/messages/file_part.rbs +0 -101
data/lib/riffer/tools/runtime.rb
CHANGED
|
@@ -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
|
-
#
|
|
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
|
|
87
|
+
#: (String) -> untyped
|
|
105
88
|
def parse_arguments(arguments)
|
|
106
|
-
return {} if arguments.
|
|
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
|
|
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
|
|
82
|
-
#
|
|
79
|
+
# Semconv's tool_call part carries arguments as a JSON object, so a string
|
|
80
|
+
# would be double-encoded.
|
|
83
81
|
#--
|
|
84
|
-
#: (
|
|
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
|
data/lib/riffer/tracing/no_op.rb
CHANGED
|
@@ -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)
|
data/lib/riffer/tracing/otel.rb
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
-
#
|
|
11
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
83
|
-
#
|
|
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
|
#--
|
data/lib/riffer/tracing.rb
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
data/lib/riffer/version.rb
CHANGED
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
|
-
#
|
|
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
|
-
#
|
|
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
|