rasti-ai 1.1.0 → 1.2.1
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 +25 -3
- data/lib/rasti/ai/open_ai/client.rb +43 -19
- 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/lib/rasti/ai.rb +4 -0
- 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 +4 -0
- data/spec/open_ai/assistant_spec.rb +9 -2
- 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
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
require 'minitest_helper'
|
|
2
|
+
|
|
3
|
+
describe Rasti::AI::MCP::Server do
|
|
4
|
+
|
|
5
|
+
class HelloWorldTool < Rasti::AI::Tool
|
|
6
|
+
def self.description
|
|
7
|
+
'Hello World'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def execute(form)
|
|
11
|
+
{text: 'Hello world'}
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class SumTool < Rasti::AI::Tool
|
|
16
|
+
class Form < Rasti::Form
|
|
17
|
+
attribute :number_a, Rasti::Types::Float
|
|
18
|
+
attribute :number_b, Rasti::Types::Float
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def execute(form)
|
|
22
|
+
{result: form.number_a + form.number_b}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class ErrorTool < Rasti::AI::Tool
|
|
27
|
+
def execute(form)
|
|
28
|
+
raise "Unexpected tool error"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
include Rack::Test::Methods
|
|
33
|
+
|
|
34
|
+
let(:app) do
|
|
35
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['App response']] }
|
|
36
|
+
Rasti::AI::MCP::Server.new app
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
before do
|
|
40
|
+
Rasti::AI::MCP::Server.clear_tools
|
|
41
|
+
Rasti::AI::MCP::Server.restore_default_configuration
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def post_mcp_request(method, params={})
|
|
45
|
+
request_data = {
|
|
46
|
+
jsonrpc: '2.0',
|
|
47
|
+
id: 1,
|
|
48
|
+
method: method
|
|
49
|
+
}
|
|
50
|
+
request_data[:params] = params unless params.empty?
|
|
51
|
+
|
|
52
|
+
post Rasti::AI::MCP::Server.relative_path, JSON.dump(request_data), CONTENT_TYPE: 'application/json'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def assert_jsonrpc_success(expected_result)
|
|
56
|
+
expected_response = {
|
|
57
|
+
jsonrpc: '2.0',
|
|
58
|
+
id: 1,
|
|
59
|
+
result: expected_result
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
assert_equal 200, last_response.status
|
|
63
|
+
assert_equal 'application/json', last_response.content_type
|
|
64
|
+
assert_equal_json JSON.dump(expected_response), last_response.body
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def assert_jsonrpc_error(expected_error)
|
|
68
|
+
expected_response = {
|
|
69
|
+
jsonrpc: '2.0',
|
|
70
|
+
id: 1,
|
|
71
|
+
error: expected_error
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
assert_equal 200, last_response.status
|
|
75
|
+
assert_equal 'application/json', last_response.content_type
|
|
76
|
+
assert_equal_json JSON.dump(expected_response), last_response.body
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe 'Tool registration' do
|
|
80
|
+
|
|
81
|
+
it 'Register tool' do
|
|
82
|
+
Rasti::AI::MCP::Server.register_tool HelloWorldTool.new
|
|
83
|
+
Rasti::AI::MCP::Server.register_tool SumTool.new
|
|
84
|
+
|
|
85
|
+
serializations = Rasti::AI::MCP::Server.tools_serializations
|
|
86
|
+
|
|
87
|
+
expeted_serializations = [
|
|
88
|
+
Rasti::AI::ToolSerializer.serialize(HelloWorldTool),
|
|
89
|
+
Rasti::AI::ToolSerializer.serialize(SumTool)
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
assert_equal expeted_serializations, serializations
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'Register duplicate tool raises error' do
|
|
96
|
+
Rasti::AI::MCP::Server.register_tool HelloWorldTool.new
|
|
97
|
+
|
|
98
|
+
error = assert_raises RuntimeError do
|
|
99
|
+
Rasti::AI::MCP::Server.register_tool HelloWorldTool.new
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
assert_equal 'Tool hello_world_tool already exist', error.message
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'Tools serializations empty when no tools' do
|
|
106
|
+
assert_empty Rasti::AI::MCP::Server.tools_serializations
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'Call tool executes registered tool' do
|
|
110
|
+
Rasti::AI::MCP::Server.register_tool HelloWorldTool.new
|
|
111
|
+
|
|
112
|
+
result = Rasti::AI::MCP::Server.call_tool 'hello_world_tool'
|
|
113
|
+
|
|
114
|
+
assert_equal '{"text":"Hello world"}', result
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'Call tool not found raises error' do
|
|
118
|
+
error = assert_raises RuntimeError do
|
|
119
|
+
Rasti::AI::MCP::Server.call_tool 'non_existent', {}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
assert_equal 'Tool non_existent not found', error.message
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe 'Initialize request' do
|
|
128
|
+
|
|
129
|
+
it 'Returns protocol version and capabilities' do
|
|
130
|
+
post_mcp_request 'initialize'
|
|
131
|
+
|
|
132
|
+
expected_result = {
|
|
133
|
+
protocolVersion: '2024-11-05',
|
|
134
|
+
capabilities: {
|
|
135
|
+
tools: {
|
|
136
|
+
list: true,
|
|
137
|
+
call: true
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
serverInfo: {
|
|
141
|
+
name: 'MCP Server',
|
|
142
|
+
version: '1.0.0'
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
assert_jsonrpc_success expected_result
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'Custom server configuration' do
|
|
150
|
+
Rasti::AI::MCP::Server.configure do |config|
|
|
151
|
+
config.server_name = 'Custom MCP Server'
|
|
152
|
+
config.server_version = '2.0.0'
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
post_mcp_request 'initialize'
|
|
156
|
+
|
|
157
|
+
expected_result = {
|
|
158
|
+
protocolVersion: '2024-11-05',
|
|
159
|
+
capabilities: {
|
|
160
|
+
tools: {
|
|
161
|
+
list: true,
|
|
162
|
+
call: true
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
serverInfo: {
|
|
166
|
+
name: 'Custom MCP Server',
|
|
167
|
+
version: '2.0.0'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
assert_jsonrpc_success expected_result
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe 'Tools list request' do
|
|
177
|
+
|
|
178
|
+
it 'Returns empty list when no tools' do
|
|
179
|
+
post_mcp_request 'tools/list'
|
|
180
|
+
|
|
181
|
+
expected_result = {
|
|
182
|
+
tools: []
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
assert_jsonrpc_success expected_result
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'Returns registered tools' do
|
|
189
|
+
Rasti::AI::MCP::Server.register_tool HelloWorldTool.new
|
|
190
|
+
Rasti::AI::MCP::Server.register_tool SumTool.new
|
|
191
|
+
|
|
192
|
+
post_mcp_request 'tools/list'
|
|
193
|
+
|
|
194
|
+
expected_result = {
|
|
195
|
+
tools: [
|
|
196
|
+
Rasti::AI::ToolSerializer.serialize(HelloWorldTool),
|
|
197
|
+
Rasti::AI::ToolSerializer.serialize(SumTool)
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
assert_jsonrpc_success expected_result
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
describe 'Tools call request' do
|
|
207
|
+
|
|
208
|
+
before do
|
|
209
|
+
Rasti::AI::MCP::Server.register_tool HelloWorldTool.new
|
|
210
|
+
Rasti::AI::MCP::Server.register_tool SumTool.new
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'Executes tool with arguments' do
|
|
214
|
+
params = {
|
|
215
|
+
name: 'sum_tool',
|
|
216
|
+
arguments: {
|
|
217
|
+
number_a: 1,
|
|
218
|
+
number_b: 2
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
post_mcp_request 'tools/call', params
|
|
223
|
+
|
|
224
|
+
expected_result = {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: 'text',
|
|
228
|
+
text: '{"result":3.0}'
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
assert_jsonrpc_success expected_result
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it 'Executes tool without arguments' do
|
|
237
|
+
params = {
|
|
238
|
+
name: 'hello_world_tool'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
post_mcp_request 'tools/call', params
|
|
242
|
+
|
|
243
|
+
expected_result = {
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: 'text',
|
|
247
|
+
text: '{"text":"Hello world"}'
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
assert_jsonrpc_success expected_result
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'Tool call with nonexistent tool returns error' do
|
|
256
|
+
params = {
|
|
257
|
+
name: 'nonexistent'
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
post_mcp_request 'tools/call', params
|
|
261
|
+
|
|
262
|
+
expected_error = {
|
|
263
|
+
code: -32603,
|
|
264
|
+
message: 'Tool nonexistent not found'
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
assert_jsonrpc_error expected_error
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'Tool execution error returns error response' do
|
|
271
|
+
Rasti::AI::MCP::Server.register_tool ErrorTool.new
|
|
272
|
+
|
|
273
|
+
params = {
|
|
274
|
+
name: 'error_tool'
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
post_mcp_request 'tools/call', params
|
|
278
|
+
|
|
279
|
+
expected_error = {
|
|
280
|
+
code: -32603,
|
|
281
|
+
message: 'Unexpected tool error'
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
assert_jsonrpc_error expected_error
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
describe 'Error handling' do
|
|
290
|
+
|
|
291
|
+
it 'Method not found' do
|
|
292
|
+
post_mcp_request 'invalid/method'
|
|
293
|
+
|
|
294
|
+
expected_error = {
|
|
295
|
+
code: -32601,
|
|
296
|
+
message: 'Method not found'
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
assert_jsonrpc_error expected_error
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it 'Invalid JSON returns 400' do
|
|
303
|
+
post '/mcp', 'invalid json{'
|
|
304
|
+
|
|
305
|
+
assert_equal 400, last_response.status
|
|
306
|
+
assert_equal 'application/json', last_response.content_type
|
|
307
|
+
|
|
308
|
+
json_response = JSON.parse last_response.body
|
|
309
|
+
assert_equal -32700, json_response['error']['code']
|
|
310
|
+
assert_match /unexpected token/, json_response['error']['message']
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'Unhandled exception returns 500' do
|
|
314
|
+
app.stub :handle_initialize, ->(_) { raise 'Unexpected server error' } do
|
|
315
|
+
post_mcp_request 'initialize'
|
|
316
|
+
|
|
317
|
+
expected_response = {
|
|
318
|
+
jsonrpc: '2.0',
|
|
319
|
+
id: nil,
|
|
320
|
+
error: {
|
|
321
|
+
code: -32603,
|
|
322
|
+
message: 'Unexpected server error'
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
assert_equal 500, last_response.status
|
|
327
|
+
assert_equal 'application/json', last_response.content_type
|
|
328
|
+
assert_equal_json JSON.dump(expected_response), last_response.body
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
describe 'Middleware behavior' do
|
|
335
|
+
|
|
336
|
+
it 'Non MCP requests passed to app' do
|
|
337
|
+
get '/other/path'
|
|
338
|
+
|
|
339
|
+
assert_equal 200, last_response.status
|
|
340
|
+
assert_equal 'App response', last_response.body
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it 'GET requests to MCP path passed to app' do
|
|
344
|
+
get '/mcp'
|
|
345
|
+
|
|
346
|
+
assert_equal 200, last_response.status
|
|
347
|
+
assert_equal 'App response', last_response.body
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'POST to different path passed to app' do
|
|
351
|
+
post '/other/path'
|
|
352
|
+
|
|
353
|
+
assert_equal 200, last_response.status
|
|
354
|
+
assert_equal 'App response', last_response.body
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it 'Custom relative path' do
|
|
358
|
+
Rasti::AI::MCP::Server.configure do |config|
|
|
359
|
+
config.relative_path = '/path/too/custom_mcp'
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
post '/mcp'
|
|
363
|
+
assert_equal 200, last_response.status
|
|
364
|
+
assert_equal 'App response', last_response.body
|
|
365
|
+
|
|
366
|
+
post_mcp_request 'tools/list'
|
|
367
|
+
assert_jsonrpc_success tools: []
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
end
|
data/spec/minitest_helper.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
require 'coverage_helper'
|
|
2
2
|
require 'minitest/autorun'
|
|
3
3
|
require 'minitest/colorin'
|
|
4
|
+
require 'minitest/extended_assertions'
|
|
4
5
|
require 'webmock/minitest'
|
|
6
|
+
require 'rack/test'
|
|
5
7
|
require 'pry-nav'
|
|
6
8
|
require 'rasti-ai'
|
|
7
9
|
require 'securerandom'
|
|
@@ -13,6 +15,8 @@ require_relative 'support/helpers/resources'
|
|
|
13
15
|
Rasti::AI.configure do |config|
|
|
14
16
|
config.logger.level = Logger::FATAL
|
|
15
17
|
|
|
18
|
+
config.http_max_retries = 1
|
|
19
|
+
|
|
16
20
|
config.openai_api_key = 'test_api_key'
|
|
17
21
|
config.openai_default_model = 'gpt-test'
|
|
18
22
|
end
|
|
@@ -163,11 +163,18 @@ describe Rasti::AI::OpenAI::Assistant do
|
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
def stub_client_request(role:, content:, response:, tools:[])
|
|
166
|
+
serialized_tools = tools.map do |tool|
|
|
167
|
+
{
|
|
168
|
+
type: 'function',
|
|
169
|
+
function: Rasti::AI::ToolSerializer.serialize(tool.class)
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
|
|
166
173
|
client.expect :chat_completions, response do |params|
|
|
167
174
|
last_message = params[:messages].last
|
|
168
175
|
last_message[:role] == role &&
|
|
169
176
|
last_message[:content] == content &&
|
|
170
|
-
params[:tools] ==
|
|
177
|
+
params[:tools] == serialized_tools
|
|
171
178
|
end
|
|
172
179
|
end
|
|
173
180
|
|
|
@@ -295,4 +302,4 @@ describe Rasti::AI::OpenAI::Assistant do
|
|
|
295
302
|
|
|
296
303
|
end
|
|
297
304
|
|
|
298
|
-
end
|
|
305
|
+
end
|
|
@@ -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.1
|
|
4
|
+
version: 1.2.1
|
|
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-12-02 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
|