prescient 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/.rubocop.yml +6 -4
- data/.yardopts +11 -0
- data/CHANGELOG.md +101 -0
- data/INTEGRATION_GUIDE.md +361 -0
- data/LICENSE.txt +1 -1
- data/README.md +217 -100
- data/Rakefile +84 -8
- data/Steepfile +17 -0
- data/VECTOR_SEARCH_GUIDE.md +74 -47
- data/docker-compose.yml +3 -5
- data/examples/README.md +45 -0
- data/examples/basic_usage.rb +2 -2
- data/examples/custom_contexts.rb +6 -6
- data/examples/custom_prompts.rb +5 -5
- data/examples/vector_search.rb +2 -2
- data/lib/prescient/base.rb +187 -28
- data/lib/prescient/client.rb +169 -31
- data/lib/prescient/errors.rb +51 -0
- data/lib/prescient/pgvector.rb +194 -0
- data/lib/prescient/provider/anthropic.rb +55 -54
- data/lib/prescient/provider/huggingface.rb +77 -79
- data/lib/prescient/provider/ollama.rb +46 -35
- data/lib/prescient/provider/openai.rb +38 -31
- data/lib/prescient/version.rb +2 -1
- data/lib/prescient.rb +160 -36
- data/scripts/setup-ollama-models.sh +2 -2
- data/sig/prescient.rbs +221 -1
- metadata +23 -184
- data/prescient.gemspec +0 -51
data/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Prescient
|
|
2
2
|
|
|
3
|
-
Prescient
|
|
3
|
+
Prescient is a boring AI provider abstraction for Ruby. Configure your AI providers once, then use the same interface regardless of whether the request is handled by OpenAI, Anthropic, Ollama, or Hugging Face. Prescient handles provider selection, retries, health checks, and fallback.
|
|
4
|
+
|
|
5
|
+
For focused guidance, see the [examples guide](examples/README.md),
|
|
6
|
+
[Rails integration guide](INTEGRATION_GUIDE.md), and
|
|
7
|
+
[pgvector guide](VECTOR_SEARCH_GUIDE.md).
|
|
4
8
|
|
|
5
9
|
## Features
|
|
6
10
|
|
|
@@ -16,19 +20,19 @@ Prescient provides a unified interface for AI providers including Ollama (local)
|
|
|
16
20
|
|
|
17
21
|
### Ollama (Local)
|
|
18
22
|
|
|
19
|
-
- **Models**: Any Ollama-compatible model (llama3.
|
|
23
|
+
- **Models**: Any Ollama-compatible model (llama3.2, nomic-embed-text, etc.)
|
|
20
24
|
- **Capabilities**: Embeddings, Text Generation, Model Management
|
|
21
25
|
- **Use Case**: Privacy-focused, local deployments
|
|
22
26
|
|
|
23
27
|
### Anthropic Claude
|
|
24
28
|
|
|
25
|
-
- **Models**: Claude
|
|
29
|
+
- **Models**: Current Claude models selected through the Anthropic Models API
|
|
26
30
|
- **Capabilities**: Text Generation only (no embeddings)
|
|
27
31
|
- **Use Case**: High-quality conversational AI
|
|
28
32
|
|
|
29
33
|
### OpenAI
|
|
30
34
|
|
|
31
|
-
- **Models**:
|
|
35
|
+
- **Models**: Current GPT and text-embedding models selected through the OpenAI Models API
|
|
32
36
|
- **Capabilities**: Embeddings, Text Generation
|
|
33
37
|
- **Use Case**: Proven performance, wide model selection
|
|
34
38
|
|
|
@@ -66,21 +70,21 @@ gem install prescient
|
|
|
66
70
|
# Ollama (Local)
|
|
67
71
|
OLLAMA_URL=http://localhost:11434
|
|
68
72
|
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
|
69
|
-
OLLAMA_CHAT_MODEL=llama3.
|
|
73
|
+
OLLAMA_CHAT_MODEL=llama3.2:3b
|
|
70
74
|
|
|
71
75
|
# Anthropic
|
|
72
76
|
ANTHROPIC_API_KEY=your_api_key
|
|
73
|
-
ANTHROPIC_MODEL=claude-
|
|
77
|
+
ANTHROPIC_MODEL=claude-sonnet-4-20250514
|
|
74
78
|
|
|
75
79
|
# OpenAI
|
|
76
80
|
OPENAI_API_KEY=your_api_key
|
|
77
81
|
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
|
78
|
-
OPENAI_CHAT_MODEL=gpt-
|
|
82
|
+
OPENAI_CHAT_MODEL=gpt-4.1-mini
|
|
79
83
|
|
|
80
84
|
# HuggingFace
|
|
81
85
|
HUGGINGFACE_API_KEY=your_api_key
|
|
82
86
|
HUGGINGFACE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
|
83
|
-
HUGGINGFACE_CHAT_MODEL=
|
|
87
|
+
HUGGINGFACE_CHAT_MODEL=google/gemma-2-2b-it
|
|
84
88
|
```
|
|
85
89
|
|
|
86
90
|
### Programmatic Configuration
|
|
@@ -96,27 +100,74 @@ Prescient.configure do |config|
|
|
|
96
100
|
config.retry_delay = 1.0
|
|
97
101
|
|
|
98
102
|
# Add custom Ollama configuration
|
|
99
|
-
config.add_provider(:ollama, Prescient::Ollama
|
|
103
|
+
config.add_provider(:ollama, Prescient::Provider::Ollama,
|
|
100
104
|
url: 'http://localhost:11434',
|
|
101
105
|
embedding_model: 'nomic-embed-text',
|
|
102
|
-
chat_model: 'llama3.
|
|
106
|
+
chat_model: 'llama3.2:3b'
|
|
103
107
|
)
|
|
104
108
|
|
|
105
109
|
# Add Anthropic
|
|
106
|
-
config.add_provider(:anthropic, Prescient::Anthropic
|
|
110
|
+
config.add_provider(:anthropic, Prescient::Provider::Anthropic,
|
|
107
111
|
api_key: ENV['ANTHROPIC_API_KEY'],
|
|
108
|
-
model: 'claude-
|
|
112
|
+
model: 'claude-sonnet-4-20250514'
|
|
109
113
|
)
|
|
110
114
|
|
|
111
115
|
# Add OpenAI
|
|
112
|
-
config.add_provider(:openai, Prescient::OpenAI
|
|
116
|
+
config.add_provider(:openai, Prescient::Provider::OpenAI,
|
|
117
|
+
api_key: ENV['OPENAI_API_KEY'],
|
|
118
|
+
embedding_model: 'text-embedding-3-small',
|
|
119
|
+
chat_model: 'gpt-4.1-mini'
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Provider Fallback Configuration
|
|
125
|
+
|
|
126
|
+
Prescient supports automatic fallback to backup providers when the primary provider fails. This ensures high availability for your AI applications.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
Prescient.configure do |config|
|
|
130
|
+
# Configure primary provider
|
|
131
|
+
config.add_provider(:primary, Prescient::Provider::OpenAI,
|
|
113
132
|
api_key: ENV['OPENAI_API_KEY'],
|
|
114
133
|
embedding_model: 'text-embedding-3-small',
|
|
115
|
-
chat_model: 'gpt-
|
|
134
|
+
chat_model: 'gpt-4.1-mini'
|
|
116
135
|
)
|
|
136
|
+
|
|
137
|
+
# Configure backup providers
|
|
138
|
+
config.add_provider(:backup1, Prescient::Provider::Anthropic,
|
|
139
|
+
api_key: ENV['ANTHROPIC_API_KEY'],
|
|
140
|
+
model: 'claude-sonnet-4-20250514'
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
config.add_provider(:backup2, Prescient::Provider::Ollama,
|
|
144
|
+
url: 'http://localhost:11434',
|
|
145
|
+
embedding_model: 'nomic-embed-text',
|
|
146
|
+
chat_model: 'llama3.2:3b'
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Configure fallback order
|
|
150
|
+
config.fallback_providers = [:backup1, :backup2]
|
|
117
151
|
end
|
|
152
|
+
|
|
153
|
+
# Client with fallback enabled (default)
|
|
154
|
+
client = Prescient::Client.new(:primary, enable_fallback: true)
|
|
155
|
+
|
|
156
|
+
# Client without fallback
|
|
157
|
+
client_no_fallback = Prescient::Client.new(:primary, enable_fallback: false)
|
|
158
|
+
|
|
159
|
+
# Convenience methods also support fallback
|
|
160
|
+
response = Prescient.generate_response("Hello", provider: :primary, enable_fallback: true)
|
|
118
161
|
```
|
|
119
162
|
|
|
163
|
+
**Fallback Behavior:**
|
|
164
|
+
- When a provider fails with a persistent error, Prescient automatically tries the next available provider
|
|
165
|
+
- Configured fallback providers are tried in order; the provider operation determines availability
|
|
166
|
+
- If no fallback providers are configured, all configured providers are tried as fallbacks
|
|
167
|
+
- Transient errors (rate limits, timeouts) still use retry logic before fallback
|
|
168
|
+
- Provider-service failures, connection failures, rate limits, and unavailable models may trigger fallback; authentication and invalid-request errors are returned to the caller
|
|
169
|
+
- The fallback process preserves all method arguments and options
|
|
170
|
+
|
|
120
171
|
## Usage
|
|
121
172
|
|
|
122
173
|
### Quick Start
|
|
@@ -129,7 +180,7 @@ client = Prescient.client
|
|
|
129
180
|
|
|
130
181
|
# Generate embeddings
|
|
131
182
|
embedding = client.generate_embedding("Your text here")
|
|
132
|
-
# => [0.1, 0.2, 0.3, ...] (
|
|
183
|
+
# => [0.1, 0.2, 0.3, ...] (model-dependent vector dimensions)
|
|
133
184
|
|
|
134
185
|
# Generate text responses
|
|
135
186
|
response = client.generate_response("What is Ruby?")
|
|
@@ -170,8 +221,8 @@ response = Prescient.generate_response(query, context_items,
|
|
|
170
221
|
)
|
|
171
222
|
|
|
172
223
|
puts response[:response]
|
|
173
|
-
puts "Model:
|
|
174
|
-
puts "Provider:
|
|
224
|
+
puts "Model: " + response[:model]
|
|
225
|
+
puts "Provider: " + response[:provider]
|
|
175
226
|
```
|
|
176
227
|
|
|
177
228
|
### Error Handling
|
|
@@ -192,12 +243,20 @@ end
|
|
|
192
243
|
|
|
193
244
|
### Health Monitoring
|
|
194
245
|
|
|
246
|
+
Health results separate transport reachability from configured-model readiness:
|
|
247
|
+
`reachable: true` means the provider answered, while `ready: true` means the
|
|
248
|
+
configured operation models were found or validated. Fallback uses the actual
|
|
249
|
+
operation and does not perform an additional health request.
|
|
250
|
+
|
|
195
251
|
```ruby
|
|
196
252
|
# Check all providers
|
|
197
|
-
|
|
253
|
+
Prescient.configuration.providers.keys.each do |provider|
|
|
198
254
|
health = Prescient.health_check(provider: provider)
|
|
199
255
|
puts "#{provider}: #{health[:status]}"
|
|
200
|
-
puts "
|
|
256
|
+
puts "Reachable: #{health[:reachable]}"
|
|
257
|
+
puts "Ready: #{health[:ready]}"
|
|
258
|
+
rescue Prescient::Error => e
|
|
259
|
+
puts "#{provider}: unavailable (#{e.message})"
|
|
201
260
|
end
|
|
202
261
|
```
|
|
203
262
|
|
|
@@ -210,18 +269,18 @@ Prescient.configure do |config|
|
|
|
210
269
|
config.add_provider(:customer_service, Prescient::Provider::OpenAI,
|
|
211
270
|
api_key: ENV['OPENAI_API_KEY'],
|
|
212
271
|
embedding_model: 'text-embedding-3-small',
|
|
213
|
-
chat_model: 'gpt-
|
|
272
|
+
chat_model: 'gpt-4.1-mini',
|
|
214
273
|
prompt_templates: {
|
|
215
274
|
system_prompt: 'You are a friendly customer service representative.',
|
|
216
275
|
no_context_template: <<~TEMPLATE.strip,
|
|
217
|
-
%{system_prompt}
|
|
276
|
+
%{ system_prompt }
|
|
218
277
|
|
|
219
278
|
Customer Question: %{query}
|
|
220
279
|
|
|
221
280
|
Please provide a helpful response.
|
|
222
281
|
TEMPLATE
|
|
223
282
|
with_context_template: <<~TEMPLATE.strip
|
|
224
|
-
%{system_prompt} Use the company info below to help answer.
|
|
283
|
+
%{ system_prompt } Use the company info below to help answer.
|
|
225
284
|
|
|
226
285
|
Company Information:
|
|
227
286
|
%{context}
|
|
@@ -259,6 +318,7 @@ prompt_templates: {
|
|
|
259
318
|
system_prompt: 'You are a technical documentation assistant. Provide detailed explanations with code examples.',
|
|
260
319
|
# ... templates
|
|
261
320
|
}
|
|
321
|
+
|
|
262
322
|
```
|
|
263
323
|
|
|
264
324
|
#### Creative Writing
|
|
@@ -316,6 +376,14 @@ response = client.generate_response("I need a laptop for work", products)
|
|
|
316
376
|
- **fields** - Array of field names available for this context type
|
|
317
377
|
- **format** - Template string for displaying context items
|
|
318
378
|
- **embedding_fields** - Specific fields to use when generating embeddings
|
|
379
|
+
- **context_excluded_fields** - Additional field names excluded from generic embedding text; built-in metadata exclusions remain active
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
config.add_provider(:openai, Prescient::Provider::OpenAI,
|
|
383
|
+
api_key: ENV['OPENAI_API_KEY'],
|
|
384
|
+
context_excluded_fields: %w[tenant_id internal_notes]
|
|
385
|
+
)
|
|
386
|
+
```
|
|
319
387
|
|
|
320
388
|
### Automatic Context Detection
|
|
321
389
|
|
|
@@ -337,7 +405,7 @@ The system works perfectly without any context configuration - it will:
|
|
|
337
405
|
|
|
338
406
|
```ruby
|
|
339
407
|
# No context_configs needed - works with any data!
|
|
340
|
-
client = Prescient.client
|
|
408
|
+
client = Prescient.client
|
|
341
409
|
response = client.generate_response("Analyze this", [
|
|
342
410
|
{ 'title' => 'Issue', 'content' => 'Server down', 'created_at' => '2024-01-01' },
|
|
343
411
|
{ 'name' => 'Alert', 'message' => 'High CPU usage', 'timestamp' => 1234567 }
|
|
@@ -346,9 +414,44 @@ response = client.generate_response("Analyze this", [
|
|
|
346
414
|
|
|
347
415
|
See `examples/custom_contexts.rb` for complete examples.
|
|
348
416
|
|
|
417
|
+
### Sensitive Provider Options
|
|
418
|
+
|
|
419
|
+
`provider_info` always removes the built-in sensitive keys (`api_key`,
|
|
420
|
+
`password`, `token`, and `secret`). Add project-specific keys globally with
|
|
421
|
+
`sensitive_keys`; nested hashes and arrays are sanitized recursively:
|
|
422
|
+
|
|
423
|
+
```ruby
|
|
424
|
+
Prescient.configure do |config|
|
|
425
|
+
config.sensitive_keys = %w[workspace_secret private_key]
|
|
426
|
+
end
|
|
427
|
+
```
|
|
428
|
+
|
|
349
429
|
## Vector Database Integration (pgvector)
|
|
350
430
|
|
|
351
|
-
Prescient
|
|
431
|
+
`Prescient::Pgvector::Store` is an opt-in PostgreSQL integration for storing
|
|
432
|
+
and searching embeddings. It accepts a PG-compatible connection, so `pg` stays
|
|
433
|
+
an application dependency rather than a required dependency of this gem.
|
|
434
|
+
|
|
435
|
+
```ruby
|
|
436
|
+
require 'pg'
|
|
437
|
+
|
|
438
|
+
connection = PG.connect(dbname: 'my_app')
|
|
439
|
+
store = Prescient::Pgvector::Store.new(connection: connection, dimensions: 1536)
|
|
440
|
+
store.install!
|
|
441
|
+
store.create_index!
|
|
442
|
+
|
|
443
|
+
embedding = Prescient.generate_embedding('Semantic search', provider: :openai)
|
|
444
|
+
store.upsert(
|
|
445
|
+
id: 'document-42', embedding:, provider: 'openai', model: 'text-embedding-3-small',
|
|
446
|
+
content: 'Semantic search', metadata: { category: 'guide' }
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
results = store.search(embedding:, provider: :openai, model: 'text-embedding-3-small')
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Every vector must exactly match the store's configured dimensions; Prescient
|
|
453
|
+
never pads or truncates vectors. The existing application-schema example below
|
|
454
|
+
remains available for projects that need documents, chunks, and custom metadata.
|
|
352
455
|
|
|
353
456
|
### Setup with Docker
|
|
354
457
|
|
|
@@ -356,7 +459,7 @@ The included `docker-compose.yml` provides a complete setup with PostgreSQL + pg
|
|
|
356
459
|
|
|
357
460
|
```bash
|
|
358
461
|
# Start PostgreSQL with pgvector
|
|
359
|
-
docker
|
|
462
|
+
docker compose up -d postgres
|
|
360
463
|
|
|
361
464
|
# The database will automatically:
|
|
362
465
|
# - Install pgvector extension
|
|
@@ -400,7 +503,7 @@ embedding = client.generate_embedding(text)
|
|
|
400
503
|
vector_str = "[#{embedding.join(',')}]"
|
|
401
504
|
db.exec_params(
|
|
402
505
|
"INSERT INTO document_embeddings (document_id, embedding_provider, embedding_model, embedding_dimensions, embedding, embedding_text) VALUES ($1, $2, $3, $4, $5, $6)",
|
|
403
|
-
[doc_id, 'ollama', 'nomic-embed-text',
|
|
506
|
+
[doc_id, 'ollama', 'nomic-embed-text', embedding.length, vector_str, text]
|
|
404
507
|
)
|
|
405
508
|
|
|
406
509
|
# Perform similarity search
|
|
@@ -409,10 +512,10 @@ query_embedding = client.generate_embedding(query_text)
|
|
|
409
512
|
query_vector = "[#{query_embedding.join(',')}]"
|
|
410
513
|
|
|
411
514
|
results = db.exec_params(
|
|
412
|
-
"SELECT d.title, d.content, de.embedding <=> $1::vector AS distance
|
|
413
|
-
FROM documents d
|
|
414
|
-
JOIN document_embeddings de ON d.id = de.document_id
|
|
415
|
-
ORDER BY de.embedding <=> $1::vector
|
|
515
|
+
"SELECT d.title, d.content, de.embedding <=> $1::vector AS distance
|
|
516
|
+
FROM documents d
|
|
517
|
+
JOIN document_embeddings de ON d.id = de.document_id
|
|
518
|
+
ORDER BY de.embedding <=> $1::vector
|
|
416
519
|
LIMIT 5",
|
|
417
520
|
[query_vector]
|
|
418
521
|
)
|
|
@@ -423,14 +526,14 @@ results = db.exec_params(
|
|
|
423
526
|
pgvector supports three distance functions:
|
|
424
527
|
|
|
425
528
|
- **Cosine Distance** (`<=>`): Best for normalized embeddings
|
|
426
|
-
- **L2 Distance** (`<->`): Euclidean distance, good general purpose
|
|
529
|
+
- **L2 Distance** (`<->`): Euclidean distance, good general purpose
|
|
427
530
|
- **Inner Product** (`<#>`): Dot product, useful for specific cases
|
|
428
531
|
|
|
429
532
|
```sql
|
|
430
533
|
-- Cosine similarity (most common)
|
|
431
534
|
ORDER BY embedding <=> query_vector
|
|
432
535
|
|
|
433
|
-
-- L2 distance
|
|
536
|
+
-- L2 distance
|
|
434
537
|
ORDER BY embedding <-> query_vector
|
|
435
538
|
|
|
436
539
|
-- Inner product
|
|
@@ -443,8 +546,8 @@ The setup automatically creates HNSW indexes for fast similarity search:
|
|
|
443
546
|
|
|
444
547
|
```sql
|
|
445
548
|
-- Example index for cosine distance
|
|
446
|
-
CREATE INDEX idx_embeddings_cosine
|
|
447
|
-
ON document_embeddings
|
|
549
|
+
CREATE INDEX idx_embeddings_cosine
|
|
550
|
+
ON document_embeddings
|
|
448
551
|
USING hnsw (embedding vector_cosine_ops)
|
|
449
552
|
WITH (m = 16, ef_construction = 64);
|
|
450
553
|
```
|
|
@@ -457,22 +560,22 @@ Combine vector similarity with metadata filtering:
|
|
|
457
560
|
# Search with tag filtering
|
|
458
561
|
results = db.exec_params(
|
|
459
562
|
"SELECT d.title, de.embedding <=> $1::vector as distance
|
|
460
|
-
FROM documents d
|
|
563
|
+
FROM documents d
|
|
461
564
|
JOIN document_embeddings de ON d.id = de.document_id
|
|
462
565
|
WHERE d.metadata->'tags' ? 'programming'
|
|
463
|
-
ORDER BY de.embedding <=> $1::vector
|
|
566
|
+
ORDER BY de.embedding <=> $1::vector
|
|
464
567
|
LIMIT 5",
|
|
465
568
|
[query_vector]
|
|
466
569
|
)
|
|
467
570
|
|
|
468
|
-
# Search with difficulty and tag filters
|
|
571
|
+
# Search with difficulty and tag filters
|
|
469
572
|
results = db.exec_params(
|
|
470
573
|
"SELECT d.title, de.embedding <=> $1::vector as distance
|
|
471
|
-
FROM documents d
|
|
574
|
+
FROM documents d
|
|
472
575
|
JOIN document_embeddings de ON d.id = de.document_id
|
|
473
576
|
WHERE d.metadata->>'difficulty' = 'beginner'
|
|
474
577
|
AND d.metadata->'tags' ?| $2::text[]
|
|
475
|
-
ORDER BY de.embedding <=> $1::vector
|
|
578
|
+
ORDER BY de.embedding <=> $1::vector
|
|
476
579
|
LIMIT 5",
|
|
477
580
|
[query_vector, ['ruby', 'programming']]
|
|
478
581
|
)
|
|
@@ -488,7 +591,7 @@ For large datasets, tune HNSW parameters:
|
|
|
488
591
|
-- High accuracy (slower build, more memory)
|
|
489
592
|
WITH (m = 32, ef_construction = 128)
|
|
490
593
|
|
|
491
|
-
-- Fast build (lower accuracy, less memory)
|
|
594
|
+
-- Fast build (lower accuracy, less memory)
|
|
492
595
|
WITH (m = 8, ef_construction = 32)
|
|
493
596
|
|
|
494
597
|
-- Balanced (recommended default)
|
|
@@ -502,9 +605,9 @@ WITH (m = 16, ef_construction = 64)
|
|
|
502
605
|
SET hnsw.ef_search = 100; -- Higher = more accurate, slower
|
|
503
606
|
|
|
504
607
|
-- Use EXPLAIN ANALYZE to optimize queries
|
|
505
|
-
EXPLAIN ANALYZE
|
|
506
|
-
SELECT * FROM document_embeddings
|
|
507
|
-
ORDER BY embedding <=> '[0.1,0.2,...]'::vector
|
|
608
|
+
EXPLAIN ANALYZE
|
|
609
|
+
SELECT * FROM document_embeddings
|
|
610
|
+
ORDER BY embedding <=> '[0.1,0.2,...]'::vector
|
|
508
611
|
LIMIT 10;
|
|
509
612
|
```
|
|
510
613
|
|
|
@@ -516,14 +619,14 @@ For large documents, use chunking for better search granularity:
|
|
|
516
619
|
def chunk_document(text, chunk_size: 500, overlap: 50)
|
|
517
620
|
chunks = []
|
|
518
621
|
start = 0
|
|
519
|
-
|
|
622
|
+
|
|
520
623
|
while start < text.length
|
|
521
624
|
end_pos = [start + chunk_size, text.length].min
|
|
522
625
|
chunk = text[start...end_pos]
|
|
523
626
|
chunks << chunk
|
|
524
627
|
start += chunk_size - overlap
|
|
525
628
|
end
|
|
526
|
-
|
|
629
|
+
|
|
527
630
|
chunks
|
|
528
631
|
end
|
|
529
632
|
|
|
@@ -541,13 +644,14 @@ Run the complete vector search example:
|
|
|
541
644
|
|
|
542
645
|
```bash
|
|
543
646
|
# Start services
|
|
544
|
-
docker
|
|
647
|
+
docker compose up -d postgres ollama
|
|
545
648
|
|
|
546
649
|
# Run example
|
|
547
650
|
DB_HOST=localhost ruby examples/vector_search.rb
|
|
548
651
|
```
|
|
549
652
|
|
|
550
653
|
The example demonstrates:
|
|
654
|
+
|
|
551
655
|
- Document embedding generation and storage
|
|
552
656
|
- Similarity search with different distance functions
|
|
553
657
|
- Metadata filtering and advanced queries
|
|
@@ -558,7 +662,7 @@ The example demonstrates:
|
|
|
558
662
|
### Custom Provider Implementation
|
|
559
663
|
|
|
560
664
|
```ruby
|
|
561
|
-
class MyCustomProvider < Prescient::
|
|
665
|
+
class MyCustomProvider < Prescient::Base
|
|
562
666
|
def generate_embedding(text, **options)
|
|
563
667
|
# Your implementation
|
|
564
668
|
end
|
|
@@ -594,16 +698,16 @@ client = Prescient.client(:ollama)
|
|
|
594
698
|
info = client.provider_info
|
|
595
699
|
|
|
596
700
|
puts info[:name] # => :ollama
|
|
597
|
-
puts info[:class] # => "
|
|
701
|
+
puts info[:class] # => "Ollama"
|
|
598
702
|
puts info[:available] # => true
|
|
599
|
-
puts info[:options] # => {...} (excluding sensitive data)
|
|
703
|
+
puts info[:options] # => { ... } (excluding sensitive data)
|
|
600
704
|
```
|
|
601
705
|
|
|
602
706
|
## Provider-Specific Features
|
|
603
707
|
|
|
604
708
|
### Ollama
|
|
605
709
|
|
|
606
|
-
- Model management: `pull_model`, `
|
|
710
|
+
- Model management: `pull_model`, `available_models`
|
|
607
711
|
- Local deployment support
|
|
608
712
|
- No API costs
|
|
609
713
|
|
|
@@ -617,6 +721,7 @@ puts info[:options] # => {...} (excluding sensitive data)
|
|
|
617
721
|
- Multiple embedding model sizes
|
|
618
722
|
- Latest GPT models
|
|
619
723
|
- Reliable performance
|
|
724
|
+
- Uses the Chat Completions endpoint for the stable normalized response contract; the newer Responses API remains a future compatibility extension.
|
|
620
725
|
|
|
621
726
|
### HuggingFace
|
|
622
727
|
|
|
@@ -633,6 +738,7 @@ The easiest way to get started with Prescient and Ollama is using Docker Compose
|
|
|
633
738
|
Before starting, ensure your system meets the minimum requirements for running Ollama:
|
|
634
739
|
|
|
635
740
|
#### **Minimum Requirements:**
|
|
741
|
+
|
|
636
742
|
- **CPU**: 4+ cores (x86_64 or ARM64)
|
|
637
743
|
- **RAM**: 8GB+ (16GB recommended)
|
|
638
744
|
- **Storage**: 10GB+ free space for models
|
|
@@ -640,48 +746,53 @@ Before starting, ensure your system meets the minimum requirements for running O
|
|
|
640
746
|
|
|
641
747
|
#### **Model-Specific Requirements:**
|
|
642
748
|
|
|
643
|
-
| Model
|
|
644
|
-
|
|
645
|
-
| `nomic-embed-text` | 1GB
|
|
646
|
-
| `llama3.
|
|
647
|
-
| `llama3.1:70b`
|
|
648
|
-
| `codellama:7b`
|
|
749
|
+
| Model | RAM Required | Storage | Notes |
|
|
750
|
+
| ------------------ | ------------ | ------- | --------------------------------- |
|
|
751
|
+
| `nomic-embed-text` | 1GB | 274MB | Embedding model |
|
|
752
|
+
| `llama3.2:3b` | 2GB | 2.0GB | Chat model (3B parameters) |
|
|
753
|
+
| `llama3.1:70b` | 64GB+ | 40GB | Large chat model (70B parameters) |
|
|
754
|
+
| `codellama:7b` | 8GB | 3.8GB | Code generation model |
|
|
649
755
|
|
|
650
756
|
#### **Performance Recommendations:**
|
|
757
|
+
|
|
651
758
|
- **SSD Storage**: Significantly faster model loading
|
|
652
759
|
- **GPU (Optional)**: NVIDIA GPU with 8GB+ VRAM for acceleration
|
|
653
760
|
- **Network**: Stable internet for initial model downloads
|
|
654
761
|
- **Docker**: 4GB+ memory limit configured
|
|
655
762
|
|
|
656
763
|
#### **GPU Acceleration (Optional):**
|
|
764
|
+
|
|
657
765
|
- **NVIDIA GPU**: RTX 3060+ with 8GB+ VRAM recommended
|
|
658
766
|
- **CUDA**: Version 11.8+ required
|
|
659
767
|
- **Docker**: NVIDIA Container Toolkit installed
|
|
660
768
|
- **Performance**: 3-10x faster inference with compatible models
|
|
661
769
|
|
|
662
|
-
> **💡 Tip**: Start with smaller models like `llama3.
|
|
770
|
+
> **💡 Tip**: Start with smaller models like `llama3.2:3b` and upgrade based on your hardware capabilities and performance needs.
|
|
663
771
|
|
|
664
772
|
### Quick Start with Docker
|
|
665
773
|
|
|
666
774
|
1. **Start Ollama service:**
|
|
775
|
+
|
|
667
776
|
```bash
|
|
668
|
-
docker
|
|
777
|
+
docker compose up -d ollama
|
|
669
778
|
```
|
|
670
779
|
|
|
671
780
|
2. **Pull required models:**
|
|
781
|
+
|
|
672
782
|
```bash
|
|
673
783
|
# Automatic setup
|
|
674
|
-
docker
|
|
675
|
-
|
|
784
|
+
docker compose run --rm ollama-init
|
|
785
|
+
|
|
676
786
|
# Or manual setup
|
|
677
787
|
./scripts/setup-ollama-models.sh
|
|
678
788
|
```
|
|
679
789
|
|
|
680
790
|
3. **Run examples:**
|
|
791
|
+
|
|
681
792
|
```bash
|
|
682
793
|
# Set environment variable
|
|
683
794
|
export OLLAMA_URL=http://localhost:11434
|
|
684
|
-
|
|
795
|
+
|
|
685
796
|
# Run examples
|
|
686
797
|
ruby examples/custom_contexts.rb
|
|
687
798
|
```
|
|
@@ -702,9 +813,9 @@ The included `docker-compose.yml` provides:
|
|
|
702
813
|
services:
|
|
703
814
|
ollama:
|
|
704
815
|
ports:
|
|
705
|
-
- "11434:11434"
|
|
816
|
+
- "11434:11434" # Ollama API port
|
|
706
817
|
volumes:
|
|
707
|
-
- ollama_data:/root/.ollama
|
|
818
|
+
- ollama_data:/root/.ollama # Persist models
|
|
708
819
|
environment:
|
|
709
820
|
- OLLAMA_HOST=0.0.0.0
|
|
710
821
|
- OLLAMA_ORIGINS=*
|
|
@@ -732,7 +843,7 @@ services:
|
|
|
732
843
|
# Ollama Configuration
|
|
733
844
|
OLLAMA_URL=http://localhost:11434
|
|
734
845
|
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
|
735
|
-
OLLAMA_CHAT_MODEL=llama3.
|
|
846
|
+
OLLAMA_CHAT_MODEL=llama3.2:3b
|
|
736
847
|
|
|
737
848
|
# Optional: Other AI providers
|
|
738
849
|
OPENAI_API_KEY=your_key_here
|
|
@@ -749,7 +860,7 @@ curl http://localhost:11434/api/tags
|
|
|
749
860
|
# Pull a specific model
|
|
750
861
|
curl -X POST http://localhost:11434/api/pull \
|
|
751
862
|
-H "Content-Type: application/json" \
|
|
752
|
-
-d '{"name": "llama3.
|
|
863
|
+
-d '{ "name": "llama3.2:3b"}'
|
|
753
864
|
|
|
754
865
|
# Health check
|
|
755
866
|
curl http://localhost:11434/api/version
|
|
@@ -770,6 +881,7 @@ For production use:
|
|
|
770
881
|
#### **Common Issues:**
|
|
771
882
|
|
|
772
883
|
**Out of Memory Errors:**
|
|
884
|
+
|
|
773
885
|
```bash
|
|
774
886
|
# Check available memory
|
|
775
887
|
free -h
|
|
@@ -778,10 +890,11 @@ free -h
|
|
|
778
890
|
# Settings > Resources > Memory: 8GB+
|
|
779
891
|
|
|
780
892
|
# Use smaller models if hardware limited
|
|
781
|
-
OLLAMA_CHAT_MODEL=llama3.
|
|
893
|
+
OLLAMA_CHAT_MODEL=llama3.2:3b ruby examples/custom_contexts.rb
|
|
782
894
|
```
|
|
783
895
|
|
|
784
896
|
**Slow Model Loading:**
|
|
897
|
+
|
|
785
898
|
```bash
|
|
786
899
|
# Check disk I/O
|
|
787
900
|
iostat -x 1
|
|
@@ -791,15 +904,17 @@ iostat -x 1
|
|
|
791
904
|
```
|
|
792
905
|
|
|
793
906
|
**Model Download Failures:**
|
|
907
|
+
|
|
794
908
|
```bash
|
|
795
909
|
# Check disk space
|
|
796
910
|
df -h
|
|
797
911
|
|
|
798
912
|
# Manually pull models with retry
|
|
799
|
-
docker exec prescient-ollama ollama pull llama3.
|
|
913
|
+
docker exec prescient-ollama ollama pull llama3.2:3b
|
|
800
914
|
```
|
|
801
915
|
|
|
802
916
|
**GPU Not Detected:**
|
|
917
|
+
|
|
803
918
|
```bash
|
|
804
919
|
# Check NVIDIA Docker runtime
|
|
805
920
|
docker run --rm --gpus all nvidia/cuda:11.8-base nvidia-smi
|
|
@@ -820,7 +935,7 @@ docker logs prescient-ollama
|
|
|
820
935
|
# Test API response time
|
|
821
936
|
time curl -X POST http://localhost:11434/api/generate \
|
|
822
937
|
-H "Content-Type: application/json" \
|
|
823
|
-
-d '{"model": "llama3.
|
|
938
|
+
-d '{ "model": "llama3.2:3b", "prompt": "Hello", "stream": false}'
|
|
824
939
|
```
|
|
825
940
|
|
|
826
941
|
## Testing
|
|
@@ -828,11 +943,44 @@ time curl -X POST http://localhost:11434/api/generate \
|
|
|
828
943
|
The gem includes comprehensive test coverage:
|
|
829
944
|
|
|
830
945
|
```bash
|
|
831
|
-
bundle exec
|
|
946
|
+
bundle exec rake test
|
|
832
947
|
```
|
|
833
948
|
|
|
834
949
|
## Development
|
|
835
950
|
|
|
951
|
+
### Opt-in live provider smoke tests
|
|
952
|
+
|
|
953
|
+
The default test suite uses mocked provider interactions. To exercise a live
|
|
954
|
+
provider explicitly, set `PRESCIENT_LIVE_SMOKE=1` and select one or more
|
|
955
|
+
providers with `PRESCIENT_LIVE_PROVIDERS`:
|
|
956
|
+
|
|
957
|
+
```bash
|
|
958
|
+
PRESCIENT_LIVE_SMOKE=1 \
|
|
959
|
+
PRESCIENT_LIVE_PROVIDERS=openai \
|
|
960
|
+
OPENAI_API_KEY=... \
|
|
961
|
+
bundle exec ruby -Itest test/prescient/live_provider_smoke_test.rb
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
Supported provider names are `ollama`, `anthropic`, `openai`, and
|
|
965
|
+
`huggingface`. The corresponding provider environment variables and model
|
|
966
|
+
overrides are honored. These tests are never live unless both opt-in
|
|
967
|
+
variables are set.
|
|
968
|
+
|
|
969
|
+
### RBS and Steep
|
|
970
|
+
|
|
971
|
+
Validate the curated core API signatures with:
|
|
972
|
+
|
|
973
|
+
```bash
|
|
974
|
+
bundle exec rake rbs:validate
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
Generate disposable prototypes for comparison with:
|
|
978
|
+
|
|
979
|
+
```bash
|
|
980
|
+
bundle exec rake rbs:prototype
|
|
981
|
+
bundle exec rake rbs:diff
|
|
982
|
+
```
|
|
983
|
+
|
|
836
984
|
After checking out the repo, run:
|
|
837
985
|
|
|
838
986
|
```bash
|
|
@@ -856,34 +1004,3 @@ bundle exec rake install
|
|
|
856
1004
|
## License
|
|
857
1005
|
|
|
858
1006
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
859
|
-
|
|
860
|
-
## Roadmap
|
|
861
|
-
|
|
862
|
-
### Version 0.2.0 (Planned)
|
|
863
|
-
|
|
864
|
-
- **MariaDB Vector Support**: Integration with MariaDB using external vector databases
|
|
865
|
-
- **Hybrid Database Architecture**: Support for MariaDB + Milvus/Qdrant combinations
|
|
866
|
-
- **Vector Database Adapters**: Pluggable adapters for different vector storage backends
|
|
867
|
-
- **Enhanced Chunking Strategies**: Smart document splitting with multiple algorithms
|
|
868
|
-
- **Search Result Ranking**: Advanced scoring and re-ranking capabilities
|
|
869
|
-
|
|
870
|
-
### Version 0.3.0 (Future)
|
|
871
|
-
|
|
872
|
-
- **Streaming Responses**: Real-time response streaming for chat applications
|
|
873
|
-
- **Multi-Model Ensembles**: Combine responses from multiple AI providers
|
|
874
|
-
- **Advanced Analytics**: Search performance insights and usage analytics
|
|
875
|
-
- **Cloud Provider Integration**: Direct support for Pinecone, Weaviate, etc.
|
|
876
|
-
|
|
877
|
-
## Changelog
|
|
878
|
-
|
|
879
|
-
### Version 0.1.0
|
|
880
|
-
|
|
881
|
-
- Initial release
|
|
882
|
-
- Support for Ollama, Anthropic, OpenAI, and HuggingFace
|
|
883
|
-
- Unified interface for embeddings and text generation
|
|
884
|
-
- Comprehensive error handling and retry logic
|
|
885
|
-
- Health monitoring capabilities
|
|
886
|
-
- PostgreSQL pgvector integration with complete Docker setup
|
|
887
|
-
- Vector similarity search with multiple distance functions
|
|
888
|
-
- Document chunking and metadata filtering
|
|
889
|
-
- Performance optimization guides and troubleshooting
|