raix 0.4.3 → 0.4.5

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: 65adb56d006baa2b25193eaa5f87f52b24366876139d1e023c9031e7707f62ea
4
- data.tar.gz: 545da8c28e699f87e53547b53d07fb934727948945f45998f3af5d34752a2854
3
+ metadata.gz: 27b3ab0bd21ca08ecf0b23985b1e04b1787cfdb60e534744ff32234869930989
4
+ data.tar.gz: 822f7fe8ac7c76ce77aefff637b9d5e3163b904073329bcfb99e6d3661d38c48
5
5
  SHA512:
6
- metadata.gz: 71a14254199a7e0eeb8195b842fe212516091743ab4799f349caadfeffa654eede58dc066813e4d1360c24b2384406f934086d9b167b45db70a618470c94df84
7
- data.tar.gz: 3d5a8033107bc8037862d11cd88fb3cd7a67a6884b4f7d2d8756244ac676e938094a6580bd55275b353c0b12389f292cbe958138d8d13f2d1001c5befb81662d
6
+ metadata.gz: 2d469ff07f10f1085c40f4bc456c91f753e65b09304757eb95e77fe73df13c5ba334bdfa22f3649160c863e6ff268cde4e595f92221556a7d2761667e2a35d3c
7
+ data.tar.gz: a8e367b6e85adee53056ead496c97806deaba156dcab82634763c773f52e8d4e5c60c213669bdc3d8620c301e22b24862ec7f01fe77ee7c880b633b3d919b30c
data/CHANGELOG.md CHANGED
@@ -21,3 +21,10 @@
21
21
 
22
22
  ## [0.4.3] - 2024-11-11
23
23
  - adds support for `Predicate` module
24
+
25
+ ## [0.4.4] - 2024-11-11
26
+ - adds support for multiple tool calls in a single response
27
+
28
+ ## [0.4.5] - 2024-11-11
29
+ - adds support for `ResponseFormat`
30
+ - added some missing requires to support String#squish
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- raix (0.4.3)
4
+ raix (0.4.4)
5
5
  activesupport (>= 6.0)
6
6
  open_router (~> 0.2)
7
7
  ruby-openai (~> 7.0)
data/README.md CHANGED
@@ -101,6 +101,30 @@ RSpec.describe WhatIsTheWeather do
101
101
  end
102
102
  ```
103
103
 
104
+ #### Multiple Tool Calls
105
+
106
+ 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:
107
+
108
+ ```ruby
109
+ class MultipleToolExample
110
+ include Raix::ChatCompletion
111
+ include Raix::FunctionDispatch
112
+
113
+ function :first_tool do |arguments|
114
+ "Result from first tool"
115
+ end
116
+
117
+ function :second_tool do |arguments|
118
+ "Result from second tool"
119
+ end
120
+ end
121
+
122
+ example = MultipleToolExample.new
123
+ example.transcript << { user: "Please use both tools" }
124
+ results = example.chat_completion(openai: "gpt-4o")
125
+ # => ["Result from first tool", "Result from second tool"]
126
+ ```
127
+
104
128
  #### Manually Stopping a Loop
105
129
 
106
130
  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.
@@ -309,6 +333,97 @@ question.ask("Any question")
309
333
  # => RuntimeError: Please define a yes and/or no block
310
334
  ```
311
335
 
336
+ ## Response Format (Experimental)
337
+
338
+ The `ResponseFormat` class provides a way to declare a JSON schema for the response format of an AI chat completion. It's particularly useful when you need structured responses from AI models, ensuring the output conforms to your application's requirements.
339
+
340
+ ### Features
341
+
342
+ - Converts Ruby hashes and arrays into JSON schema format
343
+ - Supports nested structures and arrays
344
+ - Enforces strict validation with `additionalProperties: false`
345
+ - Automatically marks all top-level properties as required
346
+ - Handles both simple type definitions and complex nested schemas
347
+
348
+ ### Basic Usage
349
+
350
+ ```ruby
351
+ # Simple schema with basic types
352
+ format = Raix::ResponseFormat.new("PersonInfo", {
353
+ name: { type: "string" },
354
+ age: { type: "integer" }
355
+ })
356
+
357
+ # Use in chat completion
358
+ my_ai.chat_completion(response_format: format)
359
+ ```
360
+
361
+ ### Complex Structures
362
+
363
+ ```ruby
364
+ # Nested structure with arrays
365
+ format = Raix::ResponseFormat.new("CompanyInfo", {
366
+ company: {
367
+ name: { type: "string" },
368
+ employees: [
369
+ {
370
+ name: { type: "string" },
371
+ role: { type: "string" },
372
+ skills: ["string"]
373
+ }
374
+ ],
375
+ locations: ["string"]
376
+ }
377
+ })
378
+ ```
379
+
380
+ ### Generated Schema
381
+
382
+ The ResponseFormat class generates a schema that follows this structure:
383
+
384
+ ```json
385
+ {
386
+ "type": "json_schema",
387
+ "json_schema": {
388
+ "name": "SchemaName",
389
+ "schema": {
390
+ "type": "object",
391
+ "properties": {
392
+ "property1": { "type": "string" },
393
+ "property2": { "type": "integer" }
394
+ },
395
+ "required": ["property1", "property2"],
396
+ "additionalProperties": false
397
+ },
398
+ "strict": true
399
+ }
400
+ }
401
+ ```
402
+
403
+ ### Using with Chat Completion
404
+
405
+ When used with chat completion, the AI model will format its response according to your schema:
406
+
407
+ ```ruby
408
+ class StructuredResponse
409
+ include Raix::ChatCompletion
410
+
411
+ def analyze_person(name)
412
+ format = Raix::ResponseFormat.new("PersonAnalysis", {
413
+ full_name: { type: "string" },
414
+ age_estimate: { type: "integer" },
415
+ personality_traits: ["string"]
416
+ })
417
+
418
+ transcript << { user: "Analyze the person named #{name}" }
419
+ chat_completion(response_format: format)
420
+ end
421
+ end
422
+
423
+ response = StructuredResponse.new.analyze_person("Alice")
424
+ # Returns a hash matching the defined schema
425
+ ```
426
+
312
427
  ## Installation
313
428
 
314
429
  Install the gem and add to the application's Gemfile by executing:
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_support/concern"
4
4
  require "active_support/core_ext/object/blank"
5
+ require "active_support/core_ext/string/filters"
5
6
  require "open_router"
6
7
  require "openai"
7
8
 
@@ -12,10 +13,22 @@ module Raix
12
13
  # with the OpenRouter Chat Completion API via its client. The module includes a few
13
14
  # methods that allow you to build a transcript of messages and then send them to
14
15
  # the API for completion. The API will return a response that you can use however
15
- # you see fit. If the response includes a function call, the module will dispatch
16
- # the function call and return the result. Which implies that function calls need
17
- # to be defined on the class that includes this module. (Note: You should probably
18
- # use the `FunctionDispatch` module to define functions instead of doing it manually.)
16
+ # you see fit.
17
+ #
18
+ # If the response includes a function call, the module will dispatch the function
19
+ # call and return the result. Which implies that function calls need to be defined
20
+ # on the class that includes this module. The `FunctionDispatch` module provides a
21
+ # Rails-like DSL for declaring and implementing tool functions at the top of your
22
+ # class instead of having to manually implement them as instance methods. The
23
+ # primary benefit of using the `FunctionDispatch` module is that it handles
24
+ # adding the function call results to the ongoing conversation transcript for you.
25
+ # It also triggers a new chat completion automatically if you've set the `loop`
26
+ # option to `true`, which is useful for implementing conversational chatbots that
27
+ # include tool calls.
28
+ #
29
+ # Note that some AI models can make more than a single tool function call in a
30
+ # single response. When that happens, the module will dispatch all of the function
31
+ # calls sequentially and return an array of results.
19
32
  module ChatCompletion
20
33
  extend ActiveSupport::Concern
21
34
 
@@ -92,13 +105,13 @@ module Raix
92
105
  # TODO: add a standardized callback hook for usage events
93
106
  # broadcast(:usage_event, usage_subject, self.class.name.to_s, response, premium?)
94
107
 
95
- # TODO: handle parallel tool calls
96
- if (function = response.dig("choices", 0, "message", "tool_calls", 0, "function"))
97
- @current_function = function["name"]
98
- # dispatch the called function
99
- arguments = JSON.parse(function["arguments"].presence || "{}")
100
- arguments[:bot_message] = bot_message if respond_to?(:bot_message)
101
- return send(function["name"], arguments.with_indifferent_access)
108
+ tool_calls = response.dig("choices", 0, "message", "tool_calls") || []
109
+ if tool_calls.any?
110
+ return tool_calls.map do |tool_call|
111
+ # dispatch the called function
112
+ arguments = JSON.parse(tool_call["function"]["arguments"].presence || "{}")
113
+ send(tool_call["function"]["name"], arguments.with_indifferent_access)
114
+ end
102
115
  end
103
116
 
104
117
  response.tap do |res|
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ostruct"
4
+ require "active_support/core_ext/string/filters"
4
5
 
5
6
  module Raix
6
7
  # The PromptDeclarations module provides a way to chain prompts and handle
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/object/deep_dup"
4
+ require "active_support/core_ext/string/filters"
4
5
 
5
6
  module Raix
6
7
  # Handles the formatting of responses for AI interactions.
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.3"
4
+ VERSION = "0.4.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Obie Fernandez