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



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

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



86
87
88
89
90
91
92
# File 'lib/ask/tools/tool.rb', line 86

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



28
29
30
31
# File 'lib/ask/tools/tool.rb', line 28

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

.description(text = nil) ⇒ Object



24
25
26
27
# File 'lib/ask/tools/tool.rb', line 24

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

.inherited(subclass) ⇒ Object



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

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

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



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

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



43
44
45
# File 'lib/ask/tools/tool.rb', line 43

def parameters
  @parameters ||= {}
end

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



39
40
41
# File 'lib/ask/tools/tool.rb', line 39

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

.params_schemaObject



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/ask/tools/tool.rb', line 51

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



47
48
49
# File 'lib/ask/tools/tool.rb', line 47

def provider_params
  @provider_params ||= {}
end

.resolve_params_schema(definition) ⇒ Object



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

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 = {}) ⇒ Object



135
136
137
138
139
140
141
142
143
144
# File 'lib/ask/tools/tool.rb', line 135

def call(args = {})
  normalized = normalize_args(args)
  validation = validate(normalized)
  return Ask::Result.failure(validation) if validation
  execute(**normalized)
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



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

def description
  self.class.description
end

#executeObject

Raises:

  • (NotImplementedError)


146
147
148
# File 'lib/ask/tools/tool.rb', line 146

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

#inspectObject



169
170
171
# File 'lib/ask/tools/tool.rb', line 169

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

#nameObject



116
117
118
119
120
121
122
123
124
125
# File 'lib/ask/tools/tool.rb', line 116

def name
  klass_name = self.class.name.to_s || ""
  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



131
132
133
# File 'lib/ask/tools/tool.rb', line 131

def parameters
  self.class.parameters
end

#params_schemaObject



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

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



112
113
114
# File 'lib/ask/tools/tool.rb', line 112

def provider_params
  self.class.provider_params
end

#tool_definitionObject



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

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