lluminary 0.1.0
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 +7 -0
- data/lib/lluminary/config.rb +23 -0
- data/lib/lluminary/field_description.rb +148 -0
- data/lib/lluminary/provider_error.rb +4 -0
- data/lib/lluminary/providers/base.rb +15 -0
- data/lib/lluminary/providers/bedrock.rb +51 -0
- data/lib/lluminary/providers/openai.rb +40 -0
- data/lib/lluminary/providers/test.rb +37 -0
- data/lib/lluminary/result.rb +13 -0
- data/lib/lluminary/schema.rb +61 -0
- data/lib/lluminary/schema_model.rb +87 -0
- data/lib/lluminary/task.rb +224 -0
- data/lib/lluminary/validation_error.rb +4 -0
- data/lib/lluminary/version.rb +3 -0
- data/lib/lluminary.rb +18 -0
- data/spec/examples/analyze_text_spec.rb +21 -0
- data/spec/examples/color_analyzer_spec.rb +42 -0
- data/spec/examples/content_analyzer_spec.rb +75 -0
- data/spec/examples/historical_event_analyzer_spec.rb +37 -0
- data/spec/examples/price_analyzer_spec.rb +46 -0
- data/spec/examples/quote_task_spec.rb +27 -0
- data/spec/examples/sentiment_analysis_spec.rb +45 -0
- data/spec/examples/summarize_text_spec.rb +21 -0
- data/spec/lluminary/config_spec.rb +53 -0
- data/spec/lluminary/field_description_spec.rb +36 -0
- data/spec/lluminary/providers/base_spec.rb +17 -0
- data/spec/lluminary/providers/bedrock_spec.rb +109 -0
- data/spec/lluminary/providers/openai_spec.rb +62 -0
- data/spec/lluminary/providers/test_spec.rb +57 -0
- data/spec/lluminary/result_spec.rb +31 -0
- data/spec/lluminary/schema_model_spec.rb +86 -0
- data/spec/lluminary/schema_spec.rb +302 -0
- data/spec/lluminary/task_spec.rb +777 -0
- data/spec/spec_helper.rb +4 -0
- metadata +190 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4a50b739f7e9ea0f069d17bc94d74f284e38546bd1a8abbda0a6084548803756
|
4
|
+
data.tar.gz: db0bb01c407511d3bb1e34796aae68c823c33e9da6d4b556a0e4d3a09ef16ee3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 78891cbbe5940fe7685a33080c3cd5dd62afd3864c143a49a9ea4a1e63f5a390097ec1d863a93f47af44edded688b2c5b77068d5761198d3ae057dbcdb5d5c54
|
7
|
+
data.tar.gz: 6e0c48f4d4ce3c25756e611a3506e2bd44aa32ef3643be35f9c6312bb519c23dd1fc3ad2c273db6421eccb2c96895958f6a31d0d1ebf9bcd7cada876ab99b444
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Lluminary
|
2
|
+
class Config
|
3
|
+
def initialize
|
4
|
+
@providers = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def configure
|
8
|
+
yield self
|
9
|
+
end
|
10
|
+
|
11
|
+
def provider(name, **options)
|
12
|
+
@providers[name.to_sym] = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def provider_config(provider_name)
|
16
|
+
@providers[provider_name.to_sym] || {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset!
|
20
|
+
@providers = {}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Lluminary
|
2
|
+
class FieldDescription
|
3
|
+
def initialize(name, field)
|
4
|
+
@name = name
|
5
|
+
@type = field[:type]
|
6
|
+
@description = field[:description]
|
7
|
+
@validations = field[:validations] || []
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
parts = []
|
12
|
+
parts << "#{@name} (#{type_description})"
|
13
|
+
parts << ": #{@description}" if @description
|
14
|
+
parts << " (#{validation_descriptions.join(', ')})" if validation_descriptions.any?
|
15
|
+
parts.join
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_schema_s
|
19
|
+
parts = []
|
20
|
+
parts << "#{@name} (#{type_description})"
|
21
|
+
parts << ": #{@description}" if @description
|
22
|
+
parts << "\nValidation: #{validation_descriptions.join(', ')}" if validation_descriptions.any?
|
23
|
+
parts << "\nExample: #{example_value}"
|
24
|
+
parts.join
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def type_description
|
30
|
+
case @type
|
31
|
+
when :datetime
|
32
|
+
"datetime in ISO8601 format"
|
33
|
+
else
|
34
|
+
@type.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def validation_descriptions
|
39
|
+
@validations.map do |_, options|
|
40
|
+
case options.keys.first
|
41
|
+
when :absence
|
42
|
+
"must be absent"
|
43
|
+
when :comparison
|
44
|
+
comparison_descriptions(options[:comparison])
|
45
|
+
when :exclusion
|
46
|
+
"must not be one of: #{options[:exclusion][:in].join(', ')}"
|
47
|
+
when :format
|
48
|
+
"must match format: #{options[:format][:with]}"
|
49
|
+
when :inclusion
|
50
|
+
"must be one of: #{options[:inclusion][:in].join(', ')}"
|
51
|
+
when :length
|
52
|
+
length_descriptions(options[:length])
|
53
|
+
when :numericality
|
54
|
+
numericality_descriptions(options[:numericality])
|
55
|
+
when :presence
|
56
|
+
"must be present"
|
57
|
+
end
|
58
|
+
end.compact
|
59
|
+
end
|
60
|
+
|
61
|
+
def comparison_descriptions(options)
|
62
|
+
descriptions = []
|
63
|
+
if options[:greater_than]
|
64
|
+
descriptions << "must be greater than #{options[:greater_than]}"
|
65
|
+
end
|
66
|
+
if options[:greater_than_or_equal_to]
|
67
|
+
descriptions << "must be greater than or equal to #{options[:greater_than_or_equal_to]}"
|
68
|
+
end
|
69
|
+
if options[:equal_to]
|
70
|
+
descriptions << "must be equal to #{options[:equal_to]}"
|
71
|
+
end
|
72
|
+
if options[:less_than]
|
73
|
+
descriptions << "must be less than #{options[:less_than]}"
|
74
|
+
end
|
75
|
+
if options[:less_than_or_equal_to]
|
76
|
+
descriptions << "must be less than or equal to #{options[:less_than_or_equal_to]}"
|
77
|
+
end
|
78
|
+
if options[:other_than]
|
79
|
+
descriptions << "must be other than #{options[:other_than]}"
|
80
|
+
end
|
81
|
+
descriptions.join(", ")
|
82
|
+
end
|
83
|
+
|
84
|
+
def length_descriptions(options)
|
85
|
+
descriptions = []
|
86
|
+
if options[:minimum]
|
87
|
+
descriptions << "must be at least #{options[:minimum]} characters"
|
88
|
+
end
|
89
|
+
if options[:maximum]
|
90
|
+
descriptions << "must be at most #{options[:maximum]} characters"
|
91
|
+
end
|
92
|
+
if options[:is]
|
93
|
+
descriptions << "must be exactly #{options[:is]} characters"
|
94
|
+
end
|
95
|
+
if options[:in]
|
96
|
+
descriptions << "must be between #{options[:in].min} and #{options[:in].max} characters"
|
97
|
+
end
|
98
|
+
descriptions.join(", ")
|
99
|
+
end
|
100
|
+
|
101
|
+
def numericality_descriptions(options)
|
102
|
+
descriptions = []
|
103
|
+
if options[:greater_than]
|
104
|
+
descriptions << "must be greater than #{options[:greater_than]}"
|
105
|
+
end
|
106
|
+
if options[:greater_than_or_equal_to]
|
107
|
+
descriptions << "must be greater than or equal to #{options[:greater_than_or_equal_to]}"
|
108
|
+
end
|
109
|
+
if options[:equal_to]
|
110
|
+
descriptions << "must be equal to #{options[:equal_to]}"
|
111
|
+
end
|
112
|
+
if options[:less_than]
|
113
|
+
descriptions << "must be less than #{options[:less_than]}"
|
114
|
+
end
|
115
|
+
if options[:less_than_or_equal_to]
|
116
|
+
descriptions << "must be less than or equal to #{options[:less_than_or_equal_to]}"
|
117
|
+
end
|
118
|
+
if options[:other_than]
|
119
|
+
descriptions << "must be other than #{options[:other_than]}"
|
120
|
+
end
|
121
|
+
if options[:in]
|
122
|
+
descriptions << "must be in: #{options[:in].to_a.join(', ')}"
|
123
|
+
end
|
124
|
+
if options[:odd]
|
125
|
+
descriptions << "must be odd"
|
126
|
+
end
|
127
|
+
if options[:even]
|
128
|
+
descriptions << "must be even"
|
129
|
+
end
|
130
|
+
descriptions.join(", ")
|
131
|
+
end
|
132
|
+
|
133
|
+
def example_value
|
134
|
+
case @type
|
135
|
+
when :string
|
136
|
+
"\"your #{@name} here\""
|
137
|
+
when :integer
|
138
|
+
"0"
|
139
|
+
when :datetime
|
140
|
+
"\"2024-01-01T12:00:00+00:00\""
|
141
|
+
when :boolean
|
142
|
+
"true"
|
143
|
+
when :float
|
144
|
+
"0.0"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'aws-sdk-bedrockruntime'
|
2
|
+
require 'json'
|
3
|
+
require_relative '../provider_error'
|
4
|
+
|
5
|
+
require 'pry-byebug'
|
6
|
+
|
7
|
+
module Lluminary
|
8
|
+
module Providers
|
9
|
+
class Bedrock < Base
|
10
|
+
DEFAULT_MODEL_ID = 'anthropic.claude-instant-v1'
|
11
|
+
|
12
|
+
attr_reader :client, :config
|
13
|
+
|
14
|
+
def initialize(**config)
|
15
|
+
super
|
16
|
+
@config = config
|
17
|
+
|
18
|
+
@client = Aws::BedrockRuntime::Client.new(
|
19
|
+
region: config[:region],
|
20
|
+
credentials: Aws::Credentials.new(
|
21
|
+
config[:access_key_id],
|
22
|
+
config[:secret_access_key]
|
23
|
+
)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(prompt, task)
|
28
|
+
response = @client.converse(
|
29
|
+
model_id: config[:model_id] || DEFAULT_MODEL_ID,
|
30
|
+
messages: [
|
31
|
+
{
|
32
|
+
role: 'user',
|
33
|
+
content: [{text: prompt}]
|
34
|
+
}
|
35
|
+
]
|
36
|
+
)
|
37
|
+
|
38
|
+
content = response.dig(:output, :message, :content, 0, :text)
|
39
|
+
|
40
|
+
{
|
41
|
+
raw: content,
|
42
|
+
parsed: begin
|
43
|
+
JSON.parse(content) if content
|
44
|
+
rescue JSON::ParserError
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'openai'
|
2
|
+
require 'json'
|
3
|
+
require_relative '../provider_error'
|
4
|
+
|
5
|
+
module Lluminary
|
6
|
+
module Providers
|
7
|
+
class OpenAI < Base
|
8
|
+
DEFAULT_MODEL = "gpt-3.5-turbo"
|
9
|
+
|
10
|
+
attr_reader :client, :config
|
11
|
+
|
12
|
+
def initialize(**config)
|
13
|
+
super
|
14
|
+
@config = config
|
15
|
+
@client = ::OpenAI::Client.new(access_token: config[:api_key])
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(prompt, task)
|
19
|
+
response = @client.chat(
|
20
|
+
parameters: {
|
21
|
+
model: config[:model] || DEFAULT_MODEL,
|
22
|
+
messages: [{ role: "user", content: prompt }],
|
23
|
+
response_format: { type: "json_object" }
|
24
|
+
}
|
25
|
+
)
|
26
|
+
|
27
|
+
content = response.dig('choices', 0, 'message', 'content')
|
28
|
+
|
29
|
+
{
|
30
|
+
raw: content,
|
31
|
+
parsed: begin
|
32
|
+
JSON.parse(content) if content
|
33
|
+
rescue JSON::ParserError
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Lluminary
|
2
|
+
module Providers
|
3
|
+
class Test < Base
|
4
|
+
def initialize(**config)
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(prompt, task)
|
9
|
+
response = generate_response(task.class.output_fields)
|
10
|
+
raw_response = JSON.pretty_generate(response).gsub(/\n\s*/, '')
|
11
|
+
{
|
12
|
+
raw: raw_response,
|
13
|
+
parsed: JSON.parse(raw_response)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def generate_response(fields)
|
20
|
+
fields.each_with_object({}) do |(name, field), hash|
|
21
|
+
hash[name] = generate_value(field[:type])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_value(type)
|
26
|
+
case type
|
27
|
+
when :string
|
28
|
+
"Test #{type} value"
|
29
|
+
when :integer
|
30
|
+
0
|
31
|
+
else
|
32
|
+
raise "Unsupported type: #{type}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Lluminary
|
4
|
+
class Result
|
5
|
+
attr_reader :raw_response, :output, :prompt
|
6
|
+
|
7
|
+
def initialize(raw_response:, output:, prompt:)
|
8
|
+
@raw_response = raw_response
|
9
|
+
@output = OpenStruct.new(output)
|
10
|
+
@prompt = prompt
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require_relative 'schema_model'
|
3
|
+
|
4
|
+
module Lluminary
|
5
|
+
class JsonValidator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
record.errors.add(:base, "Response must be valid JSON") unless value.is_a?(Hash)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Schema
|
12
|
+
def initialize
|
13
|
+
@fields = {}
|
14
|
+
@validations = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def string(name, description: nil)
|
18
|
+
@fields[name] = { type: :string, description: description }
|
19
|
+
end
|
20
|
+
|
21
|
+
def integer(name, description: nil)
|
22
|
+
@fields[name] = { type: :integer, description: description }
|
23
|
+
end
|
24
|
+
|
25
|
+
def boolean(name, description: nil)
|
26
|
+
@fields[name] = { type: :boolean, description: description }
|
27
|
+
end
|
28
|
+
|
29
|
+
def float(name, description: nil)
|
30
|
+
@fields[name] = { type: :float, description: description }
|
31
|
+
end
|
32
|
+
|
33
|
+
def datetime(name, description: nil)
|
34
|
+
@fields[name] = { type: :datetime, description: description }
|
35
|
+
end
|
36
|
+
|
37
|
+
def fields
|
38
|
+
@fields
|
39
|
+
end
|
40
|
+
|
41
|
+
def validates(*args, **options)
|
42
|
+
@validations << [args, options]
|
43
|
+
end
|
44
|
+
|
45
|
+
def validations_for(field_name)
|
46
|
+
@validations.select { |args, _| args.include?(field_name) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def schema_model
|
50
|
+
@schema_model ||= SchemaModel.build(
|
51
|
+
fields: @fields,
|
52
|
+
validations: @validations
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate(values)
|
57
|
+
instance = schema_model.new(values)
|
58
|
+
instance.valid? ? [] : instance.errors.full_messages
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module Lluminary
|
4
|
+
class SchemaModel
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
attr_reader :attributes
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
@attributes = attributes.transform_keys(&:to_s)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
attrs = attributes.dup
|
15
|
+
attrs.delete('raw_response')
|
16
|
+
"#<#{self.class.name} #{attrs.inspect}>"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build(fields:, validations:)
|
20
|
+
Class.new(self) do
|
21
|
+
# Add accessors for each field
|
22
|
+
fields.each_key do |name|
|
23
|
+
define_method(name) { @attributes[name.to_s] }
|
24
|
+
define_method("#{name}=") { |value| @attributes[name.to_s] = value }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add raw_response field and validation
|
28
|
+
define_method(:raw_response) { @attributes['raw_response'] }
|
29
|
+
define_method(:raw_response=) { |value| @attributes['raw_response'] = value }
|
30
|
+
|
31
|
+
validate do |record|
|
32
|
+
if record.raw_response
|
33
|
+
begin
|
34
|
+
JSON.parse(record.raw_response)
|
35
|
+
rescue JSON::ParserError
|
36
|
+
record.errors.add(:raw_response, "must be valid JSON")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add type validations
|
42
|
+
validate do |record|
|
43
|
+
record.attributes.each do |name, value|
|
44
|
+
next if name == 'raw_response'
|
45
|
+
next if value.nil?
|
46
|
+
|
47
|
+
field = fields[name.to_sym]
|
48
|
+
next unless field
|
49
|
+
|
50
|
+
case field[:type]
|
51
|
+
when :string
|
52
|
+
unless value.is_a?(String)
|
53
|
+
record.errors.add(name, "must be a String")
|
54
|
+
end
|
55
|
+
when :integer
|
56
|
+
unless value.is_a?(Integer)
|
57
|
+
record.errors.add(name, "must be an Integer")
|
58
|
+
end
|
59
|
+
when :boolean
|
60
|
+
unless value == true || value == false
|
61
|
+
record.errors.add(name, "must be true or false")
|
62
|
+
end
|
63
|
+
when :float
|
64
|
+
unless value.is_a?(Float)
|
65
|
+
record.errors.add(name, "must be a float")
|
66
|
+
end
|
67
|
+
when :datetime
|
68
|
+
unless value.is_a?(DateTime)
|
69
|
+
record.errors.add(name, "must be a DateTime")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Add ActiveModel validations
|
76
|
+
validations.each do |args, options|
|
77
|
+
validates(*args, **options)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Set model name for error messages
|
81
|
+
define_singleton_method(:model_name) do
|
82
|
+
ActiveModel::Name.new(self, nil, "SchemaModel")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|