active_model-validations-ai 0.1.0 → 0.2.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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +41 -4
- data/lib/active_model/validations/ai/locale/en.yml +1 -0
- data/lib/active_model/validations/ai/version.rb +1 -1
- data/lib/active_model/validations/ai.rb +56 -4
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d44eee74a5bd787b4ef3fa979b836d38ab849934a6c475c511a05d21c3d2568b
|
4
|
+
data.tar.gz: da9537a5b8c063d64377f8a9fe6f774903a413833b5201c95e03990e6c762a3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85afbbd6f52c16ed8d61eca71acfaa3f1aa02e3587126e7c1245304f3718e9f4b99572c7c233da1a8d1c3bf9210be61820c5f561b839e5a16d7f820c62a615d5
|
7
|
+
data.tar.gz: b44e93ca478d587b154377bb93ded494c2ab787e681e119b6065eed2e1980324581c388a729c00ec14c45af2c0dba1faa58522e8855198fd82eb54c412c2a969
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -24,17 +24,54 @@ end
|
|
24
24
|
|
25
25
|
The policy and attribute value is sent to OpenAI with our prompt to be validated with GPT. AI generally isn't near zero-cost like other APIs, so this can be expensive to run. Use with some caution.
|
26
26
|
|
27
|
-
##
|
27
|
+
## Configuration
|
28
|
+
|
29
|
+
We're using `ruby-openai` as the OpenAI client under the hood, see the documentation here for how to configure the access token.
|
30
|
+
|
31
|
+
https://github.com/alexrudall/ruby-openai#with-config
|
32
|
+
|
33
|
+
Note: if you're using [Bullet Train](bullettrain.co), fill in
|
34
|
+
`ENV["OPENAI_ACCESS_TOKEN"]` and OpenAI will already be configured for you.
|
35
|
+
|
36
|
+
### Setting request timeout
|
37
|
+
|
38
|
+
For validations we know we're not waiting for a complicated chat response, so we timeout requests after 3 seconds instead of `ruby-openai`'s 120 second default. If you need to, you can change the timeout with this:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
ActiveModel::Validations::AI.request_timeout = 5 # seconds
|
42
|
+
```
|
43
|
+
|
44
|
+
### Handling errors
|
45
|
+
|
46
|
+
We capture any `Faraday::Error` exceptions and report them. If we're in a Rails app we report via `Rails.error` otherwise it's via `ActiveSupport::ErrorReporter.new`. This gives you access to report to your existing error reporting service automatically.
|
47
|
+
|
48
|
+
You can also change the error reporter if need be:
|
28
49
|
|
29
|
-
|
50
|
+
```ruby
|
51
|
+
ActiveModel::Validations::AI.error_reporter = ActiveSupport::ErrorReporter.new
|
52
|
+
```
|
53
|
+
|
54
|
+
### Testing
|
55
|
+
|
56
|
+
We've tested that our requests to OpenAI work so you don't have to pay that cost in your apps tests.
|
57
|
+
|
58
|
+
Add this in your `test/test_helper.rb` or `spec/spec_helper.rb`:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
ActiveModel::Validations::AI.stub_with valid: true
|
62
|
+
```
|
63
|
+
|
64
|
+
Which prevent any requests from firing and assumes that any validation from this gem is valid.
|
65
|
+
|
66
|
+
## Installation
|
30
67
|
|
31
68
|
Install the gem and add to the application's Gemfile by executing:
|
32
69
|
|
33
|
-
$ bundle add
|
70
|
+
$ bundle add active_model-validations-ai
|
34
71
|
|
35
72
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
36
73
|
|
37
|
-
$ gem install
|
74
|
+
$ gem install active_model-validations-ai
|
38
75
|
|
39
76
|
## Development
|
40
77
|
|
@@ -9,20 +9,64 @@ require_relative "ai/version"
|
|
9
9
|
module ActiveModel
|
10
10
|
module Validations
|
11
11
|
module AI
|
12
|
+
singleton_class.attr_accessor :error_reporter
|
13
|
+
@error_reporter = ActiveSupport::ErrorReporter.new
|
14
|
+
|
15
|
+
singleton_class.attr_accessor :request_timeout
|
16
|
+
@request_timeout = 3 # seconds
|
17
|
+
|
12
18
|
def self.client
|
13
|
-
@client ||= OpenAI::Client.new
|
19
|
+
@client ||= OpenAI::Client.new(request_timeout: request_timeout)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stub_with(valid:)
|
23
|
+
old_client, @client = @client, MockGPT.new(request_timeout: request_timeout, valid: valid)
|
24
|
+
yield if block_given?
|
25
|
+
ensure
|
26
|
+
@client = old_client if block_given?
|
27
|
+
end
|
28
|
+
|
29
|
+
class MockGPT
|
30
|
+
singleton_class.attr_reader :response
|
31
|
+
|
32
|
+
def self.set(**response)
|
33
|
+
old_response, @response = @response, response
|
34
|
+
yield
|
35
|
+
ensure
|
36
|
+
@response = old_response
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(request_timeout:, **response)
|
40
|
+
@request_timeout, @response = request_timeout, response
|
41
|
+
end
|
42
|
+
attr_reader :request_timeout
|
43
|
+
|
44
|
+
def chat(parameters:)
|
45
|
+
raise Faraday::Error if request_timeout&.zero?
|
46
|
+
# parameters => { model:, messages: [{content:}] } # TODO: Replace with this pattern matching on Ruby 3.0+.
|
47
|
+
parameters.dig(:model) or raise ArgumentError
|
48
|
+
parameters.dig(:messages, 0, :content) or raise ArgumentError
|
49
|
+
{ "choices" => [{ "message" => { "content" => response.to_json } }] }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def response
|
55
|
+
self.class.response || @response
|
56
|
+
end
|
14
57
|
end
|
15
58
|
end
|
16
59
|
|
17
60
|
class ComplianceValidator < ::ActiveModel::EachValidator
|
18
61
|
def validate_each(record, name, value)
|
19
|
-
record.errors.add(name, :
|
62
|
+
AI.error_reporter.handle(Faraday::Error, fallback: -> { record.errors.add(name, :errored, **options) }) do
|
63
|
+
record.errors.add(name, :noncompliant, **options) unless valid?(value)
|
64
|
+
end
|
20
65
|
end
|
21
66
|
|
22
67
|
private
|
23
68
|
|
24
69
|
def valid?(value)
|
25
|
-
# TODO: Figure out how to timeout a request or handle downtime, and invalidate the attribute.
|
26
70
|
response = AI.client.chat(
|
27
71
|
parameters: {
|
28
72
|
model: "gpt-3.5-turbo",
|
@@ -34,7 +78,7 @@ module ActiveModel
|
|
34
78
|
end
|
35
79
|
|
36
80
|
def prompt_for(value)
|
37
|
-
"Validate this input '#{value}' against this policy '#{policy}'.
|
81
|
+
"Validate this input '#{value}' against this policy '#{policy}'. Respond only in JSON formatted like this '{ \"valid\": true }'."
|
38
82
|
end
|
39
83
|
|
40
84
|
def policy
|
@@ -53,3 +97,11 @@ end
|
|
53
97
|
ActiveSupport.on_load :i18n do
|
54
98
|
I18n.load_path << File.expand_path("ai/locale/en.yml", __dir__)
|
55
99
|
end
|
100
|
+
|
101
|
+
if defined?(Rails::Railtie)
|
102
|
+
class ActiveModel::Validations::AI::Railtie < Rails::Railtie
|
103
|
+
initializer "error_reporter.setup" do
|
104
|
+
ActiveModel::Validations::AI.error_reporter = Rails.error
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_model-validations-ai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kasper Timm Hansen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-11-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activemodel
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '7.0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
27
|
+
version: '7.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: ruby-openai
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -71,7 +71,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
72
72
|
- - ">="
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
74
|
+
version: 3.0.0
|
75
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
76
|
requirements:
|
77
77
|
- - ">="
|