prescient 0.0.0 → 0.2.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/.env.example +37 -0
- data/.rubocop.yml +330 -0
- data/.yardopts +14 -0
- data/CHANGELOG.md +64 -0
- data/CHANGELOG.pdf +0 -0
- data/Dockerfile.example +41 -0
- data/INTEGRATION_GUIDE.md +363 -0
- data/README.md +917 -13
- data/Rakefile +26 -3
- data/VECTOR_SEARCH_GUIDE.md +453 -0
- data/db/init/01_enable_pgvector.sql +30 -0
- data/db/init/02_create_schema.sql +108 -0
- data/db/init/03_create_indexes.sql +96 -0
- data/db/init/04_insert_sample_data.sql +121 -0
- data/db/migrate/001_create_prescient_tables.rb +158 -0
- data/docker-compose.yml +153 -0
- data/examples/basic_usage.rb +123 -0
- data/examples/custom_contexts.rb +355 -0
- data/examples/custom_prompts.rb +212 -0
- data/examples/vector_search.rb +330 -0
- data/lib/prescient/base.rb +374 -0
- data/lib/prescient/client.rb +211 -0
- data/lib/prescient/provider/anthropic.rb +146 -0
- data/lib/prescient/provider/huggingface.rb +200 -0
- data/lib/prescient/provider/ollama.rb +172 -0
- data/lib/prescient/provider/openai.rb +181 -0
- data/lib/prescient/version.rb +1 -1
- data/lib/prescient.rb +186 -2
- data/prescient.gemspec +53 -0
- data/scripts/setup-ollama-models.sh +77 -0
- metadata +252 -14
- data/.vscode/settings.json +0 -1
@@ -0,0 +1,363 @@
|
|
1
|
+
# AI Providers Integration Guide
|
2
|
+
|
3
|
+
This guide explains how to integrate the AI Providers gem into your existing Rails AI application.
|
4
|
+
|
5
|
+
## Integration Steps
|
6
|
+
|
7
|
+
### 1. Update Your Gemfile
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Add to your Gemfile
|
11
|
+
gem 'prescient', path: './prescient_gem' # Local development
|
12
|
+
# OR when published:
|
13
|
+
# gem 'prescient', '~> 0.1.0'
|
14
|
+
```
|
15
|
+
|
16
|
+
### 2. Replace Existing AI Service
|
17
|
+
|
18
|
+
**Before (Original OllamaService):**
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# app/services/ollama_service.rb
|
22
|
+
class OllamaService
|
23
|
+
def generate_embedding(text)
|
24
|
+
# Direct Ollama API calls
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_response(prompt, context_items)
|
28
|
+
# Direct Ollama API calls
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
**After (Using AI Providers Gem):**
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# app/services/ai_service.rb
|
37
|
+
class AIService
|
38
|
+
def self.client(provider = nil)
|
39
|
+
@clients ||= {}
|
40
|
+
provider_name = provider || Rails.application.config.default_ai_provider
|
41
|
+
@clients[provider_name] ||= Prescient.client(provider_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.generate_embedding(text, provider: nil)
|
45
|
+
client(provider).generate_embedding(text)
|
46
|
+
rescue Prescient::Error => e
|
47
|
+
Rails.logger.error "AI embedding generation failed: #{e.message}"
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.generate_response(prompt, context_items = [], provider: nil, **options)
|
52
|
+
client(provider).generate_response(prompt, context_items, **options)
|
53
|
+
rescue Prescient::Error => e
|
54
|
+
Rails.logger.error "AI response generation failed: #{e.message}"
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.health_check(provider: nil)
|
59
|
+
client(provider).health_check
|
60
|
+
rescue Prescient::Error => e
|
61
|
+
{ status: 'unhealthy', error: e.message }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
### 3. Configuration
|
67
|
+
|
68
|
+
**Create initializer:**
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# config/initializers/prescient.rb
|
72
|
+
Prescient.configure do |config|
|
73
|
+
config.default_provider = Rails.env.production? ? :openai : :ollama
|
74
|
+
config.timeout = 60
|
75
|
+
config.retry_attempts = 3
|
76
|
+
config.retry_delay = 1.0
|
77
|
+
|
78
|
+
# Ollama (Local/Development)
|
79
|
+
config.add_provider(:ollama, Prescient::Ollama::Provider,
|
80
|
+
url: ENV.fetch('OLLAMA_URL', 'http://localhost:11434'),
|
81
|
+
embedding_model: ENV.fetch('OLLAMA_EMBEDDING_MODEL', 'nomic-embed-text'),
|
82
|
+
chat_model: ENV.fetch('OLLAMA_CHAT_MODEL', 'llama3.1:8b'),
|
83
|
+
timeout: 120
|
84
|
+
)
|
85
|
+
|
86
|
+
# OpenAI (Production)
|
87
|
+
if ENV['OPENAI_API_KEY'].present?
|
88
|
+
config.add_provider(:openai, Prescient::OpenAI::Provider,
|
89
|
+
api_key: ENV['OPENAI_API_KEY'],
|
90
|
+
embedding_model: ENV.fetch('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'),
|
91
|
+
chat_model: ENV.fetch('OPENAI_CHAT_MODEL', 'gpt-3.5-turbo')
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Anthropic (Alternative)
|
96
|
+
if ENV['ANTHROPIC_API_KEY'].present?
|
97
|
+
config.add_provider(:anthropic, Prescient::Anthropic::Provider,
|
98
|
+
api_key: ENV['ANTHROPIC_API_KEY'],
|
99
|
+
model: ENV.fetch('ANTHROPIC_MODEL', 'claude-3-haiku-20240307')
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
# HuggingFace (Research/Open Source)
|
104
|
+
if ENV['HUGGINGFACE_API_KEY'].present?
|
105
|
+
config.add_provider(:huggingface, Prescient::HuggingFace::Provider,
|
106
|
+
api_key: ENV['HUGGINGFACE_API_KEY'],
|
107
|
+
embedding_model: ENV.fetch('HUGGINGFACE_EMBEDDING_MODEL', 'sentence-transformers/all-MiniLM-L6-v2'),
|
108
|
+
chat_model: ENV.fetch('HUGGINGFACE_CHAT_MODEL', 'microsoft/DialoGPT-medium')
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Set default provider for Rails
|
114
|
+
Rails.application.config.default_ai_provider = :ollama
|
115
|
+
```
|
116
|
+
|
117
|
+
### 4. Update Environment Variables
|
118
|
+
|
119
|
+
```bash
|
120
|
+
# .env or environment configuration
|
121
|
+
|
122
|
+
# Ollama (Local)
|
123
|
+
OLLAMA_URL=http://localhost:11434
|
124
|
+
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
125
|
+
OLLAMA_CHAT_MODEL=llama3.1:8b
|
126
|
+
|
127
|
+
# OpenAI (Production)
|
128
|
+
OPENAI_API_KEY=your_openai_api_key
|
129
|
+
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
130
|
+
OPENAI_CHAT_MODEL=gpt-3.5-turbo
|
131
|
+
|
132
|
+
# Anthropic (Alternative)
|
133
|
+
ANTHROPIC_API_KEY=your_anthropic_api_key
|
134
|
+
ANTHROPIC_MODEL=claude-3-haiku-20240307
|
135
|
+
|
136
|
+
# HuggingFace (Research)
|
137
|
+
HUGGINGFACE_API_KEY=your_huggingface_api_key
|
138
|
+
HUGGINGFACE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
139
|
+
HUGGINGFACE_CHAT_MODEL=microsoft/DialoGPT-medium
|
140
|
+
```
|
141
|
+
|
142
|
+
### 5. Update Controllers
|
143
|
+
|
144
|
+
**Before:**
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
class Api::V1::AiQueriesController < ApplicationController
|
148
|
+
def create
|
149
|
+
embedding = OllamaService.new.generate_embedding(params[:query])
|
150
|
+
# ... rest of the logic
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
**After:**
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
class Api::V1::AiQueriesController < ApplicationController
|
159
|
+
def create
|
160
|
+
# Use default provider or specify one
|
161
|
+
embedding = AIService.generate_embedding(params[:query])
|
162
|
+
|
163
|
+
# Or use specific provider
|
164
|
+
# embedding = AIService.generate_embedding(params[:query], provider: :openai)
|
165
|
+
|
166
|
+
# ... rest of the logic remains the same
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def generate_ai_response(query, context_items)
|
172
|
+
# Automatically uses configured provider with fallback
|
173
|
+
response = AIService.generate_response(
|
174
|
+
query,
|
175
|
+
context_items,
|
176
|
+
max_tokens: 2000,
|
177
|
+
temperature: 0.7
|
178
|
+
)
|
179
|
+
|
180
|
+
response[:response]
|
181
|
+
rescue Prescient::Error => e
|
182
|
+
Rails.logger.error "AI response failed: #{e.message}"
|
183
|
+
"I apologize, but I'm currently unable to generate a response. Please try again later."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
### 6. Health Check Integration
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# app/controllers/api/v1/system/health_controller.rb
|
192
|
+
class Api::V1::System::HealthController < ApplicationController
|
193
|
+
def show
|
194
|
+
health_status = {
|
195
|
+
database: database_health,
|
196
|
+
prescient: prescient_health,
|
197
|
+
overall: 'healthy'
|
198
|
+
}
|
199
|
+
|
200
|
+
# Set overall status based on critical components
|
201
|
+
if health_status[:prescient][:primary][:status] != 'healthy'
|
202
|
+
health_status[:overall] = 'degraded'
|
203
|
+
end
|
204
|
+
|
205
|
+
render json: health_status
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def prescient_health
|
211
|
+
providers = {}
|
212
|
+
|
213
|
+
# Check primary provider
|
214
|
+
primary_provider = Rails.application.config.default_ai_provider
|
215
|
+
providers[:primary] = {
|
216
|
+
name: primary_provider,
|
217
|
+
**AIService.health_check(provider: primary_provider)
|
218
|
+
}
|
219
|
+
|
220
|
+
# Check backup providers
|
221
|
+
backup_providers = [:openai, :anthropic, :huggingface] - [primary_provider]
|
222
|
+
providers[:backups] = backup_providers.map do |provider|
|
223
|
+
{
|
224
|
+
name: provider,
|
225
|
+
**AIService.health_check(provider: provider)
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
229
|
+
providers
|
230
|
+
end
|
231
|
+
|
232
|
+
def database_health
|
233
|
+
ActiveRecord::Base.connection.execute('SELECT 1')
|
234
|
+
{ status: 'healthy' }
|
235
|
+
rescue StandardError => e
|
236
|
+
{ status: 'unhealthy', error: e.message }
|
237
|
+
end
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
### 7. Migration Strategy
|
242
|
+
|
243
|
+
1. **Phase 1: Side-by-side deployment**
|
244
|
+
|
245
|
+
- Keep existing OllamaService
|
246
|
+
- Add AI Providers gem alongside
|
247
|
+
- Test thoroughly in development
|
248
|
+
|
249
|
+
2. **Phase 2: Gradual migration**
|
250
|
+
|
251
|
+
- Update one controller at a time
|
252
|
+
- Use feature flags to switch between old/new systems
|
253
|
+
- Monitor performance and error rates
|
254
|
+
|
255
|
+
3. **Phase 3: Complete migration**
|
256
|
+
- Remove old OllamaService
|
257
|
+
- Update all controllers to use AIService
|
258
|
+
- Clean up unused code
|
259
|
+
|
260
|
+
### 8. Testing Updates
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
# spec/services/ai_service_spec.rb
|
264
|
+
RSpec.describe AIService do
|
265
|
+
before do
|
266
|
+
Prescient.configure do |config|
|
267
|
+
config.add_provider(:test, Prescient::Ollama::Provider,
|
268
|
+
url: 'http://localhost:11434',
|
269
|
+
embedding_model: 'test-embed',
|
270
|
+
chat_model: 'test-chat'
|
271
|
+
)
|
272
|
+
config.default_provider = :test
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe '.generate_embedding' do
|
277
|
+
it 'returns embedding vector' do
|
278
|
+
# Mock the provider response
|
279
|
+
allow_any_instance_of(Prescient::Ollama::Provider)
|
280
|
+
.to receive(:generate_embedding)
|
281
|
+
.and_return([0.1, 0.2, 0.3])
|
282
|
+
|
283
|
+
result = described_class.generate_embedding('test text')
|
284
|
+
expect(result).to eq([0.1, 0.2, 0.3])
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
### 9. Monitoring and Logging
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
# config/initializers/prescient_monitoring.rb
|
294
|
+
class PrescientMonitoring
|
295
|
+
def self.setup!
|
296
|
+
ActiveSupport::Notifications.subscribe('prescient.request') do |name, start, finish, id, payload|
|
297
|
+
duration = finish - start
|
298
|
+
|
299
|
+
Rails.logger.info "AI Provider Request: #{payload[:provider]} - #{payload[:operation]} - #{duration.round(3)}s"
|
300
|
+
|
301
|
+
# Send metrics to your monitoring system
|
302
|
+
# StatsD.increment('prescient.requests', tags: [
|
303
|
+
# "provider:#{payload[:provider]}",
|
304
|
+
# "operation:#{payload[:operation]}",
|
305
|
+
# "status:#{payload[:status]}"
|
306
|
+
# ])
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
PrescientMonitoring.setup! if Rails.env.production?
|
312
|
+
```
|
313
|
+
|
314
|
+
### 10. Performance Optimization
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
# app/services/ai_service.rb (enhanced)
|
318
|
+
class AIService
|
319
|
+
# Connection pooling for providers
|
320
|
+
def self.client(provider = nil)
|
321
|
+
@clients ||= {}
|
322
|
+
provider_name = provider || Rails.application.config.default_ai_provider
|
323
|
+
|
324
|
+
@clients[provider_name] ||= begin
|
325
|
+
# Use connection pooling for high-traffic applications
|
326
|
+
Prescient.client(provider_name)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Caching for embeddings (optional)
|
331
|
+
def self.generate_embedding(text, provider: nil)
|
332
|
+
cache_key = "ai_embedding:#{ Digest::SHA256.hexdigest(text)}:#{ provider}"
|
333
|
+
|
334
|
+
Rails.cache.fetch(cache_key, expires_in: 1.hour) do
|
335
|
+
client(provider).generate_embedding(text)
|
336
|
+
end
|
337
|
+
rescue Prescient::Error => e
|
338
|
+
Rails.logger.error "AI embedding generation failed: #{e.message}"
|
339
|
+
raise
|
340
|
+
end
|
341
|
+
end
|
342
|
+
```
|
343
|
+
|
344
|
+
## Benefits of Migration
|
345
|
+
|
346
|
+
1. **Provider Flexibility**: Easy switching between AI providers
|
347
|
+
2. **Fallback Support**: Automatic fallback to backup providers
|
348
|
+
3. **Better Error Handling**: Comprehensive error classification
|
349
|
+
4. **Monitoring**: Built-in health checks and metrics
|
350
|
+
5. **Testing**: Easier mocking and testing
|
351
|
+
6. **Scalability**: Better support for different deployment scenarios
|
352
|
+
7. **Cost Optimization**: Use local models for development, cloud for production
|
353
|
+
|
354
|
+
## Rollback Plan
|
355
|
+
|
356
|
+
If issues arise, quickly rollback by:
|
357
|
+
|
358
|
+
1. Revert initializer changes
|
359
|
+
2. Switch controllers back to OllamaService
|
360
|
+
3. Deploy previous version
|
361
|
+
4. Debug issues separately
|
362
|
+
|
363
|
+
The gem structure allows for easy rollback since it's designed as a drop-in replacement.
|