Class: LLM::Context
- Inherits:
-
Object
- Object
- LLM::Context
- Includes:
- Deserializer, Serializer
- Defined in:
- lib/llm/context.rb,
lib/llm/context/serializer.rb,
lib/llm/context/deserializer.rb
Overview
LLM::Context is the stateful execution boundary in llm.rb.
It holds the evolving runtime state for an LLM workflow: conversation history, tool calls and returns, schema and streaming configuration, accumulated usage, and request ownership for interruption.
This is broader than prompt context alone. A context is the object that lets one-off prompts, streaming turns, tool execution, persistence, retries, and serialized long-lived workflows all run through the same model.
A context can drive the chat completions API that all providers support or the Responses API on providers that expose it.
Defined Under Namespace
Modules: Deserializer, Serializer
Instance Attribute Summary collapse
-
#messages ⇒
LLM::Buffer<LLM::Message> readonly
Returns the accumulated message history for this context.
-
#llm ⇒
LLM::Provider readonly
Returns a provider.
-
#mode ⇒
Symbol readonly
Returns the context mode.
-
#compacted ⇒
Boolean (also: #compacted?) private
Returns whether the context has been compacted and no later model response has cleared that state.
Instance Method Summary collapse
-
#context_window
⇒ Integer
Returns the model's context window.
-
#initialize(llm,
params = {}) ⇒ Context constructor
A new instance of Context.
-
#compactor ⇒
LLM::Compactor
Returns a context compactor This feature is inspired by the compaction approach developed by General Intelligence Systems.
-
#compactor=(compactor)
⇒ LLM::Compactor, ...
Sets a context compactor or compactor config.
-
#guard ⇒
#call?
Returns a guard, if configured.
-
#guard=(guard) ⇒
#call, ...
Sets a guard or guard config.
-
#transformer ⇒
#call?
Returns a transformer, if configured.
-
#transformer=(transformer)
⇒ #call?
Sets a transformer.
-
#talk(prompt, params
= {}) ⇒ LLM::Response (also: #chat)
Interact with the context via the chat completions API.
-
#respond(prompt,
params = {}) ⇒ LLM::Response
Interact with the context via the responses API.
- #inspect ⇒ String
-
#functions ⇒
Array<LLM::Function>
Returns an array of functions that can be called.
-
#call(target) ⇒
Array<LLM::Function::Return>
Calls a named collection of work through the context.
-
#spawn(function,
strategy) ⇒ LLM::Function::Return, LLM::Function::Task
Spawns a function through the context.
-
#returns ⇒
Array<LLM::Function::Return>
Returns tool returns accumulated in this context.
-
#wait(strategy) ⇒
Array<LLM::Function::Return>
Waits for queued tool work to finish.
-
#interrupt! ⇒
nil (also: #cancel!)
Interrupt the active request, if any.
-
#usage ⇒
LLM::Object
Returns token usage accumulated in this context.
-
#prompt(&b) ⇒
LLM::Prompt (also: #build_prompt)
Build a role-aware prompt for a single request.
-
#image_url(url)
⇒ LLM::Object
Recongize an object as a URL to an image.
-
#local_file(path) ⇒
LLM::Object
Recongize an object as a local file.
-
#remote_file(res) ⇒
LLM::Object
Reconginize an object as a remote file.
-
#tracer ⇒
LLM::Tracer
Returns an LLM tracer.
-
#model ⇒
String
Returns the model a Context is actively using.
- #to_h ⇒ Hash
- #to_json ⇒ String
-
#serialize(path:) ⇒
void (also: #save)
Save the current context state.
-
#cost ⇒
LLM::Cost
Returns an approximate cost for a given context based on both the provider, and model.
-
#params ⇒
Hash
Returns the default params for this context.
Methods included from Deserializer
#deserialize, #deserialize_message
Constructor Details
#initialize(llm, params = {}) ⇒ Context
Returns a new instance of Context.
84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/llm/context.rb', line 84 def initialize(llm, params = {}) @llm = llm @mode = params.delete(:mode) || :completions @compactor = params.delete(:compactor) @guard = params.delete(:guard) @transformer = params.delete(:transformer) tools = [*params.delete(:tools), *load_skills(params.delete(:skills))] @params = {model: llm.default_model, schema: nil}.compact.merge!(params) @params[:tools] = tools unless tools.empty? @messages = LLM::Buffer.new(llm) end |
Instance Attribute Details
#messages ⇒ LLM::Buffer<LLM::Message> (readonly)
Returns the accumulated message history for this context
54 55 56 |
# File 'lib/llm/context.rb', line 54 def @messages end |
#llm ⇒ LLM::Provider (readonly)
Returns a provider
59 60 61 |
# File 'lib/llm/context.rb', line 59 def llm @llm end |
#mode ⇒ Symbol (readonly)
Returns the context mode
64 65 66 |
# File 'lib/llm/context.rb', line 64 def mode @mode end |
#compacted ⇒ Boolean Also known as: compacted?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns whether the context has been compacted and no later model response has cleared that state.
119 120 121 |
# File 'lib/llm/context.rb', line 119 def compacted @compacted end |
Instance Method Details
#context_window ⇒ Integer
This method returns 0 when the provider or model can't be found within Registry.
Returns the model's context window. The context window is the maximum amount of input and output tokens a model can consider in a single request.
477 478 479 480 481 482 483 484 |
# File 'lib/llm/context.rb', line 477 def context_window LLM .registry_for(llm) .limit(model:) .context rescue LLM::NoSuchModelError, LLM::NoSuchRegistryError 0 end |
#compactor ⇒ LLM::Compactor
Returns a context compactor This feature is inspired by the compaction approach developed by General Intelligence Systems.
101 102 103 104 |
# File 'lib/llm/context.rb', line 101 def compactor @compactor = LLM::Compactor.new(self, @compactor || {}) unless LLM::Compactor === @compactor @compactor end |
#compactor=(compactor) ⇒ LLM::Compactor, ...
Sets a context compactor or compactor config
110 111 112 |
# File 'lib/llm/context.rb', line 110 def compactor=(compactor) @compactor = compactor end |
#guard ⇒ #call?
Returns a guard, if configured.
Guards are context-level supervisors for agentic execution. A guard can inspect the runtime state and decide whether pending tool work should be blocked before the context keeps looping.
The built-in implementation is LLM::LoopGuard, which detects repeated tool-call patterns and turns them into in-band LLM::GuardError tool returns.
134 135 136 137 138 139 |
# File 'lib/llm/context.rb', line 134 def guard return if @guard.nil? || @guard == false @guard = LLM::LoopGuard.new if @guard == true @guard = LLM::LoopGuard.new(@guard) if Hash === @guard @guard end |
#guard=(guard) ⇒ #call, ...
Sets a guard or guard config.
Guards must implement call(ctx) and return either
nil or a warning string. Returning a warning tells
the context to block pending tool work with guarded tool errors
instead of continuing the loop.
150 151 152 |
# File 'lib/llm/context.rb', line 150 def guard=(guard) @guard = guard end |
#transformer ⇒ #call?
Returns a transformer, if configured.
Transformers can rewrite outgoing prompts and params before a request is sent to the provider.
161 162 163 |
# File 'lib/llm/context.rb', line 161 def transformer @transformer end |
#transformer=(transformer) ⇒ #call?
Sets a transformer.
Transformers must implement call(ctx, prompt,
params) and return a two-element array of [prompt,
params].
173 174 175 |
# File 'lib/llm/context.rb', line 173 def transformer=(transformer) @transformer = transformer end |
#talk(prompt, params = {}) ⇒ LLM::Response Also known as: chat
Interact with the context via the chat completions API. This method immediately sends a request to the LLM and returns the response.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/llm/context.rb', line 188 def talk(prompt, params = {}) return respond(prompt, params) if mode == :responses @owner = @llm.request_owner compactor.compact!(prompt) if compactor.compact?(prompt) params = params.merge(messages: @messages.to_a) params = @params.merge(params) prompt, params = transform(prompt, params) bind!(params[:stream], params[:model], params[:tools]) res = @llm.complete(prompt, params) self.compacted = false role = params[:role] || @llm.user_role role = @llm.tool_role if params[:role].nil? && [*prompt].grep(LLM::Function::Return).any? @messages.concat LLM::Prompt === prompt ? prompt.to_a : [LLM::Message.new(role, prompt)] @messages.concat [res.choices[-1]] res end |
#respond(prompt, params = {}) ⇒ LLM::Response
Not all LLM providers support this API
Interact with the context via the responses API. This method immediately sends a request to the LLM and returns the response.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/llm/context.rb', line 219 def respond(prompt, params = {}) @owner = @llm.request_owner compactor.compact!(prompt) if compactor.compact?(prompt) params = @params.merge(params) prompt, params = transform(prompt, params) bind!(params[:stream], params[:model], params[:tools]) res_id = params[:store] == false ? nil : @messages.find(&:assistant?)&.response&.response_id params = params.merge(previous_response_id: res_id, input: @messages.to_a).compact res = @llm.responses.create(prompt, params) self.compacted = false role = params[:role] || @llm.user_role @messages.concat LLM::Prompt === prompt ? prompt.to_a : [LLM::Message.new(role, prompt)] @messages.concat [res.choices[-1]] res end |
#inspect ⇒ String
237 238 239 240 241 |
# File 'lib/llm/context.rb', line 237 def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ "@llm=#{@llm.class}, @mode=#{@mode.inspect}, @params=#{@params.inspect}, " \ "@messages=#{@messages.inspect}>" end |
#functions ⇒ Array<LLM::Function>
Returns an array of functions that can be called
246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/llm/context.rb', line 246 def functions return_ids = returns.map(&:id) @messages .select(&:assistant?) .flat_map do |msg| fns = msg.functions.select { _1.pending? && !return_ids.include?(_1.id) } fns.each do |fn| fn.tracer = tracer fn.model = msg.model end end.extend(LLM::Function::Array) end |
#call(target) ⇒ Array<LLM::Function::Return>
Calls a named collection of work through the context.
This currently supports :functions, forwarding to
functions.call.
267 268 269 270 271 272 |
# File 'lib/llm/context.rb', line 267 def call(target) case target when :functions then guarded_returns || functions.call else raise ArgumentError, "Unknown target: #{target.inspect}. Expected :functions" end end |
#spawn(function, strategy) ⇒ LLM::Function::Return, LLM::Function::Task
Spawns a function through the context.
When a guard is configured, this method can return an in-band guarded tool error instead of spawning work.
283 284 285 286 287 |
# File 'lib/llm/context.rb', line 283 def spawn(function, strategy) warning = guard&.call(self) return guarded_return_for(function, warning) if warning function.spawn(strategy) end |
#returns ⇒ Array<LLM::Function::Return>
Returns tool returns accumulated in this context
292 293 294 295 296 297 298 299 300 |
# File 'lib/llm/context.rb', line 292 def returns @messages .select(&:tool_return?) .flat_map do |msg| LLM::Function::Return === msg.content ? [msg.content] : [*msg.content].grep(LLM::Function::Return) end end |
#wait(strategy) ⇒ Array<LLM::Function::Return>
Waits for queued tool work to finish.
This prefers queued streamed tool work when the configured stream exposes a non-empty queue. Otherwise it falls back to waiting on the context's pending functions directly.
314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/llm/context.rb', line 314 def wait(strategy) if LLM::Stream === stream && !stream.queue.empty? @queue = stream.queue @queue.wait(strategy) else return guarded_returns if guarded_returns @queue = functions.spawn(strategy) @queue.wait end ensure @queue = nil @stream = nil end |
#interrupt! ⇒ nil Also known as: cancel!
Interrupt the active request, if any. This is inspired by Go's context cancellation model.
332 333 334 335 336 337 338 339 340 341 |
# File 'lib/llm/context.rb', line 332 def interrupt! pending = functions.to_a llm.interrupt!(@owner) queue&.interrupt! return if pending.empty? pending.each(&:interrupt!) returns = pending.map { _1.cancel(reason: "function call cancelled") } @messages << LLM::Message.new(@llm.tool_role, returns) nil end |
#usage ⇒ LLM::Object
Returns token usage accumulated in this context
347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/llm/context.rb', line 347 def usage if usage = @messages.find(&:assistant?)&.usage LLM::Object.from( input_tokens: usage.input_tokens || 0, output_tokens: usage.output_tokens || 0, reasoning_tokens: usage.reasoning_tokens || 0, total_tokens: usage.total_tokens || 0 ) else ZERO_USAGE end end |
#prompt(&b) ⇒ LLM::Prompt Also known as: build_prompt
Build a role-aware prompt for a single request.
Prefer this method over #build_prompt. The older method name is kept for backward compatibility.
375 376 377 |
# File 'lib/llm/context.rb', line 375 def prompt(&b) LLM::Prompt.new(@llm, &b) end |
#image_url(url) ⇒ LLM::Object
Recongize an object as a URL to an image
386 387 388 |
# File 'lib/llm/context.rb', line 386 def image_url(url) LLM::Object.from(value: url, kind: :image_url) end |
#local_file(path) ⇒ LLM::Object
Recongize an object as a local file
396 397 398 |
# File 'lib/llm/context.rb', line 396 def local_file(path) LLM::Object.from(value: LLM.File(path), kind: :local_file) end |
#remote_file(res) ⇒ LLM::Object
Reconginize an object as a remote file
406 407 408 |
# File 'lib/llm/context.rb', line 406 def remote_file(res) LLM::Object.from(value: res, kind: :remote_file) end |
#tracer ⇒ LLM::Tracer
Returns an LLM tracer
413 414 415 |
# File 'lib/llm/context.rb', line 413 def tracer @llm.tracer end |
#model ⇒ String
Returns the model a Context is actively using
420 421 422 |
# File 'lib/llm/context.rb', line 420 def model .find(&:assistant?)&.model || @params[:model] end |
#to_h ⇒ Hash
426 427 428 429 430 431 432 433 |
# File 'lib/llm/context.rb', line 426 def to_h { schema_version: 1, model:, compacted:, messages: @messages.map { (_1) } } end |
#to_json ⇒ String
437 438 439 |
# File 'lib/llm/context.rb', line 437 def to_json(...) to_h.to_json(...) end |
#serialize(path:) ⇒ void Also known as: save
This method returns an undefined value.
Save the current context state
451 452 453 |
# File 'lib/llm/context.rb', line 451 def serialize(path:) ::File.binwrite path, LLM.json.dump(to_h) end |
#cost ⇒ LLM::Cost
Returns an approximate cost for a given context based on both the provider, and model
460 461 462 463 464 465 466 467 |
# File 'lib/llm/context.rb', line 460 def cost cost = LLM.registry_for(llm).cost(model:) input_cost = (cost.input.to_f / 1_000_000.0) * usage.input_tokens output_cost = (cost.output.to_f / 1_000_000.0) * usage.output_tokens LLM::Cost.new(input_cost, output_cost) rescue LLM::NoSuchModelError, LLM::NoSuchRegistryError LLM::Cost.new(0, 0) end |
#params ⇒ Hash
Returns the default params for this context
69 70 71 |
# File 'lib/llm/context.rb', line 69 def params @params.dup end |