kbs 0.0.1 ā 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 +4 -4
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +68 -2
- data/README.md +235 -334
- data/docs/DOCUMENTATION_STATUS.md +158 -0
- data/docs/advanced/custom-persistence.md +775 -0
- data/docs/advanced/debugging.md +726 -0
- data/docs/advanced/index.md +8 -0
- data/docs/advanced/performance.md +832 -0
- data/docs/advanced/testing.md +691 -0
- data/docs/api/blackboard.md +1157 -0
- data/docs/api/engine.md +978 -0
- data/docs/api/facts.md +1212 -0
- data/docs/api/index.md +12 -0
- data/docs/api/rules.md +1034 -0
- data/docs/architecture/blackboard.md +553 -0
- data/docs/architecture/index.md +277 -0
- data/docs/architecture/network-structure.md +343 -0
- data/docs/architecture/rete-algorithm.md +737 -0
- data/docs/assets/css/custom.css +83 -0
- data/docs/assets/images/blackboard-architecture.svg +136 -0
- data/docs/assets/images/compiled-network.svg +101 -0
- data/docs/assets/images/fact-assertion-flow.svg +117 -0
- data/docs/assets/images/kbs.jpg +0 -0
- data/docs/assets/images/pattern-matching-trace.svg +136 -0
- data/docs/assets/images/rete-network-layers.svg +96 -0
- data/docs/assets/images/system-layers.svg +69 -0
- data/docs/assets/images/trading-signal-network.svg +139 -0
- data/docs/assets/js/mathjax.js +17 -0
- data/docs/examples/expert-systems.md +1031 -0
- data/docs/examples/index.md +9 -0
- data/docs/examples/multi-agent.md +1335 -0
- data/docs/examples/stock-trading.md +488 -0
- data/docs/guides/blackboard-memory.md +558 -0
- data/docs/guides/dsl.md +1321 -0
- data/docs/guides/facts.md +652 -0
- data/docs/guides/getting-started.md +383 -0
- data/docs/guides/index.md +23 -0
- data/docs/guides/negation.md +529 -0
- data/docs/guides/pattern-matching.md +561 -0
- data/docs/guides/persistence.md +451 -0
- data/docs/guides/variable-binding.md +491 -0
- data/docs/guides/writing-rules.md +755 -0
- data/docs/index.md +157 -0
- data/docs/installation.md +156 -0
- data/docs/quick-start.md +228 -0
- data/examples/README.md +2 -2
- data/examples/advanced_example.rb +2 -2
- data/examples/advanced_example_dsl.rb +224 -0
- data/examples/ai_enhanced_kbs.rb +1 -1
- data/examples/ai_enhanced_kbs_dsl.rb +538 -0
- data/examples/blackboard_demo_dsl.rb +50 -0
- data/examples/car_diagnostic.rb +1 -1
- data/examples/car_diagnostic_dsl.rb +54 -0
- data/examples/concurrent_inference_demo.rb +5 -5
- data/examples/concurrent_inference_demo_dsl.rb +363 -0
- data/examples/csv_trading_system.rb +1 -1
- data/examples/csv_trading_system_dsl.rb +525 -0
- data/examples/knowledge_base.db +0 -0
- data/examples/portfolio_rebalancing_system.rb +2 -2
- data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
- data/examples/redis_trading_demo_dsl.rb +177 -0
- data/examples/run_all.rb +50 -0
- data/examples/run_all_dsl.rb +49 -0
- data/examples/stock_trading_advanced.rb +1 -1
- data/examples/stock_trading_advanced_dsl.rb +404 -0
- data/examples/temp.txt +7693 -0
- data/examples/temp_dsl.txt +8447 -0
- data/examples/timestamped_trading.rb +1 -1
- data/examples/timestamped_trading_dsl.rb +258 -0
- data/examples/trading_demo.rb +1 -1
- data/examples/trading_demo_dsl.rb +322 -0
- data/examples/working_demo.rb +1 -1
- data/examples/working_demo_dsl.rb +160 -0
- data/lib/kbs/blackboard/engine.rb +3 -3
- data/lib/kbs/blackboard/fact.rb +1 -1
- data/lib/kbs/condition.rb +1 -1
- data/lib/kbs/dsl/knowledge_base.rb +1 -1
- data/lib/kbs/dsl/variable.rb +1 -1
- data/lib/kbs/{rete_engine.rb ā engine.rb} +1 -1
- data/lib/kbs/fact.rb +1 -1
- data/lib/kbs/version.rb +1 -1
- data/lib/kbs.rb +2 -2
- data/mkdocs.yml +181 -0
- metadata +66 -6
- data/examples/stock_trading_system.rb.bak +0 -563
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/kbs'
|
|
4
|
+
require_relative '../lib/kbs/dsl'
|
|
5
|
+
require 'thread'
|
|
6
|
+
|
|
7
|
+
# =============================================================================
|
|
8
|
+
# Pattern 1: Auto-Inference Mode (Reactive)
|
|
9
|
+
# Rules fire immediately when facts are added
|
|
10
|
+
# =============================================================================
|
|
11
|
+
|
|
12
|
+
class ReactiveEngine < KBS::Engine
|
|
13
|
+
attr_accessor :auto_inference
|
|
14
|
+
|
|
15
|
+
def initialize(auto_inference: true)
|
|
16
|
+
super()
|
|
17
|
+
@auto_inference = auto_inference
|
|
18
|
+
@inference_mutex = Mutex.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_fact(type, attributes = {})
|
|
22
|
+
fact = super(type, attributes)
|
|
23
|
+
|
|
24
|
+
# Automatically run inference if enabled
|
|
25
|
+
if @auto_inference
|
|
26
|
+
@inference_mutex.synchronize do
|
|
27
|
+
run
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
fact
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# Pattern 2: Background Thread Mode
|
|
37
|
+
# Inference runs continuously in a background thread
|
|
38
|
+
# =============================================================================
|
|
39
|
+
|
|
40
|
+
class BackgroundInferenceEngine < KBS::Engine
|
|
41
|
+
def initialize
|
|
42
|
+
super()
|
|
43
|
+
@running = false
|
|
44
|
+
@inference_thread = nil
|
|
45
|
+
@fact_queue = Queue.new
|
|
46
|
+
@mutex = Mutex.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def start_background_inference(interval: 0.1)
|
|
50
|
+
return if @running
|
|
51
|
+
|
|
52
|
+
@running = true
|
|
53
|
+
@inference_thread = Thread.new do
|
|
54
|
+
while @running
|
|
55
|
+
begin
|
|
56
|
+
# Process any queued facts
|
|
57
|
+
until @fact_queue.empty?
|
|
58
|
+
fact_data = @fact_queue.pop(true) rescue nil
|
|
59
|
+
if fact_data
|
|
60
|
+
@mutex.synchronize do
|
|
61
|
+
super_add_fact(fact_data[:type], fact_data[:attributes])
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Run inference
|
|
67
|
+
@mutex.synchronize do
|
|
68
|
+
run
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
sleep interval
|
|
72
|
+
rescue => e
|
|
73
|
+
puts "Background inference error: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
puts "ā Background inference started (interval: #{interval}s)"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def stop_background_inference
|
|
82
|
+
@running = false
|
|
83
|
+
@inference_thread&.join
|
|
84
|
+
puts "ā Background inference stopped"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def add_fact(type, attributes = {})
|
|
88
|
+
@fact_queue.push({type: type, attributes: attributes})
|
|
89
|
+
puts " ā Fact queued: #{type}(#{attributes.map{|k,v| "#{k}: #{v}"}.join(', ')})"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def super_add_fact(type, attributes)
|
|
95
|
+
fact = KBS::Fact.new(type, attributes)
|
|
96
|
+
@working_memory.add_fact(fact)
|
|
97
|
+
fact
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# =============================================================================
|
|
102
|
+
# Pattern 3: Event-Driven with Callbacks
|
|
103
|
+
# Execute callbacks immediately when rules fire
|
|
104
|
+
# =============================================================================
|
|
105
|
+
|
|
106
|
+
class EventDrivenEngine < KBS::Engine
|
|
107
|
+
def initialize
|
|
108
|
+
super()
|
|
109
|
+
@rule_callbacks = {}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def on_rule(rule_name, &callback)
|
|
113
|
+
@rule_callbacks[rule_name] = callback
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def add_fact(type, attributes = {})
|
|
117
|
+
fact = super(type, attributes)
|
|
118
|
+
|
|
119
|
+
# Check each production node and fire matching rules
|
|
120
|
+
@production_nodes.each do |rule_name, node|
|
|
121
|
+
node.tokens.each do |token|
|
|
122
|
+
if token.facts.include?(fact) && @rule_callbacks[rule_name]
|
|
123
|
+
Thread.new do
|
|
124
|
+
@rule_callbacks[rule_name].call(token.facts)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
fact
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# =============================================================================
|
|
135
|
+
# DEMO: Pattern 1 - Auto-Inference (Reactive)
|
|
136
|
+
# =============================================================================
|
|
137
|
+
|
|
138
|
+
def demo_reactive_inference
|
|
139
|
+
puts "\n" + "="*80
|
|
140
|
+
puts "PATTERN 1: Auto-Inference Mode (Reactive)"
|
|
141
|
+
puts "="*80
|
|
142
|
+
puts "Facts trigger immediate inference\n\n"
|
|
143
|
+
|
|
144
|
+
engine = ReactiveEngine.new(auto_inference: true)
|
|
145
|
+
|
|
146
|
+
# Define rules using raw API (DSL doesn't work well with custom engine subclass)
|
|
147
|
+
rule = KBS::Rule.new(
|
|
148
|
+
"temperature_alert",
|
|
149
|
+
conditions: [
|
|
150
|
+
KBS::Condition.new(:sensor, {type: "temperature"}),
|
|
151
|
+
KBS::Condition.new(:reading, {value: ->(v) { v > 100 }})
|
|
152
|
+
],
|
|
153
|
+
action: lambda do |facts, bindings|
|
|
154
|
+
reading = facts.find { |f| f.type == :reading }
|
|
155
|
+
sensor = facts.find { |f| f.type == :sensor }
|
|
156
|
+
puts " š„ RULE FIRED: Temperature Alert!"
|
|
157
|
+
puts " Location: #{sensor[:location]}"
|
|
158
|
+
puts " Temperature: #{reading[:value]}°C exceeds threshold!"
|
|
159
|
+
end
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
engine.add_rule(rule)
|
|
163
|
+
|
|
164
|
+
puts "Adding sensor..."
|
|
165
|
+
engine.add_fact(:sensor, type: "temperature", location: "reactor")
|
|
166
|
+
puts " ā Sensor added (1/2 conditions met)"
|
|
167
|
+
|
|
168
|
+
puts "\nAdding high temperature reading (should fire rule immediately)..."
|
|
169
|
+
engine.add_fact(:reading, value: 105, unit: "celsius")
|
|
170
|
+
puts " ā Reading added (2/2 conditions met)"
|
|
171
|
+
|
|
172
|
+
puts "\nAdding another high reading (should fire again)..."
|
|
173
|
+
engine.add_fact(:reading, value: 110, unit: "celsius")
|
|
174
|
+
|
|
175
|
+
puts "\nAdding normal temperature reading (should not fire)..."
|
|
176
|
+
engine.add_fact(:reading, value: 75, unit: "celsius")
|
|
177
|
+
puts " ā Reading added but rule not fired (condition not met)"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# =============================================================================
|
|
181
|
+
# DEMO: Pattern 2 - Background Thread
|
|
182
|
+
# =============================================================================
|
|
183
|
+
|
|
184
|
+
def demo_background_inference
|
|
185
|
+
puts "\n" + "="*80
|
|
186
|
+
puts "PATTERN 2: Background Thread Mode"
|
|
187
|
+
puts "="*80
|
|
188
|
+
puts "Inference runs continuously in background\n\n"
|
|
189
|
+
|
|
190
|
+
engine = BackgroundInferenceEngine.new
|
|
191
|
+
|
|
192
|
+
# Define rules using raw API
|
|
193
|
+
rule = KBS::Rule.new(
|
|
194
|
+
"stock_momentum",
|
|
195
|
+
conditions: [
|
|
196
|
+
KBS::Condition.new(:stock, {symbol: "AAPL"}),
|
|
197
|
+
KBS::Condition.new(:price, {change: ->(v) { v > 5 }})
|
|
198
|
+
],
|
|
199
|
+
action: lambda do |facts, bindings|
|
|
200
|
+
price = facts.find { |f| f.type == :price }
|
|
201
|
+
stock = facts.find { |f| f.type == :stock }
|
|
202
|
+
puts " š RULE FIRED: Stock Momentum Alert!"
|
|
203
|
+
puts " Stock: #{stock[:symbol]}"
|
|
204
|
+
puts " Change: +#{price[:change]}%"
|
|
205
|
+
end
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
engine.add_rule(rule)
|
|
209
|
+
|
|
210
|
+
# Start background processing
|
|
211
|
+
engine.start_background_inference(interval: 0.5)
|
|
212
|
+
|
|
213
|
+
puts "\nStreaming facts (processed by background thread)...\n"
|
|
214
|
+
|
|
215
|
+
engine.add_fact(:stock, symbol: "AAPL", sector: "Technology")
|
|
216
|
+
sleep 0.7 # Let background thread process
|
|
217
|
+
|
|
218
|
+
engine.add_fact(:price, symbol: "AAPL", change: 6.5, timestamp: Time.now)
|
|
219
|
+
sleep 0.7 # Let background thread process
|
|
220
|
+
|
|
221
|
+
engine.add_fact(:price, symbol: "AAPL", change: 2.1, timestamp: Time.now)
|
|
222
|
+
sleep 0.7 # Let background thread process
|
|
223
|
+
|
|
224
|
+
engine.stop_background_inference
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# =============================================================================
|
|
228
|
+
# DEMO: Pattern 3 - Event-Driven with Callbacks
|
|
229
|
+
# =============================================================================
|
|
230
|
+
|
|
231
|
+
def demo_event_driven
|
|
232
|
+
puts "\n" + "="*80
|
|
233
|
+
puts "PATTERN 3: Event-Driven with Callbacks"
|
|
234
|
+
puts "="*80
|
|
235
|
+
puts "Rules have individual callback handlers\n\n"
|
|
236
|
+
|
|
237
|
+
engine = EventDrivenEngine.new
|
|
238
|
+
|
|
239
|
+
# Define rule using raw API
|
|
240
|
+
rule = KBS::Rule.new(
|
|
241
|
+
"order_fulfillment",
|
|
242
|
+
conditions: [
|
|
243
|
+
KBS::Condition.new(:order, {status: "pending"}),
|
|
244
|
+
KBS::Condition.new(:inventory, {available: ->(v) { v > 0 }})
|
|
245
|
+
],
|
|
246
|
+
action: lambda do |facts, bindings|
|
|
247
|
+
order = facts.find { |f| f.type == :order }
|
|
248
|
+
inventory = facts.find { |f| f.type == :inventory }
|
|
249
|
+
puts " š¦ RULE FIRED: Order Fulfillment!"
|
|
250
|
+
puts " Order ID: #{order[:id]}"
|
|
251
|
+
puts " Item: #{order[:item]}"
|
|
252
|
+
puts " Available: #{inventory[:available]}"
|
|
253
|
+
end
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
engine.add_rule(rule)
|
|
257
|
+
|
|
258
|
+
# Register callback
|
|
259
|
+
engine.on_rule("order_fulfillment") do |facts|
|
|
260
|
+
order = facts.find { |f| f.type == :order }
|
|
261
|
+
puts " ā Async callback executed for order #{order[:id]}"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
puts "Adding facts with event callbacks...\n"
|
|
265
|
+
|
|
266
|
+
engine.add_fact(:order, id: "ORD-001", status: "pending", item: "Widget")
|
|
267
|
+
puts " ā Order added (1/2 conditions met)"
|
|
268
|
+
|
|
269
|
+
engine.add_fact(:inventory, item: "Widget", available: 50)
|
|
270
|
+
puts " ā Inventory added (2/2 conditions met)"
|
|
271
|
+
|
|
272
|
+
# Trigger the event-driven execution
|
|
273
|
+
engine.run
|
|
274
|
+
|
|
275
|
+
sleep 0.5 # Let async callbacks complete
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# =============================================================================
|
|
279
|
+
# DEMO: Pattern 4 - Queue-Based Processing
|
|
280
|
+
# =============================================================================
|
|
281
|
+
|
|
282
|
+
def demo_queue_based
|
|
283
|
+
puts "\n" + "="*80
|
|
284
|
+
puts "PATTERN 4: Queue-Based Batch Processing"
|
|
285
|
+
puts "="*80
|
|
286
|
+
puts "Facts accumulate and are processed in batches\n\n"
|
|
287
|
+
|
|
288
|
+
engine = KBS::Engine.new
|
|
289
|
+
fact_queue = Queue.new
|
|
290
|
+
|
|
291
|
+
# Define rule using raw API
|
|
292
|
+
rule = KBS::Rule.new(
|
|
293
|
+
"batch_processor",
|
|
294
|
+
conditions: [
|
|
295
|
+
KBS::Condition.new(:transaction, {})
|
|
296
|
+
],
|
|
297
|
+
action: lambda do |facts, bindings|
|
|
298
|
+
tx = facts.find { |f| f.type == :transaction }
|
|
299
|
+
puts " š³ RULE FIRED: Transaction Processed!"
|
|
300
|
+
puts " ID: #{tx[:id]}"
|
|
301
|
+
puts " Amount: $#{'%.2f' % tx[:amount]}"
|
|
302
|
+
end
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
engine.add_rule(rule)
|
|
306
|
+
|
|
307
|
+
# Worker thread processes queue
|
|
308
|
+
worker = Thread.new do
|
|
309
|
+
while true
|
|
310
|
+
batch = []
|
|
311
|
+
5.times { batch << fact_queue.pop(true) rescue nil }
|
|
312
|
+
batch.compact!
|
|
313
|
+
|
|
314
|
+
break if batch.empty?
|
|
315
|
+
|
|
316
|
+
puts "\nā Processing batch of #{batch.size} facts..."
|
|
317
|
+
batch.each do |fact_data|
|
|
318
|
+
engine.add_fact(fact_data[:type], fact_data[:attributes])
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
puts "ā Running inference on batch..."
|
|
322
|
+
engine.run
|
|
323
|
+
|
|
324
|
+
sleep 0.5
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Producer adds facts to queue
|
|
329
|
+
puts "Queuing transactions...\n"
|
|
330
|
+
|
|
331
|
+
fact_queue << {type: :transaction, attributes: {id: "TX-001", amount: 100.00}}
|
|
332
|
+
fact_queue << {type: :transaction, attributes: {id: "TX-002", amount: 250.00}}
|
|
333
|
+
fact_queue << {type: :transaction, attributes: {id: "TX-003", amount: 75.50}}
|
|
334
|
+
fact_queue << {type: :transaction, attributes: {id: "TX-004", amount: 500.00}}
|
|
335
|
+
fact_queue << {type: :transaction, attributes: {id: "TX-005", amount: 125.75}}
|
|
336
|
+
|
|
337
|
+
puts " ā 5 transactions queued"
|
|
338
|
+
|
|
339
|
+
worker.join(3)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# =============================================================================
|
|
343
|
+
# Run all demos
|
|
344
|
+
# =============================================================================
|
|
345
|
+
|
|
346
|
+
if __FILE__ == $0
|
|
347
|
+
puts "\nš CONCURRENT INFERENCE PATTERNS FOR RETE\n"
|
|
348
|
+
|
|
349
|
+
demo_reactive_inference
|
|
350
|
+
sleep 1
|
|
351
|
+
|
|
352
|
+
demo_background_inference
|
|
353
|
+
sleep 1
|
|
354
|
+
|
|
355
|
+
demo_event_driven
|
|
356
|
+
sleep 1
|
|
357
|
+
|
|
358
|
+
demo_queue_based
|
|
359
|
+
|
|
360
|
+
puts "\n" + "="*80
|
|
361
|
+
puts "All demos completed!"
|
|
362
|
+
puts "="*80
|
|
363
|
+
end
|