Class: OmniAgent::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/omni_agent/agent.rb

Defined Under Namespace

Modules: ImplicitRunEntrypoints

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(provider_override: nil, model_override: nil, options_override: {}, context_override: {}) ⇒ Agent

Returns a new instance of Agent.



207
208
209
210
211
212
213
# File 'lib/omni_agent/agent.rb', line 207

def initialize(provider_override: nil, model_override: nil, options_override: {}, context_override: {})
  target_provider_name = provider_override || self.class.configured_provider_name || OmniAgent.configuration.default_provider
  target_model = model_override || self.class.configured_provider_options[:model]
  @chat_options = self.class.configured_model_options.merge(options_override)
  @provider = resolve_provider(target_provider_name, target_model)
  @default_context = context_override || {}
end

Instance Attribute Details

#providerObject (readonly)

Returns the value of attribute provider.



3
4
5
# File 'lib/omni_agent/agent.rb', line 3

def provider
  @provider
end

Class Method Details

.after_generation(*callbacks) ⇒ Object



67
68
69
# File 'lib/omni_agent/agent.rb', line 67

def after_generation(*callbacks)
  @after_generation_callbacks = configured_after_generation_callbacks + normalize_callbacks(:after_generation, callbacks)
end

.before_generation(*callbacks) ⇒ Object



63
64
65
# File 'lib/omni_agent/agent.rb', line 63

def before_generation(*callbacks)
  @before_generation_callbacks = configured_before_generation_callbacks + normalize_callbacks(:before_generation, callbacks)
end

.configured_after_generation_callbacksObject



120
# File 'lib/omni_agent/agent.rb', line 120

def configured_after_generation_callbacks; @after_generation_callbacks || []; end

.configured_before_generation_callbacksObject



119
# File 'lib/omni_agent/agent.rb', line 119

def configured_before_generation_callbacks; @before_generation_callbacks || []; end

.configured_delegated_tool_classesObject



98
99
100
# File 'lib/omni_agent/agent.rb', line 98

def configured_delegated_tool_classes
  @delegated_tool_classes || []
end

.configured_model_optionsObject



117
# File 'lib/omni_agent/agent.rb', line 117

def configured_model_options; @model_options || {}; end

.configured_provider_nameObject



115
# File 'lib/omni_agent/agent.rb', line 115

def configured_provider_name; @provider_name; end

.configured_provider_optionsObject



116
# File 'lib/omni_agent/agent.rb', line 116

def configured_provider_options; @provider_options || {}; end

.configured_tagsObject



121
# File 'lib/omni_agent/agent.rb', line 121

def configured_tags; tags; end

.configured_with_use_model?Boolean

Returns:

  • (Boolean)


118
# File 'lib/omni_agent/agent.rb', line 118

def configured_with_use_model?; @configured_with_use_model == true; end

.delegate_to(agent_class, as:, description: nil, run_alias: nil, forward: []) ⇒ Object



87
88
89
90
91
92
93
94
95
96
# File 'lib/omni_agent/agent.rb', line 87

def delegate_to(agent_class, as:, description: nil, run_alias: nil, forward: [])
  unless agent_class.is_a?(Class) && agent_class <= OmniAgent::Agent
    raise ArgumentError, "delegate_to requires an OmniAgent::Agent subclass"
  end

  tool_class = build_delegated_tool_class(agent_class, description: description, run_alias: run_alias, forward: forward)
  delegated_tools_module.const_set(delegated_tool_const_name(as), tool_class)

  @delegated_tool_classes = configured_delegated_tool_classes + [ tool_class ]
end

.inherited(subclass) ⇒ Object



123
124
125
126
# File 'lib/omni_agent/agent.rb', line 123

def inherited(subclass)
  super
  subclass.extend(ImplicitRunEntrypoints)
end

.options(**options) ⇒ Object



50
51
52
# File 'lib/omni_agent/agent.rb', line 50

def options(**options)
  @model_options = configured_model_options.merge(options)
end

.provider(name, **options) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/omni_agent/agent.rb', line 41

def provider(name, **options)
  if configured_with_use_model?
    raise OmniAgent::Error, "Cannot combine `provider` and `use_model` in the same agent. Use either `provider ..., model: ...` or `use_model ...`."
  end

  @provider_name = name
  @provider_options = options
end

.run_aliases(*method_names) ⇒ Object



77
78
79
80
81
82
83
84
85
# File 'lib/omni_agent/agent.rb', line 77

def run_aliases(*method_names)
  aliases = normalize_callbacks(:run_aliases, method_names)

  aliases.each do |method_name|
    define_method(method_name) do |input, context: {}|
      run(input, context: context, prompt_method: method_name)
    end
  end
end

.tags(*tag_names) ⇒ Object



71
72
73
74
75
# File 'lib/omni_agent/agent.rb', line 71

def tags(*tag_names)
  return @configured_tags || [] if tag_names.empty?

  @configured_tags = (tags + normalize_tags(tag_names)).uniq
end

.use_model(name) ⇒ Object



54
55
56
57
58
59
60
61
# File 'lib/omni_agent/agent.rb', line 54

def use_model(name)
  if configured_provider_name || configured_provider_options.any?
    raise OmniAgent::Error, "Cannot combine `provider` and `use_model` in the same agent. Use either `provider ..., model: ...` or `use_model ...`."
  end

  @configured_with_use_model = true
  @provider_options = { model: name }
end

.with(context = nil, provider_override: nil, model_override: nil, options_override: {}, **context_keywords) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/omni_agent/agent.rb', line 102

def with(context = nil, provider_override: nil, model_override: nil, options_override: {}, **context_keywords)
  merged_context = {}
  merged_context.merge!(context) if context.is_a?(Hash)
  merged_context.merge!(context_keywords)

  new(
    provider_override: provider_override,
    model_override: model_override,
    options_override: options_override,
    context_override: merged_context
  )
end

Instance Method Details

#available_toolsObject



307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/omni_agent/agent.rb', line 307

def available_tools
  tool_namespace = "#{self.class.name}::Tools".safe_constantize

  namespace_tools = if tool_namespace
    tool_namespace.constants.filter_map do |const_name|
      const = tool_namespace.const_get(const_name)
      const if const.is_a?(Class) && const < OmniAgent::Tool
    end
  else
    []
  end

  namespace_tools + self.class.configured_delegated_tool_classes
end

#run(input, context: {}, prompt_method: nil) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/omni_agent/agent.rb', line 215

def run(input, context: {}, prompt_method: nil)
  context = @default_context.merge(context || {})
  bind_context_instance_variables(context)

  messages = []

  run_before_generation_callbacks(input: input, context: context, messages: messages)
  sync_context_from_instance_variables(context)

  messages.replace(build_messages(input: input, history: context[:history]))
  messages[0][:content] = system_prompt(context: context, prompt_method: prompt_method)
  initial_messages_count = messages.length - 1

  filtered_tools = tool_filter(tools: available_tools, agent_tags: self.class.tags)
  max_iterations = OmniAgent.configuration.max_tool_iterations
  iterations = 0

  loop do
    iterations += 1
    if iterations > max_iterations
      raise OmniAgent::MaxToolIterationsError,
            "Exceeded max_tool_iterations (#{max_iterations}) without a final response. " \
            "Increase OmniAgent.configuration.max_tool_iterations if more tool calls are expected."
    end

    response = provider.chat(messages: messages, tools: filtered_tools, **@chat_options)

    if response.content && !response.tool_calls?
      messages << { role: "assistant", content: response.content }
      set_after_generation_state(response: response, messages: messages, initial_messages_count: initial_messages_count)
      run_after_generation_callbacks(input: input, context: context, messages: messages, response: response)
      sync_context_from_instance_variables(context)
      return response
    end

    messages << build_assistant_tool_call_message(response)
    should_stop_generation = false

    response.tool_calls.each do |tool_call|
      tool_name = tool_call[:name]
      tool_args = tool_call[:arguments]
      tool_id   = tool_call[:id]

      tool_class = filtered_tools.find do |t|
        class_name = t.name.to_s
        simple_name = class_name.respond_to?(:demodulize) ? class_name.demodulize : class_name.split("::").last
        simple_name == tool_name
      end

      if tool_class
        tool_instance = tool_class.new
        tool_instance.context = context if tool_instance.respond_to?(:context=)

        begin
          result = tool_instance.invoke(tool_args)

          messages << {
            role: "tool",
            tool_call_id: tool_id,
            name: tool_name,
            content: result.to_s
          }
        rescue => e
          messages << {
            role: "tool",
            tool_call_id: tool_id,
            name: tool_name,
            content: "Error executing tool: #{e.message}"
          }
        end

        should_stop_generation ||= tool_class.respond_to?(:stops_generation?) && tool_class.stops_generation?
        should_stop_generation ||= tool_instance.respond_to?(:stops_generation?) && tool_instance.stops_generation?
      else
        messages << {
          role: "tool",
          tool_call_id: tool_id,
          name: tool_name,
          content: "Error: Tool #{tool_name} is not registered to this agent."
        }
      end
    end

    if should_stop_generation
      set_after_generation_state(response: response, messages: messages, initial_messages_count: initial_messages_count)
      run_after_generation_callbacks(input: input, context: context, messages: messages, response: response)
      sync_context_from_instance_variables(context)
      return response
    end
  end
end