Class: Rubyagents::Agent

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

Direct Known Subclasses

CodeAgent, ToolCallingAgent

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model:, tools: [], agents: [], name: nil, description: nil, max_steps: 10, planning_interval: nil, step_callbacks: [], callbacks: [], final_answer_checks: [], prompt_templates: nil, instructions: nil, output_type: nil) ⇒ Agent

Returns a new instance of Agent.



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/rubyagents/agent.rb', line 7

def initialize(model:, tools: [], agents: [], name: nil, description: nil,
               max_steps: 10, planning_interval: nil, step_callbacks: [],
               callbacks: [], final_answer_checks: [], prompt_templates: nil,
               instructions: nil, output_type: nil)
  @name = name
  @description = description
  @model = model.is_a?(String) ? Model.for(model) : model
  @tools = build_tools(tools, agents)
  @max_steps = max_steps
  @planning_interval = planning_interval
  @step_callbacks = step_callbacks
  @callbacks = Array(callbacks)
  @final_answer_checks = final_answer_checks
  @prompt_templates = prompt_templates || PromptTemplates.new
  @instructions = instructions
  @output_type = output_type
  @memory = nil
  @interrupt_switch = false
  @step_number = 0
  @final_answer_value = nil
  @tool_map = @tools.each_with_object({}) { |t, h| h[t.class.tool_name] = t }
end

Instance Attribute Details

#callbacks ⇒ Object (readonly)

Returns the value of attribute callbacks.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def callbacks
  @callbacks
end

#description ⇒ Object (readonly)

Returns the value of attribute description.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def description
  @description
end

#max_steps ⇒ Object (readonly)

Returns the value of attribute max_steps.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def max_steps
  @max_steps
end

#memory ⇒ Object (readonly)

Returns the value of attribute memory.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def memory
  @memory
end

#model ⇒ Object (readonly)

Returns the value of attribute model.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def model
  @model
end

#name ⇒ Object (readonly)

Returns the value of attribute name.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def name
  @name
end

#planning_interval ⇒ Object (readonly)

Returns the value of attribute planning_interval.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def planning_interval
  @planning_interval
end

#step_callbacks ⇒ Object (readonly)

Returns the value of attribute step_callbacks.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def step_callbacks
  @step_callbacks
end

#tools ⇒ Object (readonly)

Returns the value of attribute tools.



5
6
7
# File 'lib/rubyagents/agent.rb', line 5

def tools
  @tools
end

Instance Method Details

#done? ⇒ Boolean

Returns:

  • (Boolean)


141
# File 'lib/rubyagents/agent.rb', line 141

def done? = !@final_answer_value.nil?

#final_answer_value ⇒ Object



143
# File 'lib/rubyagents/agent.rb', line 143

def final_answer_value = @final_answer_value

#interrupt ⇒ Object



152
153
154
# File 'lib/rubyagents/agent.rb', line 152

def interrupt
  @interrupt_switch = true
end

#reset! ⇒ Object



145
146
147
148
149
150
# File 'lib/rubyagents/agent.rb', line 145

def reset!
  @memory = nil
  @step_number = 0
  @final_answer_value = nil
  @interrupt_switch = false
end

#run(task, reset: true, return_full_result: false, &on_stream) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rubyagents/agent.rb', line 30

def run(task, reset: true, return_full_result: false, &on_stream)
  @interrupt_switch = false

  if reset || @memory.nil?
    @memory = Memory.new(system_prompt: system_prompt, task: task)
  else
    memory.add_user_message(task)
  end

  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  notify(:on_run_start, task: task)

  (1..max_steps).each do |step_number|
    raise InterruptError, "Agent interrupted" if @interrupt_switch

    notify(:on_step_start, step_number: step_number)
    maybe_plan(step_number)

    # Call LLM with timing
    llm_duration, response = timed { generate_response(memory.to_messages, &on_stream) }

    # Parse response
    thought, action = parse_response(response)
    UI.thought(thought) if thought

    # Check for final answer or execute
    if action
      result = run_action(thought, action, response, llm_duration)
      if result
        error_msg = validate_final_answer(result)
        error_msg ||= validate_output_type(result)
        if error_msg
          memory.add_user_message(error_msg)
          next
        end
        total_timing = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
        built = build_result(result, total_timing, return_full_result)
        notify(:on_run_end, result: built)
        return built
      end
    else
      UI.step_metrics(duration: llm_duration, token_usage: response.token_usage)
      step = memory.add_step(thought: thought || response.content, code: nil, observation: nil,
                             duration: llm_duration, token_usage: response.token_usage)
      notify_callbacks(step)
    end
  end

  # Max steps reached
  UI.run_summary(
    total_steps: memory.action_steps.size,
    total_duration: memory.total_duration,
    total_tokens: memory.total_tokens
  )
  UI.error("Max steps (#{max_steps}) reached without a final answer")
  notify(:on_error, error: MaxStepsError.new("Max steps (#{max_steps}) reached"))

  total_timing = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
  output = memory.last_step&.observation
  result = RunResult.new(
    output: output,
    state: "max_steps",
    steps: memory.action_steps,
    token_usage: memory.total_tokens,
    timing: total_timing
  )
  notify(:on_run_end, result: return_full_result ? result : output)
  return_full_result ? result : output
end

#step(task_or_message = nil) ⇒ Object

--- Step-by-step execution ---

Raises:



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/rubyagents/agent.rb', line 102

def step(task_or_message = nil)
  if @memory.nil?
    raise ArgumentError, "task required on first call" unless task_or_message
    @memory = Memory.new(system_prompt: system_prompt, task: task_or_message)
    @step_number = 0
    @interrupt_switch = false
    @final_answer_value = nil
  elsif task_or_message
    memory.add_user_message(task_or_message)
  end

  @step_number += 1
  raise MaxStepsError, "Max steps (#{max_steps}) reached" if @step_number > max_steps
  raise InterruptError, "Agent interrupted" if @interrupt_switch

  maybe_plan(@step_number)

  llm_duration, response = timed { generate_response(memory.to_messages) }
  thought, action = parse_response(response)

  if action
    result = run_action(thought, action, response, llm_duration)
    if result
      error_msg = validate_final_answer(result)
      error_msg ||= validate_output_type(result)
      if error_msg
        memory.add_user_message(error_msg)
      else
        @final_answer_value = result
      end
    end
  else
    memory.add_step(thought: thought || response.content,
                    duration: llm_duration, token_usage: response.token_usage)
  end

  memory.last_step
end