Class: Phronomy::Agent::AgentInvocation Private

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

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Mutable domain state for one Agent invocation.

AgentInvocation interprets Agent-internal events. FSMSession owns the transition mechanics, while Agent::Base projects the terminal outcome to both the Application listener and the returned Task.

Constant Summary collapse

TOOL_EVENT_TYPES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%i[
  tool_authorized
  tool_approval_required
  tool_completed
  tool_failed
  tool_rejected
  tool_cancelled
].freeze
LLM_EVENT_TYPES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%i[
  llm_completed
  llm_failed
].freeze
CALLBACK_FAILED_EVENTS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%i[application_callback_failed].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent:, input:, messages:, config:, approval_policy: nil, approval_listener: nil, event_listener: nil, stream_listener: nil, mode: nil, id: nil) ⇒ AgentInvocation

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of AgentInvocation.



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
# File 'lib/phronomy/agent/agent_invocation.rb', line 59

def initialize(
  agent:,
  input:,
  messages:,
  config:,
  approval_policy: nil,
  approval_listener: nil,
  event_listener: nil,
  stream_listener: nil,
  mode: nil,
  id: nil
)
  @agent = agent
  @input = input
  @messages = Array(messages)
  @config = config
  @thread_id = config[:thread_id]
  @id = (id || config[:agent_invocation_id] || SecureRandom.uuid).to_s
  invocation_context = config[:invocation_context]
  invocation_policy = if invocation_context&.respond_to?(:approval_policy)
    invocation_context.approval_policy
  end
  @approval_policy = invocation_policy || approval_policy
  @approval_listener = approval_listener
  @event_listener = event_listener || stream_listener
  @mode = (mode || (stream_listener ? :stream : :invoke)).to_sym

  @chat = nil
  @output = nil
  @usage = nil
  @input_blocked = false
  @output_blocked = false
  @block_error = nil
  @user_message_sent = false
  @pending_tool_calls = []
  @tool_invocations = []
  @approval_request = nil
  @rejected = false
  @human_rejection = false
  @approval_resume_in_progress = false
  @pending_approval_resolution_ids = []
  @error = nil
  @session_id = nil
  @phase = nil
  @current_llm_call_id = nil
  @tool_batch_llm_call_id = nil
end

Instance Attribute Details

#agentObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def agent
  @agent
end

#approval_listenerObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def approval_listener
  @approval_listener
end

#approval_policyObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def approval_policy
  @approval_policy
end

#approval_requestObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def approval_request
  @approval_request
end

#block_errorObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def block_error
  @block_error
end

#chatObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def chat
  @chat
end

#configObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def config
  @config
end

#current_llm_call_idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def current_llm_call_id
  @current_llm_call_id
end

#errorObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def error
  @error
end

#event_listenerObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def event_listener
  @event_listener
end

#idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def id
  @id
end

#inputObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def input
  @input
end

#input_blockedObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def input_blocked
  @input_blocked
end

#messagesObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def messages
  @messages
end

#modeObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def mode
  @mode
end

#outputObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def output
  @output
end

#output_blockedObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def output_blocked
  @output_blocked
end

#pending_tool_callsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def pending_tool_calls
  @pending_tool_calls
end

#phaseObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def phase
  @phase
end

#rejectedObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def rejected
  @rejected
end

#session_idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def session_id
  @session_id
end

#thread_idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def thread_id
  @thread_id
end

#tool_batch_llm_call_idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def tool_batch_llm_call_id
  @tool_batch_llm_call_id
end

#tool_invocationsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



45
46
47
# File 'lib/phronomy/agent/agent_invocation.rb', line 45

def tool_invocations
  @tool_invocations
end

#usageObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def usage
  @usage
end

#user_message_sentObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def user_message_sent
  @user_message_sent
end

Instance Method Details

#accept_tool_calls!(tool_calls, llm_call_id: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/phronomy/agent/agent_invocation.rb', line 202

def accept_tool_calls!(tool_calls, llm_call_id: nil)
  @user_message_sent = true
  @pending_tool_calls = Array(tool_calls)
  @messages = @chat.messages
  @tool_batch_llm_call_id = (llm_call_id || @current_llm_call_id)&.to_s
  @current_llm_call_id = nil
  @pending_tool_calls.each do |tool_call|
    deliver_event(
      StreamEvent.new(
        type: :tool_call,
        payload: {
          tool_call: tool_call,
          llm_call_id: @tool_batch_llm_call_id
        }.compact
      )
    )
  end
  self
end

#apply_fsm_action_result(result) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/phronomy/agent/agent_invocation.rb', line 183

def apply_fsm_action_result(result)
  event_type =
    if result.respond_to?(:error) &&
        result.error &&
        !result.error.is_a?(ToolCallIntercepted)
      :llm_failed
    else
      :llm_completed
    end
  handle_fsm_event(
    Phronomy::Event.new(
      type: event_type,
      target_id: @id,
      payload: result
    )
  )
  self
end

#apply_llm_response!(response) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/phronomy/agent/agent_invocation.rb', line 222

def apply_llm_response!(response)
  unless response
    raise Phronomy::Error, "LLM operation completed without a response"
  end

  @user_message_sent = true
  @output = response.content
  @usage = Phronomy::TokenUsage.from_tokens(response.tokens)
  @messages = @chat.messages
  @pending_tool_calls = []
  @current_llm_call_id = nil
  self
end

#approval_contextObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/phronomy/agent/agent_invocation.rb', line 245

def approval_context
  return @config[:approval_context] if @config[:approval_context]

  context = @config[:invocation_context]
  return {} unless context

  %i[
    thread_id session_id user_id token_budget
    task_id parent_task_id
  ].each_with_object({}) do |name, result|
    if context.respond_to?(name)
      result[name] = context.public_send(name)
    end
  end
end

#approval_required?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


331
332
333
334
335
336
# File 'lib/phronomy/agent/agent_invocation.rb', line 331

def approval_required?
  !@human_rejection &&
    !@approval_resume_in_progress &&
    preflight_complete? &&
    @tool_invocations.any?(&:awaiting_approval?)
end

#begin_approval_resume!(approved:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



261
262
263
264
265
266
267
268
269
# File 'lib/phronomy/agent/agent_invocation.rb', line 261

def begin_approval_resume!(approved:)
  @human_rejection = !approved
  @pending_approval_resolution_ids = @tool_invocations
    .select(&:awaiting_approval?)
    .map(&:id)
  @approval_resume_in_progress =
    !@pending_approval_resolution_ids.empty?
  self
end

#begin_llm_call!(llm_call_id) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



124
125
126
127
# File 'lib/phronomy/agent/agent_invocation.rb', line 124

def begin_llm_call!(llm_call_id)
  @current_llm_call_id = llm_call_id.to_s
  self
end

#clear_tool_batch!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



137
138
139
140
141
142
# File 'lib/phronomy/agent/agent_invocation.rb', line 137

def clear_tool_batch!
  @pending_tool_calls = []
  @tool_invocations = []
  @approval_request = nil
  @tool_batch_llm_call_id = nil
end

#handle_fsm_event(event) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



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
# File 'lib/phronomy/agent/agent_invocation.rb', line 144

def handle_fsm_event(event)
  if event.type == :llm_stream_chunk
    deliver_event(
      StreamEvent.new(
        type: :token,
        payload: {content: event.payload.fetch(:content)}
      )
    )
    return true
  end

  if LLM_EVENT_TYPES.include?(event.type)
    apply_llm_event(event)
    return true
  end

  if CALLBACK_FAILED_EVENTS.include?(event.type)
    @error ||= event.payload.fetch(:failure).to_stream_callback_error
    return true
  end

  return false unless TOOL_EVENT_TYPES.include?(event.type)

  invocation = tool_invocation(
    event.payload&.fetch(:tool_invocation_id, nil)
  )
  return true unless invocation

  @error ||= invocation.error if invocation.failed? || invocation.cancelled?
  @rejected = true if invocation.rejected?
  if @approval_resume_in_progress &&
      @pending_approval_resolution_ids.delete(invocation.id)
    if @pending_approval_resolution_ids.empty?
      @approval_resume_in_progress = false
    end
  end
  true
end

#input_blocked?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


310
311
312
# File 'lib/phronomy/agent/agent_invocation.rb', line 310

def input_blocked?
  @input_blocked
end

#input_passed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


306
307
308
# File 'lib/phronomy/agent/agent_invocation.rb', line 306

def input_passed?
  !@input_blocked
end

#merge_config!(values) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



240
241
242
243
# File 'lib/phronomy/agent/agent_invocation.rb', line 240

def merge_config!(values)
  @config.merge!(values) unless values.empty?
  self
end

#output_blocked?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


318
319
320
# File 'lib/phronomy/agent/agent_invocation.rb', line 318

def output_blocked?
  @output_blocked
end

#output_passed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


314
315
316
# File 'lib/phronomy/agent/agent_invocation.rb', line 314

def output_passed?
  !@output_blocked
end

#preflight_complete?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


326
327
328
329
# File 'lib/phronomy/agent/agent_invocation.rb', line 326

def preflight_complete?
  !@tool_invocations.empty? &&
    @tool_invocations.all?(&:preflight_settled?)
end

#prepare_approval_request!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



271
272
273
274
# File 'lib/phronomy/agent/agent_invocation.rb', line 271

def prepare_approval_request!
  @approval_request = ToolApprovalRequest.build(self)
  self
end

#ready_to_dispatch?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


338
339
340
341
342
343
344
345
346
# File 'lib/phronomy/agent/agent_invocation.rb', line 338

def ready_to_dispatch?
  !@approval_resume_in_progress &&
    preflight_complete? &&
    @tool_invocations.none?(&:awaiting_approval?) &&
    @tool_invocations.none?(&:rejected?) &&
    @tool_invocations.none?(&:failed?) &&
    @tool_invocations.none?(&:cancelled?) &&
    @tool_invocations.any?(&:authorized?)
end

#record_tool_results!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



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
# File 'lib/phronomy/agent/agent_invocation.rb', line 276

def record_tool_results!
  @tool_invocations.each do |invocation|
    tool_content = invocation.result.to_s
    @chat.add_message(
      role: :tool,
      content: tool_content,
      tool_call_id: invocation.tool_call_id
    )
    deliver_event(
      StreamEvent.new(
        type: :tool_result,
        payload: {
          tool_call_id: invocation.tool_call_id,
          tool_name: invocation.tool_name,
          tool_result: invocation.result,
          tool_message: {
            "role" => "tool",
            "content" => tool_content,
            "tool_call_id" => invocation.tool_call_id.to_s
          },
          llm_call_id: @tool_batch_llm_call_id
        }.compact
      )
    )
  end
  @messages = @chat.messages
  clear_tool_batch!
  self
end

#set_graph_metadata(thread_id: nil, phase: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



119
120
121
122
# File 'lib/phronomy/agent/agent_invocation.rb', line 119

def (thread_id: nil, phase: nil)
  @session_id = thread_id if thread_id
  @phase = phase
end

#stream_listenerObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



107
108
109
# File 'lib/phronomy/agent/agent_invocation.rb', line 107

def stream_listener
  @event_listener
end

#stream_listener=(listener) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



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

def stream_listener=(listener)
  @event_listener = listener
end

#streaming?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


115
116
117
# File 'lib/phronomy/agent/agent_invocation.rb', line 115

def streaming?
  @mode == :stream
end

#tool_batch_completed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


376
377
378
379
# File 'lib/phronomy/agent/agent_invocation.rb', line 376

def tool_batch_completed?
  tool_batch_terminal? &&
    @tool_invocations.all?(&:execution_completed?)
end

#tool_batch_failed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/phronomy/agent/agent_invocation.rb', line 353

def tool_batch_failed?
  return false if @human_rejection || @approval_resume_in_progress

  no_rejection = @tool_invocations.none?(&:rejected?)
  preflight_failure =
    preflight_complete? &&
    @tool_invocations.any? do |invocation|
      invocation.failed? || invocation.cancelled?
    end
  terminal_failure =
    tool_batch_terminal? &&
    @tool_invocations.any? do |invocation|
      invocation.failed? || invocation.cancelled?
    end
  no_rejection && (preflight_failure || terminal_failure)
end

#tool_batch_rejected?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


370
371
372
373
374
# File 'lib/phronomy/agent/agent_invocation.rb', line 370

def tool_batch_rejected?
  return false unless @tool_invocations.any?(&:rejected?)

  @human_rejection ? tool_batch_terminal? : preflight_complete?
end

#tool_batch_terminal?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


348
349
350
351
# File 'lib/phronomy/agent/agent_invocation.rb', line 348

def tool_batch_terminal?
  !@tool_invocations.empty? &&
    @tool_invocations.all?(&:terminal?)
end

#tool_call_pending?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


322
323
324
# File 'lib/phronomy/agent/agent_invocation.rb', line 322

def tool_call_pending?
  !@pending_tool_calls.empty?
end

#tool_invocation(id) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



236
237
238
# File 'lib/phronomy/agent/agent_invocation.rb', line 236

def tool_invocation(id)
  @tool_invocations.find { |invocation| invocation.id == id.to_s }
end