Class: Ragents::Agent

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

Overview

Base class for AI agents. Agents can run in isolated Ractors for concurrent execution.

Examples:

Define an agent

class ResearchAgent < Ragents::Agent
  system_prompt "You are a research assistant..."

  tool :search_web do
    description "Search the web"
    parameter :query, type: :string, required: true
    execute { |query:| WebSearch.call(query) }
  end
end

Run an agent

agent = ResearchAgent.new(provider: my_provider)
result = agent.run(context: initial_context)

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(provider:, context: nil, tools: nil, **options) ⇒ Agent

Returns a new instance of Agent.



60
61
62
63
64
65
66
67
68
# File 'lib/ragents/agent.rb', line 60

def initialize(provider:, context: nil, tools: nil, **options)
  @provider = provider
  @context = context || Context.new
  @tools = tools || self.class.tools
  @options = options
  @max_iterations = options.fetch(:max_iterations, 10)

  setup_initial_context!
end

Class Attribute Details

.defined_system_promptObject (readonly)

Returns the value of attribute defined_system_prompt.



24
25
26
# File 'lib/ragents/agent.rb', line 24

def defined_system_prompt
  @defined_system_prompt
end

.defined_toolsObject (readonly)

Returns the value of attribute defined_tools.



24
25
26
# File 'lib/ragents/agent.rb', line 24

def defined_tools
  @defined_tools
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



58
59
60
# File 'lib/ragents/agent.rb', line 58

def context
  @context
end

#optionsObject (readonly)

Returns the value of attribute options.



58
59
60
# File 'lib/ragents/agent.rb', line 58

def options
  @options
end

#providerObject (readonly)

Returns the value of attribute provider.



58
59
60
# File 'lib/ragents/agent.rb', line 58

def provider
  @provider
end

#toolsObject (readonly)

Returns the value of attribute tools.



58
59
60
# File 'lib/ragents/agent.rb', line 58

def tools
  @tools
end

Class Method Details

.resolved_system_promptObject

Get resolved system prompt



52
53
54
55
# File 'lib/ragents/agent.rb', line 52

def resolved_system_prompt
  prompt = defined_system_prompt
  prompt.is_a?(Proc) ? prompt.call : prompt
end

.run_async(provider:, context: nil, input: nil, **options) ⇒ Ractor

Run the agent in a Ractor for isolated concurrent execution

Parameters:

  • input (String, nil) (defaults to: nil)

    Optional user input

Returns:

  • (Ractor)

    The running Ractor



118
119
120
121
122
123
124
125
126
127
# File 'lib/ragents/agent.rb', line 118

def self.run_async(provider:, context: nil, input: nil, **options)
  # Prepare Ractor-shareable data
  agent_class = self
  frozen_context = context&.freeze || Context.new.freeze

  Ractor.new(agent_class, provider, frozen_context, input, options) do |klass, prov, ctx, inp, opts|
    agent = klass.new(provider: prov, context: ctx, **opts)
    agent.run(input: inp)
  end
end

.run_in_ractor(provider:, context: nil, input: nil, **options) ⇒ Object

Execute in Ractor and wait for result Uses Ruby 4.x Ractor#value (replaces deprecated #take)



131
132
133
134
# File 'lib/ragents/agent.rb', line 131

def self.run_in_ractor(provider:, context: nil, input: nil, **options)
  ractor = run_async(provider: provider, context: context, input: input, **options)
  ractor.value
end

.system_prompt(prompt = nil, &block) ⇒ Object

DSL: Define the system prompt for this agent



27
28
29
# File 'lib/ragents/agent.rb', line 27

def system_prompt(prompt = nil, &block)
  @defined_system_prompt = block_given? ? block : prompt
end

.tool(name, &block) ⇒ Object

DSL: Define a tool for this agent



32
33
34
35
# File 'lib/ragents/agent.rb', line 32

def tool(name, &block)
  @defined_tools ||= ToolRegistry.new
  @defined_tools.register(name, &block)
end

.toolsObject

Get all inherited tools



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/ragents/agent.rb', line 38

def tools
  registry = ToolRegistry.new

  # Collect tools from ancestors (inheritance chain)
  ancestors.reverse.each do |klass|
    next unless klass.respond_to?(:defined_tools) && klass.defined_tools

    klass.defined_tools.each { |t| registry.register(t.name, t) }
  end

  registry
end

Instance Method Details

#generateResponse

Run a single generation (does not auto-execute tools)

Returns:



94
95
96
97
98
99
100
# File 'lib/ragents/agent.rb', line 94

def generate
  @provider.generate(
    messages: @context.to_a,
    tools: @tools,
    **@options
  )
end

#last_responseObject

Get the last response content



111
112
113
# File 'lib/ragents/agent.rb', line 111

def last_response
  @context.last_assistant_message&.content
end

#run(input: nil) ⇒ Context

Run the agent to completion (handles tool calls automatically)

Parameters:

  • input (String, Message, nil) (defaults to: nil)

    Optional user input to add

Returns:

  • (Context)

    The final context after all processing



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ragents/agent.rb', line 73

def run(input: nil)
  add_user_input!(input) if input

  iterations = 0
  loop do
    iterations += 1
    raise MaxIterationsError, "Exceeded #{@max_iterations} iterations" if iterations > @max_iterations

    response = generate
    @context = @context.add_message(response.to_message)

    break unless response.has_tool_calls?

    execute_tool_calls(response.tool_calls)
  end

  @context
end

#say(content) ⇒ self

Add a user message and return self for chaining

Parameters:

  • content (String)

    The user message

Returns:

  • (self)


105
106
107
108
# File 'lib/ragents/agent.rb', line 105

def say(content)
  @context = @context.add_message(Message.user(content))
  self
end