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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 311c298f7aebaaa19979174f680dd21ff165ddb6e7187f622ea2e8d6e5c24e9a
4
- data.tar.gz: 13932ec4de274b41eafeae99ef0a674328ab8bb984414ec6e84e8633a125b6c9
3
+ metadata.gz: 49d83bc8390b10829bb062e0bd107eb3e2773ca59e5d61bde85c8f783621a7f4
4
+ data.tar.gz: ed6ca5031a53dc185b91ef1e745d235258b7965642a18ec4e45940aa74eaa7df
5
5
  SHA512:
6
- metadata.gz: ae9bcb3729056a8cb9440f0c9f6964fb054180535cd4ff96a8e44724728f67ac703c80c544deae12ec2accd492bbe37c5f25faac2a31b0653ca4cd0ce17fba03
7
- data.tar.gz: cd5cc04616d616717cb6c52e518a5189e7ebede35f5896549c4d39feca64bb12083059668b0b715e14595ed4d1fd0b1c1fabd541e273c7ecec7a66761631d5f7
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 a `chat_completion` method.
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Raix
4
4
  module Rails
5
- VERSION = "0.3.0"
5
+ VERSION = "0.3.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raix-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Obie Fernandez