rails_ai 0.1.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 +7 -0
- data/.rspec_status +96 -0
- data/AGENT_GUIDE.md +513 -0
- data/Appraisals +49 -0
- data/COMMERCIAL_LICENSE_TEMPLATE.md +92 -0
- data/FEATURES.md +204 -0
- data/LEGAL_PROTECTION_GUIDE.md +222 -0
- data/LICENSE +62 -0
- data/LICENSE_SUMMARY.md +74 -0
- data/MIT-LICENSE +62 -0
- data/PERFORMANCE.md +300 -0
- data/PROVIDERS.md +495 -0
- data/README.md +454 -0
- data/Rakefile +11 -0
- data/SPEED_OPTIMIZATIONS.md +217 -0
- data/STRUCTURE.md +139 -0
- data/USAGE_GUIDE.md +288 -0
- data/app/channels/ai_stream_channel.rb +33 -0
- data/app/components/ai/prompt_component.rb +25 -0
- data/app/controllers/concerns/ai/context_aware.rb +77 -0
- data/app/controllers/concerns/ai/streaming.rb +41 -0
- data/app/helpers/ai_helper.rb +164 -0
- data/app/jobs/ai/generate_embedding_job.rb +25 -0
- data/app/jobs/ai/generate_summary_job.rb +25 -0
- data/app/models/concerns/ai/embeddable.rb +38 -0
- data/app/views/rails_ai/dashboard/index.html.erb +51 -0
- data/config/routes.rb +19 -0
- data/lib/generators/rails_ai/install/install_generator.rb +38 -0
- data/lib/rails_ai/agents/agent_manager.rb +258 -0
- data/lib/rails_ai/agents/agent_team.rb +243 -0
- data/lib/rails_ai/agents/base_agent.rb +331 -0
- data/lib/rails_ai/agents/collaboration.rb +238 -0
- data/lib/rails_ai/agents/memory.rb +116 -0
- data/lib/rails_ai/agents/message_bus.rb +95 -0
- data/lib/rails_ai/agents/specialized_agents.rb +391 -0
- data/lib/rails_ai/agents/task_queue.rb +111 -0
- data/lib/rails_ai/cache.rb +14 -0
- data/lib/rails_ai/config.rb +40 -0
- data/lib/rails_ai/context.rb +7 -0
- data/lib/rails_ai/context_analyzer.rb +86 -0
- data/lib/rails_ai/engine.rb +48 -0
- data/lib/rails_ai/events.rb +9 -0
- data/lib/rails_ai/image_context.rb +110 -0
- data/lib/rails_ai/performance.rb +231 -0
- data/lib/rails_ai/provider.rb +8 -0
- data/lib/rails_ai/providers/anthropic_adapter.rb +256 -0
- data/lib/rails_ai/providers/base.rb +60 -0
- data/lib/rails_ai/providers/dummy_adapter.rb +29 -0
- data/lib/rails_ai/providers/gemini_adapter.rb +509 -0
- data/lib/rails_ai/providers/openai_adapter.rb +535 -0
- data/lib/rails_ai/providers/secure_anthropic_adapter.rb +206 -0
- data/lib/rails_ai/providers/secure_openai_adapter.rb +284 -0
- data/lib/rails_ai/railtie.rb +48 -0
- data/lib/rails_ai/redactor.rb +12 -0
- data/lib/rails_ai/security/api_key_manager.rb +82 -0
- data/lib/rails_ai/security/audit_logger.rb +46 -0
- data/lib/rails_ai/security/error_handler.rb +62 -0
- data/lib/rails_ai/security/input_validator.rb +176 -0
- data/lib/rails_ai/security/secure_file_handler.rb +45 -0
- data/lib/rails_ai/security/secure_http_client.rb +177 -0
- data/lib/rails_ai/security.rb +0 -0
- data/lib/rails_ai/version.rb +5 -0
- data/lib/rails_ai/window_context.rb +103 -0
- data/lib/rails_ai.rb +502 -0
- data/monitoring/ci_setup_guide.md +214 -0
- data/monitoring/enhanced_monitoring_script.rb +237 -0
- data/monitoring/google_alerts_setup.md +42 -0
- data/monitoring_log_20250921.txt +0 -0
- data/monitoring_script.rb +161 -0
- data/rails_ai.gemspec +54 -0
- data/scripts/security_scanner.rb +353 -0
- data/setup_monitoring.sh +163 -0
- data/wiki/API-Documentation.md +734 -0
- data/wiki/Architecture-Overview.md +672 -0
- data/wiki/Contributing-Guide.md +407 -0
- data/wiki/Development-Setup.md +532 -0
- data/wiki/Home.md +278 -0
- data/wiki/Installation-Guide.md +527 -0
- data/wiki/Quick-Start.md +186 -0
- data/wiki/README.md +135 -0
- data/wiki/Release-Process.md +467 -0
- metadata +385 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AiHelper
|
4
|
+
# Text-based AI operations
|
5
|
+
def ai_chat(prompt, model: nil, **options)
|
6
|
+
RailsAi.chat(prompt, model: model, **options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def ai_stream(prompt, model: nil, **options, &block)
|
10
|
+
RailsAi.stream(prompt, model: model, **options, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ai_embed(texts, model: nil, **options)
|
14
|
+
RailsAi.embed(texts, model: model, **options)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Image generation
|
18
|
+
def ai_generate_image(prompt, model: "dall-e-3", size: "1024x1024", **options)
|
19
|
+
RailsAi.generate_image(prompt, model: model, size: size, **options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def ai_edit_image(image, prompt, **options)
|
23
|
+
RailsAi.edit_image(image, prompt, **options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def ai_create_variation(image, **options)
|
27
|
+
RailsAi.create_variation(image, **options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Video generation
|
31
|
+
def ai_generate_video(prompt, model: "sora", duration: 5, **options)
|
32
|
+
RailsAi.generate_video(prompt, model: model, duration: duration, **options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def ai_edit_video(video, prompt, **options)
|
36
|
+
RailsAi.edit_video(video, prompt, **options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Audio generation
|
40
|
+
def ai_generate_speech(text, voice: "alloy", **options)
|
41
|
+
RailsAi.generate_speech(text, voice: voice, **options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ai_transcribe_audio(audio, **options)
|
45
|
+
RailsAi.transcribe_audio(audio, **options)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Multimodal analysis
|
49
|
+
def ai_analyze_image(image, prompt, **options)
|
50
|
+
RailsAi.analyze_image(image, prompt, **options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def ai_analyze_video(video, prompt, **options)
|
54
|
+
RailsAi.analyze_video(video, prompt, **options)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Context-aware AI operations
|
58
|
+
def ai_analyze_image_with_context(image, prompt, user_context: {}, window_context: {}, image_context: {}, **options)
|
59
|
+
RailsAi.analyze_image_with_context(image, prompt,
|
60
|
+
user_context: user_context,
|
61
|
+
window_context: window_context,
|
62
|
+
image_context: image_context,
|
63
|
+
**options
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def ai_generate_with_context(prompt, user_context: {}, window_context: {}, **options)
|
68
|
+
RailsAi.generate_with_context(prompt,
|
69
|
+
user_context: user_context,
|
70
|
+
window_context: window_context,
|
71
|
+
**options
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def ai_generate_image_with_context(prompt, user_context: {}, window_context: {}, **options)
|
76
|
+
RailsAi.generate_image_with_context(prompt,
|
77
|
+
user_context: user_context,
|
78
|
+
window_context: window_context,
|
79
|
+
**options
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Convenience methods for common AI tasks
|
84
|
+
def ai_summarize(content, model: nil, **options)
|
85
|
+
RailsAi.summarize(content, model: model, **options)
|
86
|
+
end
|
87
|
+
|
88
|
+
def ai_translate(content, target_language, **options)
|
89
|
+
RailsAi.translate(content, target_language, **options)
|
90
|
+
end
|
91
|
+
|
92
|
+
def ai_classify(content, categories, **options)
|
93
|
+
RailsAi.classify(content, categories, **options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def ai_extract_entities(content, **options)
|
97
|
+
RailsAi.extract_entities(content, **options)
|
98
|
+
end
|
99
|
+
|
100
|
+
def ai_generate_code(prompt, language: "ruby", **options)
|
101
|
+
RailsAi.generate_code(prompt, language: language, **options)
|
102
|
+
end
|
103
|
+
|
104
|
+
def ai_explain_code(code, language: "ruby", **options)
|
105
|
+
RailsAi.explain_code(code, language: language, **options)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Context extraction helpers
|
109
|
+
def ai_user_context
|
110
|
+
return {} unless respond_to?(:current_user) && current_user
|
111
|
+
|
112
|
+
{
|
113
|
+
id: current_user.id,
|
114
|
+
email: current_user.respond_to?(:email) ? current_user.email : nil,
|
115
|
+
role: current_user.respond_to?(:role) ? current_user.role : 'user',
|
116
|
+
created_at: current_user.created_at,
|
117
|
+
preferences: extract_user_preferences
|
118
|
+
}.compact
|
119
|
+
end
|
120
|
+
|
121
|
+
def ai_window_context
|
122
|
+
{
|
123
|
+
controller: controller_name,
|
124
|
+
action: action_name,
|
125
|
+
params: params.except('password', 'password_confirmation', 'token', 'secret', 'key'),
|
126
|
+
user_agent: request.user_agent,
|
127
|
+
referer: request.referer,
|
128
|
+
ip_address: request.remote_ip,
|
129
|
+
timestamp: Time.current.iso8601
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
def ai_image_context(image_data)
|
134
|
+
RailsAi::ImageContext.new(image_data).to_h
|
135
|
+
end
|
136
|
+
|
137
|
+
# Utility methods
|
138
|
+
def ai_stream_id
|
139
|
+
@ai_stream_id ||= SecureRandom.uuid
|
140
|
+
end
|
141
|
+
|
142
|
+
def ai_redact(text)
|
143
|
+
RailsAi::Redactor.call(text)
|
144
|
+
end
|
145
|
+
|
146
|
+
def ai_safe_content(text)
|
147
|
+
RailsAi::Redactor.call(text)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def extract_user_preferences
|
153
|
+
return {} unless current_user.respond_to?(:preferences)
|
154
|
+
|
155
|
+
case current_user.preferences
|
156
|
+
when Hash
|
157
|
+
current_user.preferences
|
158
|
+
when String
|
159
|
+
JSON.parse(current_user.preferences) rescue {}
|
160
|
+
else
|
161
|
+
{}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
class GenerateEmbeddingJob < ApplicationJob
|
5
|
+
queue_as :default
|
6
|
+
|
7
|
+
def perform(text, model: nil, **options)
|
8
|
+
model ||= RailsAi.config.default_model
|
9
|
+
|
10
|
+
embedding = RailsAi.provider.embed!(
|
11
|
+
texts: [text],
|
12
|
+
model: model,
|
13
|
+
**options
|
14
|
+
).first
|
15
|
+
|
16
|
+
RailsAi::Events.log!(
|
17
|
+
kind: :embedding,
|
18
|
+
name: "generated",
|
19
|
+
payload: {text_length: text.length, model: model}
|
20
|
+
)
|
21
|
+
|
22
|
+
embedding
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
class GenerateSummaryJob < ApplicationJob
|
5
|
+
queue_as :default
|
6
|
+
|
7
|
+
def perform(content, model: nil, **options)
|
8
|
+
model ||= RailsAi.config.default_model
|
9
|
+
|
10
|
+
summary = RailsAi.chat(
|
11
|
+
"Please provide a concise summary of the following content: #{content}",
|
12
|
+
model: model,
|
13
|
+
**options
|
14
|
+
)
|
15
|
+
|
16
|
+
RailsAi::Events.log!(
|
17
|
+
kind: :summary,
|
18
|
+
name: "generated",
|
19
|
+
payload: {content_length: content.length, model: model}
|
20
|
+
)
|
21
|
+
|
22
|
+
summary
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
module Embeddable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
has_many :ai_embeddings, as: :embeddable, dependent: :destroy
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate_embedding!(field: :content, model: nil)
|
12
|
+
text = send(field)
|
13
|
+
return if text.blank?
|
14
|
+
|
15
|
+
model ||= RailsAi.config.default_model
|
16
|
+
|
17
|
+
embedding = RailsAi.provider.embed!(
|
18
|
+
texts: [text],
|
19
|
+
model: model
|
20
|
+
).first
|
21
|
+
|
22
|
+
ai_embeddings.create!(
|
23
|
+
content: text,
|
24
|
+
embedding: embedding,
|
25
|
+
model: model,
|
26
|
+
field: field.to_s
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def similar_records(limit: 5, threshold: 0.8)
|
31
|
+
return [] unless ai_embeddings.any?
|
32
|
+
|
33
|
+
# This would need to be implemented based on your vector database
|
34
|
+
# For now, return empty array
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<%# frozen_string_literal: true %>
|
2
|
+
|
3
|
+
<div class="rails-ai-dashboard">
|
4
|
+
<h1>Rails AI Dashboard</h1>
|
5
|
+
|
6
|
+
<div class="ai-chat-interface">
|
7
|
+
<%= render RailsAi::PromptComponent.new(
|
8
|
+
prompt: "Hello, how can I help you today?",
|
9
|
+
stream_id: ai_stream_id
|
10
|
+
) %>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class="ai-features">
|
14
|
+
<h2>Available Features</h2>
|
15
|
+
<ul>
|
16
|
+
<li>AI Chat Interface</li>
|
17
|
+
<li>Content Summarization</li>
|
18
|
+
<li>Embedding Generation</li>
|
19
|
+
<li>Streaming Responses</li>
|
20
|
+
</ul>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<div class="ai-config">
|
24
|
+
<h2>Configuration</h2>
|
25
|
+
<p>Provider: <%= RailsAi.config.provider %></p>
|
26
|
+
<p>Model: <%= RailsAi.config.default_model %></p>
|
27
|
+
<p>Cache TTL: <%= RailsAi.config.cache_ttl %></p>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
|
31
|
+
<style>
|
32
|
+
.rails-ai-dashboard {
|
33
|
+
max-width: 800px;
|
34
|
+
margin: 0 auto;
|
35
|
+
padding: 20px;
|
36
|
+
}
|
37
|
+
|
38
|
+
.ai-chat-interface {
|
39
|
+
margin: 20px 0;
|
40
|
+
padding: 20px;
|
41
|
+
border: 1px solid #ddd;
|
42
|
+
border-radius: 8px;
|
43
|
+
}
|
44
|
+
|
45
|
+
.ai-features, .ai-config {
|
46
|
+
margin: 20px 0;
|
47
|
+
padding: 15px;
|
48
|
+
background-color: #f9f9f9;
|
49
|
+
border-radius: 8px;
|
50
|
+
}
|
51
|
+
</style>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RailsAi::Engine.routes.draw do
|
4
|
+
root "dashboard#index"
|
5
|
+
|
6
|
+
resources :streams, only: [:create] do
|
7
|
+
member do
|
8
|
+
post :stream
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace :api do
|
13
|
+
namespace :v1 do
|
14
|
+
resources :chat, only: [:create]
|
15
|
+
resources :embeddings, only: [:create]
|
16
|
+
resources :summarize, only: [:create]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
|
8
|
+
def create_initializer
|
9
|
+
copy_file "initializer.rb", "config/initializers/rails_ai.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_migrations
|
13
|
+
copy_file "ai_migrations.rb", "db/migrate/#{Time.current.strftime("%Y%m%d%H%M%S")}_create_ai_tables.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_jobs
|
17
|
+
copy_file "summarizer_job.rb", "app/jobs/ai/generate_summary_job.rb"
|
18
|
+
copy_file "summarizer_service.rb", "app/services/ai/summarizer_service.rb"
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_helpers
|
22
|
+
copy_file "ai_helper.rb", "app/helpers/ai_helper.rb"
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_components
|
26
|
+
copy_file "ai_widget_component.rb", "app/components/ai/prompt_component.rb"
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_routes
|
30
|
+
route 'mount RailsAi::Engine, at: "/rails_ai"'
|
31
|
+
end
|
32
|
+
|
33
|
+
def show_readme
|
34
|
+
readme "README" if behavior == :invoke
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
module Agents
|
5
|
+
class AgentManager
|
6
|
+
attr_reader :agents, :message_bus, :task_queue
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@agents = {}
|
10
|
+
@message_bus = MessageBus.new
|
11
|
+
@task_queue = TaskQueue.new
|
12
|
+
@running = false
|
13
|
+
@thread_pool = Concurrent::ThreadPoolExecutor.new(
|
14
|
+
min_threads: 2,
|
15
|
+
max_threads: 10,
|
16
|
+
max_queue: 100
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Agent lifecycle management
|
21
|
+
def register_agent(agent)
|
22
|
+
@agents[agent.name] = agent
|
23
|
+
@message_bus.subscribe(agent.name, agent)
|
24
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Registered agent: #{agent.name}")
|
25
|
+
agent
|
26
|
+
end
|
27
|
+
|
28
|
+
def unregister_agent(agent_name)
|
29
|
+
agent = @agents.delete(agent_name)
|
30
|
+
return false unless agent
|
31
|
+
|
32
|
+
@message_bus.unsubscribe(agent_name)
|
33
|
+
agent.stop!
|
34
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Unregistered agent: #{agent_name}")
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_agent(agent_name)
|
39
|
+
@agents[agent_name]
|
40
|
+
end
|
41
|
+
|
42
|
+
def list_agents
|
43
|
+
@agents.values.map(&:status)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Task management
|
47
|
+
def submit_task(task)
|
48
|
+
@task_queue.enqueue(task)
|
49
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Submitted task: #{task[:description]}")
|
50
|
+
task
|
51
|
+
end
|
52
|
+
|
53
|
+
def assign_task_to_agent(task, agent_name)
|
54
|
+
agent = get_agent(agent_name)
|
55
|
+
return false unless agent
|
56
|
+
|
57
|
+
agent.assign_task(task)
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_best_agent_for_task(task)
|
61
|
+
available_agents = @agents.values.select { |agent| agent.state == :active }
|
62
|
+
|
63
|
+
return nil if available_agents.empty?
|
64
|
+
|
65
|
+
# Score agents based on capabilities and current workload
|
66
|
+
scored_agents = available_agents.map do |agent|
|
67
|
+
score = calculate_agent_score(agent, task)
|
68
|
+
{ agent: agent, score: score }
|
69
|
+
end
|
70
|
+
|
71
|
+
scored_agents.max_by { |item| item[:score] }&.dig(:agent)
|
72
|
+
end
|
73
|
+
|
74
|
+
def auto_assign_task(task)
|
75
|
+
best_agent = find_best_agent_for_task(task)
|
76
|
+
return false unless best_agent
|
77
|
+
|
78
|
+
assign_task_to_agent(task, best_agent.name)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Communication
|
82
|
+
def send_message(from_agent, to_agent, message)
|
83
|
+
@message_bus.send_message(from_agent, to_agent, message)
|
84
|
+
end
|
85
|
+
|
86
|
+
def broadcast_message(from_agent, message, exclude: [])
|
87
|
+
@message_bus.broadcast(from_agent, message, exclude: exclude)
|
88
|
+
end
|
89
|
+
|
90
|
+
# System control
|
91
|
+
def start!
|
92
|
+
return false if @running
|
93
|
+
|
94
|
+
@running = true
|
95
|
+
start_task_processor
|
96
|
+
start_agent_monitor
|
97
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent Manager started")
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def stop!
|
102
|
+
return false unless @running
|
103
|
+
|
104
|
+
@running = false
|
105
|
+
@thread_pool.shutdown
|
106
|
+
@thread_pool.wait_for_termination(30)
|
107
|
+
|
108
|
+
@agents.each_value(&:stop!)
|
109
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent Manager stopped")
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
def pause!
|
114
|
+
@running = false
|
115
|
+
@agents.each_value(&:pause!)
|
116
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent Manager paused")
|
117
|
+
end
|
118
|
+
|
119
|
+
def resume!
|
120
|
+
@running = true
|
121
|
+
@agents.each_value(&:resume!)
|
122
|
+
start_task_processor
|
123
|
+
start_agent_monitor
|
124
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent Manager resumed")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Monitoring and health
|
128
|
+
def system_status
|
129
|
+
{
|
130
|
+
running: @running,
|
131
|
+
total_agents: @agents.length,
|
132
|
+
active_agents: @agents.values.count { |a| a.state == :active },
|
133
|
+
paused_agents: @agents.values.count { |a| a.state == :paused },
|
134
|
+
stopped_agents: @agents.values.count { |a| a.state == :stopped },
|
135
|
+
pending_tasks: @task_queue.size,
|
136
|
+
total_tasks_processed: @task_queue.total_processed,
|
137
|
+
thread_pool_status: @thread_pool.running?
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def health_check
|
142
|
+
agent_health = @agents.values.map do |agent|
|
143
|
+
{ name: agent.name, health: agent.health_check }
|
144
|
+
end
|
145
|
+
|
146
|
+
{
|
147
|
+
system_healthy: @running && @thread_pool.running?,
|
148
|
+
agent_health: agent_health,
|
149
|
+
memory_usage: calculate_memory_usage,
|
150
|
+
task_queue_healthy: @task_queue.size < 1000
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
# Agent collaboration
|
155
|
+
def create_agent_team(team_name, agents, collaboration_strategy: :round_robin)
|
156
|
+
team = AgentTeam.new(
|
157
|
+
name: team_name,
|
158
|
+
agents: agents,
|
159
|
+
strategy: collaboration_strategy,
|
160
|
+
manager: self
|
161
|
+
)
|
162
|
+
|
163
|
+
team.agents.each { |agent| register_agent(agent) }
|
164
|
+
team
|
165
|
+
end
|
166
|
+
|
167
|
+
def orchestrate_collaboration(task, agent_names)
|
168
|
+
agents = agent_names.map { |name| get_agent(name) }.compact
|
169
|
+
return false if agents.empty?
|
170
|
+
|
171
|
+
collaboration = Collaboration.new(
|
172
|
+
task: task,
|
173
|
+
agents: agents,
|
174
|
+
manager: self
|
175
|
+
)
|
176
|
+
|
177
|
+
collaboration.start!
|
178
|
+
collaboration
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def calculate_agent_score(agent, task)
|
184
|
+
score = 0
|
185
|
+
|
186
|
+
# Capability match (40% of score)
|
187
|
+
required_capabilities = task[:required_capabilities] || []
|
188
|
+
capability_match = required_capabilities.count { |cap| agent.has_capability?(cap) }
|
189
|
+
score += (capability_match.to_f / required_capabilities.length * 40) if required_capabilities.any?
|
190
|
+
|
191
|
+
# Current workload (30% of score)
|
192
|
+
workload_score = [0, 30 - (agent.active_tasks.length * 10)].max
|
193
|
+
score += workload_score
|
194
|
+
|
195
|
+
# Memory health (20% of score)
|
196
|
+
memory_score = agent.memory.usage_percentage < 80 ? 20 : 10
|
197
|
+
score += memory_score
|
198
|
+
|
199
|
+
# Recent activity (10% of score)
|
200
|
+
activity_score = (Time.now - agent.last_activity) < 5 * 60 ? 10 : 5
|
201
|
+
score += activity_score
|
202
|
+
|
203
|
+
score
|
204
|
+
end
|
205
|
+
|
206
|
+
def calculate_memory_usage
|
207
|
+
total_memory = @agents.values.sum { |agent| agent.memory.size }
|
208
|
+
total_capacity = @agents.values.sum { |agent| agent.memory.instance_variable_get(:@max_size) }
|
209
|
+
|
210
|
+
return 0 if total_capacity.zero?
|
211
|
+
|
212
|
+
(total_memory.to_f / total_capacity * 100).round(2)
|
213
|
+
end
|
214
|
+
|
215
|
+
def start_task_processor
|
216
|
+
@thread_pool.post do
|
217
|
+
while @running
|
218
|
+
begin
|
219
|
+
task = @task_queue.dequeue(timeout: 1)
|
220
|
+
next unless task
|
221
|
+
|
222
|
+
best_agent = find_best_agent_for_task(task)
|
223
|
+
if best_agent
|
224
|
+
assign_task_to_agent(task, best_agent.name)
|
225
|
+
else
|
226
|
+
# No available agent, re-queue task
|
227
|
+
@task_queue.enqueue(task, priority: :high)
|
228
|
+
sleep(5) # Wait before retrying
|
229
|
+
end
|
230
|
+
rescue => e
|
231
|
+
defined?(Rails) && Rails.logger && Rails.logger.error("Task processor error: #{e.message}")
|
232
|
+
sleep(1)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def start_agent_monitor
|
239
|
+
@thread_pool.post do
|
240
|
+
while @running
|
241
|
+
begin
|
242
|
+
@agents.each_value do |agent|
|
243
|
+
health = agent.health_check
|
244
|
+
unless health.values.all?
|
245
|
+
defined?(Rails) && Rails.logger && Rails.logger.warn("Agent #{agent.name} health issues: #{health}")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
sleep(30) # Check every 30 seconds
|
249
|
+
rescue => e
|
250
|
+
defined?(Rails) && Rails.logger && Rails.logger.error("Agent monitor error: #{e.message}")
|
251
|
+
sleep(5)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|