kbs 0.0.1
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/.envrc +3 -0
- data/CHANGELOG.md +5 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +481 -0
- data/Rakefile +8 -0
- data/examples/README.md +531 -0
- data/examples/advanced_example.rb +270 -0
- data/examples/ai_enhanced_kbs.rb +523 -0
- data/examples/blackboard_demo.rb +50 -0
- data/examples/car_diagnostic.rb +64 -0
- data/examples/concurrent_inference_demo.rb +363 -0
- data/examples/csv_trading_system.rb +559 -0
- data/examples/iot_demo_using_dsl.rb +83 -0
- data/examples/portfolio_rebalancing_system.rb +651 -0
- data/examples/redis_trading_demo.rb +177 -0
- data/examples/sample_stock_data.csv +46 -0
- data/examples/stock_trading_advanced.rb +469 -0
- data/examples/stock_trading_system.rb.bak +563 -0
- data/examples/timestamped_trading.rb +286 -0
- data/examples/trading_demo.rb +334 -0
- data/examples/working_demo.rb +176 -0
- data/lib/kbs/alpha_memory.rb +37 -0
- data/lib/kbs/beta_memory.rb +57 -0
- data/lib/kbs/blackboard/audit_log.rb +115 -0
- data/lib/kbs/blackboard/engine.rb +83 -0
- data/lib/kbs/blackboard/fact.rb +65 -0
- data/lib/kbs/blackboard/memory.rb +191 -0
- data/lib/kbs/blackboard/message_queue.rb +96 -0
- data/lib/kbs/blackboard/persistence/hybrid_store.rb +118 -0
- data/lib/kbs/blackboard/persistence/redis_store.rb +218 -0
- data/lib/kbs/blackboard/persistence/sqlite_store.rb +242 -0
- data/lib/kbs/blackboard/persistence/store.rb +55 -0
- data/lib/kbs/blackboard/redis_audit_log.rb +107 -0
- data/lib/kbs/blackboard/redis_message_queue.rb +111 -0
- data/lib/kbs/blackboard.rb +23 -0
- data/lib/kbs/condition.rb +26 -0
- data/lib/kbs/dsl/condition_helpers.rb +57 -0
- data/lib/kbs/dsl/knowledge_base.rb +86 -0
- data/lib/kbs/dsl/pattern_evaluator.rb +69 -0
- data/lib/kbs/dsl/rule_builder.rb +115 -0
- data/lib/kbs/dsl/variable.rb +35 -0
- data/lib/kbs/dsl.rb +18 -0
- data/lib/kbs/fact.rb +43 -0
- data/lib/kbs/join_node.rb +117 -0
- data/lib/kbs/negation_node.rb +88 -0
- data/lib/kbs/production_node.rb +28 -0
- data/lib/kbs/rete_engine.rb +108 -0
- data/lib/kbs/rule.rb +46 -0
- data/lib/kbs/token.rb +37 -0
- data/lib/kbs/version.rb +5 -0
- data/lib/kbs/working_memory.rb +32 -0
- data/lib/kbs.rb +20 -0
- metadata +164 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'ruby_llm'
|
|
4
|
+
require 'ruby_llm/mcp'
|
|
5
|
+
require_relative '../lib/kbs'
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'date'
|
|
8
|
+
|
|
9
|
+
# Configure RubyLLM for Ollama
|
|
10
|
+
RubyLLM.configure do |config|
|
|
11
|
+
config.ollama_api_base = ENV['OLLAMA_API_BASE'] || 'http://localhost:11434'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module AIEnhancedKBS
|
|
15
|
+
class AIKnowledgeSystem
|
|
16
|
+
def initialize
|
|
17
|
+
@engine = KBS::ReteEngine.new
|
|
18
|
+
@ai_client = setup_ai_client
|
|
19
|
+
@mcp_agent = setup_mcp_agent
|
|
20
|
+
@sentiment_cache = {}
|
|
21
|
+
@explanation_cache = {}
|
|
22
|
+
setup_ai_enhanced_rules
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def setup_ai_client
|
|
26
|
+
model = ENV['OLLAMA_MODEL'] || 'gpt-oss:latest'
|
|
27
|
+
puts "🤖 Initializing RubyLLM::Chat with Ollama model: #{model}"
|
|
28
|
+
RubyLLM::Chat.new(provider: :ollama, model: model)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def setup_mcp_agent
|
|
32
|
+
if defined?(RubyLLM::MCP::Agent)
|
|
33
|
+
RubyLLM::MCP::Agent.new(
|
|
34
|
+
name: "market_analyst",
|
|
35
|
+
description: "AI agent for market analysis and trading insights"
|
|
36
|
+
)
|
|
37
|
+
else
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def setup_ai_enhanced_rules
|
|
43
|
+
# Rule 1: AI-Powered Sentiment Analysis
|
|
44
|
+
sentiment_rule = KBS::Rule.new(
|
|
45
|
+
"ai_sentiment_analysis",
|
|
46
|
+
conditions: [
|
|
47
|
+
KBS::Condition.new(:news_data, {
|
|
48
|
+
symbol: ->(s) { s && s.length > 0 },
|
|
49
|
+
headline: ->(h) { h && h.length > 10 },
|
|
50
|
+
content: ->(c) { c && c.length > 50 }
|
|
51
|
+
})
|
|
52
|
+
],
|
|
53
|
+
action: lambda do |facts, bindings|
|
|
54
|
+
news = facts.find { |f| f.type == :news_data }
|
|
55
|
+
symbol = news[:symbol]
|
|
56
|
+
|
|
57
|
+
# AI-powered sentiment analysis
|
|
58
|
+
sentiment = analyze_sentiment_with_ai(news[:headline], news[:content])
|
|
59
|
+
|
|
60
|
+
puts "🤖 AI SENTIMENT ANALYSIS: #{symbol}"
|
|
61
|
+
puts " Headline: #{news[:headline][0..80]}..."
|
|
62
|
+
puts " AI Sentiment: #{sentiment[:sentiment]} (#{sentiment[:confidence]}%)"
|
|
63
|
+
puts " Key Themes: #{sentiment[:themes].join(', ')}"
|
|
64
|
+
puts " Market Impact: #{sentiment[:market_impact]}"
|
|
65
|
+
|
|
66
|
+
# Add sentiment fact to working memory
|
|
67
|
+
@engine.add_fact(:ai_sentiment, {
|
|
68
|
+
symbol: symbol,
|
|
69
|
+
sentiment_score: sentiment[:score],
|
|
70
|
+
confidence: sentiment[:confidence],
|
|
71
|
+
themes: sentiment[:themes],
|
|
72
|
+
market_impact: sentiment[:market_impact],
|
|
73
|
+
timestamp: Time.now
|
|
74
|
+
})
|
|
75
|
+
end,
|
|
76
|
+
priority: 20
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Rule 2: AI-Generated Trading Strategy
|
|
80
|
+
ai_strategy_rule = KBS::Rule.new(
|
|
81
|
+
"ai_strategy_generation",
|
|
82
|
+
conditions: [
|
|
83
|
+
KBS::Condition.new(:market_conditions, {
|
|
84
|
+
volatility: ->(v) { v && v > 25 },
|
|
85
|
+
trend: ->(t) { t && t.length > 0 }
|
|
86
|
+
}),
|
|
87
|
+
KBS::Condition.new(:portfolio_state, {
|
|
88
|
+
cash_ratio: ->(c) { c && c > 0.2 }
|
|
89
|
+
})
|
|
90
|
+
],
|
|
91
|
+
action: lambda do |facts, bindings|
|
|
92
|
+
market = facts.find { |f| f.type == :market_conditions }
|
|
93
|
+
portfolio = facts.find { |f| f.type == :portfolio_state }
|
|
94
|
+
|
|
95
|
+
# Generate AI strategy
|
|
96
|
+
strategy = generate_ai_trading_strategy(market, portfolio)
|
|
97
|
+
|
|
98
|
+
puts "🧠 AI TRADING STRATEGY"
|
|
99
|
+
puts " Market Context: #{market[:trend]} trend, #{market[:volatility]}% volatility"
|
|
100
|
+
puts " Strategy: #{strategy[:name]}"
|
|
101
|
+
puts " Rationale: #{strategy[:rationale]}"
|
|
102
|
+
puts " Actions: #{strategy[:actions].join(', ')}"
|
|
103
|
+
puts " Risk Level: #{strategy[:risk_level]}"
|
|
104
|
+
|
|
105
|
+
# Execute AI-suggested actions (would be implemented in production)
|
|
106
|
+
# execute_ai_strategy(strategy)
|
|
107
|
+
end,
|
|
108
|
+
priority: 15
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Rule 3: Dynamic Rule Generation
|
|
112
|
+
dynamic_rule_creation = KBS::Rule.new(
|
|
113
|
+
"dynamic_rule_creation",
|
|
114
|
+
conditions: [
|
|
115
|
+
KBS::Condition.new(:pattern_anomaly, {
|
|
116
|
+
pattern_type: ->(p) { p && p.length > 0 },
|
|
117
|
+
confidence: ->(c) { c && c > 0.8 },
|
|
118
|
+
occurrences: ->(o) { o && o > 5 }
|
|
119
|
+
})
|
|
120
|
+
],
|
|
121
|
+
action: lambda do |facts, bindings|
|
|
122
|
+
anomaly = facts.find { |f| f.type == :pattern_anomaly }
|
|
123
|
+
|
|
124
|
+
# AI generates new trading rule
|
|
125
|
+
new_rule_spec = generate_rule_with_ai(anomaly)
|
|
126
|
+
|
|
127
|
+
puts "🎯 AI RULE GENERATION"
|
|
128
|
+
puts " Pattern: #{anomaly[:pattern_type]}"
|
|
129
|
+
puts " New Rule: #{new_rule_spec[:name]}"
|
|
130
|
+
puts " Logic: #{new_rule_spec[:description]}"
|
|
131
|
+
|
|
132
|
+
# Dynamically add new rule to engine
|
|
133
|
+
if new_rule_spec[:valid]
|
|
134
|
+
dynamic_rule = create_rule_from_spec(new_rule_spec)
|
|
135
|
+
@engine.add_rule(dynamic_rule)
|
|
136
|
+
puts " ✅ Rule added to knowledge base"
|
|
137
|
+
end
|
|
138
|
+
end,
|
|
139
|
+
priority: 12
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Rule 4: AI Risk Assessment
|
|
143
|
+
ai_risk_assessment = KBS::Rule.new(
|
|
144
|
+
"ai_risk_assessment",
|
|
145
|
+
conditions: [
|
|
146
|
+
KBS::Condition.new(:position, {
|
|
147
|
+
unrealized_pnl: ->(pnl) { pnl && pnl.abs > 1000 }
|
|
148
|
+
}),
|
|
149
|
+
KBS::Condition.new(:market_data, {
|
|
150
|
+
symbol: ->(s) { s && s.length > 0 }
|
|
151
|
+
})
|
|
152
|
+
],
|
|
153
|
+
action: lambda do |facts, bindings|
|
|
154
|
+
position = facts.find { |f| f.type == :position }
|
|
155
|
+
market_data = facts.find { |f| f.type == :market_data }
|
|
156
|
+
|
|
157
|
+
# AI-powered risk analysis
|
|
158
|
+
risk_analysis = analyze_position_risk_with_ai(position, market_data)
|
|
159
|
+
|
|
160
|
+
puts "⚠️ AI RISK ASSESSMENT: #{position[:symbol]}"
|
|
161
|
+
puts " Current P&L: $#{position[:unrealized_pnl]}"
|
|
162
|
+
puts " Risk Level: #{risk_analysis[:risk_level]}"
|
|
163
|
+
puts " Key Risks: #{risk_analysis[:risks].join(', ')}"
|
|
164
|
+
puts " Recommendation: #{risk_analysis[:recommendation]}"
|
|
165
|
+
puts " Confidence: #{risk_analysis[:confidence]}%"
|
|
166
|
+
|
|
167
|
+
# Act on high-risk situations
|
|
168
|
+
if risk_analysis[:risk_level] == "HIGH" && risk_analysis[:confidence] > 80
|
|
169
|
+
puts " 🚨 HIGH RISK DETECTED - Consider position adjustment"
|
|
170
|
+
end
|
|
171
|
+
end,
|
|
172
|
+
priority: 18
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Rule 5: Natural Language Explanation Generator
|
|
176
|
+
explanation_rule = KBS::Rule.new(
|
|
177
|
+
"ai_explanation_generator",
|
|
178
|
+
conditions: [
|
|
179
|
+
KBS::Condition.new(:trade_recommendation, {
|
|
180
|
+
action: ->(a) { ["BUY", "SELL", "HOLD"].include?(a) },
|
|
181
|
+
symbol: ->(s) { s && s.length > 0 }
|
|
182
|
+
})
|
|
183
|
+
],
|
|
184
|
+
action: lambda do |facts, bindings|
|
|
185
|
+
recommendation = facts.find { |f| f.type == :trade_recommendation }
|
|
186
|
+
|
|
187
|
+
# Generate natural language explanation
|
|
188
|
+
explanation = generate_trade_explanation(recommendation, facts)
|
|
189
|
+
|
|
190
|
+
puts "💬 AI EXPLANATION: #{recommendation[:symbol]} #{recommendation[:action]}"
|
|
191
|
+
puts " Reasoning: #{explanation[:reasoning]}"
|
|
192
|
+
puts " Context: #{explanation[:context]}"
|
|
193
|
+
puts " Confidence: #{explanation[:confidence]}%"
|
|
194
|
+
puts " Alternative View: #{explanation[:alternative]}"
|
|
195
|
+
end,
|
|
196
|
+
priority: 5
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Rule 6: AI Pattern Recognition
|
|
200
|
+
pattern_recognition_rule = KBS::Rule.new(
|
|
201
|
+
"ai_pattern_recognition",
|
|
202
|
+
conditions: [
|
|
203
|
+
KBS::Condition.new(:price_history, {
|
|
204
|
+
symbol: ->(s) { s && s.length > 0 },
|
|
205
|
+
data_points: ->(d) { d && d.length >= 30 }
|
|
206
|
+
})
|
|
207
|
+
],
|
|
208
|
+
action: lambda do |facts, bindings|
|
|
209
|
+
price_data = facts.find { |f| f.type == :price_history }
|
|
210
|
+
|
|
211
|
+
# AI identifies patterns
|
|
212
|
+
patterns = identify_patterns_with_ai(price_data[:data_points])
|
|
213
|
+
|
|
214
|
+
if patterns.any?
|
|
215
|
+
puts "📊 AI PATTERN RECOGNITION: #{price_data[:symbol]}"
|
|
216
|
+
patterns.each do |pattern|
|
|
217
|
+
puts " Pattern: #{pattern[:name]} (#{pattern[:confidence]}%)"
|
|
218
|
+
puts " Prediction: #{pattern[:prediction]}"
|
|
219
|
+
puts " Time Horizon: #{pattern[:time_horizon]}"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end,
|
|
223
|
+
priority: 10
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
@engine.add_rule(sentiment_rule)
|
|
227
|
+
@engine.add_rule(ai_strategy_rule)
|
|
228
|
+
@engine.add_rule(dynamic_rule_creation)
|
|
229
|
+
@engine.add_rule(ai_risk_assessment)
|
|
230
|
+
@engine.add_rule(explanation_rule)
|
|
231
|
+
@engine.add_rule(pattern_recognition_rule)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def analyze_sentiment_with_ai(headline, content)
|
|
235
|
+
cache_key = "#{headline[0..50]}_#{content[0..100]}".hash
|
|
236
|
+
return @sentiment_cache[cache_key] if @sentiment_cache[cache_key]
|
|
237
|
+
|
|
238
|
+
prompt = build_sentiment_prompt(headline, content)
|
|
239
|
+
puts "\n🔗 Calling RubyLLM for sentiment analysis..."
|
|
240
|
+
|
|
241
|
+
message = @ai_client.ask(prompt)
|
|
242
|
+
response_text = message.content.to_s
|
|
243
|
+
puts "✅ Got response from Ollama (#{response_text.length} chars)"
|
|
244
|
+
puts "📝 Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
245
|
+
|
|
246
|
+
result = parse_sentiment_response(response_text)
|
|
247
|
+
|
|
248
|
+
@sentiment_cache[cache_key] = result
|
|
249
|
+
result
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def generate_ai_trading_strategy(market_conditions, portfolio_state)
|
|
253
|
+
prompt = build_strategy_prompt(market_conditions, portfolio_state)
|
|
254
|
+
puts "\n🔗 Calling RubyLLM for trading strategy..."
|
|
255
|
+
|
|
256
|
+
message = @ai_client.ask(prompt)
|
|
257
|
+
response_text = message.content.to_s
|
|
258
|
+
puts "✅ Got response from Ollama (#{response_text.length} chars)"
|
|
259
|
+
puts "📝 Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
260
|
+
|
|
261
|
+
parse_strategy_response(response_text)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def generate_rule_with_ai(anomaly_data)
|
|
265
|
+
prompt = build_rule_generation_prompt(anomaly_data)
|
|
266
|
+
puts "\n🔗 Calling RubyLLM for rule generation..."
|
|
267
|
+
|
|
268
|
+
message = @ai_client.ask(prompt)
|
|
269
|
+
response_text = message.content.to_s
|
|
270
|
+
puts "✅ Got response from Ollama (#{response_text.length} chars)"
|
|
271
|
+
puts "📝 Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
272
|
+
|
|
273
|
+
parse_rule_specification(response_text)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def analyze_position_risk_with_ai(position, market_data)
|
|
277
|
+
prompt = build_risk_analysis_prompt(position, market_data)
|
|
278
|
+
puts "\n🔗 Calling RubyLLM for risk analysis..."
|
|
279
|
+
|
|
280
|
+
message = @ai_client.ask(prompt)
|
|
281
|
+
response_text = message.content.to_s
|
|
282
|
+
puts "✅ Got response from Ollama (#{response_text.length} chars)"
|
|
283
|
+
puts "📝 Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
284
|
+
|
|
285
|
+
parse_risk_analysis(response_text)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def generate_trade_explanation(recommendation, context_facts)
|
|
289
|
+
cache_key = "#{recommendation[:symbol]}_#{recommendation[:action]}_#{context_facts.length}".hash
|
|
290
|
+
return @explanation_cache[cache_key] if @explanation_cache[cache_key]
|
|
291
|
+
|
|
292
|
+
prompt = build_explanation_prompt(recommendation, context_facts)
|
|
293
|
+
puts "\n🔗 Calling RubyLLM for trade explanation..."
|
|
294
|
+
|
|
295
|
+
message = @ai_client.ask(prompt)
|
|
296
|
+
response_text = message.content.to_s
|
|
297
|
+
puts "✅ Got response from Ollama (#{response_text.length} chars)"
|
|
298
|
+
puts "📝 Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
299
|
+
|
|
300
|
+
result = parse_explanation_response(response_text)
|
|
301
|
+
|
|
302
|
+
@explanation_cache[cache_key] = result
|
|
303
|
+
result
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def identify_patterns_with_ai(price_data)
|
|
307
|
+
prompt = build_pattern_recognition_prompt(price_data)
|
|
308
|
+
puts "\n🔗 Calling RubyLLM for pattern recognition..."
|
|
309
|
+
|
|
310
|
+
message = @ai_client.ask(prompt)
|
|
311
|
+
response_text = message.content.to_s
|
|
312
|
+
puts "✅ Got response from Ollama (#{response_text.length} chars)"
|
|
313
|
+
puts "📝 Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
314
|
+
|
|
315
|
+
parse_pattern_response(response_text)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Prompt builders
|
|
319
|
+
def build_sentiment_prompt(headline, content)
|
|
320
|
+
<<~PROMPT
|
|
321
|
+
Analyze the sentiment of this financial news for trading implications:
|
|
322
|
+
|
|
323
|
+
Headline: #{headline}
|
|
324
|
+
Content: #{content[0..500]}...
|
|
325
|
+
|
|
326
|
+
Provide a JSON response with:
|
|
327
|
+
{
|
|
328
|
+
"sentiment": "positive|negative|neutral",
|
|
329
|
+
"score": -1.0 to 1.0,
|
|
330
|
+
"confidence": 0-100,
|
|
331
|
+
"themes": ["theme1", "theme2"],
|
|
332
|
+
"market_impact": "bullish|bearish|neutral"
|
|
333
|
+
}
|
|
334
|
+
PROMPT
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def build_strategy_prompt(market_conditions, portfolio_state)
|
|
338
|
+
<<~PROMPT
|
|
339
|
+
Generate a trading strategy for these conditions:
|
|
340
|
+
|
|
341
|
+
Market: #{market_conditions[:trend]} trend, #{market_conditions[:volatility]}% volatility
|
|
342
|
+
Portfolio: #{(portfolio_state[:cash_ratio] * 100).round(1)}% cash
|
|
343
|
+
|
|
344
|
+
Provide a JSON strategy with:
|
|
345
|
+
{
|
|
346
|
+
"name": "strategy_name",
|
|
347
|
+
"rationale": "why this strategy fits",
|
|
348
|
+
"actions": ["action1", "action2"],
|
|
349
|
+
"risk_level": "LOW|MEDIUM|HIGH"
|
|
350
|
+
}
|
|
351
|
+
PROMPT
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def build_risk_analysis_prompt(position, market_data)
|
|
355
|
+
<<~PROMPT
|
|
356
|
+
Analyze the risk of this trading position:
|
|
357
|
+
|
|
358
|
+
Position: #{position[:symbol]}, P&L: $#{position[:unrealized_pnl]}
|
|
359
|
+
Market Data: #{market_data.to_json}
|
|
360
|
+
|
|
361
|
+
Provide risk assessment as JSON:
|
|
362
|
+
{
|
|
363
|
+
"risk_level": "LOW|MEDIUM|HIGH",
|
|
364
|
+
"risks": ["risk1", "risk2"],
|
|
365
|
+
"recommendation": "hold|reduce|exit",
|
|
366
|
+
"confidence": 0-100
|
|
367
|
+
}
|
|
368
|
+
PROMPT
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def build_explanation_prompt(recommendation, context_facts)
|
|
372
|
+
<<~PROMPT
|
|
373
|
+
Explain this trading recommendation in simple terms:
|
|
374
|
+
|
|
375
|
+
Recommendation: #{recommendation[:action]} #{recommendation[:symbol]}
|
|
376
|
+
Context: #{context_facts.length} supporting facts
|
|
377
|
+
|
|
378
|
+
Provide explanation as JSON:
|
|
379
|
+
{
|
|
380
|
+
"explanation": "clear explanation",
|
|
381
|
+
"reasoning": "why this makes sense",
|
|
382
|
+
"risks": ["risk1", "risk2"]
|
|
383
|
+
}
|
|
384
|
+
PROMPT
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def build_pattern_recognition_prompt(price_data)
|
|
388
|
+
<<~PROMPT
|
|
389
|
+
Identify trading patterns in this price data:
|
|
390
|
+
|
|
391
|
+
Data: #{price_data.to_json}
|
|
392
|
+
|
|
393
|
+
Return JSON array of patterns:
|
|
394
|
+
[
|
|
395
|
+
{
|
|
396
|
+
"pattern": "pattern_name",
|
|
397
|
+
"confidence": 0-100,
|
|
398
|
+
"description": "what this means"
|
|
399
|
+
}
|
|
400
|
+
]
|
|
401
|
+
PROMPT
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Response parsers
|
|
405
|
+
def extract_json(response)
|
|
406
|
+
# Strip markdown code fences if present
|
|
407
|
+
json_text = response.strip
|
|
408
|
+
json_text = json_text.gsub(/^```json\s*/, '').gsub(/```\s*$/, '').strip
|
|
409
|
+
json_text
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def parse_sentiment_response(response)
|
|
413
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def parse_strategy_response(response)
|
|
417
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def parse_explanation_response(response)
|
|
421
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def parse_pattern_response(response)
|
|
425
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def parse_risk_analysis(response)
|
|
429
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def demonstrate_ai_enhancements
|
|
433
|
+
puts "🤖 AI-ENHANCED KNOWLEDGE-BASED SYSTEM"
|
|
434
|
+
puts "=" * 70
|
|
435
|
+
puts "Integrating #{@ai_client.class.name} and #{@mcp_agent.class.name}"
|
|
436
|
+
puts "=" * 70
|
|
437
|
+
|
|
438
|
+
# Scenario 1: AI Sentiment Analysis
|
|
439
|
+
puts "\n📰 SCENARIO 1: AI-Powered News Sentiment"
|
|
440
|
+
puts "-" * 50
|
|
441
|
+
@engine.working_memory.facts.clear
|
|
442
|
+
|
|
443
|
+
@engine.add_fact(:news_data, {
|
|
444
|
+
symbol: "AAPL",
|
|
445
|
+
headline: "Apple Reports Record Q4 Earnings, Beats Expectations by 15%",
|
|
446
|
+
content: "Apple Inc. announced exceptional fourth quarter results today, with revenue growing 12% year-over-year to $94.9 billion. iPhone sales exceeded analysts' expectations, driven by strong demand for the iPhone 15 Pro models. The company also announced a new $90 billion share buyback program and increased its dividend by 4%. CEO Tim Cook expressed optimism about the AI integration roadmap and services growth trajectory.",
|
|
447
|
+
published_at: Time.now
|
|
448
|
+
})
|
|
449
|
+
@engine.run
|
|
450
|
+
|
|
451
|
+
# Scenario 2: AI Strategy Generation
|
|
452
|
+
puts "\n🧠 SCENARIO 2: AI Trading Strategy Generation"
|
|
453
|
+
puts "-" * 50
|
|
454
|
+
@engine.working_memory.facts.clear
|
|
455
|
+
|
|
456
|
+
@engine.add_fact(:market_conditions, {
|
|
457
|
+
volatility: 28.5,
|
|
458
|
+
trend: "sideways",
|
|
459
|
+
sector_rotation: "technology_to_healthcare"
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
@engine.add_fact(:portfolio_state, {
|
|
463
|
+
cash_ratio: 0.25,
|
|
464
|
+
largest_position: "AAPL",
|
|
465
|
+
sector_concentration: 0.45
|
|
466
|
+
})
|
|
467
|
+
@engine.run
|
|
468
|
+
|
|
469
|
+
# Scenario 3: AI Risk Assessment
|
|
470
|
+
puts "\n⚠️ SCENARIO 3: AI Risk Assessment"
|
|
471
|
+
puts "-" * 50
|
|
472
|
+
@engine.working_memory.facts.clear
|
|
473
|
+
|
|
474
|
+
@engine.add_fact(:position, {
|
|
475
|
+
symbol: "TSLA",
|
|
476
|
+
shares: 100,
|
|
477
|
+
entry_price: 250.00,
|
|
478
|
+
current_price: 235.00,
|
|
479
|
+
unrealized_pnl: -1500
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
@engine.add_fact(:market_data, {
|
|
483
|
+
symbol: "TSLA",
|
|
484
|
+
volatility: 45.2,
|
|
485
|
+
beta: 2.1,
|
|
486
|
+
sector: "Consumer Discretionary"
|
|
487
|
+
})
|
|
488
|
+
@engine.run
|
|
489
|
+
|
|
490
|
+
# Scenario 4: Trade Explanation
|
|
491
|
+
puts "\n💬 SCENARIO 4: AI Trade Explanation"
|
|
492
|
+
puts "-" * 50
|
|
493
|
+
@engine.working_memory.facts.clear
|
|
494
|
+
|
|
495
|
+
@engine.add_fact(:trade_recommendation, {
|
|
496
|
+
symbol: "GOOGL",
|
|
497
|
+
action: "BUY",
|
|
498
|
+
quantity: 50,
|
|
499
|
+
confidence: 85
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
@engine.add_fact(:technical_analysis, {
|
|
503
|
+
symbol: "GOOGL",
|
|
504
|
+
rsi: 35,
|
|
505
|
+
moving_average_signal: "golden_cross",
|
|
506
|
+
volume_trend: "increasing"
|
|
507
|
+
})
|
|
508
|
+
@engine.run
|
|
509
|
+
|
|
510
|
+
puts "\n" + "=" * 70
|
|
511
|
+
puts "AI ENHANCEMENT DEMONSTRATION COMPLETE"
|
|
512
|
+
puts "🎯 The system now combines rule-based logic with AI insights"
|
|
513
|
+
puts "🧠 Dynamic pattern recognition and strategy generation"
|
|
514
|
+
puts "💬 Natural language explanations for all decisions"
|
|
515
|
+
puts "⚡ Real-time sentiment analysis and risk assessment"
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
if __FILE__ == $0
|
|
521
|
+
system = AIEnhancedKBS::AIKnowledgeSystem.new
|
|
522
|
+
system.demonstrate_ai_enhancements
|
|
523
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/kbs/blackboard'
|
|
4
|
+
|
|
5
|
+
puts "Blackboard Memory System Demonstration"
|
|
6
|
+
puts "=" * 70
|
|
7
|
+
|
|
8
|
+
engine = KBS::Blackboard::Engine.new(db_path: 'knowledge_base.db')
|
|
9
|
+
|
|
10
|
+
puts "\nAdding persistent facts..."
|
|
11
|
+
sensor1 = engine.add_fact(:sensor, { location: "room_1", type: "temperature", value: 22 })
|
|
12
|
+
sensor2 = engine.add_fact(:sensor, { location: "room_2", type: "humidity", value: 65 })
|
|
13
|
+
alert = engine.add_fact(:alert, { level: "warning", message: "Check sensors" })
|
|
14
|
+
|
|
15
|
+
puts "Facts added with UUIDs:"
|
|
16
|
+
puts " Sensor 1: #{sensor1.uuid}"
|
|
17
|
+
puts " Sensor 2: #{sensor2.uuid}"
|
|
18
|
+
puts " Alert: #{alert.uuid}"
|
|
19
|
+
|
|
20
|
+
puts "\nPosting messages to blackboard..."
|
|
21
|
+
engine.post_message("TemperatureMonitor", "sensor_data", { reading: 25, timestamp: Time.now }, priority: 5)
|
|
22
|
+
engine.post_message("HumidityMonitor", "sensor_data", { reading: 70, timestamp: Time.now }, priority: 3)
|
|
23
|
+
engine.post_message("SystemController", "commands", { action: "calibrate", target: "all_sensors" }, priority: 10)
|
|
24
|
+
|
|
25
|
+
puts "\nConsuming high-priority message..."
|
|
26
|
+
message = engine.consume_message("commands", "MainController")
|
|
27
|
+
puts " Received: #{message[:content]}" if message
|
|
28
|
+
|
|
29
|
+
puts "\nUpdating sensor value..."
|
|
30
|
+
sensor1[:value] = 28
|
|
31
|
+
|
|
32
|
+
puts "\nDatabase Statistics:"
|
|
33
|
+
stats = engine.stats
|
|
34
|
+
stats.each do |key, value|
|
|
35
|
+
puts " #{key.to_s.gsub('_', ' ').capitalize}: #{value}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
puts "\nFact History (last 5 entries):"
|
|
39
|
+
history = engine.blackboard.get_history(limit: 5)
|
|
40
|
+
history.each do |entry|
|
|
41
|
+
puts " [#{entry[:timestamp].strftime('%H:%M:%S')}] #{entry[:action]}: #{entry[:fact_type]}(#{entry[:attributes]})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
puts "\nQuerying facts by type..."
|
|
45
|
+
sensors = engine.blackboard.get_facts(:sensor)
|
|
46
|
+
puts " Found #{sensors.size} sensor(s)"
|
|
47
|
+
sensors.each { |s| puts " - #{s}" }
|
|
48
|
+
|
|
49
|
+
puts "\n" + "=" * 70
|
|
50
|
+
puts "Blackboard persisted to: knowledge_base.db"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/kbs'
|
|
4
|
+
|
|
5
|
+
engine = KBS::ReteEngine.new
|
|
6
|
+
|
|
7
|
+
puts "Creating a simple expert system for diagnosing car problems..."
|
|
8
|
+
puts "-" * 60
|
|
9
|
+
|
|
10
|
+
rule1 = KBS::Rule.new(
|
|
11
|
+
"dead_battery",
|
|
12
|
+
conditions: [
|
|
13
|
+
KBS::Condition.new(:symptom, { problem: "won't start" }),
|
|
14
|
+
KBS::Condition.new(:symptom, { problem: "no lights" })
|
|
15
|
+
],
|
|
16
|
+
action: lambda do |facts, bindings|
|
|
17
|
+
puts "DIAGNOSIS: Dead battery - The car won't start and has no lights"
|
|
18
|
+
puts "RECOMMENDATION: Jump start the battery or replace it"
|
|
19
|
+
end
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
rule2 = KBS::Rule.new(
|
|
23
|
+
"flat_tire",
|
|
24
|
+
conditions: [
|
|
25
|
+
KBS::Condition.new(:symptom, { problem: "pulling to side" }),
|
|
26
|
+
KBS::Condition.new(:symptom, { problem: "low tire pressure" })
|
|
27
|
+
],
|
|
28
|
+
action: lambda do |facts, bindings|
|
|
29
|
+
puts "DIAGNOSIS: Flat or low tire"
|
|
30
|
+
puts "RECOMMENDATION: Check tire pressure and inflate or replace tire"
|
|
31
|
+
end
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
rule3 = KBS::Rule.new(
|
|
35
|
+
"overheating",
|
|
36
|
+
conditions: [
|
|
37
|
+
KBS::Condition.new(:symptom, { problem: "high temperature" }),
|
|
38
|
+
KBS::Condition.new(:symptom, { problem: "steam from hood" }, negated: false),
|
|
39
|
+
KBS::Condition.new(:symptom, { problem: "coolant leak" }, negated: true)
|
|
40
|
+
],
|
|
41
|
+
action: lambda do |facts, bindings|
|
|
42
|
+
puts "DIAGNOSIS: Engine overheating (no coolant leak detected)"
|
|
43
|
+
puts "RECOMMENDATION: Check radiator and cooling system"
|
|
44
|
+
end
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
engine.add_rule(rule1)
|
|
48
|
+
engine.add_rule(rule2)
|
|
49
|
+
engine.add_rule(rule3)
|
|
50
|
+
|
|
51
|
+
puts "\nAdding symptoms..."
|
|
52
|
+
engine.add_fact(:symptom, { problem: "won't start", severity: "high" })
|
|
53
|
+
engine.add_fact(:symptom, { problem: "no lights", severity: "high" })
|
|
54
|
+
|
|
55
|
+
puts "\nRunning inference engine..."
|
|
56
|
+
engine.run
|
|
57
|
+
|
|
58
|
+
puts "\n" + "-" * 60
|
|
59
|
+
puts "\nAdding more symptoms..."
|
|
60
|
+
engine.add_fact(:symptom, { problem: "high temperature", severity: "critical" })
|
|
61
|
+
engine.add_fact(:symptom, { problem: "steam from hood", severity: "high" })
|
|
62
|
+
|
|
63
|
+
puts "\nRunning inference engine again..."
|
|
64
|
+
engine.run
|