Class: Ask::Tool

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/tools/tool.rb

Defined Under Namespace

Classes: Halt, Parameter

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.build_schema_from_paramsObject



77
78
79
80
81
82
83
84
85
86
# File 'lib/ask/tools/tool.rb', line 77

def build_schema_from_params
  properties = parameters.to_h do |_name, param|
    schema = { type: param.type }
    schema[:description] = param.description if param.description
    schema[:items] = { type: "string" } if param.type == "array"
    [param.name.to_s, schema]
  end
  required = parameters.select { |_, p| p.required }.keys.map(&:to_s)
  { type: "object", properties: properties, required: required, additionalProperties: false }
end

.deep_stringify_keys(obj) ⇒ Object



100
101
102
103
104
105
106
# File 'lib/ask/tools/tool.rb', line 100

def deep_stringify_keys(obj)
  case obj
  when Hash then obj.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify_keys(v) }
  when Array then obj.map { |v| deep_stringify_keys(v) }
  else obj
  end
end

.descObject



30
31
32
33
# File 'lib/ask/tools/tool.rb', line 30

def description(text = nil)
  return @description unless text
  @description = text
end

.description(text = nil) ⇒ Object



26
27
28
29
# File 'lib/ask/tools/tool.rb', line 26

def description(text = nil)
  return @description unless text
  @description = text
end

.inherited(subclass) ⇒ Object



17
18
19
20
21
22
23
24
# File 'lib/ask/tools/tool.rb', line 17

def inherited(subclass)
  super
  @parameters = {} if @parameters.nil?
  subclass.instance_variable_set(:@description, nil)
  subclass.instance_variable_set(:@parameters, {})
  subclass.instance_variable_set(:@params_schema_definition, nil)
  subclass.instance_variable_set(:@tool_name, nil)
end

.name(custom = :_no_arg_given) ⇒ Object

Declare a custom tool name. Called with no argument returns the class name (via Module#name). Called with a string stores a custom name for the instance. Example: name "my_custom_tool"



36
37
38
39
40
41
42
# File 'lib/ask/tools/tool.rb', line 36

def name(custom = :_no_arg_given)
  if custom == :_no_arg_given
    super()  # Module#name, returns the Ruby class path
  else
    @tool_name = custom
  end
end

.param(name, type:, desc: nil, description: nil, required: true) ⇒ Object



44
45
46
47
48
49
50
51
# File 'lib/ask/tools/tool.rb', line 44

def param(name, type:, desc: nil, description: nil, required: true)
  type = type.to_s.downcase.to_sym
  validate_param_type!(type, name)
  parameters[name] = Parameter.new(
    name: name, type: map_type(type),
    description: desc || description, required: required
  )
end

.parametersObject



57
58
59
# File 'lib/ask/tools/tool.rb', line 57

def parameters
  @parameters ||= {}
end

.params(schema = nil, &block) ⇒ Object



53
54
55
# File 'lib/ask/tools/tool.rb', line 53

def params(schema = nil, &block)
  @params_schema_definition = schema || block
end

.params_schemaObject



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/ask/tools/tool.rb', line 65

def params_schema
  @params_schema ||= begin
    if @params_schema_definition
      deep_stringify_keys(resolve_params_schema(@params_schema_definition))
    elsif @parameters && @parameters.any?
      build_schema_from_params
    else
      nil
    end
  end
end

.provider_paramsObject



61
62
63
# File 'lib/ask/tools/tool.rb', line 61

def provider_params
  @provider_params ||= {}
end

.resolve_params_schema(definition) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ask/tools/tool.rb', line 88

def resolve_params_schema(definition)
  case definition
  when Proc
    schema_class = Ask::Schema.create(&definition)
    schema_class.new.to_json_schema.dig(:schema)
  when Hash then definition
  when ->(d) { d.respond_to?(:to_json_schema) }
    definition.to_json_schema.dig(:schema)
  else nil
  end
end

Instance Method Details

#call(args = {}, abort_controller = nil) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/ask/tools/tool.rb', line 152

def call(args = {}, abort_controller = nil)
normalized = normalize_args(args)
validation = validate(normalized)
return Ask::Result.failure(validation) if validation
normalized[:_abort_controller] = abort_controller if abort_controller
execute_kwargs = normalized.reject { |k, _| k == :_abort_controller || k == :abort_controller }
execute(**execute_kwargs)
rescue Halt => e
  Ask::Result.ok(data: e.content, metadata: { halted: true })
rescue StandardError => e
  Ask::Result.failure("#{self.class.name.split('::').last} raised #{e.class}: #{e.message}")
end

#descriptionObject



144
145
146
# File 'lib/ask/tools/tool.rb', line 144

def description
  self.class.description
end

#execute(**args) ⇒ Object

Raises:

  • (NotImplementedError)


165
166
167
# File 'lib/ask/tools/tool.rb', line 165

def execute(**args)
  raise NotImplementedError, "#{self.class} must implement #execute(**args)"
end

#inspectObject



188
189
190
# File 'lib/ask/tools/tool.rb', line 188

def inspect
  "#<#{self.class.name} name=#{name.inspect}>"
end

#nameObject



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ask/tools/tool.rb', line 130

def name
  custom = self.class.instance_variable_get(:@tool_name)
  return custom if custom

  klass_name = self.class.name.to_s.split("::").last || ""
  normalized = klass_name.dup.force_encoding("UTF-8").unicode_normalize(:nfkd)
  normalized.encode("ASCII", replace: "")
            .gsub(/[^a-zA-Z0-9_-]/, "-")
            .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
            .gsub(/([a-z\d])([A-Z])/, '\1_\2')
            .downcase
            .delete_suffix("_tool")
end

#parametersObject



148
149
150
# File 'lib/ask/tools/tool.rb', line 148

def parameters
  self.class.parameters
end

#params_schemaObject



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/ask/tools/tool.rb', line 169

def params_schema
  return @params_schema if defined?(@params_schema)
  @params_schema = begin
    if params_schema_definition
      deep_stringify_keys(resolve_params_schema(params_schema_definition))
    elsif parameters.any?
      build_schema_from_params
    else
      nil
    end
  end
end

#provider_paramsObject



126
127
128
# File 'lib/ask/tools/tool.rb', line 126

def provider_params
  self.class.provider_params
end

#tool_definitionObject



182
183
184
185
186
# File 'lib/ask/tools/tool.rb', line 182

def tool_definition
  defn = { name: name, description: description }
  defn[:input_schema] = params_schema if params_schema
  defn
end