rasti-ai 1.0.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/.github/workflows/ci.yml +44 -0
- data/.gitignore +10 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +107 -0
- data/Rakefile +25 -0
- data/lib/rasti/ai/errors.rb +39 -0
- data/lib/rasti/ai/open_ai/assistant.rb +96 -0
- data/lib/rasti/ai/open_ai/assistant_state.rb +27 -0
- data/lib/rasti/ai/open_ai/client.rb +57 -0
- data/lib/rasti/ai/open_ai/roles.rb +14 -0
- data/lib/rasti/ai/open_ai/tool_serializer.rb +111 -0
- data/lib/rasti/ai/version.rb +5 -0
- data/lib/rasti/ai.rb +24 -0
- data/lib/rasti-ai.rb +1 -0
- data/rasti-ai.gemspec +33 -0
- data/spec/coverage_helper.rb +2 -0
- data/spec/minitest_helper.rb +26 -0
- data/spec/open_ai/assistant_spec.rb +281 -0
- data/spec/open_ai/client_spec.rb +156 -0
- data/spec/open_ai/tool_serializer_spec.rb +297 -0
- data/spec/resources/open_ai/basic_request.json +1 -0
- data/spec/resources/open_ai/basic_response.json +36 -0
- data/spec/resources/open_ai/tool_request.json +1 -0
- data/spec/resources/open_ai/tool_response.json +46 -0
- data/spec/support/helpers/erb.rb +17 -0
- data/spec/support/helpers/resources.rb +23 -0
- metadata +243 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e5f39be89f25780d7f9b33adf06b12109c9ddb482c2aa85220d400cfb945c000
|
4
|
+
data.tar.gz: e4ce69118debdf636aab53cf8fa25cfd233633ac5c499f9f0bc1a4b5bc05294f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2f19e650f0908142aedbc09b9d8d372b24da94979f23464d79de17f8c44cd2c3ed86c666dfc3dc46066bf800ecd572c16cc2b3c321692601d29c76c89a38170e
|
7
|
+
data.tar.gz: a9da7e1802d9877e1171eb9cfa5b602ad642de251131ee6cdbe0bf77cbf810a24817af8846e246a2a57cf47191dc00542d60b2591ded65ef87860e3ec3104d63
|
@@ -0,0 +1,44 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ '**' ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ '**' ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
|
12
|
+
name: Tests
|
13
|
+
runs-on: ubuntu-22.04
|
14
|
+
strategy:
|
15
|
+
matrix:
|
16
|
+
ruby-version: ['2.3', '2.4', '2.5', '2.6', '2.7', '3.0', 'jruby-9.2.9.0']
|
17
|
+
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v3
|
20
|
+
|
21
|
+
- name: Set up Ruby
|
22
|
+
uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: ${{ matrix.ruby-version }}
|
25
|
+
bundler-cache: false
|
26
|
+
|
27
|
+
- name: Install native dependencies for Ruby 3.0
|
28
|
+
if: ${{ startsWith(matrix.ruby-version, '3.') }}
|
29
|
+
run: |
|
30
|
+
sudo apt-get update
|
31
|
+
sudo apt-get install -y libcurl4-openssl-dev
|
32
|
+
|
33
|
+
- name: Configure bundler
|
34
|
+
if: ${{ startsWith(matrix.ruby-version, '3.') }}
|
35
|
+
run: |
|
36
|
+
bundle config set --local force_ruby_platform true
|
37
|
+
bundle install
|
38
|
+
|
39
|
+
- name: Install dependencies for other Ruby versions
|
40
|
+
if: ${{ !startsWith(matrix.ruby-version, '3.') }}
|
41
|
+
run: bundle install
|
42
|
+
|
43
|
+
- name: Run tests
|
44
|
+
run: bundle exec rake
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rasti-ai
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.3.8
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Gabriel Naiman
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# Rasti::AI
|
2
|
+
|
3
|
+
[](https://rubygems.org/gems/rasti-ai)
|
4
|
+
[](https://github.com/gabynaiman/rasti-ai/actions/workflows/ci.yml)
|
5
|
+
|
6
|
+
AI for apps
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'rasti-ai'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install rasti-ai
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Configuration
|
27
|
+
```ruby
|
28
|
+
Rasti::AI.configure do |config|
|
29
|
+
config.logger = Logger.new 'log/development.log'
|
30
|
+
config.openai_api_key = 'abcd12345' # Default ENV['OPENAI_API_KEY']
|
31
|
+
config.openai_default_model = 'gpt-4o-mini' # Default ENV['OPENAI_DEFAULT_MODEL']
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
### Open AI
|
36
|
+
|
37
|
+
#### Assistant
|
38
|
+
```ruby
|
39
|
+
assistant = Rasti::AI::OpenAI::Assistant.new
|
40
|
+
assistant.call 'who is the best player' # => 'The best player is Lionel Messi'
|
41
|
+
```
|
42
|
+
|
43
|
+
#### Tools
|
44
|
+
```ruby
|
45
|
+
class GetCurrentTime
|
46
|
+
def call(params={})
|
47
|
+
Time.now.iso8601
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class GetCurrentWeather
|
52
|
+
def self.form
|
53
|
+
Rasti::Form[location: Rasti::Types::String]
|
54
|
+
end
|
55
|
+
|
56
|
+
def call(params={})
|
57
|
+
response = HTTP.get "https://api.wheater.com/?location=#{params['location']}"
|
58
|
+
response.body.to_s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
tools = [
|
63
|
+
GetCurrentTime.new,
|
64
|
+
GetCurrentWeather.new
|
65
|
+
]
|
66
|
+
|
67
|
+
assistant = Rasti::AI::OpenAI::Assistant.new tools: tools
|
68
|
+
|
69
|
+
assistant.call 'what time is it' # => 'The current time is 3:03 PM on April 28, 2025.'
|
70
|
+
|
71
|
+
assistant.call 'what is the weather in Buenos Aires' # => 'In Buenos Aires it is 15 degrees'
|
72
|
+
```
|
73
|
+
|
74
|
+
#### Context and state
|
75
|
+
```ruby
|
76
|
+
state = Rasti::AI::OpenAI::AssistantState.new context: 'Act as sports journalist'
|
77
|
+
|
78
|
+
assistant = Rasti::AI::OpenAI::Assistant.new state: state
|
79
|
+
|
80
|
+
assistant.call 'who is the best player'
|
81
|
+
|
82
|
+
state.messages
|
83
|
+
# [
|
84
|
+
# {
|
85
|
+
# role: 'system',
|
86
|
+
# content: 'Act as sports journalist'
|
87
|
+
# },
|
88
|
+
# {
|
89
|
+
# role: 'user',
|
90
|
+
# content: 'who is the best player'
|
91
|
+
# },
|
92
|
+
# {
|
93
|
+
# role: 'assistant',
|
94
|
+
# content: 'The best player is Lionel Messi'
|
95
|
+
# }
|
96
|
+
# ]
|
97
|
+
```
|
98
|
+
|
99
|
+
## Contributing
|
100
|
+
|
101
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/gabynaiman/rasti-ai.
|
102
|
+
|
103
|
+
|
104
|
+
## License
|
105
|
+
|
106
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
107
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Rake::TestTask.new(:spec) do |t|
|
5
|
+
t.libs << 'spec'
|
6
|
+
t.libs << 'lib'
|
7
|
+
t.pattern = ENV['DIR'] ? File.join(ENV['DIR'], '**', '*_spec.rb') : 'spec/**/*_spec.rb'
|
8
|
+
t.verbose = false
|
9
|
+
t.warning = false
|
10
|
+
t.loader = nil if ENV['TEST']
|
11
|
+
ENV['TEST'], ENV['LINE'] = ENV['TEST'].split(':') if ENV['TEST'] && !ENV['LINE']
|
12
|
+
t.options = ''
|
13
|
+
t.options << "--name=/#{ENV['NAME']}/ " if ENV['NAME']
|
14
|
+
t.options << "-l #{ENV['LINE']} " if ENV['LINE'] && ENV['TEST']
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: :spec
|
18
|
+
|
19
|
+
desc 'Pry console'
|
20
|
+
task :console do
|
21
|
+
require 'rasti-ai'
|
22
|
+
require 'pry'
|
23
|
+
ARGV.clear
|
24
|
+
Pry.start
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Rasti
|
2
|
+
module AI
|
3
|
+
module Errors
|
4
|
+
|
5
|
+
class RequestFail < StandardError
|
6
|
+
|
7
|
+
attr_reader :url, :body, :response
|
8
|
+
|
9
|
+
def initialize(url, body, response)
|
10
|
+
@url = url
|
11
|
+
@body = body
|
12
|
+
@response = response
|
13
|
+
end
|
14
|
+
|
15
|
+
def message
|
16
|
+
"Request fail\nRequest: #{url}\n#{JSON.pretty_generate(body)}\nResponse: #{response.code}\n#{response.body}"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class ToolSerializationError < StandardError
|
22
|
+
|
23
|
+
def initialize(tool_class)
|
24
|
+
super "Tool serialization error: #{tool_class}"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class UndefinedTool < StandardError
|
30
|
+
|
31
|
+
def initialize(tool_name)
|
32
|
+
super "Undefined tool #{tool_name}"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Rasti
|
2
|
+
module AI
|
3
|
+
module OpenAI
|
4
|
+
class Assistant
|
5
|
+
|
6
|
+
attr_reader :state
|
7
|
+
|
8
|
+
def initialize(client:nil, state:nil, model:nil, tools:[], logger:nil)
|
9
|
+
@client = client || Client.new
|
10
|
+
@state = state || AssistantState.new
|
11
|
+
@model = model
|
12
|
+
@tools = {}
|
13
|
+
@serialized_tools = []
|
14
|
+
@logger = logger || Rasti::AI.logger
|
15
|
+
|
16
|
+
tools.each do |tool|
|
17
|
+
serialization = ToolSerializer.serialize tool.class
|
18
|
+
@tools[serialization[:function][:name]] = tool
|
19
|
+
@serialized_tools << serialization
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(prompt)
|
24
|
+
messages << {
|
25
|
+
role: Roles::USER,
|
26
|
+
content: prompt
|
27
|
+
}
|
28
|
+
|
29
|
+
loop do
|
30
|
+
response = client.chat_completions messages: messages,
|
31
|
+
model: model,
|
32
|
+
tools: serialized_tools
|
33
|
+
|
34
|
+
choice = response['choices'][0]['message']
|
35
|
+
|
36
|
+
if choice['tool_calls']
|
37
|
+
messages << {
|
38
|
+
role: Roles::ASSISTANT,
|
39
|
+
tool_calls: choice['tool_calls']
|
40
|
+
}
|
41
|
+
|
42
|
+
choice['tool_calls'].each do |tool_call|
|
43
|
+
name = tool_call['function']['name']
|
44
|
+
args = JSON.parse tool_call['function']['arguments']
|
45
|
+
|
46
|
+
result = call_tool name, args
|
47
|
+
|
48
|
+
messages << {
|
49
|
+
role: Roles::TOOL,
|
50
|
+
tool_call_id: tool_call['id'],
|
51
|
+
content: result
|
52
|
+
}
|
53
|
+
end
|
54
|
+
else
|
55
|
+
messages << {
|
56
|
+
role: Roles::ASSISTANT,
|
57
|
+
content: choice['content']
|
58
|
+
}
|
59
|
+
|
60
|
+
return choice['content']
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_reader :client, :model, :tools, :serialized_tools, :logger
|
68
|
+
|
69
|
+
def messages
|
70
|
+
state.messages
|
71
|
+
end
|
72
|
+
|
73
|
+
def call_tool(name, args)
|
74
|
+
raise Errors::UndefinedTool.new(name) unless tools.key? name
|
75
|
+
|
76
|
+
key = "#{name} -> #{args}"
|
77
|
+
|
78
|
+
state.fetch(key) do
|
79
|
+
logger.info(self.class) { "Calling function #{name} with #{args}" }
|
80
|
+
|
81
|
+
result = tools[name].call args
|
82
|
+
|
83
|
+
logger.info(self.class) { "Function result: #{result}" }
|
84
|
+
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
rescue => ex
|
89
|
+
logger.warn(self.class) { "Function failed: #{ex.message}\n#{ex.backtrace.join("\n")}" }
|
90
|
+
"Error: #{ex.message}"
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rasti
|
2
|
+
module AI
|
3
|
+
module OpenAI
|
4
|
+
class AssistantState
|
5
|
+
|
6
|
+
attr_reader :messages
|
7
|
+
|
8
|
+
def initialize(context:nil)
|
9
|
+
@messages = []
|
10
|
+
@cache = {}
|
11
|
+
|
12
|
+
messages << {role: Roles::SYSTEM, content: context} if context
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch(key, &block)
|
16
|
+
cache[key] = block.call unless cache.key? key
|
17
|
+
cache[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :cache
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Rasti
|
2
|
+
module AI
|
3
|
+
module OpenAI
|
4
|
+
class Client
|
5
|
+
|
6
|
+
BASE_URL = 'https://api.openai.com/v1'.freeze
|
7
|
+
|
8
|
+
def initialize(api_key:nil, logger:nil)
|
9
|
+
@api_key = api_key || Rasti::AI.openai_api_key
|
10
|
+
@logger = logger || Rasti::AI.logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def chat_completions(messages:, model:nil, tools:[])
|
14
|
+
body = {
|
15
|
+
model: model || Rasti::AI.openai_default_model,
|
16
|
+
messages: messages,
|
17
|
+
tools: tools,
|
18
|
+
tool_choice: tools.empty? ? 'none' : 'auto'
|
19
|
+
}
|
20
|
+
|
21
|
+
post '/chat/completions', body
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :api_key, :logger
|
27
|
+
|
28
|
+
def post(relative_url, body)
|
29
|
+
url = "#{BASE_URL}#{relative_url}"
|
30
|
+
uri = URI(url)
|
31
|
+
|
32
|
+
logger.info(self.class) { "POST #{url}" }
|
33
|
+
logger.debug(self.class) { JSON.pretty_generate(body) }
|
34
|
+
|
35
|
+
request = Net::HTTP::Post.new uri
|
36
|
+
request['Authorization'] = "Bearer #{api_key}"
|
37
|
+
request['Content-Type'] = 'application/json'
|
38
|
+
request.body = JSON.dump(body)
|
39
|
+
|
40
|
+
http = Net::HTTP.new uri.host, uri.port
|
41
|
+
http.use_ssl = uri.scheme == 'https'
|
42
|
+
|
43
|
+
response = http.request request
|
44
|
+
|
45
|
+
logger.info(self.class) { "Response #{response.code}" }
|
46
|
+
logger.debug(self.class) { response.body }
|
47
|
+
|
48
|
+
raise Errors::RequestFail.new(url, body, response) unless response.is_a? Net::HTTPSuccess
|
49
|
+
|
50
|
+
JSON.parse response.body
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Rasti
|
2
|
+
module AI
|
3
|
+
module OpenAI
|
4
|
+
class ToolSerializer
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def serialize(tool_class)
|
8
|
+
{
|
9
|
+
type: 'function',
|
10
|
+
function: serialize_function(tool_class)
|
11
|
+
}
|
12
|
+
|
13
|
+
rescue => ex
|
14
|
+
raise Errors::ToolSerializationError.new(tool_class), cause: ex
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def serialize_function(tool_class)
|
20
|
+
serialization = {
|
21
|
+
name: serialize_name(tool_class)
|
22
|
+
}
|
23
|
+
|
24
|
+
serialization[:description] = normalize_description(tool_class.description) if tool_class.respond_to? :description
|
25
|
+
|
26
|
+
serialization[:parameters] = serialize_form(tool_class.form) if tool_class.respond_to? :form
|
27
|
+
|
28
|
+
serialization
|
29
|
+
end
|
30
|
+
|
31
|
+
def serialize_name(tool_class)
|
32
|
+
Inflecto.underscore Inflecto.demodulize(tool_class.name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def serialize_form(form_class)
|
36
|
+
serialized_attributes = form_class.attributes.each_with_object({}) do |attribute, hash|
|
37
|
+
hash[attribute.name] = serialize_attribute attribute
|
38
|
+
end
|
39
|
+
|
40
|
+
serialization = {
|
41
|
+
type: 'object',
|
42
|
+
properties: serialized_attributes
|
43
|
+
}
|
44
|
+
|
45
|
+
required_attributes = form_class.attributes.select { |a| a.option(:required) }
|
46
|
+
|
47
|
+
serialization[:required] = required_attributes.map(&:name) unless required_attributes.empty?
|
48
|
+
|
49
|
+
serialization
|
50
|
+
end
|
51
|
+
|
52
|
+
def serialize_attribute(attribute)
|
53
|
+
serialization = {}
|
54
|
+
|
55
|
+
if attribute.option(:description)
|
56
|
+
serialization[:description] = normalize_description attribute.option(:description)
|
57
|
+
end
|
58
|
+
|
59
|
+
serialization.merge! serialize_type(attribute.type)
|
60
|
+
|
61
|
+
serialization
|
62
|
+
end
|
63
|
+
|
64
|
+
def serialize_type(type)
|
65
|
+
if type == Types::String
|
66
|
+
{type: 'string'}
|
67
|
+
|
68
|
+
elsif type == Types::Integer
|
69
|
+
{type: 'integer'}
|
70
|
+
|
71
|
+
elsif type == Types::Float
|
72
|
+
{type: 'number'}
|
73
|
+
|
74
|
+
elsif type == Types::Boolean
|
75
|
+
{type: 'boolean'}
|
76
|
+
|
77
|
+
elsif type.is_a? Types::Time
|
78
|
+
{
|
79
|
+
type: 'string',
|
80
|
+
format: 'date'
|
81
|
+
}
|
82
|
+
|
83
|
+
elsif type.is_a? Types::Enum
|
84
|
+
{
|
85
|
+
type: 'string',
|
86
|
+
enum: type.values
|
87
|
+
}
|
88
|
+
|
89
|
+
elsif type.is_a? Types::Array
|
90
|
+
{
|
91
|
+
type: 'array',
|
92
|
+
items: serialize_type(type.type)
|
93
|
+
}
|
94
|
+
|
95
|
+
elsif type.is_a? Types::Model
|
96
|
+
serialize_form(type.model)
|
97
|
+
|
98
|
+
else
|
99
|
+
raise "Type not serializable #{type}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def normalize_description(description)
|
104
|
+
description.split("\n").map(&:strip).join(' ').strip
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/rasti/ai.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'multi_require'
|
2
|
+
require 'rasti-form'
|
3
|
+
require 'class_config'
|
4
|
+
require 'inflecto'
|
5
|
+
require 'net/http'
|
6
|
+
require 'uri'
|
7
|
+
require 'json'
|
8
|
+
require 'logger'
|
9
|
+
|
10
|
+
module Rasti
|
11
|
+
module AI
|
12
|
+
|
13
|
+
extend MultiRequire
|
14
|
+
extend ClassConfig
|
15
|
+
|
16
|
+
require_relative_pattern 'ai/**/*'
|
17
|
+
|
18
|
+
attr_config :logger, Logger.new(STDOUT)
|
19
|
+
|
20
|
+
attr_config :openai_api_key, ENV['OPENAI_API_KEY']
|
21
|
+
attr_config :openai_default_model, ENV['OPENAI_DEFAULT_MODEL']
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/rasti-ai.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'rasti/ai'
|
data/rasti-ai.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rasti/ai/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'rasti-ai'
|
8
|
+
spec.version = Rasti::AI::VERSION
|
9
|
+
spec.authors = ['Gabriel Naiman']
|
10
|
+
spec.email = ['gabynaiman@gmail.com']
|
11
|
+
spec.summary = 'AI for apps'
|
12
|
+
spec.description = 'AI for apps'
|
13
|
+
spec.homepage = 'https://github.com/gabynaiman/rasti-ai'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'multi_require', '~> 1.0'
|
22
|
+
spec.add_runtime_dependency 'rasti-form', '~> 6.0'
|
23
|
+
spec.add_runtime_dependency 'inflecto', '~> 0.0'
|
24
|
+
spec.add_runtime_dependency 'class_config', '~> 0.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'rake', '~> 12.0'
|
27
|
+
spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
|
28
|
+
spec.add_development_dependency 'minitest-colorin', '~> 0.1'
|
29
|
+
spec.add_development_dependency 'minitest-line', '~> 0.6'
|
30
|
+
spec.add_development_dependency 'simplecov', '~> 0.12'
|
31
|
+
spec.add_development_dependency 'pry-nav', '~> 0.2'
|
32
|
+
spec.add_development_dependency 'webmock', '~> 3.0'
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'coverage_helper'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/colorin'
|
4
|
+
require 'webmock/minitest'
|
5
|
+
require 'pry-nav'
|
6
|
+
require 'rasti-ai'
|
7
|
+
require 'securerandom'
|
8
|
+
|
9
|
+
require_relative 'support/helpers/erb'
|
10
|
+
require_relative 'support/helpers/resources'
|
11
|
+
|
12
|
+
|
13
|
+
Rasti::AI.configure do |config|
|
14
|
+
config.logger.level = Logger::FATAL
|
15
|
+
|
16
|
+
config.openai_api_key = 'test_api_key'
|
17
|
+
config.openai_default_model = 'gpt-test'
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
class Minitest::Test
|
22
|
+
|
23
|
+
include Support::Helpers::ERB
|
24
|
+
include Support::Helpers::Resources
|
25
|
+
|
26
|
+
end
|