Class: Legate::Agents::SequentialAgent

Inherits:
Legate::Agent show all
Defined in:
lib/legate/agents/sequential_agent.rb

Overview

SequentialAgent executes a series of sub-agents in a predefined order. Each sub-agent is executed one after another, with the same session and input.

Constant Summary

Constants inherited from Legate::Agent

Legate::Agent::DEFAULT_MODEL

Instance Attribute Summary

Attributes inherited from Legate::Agent

#after_agent_callback, #after_model_callback, #after_tool_callback, #auth_credential_assignments, #auth_credential_names, #auth_scheme_assignments, #auth_url_mappings, #before_agent_callback, #before_model_callback, #before_tool_callback, #definition, #description, #fallback_mode, #instruction, #logger, #model_name, #name, #parent_agent, #planner, #session_service, #state, #sub_agents, #tool_registry

Instance Method Summary collapse

Methods inherited from Legate::Agent

#add_tool, #apply_pending_state, #ask, #available_tools_metadata, define, #find_agent, #find_sub_agent, #find_tool, #find_tool_class, #initialize, #record_error_event, #register_tool_class, #root_agent, #running?, #start, #stop, #tools, #transfer_to

Constructor Details

This class inherits a constructor from Legate::Agent

Instance Method Details

#run_task(session_id:, user_input:, session_service:) ⇒ Legate::Event

Override run_task to execute sub-agents in sequence

Parameters:

  • session_id (String)

    The session ID

  • user_input (String)

    User input to process

  • session_service (Legate::SessionService::Base)

    Session service for persistence

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
99
100
101
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/legate/agents/sequential_agent.rb', line 16

def run_task(session_id:, user_input:, session_service:)
  # Verify we have sequential sub-agents defined
  unless @definition.sequential_sub_agent_names&.any?
    err_msg = "SequentialAgent '#{name}' has no sequential_sub_agent_names defined."
    Legate.logger.error(err_msg)
    return Legate::Event.new(role: :agent, content: {
                               status: :error,
                               error_message: err_msg,
                               error_class: 'ConfigurationError'
                             })
  end

  # --- Pre-execution Checks --- #
  unless running?
    err_msg = "Agent '#{name}' is not running. Call agent.start before run_task, " \
              'or use agent.ask (which starts automatically).'
    Legate.logger.error(err_msg)
    return Legate::Event.new(role: :agent, content: { status: :error, error_message: err_msg })
  end

  session = session_service.get_session(session_id: session_id)
  unless session
    err_msg = "Session not found: #{session_id}"
    Legate.logger.error(err_msg)
    return Legate::Event.new(role: :agent, content: { status: :error, error_message: err_msg })
  end
  # --------------------------- #

  # Log user input to the SequentialAgent itself
  user_event = Legate::Event.new(role: :user, content: user_input)
  session_service.append_event(session_id: session_id, event: user_event)

  # Log the execution sequence start
  Legate.logger.info("SequentialAgent '#{name}' starting execution of #{@definition.sequential_sub_agent_names.size} sub-agents in sequence.")

  # Track results of all sub-agents
  all_results = []
  final_result = nil
  current_input = user_input # Start with the original user input

  # Execute each sub-agent in order
  @definition.sequential_sub_agent_names.each_with_index do |sub_agent_name, index|
    sub_agent = find_sub_agent(sub_agent_name)
    unless sub_agent
      err_msg = "Sub-agent '#{sub_agent_name}' not found for SequentialAgent '#{name}'."
      Legate.logger.error(err_msg)
      final_result = {
        status: :error,
        error_message: err_msg,
        error_class: 'MissingSubAgentError',
        step: index + 1,
        total_steps: @definition.sequential_sub_agent_names.size,
        previous_results: all_results.map.with_index { |r, i| { agent: @definition.sequential_sub_agent_names.to_a[i], result: r } }
      }
      break # Stop the sequence on error
    end

    # Start the sub-agent if it's not already running
    sub_agent.start unless sub_agent.running?

    # If this is not the first agent, try to augment its input with previous results
    if index > 0 && all_results.any?
      # Get previous agent name - handle both Array and Set types
      previous_agent_name = nil
      previous_agent_name = if @definition.sequential_sub_agent_names.is_a?(Set)
                              # For Set, convert to Array and get the element
                              @definition.sequential_sub_agent_names.to_a[index - 1]
                            else
                              # For Array, access directly
                              @definition.sequential_sub_agent_names[index - 1]
                            end

      previous_output_key = find_sub_agent(previous_agent_name)&.definition&.output_key

      if previous_output_key && session_service.respond_to?(:get_state)
        # Get the previous agent's result from session state
        previous_result = session_service.get_state(session_id: session_id, key: previous_output_key)

        if previous_result && previous_result.is_a?(Hash) && previous_result[:result]
          # Enhanced input with previous result
          current_input = "#{current_input}\n\nHere is the result from the previous step (#{previous_agent_name}):\n#{previous_result[:result]}"
          Legate.logger.info("Enhanced input for sub-agent '#{sub_agent_name}' with previous result from '#{previous_agent_name}'")
        end
      end
    end

    # Execute the sub-agent with the updated input
    begin
      Legate.logger.info("SequentialAgent '#{name}' executing sub-agent '#{sub_agent_name}' (step #{index + 1}/#{@definition.sequential_sub_agent_names.size}).")
      sub_result = sub_agent.run_task(
        session_id: session_id,
        user_input: current_input,
        session_service: session_service
      )

      # Record the result
      all_results << sub_result.content

      # Check for error to break sequence
      if sub_result.content[:status] == :error
        Legate.logger.warn("Sub-agent '#{sub_agent_name}' returned error, breaking sequence: #{sub_result.content[:error_message]}")
        final_result = {
          status: :error,
          error_message: "Error in sub-agent '#{sub_agent_name}': #{sub_result.content[:error_message]}",
          error_class: sub_result.content[:error_class] || 'SubAgentError',
          step: index + 1,
          total_steps: @definition.sequential_sub_agent_names.size,
          sub_agent: sub_agent_name.to_s,
          sub_result: sub_result.content,
          previous_results: all_results.map.with_index { |r, i| { agent: @definition.sequential_sub_agent_names.to_a[i], result: r } }
        }
        break # Stop the sequence on error
      elsif sub_result.content[:result]
        # If the sub-agent succeeded, update the current_input to include its result for the next agent
        # Store the raw result for potential state-based chaining in next iteration
        current_input = sub_result.content[:result].to_s
      end
    rescue StandardError => e
      Legate.logger.error("Error executing sub-agent '#{sub_agent_name}': #{e.class} - #{e.message}\n#{e.backtrace.join("\n")}")
      final_result = {
        status: :error,
        error_message: "Exception in sub-agent '#{sub_agent_name}': #{e.message}",
        error_class: e.class.name,
        step: index + 1,
        total_steps: @definition.sequential_sub_agent_names.size,
        sub_agent: sub_agent_name.to_s,
        previous_results: all_results.map.with_index { |r, i| { agent: @definition.sequential_sub_agent_names.to_a[i], result: r } }
      }
      break # Stop the sequence on error
    end
  end

  # If we didn't set a final_result due to an error, create a success result with all sub-results
  if final_result.nil?
    final_result = {
      status: :success,
      result: "Completed sequential execution of #{@definition.sequential_sub_agent_names.size} sub-agents",
      steps_completed: @definition.sequential_sub_agent_names.size,
      sub_results: all_results.map.with_index { |r, i| { agent: @definition.sequential_sub_agent_names.to_a[i], result: r } }
    }
  end

  # Create the final event
  final_agent_event = Legate::Event.new(role: :agent, content: final_result)

  # Log the final event to the session
  session_service.append_event(session_id: session_id, event: final_agent_event)

  # --- MAS: Store result in session state if output_key is defined --- #
  if @definition.respond_to?(:output_key) && @definition.output_key && final_agent_event
    output_value = final_agent_event.content # Store the entire content hash

    # Make sure the output value is serializable
    serialized_value = begin
      # Convert to JSON and back to ensure it's serializable
      JSON.parse(output_value.to_json)
    rescue StandardError => e
      Legate.logger.warn("SequentialAgent '#{@name}': Failed to serialize output value: #{e.message}. Using simplified value.")
      { status: output_value[:status], result: output_value[:result].to_s }
    end

    Legate.logger.info("SequentialAgent '#{@name}' storing output to session state with key '#{@definition.output_key}' for session '#{session_id}'.")
    if session_service.respond_to?(:set_state)
      session_service.set_state(session_id: session_id, key: @definition.output_key, value: serialized_value)
    else
      Legate.logger.warn("SequentialAgent '#{@name}': Session service does not support :set_state. Cannot store output for key '#{@definition.output_key}'.")
    end
  end
  # --- End MAS State Management --- #

  final_agent_event
end