riffer 0.30.0 → 0.32.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/code-style.md +63 -4
- data/.agents/rbs-inline.md +1 -6
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +1 -2
- data/CHANGELOG.md +25 -0
- data/docs/08_MESSAGES.md +1 -1
- data/docs/14_MCP.md +50 -5
- data/docs/15_SERIALIZATION.md +23 -12
- data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
- data/lib/riffer/agent/config.rb +42 -47
- data/lib/riffer/agent/context.rb +70 -50
- data/lib/riffer/agent/response.rb +4 -20
- data/lib/riffer/agent/run.rb +28 -67
- data/lib/riffer/agent/serializer.rb +36 -85
- data/lib/riffer/agent/session/repair.rb +14 -40
- data/lib/riffer/agent/session.rb +25 -67
- data/lib/riffer/agent/structured_output/result.rb +3 -11
- data/lib/riffer/agent/structured_output.rb +5 -13
- data/lib/riffer/agent.rb +81 -199
- data/lib/riffer/config.rb +34 -101
- data/lib/riffer/evals/evaluator.rb +7 -27
- data/lib/riffer/evals/evaluator_runner.rb +11 -19
- data/lib/riffer/evals/judge.rb +4 -25
- data/lib/riffer/evals/result.rb +1 -18
- data/lib/riffer/evals/run_result.rb +0 -11
- data/lib/riffer/evals/scenario_result.rb +0 -14
- data/lib/riffer/evals.rb +0 -6
- data/lib/riffer/guardrail.rb +4 -27
- data/lib/riffer/guardrails/modification.rb +0 -10
- data/lib/riffer/guardrails/result.rb +3 -30
- data/lib/riffer/guardrails/runner.rb +5 -22
- data/lib/riffer/guardrails/tripwire.rb +1 -19
- data/lib/riffer/guardrails.rb +2 -4
- data/lib/riffer/helpers/call_or_value.rb +4 -3
- data/lib/riffer/helpers/class_name_converter.rb +3 -1
- data/lib/riffer/helpers/dependencies.rb +5 -7
- data/lib/riffer/helpers.rb +0 -5
- data/lib/riffer/mcp/authenticated_tool.rb +9 -9
- data/lib/riffer/mcp/client.rb +12 -17
- data/lib/riffer/mcp/manifest.rb +13 -10
- data/lib/riffer/mcp/registration.rb +2 -11
- data/lib/riffer/mcp/registry.rb +44 -52
- data/lib/riffer/mcp/search_tool.rb +53 -0
- data/lib/riffer/mcp/tool_factory.rb +13 -18
- data/lib/riffer/mcp.rb +12 -17
- data/lib/riffer/messages/assistant.rb +2 -9
- data/lib/riffer/messages/base.rb +46 -16
- data/lib/riffer/messages/file_part.rb +32 -24
- data/lib/riffer/messages/system.rb +0 -5
- data/lib/riffer/messages/tool.rb +0 -10
- data/lib/riffer/messages/user.rb +0 -10
- data/lib/riffer/messages.rb +0 -7
- data/lib/riffer/params/boolean.rb +2 -4
- data/lib/riffer/params/param.rb +28 -39
- data/lib/riffer/params.rb +9 -21
- data/lib/riffer/providers/amazon_bedrock.rb +42 -28
- data/lib/riffer/providers/anthropic.rb +4 -9
- data/lib/riffer/providers/azure_open_ai.rb +3 -19
- data/lib/riffer/providers/base.rb +13 -26
- data/lib/riffer/providers/gemini.rb +4 -4
- data/lib/riffer/providers/mock.rb +6 -26
- data/lib/riffer/providers/open_ai.rb +6 -8
- data/lib/riffer/providers/open_router.rb +4 -10
- data/lib/riffer/providers/repository.rb +4 -3
- data/lib/riffer/providers/token_usage.rb +9 -20
- data/lib/riffer/providers.rb +0 -8
- data/lib/riffer/runner/fibers.rb +10 -16
- data/lib/riffer/runner/sequential.rb +1 -4
- data/lib/riffer/runner/threaded.rb +3 -14
- data/lib/riffer/runner.rb +2 -15
- data/lib/riffer/skills/activate_tool.rb +2 -11
- data/lib/riffer/skills/adapter.rb +4 -22
- data/lib/riffer/skills/backend.rb +7 -21
- data/lib/riffer/skills/config.rb +10 -31
- data/lib/riffer/skills/context.rb +5 -20
- data/lib/riffer/skills/filesystem_backend.rb +7 -25
- data/lib/riffer/skills/frontmatter.rb +10 -28
- data/lib/riffer/skills/markdown_adapter.rb +2 -9
- data/lib/riffer/skills/xml_adapter.rb +2 -8
- data/lib/riffer/stream_events/base.rb +1 -6
- data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
- data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
- data/lib/riffer/stream_events/interrupt.rb +4 -7
- data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
- data/lib/riffer/stream_events/reasoning_done.rb +2 -4
- data/lib/riffer/stream_events/skill_activation.rb +2 -4
- data/lib/riffer/stream_events/text_delta.rb +0 -2
- data/lib/riffer/stream_events/text_done.rb +1 -3
- data/lib/riffer/stream_events/token_usage_done.rb +1 -8
- data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
- data/lib/riffer/stream_events/tool_call_done.rb +1 -3
- data/lib/riffer/stream_events/web_search_done.rb +1 -3
- data/lib/riffer/stream_events/web_search_status.rb +2 -3
- data/lib/riffer/stream_events.rb +0 -10
- data/lib/riffer/tool.rb +6 -13
- data/lib/riffer/tools/response.rb +8 -4
- data/lib/riffer/tools/runtime/fibers.rb +0 -3
- data/lib/riffer/tools/runtime/inline.rb +1 -4
- data/lib/riffer/tools/runtime/threaded.rb +0 -2
- data/lib/riffer/tools/runtime.rb +5 -38
- data/lib/riffer/tools/toolable.rb +5 -16
- data/lib/riffer/tools.rb +0 -4
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +7 -8
- data/sig/generated/riffer/agent/config.rbs +29 -46
- data/sig/generated/riffer/agent/context.rbs +40 -48
- data/sig/generated/riffer/agent/response.rbs +4 -20
- data/sig/generated/riffer/agent/run.rbs +12 -61
- data/sig/generated/riffer/agent/serializer.rbs +28 -81
- data/sig/generated/riffer/agent/session/repair.rbs +12 -40
- data/sig/generated/riffer/agent/session.rbs +25 -67
- data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
- data/sig/generated/riffer/agent/structured_output.rbs +5 -12
- data/sig/generated/riffer/agent.rbs +62 -191
- data/sig/generated/riffer/config.rbs +34 -100
- data/sig/generated/riffer/evals/evaluator.rbs +7 -27
- data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
- data/sig/generated/riffer/evals/judge.rbs +4 -24
- data/sig/generated/riffer/evals/result.rbs +1 -17
- data/sig/generated/riffer/evals/run_result.rbs +0 -10
- data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
- data/sig/generated/riffer/evals.rbs +0 -6
- data/sig/generated/riffer/guardrail.rbs +4 -27
- data/sig/generated/riffer/guardrails/modification.rbs +0 -10
- data/sig/generated/riffer/guardrails/result.rbs +3 -30
- data/sig/generated/riffer/guardrails/runner.rbs +5 -22
- data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
- data/sig/generated/riffer/guardrails.rbs +2 -4
- data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
- data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
- data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
- data/sig/generated/riffer/helpers.rbs +0 -5
- data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
- data/sig/generated/riffer/mcp/client.rbs +10 -16
- data/sig/generated/riffer/mcp/manifest.rbs +9 -9
- data/sig/generated/riffer/mcp/registration.rbs +2 -10
- data/sig/generated/riffer/mcp/registry.rbs +11 -18
- data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
- data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
- data/sig/generated/riffer/mcp.rbs +10 -17
- data/sig/generated/riffer/messages/assistant.rbs +2 -8
- data/sig/generated/riffer/messages/base.rbs +11 -16
- data/sig/generated/riffer/messages/file_part.rbs +13 -23
- data/sig/generated/riffer/messages/system.rbs +0 -4
- data/sig/generated/riffer/messages/tool.rbs +0 -9
- data/sig/generated/riffer/messages/user.rbs +0 -9
- data/sig/generated/riffer/messages.rbs +0 -7
- data/sig/generated/riffer/params/boolean.rbs +2 -4
- data/sig/generated/riffer/params/param.rbs +21 -39
- data/sig/generated/riffer/params.rbs +9 -21
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
- data/sig/generated/riffer/providers/anthropic.rbs +2 -7
- data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
- data/sig/generated/riffer/providers/base.rbs +9 -25
- data/sig/generated/riffer/providers/gemini.rbs +0 -2
- data/sig/generated/riffer/providers/mock.rbs +6 -26
- data/sig/generated/riffer/providers/open_ai.rbs +1 -5
- data/sig/generated/riffer/providers/open_router.rbs +4 -10
- data/sig/generated/riffer/providers/repository.rbs +2 -3
- data/sig/generated/riffer/providers/token_usage.rbs +6 -16
- data/sig/generated/riffer/providers.rbs +0 -8
- data/sig/generated/riffer/runner/fibers.rbs +8 -15
- data/sig/generated/riffer/runner/sequential.rbs +1 -3
- data/sig/generated/riffer/runner/threaded.rbs +3 -13
- data/sig/generated/riffer/runner.rbs +2 -14
- data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
- data/sig/generated/riffer/skills/adapter.rbs +4 -22
- data/sig/generated/riffer/skills/backend.rbs +7 -21
- data/sig/generated/riffer/skills/config.rbs +10 -31
- data/sig/generated/riffer/skills/context.rbs +5 -20
- data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
- data/sig/generated/riffer/skills/frontmatter.rbs +10 -27
- data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
- data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
- data/sig/generated/riffer/stream_events/base.rbs +1 -6
- data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
- data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
- data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
- data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
- data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
- data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
- data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
- data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
- data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
- data/sig/generated/riffer/stream_events.rbs +0 -10
- data/sig/generated/riffer/tool.rbs +5 -12
- data/sig/generated/riffer/tools/response.rbs +6 -4
- data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
- data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
- data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
- data/sig/generated/riffer/tools/runtime.rbs +5 -37
- data/sig/generated/riffer/tools/toolable.rbs +4 -14
- data/sig/generated/riffer/tools.rbs +0 -4
- data/sig/generated/riffer.rbs +5 -4
- data/sig/manual/riffer/agent/session/repair.rbs +5 -0
- data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
- data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
- data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
- data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
- data/sig/manual/riffer/mcp/registry.rbs +5 -0
- data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
- data/sig/manual/riffer/mcp.rbs +5 -0
- data/sig/manual/riffer/providers/repository.rbs +5 -0
- data/sig/manual/riffer.rbs +5 -0
- metadata +17 -9
- data/.agents/rdoc.md +0 -69
- data/lib/riffer/messages/converter.rb +0 -90
- data/sig/generated/riffer/messages/converter.rbs +0 -33
- data/sig/manual/riffer/tools/toolable.rbs +0 -6
data/lib/riffer/agent/context.rb
CHANGED
|
@@ -1,38 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Typed value object wrapping the runtime context Hash held by a
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# reads so tools (which receive +context:+ as a keyword) keep working
|
|
8
|
-
# with both built-in and caller-provided keys.
|
|
9
|
-
#
|
|
10
|
-
# Reserved keys (+:skills+, +:token_usage+) cannot be set by the caller
|
|
11
|
-
# at construction; they are owned by Riffer and written through the typed
|
|
12
|
-
# setters. Type invariants are enforced on write — +skills+ must be a
|
|
13
|
-
# +Riffer::Skills::Context+ (or nil); +token_usage+ must be a
|
|
14
|
-
# +Riffer::Providers::TokenUsage+ (or nil).
|
|
15
|
-
#
|
|
16
|
-
# context = Riffer::Agent::Context.new(user_id: 42)
|
|
17
|
-
# context[:user_id] # => 42
|
|
18
|
-
# context.skills # => nil
|
|
19
|
-
# context.token_usage # => nil
|
|
20
|
-
#
|
|
4
|
+
# Typed value object wrapping the runtime context Hash held by a Riffer::Agent.
|
|
5
|
+
# Exposes typed +skills+, +token_usage+, +mcp_progressive_tools+, and
|
|
6
|
+
# +discovered_tools+ accessors while preserving +#[]+ / +#dig+ for caller-provided keys.
|
|
21
7
|
class Riffer::Agent::Context
|
|
22
8
|
# @rbs @data: Hash[Symbol, untyped]
|
|
23
9
|
|
|
24
|
-
|
|
25
|
-
# constructor raises +Riffer::ArgumentError+.
|
|
26
|
-
RESERVED_KEYS = [:skills, :token_usage].freeze #: Array[Symbol]
|
|
10
|
+
RESERVED_KEYS = [:skills, :token_usage, :mcp_progressive_tools, :discovered_tools].freeze #: Array[Symbol]
|
|
27
11
|
|
|
28
|
-
# Builds a new context.
|
|
29
|
-
#
|
|
30
|
-
# [data] caller-provided Hash passed as <tt>Agent.new(context:)</tt>.
|
|
31
|
-
# Duped before storage so caller mutations do not affect the
|
|
32
|
-
# agent. Must not contain any +RESERVED_KEYS+.
|
|
33
|
-
#
|
|
34
|
-
# Raises Riffer::ArgumentError when +data+ contains a reserved key.
|
|
35
|
-
#
|
|
12
|
+
# Builds a new context. The caller Hash is duped so later caller mutations
|
|
13
|
+
# don't leak in. Raises Riffer::ArgumentError if it contains a reserved key.
|
|
36
14
|
#--
|
|
37
15
|
#: (?Hash[Symbol, untyped]) -> void
|
|
38
16
|
def initialize(data = {})
|
|
@@ -45,6 +23,8 @@ class Riffer::Agent::Context
|
|
|
45
23
|
@data = data.dup
|
|
46
24
|
@data[:skills] = nil
|
|
47
25
|
@data[:token_usage] = nil
|
|
26
|
+
@data[:mcp_progressive_tools] = nil
|
|
27
|
+
@data[:discovered_tools] = nil
|
|
48
28
|
end
|
|
49
29
|
|
|
50
30
|
# The agent's resolved +Riffer::Skills::Context+, or +nil+ when skills
|
|
@@ -56,12 +36,8 @@ class Riffer::Agent::Context
|
|
|
56
36
|
@data[:skills]
|
|
57
37
|
end
|
|
58
38
|
|
|
59
|
-
# Sets the resolved skills context.
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# Raises Riffer::ArgumentError if +value+ is neither +nil+ nor a
|
|
63
|
-
# +Riffer::Skills::Context+.
|
|
64
|
-
#
|
|
39
|
+
# Sets the resolved skills context. Raises Riffer::ArgumentError on an
|
|
40
|
+
# invalid value.
|
|
65
41
|
#--
|
|
66
42
|
#: (Riffer::Skills::Context?) -> Riffer::Skills::Context?
|
|
67
43
|
def skills=(value)
|
|
@@ -81,12 +57,8 @@ class Riffer::Agent::Context
|
|
|
81
57
|
@data[:token_usage]
|
|
82
58
|
end
|
|
83
59
|
|
|
84
|
-
# Sets the cumulative token usage.
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
# Raises Riffer::ArgumentError if +value+ is neither +nil+ nor a
|
|
88
|
-
# +Riffer::Providers::TokenUsage+.
|
|
89
|
-
#
|
|
60
|
+
# Sets the cumulative token usage. Raises Riffer::ArgumentError on an invalid
|
|
61
|
+
# value.
|
|
90
62
|
#--
|
|
91
63
|
#: (Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
|
|
92
64
|
def token_usage=(value)
|
|
@@ -97,28 +69,76 @@ class Riffer::Agent::Context
|
|
|
97
69
|
@data[:token_usage] = value
|
|
98
70
|
end
|
|
99
71
|
|
|
100
|
-
# Hash-style read
|
|
101
|
-
#
|
|
102
|
-
# <tt>context[:tenant]</tt> keep working unchanged.
|
|
103
|
-
#
|
|
72
|
+
# Hash-style read, preserved so tools can pull caller-provided keys via
|
|
73
|
+
# <tt>context[:agent]</tt>.
|
|
104
74
|
#--
|
|
105
75
|
#: (Symbol) -> untyped
|
|
106
76
|
def [](key)
|
|
107
77
|
@data[key]
|
|
108
78
|
end
|
|
109
79
|
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
80
|
+
# Auth-wrapped MCP tool classes for progressive discovery, or +nil+.
|
|
81
|
+
#--
|
|
82
|
+
#: () -> Array[singleton(Riffer::Tool)]?
|
|
83
|
+
def mcp_progressive_tools
|
|
84
|
+
@data[:mcp_progressive_tools]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Sets progressive MCP tools. Raises Riffer::ArgumentError on an invalid value.
|
|
88
|
+
#--
|
|
89
|
+
#: (Array[singleton(Riffer::Tool)]?) -> Array[singleton(Riffer::Tool)]?
|
|
90
|
+
def mcp_progressive_tools=(value)
|
|
91
|
+
valid = value.nil? || (
|
|
92
|
+
value.is_a?(Array) &&
|
|
93
|
+
value.all? { |tool| tool.is_a?(Class) && tool < Riffer::Tool }
|
|
94
|
+
)
|
|
95
|
+
unless valid
|
|
96
|
+
raise Riffer::ArgumentError,
|
|
97
|
+
"mcp_progressive_tools must be an Array of Riffer::Tool subclasses or nil, got #{value.class}"
|
|
98
|
+
end
|
|
99
|
+
@data[:mcp_progressive_tools] = value
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# MCP tool classes discovered during progressive search. Accumulates across
|
|
103
|
+
# +generate+ calls and is merged into the active tool list on every LLM call.
|
|
104
|
+
#--
|
|
105
|
+
#: () -> Array[singleton(Riffer::Tool)]?
|
|
106
|
+
def discovered_tools
|
|
107
|
+
@data[:discovered_tools]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Sets the discovered tools array. Raises Riffer::ArgumentError on an invalid value.
|
|
111
|
+
#--
|
|
112
|
+
#: (Array[singleton(Riffer::Tool)]?) -> Array[singleton(Riffer::Tool)]?
|
|
113
|
+
def discovered_tools=(value)
|
|
114
|
+
valid = value.nil? || (
|
|
115
|
+
value.is_a?(Array) &&
|
|
116
|
+
value.all? { |tool| tool.is_a?(Class) && tool < Riffer::Tool }
|
|
117
|
+
)
|
|
118
|
+
unless valid
|
|
119
|
+
raise Riffer::ArgumentError,
|
|
120
|
+
"discovered_tools must be an Array of Riffer::Tool subclasses or nil, got #{value.class}"
|
|
121
|
+
end
|
|
122
|
+
@data[:discovered_tools] = value
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Accumulates newly discovered MCP tool classes, deduplicating by name.
|
|
126
|
+
# Each call extends the existing set; calling multiple times is safe.
|
|
127
|
+
#--
|
|
128
|
+
#: (Array[singleton(Riffer::Tool)]) -> Array[singleton(Riffer::Tool)]
|
|
129
|
+
def discover_tools(tools)
|
|
130
|
+
existing = @data[:discovered_tools] || []
|
|
131
|
+
@data[:discovered_tools] = (existing + tools).uniq(&:name)
|
|
132
|
+
end
|
|
133
|
+
|
|
113
134
|
#--
|
|
114
135
|
#: (*Symbol) -> untyped
|
|
115
136
|
def dig(*keys)
|
|
116
137
|
@data.dig(*keys)
|
|
117
138
|
end
|
|
118
139
|
|
|
119
|
-
# Returns a copy of the underlying Hash
|
|
120
|
-
#
|
|
121
|
-
#
|
|
140
|
+
# Returns a copy of the underlying Hash; mutating it does not affect this
|
|
141
|
+
# context.
|
|
122
142
|
#--
|
|
123
143
|
#: () -> Hash[Symbol, untyped]
|
|
124
144
|
def to_h
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Wraps agent generation
|
|
5
|
-
#
|
|
6
|
-
# When guardrails block execution, the response will contain a tripwire
|
|
7
|
-
# with details about the block. The content will be empty for blocked responses.
|
|
4
|
+
# Wraps an agent generation response. When a guardrail blocks execution,
|
|
5
|
+
# +content+ is empty and +tripwire+ carries the block details.
|
|
8
6
|
#
|
|
9
7
|
# response = agent.generate("Hello")
|
|
10
8
|
# if response.blocked?
|
|
@@ -33,24 +31,10 @@ class Riffer::Agent::Response
|
|
|
33
31
|
# The full message history from the agent conversation.
|
|
34
32
|
attr_reader :messages #: Array[Riffer::Messages::Base]
|
|
35
33
|
|
|
36
|
-
# Call ids of tool_use blocks
|
|
37
|
-
#
|
|
38
|
-
# unanswered and +Riffer.config.experimental_history_healing+ is on.
|
|
39
|
-
# Empty otherwise.
|
|
34
|
+
# Call ids of tool_use blocks riffer filled with placeholder results this
|
|
35
|
+
# turn (when an interrupt left them unanswered and history healing is on).
|
|
40
36
|
attr_reader :healed_tool_call_ids #: Array[String]
|
|
41
37
|
|
|
42
|
-
# Creates a new response.
|
|
43
|
-
#
|
|
44
|
-
# [content] the response content.
|
|
45
|
-
# [tripwire] optional tripwire for blocked responses.
|
|
46
|
-
# [modifications] guardrail modifications applied during processing.
|
|
47
|
-
# [interrupted] whether the agent loop was interrupted by a callback.
|
|
48
|
-
# [interrupt_reason] optional reason passed via <tt>throw :riffer_interrupt, reason</tt>.
|
|
49
|
-
# [structured_output] parsed structured output when structured output is configured.
|
|
50
|
-
# [messages] the full message history from the agent conversation.
|
|
51
|
-
# [healed_tool_call_ids] call ids filled with placeholder tool results
|
|
52
|
-
# when history healing is enabled.
|
|
53
|
-
#
|
|
54
38
|
#--
|
|
55
39
|
#: (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
|
|
56
40
|
def initialize(content, tripwire: nil, modifications: [], interrupted: false, interrupt_reason: nil, structured_output: nil, messages: [], healed_tool_call_ids: [])
|
data/lib/riffer/agent/run.rb
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# runtime, structured output, session, context); Run just orchestrates.
|
|
7
|
-
#
|
|
8
|
-
# Tools and user code see the agent's +context+ (a +Riffer::Agent::Context+)
|
|
9
|
-
# unchanged through the loop, so downstream tool runtimes can read
|
|
10
|
-
# caller-provided keys via <tt>context[:agent]</tt> /
|
|
11
|
-
# <tt>context.dig(:key)</tt>, or the framework built-ins via
|
|
12
|
-
# +context.skills+. Cumulative token usage is updated into
|
|
13
|
-
# +agent.context.token_usage+ as the loop progresses.
|
|
14
|
-
#
|
|
15
|
-
# Riffer::Agent::Run.generate(agent: my_agent, prompt: "Hello")
|
|
16
|
-
# Riffer::Agent::Run.stream(agent: my_agent, prompt: "Hello")
|
|
17
|
-
#
|
|
4
|
+
# The generation loop — a pure module of functions over an +agent+, which owns
|
|
5
|
+
# every per-call value; Run just orchestrates.
|
|
18
6
|
module Riffer::Agent::Run
|
|
19
7
|
extend self
|
|
20
|
-
include Riffer::Messages::Converter
|
|
21
8
|
|
|
22
9
|
# Runs the generate loop for the given agent. See Riffer::Agent#generate
|
|
23
10
|
# for prompt/files semantics.
|
|
@@ -41,13 +28,6 @@ module Riffer::Agent::Run
|
|
|
41
28
|
|
|
42
29
|
private
|
|
43
30
|
|
|
44
|
-
# The generation loop. When +stream_yielder+ is provided, per-step events are
|
|
45
|
-
# pushed to it (and +stream+ discards the return value). When +stream_yielder+
|
|
46
|
-
# is +nil+, no events are emitted and +generate+ returns the Response
|
|
47
|
-
# directly. The two modes share every step of the loop — the only
|
|
48
|
-
# divergences are the LLM call shape (atomic vs. accumulated stream)
|
|
49
|
-
# and whether per-step events are emitted.
|
|
50
|
-
#
|
|
51
31
|
#--
|
|
52
32
|
#: (Riffer::Agent, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
53
33
|
def run_loop(agent, stream_yielder: nil)
|
|
@@ -86,17 +66,12 @@ module Riffer::Agent::Run
|
|
|
86
66
|
return final_response(agent, all_modifications)
|
|
87
67
|
end
|
|
88
68
|
|
|
89
|
-
# catch returns the thrown value when throw :riffer_interrupt fires;
|
|
90
|
-
# the return above exits on the successful (non-interrupted) path.
|
|
91
69
|
new_messages, filled = Riffer::Agent::Session::Repair.fill_orphans(agent.session.messages)
|
|
92
70
|
agent.session.set(new_messages)
|
|
93
71
|
stream_yielder << Riffer::StreamEvents::Interrupt.new(reason: reason, healed_tool_call_ids: filled) if stream_yielder
|
|
94
72
|
final_response(agent, all_modifications, interrupted: true, interrupt_reason: reason, healed_tool_call_ids: filled)
|
|
95
73
|
end
|
|
96
74
|
|
|
97
|
-
# Consumes one provider stream, forwarding every event to +stream_yielder+
|
|
98
|
-
# and folding it into an +Assistant+ message.
|
|
99
|
-
#
|
|
100
75
|
#--
|
|
101
76
|
#: (Riffer::Agent, Enumerator::Yielder) -> Riffer::Messages::Assistant
|
|
102
77
|
def accumulate_streamed_response(agent, stream_yielder)
|
|
@@ -130,9 +105,6 @@ module Riffer::Agent::Run
|
|
|
130
105
|
)
|
|
131
106
|
end
|
|
132
107
|
|
|
133
|
-
# Appends +new_modifications+ to +all_modifications+ and emits a
|
|
134
|
-
# +GuardrailModification+ event for each one when streaming.
|
|
135
|
-
#
|
|
136
108
|
#--
|
|
137
109
|
#: (Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], Array[Riffer::Guardrails::Modification]) -> void
|
|
138
110
|
def record_modifications!(stream_yielder, all_modifications, new_modifications)
|
|
@@ -140,9 +112,6 @@ module Riffer::Agent::Run
|
|
|
140
112
|
new_modifications.each { |m| stream_yielder << Riffer::StreamEvents::GuardrailModification.new(m) } if stream_yielder
|
|
141
113
|
end
|
|
142
114
|
|
|
143
|
-
# Emits a +GuardrailTripwire+ event when streaming and returns the
|
|
144
|
-
# short-circuit +Response+ for a tripped guardrail.
|
|
145
|
-
#
|
|
146
115
|
#--
|
|
147
116
|
#: (Riffer::Agent, Enumerator::Yielder?, Riffer::Guardrails::Tripwire, Array[Riffer::Guardrails::Modification]) -> Riffer::Agent::Response
|
|
148
117
|
def tripwire_response(agent, stream_yielder, tripwire, all_modifications)
|
|
@@ -150,11 +119,6 @@ module Riffer::Agent::Run
|
|
|
150
119
|
build_response(agent, "", tripwire: tripwire, modifications: all_modifications)
|
|
151
120
|
end
|
|
152
121
|
|
|
153
|
-
# Builds the final +Response+ from the session's last assistant
|
|
154
|
-
# message, validating structured output when configured. +extra+
|
|
155
|
-
# carries the interrupt-only fields (+interrupted:+, +interrupt_reason:+,
|
|
156
|
-
# +healed_tool_call_ids:+) on the interrupt exit path.
|
|
157
|
-
#
|
|
158
122
|
#--
|
|
159
123
|
#: (Riffer::Agent, Array[Riffer::Guardrails::Modification], **untyped) -> Riffer::Agent::Response
|
|
160
124
|
def final_response(agent, all_modifications, **extra)
|
|
@@ -168,7 +132,7 @@ module Riffer::Agent::Run
|
|
|
168
132
|
agent.provider.generate_text(
|
|
169
133
|
messages: agent.session.messages,
|
|
170
134
|
model: agent.model_name,
|
|
171
|
-
tools: agent
|
|
135
|
+
tools: effective_tools(agent),
|
|
172
136
|
**merged_model_options(agent)
|
|
173
137
|
)
|
|
174
138
|
end
|
|
@@ -179,7 +143,7 @@ module Riffer::Agent::Run
|
|
|
179
143
|
agent.provider.stream_text(
|
|
180
144
|
messages: agent.session.messages,
|
|
181
145
|
model: agent.model_name,
|
|
182
|
-
tools: agent
|
|
146
|
+
tools: effective_tools(agent),
|
|
183
147
|
**merged_model_options(agent)
|
|
184
148
|
)
|
|
185
149
|
end
|
|
@@ -189,7 +153,9 @@ module Riffer::Agent::Run
|
|
|
189
153
|
def execute_tool_calls(agent, assistant_message, tool_calls: assistant_message.tool_calls)
|
|
190
154
|
return if tool_calls.empty?
|
|
191
155
|
|
|
192
|
-
results = agent.tool_runtime.execute(tool_calls, tools: agent
|
|
156
|
+
results = agent.tool_runtime.execute(tool_calls, tools: effective_tools(agent), context: agent.context, assistant_message: assistant_message)
|
|
157
|
+
|
|
158
|
+
inject_discovered_tools(agent, results)
|
|
193
159
|
|
|
194
160
|
results.each do |tool_call, result|
|
|
195
161
|
agent.session.add(Riffer::Messages::Tool.new(
|
|
@@ -202,12 +168,17 @@ module Riffer::Agent::Run
|
|
|
202
168
|
end
|
|
203
169
|
end
|
|
204
170
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
171
|
+
#--
|
|
172
|
+
#: (Riffer::Agent, Array[[Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]]) -> void
|
|
173
|
+
def inject_discovered_tools(agent, results)
|
|
174
|
+
to_inject = results.flat_map { |_, result|
|
|
175
|
+
result.is_a?(Riffer::Mcp::SearchTool::Result) ? result.discovered_tools : [] #: Array[singleton(Riffer::Tool)]
|
|
176
|
+
}
|
|
177
|
+
return if to_inject.empty?
|
|
178
|
+
|
|
179
|
+
agent.context.discover_tools(to_inject)
|
|
180
|
+
end
|
|
181
|
+
|
|
211
182
|
#--
|
|
212
183
|
#: (Riffer::Agent) -> void
|
|
213
184
|
def execute_pending_tool_calls(agent)
|
|
@@ -215,11 +186,6 @@ module Riffer::Agent::Run
|
|
|
215
186
|
execute_tool_calls(agent, assistant_message, tool_calls: pending) if assistant_message
|
|
216
187
|
end
|
|
217
188
|
|
|
218
|
-
# Runs the +:before+ guardrail phase. Records any modifications into
|
|
219
|
-
# +all_modifications+ (and emits them when streaming). When a tripwire
|
|
220
|
-
# fires, yields the short-circuit +Response+ — the caller's block is
|
|
221
|
-
# expected to +return+ it from +run_loop+.
|
|
222
|
-
#
|
|
223
189
|
#--
|
|
224
190
|
#: (Riffer::Agent, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Agent::Response) -> void } -> void
|
|
225
191
|
def run_before_guardrails(agent, stream_yielder, all_modifications)
|
|
@@ -233,12 +199,6 @@ module Riffer::Agent::Run
|
|
|
233
199
|
yield tripwire_response(agent, stream_yielder, tripwire, all_modifications) if tripwire
|
|
234
200
|
end
|
|
235
201
|
|
|
236
|
-
# Runs the +:after+ guardrail phase against the assistant +response+.
|
|
237
|
-
# Records any modifications into +all_modifications+ (and emits them
|
|
238
|
-
# when streaming). When a tripwire fires, yields the short-circuit
|
|
239
|
-
# +Response+ — the caller's block is expected to +return+ it from
|
|
240
|
-
# +run_loop+. Otherwise returns the post-guardrails assistant message.
|
|
241
|
-
#
|
|
242
202
|
#--
|
|
243
203
|
#: (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Agent::Response) -> void } -> untyped
|
|
244
204
|
def run_after_guardrails(agent, response, stream_yielder, all_modifications)
|
|
@@ -265,6 +225,13 @@ module Riffer::Agent::Run
|
|
|
265
225
|
agent.structured_output.parse_and_validate(response.content).object
|
|
266
226
|
end
|
|
267
227
|
|
|
228
|
+
#--
|
|
229
|
+
#: (Riffer::Agent) -> Array[singleton(Riffer::Tool)]
|
|
230
|
+
def effective_tools(agent)
|
|
231
|
+
discovered = agent.context.discovered_tools || []
|
|
232
|
+
discovered.empty? ? agent.tools : agent.tools + discovered
|
|
233
|
+
end
|
|
234
|
+
|
|
268
235
|
#--
|
|
269
236
|
#: (Riffer::Agent) -> Hash[Symbol, untyped]
|
|
270
237
|
def merged_model_options(agent)
|
|
@@ -280,24 +247,18 @@ module Riffer::Agent::Run
|
|
|
280
247
|
Riffer::Agent::Response.new(content, tripwire: tripwire, modifications: modifications, interrupted: interrupted, interrupt_reason: interrupt_reason, structured_output: structured_output, messages: messages.frozen? ? messages : messages.dup.freeze, healed_tool_call_ids: healed_tool_call_ids)
|
|
281
248
|
end
|
|
282
249
|
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
# +files+ are supplied without a +prompt+ — the provider needs text to
|
|
286
|
-
# anchor the attachments.
|
|
287
|
-
#
|
|
250
|
+
# Raises when +files+ are supplied without a +prompt+ — the provider needs
|
|
251
|
+
# text to anchor the attachments.
|
|
288
252
|
#--
|
|
289
253
|
#: (Riffer::Agent, String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> void
|
|
290
254
|
def append_user_message(agent, prompt, files: nil)
|
|
291
255
|
raise Riffer::ArgumentError, "files: requires a prompt" if files && !files.empty? && prompt.nil?
|
|
292
256
|
return unless prompt
|
|
293
257
|
|
|
294
|
-
file_parts = (files || []).map { |f|
|
|
258
|
+
file_parts = (files || []).map { |f| Riffer::Messages::FilePart.from_hash(f) }
|
|
295
259
|
agent.session.add(Riffer::Messages::User.new(prompt, files: file_parts), silent: true)
|
|
296
260
|
end
|
|
297
261
|
|
|
298
|
-
# Accumulates token usage into +agent.context.token_usage+. Updates the
|
|
299
|
-
# context so cumulative usage persists across every run on the agent.
|
|
300
|
-
#
|
|
301
262
|
#--
|
|
302
263
|
#: (Riffer::Agent, Riffer::Providers::TokenUsage?) -> void
|
|
303
264
|
def track_token_usage(agent, usage)
|
|
@@ -3,56 +3,29 @@
|
|
|
3
3
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# +Riffer::Agent#to_h+ / +Riffer::Agent.from_h+ delegators.
|
|
6
|
+
# Turns a resolved agent into a self-contained, provider-neutral data hash and
|
|
7
|
+
# back into a runnable agent, behind the +Riffer::Agent#to_h+ /
|
|
8
|
+
# +Riffer::Agent.from_h+ delegators.
|
|
10
9
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# - <b>In-process</b> (a monolith persisting agent definitions): pass a
|
|
15
|
-
# +tool_resolver+ that looks tool descriptors up in a local registry and
|
|
16
|
-
# returns the real, body-bearing classes. They run on the default runtime.
|
|
17
|
-
# - <b>Distributed</b> (a receiver holding only the Riffer gem): the default
|
|
18
|
-
# resolver synthesizes body-less tool shells; inject a remote
|
|
19
|
-
# +Riffer::Tools::Runtime+ to forward each call back to the origin.
|
|
20
|
-
#
|
|
21
|
-
# dict = Riffer::Agent::Serializer.to_h(agent: agent)
|
|
22
|
-
# rebuilt = Riffer::Agent::Serializer.from_h(dict, context: {tenant: "acme"})
|
|
23
|
-
#
|
|
24
|
-
# == What does not transfer
|
|
25
|
-
#
|
|
26
|
-
# Guardrails and the skills subsystem (backend/adapter/catalog) are not
|
|
27
|
-
# serialized; a rebuilt agent enforces no guardrails and renders no skills
|
|
28
|
-
# catalog (the +skill_activate+ tool, if present, crosses as an ordinary
|
|
29
|
-
# tool). Secrets must not be placed in +provider_options+/+model_options+:
|
|
30
|
-
# both ride on the wire as plain data.
|
|
10
|
+
# hash = Riffer::Agent::Serializer.to_h(agent: agent)
|
|
11
|
+
# rebuilt = Riffer::Agent::Serializer.from_h(hash, context: {tenant: "acme"})
|
|
31
12
|
module Riffer::Agent::Serializer
|
|
32
13
|
extend self
|
|
33
14
|
|
|
34
|
-
# The wire format version
|
|
35
|
-
#
|
|
36
|
-
# dispatch seam that carries back-compat decoders.
|
|
15
|
+
# The wire format version, bumped only on an incompatible change to the hash
|
|
16
|
+
# shape; +from_h+ refuses any other version.
|
|
37
17
|
SCHEMA_VERSION = 1 #: Integer
|
|
38
18
|
|
|
39
|
-
# Raised by +from_h+ when the
|
|
19
|
+
# Raised by +from_h+ when the hash's +schema_version+ is unsupported.
|
|
40
20
|
class VersionError < Riffer::ArgumentError; end
|
|
41
21
|
|
|
42
22
|
# The default +tool_resolver+: synthesizes a body-less tool shell from a
|
|
43
23
|
# descriptor. Its +#call+ raises — route shells through a remote runtime.
|
|
44
24
|
DEFAULT_TOOL_RESOLVER = ->(descriptor) { build_tool_shell(descriptor) } #: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool)
|
|
45
25
|
|
|
46
|
-
# Snapshots a resolved agent into a self-contained wire
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# already been evaluated against the agent's own context, so the dict
|
|
50
|
-
# carries plain strings/data, never Procs. Tools are emitted as
|
|
51
|
-
# +{name, description, parameters_schema, timeout}+ descriptors (the
|
|
52
|
-
# resolved +agent.tools+, including MCP tools and +skill_activate+).
|
|
53
|
-
#
|
|
54
|
-
# [agent] a resolved Riffer::Agent instance.
|
|
55
|
-
#
|
|
26
|
+
# Snapshots a resolved agent into a self-contained wire hash. Proc-based
|
|
27
|
+
# settings are already evaluated against the agent's context, so the hash
|
|
28
|
+
# carries plain data, never Procs.
|
|
56
29
|
#--
|
|
57
30
|
#: (agent: Riffer::Agent) -> Hash[Symbol, untyped]
|
|
58
31
|
def to_h(agent:)
|
|
@@ -71,61 +44,45 @@ module Riffer::Agent::Serializer
|
|
|
71
44
|
}
|
|
72
45
|
end
|
|
73
46
|
|
|
74
|
-
# Reconstructs a runnable agent from a wire
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
# config (the dict is already resolved); it is threaded into tool dispatch
|
|
80
|
-
# and read by tools/runtimes at call time (e.g. a remote runtime keying off
|
|
81
|
-
# <tt>context[:tenant]</tt>). Defaults to an empty context.
|
|
82
|
-
# [tool_resolver] maps a tool descriptor to a Riffer::Tool class. Defaults
|
|
83
|
-
# to DEFAULT_TOOL_RESOLVER (body-less shells). Pass a registry lookup to
|
|
84
|
-
# rebuild real, in-process tools.
|
|
85
|
-
# [tool_runtime] an optional Riffer::Tools::Runtime to inject (e.g. a
|
|
86
|
-
# remote runtime for shells). When omitted, the agent uses the configured
|
|
87
|
-
# default (+Riffer.config.tool_runtime+).
|
|
88
|
-
#
|
|
89
|
-
# Raises Riffer::Agent::Serializer::VersionError on an unsupported
|
|
90
|
-
# +schema_version+, and Riffer::ArgumentError on a malformed dict.
|
|
47
|
+
# Reconstructs a runnable agent from a wire hash. +context+ is threaded into
|
|
48
|
+
# tool dispatch (not used to re-resolve the already-resolved config);
|
|
49
|
+
# +session+ seeds conversation history (the hash carries the agent definition,
|
|
50
|
+
# not its history). Raises Riffer::Agent::Serializer::VersionError on an
|
|
51
|
+
# unsupported +schema_version+.
|
|
91
52
|
#
|
|
92
53
|
#--
|
|
93
|
-
#: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
94
|
-
def from_h(hash, context: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
54
|
+
#: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
55
|
+
def from_h(hash, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
95
56
|
# Version -> decoder dispatch. Adding a +when 2+ arm (a backwards-compatible
|
|
96
|
-
# decoder) is how a future breaking change keeps older
|
|
57
|
+
# decoder) is how a future breaking change keeps older hashes readable.
|
|
97
58
|
case hash[:schema_version]
|
|
98
59
|
when SCHEMA_VERSION
|
|
99
|
-
decode_v1(hash, context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
60
|
+
decode_v1(hash, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
100
61
|
else
|
|
101
62
|
raise VersionError, "Unsupported schema_version: #{hash[:schema_version].inspect} (this Riffer supports #{SCHEMA_VERSION})"
|
|
102
63
|
end
|
|
103
64
|
end
|
|
104
65
|
|
|
105
|
-
# Snapshots a resolved agent to a JSON string.
|
|
106
|
-
# <tt>JSON.generate(to_h(agent:))</tt>.
|
|
107
|
-
#
|
|
66
|
+
# Snapshots a resolved agent to a JSON string.
|
|
108
67
|
#--
|
|
109
68
|
#: (agent: Riffer::Agent) -> String
|
|
110
69
|
def to_json(agent:)
|
|
111
70
|
JSON.generate(to_h(agent: agent))
|
|
112
71
|
end
|
|
113
72
|
|
|
114
|
-
# Reconstructs a runnable agent from a JSON string produced by +to_json+.
|
|
115
|
-
# Handles the JSON parse (with symbol keys) so callers don't have to. See
|
|
73
|
+
# Reconstructs a runnable agent from a JSON string produced by +to_json+. See
|
|
116
74
|
# +from_h+ for the arguments.
|
|
117
|
-
#
|
|
118
75
|
#--
|
|
119
|
-
#: (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
120
|
-
def from_json(json, context: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
121
|
-
from_h(JSON.parse(json, symbolize_names: true), context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
76
|
+
#: (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
77
|
+
def from_json(json, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
78
|
+
from_h(JSON.parse(json, symbolize_names: true), context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
122
79
|
end
|
|
123
80
|
|
|
124
81
|
private
|
|
125
82
|
|
|
126
83
|
#--
|
|
127
|
-
#: (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
128
|
-
def decode_v1(hash, context:, tool_resolver:, tool_runtime:)
|
|
84
|
+
#: (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, session: Riffer::Agent::Session?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
85
|
+
def decode_v1(hash, context:, session:, tool_resolver:, tool_runtime:)
|
|
129
86
|
tools = Array(hash[:tools]).map { |descriptor| tool_resolver.call(descriptor) }
|
|
130
87
|
|
|
131
88
|
config_args = {
|
|
@@ -142,7 +99,11 @@ module Riffer::Agent::Serializer
|
|
|
142
99
|
# Config default (Riffer.config.tool_runtime) applies.
|
|
143
100
|
config_args[:tool_runtime] = tool_runtime if tool_runtime
|
|
144
101
|
|
|
145
|
-
|
|
102
|
+
# +session+ is forwarded verbatim: when nil, Agent.new seeds a fresh session
|
|
103
|
+
# from the decoded instructions; when supplied, Agent.new uses it as-is to
|
|
104
|
+
# resume persisted history. The hash never carries history (see "What does
|
|
105
|
+
# not transfer"), so this is the only seam for rehydrating a conversation.
|
|
106
|
+
Riffer::Agent.new(config: Riffer::Agent::Config.new(**config_args), context: context, session: session)
|
|
146
107
|
end
|
|
147
108
|
|
|
148
109
|
#--
|
|
@@ -152,20 +113,16 @@ module Riffer::Agent::Serializer
|
|
|
152
113
|
Riffer::Params.from_json_schema(schema)
|
|
153
114
|
end
|
|
154
115
|
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
# awkward (e.g. proto3, which can't tell null from an absent field). The
|
|
158
|
-
# magic value lives only on the wire — +encode_max_steps+/+decode_max_steps+
|
|
159
|
-
# translate at the boundary so neither the DSL nor consumers see it.
|
|
116
|
+
# Encodes unlimited steps (+nil+ in the DSL) as +-1+ on the wire, where a
|
|
117
|
+
# JSON +null+ is awkward across transports (e.g. proto3).
|
|
160
118
|
#--
|
|
161
119
|
#: (Numeric?) -> Numeric
|
|
162
120
|
def encode_max_steps(value)
|
|
163
121
|
value.nil? ? -1 : value
|
|
164
122
|
end
|
|
165
123
|
|
|
166
|
-
# Reverses +encode_max_steps
|
|
167
|
-
#
|
|
168
|
-
# become an unbounded loop.
|
|
124
|
+
# Reverses +encode_max_steps+; a missing key falls back to the default so a
|
|
125
|
+
# partial hash can't become an unbounded loop.
|
|
169
126
|
#--
|
|
170
127
|
#: (Hash[Symbol, untyped]) -> Numeric?
|
|
171
128
|
def decode_max_steps(hash)
|
|
@@ -179,12 +136,6 @@ module Riffer::Agent::Serializer
|
|
|
179
136
|
tool_class.to_tool_schema(strict: false).merge(timeout: tool_class.timeout)
|
|
180
137
|
end
|
|
181
138
|
|
|
182
|
-
# Builds an anonymous, body-less Riffer::Tool subclass that advertises the
|
|
183
|
-
# descriptor's schema to the LLM. Its +#call+ raises — a shell only has
|
|
184
|
-
# identity, not behavior; route its calls through a remote runtime.
|
|
185
|
-
#
|
|
186
|
-
# Returns +untyped+: steep can't see that +Class.new(Riffer::Tool)+ is a
|
|
187
|
-
# +singleton(Riffer::Tool)+ (cf. Riffer::Mcp::ToolFactory#build_tool_class).
|
|
188
139
|
#--
|
|
189
140
|
#: (Hash[Symbol, untyped]) -> untyped
|
|
190
141
|
def build_tool_shell(descriptor)
|