raix 0.7 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6783ed9671864edec5d734f8003822accee2dd7b89d0453a2c6973d35ecf2bf
4
- data.tar.gz: 90eac19ba012daaeed4ce898a2be3476d1dac9c6df2cbb66fbe88a5620b971c6
3
+ metadata.gz: 5f8ebb64b9a241bce3f7865030ecdf00f5d640df62b754e5d2cbde4768502d3b
4
+ data.tar.gz: ce540f67cda77e6697c2387a3fbc399da5559e0bf7a6e51e9db5eb0e50671d74
5
5
  SHA512:
6
- metadata.gz: 6753d494dd65e960373308189d7324e973c347f627ed82463b9ae3d7079446a87b7e012949c5a69adf734a1e9f37e8684b51a04f3a7b39b6b9772b501908949a
7
- data.tar.gz: 07f8d142ca423979013c17da2f8758a7113fc583fc2ee47b10cf64072a7be12aa7b53447821340239033866e8c108b1bea76ffc202fcd24e02cf814607df5989
6
+ metadata.gz: f62b2bc73bbb7a4ed5d45a1b59a6ae1de7ff75a2e2b078bd4918f8c6af7eb5b80bbe77052f916d5709158802a241cd6dd074fa4ebcc79e2596ae837c7dc7889e
7
+ data.tar.gz: d3805d4059eba9ad536ae34fa0132a6eb0b608cc92c4d32c5fcf6840cabb5f58b85954f64ef3852459c63159b963a5e6e75e3c94d99eaf84da2046430478a11f
data/CHANGELOG.md CHANGED
@@ -16,7 +16,6 @@
16
16
  - adds support for `execute_ai_request` in `PromptDeclarations` to handle API calls
17
17
  - adds support for `chat_completion_from_superclass` in `PromptDeclarations` to handle superclass calls
18
18
  - adds support for `model`, `temperature`, and `max_tokens` in `PromptDeclarations` to access prompt parameters
19
- - fixes function return values in `FunctionDispatch` to properly return results from tool calls (thanks @ttilberg)
20
19
  - Make automatic JSON parsing available to non-OpenAI providers that don't support the response_format parameter by scanning for json XML tags
21
20
 
22
21
  ## [0.6.0] - 2024-11-12
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- raix (0.7)
4
+ raix (0.7.2)
5
5
  activesupport (>= 6.0)
6
6
  open_router (~> 0.2)
7
7
  ruby-openai (~> 7.0)
data/README.md CHANGED
@@ -4,7 +4,7 @@
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
- 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.
7
+ Understanding 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
8
 
9
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
10
 
@@ -23,6 +23,7 @@ end
23
23
 
24
24
  => "The question of the meaning of life is one of the most profound and enduring inquiries in philosophy, religion, and science.
25
25
  Different perspectives offer various answers..."
26
+ ```
26
27
 
27
28
  By default, Raix will automatically add the AI's response to the transcript. This behavior can be controlled with the `save_response` parameter, which defaults to `true`. You may want to set it to `false` when making multiple chat completion calls during the lifecycle of a single object (whether sequentially or in parallel) and want to manage the transcript updates yourself:
28
29
 
@@ -32,7 +33,7 @@ By default, Raix will automatically add the AI's response to the transcript. Thi
32
33
 
33
34
  #### Transcript Format
34
35
 
35
- 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.
36
+ 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 shown in the example above.
36
37
 
37
38
  ```ruby
38
39
  transcript << { user: "What is the meaning of life?" }
@@ -56,7 +57,7 @@ Raix supports [Predicted Outputs](https://platform.openai.com/docs/guides/latenc
56
57
 
57
58
  ### Prompt Caching
58
59
 
59
- 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".
60
+ Raix supports [Anthropic-style prompt caching](https://openrouter.ai/docs/prompt-caching#anthropic-claude) when using Anthropic's Claude 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".
60
61
 
61
62
  Note that there is a limit of four breakpoints, and the cache will expire within five minutes. Therefore, it is recommended to reserve the cache breakpoints for large bodies of text, such as character cards, CSV data, RAG data, book chapters, etc. Raix does not enforce a limit on the number of breakpoints, which means that you might get an error if you try to cache too many messages.
62
63
 
@@ -78,6 +79,26 @@ Note that there is a limit of four breakpoints, and the cache will expire within
78
79
  },
79
80
  ```
80
81
 
82
+ ### JSON Mode
83
+
84
+ Raix supports JSON mode for chat completions, which ensures that the AI model's response is valid JSON. This is particularly useful when you need structured data from the model.
85
+
86
+ When using JSON mode with OpenAI models, Raix will automatically set the `response_format` parameter on requests accordingly, and attempt to parse the entire response body as JSON.
87
+ When using JSON mode with other models (e.g. Anthropic) that don't support `response_format`, Raix will look for JSON content inside of &lt;json&gt; XML tags in the response, before
88
+ falling back to parsing the entire response body. Make sure you tell the AI to reply with JSON inside of XML tags.
89
+
90
+ ```ruby
91
+ >> my_class.chat_completion(json: true)
92
+ => { "key": "value" }
93
+ ```
94
+
95
+ When using JSON mode with non-OpenAI providers, Raix automatically sets the `require_parameters` flag to ensure proper JSON formatting. You can also combine JSON mode with other parameters:
96
+
97
+ ```ruby
98
+ >> my_class.chat_completion(json: true, openai: "gpt-4o")
99
+ => { "key": "value" }
100
+ ```
101
+
81
102
  ### Use of Tools/Functions
82
103
 
83
104
  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.
@@ -109,44 +130,39 @@ Note that for security reasons, dispatching functions only works with functions
109
130
 
110
131
  #### Multiple Tool Calls
111
132
 
112
- Some AI models (like GPT-4) can make multiple tool calls in a single response. When this happens, Raix will automatically handle all the function calls sequentially and return an array of their results. Here's an example:
133
+ Some AI models (like GPT-4) can make multiple tool calls in a single response. When this happens, Raix will automatically handle all the function calls sequentially.
134
+ If you need to capture the arguments to the function calls, do so in the block passed to `function`. The response from `chat_completion` is always the final text
135
+ response from the assistant, and is not affected by function calls.
113
136
 
114
137
  ```ruby
115
138
  class MultipleToolExample
116
139
  include Raix::ChatCompletion
117
140
  include Raix::FunctionDispatch
118
141
 
142
+ attr_reader :invocations
143
+
119
144
  function :first_tool do |arguments|
145
+ @invocations << :first
120
146
  "Result from first tool"
121
147
  end
122
148
 
123
149
  function :second_tool do |arguments|
150
+ @invocations << :second
124
151
  "Result from second tool"
125
152
  end
153
+
154
+ def initialize
155
+ @invocations = []
156
+ end
126
157
  end
127
158
 
128
159
  example = MultipleToolExample.new
129
160
  example.transcript << { user: "Please use both tools" }
130
- results = example.chat_completion(openai: "gpt-4o")
131
- # => ["Result from first tool", "Result from second tool"]
132
- ```
133
-
134
- Note that as of version 0.6.1, function return values are properly returned from tool calls, whether in single or multiple tool call scenarios. This means you can use the return values from your functions in your application logic.
135
-
136
- ```ruby
137
- class DiceRoller
138
- include Raix::ChatCompletion
139
- include Raix::FunctionDispatch
140
-
141
- function :roll_dice, "Roll a die with specified number of faces", faces: { type: :integer } do |arguments|
142
- rand(1..arguments[:faces])
143
- end
144
- end
161
+ example.chat_completion(openai: "gpt-4o")
162
+ # => "I used both tools, as requested"
145
163
 
146
- roller = DiceRoller.new
147
- roller.transcript << { user: "Roll a d20 twice" }
148
- results = roller.chat_completion(openai: "gpt-4o")
149
- # => [15, 7] # Actual random dice roll results
164
+ example.invocations
165
+ # => [:first, :second]
150
166
  ```
151
167
 
152
168
  #### Manually Stopping a Loop
@@ -224,7 +240,7 @@ class PromptSubscriber
224
240
 
225
241
  attr_accessor :conversation, :bot_message, :user_message
226
242
 
227
- # many other declarations ommitted...
243
+ # many other declarations omitted...
228
244
 
229
245
  prompt call: FetchUrlCheck
230
246
 
@@ -548,13 +564,13 @@ You will also need to configure the OpenRouter API access token as per the instr
548
564
 
549
565
  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.
550
566
 
551
- 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.
567
+ 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 unversioned `.env` file and they will be picked up by the `dotenv` gem.
552
568
 
553
569
  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).
554
570
 
555
571
  ## Contributing
556
572
 
557
- 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).
573
+ 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).
558
574
 
559
575
  ## License
560
576
 
@@ -562,4 +578,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
562
578
 
563
579
  ## Code of Conduct
564
580
 
565
- 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).
581
+ 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).
@@ -43,8 +43,9 @@ module Raix
43
43
  # @option params [Boolean] :json (false) Whether to return the parse the response as a JSON object. Will search for <json> tags in the response first, then fall back to the default JSON parsing of the entire response.
44
44
  # @option params [Boolean] :openai (false) Whether to use OpenAI's API instead of OpenRouter's.
45
45
  # @option params [Boolean] :raw (false) Whether to return the raw response or dig the text content.
46
+ # @option params [Array] :messages (nil) An array of messages to use instead of the transcript.
46
47
  # @return [String|Hash] The completed chat response.
47
- def chat_completion(params: {}, loop: false, json: false, raw: false, openai: false, save_response: true)
48
+ def chat_completion(params: {}, loop: false, json: false, raw: false, openai: false, save_response: true, messages: nil)
48
49
  # set params to default values if not provided
49
50
  params[:cache_at] ||= cache_at.presence
50
51
  params[:frequency_penalty] ||= frequency_penalty.presence
@@ -68,13 +69,17 @@ module Raix
68
69
  params[:top_logprobs] ||= top_logprobs.presence
69
70
  params[:top_p] ||= top_p.presence
70
71
 
72
+ json = true if params[:response_format].is_a?(Raix::ResponseFormat)
73
+
71
74
  if json
72
75
  unless openai
73
76
  params[:provider] ||= {}
74
77
  params[:provider][:require_parameters] = true
75
78
  end
76
- params[:response_format] ||= {}
77
- params[:response_format][:type] = "json_object"
79
+ if params[:response_format].blank?
80
+ params[:response_format] ||= {}
81
+ params[:response_format][:type] = "json_object"
82
+ end
78
83
  end
79
84
 
80
85
  # used by FunctionDispatch
@@ -87,7 +92,9 @@ module Raix
87
92
 
88
93
  # duplicate the transcript to avoid race conditions in situations where
89
94
  # chat_completion is called multiple times in parallel
90
- messages = transcript.flatten.compact.map { |msg| adapter.transform(msg) }.dup
95
+ # TODO: Defensive programming, ensure messages is an array
96
+ messages ||= transcript.flatten.compact
97
+ messages = messages.map { |msg| adapter.transform(msg) }.dup
91
98
  raise "Can't complete an empty transcript" if messages.blank?
92
99
 
93
100
  begin
@@ -70,7 +70,7 @@ module Raix
70
70
  }
71
71
  ]
72
72
  }
73
- result = instance_exec(arguments, &block).tap do |content|
73
+ instance_exec(arguments, &block).tap do |content|
74
74
  transcript << {
75
75
  role: "tool",
76
76
  tool_call_id: id,
@@ -81,10 +81,6 @@ module Raix
81
81
  end
82
82
 
83
83
  chat_completion(**chat_completion_args) if loop
84
-
85
- # Return the result of the function call in case that's what the caller wants
86
- # https://github.com/OlympiaAI/raix/issues/16
87
- result
88
84
  end
89
85
  end
90
86
  end
@@ -50,16 +50,20 @@ module Raix
50
50
  {}.tap do |response|
51
51
  case input
52
52
  when Array
53
- properties = {}
54
- input.each { |item| properties.merge!(decode(item)) }
55
-
56
53
  response[:type] = "array"
57
- response[:items] = {
58
- type: "object",
59
- properties:,
60
- required: properties.keys.select { |key| properties[key].delete(:required) },
61
- additionalProperties: false
62
- }
54
+
55
+ if input.size == 1 && input.first.is_a?(String)
56
+ response[:items] = { type: input.first }
57
+ else
58
+ properties = {}
59
+ input.each { |item| properties.merge!(decode(item)) }
60
+ response[:items] = {
61
+ type: "object",
62
+ properties:,
63
+ required: properties.keys.select { |key| properties[key].delete(:required) },
64
+ additionalProperties: false
65
+ }
66
+ end
63
67
  when Hash
64
68
  input.each do |key, value|
65
69
  response[key] = if value.is_a?(Hash) && value.key?(:type)
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.7"
4
+ VERSION = "0.7.2"
5
5
  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.7'
4
+ version: 0.7.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: 2025-04-13 00:00:00.000000000 Z
11
+ date: 2025-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport