Class: LLM::Context

Inherits:
Object
  • Object
show all
Includes:
Deserializer
Defined in:
lib/llm/context.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.

Examples:

#!/usr/bin/env ruby
require "llm"

llm = LLM.openai(key: ENV["KEY"])
ctx = LLM::Context.new(llm)

prompt = LLM::Prompt.new(llm) do
  system "Be concise and show your reasoning briefly."
  user "If a train goes 60 mph for 1.5 hours, how far does it travel?"
  user "Now double the speed for the same time."
end

ctx.talk(prompt)
ctx.messages.each { |m| puts "[#{m.role}] #{m.content}" }

Defined Under Namespace

Modules: Deserializer

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Deserializer

#deserialize_message

Constructor Details

#initialize(llm, params = {}) ⇒ Context

Returns a new instance of Context.

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

  • :mode (Symbol)

    Defaults to :completions

  • :model (String)

    Defaults to the provider’s default model

  • :tools (Array<LLM::Function>, nil)

    Defaults to nil



65
66
67
68
69
70
71
# File 'lib/llm/context.rb', line 65

def initialize(llm, params = {})
  @llm = llm
  @mode = params.delete(:mode) || :completions
  @params = {model: llm.default_model, schema: nil}.compact.merge!(params)
  @messages = LLM::Buffer.new(llm)
  @owner = Fiber.current
end

Instance Attribute Details

#llmLLM::Provider (readonly)

Returns a provider

Returns:



48
49
50
# File 'lib/llm/context.rb', line 48

def llm
  @llm
end

#messagesLLM::Buffer<LLM::Message> (readonly)

Returns the accumulated message history for this context



43
44
45
# File 'lib/llm/context.rb', line 43

def messages
  @messages
end

#modeSymbol (readonly)

Returns the context mode

Returns:

  • (Symbol)


53
54
55
# File 'lib/llm/context.rb', line 53

def mode
  @mode
end

Instance Method Details

#call(target) ⇒ Array<LLM::Function::Return>

Calls a named collection of work through the context.

This currently supports ‘:functions`, forwarding to `functions.call`.

Parameters:

  • target (Symbol)

    The work collection to call

Returns:



154
155
156
157
158
159
# File 'lib/llm/context.rb', line 154

def call(target)
  case target
  when :functions then functions.call
  else raise ArgumentError, "Unknown target: #{target.inspect}. Expected :functions"
  end
end

#context_windowInteger

Note:

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.

Returns:

  • (Integer)


351
352
353
354
355
356
357
358
# File 'lib/llm/context.rb', line 351

def context_window
  LLM
    .registry_for(llm)
    .limit(model:)
    .context
rescue LLM::NoSuchModelError, LLM::NoSuchRegistryError
  0
end

#costLLM::Cost

Returns an approximate cost for a given context based on both the provider, and model

Returns:

  • (LLM::Cost)

    Returns an approximate cost for a given context based on both the provider, and model



334
335
336
337
338
339
340
341
# File 'lib/llm/context.rb', line 334

def cost
  return LLM::Cost.new(0, 0) unless usage
  cost = LLM.registry_for(llm).cost(model:)
  LLM::Cost.new(
    (cost.input.to_f / 1_000_000.0)  * usage.input_tokens,
    (cost.output.to_f / 1_000_000.0) * usage.output_tokens
  )
end

#deserialize(path: nil, string: nil, data: nil) ⇒ LLM::Context Also known as: restore

Restore a saved context state

Parameters:

  • path (String, nil) (defaults to: nil)

    The path to a JSON file

  • string (String, nil) (defaults to: nil)

    A raw JSON string

  • data (Hash, nil) (defaults to: nil)

    A parsed context payload

Returns:

Raises:

  • (SystemCallError)

    Might raise a number of SystemCallError subclasses



315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/llm/context.rb', line 315

def deserialize(path: nil, string: nil, data: nil)
  ctx = if data
    data
  elsif path.nil? and string.nil?
    raise ArgumentError, "a path, string, or data payload is required"
  elsif path
    LLM.json.load(::File.binread(path))
  else
    LLM.json.load(string)
  end
  @messages.concat [*ctx["messages"]].map { deserialize_message(_1) }
  self
end

#functionsArray<LLM::Function>

Returns an array of functions that can be called

Returns:



133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/llm/context.rb', line 133

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

#image_url(url) ⇒ LLM::Object

Recongize an object as a URL to an image

Parameters:

  • url (String)

    The URL

Returns:



239
240
241
# File 'lib/llm/context.rb', line 239

def image_url(url)
  LLM::Object.from(value: url, kind: :image_url)
end

#inspectString

Returns:

  • (String)


124
125
126
127
128
# File 'lib/llm/context.rb', line 124

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.

Returns:

  • (nil)


197
198
199
# File 'lib/llm/context.rb', line 197

def interrupt!
  llm.interrupt!(@owner)
end

#local_file(path) ⇒ LLM::Object

Recongize an object as a local file

Parameters:

  • path (String)

    The path

Returns:



249
250
251
# File 'lib/llm/context.rb', line 249

def local_file(path)
  LLM::Object.from(value: LLM.File(path), kind: :local_file)
end

#modelString

Returns the model a Context is actively using

Returns:

  • (String)


273
274
275
# File 'lib/llm/context.rb', line 273

def model
  messages.find(&:assistant?)&.model || @params[:model]
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.

Examples:

prompt = ctx.prompt do
  system "Your task is to assist the user"
  user "Hello, can you assist me?"
end
ctx.talk(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:



228
229
230
# File 'lib/llm/context.rb', line 228

def prompt(&b)
  LLM::Prompt.new(@llm, &b)
end

#remote_file(res) ⇒ LLM::Object

Reconginize an object as a remote file

Parameters:

Returns:



259
260
261
# File 'lib/llm/context.rb', line 259

def remote_file(res)
  LLM::Object.from(value: res, kind: :remote_file)
end

#respond(prompt, params = {}) ⇒ LLM::Response

Note:

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.

Examples:

llm = LLM.openai(key: ENV["KEY"])
ctx = LLM::Context.new(llm)
res = ctx.respond("What is the capital of France?")
puts res.output_text

Parameters:

  • params (defaults to: {})

    The params, including optional :role (defaults to :user), :stream, :tools, :schema etc.

  • prompt (String)

    The input prompt to be completed

Returns:

  • (LLM::Response)

    Returns the LLM’s response for this turn.



111
112
113
114
115
116
117
118
119
120
# File 'lib/llm/context.rb', line 111

def respond(prompt, params = {})
  params = @params.merge(params)
  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)
  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

#returnsArray<LLM::Function::Return>

Returns tool returns accumulated in this context

Returns:



164
165
166
167
168
169
170
171
172
# File 'lib/llm/context.rb', line 164

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

Examples:

llm = LLM.openai(key: ENV["KEY"])
ctx = LLM::Context.new(llm)
ctx.talk "Hello"
ctx.save(path: "context.json")

Raises:

  • (SystemCallError)

    Might raise a number of SystemCallError subclasses



299
300
301
# File 'lib/llm/context.rb', line 299

def serialize(path:)
  ::File.binwrite path, LLM.json.dump(self)
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.

Examples:

llm = LLM.openai(key: ENV["KEY"])
ctx = LLM::Context.new(llm)
res = ctx.talk("Hello, what is your name?")
puts res.messages[0].content

Parameters:

  • params (defaults to: {})

    The params, including optional :role (defaults to :user), :stream, :tools, :schema etc.

  • prompt (String)

    The input prompt to be completed

Returns:

  • (LLM::Response)

    Returns the LLM’s response for this turn.



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/llm/context.rb', line 85

def talk(prompt, params = {})
  return respond(prompt, params) if mode == :responses
  params = params.merge(messages: @messages.to_a)
  params = @params.merge(params)
  res = @llm.complete(prompt, params)
  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_hHash

Returns:

  • (Hash)


279
280
281
# File 'lib/llm/context.rb', line 279

def to_h
  {schema_version: 1, model:, messages:}
end

#to_jsonString

Returns:

  • (String)


285
286
287
# File 'lib/llm/context.rb', line 285

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

#tracerLLM::Tracer

Returns an LLM tracer

Returns:



266
267
268
# File 'lib/llm/context.rb', line 266

def tracer
  @llm.tracer
end

#usageLLM::Object?

Note:

Returns token usage accumulated in this context This method returns token usage for the latest assistant message, and it returns nil for non-assistant messages.

Returns:



209
210
211
# File 'lib/llm/context.rb', line 209

def usage
  @messages.find(&:assistant?)&.usage
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.

Parameters:

  • strategy (Symbol)

    The concurrency strategy to use

Returns:



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

def wait(strategy)
  stream = @params[:stream]
  if LLM::Stream === stream && !stream.queue.empty?
    stream.wait(strategy)
  else
    functions.wait(strategy)
  end
end