raix 0.4.2 → 0.4.3

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: cd88f295667264948d2710fb7eb0bcd74e5ab0177e76678314c34958e79fa6bd
4
- data.tar.gz: b86495b4b67c5259915a7ef20502005d90ededf6be991821703d1d83e876c572
3
+ metadata.gz: 65adb56d006baa2b25193eaa5f87f52b24366876139d1e023c9031e7707f62ea
4
+ data.tar.gz: 545da8c28e699f87e53547b53d07fb934727948945f45998f3af5d34752a2854
5
5
  SHA512:
6
- metadata.gz: cf1982b065312860c046a363486169a3d4572b65bd5ded3e82e270e3cbb689a173d6ff94cbcc897d6d8e5f27ef7e6558193ea3b266c18cc5bd1a8f2ca54fa6cd
7
- data.tar.gz: 58eb7f54eb7d3a2656dbdd3e44a977c04b1dfb5ef5f7bd549ece96ecce9a931007edea5d364f189a1916883838c6a674e794799365914a1a5cab2402994da5f1
6
+ metadata.gz: 71a14254199a7e0eeb8195b842fe212516091743ab4799f349caadfeffa654eede58dc066813e4d1360c24b2384406f934086d9b167b45db70a618470c94df84
7
+ data.tar.gz: 3d5a8033107bc8037862d11cd88fb3cd7a67a6884b4f7d2d8756244ac676e938094a6580bd55275b353c0b12389f292cbe958138d8d13f2d1001c5befb81662d
data/CHANGELOG.md CHANGED
@@ -18,3 +18,6 @@
18
18
 
19
19
  ## [0.4.2] - 2024-11-05
20
20
  - adds support for [Predicted Outputs](https://platform.openai.com/docs/guides/latency-optimization#use-predicted-outputs) with the `prediction` option for OpenAI
21
+
22
+ ## [0.4.3] - 2024-11-11
23
+ - adds support for `Predicate` module
data/Gemfile CHANGED
@@ -22,3 +22,8 @@ group :development do
22
22
  gem "sorbet"
23
23
  gem "tapioca", require: false
24
24
  end
25
+
26
+ group :test do
27
+ gem "vcr"
28
+ gem "webmock"
29
+ end
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- raix (0.4.2)
4
+ raix (0.4.3)
5
5
  activesupport (>= 6.0)
6
6
  open_router (~> 0.2)
7
+ ruby-openai (~> 7.0)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
@@ -18,6 +19,8 @@ GEM
18
19
  minitest (>= 5.1)
19
20
  mutex_m
20
21
  tzinfo (~> 2.0)
22
+ addressable (2.8.6)
23
+ public_suffix (>= 2.0.2, < 6.0)
21
24
  ast (2.4.2)
22
25
  backport (1.2.0)
23
26
  base64 (0.2.0)
@@ -26,6 +29,8 @@ GEM
26
29
  coderay (1.1.3)
27
30
  concurrent-ruby (1.3.3)
28
31
  connection_pool (2.4.1)
32
+ crack (0.4.5)
33
+ rexml
29
34
  diff-lcs (1.5.1)
30
35
  dotenv (3.1.2)
31
36
  drb (2.2.1)
@@ -57,6 +62,7 @@ GEM
57
62
  guard (~> 2.1)
58
63
  guard-compat (~> 1.1)
59
64
  rspec (>= 2.99.0, < 4.0)
65
+ hashdiff (1.0.1)
60
66
  i18n (1.14.5)
61
67
  concurrent-ruby (~> 1.0)
62
68
  jaro_winkler (1.6.0)
@@ -98,6 +104,7 @@ GEM
98
104
  pry (0.14.2)
99
105
  coderay (~> 1.1)
100
106
  method_source (~> 1.0)
107
+ public_suffix (5.0.5)
101
108
  racc (1.7.3)
102
109
  rainbow (3.1.1)
103
110
  rake (13.2.0)
@@ -191,6 +198,11 @@ GEM
191
198
  concurrent-ruby (~> 1.0)
192
199
  unicode-display_width (2.5.0)
193
200
  uri (0.13.0)
201
+ vcr (6.2.0)
202
+ webmock (3.18.1)
203
+ addressable (>= 2.8.0)
204
+ crack (>= 0.3.2)
205
+ hashdiff (>= 0.4.0, < 2.0.0)
194
206
  yard (0.9.36)
195
207
  yard-sorbet (0.8.1)
196
208
  sorbet-runtime (>= 0.5)
@@ -217,6 +229,8 @@ DEPENDENCIES
217
229
  solargraph-rails (~> 0.2.0.pre)
218
230
  sorbet
219
231
  tapioca
232
+ vcr
233
+ webmock
220
234
 
221
235
  BUNDLED WITH
222
236
  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
@@ -238,6 +238,77 @@ Notably, Olympia does not use the `FunctionDispatch` module in its primary conve
238
238
 
239
239
  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.
240
240
 
241
+ ## Predicate Module
242
+
243
+ The `Raix::Predicate` module provides a simple way to handle yes/no/maybe questions using AI chat completion. It allows you to define blocks that handle different types of responses with their explanations. It is one of the concrete patterns described in the "Discrete Components" chapter of [Patterns of Application Development Using AI](https://leanpub.com/patterns-of-application-development-using-ai).
244
+
245
+ ### Usage
246
+
247
+ Include the `Raix::Predicate` module in your class and define handlers using block syntax:
248
+
249
+ ```ruby
250
+ class Question
251
+ include Raix::Predicate
252
+
253
+ yes? do |explanation|
254
+ puts "Affirmative: #{explanation}"
255
+ end
256
+
257
+ no? do |explanation|
258
+ puts "Negative: #{explanation}"
259
+ end
260
+
261
+ maybe? do |explanation|
262
+ puts "Uncertain: #{explanation}"
263
+ end
264
+ end
265
+
266
+ question = Question.new
267
+ question.ask("Is Ruby a programming language?")
268
+ # => Affirmative: Yes, Ruby is a dynamic, object-oriented programming language...
269
+ ```
270
+
271
+ ### Features
272
+
273
+ - Define handlers for yes, no, and/or maybe responses using the declarative class level block syntax.
274
+ - At least one handler (yes, no, or maybe) must be defined.
275
+ - Handlers receive the full AI response including explanation as an argument.
276
+ - Responses always start with "Yes, ", "No, ", or "Maybe, " followed by an explanation.
277
+ - Make sure to ask a question that can be answered with yes, no, or maybe (otherwise the results are indeterminate).
278
+
279
+ ### Example with Single Handler
280
+
281
+ You can define only the handlers you need:
282
+
283
+ ```ruby
284
+ class SimpleQuestion
285
+ include Raix::Predicate
286
+
287
+ # Only handle positive responses
288
+ yes? do |explanation|
289
+ puts "✅ #{explanation}"
290
+ end
291
+ end
292
+
293
+ question = SimpleQuestion.new
294
+ question.ask("Is 2 + 2 = 4?")
295
+ # => ✅ Yes, 2 + 2 equals 4, this is a fundamental mathematical fact.
296
+ ```
297
+
298
+ ### Error Handling
299
+
300
+ The module will raise a RuntimeError if you attempt to ask a question without defining any response handlers:
301
+
302
+ ```ruby
303
+ class InvalidQuestion
304
+ include Raix::Predicate
305
+ end
306
+
307
+ question = InvalidQuestion.new
308
+ question.ask("Any question")
309
+ # => RuntimeError: Please define a yes and/or no block
310
+ ```
311
+
241
312
  ## Installation
242
313
 
243
314
  Install the gem and add to the application's Gemfile by executing:
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raix
4
+ # A module for handling yes/no questions using AI chat completion.
5
+ # When included in a class, it provides methods to define handlers for
6
+ # yes and no responses.
7
+ #
8
+ # @example
9
+ # class Question
10
+ # include Raix::Predicate
11
+ #
12
+ # yes do |explanation|
13
+ # puts "Yes: #{explanation}"
14
+ # end
15
+ #
16
+ # no do |explanation|
17
+ # puts "No: #{explanation}"
18
+ # end
19
+ # end
20
+ #
21
+ # question = Question.new
22
+ # question.ask("Is Ruby a programming language?")
23
+ module Predicate
24
+ include ChatCompletion
25
+
26
+ def self.included(base)
27
+ base.extend(ClassMethods)
28
+ end
29
+
30
+ def ask(question)
31
+ raise "Please define a yes and/or no block" if self.class.yes_block.nil? && self.class.no_block.nil?
32
+
33
+ transcript << { system: "Always answer 'Yes, ', 'No, ', or 'Maybe, ' followed by a concise explanation!" }
34
+ transcript << { user: question }
35
+
36
+ chat_completion.tap do |response|
37
+ if response.downcase.start_with?("yes,")
38
+ instance_exec(response, &self.class.yes_block) if self.class.yes_block
39
+ elsif response.downcase.start_with?("no,")
40
+ instance_exec(response, &self.class.no_block) if self.class.no_block
41
+ elsif response.downcase.start_with?("maybe,")
42
+ instance_exec(response, &self.class.maybe_block) if self.class.maybe_block
43
+ end
44
+ end
45
+ end
46
+
47
+ # Class methods added to the including class
48
+ module ClassMethods
49
+ attr_reader :yes_block, :no_block, :maybe_block
50
+
51
+ def yes?(&block)
52
+ @yes_block = block
53
+ end
54
+
55
+ def no?(&block)
56
+ @no_block = block
57
+ end
58
+
59
+ def maybe?(&block)
60
+ @maybe_block = block
61
+ end
62
+ end
63
+ end
64
+ end
data/lib/raix/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Raix
4
- VERSION = "0.4.2"
4
+ VERSION = "0.4.3"
5
5
  end
data/raix.gemspec CHANGED
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  spec.add_dependency "activesupport", ">= 6.0"
32
32
  spec.add_dependency "open_router", "~> 0.2"
33
+ spec.add_dependency "ruby-openai", "~> 7.0"
33
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
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-11-05 00:00:00.000000000 Z
11
+ date: 2024-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby-openai
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '7.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '7.0'
41
55
  description:
42
56
  email:
43
57
  - obiefernandez@gmail.com
@@ -52,6 +66,7 @@ files:
52
66
  - CODE_OF_CONDUCT.md
53
67
  - Gemfile
54
68
  - Gemfile.lock
69
+ - Guardfile
55
70
  - LICENSE.txt
56
71
  - README.md
57
72
  - Rakefile
@@ -59,6 +74,7 @@ files:
59
74
  - lib/raix/chat_completion.rb
60
75
  - lib/raix/function_dispatch.rb
61
76
  - lib/raix/message_adapters/base.rb
77
+ - lib/raix/predicate.rb
62
78
  - lib/raix/prompt_declarations.rb
63
79
  - lib/raix/response_format.rb
64
80
  - lib/raix/version.rb