Class: LLM::Agent

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

Overview

LLM::Agent provides a class-level DSL for defining reusable, preconfigured assistants with defaults for model, tools, schema, and instructions.

It wraps the same stateful runtime surface as LLM::Context: message history, usage, persistence, streaming parameters, and provider-backed requests still flow through an underlying context. The defining behavior of an agent is that it automatically resolves pending tool calls for you during talk and respond, instead of leaving tool loops to the caller.

Notes:

  • Instructions are injected once unless a system message is already present.
  • An agent automatically executes tool loops (unlike LLM::Context).
  • The automatic tool loop enables the wrapped context's guard by default. The built-in LLM::LoopGuard detects repeated tool-call patterns and blocks stuck execution before more tool work is queued.
  • The default tool attempt budget is 25. After that, the agent sends advisory tool errors back through the model and keeps the loop in-band. Set tool_attempts: nil to disable that advisory behavior.
  • Tool loop execution can be configured with concurrency :call, :thread, :task, :fiber, or :ractor.

Examples:

class SystemAdmin < LLM::Agent
  set model: "gpt-4.1-nano",
      instructions: "You are a Linux system admin",
      tools: [Shell],
      schema: Result
end

llm = LLM.openai(key: ENV["KEY"])
agent = SystemAdmin.new(llm)
agent.talk("Run 'date'")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(llm, params = {}) ⇒ Agent

Returns a new instance of Agent.

Parameters:

  • llm (LLM::Provider)

    A provider

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

    The parameters to maintain throughout the conversation. Any parameter the provider supports can be included and not only those listed here.

Options Hash (params):

  • :model (String)

    Defaults to the provider's default model

  • :tools (Array<LLM::Function>, nil)

    Defaults to nil

  • :skills (Array<String>, nil)

    Defaults to nil

  • :schema (#to_json, nil)

    Defaults to nil

  • :stream (Object, Proc, nil)

    Optional stream override for this agent instance

  • :tracer (LLM::Tracer, Proc, nil)

    Optional tracer override for this agent instance

  • :concurrency (Symbol, Array<Symbol>, nil)

    Defaults to the agent class concurrency



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/llm/agent.rb', line 248

def initialize(llm, params = {})
  @llm = llm
  fields = %i[model skills schema tracer stream tools concurrency instructions confirm]
  fields_ivar = %i[tracer concurrency instructions confirm]
  fields.each do |field|
    resolvable = params.key?(field) ? params.delete(field) : self.class.public_send(field)
    resolve_symbol = !%i[concurrency].include?(field)
    resolved = resolvable != nil ? resolve_option(self, resolvable, resolve_symbol:) : resolvable
    resolved = [*resolved].map(&:to_s) if field == :confirm && resolved
    if field == :model
      params[field] = resolved unless resolved.nil? || params.key?(field)
    elsif resolved && !fields_ivar.include?(field)
      params[field] ||= resolved
    elsif fields_ivar.include?(field)
      instance_variable_set(:"@#{field}", resolved)
    end
  end
  @ctx = LLM::Context.new(llm, {guard: true}.merge(params))
end

Instance Attribute Details

#llmLLM::Provider (readonly)

Returns a provider

Returns:



43
44
45
# File 'lib/llm/agent.rb', line 43

def llm
  @llm
end

Class Method Details

.concurrency(concurrency = nil) ⇒ Symbol, ...

Set or get the tool execution concurrency.

Parameters:

  • concurrency (Symbol, Array<Symbol>, nil) (defaults to: nil)

    Controls how pending tool loops are executed:

    • :call: sequential calls
    • :thread: concurrent threads
    • :task: concurrent async tasks
    • :fiber: concurrent scheduler-backed fibers
    • :fork: forked child processes
    • :ractor: concurrent Ruby ractors for class-based tools; MCP tools are not supported, and this mode is especially useful for CPU-bound tool work Usually pass a single strategy. Arrays are only for advanced mixed-work cases and are not needed for normal queued stream tool loops.

Returns:

  • (Symbol, Array<Symbol>, nil)


159
160
161
162
# File 'lib/llm/agent.rb', line 159

def self.concurrency(concurrency = nil)
  return @concurrency if concurrency.nil?
  @concurrency = concurrency
end

.confirm(*tool_names, &block) ⇒ Array<String>, ...

Set or get the tool names that require confirmation before they can run.

When a single Symbol is given, it is stored as-is and resolved at initialization time by calling the method with that name on the agent instance. This allows dynamic tool confirmation lists.

Examples:

class MyAgent < LLM::Agent
  confirm :tools_that_need_confirmation

  def tools_that_need_confirmation
    some_condition ? %w[delete destroy] : %w[delete]
  end
end

Parameters:

  • tool_names (String, Symbol, Array<String, Symbol>, Proc)

    One or more tool names.

  • block (Proc)

    An optional, lazy-evaluated Proc

Returns:

  • (Array<String>, Proc, Symbol, nil)


225
226
227
228
229
230
231
232
# File 'lib/llm/agent.rb', line 225

def self.confirm(*tool_names, &block)
  return @confirm if tool_names.empty? && !block
  if tool_names.size == 1 && tool_names.grep(Symbol).any?
    @confirm = tool_names.first
  else
    @confirm = block || tool_names.flatten.map(&:to_s)
  end
end

.instructions(instructions = nil) ⇒ String?

Set or get the default instructions

Parameters:

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

    The system instructions

Returns:

  • (String, nil)

    Returns the current instructions when no argument is provided



139
140
141
142
# File 'lib/llm/agent.rb', line 139

def self.instructions(instructions = nil)
  return @instructions if instructions.nil?
  @instructions = instructions
end

.model(model = nil, &block) ⇒ String?

Set or get the default model

Parameters:

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

    The model identifier

Returns:

  • (String, nil)

    Returns the current model when no argument is provided



87
88
89
90
# File 'lib/llm/agent.rb', line 87

def self.model(model = nil, &block)
  return @model if model.nil? && !block
  @model = block || model
end

.schema(schema = nil, &block) ⇒ #to_json?

Set or get the default schema

Parameters:

  • schema (#to_json, nil) (defaults to: nil)

    The schema

Returns:

  • (#to_json, nil)

    Returns the current schema when no argument is provided



128
129
130
131
# File 'lib/llm/agent.rb', line 128

def self.schema(schema = nil, &block)
  return @schema if schema.nil? && !block
  @schema = block || schema
end

.set(properties) ⇒ void

This method returns an undefined value.

Bulk-assign class-level agent defaults from a Hash.

Each key is resolved by calling the corresponding class method on the agent subclass. An error is raised for unknown keys so that typos are caught early.

Examples:

class AdminAgent < LLM::Agent
  set instructions: "You are a system administrator",
      model: "gpt-4.1-nano",
      tools: [Shell, ReadFile]
end

Parameters:

  • properties (Hash)

Options Hash (properties):

  • :instructions (String)
  • :model (String)
  • :tools (Array<LLM::Function>)
  • :skills (Array<String>)
  • :schema (#to_json)
  • :concurrency (Symbol, Array<Symbol>)
  • :tracer (LLM::Tracer, Proc)
  • :stream (Object, Proc)
  • :confirm (String, Symbol, Array<String, Symbol>, Proc)

Raises:

  • (KeyError)

    when a property key does not match a class-level accessor



71
72
73
74
75
76
77
78
79
# File 'lib/llm/agent.rb', line 71

def self.set(properties)
  properties.each do
    if respond_to?(_1)
      public_send(_1, _2)
    else
      raise KeyError, "key not found: #{_1}"
    end
  end
end

.skills(*skills, &block) ⇒ Array<String>?

Set or get the default skills

Parameters:

  • skills (Array<String>, nil)

    One or more skill directories

Returns:

  • (Array<String>, nil)

    Returns the current skills when no argument is provided



113
114
115
116
117
118
119
120
# File 'lib/llm/agent.rb', line 113

def self.skills(*skills, &block)
  return @skills if skills.empty? && !block
  if skills.size == 1 and skills.grep(Symbol).any?
    @skills = skills.first
  else
    @skills = block || skills.flatten
  end
end

.stream(stream = nil, &block) ⇒ Object, ...

Set or get the default stream.

When a block is provided, it is stored and evaluated lazily against the agent instance during initialization so it can build a fresh stream for each agent.

Examples:

class Agent < LLM::Agent
  stream { MyStream.new }
end

Parameters:

  • stream (Object, Proc, nil) (defaults to: nil)

Yield Returns:

Returns:



199
200
201
202
# File 'lib/llm/agent.rb', line 199

def self.stream(stream = nil, &block)
  return @stream if stream.nil? && !block
  @stream = block || stream
end

.tools(*tools, &block) ⇒ Array<LLM::Function>

Set or get the default tools

Parameters:

Returns:

  • (Array<LLM::Function>)

    Returns the current tools when no argument is provided



98
99
100
101
102
103
104
105
# File 'lib/llm/agent.rb', line 98

def self.tools(*tools, &block)
  return @tools || [] if tools.empty? && !block
  if tools.size == 1 and tools.grep(Symbol).any?
    @tools = tools.first
  else
    @tools = block || tools.flatten
  end
end

.tracer(tracer = nil, &block) ⇒ LLM::Tracer, ...

Set or get the default tracer.

When a block is provided, it is stored and evaluated lazily against the agent instance during initialization so it can build a tracer from the resolved provider.

Examples:

class Agent < LLM::Agent
  tracer { LLM::Tracer::Logger.new(llm, io: $stdout) }
end

Parameters:

Yield Returns:

Returns:



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

def self.tracer(tracer = nil, &block)
  return @tracer if tracer.nil? && !block
  @tracer = block || tracer
end

Instance Method Details

#ask(prompt, params = {}) ⇒ Object

See Also:



290
291
292
# File 'lib/llm/agent.rb', line 290

def ask(prompt, params = {})
  run_loop(prompt, params, :ask)
end

#concurrencySymbol, ...

Returns the configured tool execution concurrency.

Returns:

  • (Symbol, Array<Symbol>, nil)


410
411
412
# File 'lib/llm/agent.rb', line 410

def concurrency
  @concurrency
end

#context_windowInteger

Returns:

  • (Integer)

See Also:



424
425
426
# File 'lib/llm/agent.rb', line 424

def context_window
  @ctx.context_window
end

#costLLM::Cost

Returns:

See Also:



417
418
419
# File 'lib/llm/agent.rb', line 417

def cost
  @ctx.cost
end

#deserialize(**kw) ⇒ LLM::Agent Also known as: restore

Returns:



504
505
506
507
# File 'lib/llm/agent.rb', line 504

def deserialize(**kw)
  @ctx.deserialize(**kw)
  self
end

#functionsArray<LLM::Function> Also known as: pending_functions

Returns:



302
303
304
# File 'lib/llm/agent.rb', line 302

def functions
  @tracer ? @llm.with_tracer(@tracer) { @ctx.functions } : @ctx.functions
end

#image_url(url) ⇒ LLM::Object

Returns a tagged object

Parameters:

  • url (String)

    The URL

Returns:



349
350
351
# File 'lib/llm/agent.rb', line 349

def image_url(url)
  @ctx.image_url(url)
end

#inspectString

Returns:

  • (String)


488
489
490
491
# File 'lib/llm/agent.rb', line 488

def inspect
  "#<#{LLM::Utils.object_id(self)} " \
  "@llm=#{@llm.class}, @mode=#{mode.inspect}, @messages=#{messages.inspect}>"
end

#interrupt!nil Also known as: cancel!

Interrupt the active request, if any.

Returns:

  • (nil)


330
331
332
# File 'lib/llm/agent.rb', line 330

def interrupt!
  @ctx.interrupt!
end

#local_file(path) ⇒ LLM::Object

Returns a tagged object

Parameters:

  • path (String)

    The path

Returns:



358
359
360
# File 'lib/llm/agent.rb', line 358

def local_file(path)
  @ctx.local_file(path)
end

#messagesLLM::Buffer<LLM::Message>



296
297
298
# File 'lib/llm/agent.rb', line 296

def messages
  @ctx.messages
end

#modeSymbol

Returns:

  • (Symbol)


403
404
405
# File 'lib/llm/agent.rb', line 403

def mode
  @ctx.mode
end

#modelString

Returns the model an Agent is actively using

Returns:

  • (String)


397
398
399
# File 'lib/llm/agent.rb', line 397

def model
  @ctx.model
end

#on_tool_confirmation(fn, strategy) ⇒ LLM::Function::Return

This method is called when confirmation is required before a tool can run.

Parameters:

  • fn (LLM::Function)

    The pending function call. It can be cancelled through the Function#cancel method.

  • strategy (Symbol, Array<Symbol>)

    The execution strategy that would be used for the tool call.

Returns:

  • (LLM::Function::Return)

    Return either fn.spawn(strategy).wait to approve execution or fn.cancel(...) to cancel the call.



521
522
523
# File 'lib/llm/agent.rb', line 521

def on_tool_confirmation(fn, strategy)
  fn.cancel
end

#paramsHash

Returns:

  • (Hash)

See Also:



469
470
471
# File 'lib/llm/agent.rb', line 469

def params
  @ctx.params
end

#prompt(&b) ⇒ LLM::Prompt Also known as: build_prompt

Parameters:

  • b (Proc)

    A block that composes messages. If it takes one argument, it receives the prompt object. Otherwise it runs in prompt context.

Returns:

See Also:



339
340
341
# File 'lib/llm/agent.rb', line 339

def prompt(&b)
  @ctx.prompt(&b)
end

#remote_file(res) ⇒ LLM::Object

Returns a tagged object

Parameters:

Returns:



367
368
369
# File 'lib/llm/agent.rb', line 367

def remote_file(res)
  @ctx.remote_file(res)
end

#repl(path: nil, tools: [], skills: [], tracer: false, trace: nil) ⇒ void

Note:

By default this method disables the tracer for the duration of the repl session, and restores it afterwards.

This method returns an undefined value.

Start a minimalist repl that can interact with the agent and its current state. This method requires the 'curses' gem to be installed and available to require.

Parameters:

  • path (String) (defaults to: nil)

    The path to a file where runtime state is read from, and written to

  • tools (Array<LLM::Tool>) (defaults to: [])

    Extra tools to attach for the repl session

  • skills (Array<String>) (defaults to: [])

    Extra skills to attach for the repl session

  • tracer (Boolean) (defaults to: false)

    When true, the tracer is kept alive during the repl session. Default is false.



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/llm/agent.rb', line 449

def repl(path: nil, tools: [], skills: [], tracer: false, trace: nil)
  if trace != nil
    warn "llm.rb: trace option is deprecated, use tracer instead"
    tracer = trace
  end
  if !tracer
    previous    = self.tracer
    self.tracer = nil
  end
  require_relative "repl" unless defined?(::LLM::Repl)
  LLM::Repl.new(agent: self, path:, tools:, skills:).start
ensure
  if !tracer
    self.tracer = previous
  end
end

#returnsArray<LLM::Function::Return>

Returns:

See Also:



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

def returns
  @ctx.returns
end

#serialize(**kw) ⇒ void Also known as: save

This method returns an undefined value.



496
497
498
# File 'lib/llm/agent.rb', line 496

def serialize(**kw)
  @ctx.serialize(**kw)
end

#streamLLM::Stream, ...

Returns a stream object, or nil

Returns:

  • (LLM::Stream, #<<, nil)

    Returns a stream object, or nil



390
391
392
# File 'lib/llm/agent.rb', line 390

def stream
  @ctx.stream
end

#talk(prompt, params = {}) ⇒ LLM::Response

Maintain a conversation via the chat completions API. This method immediately sends a request to the LLM and returns the response.

Examples:

llm = LLM.openai(key: ENV["KEY"])
agent = LLM::Agent.new(llm)
response = agent.talk("Hello, what is your name?")
puts response.choices[0].content

Parameters:

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

    The params passed to the provider, including optional :stream, :tools, :schema etc.

  • prompt (String)

    The input prompt to be completed

Options Hash (params):

  • :tool_attempts (Integer)

    The maxinum number of tool call iterations before the agent sends in-band advisory tool errors back through the model (default 25). Set to nil to disable advisory tool-limit returns.

Returns:



284
285
286
# File 'lib/llm/agent.rb', line 284

def talk(prompt, params = {})
  run_loop(prompt, params, :talk)
end

#to_hHash

Returns:

  • (Hash)

See Also:



476
477
478
# File 'lib/llm/agent.rb', line 476

def to_h
  @ctx.to_h
end

#to_jsonString

Returns:

  • (String)


482
483
484
# File 'lib/llm/agent.rb', line 482

def to_json(...)
  LLM.json.dump(to_h, ...)
end

#tracerLLM::Tracer

Returns an LLM tracer

Returns:



374
375
376
# File 'lib/llm/agent.rb', line 374

def tracer
  @tracer || @ctx.tracer
end

#tracer=(other) ⇒ void

This method returns an undefined value.

Parameters:



382
383
384
385
# File 'lib/llm/agent.rb', line 382

def tracer=(other)
  @ctx.tracer = other
  @tracer = other
end

#usageLLM::Object

Returns:



323
324
325
# File 'lib/llm/agent.rb', line 323

def usage
  @ctx.usage
end

#waitArray<LLM::Function::Return>

Returns:

See Also:



317
318
319
# File 'lib/llm/agent.rb', line 317

def wait(...)
  @tracer ? @llm.with_tracer(@tracer) { @ctx.wait(...) } : @ctx.wait(...)
end