rasti-ai 1.0.0

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.
@@ -0,0 +1,297 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::AI::OpenAI::ToolSerializer do
4
+
5
+ let(:serializer) { Rasti::AI::OpenAI::ToolSerializer }
6
+
7
+ def build_tool_class(form_class=nil)
8
+ tool_class = Minitest::Mock.new
9
+ tool_class.expect :name, 'CallCustomFunction'
10
+ tool_class.expect :form, form_class if form_class
11
+ tool_class
12
+ end
13
+
14
+ def build_serializaton(param_name:, param_type:)
15
+ {
16
+ type: 'function',
17
+ function: {
18
+ name: 'call_custom_function',
19
+ parameters: {
20
+ type: 'object',
21
+ properties: {
22
+ param_name.to_sym => {
23
+ type: param_type
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+ end
30
+
31
+ it 'Basic tool' do
32
+ tool_class = build_tool_class
33
+
34
+ serialization = serializer.serialize tool_class
35
+
36
+ expeted_serialization = {
37
+ type: 'function',
38
+ function: {
39
+ name: 'call_custom_function'
40
+ }
41
+ }
42
+
43
+ assert_equal expeted_serialization, serialization
44
+
45
+ tool_class.verify
46
+ end
47
+
48
+ it 'With description' do
49
+ tool_class = build_tool_class
50
+ tool_class.expect :description, 'Call custom function without arguments'
51
+
52
+ serialization = serializer.serialize tool_class
53
+
54
+ expeted_serialization = {
55
+ type: 'function',
56
+ function: {
57
+ name: 'call_custom_function',
58
+ description: 'Call custom function without arguments',
59
+ }
60
+ }
61
+
62
+ assert_equal expeted_serialization, serialization
63
+
64
+ tool_class.verify
65
+ end
66
+
67
+ describe 'Arguments' do
68
+
69
+ describe 'Types' do
70
+
71
+ it 'String' do
72
+ form_class = Rasti::Form[text: Rasti::Types::String]
73
+
74
+ tool_class = build_tool_class form_class
75
+
76
+ serialization = serializer.serialize tool_class
77
+
78
+ expeted_serialization = build_serializaton param_name: 'text', param_type: 'string'
79
+
80
+ assert_equal expeted_serialization, serialization
81
+
82
+ tool_class.verify
83
+ end
84
+
85
+ it 'Integer' do
86
+ form_class = Rasti::Form[int: Rasti::Types::Integer]
87
+
88
+ tool_class = build_tool_class form_class
89
+
90
+ serialization = serializer.serialize tool_class
91
+
92
+ expeted_serialization = build_serializaton param_name: 'int', param_type: 'integer'
93
+
94
+ assert_equal expeted_serialization, serialization
95
+
96
+ tool_class.verify
97
+ end
98
+
99
+ it 'Number' do
100
+ form_class = Rasti::Form[number: Rasti::Types::Float]
101
+
102
+ tool_class = build_tool_class form_class
103
+
104
+ serialization = serializer.serialize tool_class
105
+
106
+ expeted_serialization = build_serializaton param_name: 'number', param_type: 'number'
107
+
108
+ assert_equal expeted_serialization, serialization
109
+
110
+ tool_class.verify
111
+ end
112
+
113
+ it 'Boolean' do
114
+ form_class = Rasti::Form[bool: Rasti::Types::Boolean]
115
+
116
+ tool_class = build_tool_class form_class
117
+
118
+ serialization = serializer.serialize tool_class
119
+
120
+ expeted_serialization = build_serializaton param_name: 'bool', param_type: 'boolean'
121
+
122
+ assert_equal expeted_serialization, serialization
123
+
124
+ tool_class.verify
125
+ end
126
+
127
+ it 'Time' do
128
+ form_class = Rasti::Form[timestamp: Rasti::Types::Time['%Y-%m-%dT%H:%M:%S%z']]
129
+
130
+ tool_class = build_tool_class form_class
131
+
132
+ serialization = serializer.serialize tool_class
133
+
134
+ expeted_serialization = build_serializaton param_name: 'timestamp', param_type: 'string'
135
+ expeted_serialization[:function][:parameters][:properties][:timestamp][:format] = 'date'
136
+
137
+ assert_equal expeted_serialization, serialization
138
+
139
+ tool_class.verify
140
+ end
141
+
142
+ it 'Enum' do
143
+ form_class = Rasti::Form[option: Rasti::Types::Enum['option_1', 'option_2']]
144
+
145
+ tool_class = build_tool_class form_class
146
+
147
+ serialization = serializer.serialize tool_class
148
+
149
+ expeted_serialization = build_serializaton param_name: 'option', param_type: 'string'
150
+ expeted_serialization[:function][:parameters][:properties][:option][:enum] = ['option_1', 'option_2']
151
+
152
+ assert_equal expeted_serialization, serialization
153
+
154
+ tool_class.verify
155
+ end
156
+
157
+ it 'Object' do
158
+ inner_form_class = Rasti::Form[text: Rasti::Types::String, int: Rasti::Types::Integer]
159
+ form_class = Rasti::Form[form: Rasti::Types::Model[inner_form_class]]
160
+
161
+ tool_class = build_tool_class form_class
162
+
163
+ serialization = serializer.serialize tool_class
164
+
165
+ expeted_serialization = build_serializaton param_name: 'form', param_type: 'object'
166
+ expeted_serialization[:function][:parameters][:properties][:form][:properties] = {
167
+ text: {type: 'string'},
168
+ int: {type: 'integer'}
169
+ }
170
+
171
+ assert_equal expeted_serialization, serialization
172
+
173
+ tool_class.verify
174
+ end
175
+
176
+ describe 'Array' do
177
+
178
+ it 'String' do
179
+ form_class = Rasti::Form[texts: Rasti::Types::Array[Rasti::Types::String]]
180
+
181
+ tool_class = build_tool_class form_class
182
+
183
+ serialization = serializer.serialize tool_class
184
+
185
+ expeted_serialization = build_serializaton param_name: 'texts', param_type: 'array'
186
+ expeted_serialization[:function][:parameters][:properties][:texts][:items] = {type: 'string'}
187
+
188
+ assert_equal expeted_serialization, serialization
189
+
190
+ tool_class.verify
191
+ end
192
+
193
+ it 'Number' do
194
+ form_class = Rasti::Form[numbers: Rasti::Types::Array[Rasti::Types::Float]]
195
+
196
+ tool_class = build_tool_class form_class
197
+
198
+ serialization = serializer.serialize tool_class
199
+
200
+ expeted_serialization = build_serializaton param_name: 'numbers', param_type: 'array'
201
+ expeted_serialization[:function][:parameters][:properties][:numbers][:items] = {type: 'number'}
202
+
203
+ assert_equal expeted_serialization, serialization
204
+
205
+ tool_class.verify
206
+ end
207
+
208
+ it 'Object' do
209
+ inner_form_class = Rasti::Form[text: Rasti::Types::String, int: Rasti::Types::Integer]
210
+ form_class = Rasti::Form[forms: Rasti::Types::Array[Rasti::Types::Model[inner_form_class]]]
211
+
212
+ tool_class = build_tool_class form_class
213
+
214
+ serialization = serializer.serialize tool_class
215
+
216
+ expeted_serialization = build_serializaton param_name: 'forms', param_type: 'array'
217
+ expeted_serialization[:function][:parameters][:properties][:forms][:items] = {
218
+ type: 'object',
219
+ properties: {
220
+ text: {type: 'string'},
221
+ int: {type: 'integer'}
222
+ }
223
+ }
224
+
225
+ assert_equal expeted_serialization, serialization
226
+
227
+ tool_class.verify
228
+ end
229
+
230
+ end
231
+
232
+ end
233
+
234
+ it 'Required' do
235
+ form_class = Class.new Rasti::Form do
236
+ attribute :text, Rasti::Types::String, required: true
237
+ attribute :int, Rasti::Types::Integer, required: false
238
+ end
239
+
240
+ tool_class = build_tool_class form_class
241
+
242
+ serialization = serializer.serialize tool_class
243
+
244
+ expeted_serialization = build_serializaton param_name: 'form', param_type: 'object'
245
+ expeted_serialization[:function][:parameters][:properties] = {
246
+ text: {type: 'string'},
247
+ int: {type: 'integer'}
248
+ }
249
+ expeted_serialization[:function][:parameters][:required] = [:text]
250
+
251
+ assert_equal expeted_serialization, serialization
252
+
253
+ tool_class.verify
254
+ end
255
+
256
+ it 'Description' do
257
+ form_class = Class.new Rasti::Form do
258
+ attribute :text, Rasti::Types::String, description: 'Text param'
259
+ attribute :int, Rasti::Types::Integer, description: 'Int param'
260
+ end
261
+
262
+ tool_class = build_tool_class form_class
263
+
264
+ serialization = serializer.serialize tool_class
265
+
266
+ expeted_serialization = build_serializaton param_name: 'form', param_type: 'object'
267
+ expeted_serialization[:function][:parameters][:properties] = {
268
+ text: {
269
+ description: 'Text param',
270
+ type: 'string'
271
+ },
272
+ int: {
273
+ description: 'Int param',
274
+ type: 'integer'
275
+ }
276
+ }
277
+
278
+ assert_equal expeted_serialization, serialization
279
+
280
+ tool_class.verify
281
+ end
282
+
283
+ end
284
+
285
+ it 'Invalid tool' do
286
+ form_class = Rasti::Form[obj: Object]
287
+ tool_class = build_tool_class form_class
288
+
289
+ error = assert_raises(Rasti::AI::Errors::ToolSerializationError) do
290
+ serializer.serialize tool_class
291
+ end
292
+
293
+ assert_equal "Tool serialization error: #{tool_class}", error.message
294
+ assert_equal 'Type not serializable Object', error.cause.message
295
+ end
296
+
297
+ end
@@ -0,0 +1 @@
1
+ {"model":"<%= model %>","messages":[{"role":"user","content":"<%= prompt %>"}],"tools":[],"tool_choice":"none"}
@@ -0,0 +1,36 @@
1
+ {
2
+ "id": "chatcmpl-BPudorJPTVaFGNU9thj1e81Vatlpt",
3
+ "object": "chat.completion",
4
+ "created": 1745515628,
5
+ "model": "gpt-4o-mini-2024-07-18",
6
+ "choices": [
7
+ {
8
+ "index": 0,
9
+ "message": {
10
+ "role": "assistant",
11
+ "content": "<%= content %>",
12
+ "refusal": null,
13
+ "annotations": []
14
+ },
15
+ "logprobs": null,
16
+ "finish_reason": "stop"
17
+ }
18
+ ],
19
+ "usage": {
20
+ "prompt_tokens": 27,
21
+ "completion_tokens": 229,
22
+ "total_tokens": 256,
23
+ "prompt_tokens_details": {
24
+ "cached_tokens": 0,
25
+ "audio_tokens": 0
26
+ },
27
+ "completion_tokens_details": {
28
+ "reasoning_tokens": 0,
29
+ "audio_tokens": 0,
30
+ "accepted_prediction_tokens": 0,
31
+ "rejected_prediction_tokens": 0
32
+ }
33
+ },
34
+ "service_tier": "default",
35
+ "system_fingerprint": "fp_dbaca60df0"
36
+ }
@@ -0,0 +1 @@
1
+ {"model":"<%= model %>","messages":[{"role":"user","content":"<%= prompt %>"}],"tools":<%= JSON.dump(tools) %>,"tool_choice":"auto"}
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "chatcmpl-BPy9KtqtdrccgC5dTTjaCmq2KKFS6",
3
+ "object": "chat.completion",
4
+ "created": 1745529114,
5
+ "model": "gpt-4o-mini-2024-07-18",
6
+ "choices": [
7
+ {
8
+ "index": 0,
9
+ "message": {
10
+ "role": "assistant",
11
+ "content": null,
12
+ "tool_calls": [
13
+ {
14
+ "id": "call_0D9Fo0O3TEGNMlFm7KllmKLK",
15
+ "type": "function",
16
+ "function": {
17
+ "name": "<%= name %>",
18
+ "arguments": "<%= JSON.dump(arguments).gsub('"', '\\"') %>"
19
+ }
20
+ }
21
+ ],
22
+ "refusal": null,
23
+ "annotations": []
24
+ },
25
+ "logprobs": null,
26
+ "finish_reason": "tool_calls"
27
+ }
28
+ ],
29
+ "usage": {
30
+ "prompt_tokens": 83,
31
+ "completion_tokens": 23,
32
+ "total_tokens": 106,
33
+ "prompt_tokens_details": {
34
+ "cached_tokens": 0,
35
+ "audio_tokens": 0
36
+ },
37
+ "completion_tokens_details": {
38
+ "reasoning_tokens": 0,
39
+ "audio_tokens": 0,
40
+ "accepted_prediction_tokens": 0,
41
+ "rejected_prediction_tokens": 0
42
+ }
43
+ },
44
+ "service_tier": "default",
45
+ "system_fingerprint": "fp_0392822090"
46
+ }
@@ -0,0 +1,17 @@
1
+ module Support
2
+ module Helpers
3
+ module ERB
4
+
5
+ def erb(template, variables={})
6
+ variables_binding = binding.tap do |b|
7
+ variables.each do |key, value|
8
+ b.local_variable_set key, value
9
+ end
10
+ end
11
+
12
+ ::ERB.new(template).result variables_binding
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Support
2
+ module Helpers
3
+ module Resources
4
+
5
+ RESOURCES_PATH = File.expand_path('../../../resources', __FILE__)
6
+
7
+ def resource_path(relative_path)
8
+ File.join RESOURCES_PATH, relative_path
9
+ end
10
+
11
+ def read_resource(relative_path, variables={})
12
+ filename = resource_path relative_path
13
+ content = File.read filename
14
+ erb content, variables
15
+ end
16
+
17
+ def read_json_resource(relative_path, variables={})
18
+ JSON.parse read_resource(relative_path, variables)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,243 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rasti-ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Gabriel Naiman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-04-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: multi_require
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rasti-form
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: inflecto
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: class_config
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '12.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '12.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5.0'
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '5.11'
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '5.0'
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '5.11'
103
+ - !ruby/object:Gem::Dependency
104
+ name: minitest-colorin
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.1'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.1'
117
+ - !ruby/object:Gem::Dependency
118
+ name: minitest-line
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.6'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '0.6'
131
+ - !ruby/object:Gem::Dependency
132
+ name: simplecov
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.12'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '0.12'
145
+ - !ruby/object:Gem::Dependency
146
+ name: pry-nav
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '0.2'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '0.2'
159
+ - !ruby/object:Gem::Dependency
160
+ name: webmock
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '3.0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '3.0'
173
+ description: AI for apps
174
+ email:
175
+ - gabynaiman@gmail.com
176
+ executables: []
177
+ extensions: []
178
+ extra_rdoc_files: []
179
+ files:
180
+ - ".github/workflows/ci.yml"
181
+ - ".gitignore"
182
+ - ".ruby-gemset"
183
+ - ".ruby-version"
184
+ - Gemfile
185
+ - LICENSE.txt
186
+ - README.md
187
+ - Rakefile
188
+ - lib/rasti-ai.rb
189
+ - lib/rasti/ai.rb
190
+ - lib/rasti/ai/errors.rb
191
+ - lib/rasti/ai/open_ai/assistant.rb
192
+ - lib/rasti/ai/open_ai/assistant_state.rb
193
+ - lib/rasti/ai/open_ai/client.rb
194
+ - lib/rasti/ai/open_ai/roles.rb
195
+ - lib/rasti/ai/open_ai/tool_serializer.rb
196
+ - lib/rasti/ai/version.rb
197
+ - rasti-ai.gemspec
198
+ - spec/coverage_helper.rb
199
+ - spec/minitest_helper.rb
200
+ - spec/open_ai/assistant_spec.rb
201
+ - spec/open_ai/client_spec.rb
202
+ - spec/open_ai/tool_serializer_spec.rb
203
+ - spec/resources/open_ai/basic_request.json
204
+ - spec/resources/open_ai/basic_response.json
205
+ - spec/resources/open_ai/tool_request.json
206
+ - spec/resources/open_ai/tool_response.json
207
+ - spec/support/helpers/erb.rb
208
+ - spec/support/helpers/resources.rb
209
+ homepage: https://github.com/gabynaiman/rasti-ai
210
+ licenses:
211
+ - MIT
212
+ metadata: {}
213
+ post_install_message:
214
+ rdoc_options: []
215
+ require_paths:
216
+ - lib
217
+ required_ruby_version: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ required_rubygems_version: !ruby/object:Gem::Requirement
223
+ requirements:
224
+ - - ">="
225
+ - !ruby/object:Gem::Version
226
+ version: '0'
227
+ requirements: []
228
+ rubygems_version: 3.3.26
229
+ signing_key:
230
+ specification_version: 4
231
+ summary: AI for apps
232
+ test_files:
233
+ - spec/coverage_helper.rb
234
+ - spec/minitest_helper.rb
235
+ - spec/open_ai/assistant_spec.rb
236
+ - spec/open_ai/client_spec.rb
237
+ - spec/open_ai/tool_serializer_spec.rb
238
+ - spec/resources/open_ai/basic_request.json
239
+ - spec/resources/open_ai/basic_response.json
240
+ - spec/resources/open_ai/tool_request.json
241
+ - spec/resources/open_ai/tool_response.json
242
+ - spec/support/helpers/erb.rb
243
+ - spec/support/helpers/resources.rb