raix 0.4.1 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +15 -1
- data/Guardfile +72 -0
- data/README.md +79 -0
- data/lib/raix/chat_completion.rb +8 -3
- data/lib/raix/predicate.rb +64 -0
- data/lib/raix/version.rb +1 -1
- data/raix.gemspec +1 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65adb56d006baa2b25193eaa5f87f52b24366876139d1e023c9031e7707f62ea
|
4
|
+
data.tar.gz: 545da8c28e699f87e53547b53d07fb934727948945f45998f3af5d34752a2854
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71a14254199a7e0eeb8195b842fe212516091743ab4799f349caadfeffa654eede58dc066813e4d1360c24b2384406f934086d9b167b45db70a618470c94df84
|
7
|
+
data.tar.gz: 3d5a8033107bc8037862d11cd88fb3cd7a67a6884b4f7d2d8756244ac676e938094a6580bd55275b353c0b12389f292cbe958138d8d13f2d1001c5befb81662d
|
data/CHANGELOG.md
CHANGED
@@ -15,3 +15,9 @@
|
|
15
15
|
## [0.4.0] - 2024-10-18
|
16
16
|
- adds support for Anthropic-style prompt caching
|
17
17
|
- defaults to `max_completion_tokens` when using OpenAI directly
|
18
|
+
|
19
|
+
## [0.4.2] - 2024-11-05
|
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
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
raix (0.4.
|
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
@@ -42,6 +42,14 @@ transcript << { role: "user", content: "What is the meaning of life?" }
|
|
42
42
|
|
43
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
44
|
|
45
|
+
### Predicted Outputs
|
46
|
+
|
47
|
+
Raix supports [Predicted Outputs](https://platform.openai.com/docs/guides/latency-optimization#use-predicted-outputs) with the `prediction` parameter for OpenAI.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
>> ai.chat_completion(openai: "gpt-4o", params: { prediction: })
|
51
|
+
```
|
52
|
+
|
45
53
|
### Prompt Caching
|
46
54
|
|
47
55
|
Raix supports [Anthropic-style prompt caching](https://openrouter.ai/docs/prompt-caching#anthropic-claude) when using Anthropic's Claud family of models. You can specify a `cache_at` parameter when doing a chat completion. If the character count for the content of a particular message is longer than the cache_at parameter, it will be sent to Anthropic as a multipart message with a cache control "breakpoint" set to "ephemeral".
|
@@ -230,6 +238,77 @@ Notably, Olympia does not use the `FunctionDispatch` module in its primary conve
|
|
230
238
|
|
231
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.
|
232
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
|
+
|
233
312
|
## Installation
|
234
313
|
|
235
314
|
Install the gem and add to the application's Gemfile by executing:
|
data/lib/raix/chat_completion.rb
CHANGED
@@ -20,7 +20,7 @@ module Raix
|
|
20
20
|
extend ActiveSupport::Concern
|
21
21
|
|
22
22
|
attr_accessor :cache_at, :frequency_penalty, :logit_bias, :logprobs, :loop, :min_p, :model, :presence_penalty,
|
23
|
-
:repetition_penalty, :response_format, :stream, :temperature, :max_completion_tokens,
|
23
|
+
:prediction, :repetition_penalty, :response_format, :stream, :temperature, :max_completion_tokens,
|
24
24
|
:max_tokens, :seed, :stop, :top_a, :top_k, :top_logprobs, :top_p, :tools, :tool_choice, :provider
|
25
25
|
|
26
26
|
# This method performs chat completion based on the provided transcript and parameters.
|
@@ -40,6 +40,7 @@ module Raix
|
|
40
40
|
params[:max_completion_tokens] ||= max_completion_tokens.presence || Raix.configuration.max_completion_tokens
|
41
41
|
params[:max_tokens] ||= max_tokens.presence || Raix.configuration.max_tokens
|
42
42
|
params[:min_p] ||= min_p.presence
|
43
|
+
params[:prediction] = { type: "content", content: params[:prediction] || prediction } if params[:prediction] || prediction.present?
|
43
44
|
params[:presence_penalty] ||= presence_penalty.presence
|
44
45
|
params[:provider] ||= provider.presence
|
45
46
|
params[:repetition_penalty] ||= repetition_penalty.presence
|
@@ -150,8 +151,12 @@ module Raix
|
|
150
151
|
private
|
151
152
|
|
152
153
|
def openai_request(params:, model:, messages:)
|
153
|
-
|
154
|
-
|
154
|
+
if params[:prediction]
|
155
|
+
params.delete(:max_completion_tokens)
|
156
|
+
else
|
157
|
+
params[:max_completion_tokens] ||= params[:max_tokens]
|
158
|
+
params.delete(:max_tokens)
|
159
|
+
end
|
155
160
|
|
156
161
|
params[:stream] ||= stream.presence
|
157
162
|
params[:stream_options] = { include_usage: true } if params[:stream]
|
@@ -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
data/raix.gemspec
CHANGED
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.
|
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
|
+
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
|
@@ -86,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
102
|
- !ruby/object:Gem::Version
|
87
103
|
version: '0'
|
88
104
|
requirements: []
|
89
|
-
rubygems_version: 3.
|
105
|
+
rubygems_version: 3.4.10
|
90
106
|
signing_key:
|
91
107
|
specification_version: 4
|
92
108
|
summary: Ruby AI eXtensions
|