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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec_status +96 -0
  3. data/AGENT_GUIDE.md +513 -0
  4. data/Appraisals +49 -0
  5. data/COMMERCIAL_LICENSE_TEMPLATE.md +92 -0
  6. data/FEATURES.md +204 -0
  7. data/LEGAL_PROTECTION_GUIDE.md +222 -0
  8. data/LICENSE +62 -0
  9. data/LICENSE_SUMMARY.md +74 -0
  10. data/MIT-LICENSE +62 -0
  11. data/PERFORMANCE.md +300 -0
  12. data/PROVIDERS.md +495 -0
  13. data/README.md +454 -0
  14. data/Rakefile +11 -0
  15. data/SPEED_OPTIMIZATIONS.md +217 -0
  16. data/STRUCTURE.md +139 -0
  17. data/USAGE_GUIDE.md +288 -0
  18. data/app/channels/ai_stream_channel.rb +33 -0
  19. data/app/components/ai/prompt_component.rb +25 -0
  20. data/app/controllers/concerns/ai/context_aware.rb +77 -0
  21. data/app/controllers/concerns/ai/streaming.rb +41 -0
  22. data/app/helpers/ai_helper.rb +164 -0
  23. data/app/jobs/ai/generate_embedding_job.rb +25 -0
  24. data/app/jobs/ai/generate_summary_job.rb +25 -0
  25. data/app/models/concerns/ai/embeddable.rb +38 -0
  26. data/app/views/rails_ai/dashboard/index.html.erb +51 -0
  27. data/config/routes.rb +19 -0
  28. data/lib/generators/rails_ai/install/install_generator.rb +38 -0
  29. data/lib/rails_ai/agents/agent_manager.rb +258 -0
  30. data/lib/rails_ai/agents/agent_team.rb +243 -0
  31. data/lib/rails_ai/agents/base_agent.rb +331 -0
  32. data/lib/rails_ai/agents/collaboration.rb +238 -0
  33. data/lib/rails_ai/agents/memory.rb +116 -0
  34. data/lib/rails_ai/agents/message_bus.rb +95 -0
  35. data/lib/rails_ai/agents/specialized_agents.rb +391 -0
  36. data/lib/rails_ai/agents/task_queue.rb +111 -0
  37. data/lib/rails_ai/cache.rb +14 -0
  38. data/lib/rails_ai/config.rb +40 -0
  39. data/lib/rails_ai/context.rb +7 -0
  40. data/lib/rails_ai/context_analyzer.rb +86 -0
  41. data/lib/rails_ai/engine.rb +48 -0
  42. data/lib/rails_ai/events.rb +9 -0
  43. data/lib/rails_ai/image_context.rb +110 -0
  44. data/lib/rails_ai/performance.rb +231 -0
  45. data/lib/rails_ai/provider.rb +8 -0
  46. data/lib/rails_ai/providers/anthropic_adapter.rb +256 -0
  47. data/lib/rails_ai/providers/base.rb +60 -0
  48. data/lib/rails_ai/providers/dummy_adapter.rb +29 -0
  49. data/lib/rails_ai/providers/gemini_adapter.rb +509 -0
  50. data/lib/rails_ai/providers/openai_adapter.rb +535 -0
  51. data/lib/rails_ai/providers/secure_anthropic_adapter.rb +206 -0
  52. data/lib/rails_ai/providers/secure_openai_adapter.rb +284 -0
  53. data/lib/rails_ai/railtie.rb +48 -0
  54. data/lib/rails_ai/redactor.rb +12 -0
  55. data/lib/rails_ai/security/api_key_manager.rb +82 -0
  56. data/lib/rails_ai/security/audit_logger.rb +46 -0
  57. data/lib/rails_ai/security/error_handler.rb +62 -0
  58. data/lib/rails_ai/security/input_validator.rb +176 -0
  59. data/lib/rails_ai/security/secure_file_handler.rb +45 -0
  60. data/lib/rails_ai/security/secure_http_client.rb +177 -0
  61. data/lib/rails_ai/security.rb +0 -0
  62. data/lib/rails_ai/version.rb +5 -0
  63. data/lib/rails_ai/window_context.rb +103 -0
  64. data/lib/rails_ai.rb +502 -0
  65. data/monitoring/ci_setup_guide.md +214 -0
  66. data/monitoring/enhanced_monitoring_script.rb +237 -0
  67. data/monitoring/google_alerts_setup.md +42 -0
  68. data/monitoring_log_20250921.txt +0 -0
  69. data/monitoring_script.rb +161 -0
  70. data/rails_ai.gemspec +54 -0
  71. data/scripts/security_scanner.rb +353 -0
  72. data/setup_monitoring.sh +163 -0
  73. data/wiki/API-Documentation.md +734 -0
  74. data/wiki/Architecture-Overview.md +672 -0
  75. data/wiki/Contributing-Guide.md +407 -0
  76. data/wiki/Development-Setup.md +532 -0
  77. data/wiki/Home.md +278 -0
  78. data/wiki/Installation-Guide.md +527 -0
  79. data/wiki/Quick-Start.md +186 -0
  80. data/wiki/README.md +135 -0
  81. data/wiki/Release-Process.md +467 -0
  82. metadata +385 -0
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAi
4
+ module Agents
5
+ class Collaboration
6
+ attr_reader :id, :task, :agents, :manager, :status, :created_at
7
+
8
+ def initialize(task:, agents:, manager:)
9
+ @id = SecureRandom.uuid
10
+ @task = task
11
+ @agents = Array(agents)
12
+ @manager = manager
13
+ @status = :pending
14
+ @created_at = Time.now
15
+ @contributions = {}
16
+ @workflow = []
17
+ @current_phase = 0
18
+ @phases = build_workflow_phases
19
+ end
20
+
21
+ def start!
22
+ @status = :in_progress
23
+ @started_at = Time.now
24
+
25
+ defined?(Rails) && Rails.logger && Rails.logger.info("Collaboration #{@id} started with #{@agents.length} agents")
26
+
27
+ # Begin the collaboration workflow
28
+ execute_workflow_phase
29
+ self
30
+ end
31
+
32
+ def add_contribution(agent_name, contribution)
33
+ @contributions[agent_name] = {
34
+ content: contribution,
35
+ timestamp: Time.now,
36
+ phase: @current_phase
37
+ }
38
+
39
+ defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{agent_name} contributed to collaboration #{@id}")
40
+
41
+ # Check if current phase is complete
42
+ if phase_complete?
43
+ advance_to_next_phase
44
+ end
45
+ end
46
+
47
+ def get_contributions(agent_name = nil)
48
+ if agent_name
49
+ @contributions[agent_name]
50
+ else
51
+ @contributions
52
+ end
53
+ end
54
+
55
+ def get_phase_contributions(phase)
56
+ @contributions.select { |_, contrib| contrib[:phase] == phase }
57
+ end
58
+
59
+ def current_phase_info
60
+ return nil if @current_phase >= @phases.length
61
+
62
+ @phases[@current_phase]
63
+ end
64
+
65
+ def is_complete?
66
+ @status == :completed
67
+ end
68
+
69
+ def is_failed?
70
+ @status == :failed
71
+ end
72
+
73
+ def duration
74
+ return nil unless @started_at
75
+
76
+ end_time = @completed_at || Time.now
77
+ end_time - @started_at
78
+ end
79
+
80
+ def summary
81
+ {
82
+ id: @id,
83
+ task: @task,
84
+ agents: @agents.map(&:name),
85
+ status: @status,
86
+ current_phase: @current_phase,
87
+ total_phases: @phases.length,
88
+ contributions_count: @contributions.length,
89
+ duration: duration,
90
+ created_at: @created_at,
91
+ started_at: @started_at,
92
+ completed_at: @completed_at
93
+ }
94
+ end
95
+
96
+ def complete!(result = nil)
97
+ @status = :completed
98
+ @completed_at = Time.now
99
+ @result = result
100
+
101
+ defined?(Rails) && Rails.logger && Rails.logger.info("Collaboration #{@id} completed in #{duration&.round(2)} seconds")
102
+
103
+ # Notify all participating agents
104
+ @agents.each do |agent|
105
+ @manager.send_message("collaboration_system", agent.name, {
106
+ type: :collaboration_completed,
107
+ collaboration_id: @id,
108
+ result: result
109
+ })
110
+ end
111
+
112
+ self
113
+ end
114
+
115
+ def fail!(error)
116
+ @status = :failed
117
+ @failed_at = Time.now
118
+ @error = error
119
+
120
+ defined?(Rails) && Rails.logger && Rails.logger.error("Collaboration #{@id} failed: #{error}")
121
+
122
+ # Notify all participating agents
123
+ @agents.each do |agent|
124
+ @manager.send_message("collaboration_system", agent.name, {
125
+ type: :collaboration_failed,
126
+ collaboration_id: @id,
127
+ error: error
128
+ })
129
+ end
130
+
131
+ self
132
+ end
133
+
134
+ private
135
+
136
+ def build_workflow_phases
137
+ case @task[:type] || :general
138
+ when :analysis
139
+ [
140
+ { name: "data_gathering", description: "Gather and analyze data", required_agents: @agents.length },
141
+ { name: "pattern_recognition", description: "Identify patterns and insights", required_agents: @agents.length },
142
+ { name: "synthesis", description: "Synthesize findings into conclusions", required_agents: 1 }
143
+ ]
144
+ when :creative
145
+ [
146
+ { name: "brainstorming", description: "Generate creative ideas", required_agents: @agents.length },
147
+ { name: "refinement", description: "Refine and improve ideas", required_agents: @agents.length },
148
+ { name: "finalization", description: "Finalize the creative output", required_agents: 1 }
149
+ ]
150
+ when :problem_solving
151
+ [
152
+ { name: "problem_analysis", description: "Analyze the problem thoroughly", required_agents: @agents.length },
153
+ { name: "solution_generation", description: "Generate potential solutions", required_agents: @agents.length },
154
+ { name: "solution_evaluation", description: "Evaluate and select best solution", required_agents: @agents.length },
155
+ { name: "implementation_plan", description: "Create implementation plan", required_agents: 1 }
156
+ ]
157
+ else
158
+ [
159
+ { name: "discussion", description: "Discuss the task", required_agents: @agents.length },
160
+ { name: "consensus", description: "Reach consensus on approach", required_agents: @agents.length },
161
+ { name: "execution", description: "Execute the agreed approach", required_agents: 1 }
162
+ ]
163
+ end
164
+ end
165
+
166
+ def execute_workflow_phase
167
+ return if @current_phase >= @phases.length
168
+
169
+ phase = @phases[@current_phase]
170
+
171
+ # Notify agents about the new phase
172
+ @agents.each do |agent|
173
+ @manager.send_message("collaboration_system", agent.name, {
174
+ type: :collaboration_phase,
175
+ collaboration_id: @id,
176
+ phase: phase,
177
+ phase_number: @current_phase + 1,
178
+ total_phases: @phases.length
179
+ })
180
+ end
181
+
182
+ defined?(Rails) && Rails.logger && Rails.logger.info("Collaboration #{@id} started phase #{@current_phase + 1}: #{phase[:name]}")
183
+ end
184
+
185
+ def phase_complete?
186
+ return false if @current_phase >= @phases.length
187
+
188
+ phase = @phases[@current_phase]
189
+ current_contributions = get_phase_contributions(@current_phase)
190
+
191
+ current_contributions.length >= phase[:required_agents]
192
+ end
193
+
194
+ def advance_to_next_phase
195
+ @current_phase += 1
196
+
197
+ if @current_phase >= @phases.length
198
+ # All phases complete, synthesize final result
199
+ synthesize_result
200
+ else
201
+ # Start next phase
202
+ execute_workflow_phase
203
+ end
204
+ end
205
+
206
+ def synthesize_result
207
+ # Combine all contributions into a final result
208
+ all_contributions = @contributions.values.map { |c| c[:content] }
209
+
210
+ synthesis_prompt = build_synthesis_prompt(all_contributions)
211
+
212
+ # Use the first agent to synthesize (or could use a dedicated synthesis agent)
213
+ synthesizer = @agents.first
214
+ result = synthesizer.think(synthesis_prompt, context: {
215
+ task: @task,
216
+ contributions: all_contributions,
217
+ collaboration_id: @id
218
+ })
219
+
220
+ complete!(result)
221
+ end
222
+
223
+ def build_synthesis_prompt(contributions)
224
+ <<~PROMPT
225
+ You are synthesizing the results of a multi-agent collaboration.
226
+
227
+ Original Task: #{@task[:description]}
228
+
229
+ Agent Contributions:
230
+ #{contributions.map.with_index { |c, i| "#{i + 1}. #{c}" }.join("\n")}
231
+
232
+ Please synthesize these contributions into a coherent, comprehensive result that addresses the original task.
233
+ Consider the different perspectives and insights provided by each agent.
234
+ PROMPT
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAi
4
+ module Agents
5
+ class Memory
6
+ IMPORTANCE_LEVELS = {
7
+ critical: 4,
8
+ high: 3,
9
+ normal: 2,
10
+ low: 1
11
+ }.freeze
12
+
13
+ def initialize(max_size: 1000)
14
+ @max_size = max_size
15
+ @memories = []
16
+ @index = {}
17
+ end
18
+
19
+ def add(key, value, importance: :normal)
20
+ memory_item = {
21
+ key: key,
22
+ value: value,
23
+ importance: importance,
24
+ importance_score: IMPORTANCE_LEVELS[importance] || 2,
25
+ created_at: Time.now,
26
+ accessed_at: Time.now,
27
+ access_count: 0
28
+ }
29
+
30
+ # Remove oldest low-importance memory if at capacity
31
+ if @memories.length >= @max_size
32
+ remove_oldest_low_importance
33
+ end
34
+
35
+ @memories << memory_item
36
+ @index[key] = memory_item
37
+ memory_item
38
+ end
39
+
40
+ def get(key)
41
+ memory_item = @index[key]
42
+ return nil unless memory_item
43
+
44
+ memory_item[:accessed_at] = Time.now
45
+ memory_item[:access_count] += 1
46
+ memory_item[:value]
47
+ end
48
+
49
+ def remove(key)
50
+ memory_item = @index.delete(key)
51
+ return nil unless memory_item
52
+
53
+ @memories.delete(memory_item)
54
+ memory_item[:value]
55
+ end
56
+
57
+ def search(query, limit: 10)
58
+ query_lower = query.downcase
59
+
60
+ @memories
61
+ .select do |memory|
62
+ memory[:value].to_s.downcase.include?(query_lower) ||
63
+ memory[:key].to_s.downcase.include?(query_lower)
64
+ end
65
+ .sort_by { |memory| -memory[:importance_score] }
66
+ .first(limit)
67
+ .map { |memory| memory[:value] }
68
+ end
69
+
70
+ def recent(count = 10)
71
+ @memories
72
+ .sort_by { |memory| -memory[:created_at].to_f }
73
+ .first(count)
74
+ .map { |memory| { key: memory[:key], value: memory[:value], created_at: memory[:created_at] } }
75
+ end
76
+
77
+ def important(count = 5)
78
+ @memories
79
+ .select { |memory| memory[:importance_score] >= 3 }
80
+ .sort_by { |memory| -memory[:importance_score] }
81
+ .first(count)
82
+ .map { |memory| { key: memory[:key], value: memory[:value], importance: memory[:importance] } }
83
+ end
84
+
85
+ def usage_percentage
86
+ (@memories.length.to_f / @max_size * 100).round(2)
87
+ end
88
+
89
+ def clear!
90
+ @memories.clear
91
+ @index.clear
92
+ end
93
+
94
+ def size
95
+ @memories.length
96
+ end
97
+
98
+ def empty?
99
+ @memories.empty?
100
+ end
101
+
102
+ private
103
+
104
+ def remove_oldest_low_importance
105
+ oldest_low_importance = @memories
106
+ .select { |memory| memory[:importance_score] <= 2 }
107
+ .min_by { |memory| memory[:created_at] }
108
+
109
+ if oldest_low_importance
110
+ @memories.delete(oldest_low_importance)
111
+ @index.delete(oldest_low_importance[:key])
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAi
4
+ module Agents
5
+ class MessageBus
6
+ def initialize
7
+ @subscribers = {}
8
+ @message_history = []
9
+ @max_history = 10000
10
+ end
11
+
12
+ def subscribe(agent_name, agent)
13
+ @subscribers[agent_name] = agent
14
+ defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{agent_name} subscribed to message bus")
15
+ end
16
+
17
+ def unsubscribe(agent_name)
18
+ @subscribers.delete(agent_name)
19
+ defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{agent_name} unsubscribed from message bus")
20
+ end
21
+
22
+ def send_message(from_agent, to_agent, message)
23
+ return false unless @subscribers[to_agent]
24
+
25
+ message_obj = {
26
+ id: SecureRandom.uuid,
27
+ from: from_agent,
28
+ to: to_agent,
29
+ content: message,
30
+ timestamp: Time.now,
31
+ delivered: false
32
+ }
33
+
34
+ begin
35
+ @subscribers[to_agent].receive_message(message_obj)
36
+ message_obj[:delivered] = true
37
+ @message_history << message_obj
38
+ trim_history
39
+ defined?(Rails) && Rails.logger && Rails.logger.info("Message sent from #{from_agent} to #{to_agent}")
40
+ true
41
+ rescue => e
42
+ defined?(Rails) && Rails.logger && Rails.logger.error("Failed to deliver message: #{e.message}")
43
+ false
44
+ end
45
+ end
46
+
47
+ def broadcast(from_agent, message, exclude: [])
48
+ delivered_count = 0
49
+
50
+ @subscribers.each do |agent_name, agent|
51
+ next if exclude.include?(agent_name) || agent_name == from_agent
52
+
53
+ if send_message(from_agent, agent_name, message)
54
+ delivered_count += 1
55
+ end
56
+ end
57
+
58
+ defined?(Rails) && Rails.logger && Rails.logger.info("Broadcast message from #{from_agent} to #{delivered_count} agents")
59
+ delivered_count
60
+ end
61
+
62
+ def get_messages_for_agent(agent_name, from_agent: nil, limit: 100)
63
+ messages = @message_history.select { |m| m[:to] == agent_name }
64
+ messages = messages.select { |m| m[:from] == from_agent } if from_agent
65
+ messages.last(limit)
66
+ end
67
+
68
+ def get_message_history(limit: 1000)
69
+ @message_history.last(limit)
70
+ end
71
+
72
+ def clear_history!
73
+ @message_history.clear
74
+ defined?(Rails) && Rails.logger && Rails.logger.info("Message history cleared")
75
+ end
76
+
77
+ def stats
78
+ {
79
+ total_subscribers: @subscribers.length,
80
+ total_messages: @message_history.length,
81
+ delivered_messages: @message_history.count { |m| m[:delivered] },
82
+ failed_messages: @message_history.count { |m| !m[:delivered] }
83
+ }
84
+ end
85
+
86
+ private
87
+
88
+ def trim_history
89
+ return if @message_history.length <= @max_history
90
+
91
+ @message_history = @message_history.last(@max_history)
92
+ end
93
+ end
94
+ end
95
+ end