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
  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



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/llm/agent.rb', line 212

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)


123
124
125
126
# File 'lib/llm/agent.rb', line 123

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)


189
190
191
192
193
194
195
196
# File 'lib/llm/agent.rb', line 189

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



103
104
105
106
# File 'lib/llm/agent.rb', line 103

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



51
52
53
54
# File 'lib/llm/agent.rb', line 51

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



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

def self.schema(schema = nil, &block)
  return @schema if schema.nil? && !block
  @schema = block || schema
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



77
78
79
80
81
82
83
84
# File 'lib/llm/agent.rb', line 77

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:



163
164
165
166
# File 'lib/llm/agent.rb', line 163

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



62
63
64
65
66
67
68
69
# File 'lib/llm/agent.rb', line 62

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:



143
144
145
146
# File 'lib/llm/agent.rb', line 143

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:



254
255
256
# File 'lib/llm/agent.rb', line 254

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

#concurrencySymbol, ...

Returns the configured tool execution concurrency.

Returns:

  • (Symbol, Array<Symbol>, nil)


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

def concurrency
  @concurrency
end

#context_windowInteger

Returns:

  • (Integer)

See Also:



387
388
389
# File 'lib/llm/agent.rb', line 387

def context_window
  @ctx.context_window
end

#costLLM::Cost

Returns:

See Also:



380
381
382
# File 'lib/llm/agent.rb', line 380

def cost
  @ctx.cost
end

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



440
441
442
# File 'lib/llm/agent.rb', line 440

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

#functionsArray<LLM::Function>

Returns:



266
267
268
# File 'lib/llm/agent.rb', line 266

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:



312
313
314
# File 'lib/llm/agent.rb', line 312

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

#inspectString

Returns:

  • (String)


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

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)


293
294
295
# File 'lib/llm/agent.rb', line 293

def interrupt!
  @ctx.interrupt!
end

#local_file(path) ⇒ LLM::Object

Returns a tagged object

Parameters:

  • path (String)

    The path

Returns:



321
322
323
# File 'lib/llm/agent.rb', line 321

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

#messagesLLM::Buffer<LLM::Message>



260
261
262
# File 'lib/llm/agent.rb', line 260

def messages
  @ctx.messages
end

#modeSymbol

Returns:

  • (Symbol)


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

def mode
  @ctx.mode
end

#modelString

Returns the model an Agent is actively using

Returns:

  • (String)


360
361
362
# File 'lib/llm/agent.rb', line 360

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.



456
457
458
# File 'lib/llm/agent.rb', line 456

def on_tool_confirmation(fn, strategy)
  fn.cancel
end

#paramsHash

Returns:

  • (Hash)

See Also:



405
406
407
# File 'lib/llm/agent.rb', line 405

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:



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

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

#remote_file(res) ⇒ LLM::Object

Returns a tagged object

Parameters:

Returns:



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

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

#replvoid

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.



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

def repl
  require_relative "repl" unless defined?(::LLM::Repl)
  LLM::Repl.new(self).start
end

#returnsArray<LLM::Function::Return>

Returns:

See Also:



273
274
275
# File 'lib/llm/agent.rb', line 273

def returns
  @ctx.returns
end

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

This method returns an undefined value.



432
433
434
# File 'lib/llm/agent.rb', line 432

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



353
354
355
# File 'lib/llm/agent.rb', line 353

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:



248
249
250
# File 'lib/llm/agent.rb', line 248

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

#to_hHash

Returns:

  • (Hash)

See Also:



412
413
414
# File 'lib/llm/agent.rb', line 412

def to_h
  @ctx.to_h
end

#to_jsonString

Returns:

  • (String)


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

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

#tracerLLM::Tracer

Returns an LLM tracer

Returns:



337
338
339
# File 'lib/llm/agent.rb', line 337

def tracer
  @tracer || @ctx.tracer
end

#tracer=(other) ⇒ void

This method returns an undefined value.

Parameters:



345
346
347
348
# File 'lib/llm/agent.rb', line 345

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

#usageLLM::Object

Returns:



286
287
288
# File 'lib/llm/agent.rb', line 286

def usage
  @ctx.usage
end

#waitArray<LLM::Function::Return>

Returns:

See Also:



280
281
282
# File 'lib/llm/agent.rb', line 280

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