Class: PatientLLM::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/patient_llm/agent.rb,
lib/patient_llm/agent/failure.rb,
lib/patient_llm/agent/response.rb

Overview

Base class for declarative LLM agents. An agent bundles everything about one LLM integration in a single class: the provider, model, generation settings, tools (schema and handler together), structured output schema, and completion handling. The agent class itself is the callback identity, so its name is what travels through the job queue — everything else stays in code and is re-resolved in the worker process.

Subclasses inherit every declaration from their parent agent class, including tools and the output schema. Override a setting by redeclaring it, or remove an inherited scalar setting by passing an explicit nil (e.g. temperature nil).

The completed/failed/tool_round hooks can be redirected to another class for one request by passing it as the :callback option to Agent.ask; the agent still supplies the configuration and the tools.

Examples:

class TripPlannerAgent < PatientLLM::Agent
  provider :openai
  model "gpt-5"
  system "You are a travel assistant. Be concise."
  temperature 0.3
  max_tool_iterations 5

  tool :weather, "Get weather forecast for a city" do
    param :city, :string, "City name", required: true
    param :country, :string
  end

  output do
    field :summary, :string, required: true
    field :packing_list, array: :string
  end

  def weather(city:, country: nil)
    WeatherService.forecast(city: city, country: country)
  end

  def completed(response)
    Trip.find(response.context[:trip_id]).update!(plan: response.object, agent_state: response.state)
  end

  def failed(failure)
    Rails.logger.error("#{failure.error_type}: #{failure.message}")
  end
end

TripPlannerAgent.ask("Plan a weekend in NYC", context: {trip_id: trip.id})
TripPlannerAgent.continue(trip.agent_state, "Make it kid-friendly", context: {trip_id: trip.id})
response = TripPlannerAgent.ask!("Plan a weekend in NYC")  # inline, for consoles and tests

Defined Under Namespace

Classes: Failure, Response

Constant Summary collapse

PLUMBING_METHODS =

Methods that implement the callback plumbing contract. Subclasses must override the completed/failed/tool_round hooks instead.

%i[on_complete on_tool_use on_error prepare handles_tool? invoke_tool].freeze
HOOKS =

The user-facing hooks that a class passed as the :callback option to ask may implement to take over from the agent's own hooks.

%i[completed failed tool_round].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#providerString? (readonly)

Returns the provider name for the current invocation.

Returns:

  • (String, nil)

    the provider name for the current invocation



519
520
521
# File 'lib/patient_llm/agent.rb', line 519

def provider
  @provider
end

#sessionPromptBuilder::Session? (readonly)

Returns the session for the current invocation.

Returns:

  • (PromptBuilder::Session, nil)

    the session for the current invocation



516
517
518
# File 'lib/patient_llm/agent.rb', line 516

def session
  @session
end

Class Method Details

.apply_configuration(session, except: []) ⇒ PromptBuilder::Session

Apply the agent's declarations to a session. Used for both new sessions and sessions restored from persisted state. Fields listed in except are left untouched (used by build_session so per-request options win over the agent's declarations).

Parameters:

  • session (PromptBuilder::Session)
  • except (Array<Symbol>) (defaults to: [])

    session fields to skip

Returns:

  • (PromptBuilder::Session)


375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/patient_llm/agent.rb', line 375

def apply_configuration(session, except: [])
  system_message = system
  model_value = model
  instructions_value = instructions
  temperature_value = temperature
  max_output_tokens_value = max_output_tokens
  extra_value = extra
  apply_system_message(session, system_message) if system_message && !except.include?(:system)
  session.model = model_value if model_value && !except.include?(:model)
  session.instructions = instructions_value if instructions_value && !except.include?(:instructions)
  session.temperature = temperature_value if temperature_value && !except.include?(:temperature)
  session.max_output_tokens = max_output_tokens_value if max_output_tokens_value && !except.include?(:max_output_tokens)
  session.extra = extra_value if extra_value && !except.include?(:extra)
  session.think(**reasoning.transform_keys(&:to_sym)) if reasoning && !except.include?(:reasoning)
  if output_schema && !except.include?(:text)
    session.json_output(output_schema[:schema], name: output_schema[:name], strict: output_schema[:strict])
  end
  tools.each do |name, declaration|
    session.register_tool(
      name,
      description: declaration[:description],
      parameters: declaration[:parameters],
      strict: declaration[:strict] || false
    )
  end
  session
end

.ask(message = nil, context: {}, session: nil, callback: nil, **options) ⇒ Object

Send a message to the agent asynchronously. When the final response arrives (after any automatic tool rounds), the agent's completed hook is invoked in the worker; errors invoke failed.

Parameters:

  • message (String, Array, Hash, nil) (defaults to: nil)

    the user message to add

  • context (Hash) (defaults to: {})

    JSON-native data available as context on the response/failure objects in hooks and to tool methods

  • session (PromptBuilder::Session, nil) (defaults to: nil)

    an existing session to use instead of building a new one. The session is sent as-is; the agent's declarations are not applied to it (use continue for restored conversations).

  • callback (Class, String, nil) (defaults to: nil)

    a named class to receive the HOOKS instead of this agent. A fresh instance is created in the worker for each invocation; hooks the class does not implement fall back to the agent's own. Tools and configuration still come from the agent. Defaults to sending the hooks to the agent itself.

  • options (Hash)

    per-request overrides forwarded to PatientLLM.ask (url:, serializer:, path:, headers:, params:, preprocessors:, timeout:, max_tool_iterations:), plus session options (model:, temperature:, extra:, etc. — any PromptBuilder::Session::INITIALIZE_OPTIONS key) applied to the newly built session; session options raise ArgumentError when a session: is supplied.

Returns:

  • (Object)

    handler-specific identifier for the enqueued request

Raises:

  • (ArgumentError)


284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/patient_llm/agent.rb', line 284

def ask(message = nil, context: {}, session: nil, callback: nil, **options)
  session_options = options.slice(*PromptBuilder::Session::INITIALIZE_OPTIONS)
  raise ArgumentError.new("session options cannot be passed when a session is provided") if session && session_options.any?

  session ||= build_session(**session_options)
  session.user(message) if message

  ask_options = options.except(*PromptBuilder::Session::INITIALIZE_OPTIONS)
  iterations = max_tool_iterations
  ask_options[:max_tool_iterations] ||= iterations if iterations

  agent_callback_args = {"context" => PromptBuilder.jsonify(context || {})}
  callback_name = callback_class_name(callback)
  agent_callback_args["callback"] = callback_name if callback_name

  PatientLLM.ask(
    session,
    provider: provider_name!,
    callback: self,
    callback_args: agent_callback_args,
    **ask_options
  )
end

.ask!(message = nil, context: {}, session: nil, **options) ⇒ Agent::Response

Send a message and execute the request inline (synchronously, in-process), returning the final Response. The automatic tool loop runs inline too. Intended for consoles, development, and tests. The completed/failed hooks still run, so this exercises real code paths.

Parameters:

  • message (String, Array, Hash, nil) (defaults to: nil)

    the user message to add

  • context (Hash) (defaults to: {})

    JSON-native data available as context on the response/failure objects in hooks

  • session (PromptBuilder::Session, nil) (defaults to: nil)

    an existing session to use

  • options (Hash)

    per-request overrides forwarded to ask (including callback:) and PatientLLM.ask

Returns:

Raises:

  • (PatientHttp::Error)

    when the request fails

  • (RuntimeError)

    when the request completes without capturing a response



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/patient_llm/agent.rb', line 339

def ask!(message = nil, context: {}, session: nil, **options)
  capture = {}
  previous = Thread.current.thread_variable_get(:patient_llm_agent_capture)
  Thread.current.thread_variable_set(:patient_llm_agent_capture, capture)
  begin
    PatientLLM.inline do
      ask(message, context: context, session: session, **options)
    end
  ensure
    Thread.current.thread_variable_set(:patient_llm_agent_capture, previous)
  end

  raise capture[:error] if capture[:error]

  capture[:response] || raise("No response was captured; the request did not complete")
end

.build_session(**options) ⇒ PromptBuilder::Session

Build a new session from the agent's declarations. Options passed by the caller take precedence over the agent's declarations for the same fields (pass an explicit nil to unset a declared value for one request).

Returns:

  • (PromptBuilder::Session)


361
362
363
364
365
# File 'lib/patient_llm/agent.rb', line 361

def build_session(**options)
  session = PromptBuilder::Session.new(**options)
  apply_configuration(session, except: options.keys)
  session
end

.continue(state, message = nil, context: {}, **options) ⇒ Object

Continue a persisted conversation. The session is restored from the state hash (as returned by response.state), the agent's current configuration is re-applied to it (so model/instruction/tool/schema changes deploy cleanly to older conversations), and the message is sent.

Parameters:

  • state (Hash)

    a session state hash from response.state

  • message (String, Array, Hash, nil) (defaults to: nil)

    the user message to add

  • context (Hash) (defaults to: {})

    JSON-native data available as context on the response/failure objects in hooks

  • options (Hash)

    per-request overrides forwarded to ask (including callback:) and PatientLLM.ask. Session options (model:, extra:, etc.) raise ArgumentError because continue supplies its own session.

Returns:

  • (Object)

    handler-specific identifier for the enqueued request



320
321
322
323
324
# File 'lib/patient_llm/agent.rb', line 320

def continue(state, message = nil, context: {}, **options)
  session = PromptBuilder::Session.from_h(state)
  apply_configuration(session)
  ask(message, context: context, session: session, **options)
end

.extra(value = NOT_SPECIFIED) { ... } ⇒ Hash?

DSL: get or set provider-specific extra data set on sessions built by this agent (e.g. the Bedrock Converse :guardrail_config). The recognized keys depend on the serializer used for the request. The value is inherited from the parent agent class unless set; redeclaring replaces the whole hash. Pass an explicit nil to remove an inherited value.

Parameters:

  • value (Hash, nil) (defaults to: NOT_SPECIFIED)

    the extra data. Passing an explicit nil removes the previously set value.

Yields:

  • an optional block (or callable argument) called each time the value is read so it can be generated dynamically (e.g. extra { LLMConfiguration.extra_hash }); the result is validated and jsonified at read time.

Returns:

  • (Hash, nil)


197
198
199
200
201
202
# File 'lib/patient_llm/agent.rb', line 197

def extra(value = NOT_SPECIFIED, &block)
  get_or_set_setting(:extra, value, block) do |resolved|
    raise ArgumentError, "extra must be a Hash" unless resolved.nil? || resolved.is_a?(Hash)
    resolved.nil? ? nil : PromptBuilder.jsonify(resolved)
  end
end

.instructions(value = NOT_SPECIFIED) { ... } ⇒ String?

DSL: get or set instructions applied to every request. These are appended to the system prompt on APIs that don't support instructions as a separate field. The value is inherited from the parent agent class unless set; pass an explicit nil to remove an inherited value.

Parameters:

  • value (String, nil) (defaults to: NOT_SPECIFIED)

    the instructions. Passing an explicit nil removes the previously set value.

Yields:

  • an optional block (or callable argument) called each time the value is read so it can be generated dynamically.

Returns:

  • (String, nil)


118
119
120
# File 'lib/patient_llm/agent.rb', line 118

def instructions(value = NOT_SPECIFIED, &block)
  get_or_set_setting(:instructions, value, block)
end

.max_output_tokens(value = NOT_SPECIFIED) { ... } ⇒ Integer?

DSL: get or set the maximum output tokens. The value is inherited from the parent agent class unless set; pass an explicit nil to remove an inherited value.

Parameters:

  • value (Integer, nil) (defaults to: NOT_SPECIFIED)

    the maximum output tokens. Passing an explicit nil removes the previously set value.

Yields:

  • an optional block (or callable argument) called each time the value is read so it can be generated dynamically.

Returns:

  • (Integer, nil)


168
169
170
# File 'lib/patient_llm/agent.rb', line 168

def max_output_tokens(value = NOT_SPECIFIED, &block)
  get_or_set_setting(:max_output_tokens, value, block)
end

.max_tool_iterations(value = NOT_SPECIFIED) { ... } ⇒ Integer?

DSL: get or set the maximum automatic tool-execution rounds. The value is inherited from the parent agent class unless set; pass an explicit nil to remove an inherited value.

Parameters:

  • value (Integer, nil) (defaults to: NOT_SPECIFIED)

    the maximum automatic tool-execution rounds. Passing an explicit nil removes the previously set value.

Yields:

  • an optional block (or callable argument) called each time the value is read so it can be generated dynamically.

Returns:

  • (Integer, nil)


180
181
182
# File 'lib/patient_llm/agent.rb', line 180

def max_tool_iterations(value = NOT_SPECIFIED, &block)
  get_or_set_setting(:max_tool_iterations, value, block)
end

.model(value = NOT_SPECIFIED) { ... } ⇒ String?

DSL: get or set the model. The value is inherited from the parent agent class unless set; pass an explicit nil to remove an inherited value.

Parameters:

  • value (String, nil) (defaults to: NOT_SPECIFIED)

    the model name. Passing an explicit nil removes the previously set value.

Yields:

  • an optional block (or callable argument) called each time the value is read so it can be generated dynamically.

Returns:

  • (String, nil)


93
94
95
# File 'lib/patient_llm/agent.rb', line 93

def model(value = NOT_SPECIFIED, &block)
  get_or_set_setting(:model, value, block)
end

.output(schema: nil, name: "response", strict: nil) { ... } ⇒ void

This method returns an undefined value.

DSL: declare a structured output schema. The model's response is parsed into a Hash available as response.object in the completed hook. Use a Schema block or pass a raw JSON Schema hash with schema:.

Parameters:

  • schema (Hash, nil) (defaults to: nil)

    a raw JSON Schema hash

  • name (String) (defaults to: "response")

    the schema name sent to the provider

  • strict (Boolean, nil) (defaults to: nil)

    whether strict schema adherence is requested

Yields:

  • an optional Schema block declaring the fields

Raises:

  • (ArgumentError)


242
243
244
245
246
247
248
249
250
251
# File 'lib/patient_llm/agent.rb', line 242

def output(schema: nil, name: "response", strict: nil, &block)
  raise ArgumentError, "pass either schema: or a schema block, not both" if schema && block
  raise ArgumentError, "output requires schema: or a schema block" if schema.nil? && block.nil?

  @output_schema = {
    name: name.to_s,
    schema: schema ? PromptBuilder.jsonify(schema) : Schema.build(&block),
    strict: strict
  }
end

.output_schemaHash?

The declared output schema, inherited from the parent agent class unless declared with output.

Returns:

  • (Hash, nil)

    the schema declaration (:name, :schema, :strict)



257
258
259
# File 'lib/patient_llm/agent.rb', line 257

def output_schema
  inherited_setting(:output_schema)
end

.provider(name = NOT_SPECIFIED) { ... } ⇒ Symbol?

DSL: get or set the provider name for this agent. The value is inherited from the parent agent class unless set; pass an explicit nil to remove an inherited value.

Parameters:

  • name (Symbol, String, nil) (defaults to: NOT_SPECIFIED)

    the registered provider name. Passing an explicit nil removes the previously set value.

Yields:

  • an optional block (or callable argument) called each time the value is read so it can be generated dynamically; the result is coerced to a Symbol at read time.

Returns:

  • (Symbol, nil)


82
83
84
# File 'lib/patient_llm/agent.rb', line 82

def provider(name = NOT_SPECIFIED, &block)
  get_or_set_setting(:provider, name, block) { |value| value&.to_sym }
end

.reasoning(value = NOT_SPECIFIED, effort: nil, budget_tokens: nil) ⇒ Hash?

DSL: get or set the reasoning configuration. Accepts a portable effort level (:minimal, :low, :medium, :high, :xhigh, :max) or explicit options (effort: or budget_tokens:) which are applied with session.think. The value is inherited from the parent agent class unless set; pass an explicit nil to remove an inherited value.

Parameters:

  • value (Symbol, String, nil) (defaults to: NOT_SPECIFIED)

    a portable effort level. Passing an explicit nil removes the previously set value.

  • effort (Symbol, String, nil) (defaults to: nil)

    explicit effort level

  • budget_tokens (Integer, nil) (defaults to: nil)

    explicit thinking token budget

Returns:

  • (Hash, nil)

    the reasoning options

Raises:

  • (ArgumentError)


145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/patient_llm/agent.rb', line 145

def reasoning(value = NOT_SPECIFIED, effort: nil, budget_tokens: nil)
  return inherited_setting(:reasoning) if value.equal?(NOT_SPECIFIED) && effort.nil? && budget_tokens.nil?

  passed = [(value.equal?(NOT_SPECIFIED) ? nil : value), effort, budget_tokens].compact
  raise ArgumentError, "pass a level, effort:, or budget_tokens: — not more than one" if passed.size > 1

  @reasoning = if passed.empty?
    nil
  elsif effort || budget_tokens
    {effort: effort&.to_s, budget_tokens: budget_tokens}.compact
  else
    {effort: value.to_s}
  end
end

.system(value = NOT_SPECIFIED) { ... } ⇒ String?

DSL: get or set the system message. The value is inherited from the parent agent class unless set; pass an explicit nil to remove an inherited value.

Parameters:

  • value (String, nil) (defaults to: NOT_SPECIFIED)

    the system message. Passing an explicit nil removes the previously set value.

Yields:

  • an optional block (or callable argument) called each time the value is read so it can be generated dynamically.

Returns:

  • (String, nil)


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

def system(value = NOT_SPECIFIED, &block)
  get_or_set_setting(:system, value, block)
end

.temperature(value = NOT_SPECIFIED) { ... } ⇒ Numeric?

DSL: get or set the sampling temperature. The value is inherited from the parent agent class unless set; pass an explicit nil to remove an inherited value.

Parameters:

  • value (Numeric, nil) (defaults to: NOT_SPECIFIED)

    the temperature. Passing an explicit nil removes the previously set value.

Yields:

  • an optional block (or callable argument) called each time the value is read so it can be generated dynamically.

Returns:

  • (Numeric, nil)


130
131
132
# File 'lib/patient_llm/agent.rb', line 130

def temperature(value = NOT_SPECIFIED, &block)
  get_or_set_setting(:temperature, value, block)
end

.tool(name, description = nil, parameters: nil, strict: nil) { ... } ⇒ void

This method returns an undefined value.

DSL: declare a tool. The tool's handler is the instance method with the same name; define it in the class body with keyword arguments matching the declared parameters. The schema can be declared with a Schema block or passed as a raw JSON Schema hash with parameters:.

Parameters:

  • name (Symbol, String)

    the tool name (must be a valid method name)

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

    what the tool does

  • parameters (Hash, nil) (defaults to: nil)

    a raw JSON Schema hash for the parameters

  • strict (Boolean, nil) (defaults to: nil)

    whether strict schema adherence is requested

Yields:

  • an optional Schema block declaring the parameters

Raises:

  • (ArgumentError)


215
216
217
218
219
220
# File 'lib/patient_llm/agent.rb', line 215

def tool(name, description = nil, parameters: nil, strict: nil, &block)
  raise ArgumentError, "pass either parameters: or a schema block, not both" if parameters && block

  schema = parameters ? PromptBuilder.jsonify(parameters) : Schema.build(&block)
  own_tools[name.to_s] = {description: description, parameters: schema, strict: strict}
end

.tool_declared?(name) ⇒ Boolean

Check if a tool name is declared on this agent.

Parameters:

  • name (Symbol, String)

    the tool name

Returns:

  • (Boolean)


407
408
409
# File 'lib/patient_llm/agent.rb', line 407

def tool_declared?(name)
  tools.key?(name.to_s)
end

.toolsHash<String, Hash>

The declared tools, including tools inherited from parent agent classes. A tool redeclared in a subclass replaces the inherited declaration. The returned hash is a copy; declare tools with the tool DSL method rather than mutating it.

Returns:

  • (Hash<String, Hash>)

    tool name to declaration



228
229
230
231
# File 'lib/patient_llm/agent.rb', line 228

def tools
  merged = (self == PatientLLM::Agent) ? {} : superclass.tools
  merged.merge!(deep_dup(own_tools))
end

Instance Method Details

#completed(response) ⇒ void

This method returns an undefined value.

Hook: invoked with the final Response after any automatic tool rounds complete. Override in your agent, or in a class passed as the :callback option to ask.

Parameters:

  • response (Agent::Response)

    the final response; exposes the text, structured output, session state, HTTP exchange, and context



635
636
# File 'lib/patient_llm/agent.rb', line 635

def completed(response)
end

#failed(failure) ⇒ void

This method returns an undefined value.

Hook: invoked when a request fails. Override in your agent, or in a class passed as the :callback option to ask. The default implementation re-raises the error so unhandled failures are never silently lost — under a job system this fails the callback job, making the error visible to its retry and error reporting. During inline execution the raise is skipped because ask! raises the captured error itself (raising here as well would make the executor invoke the error callback a second time with a wrapped error).

Parameters:

  • failure (Agent::Failure)

    the failure; exposes the error along with the session, HTTP exchange, and context



650
651
652
# File 'lib/patient_llm/agent.rb', line 650

def failed(failure)
  raise failure.error unless Thread.current.thread_variable_get(:patient_llm_agent_capture)
end

#handles_tool?(name) ⇒ 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.

Plumbing: whether this agent handles the named tool with an instance method. Used by Callback to route tool execution. Only public methods defined by the agent class hierarchy (or modules mixed into it) count as handlers — a declared tool name that collides with an inherited Object method must not route LLM-controlled invocations to it. A declared tool with no valid handler raises rather than returning false so a private handler or a typo'd method name fails loudly instead of producing a bogus completion.

Returns:

  • (Boolean)


603
604
605
606
607
608
609
610
611
612
# File 'lib/patient_llm/agent.rb', line 603

def handles_tool?(name)
  return false unless self.class.tool_declared?(name)

  method_name = name.to_sym
  unless respond_to?(method_name) && tool_method_owned_by_agent?(method_name)
    raise NoMethodError, "#{self.class} declares tool #{name.to_s.inspect} but does not define a public instance method ##{method_name} to handle it"
  end

  true
end

#invoke_tool(name, arguments, callback_args: 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.

Plumbing: invoke the tool's instance method with the LLM-provided arguments as keywords. A tool method that declares a context: keyword also receives the context passed to ask/continue, extracted from the callback args supplied by Callback.

Raises:

  • (ArgumentError)


620
621
622
623
624
625
626
# File 'lib/patient_llm/agent.rb', line 620

def invoke_tool(name, arguments, callback_args: nil)
  raise ArgumentError, "#{self.class} does not handle tool #{name.inspect}" unless handles_tool?(name)

  kwargs = (arguments || {}).transform_keys(&:to_sym)
  kwargs[:context] = extract_context(callback_args) if tool_accepts_context?(name)
  public_send(name.to_sym, **kwargs)
end

#on_complete(session:, provider:, llm_response:, callback_args:, http_response:, request_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.

Plumbing: adapter for the Callback contract. Do not override; implement completed(response) instead.



544
545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/patient_llm/agent.rb', line 544

def on_complete(session:, provider:, llm_response:, callback_args:, http_response:, request_id:)
  prepare(session: session, provider: provider, callback_args: callback_args, http_response: http_response, request_id: request_id)
  response = Response.new(
    llm_response,
    session: session,
    output_schema: self.class.output_schema,
    http_response: http_response,
    http_request_id: http_response&.request_id,
    context: extract_context(callback_args)
  )
  capture_result(:response, response)
  dispatch_hook(:completed, response)
end

#on_error(session:, provider:, callback_args:, error:, http_response:, request_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.

Plumbing: adapter for the Callback contract. Do not override; implement failed(failure) instead.



578
579
580
581
582
583
584
585
586
587
588
589
590
591
# File 'lib/patient_llm/agent.rb', line 578

def on_error(session:, provider:, callback_args:, error:, http_response:, request_id:)
  prepare(session: session, provider: provider, callback_args: callback_args, http_response: http_response, request_id: request_id)
  http_request_id = http_response&.request_id
  http_request_id ||= error.request_id if error.respond_to?(:request_id)
  failure = Failure.new(
    error,
    session: session,
    http_response: http_response,
    http_request_id: http_request_id,
    context: extract_context(callback_args)
  )
  capture_result(:error, error)
  dispatch_hook(:failed, failure)
end

#on_tool_use(session:, provider:, llm_response:, callback_args:, http_response:, request_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.

Plumbing: adapter for the Callback contract. Do not override; implement tool_round(response) instead.



562
563
564
565
566
567
568
569
570
571
572
# File 'lib/patient_llm/agent.rb', line 562

def on_tool_use(session:, provider:, llm_response:, callback_args:, http_response:, request_id:)
  prepare(session: session, provider: provider, callback_args: callback_args, http_response: http_response, request_id: request_id)
  response = Response.new(
    llm_response,
    session: session,
    http_response: http_response,
    http_request_id: http_response&.request_id,
    context: extract_context(callback_args)
  )
  dispatch_hook(:tool_round, response)
end

#prepare(session: nil, provider: nil, callback_args: nil, http_response: nil, request_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.

Plumbing: called by Callback before hooks and tool execution to make the invocation state available to instance methods. Do not override.



525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/patient_llm/agent.rb', line 525

def prepare(session: nil, provider: nil, callback_args: nil, http_response: nil, request_id: nil)
  @session = session
  @provider = provider
  @callback_delegate = build_callback_delegate(callback_args)
  if @callback_delegate.is_a?(PatientLLM::Agent)
    @callback_delegate.prepare(
      session: session,
      provider: provider,
      callback_args: callback_args,
      http_response: http_response,
      request_id: request_id
    )
  end
end

#tool_round(response) ⇒ void

This method returns an undefined value.

Hook: invoked once per automatic tool round, after the tools run and before the next request is issued. Override in your agent (or in a class passed as the :callback option to ask) to observe intermediate progress.

Parameters:



660
661
# File 'lib/patient_llm/agent.rb', line 660

def tool_round(response)
end