rack-ai 0.1.0 → 0.3.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/CHANGELOG.md +96 -35
- data/examples/comprehensive_example.rb +203 -0
- data/examples/rails_integration_advanced.rb +353 -0
- data/examples/sinatra_microservice.rb +431 -0
- data/lib/rack/ai/configuration.rb +47 -36
- data/lib/rack/ai/features/anomaly_detection.rb +236 -0
- data/lib/rack/ai/features/rate_limiting.rb +114 -0
- data/lib/rack/ai/middleware.rb +4 -2
- data/lib/rack/ai/utils/enhanced_logger.rb +130 -0
- data/lib/rack/ai/utils/logger.rb +6 -5
- data/lib/rack/ai/version.rb +1 -1
- data/lib/rack/ai.rb +4 -1
- metadata +7 -1
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module AI
|
5
|
+
module Features
|
6
|
+
class AnomalyDetection
|
7
|
+
attr_reader :name, :provider, :config
|
8
|
+
|
9
|
+
def initialize(provider, config)
|
10
|
+
@name = :anomaly_detection
|
11
|
+
@provider = provider
|
12
|
+
@config = config
|
13
|
+
@baseline_metrics = {}
|
14
|
+
@request_patterns = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def enabled?
|
18
|
+
@config.feature_enabled?(:anomaly_detection)
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_response?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_request(env)
|
26
|
+
current_time = Time.now
|
27
|
+
client_ip = get_client_ip(env)
|
28
|
+
|
29
|
+
# Extract request metrics
|
30
|
+
metrics = extract_request_metrics(env)
|
31
|
+
|
32
|
+
# Update baseline and detect anomalies
|
33
|
+
anomalies = detect_anomalies(client_ip, metrics, current_time)
|
34
|
+
|
35
|
+
# Calculate risk score
|
36
|
+
risk_score = calculate_risk_score(anomalies, metrics)
|
37
|
+
|
38
|
+
# Determine action based on risk score
|
39
|
+
action = determine_action(risk_score)
|
40
|
+
|
41
|
+
{
|
42
|
+
action: action,
|
43
|
+
feature: :anomaly_detection,
|
44
|
+
timestamp: current_time.iso8601,
|
45
|
+
risk_score: risk_score,
|
46
|
+
anomalies: anomalies,
|
47
|
+
metrics: metrics,
|
48
|
+
baseline_deviation: calculate_baseline_deviation(metrics)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def get_client_ip(env)
|
55
|
+
env['HTTP_X_FORWARDED_FOR']&.split(',')&.first&.strip ||
|
56
|
+
env['HTTP_X_REAL_IP'] ||
|
57
|
+
env['REMOTE_ADDR'] ||
|
58
|
+
'unknown'
|
59
|
+
end
|
60
|
+
|
61
|
+
def extract_request_metrics(env)
|
62
|
+
{
|
63
|
+
request_size: calculate_request_size(env),
|
64
|
+
header_count: env.select { |k, _| k.start_with?('HTTP_') }.size,
|
65
|
+
path_length: env['PATH_INFO']&.length || 0,
|
66
|
+
query_complexity: calculate_query_complexity(env['QUERY_STRING']),
|
67
|
+
user_agent_entropy: calculate_entropy(env['HTTP_USER_AGENT']),
|
68
|
+
request_frequency: calculate_request_frequency(get_client_ip(env)),
|
69
|
+
time_of_day: Time.now.hour,
|
70
|
+
day_of_week: Time.now.wday
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def calculate_request_size(env)
|
75
|
+
size = 0
|
76
|
+
size += env['CONTENT_LENGTH'].to_i if env['CONTENT_LENGTH']
|
77
|
+
size += env['QUERY_STRING']&.length || 0
|
78
|
+
env.each { |k, v| size += k.length + v.to_s.length if k.start_with?('HTTP_') }
|
79
|
+
size
|
80
|
+
end
|
81
|
+
|
82
|
+
def calculate_query_complexity(query_string)
|
83
|
+
return 0 unless query_string
|
84
|
+
|
85
|
+
params = query_string.split('&')
|
86
|
+
complexity = params.size
|
87
|
+
|
88
|
+
# Add complexity for nested parameters, arrays, etc.
|
89
|
+
params.each do |param|
|
90
|
+
complexity += 1 if param.include?('[')
|
91
|
+
complexity += 1 if param.include?('%')
|
92
|
+
complexity += param.scan(/[=&]/).size
|
93
|
+
end
|
94
|
+
|
95
|
+
complexity
|
96
|
+
end
|
97
|
+
|
98
|
+
def calculate_entropy(string)
|
99
|
+
return 0 unless string
|
100
|
+
|
101
|
+
frequencies = Hash.new(0)
|
102
|
+
string.each_char { |char| frequencies[char] += 1 }
|
103
|
+
|
104
|
+
entropy = 0
|
105
|
+
string.length.times do
|
106
|
+
freq = frequencies.values.shift
|
107
|
+
next if freq == 0
|
108
|
+
|
109
|
+
probability = freq.to_f / string.length
|
110
|
+
entropy -= probability * Math.log2(probability)
|
111
|
+
end
|
112
|
+
|
113
|
+
entropy
|
114
|
+
end
|
115
|
+
|
116
|
+
def calculate_request_frequency(client_ip)
|
117
|
+
current_time = Time.now
|
118
|
+
@request_patterns[client_ip] ||= []
|
119
|
+
|
120
|
+
# Clean old entries (older than 1 hour)
|
121
|
+
@request_patterns[client_ip].reject! { |time| current_time - time > 3600 }
|
122
|
+
|
123
|
+
# Add current request
|
124
|
+
@request_patterns[client_ip] << current_time
|
125
|
+
|
126
|
+
# Return requests per minute
|
127
|
+
recent_requests = @request_patterns[client_ip].count { |time| current_time - time <= 60 }
|
128
|
+
recent_requests
|
129
|
+
end
|
130
|
+
|
131
|
+
def detect_anomalies(client_ip, metrics, current_time)
|
132
|
+
anomalies = []
|
133
|
+
|
134
|
+
# Update baseline for this client
|
135
|
+
@baseline_metrics[client_ip] ||= initialize_baseline
|
136
|
+
baseline = @baseline_metrics[client_ip]
|
137
|
+
|
138
|
+
metrics.each do |metric, value|
|
139
|
+
next unless value.is_a?(Numeric)
|
140
|
+
|
141
|
+
# Calculate z-score
|
142
|
+
mean = baseline[metric][:mean]
|
143
|
+
std_dev = baseline[metric][:std_dev]
|
144
|
+
|
145
|
+
if std_dev > 0
|
146
|
+
z_score = (value - mean).abs / std_dev
|
147
|
+
|
148
|
+
if z_score > 3 # 3 standard deviations
|
149
|
+
anomalies << {
|
150
|
+
metric: metric,
|
151
|
+
value: value,
|
152
|
+
expected_range: [mean - 2 * std_dev, mean + 2 * std_dev],
|
153
|
+
z_score: z_score,
|
154
|
+
severity: z_score > 5 ? :high : :medium
|
155
|
+
}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Update baseline with exponential moving average
|
160
|
+
alpha = 0.1
|
161
|
+
baseline[metric][:mean] = alpha * value + (1 - alpha) * mean
|
162
|
+
|
163
|
+
# Update standard deviation
|
164
|
+
variance = baseline[metric][:variance] || 0
|
165
|
+
new_variance = alpha * (value - mean) ** 2 + (1 - alpha) * variance
|
166
|
+
baseline[metric][:variance] = new_variance
|
167
|
+
baseline[metric][:std_dev] = Math.sqrt(new_variance)
|
168
|
+
end
|
169
|
+
|
170
|
+
anomalies
|
171
|
+
end
|
172
|
+
|
173
|
+
def initialize_baseline
|
174
|
+
Hash.new do |hash, key|
|
175
|
+
hash[key] = { mean: 0, variance: 0, std_dev: 1 }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def calculate_risk_score(anomalies, metrics)
|
180
|
+
base_score = 0
|
181
|
+
|
182
|
+
# Score based on anomalies
|
183
|
+
anomalies.each do |anomaly|
|
184
|
+
case anomaly[:severity]
|
185
|
+
when :high
|
186
|
+
base_score += 30
|
187
|
+
when :medium
|
188
|
+
base_score += 15
|
189
|
+
else
|
190
|
+
base_score += 5
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Additional risk factors
|
195
|
+
base_score += 10 if metrics[:request_frequency] > 60 # More than 1 req/sec
|
196
|
+
base_score += 15 if metrics[:request_size] > 1_000_000 # Large requests
|
197
|
+
base_score += 5 if metrics[:query_complexity] > 50 # Complex queries
|
198
|
+
base_score += 10 if metrics[:user_agent_entropy] < 2 # Suspicious UA
|
199
|
+
|
200
|
+
# Time-based risk (higher risk during off-hours)
|
201
|
+
if metrics[:time_of_day] < 6 || metrics[:time_of_day] > 22
|
202
|
+
base_score += 5
|
203
|
+
end
|
204
|
+
|
205
|
+
[base_score, 100].min # Cap at 100
|
206
|
+
end
|
207
|
+
|
208
|
+
def calculate_baseline_deviation(metrics)
|
209
|
+
# Simple calculation of how much current metrics deviate from expected
|
210
|
+
deviation_score = 0
|
211
|
+
|
212
|
+
metrics.each do |_, value|
|
213
|
+
next unless value.is_a?(Numeric)
|
214
|
+
# Simplified deviation calculation
|
215
|
+
deviation_score += value > 1000 ? 1 : 0
|
216
|
+
end
|
217
|
+
|
218
|
+
deviation_score
|
219
|
+
end
|
220
|
+
|
221
|
+
def determine_action(risk_score)
|
222
|
+
case risk_score
|
223
|
+
when 0..20
|
224
|
+
:allow
|
225
|
+
when 21..50
|
226
|
+
:monitor
|
227
|
+
when 51..80
|
228
|
+
:challenge
|
229
|
+
else
|
230
|
+
:block
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module AI
|
5
|
+
module Features
|
6
|
+
class RateLimiting
|
7
|
+
attr_reader :name, :provider, :config
|
8
|
+
|
9
|
+
def initialize(provider, config)
|
10
|
+
@name = :rate_limiting
|
11
|
+
@provider = provider
|
12
|
+
@config = config
|
13
|
+
@rate_store = {}
|
14
|
+
@cleanup_timer = Time.now
|
15
|
+
end
|
16
|
+
|
17
|
+
def enabled?
|
18
|
+
@config.feature_enabled?(:rate_limiting)
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_response?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_request(env)
|
26
|
+
client_ip = get_client_ip(env)
|
27
|
+
current_time = Time.now
|
28
|
+
|
29
|
+
# Cleanup old entries every 5 minutes
|
30
|
+
cleanup_old_entries if current_time - @cleanup_timer > 300
|
31
|
+
|
32
|
+
# Get rate limit settings
|
33
|
+
window_size = @config.rate_limiting.window_size || 3600 # 1 hour default
|
34
|
+
max_requests = @config.rate_limiting.max_requests || 1000 # 1000 requests default
|
35
|
+
|
36
|
+
# Initialize client data if not exists
|
37
|
+
@rate_store[client_ip] ||= { requests: [], blocked_until: nil }
|
38
|
+
client_data = @rate_store[client_ip]
|
39
|
+
|
40
|
+
# Check if client is currently blocked
|
41
|
+
if client_data[:blocked_until] && current_time < client_data[:blocked_until]
|
42
|
+
return {
|
43
|
+
action: :block,
|
44
|
+
reason: "Rate limit exceeded - blocked until #{client_data[:blocked_until]}",
|
45
|
+
status: 429,
|
46
|
+
feature: :rate_limiting,
|
47
|
+
timestamp: current_time.iso8601,
|
48
|
+
retry_after: (client_data[:blocked_until] - current_time).to_i
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Remove old requests outside the window
|
53
|
+
window_start = current_time - window_size
|
54
|
+
client_data[:requests].reject! { |timestamp| timestamp < window_start }
|
55
|
+
|
56
|
+
# Check if rate limit is exceeded
|
57
|
+
if client_data[:requests].length >= max_requests
|
58
|
+
block_duration = @config.rate_limiting.block_duration || 3600 # 1 hour default
|
59
|
+
client_data[:blocked_until] = current_time + block_duration
|
60
|
+
|
61
|
+
return {
|
62
|
+
action: :block,
|
63
|
+
reason: "Rate limit exceeded: #{client_data[:requests].length}/#{max_requests} requests in #{window_size}s",
|
64
|
+
status: 429,
|
65
|
+
feature: :rate_limiting,
|
66
|
+
timestamp: current_time.iso8601,
|
67
|
+
retry_after: block_duration,
|
68
|
+
requests_count: client_data[:requests].length,
|
69
|
+
window_size: window_size
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add current request
|
74
|
+
client_data[:requests] << current_time
|
75
|
+
|
76
|
+
{
|
77
|
+
action: :allow,
|
78
|
+
feature: :rate_limiting,
|
79
|
+
timestamp: current_time.iso8601,
|
80
|
+
requests_count: client_data[:requests].length,
|
81
|
+
requests_remaining: max_requests - client_data[:requests].length,
|
82
|
+
window_size: window_size,
|
83
|
+
reset_time: (window_start + window_size).iso8601
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def get_client_ip(env)
|
90
|
+
# Try various headers for real IP
|
91
|
+
forwarded_for = env['HTTP_X_FORWARDED_FOR']
|
92
|
+
return forwarded_for.split(',').first.strip if forwarded_for
|
93
|
+
|
94
|
+
real_ip = env['HTTP_X_REAL_IP']
|
95
|
+
return real_ip if real_ip
|
96
|
+
|
97
|
+
env['REMOTE_ADDR'] || 'unknown'
|
98
|
+
end
|
99
|
+
|
100
|
+
def cleanup_old_entries
|
101
|
+
current_time = Time.now
|
102
|
+
@rate_store.each do |ip, data|
|
103
|
+
# Remove clients with no recent activity and no active blocks
|
104
|
+
if data[:requests].empty? &&
|
105
|
+
(data[:blocked_until].nil? || current_time > data[:blocked_until])
|
106
|
+
@rate_store.delete(ip)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
@cleanup_timer = current_time
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/rack/ai/middleware.rb
CHANGED
@@ -83,7 +83,9 @@ module Rack
|
|
83
83
|
routing: Features::Routing,
|
84
84
|
logging: Features::Logging,
|
85
85
|
enhancement: Features::Enhancement,
|
86
|
-
security: Features::Security
|
86
|
+
security: Features::Security,
|
87
|
+
rate_limiting: Features::RateLimiting,
|
88
|
+
anomaly_detection: Features::AnomalyDetection
|
87
89
|
}
|
88
90
|
|
89
91
|
features = @config.features.map do |feature_name|
|
@@ -93,7 +95,7 @@ module Rack
|
|
93
95
|
feature_class.new(@provider, @config)
|
94
96
|
end
|
95
97
|
|
96
|
-
Utils::Logger.debug("Built features: #{features.map(
|
98
|
+
Utils::Logger.debug("Built features: #{features.map { |f| f.class.name.split('::').last }}")
|
97
99
|
features
|
98
100
|
end
|
99
101
|
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
module AI
|
8
|
+
module Utils
|
9
|
+
class AdvancedLogger
|
10
|
+
LOG_LEVELS = {
|
11
|
+
debug: ::Logger::DEBUG,
|
12
|
+
info: ::Logger::INFO,
|
13
|
+
warn: ::Logger::WARN,
|
14
|
+
error: ::Logger::ERROR,
|
15
|
+
fatal: ::Logger::FATAL
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def self.instance
|
19
|
+
@instance ||= new
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(output = $stdout, level: :info, format: :json)
|
23
|
+
@logger = ::Logger.new(output)
|
24
|
+
@logger.level = LOG_LEVELS[level] || ::Logger::INFO
|
25
|
+
@format = format
|
26
|
+
@logger.formatter = method(:format_message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def debug(message, context = {})
|
30
|
+
log(:debug, message, context)
|
31
|
+
end
|
32
|
+
|
33
|
+
def info(message, context = {})
|
34
|
+
log(:info, message, context)
|
35
|
+
end
|
36
|
+
|
37
|
+
def warn(message, context = {})
|
38
|
+
log(:warn, message, context)
|
39
|
+
end
|
40
|
+
|
41
|
+
def error(message, context = {})
|
42
|
+
log(:error, message, context)
|
43
|
+
end
|
44
|
+
|
45
|
+
def fatal(message, context = {})
|
46
|
+
log(:fatal, message, context)
|
47
|
+
end
|
48
|
+
|
49
|
+
def log_request(env, duration: nil, status: nil)
|
50
|
+
context = {
|
51
|
+
method: env['REQUEST_METHOD'],
|
52
|
+
path: env['PATH_INFO'],
|
53
|
+
query: env['QUERY_STRING'],
|
54
|
+
user_agent: env['HTTP_USER_AGENT'],
|
55
|
+
remote_ip: env['REMOTE_ADDR'],
|
56
|
+
duration_ms: duration ? (duration * 1000).round(2) : nil,
|
57
|
+
status: status
|
58
|
+
}.compact
|
59
|
+
|
60
|
+
info("HTTP Request", context)
|
61
|
+
end
|
62
|
+
|
63
|
+
def log_ai_processing(feature, result, duration: nil)
|
64
|
+
context = {
|
65
|
+
feature: feature,
|
66
|
+
action: result[:action],
|
67
|
+
confidence: result[:confidence],
|
68
|
+
duration_ms: duration ? (duration * 1000).round(2) : nil,
|
69
|
+
provider: result[:provider]
|
70
|
+
}.compact
|
71
|
+
|
72
|
+
info("AI Processing", context)
|
73
|
+
end
|
74
|
+
|
75
|
+
def log_error(error, context = {})
|
76
|
+
error_context = {
|
77
|
+
error_class: error.class.name,
|
78
|
+
error_message: error.message,
|
79
|
+
backtrace: error.backtrace&.first(5)
|
80
|
+
}.merge(context)
|
81
|
+
|
82
|
+
error("Application Error", error_context)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def log(level, message, context)
|
88
|
+
@logger.public_send(level) do
|
89
|
+
case @format
|
90
|
+
when :json
|
91
|
+
format_json(level, message, context)
|
92
|
+
when :structured
|
93
|
+
format_structured(level, message, context)
|
94
|
+
else
|
95
|
+
format_simple(level, message, context)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def format_message(severity, datetime, progname, msg)
|
101
|
+
msg
|
102
|
+
end
|
103
|
+
|
104
|
+
def format_json(level, message, context)
|
105
|
+
{
|
106
|
+
timestamp: Time.now.iso8601,
|
107
|
+
level: level.to_s.upcase,
|
108
|
+
message: message,
|
109
|
+
component: 'rack-ai',
|
110
|
+
**context
|
111
|
+
}.to_json + "\n"
|
112
|
+
end
|
113
|
+
|
114
|
+
def format_structured(level, message, context)
|
115
|
+
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
116
|
+
context_str = context.empty? ? '' : " #{context.inspect}"
|
117
|
+
"[#{timestamp}] #{level.to_s.upcase} [rack-ai] #{message}#{context_str}\n"
|
118
|
+
end
|
119
|
+
|
120
|
+
def format_simple(level, message, context)
|
121
|
+
context_str = context.empty? ? '' : " (#{context.map { |k, v| "#{k}=#{v}" }.join(', ')})"
|
122
|
+
"#{message}#{context_str}\n"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Alias for backward compatibility
|
127
|
+
EnhancedLogger = AdvancedLogger
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/lib/rack/ai/utils/logger.rb
CHANGED
@@ -13,23 +13,23 @@ module Rack
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def debug(message, metadata = {})
|
16
|
-
|
16
|
+
puts "[DEBUG] #{message} #{metadata}" if ENV['RACK_AI_DEBUG']
|
17
17
|
end
|
18
18
|
|
19
19
|
def info(message, metadata = {})
|
20
|
-
|
20
|
+
puts "[INFO] #{message} #{metadata}"
|
21
21
|
end
|
22
22
|
|
23
23
|
def warn(message, metadata = {})
|
24
|
-
|
24
|
+
puts "[WARN] #{message} #{metadata}"
|
25
25
|
end
|
26
26
|
|
27
27
|
def error(message, metadata = {})
|
28
|
-
|
28
|
+
puts "[ERROR] #{message} #{metadata}"
|
29
29
|
end
|
30
30
|
|
31
31
|
def fatal(message, metadata = {})
|
32
|
-
|
32
|
+
puts "[FATAL] #{message} #{metadata}"
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
@@ -106,6 +106,7 @@ module Rack
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
end
|
109
|
+
|
109
110
|
end
|
110
111
|
end
|
111
112
|
end
|
data/lib/rack/ai/version.rb
CHANGED
data/lib/rack/ai.rb
CHANGED
@@ -8,15 +8,18 @@ require_relative "ai/providers/openai"
|
|
8
8
|
require_relative "ai/providers/huggingface"
|
9
9
|
require_relative "ai/providers/local"
|
10
10
|
require_relative "ai/features/classification"
|
11
|
+
require_relative "ai/features/security"
|
11
12
|
require_relative "ai/features/moderation"
|
12
13
|
require_relative "ai/features/caching"
|
13
14
|
require_relative "ai/features/routing"
|
14
15
|
require_relative "ai/features/logging"
|
15
16
|
require_relative "ai/features/enhancement"
|
16
|
-
require_relative "ai/features/
|
17
|
+
require_relative "ai/features/rate_limiting"
|
18
|
+
require_relative "ai/features/anomaly_detection"
|
17
19
|
require_relative "ai/utils/logger"
|
18
20
|
require_relative "ai/utils/metrics"
|
19
21
|
require_relative "ai/utils/sanitizer"
|
22
|
+
require_relative "ai/utils/enhanced_logger"
|
20
23
|
|
21
24
|
module Rack
|
22
25
|
module AI
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-ai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ahmet KAHRAMAN
|
@@ -241,15 +241,20 @@ files:
|
|
241
241
|
- ROADMAP.md
|
242
242
|
- Rakefile
|
243
243
|
- benchmarks/performance_benchmark.rb
|
244
|
+
- examples/comprehensive_example.rb
|
244
245
|
- examples/rails_integration.rb
|
246
|
+
- examples/rails_integration_advanced.rb
|
245
247
|
- examples/sinatra_integration.rb
|
248
|
+
- examples/sinatra_microservice.rb
|
246
249
|
- lib/rack/ai.rb
|
247
250
|
- lib/rack/ai/configuration.rb
|
251
|
+
- lib/rack/ai/features/anomaly_detection.rb
|
248
252
|
- lib/rack/ai/features/caching.rb
|
249
253
|
- lib/rack/ai/features/classification.rb
|
250
254
|
- lib/rack/ai/features/enhancement.rb
|
251
255
|
- lib/rack/ai/features/logging.rb
|
252
256
|
- lib/rack/ai/features/moderation.rb
|
257
|
+
- lib/rack/ai/features/rate_limiting.rb
|
253
258
|
- lib/rack/ai/features/routing.rb
|
254
259
|
- lib/rack/ai/features/security.rb
|
255
260
|
- lib/rack/ai/middleware.rb
|
@@ -257,6 +262,7 @@ files:
|
|
257
262
|
- lib/rack/ai/providers/huggingface.rb
|
258
263
|
- lib/rack/ai/providers/local.rb
|
259
264
|
- lib/rack/ai/providers/openai.rb
|
265
|
+
- lib/rack/ai/utils/enhanced_logger.rb
|
260
266
|
- lib/rack/ai/utils/logger.rb
|
261
267
|
- lib/rack/ai/utils/metrics.rb
|
262
268
|
- lib/rack/ai/utils/sanitizer.rb
|