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,538 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'ruby_llm'
|
|
4
|
+
require 'ruby_llm/mcp'
|
|
5
|
+
require_relative '../lib/kbs/dsl'
|
|
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
|
+
include KBS::DSL::ConditionHelpers
|
|
17
|
+
|
|
18
|
+
attr_reader :kb
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@kb = nil
|
|
22
|
+
@ai_client = setup_ai_client
|
|
23
|
+
@mcp_agent = setup_mcp_agent
|
|
24
|
+
@sentiment_cache = {}
|
|
25
|
+
@explanation_cache = {}
|
|
26
|
+
setup_ai_enhanced_rules
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def setup_ai_client
|
|
30
|
+
model = ENV['OLLAMA_MODEL'] || 'gpt-oss:latest'
|
|
31
|
+
puts "š¤ Initializing RubyLLM::Chat with Ollama model: #{model}"
|
|
32
|
+
RubyLLM::Chat.new(provider: :ollama, model: model)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def setup_mcp_agent
|
|
36
|
+
if defined?(RubyLLM::MCP::Agent)
|
|
37
|
+
RubyLLM::MCP::Agent.new(
|
|
38
|
+
name: "market_analyst",
|
|
39
|
+
description: "AI agent for market analysis and trading insights"
|
|
40
|
+
)
|
|
41
|
+
else
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def setup_ai_enhanced_rules
|
|
47
|
+
ai_sys = self # Capture self for use in perform blocks
|
|
48
|
+
@kb = KBS.knowledge_base do
|
|
49
|
+
rule "ai_sentiment_analysis" do
|
|
50
|
+
priority 20
|
|
51
|
+
on :news_data,
|
|
52
|
+
symbol: satisfies { |s| s && s.length > 0 },
|
|
53
|
+
headline: satisfies { |h| h && h.length > 10 },
|
|
54
|
+
content: satisfies { |c| c && c.length > 50 }
|
|
55
|
+
|
|
56
|
+
perform do |facts|
|
|
57
|
+
news = facts.find { |f| f.type == :news_data }
|
|
58
|
+
symbol = news[:symbol]
|
|
59
|
+
|
|
60
|
+
# AI-powered sentiment analysis
|
|
61
|
+
sentiment = ai_sys.analyze_sentiment_with_ai(news[:headline], news[:content])
|
|
62
|
+
|
|
63
|
+
puts "š¤ AI SENTIMENT ANALYSIS: #{symbol}"
|
|
64
|
+
puts " Headline: #{news[:headline][0..80]}..."
|
|
65
|
+
puts " AI Sentiment: #{sentiment[:sentiment]} (#{sentiment[:confidence]}%)"
|
|
66
|
+
puts " Key Themes: #{sentiment[:themes].join(', ')}"
|
|
67
|
+
puts " Market Impact: #{sentiment[:market_impact]}"
|
|
68
|
+
|
|
69
|
+
# Add sentiment fact to working memory
|
|
70
|
+
ai_sys.kb.fact :ai_sentiment, {
|
|
71
|
+
symbol: symbol,
|
|
72
|
+
sentiment_score: sentiment[:score],
|
|
73
|
+
confidence: sentiment[:confidence],
|
|
74
|
+
themes: sentiment[:themes],
|
|
75
|
+
market_impact: sentiment[:market_impact],
|
|
76
|
+
timestamp: Time.now
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
rule "ai_strategy_generation" do
|
|
82
|
+
priority 15
|
|
83
|
+
on :market_conditions,
|
|
84
|
+
volatility: satisfies { |v| v && v > 25 },
|
|
85
|
+
trend: satisfies { |t| t && t.length > 0 }
|
|
86
|
+
on :portfolio_state, cash_ratio: satisfies { |c| c && c > 0.2 }
|
|
87
|
+
|
|
88
|
+
perform do |facts|
|
|
89
|
+
market = facts.find { |f| f.type == :market_conditions }
|
|
90
|
+
portfolio = facts.find { |f| f.type == :portfolio_state }
|
|
91
|
+
|
|
92
|
+
# Generate AI strategy
|
|
93
|
+
strategy = ai_sys.generate_ai_trading_strategy(market, portfolio)
|
|
94
|
+
|
|
95
|
+
puts "š§ AI TRADING STRATEGY"
|
|
96
|
+
puts " Market Context: #{market[:trend]} trend, #{market[:volatility]}% volatility"
|
|
97
|
+
puts " Strategy: #{strategy[:name]}"
|
|
98
|
+
puts " Rationale: #{strategy[:rationale]}"
|
|
99
|
+
puts " Actions: #{strategy[:actions].join(', ')}"
|
|
100
|
+
puts " Risk Level: #{strategy[:risk_level]}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
rule "dynamic_rule_creation" do
|
|
105
|
+
priority 12
|
|
106
|
+
on :pattern_anomaly,
|
|
107
|
+
pattern_type: satisfies { |p| p && p.length > 0 },
|
|
108
|
+
confidence: satisfies { |c| c && c > 0.8 },
|
|
109
|
+
occurrences: satisfies { |o| o && o > 5 }
|
|
110
|
+
|
|
111
|
+
perform do |facts|
|
|
112
|
+
anomaly = facts.find { |f| f.type == :pattern_anomaly }
|
|
113
|
+
|
|
114
|
+
# AI generates new trading rule
|
|
115
|
+
new_rule_spec = generate_rule_with_ai(anomaly)
|
|
116
|
+
|
|
117
|
+
puts "šÆ AI RULE GENERATION"
|
|
118
|
+
puts " Pattern: #{anomaly[:pattern_type]}"
|
|
119
|
+
puts " New Rule: #{new_rule_spec[:name]}"
|
|
120
|
+
puts " Logic: #{new_rule_spec[:description]}"
|
|
121
|
+
|
|
122
|
+
# Dynamically add new rule to engine
|
|
123
|
+
if new_rule_spec[:valid]
|
|
124
|
+
dynamic_rule = create_rule_from_spec(new_rule_spec)
|
|
125
|
+
@kb.engine.add_rule(dynamic_rule)
|
|
126
|
+
puts " ā
Rule added to knowledge base"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
rule "ai_risk_assessment" do
|
|
132
|
+
priority 18
|
|
133
|
+
on :position, unrealized_pnl: satisfies { |pnl| pnl && pnl.abs > 1000 }
|
|
134
|
+
on :market_data, symbol: satisfies { |s| s && s.length > 0 }
|
|
135
|
+
|
|
136
|
+
perform do |facts|
|
|
137
|
+
position = facts.find { |f| f.type == :position }
|
|
138
|
+
market_data = facts.find { |f| f.type == :market_data }
|
|
139
|
+
|
|
140
|
+
# AI-powered risk analysis
|
|
141
|
+
risk_analysis = ai_sys.analyze_position_risk_with_ai(position, market_data)
|
|
142
|
+
|
|
143
|
+
puts "ā ļø AI RISK ASSESSMENT: #{position[:symbol]}"
|
|
144
|
+
puts " Current P&L: $#{position[:unrealized_pnl]}"
|
|
145
|
+
puts " Risk Level: #{risk_analysis[:risk_level]}"
|
|
146
|
+
puts " Key Risks: #{risk_analysis[:risks].join(', ')}"
|
|
147
|
+
puts " Recommendation: #{risk_analysis[:recommendation]}"
|
|
148
|
+
puts " Confidence: #{risk_analysis[:confidence]}%"
|
|
149
|
+
|
|
150
|
+
# Act on high-risk situations
|
|
151
|
+
if risk_analysis[:risk_level] == "HIGH" && risk_analysis[:confidence] > 80
|
|
152
|
+
puts " šØ HIGH RISK DETECTED - Consider position adjustment"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
rule "ai_explanation_generator" do
|
|
158
|
+
priority 5
|
|
159
|
+
on :trade_recommendation,
|
|
160
|
+
action: satisfies { |a| ["BUY", "SELL", "HOLD"].include?(a) },
|
|
161
|
+
symbol: satisfies { |s| s && s.length > 0 }
|
|
162
|
+
|
|
163
|
+
perform do |facts|
|
|
164
|
+
recommendation = facts.find { |f| f.type == :trade_recommendation }
|
|
165
|
+
|
|
166
|
+
# Generate natural language explanation
|
|
167
|
+
explanation = ai_sys.generate_trade_explanation(recommendation, facts)
|
|
168
|
+
|
|
169
|
+
puts "š¬ AI EXPLANATION: #{recommendation[:symbol]} #{recommendation[:action]}"
|
|
170
|
+
puts " Reasoning: #{explanation[:reasoning]}"
|
|
171
|
+
puts " Context: #{explanation[:context]}"
|
|
172
|
+
puts " Confidence: #{explanation[:confidence]}%"
|
|
173
|
+
puts " Alternative View: #{explanation[:alternative]}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
rule "ai_pattern_recognition" do
|
|
178
|
+
priority 10
|
|
179
|
+
on :price_history,
|
|
180
|
+
symbol: satisfies { |s| s && s.length > 0 },
|
|
181
|
+
data_points: satisfies { |d| d && d.length >= 30 }
|
|
182
|
+
|
|
183
|
+
perform do |facts|
|
|
184
|
+
price_data = facts.find { |f| f.type == :price_history }
|
|
185
|
+
|
|
186
|
+
# AI identifies patterns
|
|
187
|
+
patterns = ai_sys.identify_patterns_with_ai(price_data[:data_points])
|
|
188
|
+
|
|
189
|
+
if patterns.any?
|
|
190
|
+
puts "š AI PATTERN RECOGNITION: #{price_data[:symbol]}"
|
|
191
|
+
patterns.each do |pattern|
|
|
192
|
+
puts " Pattern: #{pattern[:name]} (#{pattern[:confidence]}%)"
|
|
193
|
+
puts " Prediction: #{pattern[:prediction]}"
|
|
194
|
+
puts " Time Horizon: #{pattern[:time_horizon]}"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def analyze_sentiment_with_ai(headline, content)
|
|
203
|
+
cache_key = "#{headline[0..50]}_#{content[0..100]}".hash
|
|
204
|
+
return @sentiment_cache[cache_key] if @sentiment_cache[cache_key]
|
|
205
|
+
|
|
206
|
+
prompt = build_sentiment_prompt(headline, content)
|
|
207
|
+
puts "\nš Calling RubyLLM for sentiment analysis..."
|
|
208
|
+
|
|
209
|
+
message = @ai_client.ask(prompt)
|
|
210
|
+
response_text = message.content.to_s
|
|
211
|
+
puts "ā
Got response from Ollama (#{response_text.length} chars)"
|
|
212
|
+
puts "š Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
213
|
+
|
|
214
|
+
result = parse_sentiment_response(response_text)
|
|
215
|
+
|
|
216
|
+
@sentiment_cache[cache_key] = result
|
|
217
|
+
result
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def generate_ai_trading_strategy(market_conditions, portfolio_state)
|
|
221
|
+
prompt = build_strategy_prompt(market_conditions, portfolio_state)
|
|
222
|
+
puts "\nš Calling RubyLLM for trading strategy..."
|
|
223
|
+
|
|
224
|
+
message = @ai_client.ask(prompt)
|
|
225
|
+
response_text = message.content.to_s
|
|
226
|
+
puts "ā
Got response from Ollama (#{response_text.length} chars)"
|
|
227
|
+
puts "š Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
228
|
+
|
|
229
|
+
parse_strategy_response(response_text)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def generate_rule_with_ai(anomaly_data)
|
|
233
|
+
prompt = build_rule_generation_prompt(anomaly_data)
|
|
234
|
+
puts "\nš Calling RubyLLM for rule generation..."
|
|
235
|
+
|
|
236
|
+
message = @ai_client.ask(prompt)
|
|
237
|
+
response_text = message.content.to_s
|
|
238
|
+
puts "ā
Got response from Ollama (#{response_text.length} chars)"
|
|
239
|
+
puts "š Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
240
|
+
|
|
241
|
+
parse_rule_specification(response_text)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def analyze_position_risk_with_ai(position, market_data)
|
|
245
|
+
prompt = build_risk_analysis_prompt(position, market_data)
|
|
246
|
+
puts "\nš Calling RubyLLM for risk analysis..."
|
|
247
|
+
|
|
248
|
+
message = @ai_client.ask(prompt)
|
|
249
|
+
response_text = message.content.to_s
|
|
250
|
+
puts "ā
Got response from Ollama (#{response_text.length} chars)"
|
|
251
|
+
puts "š Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
252
|
+
|
|
253
|
+
parse_risk_analysis(response_text)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def generate_trade_explanation(recommendation, context_facts)
|
|
257
|
+
cache_key = "#{recommendation[:symbol]}_#{recommendation[:action]}_#{context_facts.length}".hash
|
|
258
|
+
return @explanation_cache[cache_key] if @explanation_cache[cache_key]
|
|
259
|
+
|
|
260
|
+
prompt = build_explanation_prompt(recommendation, context_facts)
|
|
261
|
+
puts "\nš Calling RubyLLM for trade explanation..."
|
|
262
|
+
|
|
263
|
+
message = @ai_client.ask(prompt)
|
|
264
|
+
response_text = message.content.to_s
|
|
265
|
+
puts "ā
Got response from Ollama (#{response_text.length} chars)"
|
|
266
|
+
puts "š Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
267
|
+
|
|
268
|
+
result = parse_explanation_response(response_text)
|
|
269
|
+
|
|
270
|
+
@explanation_cache[cache_key] = result
|
|
271
|
+
result
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def identify_patterns_with_ai(price_data)
|
|
275
|
+
prompt = build_pattern_recognition_prompt(price_data)
|
|
276
|
+
puts "\nš Calling RubyLLM for pattern recognition..."
|
|
277
|
+
|
|
278
|
+
message = @ai_client.ask(prompt)
|
|
279
|
+
response_text = message.content.to_s
|
|
280
|
+
puts "ā
Got response from Ollama (#{response_text.length} chars)"
|
|
281
|
+
puts "š Response: #{response_text[0..200]}..." if response_text.length > 200
|
|
282
|
+
|
|
283
|
+
parse_pattern_response(response_text)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Prompt builders
|
|
287
|
+
def build_sentiment_prompt(headline, content)
|
|
288
|
+
<<~PROMPT
|
|
289
|
+
Analyze the sentiment of this financial news for trading implications:
|
|
290
|
+
|
|
291
|
+
Headline: #{headline}
|
|
292
|
+
Content: #{content[0..500]}...
|
|
293
|
+
|
|
294
|
+
Provide a JSON response with:
|
|
295
|
+
{
|
|
296
|
+
"sentiment": "positive|negative|neutral",
|
|
297
|
+
"score": -1.0 to 1.0,
|
|
298
|
+
"confidence": 0-100,
|
|
299
|
+
"themes": ["theme1", "theme2"],
|
|
300
|
+
"market_impact": "bullish|bearish|neutral"
|
|
301
|
+
}
|
|
302
|
+
PROMPT
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def build_strategy_prompt(market_conditions, portfolio_state)
|
|
306
|
+
<<~PROMPT
|
|
307
|
+
Generate a trading strategy for these conditions:
|
|
308
|
+
|
|
309
|
+
Market: #{market_conditions[:trend]} trend, #{market_conditions[:volatility]}% volatility
|
|
310
|
+
Portfolio: #{(portfolio_state[:cash_ratio] * 100).round(1)}% cash
|
|
311
|
+
|
|
312
|
+
Provide a JSON strategy with:
|
|
313
|
+
{
|
|
314
|
+
"name": "strategy_name",
|
|
315
|
+
"rationale": "why this strategy fits",
|
|
316
|
+
"actions": ["action1", "action2"],
|
|
317
|
+
"risk_level": "LOW|MEDIUM|HIGH"
|
|
318
|
+
}
|
|
319
|
+
PROMPT
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def build_risk_analysis_prompt(position, market_data)
|
|
323
|
+
<<~PROMPT
|
|
324
|
+
Analyze the risk of this trading position:
|
|
325
|
+
|
|
326
|
+
Position: #{position[:symbol]}, P&L: $#{position[:unrealized_pnl]}
|
|
327
|
+
Market Data: #{market_data.to_json}
|
|
328
|
+
|
|
329
|
+
Provide risk assessment as JSON:
|
|
330
|
+
{
|
|
331
|
+
"risk_level": "LOW|MEDIUM|HIGH",
|
|
332
|
+
"risks": ["risk1", "risk2"],
|
|
333
|
+
"recommendation": "hold|reduce|exit",
|
|
334
|
+
"confidence": 0-100
|
|
335
|
+
}
|
|
336
|
+
PROMPT
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def build_explanation_prompt(recommendation, context_facts)
|
|
340
|
+
<<~PROMPT
|
|
341
|
+
Explain this trading recommendation in simple terms:
|
|
342
|
+
|
|
343
|
+
Recommendation: #{recommendation[:action]} #{recommendation[:symbol]}
|
|
344
|
+
Context: #{context_facts.length} supporting facts
|
|
345
|
+
|
|
346
|
+
Provide explanation as JSON:
|
|
347
|
+
{
|
|
348
|
+
"explanation": "clear explanation",
|
|
349
|
+
"reasoning": "why this makes sense",
|
|
350
|
+
"risks": ["risk1", "risk2"]
|
|
351
|
+
}
|
|
352
|
+
PROMPT
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def build_pattern_recognition_prompt(price_data)
|
|
356
|
+
<<~PROMPT
|
|
357
|
+
Identify trading patterns in this price data:
|
|
358
|
+
|
|
359
|
+
Data: #{price_data.to_json}
|
|
360
|
+
|
|
361
|
+
Return JSON array of patterns:
|
|
362
|
+
[
|
|
363
|
+
{
|
|
364
|
+
"pattern": "pattern_name",
|
|
365
|
+
"confidence": 0-100,
|
|
366
|
+
"description": "what this means"
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
PROMPT
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def build_rule_generation_prompt(anomaly_data)
|
|
373
|
+
<<~PROMPT
|
|
374
|
+
Generate a trading rule specification for this anomaly pattern:
|
|
375
|
+
|
|
376
|
+
Pattern Type: #{anomaly_data[:pattern_type]}
|
|
377
|
+
Confidence: #{anomaly_data[:confidence]}
|
|
378
|
+
Occurrences: #{anomaly_data[:occurrences]}
|
|
379
|
+
|
|
380
|
+
Return JSON with:
|
|
381
|
+
{
|
|
382
|
+
"name": "rule_name",
|
|
383
|
+
"description": "what this rule does",
|
|
384
|
+
"valid": true|false
|
|
385
|
+
}
|
|
386
|
+
PROMPT
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def create_rule_from_spec(spec)
|
|
390
|
+
# Simplified rule creation - in production would parse spec more thoroughly
|
|
391
|
+
KBS::Rule.new(
|
|
392
|
+
spec[:name],
|
|
393
|
+
conditions: [],
|
|
394
|
+
action: lambda { |facts, bindings| puts "Dynamic rule fired: #{spec[:name]}" }
|
|
395
|
+
)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Response parsers
|
|
399
|
+
def extract_json(response)
|
|
400
|
+
# Strip markdown code fences if present
|
|
401
|
+
json_text = response.strip
|
|
402
|
+
json_text = json_text.gsub(/^```json\s*/, '').gsub(/```\s*$/, '').strip
|
|
403
|
+
json_text
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def parse_sentiment_response(response)
|
|
407
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def parse_strategy_response(response)
|
|
411
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def parse_explanation_response(response)
|
|
415
|
+
result = JSON.parse(extract_json(response), symbolize_names: true)
|
|
416
|
+
# Ensure all expected keys exist
|
|
417
|
+
{
|
|
418
|
+
reasoning: result[:reasoning] || result[:explanation] || "No reasoning provided",
|
|
419
|
+
context: result[:context] || "No context provided",
|
|
420
|
+
confidence: result[:confidence] || 50,
|
|
421
|
+
alternative: result[:alternative] || result[:risks]&.join(", ") || "No alternatives provided"
|
|
422
|
+
}
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def parse_pattern_response(response)
|
|
426
|
+
patterns = JSON.parse(extract_json(response), symbolize_names: true)
|
|
427
|
+
# Ensure array format and normalize keys
|
|
428
|
+
patterns = [patterns] unless patterns.is_a?(Array)
|
|
429
|
+
patterns.map do |p|
|
|
430
|
+
{
|
|
431
|
+
name: p[:pattern] || p[:name] || "Unknown Pattern",
|
|
432
|
+
confidence: p[:confidence] || 50,
|
|
433
|
+
prediction: p[:prediction] || p[:description] || "No prediction",
|
|
434
|
+
time_horizon: p[:time_horizon] || "Unknown"
|
|
435
|
+
}
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def parse_risk_analysis(response)
|
|
440
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def parse_rule_specification(response)
|
|
444
|
+
JSON.parse(extract_json(response), symbolize_names: true)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def demonstrate_ai_enhancements
|
|
448
|
+
puts "š¤ AI-ENHANCED KNOWLEDGE-BASED SYSTEM"
|
|
449
|
+
puts "=" * 70
|
|
450
|
+
puts "Integrating #{@ai_client.class.name} and #{@mcp_agent.class.name if @mcp_agent}"
|
|
451
|
+
puts "=" * 70
|
|
452
|
+
|
|
453
|
+
# Scenario 1: AI Sentiment Analysis
|
|
454
|
+
puts "\nš° SCENARIO 1: AI-Powered News Sentiment"
|
|
455
|
+
puts "-" * 50
|
|
456
|
+
@kb.reset
|
|
457
|
+
|
|
458
|
+
@kb.fact :news_data, {
|
|
459
|
+
symbol: "AAPL",
|
|
460
|
+
headline: "Apple Reports Record Q4 Earnings, Beats Expectations by 15%",
|
|
461
|
+
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.",
|
|
462
|
+
published_at: Time.now
|
|
463
|
+
}
|
|
464
|
+
@kb.run
|
|
465
|
+
|
|
466
|
+
# Scenario 2: AI Strategy Generation
|
|
467
|
+
puts "\nš§ SCENARIO 2: AI Trading Strategy Generation"
|
|
468
|
+
puts "-" * 50
|
|
469
|
+
@kb.reset
|
|
470
|
+
|
|
471
|
+
@kb.fact :market_conditions, {
|
|
472
|
+
volatility: 28.5,
|
|
473
|
+
trend: "sideways",
|
|
474
|
+
sector_rotation: "technology_to_healthcare"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
@kb.fact :portfolio_state, {
|
|
478
|
+
cash_ratio: 0.25,
|
|
479
|
+
largest_position: "AAPL",
|
|
480
|
+
sector_concentration: 0.45
|
|
481
|
+
}
|
|
482
|
+
@kb.run
|
|
483
|
+
|
|
484
|
+
# Scenario 3: AI Risk Assessment
|
|
485
|
+
puts "\nā ļø SCENARIO 3: AI Risk Assessment"
|
|
486
|
+
puts "-" * 50
|
|
487
|
+
@kb.reset
|
|
488
|
+
|
|
489
|
+
@kb.fact :position, {
|
|
490
|
+
symbol: "TSLA",
|
|
491
|
+
shares: 100,
|
|
492
|
+
entry_price: 250.00,
|
|
493
|
+
current_price: 235.00,
|
|
494
|
+
unrealized_pnl: -1500
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@kb.fact :market_data, {
|
|
498
|
+
symbol: "TSLA",
|
|
499
|
+
volatility: 45.2,
|
|
500
|
+
beta: 2.1,
|
|
501
|
+
sector: "Consumer Discretionary"
|
|
502
|
+
}
|
|
503
|
+
@kb.run
|
|
504
|
+
|
|
505
|
+
# Scenario 4: Trade Explanation
|
|
506
|
+
puts "\nš¬ SCENARIO 4: AI Trade Explanation"
|
|
507
|
+
puts "-" * 50
|
|
508
|
+
@kb.reset
|
|
509
|
+
|
|
510
|
+
@kb.fact :trade_recommendation, {
|
|
511
|
+
symbol: "GOOGL",
|
|
512
|
+
action: "BUY",
|
|
513
|
+
quantity: 50,
|
|
514
|
+
confidence: 85
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
@kb.fact :technical_analysis, {
|
|
518
|
+
symbol: "GOOGL",
|
|
519
|
+
rsi: 35,
|
|
520
|
+
moving_average_signal: "golden_cross",
|
|
521
|
+
volume_trend: "increasing"
|
|
522
|
+
}
|
|
523
|
+
@kb.run
|
|
524
|
+
|
|
525
|
+
puts "\n" + "=" * 70
|
|
526
|
+
puts "AI ENHANCEMENT DEMONSTRATION COMPLETE"
|
|
527
|
+
puts "šÆ The system now combines rule-based logic with AI insights"
|
|
528
|
+
puts "š§ Dynamic pattern recognition and strategy generation"
|
|
529
|
+
puts "š¬ Natural language explanations for all decisions"
|
|
530
|
+
puts "ā” Real-time sentiment analysis and risk assessment"
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
if __FILE__ == $0
|
|
536
|
+
system = AIEnhancedKBS::AIKnowledgeSystem.new
|
|
537
|
+
system.demonstrate_ai_enhancements
|
|
538
|
+
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"
|
data/examples/car_diagnostic.rb
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/kbs/dsl'
|
|
4
|
+
|
|
5
|
+
puts "Creating a simple expert system for diagnosing car problems..."
|
|
6
|
+
puts "-" * 60
|
|
7
|
+
|
|
8
|
+
kb = KBS.knowledge_base do
|
|
9
|
+
rule "dead_battery" do
|
|
10
|
+
on :symptom, problem: "won't start"
|
|
11
|
+
on :symptom, problem: "no lights"
|
|
12
|
+
|
|
13
|
+
perform do |facts, bindings|
|
|
14
|
+
puts "DIAGNOSIS: Dead battery - The car won't start and has no lights"
|
|
15
|
+
puts "RECOMMENDATION: Jump start the battery or replace it"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
rule "flat_tire" do
|
|
20
|
+
on :symptom, problem: "pulling to side"
|
|
21
|
+
on :symptom, problem: "low tire pressure"
|
|
22
|
+
|
|
23
|
+
perform do |facts, bindings|
|
|
24
|
+
puts "DIAGNOSIS: Flat or low tire"
|
|
25
|
+
puts "RECOMMENDATION: Check tire pressure and inflate or replace tire"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
rule "overheating" do
|
|
30
|
+
on :symptom, problem: "high temperature"
|
|
31
|
+
on :symptom, problem: "steam from hood"
|
|
32
|
+
without.on :symptom, problem: "coolant leak"
|
|
33
|
+
|
|
34
|
+
perform do |facts, bindings|
|
|
35
|
+
puts "DIAGNOSIS: Engine overheating (no coolant leak detected)"
|
|
36
|
+
puts "RECOMMENDATION: Check radiator and cooling system"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
puts "\nAdding symptoms..."
|
|
42
|
+
kb.fact :symptom, { problem: "won't start", severity: "high" }
|
|
43
|
+
kb.fact :symptom, { problem: "no lights", severity: "high" }
|
|
44
|
+
|
|
45
|
+
puts "\nRunning inference engine..."
|
|
46
|
+
kb.run
|
|
47
|
+
|
|
48
|
+
puts "\n" + "-" * 60
|
|
49
|
+
puts "\nAdding more symptoms..."
|
|
50
|
+
kb.fact :symptom, { problem: "high temperature", severity: "critical" }
|
|
51
|
+
kb.fact :symptom, { problem: "steam from hood", severity: "high" }
|
|
52
|
+
|
|
53
|
+
puts "\nRunning inference engine again..."
|
|
54
|
+
kb.run
|
|
@@ -9,7 +9,7 @@ require 'thread'
|
|
|
9
9
|
# Rules fire immediately when facts are added
|
|
10
10
|
# =============================================================================
|
|
11
11
|
|
|
12
|
-
class ReactiveEngine < KBS::
|
|
12
|
+
class ReactiveEngine < KBS::Engine
|
|
13
13
|
attr_accessor :auto_inference
|
|
14
14
|
|
|
15
15
|
def initialize(auto_inference: true)
|
|
@@ -37,7 +37,7 @@ end
|
|
|
37
37
|
# Inference runs continuously in a background thread
|
|
38
38
|
# =============================================================================
|
|
39
39
|
|
|
40
|
-
class BackgroundInferenceEngine < KBS::
|
|
40
|
+
class BackgroundInferenceEngine < KBS::Engine
|
|
41
41
|
def initialize
|
|
42
42
|
super()
|
|
43
43
|
@running = false
|
|
@@ -103,7 +103,7 @@ end
|
|
|
103
103
|
# Execute callbacks immediately when rules fire
|
|
104
104
|
# =============================================================================
|
|
105
105
|
|
|
106
|
-
class EventDrivenEngine < KBS::
|
|
106
|
+
class EventDrivenEngine < KBS::Engine
|
|
107
107
|
def initialize
|
|
108
108
|
super()
|
|
109
109
|
@rule_callbacks = {}
|
|
@@ -285,7 +285,7 @@ def demo_queue_based
|
|
|
285
285
|
puts "="*80
|
|
286
286
|
puts "Facts accumulate and are processed in batches\n\n"
|
|
287
287
|
|
|
288
|
-
engine = KBS::
|
|
288
|
+
engine = KBS::Engine.new
|
|
289
289
|
fact_queue = Queue.new
|
|
290
290
|
|
|
291
291
|
# Define rule
|
|
@@ -344,7 +344,7 @@ end
|
|
|
344
344
|
# =============================================================================
|
|
345
345
|
|
|
346
346
|
if __FILE__ == $0
|
|
347
|
-
puts "\nš CONCURRENT INFERENCE PATTERNS FOR RETE
|
|
347
|
+
puts "\nš CONCURRENT INFERENCE PATTERNS FOR RETE\n"
|
|
348
348
|
|
|
349
349
|
demo_reactive_inference
|
|
350
350
|
sleep 1
|