llm.rb 4.20.2 → 4.22.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/CHANGELOG.md +70 -0
- data/README.md +286 -52
- data/data/anthropic.json +35 -2
- data/data/google.json +7 -2
- data/data/openai.json +0 -30
- data/lib/llm/active_record/acts_as_agent.rb +11 -64
- data/lib/llm/active_record/acts_as_llm.rb +81 -61
- data/lib/llm/agent.rb +28 -4
- data/lib/llm/context.rb +14 -0
- data/lib/llm/sequel/agent.rb +94 -0
- data/lib/llm/sequel/plugin.rb +82 -60
- data/lib/llm/skill.rb +131 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +1 -0
- data/lib/sequel/plugins/agent.rb +8 -0
- data/llm.gemspec +3 -0
- metadata +46 -1
|
@@ -13,6 +13,7 @@ module LLM::ActiveRecord
|
|
|
13
13
|
EMPTY_HASH = LLM::ActiveRecord::ActsAsLLM::EMPTY_HASH
|
|
14
14
|
DEFAULT_USAGE_COLUMNS = LLM::ActiveRecord::ActsAsLLM::DEFAULT_USAGE_COLUMNS
|
|
15
15
|
DEFAULTS = LLM::ActiveRecord::ActsAsLLM::DEFAULTS
|
|
16
|
+
Utils = LLM::ActiveRecord::ActsAsLLM::Utils
|
|
16
17
|
|
|
17
18
|
module ClassMethods
|
|
18
19
|
def model(model = nil)
|
|
@@ -52,7 +53,7 @@ module LLM::ActiveRecord
|
|
|
52
53
|
# @param [Class] model
|
|
53
54
|
# @return [void]
|
|
54
55
|
def self.extended(model)
|
|
55
|
-
options = model.
|
|
56
|
+
options = model.llm_plugin_options
|
|
56
57
|
model.validates options[:provider_column], options[:model_column], presence: true
|
|
57
58
|
model.include LLM::ActiveRecord::ActsAsLLM::InstanceMethods unless model.ancestors.include?(LLM::ActiveRecord::ActsAsLLM::InstanceMethods)
|
|
58
59
|
model.include InstanceMethods unless model.ancestors.include?(InstanceMethods)
|
|
@@ -79,8 +80,8 @@ module LLM::ActiveRecord
|
|
|
79
80
|
def acts_as_agent(options = EMPTY_HASH, &block)
|
|
80
81
|
options = DEFAULTS.merge(options)
|
|
81
82
|
usage_columns = DEFAULT_USAGE_COLUMNS.merge(options[:usage_columns] || EMPTY_HASH)
|
|
82
|
-
class_attribute :
|
|
83
|
-
self.
|
|
83
|
+
class_attribute :llm_plugin_options, instance_accessor: false, default: DEFAULTS unless respond_to?(:llm_plugin_options)
|
|
84
|
+
self.llm_plugin_options = options.merge(usage_columns: usage_columns.freeze).freeze
|
|
84
85
|
extend Hooks
|
|
85
86
|
class_exec(&block) if block
|
|
86
87
|
end
|
|
@@ -90,12 +91,13 @@ module LLM::ActiveRecord
|
|
|
90
91
|
# Returns the resolved provider instance for this record.
|
|
91
92
|
# @return [LLM::Provider]
|
|
92
93
|
def llm
|
|
93
|
-
options = self.class.
|
|
94
|
+
options = self.class.llm_plugin_options
|
|
95
|
+
columns = Utils.columns(options)
|
|
94
96
|
provider = self[columns[:provider_column]]
|
|
95
|
-
kwargs = resolve_options(options[:provider])
|
|
97
|
+
kwargs = Utils.resolve_options(self, options[:provider], ActsAsAgent::EMPTY_HASH)
|
|
96
98
|
return @llm if @llm
|
|
97
99
|
@llm = LLM.method(provider).call(**kwargs)
|
|
98
|
-
@llm.tracer = resolve_option(options[:tracer]) if options[:tracer]
|
|
100
|
+
@llm.tracer = Utils.resolve_option(self, options[:tracer]) if options[:tracer]
|
|
99
101
|
@llm
|
|
100
102
|
end
|
|
101
103
|
|
|
@@ -105,8 +107,9 @@ module LLM::ActiveRecord
|
|
|
105
107
|
# @return [LLM::Agent]
|
|
106
108
|
def ctx
|
|
107
109
|
@ctx ||= begin
|
|
108
|
-
options = self.class.
|
|
109
|
-
|
|
110
|
+
options = self.class.llm_plugin_options
|
|
111
|
+
columns = Utils.columns(options)
|
|
112
|
+
params = Utils.resolve_options(self, options[:context], ActsAsAgent::EMPTY_HASH).dup
|
|
110
113
|
params[:model] ||= self[columns[:model_column]]
|
|
111
114
|
ctx = self.class.agent.new(llm, params.compact)
|
|
112
115
|
data = self[columns[:data_column]]
|
|
@@ -121,62 +124,6 @@ module LLM::ActiveRecord
|
|
|
121
124
|
end
|
|
122
125
|
end
|
|
123
126
|
end
|
|
124
|
-
|
|
125
|
-
##
|
|
126
|
-
# @return [void]
|
|
127
|
-
def flush
|
|
128
|
-
attrs = {
|
|
129
|
-
columns[:data_column] => serialize_context(self.class.llm_agent_options[:format]),
|
|
130
|
-
columns[:input_tokens] => ctx.usage.input_tokens,
|
|
131
|
-
columns[:output_tokens] => ctx.usage.output_tokens,
|
|
132
|
-
columns[:total_tokens] => ctx.usage.total_tokens
|
|
133
|
-
}
|
|
134
|
-
assign_attributes(attrs)
|
|
135
|
-
save!
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
##
|
|
139
|
-
# @return [Hash]
|
|
140
|
-
def resolve_option(option)
|
|
141
|
-
case option
|
|
142
|
-
when Proc then instance_exec(&option)
|
|
143
|
-
when Symbol then send(option)
|
|
144
|
-
when Hash then option.dup
|
|
145
|
-
else option
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
##
|
|
150
|
-
# @return [Hash]
|
|
151
|
-
def resolve_options(option)
|
|
152
|
-
case option
|
|
153
|
-
when Proc, Symbol, Hash then resolve_option(option)
|
|
154
|
-
else ActsAsAgent::EMPTY_HASH.dup
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def serialize_context(format)
|
|
159
|
-
case format
|
|
160
|
-
when :string then ctx.to_json
|
|
161
|
-
when :json, :jsonb then ctx.to_h
|
|
162
|
-
else raise ArgumentError, "Unknown format: #{format.inspect}"
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def columns
|
|
167
|
-
@columns ||= begin
|
|
168
|
-
options = self.class.llm_agent_options
|
|
169
|
-
usage_columns = options[:usage_columns]
|
|
170
|
-
{
|
|
171
|
-
provider_column: options[:provider_column],
|
|
172
|
-
model_column: options[:model_column],
|
|
173
|
-
data_column: options[:data_column],
|
|
174
|
-
input_tokens: usage_columns[:input_tokens],
|
|
175
|
-
output_tokens: usage_columns[:output_tokens],
|
|
176
|
-
total_tokens: usage_columns[:total_tokens]
|
|
177
|
-
}.freeze
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
127
|
end
|
|
181
128
|
end
|
|
182
129
|
end
|
|
@@ -33,6 +33,77 @@ module LLM::ActiveRecord
|
|
|
33
33
|
context: EMPTY_HASH
|
|
34
34
|
}.freeze
|
|
35
35
|
|
|
36
|
+
##
|
|
37
|
+
# Shared helper methods for the ORM wrapper.
|
|
38
|
+
#
|
|
39
|
+
# These utilities keep persistence plumbing out of the wrapped model's
|
|
40
|
+
# method namespace so the injected surface stays focused on the runtime
|
|
41
|
+
# API itself.
|
|
42
|
+
# @api private
|
|
43
|
+
module Utils
|
|
44
|
+
##
|
|
45
|
+
# Resolves a single configured option against a model instance.
|
|
46
|
+
# @return [Object]
|
|
47
|
+
def self.resolve_option(obj, option)
|
|
48
|
+
case option
|
|
49
|
+
when Proc then obj.instance_exec(&option)
|
|
50
|
+
when Symbol then obj.send(option)
|
|
51
|
+
when Hash then option.dup
|
|
52
|
+
else option
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# Resolves hash-like wrapper options against a model instance.
|
|
58
|
+
# @return [Hash]
|
|
59
|
+
def self.resolve_options(obj, option, empty_hash)
|
|
60
|
+
case option
|
|
61
|
+
when Proc, Symbol, Hash then resolve_option(obj, option)
|
|
62
|
+
else empty_hash.dup
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Serializes the runtime into the configured storage format.
|
|
68
|
+
# @return [String, Hash]
|
|
69
|
+
def self.serialize_context(ctx, format)
|
|
70
|
+
case format
|
|
71
|
+
when :string then ctx.to_json
|
|
72
|
+
when :json, :jsonb then ctx.to_h
|
|
73
|
+
else raise ArgumentError, "Unknown format: #{format.inspect}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# Maps wrapper options onto the record's storage columns.
|
|
79
|
+
# @return [Hash]
|
|
80
|
+
def self.columns(options)
|
|
81
|
+
usage_columns = options[:usage_columns]
|
|
82
|
+
{
|
|
83
|
+
provider_column: options[:provider_column],
|
|
84
|
+
model_column: options[:model_column],
|
|
85
|
+
data_column: options[:data_column],
|
|
86
|
+
input_tokens: usage_columns[:input_tokens],
|
|
87
|
+
output_tokens: usage_columns[:output_tokens],
|
|
88
|
+
total_tokens: usage_columns[:total_tokens]
|
|
89
|
+
}.freeze
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Persists the runtime state and usage columns back onto the record.
|
|
94
|
+
# @return [void]
|
|
95
|
+
def self.save(obj, ctx, options)
|
|
96
|
+
columns = self.columns(options)
|
|
97
|
+
obj.assign_attributes(
|
|
98
|
+
columns[:data_column] => serialize_context(ctx, options[:format]),
|
|
99
|
+
columns[:input_tokens] => ctx.usage.input_tokens,
|
|
100
|
+
columns[:output_tokens] => ctx.usage.output_tokens,
|
|
101
|
+
columns[:total_tokens] => ctx.usage.total_tokens
|
|
102
|
+
)
|
|
103
|
+
obj.save!
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
36
107
|
module Hooks
|
|
37
108
|
##
|
|
38
109
|
# Called when hooks are extended onto an ActiveRecord model.
|
|
@@ -72,7 +143,8 @@ module LLM::ActiveRecord
|
|
|
72
143
|
# @see LLM::Context#talk
|
|
73
144
|
# @return [LLM::Response]
|
|
74
145
|
def talk(...)
|
|
75
|
-
|
|
146
|
+
options = self.class.llm_plugin_options
|
|
147
|
+
ctx.talk(...).tap { Utils.save(self, ctx, options) }
|
|
76
148
|
end
|
|
77
149
|
|
|
78
150
|
##
|
|
@@ -80,7 +152,8 @@ module LLM::ActiveRecord
|
|
|
80
152
|
# @see LLM::Context#respond
|
|
81
153
|
# @return [LLM::Response]
|
|
82
154
|
def respond(...)
|
|
83
|
-
|
|
155
|
+
options = self.class.llm_plugin_options
|
|
156
|
+
ctx.respond(...).tap { Utils.save(self, ctx, options) }
|
|
84
157
|
end
|
|
85
158
|
|
|
86
159
|
##
|
|
@@ -155,6 +228,7 @@ module LLM::ActiveRecord
|
|
|
155
228
|
# Returns usage from the mapped usage columns.
|
|
156
229
|
# @return [LLM::Object]
|
|
157
230
|
def usage
|
|
231
|
+
columns = Utils.columns(self.class.llm_plugin_options)
|
|
158
232
|
LLM::Object.from(
|
|
159
233
|
input_tokens: self[columns[:input_tokens]] || 0,
|
|
160
234
|
output_tokens: self[columns[:output_tokens]] || 0,
|
|
@@ -211,11 +285,12 @@ module LLM::ActiveRecord
|
|
|
211
285
|
# @return [LLM::Provider]
|
|
212
286
|
def llm
|
|
213
287
|
options = self.class.llm_plugin_options
|
|
288
|
+
columns = Utils.columns(options)
|
|
214
289
|
provider = self[columns[:provider_column]]
|
|
215
|
-
kwargs = resolve_options(options[:provider])
|
|
290
|
+
kwargs = Utils.resolve_options(self, options[:provider], ActsAsLLM::EMPTY_HASH)
|
|
216
291
|
return @llm if @llm
|
|
217
292
|
@llm = LLM.method(provider).call(**kwargs)
|
|
218
|
-
@llm.tracer = resolve_option(options[:tracer]) if options[:tracer]
|
|
293
|
+
@llm.tracer = Utils.resolve_option(self, options[:tracer]) if options[:tracer]
|
|
219
294
|
@llm
|
|
220
295
|
end
|
|
221
296
|
|
|
@@ -226,7 +301,8 @@ module LLM::ActiveRecord
|
|
|
226
301
|
def ctx
|
|
227
302
|
@ctx ||= begin
|
|
228
303
|
options = self.class.llm_plugin_options
|
|
229
|
-
|
|
304
|
+
columns = Utils.columns(options)
|
|
305
|
+
params = Utils.resolve_options(self, options[:context], ActsAsLLM::EMPTY_HASH).dup
|
|
230
306
|
params[:model] ||= self[columns[:model_column]]
|
|
231
307
|
ctx = LLM::Context.new(llm, params.compact)
|
|
232
308
|
data = self[columns[:data_column]]
|
|
@@ -241,62 +317,6 @@ module LLM::ActiveRecord
|
|
|
241
317
|
end
|
|
242
318
|
end
|
|
243
319
|
end
|
|
244
|
-
|
|
245
|
-
##
|
|
246
|
-
# @return [void]
|
|
247
|
-
def flush
|
|
248
|
-
attrs = {
|
|
249
|
-
columns[:data_column] => serialize_context(self.class.llm_plugin_options[:format]),
|
|
250
|
-
columns[:input_tokens] => ctx.usage.input_tokens,
|
|
251
|
-
columns[:output_tokens] => ctx.usage.output_tokens,
|
|
252
|
-
columns[:total_tokens] => ctx.usage.total_tokens
|
|
253
|
-
}
|
|
254
|
-
assign_attributes(attrs)
|
|
255
|
-
save!
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
##
|
|
259
|
-
# @return [Hash]
|
|
260
|
-
def resolve_option(option)
|
|
261
|
-
case option
|
|
262
|
-
when Proc then instance_exec(&option)
|
|
263
|
-
when Symbol then send(option)
|
|
264
|
-
when Hash then option.dup
|
|
265
|
-
else option
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
##
|
|
270
|
-
# @return [Hash]
|
|
271
|
-
def resolve_options(option)
|
|
272
|
-
case option
|
|
273
|
-
when Proc, Symbol, Hash then resolve_option(option)
|
|
274
|
-
else ActsAsLLM::EMPTY_HASH.dup
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def serialize_context(format)
|
|
279
|
-
case format
|
|
280
|
-
when :string then ctx.to_json
|
|
281
|
-
when :json, :jsonb then ctx.to_h
|
|
282
|
-
else raise ArgumentError, "Unknown format: #{format.inspect}"
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
def columns
|
|
287
|
-
@columns ||= begin
|
|
288
|
-
options = self.class.llm_plugin_options
|
|
289
|
-
usage_columns = options[:usage_columns]
|
|
290
|
-
{
|
|
291
|
-
provider_column: options[:provider_column],
|
|
292
|
-
model_column: options[:model_column],
|
|
293
|
-
data_column: options[:data_column],
|
|
294
|
-
input_tokens: usage_columns[:input_tokens],
|
|
295
|
-
output_tokens: usage_columns[:output_tokens],
|
|
296
|
-
total_tokens: usage_columns[:total_tokens]
|
|
297
|
-
}.freeze
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
320
|
end
|
|
301
321
|
end
|
|
302
322
|
end
|
data/lib/llm/agent.rb
CHANGED
|
@@ -14,7 +14,7 @@ module LLM
|
|
|
14
14
|
# `respond`, instead of leaving tool loops to the caller.
|
|
15
15
|
#
|
|
16
16
|
# **Notes:**
|
|
17
|
-
# * Instructions are injected
|
|
17
|
+
# * Instructions are injected once unless a system message is already present.
|
|
18
18
|
# * An agent automatically executes tool loops (unlike {LLM::Context LLM::Context}).
|
|
19
19
|
# * Tool loop execution can be configured with `concurrency :call`,
|
|
20
20
|
# `:thread`, `:task`, `:fiber`, `:ractor`, or a list of queued task
|
|
@@ -59,6 +59,17 @@ module LLM
|
|
|
59
59
|
@tools = tools.flatten
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
##
|
|
63
|
+
# Set or get the default skills
|
|
64
|
+
# @param [Array<String>, nil] skills
|
|
65
|
+
# One or more skill directories
|
|
66
|
+
# @return [Array<String>, nil]
|
|
67
|
+
# Returns the current skills when no argument is provided
|
|
68
|
+
def self.skills(*skills)
|
|
69
|
+
return @skills if skills.empty?
|
|
70
|
+
@skills = skills.flatten
|
|
71
|
+
end
|
|
72
|
+
|
|
62
73
|
##
|
|
63
74
|
# Set or get the default schema
|
|
64
75
|
# @param [#to_json, nil] schema
|
|
@@ -110,10 +121,11 @@ module LLM
|
|
|
110
121
|
# not only those listed here.
|
|
111
122
|
# @option params [String] :model Defaults to the provider's default model
|
|
112
123
|
# @option params [Array<LLM::Function>, nil] :tools Defaults to nil
|
|
124
|
+
# @option params [Array<String>, nil] :skills Defaults to nil
|
|
113
125
|
# @option params [#to_json, nil] :schema Defaults to nil
|
|
114
126
|
# @option params [Symbol, Array<Symbol>, nil] :concurrency Defaults to the agent class concurrency
|
|
115
127
|
def initialize(llm, params = {})
|
|
116
|
-
defaults = {model: self.class.model, tools: self.class.tools, schema: self.class.schema}.compact
|
|
128
|
+
defaults = {model: self.class.model, tools: self.class.tools, skills: self.class.skills, schema: self.class.schema}.compact
|
|
117
129
|
@concurrency = params.delete(:concurrency) || self.class.concurrency
|
|
118
130
|
@llm = llm
|
|
119
131
|
@ctx = LLM::Context.new(llm, defaults.merge(params))
|
|
@@ -337,16 +349,28 @@ module LLM
|
|
|
337
349
|
instr = self.class.instructions
|
|
338
350
|
return new_prompt unless instr
|
|
339
351
|
if LLM::Prompt === new_prompt
|
|
340
|
-
new_prompt.system(instr) if
|
|
352
|
+
new_prompt.system(instr) if inject_instructions?(new_prompt)
|
|
341
353
|
new_prompt
|
|
342
354
|
else
|
|
343
355
|
prompt do
|
|
344
|
-
_1.system(instr) if
|
|
356
|
+
_1.system(instr) if inject_instructions?
|
|
345
357
|
_1.user(new_prompt)
|
|
346
358
|
end
|
|
347
359
|
end
|
|
348
360
|
end
|
|
349
361
|
|
|
362
|
+
##
|
|
363
|
+
# Returns true when agent instructions should be injected for the turn.
|
|
364
|
+
# Instructions are injected once unless a system message is already
|
|
365
|
+
# present in the existing context or the prompt being sent.
|
|
366
|
+
# @param [LLM::Prompt, nil] prompt
|
|
367
|
+
# @return [Boolean]
|
|
368
|
+
def inject_instructions?(prompt = nil)
|
|
369
|
+
return false if @ctx.messages.any?(&:system?)
|
|
370
|
+
return true if prompt.nil?
|
|
371
|
+
!prompt.to_a.any?(&:system?)
|
|
372
|
+
end
|
|
373
|
+
|
|
350
374
|
##
|
|
351
375
|
# @return [Array<LLM::Function::Return>]
|
|
352
376
|
def call_functions
|
data/lib/llm/context.rb
CHANGED
|
@@ -54,6 +54,13 @@ module LLM
|
|
|
54
54
|
# @return [Symbol]
|
|
55
55
|
attr_reader :mode
|
|
56
56
|
|
|
57
|
+
##
|
|
58
|
+
# Returns the default params for this context
|
|
59
|
+
# @return [Hash]
|
|
60
|
+
def params
|
|
61
|
+
@params.dup
|
|
62
|
+
end
|
|
63
|
+
|
|
57
64
|
##
|
|
58
65
|
# @param [LLM::Provider] llm
|
|
59
66
|
# A provider
|
|
@@ -64,10 +71,13 @@ module LLM
|
|
|
64
71
|
# @option params [Symbol] :mode Defaults to :completions
|
|
65
72
|
# @option params [String] :model Defaults to the provider's default model
|
|
66
73
|
# @option params [Array<LLM::Function>, nil] :tools Defaults to nil
|
|
74
|
+
# @option params [Array<String>, nil] :skills Defaults to nil
|
|
67
75
|
def initialize(llm, params = {})
|
|
68
76
|
@llm = llm
|
|
69
77
|
@mode = params.delete(:mode) || :completions
|
|
78
|
+
tools = [*params.delete(:tools), *load_skills(params.delete(:skills))]
|
|
70
79
|
@params = {model: llm.default_model, schema: nil}.compact.merge!(params)
|
|
80
|
+
@params[:tools] = tools unless tools.empty?
|
|
71
81
|
@messages = LLM::Buffer.new(llm)
|
|
72
82
|
end
|
|
73
83
|
|
|
@@ -345,6 +355,10 @@ module LLM
|
|
|
345
355
|
stream.extra[:tracer] = tracer
|
|
346
356
|
stream.extra[:model] = model
|
|
347
357
|
end
|
|
358
|
+
|
|
359
|
+
def load_skills(skills)
|
|
360
|
+
[*skills].map { LLM::Skill.load(_1).to_tool(self) }
|
|
361
|
+
end
|
|
348
362
|
end
|
|
349
363
|
|
|
350
364
|
# Backward-compatible alias
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM::Sequel
|
|
4
|
+
##
|
|
5
|
+
# Sequel plugin for persisting {LLM::Agent LLM::Agent} state.
|
|
6
|
+
#
|
|
7
|
+
# This wrapper reuses the same record-backed runtime surface as
|
|
8
|
+
# {LLM::Sequel::Plugin}, but builds an {LLM::Agent LLM::Agent} instead of an
|
|
9
|
+
# {LLM::Context LLM::Context}. Agent defaults such as model, tools, schema,
|
|
10
|
+
# instructions, and concurrency are configured on the model class and
|
|
11
|
+
# forwarded to an internal agent subclass.
|
|
12
|
+
module Agent
|
|
13
|
+
require_relative "plugin"
|
|
14
|
+
EMPTY_HASH = LLM::Sequel::Plugin::EMPTY_HASH
|
|
15
|
+
DEFAULT_USAGE_COLUMNS = LLM::Sequel::Plugin::DEFAULT_USAGE_COLUMNS
|
|
16
|
+
DEFAULTS = LLM::Sequel::Plugin::DEFAULTS
|
|
17
|
+
Utils = LLM::Sequel::Plugin::Utils
|
|
18
|
+
|
|
19
|
+
def self.apply(model, **)
|
|
20
|
+
model.extend ClassMethods
|
|
21
|
+
model.include LLM::Sequel::Plugin::InstanceMethods
|
|
22
|
+
model.include InstanceMethods
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.configure(model, options = EMPTY_HASH, &block)
|
|
26
|
+
options = DEFAULTS.merge(options)
|
|
27
|
+
usage_columns = DEFAULT_USAGE_COLUMNS.merge(options[:usage_columns] || EMPTY_HASH)
|
|
28
|
+
model.instance_variable_set(
|
|
29
|
+
:@llm_agent_options,
|
|
30
|
+
options.merge(usage_columns: usage_columns.freeze).freeze
|
|
31
|
+
)
|
|
32
|
+
model.instance_exec(&block) if block
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module ClassMethods
|
|
36
|
+
def llm_plugin_options
|
|
37
|
+
@llm_agent_options || Agent::DEFAULTS
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def model(model = nil)
|
|
41
|
+
return agent.model if model.nil?
|
|
42
|
+
agent.model(model)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def tools(*tools)
|
|
46
|
+
return agent.tools if tools.empty?
|
|
47
|
+
agent.tools(*tools)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def schema(schema = nil)
|
|
51
|
+
return agent.schema if schema.nil?
|
|
52
|
+
agent.schema(schema)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def instructions(instructions = nil)
|
|
56
|
+
return agent.instructions if instructions.nil?
|
|
57
|
+
agent.instructions(instructions)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def concurrency(concurrency = nil)
|
|
61
|
+
return agent.concurrency if concurrency.nil?
|
|
62
|
+
agent.concurrency(concurrency)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def agent
|
|
66
|
+
@agent ||= Class.new(LLM::Agent)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module InstanceMethods
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def ctx
|
|
74
|
+
@ctx ||= begin
|
|
75
|
+
options = self.class.llm_plugin_options
|
|
76
|
+
columns = Agent::Utils.columns(options)
|
|
77
|
+
params = Agent::Utils.resolve_options(self, options[:context], Agent::EMPTY_HASH).dup
|
|
78
|
+
params[:model] ||= self[columns[:model_column]]
|
|
79
|
+
ctx = self.class.agent.new(llm, params.compact)
|
|
80
|
+
data = self[columns[:data_column]]
|
|
81
|
+
if data.nil? || data == ""
|
|
82
|
+
ctx
|
|
83
|
+
else
|
|
84
|
+
case options[:format]
|
|
85
|
+
when :string then ctx.restore(string: data)
|
|
86
|
+
when :json, :jsonb then ctx.restore(data:)
|
|
87
|
+
else raise ArgumentError, "Unknown format: #{options[:format].inspect}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|