Class: LLM::Agent
- Inherits:
-
Object
- Object
- LLM::Agent
- Defined in:
- lib/llm/agent.rb
Overview
LLM::Agent is the recommended entry point for most use-cases. It 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,
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
guardby default. The built-in LLM::Guard::Loop detects repeated tool-call patterns and blocks stuck execution before more tool work is queued. - The tool loop can be bounded with
tool_budget. Once the budget is spent, the agent sends an in-band advisory message back through the model and keeps the loop in-band. By default no budget is set (nil), so the feature is disabled. - Tool loop execution can be configured with
concurrency :sequential,:thread,:async,:fiber,:fork, or:ractor.
Instance Attribute Summary collapse
-
#llm ⇒ LLM::Provider
readonly
Returns a provider.
Class Method Summary collapse
-
.concurrency(concurrency = nil) ⇒ Symbol, ...
Set or get the tool execution concurrency.
-
.confirm(*tool_names, &block) ⇒ Array<String>, ...
Set or get the tool names that require confirmation before they can run.
-
.description(desc = UNDEFINED, &block) ⇒ String?
Set or get an agent's description.
-
.instructions(instructions = nil) ⇒ String?
Set or get the default instructions.
-
.model(model = nil, &block) ⇒ String?
Set or get the default model.
-
.name(name = UNDEFINED, &block) ⇒ String
Set or get an agent's name.
-
.path(path = UNDEFINED, &block) ⇒ String?
Set the file path where an agent's memory can be restored from, and written to.
-
.schema(schema = nil, &block) ⇒ #to_json?
Set or get the default schema.
-
.set(properties) ⇒ void
Bulk-assign class-level agent defaults from a Hash.
-
.skills(*skills, &block) ⇒ Array<String>?
Set or get the default skills.
-
.stream(stream = nil, &block) ⇒ Object, ...
Set or get the default stream.
-
.tool_budget(budget = UNDEFINED, &block) ⇒ Integer?
Set or get the maximum number of tool calls that are allowed in a single turn.
-
.tools(*tools, &block) ⇒ Array<LLM::Function>
Set or get the default tools.
-
.tracer(tracer = nil, &block) ⇒ LLM::Tracer, ...
Set or get the default tracer.
Instance Method Summary collapse
- #ask(prompt, params = {}) ⇒ Object
- #compacted? ⇒ Boolean
-
#concurrency ⇒ Symbol, ...
Returns the configured tool execution concurrency.
- #context_window ⇒ Integer
- #cost ⇒ LLM::Cost
-
#description ⇒ String?
Returns the agent's description.
- #deserialize(**kw) ⇒ LLM::Agent (also: #restore)
-
#image_url(url) ⇒ LLM::Object
Returns a tagged object.
-
#initialize(llm, params = {}) ⇒ Agent
constructor
A new instance of Agent.
- #inspect ⇒ String
-
#interrupt! ⇒ nil
(also: #cancel!)
Interrupt the active request, if any.
-
#local_file(path) ⇒ LLM::Object
Returns a tagged object.
- #messages ⇒ LLM::Buffer<LLM::Message>
- #mode ⇒ Symbol
-
#model ⇒ String
Returns the model an Agent is actively using.
-
#name ⇒ String
Returns the agent's name.
-
#on_tool_confirmation(fn, strategy) ⇒ LLM::Function::Return
This method is called when confirmation is required before a tool can run.
- #params ⇒ Hash
-
#path ⇒ String?
Returns a file path where an agent's memory is restored from, and written to after each turn.
- #pending_functions ⇒ Array<LLM::Function>
- #prompt(&b) ⇒ LLM::Prompt (also: #build_prompt)
-
#remote_file(res) ⇒ LLM::Object
Returns a tagged object.
-
#repl(name: self.name, path: nil, tools: [], skills: [], tracer: false, trace: nil) ⇒ void
Start a minimalist repl that can interact with the agent and its current state.
- #returns ⇒ Array<LLM::Function::Return>
- #serialize(**kw) ⇒ void (also: #save)
-
#stream ⇒ LLM::Stream, ...
Returns a stream object, or nil.
-
#talk(prompt, params = {}) ⇒ LLM::Response
Maintain a conversation via the chat completions API.
- #to_h ⇒ Hash
- #to_json ⇒ String
-
#tracer ⇒ LLM::Tracer
Returns an LLM tracer.
- #tracer=(other) ⇒ void
- #usage ⇒ LLM::Object
- #wait ⇒ Array<LLM::Function::Return>
Constructor Details
#initialize(llm, params = {}) ⇒ Agent
Returns a new instance of Agent.
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/llm/agent.rb', line 352 def initialize(llm, params = {}) params = {}.merge!(params) @llm = llm fields = %i[name description path tool_budget model skills schema tracer stream tools concurrency instructions confirm] fields_ivar = %i[name description path tool_budget 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: LLM::Guard::Loop}.merge(params)) @path and File.readable?(@path) ? @ctx.restore(path:) : nil end |
Instance Attribute Details
#llm ⇒ LLM::Provider (readonly)
Returns a provider
70 71 72 |
# File 'lib/llm/agent.rb', line 70 def llm @llm end |
Class Method Details
.concurrency(concurrency = nil) ⇒ Symbol, ...
Set or get the tool execution concurrency.
227 228 229 230 |
# File 'lib/llm/agent.rb', line 227 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.
293 294 295 296 297 298 299 300 |
# File 'lib/llm/agent.rb', line 293 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 |
.description(desc = UNDEFINED, &block) ⇒ String?
This method serves as a self-documenting string. It is optional but recommended.
Set or get an agent's description
141 142 143 144 145 146 147 |
# File 'lib/llm/agent.rb', line 141 def self.description(desc = UNDEFINED, &block) if desc.equal?(UNDEFINED) @desc else @desc = block || desc end end |
.instructions(instructions = nil) ⇒ String?
Set or get the default instructions
207 208 209 210 |
# File 'lib/llm/agent.rb', line 207 def self.instructions(instructions = nil) return @instructions if instructions.nil? @instructions = instructions end |
.model(model = nil, &block) ⇒ String?
Set or get the default model
155 156 157 158 |
# File 'lib/llm/agent.rb', line 155 def self.model(model = nil, &block) return @model if model.nil? && !block @model = block || model end |
.name(name = UNDEFINED, &block) ⇒ String
This method serves as a self-documenting string and it is used by LLM::Repl. It is optional but recommended.
Set or get an agent's name
119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/llm/agent.rb', line 119 def self.name(name = UNDEFINED, &block) if name.equal?(UNDEFINED) if @name.nil? name = to_s.split("::").last @name = name.gsub(CASE_PATTERN, "-").downcase else @name end else @name = block || name end end |
.path(path = UNDEFINED, &block) ⇒ String?
Set the file path where an agent's memory can be restored from, and written to.
308 309 310 311 312 313 314 |
# File 'lib/llm/agent.rb', line 308 def self.path(path = UNDEFINED, &block) if path.equal?(UNDEFINED) @path else @path = path || block end end |
.schema(schema = nil, &block) ⇒ #to_json?
Set or get the default schema
196 197 198 199 |
# File 'lib/llm/agent.rb', line 196 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.
99 100 101 102 103 104 105 106 107 |
# File 'lib/llm/agent.rb', line 99 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
181 182 183 184 185 186 187 188 |
# File 'lib/llm/agent.rb', line 181 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.
267 268 269 270 |
# File 'lib/llm/agent.rb', line 267 def self.stream(stream = nil, &block) return @stream if stream.nil? && !block @stream = block || stream end |
.tool_budget(budget = UNDEFINED, &block) ⇒ Integer?
By default this feature is disabled
(set to nil).
Set or get the maximum number of tool calls that are allowed in a single turn. Once the budget is spent, we will return an in-band message that informs the model it has spent its tool call budget - and usually a model will change course afterwards.
330 331 332 333 334 335 336 |
# File 'lib/llm/agent.rb', line 330 def self.tool_budget(budget = UNDEFINED, &block) if budget.equal?(UNDEFINED) @tool_budget else @tool_budget = budget || block end end |
.tools(*tools, &block) ⇒ Array<LLM::Function>
Set or get the default tools
166 167 168 169 170 171 172 173 |
# File 'lib/llm/agent.rb', line 166 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.
247 248 249 250 |
# File 'lib/llm/agent.rb', line 247 def self.tracer(tracer = nil, &block) return @tracer if tracer.nil? && !block @tracer = block || tracer end |
Instance Method Details
#ask(prompt, params = {}) ⇒ Object
421 422 423 424 425 |
# File 'lib/llm/agent.rb', line 421 def ask(prompt, params = {}) res = run_loop(prompt, params, :ask) path ? @ctx.save(path:) : nil res end |
#compacted? ⇒ Boolean
563 564 565 |
# File 'lib/llm/agent.rb', line 563 def compacted? @ctx.compacted? end |
#concurrency ⇒ Symbol, ...
Returns the configured tool execution concurrency.
542 543 544 |
# File 'lib/llm/agent.rb', line 542 def concurrency @concurrency end |
#context_window ⇒ Integer
556 557 558 |
# File 'lib/llm/agent.rb', line 556 def context_window @ctx.context_window end |
#description ⇒ String?
Returns the agent's description
392 393 394 |
# File 'lib/llm/agent.rb', line 392 def description @description end |
#deserialize(**kw) ⇒ LLM::Agent Also known as: restore
646 647 648 649 |
# File 'lib/llm/agent.rb', line 646 def deserialize(**kw) @ctx.deserialize(**kw) self end |
#image_url(url) ⇒ LLM::Object
Returns a tagged object
481 482 483 |
# File 'lib/llm/agent.rb', line 481 def image_url(url) @ctx.image_url(url) end |
#inspect ⇒ String
630 631 632 633 |
# File 'lib/llm/agent.rb', line 630 def inspect "#<#{LLM::Utils.object_id(self)} " \ "@llm=#{@llm.class}, @mode=#{mode.inspect}, @messages=#{.inspect}>" end |
#interrupt! ⇒ nil Also known as: cancel!
Interrupt the active request, if any.
462 463 464 |
# File 'lib/llm/agent.rb', line 462 def interrupt! @ctx.interrupt! end |
#local_file(path) ⇒ LLM::Object
Returns a tagged object
490 491 492 |
# File 'lib/llm/agent.rb', line 490 def local_file(path) @ctx.local_file(path) end |
#mode ⇒ Symbol
535 536 537 |
# File 'lib/llm/agent.rb', line 535 def mode @ctx.mode end |
#model ⇒ String
Returns the model an Agent is actively using
529 530 531 |
# File 'lib/llm/agent.rb', line 529 def model @ctx.model end |
#name ⇒ String
Returns the agent's name
377 378 379 |
# File 'lib/llm/agent.rb', line 377 def name @name end |
#on_tool_confirmation(fn, strategy) ⇒ LLM::Function::Return
This method is called when confirmation is required before a tool can run.
663 664 665 |
# File 'lib/llm/agent.rb', line 663 def on_tool_confirmation(fn, strategy) fn.cancel end |
#params ⇒ Hash
611 612 613 |
# File 'lib/llm/agent.rb', line 611 def params @ctx.params end |
#path ⇒ String?
Returns a file path where an agent's memory is restored from, and written to after each turn.
385 386 387 |
# File 'lib/llm/agent.rb', line 385 def path @path end |
#pending_functions ⇒ Array<LLM::Function>
435 436 437 |
# File 'lib/llm/agent.rb', line 435 def pending_functions @tracer ? @llm.with_tracer(@tracer) { @ctx.pending_functions } : @ctx.pending_functions end |
#prompt(&b) ⇒ LLM::Prompt Also known as: build_prompt
471 472 473 |
# File 'lib/llm/agent.rb', line 471 def prompt(&b) @ctx.prompt(&b) end |
#remote_file(res) ⇒ LLM::Object
Returns a tagged object
499 500 501 |
# File 'lib/llm/agent.rb', line 499 def remote_file(res) @ctx.remote_file(res) end |
#repl(name: self.name, path: nil, tools: [], skills: [], tracer: false, trace: nil) ⇒ void
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.
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 |
# File 'lib/llm/agent.rb', line 591 def repl(name: self.name, 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, name:, path:, tools:, skills:).start ensure if !tracer self.tracer = previous end end |
#returns ⇒ Array<LLM::Function::Return>
442 443 444 |
# File 'lib/llm/agent.rb', line 442 def returns @ctx.returns end |
#serialize(**kw) ⇒ void Also known as: save
This method returns an undefined value.
638 639 640 |
# File 'lib/llm/agent.rb', line 638 def serialize(**kw) @ctx.serialize(**kw) end |
#stream ⇒ LLM::Stream, ...
Returns a stream object, or nil
522 523 524 |
# File 'lib/llm/agent.rb', line 522 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.
413 414 415 416 417 |
# File 'lib/llm/agent.rb', line 413 def talk(prompt, params = {}) res = run_loop(prompt, params, :talk) path ? @ctx.save(path:) : nil res end |
#to_h ⇒ Hash
618 619 620 |
# File 'lib/llm/agent.rb', line 618 def to_h @ctx.to_h end |
#to_json ⇒ String
624 625 626 |
# File 'lib/llm/agent.rb', line 624 def to_json(...) LLM.json.dump(to_h, ...) end |
#tracer ⇒ LLM::Tracer
Returns an LLM tracer
506 507 508 |
# File 'lib/llm/agent.rb', line 506 def tracer @tracer || @ctx.tracer end |
#tracer=(other) ⇒ void
This method returns an undefined value.
514 515 516 517 |
# File 'lib/llm/agent.rb', line 514 def tracer=(other) @ctx.tracer = other @tracer = other end |
#wait ⇒ Array<LLM::Function::Return>
449 450 451 |
# File 'lib/llm/agent.rb', line 449 def wait(...) @tracer ? @llm.with_tracer(@tracer) { @ctx.wait(...) } : @ctx.wait(...) end |