riffer 0.27.2 → 0.29.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/.agents/architecture.md +18 -11
- data/.agents/code-style.md +1 -1
- data/.agents/rbs-inline.md +2 -2
- data/.agents/testing.md +9 -5
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +17 -10
- data/CHANGELOG.md +31 -0
- data/README.md +17 -18
- data/Steepfile +7 -1
- data/docs/03_AGENTS.md +34 -3
- data/docs/04_AGENT_LIFECYCLE.md +134 -86
- data/docs/05_AGENT_LOOP.md +2 -2
- data/docs/06_TOOLS.md +9 -4
- data/docs/07_TOOL_ADVANCED.md +23 -19
- data/docs/08_MESSAGES.md +28 -31
- data/docs/09_STREAM_EVENTS.md +1 -1
- data/docs/10_CONFIGURATION.md +25 -15
- data/docs/providers/01_PROVIDERS.md +6 -0
- data/docs/providers/06_MOCK_PROVIDER.md +2 -1
- data/docs/providers/07_CUSTOM_PROVIDERS.md +4 -4
- data/docs/providers/08_GEMINI.md +2 -2
- data/docs/providers/09_OPENROUTER.md +242 -0
- data/lib/riffer/agent/config.rb +173 -0
- data/lib/riffer/agent/context.rb +125 -0
- data/lib/riffer/agent/response.rb +11 -2
- data/lib/riffer/agent/run.rb +308 -0
- data/lib/riffer/agent/session/repair.rb +112 -0
- data/lib/riffer/agent/session.rb +268 -0
- data/lib/riffer/{structured_output → agent/structured_output}/result.rb +1 -1
- data/lib/riffer/{structured_output.rb → agent/structured_output.rb} +4 -4
- data/lib/riffer/agent.rb +246 -684
- data/lib/riffer/config.rb +56 -7
- data/lib/riffer/evals/evaluator.rb +13 -3
- data/lib/riffer/evals/judge.rb +2 -2
- data/lib/riffer/evals/run_result.rb +2 -1
- data/lib/riffer/evals/scenario_result.rb +2 -1
- data/lib/riffer/guardrails/runner.rb +3 -2
- data/lib/riffer/helpers/call_or_value.rb +16 -0
- data/lib/riffer/helpers.rb +0 -1
- data/lib/riffer/mcp/authenticated_tool.rb +4 -0
- data/lib/riffer/mcp/client.rb +1 -1
- data/lib/riffer/mcp/registration.rb +2 -3
- data/lib/riffer/mcp/registry.rb +3 -1
- data/lib/riffer/mcp/tool_factory.rb +5 -0
- data/lib/riffer/messages/assistant.rb +9 -3
- data/lib/riffer/messages/base.rb +22 -0
- data/lib/riffer/messages/converter.rb +6 -6
- data/lib/riffer/{file_part.rb → messages/file_part.rb} +5 -5
- data/lib/riffer/messages/tool.rb +1 -1
- data/lib/riffer/messages/user.rb +4 -4
- data/lib/riffer/{boolean.rb → params/boolean.rb} +3 -3
- data/lib/riffer/{param.rb → params/param.rb} +6 -6
- data/lib/riffer/params.rb +27 -21
- data/lib/riffer/providers/amazon_bedrock.rb +19 -20
- data/lib/riffer/providers/anthropic.rb +27 -28
- data/lib/riffer/providers/base.rb +10 -9
- data/lib/riffer/providers/gemini.rb +15 -12
- data/lib/riffer/providers/mock.rb +41 -13
- data/lib/riffer/providers/open_ai.rb +24 -22
- data/lib/riffer/providers/open_router.rb +318 -0
- data/lib/riffer/providers/repository.rb +1 -0
- data/lib/riffer/{token_usage.rb → providers/token_usage.rb} +4 -4
- data/lib/riffer/providers.rb +1 -0
- data/lib/riffer/runner/fibers.rb +4 -3
- data/lib/riffer/runner/sequential.rb +1 -1
- data/lib/riffer/runner/threaded.rb +1 -1
- data/lib/riffer/runner.rb +1 -1
- data/lib/riffer/skills/activate_tool.rb +4 -3
- data/lib/riffer/skills/config.rb +1 -1
- data/lib/riffer/skills/context.rb +3 -3
- data/lib/riffer/skills/filesystem_backend.rb +7 -5
- data/lib/riffer/skills/markdown_adapter.rb +1 -1
- data/lib/riffer/skills/xml_adapter.rb +1 -1
- data/lib/riffer/stream_events/interrupt.rb +10 -3
- data/lib/riffer/stream_events/token_usage_done.rb +2 -2
- data/lib/riffer/stream_events/web_search_status.rb +1 -1
- data/lib/riffer/tool.rb +3 -3
- data/lib/riffer/{tool_runtime → tools/runtime}/fibers.rb +2 -2
- data/lib/riffer/{tool_runtime → tools/runtime}/inline.rb +1 -1
- data/lib/riffer/{tool_runtime → tools/runtime}/threaded.rb +2 -2
- data/lib/riffer/{tool_runtime.rb → tools/runtime.rb} +21 -15
- data/lib/riffer/{toolable.rb → tools/toolable.rb} +12 -9
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -1
- data/sig/generated/riffer/agent/config.rbs +119 -0
- data/sig/generated/riffer/agent/context.rbs +91 -0
- data/sig/generated/riffer/agent/response.rbs +10 -2
- data/sig/generated/riffer/agent/run.rbs +144 -0
- data/sig/generated/riffer/agent/session/repair.rbs +51 -0
- data/sig/generated/riffer/agent/session.rbs +145 -0
- data/sig/generated/riffer/{structured_output → agent/structured_output}/result.rbs +2 -2
- data/sig/generated/riffer/{structured_output.rbs → agent/structured_output.rbs} +6 -6
- data/sig/generated/riffer/agent.rbs +154 -225
- data/sig/generated/riffer/config.rbs +50 -5
- data/sig/generated/riffer/evals/judge.rbs +2 -2
- data/sig/generated/riffer/helpers/call_or_value.rbs +9 -0
- data/sig/generated/riffer/helpers.rbs +0 -1
- data/sig/generated/riffer/messages/assistant.rbs +7 -3
- data/sig/generated/riffer/messages/base.rbs +18 -0
- data/sig/generated/riffer/messages/converter.rbs +4 -4
- data/sig/generated/riffer/{file_part.rbs → messages/file_part.rbs} +5 -5
- data/sig/generated/riffer/messages/user.rbs +4 -4
- data/sig/generated/riffer/params/boolean.rbs +10 -0
- data/sig/generated/riffer/{param.rbs → params/param.rbs} +3 -3
- data/sig/generated/riffer/params.rbs +15 -15
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +22 -22
- data/sig/generated/riffer/providers/anthropic.rbs +4 -4
- data/sig/generated/riffer/providers/base.rbs +10 -10
- data/sig/generated/riffer/providers/gemini.rbs +4 -4
- data/sig/generated/riffer/providers/mock.rbs +25 -5
- data/sig/generated/riffer/providers/open_ai.rbs +4 -4
- data/sig/generated/riffer/providers/open_router.rbs +85 -0
- data/sig/generated/riffer/{token_usage.rbs → providers/token_usage.rbs} +5 -5
- data/sig/generated/riffer/providers.rbs +1 -0
- data/sig/generated/riffer/runner/fibers.rbs +2 -2
- data/sig/generated/riffer/runner/sequential.rbs +2 -2
- data/sig/generated/riffer/runner/threaded.rbs +2 -2
- data/sig/generated/riffer/runner.rbs +2 -2
- data/sig/generated/riffer/skills/activate_tool.rbs +4 -3
- data/sig/generated/riffer/skills/config.rbs +1 -1
- data/sig/generated/riffer/skills/context.rbs +2 -2
- data/sig/generated/riffer/stream_events/interrupt.rbs +7 -2
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +3 -3
- data/sig/generated/riffer/tool.rbs +5 -5
- data/sig/generated/riffer/{tool_runtime → tools/runtime}/fibers.rbs +3 -3
- data/sig/generated/riffer/{tool_runtime → tools/runtime}/inline.rbs +2 -2
- data/sig/generated/riffer/{tool_runtime → tools/runtime}/threaded.rbs +3 -3
- data/sig/generated/riffer/{tool_runtime.rbs → tools/runtime.rbs} +19 -13
- data/sig/generated/riffer/{toolable.rbs → tools/toolable.rbs} +6 -6
- data/sig/stubs/agent_ivars.rbs +7 -0
- data/sig/stubs/async.rbs +24 -0
- data/sig/stubs/aws-sdk-core/seahorse_request_context.rbs +7 -0
- data/sig/stubs/aws-sdk-core/static_token_provider.rbs +5 -0
- data/sig/stubs/extend_self.rbs +11 -0
- data/sig/stubs/lib_ivars.rbs +101 -0
- data/sig/stubs/mcp_sdk.rbs +22 -0
- data/sig/stubs/provider_ivars.rbs +36 -0
- data/sig/stubs/provider_sdk_methods.rbs +50 -0
- data/sig/stubs/zeitwerk.rbs +12 -0
- metadata +54 -33
- data/lib/riffer/core.rb +0 -28
- data/lib/riffer/helpers/validations.rb +0 -18
- data/sig/generated/riffer/boolean.rbs +0 -10
- data/sig/generated/riffer/core.rbs +0 -19
- data/sig/generated/riffer/helpers/validations.rbs +0 -12
|
@@ -31,8 +31,9 @@ class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
|
|
|
31
31
|
#--
|
|
32
32
|
#: () -> Array[Riffer::Skills::Frontmatter]
|
|
33
33
|
def list_skills
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
cache = {} #: Hash[String, String]
|
|
35
|
+
@skills_cache = cache
|
|
36
|
+
frontmatters = [] #: Array[Riffer::Skills::Frontmatter]
|
|
36
37
|
|
|
37
38
|
@paths.each do |path|
|
|
38
39
|
next unless File.directory?(path)
|
|
@@ -45,10 +46,10 @@ class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
|
|
|
45
46
|
frontmatter = Riffer::Skills::Frontmatter.parse_frontmatter(File.read(skill_file))
|
|
46
47
|
|
|
47
48
|
validate_dirname_matches_name!(dirname, frontmatter.name)
|
|
48
|
-
next if
|
|
49
|
+
next if cache.key?(frontmatter.name)
|
|
49
50
|
|
|
50
51
|
frontmatters << frontmatter
|
|
51
|
-
|
|
52
|
+
cache[frontmatter.name] = dir
|
|
52
53
|
end
|
|
53
54
|
end
|
|
54
55
|
|
|
@@ -65,7 +66,8 @@ class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
|
|
|
65
66
|
#: (String) -> String
|
|
66
67
|
def read_skill(name)
|
|
67
68
|
list_skills unless @skills_cache
|
|
68
|
-
|
|
69
|
+
cache = @skills_cache #: Hash[String, String]
|
|
70
|
+
dir = cache[name]
|
|
69
71
|
raise Riffer::ArgumentError, "Skill not found: '#{name}'" unless dir
|
|
70
72
|
|
|
71
73
|
_, body = Riffer::Skills::Frontmatter.parse(File.read(File.join(dir, SKILL_FILENAME)))
|
|
@@ -15,7 +15,7 @@ class Riffer::Skills::MarkdownAdapter < Riffer::Skills::Adapter
|
|
|
15
15
|
#--
|
|
16
16
|
#: (Array[Riffer::Skills::Frontmatter]) -> String
|
|
17
17
|
def render_catalog(skills)
|
|
18
|
-
lines = []
|
|
18
|
+
lines = [] #: Array[String]
|
|
19
19
|
lines << "## Available Skills"
|
|
20
20
|
lines << ""
|
|
21
21
|
lines << "When a user's request matches a skill description below, call the `#{skill_activate_tool.name}` tool with the skill name. After activation, follow the skill's instructions."
|
|
@@ -16,7 +16,7 @@ class Riffer::Skills::XmlAdapter < Riffer::Skills::Adapter
|
|
|
16
16
|
#--
|
|
17
17
|
#: (Array[Riffer::Skills::Frontmatter]) -> String
|
|
18
18
|
def render_catalog(skills)
|
|
19
|
-
lines = []
|
|
19
|
+
lines = [] #: Array[String]
|
|
20
20
|
lines << "When a user's request matches a skill description below, call the `#{skill_activate_tool.name}` tool with the skill name. After activation, follow the skill's instructions."
|
|
21
21
|
lines << ""
|
|
22
22
|
lines << "<available_skills>"
|
|
@@ -8,11 +8,17 @@ class Riffer::StreamEvents::Interrupt < Riffer::StreamEvents::Base
|
|
|
8
8
|
# The reason provided with the interrupt, if any.
|
|
9
9
|
attr_reader :reason #: (String | Symbol)?
|
|
10
10
|
|
|
11
|
+
# Call ids of tool_use blocks that riffer filled with placeholder
|
|
12
|
+
# results when the interrupt fired. Populated only when
|
|
13
|
+
# +Riffer.config.experimental_history_healing+ is on.
|
|
14
|
+
attr_reader :healed_tool_call_ids #: Array[String]
|
|
15
|
+
|
|
11
16
|
#--
|
|
12
|
-
#: (?reason: (String | Symbol)?) -> void
|
|
13
|
-
def initialize(reason: nil)
|
|
17
|
+
#: (?reason: (String | Symbol)?, ?healed_tool_call_ids: Array[String]) -> void
|
|
18
|
+
def initialize(reason: nil, healed_tool_call_ids: [])
|
|
14
19
|
super(role: :system)
|
|
15
20
|
@reason = reason
|
|
21
|
+
@healed_tool_call_ids = healed_tool_call_ids
|
|
16
22
|
end
|
|
17
23
|
|
|
18
24
|
# Converts the event to a hash.
|
|
@@ -20,8 +26,9 @@ class Riffer::StreamEvents::Interrupt < Riffer::StreamEvents::Base
|
|
|
20
26
|
#--
|
|
21
27
|
#: () -> Hash[Symbol, untyped]
|
|
22
28
|
def to_h
|
|
23
|
-
h = {role: @role, interrupt: true}
|
|
29
|
+
h = {role: @role, interrupt: true} #: Hash[Symbol, untyped]
|
|
24
30
|
h[:reason] = @reason if @reason
|
|
31
|
+
h[:healed_tool_call_ids] = @healed_tool_call_ids unless @healed_tool_call_ids.empty?
|
|
25
32
|
h
|
|
26
33
|
end
|
|
27
34
|
end
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
#
|
|
12
12
|
class Riffer::StreamEvents::TokenUsageDone < Riffer::StreamEvents::Base
|
|
13
13
|
# The token usage data for this response.
|
|
14
|
-
attr_reader :token_usage #: Riffer::TokenUsage
|
|
14
|
+
attr_reader :token_usage #: Riffer::Providers::TokenUsage
|
|
15
15
|
|
|
16
16
|
#--
|
|
17
|
-
#: (token_usage: Riffer::TokenUsage, ?role: Symbol) -> void
|
|
17
|
+
#: (token_usage: Riffer::Providers::TokenUsage, ?role: Symbol) -> void
|
|
18
18
|
def initialize(token_usage:, role: :assistant)
|
|
19
19
|
super(role: role)
|
|
20
20
|
@token_usage = token_usage
|
|
@@ -26,7 +26,7 @@ class Riffer::StreamEvents::WebSearchStatus < Riffer::StreamEvents::Base
|
|
|
26
26
|
#--
|
|
27
27
|
#: () -> Hash[Symbol, untyped]
|
|
28
28
|
def to_h
|
|
29
|
-
h = {role: @role, status: @status}
|
|
29
|
+
h = {role: @role, status: @status} #: Hash[Symbol, untyped]
|
|
30
30
|
h[:url] = @url if @url
|
|
31
31
|
h[:query] = @query if @query
|
|
32
32
|
h
|
data/lib/riffer/tool.rb
CHANGED
|
@@ -24,7 +24,7 @@ require "timeout"
|
|
|
24
24
|
# end
|
|
25
25
|
#
|
|
26
26
|
class Riffer::Tool
|
|
27
|
-
extend Riffer::Toolable
|
|
27
|
+
extend Riffer::Tools::Toolable
|
|
28
28
|
|
|
29
29
|
kind :tool
|
|
30
30
|
|
|
@@ -33,7 +33,7 @@ class Riffer::Tool
|
|
|
33
33
|
# Raises NotImplementedError if not implemented by subclass.
|
|
34
34
|
#
|
|
35
35
|
#--
|
|
36
|
-
#: (context:
|
|
36
|
+
#: (context: Riffer::Agent::Context?, **untyped) -> Riffer::Tools::Response
|
|
37
37
|
def call(context:, **kwargs)
|
|
38
38
|
raise NotImplementedError, "#{self.class} must implement #call"
|
|
39
39
|
end
|
|
@@ -69,7 +69,7 @@ class Riffer::Tool
|
|
|
69
69
|
# Raises Riffer::Error if the tool does not return a Response object.
|
|
70
70
|
#
|
|
71
71
|
#--
|
|
72
|
-
#: (context:
|
|
72
|
+
#: (context: Riffer::Agent::Context?, **untyped) -> Riffer::Tools::Response
|
|
73
73
|
def call_with_validation(context:, **kwargs)
|
|
74
74
|
params_builder = self.class.params
|
|
75
75
|
validated_args = params_builder ? params_builder.validate(kwargs) : kwargs
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
# Executes tool calls concurrently using fibers via the +async+ gem.
|
|
5
5
|
#
|
|
6
6
|
# class MyAgent < Riffer::Agent
|
|
7
|
-
# tool_runtime Riffer::
|
|
7
|
+
# tool_runtime Riffer::Tools::Runtime::Fibers
|
|
8
8
|
# end
|
|
9
9
|
#
|
|
10
|
-
class Riffer::
|
|
10
|
+
class Riffer::Tools::Runtime::Fibers < Riffer::Tools::Runtime
|
|
11
11
|
# [max_concurrency] maximum number of tool calls to execute simultaneously.
|
|
12
12
|
# When +nil+, all tool calls run as fibers without limit.
|
|
13
13
|
#
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
# Executes tool calls concurrently using threads.
|
|
5
5
|
#
|
|
6
6
|
# class MyAgent < Riffer::Agent
|
|
7
|
-
# tool_runtime Riffer::
|
|
7
|
+
# tool_runtime Riffer::Tools::Runtime::Threaded
|
|
8
8
|
# end
|
|
9
9
|
#
|
|
10
|
-
class Riffer::
|
|
10
|
+
class Riffer::Tools::Runtime::Threaded < Riffer::Tools::Runtime
|
|
11
11
|
DEFAULT_MAX_CONCURRENCY = 5 #: Integer
|
|
12
12
|
|
|
13
13
|
# [max_concurrency] maximum number of tool calls to execute simultaneously.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
|
-
# Riffer::
|
|
6
|
+
# Riffer::Tools::Runtime handles tool call execution for an agent.
|
|
7
7
|
#
|
|
8
8
|
# Composes with a Riffer::Runner for concurrency control and provides
|
|
9
9
|
# +execute+ as the sole public entry point.
|
|
@@ -11,19 +11,19 @@ require "json"
|
|
|
11
11
|
# Subclass and override +dispatch_tool_call+ to customize how individual
|
|
12
12
|
# tool calls are dispatched (e.g., HTTP, gRPC).
|
|
13
13
|
#
|
|
14
|
-
# runtime = Riffer::
|
|
14
|
+
# runtime = Riffer::Tools::Runtime::Inline.new
|
|
15
15
|
# results = runtime.execute(tool_calls, tools: tools, context: context)
|
|
16
16
|
#
|
|
17
|
-
class Riffer::
|
|
17
|
+
class Riffer::Tools::Runtime
|
|
18
18
|
# [runner] the concurrency runner to use for batch execution.
|
|
19
19
|
#
|
|
20
|
-
# Subclasses must provide a runner; instantiating
|
|
20
|
+
# Subclasses must provide a runner; instantiating Riffer::Tools::Runtime directly
|
|
21
21
|
# raises +NotImplementedError+.
|
|
22
22
|
#
|
|
23
23
|
#--
|
|
24
24
|
#: (runner: Riffer::Runner) -> void
|
|
25
25
|
def initialize(runner:)
|
|
26
|
-
raise NotImplementedError, "#{self.class} is abstract — use a subclass like Riffer::
|
|
26
|
+
raise NotImplementedError, "#{self.class} is abstract — use a subclass like Riffer::Tools::Runtime::Inline" if instance_of?(Riffer::Tools::Runtime)
|
|
27
27
|
@runner = runner
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -32,13 +32,17 @@ class Riffer::ToolRuntime
|
|
|
32
32
|
# [tool_calls] the tool calls to execute.
|
|
33
33
|
# [tools] the resolved tool classes.
|
|
34
34
|
# [context] the context hash.
|
|
35
|
+
# [assistant_message] the assistant message that produced these tool
|
|
36
|
+
# calls, when known. Forwarded to +around_tool_call+ and
|
|
37
|
+
# +dispatch_tool_call+ so subclasses can access it (e.g. for
|
|
38
|
+
# instrumentation that needs the accompanying assistant text).
|
|
35
39
|
#
|
|
36
40
|
#--
|
|
37
|
-
#: (Array[Riffer::Messages::Assistant::ToolCall], tools: Array[singleton(Riffer::Tool)], context:
|
|
38
|
-
def execute(tool_calls, tools:, context:)
|
|
41
|
+
#: (Array[Riffer::Messages::Assistant::ToolCall], tools: Array[singleton(Riffer::Tool)], context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) -> Array[[Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]]
|
|
42
|
+
def execute(tool_calls, tools:, context:, assistant_message: nil)
|
|
39
43
|
@runner.map(tool_calls, context: context) do |tool_call|
|
|
40
|
-
result = around_tool_call(tool_call, context: context) do
|
|
41
|
-
dispatch_tool_call(tool_call, tools: tools, context: context)
|
|
44
|
+
result = around_tool_call(tool_call, context: context, assistant_message: assistant_message) do
|
|
45
|
+
dispatch_tool_call(tool_call, tools: tools, context: context, assistant_message: assistant_message)
|
|
42
46
|
end
|
|
43
47
|
[tool_call, result]
|
|
44
48
|
end
|
|
@@ -49,10 +53,10 @@ class Riffer::ToolRuntime
|
|
|
49
53
|
#
|
|
50
54
|
# The default implementation simply yields.
|
|
51
55
|
#
|
|
52
|
-
# class InstrumentedRuntime < Riffer::
|
|
56
|
+
# class InstrumentedRuntime < Riffer::Tools::Runtime::Inline
|
|
53
57
|
# private
|
|
54
58
|
#
|
|
55
|
-
# def around_tool_call(tool_call, context:)
|
|
59
|
+
# def around_tool_call(tool_call, context:, assistant_message: nil)
|
|
56
60
|
# start = Time.now
|
|
57
61
|
# result = yield
|
|
58
62
|
# Rails.logger.info("Tool #{tool_call.name} took #{Time.now - start}s")
|
|
@@ -61,8 +65,8 @@ class Riffer::ToolRuntime
|
|
|
61
65
|
# end
|
|
62
66
|
#
|
|
63
67
|
#--
|
|
64
|
-
#: (Riffer::Messages::Assistant::ToolCall, context:
|
|
65
|
-
def around_tool_call(tool_call, context:)
|
|
68
|
+
#: (Riffer::Messages::Assistant::ToolCall, context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) { () -> Riffer::Tools::Response } -> Riffer::Tools::Response
|
|
69
|
+
def around_tool_call(tool_call, context:, assistant_message: nil)
|
|
66
70
|
yield
|
|
67
71
|
end
|
|
68
72
|
|
|
@@ -74,10 +78,12 @@ class Riffer::ToolRuntime
|
|
|
74
78
|
# [tool_call] the tool call to execute.
|
|
75
79
|
# [tools] the resolved tool classes.
|
|
76
80
|
# [context] the context hash.
|
|
81
|
+
# [assistant_message] the assistant message that produced this tool
|
|
82
|
+
# call, when known.
|
|
77
83
|
#
|
|
78
84
|
#--
|
|
79
|
-
#: (Riffer::Messages::Assistant::ToolCall, tools: Array[singleton(Riffer::Tool)], context:
|
|
80
|
-
def dispatch_tool_call(tool_call, tools:, context:)
|
|
85
|
+
#: (Riffer::Messages::Assistant::ToolCall, tools: Array[singleton(Riffer::Tool)], context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) -> Riffer::Tools::Response
|
|
86
|
+
def dispatch_tool_call(tool_call, tools:, context:, assistant_message: nil)
|
|
81
87
|
tool_class = tools.find { |tc| tc.name == tool_call.name }
|
|
82
88
|
|
|
83
89
|
if tool_class.nil?
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Riffer::Toolable provides the shared class-level DSL for anything that can
|
|
4
|
+
# Riffer::Tools::Toolable provides the shared class-level DSL for anything that can
|
|
5
5
|
# present as a tool to an LLM — tools today, and subagents/workflows in the
|
|
6
6
|
# future.
|
|
7
7
|
#
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# are NOT part of Toolable — those belong on Riffer::Tool.
|
|
14
14
|
#
|
|
15
15
|
# class MyTool
|
|
16
|
-
# extend Riffer::Toolable
|
|
16
|
+
# extend Riffer::Tools::Toolable
|
|
17
17
|
#
|
|
18
18
|
# description "Does something useful"
|
|
19
19
|
#
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
# end
|
|
23
23
|
# end
|
|
24
24
|
#
|
|
25
|
-
module Riffer::Toolable
|
|
25
|
+
module Riffer::Tools::Toolable
|
|
26
26
|
DEFAULT_TIMEOUT = 10 #: Integer
|
|
27
27
|
|
|
28
28
|
# Tracks all classes that extend Toolable.
|
|
@@ -31,8 +31,8 @@ module Riffer::Toolable
|
|
|
31
31
|
#: (Module) -> void
|
|
32
32
|
def self.extended(base)
|
|
33
33
|
base.extend Riffer::Helpers::ClassNameConverter
|
|
34
|
-
@extenders ||= []
|
|
35
|
-
|
|
34
|
+
extenders = (@extenders ||= []) #: Array[Module]
|
|
35
|
+
extenders << base
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Returns all classes that have extended Toolable.
|
|
@@ -82,11 +82,12 @@ module Riffer::Toolable
|
|
|
82
82
|
# Defines parameters using the Params DSL.
|
|
83
83
|
#
|
|
84
84
|
#--
|
|
85
|
-
#: () ?{ () -> void } -> Riffer::Params?
|
|
85
|
+
#: () ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
|
|
86
86
|
def params(&block)
|
|
87
87
|
return @params_builder if block.nil?
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
builder = Riffer::Params.new
|
|
89
|
+
builder.instance_eval(&block)
|
|
90
|
+
@params_builder = builder
|
|
90
91
|
end
|
|
91
92
|
|
|
92
93
|
# Returns the JSON Schema for the tool's parameters.
|
|
@@ -135,6 +136,8 @@ module Riffer::Toolable
|
|
|
135
136
|
private
|
|
136
137
|
|
|
137
138
|
def empty_schema # :nodoc:
|
|
138
|
-
|
|
139
|
+
properties = {} #: Hash[Symbol, untyped]
|
|
140
|
+
required = [] #: Array[untyped]
|
|
141
|
+
{type: "object", properties: properties, required: required, additionalProperties: false}
|
|
139
142
|
end
|
|
140
143
|
end
|
data/lib/riffer/version.rb
CHANGED
data/lib/riffer.rb
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Generated from lib/riffer/agent/config.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
# Typed configuration object holding every class-level DSL setting on a
|
|
4
|
+
# Riffer::Agent subclass.
|
|
5
|
+
#
|
|
6
|
+
# Each subclass of Riffer::Agent owns one Config, accessible via the class
|
|
7
|
+
# method <tt>config</tt>. The class-level DSL (+model+, +instructions+, +uses_tools+,
|
|
8
|
+
# etc.) reads and mutates this Config in place. Append-style DSL methods
|
|
9
|
+
# (+use_mcp+, +guardrail+) are handled by the +add_mcp+ and +add_guardrail+
|
|
10
|
+
# helpers below.
|
|
11
|
+
#
|
|
12
|
+
# Config stores Procs unresolved. Per-instance resolution happens elsewhere
|
|
13
|
+
# (instructions, model, tools, tool runtime, skills).
|
|
14
|
+
class Riffer::Agent::Config
|
|
15
|
+
DEFAULT_MAX_STEPS: Integer
|
|
16
|
+
|
|
17
|
+
attr_reader identifier: String?
|
|
18
|
+
|
|
19
|
+
attr_reader model: (String | Proc)?
|
|
20
|
+
|
|
21
|
+
attr_reader instructions: (String | Proc)?
|
|
22
|
+
|
|
23
|
+
attr_accessor provider_options: Hash[Symbol, untyped]
|
|
24
|
+
|
|
25
|
+
attr_accessor model_options: Hash[Symbol, untyped]
|
|
26
|
+
|
|
27
|
+
attr_reader structured_output: Riffer::Params?
|
|
28
|
+
|
|
29
|
+
attr_accessor max_steps: Numeric
|
|
30
|
+
|
|
31
|
+
attr_accessor tools_config: (Array[singleton(Riffer::Tool)] | Proc)?
|
|
32
|
+
|
|
33
|
+
attr_reader mcp_configs: Array[Hash[Symbol, untyped]]
|
|
34
|
+
|
|
35
|
+
attr_reader tool_runtime: singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc
|
|
36
|
+
|
|
37
|
+
attr_accessor skills_config: Riffer::Skills::Config?
|
|
38
|
+
|
|
39
|
+
attr_reader guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]
|
|
40
|
+
|
|
41
|
+
# Builds a new Config. All fields are optional; unset fields take the
|
|
42
|
+
# documented defaults.
|
|
43
|
+
#
|
|
44
|
+
# Raises Riffer::ArgumentError if +model+ or +instructions+ is provided
|
|
45
|
+
# as a non-String, non-Proc value (or as an empty String).
|
|
46
|
+
#
|
|
47
|
+
# --
|
|
48
|
+
# : (?identifier: String?, ?model: (String | Proc)?, ?instructions: (String | Proc)?, ?provider_options: Hash[Symbol, untyped], ?model_options: Hash[Symbol, untyped], ?structured_output: Riffer::Params?, ?max_steps: Numeric, ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?, ?mcp_configs: Array[Hash[Symbol, untyped]], ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc), ?skills_config: Riffer::Skills::Config?, ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]) -> void
|
|
49
|
+
def initialize: (?identifier: String?, ?model: (String | Proc)?, ?instructions: (String | Proc)?, ?provider_options: Hash[Symbol, untyped], ?model_options: Hash[Symbol, untyped], ?structured_output: Riffer::Params?, ?max_steps: Numeric, ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?, ?mcp_configs: Array[Hash[Symbol, untyped]], ?tool_runtime: singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc, ?skills_config: Riffer::Skills::Config?, ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]) -> void
|
|
50
|
+
|
|
51
|
+
# Sets +identifier+. Accepts +nil+ or any value, coerced to String.
|
|
52
|
+
#
|
|
53
|
+
# --
|
|
54
|
+
# : (untyped) -> String?
|
|
55
|
+
def identifier=: (untyped) -> String?
|
|
56
|
+
|
|
57
|
+
# Sets +structured_output+. Accepts a Riffer::Params instance or +nil+.
|
|
58
|
+
#
|
|
59
|
+
# Raises Riffer::ArgumentError on any other type.
|
|
60
|
+
#
|
|
61
|
+
# --
|
|
62
|
+
# : (Riffer::Params?) -> Riffer::Params?
|
|
63
|
+
def structured_output=: (Riffer::Params?) -> Riffer::Params?
|
|
64
|
+
|
|
65
|
+
# Sets +tool_runtime+. Accepts a Riffer::Tools::Runtime subclass, a
|
|
66
|
+
# Riffer::Tools::Runtime instance, or a Proc.
|
|
67
|
+
#
|
|
68
|
+
# Raises Riffer::ArgumentError on any other type.
|
|
69
|
+
#
|
|
70
|
+
# --
|
|
71
|
+
# : ((singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
|
|
72
|
+
def tool_runtime=: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
|
|
73
|
+
|
|
74
|
+
# Sets +model+. Accepts a String ("provider/model"), a Proc, or +nil+.
|
|
75
|
+
#
|
|
76
|
+
# Raises Riffer::ArgumentError on non-String, non-Proc, or empty-String values.
|
|
77
|
+
#
|
|
78
|
+
# --
|
|
79
|
+
# : ((String | Proc)?) -> (String | Proc)?
|
|
80
|
+
def model=: ((String | Proc)?) -> (String | Proc)?
|
|
81
|
+
|
|
82
|
+
# Sets +instructions+. Accepts a String, a Proc, or +nil+.
|
|
83
|
+
#
|
|
84
|
+
# Raises Riffer::ArgumentError on non-String, non-Proc, or empty-String values.
|
|
85
|
+
#
|
|
86
|
+
# --
|
|
87
|
+
# : ((String | Proc)?) -> (String | Proc)?
|
|
88
|
+
def instructions=: ((String | Proc)?) -> (String | Proc)?
|
|
89
|
+
|
|
90
|
+
# Appends an MCP tag entry to +mcp_configs+.
|
|
91
|
+
#
|
|
92
|
+
# --
|
|
93
|
+
# : (String | Symbol) -> Array[Hash[Symbol, untyped]]
|
|
94
|
+
def add_mcp: (String | Symbol) -> Array[Hash[Symbol, untyped]]
|
|
95
|
+
|
|
96
|
+
# Appends a guardrail entry to +guardrails+ for the given phase.
|
|
97
|
+
#
|
|
98
|
+
# [phase] +:before+, +:after+, or +:around+. +:around+ appends to both
|
|
99
|
+
# +:before+ and +:after+.
|
|
100
|
+
# [klass] the Riffer::Guardrail subclass to register.
|
|
101
|
+
# [options] options forwarded to the guardrail at runtime.
|
|
102
|
+
#
|
|
103
|
+
# Raises Riffer::ArgumentError on an invalid phase or non-Guardrail class.
|
|
104
|
+
#
|
|
105
|
+
# --
|
|
106
|
+
# : (Symbol, klass: singleton(Riffer::Guardrail), ?options: Hash[Symbol, untyped]) -> void
|
|
107
|
+
def add_guardrail: (Symbol, klass: singleton(Riffer::Guardrail), ?options: Hash[Symbol, untyped]) -> void
|
|
108
|
+
|
|
109
|
+
# Returns the guardrail entries for the given phase, or +[]+ if none.
|
|
110
|
+
#
|
|
111
|
+
# --
|
|
112
|
+
# : (Symbol) -> Array[Hash[Symbol, untyped]]
|
|
113
|
+
def guardrails_for: (Symbol) -> Array[Hash[Symbol, untyped]]
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
# : (untyped, String) -> void
|
|
118
|
+
def validate_string_or_proc!: (untyped, String) -> void
|
|
119
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Generated from lib/riffer/agent/context.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
# Typed value object wrapping the runtime context Hash held by a
|
|
4
|
+
# Riffer::Agent. Exposes first-class accessors for the framework-managed
|
|
5
|
+
# entries — +skills+ and +token_usage+ — and preserves +#[]+ / +#dig+
|
|
6
|
+
# reads so tools (which receive +context:+ as a keyword) keep working
|
|
7
|
+
# with both built-in and caller-provided keys.
|
|
8
|
+
#
|
|
9
|
+
# Reserved keys (+:skills+, +:token_usage+) cannot be set by the caller
|
|
10
|
+
# at construction; they are owned by Riffer and written through the typed
|
|
11
|
+
# setters. Type invariants are enforced on write — +skills+ must be a
|
|
12
|
+
# +Riffer::Skills::Context+ (or nil); +token_usage+ must be a
|
|
13
|
+
# +Riffer::Providers::TokenUsage+ (or nil).
|
|
14
|
+
#
|
|
15
|
+
# context = Riffer::Agent::Context.new(user_id: 42)
|
|
16
|
+
# context[:user_id] # => 42
|
|
17
|
+
# context.skills # => nil
|
|
18
|
+
# context.token_usage # => nil
|
|
19
|
+
class Riffer::Agent::Context
|
|
20
|
+
# Keys reserved for framework use. Passing any of these to the
|
|
21
|
+
# constructor raises +Riffer::ArgumentError+.
|
|
22
|
+
RESERVED_KEYS: Array[Symbol]
|
|
23
|
+
|
|
24
|
+
# Builds a new context.
|
|
25
|
+
#
|
|
26
|
+
# [data] caller-provided Hash passed as <tt>Agent.new(context:)</tt>.
|
|
27
|
+
# Duped before storage so caller mutations do not affect the
|
|
28
|
+
# agent. Must not contain any +RESERVED_KEYS+.
|
|
29
|
+
#
|
|
30
|
+
# Raises Riffer::ArgumentError when +data+ contains a reserved key.
|
|
31
|
+
#
|
|
32
|
+
# --
|
|
33
|
+
# : (?Hash[Symbol, untyped]) -> void
|
|
34
|
+
def initialize: (?Hash[Symbol, untyped]) -> void
|
|
35
|
+
|
|
36
|
+
# The agent's resolved +Riffer::Skills::Context+, or +nil+ when skills
|
|
37
|
+
# are not configured.
|
|
38
|
+
#
|
|
39
|
+
# --
|
|
40
|
+
# : () -> Riffer::Skills::Context?
|
|
41
|
+
def skills: () -> Riffer::Skills::Context?
|
|
42
|
+
|
|
43
|
+
# Sets the resolved skills context. Called once by +Riffer::Agent+
|
|
44
|
+
# during construction.
|
|
45
|
+
#
|
|
46
|
+
# Raises Riffer::ArgumentError if +value+ is neither +nil+ nor a
|
|
47
|
+
# +Riffer::Skills::Context+.
|
|
48
|
+
#
|
|
49
|
+
# --
|
|
50
|
+
# : (Riffer::Skills::Context?) -> Riffer::Skills::Context?
|
|
51
|
+
def skills=: (Riffer::Skills::Context?) -> Riffer::Skills::Context?
|
|
52
|
+
|
|
53
|
+
# The cumulative +Riffer::Providers::TokenUsage+ across every Run on this agent,
|
|
54
|
+
# or +nil+ before the first response is recorded.
|
|
55
|
+
#
|
|
56
|
+
# --
|
|
57
|
+
# : () -> Riffer::Providers::TokenUsage?
|
|
58
|
+
def token_usage: () -> Riffer::Providers::TokenUsage?
|
|
59
|
+
|
|
60
|
+
# Sets the cumulative token usage. Called by +Riffer::Agent::Run+ after
|
|
61
|
+
# each LLM response.
|
|
62
|
+
#
|
|
63
|
+
# Raises Riffer::ArgumentError if +value+ is neither +nil+ nor a
|
|
64
|
+
# +Riffer::Providers::TokenUsage+.
|
|
65
|
+
#
|
|
66
|
+
# --
|
|
67
|
+
# : (Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
|
|
68
|
+
def token_usage=: (Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
|
|
69
|
+
|
|
70
|
+
# Hash-style read. Preserved so downstream tool runtimes pulling
|
|
71
|
+
# caller-provided keys via <tt>context[:agent]</tt> or
|
|
72
|
+
# <tt>context[:tenant]</tt> keep working unchanged.
|
|
73
|
+
#
|
|
74
|
+
# --
|
|
75
|
+
# : (Symbol) -> untyped
|
|
76
|
+
def []: (Symbol) -> untyped
|
|
77
|
+
|
|
78
|
+
# Hash-style dig. Preserved for tools using
|
|
79
|
+
# <tt>context&.dig(:user_id)</tt>.
|
|
80
|
+
#
|
|
81
|
+
# --
|
|
82
|
+
# : (*Symbol) -> untyped
|
|
83
|
+
def dig: (*Symbol) -> untyped
|
|
84
|
+
|
|
85
|
+
# Returns a copy of the underlying Hash. Mutating the result does not
|
|
86
|
+
# affect this context.
|
|
87
|
+
#
|
|
88
|
+
# --
|
|
89
|
+
# : () -> Hash[Symbol, untyped]
|
|
90
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
91
|
+
end
|
|
@@ -30,6 +30,12 @@ class Riffer::Agent::Response
|
|
|
30
30
|
# The full message history from the agent conversation.
|
|
31
31
|
attr_reader messages: Array[Riffer::Messages::Base]
|
|
32
32
|
|
|
33
|
+
# Call ids of tool_use blocks that riffer filled with placeholder
|
|
34
|
+
# results during this turn — populated when an interrupt left them
|
|
35
|
+
# unanswered and +Riffer.config.experimental_history_healing+ is on.
|
|
36
|
+
# Empty otherwise.
|
|
37
|
+
attr_reader healed_tool_call_ids: Array[String]
|
|
38
|
+
|
|
33
39
|
# Creates a new response.
|
|
34
40
|
#
|
|
35
41
|
# [content] the response content.
|
|
@@ -39,10 +45,12 @@ class Riffer::Agent::Response
|
|
|
39
45
|
# [interrupt_reason] optional reason passed via <tt>throw :riffer_interrupt, reason</tt>.
|
|
40
46
|
# [structured_output] parsed structured output when structured output is configured.
|
|
41
47
|
# [messages] the full message history from the agent conversation.
|
|
48
|
+
# [healed_tool_call_ids] call ids filled with placeholder tool results
|
|
49
|
+
# when history healing is enabled.
|
|
42
50
|
#
|
|
43
51
|
# --
|
|
44
|
-
# : (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base]) -> void
|
|
45
|
-
def initialize: (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base]) -> void
|
|
52
|
+
# : (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String]) -> void
|
|
53
|
+
def initialize: (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String]) -> void
|
|
46
54
|
|
|
47
55
|
# Returns true if the response was blocked by a guardrail.
|
|
48
56
|
#
|