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:

  • provider (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



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/llm/agent.rb', line 187

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


115
116
117
118
# File 'lib/llm/agent.rb', line 115

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.

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, nil)


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

def self.confirm(*tool_names, &block)
  return @confirm if tool_names.empty? && !block
  @confirm = block || tool_names.flatten.map(&:to_s)
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



95
96
97
98
# File 'lib/llm/agent.rb', line 95

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



84
85
86
87
# File 'lib/llm/agent.rb', line 84

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



73
74
75
76
# File 'lib/llm/agent.rb', line 73

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



155
156
157
158
# File 'lib/llm/agent.rb', line 155

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

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



135
136
137
138
# File 'lib/llm/agent.rb', line 135

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

Instance Method Details

#concurrencySymbol, ...

Returns the configured tool execution concurrency.

Returns:

  • (Symbol, Array<Symbol>, nil)


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

def concurrency
  @concurrency
end

#context_windowInteger

Returns:

  • (Integer)

See Also:



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

def context_window
  @ctx.context_window
end

#costLLM::Cost

Returns:

See Also:



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

def cost
  @ctx.cost
end

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



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

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

#functionsArray<LLM::Function>

Returns:



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

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:



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

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

#inspectString

Returns:

  • (String)


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

def inspect
  "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
  "@llm=#{@llm.class}, @mode=#{mode.inspect}, @messages=#{messages.inspect}>"
end

#interrupt!nil Also known as: cancel!

Interrupt the active request, if any.

Returns:

  • (nil)


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

def interrupt!
  @ctx.interrupt!
end

#local_file(path) ⇒ LLM::Object

Returns a tagged object

Parameters:

  • path (String)

    The path

Returns:



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

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

#messagesLLM::Buffer<LLM::Message>



230
231
232
# File 'lib/llm/agent.rb', line 230

def messages
  @ctx.messages
end

#modeSymbol

Returns:

  • (Symbol)


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

def mode
  @ctx.mode
end

#modelString

Returns the model an Agent is actively using

Returns:

  • (String)


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

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.



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

def on_tool_confirmation(fn, strategy)
  fn.cancel
end

#paramsHash

Returns:

  • (Hash)

See Also:



355
356
357
# File 'lib/llm/agent.rb', line 355

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:



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

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

#remote_file(res) ⇒ LLM::Object

Returns a tagged object

Parameters:

Returns:



300
301
302
# File 'lib/llm/agent.rb', line 300

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

#returnsArray<LLM::Function::Return>

Returns:

See Also:



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

def returns
  @ctx.returns
end

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

This method returns an undefined value.



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

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



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

def stream
  @ctx.stream
end

#talk(prompt, params = {}) ⇒ LLM::Response Also known as: chat

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:

  • (LLM::Response)

    Returns the LLM’s response for this turn.



223
224
225
# File 'lib/llm/agent.rb', line 223

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

#to_hHash

Returns:

  • (Hash)

See Also:



362
363
364
# File 'lib/llm/agent.rb', line 362

def to_h
  @ctx.to_h
end

#to_jsonString

Returns:

  • (String)


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

def to_json(...)
  to_h.to_json(...)
end

#tracerLLM::Tracer

Returns an LLM tracer

Returns:



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

def tracer
  @tracer || @ctx.tracer
end

#usageLLM::Object

Returns:



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

def usage
  @ctx.usage
end

#waitArray<LLM::Function::Return>

Returns:

See Also:



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

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