rasti-ai 1.0.1 → 1.2.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/README.md +151 -3
- data/lib/rasti/ai/mcp/client.rb +68 -0
- data/lib/rasti/ai/mcp/errors.rb +29 -0
- data/lib/rasti/ai/mcp/server.rb +156 -0
- data/lib/rasti/ai/open_ai/assistant.rb +37 -4
- data/lib/rasti/ai/tool.rb +23 -0
- data/lib/rasti/ai/tool_serializer.rb +112 -0
- data/lib/rasti/ai/version.rb +1 -1
- data/log/.gitkeep +0 -0
- data/rasti-ai.gemspec +2 -0
- data/spec/mcp/client_spec.rb +256 -0
- data/spec/mcp/server_spec.rb +372 -0
- data/spec/minitest_helper.rb +2 -0
- data/spec/open_ai/assistant_spec.rb +29 -5
- data/spec/{open_ai/tool_serializer_spec.rb → tool_serializer_spec.rb} +21 -29
- metadata +42 -5
- data/lib/rasti/ai/open_ai/tool_serializer.rb +0 -111
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
require 'minitest_helper'
|
|
2
2
|
|
|
3
|
-
describe Rasti::AI::
|
|
3
|
+
describe Rasti::AI::ToolSerializer do
|
|
4
4
|
|
|
5
|
-
let(:serializer) { Rasti::AI::
|
|
5
|
+
let(:serializer) { Rasti::AI::ToolSerializer }
|
|
6
6
|
|
|
7
7
|
def build_tool_class(form_class=nil)
|
|
8
8
|
tool_class = Minitest::Mock.new
|
|
@@ -13,15 +13,12 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
13
13
|
|
|
14
14
|
def build_serializaton(param_name:, param_type:)
|
|
15
15
|
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
param_name.to_sym => {
|
|
23
|
-
type: param_type
|
|
24
|
-
}
|
|
16
|
+
name: 'call_custom_function',
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
param_name.to_sym => {
|
|
21
|
+
type: param_type
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
24
|
}
|
|
@@ -34,10 +31,7 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
34
31
|
serialization = serializer.serialize tool_class
|
|
35
32
|
|
|
36
33
|
expeted_serialization = {
|
|
37
|
-
|
|
38
|
-
function: {
|
|
39
|
-
name: 'call_custom_function'
|
|
40
|
-
}
|
|
34
|
+
name: 'call_custom_function'
|
|
41
35
|
}
|
|
42
36
|
|
|
43
37
|
assert_equal expeted_serialization, serialization
|
|
@@ -52,11 +46,8 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
52
46
|
serialization = serializer.serialize tool_class
|
|
53
47
|
|
|
54
48
|
expeted_serialization = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
name: 'call_custom_function',
|
|
58
|
-
description: 'Call custom function without arguments',
|
|
59
|
-
}
|
|
49
|
+
name: 'call_custom_function',
|
|
50
|
+
description: 'Call custom function without arguments',
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
assert_equal expeted_serialization, serialization
|
|
@@ -132,7 +123,7 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
132
123
|
serialization = serializer.serialize tool_class
|
|
133
124
|
|
|
134
125
|
expeted_serialization = build_serializaton param_name: 'timestamp', param_type: 'string'
|
|
135
|
-
expeted_serialization[:
|
|
126
|
+
expeted_serialization[:inputSchema][:properties][:timestamp][:format] = 'date'
|
|
136
127
|
|
|
137
128
|
assert_equal expeted_serialization, serialization
|
|
138
129
|
|
|
@@ -147,7 +138,8 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
147
138
|
serialization = serializer.serialize tool_class
|
|
148
139
|
|
|
149
140
|
expeted_serialization = build_serializaton param_name: 'option', param_type: 'string'
|
|
150
|
-
expeted_serialization[:
|
|
141
|
+
expeted_serialization[:inputSchema][:properties][:option][:enum] = ['option_1', 'option_2']
|
|
142
|
+
expeted_serialization[:inputSchema][:properties][:option][:description] = 'option_1, option_2'
|
|
151
143
|
|
|
152
144
|
assert_equal expeted_serialization, serialization
|
|
153
145
|
|
|
@@ -163,7 +155,7 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
163
155
|
serialization = serializer.serialize tool_class
|
|
164
156
|
|
|
165
157
|
expeted_serialization = build_serializaton param_name: 'form', param_type: 'object'
|
|
166
|
-
expeted_serialization[:
|
|
158
|
+
expeted_serialization[:inputSchema][:properties][:form][:properties] = {
|
|
167
159
|
text: {type: 'string'},
|
|
168
160
|
int: {type: 'integer'}
|
|
169
161
|
}
|
|
@@ -183,7 +175,7 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
183
175
|
serialization = serializer.serialize tool_class
|
|
184
176
|
|
|
185
177
|
expeted_serialization = build_serializaton param_name: 'texts', param_type: 'array'
|
|
186
|
-
expeted_serialization[:
|
|
178
|
+
expeted_serialization[:inputSchema][:properties][:texts][:items] = {type: 'string'}
|
|
187
179
|
|
|
188
180
|
assert_equal expeted_serialization, serialization
|
|
189
181
|
|
|
@@ -198,7 +190,7 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
198
190
|
serialization = serializer.serialize tool_class
|
|
199
191
|
|
|
200
192
|
expeted_serialization = build_serializaton param_name: 'numbers', param_type: 'array'
|
|
201
|
-
expeted_serialization[:
|
|
193
|
+
expeted_serialization[:inputSchema][:properties][:numbers][:items] = {type: 'number'}
|
|
202
194
|
|
|
203
195
|
assert_equal expeted_serialization, serialization
|
|
204
196
|
|
|
@@ -214,7 +206,7 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
214
206
|
serialization = serializer.serialize tool_class
|
|
215
207
|
|
|
216
208
|
expeted_serialization = build_serializaton param_name: 'forms', param_type: 'array'
|
|
217
|
-
expeted_serialization[:
|
|
209
|
+
expeted_serialization[:inputSchema][:properties][:forms][:items] = {
|
|
218
210
|
type: 'object',
|
|
219
211
|
properties: {
|
|
220
212
|
text: {type: 'string'},
|
|
@@ -242,11 +234,11 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
242
234
|
serialization = serializer.serialize tool_class
|
|
243
235
|
|
|
244
236
|
expeted_serialization = build_serializaton param_name: 'form', param_type: 'object'
|
|
245
|
-
expeted_serialization[:
|
|
237
|
+
expeted_serialization[:inputSchema][:properties] = {
|
|
246
238
|
text: {type: 'string'},
|
|
247
239
|
int: {type: 'integer'}
|
|
248
240
|
}
|
|
249
|
-
expeted_serialization[:
|
|
241
|
+
expeted_serialization[:inputSchema][:required] = [:text]
|
|
250
242
|
|
|
251
243
|
assert_equal expeted_serialization, serialization
|
|
252
244
|
|
|
@@ -264,7 +256,7 @@ describe Rasti::AI::OpenAI::ToolSerializer do
|
|
|
264
256
|
serialization = serializer.serialize tool_class
|
|
265
257
|
|
|
266
258
|
expeted_serialization = build_serializaton param_name: 'form', param_type: 'object'
|
|
267
|
-
expeted_serialization[:
|
|
259
|
+
expeted_serialization[:inputSchema][:properties] = {
|
|
268
260
|
text: {
|
|
269
261
|
description: 'Text param',
|
|
270
262
|
type: 'string'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rasti-ai
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabriel Naiman
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-11-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: multi_require
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '12.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rack-test
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '2.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '2.0'
|
|
83
97
|
- !ruby/object:Gem::Dependency
|
|
84
98
|
name: minitest
|
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -128,6 +142,20 @@ dependencies:
|
|
|
128
142
|
- - "~>"
|
|
129
143
|
- !ruby/object:Gem::Version
|
|
130
144
|
version: '0.6'
|
|
145
|
+
- !ruby/object:Gem::Dependency
|
|
146
|
+
name: minitest-extended_assertions
|
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '1.0'
|
|
152
|
+
type: :development
|
|
153
|
+
prerelease: false
|
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '1.0'
|
|
131
159
|
- !ruby/object:Gem::Dependency
|
|
132
160
|
name: simplecov
|
|
133
161
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -188,24 +216,31 @@ files:
|
|
|
188
216
|
- lib/rasti-ai.rb
|
|
189
217
|
- lib/rasti/ai.rb
|
|
190
218
|
- lib/rasti/ai/errors.rb
|
|
219
|
+
- lib/rasti/ai/mcp/client.rb
|
|
220
|
+
- lib/rasti/ai/mcp/errors.rb
|
|
221
|
+
- lib/rasti/ai/mcp/server.rb
|
|
191
222
|
- lib/rasti/ai/open_ai/assistant.rb
|
|
192
223
|
- lib/rasti/ai/open_ai/assistant_state.rb
|
|
193
224
|
- lib/rasti/ai/open_ai/client.rb
|
|
194
225
|
- lib/rasti/ai/open_ai/roles.rb
|
|
195
|
-
- lib/rasti/ai/
|
|
226
|
+
- lib/rasti/ai/tool.rb
|
|
227
|
+
- lib/rasti/ai/tool_serializer.rb
|
|
196
228
|
- lib/rasti/ai/version.rb
|
|
229
|
+
- log/.gitkeep
|
|
197
230
|
- rasti-ai.gemspec
|
|
198
231
|
- spec/coverage_helper.rb
|
|
232
|
+
- spec/mcp/client_spec.rb
|
|
233
|
+
- spec/mcp/server_spec.rb
|
|
199
234
|
- spec/minitest_helper.rb
|
|
200
235
|
- spec/open_ai/assistant_spec.rb
|
|
201
236
|
- spec/open_ai/client_spec.rb
|
|
202
|
-
- spec/open_ai/tool_serializer_spec.rb
|
|
203
237
|
- spec/resources/open_ai/basic_request.json
|
|
204
238
|
- spec/resources/open_ai/basic_response.json
|
|
205
239
|
- spec/resources/open_ai/tool_request.json
|
|
206
240
|
- spec/resources/open_ai/tool_response.json
|
|
207
241
|
- spec/support/helpers/erb.rb
|
|
208
242
|
- spec/support/helpers/resources.rb
|
|
243
|
+
- spec/tool_serializer_spec.rb
|
|
209
244
|
homepage: https://github.com/gabynaiman/rasti-ai
|
|
210
245
|
licenses:
|
|
211
246
|
- MIT
|
|
@@ -231,13 +266,15 @@ specification_version: 4
|
|
|
231
266
|
summary: AI for apps
|
|
232
267
|
test_files:
|
|
233
268
|
- spec/coverage_helper.rb
|
|
269
|
+
- spec/mcp/client_spec.rb
|
|
270
|
+
- spec/mcp/server_spec.rb
|
|
234
271
|
- spec/minitest_helper.rb
|
|
235
272
|
- spec/open_ai/assistant_spec.rb
|
|
236
273
|
- spec/open_ai/client_spec.rb
|
|
237
|
-
- spec/open_ai/tool_serializer_spec.rb
|
|
238
274
|
- spec/resources/open_ai/basic_request.json
|
|
239
275
|
- spec/resources/open_ai/basic_response.json
|
|
240
276
|
- spec/resources/open_ai/tool_request.json
|
|
241
277
|
- spec/resources/open_ai/tool_response.json
|
|
242
278
|
- spec/support/helpers/erb.rb
|
|
243
279
|
- spec/support/helpers/resources.rb
|
|
280
|
+
- spec/tool_serializer_spec.rb
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
module Rasti
|
|
2
|
-
module AI
|
|
3
|
-
module OpenAI
|
|
4
|
-
class ToolSerializer
|
|
5
|
-
class << self
|
|
6
|
-
|
|
7
|
-
def serialize(tool_class)
|
|
8
|
-
{
|
|
9
|
-
type: 'function',
|
|
10
|
-
function: serialize_function(tool_class)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
rescue => ex
|
|
14
|
-
raise Errors::ToolSerializationError.new(tool_class), cause: ex
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
def serialize_function(tool_class)
|
|
20
|
-
serialization = {
|
|
21
|
-
name: serialize_name(tool_class)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
serialization[:description] = normalize_description(tool_class.description) if tool_class.respond_to? :description
|
|
25
|
-
|
|
26
|
-
serialization[:parameters] = serialize_form(tool_class.form) if tool_class.respond_to? :form
|
|
27
|
-
|
|
28
|
-
serialization
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def serialize_name(tool_class)
|
|
32
|
-
Inflecto.underscore Inflecto.demodulize(tool_class.name)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def serialize_form(form_class)
|
|
36
|
-
serialized_attributes = form_class.attributes.each_with_object({}) do |attribute, hash|
|
|
37
|
-
hash[attribute.name] = serialize_attribute attribute
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
serialization = {
|
|
41
|
-
type: 'object',
|
|
42
|
-
properties: serialized_attributes
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
required_attributes = form_class.attributes.select { |a| a.option(:required) }
|
|
46
|
-
|
|
47
|
-
serialization[:required] = required_attributes.map(&:name) unless required_attributes.empty?
|
|
48
|
-
|
|
49
|
-
serialization
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def serialize_attribute(attribute)
|
|
53
|
-
serialization = {}
|
|
54
|
-
|
|
55
|
-
if attribute.option(:description)
|
|
56
|
-
serialization[:description] = normalize_description attribute.option(:description)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
serialization.merge! serialize_type(attribute.type)
|
|
60
|
-
|
|
61
|
-
serialization
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def serialize_type(type)
|
|
65
|
-
if type == Types::String
|
|
66
|
-
{type: 'string'}
|
|
67
|
-
|
|
68
|
-
elsif type == Types::Integer
|
|
69
|
-
{type: 'integer'}
|
|
70
|
-
|
|
71
|
-
elsif type == Types::Float
|
|
72
|
-
{type: 'number'}
|
|
73
|
-
|
|
74
|
-
elsif type == Types::Boolean
|
|
75
|
-
{type: 'boolean'}
|
|
76
|
-
|
|
77
|
-
elsif type.is_a? Types::Time
|
|
78
|
-
{
|
|
79
|
-
type: 'string',
|
|
80
|
-
format: 'date'
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
elsif type.is_a? Types::Enum
|
|
84
|
-
{
|
|
85
|
-
type: 'string',
|
|
86
|
-
enum: type.values
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
elsif type.is_a? Types::Array
|
|
90
|
-
{
|
|
91
|
-
type: 'array',
|
|
92
|
-
items: serialize_type(type.type)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
elsif type.is_a? Types::Model
|
|
96
|
-
serialize_form(type.model)
|
|
97
|
-
|
|
98
|
-
else
|
|
99
|
-
raise "Type not serializable #{type}"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def normalize_description(description)
|
|
104
|
-
description.split("\n").map(&:strip).join(' ').strip
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|