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.
- checksums.yaml +4 -4
- data/.env.example +34 -0
- data/.rubocop.yml +7 -4
- data/.ruby-version +1 -0
- data/CLAUDE.md +4 -0
- data/Gemfile +21 -2
- data/Gemfile.lock +87 -12
- data/README.md +295 -2
- data/Rakefile +1 -0
- data/db/migrations/001_create_initial_tables.rb +96 -0
- data/db/migrations/002_create_job_results.rb +39 -0
- data/desiru.db +0 -0
- data/desiru.gemspec +2 -5
- data/docs/background_processing_roadmap.md +87 -0
- data/docs/job_scheduling.md +167 -0
- data/dspy-analysis-swarm.yml +60 -0
- data/dspy-feature-analysis.md +121 -0
- data/examples/README.md +69 -0
- data/examples/api_with_persistence.rb +122 -0
- data/examples/assertions_example.rb +232 -0
- data/examples/async_processing.rb +2 -0
- data/examples/few_shot_learning.rb +1 -2
- data/examples/graphql_api.rb +4 -2
- data/examples/graphql_integration.rb +3 -3
- data/examples/graphql_optimization_summary.md +143 -0
- data/examples/graphql_performance_benchmark.rb +247 -0
- data/examples/persistence_example.rb +102 -0
- data/examples/react_agent.rb +203 -0
- data/examples/rest_api.rb +173 -0
- data/examples/rest_api_advanced.rb +333 -0
- data/examples/scheduled_job_example.rb +116 -0
- data/examples/simple_qa.rb +1 -2
- data/examples/sinatra_api.rb +109 -0
- data/examples/typed_signatures.rb +1 -2
- data/graphql_optimization_summary.md +53 -0
- data/lib/desiru/api/grape_integration.rb +284 -0
- data/lib/desiru/api/persistence_middleware.rb +148 -0
- data/lib/desiru/api/sinatra_integration.rb +217 -0
- data/lib/desiru/api.rb +42 -0
- data/lib/desiru/assertions.rb +74 -0
- data/lib/desiru/async_status.rb +65 -0
- data/lib/desiru/cache.rb +1 -1
- data/lib/desiru/configuration.rb +2 -1
- data/lib/desiru/errors.rb +160 -0
- data/lib/desiru/field.rb +17 -14
- data/lib/desiru/graphql/batch_loader.rb +85 -0
- data/lib/desiru/graphql/data_loader.rb +242 -75
- data/lib/desiru/graphql/enum_builder.rb +75 -0
- data/lib/desiru/graphql/executor.rb +37 -4
- data/lib/desiru/graphql/schema_generator.rb +62 -158
- data/lib/desiru/graphql/type_builder.rb +138 -0
- data/lib/desiru/graphql/type_cache_warmer.rb +91 -0
- data/lib/desiru/jobs/async_predict.rb +1 -1
- data/lib/desiru/jobs/base.rb +67 -0
- data/lib/desiru/jobs/batch_processor.rb +6 -6
- data/lib/desiru/jobs/retriable.rb +119 -0
- data/lib/desiru/jobs/retry_strategies.rb +169 -0
- data/lib/desiru/jobs/scheduler.rb +219 -0
- data/lib/desiru/jobs/webhook_notifier.rb +242 -0
- data/lib/desiru/models/anthropic.rb +164 -0
- data/lib/desiru/models/base.rb +37 -3
- data/lib/desiru/models/open_ai.rb +151 -0
- data/lib/desiru/models/open_router.rb +161 -0
- data/lib/desiru/module.rb +59 -9
- data/lib/desiru/modules/chain_of_thought.rb +3 -3
- data/lib/desiru/modules/majority.rb +51 -0
- data/lib/desiru/modules/multi_chain_comparison.rb +204 -0
- data/lib/desiru/modules/predict.rb +8 -1
- data/lib/desiru/modules/program_of_thought.rb +139 -0
- data/lib/desiru/modules/react.rb +273 -0
- data/lib/desiru/modules/retrieve.rb +4 -2
- data/lib/desiru/optimizers/base.rb +2 -4
- data/lib/desiru/optimizers/bootstrap_few_shot.rb +2 -2
- data/lib/desiru/optimizers/copro.rb +268 -0
- data/lib/desiru/optimizers/knn_few_shot.rb +185 -0
- data/lib/desiru/persistence/database.rb +71 -0
- data/lib/desiru/persistence/models/api_request.rb +38 -0
- data/lib/desiru/persistence/models/job_result.rb +138 -0
- data/lib/desiru/persistence/models/module_execution.rb +37 -0
- data/lib/desiru/persistence/models/optimization_result.rb +28 -0
- data/lib/desiru/persistence/models/training_example.rb +25 -0
- data/lib/desiru/persistence/models.rb +11 -0
- data/lib/desiru/persistence/repositories/api_request_repository.rb +98 -0
- data/lib/desiru/persistence/repositories/base_repository.rb +77 -0
- data/lib/desiru/persistence/repositories/job_result_repository.rb +116 -0
- data/lib/desiru/persistence/repositories/module_execution_repository.rb +85 -0
- data/lib/desiru/persistence/repositories/optimization_result_repository.rb +67 -0
- data/lib/desiru/persistence/repositories/training_example_repository.rb +102 -0
- data/lib/desiru/persistence/repository.rb +29 -0
- data/lib/desiru/persistence/setup.rb +77 -0
- data/lib/desiru/persistence.rb +49 -0
- data/lib/desiru/registry.rb +3 -5
- data/lib/desiru/signature.rb +91 -24
- data/lib/desiru/version.rb +1 -1
- data/lib/desiru.rb +23 -8
- data/missing-features-analysis.md +192 -0
- metadata +63 -45
- 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
|
data/examples/simple_qa.rb
CHANGED
@@ -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::
|
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
|
)
|