Class: RubyLLM::Agents::Pipeline::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_llm/agents/pipeline/context.rb

Overview

Carries request/response data through the middleware pipeline.

All data flows explicitly through this object - no hidden instance variables or implicit state. This makes the data flow visible and testable.

Examples:

Creating a context

context = Context.new(
  input: "Hello world",
  agent_class: MyEmbedder,
  model: "text-embedding-3-small"
)

Accessing data set by middleware

context.tenant_id          # Set by Tenant middleware
context.cached?            # Set by Cache middleware
context.duration_ms        # Computed from timestamps

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input:, agent_class:, agent_instance: nil, model: nil, tenant: nil, skip_cache: false, stream_block: nil, stream_events: false, parent_execution_id: nil, root_execution_id: nil, **options) ⇒ Context

Creates a new pipeline context

Parameters:

  • input (Object)

    The input data for the agent

  • agent_class (Class)

    The agent class being executed

  • agent_instance (Object, nil) (defaults to: nil)

    The agent instance

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

    Override model (defaults to agent_class.model)

  • tenant (Hash, Object, nil) (defaults to: nil)

    Raw tenant (resolved by Tenant middleware)

  • skip_cache (Boolean) (defaults to: false)

    Whether to skip caching

  • stream_block (Proc, nil) (defaults to: nil)

    Block for streaming

  • options (Hash)

    Additional options passed to the agent



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 83

def initialize(input:, agent_class:, agent_instance: nil, model: nil, tenant: nil, skip_cache: false, stream_block: nil, stream_events: false, parent_execution_id: nil, root_execution_id: nil, **options)
  @input = input
  @agent_class = agent_class
  @agent_instance = agent_instance
  @agent_type = extract_agent_type(agent_class)
  @model = model || extract_model(agent_class)

  # Execution hierarchy
  @parent_execution_id = parent_execution_id
  @root_execution_id = root_execution_id

  # Store tenant in options for middleware to resolve
  @options = options.merge(tenant: tenant).compact

  # Tenant fields (set by Tenant middleware)
  @tenant_id = nil
  @tenant_object = nil
  @tenant_config = nil

  # Execution options
  @skip_cache = skip_cache
  @stream_block = stream_block
  @stream_events = stream_events

  # Debug trace
  @trace = []
  @trace_enabled = options[:debug] == true

  # Initialize tracking fields
  @attempt = 0
  @attempts_made = 0
  @cached = false
  @metadata = {}

  # Initialize cost fields
  @input_tokens = 0
  @output_tokens = 0
  @input_cost = 0.0
  @output_cost = 0.0
  @total_cost = 0.0
end

Instance Attribute Details

#agent_classObject (readonly)

Agent metadata



71
72
73
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 71

def agent_class
  @agent_class
end

#agent_instanceObject

Agent reference (for execution)



29
30
31
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 29

def agent_instance
  @agent_instance
end

#agent_typeObject (readonly)

Agent metadata



71
72
73
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 71

def agent_type
  @agent_type
end

#attemptObject

Execution tracking (set by Instrumentation middleware)



35
36
37
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 35

def attempt
  @attempt
end

#attempts_madeObject

Execution tracking (set by Instrumentation middleware)



35
36
37
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 35

def attempts_made
  @attempts_made
end

#cache_creation_tokensObject

Prompt-cache usage. First-class accessors, not metadata keys: these feed both the persisted execution record and the input-cost split, so a generic bag entry would be silently droppable (it was).



49
50
51
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 49

def cache_creation_tokens
  @cache_creation_tokens
end

#cachedObject

Result data (set by core execute method)



41
42
43
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 41

def cached
  @cached
end

#cached_tokensObject

Prompt-cache usage. First-class accessors, not metadata keys: these feed both the persisted execution record and the input-cost split, so a generic bag entry would be silently droppable (it was).



49
50
51
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 49

def cached_tokens
  @cached_tokens
end

#completed_atObject

Execution tracking (set by Instrumentation middleware)



35
36
37
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 35

def completed_at
  @completed_at
end

#errorObject

Result data (set by core execute method)



41
42
43
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 41

def error
  @error
end

#execution_idObject

Execution tracking (set by Instrumentation middleware)



35
36
37
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 35

def execution_id
  @execution_id
end

#finish_reasonObject

Response metadata



62
63
64
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 62

def finish_reason
  @finish_reason
end

#inputObject

Request data



26
27
28
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 26

def input
  @input
end

#input_costObject

Cost tracking



44
45
46
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 44

def input_cost
  @input_cost
end

#input_tokensObject

Cost tracking



44
45
46
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 44

def input_tokens
  @input_tokens
end

#modelObject

Request data



26
27
28
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 26

def model
  @model
end

#model_usedObject

Response metadata



62
63
64
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 62

def model_used
  @model_used
end

#optionsObject

Request data



26
27
28
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 26

def options
  @options
end

#outputObject

Result data (set by core execute method)



41
42
43
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 41

def output
  @output
end

#output_costObject

Cost tracking



44
45
46
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 44

def output_cost
  @output_cost
end

#output_tokensObject

Cost tracking



44
45
46
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 44

def output_tokens
  @output_tokens
end

#parent_execution_idObject

Execution hierarchy (agent-as-tool)



38
39
40
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 38

def parent_execution_id
  @parent_execution_id
end

#root_execution_idObject

Execution hierarchy (agent-as-tool)



38
39
40
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 38

def root_execution_id
  @root_execution_id
end

#skip_cacheObject

Streaming support



68
69
70
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 68

def skip_cache
  @skip_cache
end

#started_atObject

Execution tracking (set by Instrumentation middleware)



35
36
37
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 35

def started_at
  @started_at
end

#stream_blockObject

Streaming support



68
69
70
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 68

def stream_block
  @stream_block
end

#stream_eventsObject

Streaming support



68
69
70
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 68

def stream_events
  @stream_events
end

#tenant_api_keysObject

Per-tenant provider API keys, set by the Tenant middleware.

A first-class accessor for the opposite reason to the above: the metadata bag is copied wholesale onto the persisted execution record and rendered by the dashboard, so a credential stored there leaked into the database in plaintext and onto the execution page (it did). Keeping it off the bag makes that leak structurally impossible rather than dependent on a redaction list.



59
60
61
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 59

def tenant_api_keys
  @tenant_api_keys
end

#tenant_configObject

Tenant data (set by Tenant middleware, or passed in)



32
33
34
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 32

def tenant_config
  @tenant_config
end

#tenant_idObject

Tenant data (set by Tenant middleware, or passed in)



32
33
34
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 32

def tenant_id
  @tenant_id
end

#tenant_objectObject

Tenant data (set by Tenant middleware, or passed in)



32
33
34
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 32

def tenant_object
  @tenant_object
end

#time_to_first_token_msObject

Response metadata



62
63
64
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 62

def time_to_first_token_ms
  @time_to_first_token_ms
end

#total_costObject

Cost tracking



44
45
46
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 44

def total_cost
  @total_cost
end

#traceObject

Debug trace (set when debug: true is passed)



65
66
67
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 65

def trace
  @trace
end

Instance Method Details

#[](key) ⇒ Object

Custom metadata storage - read

Parameters:

  • key (Symbol, String)

    The metadata key

Returns:

  • (Object)

    The stored value



208
209
210
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 208

def [](key)
  @metadata[key]
end

#[]=(key, value) ⇒ Object

Custom metadata storage - write

Parameters:

  • key (Symbol, String)

    The metadata key

  • value (Object)

    The value to store



216
217
218
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 216

def []=(key, value)
  @metadata[key] = value
end

#add_trace(middleware_name, started_at:, duration_ms:, action: nil) ⇒ Object

Adds a trace entry for a middleware execution

Parameters:

  • middleware_name (String)

    Name of the middleware

  • started_at (Time)

    When the middleware started

  • duration_ms (Float)

    How long the middleware took in ms

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

    Optional action description (e.g., "cache hit")



147
148
149
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 147

def add_trace(middleware_name, started_at:, duration_ms:, action: nil)
  @trace << {middleware: middleware_name, started_at: started_at, duration_ms: duration_ms, action: action}.compact
end

#cached?Boolean

Was the result served from cache?

Returns:

  • (Boolean)


161
162
163
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 161

def cached?
  @cached == true
end

#dup_for_retryContext

Creates a duplicate context for retry attempts

Returns:

  • (Context)

    A new context with the same input but reset state



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 257

def dup_for_retry
  # Extract tenant from options since dup_for_retry is called after middleware
  # has already resolved it - we want to preserve the resolved state
  opts_without_tenant = @options.except(:tenant)

  new_ctx = self.class.new(
    input: @input,
    agent_class: @agent_class,
    agent_instance: @agent_instance,
    model: @model,
    skip_cache: @skip_cache,
    stream_block: @stream_block,
    stream_events: @stream_events,
    **opts_without_tenant
  )
  # Preserve resolved tenant state
  new_ctx.tenant_id = @tenant_id
  new_ctx.tenant_object = @tenant_object
  new_ctx.tenant_config = @tenant_config
  new_ctx.started_at = @started_at
  new_ctx.attempts_made = @attempts_made
  # Preserve execution hierarchy
  new_ctx.parent_execution_id = @parent_execution_id
  new_ctx.root_execution_id = @root_execution_id
  # Preserve trace across retries
  new_ctx.trace = @trace
  new_ctx
end

#duration_msInteger?

Duration in milliseconds

Returns:

  • (Integer, nil)

    Duration in ms, or nil if not yet completed



128
129
130
131
132
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 128

def duration_ms
  return nil unless @started_at && @completed_at

  ((@completed_at - @started_at) * 1000).to_i
end

#failed?Boolean

Did execution fail?

Returns:

  • (Boolean)


175
176
177
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 175

def failed?
  !@error.nil?
end

#llmRubyLLM::Context, RubyLLM

Returns a RubyLLM interface scoped to tenant API keys when present.

When tenant API keys are stored on this context (by the Tenant middleware), returns a RubyLLM::Context with a cloned config that has tenant-specific keys applied. This avoids mutating global RubyLLM configuration, making multi-tenant LLM calls thread-safe.

When no tenant API keys are present, returns the RubyLLM module directly (which uses the global configuration).

Returns:

  • (RubyLLM::Context, RubyLLM)

    Scoped context or global module



197
198
199
200
201
202
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 197

def llm
  api_keys = @tenant_api_keys
  return RubyLLM if api_keys.nil? || api_keys.empty?

  @llm_context ||= build_llm_context(api_keys)
end

#metadataHash

Returns all custom metadata

Returns:

  • (Hash)

    The metadata hash



223
224
225
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 223

def 
  @metadata.dup
end

#stream_events?Boolean

Are stream events enabled?

Returns:

  • (Boolean)


154
155
156
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 154

def stream_events?
  @stream_events == true
end

#success?Boolean

Did execution succeed?

Returns:

  • (Boolean)


168
169
170
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 168

def success?
  @error.nil? && !@output.nil?
end

#to_hHash

Convert to hash for logging/recording

Returns:

  • (Hash)

    Hash representation of the context



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 230

def to_h
  {
    agent_class: @agent_class&.name,
    agent_type: @agent_type,
    model: @model,
    model_used: @model_used,
    tenant_id: @tenant_id,
    duration_ms: duration_ms,
    cached: cached?,
    success: success?,
    input_tokens: @input_tokens,
    output_tokens: @output_tokens,
    total_tokens: total_tokens,
    input_cost: @input_cost,
    output_cost: @output_cost,
    total_cost: @total_cost,
    finish_reason: @finish_reason,
    time_to_first_token_ms: @time_to_first_token_ms,
    attempts_made: @attempts_made,
    error_class: @error&.class&.name,
    error_message: @error&.message
  }.compact
end

#total_tokensInteger

Total tokens used (input + output)

Returns:

  • (Integer)


182
183
184
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 182

def total_tokens
  (@input_tokens || 0) + (@output_tokens || 0)
end

#trace_enabled?Boolean

Is debug tracing enabled?

Returns:

  • (Boolean)


137
138
139
# File 'lib/ruby_llm/agents/pipeline/context.rb', line 137

def trace_enabled?
  @trace_enabled
end