omni_agent 0.1.8 → 0.1.10
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/CHANGELOG.md +26 -0
- data/README.md +74 -1
- data/exe/omni_agent +22 -6
- data/lib/generators/omni_agent/agent/agent_generator.rb +1 -0
- data/lib/generators/omni_agent/install/install_generator.rb +1 -0
- data/lib/generators/omni_agent/mcp_server/mcp_server_generator.rb +151 -0
- data/lib/omni_agent/agent.rb +51 -36
- data/lib/omni_agent/configuration.rb +4 -1
- data/lib/omni_agent/engine.rb +4 -0
- data/lib/omni_agent/errors.rb +2 -0
- data/lib/omni_agent/mcp/authenticator.rb +82 -0
- data/lib/omni_agent/mcp/handler.rb +128 -0
- data/lib/omni_agent/mcp/rack_app.rb +107 -0
- data/lib/omni_agent/mcp/request.rb +24 -0
- data/lib/omni_agent/mcp/routing.rb +57 -0
- data/lib/omni_agent/mcp/server.rb +238 -0
- data/lib/omni_agent/mcp/stdio.rb +56 -0
- data/lib/omni_agent/mcp/tool_adapter.rb +45 -0
- data/lib/omni_agent/mcp.rb +31 -0
- data/lib/omni_agent/providers/base.rb +1 -1
- data/lib/omni_agent/providers/mock.rb +8 -1
- data/lib/omni_agent/providers/ollama.rb +30 -0
- data/lib/omni_agent/providers/openai.rb +23 -3
- data/lib/omni_agent/providers.rb +1 -0
- data/lib/omni_agent/streaming/event.rb +54 -0
- data/lib/omni_agent/streaming/proxy.rb +17 -0
- data/lib/omni_agent/tool/schema_builder.rb +130 -10
- data/lib/omni_agent/tool.rb +117 -1
- data/lib/omni_agent/version.rb +1 -1
- data/lib/omni_agent.rb +1 -1
- data/lib/tasks/omni_agent_mcp_tasks.rake +9 -0
- metadata +15 -1
|
@@ -3,9 +3,16 @@ module OmniAgent
|
|
|
3
3
|
class Mock < Base
|
|
4
4
|
LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
|
5
5
|
|
|
6
|
-
def chat(messages:, tools: [], **_options)
|
|
6
|
+
def chat(messages:, tools: [], stream: nil, **_options)
|
|
7
7
|
validate_messages!(messages, allowed_roles: %i[system user assistant tool])
|
|
8
8
|
|
|
9
|
+
if stream
|
|
10
|
+
LOREM_IPSUM.split(" ").each_with_index do |word, index|
|
|
11
|
+
chunk = index.zero? ? word : " #{word}"
|
|
12
|
+
stream.call(OmniAgent::Streaming::Event.text(chunk))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
9
16
|
OmniAgent::Providers::Response.new(
|
|
10
17
|
content: LOREM_IPSUM,
|
|
11
18
|
raw_request: {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# lib/omni_agent/providers/ollama.rb
|
|
2
|
+
module OmniAgent
|
|
3
|
+
module Providers
|
|
4
|
+
class Ollama < OpenAI
|
|
5
|
+
protected
|
|
6
|
+
|
|
7
|
+
def provider_label
|
|
8
|
+
"Ollama"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def client
|
|
12
|
+
@client ||= ::OpenAI::Client.new(api_key: @api_key, base_url: base_url)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def default_api_key
|
|
16
|
+
ENV.fetch("OLLAMA_API_KEY", "ollama")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def default_model
|
|
20
|
+
ENV.fetch("OLLAMA_MODEL", "llama3.1")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def base_url
|
|
26
|
+
"#{ENV.fetch('OLLAMA_HOST', 'http://localhost:11434')}/v1"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -12,7 +12,7 @@ module OmniAgent
|
|
|
12
12
|
"Please add `gem 'openai'` to your Gemfile."
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def chat(messages:, tools: [], **options)
|
|
15
|
+
def chat(messages:, tools: [], stream: nil, **options)
|
|
16
16
|
validate_messages!(messages, allowed_roles: %i[system user assistant tool])
|
|
17
17
|
|
|
18
18
|
messages = messages.map do |msg|
|
|
@@ -44,15 +44,23 @@ module OmniAgent
|
|
|
44
44
|
payload[:tools] = openai_tools if openai_tools.any?
|
|
45
45
|
payload.merge!(options) if options.any?
|
|
46
46
|
|
|
47
|
-
response =
|
|
47
|
+
response = if stream
|
|
48
|
+
stream_chat(payload: payload, stream: stream)
|
|
49
|
+
else
|
|
50
|
+
with_retries { client.chat.completions.create(**payload) }
|
|
51
|
+
end
|
|
48
52
|
|
|
49
53
|
parse_response(response, payload)
|
|
50
54
|
rescue => e
|
|
51
|
-
raise OmniAgent::Error, "Error during
|
|
55
|
+
raise OmniAgent::Error, "Error during #{provider_label} chat: #{e.message}"
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
protected
|
|
55
59
|
|
|
60
|
+
def provider_label
|
|
61
|
+
"OpenAI"
|
|
62
|
+
end
|
|
63
|
+
|
|
56
64
|
def retryable_error?(error)
|
|
57
65
|
defined?(::OpenAI::Errors) &&
|
|
58
66
|
(error.is_a?(::OpenAI::Errors::RateLimitError) ||
|
|
@@ -74,6 +82,18 @@ module OmniAgent
|
|
|
74
82
|
|
|
75
83
|
private
|
|
76
84
|
|
|
85
|
+
def stream_chat(payload:, stream:)
|
|
86
|
+
chat_stream = with_retries { client.chat.completions.stream(**payload) }
|
|
87
|
+
|
|
88
|
+
chat_stream.each do |event|
|
|
89
|
+
next unless event.is_a?(::OpenAI::Helpers::Streaming::ChatContentDeltaEvent)
|
|
90
|
+
|
|
91
|
+
stream.call(OmniAgent::Streaming::Event.text(event.delta))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
chat_stream.get_final_completion
|
|
95
|
+
end
|
|
96
|
+
|
|
77
97
|
def format_tool(tool_class)
|
|
78
98
|
schema = tool_class.json_schema.dup
|
|
79
99
|
|
data/lib/omni_agent/providers.rb
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module OmniAgent
|
|
2
|
+
module Streaming
|
|
3
|
+
class Event
|
|
4
|
+
attr_reader :type, :text, :tool_name, :tool_arguments, :tool_id, :content, :error, :response
|
|
5
|
+
|
|
6
|
+
def self.text(delta)
|
|
7
|
+
new(type: :text, text: delta)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.tool_call(name:, arguments:, id:)
|
|
11
|
+
new(type: :tool_call, tool_name: name, tool_arguments: arguments, tool_id: id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.tool_result(name:, id:, content:, error: false)
|
|
15
|
+
new(type: :tool_result, tool_name: name, tool_id: id, content: content, error: error)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.done(response)
|
|
19
|
+
new(type: :done, response: response)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(type:, text: nil, tool_name: nil, tool_arguments: nil, tool_id: nil, content: nil, error: false, response: nil)
|
|
23
|
+
@type = type
|
|
24
|
+
@text = text
|
|
25
|
+
@tool_name = tool_name
|
|
26
|
+
@tool_arguments = tool_arguments
|
|
27
|
+
@tool_id = tool_id
|
|
28
|
+
@content = content
|
|
29
|
+
@error = error
|
|
30
|
+
@response = response
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def text?
|
|
34
|
+
type == :text
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def tool_call?
|
|
38
|
+
type == :tool_call
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def tool_result?
|
|
42
|
+
type == :tool_result
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def done?
|
|
46
|
+
type == :done
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def error?
|
|
50
|
+
@error == true
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module OmniAgent
|
|
2
|
+
module Streaming
|
|
3
|
+
class Proxy
|
|
4
|
+
def initialize(agent)
|
|
5
|
+
@agent = agent
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
9
|
+
@agent.public_send(name, *args, **kwargs, &block)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def respond_to_missing?(name, include_private = false)
|
|
13
|
+
@agent.respond_to?(name, include_private) || super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,26 +1,46 @@
|
|
|
1
1
|
module OmniAgent
|
|
2
2
|
class Tool
|
|
3
3
|
class SchemaBuilder
|
|
4
|
-
attr_reader :properties, :required_fields
|
|
4
|
+
attr_reader :properties, :required_fields, :validators, :polymorphics
|
|
5
5
|
|
|
6
6
|
def initialize
|
|
7
7
|
@properties = {}
|
|
8
8
|
@required_fields = []
|
|
9
|
+
@validators = {}
|
|
10
|
+
@polymorphics = {}
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
def string(name, description: nil, required: true)
|
|
12
|
-
|
|
13
|
+
def string(name, description: nil, required: true, min_length: nil, max_length: nil, pattern: nil, format: nil, validate: nil)
|
|
14
|
+
constraints = {}
|
|
15
|
+
constraints[:minLength] = min_length if min_length
|
|
16
|
+
constraints[:maxLength] = max_length if max_length
|
|
17
|
+
constraints[:pattern] = pattern.is_a?(Regexp) ? pattern.source : pattern if pattern
|
|
18
|
+
constraints[:format] = format if format
|
|
19
|
+
|
|
20
|
+
add_property(name, type: "string", description: description, required: required, constraints: constraints, validate: validate)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def integer(name, description: nil, required: true, min: nil, max: nil, validate: nil)
|
|
24
|
+
constraints = {}
|
|
25
|
+
constraints[:minimum] = min if min
|
|
26
|
+
constraints[:maximum] = max if max
|
|
27
|
+
|
|
28
|
+
add_property(name, type: "integer", description: description, required: required, constraints: constraints, validate: validate)
|
|
13
29
|
end
|
|
14
30
|
|
|
15
|
-
def
|
|
16
|
-
|
|
31
|
+
def number(name, description: nil, required: true, min: nil, max: nil, validate: nil)
|
|
32
|
+
constraints = {}
|
|
33
|
+
constraints[:minimum] = min if min
|
|
34
|
+
constraints[:maximum] = max if max
|
|
35
|
+
|
|
36
|
+
add_property(name, type: "number", description: description, required: required, constraints: constraints, validate: validate)
|
|
17
37
|
end
|
|
18
38
|
|
|
19
|
-
def boolean(name, description: nil, required: true)
|
|
20
|
-
add_property(name, type: "boolean", description: description, required: required)
|
|
39
|
+
def boolean(name, description: nil, required: true, validate: nil)
|
|
40
|
+
add_property(name, type: "boolean", description: description, required: required, validate: validate)
|
|
21
41
|
end
|
|
22
42
|
|
|
23
|
-
def array(name, items_type: nil, description: nil, required: true, &block)
|
|
43
|
+
def array(name, items_type: nil, description: nil, required: true, min_items: nil, max_items: nil, validate: nil, &block)
|
|
24
44
|
property = { type: "array" }
|
|
25
45
|
property[:description] = description if description
|
|
26
46
|
|
|
@@ -38,11 +58,15 @@ module OmniAgent
|
|
|
38
58
|
property[:items] = { type: items_type || "string" }
|
|
39
59
|
end
|
|
40
60
|
|
|
61
|
+
property[:minItems] = min_items if min_items
|
|
62
|
+
property[:maxItems] = max_items if max_items
|
|
63
|
+
|
|
41
64
|
@properties[name] = property
|
|
42
65
|
@required_fields << name.to_s if required
|
|
66
|
+
@validators[name] = validate if validate
|
|
43
67
|
end
|
|
44
68
|
|
|
45
|
-
def hash(name, description: nil, required: true, &block)
|
|
69
|
+
def hash(name, description: nil, required: true, validate: nil, &block)
|
|
46
70
|
property = { type: "object" }
|
|
47
71
|
property[:description] = description if description
|
|
48
72
|
|
|
@@ -59,16 +83,112 @@ module OmniAgent
|
|
|
59
83
|
|
|
60
84
|
@properties[name] = property
|
|
61
85
|
@required_fields << name.to_s if required
|
|
86
|
+
@validators[name] = validate if validate
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def enum(name, values:, description: nil, required: true, validate: nil)
|
|
90
|
+
raise ArgumentError, "enum requires at least one value" if values.empty?
|
|
91
|
+
|
|
92
|
+
normalized_values = values.map { |v| v.is_a?(Symbol) ? v.to_s : v }
|
|
93
|
+
value_data_type = enum_data_type(normalized_values)
|
|
94
|
+
|
|
95
|
+
property = { type: value_data_type, enum: normalized_values }
|
|
96
|
+
property[:description] = description if description
|
|
97
|
+
@properties[name] = property
|
|
98
|
+
@required_fields << name.to_s if required
|
|
99
|
+
@validators[name] = validate if validate
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def polymorphic(name, types: nil, description: nil, id_type: "string", required: true, resolve: false, &block)
|
|
103
|
+
if resolve && (types.nil? || types.empty?)
|
|
104
|
+
raise ArgumentError, "polymorphic resolve: true requires types:"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
type_values = types
|
|
108
|
+
type_description = description ? "#{description} (type)" : nil
|
|
109
|
+
id_type_value = id_type
|
|
110
|
+
id_description = description ? "#{description} (id)" : nil
|
|
111
|
+
|
|
112
|
+
if block_given?
|
|
113
|
+
field_builder = PolymorphicFieldBuilder.new
|
|
114
|
+
field_builder.instance_eval(&block)
|
|
115
|
+
|
|
116
|
+
type_values = field_builder.type_values || type_values
|
|
117
|
+
type_description = field_builder.type_description || type_description
|
|
118
|
+
id_type_value = field_builder.id_type || id_type_value
|
|
119
|
+
id_description = field_builder.id_description || id_description
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
type_field = :"#{name}_type"
|
|
123
|
+
id_field = :"#{name}_id"
|
|
124
|
+
id_json_type = id_type_value.to_s == "integer" ? "integer" : "string"
|
|
125
|
+
|
|
126
|
+
if type_values && !type_values.empty?
|
|
127
|
+
enum(type_field, values: type_values, description: type_description, required: required)
|
|
128
|
+
else
|
|
129
|
+
string(type_field, description: type_description, required: required)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
if id_json_type == "integer"
|
|
133
|
+
integer(id_field, description: id_description, required: required)
|
|
134
|
+
else
|
|
135
|
+
string(id_field, description: id_description, required: required)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
@polymorphics[name] = {
|
|
139
|
+
type_field: type_field,
|
|
140
|
+
id_field: id_field,
|
|
141
|
+
types: type_values,
|
|
142
|
+
id_type: id_json_type,
|
|
143
|
+
resolve: resolve
|
|
144
|
+
}
|
|
62
145
|
end
|
|
63
146
|
|
|
64
147
|
private
|
|
65
148
|
|
|
66
|
-
def
|
|
149
|
+
def enum_data_type(values)
|
|
150
|
+
types = values.map { |v| json_type_for(v) }.uniq
|
|
151
|
+
|
|
152
|
+
if types.size > 1
|
|
153
|
+
raise ArgumentError, "enum values must all be the same type, got: #{types.join(', ')}"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
types.first
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def json_type_for(value)
|
|
160
|
+
case value
|
|
161
|
+
when Integer then "integer"
|
|
162
|
+
when Float then "number"
|
|
163
|
+
when String then "string"
|
|
164
|
+
when true, false then "boolean"
|
|
165
|
+
else
|
|
166
|
+
raise ArgumentError, "unsupported enum value type: #{value.class}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def add_property(name, type:, description:, required:, constraints: {}, validate: nil)
|
|
67
171
|
property = { type: type }
|
|
68
172
|
property[:description] = description if description
|
|
173
|
+
property.merge!(constraints)
|
|
69
174
|
|
|
70
175
|
@properties[name] = property
|
|
71
176
|
@required_fields << name.to_s if required
|
|
177
|
+
@validators[name] = validate if validate
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
class PolymorphicFieldBuilder
|
|
181
|
+
attr_reader :type_values, :type_description, :id_type, :id_description
|
|
182
|
+
|
|
183
|
+
def type(values: nil, description: nil)
|
|
184
|
+
@type_values = values
|
|
185
|
+
@type_description = description
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def id(type: "string", description: nil)
|
|
189
|
+
@id_type = type
|
|
190
|
+
@id_description = description
|
|
191
|
+
end
|
|
72
192
|
end
|
|
73
193
|
end
|
|
74
194
|
end
|
data/lib/omni_agent/tool.rb
CHANGED
|
@@ -11,6 +11,31 @@ module OmniAgent
|
|
|
11
11
|
@metadata || {}
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
def title(text = nil)
|
|
15
|
+
@title = text if text
|
|
16
|
+
@title
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def annotations(read_only: nil, destructive: nil, idempotent: nil, open_world: nil)
|
|
20
|
+
@annotations = {
|
|
21
|
+
read_only: read_only,
|
|
22
|
+
destructive: destructive,
|
|
23
|
+
idempotent: idempotent,
|
|
24
|
+
open_world: open_world
|
|
25
|
+
}.compact
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mcp_annotations
|
|
29
|
+
hints = @annotations || {}
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
readOnlyHint: hints[:read_only],
|
|
33
|
+
destructiveHint: hints[:destructive],
|
|
34
|
+
idempotentHint: hints[:idempotent],
|
|
35
|
+
openWorldHint: hints[:open_world]
|
|
36
|
+
}.compact
|
|
37
|
+
end
|
|
38
|
+
|
|
14
39
|
def tags(*tag_names)
|
|
15
40
|
return @tags || [] if tag_names.empty?
|
|
16
41
|
|
|
@@ -28,6 +53,8 @@ module OmniAgent
|
|
|
28
53
|
|
|
29
54
|
@properties = builder.properties
|
|
30
55
|
@required = builder.required_fields
|
|
56
|
+
@validators = builder.validators
|
|
57
|
+
@polymorphics = builder.polymorphics
|
|
31
58
|
end
|
|
32
59
|
end
|
|
33
60
|
|
|
@@ -43,7 +70,15 @@ module OmniAgent
|
|
|
43
70
|
def parse_arguments(arguments_hash)
|
|
44
71
|
kwargs = arguments_hash.transform_keys(&:to_sym)
|
|
45
72
|
valid_keys = (@properties || {}).keys.map(&:to_sym)
|
|
46
|
-
kwargs.slice(*valid_keys)
|
|
73
|
+
filtered = kwargs.slice(*valid_keys)
|
|
74
|
+
|
|
75
|
+
validate_required!(filtered)
|
|
76
|
+
validate_enums!(filtered)
|
|
77
|
+
validate_constraints!(filtered)
|
|
78
|
+
run_custom_validators!(filtered)
|
|
79
|
+
resolve_polymorphics!(filtered)
|
|
80
|
+
|
|
81
|
+
filtered
|
|
47
82
|
end
|
|
48
83
|
|
|
49
84
|
def stops_generation(value = true)
|
|
@@ -56,6 +91,87 @@ module OmniAgent
|
|
|
56
91
|
|
|
57
92
|
private
|
|
58
93
|
|
|
94
|
+
def validate_required!(kwargs)
|
|
95
|
+
missing = (@required || []).map(&:to_sym) - kwargs.keys
|
|
96
|
+
return if missing.empty?
|
|
97
|
+
|
|
98
|
+
raise ArgumentError, "missing required argument(s): #{missing.join(', ')}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def validate_enums!(kwargs)
|
|
102
|
+
(@properties || {}).each do |name, property|
|
|
103
|
+
allowed_values = property[:enum]
|
|
104
|
+
next unless allowed_values
|
|
105
|
+
|
|
106
|
+
key = name.to_sym
|
|
107
|
+
next unless kwargs.key?(key)
|
|
108
|
+
|
|
109
|
+
unless allowed_values.include?(kwargs[key])
|
|
110
|
+
raise ArgumentError, "invalid value for #{name}: #{kwargs[key].inspect} (must be one of: #{allowed_values.join(', ')})"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def validate_constraints!(kwargs)
|
|
116
|
+
(@properties || {}).each do |name, property|
|
|
117
|
+
key = name.to_sym
|
|
118
|
+
next unless kwargs.key?(key)
|
|
119
|
+
|
|
120
|
+
check_constraints!(name, property, kwargs[key])
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def check_constraints!(name, property, value)
|
|
125
|
+
if property[:minLength] && value.is_a?(String) && value.length < property[:minLength]
|
|
126
|
+
raise ArgumentError, "#{name} must be at least #{property[:minLength]} characters"
|
|
127
|
+
end
|
|
128
|
+
if property[:maxLength] && value.is_a?(String) && value.length > property[:maxLength]
|
|
129
|
+
raise ArgumentError, "#{name} must be at most #{property[:maxLength]} characters"
|
|
130
|
+
end
|
|
131
|
+
if property[:pattern] && value.is_a?(String) && !value.match?(Regexp.new(property[:pattern]))
|
|
132
|
+
raise ArgumentError, "#{name} does not match required pattern #{property[:pattern]}"
|
|
133
|
+
end
|
|
134
|
+
if property[:minimum] && value.is_a?(Numeric) && value < property[:minimum]
|
|
135
|
+
raise ArgumentError, "#{name} must be >= #{property[:minimum]}"
|
|
136
|
+
end
|
|
137
|
+
if property[:maximum] && value.is_a?(Numeric) && value > property[:maximum]
|
|
138
|
+
raise ArgumentError, "#{name} must be <= #{property[:maximum]}"
|
|
139
|
+
end
|
|
140
|
+
if property[:minItems] && value.is_a?(Array) && value.length < property[:minItems]
|
|
141
|
+
raise ArgumentError, "#{name} must have at least #{property[:minItems]} items"
|
|
142
|
+
end
|
|
143
|
+
if property[:maxItems] && value.is_a?(Array) && value.length > property[:maxItems]
|
|
144
|
+
raise ArgumentError, "#{name} must have at most #{property[:maxItems]} items"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def run_custom_validators!(kwargs)
|
|
149
|
+
(@validators || {}).each do |name, validator|
|
|
150
|
+
key = name.to_sym
|
|
151
|
+
next unless kwargs.key?(key)
|
|
152
|
+
|
|
153
|
+
result = validator.call(kwargs[key])
|
|
154
|
+
raise ArgumentError, "invalid value for #{name}" if result == false
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def resolve_polymorphics!(kwargs)
|
|
159
|
+
(@polymorphics || {}).each do |name, group|
|
|
160
|
+
next unless group[:resolve]
|
|
161
|
+
|
|
162
|
+
type_field = group[:type_field]
|
|
163
|
+
id_field = group[:id_field]
|
|
164
|
+
next unless kwargs.key?(type_field) && kwargs.key?(id_field)
|
|
165
|
+
|
|
166
|
+
type_value = kwargs.delete(type_field)
|
|
167
|
+
id_value = kwargs.delete(id_field)
|
|
168
|
+
|
|
169
|
+
klass = Object.const_get(type_value.to_s)
|
|
170
|
+
record = klass.find(id_value)
|
|
171
|
+
kwargs[name.to_sym] = record
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
59
175
|
def normalize_tags(tag_names)
|
|
60
176
|
raise ArgumentError, "tags requires at least one tag" if tag_names.empty?
|
|
61
177
|
|
data/lib/omni_agent/version.rb
CHANGED
data/lib/omni_agent.rb
CHANGED
|
@@ -3,7 +3,7 @@ require "omni_agent/engine" if defined?(Rails)
|
|
|
3
3
|
require "zeitwerk"
|
|
4
4
|
|
|
5
5
|
loader = Zeitwerk::Loader.for_gem
|
|
6
|
-
loader.inflector.inflect("openai" => "OpenAI", "cli" => "CLI")
|
|
6
|
+
loader.inflector.inflect("openai" => "OpenAI", "cli" => "CLI", "mcp" => "MCP")
|
|
7
7
|
loader.ignore(File.expand_path("generators", __dir__))
|
|
8
8
|
loader.setup
|
|
9
9
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
namespace :omni_agent do
|
|
2
|
+
desc 'Run an MCP server over stdio. Usage: rake "omni_agent:mcp[SupportServer]"'
|
|
3
|
+
task :mcp, [ :server ] => :environment do |_task, args|
|
|
4
|
+
server = args[:server].to_s.strip
|
|
5
|
+
abort 'Please provide a server class. Example: bin/rails "omni_agent:mcp[SupportServer]"' if server.empty?
|
|
6
|
+
|
|
7
|
+
OmniAgent::MCP::Stdio.start(server)
|
|
8
|
+
end
|
|
9
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omni_agent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ACR1209
|
|
@@ -62,6 +62,7 @@ files:
|
|
|
62
62
|
- lib/generators/omni_agent/agent/agent_generator.rb
|
|
63
63
|
- lib/generators/omni_agent/eval/eval_generator.rb
|
|
64
64
|
- lib/generators/omni_agent/install/install_generator.rb
|
|
65
|
+
- lib/generators/omni_agent/mcp_server/mcp_server_generator.rb
|
|
65
66
|
- lib/omni_agent.rb
|
|
66
67
|
- lib/omni_agent/agent.rb
|
|
67
68
|
- lib/omni_agent/configuration.rb
|
|
@@ -81,18 +82,31 @@ files:
|
|
|
81
82
|
- lib/omni_agent/eval/run.rb
|
|
82
83
|
- lib/omni_agent/eval/runner.rb
|
|
83
84
|
- lib/omni_agent/eval/tool_call_assertion.rb
|
|
85
|
+
- lib/omni_agent/mcp.rb
|
|
86
|
+
- lib/omni_agent/mcp/authenticator.rb
|
|
87
|
+
- lib/omni_agent/mcp/handler.rb
|
|
88
|
+
- lib/omni_agent/mcp/rack_app.rb
|
|
89
|
+
- lib/omni_agent/mcp/request.rb
|
|
90
|
+
- lib/omni_agent/mcp/routing.rb
|
|
91
|
+
- lib/omni_agent/mcp/server.rb
|
|
92
|
+
- lib/omni_agent/mcp/stdio.rb
|
|
93
|
+
- lib/omni_agent/mcp/tool_adapter.rb
|
|
84
94
|
- lib/omni_agent/providers.rb
|
|
85
95
|
- lib/omni_agent/providers/base.rb
|
|
86
96
|
- lib/omni_agent/providers/mock.rb
|
|
87
97
|
- lib/omni_agent/providers/mock_judge.rb
|
|
98
|
+
- lib/omni_agent/providers/ollama.rb
|
|
88
99
|
- lib/omni_agent/providers/openai.rb
|
|
89
100
|
- lib/omni_agent/providers/response.rb
|
|
90
101
|
- lib/omni_agent/railtie.rb
|
|
102
|
+
- lib/omni_agent/streaming/event.rb
|
|
103
|
+
- lib/omni_agent/streaming/proxy.rb
|
|
91
104
|
- lib/omni_agent/tasks/omni_agent_tasks.rake
|
|
92
105
|
- lib/omni_agent/tool.rb
|
|
93
106
|
- lib/omni_agent/tool/schema_builder.rb
|
|
94
107
|
- lib/omni_agent/version.rb
|
|
95
108
|
- lib/tasks/omni_agent_eval_tasks.rake
|
|
109
|
+
- lib/tasks/omni_agent_mcp_tasks.rake
|
|
96
110
|
homepage: https://github.com/ACR1209/omni_agent
|
|
97
111
|
licenses:
|
|
98
112
|
- MIT
|