omni_agent 0.1.7 → 0.1.9
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 +60 -1
- data/lib/omni_agent/agent.rb +95 -11
- data/lib/omni_agent/configuration.rb +2 -1
- data/lib/omni_agent/errors.rb +2 -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 +98 -1
- data/lib/omni_agent/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66c390e526b0bd6176f8755538304d0e0e99d9cc36627be1ee9b63119b53ce34
|
|
4
|
+
data.tar.gz: 4918452fbba6d30772abbf9d072d0c16cf363d5220a099183e9caa04d7481220
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 522e4be90a757a9056e95cf45f5aafb1f18d7b6914e1ca667e32b3774ab70422f552e333ac2e340c4d1dab1c628350e04d494a9726b908467eb53723ae300dd6
|
|
7
|
+
data.tar.gz: c2c7939747237e554df0ac2569b84e36b164c1235f75fa876f04518cb644ad02579e18f4ee168c53ff55f32b75c8a58284ff921b217eda4b9148f67a8d5e4de7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.9](https://github.com/ACR1209/omni_agent/compare/omni_agent/v0.1.8...omni_agent/v0.1.9) (2026-07-12)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **agent:** support streaming responses via stream sink ([7cf1fba](https://github.com/ACR1209/omni_agent/commit/7cf1fba4fa378c5cee7f464354f760506a0ff513))
|
|
11
|
+
* **docs:** add enum type for temperature unit in input schema ([d992658](https://github.com/ACR1209/omni_agent/commit/d99265836307ce8f863b060eff73c7acdbc8d2ff))
|
|
12
|
+
* **providers:** add Ollama provider via OpenAI-compat endpoint ([4ae92ad](https://github.com/ACR1209/omni_agent/commit/4ae92ad0f549119b43c2e8a7381f4f7245aa2f68))
|
|
13
|
+
* **tool:** add optional constraint validations and custom validate procs ([db8371a](https://github.com/ACR1209/omni_agent/commit/db8371a189f710e812c3c3ce15f200df1ba94902))
|
|
14
|
+
* **tool:** add polymorphic reference datatype ([1686bbb](https://github.com/ACR1209/omni_agent/commit/1686bbbf41932622a5537c335da8c41c68a73f73))
|
|
15
|
+
* **tool:** infer and validate enum value types in schema builder ([ceb3e78](https://github.com/ACR1209/omni_agent/commit/ceb3e780498caa1158e9fc8af3eb6e9b95c33ddb))
|
|
16
|
+
* **tool:** validate required and enum arguments on invoke ([543c6e0](https://github.com/ACR1209/omni_agent/commit/543c6e08f9c394b84c73bc5957aff7058e0fab19))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **lint:** add space inside empty block braces ([9116b3f](https://github.com/ACR1209/omni_agent/commit/9116b3ff82e9fab9824347515b4c1cd40a40785e))
|
|
22
|
+
* **spec:** redact OpenAI API key from recorded VCR cassettes ([53cecd5](https://github.com/ACR1209/omni_agent/commit/53cecd540b6dd52ddc5bf3da0550836fbfcfab88))
|
|
23
|
+
|
|
24
|
+
## [0.1.8](https://github.com/ACR1209/omni_agent/compare/omni_agent/v0.1.7...omni_agent/v0.1.8) (2026-07-07)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* implement multi-agent delegation with context forwarding and depth control ([e9b5497](https://github.com/ACR1209/omni_agent/commit/e9b5497b0af89172701bf4c5d93b8d5dccc77a26))
|
|
30
|
+
|
|
5
31
|
## [0.1.7](https://github.com/ACR1209/omni_agent/compare/omni_agent-v0.1.6...omni_agent/v0.1.7) (2026-06-22)
|
|
6
32
|
|
|
7
33
|
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ tool schemas, and generation lifecycle callbacks.
|
|
|
11
11
|
- Prompt composition from ERB files in `app/agents/<agent_name>/`
|
|
12
12
|
- Agent callbacks (`before_generation`, `after_generation`)
|
|
13
13
|
- Agent and tool tags to support filtering strategies
|
|
14
|
-
- OpenAI provider integration out of the box
|
|
14
|
+
- OpenAI provider integration out of the box, plus an Ollama provider for local models
|
|
15
15
|
- Rake tasks and Rails generators for scaffolding
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
@@ -27,6 +27,8 @@ Add the provider you're using to the Gemfile as well:
|
|
|
27
27
|
gem "openai"
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
The `ollama` provider also depends on the `openai` gem — it talks to Ollama's OpenAI-compatible endpoint, so no separate gem is needed.
|
|
31
|
+
|
|
30
32
|
Then run:
|
|
31
33
|
|
|
32
34
|
```bash
|
|
@@ -53,6 +55,14 @@ bundle exec rails generate omni_agent:agent ResearchAgent --model gpt-4.1-mini -
|
|
|
53
55
|
OPENAI_ACCESS_TOKEN=your_api_key_here
|
|
54
56
|
```
|
|
55
57
|
|
|
58
|
+
To use Ollama instead, set `config.default_provider = :ollama` (see below) and, if needed, override its endpoint/model:
|
|
59
|
+
|
|
60
|
+
```dotenv
|
|
61
|
+
OLLAMA_HOST=http://localhost:11434 # defaults to this
|
|
62
|
+
OLLAMA_MODEL=llama3.1 # defaults to this; pick a tool-calling-capable model
|
|
63
|
+
OLLAMA_API_KEY=ollama # unused by Ollama, but required by the openai client
|
|
64
|
+
```
|
|
65
|
+
|
|
56
66
|
4. Implement your agent prompt and optional tools under:
|
|
57
67
|
|
|
58
68
|
```text
|
|
@@ -97,6 +107,55 @@ module ResearchAgent::Tools
|
|
|
97
107
|
end
|
|
98
108
|
```
|
|
99
109
|
|
|
110
|
+
## Multi-Agent Delegation
|
|
111
|
+
|
|
112
|
+
`delegate_to` turns an agent into a supervisor: it wraps another agent class as a tool, so the supervisor's LLM can decide when to hand off to it. The delegated agent is a normal, independently defined agent (e.g. `app/agents/research_agent.rb`) — no manual tool file needed.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
class SupervisorAgent < OmniAgent::Agent
|
|
116
|
+
use_model "gpt-4o"
|
|
117
|
+
|
|
118
|
+
delegate_to ResearchAgent, as: :research, description: "Look up factual info"
|
|
119
|
+
delegate_to MathAgent, as: :calculate, description: "Do arithmetic"
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Each delegated agent runs in isolation (its own fresh instance, no shared context) and returns its final answer as the tool result. Delegation depth is capped by `OmniAgent.configuration.max_delegation_depth` (default `5`) to guard against runaway recursive delegation; exceeding it raises `OmniAgent::MaxDelegationDepthError`.
|
|
124
|
+
|
|
125
|
+
Pass `run_alias:` to call a `run_aliases` method (or any zero-arg run entrypoint) on the delegated agent instead of its default `#run` — useful when the sub-agent should render a different prompt file for delegated calls:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
class SupervisorAgent < OmniAgent::Agent
|
|
129
|
+
delegate_to SupportAgent, as: :triage_ticket, run_alias: :triage
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Pass `forward:` to share part (or all) of the supervisor's context with the delegated agent — an array of context keys, or `true` to forward everything. Omitted keys, and the default (`forward: []`), keep the delegated agent fully isolated:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
class SupervisorAgent < OmniAgent::Agent
|
|
137
|
+
delegate_to ResearchAgent, as: :research, forward: [ :user, :locale ]
|
|
138
|
+
delegate_to MathAgent, as: :calculate, forward: true
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Streaming
|
|
143
|
+
|
|
144
|
+
Prefix any run entrypoint with `.stream` and pass a block to receive the response as it's generated instead of waiting for the full result:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
ResearchAgent.with(user_id: 42).stream.run("What's new?") do |event|
|
|
148
|
+
case event.type
|
|
149
|
+
when :text then print event.text
|
|
150
|
+
when :tool_call then puts "\n[using #{event.tool_name}...]"
|
|
151
|
+
when :tool_result then puts "[#{event.error? ? "failed" : "done"}]"
|
|
152
|
+
when :done then puts "\n---"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`.stream` must come before the call (`.stream.run(...)`, not `.run(...).stream`) — without it, or without a block, behavior is unchanged and the same `Response` is returned either way. Only the `openai`, `ollama`, and `mock` providers stream today. See [Streaming Responses](omniagent-docs/docs/agent/streaming.mdx) for the full event reference.
|
|
158
|
+
|
|
100
159
|
## Evals
|
|
101
160
|
|
|
102
161
|
`OmniAgent::Eval` lets you test agent quality: deterministic assertions (tool calls, output matching) and pluggable LLM-as-judge scoring.
|
data/lib/omni_agent/agent.rb
CHANGED
|
@@ -20,7 +20,7 @@ module OmniAgent
|
|
|
20
20
|
@_omni_agent_wrapping_method = true
|
|
21
21
|
alias_method alias_name, method_name
|
|
22
22
|
|
|
23
|
-
define_method(method_name) do |input = nil, context: {}, **context_keywords|
|
|
23
|
+
define_method(method_name) do |input = nil, context: {}, **context_keywords, &block|
|
|
24
24
|
if input.nil? && context == {} && context_keywords.empty?
|
|
25
25
|
run_alias_entrypoint_logic(alias_name)
|
|
26
26
|
else
|
|
@@ -29,7 +29,7 @@ module OmniAgent
|
|
|
29
29
|
|
|
30
30
|
run_input = run_alias_entrypoint_logic(alias_name, fallback_input: input)
|
|
31
31
|
|
|
32
|
-
run(run_input, context: merged_context, prompt_method: method_name)
|
|
32
|
+
run(run_input, context: merged_context, prompt_method: method_name, &block)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
ensure
|
|
@@ -78,12 +78,27 @@ module OmniAgent
|
|
|
78
78
|
aliases = normalize_callbacks(:run_aliases, method_names)
|
|
79
79
|
|
|
80
80
|
aliases.each do |method_name|
|
|
81
|
-
define_method(method_name) do |input, context: {}|
|
|
82
|
-
run(input, context: context, prompt_method: method_name)
|
|
81
|
+
define_method(method_name) do |input, context: {}, &block|
|
|
82
|
+
run(input, context: context, prompt_method: method_name, &block)
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
def delegate_to(agent_class, as:, description: nil, run_alias: nil, forward: [])
|
|
88
|
+
unless agent_class.is_a?(Class) && agent_class <= OmniAgent::Agent
|
|
89
|
+
raise ArgumentError, "delegate_to requires an OmniAgent::Agent subclass"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
tool_class = build_delegated_tool_class(agent_class, description: description, run_alias: run_alias, forward: forward)
|
|
93
|
+
delegated_tools_module.const_set(delegated_tool_const_name(as), tool_class)
|
|
94
|
+
|
|
95
|
+
@delegated_tool_classes = configured_delegated_tool_classes + [ tool_class ]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def configured_delegated_tool_classes
|
|
99
|
+
@delegated_tool_classes || []
|
|
100
|
+
end
|
|
101
|
+
|
|
87
102
|
def with(context = nil, provider_override: nil, model_override: nil, options_override: {}, **context_keywords)
|
|
88
103
|
merged_context = {}
|
|
89
104
|
merged_context.merge!(context) if context.is_a?(Hash)
|
|
@@ -112,6 +127,58 @@ module OmniAgent
|
|
|
112
127
|
|
|
113
128
|
private
|
|
114
129
|
|
|
130
|
+
def delegated_tools_module
|
|
131
|
+
@delegated_tools_module ||= const_set(:DelegatedTools, Module.new)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def delegated_tool_const_name(as)
|
|
135
|
+
as.to_s.split(/[_\s]+/).reject(&:empty?).map { |part| part[0].upcase + part[1..] }.join
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def build_delegated_tool_class(agent_class, description:, run_alias:, forward:)
|
|
139
|
+
tool_description = description || "Delegate to #{agent_class.name}."
|
|
140
|
+
|
|
141
|
+
Class.new(OmniAgent::Tool) do
|
|
142
|
+
description tool_description
|
|
143
|
+
|
|
144
|
+
input do
|
|
145
|
+
string :input, description: "Input/question to send to the delegated agent."
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
define_method(:execute) do |input:|
|
|
149
|
+
forwarded_context = OmniAgent::Agent.__send__(:filter_forwarded_context, context, forward)
|
|
150
|
+
OmniAgent::Agent.__send__(:run_delegated_agent, agent_class, input, run_alias, forwarded_context)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def filter_forwarded_context(context, forward)
|
|
156
|
+
return {} unless context.is_a?(Hash)
|
|
157
|
+
return context.dup if forward == true
|
|
158
|
+
return {} if forward.nil? || forward == false
|
|
159
|
+
|
|
160
|
+
keys = Array(forward).map(&:to_sym)
|
|
161
|
+
context.select { |key, _| keys.include?(key.to_sym) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def run_delegated_agent(agent_class, input, run_alias, forwarded_context)
|
|
165
|
+
depth = (Thread.current[:omni_agent_delegation_depth] ||= 0)
|
|
166
|
+
max_depth = OmniAgent.configuration.max_delegation_depth
|
|
167
|
+
|
|
168
|
+
if depth >= max_depth
|
|
169
|
+
raise OmniAgent::MaxDelegationDepthError,
|
|
170
|
+
"Exceeded max_delegation_depth (#{max_depth}) while delegating to #{agent_class.name}."
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
Thread.current[:omni_agent_delegation_depth] = depth + 1
|
|
174
|
+
begin
|
|
175
|
+
entrypoint = run_alias || :run
|
|
176
|
+
agent_class.new.public_send(entrypoint, input, context: forwarded_context).answer
|
|
177
|
+
ensure
|
|
178
|
+
Thread.current[:omni_agent_delegation_depth] = depth
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
115
182
|
def normalize_callbacks(callback_type, callbacks)
|
|
116
183
|
raise ArgumentError, "#{callback_type} requires at least one method name" if callbacks.empty?
|
|
117
184
|
|
|
@@ -145,7 +212,11 @@ module OmniAgent
|
|
|
145
212
|
@default_context = context_override || {}
|
|
146
213
|
end
|
|
147
214
|
|
|
148
|
-
def
|
|
215
|
+
def stream
|
|
216
|
+
OmniAgent::Streaming::Proxy.new(self)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def run(input, context: {}, prompt_method: nil, &stream)
|
|
149
220
|
context = @default_context.merge(context || {})
|
|
150
221
|
bind_context_instance_variables(context)
|
|
151
222
|
|
|
@@ -170,13 +241,14 @@ module OmniAgent
|
|
|
170
241
|
"Increase OmniAgent.configuration.max_tool_iterations if more tool calls are expected."
|
|
171
242
|
end
|
|
172
243
|
|
|
173
|
-
response = provider.chat(messages: messages, tools: filtered_tools, **@chat_options)
|
|
244
|
+
response = provider.chat(messages: messages, tools: filtered_tools, stream: stream, **@chat_options)
|
|
174
245
|
|
|
175
246
|
if response.content && !response.tool_calls?
|
|
176
247
|
messages << { role: "assistant", content: response.content }
|
|
177
248
|
set_after_generation_state(response: response, messages: messages, initial_messages_count: initial_messages_count)
|
|
178
249
|
run_after_generation_callbacks(input: input, context: context, messages: messages, response: response)
|
|
179
250
|
sync_context_from_instance_variables(context)
|
|
251
|
+
stream&.call(OmniAgent::Streaming::Event.done(response))
|
|
180
252
|
return response
|
|
181
253
|
end
|
|
182
254
|
|
|
@@ -196,6 +268,9 @@ module OmniAgent
|
|
|
196
268
|
|
|
197
269
|
if tool_class
|
|
198
270
|
tool_instance = tool_class.new
|
|
271
|
+
tool_instance.context = context if tool_instance.respond_to?(:context=)
|
|
272
|
+
|
|
273
|
+
stream&.call(OmniAgent::Streaming::Event.tool_call(name: tool_name, arguments: tool_args, id: tool_id))
|
|
199
274
|
|
|
200
275
|
begin
|
|
201
276
|
result = tool_instance.invoke(tool_args)
|
|
@@ -206,13 +281,16 @@ module OmniAgent
|
|
|
206
281
|
name: tool_name,
|
|
207
282
|
content: result.to_s
|
|
208
283
|
}
|
|
284
|
+
stream&.call(OmniAgent::Streaming::Event.tool_result(name: tool_name, id: tool_id, content: result.to_s))
|
|
209
285
|
rescue => e
|
|
286
|
+
error_content = "Error executing tool: #{e.message}"
|
|
210
287
|
messages << {
|
|
211
288
|
role: "tool",
|
|
212
289
|
tool_call_id: tool_id,
|
|
213
290
|
name: tool_name,
|
|
214
|
-
content:
|
|
291
|
+
content: error_content
|
|
215
292
|
}
|
|
293
|
+
stream&.call(OmniAgent::Streaming::Event.tool_result(name: tool_name, id: tool_id, content: error_content, error: true))
|
|
216
294
|
end
|
|
217
295
|
|
|
218
296
|
should_stop_generation ||= tool_class.respond_to?(:stops_generation?) && tool_class.stops_generation?
|
|
@@ -231,6 +309,7 @@ module OmniAgent
|
|
|
231
309
|
set_after_generation_state(response: response, messages: messages, initial_messages_count: initial_messages_count)
|
|
232
310
|
run_after_generation_callbacks(input: input, context: context, messages: messages, response: response)
|
|
233
311
|
sync_context_from_instance_variables(context)
|
|
312
|
+
stream&.call(OmniAgent::Streaming::Event.done(response))
|
|
234
313
|
return response
|
|
235
314
|
end
|
|
236
315
|
end
|
|
@@ -238,12 +317,17 @@ module OmniAgent
|
|
|
238
317
|
|
|
239
318
|
def available_tools
|
|
240
319
|
tool_namespace = "#{self.class.name}::Tools".safe_constantize
|
|
241
|
-
return [] unless tool_namespace
|
|
242
320
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
321
|
+
namespace_tools = if tool_namespace
|
|
322
|
+
tool_namespace.constants.filter_map do |const_name|
|
|
323
|
+
const = tool_namespace.const_get(const_name)
|
|
324
|
+
const if const.is_a?(Class) && const < OmniAgent::Tool
|
|
325
|
+
end
|
|
326
|
+
else
|
|
327
|
+
[]
|
|
246
328
|
end
|
|
329
|
+
|
|
330
|
+
namespace_tools + self.class.configured_delegated_tool_classes
|
|
247
331
|
end
|
|
248
332
|
|
|
249
333
|
private
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module OmniAgent
|
|
2
2
|
class Configuration
|
|
3
3
|
attr_accessor :default_provider, :default_model, :max_retries, :retry_base_delay, :max_tool_iterations,
|
|
4
|
-
:eval_judge_provider, :eval_judge_model, :eval_cache_enabled, :eval_cache_path
|
|
4
|
+
:max_delegation_depth, :eval_judge_provider, :eval_judge_model, :eval_cache_enabled, :eval_cache_path
|
|
5
5
|
|
|
6
6
|
def initialize
|
|
7
7
|
@default_provider = :openai
|
|
@@ -9,6 +9,7 @@ module OmniAgent
|
|
|
9
9
|
@max_retries = 3
|
|
10
10
|
@retry_base_delay = 0.5
|
|
11
11
|
@max_tool_iterations = 10
|
|
12
|
+
@max_delegation_depth = 5
|
|
12
13
|
@eval_judge_provider = nil
|
|
13
14
|
@eval_judge_model = nil
|
|
14
15
|
@eval_cache_enabled = true
|
data/lib/omni_agent/errors.rb
CHANGED
|
@@ -4,6 +4,7 @@ module OmniAgent
|
|
|
4
4
|
class MissingDependencyError < Error; end
|
|
5
5
|
class UnknownProviderError < Error; end
|
|
6
6
|
class MaxToolIterationsError < Error; end
|
|
7
|
+
class MaxDelegationDepthError < Error; end
|
|
7
8
|
class EvalAssertionError < Error; end
|
|
8
9
|
end
|
|
9
10
|
|
|
@@ -11,5 +12,6 @@ module OmniAgent
|
|
|
11
12
|
MissingDependencyError = Errors::MissingDependencyError
|
|
12
13
|
UnknownProviderError = Errors::UnknownProviderError
|
|
13
14
|
MaxToolIterationsError = Errors::MaxToolIterationsError
|
|
15
|
+
MaxDelegationDepthError = Errors::MaxDelegationDepthError
|
|
14
16
|
EvalAssertionError = Errors::EvalAssertionError
|
|
15
17
|
end
|
|
@@ -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
|
@@ -28,6 +28,8 @@ module OmniAgent
|
|
|
28
28
|
|
|
29
29
|
@properties = builder.properties
|
|
30
30
|
@required = builder.required_fields
|
|
31
|
+
@validators = builder.validators
|
|
32
|
+
@polymorphics = builder.polymorphics
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
|
|
@@ -43,7 +45,15 @@ module OmniAgent
|
|
|
43
45
|
def parse_arguments(arguments_hash)
|
|
44
46
|
kwargs = arguments_hash.transform_keys(&:to_sym)
|
|
45
47
|
valid_keys = (@properties || {}).keys.map(&:to_sym)
|
|
46
|
-
kwargs.slice(*valid_keys)
|
|
48
|
+
filtered = kwargs.slice(*valid_keys)
|
|
49
|
+
|
|
50
|
+
validate_required!(filtered)
|
|
51
|
+
validate_enums!(filtered)
|
|
52
|
+
validate_constraints!(filtered)
|
|
53
|
+
run_custom_validators!(filtered)
|
|
54
|
+
resolve_polymorphics!(filtered)
|
|
55
|
+
|
|
56
|
+
filtered
|
|
47
57
|
end
|
|
48
58
|
|
|
49
59
|
def stops_generation(value = true)
|
|
@@ -56,6 +66,87 @@ module OmniAgent
|
|
|
56
66
|
|
|
57
67
|
private
|
|
58
68
|
|
|
69
|
+
def validate_required!(kwargs)
|
|
70
|
+
missing = (@required || []).map(&:to_sym) - kwargs.keys
|
|
71
|
+
return if missing.empty?
|
|
72
|
+
|
|
73
|
+
raise ArgumentError, "missing required argument(s): #{missing.join(', ')}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate_enums!(kwargs)
|
|
77
|
+
(@properties || {}).each do |name, property|
|
|
78
|
+
allowed_values = property[:enum]
|
|
79
|
+
next unless allowed_values
|
|
80
|
+
|
|
81
|
+
key = name.to_sym
|
|
82
|
+
next unless kwargs.key?(key)
|
|
83
|
+
|
|
84
|
+
unless allowed_values.include?(kwargs[key])
|
|
85
|
+
raise ArgumentError, "invalid value for #{name}: #{kwargs[key].inspect} (must be one of: #{allowed_values.join(', ')})"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def validate_constraints!(kwargs)
|
|
91
|
+
(@properties || {}).each do |name, property|
|
|
92
|
+
key = name.to_sym
|
|
93
|
+
next unless kwargs.key?(key)
|
|
94
|
+
|
|
95
|
+
check_constraints!(name, property, kwargs[key])
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def check_constraints!(name, property, value)
|
|
100
|
+
if property[:minLength] && value.is_a?(String) && value.length < property[:minLength]
|
|
101
|
+
raise ArgumentError, "#{name} must be at least #{property[:minLength]} characters"
|
|
102
|
+
end
|
|
103
|
+
if property[:maxLength] && value.is_a?(String) && value.length > property[:maxLength]
|
|
104
|
+
raise ArgumentError, "#{name} must be at most #{property[:maxLength]} characters"
|
|
105
|
+
end
|
|
106
|
+
if property[:pattern] && value.is_a?(String) && !value.match?(Regexp.new(property[:pattern]))
|
|
107
|
+
raise ArgumentError, "#{name} does not match required pattern #{property[:pattern]}"
|
|
108
|
+
end
|
|
109
|
+
if property[:minimum] && value.is_a?(Numeric) && value < property[:minimum]
|
|
110
|
+
raise ArgumentError, "#{name} must be >= #{property[:minimum]}"
|
|
111
|
+
end
|
|
112
|
+
if property[:maximum] && value.is_a?(Numeric) && value > property[:maximum]
|
|
113
|
+
raise ArgumentError, "#{name} must be <= #{property[:maximum]}"
|
|
114
|
+
end
|
|
115
|
+
if property[:minItems] && value.is_a?(Array) && value.length < property[:minItems]
|
|
116
|
+
raise ArgumentError, "#{name} must have at least #{property[:minItems]} items"
|
|
117
|
+
end
|
|
118
|
+
if property[:maxItems] && value.is_a?(Array) && value.length > property[:maxItems]
|
|
119
|
+
raise ArgumentError, "#{name} must have at most #{property[:maxItems]} items"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def run_custom_validators!(kwargs)
|
|
124
|
+
(@validators || {}).each do |name, validator|
|
|
125
|
+
key = name.to_sym
|
|
126
|
+
next unless kwargs.key?(key)
|
|
127
|
+
|
|
128
|
+
result = validator.call(kwargs[key])
|
|
129
|
+
raise ArgumentError, "invalid value for #{name}" if result == false
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def resolve_polymorphics!(kwargs)
|
|
134
|
+
(@polymorphics || {}).each do |name, group|
|
|
135
|
+
next unless group[:resolve]
|
|
136
|
+
|
|
137
|
+
type_field = group[:type_field]
|
|
138
|
+
id_field = group[:id_field]
|
|
139
|
+
next unless kwargs.key?(type_field) && kwargs.key?(id_field)
|
|
140
|
+
|
|
141
|
+
type_value = kwargs.delete(type_field)
|
|
142
|
+
id_value = kwargs.delete(id_field)
|
|
143
|
+
|
|
144
|
+
klass = Object.const_get(type_value.to_s)
|
|
145
|
+
record = klass.find(id_value)
|
|
146
|
+
kwargs[name.to_sym] = record
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
59
150
|
def normalize_tags(tag_names)
|
|
60
151
|
raise ArgumentError, "tags requires at least one tag" if tag_names.empty?
|
|
61
152
|
|
|
@@ -69,6 +160,12 @@ module OmniAgent
|
|
|
69
160
|
end
|
|
70
161
|
end
|
|
71
162
|
|
|
163
|
+
attr_accessor :context
|
|
164
|
+
|
|
165
|
+
def initialize
|
|
166
|
+
@context = {}
|
|
167
|
+
end
|
|
168
|
+
|
|
72
169
|
def invoke(arguments_hash)
|
|
73
170
|
filtered_kwargs = self.class.parse_arguments(arguments_hash)
|
|
74
171
|
execute(**filtered_kwargs)
|
data/lib/omni_agent/version.rb
CHANGED
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.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ACR1209
|
|
@@ -85,9 +85,12 @@ files:
|
|
|
85
85
|
- lib/omni_agent/providers/base.rb
|
|
86
86
|
- lib/omni_agent/providers/mock.rb
|
|
87
87
|
- lib/omni_agent/providers/mock_judge.rb
|
|
88
|
+
- lib/omni_agent/providers/ollama.rb
|
|
88
89
|
- lib/omni_agent/providers/openai.rb
|
|
89
90
|
- lib/omni_agent/providers/response.rb
|
|
90
91
|
- lib/omni_agent/railtie.rb
|
|
92
|
+
- lib/omni_agent/streaming/event.rb
|
|
93
|
+
- lib/omni_agent/streaming/proxy.rb
|
|
91
94
|
- lib/omni_agent/tasks/omni_agent_tasks.rake
|
|
92
95
|
- lib/omni_agent/tool.rb
|
|
93
96
|
- lib/omni_agent/tool/schema_builder.rb
|