riffer 0.6.1 → 0.8.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 +113 -0
- data/.agents/code-style.md +42 -0
- data/.agents/providers.md +46 -0
- data/.agents/rdoc.md +51 -0
- data/.agents/testing.md +56 -0
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +28 -0
- data/CHANGELOG.md +17 -0
- data/README.md +26 -36
- data/Rakefile +1 -1
- data/docs/01_OVERVIEW.md +106 -0
- data/docs/02_GETTING_STARTED.md +128 -0
- data/docs/03_AGENTS.md +226 -0
- data/docs/04_TOOLS.md +251 -0
- data/docs/05_MESSAGES.md +173 -0
- data/docs/06_STREAM_EVENTS.md +191 -0
- data/docs/07_CONFIGURATION.md +195 -0
- data/docs_providers/01_PROVIDERS.md +168 -0
- data/docs_providers/02_AMAZON_BEDROCK.md +196 -0
- data/docs_providers/03_ANTHROPIC.md +211 -0
- data/docs_providers/04_OPENAI.md +157 -0
- data/docs_providers/05_TEST_PROVIDER.md +163 -0
- data/docs_providers/06_CUSTOM_PROVIDERS.md +304 -0
- data/lib/riffer/agent.rb +220 -57
- data/lib/riffer/config.rb +20 -12
- data/lib/riffer/core.rb +7 -7
- data/lib/riffer/helpers/class_name_converter.rb +6 -3
- data/lib/riffer/helpers/dependencies.rb +18 -0
- data/lib/riffer/helpers/validations.rb +9 -0
- data/lib/riffer/messages/assistant.rb +23 -1
- data/lib/riffer/messages/base.rb +15 -0
- data/lib/riffer/messages/converter.rb +15 -5
- data/lib/riffer/messages/system.rb +8 -1
- data/lib/riffer/messages/tool.rb +58 -4
- data/lib/riffer/messages/user.rb +8 -1
- data/lib/riffer/messages.rb +7 -0
- data/lib/riffer/providers/amazon_bedrock.rb +128 -13
- data/lib/riffer/providers/anthropic.rb +209 -0
- data/lib/riffer/providers/base.rb +23 -18
- data/lib/riffer/providers/open_ai.rb +119 -39
- data/lib/riffer/providers/repository.rb +9 -4
- data/lib/riffer/providers/test.rb +78 -24
- data/lib/riffer/providers.rb +6 -0
- data/lib/riffer/stream_events/base.rb +13 -1
- data/lib/riffer/stream_events/reasoning_delta.rb +15 -1
- data/lib/riffer/stream_events/reasoning_done.rb +15 -1
- data/lib/riffer/stream_events/text_delta.rb +14 -1
- data/lib/riffer/stream_events/text_done.rb +14 -1
- data/lib/riffer/stream_events/tool_call_delta.rb +35 -0
- data/lib/riffer/stream_events/tool_call_done.rb +40 -0
- data/lib/riffer/stream_events.rb +9 -0
- data/lib/riffer/tool.rb +120 -0
- data/lib/riffer/tools/param.rb +68 -0
- data/lib/riffer/tools/params.rb +118 -0
- data/lib/riffer/tools.rb +9 -0
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +23 -19
- metadata +41 -2
- data/CLAUDE.md +0 -73
data/lib/riffer/agent.rb
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
3
5
|
# Riffer::Agent is the base class for all agents in the Riffer framework.
|
|
4
6
|
#
|
|
5
7
|
# Provides orchestration for LLM calls, tool use, and message management.
|
|
8
|
+
# Subclass this to create your own agents.
|
|
9
|
+
#
|
|
10
|
+
# See Riffer::Messages and Riffer::Providers.
|
|
11
|
+
#
|
|
12
|
+
# class MyAgent < Riffer::Agent
|
|
13
|
+
# model 'openai/gpt-4o'
|
|
14
|
+
# instructions 'You are a helpful assistant.'
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# agent = MyAgent.new
|
|
18
|
+
# agent.generate('Hello!')
|
|
6
19
|
#
|
|
7
|
-
# @abstract
|
|
8
|
-
# @see Riffer::Messages
|
|
9
|
-
# @see Riffer::Providers
|
|
10
20
|
class Riffer::Agent
|
|
11
21
|
include Riffer::Messages::Converter
|
|
12
22
|
|
|
@@ -14,64 +24,99 @@ class Riffer::Agent
|
|
|
14
24
|
include Riffer::Helpers::ClassNameConverter
|
|
15
25
|
include Riffer::Helpers::Validations
|
|
16
26
|
|
|
17
|
-
# Gets or sets the agent identifier
|
|
18
|
-
#
|
|
19
|
-
#
|
|
27
|
+
# Gets or sets the agent identifier.
|
|
28
|
+
#
|
|
29
|
+
# value:: String or nil - the identifier to set, or nil to get
|
|
30
|
+
#
|
|
31
|
+
# Returns String - the agent identifier.
|
|
20
32
|
def identifier(value = nil)
|
|
21
33
|
return @identifier || class_name_to_path(name) if value.nil?
|
|
22
34
|
@identifier = value.to_s
|
|
23
35
|
end
|
|
24
36
|
|
|
25
|
-
# Gets or sets the model string (e.g., "openai/gpt-
|
|
26
|
-
#
|
|
27
|
-
#
|
|
37
|
+
# Gets or sets the model string (e.g., "openai/gpt-4o").
|
|
38
|
+
#
|
|
39
|
+
# model_string:: String or nil - the model string to set, or nil to get
|
|
40
|
+
#
|
|
41
|
+
# Returns String - the model string.
|
|
28
42
|
def model(model_string = nil)
|
|
29
43
|
return @model if model_string.nil?
|
|
30
44
|
validate_is_string!(model_string, "model")
|
|
31
45
|
@model = model_string
|
|
32
46
|
end
|
|
33
47
|
|
|
34
|
-
# Gets or sets the agent instructions
|
|
35
|
-
#
|
|
36
|
-
#
|
|
48
|
+
# Gets or sets the agent instructions.
|
|
49
|
+
#
|
|
50
|
+
# instructions_text:: String or nil - the instructions to set, or nil to get
|
|
51
|
+
#
|
|
52
|
+
# Returns String - the agent instructions.
|
|
37
53
|
def instructions(instructions_text = nil)
|
|
38
54
|
return @instructions if instructions_text.nil?
|
|
39
55
|
validate_is_string!(instructions_text, "instructions")
|
|
40
56
|
@instructions = instructions_text
|
|
41
57
|
end
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
# Gets or sets provider options passed to the provider client.
|
|
60
|
+
#
|
|
61
|
+
# options:: Hash or nil - the options to set, or nil to get
|
|
62
|
+
#
|
|
63
|
+
# Returns Hash - the provider options.
|
|
64
|
+
def provider_options(options = nil)
|
|
65
|
+
return @provider_options || {} if options.nil?
|
|
66
|
+
@provider_options = options
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Gets or sets model options passed to generate_text/stream_text.
|
|
70
|
+
#
|
|
71
|
+
# options:: Hash or nil - the options to set, or nil to get
|
|
72
|
+
#
|
|
73
|
+
# Returns Hash - the model options.
|
|
74
|
+
def model_options(options = nil)
|
|
75
|
+
return @model_options || {} if options.nil?
|
|
76
|
+
@model_options = options
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Gets or sets the tools used by this agent.
|
|
80
|
+
#
|
|
81
|
+
# tools_or_lambda:: Array of Tool classes, Proc, or nil - tools array or lambda returning tools
|
|
82
|
+
#
|
|
83
|
+
# Returns Array, Proc, or nil - the tools configuration.
|
|
84
|
+
def uses_tools(tools_or_lambda = nil)
|
|
85
|
+
return @tools_config if tools_or_lambda.nil?
|
|
86
|
+
@tools_config = tools_or_lambda
|
|
47
87
|
end
|
|
48
88
|
|
|
49
|
-
# Finds an agent class by identifier
|
|
50
|
-
#
|
|
51
|
-
#
|
|
89
|
+
# Finds an agent class by identifier.
|
|
90
|
+
#
|
|
91
|
+
# identifier:: String - the identifier to search for
|
|
92
|
+
#
|
|
93
|
+
# Returns Class or nil - the agent class, or nil if not found.
|
|
52
94
|
def find(identifier)
|
|
53
95
|
subclasses.find { |agent_class| agent_class.identifier == identifier.to_s }
|
|
54
96
|
end
|
|
55
97
|
|
|
56
|
-
# Returns all agent subclasses
|
|
57
|
-
#
|
|
98
|
+
# Returns all agent subclasses.
|
|
99
|
+
#
|
|
100
|
+
# Returns Array of Class - all agent subclasses.
|
|
58
101
|
def all
|
|
59
102
|
subclasses
|
|
60
103
|
end
|
|
61
104
|
end
|
|
62
105
|
|
|
63
|
-
# The message history for the agent
|
|
64
|
-
#
|
|
106
|
+
# The message history for the agent.
|
|
107
|
+
#
|
|
108
|
+
# Returns Array of Riffer::Messages::Base.
|
|
65
109
|
attr_reader :messages
|
|
66
110
|
|
|
67
|
-
# Initializes a new agent
|
|
68
|
-
#
|
|
69
|
-
#
|
|
111
|
+
# Initializes a new agent.
|
|
112
|
+
#
|
|
113
|
+
# Raises Riffer::ArgumentError if the configured model string is invalid
|
|
114
|
+
# (must be "provider/model" format).
|
|
70
115
|
def initialize
|
|
71
116
|
@messages = []
|
|
117
|
+
@message_callbacks = []
|
|
72
118
|
@model_string = self.class.model
|
|
73
119
|
@instructions_text = self.class.instructions
|
|
74
|
-
@reasoning = self.class.reasoning
|
|
75
120
|
|
|
76
121
|
provider_name, model_name = @model_string.split("/", 2)
|
|
77
122
|
|
|
@@ -81,15 +126,20 @@ class Riffer::Agent
|
|
|
81
126
|
@model_name = model_name
|
|
82
127
|
end
|
|
83
128
|
|
|
84
|
-
# Generates a response from the agent
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
|
|
129
|
+
# Generates a response from the agent.
|
|
130
|
+
#
|
|
131
|
+
# prompt_or_messages:: String or Array - a string prompt or array of message hashes/objects
|
|
132
|
+
# tool_context:: Object or nil - optional context object passed to all tool calls
|
|
133
|
+
#
|
|
134
|
+
# Returns String - the final response content.
|
|
135
|
+
def generate(prompt_or_messages, tool_context: nil)
|
|
136
|
+
@tool_context = tool_context
|
|
137
|
+
@resolved_tools = nil
|
|
88
138
|
initialize_messages(prompt_or_messages)
|
|
89
139
|
|
|
90
140
|
loop do
|
|
91
141
|
response = call_llm
|
|
92
|
-
|
|
142
|
+
add_message(response)
|
|
93
143
|
|
|
94
144
|
break unless has_tool_calls?(response)
|
|
95
145
|
|
|
@@ -99,33 +149,76 @@ class Riffer::Agent
|
|
|
99
149
|
extract_final_response
|
|
100
150
|
end
|
|
101
151
|
|
|
102
|
-
# Streams a response from the agent
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
|
|
152
|
+
# Streams a response from the agent.
|
|
153
|
+
#
|
|
154
|
+
# prompt_or_messages:: String or Array - a string prompt or array of message hashes/objects
|
|
155
|
+
# tool_context:: Object or nil - optional context object passed to all tool calls
|
|
156
|
+
#
|
|
157
|
+
# Returns Enumerator - an enumerator yielding stream events.
|
|
158
|
+
def stream(prompt_or_messages, tool_context: nil)
|
|
159
|
+
@tool_context = tool_context
|
|
160
|
+
@resolved_tools = nil
|
|
106
161
|
initialize_messages(prompt_or_messages)
|
|
107
162
|
|
|
108
163
|
Enumerator.new do |yielder|
|
|
109
|
-
|
|
164
|
+
loop do
|
|
165
|
+
accumulated_content = ""
|
|
166
|
+
accumulated_tool_calls = []
|
|
167
|
+
current_tool_call = nil
|
|
168
|
+
|
|
169
|
+
call_llm_stream.each do |event|
|
|
170
|
+
yielder << event
|
|
171
|
+
|
|
172
|
+
case event
|
|
173
|
+
when Riffer::StreamEvents::TextDelta
|
|
174
|
+
accumulated_content += event.content
|
|
175
|
+
when Riffer::StreamEvents::TextDone
|
|
176
|
+
accumulated_content = event.content
|
|
177
|
+
when Riffer::StreamEvents::ToolCallDelta
|
|
178
|
+
current_tool_call ||= {item_id: event.item_id, name: event.name, arguments: ""}
|
|
179
|
+
current_tool_call[:arguments] += event.arguments_delta
|
|
180
|
+
current_tool_call[:name] ||= event.name
|
|
181
|
+
when Riffer::StreamEvents::ToolCallDone
|
|
182
|
+
accumulated_tool_calls << {
|
|
183
|
+
id: event.item_id,
|
|
184
|
+
call_id: event.call_id,
|
|
185
|
+
name: event.name,
|
|
186
|
+
arguments: event.arguments
|
|
187
|
+
}
|
|
188
|
+
current_tool_call = nil
|
|
189
|
+
end
|
|
190
|
+
end
|
|
110
191
|
|
|
111
|
-
|
|
112
|
-
|
|
192
|
+
response = Riffer::Messages::Assistant.new(accumulated_content, tool_calls: accumulated_tool_calls)
|
|
193
|
+
add_message(response)
|
|
113
194
|
|
|
114
|
-
|
|
115
|
-
when Riffer::StreamEvents::TextDelta
|
|
116
|
-
accumulated_content += event.content
|
|
117
|
-
when Riffer::StreamEvents::TextDone
|
|
118
|
-
accumulated_content = event.content
|
|
119
|
-
end
|
|
120
|
-
end
|
|
195
|
+
break unless has_tool_calls?(response)
|
|
121
196
|
|
|
122
|
-
|
|
123
|
-
|
|
197
|
+
execute_tool_calls(response)
|
|
198
|
+
end
|
|
124
199
|
end
|
|
125
200
|
end
|
|
126
201
|
|
|
202
|
+
# Registers a callback to be invoked when messages are added during generation.
|
|
203
|
+
#
|
|
204
|
+
# block:: Block - callback receiving a Riffer::Messages::Base subclass
|
|
205
|
+
#
|
|
206
|
+
# Raises Riffer::ArgumentError if no block is given.
|
|
207
|
+
#
|
|
208
|
+
# Returns self for method chaining.
|
|
209
|
+
def on_message(&block)
|
|
210
|
+
raise Riffer::ArgumentError, "on_message requires a block" unless block_given?
|
|
211
|
+
@message_callbacks << block
|
|
212
|
+
self
|
|
213
|
+
end
|
|
214
|
+
|
|
127
215
|
private
|
|
128
216
|
|
|
217
|
+
def add_message(message)
|
|
218
|
+
@messages << message
|
|
219
|
+
@message_callbacks.each { |callback| callback.call(message) }
|
|
220
|
+
end
|
|
221
|
+
|
|
129
222
|
def initialize_messages(prompt_or_messages)
|
|
130
223
|
@messages = []
|
|
131
224
|
@messages << Riffer::Messages::System.new(@instructions_text) if @instructions_text
|
|
@@ -140,18 +233,28 @@ class Riffer::Agent
|
|
|
140
233
|
end
|
|
141
234
|
|
|
142
235
|
def call_llm
|
|
143
|
-
provider_instance.generate_text(
|
|
236
|
+
provider_instance.generate_text(
|
|
237
|
+
messages: @messages,
|
|
238
|
+
model: @model_name,
|
|
239
|
+
tools: resolved_tools,
|
|
240
|
+
**self.class.model_options
|
|
241
|
+
)
|
|
144
242
|
end
|
|
145
243
|
|
|
146
244
|
def call_llm_stream
|
|
147
|
-
provider_instance.stream_text(
|
|
245
|
+
provider_instance.stream_text(
|
|
246
|
+
messages: @messages,
|
|
247
|
+
model: @model_name,
|
|
248
|
+
tools: resolved_tools,
|
|
249
|
+
**self.class.model_options
|
|
250
|
+
)
|
|
148
251
|
end
|
|
149
252
|
|
|
150
253
|
def provider_instance
|
|
151
254
|
@provider_instance ||= begin
|
|
152
255
|
provider_class = Riffer::Providers::Repository.find(@provider_name)
|
|
153
256
|
raise Riffer::ArgumentError, "Provider not found: #{@provider_name}" unless provider_class
|
|
154
|
-
provider_class.new
|
|
257
|
+
provider_class.new(**self.class.provider_options)
|
|
155
258
|
end
|
|
156
259
|
end
|
|
157
260
|
|
|
@@ -161,17 +264,77 @@ class Riffer::Agent
|
|
|
161
264
|
|
|
162
265
|
def execute_tool_calls(response)
|
|
163
266
|
response.tool_calls.each do |tool_call|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
267
|
+
result = execute_tool_call(tool_call)
|
|
268
|
+
add_message(Riffer::Messages::Tool.new(
|
|
269
|
+
result[:content],
|
|
167
270
|
tool_call_id: tool_call[:id],
|
|
168
|
-
name: tool_call[:name]
|
|
169
|
-
|
|
271
|
+
name: tool_call[:name],
|
|
272
|
+
error: result[:error],
|
|
273
|
+
error_type: result[:error_type]
|
|
274
|
+
))
|
|
170
275
|
end
|
|
171
276
|
end
|
|
172
277
|
|
|
173
278
|
def execute_tool_call(tool_call)
|
|
174
|
-
|
|
279
|
+
tool_class = find_tool_class(tool_call[:name])
|
|
280
|
+
|
|
281
|
+
if tool_class.nil?
|
|
282
|
+
return {
|
|
283
|
+
content: "Error: Unknown tool '#{tool_call[:name]}'",
|
|
284
|
+
error: "Unknown tool '#{tool_call[:name]}'",
|
|
285
|
+
error_type: :unknown_tool
|
|
286
|
+
}
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
tool_instance = tool_class.new
|
|
290
|
+
arguments = parse_tool_arguments(tool_call[:arguments])
|
|
291
|
+
|
|
292
|
+
begin
|
|
293
|
+
result = tool_instance.call_with_validation(context: @tool_context, **arguments)
|
|
294
|
+
{content: result.to_s, error: nil, error_type: nil}
|
|
295
|
+
rescue Riffer::TimeoutError => e
|
|
296
|
+
{
|
|
297
|
+
content: "Error: #{e.message}",
|
|
298
|
+
error: e.message,
|
|
299
|
+
error_type: :timeout_error
|
|
300
|
+
}
|
|
301
|
+
rescue Riffer::ValidationError => e
|
|
302
|
+
{
|
|
303
|
+
content: "Validation error: #{e.message}",
|
|
304
|
+
error: e.message,
|
|
305
|
+
error_type: :validation_error
|
|
306
|
+
}
|
|
307
|
+
rescue => e
|
|
308
|
+
{
|
|
309
|
+
content: "Error executing tool: #{e.message}",
|
|
310
|
+
error: e.message,
|
|
311
|
+
error_type: :execution_error
|
|
312
|
+
}
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def resolved_tools
|
|
317
|
+
@resolved_tools ||= begin
|
|
318
|
+
config = self.class.uses_tools
|
|
319
|
+
return [] if config.nil?
|
|
320
|
+
|
|
321
|
+
if config.is_a?(Proc)
|
|
322
|
+
(config.arity == 0) ? config.call : config.call(@tool_context)
|
|
323
|
+
else
|
|
324
|
+
config
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def find_tool_class(name)
|
|
330
|
+
resolved_tools.find { |tool_class| tool_class.name == name }
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def parse_tool_arguments(arguments)
|
|
334
|
+
return {} if arguments.nil? || arguments.empty?
|
|
335
|
+
|
|
336
|
+
args = arguments.is_a?(String) ? JSON.parse(arguments) : arguments
|
|
337
|
+
args.transform_keys(&:to_sym)
|
|
175
338
|
end
|
|
176
339
|
|
|
177
340
|
def extract_final_response
|
data/lib/riffer/config.rb
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Configuration for the Riffer framework
|
|
3
|
+
# Configuration for the Riffer framework.
|
|
4
4
|
#
|
|
5
5
|
# Provides configuration options for AI providers and other settings.
|
|
6
6
|
#
|
|
7
|
-
# @example Setting the OpenAI API key
|
|
8
7
|
# Riffer.config.openai.api_key = "sk-..."
|
|
9
8
|
#
|
|
10
|
-
# @example Setting Amazon Bedrock configuration
|
|
11
9
|
# Riffer.config.amazon_bedrock.region = "us-east-1"
|
|
12
10
|
# Riffer.config.amazon_bedrock.api_token = "..."
|
|
11
|
+
#
|
|
12
|
+
# Riffer.config.anthropic.api_key = "sk-ant-..."
|
|
13
|
+
#
|
|
13
14
|
class Riffer::Config
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# Amazon Bedrock configuration
|
|
19
|
-
# @return [Struct]
|
|
15
|
+
# Amazon Bedrock configuration (Struct with +api_token+ and +region+).
|
|
16
|
+
#
|
|
17
|
+
# Returns Struct.
|
|
20
18
|
attr_reader :amazon_bedrock
|
|
21
19
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
20
|
+
# Anthropic configuration (Struct with +api_key+).
|
|
21
|
+
#
|
|
22
|
+
# Returns Struct.
|
|
23
|
+
attr_reader :anthropic
|
|
24
|
+
|
|
25
|
+
# OpenAI configuration (Struct with +api_key+).
|
|
26
|
+
#
|
|
27
|
+
# Returns Struct.
|
|
28
|
+
attr_reader :openai
|
|
29
|
+
|
|
30
|
+
# Initializes the configuration.
|
|
24
31
|
def initialize
|
|
25
|
-
@openai = Struct.new(:api_key).new
|
|
26
32
|
@amazon_bedrock = Struct.new(:api_token, :region).new
|
|
33
|
+
@anthropic = Struct.new(:api_key).new
|
|
34
|
+
@openai = Struct.new(:api_key).new
|
|
27
35
|
end
|
|
28
36
|
end
|
data/lib/riffer/core.rb
CHANGED
|
@@ -6,21 +6,21 @@ require "logger"
|
|
|
6
6
|
#
|
|
7
7
|
# Handles logging and configuration for the framework.
|
|
8
8
|
class Riffer::Core
|
|
9
|
-
# The logger instance for Riffer
|
|
10
|
-
#
|
|
9
|
+
# The logger instance for Riffer.
|
|
10
|
+
#
|
|
11
|
+
# Returns Logger.
|
|
11
12
|
attr_reader :logger
|
|
12
13
|
|
|
13
|
-
# Initializes the core object and logger
|
|
14
|
-
# @return [void]
|
|
14
|
+
# Initializes the core object and logger.
|
|
15
15
|
def initialize
|
|
16
16
|
@logger = Logger.new($stdout)
|
|
17
17
|
@logger.level = Logger::INFO
|
|
18
18
|
@storage_registry = {}
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
# Yields self for configuration
|
|
22
|
-
#
|
|
23
|
-
#
|
|
21
|
+
# Yields self for configuration.
|
|
22
|
+
#
|
|
23
|
+
# Yields core (Riffer::Core) to the block.
|
|
24
24
|
def configure
|
|
25
25
|
yield self if block_given?
|
|
26
26
|
end
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Helper module for converting class names.
|
|
3
4
|
module Riffer::Helpers::ClassNameConverter
|
|
4
|
-
# Converts a class name to snake_case path format
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# Converts a class name to snake_case path format.
|
|
6
|
+
#
|
|
7
|
+
# class_name:: String - the class name (e.g., "Riffer::Agent")
|
|
8
|
+
#
|
|
9
|
+
# Returns String - the snake_case path (e.g., "riffer/agent").
|
|
7
10
|
def class_name_to_path(class_name)
|
|
8
11
|
class_name
|
|
9
12
|
.to_s
|
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Helper module for lazy loading gem dependencies.
|
|
4
|
+
#
|
|
5
|
+
# Used by providers to load their required gems only when needed.
|
|
3
6
|
module Riffer::Helpers::Dependencies
|
|
7
|
+
# Raised when a required gem cannot be loaded.
|
|
4
8
|
class LoadError < ::LoadError; end
|
|
9
|
+
|
|
10
|
+
# Raised when a gem version requirement is not satisfied.
|
|
5
11
|
class VersionError < ScriptError; end
|
|
6
12
|
|
|
13
|
+
# Declares a dependency on a gem.
|
|
14
|
+
#
|
|
15
|
+
# Verifies the gem is installed and satisfies version requirements,
|
|
16
|
+
# then requires it.
|
|
17
|
+
#
|
|
18
|
+
# gem_name:: String - the gem name
|
|
19
|
+
# req:: Boolean or String - true to require the gem, false to skip, or String to require a different lib
|
|
20
|
+
#
|
|
21
|
+
# Returns true if successful.
|
|
22
|
+
#
|
|
23
|
+
# Raises LoadError if the gem is not installed.
|
|
24
|
+
# Raises VersionError if the gem version does not satisfy requirements.
|
|
7
25
|
def depends_on(gem_name, req: true)
|
|
8
26
|
gem(gem_name)
|
|
9
27
|
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Helper module for input validation.
|
|
3
4
|
module Riffer::Helpers::Validations
|
|
5
|
+
# Validates that a value is a non-empty string.
|
|
6
|
+
#
|
|
7
|
+
# value:: Object - the value to validate
|
|
8
|
+
# name:: String - the name of the value for error messages
|
|
9
|
+
#
|
|
10
|
+
# Returns true if valid.
|
|
11
|
+
#
|
|
12
|
+
# Raises Riffer::ArgumentError if the value is not a string or is empty.
|
|
4
13
|
def validate_is_string!(value, name = "value")
|
|
5
14
|
raise Riffer::ArgumentError, "#{name} must be a String" unless value.is_a?(String)
|
|
6
15
|
raise Riffer::ArgumentError, "#{name} cannot be empty" if value.strip.empty?
|
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Represents an assistant (LLM) message in a conversation.
|
|
4
|
+
#
|
|
5
|
+
# May include tool calls when the LLM requests tool execution.
|
|
6
|
+
#
|
|
7
|
+
# msg = Riffer::Messages::Assistant.new("Hello!")
|
|
8
|
+
# msg.role # => :assistant
|
|
9
|
+
# msg.content # => "Hello!"
|
|
10
|
+
# msg.tool_calls # => []
|
|
11
|
+
#
|
|
3
12
|
class Riffer::Messages::Assistant < Riffer::Messages::Base
|
|
13
|
+
# Array of tool calls requested by the assistant.
|
|
14
|
+
#
|
|
15
|
+
# Each tool call is a Hash with +:id+, +:call_id+, +:name+, and +:arguments+ keys.
|
|
16
|
+
#
|
|
17
|
+
# Returns Array of Hash.
|
|
4
18
|
attr_reader :tool_calls
|
|
5
19
|
|
|
20
|
+
# Creates a new assistant message.
|
|
21
|
+
#
|
|
22
|
+
# content:: String - the message content
|
|
23
|
+
# tool_calls:: Array of Hash - optional tool calls
|
|
6
24
|
def initialize(content, tool_calls: [])
|
|
7
25
|
super(content)
|
|
8
26
|
@tool_calls = tool_calls
|
|
9
27
|
end
|
|
10
28
|
|
|
29
|
+
# Returns :assistant.
|
|
11
30
|
def role
|
|
12
|
-
|
|
31
|
+
:assistant
|
|
13
32
|
end
|
|
14
33
|
|
|
34
|
+
# Converts the message to a hash.
|
|
35
|
+
#
|
|
36
|
+
# Returns Hash with +:role+, +:content+, and optionally +:tool_calls+.
|
|
15
37
|
def to_h
|
|
16
38
|
hash = {role: role, content: content}
|
|
17
39
|
hash[:tool_calls] = tool_calls unless tool_calls.empty?
|
data/lib/riffer/messages/base.rb
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Base class for all message types in the Riffer framework.
|
|
4
|
+
#
|
|
5
|
+
# Subclasses must implement the +role+ method.
|
|
3
6
|
class Riffer::Messages::Base
|
|
7
|
+
# The message content.
|
|
8
|
+
#
|
|
9
|
+
# Returns String.
|
|
4
10
|
attr_reader :content
|
|
5
11
|
|
|
12
|
+
# Creates a new message.
|
|
13
|
+
#
|
|
14
|
+
# content:: String - the message content
|
|
6
15
|
def initialize(content)
|
|
7
16
|
@content = content
|
|
8
17
|
end
|
|
9
18
|
|
|
19
|
+
# Converts the message to a hash.
|
|
20
|
+
#
|
|
21
|
+
# Returns Hash with +:role+ and +:content+ keys.
|
|
10
22
|
def to_h
|
|
11
23
|
{role: role, content: content}
|
|
12
24
|
end
|
|
13
25
|
|
|
26
|
+
# Returns the message role.
|
|
27
|
+
#
|
|
28
|
+
# Raises NotImplementedError if not implemented by subclass.
|
|
14
29
|
def role
|
|
15
30
|
raise NotImplementedError, "Subclasses must implement #role"
|
|
16
31
|
end
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Module for converting hashes to message objects.
|
|
4
|
+
#
|
|
5
|
+
# Included in Agent and Provider classes to handle message normalization.
|
|
3
6
|
module Riffer::Messages::Converter
|
|
7
|
+
# Converts a hash or message object to a Riffer::Messages::Base subclass.
|
|
8
|
+
#
|
|
9
|
+
# msg:: Hash or Riffer::Messages::Base - the message to convert
|
|
10
|
+
#
|
|
11
|
+
# Returns Riffer::Messages::Base subclass.
|
|
12
|
+
#
|
|
13
|
+
# Raises Riffer::ArgumentError if the message format is invalid.
|
|
4
14
|
def convert_to_message_object(msg)
|
|
5
15
|
return msg if msg.is_a?(Riffer::Messages::Base)
|
|
6
16
|
|
|
@@ -21,15 +31,15 @@ module Riffer::Messages::Converter
|
|
|
21
31
|
raise Riffer::ArgumentError, "Message hash must include a 'role' key"
|
|
22
32
|
end
|
|
23
33
|
|
|
24
|
-
case role
|
|
25
|
-
when
|
|
34
|
+
case role.to_sym
|
|
35
|
+
when :user
|
|
26
36
|
Riffer::Messages::User.new(content)
|
|
27
|
-
when
|
|
37
|
+
when :assistant
|
|
28
38
|
tool_calls = hash[:tool_calls] || hash["tool_calls"] || []
|
|
29
39
|
Riffer::Messages::Assistant.new(content, tool_calls: tool_calls)
|
|
30
|
-
when
|
|
40
|
+
when :system
|
|
31
41
|
Riffer::Messages::System.new(content)
|
|
32
|
-
when
|
|
42
|
+
when :tool
|
|
33
43
|
tool_call_id = hash[:tool_call_id] || hash["tool_call_id"]
|
|
34
44
|
name = hash[:name] || hash["name"]
|
|
35
45
|
Riffer::Messages::Tool.new(content, tool_call_id: tool_call_id, name: name)
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Represents a system message (instructions) in a conversation.
|
|
4
|
+
#
|
|
5
|
+
# msg = Riffer::Messages::System.new("You are a helpful assistant.")
|
|
6
|
+
# msg.role # => :system
|
|
7
|
+
# msg.content # => "You are a helpful assistant."
|
|
8
|
+
#
|
|
3
9
|
class Riffer::Messages::System < Riffer::Messages::Base
|
|
10
|
+
# Returns :system.
|
|
4
11
|
def role
|
|
5
|
-
|
|
12
|
+
:system
|
|
6
13
|
end
|
|
7
14
|
end
|