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



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/llm/agent.rb', line 208

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

.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
# File 'lib/llm/agent.rb', line 62

def self.tools(*tools, &block)
  return @tools || [] if tools.empty? && !block
  @tools = block || tools.flatten
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



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

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

.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



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

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



99
100
101
102
# File 'lib/llm/agent.rb', line 99

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

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


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

def self.concurrency(concurrency = nil)
  return @concurrency if concurrency.nil?
  @concurrency = concurrency
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:



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

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



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

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


185
186
187
188
189
190
191
192
# File 'lib/llm/agent.rb', line 185

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

.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

Instance Method Details

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



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

def on_tool_confirmation(fn, strategy)
  fn.cancel
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:



244
245
246
# File 'lib/llm/agent.rb', line 244

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

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

See Also:



250
251
252
# File 'lib/llm/agent.rb', line 250

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

#messagesLLM::Buffer<LLM::Message>



256
257
258
# File 'lib/llm/agent.rb', line 256

def messages
  @ctx.messages
end

#functionsArray<LLM::Function>

Returns:



262
263
264
# File 'lib/llm/agent.rb', line 262

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

#returnsArray<LLM::Function::Return>

Returns:

See Also:



269
270
271
# File 'lib/llm/agent.rb', line 269

def returns
  @ctx.returns
end

#waitArray<LLM::Function::Return>

Returns:

See Also:



276
277
278
# File 'lib/llm/agent.rb', line 276

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

#usageLLM::Object

Returns:



282
283
284
# File 'lib/llm/agent.rb', line 282

def usage
  @ctx.usage
end

#interrupt!nil Also known as: cancel!

Interrupt the active request, if any.

Returns:

  • (nil)


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

def interrupt!
  @ctx.interrupt!
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:



298
299
300
# File 'lib/llm/agent.rb', line 298

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

#image_url(url) ⇒ LLM::Object

Returns a tagged object

Parameters:

  • url (String)

    The URL

Returns:



308
309
310
# File 'lib/llm/agent.rb', line 308

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

#local_file(path) ⇒ LLM::Object

Returns a tagged object

Parameters:

  • path (String)

    The path

Returns:



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

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

#remote_file(res) ⇒ LLM::Object

Returns a tagged object

Parameters:

Returns:



326
327
328
# File 'lib/llm/agent.rb', line 326

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

#tracerLLM::Tracer

Returns an LLM tracer

Returns:



333
334
335
# File 'lib/llm/agent.rb', line 333

def tracer
  @tracer || @ctx.tracer
end

#streamLLM::Stream, ...

Returns a stream object, or nil

Returns:

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

    Returns a stream object, or nil



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

def stream
  @ctx.stream
end

#modelString

Returns the model an Agent is actively using

Returns:

  • (String)


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

def model
  @ctx.model
end

#modeSymbol

Returns:

  • (Symbol)


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

def mode
  @ctx.mode
end

#concurrencySymbol, ...

Returns the configured tool execution concurrency.

Returns:

  • (Symbol, Array<Symbol>, nil)


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

def concurrency
  @concurrency
end

#costLLM::Cost

Returns:

See Also:



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

def cost
  @ctx.cost
end

#context_windowInteger

Returns:

  • (Integer)

See Also:



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

def context_window
  @ctx.context_window
end

#paramsHash

Returns:

  • (Hash)

See Also:



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

def params
  @ctx.params
end

#to_hHash

Returns:

  • (Hash)

See Also:



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

def to_h
  @ctx.to_h
end

#to_jsonString

Returns:

  • (String)


394
395
396
# File 'lib/llm/agent.rb', line 394

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

#inspectString

Returns:

  • (String)


400
401
402
403
# File 'lib/llm/agent.rb', line 400

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

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

This method returns an undefined value.



408
409
410
# File 'lib/llm/agent.rb', line 408

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

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



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

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