Class: SignalWire::DataMap

Inherits:
Object
  • Object
show all
Defined in:
lib/signalwire/datamap/data_map.rb

Overview

Fluent builder for server-side DataMap tools.

DataMap tools execute on SignalWire servers without requiring webhook endpoints. This class provides a chainable API for building data_map configurations that become SWAIG function definitions.

All mutator methods return self so calls can be chained:

dm = DataMap.new('get_weather')
     .purpose('Get current weather')
     .parameter('location', 'string', 'City name', required: true)
     .webhook('GET', 'https://api.weather.com/v1/current?q=${location}')
     .output(Swaig::FunctionResult.new('Weather: ${response.current.temp_f}F'))

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(function_name) ⇒ DataMap

Returns a new instance of DataMap.



28
29
30
31
32
33
34
35
36
37
# File 'lib/signalwire/datamap/data_map.rb', line 28

def initialize(function_name)
  @function_name = function_name
  @purpose_text = ''
  @parameters = {}       # name => { "type" => ..., "description" => ... }
  @required_params = []
  @expressions = []
  @webhooks = []
  @fallback_output = nil
  @global_error_keys = []
end

Instance Attribute Details

#function_nameObject (readonly)

Returns the value of attribute function_name.



26
27
28
# File 'lib/signalwire/datamap/data_map.rb', line 26

def function_name
  @function_name
end

Class Method Details

.create_expression_tool(name:, patterns:, parameters: nil) ⇒ DataMap

Build an expression-only tool (no HTTP calls).

Parameters:

  • name (String)
  • patterns (Hash)

    test_value => [pattern, Swaig::FunctionResult]

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

    same format as create_simple_api_tool

Returns:



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/signalwire/datamap/data_map.rb', line 294

def self.create_expression_tool(name:, patterns:, parameters: nil)
  dm = new(name)

  if parameters
    parameters.each do |pname, pdef|
      dm.parameter(
        pname,
        pdef.fetch("type", "string"),
        pdef.fetch("description", "#{pname} parameter"),
        required: pdef.fetch("required", false)
      )
    end
  end

  patterns.each do |test_value, (pattern, result)|
    dm.expression(test_value, pattern, result)
  end

  dm
end

.create_simple_api_tool(name:, url:, response_template:, parameters: nil, method: 'GET', headers: nil, body: nil, error_keys: nil) ⇒ DataMap

Build a simple API-calling tool in one shot.

Parameters:

  • name (String)
  • url (String)
  • response_template (String)
  • parameters (Hash, nil) (defaults to: nil)

    name => { “type” => …, “description” => …, “required” => bool }

  • method (String) (defaults to: 'GET')

    HTTP method (default GET)

  • headers (Hash, nil) (defaults to: nil)
  • body (Hash, nil) (defaults to: nil)
  • error_keys (Array<String>, nil) (defaults to: nil)

Returns:



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/signalwire/datamap/data_map.rb', line 266

def self.create_simple_api_tool(name:, url:, response_template:, parameters: nil,
                                method: 'GET', headers: nil, body: nil, error_keys: nil)
  dm = new(name)

  if parameters
    parameters.each do |pname, pdef|
      dm.parameter(
        pname,
        pdef.fetch("type", "string"),
        pdef.fetch("description", "#{pname} parameter"),
        required: pdef.fetch("required", false)
      )
    end
  end

  dm.webhook(method, url, headers: headers)
  dm.body(body) if body
  dm.error_keys(error_keys) if error_keys
  dm.output(Swaig::FunctionResult.new(response_template))
  dm
end

Instance Method Details

#body(data) ⇒ Object

Set the request body for the most-recently-added webhook (POST / PUT).

Raises:

  • (ArgumentError)


156
157
158
159
160
161
# File 'lib/signalwire/datamap/data_map.rb', line 156

def body(data)
  raise ArgumentError, "Must add webhook before setting body" if @webhooks.empty?

  @webhooks.last["body"] = data
  self
end

#description(desc) ⇒ Object

Alias for purpose. Sets the LLM-facing tool description. This string is read by the model to decide WHEN to call this tool. See purpose for bad-vs-good examples.



64
65
66
# File 'lib/signalwire/datamap/data_map.rb', line 64

def description(desc)
  purpose(desc)
end

#error_keys(keys) ⇒ Object

Set error keys on the most-recently-added webhook, or at the top level if no webhook has been added yet.



206
207
208
209
210
211
212
213
# File 'lib/signalwire/datamap/data_map.rb', line 206

def error_keys(keys)
  if @webhooks.any?
    @webhooks.last["error_keys"] = keys
  else
    @global_error_keys = keys
  end
  self
end

#expression(test_value, pattern, output, nomatch_output: nil) ⇒ Object

Add an expression (pattern-matching rule).

Parameters:

  • test_value (String)

    template string to test, e.g. “$SignalWire::DataMap.argsargs.command”

  • pattern (String, Regexp)

    regex pattern to match against

  • output (Swaig::FunctionResult, Hash)

    result when pattern matches

  • nomatch_output (Swaig::FunctionResult, Hash, nil) (defaults to: nil)

    result when pattern does not match



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/signalwire/datamap/data_map.rb', line 107

def expression(test_value, pattern, output, nomatch_output: nil)
  pattern_str = pattern.is_a?(Regexp) ? pattern.source : pattern.to_s
  output_h = output.respond_to?(:to_h) ? output.to_h : output

  expr_def = {
    "string"  => test_value,
    "pattern" => pattern_str,
    "output"  => output_h
  }

  if nomatch_output
    nomatch_h = nomatch_output.respond_to?(:to_h) ? nomatch_output.to_h : nomatch_output
    expr_def["nomatch-output"] = nomatch_h
  end

  @expressions << expr_def
  self
end

#fallback_output(result) ⇒ Object

Set a fallback output used when all webhooks fail.

Parameters:



199
200
201
202
# File 'lib/signalwire/datamap/data_map.rb', line 199

def fallback_output(result)
  @fallback_output = result.respond_to?(:to_h) ? result.to_h : result
  self
end

#foreach(config) ⇒ Object

Configure array processing on the most-recently-added webhook response.

Parameters:

  • config (Hash)

    must include keys: input_key, output_key, append. Optional: max.

Raises:

  • (ArgumentError)


174
175
176
177
178
179
180
181
182
183
184
# File 'lib/signalwire/datamap/data_map.rb', line 174

def foreach(config)
  raise ArgumentError, "Must add webhook before setting foreach" if @webhooks.empty?
  raise ArgumentError, "foreach config must be a Hash" unless config.is_a?(Hash)

  required_keys = %w[input_key output_key append]
  missing = required_keys - config.keys.map(&:to_s)
  raise ArgumentError, "foreach config missing required keys: #{missing.inspect}" unless missing.empty?

  @webhooks.last["foreach"] = config
  self
end

#global_error_keys(keys) ⇒ Object

Set top-level error keys (applies to all webhooks).



216
217
218
219
# File 'lib/signalwire/datamap/data_map.rb', line 216

def global_error_keys(keys)
  @global_error_keys = keys
  self
end

#output(result) ⇒ Object

Set the output result for the most-recently-added webhook.

Parameters:

Raises:

  • (ArgumentError)


189
190
191
192
193
194
# File 'lib/signalwire/datamap/data_map.rb', line 189

def output(result)
  raise ArgumentError, "Must add webhook before setting output" if @webhooks.empty?

  @webhooks.last["output"] = result.respond_to?(:to_h) ? result.to_h : result
  self
end

#parameter(name, type, desc, required: false, enum: nil) ⇒ Object

Add a typed parameter to the function signature — the desc is LLM-FACING.

Each parameter description is rendered into the OpenAI tool schema under parameters.properties.<name>.description and sent to the model. The model uses it to decide HOW to fill in the argument from user speech. It is prompt engineering, not developer FYI.

Bad vs good

BAD : .parameter("city", "string", "the city")
GOOD: .parameter("city", "string",
          "The name of the city to get weather for, e.g. "   \
          "'San Francisco'. Ask the user if they did not "    \
          "provide one. Include the state or country if the " \
          "city name is ambiguous.")

Parameters:

  • name (String)
  • type (String)

    JSON-Schema type (string, number, boolean, array, object)

  • desc (String)

    LLM-facing prompt-engineering description telling the model how to extract this value from the user’s utterance

  • required (Boolean) (defaults to: false)

    whether the parameter is required

  • enum (Array<String>, nil) (defaults to: nil)

    optional list of allowed values



93
94
95
96
97
98
99
# File 'lib/signalwire/datamap/data_map.rb', line 93

def parameter(name, type, desc, required: false, enum: nil)
  param_def = { "type" => type, "description" => desc }
  param_def["enum"] = enum if enum && !enum.empty?
  @parameters[name] = param_def
  @required_params << name if required && !@required_params.include?(name)
  self
end

#params(data) ⇒ Object

Set request params for the most-recently-added webhook.

Raises:

  • (ArgumentError)


164
165
166
167
168
169
# File 'lib/signalwire/datamap/data_map.rb', line 164

def params(data)
  raise ArgumentError, "Must add webhook before setting params" if @webhooks.empty?

  @webhooks.last["params"] = data
  self
end

#purpose(desc) ⇒ Object

Set the LLM-facing tool description (a.k.a. “purpose”). *PROMPT ENGINEERING*, not developer documentation.

The description string is rendered into the OpenAI tool schema description field on every LLM turn. The model reads it to decide WHEN to call this tool. A vague purpose is the #1 cause of “the model has the right tool but doesn’t call it” failures with data-map tools.

Bad vs good

BAD : .purpose("weather api")
GOOD: .purpose("Get the current weather conditions and "      \
               "forecast for a specific city. Use this "        \
               "whenever the user asks about weather, "         \
               "temperature, rain, or similar conditions in a " \
               "named location.")


56
57
58
59
# File 'lib/signalwire/datamap/data_map.rb', line 56

def purpose(desc)
  @purpose_text = desc
  self
end

#to_swaig_functionHash

Serialize this DataMap into a complete SWAIG function definition Hash.

Returns:

  • (Hash)

    with keys: “function”, “description”, “parameters”, “data_map”



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/signalwire/datamap/data_map.rb', line 224

def to_swaig_function
  # Build parameter schema
  if @parameters.any?
    param_schema = {
      "type"       => "object",
      "properties" => @parameters.dup
    }
    param_schema["required"] = @required_params.dup if @required_params.any?
  else
    param_schema = { "type" => "object", "properties" => {} }
  end

  # Build data_map
  data_map = {}
  data_map["expressions"] = @expressions      if @expressions.any?
  data_map["webhooks"]    = @webhooks          if @webhooks.any?
  data_map["output"]      = @fallback_output   if @fallback_output
  data_map["error_keys"]  = @global_error_keys if @global_error_keys.any?

  {
    "function"    => @function_name,
    "description" => @purpose_text.empty? ? "Execute #{@function_name}" : @purpose_text,
    "parameters"  => param_schema,
    "data_map"    => data_map
  }
end

#webhook(method, url, headers: nil, form_param: nil, input_args_as_params: false, require_args: nil) ⇒ Object

Add a webhook (HTTP call) to the data_map pipeline.

Parameters:

  • method (String)

    HTTP method (GET, POST, PUT, DELETE, etc.)

  • url (String)

    endpoint URL (may contain $variable substitutions)

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

    optional HTTP headers

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

    send JSON body as a single form parameter

  • input_args_as_params (Boolean) (defaults to: false)

    merge function arguments into params

  • require_args (Array<String>, nil) (defaults to: nil)

    only execute when these args are present



134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/signalwire/datamap/data_map.rb', line 134

def webhook(method, url, headers: nil, form_param: nil, input_args_as_params: false, require_args: nil)
  wh = {
    "url"    => url,
    "method" => method.upcase
  }
  wh["headers"]              = headers           if headers
  wh["form_param"]           = form_param        if form_param
  wh["input_args_as_params"] = true               if input_args_as_params
  wh["require_args"]         = require_args       if require_args
  @webhooks << wh
  self
end

#webhook_expressions(expressions) ⇒ Object

Add expressions to run after the most-recently-added webhook completes.

Raises:

  • (ArgumentError)


148
149
150
151
152
153
# File 'lib/signalwire/datamap/data_map.rb', line 148

def webhook_expressions(expressions)
  raise ArgumentError, "Must add webhook before setting webhook expressions" if @webhooks.empty?

  @webhooks.last["expressions"] = expressions
  self
end