raix-rails 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +187 -1
- data/lib/raix/function_dispatch.rb +7 -0
- data/lib/raix/rails/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49d83bc8390b10829bb062e0bd107eb3e2773ca59e5d61bde85c8f783621a7f4
|
4
|
+
data.tar.gz: ed6ca5031a53dc185b91ef1e745d235258b7965642a18ec4e45940aa74eaa7df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfca0e561cc16dda50d53f606fba7b394a7aa14e6e7ee6ceec4352a8522d9b39c042d4b04aaaf7675485366353d45b91898d9cc7ec8e3844cbf8edc58f29de56
|
7
|
+
data.tar.gz: 26344feccfa39fe084ecbdf5fca52150815e6f728c5b857123a59ecedc8318c0f9ee4546ee3b011165d2f9cd079ee1f6dbf05bc6c34206ae17dbdbb268e7db2a
|
data/README.md
CHANGED
@@ -10,16 +10,202 @@ At the moment, Raix natively supports use of either OpenAI or OpenRouter as its
|
|
10
10
|
|
11
11
|
### Chat Completions
|
12
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
|
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.)
|
14
44
|
|
15
45
|
### Use of Tools/Functions
|
16
46
|
|
17
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.
|
18
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
|
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
|
+
|
19
130
|
### Prompt Declarations
|
20
131
|
|
21
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`.
|
22
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
|
+
|
23
209
|
## Installation
|
24
210
|
|
25
211
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -97,6 +97,13 @@ module Raix
|
|
97
97
|
super
|
98
98
|
end
|
99
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
|
+
|
100
107
|
def tools
|
101
108
|
self.class.functions.map { |function| { type: "function", function: } }
|
102
109
|
end
|
data/lib/raix/rails/version.rb
CHANGED