rack-ai 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.
@@ -0,0 +1,458 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Sinatra Integration Example for Rack::AI
4
+ #
5
+ # This example shows how to integrate Rack::AI with a Sinatra application
6
+ # for lightweight AI-powered request processing and security.
7
+
8
+ require "sinatra/base"
9
+ require "rack/ai"
10
+ require "json"
11
+
12
+ class AIEnhancedApp < Sinatra::Base
13
+ # Configure Rack::AI middleware
14
+ use Rack::AI::Middleware,
15
+ provider: :openai,
16
+ api_key: ENV["OPENAI_API_KEY"],
17
+ features: [:classification, :security, :moderation],
18
+ fail_safe: true,
19
+ async_processing: false # Synchronous for simpler Sinatra apps
20
+
21
+ # Global configuration
22
+ configure do
23
+ Rack::AI.configure do |config|
24
+ config.provider = :openai
25
+ config.api_key = ENV["OPENAI_API_KEY"]
26
+ config.features = [:classification, :security, :moderation, :logging]
27
+ config.fail_safe = true
28
+ config.async_processing = false
29
+
30
+ # Simplified configuration for Sinatra
31
+ config.classification.confidence_threshold = 0.7
32
+ config.moderation.toxicity_threshold = 0.8
33
+ config.sanitize_logs = true
34
+ config.log_level = :info
35
+ end
36
+
37
+ # Enable JSON parsing
38
+ set :parse_json_body, true
39
+ end
40
+
41
+ # Helper methods for AI results
42
+ helpers do
43
+ def ai_results
44
+ @ai_results ||= request.env["rack.ai"]&.dig(:results) || {}
45
+ end
46
+
47
+ def classified_as?(type)
48
+ ai_results[:classification]&.dig(:classification) == type.to_sym
49
+ end
50
+
51
+ def threat_level
52
+ ai_results[:security]&.dig(:threat_level) || :low
53
+ end
54
+
55
+ def content_flagged?
56
+ ai_results[:moderation]&.dig(:flagged) || false
57
+ end
58
+
59
+ def ai_confidence(feature)
60
+ ai_results[feature.to_sym]&.dig(:confidence) || 0.0
61
+ end
62
+
63
+ def block_if_threat!
64
+ case threat_level
65
+ when :high
66
+ halt 403, json({ error: "Security threat detected", code: "HIGH_THREAT" })
67
+ when :medium
68
+ headers["X-Security-Warning"] = "Medium threat detected"
69
+ end
70
+ end
71
+
72
+ def block_if_spam!
73
+ if classified_as?(:spam) && ai_confidence(:classification) > 0.8
74
+ halt 403, json({ error: "Request blocked as spam", code: "SPAM_DETECTED" })
75
+ end
76
+ end
77
+
78
+ def json(data)
79
+ content_type :json
80
+ data.to_json
81
+ end
82
+ end
83
+
84
+ # Before filter to check AI results
85
+ before do
86
+ # Block high-security threats immediately
87
+ block_if_threat!
88
+
89
+ # Block obvious spam
90
+ block_if_spam!
91
+
92
+ # Log suspicious activity
93
+ if classified_as?(:suspicious)
94
+ logger.warn "Suspicious request: #{request.ip} -> #{request.path}"
95
+ end
96
+
97
+ # Add AI headers for debugging
98
+ if settings.development?
99
+ headers["X-AI-Classification"] = ai_results[:classification]&.dig(:classification).to_s
100
+ headers["X-AI-Threat-Level"] = threat_level.to_s
101
+ end
102
+ end
103
+
104
+ # Routes
105
+
106
+ # Home page with AI-enhanced content
107
+ get "/" do
108
+ erb :index, locals: {
109
+ classification: ai_results[:classification],
110
+ is_bot: classified_as?(:bot)
111
+ }
112
+ end
113
+
114
+ # API endpoint with AI protection
115
+ get "/api/data" do
116
+ # Different responses based on classification
117
+ case ai_results[:classification]&.dig(:classification)
118
+ when :bot
119
+ # Simplified response for bots
120
+ json({
121
+ message: "Bot-optimized response",
122
+ data: { items: 10, cached: true },
123
+ timestamp: Time.now.iso8601
124
+ })
125
+ when :human
126
+ # Full response for humans
127
+ json({
128
+ message: "Welcome human user!",
129
+ data: generate_full_data,
130
+ metadata: {
131
+ classification: "human",
132
+ confidence: ai_confidence(:classification)
133
+ },
134
+ timestamp: Time.now.iso8601
135
+ })
136
+ else
137
+ # Default response
138
+ json({
139
+ message: "Standard API response",
140
+ data: generate_standard_data,
141
+ timestamp: Time.now.iso8601
142
+ })
143
+ end
144
+ end
145
+
146
+ # Content submission with AI moderation
147
+ post "/api/content" do
148
+ # Check for content moderation violations
149
+ if content_flagged?
150
+ moderation = ai_results[:moderation]
151
+ halt 400, json({
152
+ error: "Content moderation violation",
153
+ categories: moderation[:categories],
154
+ code: "CONTENT_FLAGGED"
155
+ })
156
+ end
157
+
158
+ # Process the content
159
+ content = params[:content] || request.body.read
160
+
161
+ # Store with AI metadata
162
+ result = store_content(content, ai_results)
163
+
164
+ json({
165
+ message: "Content stored successfully",
166
+ id: result[:id],
167
+ ai_analysis: {
168
+ classification: ai_results[:classification]&.dig(:classification),
169
+ moderation_passed: !content_flagged?
170
+ }
171
+ })
172
+ end
173
+
174
+ # Admin endpoint with enhanced security
175
+ get "/admin/*" do
176
+ # Extra security for admin routes
177
+ if threat_level != :low
178
+ halt 403, json({ error: "Access denied due to security concerns" })
179
+ end
180
+
181
+ if classified_as?(:bot) && ai_confidence(:classification) > 0.9
182
+ halt 403, json({ error: "Bot access not allowed to admin area" })
183
+ end
184
+
185
+ # Admin functionality here
186
+ json({ message: "Admin access granted", path: params[:splat].first })
187
+ end
188
+
189
+ # AI insights endpoint
190
+ get "/ai/insights" do
191
+ content_type :json
192
+
193
+ insights = {
194
+ current_request: {
195
+ classification: ai_results[:classification],
196
+ security: ai_results[:security],
197
+ moderation: ai_results[:moderation]
198
+ },
199
+ processing_time: Time.now - request.env["rack.ai"][:start_time],
200
+ features_enabled: Rack::AI.configuration.features
201
+ }
202
+
203
+ insights.to_json
204
+ end
205
+
206
+ # Health check endpoint (bypasses AI processing)
207
+ get "/health" do
208
+ json({ status: "ok", timestamp: Time.now.iso8601 })
209
+ end
210
+
211
+ # Error handlers with AI context
212
+ error 403 do
213
+ ai_context = {
214
+ classification: ai_results[:classification]&.dig(:classification),
215
+ threat_level: threat_level,
216
+ request_id: request.env["rack.ai"]&.dig(:metadata, :request_id)
217
+ }
218
+
219
+ json({
220
+ error: "Forbidden",
221
+ message: "Access denied by AI security system",
222
+ ai_context: ai_context,
223
+ timestamp: Time.now.iso8601
224
+ })
225
+ end
226
+
227
+ error 500 do
228
+ logger.error "Server error with AI context: #{ai_results}"
229
+
230
+ json({
231
+ error: "Internal Server Error",
232
+ message: "An error occurred while processing your request",
233
+ timestamp: Time.now.iso8601
234
+ })
235
+ end
236
+
237
+ # Private helper methods
238
+ private
239
+
240
+ def generate_full_data
241
+ {
242
+ users: [
243
+ { id: 1, name: "Alice", role: "admin" },
244
+ { id: 2, name: "Bob", role: "user" }
245
+ ],
246
+ posts: [
247
+ { id: 1, title: "Welcome Post", author_id: 1 },
248
+ { id: 2, title: "Getting Started", author_id: 2 }
249
+ ],
250
+ metadata: {
251
+ total_users: 2,
252
+ total_posts: 2,
253
+ generated_at: Time.now.iso8601
254
+ }
255
+ }
256
+ end
257
+
258
+ def generate_standard_data
259
+ {
260
+ summary: "Basic data summary",
261
+ count: 42,
262
+ status: "active"
263
+ }
264
+ end
265
+
266
+ def store_content(content, ai_metadata)
267
+ # Simulate content storage with AI metadata
268
+ {
269
+ id: SecureRandom.uuid,
270
+ content: content[0..100], # Store first 100 chars for demo
271
+ ai_metadata: {
272
+ classification: ai_metadata[:classification]&.dig(:classification),
273
+ confidence: ai_metadata[:classification]&.dig(:confidence),
274
+ moderation_passed: !ai_metadata[:moderation]&.dig(:flagged),
275
+ stored_at: Time.now.iso8601
276
+ }
277
+ }
278
+ end
279
+ end
280
+
281
+ # Example of a specialized AI-powered API
282
+ class AISecurityAPI < Sinatra::Base
283
+ # More restrictive AI configuration for security API
284
+ use Rack::AI::Middleware,
285
+ provider: :openai,
286
+ api_key: ENV["OPENAI_API_KEY"],
287
+ features: [:security, :classification],
288
+ fail_safe: false, # Strict mode - fail if AI is unavailable
289
+ async_processing: false
290
+
291
+ configure do
292
+ Rack::AI.configure do |config|
293
+ config.classification.confidence_threshold = 0.9 # Higher threshold
294
+ config.security.rate_limit = 100 # Lower rate limit
295
+ end
296
+ end
297
+
298
+ helpers do
299
+ def require_high_confidence!
300
+ classification = request.env["rack.ai"]&.dig(:results, :classification)
301
+ confidence = classification&.dig(:confidence) || 0.0
302
+
303
+ if confidence < 0.9
304
+ halt 403, { error: "High confidence classification required" }.to_json
305
+ end
306
+ end
307
+
308
+ def security_analysis
309
+ request.env["rack.ai"]&.dig(:results, :security) || {}
310
+ end
311
+ end
312
+
313
+ before do
314
+ content_type :json
315
+ require_high_confidence!
316
+ end
317
+
318
+ get "/security/analyze" do
319
+ {
320
+ security_analysis: security_analysis,
321
+ threat_assessment: {
322
+ level: security_analysis[:threat_level],
323
+ confidence: security_analysis[:confidence],
324
+ recommendations: generate_security_recommendations
325
+ },
326
+ timestamp: Time.now.iso8601
327
+ }.to_json
328
+ end
329
+
330
+ post "/security/report" do
331
+ incident_data = JSON.parse(request.body.read)
332
+
333
+ # Enhanced incident analysis with AI
334
+ analysis = {
335
+ incident: incident_data,
336
+ ai_analysis: security_analysis,
337
+ severity: calculate_severity(incident_data, security_analysis),
338
+ response_required: security_analysis[:threat_level] == :high
339
+ }
340
+
341
+ # Store incident report
342
+ incident_id = store_security_incident(analysis)
343
+
344
+ {
345
+ incident_id: incident_id,
346
+ analysis: analysis,
347
+ timestamp: Time.now.iso8601
348
+ }.to_json
349
+ end
350
+
351
+ private
352
+
353
+ def generate_security_recommendations
354
+ case security_analysis[:threat_level]
355
+ when :high
356
+ ["Block IP immediately", "Review access logs", "Alert security team"]
357
+ when :medium
358
+ ["Monitor closely", "Require additional authentication"]
359
+ else
360
+ ["Continue normal monitoring"]
361
+ end
362
+ end
363
+
364
+ def calculate_severity(incident_data, ai_analysis)
365
+ base_severity = incident_data["severity"] || "low"
366
+ ai_threat = ai_analysis[:threat_level] || :low
367
+
368
+ return "critical" if ai_threat == :high
369
+ return "high" if ai_threat == :medium && base_severity != "low"
370
+
371
+ base_severity
372
+ end
373
+
374
+ def store_security_incident(analysis)
375
+ # Simulate storing security incident
376
+ SecureRandom.uuid
377
+ end
378
+ end
379
+
380
+ # Example Rack application with custom AI middleware
381
+ class CustomAIApp
382
+ def initialize
383
+ @app = Rack::Builder.new do
384
+ # Custom AI configuration
385
+ use Rack::AI::Middleware,
386
+ provider: :huggingface, # Using HuggingFace instead of OpenAI
387
+ api_key: ENV["HUGGINGFACE_API_KEY"],
388
+ features: [:classification, :moderation],
389
+ fail_safe: true
390
+
391
+ # Custom middleware that uses AI results
392
+ use CustomAIProcessor
393
+
394
+ # Main application
395
+ run lambda { |env|
396
+ ai_results = env["rack.ai"]&.dig(:results) || {}
397
+
398
+ response_body = {
399
+ message: "Hello from Custom AI App!",
400
+ ai_results: ai_results,
401
+ timestamp: Time.now.iso8601
402
+ }.to_json
403
+
404
+ [200, { "Content-Type" => "application/json" }, [response_body]]
405
+ }
406
+ end
407
+ end
408
+
409
+ def call(env)
410
+ @app.call(env)
411
+ end
412
+ end
413
+
414
+ # Custom middleware that processes AI results
415
+ class CustomAIProcessor
416
+ def initialize(app)
417
+ @app = app
418
+ end
419
+
420
+ def call(env)
421
+ status, headers, body = @app.call(env)
422
+
423
+ # Add custom AI processing
424
+ ai_results = env["rack.ai"]&.dig(:results) || {}
425
+
426
+ # Custom header based on AI classification
427
+ if ai_results[:classification]
428
+ classification = ai_results[:classification][:classification]
429
+ headers["X-Custom-AI-Class"] = classification.to_s.upcase
430
+
431
+ # Custom rate limiting based on classification
432
+ if classification == :bot
433
+ headers["X-RateLimit-Remaining"] = "100"
434
+ elsif classification == :human
435
+ headers["X-RateLimit-Remaining"] = "1000"
436
+ end
437
+ end
438
+
439
+ [status, headers, body]
440
+ end
441
+ end
442
+
443
+ # Run the application
444
+ if __FILE__ == $0
445
+ # Choose which app to run based on environment variable
446
+ app_type = ENV["APP_TYPE"] || "main"
447
+
448
+ app = case app_type
449
+ when "security"
450
+ AISecurityAPI
451
+ when "custom"
452
+ CustomAIApp.new
453
+ else
454
+ AIEnhancedApp
455
+ end
456
+
457
+ Rack::Handler::WEBrick.run app, Port: 4567
458
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-configurable"
4
+ require "dry-validation"
5
+ require "ostruct"
6
+
7
+ module Rack
8
+ module AI
9
+ class Configuration
10
+ extend Dry::Configurable
11
+
12
+ # AI Provider settings
13
+ setting :provider, default: :openai
14
+ setting :api_key, default: nil
15
+ setting :api_url, default: nil
16
+ setting :timeout, default: 30
17
+ setting :retries, default: 3
18
+
19
+ # Feature toggles
20
+ setting :features, default: [:classification, :moderation]
21
+ setting :fail_safe, default: true
22
+ setting :async_processing, default: true
23
+
24
+ # Security settings
25
+ setting :sanitize_logs, default: true
26
+ setting :allowed_data_types, default: [:headers, :query_params]
27
+ setting :blocked_data_types, default: [:body, :cookies]
28
+
29
+ # Performance settings
30
+ setting :cache_enabled, default: true
31
+ setting :cache_ttl, default: 3600
32
+ setting :rate_limit, default: 1000
33
+ setting :batch_size, default: 10
34
+
35
+ # Logging and monitoring
36
+ setting :log_level, default: :info
37
+ setting :metrics_enabled, default: true
38
+ setting :explain_decisions, default: false
39
+
40
+ # Custom configurations per feature
41
+ setting :classification do
42
+ setting :confidence_threshold, default: 0.8
43
+ setting :categories, default: [:spam, :bot, :human, :suspicious]
44
+ end
45
+
46
+ setting :moderation do
47
+ setting :toxicity_threshold, default: 0.7
48
+ setting :check_response, default: false
49
+ setting :block_on_violation, default: true
50
+ end
51
+
52
+ setting :caching do
53
+ setting :predictive_enabled, default: true
54
+ setting :prefetch_threshold, default: 0.9
55
+ setting :redis_url, default: "redis://localhost:6379"
56
+ end
57
+
58
+ setting :routing do
59
+ setting :smart_routing_enabled, default: true
60
+ setting :suspicious_route, default: "/captcha"
61
+ setting :bot_route, default: "/api/bot"
62
+ end
63
+
64
+ # Instance methods for configuration access
65
+ attr_accessor :provider, :api_key, :api_url, :timeout, :retries, :features,
66
+ :fail_safe, :async_processing, :sanitize_logs, :allowed_data_types,
67
+ :blocked_data_types, :cache_enabled, :cache_ttl, :rate_limit,
68
+ :batch_size, :log_level, :metrics_enabled, :explain_decisions
69
+
70
+ def initialize
71
+ # Set default values
72
+ @provider = :openai
73
+ @api_key = nil
74
+ @api_url = nil
75
+ @timeout = 30
76
+ @retries = 3
77
+ @features = [:classification, :moderation]
78
+ @fail_safe = true
79
+ @async_processing = true
80
+ @sanitize_logs = true
81
+ @allowed_data_types = [:headers, :query_params]
82
+ @blocked_data_types = [:body, :cookies]
83
+ @cache_enabled = true
84
+ @cache_ttl = 3600
85
+ @rate_limit = 1000
86
+ @batch_size = 10
87
+ @log_level = :info
88
+ @metrics_enabled = true
89
+ @explain_decisions = false
90
+
91
+ # Initialize nested configurations
92
+ @classification = OpenStruct.new(
93
+ confidence_threshold: 0.8,
94
+ categories: [:spam, :bot, :human, :suspicious]
95
+ )
96
+
97
+ @moderation = OpenStruct.new(
98
+ toxicity_threshold: 0.7,
99
+ check_response: false,
100
+ block_on_violation: true
101
+ )
102
+
103
+ @caching = OpenStruct.new(
104
+ predictive_enabled: true,
105
+ prefetch_threshold: 0.9,
106
+ redis_url: "redis://localhost:6379"
107
+ )
108
+
109
+ @routing = OpenStruct.new(
110
+ smart_routing_enabled: true,
111
+ suspicious_route: "/captcha",
112
+ bot_route: "/api/bot"
113
+ )
114
+ end
115
+
116
+ def classification
117
+ @classification
118
+ end
119
+
120
+ def moderation
121
+ @moderation
122
+ end
123
+
124
+ def caching
125
+ @caching
126
+ end
127
+
128
+ def routing
129
+ @routing
130
+ end
131
+
132
+ # For compatibility with middleware
133
+ def config
134
+ self
135
+ end
136
+
137
+ # Validation schema
138
+ ConfigSchema = Dry::Validation.Contract do
139
+ params do
140
+ required(:provider).filled(:symbol)
141
+ optional(:api_key).maybe(:string)
142
+ optional(:api_url).maybe(:string)
143
+ required(:timeout).filled(:integer)
144
+ required(:retries).filled(:integer)
145
+ required(:features).filled(:array)
146
+ required(:fail_safe).filled(:bool)
147
+ end
148
+
149
+ rule(:provider) do
150
+ key.failure("must be one of: openai, huggingface, local") unless [:openai, :huggingface, :local].include?(value)
151
+ end
152
+
153
+ rule(:timeout) do
154
+ key.failure("must be positive") if value <= 0
155
+ end
156
+
157
+ rule(:retries) do
158
+ key.failure("must be non-negative") if value < 0
159
+ end
160
+ end
161
+
162
+ def validate!
163
+ result = ConfigSchema.call(to_h)
164
+ return true if result.success?
165
+
166
+ errors = result.errors.to_h
167
+ raise ConfigurationError, "Invalid configuration: #{errors}"
168
+ end
169
+
170
+ def to_h
171
+ {
172
+ provider: @provider,
173
+ api_key: @api_key,
174
+ api_url: @api_url,
175
+ timeout: @timeout,
176
+ retries: @retries,
177
+ features: @features,
178
+ fail_safe: @fail_safe,
179
+ async_processing: @async_processing,
180
+ sanitize_logs: @sanitize_logs,
181
+ allowed_data_types: @allowed_data_types,
182
+ blocked_data_types: @blocked_data_types,
183
+ cache_enabled: @cache_enabled,
184
+ cache_ttl: @cache_ttl,
185
+ rate_limit: @rate_limit,
186
+ batch_size: @batch_size,
187
+ log_level: @log_level,
188
+ metrics_enabled: @metrics_enabled,
189
+ explain_decisions: @explain_decisions
190
+ }
191
+ end
192
+
193
+ def feature_enabled?(feature)
194
+ @features.include?(feature.to_sym)
195
+ end
196
+
197
+ def provider_config
198
+ {
199
+ provider: @provider,
200
+ api_key: @api_key,
201
+ api_url: @api_url,
202
+ timeout: @timeout,
203
+ retries: @retries
204
+ }
205
+ end
206
+ end
207
+ end
208
+ end