riffer 0.29.0 → 0.30.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/rbs-inline.md +51 -0
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +18 -0
- data/README.md +1 -0
- data/Steepfile +2 -1
- data/docs/01_OVERVIEW.md +1 -0
- data/docs/03_AGENTS.md +1 -1
- data/docs/15_SERIALIZATION.md +103 -0
- data/lib/riffer/agent/config.rb +2 -2
- data/lib/riffer/agent/context.rb +2 -0
- data/lib/riffer/agent/response.rb +2 -0
- data/lib/riffer/agent/run.rb +2 -1
- data/lib/riffer/agent/serializer.rb +215 -0
- data/lib/riffer/agent/session.rb +2 -0
- data/lib/riffer/agent.rb +84 -18
- data/lib/riffer/evals/evaluator.rb +5 -0
- data/lib/riffer/evals/judge.rb +5 -0
- data/lib/riffer/mcp/client.rb +2 -0
- data/lib/riffer/mcp/registration.rb +4 -0
- data/lib/riffer/mcp/registry.rb +3 -0
- data/lib/riffer/messages/file_part.rb +2 -0
- data/lib/riffer/params/param.rb +84 -4
- data/lib/riffer/params.rb +34 -3
- data/lib/riffer/providers/amazon_bedrock.rb +28 -21
- data/lib/riffer/providers/anthropic.rb +13 -9
- data/lib/riffer/providers/base.rb +2 -0
- data/lib/riffer/providers/gemini.rb +4 -0
- data/lib/riffer/providers/mock.rb +4 -0
- data/lib/riffer/providers/open_ai.rb +10 -7
- data/lib/riffer/providers/open_router.rb +25 -18
- data/lib/riffer/runner/fibers.rb +2 -0
- data/lib/riffer/runner/threaded.rb +2 -0
- data/lib/riffer/skills/config.rb +5 -0
- data/lib/riffer/skills/context.rb +3 -0
- data/lib/riffer/skills/filesystem_backend.rb +3 -0
- data/lib/riffer/tools/response.rb +2 -0
- data/lib/riffer/tools/runtime.rb +2 -0
- data/lib/riffer/tools/toolable.rb +7 -0
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -0
- data/sig/_private/anthropic.rbs +16 -0
- data/sig/_private/openai.rbs +29 -0
- data/sig/_private/riffer/providers/amazon_bedrock.rbs +4 -0
- data/sig/_private/riffer/providers/anthropic.rbs +4 -0
- data/sig/_private/riffer/providers/open_ai.rbs +4 -0
- data/sig/_private/riffer/providers/open_router.rbs +4 -0
- data/sig/generated/riffer/agent/config.rbs +3 -3
- data/sig/generated/riffer/agent/context.rbs +2 -0
- data/sig/generated/riffer/agent/response.rbs +2 -0
- data/sig/generated/riffer/agent/serializer.rbs +132 -0
- data/sig/generated/riffer/agent/session.rbs +2 -0
- data/sig/generated/riffer/agent.rbs +67 -11
- data/sig/generated/riffer/evals/evaluator.rbs +8 -0
- data/sig/generated/riffer/evals/judge.rbs +8 -0
- data/sig/generated/riffer/mcp/client.rbs +2 -0
- data/sig/generated/riffer/mcp/registration.rbs +6 -0
- data/sig/generated/riffer/mcp/registry.rbs +4 -0
- data/sig/generated/riffer/messages/file_part.rbs +2 -0
- data/sig/generated/riffer/params/param.rbs +46 -5
- data/sig/generated/riffer/params.rbs +25 -6
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +20 -20
- data/sig/generated/riffer/providers/anthropic.rbs +10 -10
- data/sig/generated/riffer/providers/base.rbs +2 -0
- data/sig/generated/riffer/providers/gemini.rbs +6 -0
- data/sig/generated/riffer/providers/mock.rbs +6 -0
- data/sig/generated/riffer/providers/open_ai.rbs +8 -8
- data/sig/generated/riffer/providers/open_router.rbs +16 -16
- data/sig/generated/riffer/runner/fibers.rbs +2 -0
- data/sig/generated/riffer/runner/threaded.rbs +2 -0
- data/sig/generated/riffer/skills/config.rbs +8 -0
- data/sig/generated/riffer/skills/context.rbs +4 -0
- data/sig/generated/riffer/skills/filesystem_backend.rbs +4 -0
- data/sig/generated/riffer/tools/response.rbs +2 -0
- data/sig/generated/riffer/tools/runtime.rbs +2 -0
- data/sig/generated/riffer/tools/toolable.rbs +12 -0
- data/sig/generated/riffer.rbs +2 -0
- data/sig/manifest.yaml +3 -0
- data/sig/manual/riffer/agent/run.rbs +5 -0
- data/sig/manual/riffer/agent/serializer.rbs +5 -0
- data/sig/manual/riffer/helpers/call_or_value.rbs +5 -0
- data/sig/manual/riffer/tools/toolable.rbs +6 -0
- metadata +20 -11
- data/sig/stubs/agent_ivars.rbs +0 -7
- data/sig/stubs/extend_self.rbs +0 -11
- data/sig/stubs/lib_ivars.rbs +0 -101
- data/sig/stubs/provider_ivars.rbs +0 -36
- data/sig/stubs/provider_sdk_methods.rbs +0 -50
- /data/sig/{stubs → _private}/async.rbs +0 -0
- /data/sig/{stubs → _private}/aws-sdk-core/seahorse_request_context.rbs +0 -0
- /data/sig/{stubs → _private}/aws-sdk-core/static_token_provider.rbs +0 -0
- /data/sig/{stubs/mcp_sdk.rbs → _private/mcp.rbs} +0 -0
- /data/sig/{stubs → _private}/zeitwerk.rbs +0 -0
|
@@ -66,15 +66,16 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
#--
|
|
69
|
-
#: (Hash[Symbol, untyped]) ->
|
|
69
|
+
#: (Hash[Symbol, untyped]) -> untyped
|
|
70
70
|
def execute_generate(params)
|
|
71
71
|
@client.responses.create(params)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
#--
|
|
75
|
-
#: (
|
|
75
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
76
76
|
def extract_token_usage(response)
|
|
77
|
-
|
|
77
|
+
typed_response = response #: OpenAI::Models::Responses::Response
|
|
78
|
+
usage = typed_response.usage
|
|
78
79
|
return nil unless usage
|
|
79
80
|
|
|
80
81
|
Riffer::Providers::TokenUsage.new(
|
|
@@ -84,11 +85,12 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
#--
|
|
87
|
-
#: (
|
|
88
|
+
#: (untyped) -> String
|
|
88
89
|
def extract_content(response)
|
|
90
|
+
typed_response = response #: OpenAI::Models::Responses::Response
|
|
89
91
|
text_content = ""
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
typed_response.output.each do |item|
|
|
92
94
|
next unless item.is_a?(::OpenAI::Models::Responses::ResponseOutputMessage)
|
|
93
95
|
|
|
94
96
|
text_block = item.content.find { |c| c.is_a?(::OpenAI::Models::Responses::ResponseOutputText) }
|
|
@@ -99,11 +101,12 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
99
101
|
end
|
|
100
102
|
|
|
101
103
|
#--
|
|
102
|
-
#: (
|
|
104
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
103
105
|
def extract_tool_calls(response)
|
|
106
|
+
typed_response = response #: OpenAI::Models::Responses::Response
|
|
104
107
|
tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
typed_response.output.each do |item|
|
|
107
110
|
next unless item.is_a?(::OpenAI::Models::Responses::ResponseFunctionToolCall)
|
|
108
111
|
|
|
109
112
|
tool_calls << Riffer::Messages::Assistant::ToolCall.new(
|
|
@@ -64,15 +64,16 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
#--
|
|
67
|
-
#: (Hash[Symbol, untyped]) ->
|
|
67
|
+
#: (Hash[Symbol, untyped]) -> untyped
|
|
68
68
|
def execute_generate(params)
|
|
69
69
|
@client.chat.completions.create(**params)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
#--
|
|
73
|
-
#: (
|
|
73
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
74
74
|
def extract_token_usage(response)
|
|
75
|
-
|
|
75
|
+
typed_response = response #: OpenAI::Models::Chat::ChatCompletion
|
|
76
|
+
usage = typed_response.usage
|
|
76
77
|
return nil unless usage
|
|
77
78
|
|
|
78
79
|
Riffer::Providers::TokenUsage.new(
|
|
@@ -82,15 +83,17 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
#--
|
|
85
|
-
#: (
|
|
86
|
+
#: (untyped) -> String
|
|
86
87
|
def extract_content(response)
|
|
87
|
-
response
|
|
88
|
+
typed_response = response #: OpenAI::Models::Chat::ChatCompletion
|
|
89
|
+
typed_response.choices.first&.message&.content || ""
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
#--
|
|
91
|
-
#: (
|
|
93
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
92
94
|
def extract_tool_calls(response)
|
|
93
|
-
|
|
95
|
+
typed_response = response #: OpenAI::Models::Chat::ChatCompletion
|
|
96
|
+
message = typed_response.choices.first&.message
|
|
94
97
|
return [] unless message
|
|
95
98
|
|
|
96
99
|
tool_calls = message.tool_calls
|
|
@@ -145,9 +148,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
145
148
|
end
|
|
146
149
|
|
|
147
150
|
#--
|
|
148
|
-
#: (
|
|
151
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
149
152
|
def handle_stream_chunk(chunk, state:, yielder:)
|
|
150
|
-
|
|
153
|
+
typed_chunk = chunk #: OpenAI::Models::Chat::ChatCompletionChunk
|
|
154
|
+
choice = typed_chunk.choices&.first
|
|
151
155
|
delta = choice&.delta
|
|
152
156
|
|
|
153
157
|
if delta
|
|
@@ -160,20 +164,21 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
160
164
|
emit_tool_call_done_events(state: state, yielder: yielder)
|
|
161
165
|
end
|
|
162
166
|
|
|
163
|
-
return unless
|
|
167
|
+
return unless typed_chunk.usage
|
|
164
168
|
|
|
165
169
|
yielder << Riffer::StreamEvents::TokenUsageDone.new(
|
|
166
170
|
token_usage: Riffer::Providers::TokenUsage.new(
|
|
167
|
-
input_tokens:
|
|
168
|
-
output_tokens:
|
|
171
|
+
input_tokens: typed_chunk.usage.prompt_tokens,
|
|
172
|
+
output_tokens: typed_chunk.usage.completion_tokens
|
|
169
173
|
)
|
|
170
174
|
)
|
|
171
175
|
end
|
|
172
176
|
|
|
173
177
|
#--
|
|
174
|
-
#: (
|
|
178
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
175
179
|
def handle_text_delta(delta, state:, yielder:)
|
|
176
|
-
|
|
180
|
+
typed_delta = delta #: OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta
|
|
181
|
+
content = typed_delta.content
|
|
177
182
|
return if content.nil? || content.empty?
|
|
178
183
|
|
|
179
184
|
state[:text] << content
|
|
@@ -194,9 +199,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
194
199
|
end
|
|
195
200
|
|
|
196
201
|
#--
|
|
197
|
-
#: (
|
|
202
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
198
203
|
def handle_tool_call_deltas(delta, state:, yielder:)
|
|
199
|
-
|
|
204
|
+
typed_delta = delta #: OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta
|
|
205
|
+
tool_calls = typed_delta.tool_calls
|
|
200
206
|
return if tool_calls.nil? || tool_calls.empty?
|
|
201
207
|
|
|
202
208
|
tool_calls.each do |tc|
|
|
@@ -236,9 +242,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
236
242
|
end
|
|
237
243
|
|
|
238
244
|
#--
|
|
239
|
-
#: (
|
|
245
|
+
#: (untyped) -> bool
|
|
240
246
|
def finish_reason_is_tool_calls?(choice)
|
|
241
|
-
choice
|
|
247
|
+
typed_choice = choice #: OpenAI::Models::Chat::ChatCompletionChunk::Choice
|
|
248
|
+
typed_choice.finish_reason.to_s == "tool_calls"
|
|
242
249
|
end
|
|
243
250
|
|
|
244
251
|
#--
|
data/lib/riffer/runner/fibers.rb
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
# runner.map(items) { |item| expensive_operation(item) }
|
|
15
15
|
#
|
|
16
16
|
class Riffer::Runner::Fibers < Riffer::Runner
|
|
17
|
+
# @rbs @max_concurrency: Integer?
|
|
18
|
+
|
|
17
19
|
include Riffer::Helpers::Dependencies
|
|
18
20
|
|
|
19
21
|
# [max_concurrency] maximum number of fibers to run simultaneously.
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
# runner.map(items) { |item| expensive_operation(item) }
|
|
15
15
|
#
|
|
16
16
|
class Riffer::Runner::Threaded < Riffer::Runner
|
|
17
|
+
# @rbs @max_concurrency: Integer
|
|
18
|
+
|
|
17
19
|
DEFAULT_MAX_CONCURRENCY = 5 #: Integer
|
|
18
20
|
|
|
19
21
|
# [max_concurrency] maximum number of threads to run simultaneously.
|
data/lib/riffer/skills/config.rb
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
# activate ["code-review"]
|
|
12
12
|
# end
|
|
13
13
|
class Riffer::Skills::Config
|
|
14
|
+
# @rbs @backend: (Riffer::Skills::Backend | Proc)?
|
|
15
|
+
# @rbs @adapter: singleton(Riffer::Skills::Adapter)?
|
|
16
|
+
# @rbs @activate: (Array[String] | Proc)?
|
|
17
|
+
# @rbs @activate_tool: singleton(Riffer::Tool)?
|
|
18
|
+
|
|
14
19
|
# Creates a new Config with all options unset.
|
|
15
20
|
#
|
|
16
21
|
#--
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
#
|
|
12
12
|
# See Riffer::Skills::Backend, Riffer::Skills::Frontmatter.
|
|
13
13
|
class Riffer::Skills::Context
|
|
14
|
+
# @rbs @backend: Riffer::Skills::Backend
|
|
15
|
+
# @rbs @activated: Hash[String, String]
|
|
16
|
+
|
|
14
17
|
# Skill catalog indexed by name.
|
|
15
18
|
attr_reader :skills #: Hash[String, Riffer::Skills::Frontmatter]
|
|
16
19
|
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
# backend.read_skill("code-review") # => "Full skill instructions..."
|
|
12
12
|
#
|
|
13
13
|
class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
|
|
14
|
+
# @rbs @paths: Array[String]
|
|
15
|
+
# @rbs @skills_cache: Hash[String, String]?
|
|
16
|
+
|
|
14
17
|
# Creates a new FilesystemBackend.
|
|
15
18
|
#
|
|
16
19
|
# [paths] one or more directory paths to scan for skills.
|
data/lib/riffer/tools/runtime.rb
CHANGED
|
@@ -15,6 +15,8 @@ require "json"
|
|
|
15
15
|
# results = runtime.execute(tool_calls, tools: tools, context: context)
|
|
16
16
|
#
|
|
17
17
|
class Riffer::Tools::Runtime
|
|
18
|
+
# @rbs @runner: Riffer::Runner
|
|
19
|
+
|
|
18
20
|
# [runner] the concurrency runner to use for batch execution.
|
|
19
21
|
#
|
|
20
22
|
# Subclasses must provide a runner; instantiating Riffer::Tools::Runtime directly
|
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
# end
|
|
24
24
|
#
|
|
25
25
|
module Riffer::Tools::Toolable
|
|
26
|
+
# @rbs self.@extenders: Array[Module]?
|
|
27
|
+
# @rbs @description: String?
|
|
28
|
+
# @rbs @identifier: String?
|
|
29
|
+
# @rbs @timeout: (Integer | Float)?
|
|
30
|
+
# @rbs @params_builder: Riffer::Params?
|
|
31
|
+
# @rbs @kind: Symbol?
|
|
32
|
+
|
|
26
33
|
DEFAULT_TIMEOUT = 10 #: Integer
|
|
27
34
|
|
|
28
35
|
# Tracks all classes that extend Toolable.
|
data/lib/riffer/version.rb
CHANGED
data/lib/riffer.rb
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Arity patch for the `anthropic` gem (see openai.rbs for the rationale).
|
|
2
|
+
# `Messages#create` is positional at runtime but typed keyword-only in the gem's
|
|
3
|
+
# RBS; `Messages#stream` is omitted from the gem's RBS but present at runtime
|
|
4
|
+
# (returns a MessageStream helper). Loaded only for riffer's own Steep run.
|
|
5
|
+
module Anthropic
|
|
6
|
+
module Resources
|
|
7
|
+
class Messages
|
|
8
|
+
def create: (Hash[Symbol, untyped] params) -> Anthropic::Models::Message
|
|
9
|
+
| (**untyped) -> Anthropic::Models::Message
|
|
10
|
+
| ...
|
|
11
|
+
|
|
12
|
+
def stream: (Hash[Symbol, untyped] params) -> untyped
|
|
13
|
+
| (**untyped) -> untyped
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Arity patch for the `openai` gem. Its Resources methods are positional
|
|
2
|
+
# `def x(params)` at runtime, but the gem's shipped RBS advertises keyword-only
|
|
3
|
+
# signatures. We accept a positional hash or a keyword splat (`| ...` extends the
|
|
4
|
+
# gem's existing overloads). Streaming returns `untyped` — the event objects are a
|
|
5
|
+
# ~60-member union dispatched dynamically on `#type`. Loaded only for riffer's own
|
|
6
|
+
# Steep run (consumers skip sig/_private).
|
|
7
|
+
module OpenAI
|
|
8
|
+
module Resources
|
|
9
|
+
class Responses
|
|
10
|
+
def create: (Hash[Symbol, untyped] params) -> OpenAI::Models::Responses::Response
|
|
11
|
+
| (**untyped) -> OpenAI::Models::Responses::Response
|
|
12
|
+
| ...
|
|
13
|
+
def stream: (Hash[Symbol, untyped] params) -> untyped
|
|
14
|
+
| (**untyped) -> untyped
|
|
15
|
+
| ...
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Chat
|
|
19
|
+
class Completions
|
|
20
|
+
def create: (Hash[Symbol, untyped] params) -> OpenAI::Models::Chat::ChatCompletion
|
|
21
|
+
| (**untyped) -> OpenAI::Models::Chat::ChatCompletion
|
|
22
|
+
| ...
|
|
23
|
+
def stream_raw: (Hash[Symbol, untyped] params) -> untyped
|
|
24
|
+
| (**untyped) -> untyped
|
|
25
|
+
| ...
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -26,7 +26,7 @@ class Riffer::Agent::Config
|
|
|
26
26
|
|
|
27
27
|
attr_reader structured_output: Riffer::Params?
|
|
28
28
|
|
|
29
|
-
attr_accessor max_steps: Numeric
|
|
29
|
+
attr_accessor max_steps: Numeric?
|
|
30
30
|
|
|
31
31
|
attr_accessor tools_config: (Array[singleton(Riffer::Tool)] | Proc)?
|
|
32
32
|
|
|
@@ -45,8 +45,8 @@ class Riffer::Agent::Config
|
|
|
45
45
|
# as a non-String, non-Proc value (or as an empty String).
|
|
46
46
|
#
|
|
47
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
|
|
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
|
|
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
50
|
|
|
51
51
|
# Sets +identifier+. Accepts +nil+ or any value, coerced to String.
|
|
52
52
|
#
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
# context.skills # => nil
|
|
18
18
|
# context.token_usage # => nil
|
|
19
19
|
class Riffer::Agent::Context
|
|
20
|
+
@data: Hash[Symbol, untyped]
|
|
21
|
+
|
|
20
22
|
# Keys reserved for framework use. Passing any of these to the
|
|
21
23
|
# constructor raises +Riffer::ArgumentError+.
|
|
22
24
|
RESERVED_KEYS: Array[Symbol]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Generated from lib/riffer/agent/serializer.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
# Riffer::Agent::Serializer turns a resolved agent into a self-contained,
|
|
4
|
+
# provider-neutral data dict and back into a runnable agent. A pure module
|
|
5
|
+
# (sibling to Riffer::Agent::Run), reached most often through the
|
|
6
|
+
# +Riffer::Agent#to_h+ / +Riffer::Agent.from_h+ delegators.
|
|
7
|
+
#
|
|
8
|
+
# The dict carries only data — no Procs, no class references, no tool
|
|
9
|
+
# runtime. The same dict serves two rehydration targets:
|
|
10
|
+
#
|
|
11
|
+
# - <b>In-process</b> (a monolith persisting agent definitions): pass a
|
|
12
|
+
# +tool_resolver+ that looks tool descriptors up in a local registry and
|
|
13
|
+
# returns the real, body-bearing classes. They run on the default runtime.
|
|
14
|
+
# - <b>Distributed</b> (a receiver holding only the Riffer gem): the default
|
|
15
|
+
# resolver synthesizes body-less tool shells; inject a remote
|
|
16
|
+
# +Riffer::Tools::Runtime+ to forward each call back to the origin.
|
|
17
|
+
#
|
|
18
|
+
# dict = Riffer::Agent::Serializer.to_h(agent: agent)
|
|
19
|
+
# rebuilt = Riffer::Agent::Serializer.from_h(dict, context: {tenant: "acme"})
|
|
20
|
+
#
|
|
21
|
+
# == What does not transfer
|
|
22
|
+
#
|
|
23
|
+
# Guardrails and the skills subsystem (backend/adapter/catalog) are not
|
|
24
|
+
# serialized; a rebuilt agent enforces no guardrails and renders no skills
|
|
25
|
+
# catalog (the +skill_activate+ tool, if present, crosses as an ordinary
|
|
26
|
+
# tool). Secrets must not be placed in +provider_options+/+model_options+:
|
|
27
|
+
# both ride on the wire as plain data.
|
|
28
|
+
module Riffer::Agent::Serializer
|
|
29
|
+
# The wire format version. Bumped only on an incompatible change to the
|
|
30
|
+
# dict shape; +from_h+ refuses any other version. See +from_h+ for the
|
|
31
|
+
# dispatch seam that carries back-compat decoders.
|
|
32
|
+
SCHEMA_VERSION: Integer
|
|
33
|
+
|
|
34
|
+
# Raised by +from_h+ when the dict's +schema_version+ is unsupported.
|
|
35
|
+
class VersionError < Riffer::ArgumentError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The default +tool_resolver+: synthesizes a body-less tool shell from a
|
|
39
|
+
# descriptor. Its +#call+ raises — route shells through a remote runtime.
|
|
40
|
+
DEFAULT_TOOL_RESOLVER: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool)
|
|
41
|
+
|
|
42
|
+
# Snapshots a resolved agent into a self-contained wire dict.
|
|
43
|
+
#
|
|
44
|
+
# Reads the agent's resolved instance state — Proc-based settings have
|
|
45
|
+
# already been evaluated against the agent's own context, so the dict
|
|
46
|
+
# carries plain strings/data, never Procs. Tools are emitted as
|
|
47
|
+
# +{name, description, parameters_schema, timeout}+ descriptors (the
|
|
48
|
+
# resolved +agent.tools+, including MCP tools and +skill_activate+).
|
|
49
|
+
#
|
|
50
|
+
# [agent] a resolved Riffer::Agent instance.
|
|
51
|
+
#
|
|
52
|
+
# --
|
|
53
|
+
# : (agent: Riffer::Agent) -> Hash[Symbol, untyped]
|
|
54
|
+
def to_h: (agent: Riffer::Agent) -> Hash[Symbol, untyped]
|
|
55
|
+
|
|
56
|
+
# Reconstructs a runnable agent from a wire dict.
|
|
57
|
+
#
|
|
58
|
+
# [hash] a Symbol-keyed wire dict (parse JSON with +symbolize_names: true+).
|
|
59
|
+
# [context] the rebuilt agent's runtime context — the same value you'd pass
|
|
60
|
+
# to +Agent.new(context:)+. It is *not* used to re-resolve serialized
|
|
61
|
+
# config (the dict is already resolved); it is threaded into tool dispatch
|
|
62
|
+
# and read by tools/runtimes at call time (e.g. a remote runtime keying off
|
|
63
|
+
# <tt>context[:tenant]</tt>). Defaults to an empty context.
|
|
64
|
+
# [tool_resolver] maps a tool descriptor to a Riffer::Tool class. Defaults
|
|
65
|
+
# to DEFAULT_TOOL_RESOLVER (body-less shells). Pass a registry lookup to
|
|
66
|
+
# rebuild real, in-process tools.
|
|
67
|
+
# [tool_runtime] an optional Riffer::Tools::Runtime to inject (e.g. a
|
|
68
|
+
# remote runtime for shells). When omitted, the agent uses the configured
|
|
69
|
+
# default (+Riffer.config.tool_runtime+).
|
|
70
|
+
#
|
|
71
|
+
# Raises Riffer::Agent::Serializer::VersionError on an unsupported
|
|
72
|
+
# +schema_version+, and Riffer::ArgumentError on a malformed dict.
|
|
73
|
+
#
|
|
74
|
+
# --
|
|
75
|
+
# : (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
|
|
76
|
+
def from_h: (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
|
|
77
|
+
|
|
78
|
+
# Snapshots a resolved agent to a JSON string. Convenience over
|
|
79
|
+
# <tt>JSON.generate(to_h(agent:))</tt>.
|
|
80
|
+
#
|
|
81
|
+
# --
|
|
82
|
+
# : (agent: Riffer::Agent) -> String
|
|
83
|
+
def to_json: (agent: Riffer::Agent) -> String
|
|
84
|
+
|
|
85
|
+
# Reconstructs a runnable agent from a JSON string produced by +to_json+.
|
|
86
|
+
# Handles the JSON parse (with symbol keys) so callers don't have to. See
|
|
87
|
+
# +from_h+ for the arguments.
|
|
88
|
+
#
|
|
89
|
+
# --
|
|
90
|
+
# : (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
|
|
91
|
+
def from_json: (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
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# --
|
|
96
|
+
# : (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
|
|
97
|
+
def decode_v1: (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
|
|
98
|
+
|
|
99
|
+
# --
|
|
100
|
+
# : (Hash[Symbol, untyped]?) -> Riffer::Params?
|
|
101
|
+
def decode_structured_output: (Hash[Symbol, untyped]?) -> Riffer::Params?
|
|
102
|
+
|
|
103
|
+
# The DSL represents unlimited steps as +nil+, but the wire encodes it as
|
|
104
|
+
# +-1+ so the dict stays portable across transports where JSON +null+ is
|
|
105
|
+
# awkward (e.g. proto3, which can't tell null from an absent field). The
|
|
106
|
+
# magic value lives only on the wire — +encode_max_steps+/+decode_max_steps+
|
|
107
|
+
# translate at the boundary so neither the DSL nor consumers see it.
|
|
108
|
+
# --
|
|
109
|
+
# : (Numeric?) -> Numeric
|
|
110
|
+
def encode_max_steps: (Numeric?) -> Numeric
|
|
111
|
+
|
|
112
|
+
# Reverses +encode_max_steps+: +-1+ (or a literal +null+) means unlimited.
|
|
113
|
+
# An absent key falls back to the default — a partial dict must not silently
|
|
114
|
+
# become an unbounded loop.
|
|
115
|
+
# --
|
|
116
|
+
# : (Hash[Symbol, untyped]) -> Numeric?
|
|
117
|
+
def decode_max_steps: (Hash[Symbol, untyped]) -> Numeric?
|
|
118
|
+
|
|
119
|
+
# --
|
|
120
|
+
# : (singleton(Riffer::Tool)) -> Hash[Symbol, untyped]
|
|
121
|
+
def tool_descriptor: (singleton(Riffer::Tool)) -> Hash[Symbol, untyped]
|
|
122
|
+
|
|
123
|
+
# Builds an anonymous, body-less Riffer::Tool subclass that advertises the
|
|
124
|
+
# descriptor's schema to the LLM. Its +#call+ raises — a shell only has
|
|
125
|
+
# identity, not behavior; route its calls through a remote runtime.
|
|
126
|
+
#
|
|
127
|
+
# Returns +untyped+: steep can't see that +Class.new(Riffer::Tool)+ is a
|
|
128
|
+
# +singleton(Riffer::Tool)+ (cf. Riffer::Mcp::ToolFactory#build_tool_class).
|
|
129
|
+
# --
|
|
130
|
+
# : (Hash[Symbol, untyped]) -> untyped
|
|
131
|
+
def build_tool_shell: (Hash[Symbol, untyped]) -> untyped
|
|
132
|
+
end
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
# agent = MyAgent.new
|
|
16
16
|
# agent.generate('Hello!')
|
|
17
17
|
class Riffer::Agent
|
|
18
|
+
self.@config: Riffer::Agent::Config?
|
|
19
|
+
|
|
18
20
|
include Riffer::Messages::Converter
|
|
19
21
|
|
|
20
22
|
extend Riffer::Helpers::ClassNameConverter
|
|
@@ -78,12 +80,17 @@ class Riffer::Agent
|
|
|
78
80
|
|
|
79
81
|
# Gets or sets the maximum number of LLM call steps in the tool-use loop.
|
|
80
82
|
#
|
|
81
|
-
# Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to
|
|
82
|
-
#
|
|
83
|
+
# Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to +nil+
|
|
84
|
+
# for unlimited steps. The splat distinguishes a getter call (no argument)
|
|
85
|
+
# from setting the limit to +nil+.
|
|
86
|
+
#
|
|
87
|
+
# max_steps # reads the current limit
|
|
88
|
+
# max_steps 8 # cap the loop at 8 steps
|
|
89
|
+
# max_steps nil # unlimited
|
|
83
90
|
#
|
|
84
91
|
# --
|
|
85
|
-
# : (
|
|
86
|
-
def self.max_steps: (
|
|
92
|
+
# : (*Numeric?) -> Numeric?
|
|
93
|
+
def self.max_steps: (*Numeric?) -> Numeric?
|
|
87
94
|
|
|
88
95
|
# Gets or sets the tools used by this agent.
|
|
89
96
|
#
|
|
@@ -157,6 +164,26 @@ class Riffer::Agent
|
|
|
157
164
|
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
158
165
|
def self.stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
159
166
|
|
|
167
|
+
# Reconstructs a runnable agent from a wire dict produced by +#to_h+.
|
|
168
|
+
#
|
|
169
|
+
# Delegates to Riffer::Agent::Serializer.from_h. See it for the
|
|
170
|
+
# +tool_resolver+ / +tool_runtime+ injection points and what does not
|
|
171
|
+
# transfer.
|
|
172
|
+
#
|
|
173
|
+
# --
|
|
174
|
+
# : (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
|
|
175
|
+
def self.from_h: (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
|
|
176
|
+
|
|
177
|
+
# Reconstructs a runnable agent from a JSON string produced by +#to_json+.
|
|
178
|
+
#
|
|
179
|
+
# Delegates to Riffer::Agent::Serializer.from_json, which parses the JSON
|
|
180
|
+
# (with symbol keys) for you. See Riffer::Agent::Serializer.from_h for the
|
|
181
|
+
# +tool_resolver+ / +tool_runtime+ injection points.
|
|
182
|
+
#
|
|
183
|
+
# --
|
|
184
|
+
# : (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
|
|
185
|
+
def self.from_json: (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
|
|
186
|
+
|
|
160
187
|
# Registers a guardrail for input, output, or both phases.
|
|
161
188
|
#
|
|
162
189
|
# [phase] :before, :after, or :around.
|
|
@@ -208,6 +235,11 @@ class Riffer::Agent
|
|
|
208
235
|
# reserved and cannot be passed by the caller.
|
|
209
236
|
attr_reader context: Riffer::Agent::Context
|
|
210
237
|
|
|
238
|
+
# The resolved provider name (the part before "provider/"), e.g. +"openai"+.
|
|
239
|
+
# Resolved eagerly at +Agent.new+ alongside +model_name+; together they
|
|
240
|
+
# form the provider-neutral model identifier the agent serializes.
|
|
241
|
+
attr_reader provider_name: String
|
|
242
|
+
|
|
211
243
|
# The resolved model name (the part after "provider/"), used as the model
|
|
212
244
|
# argument on every LLM call. Resolved eagerly at +Agent.new+.
|
|
213
245
|
attr_reader model_name: String
|
|
@@ -298,6 +330,21 @@ class Riffer::Agent
|
|
|
298
330
|
# : (?(String | Symbol)?) -> void
|
|
299
331
|
def interrupt!: (?(String | Symbol)?) -> void
|
|
300
332
|
|
|
333
|
+
# Snapshots this resolved agent into a self-contained, provider-neutral
|
|
334
|
+
# wire dict. Delegates to Riffer::Agent::Serializer.to_h.
|
|
335
|
+
#
|
|
336
|
+
# --
|
|
337
|
+
# : () -> Hash[Symbol, untyped]
|
|
338
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
339
|
+
|
|
340
|
+
# Snapshots this resolved agent into a wire JSON string. Delegates to
|
|
341
|
+
# Riffer::Agent::Serializer.to_json. The +*+ absorbs the JSON generator
|
|
342
|
+
# state argument so <tt>JSON.generate(agent)</tt> works too.
|
|
343
|
+
#
|
|
344
|
+
# --
|
|
345
|
+
# : (*untyped) -> String
|
|
346
|
+
def to_json: (*untyped) -> String
|
|
347
|
+
|
|
301
348
|
private
|
|
302
349
|
|
|
303
350
|
# --
|
|
@@ -309,21 +356,30 @@ class Riffer::Agent
|
|
|
309
356
|
def build_skills_message: () -> Riffer::Messages::System?
|
|
310
357
|
|
|
311
358
|
# Resolves +Config#model+ to a "provider/model" string (calling the Proc
|
|
312
|
-
# form against +@context+)
|
|
359
|
+
# form against +@context+) and parses it.
|
|
360
|
+
#
|
|
361
|
+
# Returns +[provider_name, model_name]+. Raises Riffer::ArgumentError on an
|
|
362
|
+
# invalid model string.
|
|
363
|
+
#
|
|
364
|
+
# --
|
|
365
|
+
# : () -> [String, String]
|
|
366
|
+
def resolve_provider_and_model: () -> [ String, String ]
|
|
367
|
+
|
|
368
|
+
# Builds the provider client from the resolved +@provider_name+ and the
|
|
369
|
+
# configured +provider_options+.
|
|
313
370
|
#
|
|
314
|
-
#
|
|
315
|
-
# an invalid model string or an unregistered provider.
|
|
371
|
+
# Raises Riffer::ArgumentError on an unregistered provider.
|
|
316
372
|
#
|
|
317
373
|
# --
|
|
318
|
-
# : () ->
|
|
319
|
-
def
|
|
374
|
+
# : () -> Riffer::Providers::Base
|
|
375
|
+
def build_provider: () -> Riffer::Providers::Base
|
|
320
376
|
|
|
321
377
|
# Resolves the skills backend, lists skills, and selects an adapter.
|
|
322
378
|
# Returns nil if skills are unconfigured or the backend is empty.
|
|
323
379
|
#
|
|
324
380
|
# --
|
|
325
|
-
# : (
|
|
326
|
-
def resolve_skills: (
|
|
381
|
+
# : () -> Riffer::Skills::Context?
|
|
382
|
+
def resolve_skills: () -> Riffer::Skills::Context?
|
|
327
383
|
|
|
328
384
|
# --
|
|
329
385
|
# : () -> Riffer::Agent::StructuredOutput?
|