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
-
#compacted ⇒ Boolean
(also: #compacted?)
private
Returns whether the context has been compacted and no later model response has cleared that state.
-
#llm ⇒ LLM::Provider
readonly
Returns a provider.
-
#messages ⇒ LLM::Buffer<LLM::Message>
readonly
Returns the accumulated message history for this context.
-
#mode ⇒ Symbol
readonly
Returns the context mode.
Instance Method Summary collapse
-
#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.
-
#context_window ⇒ Integer
Returns the model’s context window.
-
#cost ⇒ LLM::Cost
Returns an approximate cost for a given context based on both the provider, and model.
-
#functions ⇒ Array<LLM::Function>
Returns an array of functions that can be called.
-
#functions? ⇒ Boolean
Returns whether there is pending tool work in this context.
-
#guard ⇒ #call?
Returns a guard, if configured.
-
#guard=(guard) ⇒ #call, ...
Sets a guard or guard config.
-
#image_url(url) ⇒ LLM::Object
Recongize an object as a URL to an image.
-
#initialize(llm, params = {}) ⇒ Context
constructor
A new instance of Context.
- #inspect ⇒ String
-
#interrupt! ⇒ nil
(also: #cancel!)
Interrupt the active request, if any.
-
#local_file(path) ⇒ LLM::Object
Recongize an object as a local file.
-
#model ⇒ String
Returns the model a Context is actively using.
-
#params ⇒ Hash
Returns the default params for this context.
-
#prompt(&b) ⇒ LLM::Prompt
(also: #build_prompt)
Build a role-aware prompt for a single request.
-
#remote_file(res) ⇒ LLM::Object
Reconginize an object as a remote file.
-
#respond(prompt, params = {}) ⇒ LLM::Response
Interact with the context via the responses API.
-
#returns ⇒ Array<LLM::Function::Return>
Returns tool returns accumulated in this context.
-
#serialize(path:) ⇒ void
(also: #save)
Save the current context state.
-
#spawn(function, strategy) ⇒ LLM::Function::Return, LLM::Function::Task
Spawns a function through the context.
-
#stream ⇒ LLM::Stream, ...
Returns a stream object, or nil.
-
#talk(prompt, params = {}) ⇒ LLM::Response
(also: #chat)
Interact with the context via the chat completions API.
- #to_h ⇒ Hash
- #to_json ⇒ String
-
#tracer ⇒ LLM::Tracer
Returns an LLM tracer.
-
#transformer ⇒ #call?
Returns a transformer, if configured.
-
#transformer=(transformer) ⇒ #call?
Sets a transformer.
-
#usage ⇒ LLM::Object
Returns token usage accumulated in this context.
-
#wait(strategy) ⇒ Array<LLM::Function::Return>
Waits for queued tool work to finish.
Methods included from Deserializer
#deserialize, #deserialize_message
Constructor Details
#initialize(llm, params = {}) ⇒ Context
Returns a new instance of Context.
89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/llm/context.rb', line 89 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
#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.
124 125 126 |
# File 'lib/llm/context.rb', line 124 def compacted @compacted end |
#llm ⇒ LLM::Provider (readonly)
Returns a provider
64 65 66 |
# File 'lib/llm/context.rb', line 64 def llm @llm end |
#messages ⇒ LLM::Buffer<LLM::Message> (readonly)
Returns the accumulated message history for this context
59 60 61 |
# File 'lib/llm/context.rb', line 59 def @messages end |
#mode ⇒ Symbol (readonly)
Returns the context mode
69 70 71 |
# File 'lib/llm/context.rb', line 69 def mode @mode end |
Instance Method Details
#compactor ⇒ LLM::Compactor
Returns a context compactor This feature is inspired by the compaction approach developed by General Intelligence Systems.
106 107 108 109 |
# File 'lib/llm/context.rb', line 106 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
115 116 117 |
# File 'lib/llm/context.rb', line 115 def compactor=(compactor) @compactor = compactor end |
#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.
485 486 487 488 489 490 491 492 |
# File 'lib/llm/context.rb', line 485 def context_window LLM .registry_for(llm) .limit(model:) .context rescue LLM::NoSuchModelError, LLM::NoSuchRegistryError 0 end |
#cost ⇒ LLM::Cost
Returns an approximate cost for a given context based on both the provider, and model
473 474 475 |
# File 'lib/llm/context.rb', line 473 def cost LLM::Cost.from(self) end |
#functions ⇒ Array<LLM::Function>
Returns an array of functions that can be called
251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/llm/context.rb', line 251 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 |
#functions? ⇒ Boolean
Returns whether there is pending tool work in this context. This prefers queued streamed tool work when present, and otherwise falls back to unresolved functions derived from the message history.
269 270 271 272 |
# File 'lib/llm/context.rb', line 269 def functions? pending = queue (pending && !pending.empty?) || functions.any? 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.
139 140 141 142 143 144 |
# File 'lib/llm/context.rb', line 139 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.
155 156 157 |
# File 'lib/llm/context.rb', line 155 def guard=(guard) @guard = guard end |
#image_url(url) ⇒ LLM::Object
Recongize an object as a URL to an image
392 393 394 |
# File 'lib/llm/context.rb', line 392 def image_url(url) LLM::Object.from(value: url, kind: :image_url) end |
#inspect ⇒ String
242 243 244 245 246 |
# File 'lib/llm/context.rb', line 242 def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ "@llm=#{@llm.class}, @mode=#{@mode.inspect}, @params=#{@params.inspect}, " \ "@messages=#{@messages.inspect}>" end |
#interrupt! ⇒ nil Also known as: cancel!
Interrupt the active request, if any. This is inspired by Go’s context cancellation model.
333 334 335 336 337 338 339 340 341 342 |
# File 'lib/llm/context.rb', line 333 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 |
#local_file(path) ⇒ LLM::Object
Recongize an object as a local file
402 403 404 |
# File 'lib/llm/context.rb', line 402 def local_file(path) LLM::Object.from(value: LLM.File(path), kind: :local_file) end |
#model ⇒ String
Returns the model a Context is actively using
433 434 435 |
# File 'lib/llm/context.rb', line 433 def model .find(&:assistant?)&.model || @params[:model] end |
#params ⇒ Hash
Returns the default params for this context
74 75 76 |
# File 'lib/llm/context.rb', line 74 def params @params.dup 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.
381 382 383 |
# File 'lib/llm/context.rb', line 381 def prompt(&b) LLM::Prompt.new(@llm, &b) end |
#remote_file(res) ⇒ LLM::Object
Reconginize an object as a remote file
412 413 414 |
# File 'lib/llm/context.rb', line 412 def remote_file(res) LLM::Object.from(value: res, kind: :remote_file) 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.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/llm/context.rb', line 224 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 |
#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 |
#serialize(path:) ⇒ void Also known as: save
This method returns an undefined value.
Save the current context state
464 465 466 |
# File 'lib/llm/context.rb', line 464 def serialize(path:) ::File.binwrite path, LLM.json.dump(to_h) 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 |
#stream ⇒ LLM::Stream, ...
Returns a stream object, or nil
426 427 428 |
# File 'lib/llm/context.rb', line 426 def stream @stream || @params[:stream] 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.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/llm/context.rb', line 193 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 |
#to_h ⇒ Hash
439 440 441 442 443 444 445 446 |
# File 'lib/llm/context.rb', line 439 def to_h { schema_version: 1, model:, compacted:, messages: @messages.map { (_1) } } end |
#to_json ⇒ String
450 451 452 |
# File 'lib/llm/context.rb', line 450 def to_json(...) to_h.to_json(...) end |
#tracer ⇒ LLM::Tracer
Returns an LLM tracer
419 420 421 |
# File 'lib/llm/context.rb', line 419 def tracer @llm.tracer end |
#transformer ⇒ #call?
Returns a transformer, if configured.
Transformers can rewrite outgoing prompts and params before a request is sent to the provider.
166 167 168 |
# File 'lib/llm/context.rb', line 166 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]`.
178 179 180 |
# File 'lib/llm/context.rb', line 178 def transformer=(transformer) @transformer = transformer end |
#usage ⇒ LLM::Object
Returns token usage accumulated in this context
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/llm/context.rb', line 348 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, input_audio_tokens: usage.input_audio_tokens || 0, output_audio_tokens: usage.output_audio_tokens || 0, input_image_tokens: usage.input_image_tokens || 0, cache_read_tokens: usage.cache_read_tokens || 0, cache_write_tokens: usage.cache_write_tokens || 0, total_tokens: usage.total_tokens || 0 ) else ZERO_USAGE 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.
315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/llm/context.rb', line 315 def wait(strategy) if LLM::Stream === stream && !stream.queue.empty? @queue = stream.queue @queue.wait else return guarded_returns if guarded_returns @queue = functions.spawn(strategy) @queue.wait end ensure @queue = nil @stream = nil end |