desiru 0.1.0 → 0.1.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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +34 -0
  3. data/.rubocop.yml +7 -4
  4. data/.ruby-version +1 -0
  5. data/CLAUDE.md +4 -0
  6. data/Gemfile +21 -2
  7. data/Gemfile.lock +87 -12
  8. data/README.md +295 -2
  9. data/Rakefile +1 -0
  10. data/db/migrations/001_create_initial_tables.rb +96 -0
  11. data/db/migrations/002_create_job_results.rb +39 -0
  12. data/desiru.db +0 -0
  13. data/desiru.gemspec +2 -5
  14. data/docs/background_processing_roadmap.md +87 -0
  15. data/docs/job_scheduling.md +167 -0
  16. data/dspy-analysis-swarm.yml +60 -0
  17. data/dspy-feature-analysis.md +121 -0
  18. data/examples/README.md +69 -0
  19. data/examples/api_with_persistence.rb +122 -0
  20. data/examples/assertions_example.rb +232 -0
  21. data/examples/async_processing.rb +2 -0
  22. data/examples/few_shot_learning.rb +1 -2
  23. data/examples/graphql_api.rb +4 -2
  24. data/examples/graphql_integration.rb +3 -3
  25. data/examples/graphql_optimization_summary.md +143 -0
  26. data/examples/graphql_performance_benchmark.rb +247 -0
  27. data/examples/persistence_example.rb +102 -0
  28. data/examples/react_agent.rb +203 -0
  29. data/examples/rest_api.rb +173 -0
  30. data/examples/rest_api_advanced.rb +333 -0
  31. data/examples/scheduled_job_example.rb +116 -0
  32. data/examples/simple_qa.rb +1 -2
  33. data/examples/sinatra_api.rb +109 -0
  34. data/examples/typed_signatures.rb +1 -2
  35. data/graphql_optimization_summary.md +53 -0
  36. data/lib/desiru/api/grape_integration.rb +284 -0
  37. data/lib/desiru/api/persistence_middleware.rb +148 -0
  38. data/lib/desiru/api/sinatra_integration.rb +217 -0
  39. data/lib/desiru/api.rb +42 -0
  40. data/lib/desiru/assertions.rb +74 -0
  41. data/lib/desiru/async_status.rb +65 -0
  42. data/lib/desiru/cache.rb +1 -1
  43. data/lib/desiru/configuration.rb +2 -1
  44. data/lib/desiru/errors.rb +160 -0
  45. data/lib/desiru/field.rb +17 -14
  46. data/lib/desiru/graphql/batch_loader.rb +85 -0
  47. data/lib/desiru/graphql/data_loader.rb +242 -75
  48. data/lib/desiru/graphql/enum_builder.rb +75 -0
  49. data/lib/desiru/graphql/executor.rb +37 -4
  50. data/lib/desiru/graphql/schema_generator.rb +62 -158
  51. data/lib/desiru/graphql/type_builder.rb +138 -0
  52. data/lib/desiru/graphql/type_cache_warmer.rb +91 -0
  53. data/lib/desiru/jobs/async_predict.rb +1 -1
  54. data/lib/desiru/jobs/base.rb +67 -0
  55. data/lib/desiru/jobs/batch_processor.rb +6 -6
  56. data/lib/desiru/jobs/retriable.rb +119 -0
  57. data/lib/desiru/jobs/retry_strategies.rb +169 -0
  58. data/lib/desiru/jobs/scheduler.rb +219 -0
  59. data/lib/desiru/jobs/webhook_notifier.rb +242 -0
  60. data/lib/desiru/models/anthropic.rb +164 -0
  61. data/lib/desiru/models/base.rb +37 -3
  62. data/lib/desiru/models/open_ai.rb +151 -0
  63. data/lib/desiru/models/open_router.rb +161 -0
  64. data/lib/desiru/module.rb +59 -9
  65. data/lib/desiru/modules/chain_of_thought.rb +3 -3
  66. data/lib/desiru/modules/majority.rb +51 -0
  67. data/lib/desiru/modules/multi_chain_comparison.rb +204 -0
  68. data/lib/desiru/modules/predict.rb +8 -1
  69. data/lib/desiru/modules/program_of_thought.rb +139 -0
  70. data/lib/desiru/modules/react.rb +273 -0
  71. data/lib/desiru/modules/retrieve.rb +4 -2
  72. data/lib/desiru/optimizers/base.rb +2 -4
  73. data/lib/desiru/optimizers/bootstrap_few_shot.rb +2 -2
  74. data/lib/desiru/optimizers/copro.rb +268 -0
  75. data/lib/desiru/optimizers/knn_few_shot.rb +185 -0
  76. data/lib/desiru/persistence/database.rb +71 -0
  77. data/lib/desiru/persistence/models/api_request.rb +38 -0
  78. data/lib/desiru/persistence/models/job_result.rb +138 -0
  79. data/lib/desiru/persistence/models/module_execution.rb +37 -0
  80. data/lib/desiru/persistence/models/optimization_result.rb +28 -0
  81. data/lib/desiru/persistence/models/training_example.rb +25 -0
  82. data/lib/desiru/persistence/models.rb +11 -0
  83. data/lib/desiru/persistence/repositories/api_request_repository.rb +98 -0
  84. data/lib/desiru/persistence/repositories/base_repository.rb +77 -0
  85. data/lib/desiru/persistence/repositories/job_result_repository.rb +116 -0
  86. data/lib/desiru/persistence/repositories/module_execution_repository.rb +85 -0
  87. data/lib/desiru/persistence/repositories/optimization_result_repository.rb +67 -0
  88. data/lib/desiru/persistence/repositories/training_example_repository.rb +102 -0
  89. data/lib/desiru/persistence/repository.rb +29 -0
  90. data/lib/desiru/persistence/setup.rb +77 -0
  91. data/lib/desiru/persistence.rb +49 -0
  92. data/lib/desiru/registry.rb +3 -5
  93. data/lib/desiru/signature.rb +91 -24
  94. data/lib/desiru/version.rb +1 -1
  95. data/lib/desiru.rb +23 -8
  96. data/missing-features-analysis.md +192 -0
  97. metadata +63 -45
  98. data/lib/desiru/models/raix_adapter.rb +0 -210
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'desiru'
6
+ require 'rack'
7
+ require 'rack/handler/webrick'
8
+
9
+ # This example demonstrates how to create a REST API using Desiru's Grape integration
10
+
11
+ # Configure Desiru
12
+ Desiru.configure do |config|
13
+ config.default_model = Desiru::Models::OpenAI.new(
14
+ api_key: ENV['OPENAI_API_KEY'] || 'your-api-key',
15
+ model: 'gpt-3.5-turbo'
16
+ )
17
+ end
18
+
19
+ # Create some Desiru modules
20
+
21
+ # Simple Q&A module
22
+ qa_module = Desiru::Modules::Predict.new(
23
+ Desiru::Signature.new(
24
+ 'question: string -> answer: string',
25
+ descriptions: {
26
+ question: 'The question to answer',
27
+ answer: 'The generated answer'
28
+ }
29
+ )
30
+ )
31
+
32
+ # Summarization module
33
+ summarizer = Desiru::Modules::ChainOfThought.new(
34
+ Desiru::Signature.new(
35
+ 'text: string, max_words: int -> summary: string, key_points: list[str]',
36
+ descriptions: {
37
+ text: 'The text to summarize',
38
+ max_words: 'Maximum words in the summary',
39
+ summary: 'A concise summary',
40
+ key_points: 'Key points from the text'
41
+ }
42
+ )
43
+ )
44
+
45
+ # Classification module
46
+ classifier = Desiru::Modules::Predict.new(
47
+ Desiru::Signature.new(
48
+ "text: string -> category: Literal['technical', 'business', 'general'], confidence: float",
49
+ descriptions: {
50
+ text: 'Text to classify',
51
+ category: 'The category of the text',
52
+ confidence: 'Confidence score (0-1)'
53
+ }
54
+ )
55
+ )
56
+
57
+ # Create the API
58
+ api = Desiru::API.create(async_enabled: true, stream_enabled: true) do
59
+ # Register modules with their endpoints
60
+ register_module '/qa', qa_module,
61
+ description: 'Answer questions using AI'
62
+
63
+ register_module '/summarize', summarizer,
64
+ description: 'Summarize text with key points extraction'
65
+
66
+ register_module '/classify', classifier,
67
+ description: 'Classify text into categories'
68
+ end
69
+
70
+ # Create a Rack app
71
+ app = api.to_rack_app
72
+
73
+ # Add some middleware for logging
74
+ logged_app = Rack::Builder.new do
75
+ use Rack::Logger
76
+ use Rack::CommonLogger
77
+
78
+ map '/' do
79
+ run proc { |env|
80
+ if env['PATH_INFO'] == '/'
81
+ [200, { 'Content-Type' => 'text/html' }, [<<~HTML]]
82
+ <!DOCTYPE html>
83
+ <html>
84
+ <head>
85
+ <title>Desiru REST API</title>
86
+ <style>
87
+ body { font-family: Arial, sans-serif; margin: 40px; }
88
+ h1 { color: #333; }
89
+ .endpoint { background: #f4f4f4; padding: 10px; margin: 10px 0; border-radius: 5px; }
90
+ code { background: #e0e0e0; padding: 2px 5px; border-radius: 3px; }
91
+ </style>
92
+ </head>
93
+ <body>
94
+ <h1>Desiru REST API</h1>
95
+ <p>Welcome to the Desiru REST API powered by Grape!</p>
96
+ #{' '}
97
+ <h2>Available Endpoints:</h2>
98
+ #{' '}
99
+ <div class="endpoint">
100
+ <h3>POST /api/v1/qa</h3>
101
+ <p>Answer questions using AI</p>
102
+ <p>Parameters: <code>question</code> (string), <code>async</code> (boolean, optional)</p>
103
+ </div>
104
+ #{' '}
105
+ <div class="endpoint">
106
+ <h3>POST /api/v1/summarize</h3>
107
+ <p>Summarize text with key points extraction</p>
108
+ <p>Parameters: <code>text</code> (string), <code>max_words</code> (integer), <code>async</code> (boolean, optional)</p>
109
+ </div>
110
+ #{' '}
111
+ <div class="endpoint">
112
+ <h3>POST /api/v1/classify</h3>
113
+ <p>Classify text into categories</p>
114
+ <p>Parameters: <code>text</code> (string), <code>async</code> (boolean, optional)</p>
115
+ </div>
116
+ #{' '}
117
+ <div class="endpoint">
118
+ <h3>GET /api/v1/jobs/:id</h3>
119
+ <p>Check status of async jobs</p>
120
+ </div>
121
+ #{' '}
122
+ <div class="endpoint">
123
+ <h3>POST /api/v1/stream/*</h3>
124
+ <p>Streaming versions of all endpoints (Server-Sent Events)</p>
125
+ </div>
126
+ #{' '}
127
+ <h2>Example Usage:</h2>
128
+ <pre>
129
+ # Synchronous request
130
+ curl -X POST http://localhost:9292/api/v1/qa \\
131
+ -H "Content-Type: application/json" \\
132
+ -d '{"question": "What is Ruby?"}'
133
+
134
+ # Async request
135
+ curl -X POST http://localhost:9292/api/v1/summarize \\
136
+ -H "Content-Type: application/json" \\
137
+ -d '{"text": "Long text here...", "max_words": 100, "async": true}'
138
+
139
+ # Check job status
140
+ curl http://localhost:9292/api/v1/jobs/JOB_ID
141
+
142
+ # Streaming request
143
+ curl -X POST http://localhost:9292/api/v1/stream/qa \\
144
+ -H "Content-Type: application/json" \\
145
+ -H "Accept: text/event-stream" \\
146
+ -d '{"question": "What is Ruby?"}'
147
+ </pre>
148
+ </body>
149
+ </html>
150
+ HTML
151
+ else
152
+ [404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
153
+ end
154
+ }
155
+ end
156
+
157
+ run app
158
+ end
159
+
160
+ # Start the server
161
+ if __FILE__ == $PROGRAM_NAME
162
+ puts "Starting Desiru REST API server..."
163
+ puts "Visit http://localhost:9292 for documentation"
164
+ puts "API endpoints available at http://localhost:9292/api/v1/*"
165
+ puts "Press Ctrl+C to stop"
166
+
167
+ # Run the server
168
+ Rack::Server.start(
169
+ app: logged_app,
170
+ Port: ENV['PORT'] || 9292,
171
+ Host: '0.0.0.0'
172
+ )
173
+ end
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'desiru'
6
+ require 'rack'
7
+ require 'rack/handler/webrick'
8
+ require 'rack/throttle'
9
+ require 'jwt'
10
+
11
+ # Advanced REST API example with authentication, rate limiting, and custom middleware
12
+
13
+ # Configure Desiru
14
+ Desiru.configure do |config|
15
+ config.default_model = Desiru::Models::OpenAI.new(
16
+ api_key: ENV['OPENAI_API_KEY'] || 'your-api-key',
17
+ model: 'gpt-3.5-turbo'
18
+ )
19
+ config.redis_url = ENV['REDIS_URL'] || 'redis://localhost:6379'
20
+ end
21
+
22
+ # Custom authentication middleware
23
+ class AuthMiddleware
24
+ def initialize(app)
25
+ @app = app
26
+ @secret = ENV['JWT_SECRET'] || 'your-secret-key'
27
+ end
28
+
29
+ def call(env)
30
+ # Skip auth for health check and root
31
+ return @app.call(env) if env['PATH_INFO'] =~ %r{^(/|/api/v1/health)$}
32
+
33
+ auth_header = env['HTTP_AUTHORIZATION']
34
+
35
+ if auth_header&.start_with?('Bearer ')
36
+ token = auth_header.split[1]
37
+
38
+ begin
39
+ payload = JWT.decode(token, @secret, true, algorithm: 'HS256')[0]
40
+ env['current_user'] = payload['user_id']
41
+ env['user_tier'] = payload['tier'] || 'free'
42
+ @app.call(env)
43
+ rescue JWT::DecodeError
44
+ [401, { 'Content-Type' => 'application/json' }, [{ error: 'Invalid token' }.to_json]]
45
+ end
46
+ else
47
+ [401, { 'Content-Type' => 'application/json' }, [{ error: 'Authentication required' }.to_json]]
48
+ end
49
+ end
50
+ end
51
+
52
+ # Custom rate limiter based on user tier
53
+ class TieredRateLimiter < Rack::Throttle::Hourly
54
+ def initialize(app, options = {})
55
+ super
56
+ end
57
+
58
+ def max_per_window(request)
59
+ case request.env['user_tier']
60
+ when 'premium'
61
+ 1000
62
+ when 'standard'
63
+ 100
64
+ else
65
+ 10 # free tier
66
+ end
67
+ end
68
+
69
+ def client_identifier(request)
70
+ request.env['current_user'] || 'anonymous'
71
+ end
72
+ end
73
+
74
+ # Create advanced Desiru modules with validation
75
+
76
+ # Enhanced Q&A module with context
77
+ Desiru::Modules::ChainOfThought.new(
78
+ Desiru::Signature.new(
79
+ 'question: string, context: list[str] -> answer: string, sources: list[int], confidence: float',
80
+ descriptions: {
81
+ question: 'The question to answer',
82
+ context: 'Optional context documents',
83
+ answer: 'The generated answer',
84
+ sources: 'Indices of context documents used',
85
+ confidence: 'Answer confidence (0-1)'
86
+ }
87
+ )
88
+ )
89
+
90
+ # Multi-language translation
91
+ Desiru::Modules::Predict.new(
92
+ Desiru::Signature.new(
93
+ "text: string, target_language: Literal['es', 'fr', 'de', 'ja', 'zh'] -> " \
94
+ "translation: string, detected_language: string",
95
+ descriptions: {
96
+ text: 'Text to translate',
97
+ target_language: 'Target language code',
98
+ translation: 'Translated text',
99
+ detected_language: 'Detected source language'
100
+ }
101
+ )
102
+ )
103
+
104
+ # Code analysis module
105
+ Desiru::Modules::ChainOfThought.new(
106
+ Desiru::Signature.new(
107
+ 'code: string, language: string -> issues: list[str], suggestions: list[str], complexity: int',
108
+ descriptions: {
109
+ code: 'Source code to analyze',
110
+ language: 'Programming language',
111
+ issues: 'Potential issues found',
112
+ suggestions: 'Improvement suggestions',
113
+ complexity: 'Cyclomatic complexity estimate'
114
+ }
115
+ )
116
+ )
117
+
118
+ # Custom Grape API with enhanced features
119
+ class AdvancedAPI < Grape::API
120
+ format :json
121
+ prefix :api
122
+ version 'v1', using: :path
123
+
124
+ # Global exception handling
125
+ rescue_from :all do |e|
126
+ error!({ error: 'Internal server error', message: e.message }, 500)
127
+ end
128
+
129
+ rescue_from Grape::Exceptions::ValidationErrors do |e|
130
+ error!({ error: 'Validation failed', details: e.full_messages }, 422)
131
+ end
132
+
133
+ helpers do
134
+ def current_user
135
+ env['current_user']
136
+ end
137
+
138
+ def user_tier
139
+ env['user_tier'] || 'free'
140
+ end
141
+
142
+ def check_tier_access(required_tier)
143
+ tier_levels = { 'free' => 0, 'standard' => 1, 'premium' => 2 }
144
+
145
+ return unless tier_levels[user_tier] < tier_levels[required_tier]
146
+
147
+ error!({ error: "This endpoint requires #{required_tier} tier or higher" }, 403)
148
+ end
149
+
150
+ def log_usage(endpoint, _params)
151
+ # Log API usage for analytics
152
+ puts "[API Usage] User: #{current_user}, Endpoint: #{endpoint}, Tier: #{user_tier}"
153
+ end
154
+ end
155
+
156
+ before do
157
+ # Log all requests
158
+ log_usage(request.path, params)
159
+ end
160
+
161
+ # Health check with system info
162
+ desc 'Health check with system information'
163
+ get '/health' do
164
+ {
165
+ status: 'ok',
166
+ timestamp: Time.now.iso8601,
167
+ version: Desiru::VERSION,
168
+ redis_connected: Desiru.redis_connected?,
169
+ user_tier: user_tier
170
+ }
171
+ end
172
+
173
+ # Q&A endpoint with context support
174
+ desc 'Answer questions with optional context'
175
+ params do
176
+ requires :question, type: String, desc: 'Question to answer'
177
+ optional :context, type: [String], desc: 'Context documents'
178
+ optional :async, type: Boolean, desc: 'Process asynchronously'
179
+ end
180
+ post '/qa' do
181
+ inputs = {
182
+ question: params[:question],
183
+ context: params[:context] || []
184
+ }
185
+
186
+ if params[:async]
187
+ result = qa_with_context.call_async(inputs)
188
+ {
189
+ job_id: result.job_id,
190
+ status_url: "/api/v1/jobs/#{result.job_id}"
191
+ }
192
+ else
193
+ result = qa_with_context.call(inputs)
194
+ result.merge(user_tier: user_tier)
195
+ end
196
+ end
197
+
198
+ # Translation endpoint (premium feature)
199
+ desc 'Translate text (Premium tier required)'
200
+ params do
201
+ requires :text, type: String, desc: 'Text to translate'
202
+ requires :target_language, type: String, values: %w[es fr de ja zh]
203
+ end
204
+ post '/translate' do
205
+ check_tier_access('premium')
206
+
207
+ result = translator.call(
208
+ text: params[:text],
209
+ target_language: params[:target_language]
210
+ )
211
+
212
+ result
213
+ end
214
+
215
+ # Code analysis endpoint
216
+ desc 'Analyze code for issues and improvements'
217
+ params do
218
+ requires :code, type: String, desc: 'Source code'
219
+ requires :language, type: String, desc: 'Programming language'
220
+ end
221
+ post '/analyze-code' do
222
+ check_tier_access('standard')
223
+
224
+ result = code_analyzer.call(
225
+ code: params[:code],
226
+ language: params[:language]
227
+ )
228
+
229
+ result
230
+ end
231
+
232
+ # Batch processing endpoint
233
+ desc 'Process multiple requests in batch'
234
+ params do
235
+ requires :requests, type: Array do
236
+ requires :endpoint, type: String, values: ['/qa', '/translate', '/analyze-code']
237
+ requires :params, type: Hash
238
+ end
239
+ end
240
+ post '/batch' do
241
+ check_tier_access('premium')
242
+
243
+ max_batch_size = 10
244
+ error!({ error: "Batch size exceeds maximum of #{max_batch_size}" }, 422) if params[:requests].size > max_batch_size
245
+
246
+ # Process batch asynchronously
247
+ batch_job_id = SecureRandom.uuid
248
+
249
+ # In a real implementation, this would queue a background job
250
+ {
251
+ batch_id: batch_job_id,
252
+ request_count: params[:requests].size,
253
+ status_url: "/api/v1/batches/#{batch_job_id}"
254
+ }
255
+ end
256
+
257
+ # Admin endpoints
258
+ namespace :admin do
259
+ before do
260
+ # Extra admin check
261
+ error!({ error: 'Admin access required' }, 403) unless current_user&.start_with?('admin_')
262
+ end
263
+
264
+ desc 'Get usage statistics'
265
+ get '/stats' do
266
+ {
267
+ total_requests: 12_345,
268
+ requests_today: 543,
269
+ active_users: 89,
270
+ avg_response_time: 1.23
271
+ }
272
+ end
273
+ end
274
+ end
275
+
276
+ # Create the application
277
+ app = Rack::Builder.new do
278
+ # Logging
279
+ use Rack::Logger
280
+ use Rack::CommonLogger
281
+
282
+ # CORS support
283
+ use Rack::Cors do
284
+ allow do
285
+ origins '*'
286
+ resource '*',
287
+ headers: :any,
288
+ methods: %i[get post put delete options],
289
+ expose: %w[X-Rate-Limit-Remaining X-Rate-Limit-Reset]
290
+ end
291
+ end
292
+
293
+ # Authentication
294
+ use AuthMiddleware
295
+
296
+ # Rate limiting
297
+ use TieredRateLimiter,
298
+ cache: Desiru.redis,
299
+ key_prefix: :throttle
300
+
301
+ # Mount the API
302
+ run AdvancedAPI
303
+ end
304
+
305
+ # Utility to generate JWT tokens for testing
306
+ def generate_token(user_id, tier = 'free')
307
+ secret = ENV['JWT_SECRET'] || 'your-secret-key'
308
+ payload = {
309
+ user_id: user_id,
310
+ tier: tier,
311
+ iat: Time.now.to_i,
312
+ exp: Time.now.to_i + 3600 # 1 hour expiration
313
+ }
314
+ JWT.encode(payload, secret, 'HS256')
315
+ end
316
+
317
+ if __FILE__ == $PROGRAM_NAME
318
+ puts "Starting Advanced Desiru REST API server..."
319
+ puts "\nExample tokens for testing:"
320
+ puts "Free tier: Bearer #{generate_token('user123', 'free')}"
321
+ puts "Standard tier: Bearer #{generate_token('user456', 'standard')}"
322
+ puts "Premium tier: Bearer #{generate_token('user789', 'premium')}"
323
+ puts "Admin: Bearer #{generate_token('admin_001', 'premium')}"
324
+ puts "\nExample requests:"
325
+ puts "curl -H 'Authorization: Bearer TOKEN' http://localhost:9292/api/v1/health"
326
+ puts "\nPress Ctrl+C to stop"
327
+
328
+ Rack::Server.start(
329
+ app: app,
330
+ Port: ENV['PORT'] || 9292,
331
+ Host: '0.0.0.0'
332
+ )
333
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'desiru'
4
+ require 'desiru/jobs/scheduler'
5
+
6
+ # Example of a scheduled job that performs periodic tasks
7
+ class CleanupJob < Desiru::Jobs::Base
8
+ include Desiru::Jobs::Schedulable
9
+
10
+ def perform(job_id = nil)
11
+ # Simulate cleanup work
12
+ puts "[#{Time.now}] Running cleanup job #{job_id}"
13
+
14
+ # Example: Clean up old job results
15
+ expired_count = cleanup_expired_results
16
+
17
+ # Store the result
18
+ store_result(job_id || "cleanup-#{Time.now.to_i}", {
19
+ status: 'completed',
20
+ expired_count: expired_count,
21
+ timestamp: Time.now.to_s
22
+ })
23
+ end
24
+
25
+ private
26
+
27
+ def cleanup_expired_results
28
+ # Simulate cleanup logic
29
+ rand(0..10)
30
+ end
31
+ end
32
+
33
+ # Example of a report generation job
34
+ class DailyReportJob < Desiru::Jobs::Base
35
+ include Desiru::Jobs::Schedulable
36
+
37
+ def perform(job_id = nil, report_type = 'summary')
38
+ puts "[#{Time.now}] Generating #{report_type} report"
39
+
40
+ # Simulate report generation
41
+ report_data = generate_report(report_type)
42
+
43
+ store_result(job_id || "report-#{Time.now.to_i}", {
44
+ status: 'completed',
45
+ type: report_type,
46
+ data: report_data,
47
+ generated_at: Time.now.to_s
48
+ })
49
+ end
50
+
51
+ private
52
+
53
+ def generate_report(type)
54
+ # Simulate report generation
55
+ {
56
+ total_jobs: rand(100..1000),
57
+ successful: rand(80..100),
58
+ failed: rand(0..20),
59
+ report_type: type
60
+ }
61
+ end
62
+ end
63
+
64
+ # Example usage
65
+ if __FILE__ == $PROGRAM_NAME
66
+ scheduler = Desiru::Jobs::Scheduler.instance
67
+
68
+ # Schedule cleanup job to run every 30 seconds
69
+ CleanupJob.schedule(cron: '30')
70
+
71
+ # Schedule cleanup with a custom name
72
+ CleanupJob.schedule(name: 'hourly_cleanup', cron: 'every 1 hour')
73
+
74
+ # Schedule daily report at 9:00 AM
75
+ DailyReportJob.schedule(
76
+ name: 'morning_report',
77
+ cron: '0 9 * * *',
78
+ args: ['detailed']
79
+ )
80
+
81
+ # Schedule summary report every 5 minutes
82
+ DailyReportJob.schedule(
83
+ name: 'summary_report',
84
+ cron: 'every 5 minutes',
85
+ args: ['summary']
86
+ )
87
+
88
+ # Check scheduled jobs
89
+ puts "Jobs scheduled:"
90
+ puts "- CleanupJob: #{CleanupJob.scheduled?}"
91
+ puts "- Hourly Cleanup: #{scheduler.job_info('hourly_cleanup') ? 'Yes' : 'No'}"
92
+ puts "- Morning Report: #{scheduler.job_info('morning_report') ? 'Yes' : 'No'}"
93
+ puts "- Summary Report: #{scheduler.job_info('summary_report') ? 'Yes' : 'No'}"
94
+
95
+ # Start the scheduler
96
+ puts "\nStarting scheduler..."
97
+ scheduler.start
98
+
99
+ # Run for demonstration (in production, this would run continuously)
100
+ puts "Scheduler running. Press Ctrl+C to stop."
101
+
102
+ begin
103
+ sleep
104
+ rescue Interrupt
105
+ puts "\nStopping scheduler..."
106
+ scheduler.stop
107
+
108
+ # Optionally unschedule jobs
109
+ CleanupJob.unschedule
110
+ CleanupJob.unschedule(name: 'hourly_cleanup')
111
+ DailyReportJob.unschedule(name: 'morning_report')
112
+ DailyReportJob.unschedule(name: 'summary_report')
113
+
114
+ puts "Scheduler stopped."
115
+ end
116
+ end
@@ -6,8 +6,7 @@ require 'desiru'
6
6
 
7
7
  # Configure Desiru with OpenAI model
8
8
  Desiru.configure do |config|
9
- config.default_model = Desiru::Models::RaixAdapter.new(
10
- provider: :openai,
9
+ config.default_model = Desiru::Models::OpenAI.new(
11
10
  model: 'gpt-3.5-turbo',
12
11
  api_key: ENV['OPENAI_API_KEY'] || raise('Please set OPENAI_API_KEY environment variable')
13
12
  )