raix 0.1.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +20 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +6 -1
- data/Gemfile +17 -5
- data/Gemfile.lock +161 -1
- data/README.md +211 -12
- data/lib/raix/chat_completion.rb +179 -0
- data/lib/raix/function_dispatch.rb +111 -0
- data/lib/raix/prompt_declarations.rb +118 -0
- data/lib/raix/version.rb +1 -1
- data/lib/raix.rb +48 -2
- data/raix.gemspec +3 -6
- metadata +37 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29e66d0046995dca8f9d093ccfd719e54905f94c62d5ee4e78588e1b40b0dadd
|
4
|
+
data.tar.gz: 7b1d4544576891124e209130d339be55fe35573e05dcb05474c526655e850390
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8989d6e4c4422054a2e452637ffc3205a7242a060f32c86fcf5c72ff1050f50d7bc6b20a9e4eb147398cdbc461c6fe80cf1f1426e0401fe944ce484ae70d4f9
|
7
|
+
data.tar.gz: 364ba4571799b8a7eea4abae9f4a905a32121303f62e22e66bff9f4663831b6311efa3eb9238f4eaa326456c2db467d7de0277000e42a11db710435c3e482679
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
AllCops:
|
2
|
-
|
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
data/Gemfile
CHANGED
@@ -2,11 +2,23 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
# Specify your gem's dependencies in raix.gemspec
|
5
|
+
# Specify your gem's dependencies in raix-rails.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem "
|
8
|
+
gem "activesupport", ">= 6.0"
|
9
|
+
gem "faraday-retry"
|
10
|
+
gem "open_router", "~> 0.3"
|
11
|
+
gem "ruby-openai", "~> 7.0"
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
gem "
|
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
CHANGED
@@ -1,23 +1,116 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
raix (0.1
|
4
|
+
raix (0.3.1)
|
5
|
+
activesupport (>= 6.0)
|
6
|
+
open_router (~> 0.2)
|
5
7
|
|
6
8
|
GEM
|
7
9
|
remote: https://rubygems.org/
|
8
10
|
specs:
|
11
|
+
activesupport (7.1.3.4)
|
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)
|
9
21
|
ast (2.4.2)
|
22
|
+
backport (1.2.0)
|
23
|
+
base64 (0.2.0)
|
24
|
+
benchmark (0.3.0)
|
25
|
+
bigdecimal (3.1.8)
|
26
|
+
coderay (1.1.3)
|
27
|
+
concurrent-ruby (1.3.3)
|
28
|
+
connection_pool (2.4.1)
|
10
29
|
diff-lcs (1.5.1)
|
30
|
+
dotenv (3.1.2)
|
31
|
+
drb (2.2.1)
|
32
|
+
e2mmap (0.1.0)
|
33
|
+
erubi (1.13.0)
|
34
|
+
event_stream_parser (1.0.0)
|
35
|
+
faraday (2.9.2)
|
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.17.0-arm64-darwin)
|
44
|
+
ffi (1.17.0-x86_64-linux-gnu)
|
45
|
+
formatador (1.1.0)
|
46
|
+
guard (2.18.1)
|
47
|
+
formatador (>= 0.2.4)
|
48
|
+
listen (>= 2.7, < 4.0)
|
49
|
+
lumberjack (>= 1.0.12, < 2.0)
|
50
|
+
nenv (~> 0.1)
|
51
|
+
notiffany (~> 0.0)
|
52
|
+
pry (>= 0.13.0)
|
53
|
+
shellany (~> 0.0)
|
54
|
+
thor (>= 0.18.1)
|
55
|
+
guard-compat (1.2.1)
|
56
|
+
guard-rspec (4.7.3)
|
57
|
+
guard (~> 2.1)
|
58
|
+
guard-compat (~> 1.1)
|
59
|
+
rspec (>= 2.99.0, < 4.0)
|
60
|
+
i18n (1.14.5)
|
61
|
+
concurrent-ruby (~> 1.0)
|
62
|
+
jaro_winkler (1.6.0)
|
11
63
|
json (2.7.1)
|
64
|
+
kramdown (2.4.0)
|
65
|
+
rexml
|
66
|
+
kramdown-parser-gfm (1.1.0)
|
67
|
+
kramdown (~> 2.0)
|
12
68
|
language_server-protocol (3.17.0.3)
|
69
|
+
listen (3.9.0)
|
70
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
71
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
72
|
+
lumberjack (1.2.10)
|
73
|
+
method_source (1.1.0)
|
74
|
+
minitest (5.24.0)
|
75
|
+
multipart-post (2.4.1)
|
76
|
+
mutex_m (0.2.0)
|
77
|
+
nenv (0.3.0)
|
78
|
+
net-http (0.4.1)
|
79
|
+
uri
|
80
|
+
netrc (0.11.0)
|
81
|
+
nokogiri (1.16.6-arm64-darwin)
|
82
|
+
racc (~> 1.4)
|
83
|
+
nokogiri (1.16.6-x86_64-linux)
|
84
|
+
racc (~> 1.4)
|
85
|
+
notiffany (0.1.3)
|
86
|
+
nenv (~> 0.1)
|
87
|
+
shellany (~> 0.0)
|
88
|
+
open_router (0.3.3)
|
89
|
+
activesupport (>= 6.0)
|
90
|
+
dotenv (>= 2)
|
91
|
+
faraday (>= 1)
|
92
|
+
faraday-multipart (>= 1)
|
13
93
|
parallel (1.24.0)
|
14
94
|
parser (3.3.0.5)
|
15
95
|
ast (~> 2.4.1)
|
16
96
|
racc
|
97
|
+
prism (0.30.0)
|
98
|
+
pry (0.14.2)
|
99
|
+
coderay (~> 1.1)
|
100
|
+
method_source (~> 1.0)
|
17
101
|
racc (1.7.3)
|
18
102
|
rainbow (3.1.1)
|
19
103
|
rake (13.2.0)
|
104
|
+
rb-fsevent (0.11.2)
|
105
|
+
rb-inotify (0.11.1)
|
106
|
+
ffi (~> 1.0)
|
107
|
+
rbi (0.1.13)
|
108
|
+
prism (>= 0.18.0, < 1.0.0)
|
109
|
+
sorbet-runtime (>= 0.5.9204)
|
110
|
+
rbs (2.8.4)
|
20
111
|
regexp_parser (2.9.0)
|
112
|
+
reverse_markdown (2.1.1)
|
113
|
+
nokogiri
|
21
114
|
rexml (3.2.6)
|
22
115
|
rspec (3.13.0)
|
23
116
|
rspec-core (~> 3.13.0)
|
@@ -45,17 +138,84 @@ GEM
|
|
45
138
|
unicode-display_width (>= 2.4.0, < 3.0)
|
46
139
|
rubocop-ast (1.31.2)
|
47
140
|
parser (>= 3.3.0.4)
|
141
|
+
ruby-openai (7.1.0)
|
142
|
+
event_stream_parser (>= 0.3.0, < 2.0.0)
|
143
|
+
faraday (>= 1)
|
144
|
+
faraday-multipart (>= 1)
|
48
145
|
ruby-progressbar (1.13.0)
|
146
|
+
shellany (0.0.1)
|
147
|
+
solargraph (0.50.0)
|
148
|
+
backport (~> 1.2)
|
149
|
+
benchmark
|
150
|
+
bundler (~> 2.0)
|
151
|
+
diff-lcs (~> 1.4)
|
152
|
+
e2mmap
|
153
|
+
jaro_winkler (~> 1.5)
|
154
|
+
kramdown (~> 2.3)
|
155
|
+
kramdown-parser-gfm (~> 1.1)
|
156
|
+
parser (~> 3.0)
|
157
|
+
rbs (~> 2.0)
|
158
|
+
reverse_markdown (~> 2.0)
|
159
|
+
rubocop (~> 1.38)
|
160
|
+
thor (~> 1.0)
|
161
|
+
tilt (~> 2.0)
|
162
|
+
yard (~> 0.9, >= 0.9.24)
|
163
|
+
solargraph-rails (0.2.2.pre.4)
|
164
|
+
activesupport
|
165
|
+
solargraph (>= 0.41.1)
|
166
|
+
sorbet (0.5.11447)
|
167
|
+
sorbet-static (= 0.5.11447)
|
168
|
+
sorbet-runtime (0.5.11447)
|
169
|
+
sorbet-static (0.5.11447-universal-darwin)
|
170
|
+
sorbet-static (0.5.11447-x86_64-linux)
|
171
|
+
sorbet-static-and-runtime (0.5.11447)
|
172
|
+
sorbet (= 0.5.11447)
|
173
|
+
sorbet-runtime (= 0.5.11447)
|
174
|
+
spoom (1.3.2)
|
175
|
+
erubi (>= 1.10.0)
|
176
|
+
prism (>= 0.19.0)
|
177
|
+
sorbet-static-and-runtime (>= 0.5.10187)
|
178
|
+
thor (>= 0.19.2)
|
179
|
+
tapioca (0.14.4)
|
180
|
+
bundler (>= 2.2.25)
|
181
|
+
netrc (>= 0.11.0)
|
182
|
+
parallel (>= 1.21.0)
|
183
|
+
rbi (>= 0.1.4, < 0.2)
|
184
|
+
sorbet-static-and-runtime (>= 0.5.11087)
|
185
|
+
spoom (>= 1.2.0)
|
186
|
+
thor (>= 1.2.0)
|
187
|
+
yard-sorbet
|
188
|
+
thor (1.3.1)
|
189
|
+
tilt (2.3.0)
|
190
|
+
tzinfo (2.0.6)
|
191
|
+
concurrent-ruby (~> 1.0)
|
49
192
|
unicode-display_width (2.5.0)
|
193
|
+
uri (0.13.0)
|
194
|
+
yard (0.9.36)
|
195
|
+
yard-sorbet (0.8.1)
|
196
|
+
sorbet-runtime (>= 0.5)
|
197
|
+
yard (>= 0.9)
|
50
198
|
|
51
199
|
PLATFORMS
|
52
200
|
arm64-darwin-21
|
201
|
+
x86_64-linux
|
53
202
|
|
54
203
|
DEPENDENCIES
|
204
|
+
activesupport (>= 6.0)
|
205
|
+
dotenv (>= 2)
|
206
|
+
faraday-retry
|
207
|
+
guard
|
208
|
+
guard-rspec
|
209
|
+
open_router (~> 0.3)
|
210
|
+
pry (>= 0.14)
|
55
211
|
raix!
|
56
212
|
rake (~> 13.0)
|
57
213
|
rspec (~> 3.0)
|
58
214
|
rubocop (~> 1.21)
|
215
|
+
ruby-openai (~> 7.0)
|
216
|
+
solargraph-rails (~> 0.2.0.pre)
|
217
|
+
sorbet
|
218
|
+
tapioca
|
59
219
|
|
60
220
|
BUNDLED WITH
|
61
221
|
2.4.12
|
data/README.md
CHANGED
@@ -1,34 +1,233 @@
|
|
1
|
-
#
|
1
|
+
# Ruby AI eXtensions
|
2
2
|
|
3
|
-
|
3
|
+
## What's Raix
|
4
4
|
|
5
|
-
|
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 Ruby 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
|
-
|
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
|
8
114
|
|
9
|
-
|
115
|
+
function :schedule_shipping do
|
116
|
+
ShippingSchedulerWorker.perform(@order)
|
117
|
+
end
|
118
|
+
|
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
|
213
|
+
$ bundle add raix
|
14
214
|
|
15
215
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
216
|
|
17
|
-
$ gem install
|
217
|
+
$ gem install raix
|
18
218
|
|
19
|
-
## Usage
|
20
|
-
|
21
|
-
TODO: Write usage instructions here
|
22
219
|
|
23
220
|
## Development
|
24
221
|
|
25
222
|
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
223
|
|
224
|
+
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.
|
225
|
+
|
27
226
|
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
227
|
|
29
228
|
## Contributing
|
30
229
|
|
31
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/[
|
230
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[OlympiaAI]/raix. 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/blob/main/CODE_OF_CONDUCT.md).
|
32
231
|
|
33
232
|
## License
|
34
233
|
|
@@ -36,4 +235,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
36
235
|
|
37
236
|
## Code of Conduct
|
38
237
|
|
39
|
-
Everyone interacting in the Raix project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[
|
238
|
+
Everyone interacting in the Raix project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[OlympiaAI]/raix/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,118 @@
|
|
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
|
+
# @param stream [Boolean] Whether to stream the response.
|
21
|
+
def prompt(text:, system: nil, success: nil, params: {}, stream: false)
|
22
|
+
name = Digest::SHA256.hexdigest(text.inspect)[0..7]
|
23
|
+
prompts << begin
|
24
|
+
OpenStruct.new({ name:, system:, text:, success:, params:, stream: })
|
25
|
+
end
|
26
|
+
|
27
|
+
define_method(name) do |response|
|
28
|
+
if Rails.env.local?
|
29
|
+
puts "_" * 80
|
30
|
+
puts "PromptDeclarations#response:"
|
31
|
+
puts "#{text.source_location} (#{name})"
|
32
|
+
puts response
|
33
|
+
puts "_" * 80
|
34
|
+
end
|
35
|
+
|
36
|
+
return response if success.nil?
|
37
|
+
return send(success, response) if success.is_a?(Symbol)
|
38
|
+
|
39
|
+
instance_exec(response, &success)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# the list of prompts declared at class level
|
44
|
+
def prompts
|
45
|
+
@prompts ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
# getter/setter for system prompt declared at class level
|
49
|
+
def system_prompt(prompt = nil)
|
50
|
+
prompt ? @system_prompt = prompt.squish : @system_prompt
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Executes the chat completion process based on the class-level declared prompts.
|
55
|
+
# The response to each prompt is added to the transcript automatically and returned.
|
56
|
+
#
|
57
|
+
# Prompts require at least a `text` lambda parameter.
|
58
|
+
#
|
59
|
+
# @param params [Hash] Parameters for the chat completion override those defined in the current prompt.
|
60
|
+
# @option params [Boolean] :raw (false) Whether to return the raw response or dig the text content.
|
61
|
+
#
|
62
|
+
# Uses system prompt in following order of priority:
|
63
|
+
# - system lambda specified in the prompt declaration
|
64
|
+
# - system_prompt instance method if defined
|
65
|
+
# - system_prompt class-level declaration if defined
|
66
|
+
#
|
67
|
+
# TODO: shortcut syntax passes just a string prompt if no other options are needed.
|
68
|
+
#
|
69
|
+
# @raise [RuntimeError] If no prompts are defined.
|
70
|
+
#
|
71
|
+
def chat_completion(params: {}, raw: false)
|
72
|
+
raise "No prompts defined" unless self.class.prompts.present?
|
73
|
+
|
74
|
+
current_prompts = self.class.prompts.clone
|
75
|
+
|
76
|
+
while (@current_prompt = current_prompts.shift)
|
77
|
+
__system_prompt = instance_exec(&@current_prompt.system) if @current_prompt.system.present? # rubocop:disable Lint/UnderscorePrefixedVariableName
|
78
|
+
__system_prompt ||= system_prompt if respond_to?(:system_prompt)
|
79
|
+
__system_prompt ||= self.class.system_prompt.presence
|
80
|
+
transcript << { system: __system_prompt } if __system_prompt
|
81
|
+
transcript << { user: instance_exec(&@current_prompt.text) } # text is required
|
82
|
+
|
83
|
+
params = @current_prompt.params.merge(params)
|
84
|
+
|
85
|
+
# set the stream if necessary
|
86
|
+
self.stream = instance_exec(¤t_prompt.stream) if current_prompt.stream.present?
|
87
|
+
|
88
|
+
super(params:, raw:).then do |response|
|
89
|
+
transcript << { assistant: response }
|
90
|
+
@last_response = send(@current_prompt.name, response)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
@last_response
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns the model parameter of the current prompt or the default model.
|
98
|
+
#
|
99
|
+
# @return [Object] The model parameter of the current prompt or the default model.
|
100
|
+
def model
|
101
|
+
@current_prompt.params[:model] || super
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns the temperature parameter of the current prompt or the default temperature.
|
105
|
+
#
|
106
|
+
# @return [Float] The temperature parameter of the current prompt or the default temperature.
|
107
|
+
def temperature
|
108
|
+
@current_prompt.params[:temperature] || super
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the max_tokens parameter of the current prompt or the default max_tokens.
|
112
|
+
#
|
113
|
+
# @return [Integer] The max_tokens parameter of the current prompt or the default max_tokens.
|
114
|
+
def max_tokens
|
115
|
+
@current_prompt.params[:max_tokens] || super
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/raix/version.rb
CHANGED
data/lib/raix.rb
CHANGED
@@ -1,8 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "raix/version"
|
4
|
+
require_relative "raix/chat_completion"
|
5
|
+
require_relative "raix/function_dispatch"
|
6
|
+
require_relative "raix/prompt_declarations"
|
4
7
|
|
8
|
+
# The Raix module provides configuration options for the Raix gem.
|
5
9
|
module Raix
|
6
|
-
class
|
7
|
-
|
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
|
8
54
|
end
|
data/raix.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary = "Ruby AI eXtensions"
|
12
12
|
spec.homepage = "https://github.com/OlympiaAI/raix"
|
13
13
|
spec.license = "MIT"
|
14
|
-
spec.required_ruby_version = ">= 3.2.
|
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"
|
@@ -28,9 +28,6 @@ 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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# For more information and examples about making a new gem, check out our
|
35
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
31
|
+
spec.add_dependency "activesupport", ">= 6.0"
|
32
|
+
spec.add_dependency "open_router", "~> 0.2"
|
36
33
|
end
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raix
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.2
|
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-
|
12
|
-
dependencies:
|
11
|
+
date: 2024-06-30 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,6 +47,7 @@ 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
|
@@ -27,6 +56,9 @@ files:
|
|
27
56
|
- README.md
|
28
57
|
- Rakefile
|
29
58
|
- lib/raix.rb
|
59
|
+
- lib/raix/chat_completion.rb
|
60
|
+
- lib/raix/function_dispatch.rb
|
61
|
+
- lib/raix/prompt_declarations.rb
|
30
62
|
- lib/raix/version.rb
|
31
63
|
- raix.gemspec
|
32
64
|
- sig/raix.rbs
|
@@ -45,14 +77,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
77
|
requirements:
|
46
78
|
- - ">="
|
47
79
|
- !ruby/object:Gem::Version
|
48
|
-
version: 3.2.
|
80
|
+
version: 3.2.2
|
49
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
82
|
requirements:
|
51
83
|
- - ">="
|
52
84
|
- !ruby/object:Gem::Version
|
53
85
|
version: '0'
|
54
86
|
requirements: []
|
55
|
-
rubygems_version: 3.4.
|
87
|
+
rubygems_version: 3.4.10
|
56
88
|
signing_key:
|
57
89
|
specification_version: 4
|
58
90
|
summary: Ruby AI eXtensions
|