Class: SignalWire::Contexts::Step

Inherits:
Object
  • Object
show all
Defined in:
lib/signalwire/contexts/context_builder.rb

Overview

Represents a single step within a context.

All mutator methods return self for fluent chaining.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ Step

Returns a new instance of Step.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/signalwire/contexts/context_builder.rb', line 92

def initialize(name)
  @name = name
  @text = nil
  @step_criteria   = nil
  @functions       = nil  # nil | "none" | Array<String>
  @valid_steps     = nil
  @valid_contexts  = nil
  @sections        = []
  @gather_info     = nil

  # Behavior flags
  @end              = false
  @skip_user_turn   = false
  @skip_to_next_step = false

  # Reset object for context-switching from steps
  @reset_system_prompt = nil
  @reset_user_prompt   = nil
  @reset_consolidate   = false
  @reset_full_reset    = false
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



90
91
92
# File 'lib/signalwire/contexts/context_builder.rb', line 90

def name
  @name
end

Instance Method Details

#add_bullets(title, bullets) ⇒ Object

Add a POM section with bullet points. Mutually exclusive with set_text.

Raises:

  • (ArgumentError)


131
132
133
134
135
136
# File 'lib/signalwire/contexts/context_builder.rb', line 131

def add_bullets(title, bullets)
  raise ArgumentError, "Cannot add POM sections when set_text has been used" unless @text.nil?

  @sections << { "title" => title, "bullets" => bullets }
  self
end

#add_gather_question(key:, question:, type: 'string', confirm: false, prompt: nil, functions: nil) ⇒ Object

Add a question to this step’s gather_info configuration. set_gather_info must be called first.

IMPORTANT — gather mode locks function access:

While the model is asking gather questions, the runtime
forcibly deactivates ALL of the step's other functions. The
only callable tools during a gather question are:

  - +gather_submit+ (the native answer-submission tool)
  - Whatever names you pass in this question's +functions:+
    option

+next_step+ and +change_context+ are also filtered out — the
model cannot navigate away until the gather completes. This
is by design: it forces a tight ask → submit → next-question
loop.

If a question needs to call out to a tool (e.g. validate an
email, geocode a ZIP), pass that tool name in this question's
+functions:+ option. Functions listed here are active ONLY for
this question.

Python parity: “add_gather_question(key, question, type=‘string’, confirm=False, prompt=None, functions=None)“. Ruby exposes the same parameter set as keyword args.

Raises:

  • (ArgumentError)


251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/signalwire/contexts/context_builder.rb', line 251

def add_gather_question(key:, question:, type: 'string', confirm: false,
                        prompt: nil, functions: nil)
  raise ArgumentError, "Must call set_gather_info before add_gather_question" if @gather_info.nil?

  @gather_info.add_question(
    key:       key,
    question:  question,
    type:      type,
    confirm:   confirm,
    prompt:    prompt,
    functions: functions
  )
  self
end

#add_section(title, body) ⇒ Object

Add a POM section (title + body). Mutually exclusive with set_text.

Raises:

  • (ArgumentError)


123
124
125
126
127
128
# File 'lib/signalwire/contexts/context_builder.rb', line 123

def add_section(title, body)
  raise ArgumentError, "Cannot add POM sections when set_text has been used" unless @text.nil?

  @sections << { "title" => title, "body" => body }
  self
end

#clear_sectionsObject

Remove all POM sections and direct text.



267
268
269
270
271
# File 'lib/signalwire/contexts/context_builder.rb', line 267

def clear_sections
  @sections = []
  @text = nil
  self
end

#set_end(is_end) ⇒ Object

Mark this step as terminal for the step flow.

IMPORTANT: is_end = true does NOT end the conversation or hang up the call. It exits step mode entirely after this step executes — clearing the steps list, current step index, valid_steps, and valid_contexts. The agent keeps running, but operates only under the base system prompt and the context-level prompt; no more step instructions are injected and no more next_step tool is offered.

To actually end the call, call a hangup tool or define a hangup hook.



201
202
203
204
# File 'lib/signalwire/contexts/context_builder.rb', line 201

def set_end(is_end)
  @end = is_end
  self
end

#set_functions(functions) ⇒ Object

Set which non-internal functions are callable while this step is active.

IMPORTANT — inheritance behavior:

If you do NOT call this method, the step inherits whichever
function set was active on the previous step (or the previous
context's last step). The server-side runtime only resets the
active set when a step explicitly declares its +functions+
field. This is the most common source of bugs in multi-step
agents: forgetting +set_functions+ on a later step lets the
previous step's tools leak through. Best practice is to call
+set_functions+ explicitly on every step that should differ
from the previous one.

Keep the per-step active set small: LLM tool selection accuracy degrades noticeably past ~7-8 simultaneously-active tools per call. Use per-step whitelisting to partition large tool collections.

Internal functions (e.g. gather_submit, hangup hook) are ALWAYS protected and cannot be deactivated by this whitelist. The native navigation tools next_step and change_context are injected automatically when set_valid_steps/set_valid_contexts is used; they are not affected by this list and do not need to appear in it.

Parameters:

  • functions (String, Array<String>)

    one of:

    • Array<String> — whitelist of function names allowed in this step. Functions not in the list become inactive.

    • — explicit disable-all (no user functions callable).

    • “none” — synonym for [], same effect.



174
175
176
177
# File 'lib/signalwire/contexts/context_builder.rb', line 174

def set_functions(functions)
  @functions = functions
  self
end

#set_gather_info(output_key: nil, completion_action: nil, prompt: nil) ⇒ Object

Enable info gathering for this step. Returns self. After calling this, use add_gather_question to define questions.



218
219
220
221
222
223
224
225
# File 'lib/signalwire/contexts/context_builder.rb', line 218

def set_gather_info(output_key: nil, completion_action: nil, prompt: nil)
  @gather_info = GatherInfo.new(
    output_key:        output_key,
    completion_action: completion_action,
    prompt:            prompt
  )
  self
end

#set_reset_consolidate(val) ⇒ Object



283
284
285
286
# File 'lib/signalwire/contexts/context_builder.rb', line 283

def set_reset_consolidate(val)
  @reset_consolidate = val
  self
end

#set_reset_full_reset(val) ⇒ Object



288
289
290
291
# File 'lib/signalwire/contexts/context_builder.rb', line 288

def set_reset_full_reset(val)
  @reset_full_reset = val
  self
end

#set_reset_system_prompt(prompt) ⇒ Object



273
274
275
276
# File 'lib/signalwire/contexts/context_builder.rb', line 273

def set_reset_system_prompt(prompt)
  @reset_system_prompt = prompt
  self
end

#set_reset_user_prompt(prompt) ⇒ Object



278
279
280
281
# File 'lib/signalwire/contexts/context_builder.rb', line 278

def set_reset_user_prompt(prompt)
  @reset_user_prompt = prompt
  self
end

#set_skip_to_next_step(skip) ⇒ Object



211
212
213
214
# File 'lib/signalwire/contexts/context_builder.rb', line 211

def set_skip_to_next_step(skip)
  @skip_to_next_step = skip
  self
end

#set_skip_user_turn(skip) ⇒ Object



206
207
208
209
# File 'lib/signalwire/contexts/context_builder.rb', line 206

def set_skip_user_turn(skip)
  @skip_user_turn = skip
  self
end

#set_step_criteria(criteria) ⇒ Object



138
139
140
141
# File 'lib/signalwire/contexts/context_builder.rb', line 138

def set_step_criteria(criteria)
  @step_criteria = criteria
  self
end

#set_text(text) ⇒ Object

Set the step’s prompt text directly. Mutually exclusive with POM sections.

Raises:

  • (ArgumentError)


115
116
117
118
119
120
# File 'lib/signalwire/contexts/context_builder.rb', line 115

def set_text(text)
  raise ArgumentError, "Cannot use set_text when POM sections have been added" if @sections.any?

  @text = text
  self
end

#set_valid_contexts(contexts) ⇒ Object



184
185
186
187
# File 'lib/signalwire/contexts/context_builder.rb', line 184

def set_valid_contexts(contexts)
  @valid_contexts = contexts
  self
end

#set_valid_steps(steps) ⇒ Object



179
180
181
182
# File 'lib/signalwire/contexts/context_builder.rb', line 179

def set_valid_steps(steps)
  @valid_steps = steps
  self
end

#to_hObject



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/signalwire/contexts/context_builder.rb', line 293

def to_h
  step_h = {
    "name" => @name,
    "text" => render_text
  }

  step_h["step_criteria"]    = @step_criteria   if @step_criteria
  step_h["functions"]        = @functions        unless @functions.nil?
  step_h["valid_steps"]      = @valid_steps      if @valid_steps
  step_h["valid_contexts"]   = @valid_contexts   if @valid_contexts
  step_h["end"]              = true               if @end
  step_h["skip_user_turn"]   = true               if @skip_user_turn
  step_h["skip_to_next_step"] = true              if @skip_to_next_step

  reset = {}
  reset["system_prompt"] = @reset_system_prompt if @reset_system_prompt
  reset["user_prompt"]   = @reset_user_prompt   if @reset_user_prompt
  reset["consolidate"]   = @reset_consolidate   if @reset_consolidate
  reset["full_reset"]    = @reset_full_reset    if @reset_full_reset
  step_h["reset"] = reset if reset.any?

  step_h["gather_info"] = @gather_info.to_h if @gather_info

  step_h
end