raix 0.8.0 → 0.8.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: '08737974243ed41d4db2406405a4d081e31972cf173cfc3b2859cb97eabf0848'
4
- data.tar.gz: 0d78927f5a35fad586f300bff4d86ce773d7bb7cd9f6787320e1cff6297f1aed
3
+ metadata.gz: 04e086a8f8f010507fef64ba8f2a1fa7c4508b34d85b39d8d045088410ba041c
4
+ data.tar.gz: b6ddc5948d4d71a967321cfe664a6fb2ad6f38833c7067e0915216ce7fc64646
5
5
  SHA512:
6
- metadata.gz: c0eec6ef99a37cd8ba69dfec62c8be70c9a8f6ed0be78b24ed184bde364c78f5189ccac71e3196f56af4c85390844b044c30be1b69172fe4e0f3c584903ea7c0
7
- data.tar.gz: 90f084213b11dd737b55ed89fd21e75cf23b4c0ef5ab0daca8652f974b9fdb0f40a68ee07ea2abcd0b6af715ae3edc684643034b7e7e3455f7d05fae9ee1e473
6
+ metadata.gz: 1558de827080ea2fb1c0ab0a953ae4d4cb1a9f8ad1aaae0690942f78bfde31275739857948b064721aa83b0f9b963c91c4bcb8dc5be460283e88ee2294e10e64
7
+ data.tar.gz: 001c63730739473375f0bff5c7e4edb78e4247000825799cff5cd98f9ae56bf6f2a5c436f1334b03e05de3257ef081498e2c456567a70e65417c19b70ab8918b
data/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
- ## [0.8.0] - 2024-05-08
1
+ ## [0.8.2] - 2025-04-29
2
+ - Extracts function call dispatch into a public `dispatch_tool_function` that can be overridden in subclasses
3
+ - Uses `public_send` instead of `send` for better security and explicitness
4
+
5
+ ## [0.8.1] - 2025-04-24
6
+ Added ability to filter tool functions (or disable completely) when calling `chat_completion`. Thanks to @parruda for the contribution.
7
+
8
+ ## [0.8.0] - 2025-04-23
2
9
  ### Added
3
10
  * **MCP integration (Experimental)** — new `Raix::MCP` concern and `mcp` DSL for declaring remote MCP servers.
4
11
  * Automatically fetches `tools/list`, registers remote tools as OpenAI‑compatible function schemas, and defines proxy methods that forward `tools/call`.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- raix (0.8.0)
4
+ raix (0.8.2)
5
5
  activesupport (>= 6.0)
6
6
  faraday-retry (~> 2.0)
7
7
  open_router (~> 0.2)
data/README.md CHANGED
@@ -134,6 +134,41 @@ end
134
134
 
135
135
  Note that for security reasons, dispatching functions only works with functions implemented using `Raix::FunctionDispatch#function` or directly on the class.
136
136
 
137
+ #### Tool Filtering
138
+
139
+ You can control which tools are available to the AI on a given chat completion request using the `tools` parameter in the `chat_completion` method:
140
+
141
+ ```ruby
142
+ class WeatherAndTime
143
+ include Raix::ChatCompletion
144
+ include Raix::FunctionDispatch
145
+
146
+ function :check_weather, "Check the weather for a location", location: { type: "string" } do |arguments|
147
+ "The weather in #{arguments[:location]} is sunny"
148
+ end
149
+
150
+ function :get_time, "Get the current time" do |_arguments|
151
+ "The time is 12:00 PM"
152
+ end
153
+ end
154
+
155
+ weather = WeatherAndTime.new
156
+
157
+ # Don't pass any tools to the LLM
158
+ weather.chat_completion(tools: false)
159
+
160
+ # Only pass specific tools to the LLM
161
+ weather.chat_completion(tools: [:check_weather])
162
+
163
+ # Pass all declared tools (default behavior)
164
+ weather.chat_completion
165
+ ```
166
+
167
+ The `tools` parameter accepts three types of values:
168
+ - `false`: No tools are passed to the LLM
169
+ - An array of symbols: Only the specified tools are passed (raises `Raix::UndeclaredToolError` if any tool is not declared)
170
+ - Not provided: All declared tools are passed (default behavior)
171
+
137
172
  #### Multiple Tool Calls
138
173
 
139
174
  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.
@@ -171,6 +206,28 @@ example.invocations
171
206
  # => [:first, :second]
172
207
  ```
173
208
 
209
+ #### Customizing Function Dispatch
210
+
211
+ You can customize how function calls are handled by overriding the `dispatch_tool_function` in your class. This is useful if you need to add logging, caching, error handling, or other custom behavior around function calls.
212
+
213
+ ```ruby
214
+ class CustomDispatchExample
215
+ include Raix::ChatCompletion
216
+ include Raix::FunctionDispatch
217
+
218
+ function :example_tool do |arguments|
219
+ "Result from example tool"
220
+ end
221
+
222
+ def dispatch_tool_function(function_name, arguments)
223
+ puts "Calling #{function_name} with #{arguments}"
224
+ result = super
225
+ puts "Result: #{result}"
226
+ result
227
+ end
228
+ end
229
+ ```
230
+
174
231
  #### Manually Stopping a Loop
175
232
 
176
233
  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.
@@ -9,6 +9,8 @@ require "openai"
9
9
  require_relative "message_adapters/base"
10
10
 
11
11
  module Raix
12
+ class UndeclaredToolError < StandardError; end
13
+
12
14
  # The `ChatCompletion`` module is a Rails concern that provides a way to interact
13
15
  # with the OpenRouter Chat Completion API via its client. The module includes a few
14
16
  # methods that allow you to build a transcript of messages and then send them to
@@ -44,8 +46,9 @@ module Raix
44
46
  # @option params [Boolean] :openai (false) Whether to use OpenAI's API instead of OpenRouter's.
45
47
  # @option params [Boolean] :raw (false) Whether to return the raw response or dig the text content.
46
48
  # @option params [Array] :messages (nil) An array of messages to use instead of the transcript.
49
+ # @option tools [Array|false] :tools (nil) Tools to pass to the LLM. If false, no tools are passed. If an array, only declared tools in the array are passed.
47
50
  # @return [String|Hash] The completed chat response.
48
- def chat_completion(params: {}, loop: false, json: false, raw: false, openai: false, save_response: true, messages: nil)
51
+ def chat_completion(params: {}, loop: false, json: false, raw: false, openai: false, save_response: true, messages: nil, tools: nil)
49
52
  # set params to default values if not provided
50
53
  params[:cache_at] ||= cache_at.presence
51
54
  params[:frequency_penalty] ||= frequency_penalty.presence
@@ -63,7 +66,13 @@ module Raix
63
66
  params[:stop] ||= stop.presence
64
67
  params[:temperature] ||= temperature.presence || Raix.configuration.temperature
65
68
  params[:tool_choice] ||= tool_choice.presence
66
- params[:tools] ||= tools.presence
69
+ params[:tools] = if tools == false
70
+ nil
71
+ elsif tools.is_a?(Array)
72
+ filtered_tools(tools)
73
+ else
74
+ self.tools.presence
75
+ end
67
76
  params[:top_a] ||= top_a.presence
68
77
  params[:top_k] ||= top_k.presence
69
78
  params[:top_logprobs] ||= top_logprobs.presence
@@ -123,7 +132,7 @@ module Raix
123
132
  function_name = tool_call["function"]["name"]
124
133
  raise "Unauthorized function call: #{function_name}" unless self.class.functions.map { |f| f[:name].to_sym }.include?(function_name.to_sym)
125
134
 
126
- send(function_name, arguments.with_indifferent_access)
135
+ dispatch_tool_function(function_name, arguments.with_indifferent_access)
127
136
  end
128
137
  end
129
138
 
@@ -180,8 +189,30 @@ module Raix
180
189
  @transcript ||= []
181
190
  end
182
191
 
192
+ # Dispatches a tool function call with the given function name and arguments.
193
+ # This method can be overridden in subclasses to customize how function calls are handled.
194
+ #
195
+ # @param function_name [String] The name of the function to call
196
+ # @param arguments [Hash] The arguments to pass to the function
197
+ # @return [Object] The result of the function call
198
+ def dispatch_tool_function(function_name, arguments)
199
+ public_send(function_name, arguments)
200
+ end
201
+
183
202
  private
184
203
 
204
+ def filtered_tools(tool_names)
205
+ return nil if tool_names.blank?
206
+
207
+ requested_tools = tool_names.map(&:to_sym)
208
+ available_tool_names = tools.map { |tool| tool.dig(:function, :name).to_sym }
209
+
210
+ undeclared_tools = requested_tools - available_tool_names
211
+ raise UndeclaredToolError, "Undeclared tools: #{undeclared_tools.join(", ")}" if undeclared_tools.any?
212
+
213
+ tools.select { |tool| requested_tools.include?(tool.dig(:function, :name).to_sym) }
214
+ end
215
+
185
216
  def openai_request(params:, model:, messages:)
186
217
  if params[:prediction]
187
218
  params.delete(:max_completion_tokens)
@@ -94,10 +94,7 @@ module Raix
94
94
  end
95
95
 
96
96
  def chat_completion(**chat_completion_args)
97
- raise "No functions defined" if self.class.functions.blank?
98
-
99
97
  self.chat_completion_args = chat_completion_args
100
-
101
98
  super
102
99
  end
103
100
 
@@ -109,6 +106,8 @@ module Raix
109
106
  end
110
107
 
111
108
  def tools
109
+ return [] unless self.class.functions
110
+
112
111
  self.class.functions.map { |function| { type: "function", function: } }
113
112
  end
114
113
  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.8.0"
4
+ VERSION = "0.8.2"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Obie Fernandez
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-23 00:00:00.000000000 Z
10
+ date: 2025-04-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport