raix-rails 0.1.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '085212953495c6a0683c7db0fc65a89b35d7d803b2d82cd46b09e3bfb61d144e'
4
- data.tar.gz: 5a931715095c683de5702c31d65605ee371c21b5db9793a23ba2064030acae7d
3
+ metadata.gz: 49d83bc8390b10829bb062e0bd107eb3e2773ca59e5d61bde85c8f783621a7f4
4
+ data.tar.gz: ed6ca5031a53dc185b91ef1e745d235258b7965642a18ec4e45940aa74eaa7df
5
5
  SHA512:
6
- metadata.gz: 6661686a42c89a188783c0f0d82462b7f0d897794cd9b7fa0b8bbcfd1ccd564627fa3c586aad2b57fd4e56198eb2a162a619d0290bb7e042745400cbc3b1799b
7
- data.tar.gz: 48e37e51152a9ab718789089bfa572a4b79c3f05f8e453353319714ed03a7205bbbeafbb8f6ef1eaec25d85004003070847841142c2885f042204c934b92b4b3
6
+ metadata.gz: cfca0e561cc16dda50d53f606fba7b394a7aa14e6e7ee6ceec4352a8522d9b39c042d4b04aaaf7675485366353d45b91898d9cc7ec8e3844cbf8edc58f29de56
7
+ data.tar.gz: 26344feccfa39fe084ecbdf5fca52150815e6f728c5b857123a59ecedc8318c0f9ee4546ee3b011165d2f9cd079ee1f6dbf05bc6c34206ae17dbdbb268e7db2a
data/.rubocop.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.6
2
+ SuggestExtensions: false
3
+ TargetRubyVersion: 3.2.1
3
4
 
4
5
  Style/StringLiterals:
5
6
  Enabled: true
@@ -11,3 +12,21 @@ Style/StringLiteralsInInterpolation:
11
12
 
12
13
  Layout/LineLength:
13
14
  Max: 120
15
+
16
+ Metrics/BlockLength:
17
+ Enabled: false
18
+
19
+ Metrics/MethodLength:
20
+ Enabled: false
21
+
22
+ Metrics/ModuleLength:
23
+ Enabled: false
24
+
25
+ Metrics/AbcSize:
26
+ Enabled: false
27
+
28
+ Metrics/CyclomaticComplexity:
29
+ Enabled: false
30
+
31
+ Metrics/PerceivedComplexity:
32
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.2
data/CHANGELOG.md CHANGED
@@ -2,4 +2,9 @@
2
2
 
3
3
  ## [0.1.0] - 2024-04-03
4
4
 
5
- - Initial release
5
+ - Initial release, placeholder gem
6
+
7
+ ## [0.2.0] - tbd
8
+ - adds `ChatCompletion` module
9
+ - adds `PromptDeclarations` module
10
+ - adds `FunctionDispatch` module
data/Gemfile CHANGED
@@ -5,8 +5,20 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in raix-rails.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
8
+ gem "activesupport", ">= 6.0"
9
+ gem "faraday-retry"
10
+ gem "open_router", "~> 0.3"
11
+ gem "ruby-openai", "~> 7.0"
9
12
 
10
- gem "rspec", "~> 3.0"
11
-
12
- gem "rubocop", "~> 1.21"
13
+ group :development do
14
+ gem "dotenv", ">= 2"
15
+ gem "guard"
16
+ gem "guard-rspec"
17
+ gem "pry", ">= 0.14"
18
+ gem "rake", "~> 13.0"
19
+ gem "rspec", "~> 3.0"
20
+ gem "rubocop", "~> 1.21"
21
+ gem "solargraph-rails", "~> 0.2.0.pre"
22
+ gem "sorbet"
23
+ gem "tapioca", require: false
24
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,220 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ raix-rails (0.3.0)
5
+ activesupport (>= 6.0)
6
+ open_router (~> 0.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (7.1.3.2)
12
+ base64
13
+ bigdecimal
14
+ concurrent-ruby (~> 1.0, >= 1.0.2)
15
+ connection_pool (>= 2.2.5)
16
+ drb
17
+ i18n (>= 1.6, < 2)
18
+ minitest (>= 5.1)
19
+ mutex_m
20
+ tzinfo (~> 2.0)
21
+ ast (2.4.2)
22
+ backport (1.2.0)
23
+ base64 (0.2.0)
24
+ benchmark (0.3.0)
25
+ bigdecimal (3.1.7)
26
+ coderay (1.1.3)
27
+ concurrent-ruby (1.2.3)
28
+ connection_pool (2.4.1)
29
+ diff-lcs (1.5.1)
30
+ dotenv (3.1.0)
31
+ drb (2.2.1)
32
+ e2mmap (0.1.0)
33
+ erubi (1.12.0)
34
+ event_stream_parser (1.0.0)
35
+ faraday (2.9.0)
36
+ faraday-net_http (>= 2.0, < 3.2)
37
+ faraday-multipart (1.0.4)
38
+ multipart-post (~> 2)
39
+ faraday-net_http (3.1.0)
40
+ net-http
41
+ faraday-retry (2.2.1)
42
+ faraday (~> 2.0)
43
+ ffi (1.16.3)
44
+ formatador (1.1.0)
45
+ guard (2.18.1)
46
+ formatador (>= 0.2.4)
47
+ listen (>= 2.7, < 4.0)
48
+ lumberjack (>= 1.0.12, < 2.0)
49
+ nenv (~> 0.1)
50
+ notiffany (~> 0.0)
51
+ pry (>= 0.13.0)
52
+ shellany (~> 0.0)
53
+ thor (>= 0.18.1)
54
+ guard-compat (1.2.1)
55
+ guard-rspec (4.7.3)
56
+ guard (~> 2.1)
57
+ guard-compat (~> 1.1)
58
+ rspec (>= 2.99.0, < 4.0)
59
+ i18n (1.14.4)
60
+ concurrent-ruby (~> 1.0)
61
+ jaro_winkler (1.5.6)
62
+ json (2.7.1)
63
+ kramdown (2.4.0)
64
+ rexml
65
+ kramdown-parser-gfm (1.1.0)
66
+ kramdown (~> 2.0)
67
+ language_server-protocol (3.17.0.3)
68
+ listen (3.9.0)
69
+ rb-fsevent (~> 0.10, >= 0.10.3)
70
+ rb-inotify (~> 0.9, >= 0.9.10)
71
+ lumberjack (1.2.10)
72
+ method_source (1.1.0)
73
+ minitest (5.22.3)
74
+ multipart-post (2.4.1)
75
+ mutex_m (0.2.0)
76
+ nenv (0.3.0)
77
+ net-http (0.4.1)
78
+ uri
79
+ netrc (0.11.0)
80
+ nokogiri (1.16.4-arm64-darwin)
81
+ racc (~> 1.4)
82
+ nokogiri (1.16.4-x86_64-linux)
83
+ racc (~> 1.4)
84
+ notiffany (0.1.3)
85
+ nenv (~> 0.1)
86
+ shellany (~> 0.0)
87
+ open_router (0.3.0)
88
+ activesupport (>= 6.0)
89
+ dotenv (>= 2)
90
+ faraday (>= 1)
91
+ faraday-multipart (>= 1)
92
+ parallel (1.24.0)
93
+ parser (3.3.0.5)
94
+ ast (~> 2.4.1)
95
+ racc
96
+ prism (0.24.0)
97
+ pry (0.14.2)
98
+ coderay (~> 1.1)
99
+ method_source (~> 1.0)
100
+ racc (1.7.3)
101
+ rainbow (3.1.1)
102
+ rake (13.2.0)
103
+ rb-fsevent (0.11.2)
104
+ rb-inotify (0.11.1)
105
+ ffi (~> 1.0)
106
+ rbi (0.1.10)
107
+ prism (>= 0.18.0, < 0.25)
108
+ sorbet-runtime (>= 0.5.9204)
109
+ rbs (2.8.4)
110
+ regexp_parser (2.9.0)
111
+ reverse_markdown (2.1.1)
112
+ nokogiri
113
+ rexml (3.2.6)
114
+ rspec (3.13.0)
115
+ rspec-core (~> 3.13.0)
116
+ rspec-expectations (~> 3.13.0)
117
+ rspec-mocks (~> 3.13.0)
118
+ rspec-core (3.13.0)
119
+ rspec-support (~> 3.13.0)
120
+ rspec-expectations (3.13.0)
121
+ diff-lcs (>= 1.2.0, < 2.0)
122
+ rspec-support (~> 3.13.0)
123
+ rspec-mocks (3.13.0)
124
+ diff-lcs (>= 1.2.0, < 2.0)
125
+ rspec-support (~> 3.13.0)
126
+ rspec-support (3.13.1)
127
+ rubocop (1.62.1)
128
+ json (~> 2.3)
129
+ language_server-protocol (>= 3.17.0)
130
+ parallel (~> 1.10)
131
+ parser (>= 3.3.0.2)
132
+ rainbow (>= 2.2.2, < 4.0)
133
+ regexp_parser (>= 1.8, < 3.0)
134
+ rexml (>= 3.2.5, < 4.0)
135
+ rubocop-ast (>= 1.31.1, < 2.0)
136
+ ruby-progressbar (~> 1.7)
137
+ unicode-display_width (>= 2.4.0, < 3.0)
138
+ rubocop-ast (1.31.2)
139
+ parser (>= 3.3.0.4)
140
+ ruby-openai (7.0.1)
141
+ event_stream_parser (>= 0.3.0, < 2.0.0)
142
+ faraday (>= 1)
143
+ faraday-multipart (>= 1)
144
+ ruby-progressbar (1.13.0)
145
+ shellany (0.0.1)
146
+ solargraph (0.50.0)
147
+ backport (~> 1.2)
148
+ benchmark
149
+ bundler (~> 2.0)
150
+ diff-lcs (~> 1.4)
151
+ e2mmap
152
+ jaro_winkler (~> 1.5)
153
+ kramdown (~> 2.3)
154
+ kramdown-parser-gfm (~> 1.1)
155
+ parser (~> 3.0)
156
+ rbs (~> 2.0)
157
+ reverse_markdown (~> 2.0)
158
+ rubocop (~> 1.38)
159
+ thor (~> 1.0)
160
+ tilt (~> 2.0)
161
+ yard (~> 0.9, >= 0.9.24)
162
+ solargraph-rails (0.2.2.pre.4)
163
+ activesupport
164
+ solargraph (>= 0.41.1)
165
+ sorbet (0.5.11346)
166
+ sorbet-static (= 0.5.11346)
167
+ sorbet-runtime (0.5.11346)
168
+ sorbet-static (0.5.11346-universal-darwin)
169
+ sorbet-static (0.5.11346-x86_64-linux)
170
+ sorbet-static-and-runtime (0.5.11346)
171
+ sorbet (= 0.5.11346)
172
+ sorbet-runtime (= 0.5.11346)
173
+ spoom (1.3.0)
174
+ erubi (>= 1.10.0)
175
+ prism (>= 0.19.0)
176
+ sorbet-static-and-runtime (>= 0.5.10187)
177
+ thor (>= 0.19.2)
178
+ tapioca (0.13.3)
179
+ bundler (>= 2.2.25)
180
+ netrc (>= 0.11.0)
181
+ parallel (>= 1.21.0)
182
+ rbi (>= 0.1.4, < 0.2)
183
+ sorbet-static-and-runtime (>= 0.5.11087)
184
+ spoom (>= 1.2.0)
185
+ thor (>= 1.2.0)
186
+ yard-sorbet
187
+ thor (1.3.1)
188
+ tilt (2.3.0)
189
+ tzinfo (2.0.6)
190
+ concurrent-ruby (~> 1.0)
191
+ unicode-display_width (2.5.0)
192
+ uri (0.13.0)
193
+ yard (0.9.36)
194
+ yard-sorbet (0.8.1)
195
+ sorbet-runtime (>= 0.5)
196
+ yard (>= 0.9)
197
+
198
+ PLATFORMS
199
+ arm64-darwin-21
200
+ x86_64-linux
201
+
202
+ DEPENDENCIES
203
+ activesupport (>= 6.0)
204
+ dotenv (>= 2)
205
+ faraday-retry
206
+ guard
207
+ guard-rspec
208
+ open_router (~> 0.3)
209
+ pry (>= 0.14)
210
+ raix-rails!
211
+ rake (~> 13.0)
212
+ rspec (~> 3.0)
213
+ rubocop (~> 1.21)
214
+ ruby-openai (~> 7.0)
215
+ solargraph-rails (~> 0.2.0.pre)
216
+ sorbet
217
+ tapioca
218
+
219
+ BUNDLED WITH
220
+ 2.4.12
data/Guardfile ADDED
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A sample Guardfile
4
+ # More info at https://github.com/guard/guard#readme
5
+
6
+ ## Uncomment and set this to only include directories you want to watch
7
+ # directories %w(app lib config test spec features) \
8
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
9
+
10
+ ## Note: if you are using the `directories` clause above and you are not
11
+ ## watching the project directory ('.'), then you will want to move
12
+ ## the Guardfile to a watched dir and symlink it back, e.g.
13
+ #
14
+ # $ mkdir config
15
+ # $ mv Guardfile config/
16
+ # $ ln -s config/Guardfile .
17
+ #
18
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
+
20
+ # NOTE: The cmd option is now required due to the increasing number of ways
21
+ # rspec may be run, below are examples of the most common uses.
22
+ # * bundler: 'bundle exec rspec'
23
+ # * bundler binstubs: 'bin/rspec'
24
+ # * spring: 'bin/rspec' (This will use spring if running and you have
25
+ # installed the spring binstubs per the docs)
26
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
27
+ # * 'just' rspec: 'rspec'
28
+
29
+ guard :rspec, cmd: "bundle exec rspec" do
30
+ require "guard/rspec/dsl"
31
+ dsl = Guard::RSpec::Dsl.new(self)
32
+
33
+ # Feel free to open issues for suggestions and improvements
34
+
35
+ # RSpec files
36
+ rspec = dsl.rspec
37
+ watch(rspec.spec_helper) { rspec.spec_dir }
38
+ watch(rspec.spec_support) { rspec.spec_dir }
39
+ watch(rspec.spec_files)
40
+
41
+ # Ruby files
42
+ ruby = dsl.ruby
43
+ dsl.watch_spec_files_for(ruby.lib_files)
44
+
45
+ # Rails files
46
+ rails = dsl.rails(view_extensions: %w[erb haml slim])
47
+ dsl.watch_spec_files_for(rails.app_files)
48
+ dsl.watch_spec_files_for(rails.views)
49
+
50
+ watch(rails.controllers) do |m|
51
+ [
52
+ rspec.spec.call("routing/#{m[1]}_routing"),
53
+ rspec.spec.call("controllers/#{m[1]}_controller"),
54
+ rspec.spec.call("acceptance/#{m[1]}")
55
+ ]
56
+ end
57
+
58
+ # Rails config changes
59
+ watch(rails.spec_helper) { rspec.spec_dir }
60
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
61
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
62
+
63
+ # Capybara features specs
64
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
65
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
66
+
67
+ # Turnip features and steps
68
+ watch(%r{^spec/acceptance/(.+)\.feature$})
69
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
70
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
71
+ end
72
+ end
data/README.md CHANGED
@@ -1,34 +1,250 @@
1
- # Raix::Rails
1
+ # Ruby AI eXtensions for Rails
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ ## What's Raix for Rails?
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/raix/rails`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ Raix (pronounced "ray" because the x is silent) is a library that gives you everything you need to add discrete large-language model (LLM) AI components to your Rails applications. Raix consists of proven code that has been extracted from [Olympia](https://olympia.chat), the world's leading virtual AI team platform, and probably one of the biggest and most successful AI chat projects written completely in Ruby.
6
6
 
7
- ## Installation
7
+ Understanding the how to use discrete AI components in otherwise normal code is key to productively leveraging Raix, and the subject of a book written by Raix's author Obie Fernandez, titled [Patterns of Application Development Using AI](https://leanpub.com/patterns-of-application-development-using-ai). You can easily support the ongoing development of this project by buying the book at Leanpub.
8
+
9
+ At the moment, Raix natively supports use of either OpenAI or OpenRouter as its underlying AI provider. Eventually you will be able to specify your AI provider via an adapter, kind of like ActiveRecord maps to databases. Note that you can also use Raix to add AI capabilities to non-Rails applications as long as you include ActiveSupport as a dependency. Extracting the base code to its own standalone library without Rails dependencies is on the roadmap, but not a high priority.
10
+
11
+ ### Chat Completions
12
+
13
+ Raix consists of three modules that can be mixed in to Ruby classes to give them AI powers. The first (and mandatory) module is `ChatCompletion`, which provides `transcript` and `chat_completion` methods.
14
+
15
+ ```ruby
16
+ class MeaningOfLife
17
+ include Raix::ChatCompletion
18
+ end
19
+
20
+ >> ai = MeaningOfLife.new
21
+ >> ai.transcript << { user: "What is the meaning of life?" }
22
+ >> ai.chat_completion
23
+
24
+ => "The question of the meaning of life is one of the most profound and enduring inquiries in philosophy, religion, and science.
25
+ Different perspectives offer various answers..."
26
+
27
+ ```
28
+
29
+ #### Transcript Format
30
+
31
+ The transcript accepts both abbreviated and standard OpenAI message hash formats. The abbreviated format, suitable for system, assistant, and user messages is simply a mapping of `role => content`, as show in the example above.
32
+
33
+ ```ruby
34
+ transcript << { user: "What is the meaning of life?" }
35
+ ```
36
+
37
+ As mentioned, Raix also understands standard OpenAI messages hashes. The previous example could be written as:
38
+
39
+ ```ruby
40
+ transcript << { role: "user", content: "What is the meaning of life?" }
41
+ ```
42
+
43
+ One of the advantages of OpenRouter and the reason that it is used by default by this library is that it handles mapping message formats from the OpenAI standard to whatever other model you're wanting to use (Anthropic, Cohere, etc.)
44
+
45
+ ### Use of Tools/Functions
46
+
47
+ The second (optional) module that you can add to your Ruby classes after `ChatCompletion` is `FunctionDispatch`. It lets you declare and implement functions to be called at the AI's discretion as part of a chat completion "loop" in a declarative, Rails-like "DSL" fashion.
48
+
49
+ Most end-user facing AI components that include functions should be invoked using `chat_completion(loop: true)`, so that the results of each function call are added to the transcript and chat completion is triggered again. The looping will continue until the AI generates a plain text response.
50
+
51
+ ```ruby
52
+ class WhatIsTheWeather
53
+ include Raix::ChatCompletion
54
+ include Raix::FunctionDispatch
55
+
56
+ function :check_weather, "Check the weather for a location", location: { type: "string" } do |arguments|
57
+ "The weather in #{arguments[:location]} is hot and sunny"
58
+ end
59
+ end
60
+
61
+ RSpec.describe WhatIsTheWeather do
62
+ subject { described_class.new }
63
+
64
+ it "can call a function and loop to provide text response" do
65
+ subject.transcript << { user: "What is the weather in Zipolite, Oaxaca?" }
66
+ response = subject.chat_completion(openai: "gpt-4o", loop: true)
67
+ expect(response).to include("hot and sunny")
68
+ end
69
+ end
70
+ ```
71
+
72
+ #### Manually Stopping a Loop
73
+
74
+ To loop AI components that don't interact with end users, at least one function block should invoke `stop_looping!` whenever you're ready to stop processing.
75
+
76
+ ```ruby
77
+ class OrderProcessor
78
+ include Raix::ChatCompletion
79
+ include Raix::FunctionDispatch
80
+
81
+ SYSTEM_DIRECTIVE = "You are an order processor, tasked with order validation, inventory check,
82
+ payment processing, and shipping."
83
+
84
+ attr_accessor :order
85
+
86
+ def initialize(order)
87
+ self.order = order
88
+ transcript << { system: SYSTEM_DIRECTIVE }
89
+ transcript << { user: order.to_json }
90
+ end
91
+
92
+ def perform
93
+ # will continue looping until `stop_looping!` is called
94
+ chat_completion(loop: true)
95
+ end
96
+
97
+
98
+ # implementation of functions that can be called by the AI
99
+ # entirely at its discretion, depending on the needs of the order.
100
+ # The return value of each `perform` method will be added to the
101
+ # transcript of the conversation as a function result.
102
+
103
+ function :validate_order do
104
+ OrderValidationWorker.perform(@order)
105
+ end
106
+
107
+ function :check_inventory do
108
+ InventoryCheckWorker.perform(@order)
109
+ end
110
+
111
+ function :process_payment do
112
+ PaymentProcessingWorker.perform(@order)
113
+ end
114
+
115
+ function :schedule_shipping do
116
+ ShippingSchedulerWorker.perform(@order)
117
+ end
8
118
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
119
+ function :send_confirmation do
120
+ OrderConfirmationWorker.perform(@order)
121
+ end
122
+
123
+ function :finished_processing do
124
+ order.update!(transcript:, processed_at: Time.current)
125
+ stop_looping!
126
+ end
127
+ end
128
+ ```
129
+
130
+ ### Prompt Declarations
131
+
132
+ The third (also optional) module that you can add mix in along with `ChatCompletion` is `PromptDeclarations`. It provides the ability to declare a "Prompt Chain" (series of prompts to be called in a sequence), and also features a declarative, Rails-like "DSL" of its own. Prompts can be defined inline or delegate to callable prompt objects, which themselves implement `ChatCompletion`.
133
+
134
+ The following example is a rough excerpt of the main "Conversation Loop" in Olympia, which pre-processes user messages to check for
135
+ the presence of URLs and scan memory before submitting as a prompt to GPT-4. Note that prompt declarations are executed in the order
136
+ that they are declared. The `FetchUrlCheck` callable prompt class is included for instructional purposes. Note that it is passed the
137
+ an instance of the object that is calling it in its initializer as its `context`. The passing of context means that you can assemble
138
+ composite prompt structures of arbitrary depth.
139
+
140
+ ```ruby
141
+ class PromptSubscriber
142
+ include Raix::ChatCompletion
143
+ include Raix::PromptDeclarations
144
+
145
+ attr_accessor :conversation, :bot_message, :user_message
146
+
147
+ # many other declarations ommitted...
148
+
149
+ prompt call: FetchUrlCheck
150
+
151
+ prompt call: MemoryScan
152
+
153
+ prompt text: -> { user_message.content }, stream: -> { ReplyStream.new(self) }, until: -> { bot_message.complete? }
154
+
155
+ def initialize(conversation)
156
+ self.conversation = conversation
157
+ end
158
+
159
+ def message_created(user_message)
160
+ self.user_message = user_message
161
+ self.bot_message = conversation.bot_message!(responding_to: user_message)
162
+
163
+ chat_completion(loop: true, openai: "gpt-4o")
164
+ end
165
+
166
+ ...
167
+
168
+ end
169
+
170
+ class FetchUrlCheck
171
+ include ChatCompletion
172
+ include FunctionDispatch
173
+
174
+ REGEX = %r{\b(?:http(s)?://)?(?:www\.)?[a-zA-Z0-9-]+(\.[a-zA-Z]{2,})+(/[^\s]*)?\b}
175
+
176
+ attr_accessor :context, :conversation
177
+
178
+ delegate :user_message, to: :context
179
+ delegate :content, to: :user_message
180
+
181
+ def initialize(context)
182
+ self.context = context
183
+ self.conversation = context.conversation
184
+ self.model = "anthropic/claude-3-haiku"
185
+ end
186
+
187
+ def call
188
+ return unless content&.match?(REGEX)
189
+
190
+ transcript << { system: "Call the `fetch` function if the user mentions a website, otherwise say nil" }
191
+ transcript << { user: content }
192
+
193
+ chat_completion # TODO: consider looping to fetch more than one URL per user message
194
+ end
195
+
196
+ function :fetch, "Gets the plain text contents of a web page", url: { type: "string" } do |arguments|
197
+ Tools::FetchUrl.fetch(arguments[:url]).tap do |result|
198
+ parent = conversation.function_call!("fetch_url", arguments, parent: user_message)
199
+ conversation.function_result!("fetch_url", result, parent:)
200
+ end
201
+ end
202
+
203
+ ```
204
+
205
+ Notably, Olympia does not use the `FunctionDispatch` module in its primary conversation loop because it does not have a fixed set of tools that are included in every single prompt. Functions are made available dynamically based on a number of factors including the user's plan tier and capabilities of the assistant with whom the user is conversing.
206
+
207
+ Streaming of the AI's response to the end user is handled by the `ReplyStream` class, passed to the final prompt declaration as its `stream` parameter. [Patterns of Application Development Using AI](https://leanpub.com/patterns-of-application-development-using-ai) devotes a whole chapter to describing how to write your own `ReplyStream` class.
208
+
209
+ ## Installation
10
210
 
11
211
  Install the gem and add to the application's Gemfile by executing:
12
212
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
213
+ $ bundle add raix-rails
14
214
 
15
215
  If bundler is not being used to manage dependencies, install the gem by executing:
16
216
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
217
+ $ gem install raix-rails
18
218
 
19
219
  ## Usage
20
220
 
21
- TODO: Write usage instructions here
221
+ ```ruby
222
+ class MeaningOfLife
223
+ include Raix::ChatCompletion
224
+
225
+ def initialize
226
+ self.model = "meta-llama/llama-3-8b-instruct:free"
227
+ transcript << { user: "What is the meaning of life?" }
228
+ end
229
+ end
230
+
231
+ >> MeaningOfLife.new.chat_completion
232
+ => "The meaning of life is a question that has puzzled philosophers, scientists, and thinkers for centuries.
233
+ There is no one definitive answer, as it is a deeply personal and subjective question that can vary..."
234
+ ```
235
+
22
236
 
23
237
  ## Development
24
238
 
25
239
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
240
 
241
+ Specs require `OR_ACCESS_TOKEN` and `OAI_ACCESS_TOKEN` environment variables, for access to OpenRouter and OpenAI, respectively. You can add those keys to a local unversionsed `.env` file and they will be picked up by the `dotenv` gem.
242
+
27
243
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
244
 
29
245
  ## Contributing
30
246
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/raix-rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/raix-rails/blob/main/CODE_OF_CONDUCT.md).
247
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[OlympiaAI]/raix-rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[OlympiaAI]/raix-rails/blob/main/CODE_OF_CONDUCT.md).
32
248
 
33
249
  ## License
34
250
 
@@ -36,4 +252,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
36
252
 
37
253
  ## Code of Conduct
38
254
 
39
- Everyone interacting in the Raix::Rails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/raix-rails/blob/main/CODE_OF_CONDUCT.md).
255
+ Everyone interacting in the Raix::Rails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[OlympiaAI]/raix-rails/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/core_ext/object/blank"
5
+ require "open_router"
6
+ require "openai"
7
+
8
+ module Raix
9
+ # The `ChatCompletion`` module is a Rails concern that provides a way to interact
10
+ # with the OpenRouter Chat Completion API via its client. The module includes a few
11
+ # methods that allow you to build a transcript of messages and then send them to
12
+ # the API for completion. The API will return a response that you can use however
13
+ # you see fit. If the response includes a function call, the module will dispatch
14
+ # the function call and return the result. Which implies that function calls need
15
+ # to be defined on the class that includes this module. (Note: You should probably
16
+ # use the `FunctionDispatch` module to define functions instead of doing it manually.)
17
+ module ChatCompletion
18
+ extend ActiveSupport::Concern
19
+
20
+ attr_accessor :frequency_penalty, :logit_bias, :logprobs, :loop, :min_p, :model, :presence_penalty,
21
+ :repetition_penalty, :response_format, :stream, :temperature, :max_tokens, :seed, :stop, :top_a,
22
+ :top_k, :top_logprobs, :top_p, :tools, :tool_choice, :provider
23
+
24
+ # This method performs chat completion based on the provided transcript and parameters.
25
+ #
26
+ # @param params [Hash] The parameters for chat completion.
27
+ # @option loop [Boolean] :loop (false) Whether to loop the chat completion after function calls.
28
+ # @option params [Boolean] :json (false) Whether to return the parse the response as a JSON object.
29
+ # @option params [Boolean] :openai (false) Whether to use OpenAI's API instead of OpenRouter's.
30
+ # @option params [Boolean] :raw (false) Whether to return the raw response or dig the text content.
31
+ # @return [String|Hash] The completed chat response.
32
+ def chat_completion(params: {}, loop: false, json: false, raw: false, openai: false)
33
+ messages = transcript.flatten.compact.map { |msg| transform_message_format(msg) }
34
+ raise "Can't complete an empty transcript" if messages.blank?
35
+
36
+ # used by FunctionDispatch
37
+ self.loop = loop
38
+
39
+ # set params to default values if not provided
40
+ params[:frequency_penalty] ||= frequency_penalty.presence
41
+ params[:logit_bias] ||= logit_bias.presence
42
+ params[:logprobs] ||= logprobs.presence
43
+ params[:max_tokens] ||= max_tokens.presence || Raix.configuration.max_tokens
44
+ params[:min_p] ||= min_p.presence
45
+ params[:presence_penalty] ||= presence_penalty.presence
46
+ params[:provider] ||= provider.presence
47
+ params[:repetition_penalty] ||= repetition_penalty.presence
48
+ params[:response_format] ||= response_format.presence
49
+ params[:seed] ||= seed.presence
50
+ params[:stop] ||= stop.presence
51
+ params[:temperature] ||= temperature.presence || Raix.configuration.temperature
52
+ params[:tool_choice] ||= tool_choice.presence
53
+ params[:tools] ||= tools.presence
54
+ params[:top_a] ||= top_a.presence
55
+ params[:top_k] ||= top_k.presence
56
+ params[:top_logprobs] ||= top_logprobs.presence
57
+ params[:top_p] ||= top_p.presence
58
+
59
+ if json
60
+ params[:provider] ||= {}
61
+ params[:provider][:require_parameters] = true
62
+ params[:response_format] ||= {}
63
+ params[:response_format][:type] = "json_object"
64
+ end
65
+
66
+ # set the model to the default if not provided
67
+ self.model ||= Raix.configuration.model
68
+
69
+ begin
70
+ response = if openai
71
+ openai_request(params:, model: openai,
72
+ messages:)
73
+ else
74
+ openrouter_request(
75
+ params:, model:, messages:
76
+ )
77
+ end
78
+ retry_count = 0
79
+ content = nil
80
+
81
+ # no need for additional processing if streaming
82
+ return if stream && response.blank?
83
+
84
+ # tuck the full response into a thread local in case needed
85
+ Thread.current[:chat_completion_response] = response.with_indifferent_access
86
+
87
+ # TODO: add a standardized callback hook for usage events
88
+ # broadcast(:usage_event, usage_subject, self.class.name.to_s, response, premium?)
89
+
90
+ # TODO: handle parallel tool calls
91
+ if (function = response.dig("choices", 0, "message", "tool_calls", 0, "function"))
92
+ @current_function = function["name"]
93
+ # dispatch the called function
94
+ arguments = JSON.parse(function["arguments"].presence || "{}")
95
+ arguments[:bot_message] = bot_message if respond_to?(:bot_message)
96
+ return send(function["name"], arguments.with_indifferent_access)
97
+ end
98
+
99
+ response.tap do |res|
100
+ content = res.dig("choices", 0, "message", "content")
101
+ if json
102
+ content = content.squish
103
+ return JSON.parse(content)
104
+ end
105
+
106
+ return content unless raw
107
+ end
108
+ rescue JSON::ParserError => e
109
+ if e.message.include?("not a valid") # blank JSON
110
+ puts "Retrying blank JSON response... (#{retry_count} attempts) #{e.message}"
111
+ retry_count += 1
112
+ sleep 1 * retry_count # backoff
113
+ retry if retry_count < 3
114
+
115
+ raise e # just fail if we can't get content after 3 attempts
116
+ end
117
+
118
+ # attempt to fix the JSON
119
+ JsonFixer.new.call(content, e.message)
120
+ rescue Faraday::BadRequestError => e
121
+ # make sure we see the actual error message on console or Honeybadger
122
+ puts "Chat completion failed!!!!!!!!!!!!!!!!: #{e.response[:body]}"
123
+ raise e
124
+ end
125
+ end
126
+
127
+ # This method returns the transcript array.
128
+ # Manually add your messages to it in the following abbreviated format
129
+ # before calling `chat_completion`.
130
+ #
131
+ # { system: "You are a pumpkin" },
132
+ # { user: "Hey what time is it?" },
133
+ # { assistant: "Sorry, pumpkins do not wear watches" }
134
+ #
135
+ # to add a function result use the following format:
136
+ # { function: result, name: 'fancy_pants_function' }
137
+ #
138
+ # @return [Array] The transcript array.
139
+ def transcript
140
+ @transcript ||= []
141
+ end
142
+
143
+ private
144
+
145
+ def openai_request(params:, model:, messages:)
146
+ params[:stream] ||= stream.presence
147
+ Raix.configuration.openai_client.chat(parameters: params.compact.merge(model:, messages:))
148
+ end
149
+
150
+ def openrouter_request(params:, model:, messages:)
151
+ retry_count = 0
152
+
153
+ begin
154
+ Raix.configuration.openrouter_client.complete(messages, model:, extras: params.compact, stream:)
155
+ rescue OpenRouter::ServerError => e
156
+ if e.message.include?("retry")
157
+ puts "Retrying OpenRouter request... (#{retry_count} attempts) #{e.message}"
158
+ retry_count += 1
159
+ sleep 1 * retry_count # backoff
160
+ retry if retry_count < 5
161
+ end
162
+
163
+ raise e
164
+ end
165
+ end
166
+
167
+ def transform_message_format(message)
168
+ return message if message[:role].present?
169
+
170
+ if message[:function].present?
171
+ { role: "assistant", name: message.dig(:function, :name), content: message.dig(:function, :arguments).to_json }
172
+ elsif message[:result].present?
173
+ { role: "function", name: message[:name], content: message[:result] }
174
+ else
175
+ { role: message.first.first, content: message.first.last }
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ module Raix
5
+ # Provides declarative function definition for ChatCompletion classes.
6
+ #
7
+ # Example:
8
+ #
9
+ # class MeaningOfLife
10
+ # include Raix::ChatCompletion
11
+ # include Raix::FunctionDispatch
12
+ #
13
+ # function :ask_deep_thought do
14
+ # wait 236_682_000_000_000
15
+ # "The meaning of life is 42"
16
+ # end
17
+ #
18
+ # def initialize
19
+ # transcript << { user: "What is the meaning of life?" }
20
+ # chat_completion
21
+ # end
22
+ # end
23
+ module FunctionDispatch
24
+ extend ActiveSupport::Concern
25
+
26
+ class_methods do
27
+ attr_reader :functions
28
+
29
+ # Defines a function that can be dispatched by the ChatCompletion module while
30
+ # processing the response from an AI model.
31
+ #
32
+ # Declaring a function here will automatically add it (in JSON Schema format) to
33
+ # the list of tools provided to the OpenRouter Chat Completion API. The function
34
+ # will be dispatched by name, so make sure the name is unique. The function's block
35
+ # argument will be executed in the instance context of the class that includes this module.
36
+ #
37
+ # Example:
38
+ # function :google_search, description: "Search Google for something", query: { type: "string" } do |arguments|
39
+ # GoogleSearch.new(arguments[:query]).search
40
+ # end
41
+ #
42
+ # @param name [Symbol] The name of the function.
43
+ # @param description [String] An optional description of the function.
44
+ # @param parameters [Hash] The parameters that the function accepts.
45
+ # @param block [Proc] The block of code to execute when the function is called.
46
+ def function(name, description = nil, **parameters, &block)
47
+ @functions ||= []
48
+ @functions << begin
49
+ { name:, parameters: { type: "object", properties: {} } }.tap do |definition|
50
+ definition[:description] = description if description.present?
51
+ parameters.map do |key, value|
52
+ definition[:parameters][:properties][key] = value
53
+ end
54
+ end
55
+ end
56
+
57
+ define_method(name) do |arguments|
58
+ id = SecureRandom.uuid[0, 23]
59
+ transcript << {
60
+ role: "assistant",
61
+ content: nil,
62
+ tool_calls: [
63
+ {
64
+ id:,
65
+ type: "function",
66
+ function: {
67
+ name:,
68
+ arguments: arguments.to_json
69
+ }
70
+ }
71
+ ]
72
+ }
73
+ instance_exec(arguments, &block).tap do |content|
74
+ transcript << {
75
+ role: "tool",
76
+ tool_call_id: id,
77
+ name:,
78
+ content: content.to_s
79
+ }
80
+ # TODO: add on_error handler as optional parameter to function
81
+ end
82
+
83
+ chat_completion(**chat_completion_args) if loop
84
+ end
85
+ end
86
+ end
87
+
88
+ included do
89
+ attr_accessor :chat_completion_args
90
+ end
91
+
92
+ def chat_completion(**chat_completion_args)
93
+ raise "No functions defined" if self.class.functions.blank?
94
+
95
+ self.chat_completion_args = chat_completion_args
96
+
97
+ super
98
+ end
99
+
100
+ # Stops the looping of chat completion after function calls.
101
+ # Useful for manually halting processing in workflow components
102
+ # that do not require a final text response to an end user.
103
+ def stop_looping!
104
+ self.loop = false
105
+ end
106
+
107
+ def tools
108
+ self.class.functions.map { |function| { type: "function", function: } }
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+
5
+ module Raix
6
+ # The PromptDeclarations module provides a way to chain prompts and handle
7
+ # user responses in a serialized manner (in the order they were defined),
8
+ # with support for functions if the FunctionDispatch module is also included.
9
+ module PromptDeclarations
10
+ extend ActiveSupport::Concern
11
+ extend ChatCompletion
12
+
13
+ module ClassMethods # rubocop:disable Style/Documentation
14
+ # Adds a prompt to the list of prompts.
15
+ #
16
+ # @param system [Proc] A lambda that generates the system message.
17
+ # @param text [Proc] A lambda that generates the prompt text. (Required)
18
+ # @param success [Proc] The block of code to execute when the prompt is answered.
19
+ # @param parameters [Hash] Additional parameters for the completion API call
20
+ def prompt(text:, system: nil, success: nil, params: {})
21
+ name = Digest::SHA256.hexdigest(text.inspect)[0..7]
22
+ prompts << begin
23
+ OpenStruct.new({ name:, system:, text:, success:, params: })
24
+ end
25
+
26
+ define_method(name) do |response|
27
+ if Rails.env.local?
28
+ puts "_" * 80
29
+ puts "PromptDeclarations#response:"
30
+ puts "#{text.source_location} (#{name})"
31
+ puts response
32
+ puts "_" * 80
33
+ end
34
+
35
+ return response if success.nil?
36
+ return send(success, response) if success.is_a?(Symbol)
37
+
38
+ instance_exec(response, &success)
39
+ end
40
+ end
41
+
42
+ # the list of prompts declared at class level
43
+ def prompts
44
+ @prompts ||= []
45
+ end
46
+
47
+ # getter/setter for system prompt declared at class level
48
+ def system_prompt(prompt = nil)
49
+ prompt ? @system_prompt = prompt.squish : @system_prompt
50
+ end
51
+ end
52
+
53
+ # Executes the chat completion process based on the class-level declared prompts.
54
+ # The response to each prompt is added to the transcript automatically and returned.
55
+ #
56
+ # Prompts require at least a `text` lambda parameter.
57
+ #
58
+ # @param params [Hash] Parameters for the chat completion override those defined in the current prompt.
59
+ # @option params [Boolean] :raw (false) Whether to return the raw response or dig the text content.
60
+ #
61
+ # Uses system prompt in following order of priority:
62
+ # - system lambda specified in the prompt declaration
63
+ # - system_prompt instance method if defined
64
+ # - system_prompt class-level declaration if defined
65
+ #
66
+ # TODO: shortcut syntax passes just a string prompt if no other options are needed.
67
+ #
68
+ # @raise [RuntimeError] If no prompts are defined.
69
+ #
70
+ def chat_completion(params: {}, raw: false)
71
+ raise "No prompts defined" unless self.class.prompts.present?
72
+
73
+ current_prompts = self.class.prompts.clone
74
+
75
+ while (@current_prompt = current_prompts.shift)
76
+ __system_prompt = instance_exec(&@current_prompt.system) if @current_prompt.system.present? # rubocop:disable Lint/UnderscorePrefixedVariableName
77
+ __system_prompt ||= system_prompt if respond_to?(:system_prompt)
78
+ __system_prompt ||= self.class.system_prompt.presence
79
+ transcript << { system: __system_prompt } if __system_prompt
80
+ transcript << { user: instance_exec(&@current_prompt.text) } # text is required
81
+
82
+ params = @current_prompt.params.merge(params)
83
+
84
+ super(params:, raw:).then do |response|
85
+ transcript << { assistant: response }
86
+ @last_response = send(@current_prompt.name, response)
87
+ end
88
+ end
89
+
90
+ @last_response
91
+ end
92
+
93
+ # Returns the model parameter of the current prompt or the default model.
94
+ #
95
+ # @return [Object] The model parameter of the current prompt or the default model.
96
+ def model
97
+ @current_prompt.params[:model] || super
98
+ end
99
+
100
+ # Returns the temperature parameter of the current prompt or the default temperature.
101
+ #
102
+ # @return [Float] The temperature parameter of the current prompt or the default temperature.
103
+ def temperature
104
+ @current_prompt.params[:temperature] || super
105
+ end
106
+
107
+ # Returns the max_tokens parameter of the current prompt or the default max_tokens.
108
+ #
109
+ # @return [Integer] The max_tokens parameter of the current prompt or the default max_tokens.
110
+ def max_tokens
111
+ @current_prompt.params[:max_tokens] || super
112
+ end
113
+ end
114
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Raix
4
4
  module Rails
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.1"
6
6
  end
7
7
  end
data/lib/raix.rb ADDED
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "raix/rails/version"
4
+ require_relative "raix/chat_completion"
5
+ require_relative "raix/function_dispatch"
6
+ require_relative "raix/prompt_declarations"
7
+
8
+ # The Raix module provides configuration options for the Raix gem.
9
+ module Raix
10
+ # The Configuration class holds the configuration options for the Raix gem.
11
+ class Configuration
12
+ # The temperature option determines the randomness of the generated text.
13
+ # Higher values result in more random output.
14
+ attr_accessor :temperature
15
+
16
+ # The max_tokens option determines the maximum number of tokens to generate.
17
+ attr_accessor :max_tokens
18
+
19
+ # The model option determines the model to use for text generation. This option
20
+ # is normally set in each class that includes the ChatCompletion module.
21
+ attr_accessor :model
22
+
23
+ # The openrouter_client option determines the default client to use for communicatio.
24
+ attr_accessor :openrouter_client
25
+
26
+ # The openai_client option determines the OpenAI client to use for communication.
27
+ attr_accessor :openai_client
28
+
29
+ DEFAULT_MAX_TOKENS = 1000
30
+ DEFAULT_MODEL = "meta-llama/llama-3-8b-instruct:free"
31
+ DEFAULT_TEMPERATURE = 0.0
32
+
33
+ # Initializes a new instance of the Configuration class with default values.
34
+ def initialize
35
+ self.temperature = DEFAULT_TEMPERATURE
36
+ self.max_tokens = DEFAULT_MAX_TOKENS
37
+ self.model = DEFAULT_MODEL
38
+ end
39
+ end
40
+
41
+ class << self
42
+ attr_writer :configuration
43
+ end
44
+
45
+ # Returns the current configuration instance.
46
+ def self.configuration
47
+ @configuration ||= Configuration.new
48
+ end
49
+
50
+ # Configures the Raix gem using a block.
51
+ def self.configure
52
+ yield(configuration)
53
+ end
54
+ end
data/raix-rails.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = "Ruby AI eXtensions for Rails"
12
12
  spec.homepage = "https://github.com/OlympiaAI/raix-rails"
13
13
  spec.license = "MIT"
14
- spec.required_ruby_version = ">= 3.2.1"
14
+ spec.required_ruby_version = ">= 3.2.2"
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
17
  spec.metadata["source_code_uri"] = "https://github.com/OlympiaAI/raix-rails"
@@ -28,8 +28,8 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
 
31
- # Uncomment to register a new dependency of your gem
32
- # spec.add_dependency "example-gem", "~> 1.0"
31
+ spec.add_dependency "activesupport", ">= 6.0"
32
+ spec.add_dependency "open_router", "~> 0.2"
33
33
 
34
34
  # For more information and examples about making a new gem, check out our
35
35
  # guide at: https://bundler.io/guides/creating_gem.html
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raix-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Obie Fernandez
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-03 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-05-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: open_router
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
13
41
  description:
14
42
  email:
15
43
  - obiefernandez@gmail.com
@@ -19,13 +47,19 @@ extra_rdoc_files: []
19
47
  files:
20
48
  - ".rspec"
21
49
  - ".rubocop.yml"
50
+ - ".ruby-version"
22
51
  - CHANGELOG.md
23
52
  - CODE_OF_CONDUCT.md
24
53
  - Gemfile
54
+ - Gemfile.lock
55
+ - Guardfile
25
56
  - LICENSE.txt
26
57
  - README.md
27
58
  - Rakefile
28
- - lib/raix/rails.rb
59
+ - lib/raix.rb
60
+ - lib/raix/chat_completion.rb
61
+ - lib/raix/function_dispatch.rb
62
+ - lib/raix/prompt_declarations.rb
29
63
  - lib/raix/rails/version.rb
30
64
  - raix-rails.gemspec
31
65
  - sig/raix/rails.rbs
@@ -44,14 +78,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
78
  requirements:
45
79
  - - ">="
46
80
  - !ruby/object:Gem::Version
47
- version: 3.2.1
81
+ version: 3.2.2
48
82
  required_rubygems_version: !ruby/object:Gem::Requirement
49
83
  requirements:
50
84
  - - ">="
51
85
  - !ruby/object:Gem::Version
52
86
  version: '0'
53
87
  requirements: []
54
- rubygems_version: 3.4.12
88
+ rubygems_version: 3.4.10
55
89
  signing_key:
56
90
  specification_version: 4
57
91
  summary: Ruby AI eXtensions for Rails
data/lib/raix/rails.rb DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "rails/version"
4
-
5
- module Raix
6
- module Rails
7
- class Error < StandardError; end
8
- # Your code goes here...
9
- end
10
- end