lluminary 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/lluminary/models/base.rb +177 -66
- data/lib/lluminary/schema.rb +37 -7
- data/lib/lluminary/schema_model.rb +149 -65
- data/lib/lluminary/task.rb +37 -0
- data/lib/lluminary/tasks/describe_openai_model.rb +61 -0
- data/lib/lluminary/tasks/identify_and_describe_open_ai_models.rb +51 -0
- data/spec/examples/character_profiler_spec.rb +85 -0
- data/spec/lluminary/models/base_spec.rb +933 -100
- data/spec/lluminary/schema_model_spec.rb +259 -0
- data/spec/lluminary/schema_spec.rb +228 -134
- data/spec/lluminary/task_custom_validation_spec.rb +262 -0
- data/spec/spec_helper.rb +3 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cacc0edbbc80b69f4c95d6b9888707651dffc5189432920b1c814abed095ea01
|
4
|
+
data.tar.gz: 647f143f0be6053b7ca80868e2d6bd5058cfee8fae0d0e12a51ad59f8e8d5233
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63dce0789e7b175ad324d7de8f7bbd4c90da575e314f14cbd3c4ab9c6b3cab5b942cd279a15b545ac322f267bda221efd3988c6b7e9c7f2f930f1de0c1af87ac
|
7
|
+
data.tar.gz: 30a523b37b850f6603e466e05c49ceb8eeb03fb8ee81c3b237b97b175ace6082e0e97ace99b210e2807fb1d9feab2520a4b95753d5690e7fb46d02df1faa0389
|
@@ -25,11 +25,13 @@ module Lluminary
|
|
25
25
|
|
26
26
|
#{output_preamble}
|
27
27
|
|
28
|
-
#{
|
28
|
+
#{format_fields_descriptions(task.class.output_fields)}
|
29
29
|
|
30
|
+
#{format_additional_validations(task.class.output_custom_validations)}
|
31
|
+
|
30
32
|
#{json_preamble}
|
31
33
|
|
32
|
-
#{
|
34
|
+
#{generate_example_json_object(task.class.output_fields)}
|
33
35
|
PROMPT
|
34
36
|
end
|
35
37
|
|
@@ -46,30 +48,142 @@ module Lluminary
|
|
46
48
|
"Your response must be ONLY this JSON object:"
|
47
49
|
end
|
48
50
|
|
49
|
-
def
|
51
|
+
def format_fields_descriptions(fields)
|
50
52
|
fields
|
51
|
-
.map
|
52
|
-
|
53
|
-
|
53
|
+
.map { |name, field| format_field_description(name, field) }
|
54
|
+
.compact # Remove nil entries from skipped types
|
55
|
+
.join("\n\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
def format_field_description(name, field, name_for_example = nil)
|
59
|
+
case field[:type]
|
60
|
+
when :hash
|
61
|
+
format_hash_description(name, field, name_for_example)
|
62
|
+
when :array
|
63
|
+
format_array_description(name, field, name_for_example)
|
64
|
+
else
|
65
|
+
format_simple_field_description(name, field, name_for_example)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def format_hash_description(name, field, name_for_example = nil)
|
70
|
+
return nil unless field[:fields]
|
71
|
+
|
72
|
+
lines = build_field_description_lines(name, field, name_for_example)
|
54
73
|
|
55
|
-
|
56
|
-
|
57
|
-
|
74
|
+
# Add descriptions for each field in the hash
|
75
|
+
field[:fields].each do |subname, subfield|
|
76
|
+
lines << "\n#{format_field_description("#{name}.#{subname}", subfield, subname)}"
|
77
|
+
end
|
78
|
+
|
79
|
+
lines.join("\n")
|
80
|
+
end
|
58
81
|
|
59
|
-
|
60
|
-
|
82
|
+
# Helper to ensure consistent JSON formatting for examples
|
83
|
+
def format_json_for_examples(value)
|
84
|
+
case value
|
85
|
+
when Hash, Array
|
86
|
+
JSON.generate(value)
|
87
|
+
else
|
88
|
+
value.inspect
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def format_simple_field_description(name, field, name_for_example = nil)
|
93
|
+
build_field_description_lines(name, field, name_for_example).join("\n")
|
94
|
+
end
|
95
|
+
|
96
|
+
def format_array_description(name, field, name_for_example = nil)
|
97
|
+
lines = build_field_description_lines(name, field, name_for_example)
|
98
|
+
|
99
|
+
if field[:element_type]
|
100
|
+
if field[:element_type][:type] == :array
|
101
|
+
# Create a nested array field by adding [] to the name
|
102
|
+
# and recursively call format_array_description
|
103
|
+
element_field = field[:element_type].dup
|
104
|
+
nested_description =
|
105
|
+
format_array_description("#{name}[]", element_field, "item")
|
106
|
+
lines << "\n#{nested_description}"
|
107
|
+
elsif field[:element_type][:type] == :hash &&
|
108
|
+
field[:element_type][:fields]
|
109
|
+
field[:element_type][:fields].each do |subname, subfield|
|
110
|
+
inner_field = {
|
111
|
+
type: subfield[:type],
|
112
|
+
description: subfield[:description]
|
113
|
+
}
|
114
|
+
inner_lines =
|
115
|
+
build_field_description_lines(
|
116
|
+
"#{name}[].#{subname}",
|
117
|
+
inner_field,
|
118
|
+
subname
|
119
|
+
)
|
120
|
+
lines << "\n#{inner_lines.join("\n")}"
|
61
121
|
end
|
122
|
+
else
|
123
|
+
inner_field = {
|
124
|
+
type: field[:element_type][:type],
|
125
|
+
description: field[:element_type][:description]
|
126
|
+
}
|
127
|
+
inner_lines =
|
128
|
+
build_field_description_lines("#{name}[]", inner_field, "item")
|
129
|
+
lines << "\n#{inner_lines.join("\n")}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
lines.join("\n")
|
134
|
+
end
|
135
|
+
|
136
|
+
# Common method for building field description lines
|
137
|
+
def build_field_description_lines(name, field, name_for_example = nil)
|
138
|
+
lines = []
|
139
|
+
# Add field description
|
140
|
+
lines << "# #{name}"
|
141
|
+
lines << "Description: #{field[:description]}" if field[:description]
|
142
|
+
lines << "Type: #{format_type(field)}"
|
62
143
|
|
63
|
-
|
64
|
-
|
144
|
+
# Add validation info
|
145
|
+
if (validations = describe_validations(field))
|
146
|
+
lines << "Validations: #{validations}"
|
147
|
+
end
|
148
|
+
|
149
|
+
# Generate and add example
|
150
|
+
example_value =
|
151
|
+
generate_example_value(
|
152
|
+
name_for_example || name.to_s.split(".").last,
|
153
|
+
field
|
154
|
+
)
|
155
|
+
lines << "Example: #{format_json_for_examples(example_value)}"
|
156
|
+
|
157
|
+
lines
|
158
|
+
end
|
159
|
+
|
160
|
+
def format_type(field)
|
161
|
+
case field[:type]
|
162
|
+
when :datetime
|
163
|
+
"datetime in ISO8601 format"
|
164
|
+
when :array
|
165
|
+
if field[:element_type].nil?
|
166
|
+
"array"
|
167
|
+
elsif field[:element_type][:type] == :array
|
168
|
+
"array of arrays"
|
169
|
+
elsif field[:element_type][:type] == :datetime
|
170
|
+
"array of datetimes in ISO8601 format"
|
171
|
+
elsif field[:element_type][:type] == :hash
|
172
|
+
"array of objects"
|
173
|
+
else
|
174
|
+
"array of #{field[:element_type][:type]}s"
|
65
175
|
end
|
66
|
-
|
176
|
+
when :hash
|
177
|
+
"object"
|
178
|
+
else
|
179
|
+
field[:type].to_s
|
180
|
+
end
|
67
181
|
end
|
68
182
|
|
69
|
-
def describe_validations(
|
70
|
-
return unless validations&.any?
|
183
|
+
def describe_validations(field)
|
184
|
+
return unless field[:validations]&.any?
|
71
185
|
|
72
|
-
validations
|
186
|
+
field[:validations]
|
73
187
|
.map do |options|
|
74
188
|
case options.keys.first
|
75
189
|
when :presence
|
@@ -81,7 +195,7 @@ module Lluminary
|
|
81
195
|
when :format
|
82
196
|
"must match format: #{options[:format][:with]}"
|
83
197
|
when :length
|
84
|
-
describe_length_validation(options[:length])
|
198
|
+
describe_length_validation(options[:length], field[:type])
|
85
199
|
when :numericality
|
86
200
|
describe_numericality_validation(options[:numericality])
|
87
201
|
when :comparison
|
@@ -94,43 +208,29 @@ module Lluminary
|
|
94
208
|
.join(", ")
|
95
209
|
end
|
96
210
|
|
97
|
-
def describe_length_validation(options)
|
211
|
+
def describe_length_validation(options, field_type = nil)
|
98
212
|
descriptions = []
|
213
|
+
units = field_type == :array ? "elements" : "characters"
|
214
|
+
|
99
215
|
if options[:minimum]
|
100
|
-
descriptions << "must
|
216
|
+
descriptions << "must have at least #{options[:minimum]} #{units}"
|
101
217
|
end
|
102
218
|
if options[:maximum]
|
103
|
-
descriptions << "must
|
219
|
+
descriptions << "must have at most #{options[:maximum]} #{units}"
|
104
220
|
end
|
105
221
|
if options[:is]
|
106
|
-
descriptions << "must
|
222
|
+
descriptions << "must have exactly #{options[:is]} #{units}"
|
107
223
|
end
|
108
224
|
if options[:in]
|
109
|
-
descriptions << "must
|
225
|
+
descriptions << "must have between #{options[:in].min} and #{options[:in].max} #{units}"
|
110
226
|
end
|
111
227
|
descriptions.join(", ")
|
112
228
|
end
|
113
229
|
|
114
230
|
def describe_numericality_validation(options)
|
115
231
|
descriptions = []
|
116
|
-
|
117
|
-
|
118
|
-
end
|
119
|
-
if options[:greater_than_or_equal_to]
|
120
|
-
descriptions << "must be greater than or equal to #{options[:greater_than_or_equal_to]}"
|
121
|
-
end
|
122
|
-
if options[:equal_to]
|
123
|
-
descriptions << "must be equal to #{options[:equal_to]}"
|
124
|
-
end
|
125
|
-
if options[:less_than]
|
126
|
-
descriptions << "must be less than #{options[:less_than]}"
|
127
|
-
end
|
128
|
-
if options[:less_than_or_equal_to]
|
129
|
-
descriptions << "must be less than or equal to #{options[:less_than_or_equal_to]}"
|
130
|
-
end
|
131
|
-
if options[:other_than]
|
132
|
-
descriptions << "must be other than #{options[:other_than]}"
|
133
|
-
end
|
232
|
+
descriptions.concat(describe_common_comparisons(options))
|
233
|
+
|
134
234
|
if options[:in]
|
135
235
|
descriptions << "must be in: #{options[:in].to_a.join(", ")}"
|
136
236
|
end
|
@@ -140,6 +240,10 @@ module Lluminary
|
|
140
240
|
end
|
141
241
|
|
142
242
|
def describe_comparison_validation(options)
|
243
|
+
describe_common_comparisons(options).join(", ")
|
244
|
+
end
|
245
|
+
|
246
|
+
def describe_common_comparisons(options)
|
143
247
|
descriptions = []
|
144
248
|
if options[:greater_than]
|
145
249
|
descriptions << "must be greater than #{options[:greater_than]}"
|
@@ -159,10 +263,10 @@ module Lluminary
|
|
159
263
|
if options[:other_than]
|
160
264
|
descriptions << "must be other than #{options[:other_than]}"
|
161
265
|
end
|
162
|
-
descriptions
|
266
|
+
descriptions
|
163
267
|
end
|
164
268
|
|
165
|
-
def
|
269
|
+
def generate_example_json_object(fields)
|
166
270
|
example =
|
167
271
|
fields.each_with_object({}) do |(name, field), hash|
|
168
272
|
hash[name] = generate_example_value(name, field)
|
@@ -170,26 +274,14 @@ module Lluminary
|
|
170
274
|
JSON.pretty_generate(example)
|
171
275
|
end
|
172
276
|
|
173
|
-
def format_type(field)
|
174
|
-
type = field[:type]
|
175
|
-
case type
|
176
|
-
when :datetime
|
177
|
-
"datetime in ISO8601 format"
|
178
|
-
when :array
|
179
|
-
if field[:element_type]
|
180
|
-
"array of #{format_type(field[:element_type])}"
|
181
|
-
else
|
182
|
-
"array"
|
183
|
-
end
|
184
|
-
else
|
185
|
-
type.to_s
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
277
|
def generate_example_value(name, field)
|
190
278
|
case field[:type]
|
191
279
|
when :string
|
192
|
-
"
|
280
|
+
if name == "item" # For items in arrays
|
281
|
+
"first #{name}"
|
282
|
+
else
|
283
|
+
"your #{name} here"
|
284
|
+
end
|
193
285
|
when :integer
|
194
286
|
0
|
195
287
|
when :datetime
|
@@ -200,6 +292,8 @@ module Lluminary
|
|
200
292
|
0.0
|
201
293
|
when :array
|
202
294
|
generate_array_example(name, field)
|
295
|
+
when :hash
|
296
|
+
generate_hash_example(name, field)
|
203
297
|
end
|
204
298
|
end
|
205
299
|
|
@@ -208,11 +302,7 @@ module Lluminary
|
|
208
302
|
|
209
303
|
case field[:element_type][:type]
|
210
304
|
when :string
|
211
|
-
[
|
212
|
-
"first #{name.to_s.singularize}",
|
213
|
-
"second #{name.to_s.singularize}",
|
214
|
-
"..."
|
215
|
-
]
|
305
|
+
["first #{name.to_s.singularize}", "second #{name.to_s.singularize}"]
|
216
306
|
when :integer
|
217
307
|
[1, 2, 3]
|
218
308
|
when :float
|
@@ -226,10 +316,31 @@ module Lluminary
|
|
226
316
|
inner_example = generate_array_example("item", field[:element_type])
|
227
317
|
[inner_example, inner_example]
|
228
318
|
else
|
229
|
-
[[
|
319
|
+
[[], []]
|
230
320
|
end
|
321
|
+
when :hash
|
322
|
+
example =
|
323
|
+
generate_hash_example(name.to_s.singularize, field[:element_type])
|
324
|
+
[example, example]
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def generate_hash_example(name, field)
|
329
|
+
return {} unless field[:fields]
|
330
|
+
|
331
|
+
field[:fields].each_with_object({}) do |(subname, subfield), hash|
|
332
|
+
hash[subname] = generate_example_value(subname, subfield)
|
231
333
|
end
|
232
334
|
end
|
335
|
+
|
336
|
+
def format_additional_validations(custom_validations)
|
337
|
+
descriptions = custom_validations.map { |v| v[:description] }.compact
|
338
|
+
return "" if descriptions.empty?
|
339
|
+
|
340
|
+
section = ["Additional Validations:"]
|
341
|
+
descriptions.each { |desc| section << "- #{desc}" }
|
342
|
+
"#{section.join("\n")}\n"
|
343
|
+
end
|
233
344
|
end
|
234
345
|
end
|
235
346
|
end
|
data/lib/lluminary/schema.rb
CHANGED
@@ -9,6 +9,7 @@ module Lluminary
|
|
9
9
|
def initialize
|
10
10
|
@fields = {}
|
11
11
|
@validations = []
|
12
|
+
@custom_validations = []
|
12
13
|
end
|
13
14
|
|
14
15
|
def string(name, description: nil)
|
@@ -42,7 +43,22 @@ module Lluminary
|
|
42
43
|
@fields[name] = field
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
|
+
def hash(name, description: nil, &block)
|
47
|
+
unless block
|
48
|
+
raise ArgumentError, "Hash fields must be defined with a block"
|
49
|
+
end
|
50
|
+
|
51
|
+
nested_schema = Schema.new
|
52
|
+
nested_schema.instance_eval(&block)
|
53
|
+
|
54
|
+
@fields[name] = {
|
55
|
+
type: :hash,
|
56
|
+
description: description,
|
57
|
+
fields: nested_schema.fields
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :fields, :custom_validations
|
46
62
|
|
47
63
|
def validates(*args, **options)
|
48
64
|
@validations << [args, options]
|
@@ -57,18 +73,21 @@ module Lluminary
|
|
57
73
|
end
|
58
74
|
end
|
59
75
|
|
76
|
+
def validate(method_name, description: nil)
|
77
|
+
@custom_validations << { method: method_name, description: description }
|
78
|
+
end
|
79
|
+
|
60
80
|
def validations_for(field_name)
|
61
81
|
@validations.select { |args, _| args.include?(field_name) }
|
62
82
|
end
|
63
83
|
|
64
84
|
def schema_model
|
65
85
|
@schema_model ||=
|
66
|
-
SchemaModel.build(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
instance.valid? ? [] : instance.errors.full_messages
|
86
|
+
SchemaModel.build(
|
87
|
+
fields: @fields,
|
88
|
+
validations: @validations,
|
89
|
+
custom_validations: @custom_validations
|
90
|
+
)
|
72
91
|
end
|
73
92
|
|
74
93
|
# Internal class for defining array element types
|
@@ -100,6 +119,17 @@ module Lluminary
|
|
100
119
|
) if block
|
101
120
|
field
|
102
121
|
end
|
122
|
+
|
123
|
+
def hash(description: nil, &block)
|
124
|
+
unless block
|
125
|
+
raise ArgumentError, "Hash fields must be defined with a block"
|
126
|
+
end
|
127
|
+
|
128
|
+
nested_schema = Schema.new
|
129
|
+
nested_schema.instance_eval(&block)
|
130
|
+
|
131
|
+
{ type: :hash, description: description, fields: nested_schema.fields }
|
132
|
+
end
|
103
133
|
end
|
104
134
|
end
|
105
135
|
end
|