smart_message 0.0.10 → 0.0.12
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/.github/workflows/deploy-github-pages.yml +38 -0
- data/.gitignore +5 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +35 -4
- data/README.md +169 -71
- data/Rakefile +29 -4
- data/docs/assets/images/ddq_architecture.svg +130 -0
- data/docs/assets/images/dlq_architecture.svg +115 -0
- data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
- data/docs/assets/images/enhanced-fluent-api.svg +149 -0
- data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
- data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
- data/docs/assets/images/fluent-api-demo.svg +59 -0
- data/docs/assets/images/performance-comparison.svg +161 -0
- data/docs/assets/images/redis-basic-architecture.svg +53 -0
- data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
- data/docs/assets/images/redis-queue-architecture.svg +101 -0
- data/docs/assets/images/smart_message.jpg +0 -0
- data/docs/assets/images/smart_message_walking.jpg +0 -0
- data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
- data/docs/assets/images/transport-comparison-matrix.svg +171 -0
- data/docs/assets/javascripts/mathjax.js +17 -0
- data/docs/assets/stylesheets/extra.css +51 -0
- data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
- data/docs/{architecture.md → core-concepts/architecture.md} +78 -138
- data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
- data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
- data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
- data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
- data/docs/{examples.md → getting-started/examples.md} +115 -89
- data/docs/{getting-started.md → getting-started/quick-start.md} +47 -18
- data/docs/guides/redis-queue-getting-started.md +697 -0
- data/docs/guides/redis-queue-patterns.md +889 -0
- data/docs/guides/redis-queue-production.md +1091 -0
- data/docs/index.md +64 -0
- data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
- data/docs/{logging.md → reference/logging.md} +1 -1
- data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
- data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
- data/docs/{serializers.md → reference/serializers.md} +3 -5
- data/docs/{transports.md → reference/transports.md} +133 -11
- data/docs/transports/memory-transport.md +374 -0
- data/docs/transports/redis-enhanced-transport.md +524 -0
- data/docs/transports/redis-queue-transport.md +1304 -0
- data/docs/transports/redis-transport-comparison.md +496 -0
- data/docs/transports/redis-transport.md +509 -0
- data/examples/README.md +98 -5
- data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
- data/examples/city_scenario/README.md +515 -0
- data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
- data/examples/city_scenario/citizen.rb +195 -0
- data/examples/city_scenario/city_diagram.svg +125 -0
- data/examples/city_scenario/common/health_monitor.rb +80 -0
- data/examples/city_scenario/common/logger.rb +30 -0
- data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
- data/examples/city_scenario/fire_department.rb +446 -0
- data/examples/city_scenario/fire_emergency_flow.svg +95 -0
- data/examples/city_scenario/health_department.rb +100 -0
- data/examples/city_scenario/health_monitoring_system.svg +130 -0
- data/examples/city_scenario/house.rb +244 -0
- data/examples/city_scenario/local_bank.rb +217 -0
- data/examples/city_scenario/messages/emergency_911_message.rb +81 -0
- data/examples/city_scenario/messages/emergency_resolved_message.rb +43 -0
- data/examples/city_scenario/messages/fire_dispatch_message.rb +43 -0
- data/examples/city_scenario/messages/fire_emergency_message.rb +45 -0
- data/examples/city_scenario/messages/health_check_message.rb +22 -0
- data/examples/city_scenario/messages/health_status_message.rb +35 -0
- data/examples/city_scenario/messages/police_dispatch_message.rb +46 -0
- data/examples/city_scenario/messages/silent_alarm_message.rb +38 -0
- data/examples/city_scenario/police_department.rb +316 -0
- data/examples/city_scenario/redis_monitor.rb +129 -0
- data/examples/city_scenario/redis_stats.rb +743 -0
- data/examples/city_scenario/room_for_improvement.md +240 -0
- data/examples/city_scenario/security_emergency_flow.svg +95 -0
- data/examples/city_scenario/service_internal_architecture.svg +154 -0
- data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
- data/examples/city_scenario/start_demo.sh +236 -0
- data/examples/city_scenario/stop_demo.sh +106 -0
- data/examples/city_scenario/visitor.rb +631 -0
- data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -1
- data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +13 -40
- data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -1
- data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +2 -2
- data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +4 -4
- data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
- data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +2 -2
- data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +17 -14
- data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +4 -4
- data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +8 -8
- data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +6 -6
- data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +2 -2
- data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +6 -6
- data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +19 -8
- data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -1
- data/examples/memory/README.md +163 -0
- data/examples/memory/memory_transport_architecture.svg +90 -0
- data/examples/memory/point_to_point_pattern.svg +94 -0
- data/examples/memory/publish_subscribe_pattern.svg +125 -0
- data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +5 -5
- data/examples/redis/README.md +230 -0
- data/examples/redis/alert_system_flow.svg +127 -0
- data/examples/redis/dashboard_status_flow.svg +107 -0
- data/examples/redis/device_command_flow.svg +113 -0
- data/examples/redis/redis_transport_architecture.svg +115 -0
- data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
- data/examples/redis/smart_home_system_architecture.svg +133 -0
- data/examples/redis_enhanced/README.md +319 -0
- data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_enhanced/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/01_basic_messaging.rb +221 -0
- data/examples/redis_queue/01_comprehensive_examples.rb +508 -0
- data/examples/redis_queue/02_pattern_routing.rb +405 -0
- data/examples/redis_queue/03_fluent_api.rb +422 -0
- data/examples/redis_queue/04_load_balancing.rb +486 -0
- data/examples/redis_queue/05_microservices.rb +735 -0
- data/examples/redis_queue/06_emergency_alerts.rb +777 -0
- data/examples/redis_queue/07_queue_management.rb +587 -0
- data/examples/redis_queue/README.md +366 -0
- data/examples/redis_queue/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_queue/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_queue/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_queue/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/redis_queue_architecture.svg +148 -0
- data/ideas/README.md +41 -0
- data/ideas/agents.md +1001 -0
- data/ideas/database_transport.md +980 -0
- data/ideas/improvement.md +359 -0
- data/ideas/meshage.md +1788 -0
- data/ideas/message_discovery.md +178 -0
- data/ideas/message_schema.md +1381 -0
- data/lib/smart_message/.idea/.gitignore +8 -0
- data/lib/smart_message/.idea/markdown.xml +6 -0
- data/lib/smart_message/.idea/misc.xml +4 -0
- data/lib/smart_message/.idea/modules.xml +8 -0
- data/lib/smart_message/.idea/smart_message.iml +16 -0
- data/lib/smart_message/.idea/vcs.xml +6 -0
- data/lib/smart_message/addressing.rb +15 -0
- data/lib/smart_message/base.rb +0 -2
- data/lib/smart_message/configuration.rb +1 -1
- data/lib/smart_message/logger.rb +15 -4
- data/lib/smart_message/plugins.rb +5 -2
- data/lib/smart_message/serializer.rb +14 -0
- data/lib/smart_message/transport/redis_enhanced_transport.rb +399 -0
- data/lib/smart_message/transport/redis_queue_transport.rb +555 -0
- data/lib/smart_message/transport/registry.rb +1 -0
- data/lib/smart_message/transport.rb +34 -1
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message.rb +5 -52
- data/mkdocs.yml +184 -0
- data/p2p_plan.md +326 -0
- data/p2p_roadmap.md +287 -0
- data/smart_message.gemspec +2 -0
- data/smart_message.svg +51 -0
- metadata +170 -44
- data/docs/README.md +0 -57
- data/examples/dead_letters.jsonl +0 -12
- data/examples/temp.txt +0 -94
- data/examples/tmux_chat/README.md +0 -283
- data/examples/tmux_chat/bot_agent.rb +0 -278
- data/examples/tmux_chat/human_agent.rb +0 -199
- data/examples/tmux_chat/room_monitor.rb +0 -160
- data/examples/tmux_chat/shared_chat_system.rb +0 -328
- data/examples/tmux_chat/start_chat_demo.sh +0 -190
- data/examples/tmux_chat/stop_chat_demo.sh +0 -22
- /data/docs/{properties.md → core-concepts/properties.md} +0 -0
- /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -0,0 +1,364 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# smart_message_ai_agent.rb
|
3
|
+
#
|
4
|
+
# Abstract base class for building AI agents that can dynamically discover,
|
5
|
+
# select, and use SmartMessage classes for communication based on context.
|
6
|
+
#
|
7
|
+
# This pattern enables AI agents to:
|
8
|
+
# 1. Discover available message types through introspection
|
9
|
+
# 2. Use LLM to select appropriate messages for scenarios
|
10
|
+
# 3. Generate valid message property values using AI
|
11
|
+
# 4. Handle validation errors with AI-assisted correction
|
12
|
+
# 5. Publish messages through SmartMessage transport
|
13
|
+
|
14
|
+
require 'ruby_llm'
|
15
|
+
require 'json'
|
16
|
+
require 'securerandom'
|
17
|
+
|
18
|
+
class SmartMessageAIAgent
|
19
|
+
attr_reader :service_name, :llm, :ai_available, :logger
|
20
|
+
|
21
|
+
def initialize(service_name, logger = nil)
|
22
|
+
@service_name = service_name
|
23
|
+
@logger = logger || default_logger
|
24
|
+
@message_cache = {}
|
25
|
+
@retry_attempts = 3
|
26
|
+
|
27
|
+
setup_ai
|
28
|
+
logger.info("#{self.class} initialized as '#{@service_name}' - AI-powered messaging agent ready")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Override this method to configure RubyLLM with your API keys
|
32
|
+
def configure_rubyllm
|
33
|
+
RubyLLM.configure do |config|
|
34
|
+
config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY', nil)
|
35
|
+
config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
|
36
|
+
# Add other provider configurations as needed
|
37
|
+
|
38
|
+
config.log_file = "log/#{@service_name}_llm.log"
|
39
|
+
config.log_level = :info
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Override this to specify which module contains your message classes
|
44
|
+
# Default: Messages
|
45
|
+
def message_module
|
46
|
+
Messages
|
47
|
+
end
|
48
|
+
|
49
|
+
# Override this to filter which message classes are available to the agent
|
50
|
+
# Return true to include the message class, false to exclude
|
51
|
+
def include_message_class?(message_class)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Override this to provide scenario-specific context for message selection
|
56
|
+
def build_selection_context(scenario)
|
57
|
+
"An AI agent needs to send a message for this scenario: #{scenario[:description]}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Override this to provide scenario-specific context for property generation
|
61
|
+
def build_property_context(scenario, message_class)
|
62
|
+
"Generate realistic values for a #{message_class} message based on: #{scenario[:description]}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Override this to provide fallback values for specific message types
|
66
|
+
def generate_fallback_values(message_class, scenario = nil)
|
67
|
+
{
|
68
|
+
'timestamp' => Time.now.iso8601,
|
69
|
+
'from' => @service_name,
|
70
|
+
'details' => "Generated by #{@service_name} agent"
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Core method to send a message based on a scenario
|
75
|
+
def send_message_for_scenario(scenario)
|
76
|
+
logger.info("Processing scenario: #{scenario[:type]}")
|
77
|
+
|
78
|
+
# Step 1: Discover available messages
|
79
|
+
available_messages = discover_message_types
|
80
|
+
|
81
|
+
# Step 2: Select appropriate message type
|
82
|
+
selected_class = select_message_type(available_messages, scenario)
|
83
|
+
return nil unless selected_class
|
84
|
+
|
85
|
+
# Step 3: Generate and publish message with retry logic
|
86
|
+
publish_with_retry(selected_class, scenario)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Discover all available SmartMessage classes
|
90
|
+
def discover_message_types
|
91
|
+
return @message_cache if @message_cache.any?
|
92
|
+
|
93
|
+
logger.info("Discovering message types from #{message_module}")
|
94
|
+
|
95
|
+
message_module.constants.each do |const_name|
|
96
|
+
const = message_module.const_get(const_name)
|
97
|
+
|
98
|
+
if const.is_a?(Class) && const < SmartMessage::Base && include_message_class?(const)
|
99
|
+
description = const.respond_to?(:description) ? const.description : "No description available"
|
100
|
+
@message_cache[const_name.to_s] = {
|
101
|
+
class: const,
|
102
|
+
description: description
|
103
|
+
}
|
104
|
+
logger.debug("Discovered: #{const_name} - #{description[0..100]}")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
logger.info("Discovered #{@message_cache.size} message types")
|
109
|
+
@message_cache
|
110
|
+
end
|
111
|
+
|
112
|
+
# Use AI to select the most appropriate message type
|
113
|
+
def select_message_type(available_messages, scenario)
|
114
|
+
logger.info("Selecting message type for scenario: #{scenario[:type]}")
|
115
|
+
|
116
|
+
if @ai_available
|
117
|
+
selected = ai_select_message(available_messages, scenario)
|
118
|
+
return selected if selected
|
119
|
+
end
|
120
|
+
|
121
|
+
# Fallback selection logic
|
122
|
+
fallback_select_message(available_messages, scenario)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Generate message instance with AI-provided or fallback values
|
126
|
+
def generate_message_instance(message_class, scenario, validation_errors = [])
|
127
|
+
properties = collect_property_metadata(message_class)
|
128
|
+
|
129
|
+
values = if @ai_available
|
130
|
+
ai_generate_properties(message_class, properties, scenario, validation_errors)
|
131
|
+
else
|
132
|
+
generate_fallback_values(message_class, scenario)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Ensure 'from' is set to our service name
|
136
|
+
values['from'] = @service_name
|
137
|
+
|
138
|
+
# Create instance
|
139
|
+
kwargs = values.transform_keys(&:to_sym)
|
140
|
+
message_class.new(**kwargs)
|
141
|
+
rescue => e
|
142
|
+
logger.error("Failed to create message instance: #{e.message}")
|
143
|
+
raise
|
144
|
+
end
|
145
|
+
|
146
|
+
# Publish message with retry on validation errors
|
147
|
+
def publish_with_retry(message_class, scenario)
|
148
|
+
validation_errors = []
|
149
|
+
|
150
|
+
@retry_attempts.times do |attempt|
|
151
|
+
begin
|
152
|
+
message = generate_message_instance(message_class, scenario, validation_errors)
|
153
|
+
message.publish
|
154
|
+
logger.info("Successfully published #{message_class} (attempt #{attempt + 1})")
|
155
|
+
return message
|
156
|
+
rescue => e
|
157
|
+
if validation_error = parse_validation_error(e.message)
|
158
|
+
validation_errors = build_validation_context(validation_error)
|
159
|
+
logger.warn("Validation error on attempt #{attempt + 1}: #{validation_error[:message]}")
|
160
|
+
next if attempt < @retry_attempts - 1
|
161
|
+
end
|
162
|
+
raise
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def setup_ai
|
170
|
+
begin
|
171
|
+
configure_rubyllm
|
172
|
+
@llm = RubyLLM.chat
|
173
|
+
@ai_available = true
|
174
|
+
logger.info("AI model initialized successfully")
|
175
|
+
rescue => e
|
176
|
+
@ai_available = false
|
177
|
+
logger.warn("AI not available: #{e.message}. Using fallback logic.")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def default_logger
|
182
|
+
require 'logger'
|
183
|
+
Logger.new("log/#{@service_name}.log")
|
184
|
+
end
|
185
|
+
|
186
|
+
def ai_select_message(available_messages, scenario)
|
187
|
+
descriptions = available_messages.map { |name, info| "#{name}: #{info[:description]}" }.join("\n\n")
|
188
|
+
|
189
|
+
prompt = <<~PROMPT
|
190
|
+
#{build_selection_context(scenario)}
|
191
|
+
|
192
|
+
Available message types:
|
193
|
+
#{descriptions}
|
194
|
+
|
195
|
+
Which message type is most appropriate? Respond with ONLY the class name.
|
196
|
+
PROMPT
|
197
|
+
|
198
|
+
begin
|
199
|
+
response = @llm.ask(prompt)
|
200
|
+
class_name = response.content.strip
|
201
|
+
|
202
|
+
if available_messages[class_name]
|
203
|
+
logger.info("AI selected: #{class_name}")
|
204
|
+
return available_messages[class_name][:class]
|
205
|
+
end
|
206
|
+
rescue => e
|
207
|
+
logger.error("AI selection failed: #{e.message}")
|
208
|
+
end
|
209
|
+
|
210
|
+
nil
|
211
|
+
end
|
212
|
+
|
213
|
+
def fallback_select_message(available_messages, scenario)
|
214
|
+
# Simple keyword matching fallback
|
215
|
+
type_keyword = scenario[:type].to_s.downcase
|
216
|
+
|
217
|
+
# Try exact match
|
218
|
+
match = available_messages.find { |name, _| name.downcase.include?(type_keyword) }
|
219
|
+
return match[1][:class] if match
|
220
|
+
|
221
|
+
# Try partial match on description
|
222
|
+
match = available_messages.find { |_, info| info[:description].downcase.include?(type_keyword) }
|
223
|
+
return match[1][:class] if match
|
224
|
+
|
225
|
+
# Return first available
|
226
|
+
first = available_messages.first
|
227
|
+
first[1][:class] if first
|
228
|
+
end
|
229
|
+
|
230
|
+
def collect_property_metadata(message_class)
|
231
|
+
return {} unless message_class.respond_to?(:property_descriptions)
|
232
|
+
|
233
|
+
properties = {}
|
234
|
+
message_class.property_descriptions.each do |prop, desc|
|
235
|
+
enhanced_desc = desc.to_s
|
236
|
+
|
237
|
+
# Add validation constraints if available
|
238
|
+
if message_class.respond_to?(:property_validations)
|
239
|
+
validation = message_class.property_validations[prop]
|
240
|
+
if validation && validation[:validation_message]
|
241
|
+
enhanced_desc += " (#{validation[:validation_message]})"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Check for valid value constants
|
246
|
+
const_name = "VALID_#{prop.to_s.upcase}S"
|
247
|
+
if message_class.const_defined?(const_name)
|
248
|
+
valid_values = message_class.const_get(const_name)
|
249
|
+
enhanced_desc += " Valid values: #{valid_values.join(', ')}"
|
250
|
+
end
|
251
|
+
|
252
|
+
properties[prop] = enhanced_desc
|
253
|
+
end
|
254
|
+
|
255
|
+
properties
|
256
|
+
end
|
257
|
+
|
258
|
+
def ai_generate_properties(message_class, properties, scenario, validation_errors)
|
259
|
+
properties_text = properties.map { |prop, desc| "#{prop}: #{desc}" }.join("\n")
|
260
|
+
|
261
|
+
error_context = if validation_errors.any?
|
262
|
+
"\n\nPREVIOUS VALIDATION ERRORS:\n" + validation_errors.join("\n") +
|
263
|
+
"\n\nFix these specific properties with valid values."
|
264
|
+
else
|
265
|
+
""
|
266
|
+
end
|
267
|
+
|
268
|
+
prompt = <<~PROMPT
|
269
|
+
#{build_property_context(scenario, message_class)}
|
270
|
+
|
271
|
+
Properties to fill:
|
272
|
+
#{properties_text}
|
273
|
+
|
274
|
+
The 'from' field should be '#{@service_name}'.
|
275
|
+
#{error_context}
|
276
|
+
|
277
|
+
Respond with a JSON object containing the property values.
|
278
|
+
PROMPT
|
279
|
+
|
280
|
+
begin
|
281
|
+
response = @llm.ask(prompt)
|
282
|
+
JSON.parse(response.content)
|
283
|
+
rescue => e
|
284
|
+
logger.error("AI property generation failed: #{e.message}")
|
285
|
+
generate_fallback_values(message_class, scenario)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def parse_validation_error(error_message)
|
290
|
+
# Parse validation errors in various formats
|
291
|
+
if error_message =~ /Messages::(\w+)#(\w+):\s*(.+)/
|
292
|
+
return {
|
293
|
+
class_name: $1,
|
294
|
+
property: $2,
|
295
|
+
message: $3,
|
296
|
+
valid_values: extract_valid_values($3)
|
297
|
+
}
|
298
|
+
elsif error_message =~ /property\s+'(\w+)'\s+is\s+required/i
|
299
|
+
return {
|
300
|
+
property: $1,
|
301
|
+
message: error_message
|
302
|
+
}
|
303
|
+
elsif error_message.include?("ValidationError")
|
304
|
+
return {
|
305
|
+
property: "unknown",
|
306
|
+
message: error_message
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
nil
|
311
|
+
end
|
312
|
+
|
313
|
+
def extract_valid_values(message)
|
314
|
+
if message =~ /must be(?:\s+one\s+of)?:\s*(.+)/i
|
315
|
+
$1.strip.split(/,\s*/).map(&:strip)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def build_validation_context(validation_error)
|
320
|
+
context = ["Property '#{validation_error[:property]}' has invalid value."]
|
321
|
+
context << "Error: #{validation_error[:message]}"
|
322
|
+
context << "Valid values: #{validation_error[:valid_values].join(', ')}" if validation_error[:valid_values]
|
323
|
+
context
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Example concrete implementation
|
328
|
+
class ExampleAIAgent < SmartMessageAIAgent
|
329
|
+
def initialize
|
330
|
+
super('example_agent')
|
331
|
+
end
|
332
|
+
|
333
|
+
# Customize which messages this agent can use
|
334
|
+
def include_message_class?(message_class)
|
335
|
+
# Only include emergency-related messages
|
336
|
+
message_class.to_s.include?('Emergency') || message_class.to_s.include?('Alert')
|
337
|
+
end
|
338
|
+
|
339
|
+
# Provide domain-specific context
|
340
|
+
def build_selection_context(scenario)
|
341
|
+
"An emergency response agent observed: #{scenario[:description]}. Select the appropriate emergency message type."
|
342
|
+
end
|
343
|
+
|
344
|
+
def build_property_context(scenario, message_class)
|
345
|
+
"Generate emergency report values for #{message_class}. Incident: #{scenario[:description]}. Use high severity for serious incidents."
|
346
|
+
end
|
347
|
+
|
348
|
+
# Domain-specific fallback values
|
349
|
+
def generate_fallback_values(message_class, scenario = nil)
|
350
|
+
base_values = super
|
351
|
+
|
352
|
+
# Add emergency-specific defaults
|
353
|
+
if message_class.to_s.include?('Emergency')
|
354
|
+
base_values.merge!({
|
355
|
+
'severity' => 'high',
|
356
|
+
'response_required' => true,
|
357
|
+
'location' => 'Unknown Location',
|
358
|
+
'reported_by' => @service_name
|
359
|
+
})
|
360
|
+
end
|
361
|
+
|
362
|
+
base_values
|
363
|
+
end
|
364
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# SmartMessage City Demo Launcher for iTerm2
|
4
|
+
# Creates a new iTerm2 window with separate tabs for each city service
|
5
|
+
|
6
|
+
DEMO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
7
|
+
|
8
|
+
echo "Removing old log files..."
|
9
|
+
rm -f $DEMO_DIR/log/*.log
|
10
|
+
|
11
|
+
# Check if iTerm2 is available
|
12
|
+
if ! ls /Applications/iTerm.app &>/dev/null; then
|
13
|
+
echo "Error: iTerm2 is not installed."
|
14
|
+
echo "Please install iTerm2 from https://iterm2.com/"
|
15
|
+
exit 1
|
16
|
+
fi
|
17
|
+
|
18
|
+
echo "Starting SmartMessage City Demo in iTerm2..."
|
19
|
+
|
20
|
+
# Create the iTerm2 window and tabs using AppleScript
|
21
|
+
osascript <<EOF
|
22
|
+
tell application "iTerm2"
|
23
|
+
activate
|
24
|
+
|
25
|
+
-- Create new window
|
26
|
+
set newWindow to (create window with default profile)
|
27
|
+
|
28
|
+
-- Tab 1: Health Department (already created)
|
29
|
+
tell current session of current tab of newWindow
|
30
|
+
set name to "Health Department"
|
31
|
+
write text "cd '$DEMO_DIR'"
|
32
|
+
write text "clear"
|
33
|
+
write text "echo 'Starting Health Department...'"
|
34
|
+
write text "ruby health_department.rb; exit"
|
35
|
+
end tell
|
36
|
+
|
37
|
+
-- Tab 2: Police Department
|
38
|
+
tell newWindow
|
39
|
+
set newTab to (create tab with default profile)
|
40
|
+
tell current session of newTab
|
41
|
+
set name to "Police Department"
|
42
|
+
write text "cd '$DEMO_DIR'"
|
43
|
+
write text "clear"
|
44
|
+
write text "echo 'Starting Police Department...'"
|
45
|
+
write text "ruby police_department.rb; exit"
|
46
|
+
end tell
|
47
|
+
end tell
|
48
|
+
|
49
|
+
-- Tab 3: Fire Department
|
50
|
+
tell newWindow
|
51
|
+
set newTab to (create tab with default profile)
|
52
|
+
tell current session of newTab
|
53
|
+
set name to "Fire Department"
|
54
|
+
write text "cd '$DEMO_DIR'"
|
55
|
+
write text "clear"
|
56
|
+
write text "echo 'Starting Fire Department...'"
|
57
|
+
write text "ruby fire_department.rb; exit"
|
58
|
+
end tell
|
59
|
+
end tell
|
60
|
+
|
61
|
+
-- Tab 4: Local Bank
|
62
|
+
tell newWindow
|
63
|
+
set newTab to (create tab with default profile)
|
64
|
+
tell current session of newTab
|
65
|
+
set name to "Local Bank"
|
66
|
+
write text "cd '$DEMO_DIR'"
|
67
|
+
write text "clear"
|
68
|
+
write text "echo 'Starting Local Bank...'"
|
69
|
+
write text "ruby local_bank.rb; exit"
|
70
|
+
end tell
|
71
|
+
end tell
|
72
|
+
|
73
|
+
-- Tab 5: House #1
|
74
|
+
tell newWindow
|
75
|
+
set newTab to (create tab with default profile)
|
76
|
+
tell current session of newTab
|
77
|
+
set name to "House #1"
|
78
|
+
write text "cd '$DEMO_DIR'"
|
79
|
+
write text "clear"
|
80
|
+
write text "echo 'Starting House #1...'"
|
81
|
+
write text "ruby house.rb '456 Oak Street'; exit"
|
82
|
+
end tell
|
83
|
+
end tell
|
84
|
+
|
85
|
+
-- Tab 6: House #2
|
86
|
+
tell newWindow
|
87
|
+
set newTab to (create tab with default profile)
|
88
|
+
tell current session of newTab
|
89
|
+
set name to "House #2"
|
90
|
+
write text "cd '$DEMO_DIR'"
|
91
|
+
write text "clear"
|
92
|
+
write text "echo 'Starting House #2...'"
|
93
|
+
write text "ruby house.rb '789 Pine Lane'; exit"
|
94
|
+
end tell
|
95
|
+
end tell
|
96
|
+
|
97
|
+
-- Tab 7: Emergency Dispatch (911)
|
98
|
+
tell newWindow
|
99
|
+
set newTab to (create tab with default profile)
|
100
|
+
tell current session of newTab
|
101
|
+
set name to "911 Dispatch"
|
102
|
+
write text "cd '$DEMO_DIR'"
|
103
|
+
write text "clear"
|
104
|
+
write text "echo 'Starting Emergency Dispatch Center (911)...'"
|
105
|
+
write text "ruby emergency_dispatch_center.rb; exit"
|
106
|
+
end tell
|
107
|
+
end tell
|
108
|
+
|
109
|
+
-- Tab 8: Citizen Caller
|
110
|
+
tell newWindow
|
111
|
+
set newTab to (create tab with default profile)
|
112
|
+
tell current session of newTab
|
113
|
+
set name to "Citizen"
|
114
|
+
write text "cd '$DEMO_DIR'"
|
115
|
+
write text "clear"
|
116
|
+
write text "echo 'Starting Citizen 911 Caller...'"
|
117
|
+
write text "sleep 3; ruby citizen.rb auto; exit"
|
118
|
+
end tell
|
119
|
+
end tell
|
120
|
+
|
121
|
+
-- Tab 9: Redis Monitor
|
122
|
+
tell newWindow
|
123
|
+
set newTab to (create tab with default profile)
|
124
|
+
tell current session of newTab
|
125
|
+
set name to "Redis Monitor"
|
126
|
+
write text "cd '$DEMO_DIR'"
|
127
|
+
write text "clear"
|
128
|
+
write text "echo 'Starting Redis Message Monitor...'"
|
129
|
+
write text "ruby redis_monitor.rb; exit"
|
130
|
+
end tell
|
131
|
+
end tell
|
132
|
+
|
133
|
+
-- Tab 10: Redis Statistics
|
134
|
+
tell newWindow
|
135
|
+
set newTab to (create tab with default profile)
|
136
|
+
tell current session of newTab
|
137
|
+
set name to "Redis Statistics"
|
138
|
+
write text "cd '$DEMO_DIR'"
|
139
|
+
write text "clear"
|
140
|
+
write text "echo 'Starting Redis Statistics Dashboard...'"
|
141
|
+
write text "ruby redis_stats.rb; exit"
|
142
|
+
end tell
|
143
|
+
end tell
|
144
|
+
|
145
|
+
-- Tab 11: Control Panel
|
146
|
+
tell newWindow
|
147
|
+
set newTab to (create tab with default profile)
|
148
|
+
tell current session of newTab
|
149
|
+
set name to "Control Panel"
|
150
|
+
write text "cd '$DEMO_DIR'"
|
151
|
+
write text "clear"
|
152
|
+
write text "cat << 'CONTROL_PANEL_EOF'
|
153
|
+
===== SmartMessage City Demo Control Panel =====
|
154
|
+
|
155
|
+
CITY SERVICES:
|
156
|
+
Tab 1: Health Department (broadcasts health checks every 5s)
|
157
|
+
Tab 2: Police Department (responds to alarms & 911 calls)
|
158
|
+
Tab 3: Fire Department (responds to fires & 911 emergencies)
|
159
|
+
Tab 4: Local Bank (occasional silent alarms)
|
160
|
+
Tab 5: House #1 (456 Oak Street)
|
161
|
+
Tab 6: House #2 (789 Pine Lane)
|
162
|
+
Tab 7: 911 Dispatch (emergency call routing center)
|
163
|
+
Tab 8: Citizen (makes 911 calls automatically)
|
164
|
+
Tab 9: Redis Monitor (real-time message traffic)
|
165
|
+
Tab 10: Redis Statistics (performance dashboard)
|
166
|
+
Tab 11: Control Panel (this tab)
|
167
|
+
|
168
|
+
CONTROLS:
|
169
|
+
./stop_demo.sh - Stop all city services
|
170
|
+
Cmd+W - Close current tab
|
171
|
+
Cmd+1,2,3,4,5,6,7,8,9,0 - Switch to tab
|
172
|
+
Ctrl+C - Stop service in current tab
|
173
|
+
|
174
|
+
MESSAGE FLOWS:
|
175
|
+
Health Checks: Health Dept -> All Services
|
176
|
+
Health Status: All Services -> Health Dept (colored output)
|
177
|
+
Bank Alarms: Bank -> Police (silent alarm system)
|
178
|
+
House Fires: Houses -> Fire Dept (emergency response)
|
179
|
+
911 Calls: Citizen -> 911 Dispatch -> Police/Fire
|
180
|
+
Emergencies: All -> Health Dept (incident resolution)
|
181
|
+
|
182
|
+
WHAT TO WATCH:
|
183
|
+
Tab 1: Health status with GREEN/YELLOW/ORANGE/RED colors
|
184
|
+
Tab 2: Police dispatching units to crimes & accidents
|
185
|
+
Tab 3: Fire trucks responding to fires, medical & rescue calls
|
186
|
+
Tab 4: Bank triggering occasional silent alarms
|
187
|
+
Tab 5/6: Houses occasionally catching fire
|
188
|
+
Tab 7: 911 dispatch routing emergency calls
|
189
|
+
Tab 8: Citizens calling 911 with various emergencies
|
190
|
+
Tab 9: Real-time Redis message traffic (color-coded)
|
191
|
+
Tab 10: Redis performance metrics & pub/sub statistics
|
192
|
+
|
193
|
+
STATUS COLORS:
|
194
|
+
🟢 Green: healthy 🟡 Yellow: warning
|
195
|
+
🟠 Orange: critical 🔴 Red: failed
|
196
|
+
|
197
|
+
Ready! Watch the city come alive with emergency services!
|
198
|
+
========================================================
|
199
|
+
CONTROL_PANEL_EOF"
|
200
|
+
write text "# Type 'exit' or use ./stop_demo.sh to close all tabs"
|
201
|
+
write text "bash"
|
202
|
+
end tell
|
203
|
+
end tell
|
204
|
+
|
205
|
+
-- Switch back to first tab
|
206
|
+
tell newWindow
|
207
|
+
select (first tab)
|
208
|
+
end tell
|
209
|
+
|
210
|
+
end tell
|
211
|
+
EOF
|
212
|
+
|
213
|
+
if [ $? -eq 0 ]; then
|
214
|
+
echo ""
|
215
|
+
echo "✅ City Demo started successfully in iTerm2!"
|
216
|
+
echo ""
|
217
|
+
echo "🏥 Tab 1: Health Department - monitors all city services"
|
218
|
+
echo "🚔 Tab 2: Police Department - responds to alarms & 911 calls"
|
219
|
+
echo "🚒 Tab 3: Fire Department - responds to fires & 911 emergencies"
|
220
|
+
echo "🏦 Tab 4: Local Bank - triggers occasional alarms"
|
221
|
+
echo "🏠 Tab 5/6: Houses - occasionally catch fire"
|
222
|
+
echo "📞 Tab 7: 911 Dispatch - emergency call routing center"
|
223
|
+
echo "👤 Tab 8: Citizen - makes 911 calls automatically"
|
224
|
+
echo "🔍 Tab 9: Redis Monitor - real-time message traffic"
|
225
|
+
echo "📊 Tab 10: Redis Statistics - performance dashboard"
|
226
|
+
echo ""
|
227
|
+
echo "📱 Use Cmd+1,2,3,4,5,6,7,8,9,0 to switch between tabs"
|
228
|
+
echo "🛑 Run ./stop_demo.sh to stop all services"
|
229
|
+
echo ""
|
230
|
+
echo "🌟 Watch Tab 1 for colored health status updates!"
|
231
|
+
echo "📞 Watch Tab 7 for 911 dispatch routing & Tab 8 for citizen emergencies!"
|
232
|
+
echo "🔍 Check Tab 9 for real-time message traffic & Tab 10 for Redis stats!"
|
233
|
+
else
|
234
|
+
echo "��� Failed to start demo. Please check that iTerm2 is installed and running."
|
235
|
+
exit 1
|
236
|
+
fi
|
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# SmartMessage City Demo Shutdown Script for iTerm2
|
4
|
+
# Stops all city services and closes the iTerm2 demo window
|
5
|
+
|
6
|
+
echo "Stopping SmartMessage City Demo..."
|
7
|
+
|
8
|
+
# Check if iTerm2 is running (process name is "iTerm" not "iTerm2")
|
9
|
+
if ! pgrep -x "iTerm" > /dev/null; then
|
10
|
+
echo "iTerm2 is not running."
|
11
|
+
# Still continue to check for orphaned processes
|
12
|
+
fi
|
13
|
+
|
14
|
+
# Function to find and close demo window
|
15
|
+
echo "Looking for SmartMessage city demo window in iTerm2..."
|
16
|
+
|
17
|
+
WINDOW_CLOSED=$(osascript <<'EOF'
|
18
|
+
tell application "iTerm2"
|
19
|
+
set windowFound to false
|
20
|
+
|
21
|
+
repeat with theWindow in windows
|
22
|
+
tell theWindow
|
23
|
+
set tabNames to {}
|
24
|
+
repeat with theTab in tabs
|
25
|
+
set end of tabNames to (name of current session of theTab)
|
26
|
+
end repeat
|
27
|
+
|
28
|
+
-- Check if this window has our city demo tabs
|
29
|
+
if "Health Department" is in tabNames or "Police Department" is in tabNames or "Fire Department" is in tabNames or "Local Bank" is in tabNames or "911 Dispatch" is in tabNames then
|
30
|
+
set windowFound to true
|
31
|
+
|
32
|
+
-- Send Ctrl+C to stop programs and exit shells
|
33
|
+
repeat with theTab in tabs
|
34
|
+
tell current session of theTab
|
35
|
+
write text (character id 3) -- Ctrl+C
|
36
|
+
delay 0.5
|
37
|
+
write text "exit" -- Exit the shell to close cleanly
|
38
|
+
end tell
|
39
|
+
end repeat
|
40
|
+
|
41
|
+
delay 2
|
42
|
+
-- Force close the window
|
43
|
+
close theWindow
|
44
|
+
exit repeat
|
45
|
+
end if
|
46
|
+
end tell
|
47
|
+
end repeat
|
48
|
+
|
49
|
+
return windowFound
|
50
|
+
end tell
|
51
|
+
EOF
|
52
|
+
)
|
53
|
+
|
54
|
+
if [ "$WINDOW_CLOSED" = "true" ]; then
|
55
|
+
echo "✅ City demo window found and closed successfully."
|
56
|
+
else
|
57
|
+
echo "⚠️ No city demo window found. Checking for orphaned processes..."
|
58
|
+
fi
|
59
|
+
|
60
|
+
# Clean up any remaining city service processes
|
61
|
+
echo "Checking for remaining city service processes..."
|
62
|
+
|
63
|
+
# Include all demo programs in the cleanup
|
64
|
+
ORPHANS=$(pgrep -f "(health_department|police_department|fire_department|local_bank|house|emergency_dispatch_center|citizen|redis_monitor|redis_stats)\.rb")
|
65
|
+
|
66
|
+
if [ -n "$ORPHANS" ]; then
|
67
|
+
echo "Found orphaned city service processes. Cleaning up..."
|
68
|
+
echo "$ORPHANS" | while read pid; do
|
69
|
+
echo " Stopping process $pid..."
|
70
|
+
kill -TERM "$pid" 2>/dev/null || true
|
71
|
+
done
|
72
|
+
|
73
|
+
# Wait a moment for graceful termination
|
74
|
+
sleep 1
|
75
|
+
|
76
|
+
# Force kill any remaining processes
|
77
|
+
REMAINING=$(pgrep -f "(health_department|police_department|fire_department|local_bank|house|emergency_dispatch_center|citizen|redis_monitor|redis_stats)\.rb")
|
78
|
+
if [ -n "$REMAINING" ]; then
|
79
|
+
echo "Force killing remaining processes..."
|
80
|
+
echo "$REMAINING" | while read pid; do
|
81
|
+
echo " Force killing process $pid..."
|
82
|
+
kill -KILL "$pid" 2>/dev/null || true
|
83
|
+
done
|
84
|
+
fi
|
85
|
+
|
86
|
+
echo "✅ Orphaned processes cleaned up."
|
87
|
+
else
|
88
|
+
echo "✅ No orphaned processes found."
|
89
|
+
fi
|
90
|
+
|
91
|
+
# Clean up Redis channels and reset message counts
|
92
|
+
echo "Cleaning up Redis channels..."
|
93
|
+
redis-cli <<EOF > /dev/null 2>&1
|
94
|
+
# Unsubscribe all clients from SmartMessage channels
|
95
|
+
CLIENT LIST | grep -o 'id=[0-9]*' | cut -d= -f2 | xargs -I {} CLIENT KILL ID {}
|
96
|
+
# Note: We can't actually "clear" channels in Redis, but killing clients removes subscriptions
|
97
|
+
# The publish counts remain in Redis stats but that's historical data
|
98
|
+
EOF
|
99
|
+
|
100
|
+
echo "✅ Redis channels cleaned up."
|
101
|
+
|
102
|
+
echo ""
|
103
|
+
echo "🛑 SmartMessage city demo has been stopped."
|
104
|
+
echo " All emergency services are offline."
|
105
|
+
echo ""
|
106
|
+
echo "💡 To start the city demo again, run: ./start_demo.sh"
|