prescient 0.2.0 → 0.4.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 +0 -2
- data/.yardopts +3 -6
- data/CHANGELOG.md +53 -4
- data/INTEGRATION_GUIDE.md +27 -29
- data/LICENSE.txt +1 -1
- data/README.md +217 -74
- data/Rakefile +82 -7
- data/Steepfile +17 -0
- data/VECTOR_SEARCH_GUIDE.md +32 -8
- 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/exe/prescient +7 -0
- data/lib/prescient/base.rb +81 -26
- data/lib/prescient/cli.rb +287 -0
- data/lib/prescient/client.rb +85 -39
- data/lib/prescient/errors.rb +51 -0
- data/lib/prescient/pgvector.rb +194 -0
- data/lib/prescient/provider/anthropic.rb +57 -56
- data/lib/prescient/provider/huggingface.rb +79 -78
- data/lib/prescient/provider/ollama.rb +48 -37
- data/lib/prescient/provider/openai.rb +43 -35
- data/lib/prescient/version.rb +2 -1
- data/lib/prescient.rb +86 -63
- data/scripts/setup-ollama-models.sh +2 -2
- data/sig/prescient.rbs +233 -1
- metadata +25 -218
- data/CHANGELOG.pdf +0 -0
- data/prescient.gemspec +0 -53
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](https://github.com/kanutocd/prescient/tree/main/examples)**,
|
|
6
|
+
**[Rails integration guide](https://github.com/kanutocd/prescient/blob/main/INTEGRATION_GUIDE.md)**, and
|
|
7
|
+
**[pgvector guide](https://github.com/kanutocd/prescient/blob/main/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
|
|
|
@@ -58,6 +62,90 @@ Or install it yourself as:
|
|
|
58
62
|
gem install prescient
|
|
59
63
|
```
|
|
60
64
|
|
|
65
|
+
## Command-Line Interface
|
|
66
|
+
|
|
67
|
+
Prescient includes a thin CLI for provider inspection and common operations:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
prescient providers
|
|
71
|
+
prescient health
|
|
72
|
+
prescient config validate
|
|
73
|
+
prescient generate "Explain Ruby Ractors"
|
|
74
|
+
prescient embed "Ruby is a programming language"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Supported options include:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
--provider NAME Select a provider
|
|
81
|
+
--model NAME Override the selected operation's model
|
|
82
|
+
--chat-model NAME Override the chat model for generation
|
|
83
|
+
--embedding-model NAME Override the embedding model
|
|
84
|
+
--api-key KEY Use an API key for the operation
|
|
85
|
+
--api-key-env NAME Read the API key from an environment variable
|
|
86
|
+
--format FORMAT Select text or json output
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Use `--api-key-env` to source credentials from an environment variable. The
|
|
90
|
+
direct `--api-key` option is available for ephemeral automation but may be
|
|
91
|
+
visible in shell history or process listings. Use `--format json` for
|
|
92
|
+
machine-readable output and stdin for shell pipelines:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
printf '%s' "Explain PostgreSQL logical replication" | \
|
|
96
|
+
prescient generate --provider openai --format json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Example JSON output:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"response": "PostgreSQL logical replication is a method of replicating data between PostgreSQL databases at a logical level, allowing fine-grained control over which data is replicated and how. Unlike physical replication, which copies the entire database cluster’s data files at the storage level, logical replication works by sending changes to data (such as INSERT, UPDATE, DELETE operations) based on logical changes in the database.\n\n### Key Features of PostgreSQL Logical Replication\n\n1. **Row-Level Replication:** Logical replication replicates data changes at the row level. It streams changes to individual tables rather than the entire database.\n\n2. **Selective Replication:** You can choose specific tables to replicate rather than the whole database. This makes it useful for replicating subsets of data.\n\n3. **Asynchronous Replication:** Changes are sent asynchronously from the publisher (source) to the subscriber (target). This means there may be a slight delay between when changes are made and when they appear on the subscriber.\n\n4. **Supports Heterogeneous Setups:** Logical replication can be used between different major versions of PostgreSQL, allowing upgrades with minimal downtime. It can also be used for replication between different architectures or operating systems.\n\n5. **Bidirectional Replication:** By configuring multiple publishers and subscribers, logical replication can support multi-master setups, although care must be taken to avoid conflicts.\n\n### How Logical Replication Works\n\n- **Publisher:** The database that sends data changes. It defines one or more publications, which specify which tables and changes (inserts, updates, deletes) to replicate.\n \n- **Subscriber:** The database that receives and applies the changes. It subscribes to one or more publications from the publisher.\n\nWhen a change occurs on the publisher's table, the change is captured and sent to the subscriber, where it is applied to the corresponding table.\n\n### Setting Up Logical Replication (Basic Steps)\n\n1. **Enable required settings:** Ensure the PostgreSQL server has `wal_level` set to `logical`, and configure `max_replication_slots` and `max_wal_senders` appropriately.\n\n2. **Create a publication on the publisher:**\n\n ```sql\n CREATE PUBLICATION my_publication FOR TABLE my_table;\n ```\n\n3. **Create a subscription on the subscriber:**\n\n ```sql\n CREATE SUBSCRIPTION my_subscription\n CONNECTION 'host=publisher_host dbname=publisher_db user=replicator password=secret'\n PUBLICATION my_publication;\n ```\n\nOnce set up, changes to `my_table` on the publisher will be replicated to the subscriber.\n\n### Use Cases\n\n- **Selective data replication:** Replicating only certain tables or rows.\n- **Data integration:** Feeding data from multiple sources into a central database.\n- **Upgrading PostgreSQL versions:** Using logical replication to migrate data with minimal downtime.\n- **Multi-datacenter replication:** Replicating data across geographically distributed systems.\n\n---\n\nIn summary, PostgreSQL logical replication is a flexible, table-level replication mechanism that allows selective, asynchronous replication of data changes between PostgreSQL databases, useful for upgrades, distributed architectures, and data integration scenarios.",
|
|
104
|
+
"model": "gpt-4.1-mini",
|
|
105
|
+
"provider": "openai",
|
|
106
|
+
"processing_time": null,
|
|
107
|
+
"metadata": {
|
|
108
|
+
"usage": {
|
|
109
|
+
"prompt_tokens": 38,
|
|
110
|
+
"completion_tokens": 632,
|
|
111
|
+
"total_tokens": 670,
|
|
112
|
+
"prompt_tokens_details": {
|
|
113
|
+
"cached_tokens": 0,
|
|
114
|
+
"audio_tokens": 0
|
|
115
|
+
},
|
|
116
|
+
"completion_tokens_details": {
|
|
117
|
+
"reasoning_tokens": 0,
|
|
118
|
+
"audio_tokens": 0,
|
|
119
|
+
"accepted_prediction_tokens": 0,
|
|
120
|
+
"rejected_prediction_tokens": 0
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"finish_reason": "stop"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For automated model and credential overrides:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
prescient generate \
|
|
132
|
+
--provider openai \
|
|
133
|
+
--chat-model gpt-4.1-mini \
|
|
134
|
+
--api-key-env OPENAI_API_KEY \
|
|
135
|
+
--format json \
|
|
136
|
+
"Explain PostgreSQL logical replication"
|
|
137
|
+
|
|
138
|
+
prescient embed \
|
|
139
|
+
--provider openai \
|
|
140
|
+
--embedding-model text-embedding-3-small \
|
|
141
|
+
--api-key-env OPENAI_API_KEY \
|
|
142
|
+
"Ruby is a programming language"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The CLI writes results to stdout, diagnostics to stderr, and returns a
|
|
146
|
+
non-zero status for invalid usage, provider errors, or unreachable health
|
|
147
|
+
checks. It uses the same `Prescient::Client` execution path as Ruby callers.
|
|
148
|
+
|
|
61
149
|
## Configuration
|
|
62
150
|
|
|
63
151
|
### Environment Variables
|
|
@@ -66,21 +154,21 @@ gem install prescient
|
|
|
66
154
|
# Ollama (Local)
|
|
67
155
|
OLLAMA_URL=http://localhost:11434
|
|
68
156
|
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
|
69
|
-
OLLAMA_CHAT_MODEL=llama3.
|
|
157
|
+
OLLAMA_CHAT_MODEL=llama3.2:3b
|
|
70
158
|
|
|
71
159
|
# Anthropic
|
|
72
160
|
ANTHROPIC_API_KEY=your_api_key
|
|
73
|
-
ANTHROPIC_MODEL=claude-
|
|
161
|
+
ANTHROPIC_MODEL=claude-sonnet-4-20250514
|
|
74
162
|
|
|
75
163
|
# OpenAI
|
|
76
164
|
OPENAI_API_KEY=your_api_key
|
|
77
165
|
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
|
78
|
-
OPENAI_CHAT_MODEL=gpt-
|
|
166
|
+
OPENAI_CHAT_MODEL=gpt-4.1-mini
|
|
79
167
|
|
|
80
168
|
# HuggingFace
|
|
81
169
|
HUGGINGFACE_API_KEY=your_api_key
|
|
82
170
|
HUGGINGFACE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
|
83
|
-
HUGGINGFACE_CHAT_MODEL=
|
|
171
|
+
HUGGINGFACE_CHAT_MODEL=google/gemma-2-2b-it
|
|
84
172
|
```
|
|
85
173
|
|
|
86
174
|
### Programmatic Configuration
|
|
@@ -96,23 +184,23 @@ Prescient.configure do |config|
|
|
|
96
184
|
config.retry_delay = 1.0
|
|
97
185
|
|
|
98
186
|
# Add custom Ollama configuration
|
|
99
|
-
config.add_provider(:ollama, Prescient::Ollama
|
|
187
|
+
config.add_provider(:ollama, Prescient::Provider::Ollama,
|
|
100
188
|
url: 'http://localhost:11434',
|
|
101
189
|
embedding_model: 'nomic-embed-text',
|
|
102
|
-
chat_model: 'llama3.
|
|
190
|
+
chat_model: 'llama3.2:3b'
|
|
103
191
|
)
|
|
104
192
|
|
|
105
193
|
# Add Anthropic
|
|
106
|
-
config.add_provider(:anthropic, Prescient::Anthropic
|
|
194
|
+
config.add_provider(:anthropic, Prescient::Provider::Anthropic,
|
|
107
195
|
api_key: ENV['ANTHROPIC_API_KEY'],
|
|
108
|
-
model: 'claude-
|
|
196
|
+
model: 'claude-sonnet-4-20250514'
|
|
109
197
|
)
|
|
110
198
|
|
|
111
199
|
# Add OpenAI
|
|
112
|
-
config.add_provider(:openai, Prescient::OpenAI
|
|
200
|
+
config.add_provider(:openai, Prescient::Provider::OpenAI,
|
|
113
201
|
api_key: ENV['OPENAI_API_KEY'],
|
|
114
202
|
embedding_model: 'text-embedding-3-small',
|
|
115
|
-
chat_model: 'gpt-
|
|
203
|
+
chat_model: 'gpt-4.1-mini'
|
|
116
204
|
)
|
|
117
205
|
end
|
|
118
206
|
```
|
|
@@ -127,19 +215,19 @@ Prescient.configure do |config|
|
|
|
127
215
|
config.add_provider(:primary, Prescient::Provider::OpenAI,
|
|
128
216
|
api_key: ENV['OPENAI_API_KEY'],
|
|
129
217
|
embedding_model: 'text-embedding-3-small',
|
|
130
|
-
chat_model: 'gpt-
|
|
218
|
+
chat_model: 'gpt-4.1-mini'
|
|
131
219
|
)
|
|
132
220
|
|
|
133
221
|
# Configure backup providers
|
|
134
222
|
config.add_provider(:backup1, Prescient::Provider::Anthropic,
|
|
135
223
|
api_key: ENV['ANTHROPIC_API_KEY'],
|
|
136
|
-
model: 'claude-
|
|
224
|
+
model: 'claude-sonnet-4-20250514'
|
|
137
225
|
)
|
|
138
226
|
|
|
139
227
|
config.add_provider(:backup2, Prescient::Provider::Ollama,
|
|
140
228
|
url: 'http://localhost:11434',
|
|
141
229
|
embedding_model: 'nomic-embed-text',
|
|
142
|
-
chat_model: 'llama3.
|
|
230
|
+
chat_model: 'llama3.2:3b'
|
|
143
231
|
)
|
|
144
232
|
|
|
145
233
|
# Configure fallback order
|
|
@@ -158,9 +246,10 @@ response = Prescient.generate_response("Hello", provider: :primary, enable_fallb
|
|
|
158
246
|
|
|
159
247
|
**Fallback Behavior:**
|
|
160
248
|
- When a provider fails with a persistent error, Prescient automatically tries the next available provider
|
|
161
|
-
-
|
|
162
|
-
- If no fallback providers are configured, all
|
|
249
|
+
- Configured fallback providers are tried in order; the provider operation determines availability
|
|
250
|
+
- If no fallback providers are configured, all configured providers are tried as fallbacks
|
|
163
251
|
- Transient errors (rate limits, timeouts) still use retry logic before fallback
|
|
252
|
+
- Provider-service failures, connection failures, rate limits, and unavailable models may trigger fallback; authentication and invalid-request errors are returned to the caller
|
|
164
253
|
- The fallback process preserves all method arguments and options
|
|
165
254
|
|
|
166
255
|
## Usage
|
|
@@ -175,7 +264,7 @@ client = Prescient.client
|
|
|
175
264
|
|
|
176
265
|
# Generate embeddings
|
|
177
266
|
embedding = client.generate_embedding("Your text here")
|
|
178
|
-
# => [0.1, 0.2, 0.3, ...] (
|
|
267
|
+
# => [0.1, 0.2, 0.3, ...] (model-dependent vector dimensions)
|
|
179
268
|
|
|
180
269
|
# Generate text responses
|
|
181
270
|
response = client.generate_response("What is Ruby?")
|
|
@@ -238,12 +327,20 @@ end
|
|
|
238
327
|
|
|
239
328
|
### Health Monitoring
|
|
240
329
|
|
|
330
|
+
Health results separate transport reachability from configured-model readiness:
|
|
331
|
+
`reachable: true` means the provider answered, while `ready: true` means the
|
|
332
|
+
configured operation models were found or validated. Fallback uses the actual
|
|
333
|
+
operation and does not perform an additional health request.
|
|
334
|
+
|
|
241
335
|
```ruby
|
|
242
336
|
# Check all providers
|
|
243
|
-
|
|
337
|
+
Prescient.configuration.providers.keys.each do |provider|
|
|
244
338
|
health = Prescient.health_check(provider: provider)
|
|
245
339
|
puts "#{provider}: #{health[:status]}"
|
|
246
|
-
puts "
|
|
340
|
+
puts "Reachable: #{health[:reachable]}"
|
|
341
|
+
puts "Ready: #{health[:ready]}"
|
|
342
|
+
rescue Prescient::Error => e
|
|
343
|
+
puts "#{provider}: unavailable (#{e.message})"
|
|
247
344
|
end
|
|
248
345
|
```
|
|
249
346
|
|
|
@@ -256,7 +353,7 @@ Prescient.configure do |config|
|
|
|
256
353
|
config.add_provider(:customer_service, Prescient::Provider::OpenAI,
|
|
257
354
|
api_key: ENV['OPENAI_API_KEY'],
|
|
258
355
|
embedding_model: 'text-embedding-3-small',
|
|
259
|
-
chat_model: 'gpt-
|
|
356
|
+
chat_model: 'gpt-4.1-mini',
|
|
260
357
|
prompt_templates: {
|
|
261
358
|
system_prompt: 'You are a friendly customer service representative.',
|
|
262
359
|
no_context_template: <<~TEMPLATE.strip,
|
|
@@ -330,12 +427,12 @@ Prescient.configure do |config|
|
|
|
330
427
|
context_configs: {
|
|
331
428
|
'product' => {
|
|
332
429
|
fields: %w[name description price category brand],
|
|
333
|
-
format: '%{
|
|
430
|
+
format: '%{name} by %{brand}: %{description} - $%{price} (%{category})',
|
|
334
431
|
embedding_fields: %w[name description category brand]
|
|
335
432
|
},
|
|
336
433
|
'review' => {
|
|
337
434
|
fields: %w[product_name rating review_text reviewer_name],
|
|
338
|
-
format: '%{
|
|
435
|
+
format: '%{product_name} - %{rating}/5 stars: "%{review_text}"',
|
|
339
436
|
embedding_fields: %w[product_name review_text]
|
|
340
437
|
}
|
|
341
438
|
}
|
|
@@ -363,6 +460,14 @@ response = client.generate_response("I need a laptop for work", products)
|
|
|
363
460
|
- **fields** - Array of field names available for this context type
|
|
364
461
|
- **format** - Template string for displaying context items
|
|
365
462
|
- **embedding_fields** - Specific fields to use when generating embeddings
|
|
463
|
+
- **context_excluded_fields** - Additional field names excluded from generic embedding text; built-in metadata exclusions remain active
|
|
464
|
+
|
|
465
|
+
```ruby
|
|
466
|
+
config.add_provider(:openai, Prescient::Provider::OpenAI,
|
|
467
|
+
api_key: ENV['OPENAI_API_KEY'],
|
|
468
|
+
context_excluded_fields: %w[tenant_id internal_notes]
|
|
469
|
+
)
|
|
470
|
+
```
|
|
366
471
|
|
|
367
472
|
### Automatic Context Detection
|
|
368
473
|
|
|
@@ -384,7 +489,7 @@ The system works perfectly without any context configuration - it will:
|
|
|
384
489
|
|
|
385
490
|
```ruby
|
|
386
491
|
# No context_configs needed - works with any data!
|
|
387
|
-
client = Prescient.client
|
|
492
|
+
client = Prescient.client
|
|
388
493
|
response = client.generate_response("Analyze this", [
|
|
389
494
|
{ 'title' => 'Issue', 'content' => 'Server down', 'created_at' => '2024-01-01' },
|
|
390
495
|
{ 'name' => 'Alert', 'message' => 'High CPU usage', 'timestamp' => 1234567 }
|
|
@@ -393,9 +498,44 @@ response = client.generate_response("Analyze this", [
|
|
|
393
498
|
|
|
394
499
|
See `examples/custom_contexts.rb` for complete examples.
|
|
395
500
|
|
|
501
|
+
### Sensitive Provider Options
|
|
502
|
+
|
|
503
|
+
`provider_info` always removes the built-in sensitive keys (`api_key`,
|
|
504
|
+
`password`, `token`, and `secret`). Add project-specific keys globally with
|
|
505
|
+
`sensitive_keys`; nested hashes and arrays are sanitized recursively:
|
|
506
|
+
|
|
507
|
+
```ruby
|
|
508
|
+
Prescient.configure do |config|
|
|
509
|
+
config.sensitive_keys = %w[workspace_secret private_key]
|
|
510
|
+
end
|
|
511
|
+
```
|
|
512
|
+
|
|
396
513
|
## Vector Database Integration (pgvector)
|
|
397
514
|
|
|
398
|
-
Prescient
|
|
515
|
+
`Prescient::Pgvector::Store` is an opt-in PostgreSQL integration for storing
|
|
516
|
+
and searching embeddings. It accepts a PG-compatible connection, so `pg` stays
|
|
517
|
+
an application dependency rather than a required dependency of this gem.
|
|
518
|
+
|
|
519
|
+
```ruby
|
|
520
|
+
require 'pg'
|
|
521
|
+
|
|
522
|
+
connection = PG.connect(dbname: 'my_app')
|
|
523
|
+
store = Prescient::Pgvector::Store.new(connection: connection, dimensions: 1536)
|
|
524
|
+
store.install!
|
|
525
|
+
store.create_index!
|
|
526
|
+
|
|
527
|
+
embedding = Prescient.generate_embedding('Semantic search', provider: :openai)
|
|
528
|
+
store.upsert(
|
|
529
|
+
id: 'document-42', embedding:, provider: 'openai', model: 'text-embedding-3-small',
|
|
530
|
+
content: 'Semantic search', metadata: { category: 'guide' }
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
results = store.search(embedding:, provider: :openai, model: 'text-embedding-3-small')
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Every vector must exactly match the store's configured dimensions; Prescient
|
|
537
|
+
never pads or truncates vectors. The existing application-schema example below
|
|
538
|
+
remains available for projects that need documents, chunks, and custom metadata.
|
|
399
539
|
|
|
400
540
|
### Setup with Docker
|
|
401
541
|
|
|
@@ -403,7 +543,7 @@ The included `docker-compose.yml` provides a complete setup with PostgreSQL + pg
|
|
|
403
543
|
|
|
404
544
|
```bash
|
|
405
545
|
# Start PostgreSQL with pgvector
|
|
406
|
-
docker
|
|
546
|
+
docker compose up -d postgres
|
|
407
547
|
|
|
408
548
|
# The database will automatically:
|
|
409
549
|
# - Install pgvector extension
|
|
@@ -447,7 +587,7 @@ embedding = client.generate_embedding(text)
|
|
|
447
587
|
vector_str = "[#{embedding.join(',')}]"
|
|
448
588
|
db.exec_params(
|
|
449
589
|
"INSERT INTO document_embeddings (document_id, embedding_provider, embedding_model, embedding_dimensions, embedding, embedding_text) VALUES ($1, $2, $3, $4, $5, $6)",
|
|
450
|
-
[doc_id, 'ollama', 'nomic-embed-text',
|
|
590
|
+
[doc_id, 'ollama', 'nomic-embed-text', embedding.length, vector_str, text]
|
|
451
591
|
)
|
|
452
592
|
|
|
453
593
|
# Perform similarity search
|
|
@@ -588,7 +728,7 @@ Run the complete vector search example:
|
|
|
588
728
|
|
|
589
729
|
```bash
|
|
590
730
|
# Start services
|
|
591
|
-
docker
|
|
731
|
+
docker compose up -d postgres ollama
|
|
592
732
|
|
|
593
733
|
# Run example
|
|
594
734
|
DB_HOST=localhost ruby examples/vector_search.rb
|
|
@@ -606,7 +746,7 @@ The example demonstrates:
|
|
|
606
746
|
### Custom Provider Implementation
|
|
607
747
|
|
|
608
748
|
```ruby
|
|
609
|
-
class MyCustomProvider < Prescient::
|
|
749
|
+
class MyCustomProvider < Prescient::Base
|
|
610
750
|
def generate_embedding(text, **options)
|
|
611
751
|
# Your implementation
|
|
612
752
|
end
|
|
@@ -642,7 +782,7 @@ client = Prescient.client(:ollama)
|
|
|
642
782
|
info = client.provider_info
|
|
643
783
|
|
|
644
784
|
puts info[:name] # => :ollama
|
|
645
|
-
puts info[:class] # => "
|
|
785
|
+
puts info[:class] # => "Ollama"
|
|
646
786
|
puts info[:available] # => true
|
|
647
787
|
puts info[:options] # => { ... } (excluding sensitive data)
|
|
648
788
|
```
|
|
@@ -651,7 +791,7 @@ puts info[:options] # => { ... } (excluding sensitive data)
|
|
|
651
791
|
|
|
652
792
|
### Ollama
|
|
653
793
|
|
|
654
|
-
- Model management: `pull_model`, `
|
|
794
|
+
- Model management: `pull_model`, `available_models`
|
|
655
795
|
- Local deployment support
|
|
656
796
|
- No API costs
|
|
657
797
|
|
|
@@ -665,6 +805,7 @@ puts info[:options] # => { ... } (excluding sensitive data)
|
|
|
665
805
|
- Multiple embedding model sizes
|
|
666
806
|
- Latest GPT models
|
|
667
807
|
- Reliable performance
|
|
808
|
+
- Uses the Chat Completions endpoint for the stable normalized response contract; the newer Responses API remains a future compatibility extension.
|
|
668
809
|
|
|
669
810
|
### HuggingFace
|
|
670
811
|
|
|
@@ -692,7 +833,7 @@ Before starting, ensure your system meets the minimum requirements for running O
|
|
|
692
833
|
| Model | RAM Required | Storage | Notes |
|
|
693
834
|
| ------------------ | ------------ | ------- | --------------------------------- |
|
|
694
835
|
| `nomic-embed-text` | 1GB | 274MB | Embedding model |
|
|
695
|
-
| `llama3.
|
|
836
|
+
| `llama3.2:3b` | 2GB | 2.0GB | Chat model (3B parameters) |
|
|
696
837
|
| `llama3.1:70b` | 64GB+ | 40GB | Large chat model (70B parameters) |
|
|
697
838
|
| `codellama:7b` | 8GB | 3.8GB | Code generation model |
|
|
698
839
|
|
|
@@ -710,21 +851,21 @@ Before starting, ensure your system meets the minimum requirements for running O
|
|
|
710
851
|
- **Docker**: NVIDIA Container Toolkit installed
|
|
711
852
|
- **Performance**: 3-10x faster inference with compatible models
|
|
712
853
|
|
|
713
|
-
> **💡 Tip**: Start with smaller models like `llama3.
|
|
854
|
+
> **💡 Tip**: Start with smaller models like `llama3.2:3b` and upgrade based on your hardware capabilities and performance needs.
|
|
714
855
|
|
|
715
856
|
### Quick Start with Docker
|
|
716
857
|
|
|
717
858
|
1. **Start Ollama service:**
|
|
718
859
|
|
|
719
860
|
```bash
|
|
720
|
-
docker
|
|
861
|
+
docker compose up -d ollama
|
|
721
862
|
```
|
|
722
863
|
|
|
723
864
|
2. **Pull required models:**
|
|
724
865
|
|
|
725
866
|
```bash
|
|
726
867
|
# Automatic setup
|
|
727
|
-
docker
|
|
868
|
+
docker compose run --rm ollama-init
|
|
728
869
|
|
|
729
870
|
# Or manual setup
|
|
730
871
|
./scripts/setup-ollama-models.sh
|
|
@@ -786,7 +927,7 @@ services:
|
|
|
786
927
|
# Ollama Configuration
|
|
787
928
|
OLLAMA_URL=http://localhost:11434
|
|
788
929
|
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
|
789
|
-
OLLAMA_CHAT_MODEL=llama3.
|
|
930
|
+
OLLAMA_CHAT_MODEL=llama3.2:3b
|
|
790
931
|
|
|
791
932
|
# Optional: Other AI providers
|
|
792
933
|
OPENAI_API_KEY=your_key_here
|
|
@@ -803,7 +944,7 @@ curl http://localhost:11434/api/tags
|
|
|
803
944
|
# Pull a specific model
|
|
804
945
|
curl -X POST http://localhost:11434/api/pull \
|
|
805
946
|
-H "Content-Type: application/json" \
|
|
806
|
-
-d '{ "name": "llama3.
|
|
947
|
+
-d '{ "name": "llama3.2:3b"}'
|
|
807
948
|
|
|
808
949
|
# Health check
|
|
809
950
|
curl http://localhost:11434/api/version
|
|
@@ -833,7 +974,7 @@ free -h
|
|
|
833
974
|
# Settings > Resources > Memory: 8GB+
|
|
834
975
|
|
|
835
976
|
# Use smaller models if hardware limited
|
|
836
|
-
OLLAMA_CHAT_MODEL=llama3.
|
|
977
|
+
OLLAMA_CHAT_MODEL=llama3.2:3b ruby examples/custom_contexts.rb
|
|
837
978
|
```
|
|
838
979
|
|
|
839
980
|
**Slow Model Loading:**
|
|
@@ -853,7 +994,7 @@ iostat -x 1
|
|
|
853
994
|
df -h
|
|
854
995
|
|
|
855
996
|
# Manually pull models with retry
|
|
856
|
-
docker exec prescient-ollama ollama pull llama3.
|
|
997
|
+
docker exec prescient-ollama ollama pull llama3.2:3b
|
|
857
998
|
```
|
|
858
999
|
|
|
859
1000
|
**GPU Not Detected:**
|
|
@@ -878,7 +1019,7 @@ docker logs prescient-ollama
|
|
|
878
1019
|
# Test API response time
|
|
879
1020
|
time curl -X POST http://localhost:11434/api/generate \
|
|
880
1021
|
-H "Content-Type: application/json" \
|
|
881
|
-
-d '{ "model": "llama3.
|
|
1022
|
+
-d '{ "model": "llama3.2:3b", "prompt": "Hello", "stream": false}'
|
|
882
1023
|
```
|
|
883
1024
|
|
|
884
1025
|
## Testing
|
|
@@ -886,11 +1027,44 @@ time curl -X POST http://localhost:11434/api/generate \
|
|
|
886
1027
|
The gem includes comprehensive test coverage:
|
|
887
1028
|
|
|
888
1029
|
```bash
|
|
889
|
-
bundle exec
|
|
1030
|
+
bundle exec rake test
|
|
890
1031
|
```
|
|
891
1032
|
|
|
892
1033
|
## Development
|
|
893
1034
|
|
|
1035
|
+
### Opt-in live provider smoke tests
|
|
1036
|
+
|
|
1037
|
+
The default test suite uses mocked provider interactions. To exercise a live
|
|
1038
|
+
provider explicitly, set `PRESCIENT_LIVE_SMOKE=1` and select one or more
|
|
1039
|
+
providers with `PRESCIENT_LIVE_PROVIDERS`:
|
|
1040
|
+
|
|
1041
|
+
```bash
|
|
1042
|
+
PRESCIENT_LIVE_SMOKE=1 \
|
|
1043
|
+
PRESCIENT_LIVE_PROVIDERS=openai \
|
|
1044
|
+
OPENAI_API_KEY=... \
|
|
1045
|
+
bundle exec ruby -Itest test/prescient/live_provider_smoke_test.rb
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
Supported provider names are `ollama`, `anthropic`, `openai`, and
|
|
1049
|
+
`huggingface`. The corresponding provider environment variables and model
|
|
1050
|
+
overrides are honored. These tests are never live unless both opt-in
|
|
1051
|
+
variables are set.
|
|
1052
|
+
|
|
1053
|
+
### RBS and Steep
|
|
1054
|
+
|
|
1055
|
+
Validate the curated core API signatures with:
|
|
1056
|
+
|
|
1057
|
+
```bash
|
|
1058
|
+
bundle exec rake rbs:validate
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
Generate disposable prototypes for comparison with:
|
|
1062
|
+
|
|
1063
|
+
```bash
|
|
1064
|
+
bundle exec rake rbs:prototype
|
|
1065
|
+
bundle exec rake rbs:diff
|
|
1066
|
+
```
|
|
1067
|
+
|
|
894
1068
|
After checking out the repo, run:
|
|
895
1069
|
|
|
896
1070
|
```bash
|
|
@@ -914,34 +1088,3 @@ bundle exec rake install
|
|
|
914
1088
|
## License
|
|
915
1089
|
|
|
916
1090
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
917
|
-
|
|
918
|
-
## Roadmap
|
|
919
|
-
|
|
920
|
-
### Version 0.2.0 (Planned)
|
|
921
|
-
|
|
922
|
-
- **MariaDB Vector Support**: Integration with MariaDB using external vector databases
|
|
923
|
-
- **Hybrid Database Architecture**: Support for MariaDB + Milvus/Qdrant combinations
|
|
924
|
-
- **Vector Database Adapters**: Pluggable adapters for different vector storage backends
|
|
925
|
-
- **Enhanced Chunking Strategies**: Smart document splitting with multiple algorithms
|
|
926
|
-
- **Search Result Ranking**: Advanced scoring and re-ranking capabilities
|
|
927
|
-
|
|
928
|
-
### Version 0.3.0 (Future)
|
|
929
|
-
|
|
930
|
-
- **Streaming Responses**: Real-time response streaming for chat applications
|
|
931
|
-
- **Multi-Model Ensembles**: Combine responses from multiple AI providers
|
|
932
|
-
- **Advanced Analytics**: Search performance insights and usage analytics
|
|
933
|
-
- **Cloud Provider Integration**: Direct support for Pinecone, Weaviate, etc.
|
|
934
|
-
|
|
935
|
-
## Changelog
|
|
936
|
-
|
|
937
|
-
### Version 0.1.0
|
|
938
|
-
|
|
939
|
-
- Initial release
|
|
940
|
-
- Support for Ollama, Anthropic, OpenAI, and HuggingFace
|
|
941
|
-
- Unified interface for embeddings and text generation
|
|
942
|
-
- Comprehensive error handling and retry logic
|
|
943
|
-
- Health monitoring capabilities
|
|
944
|
-
- PostgreSQL pgvector integration with complete Docker setup
|
|
945
|
-
- Vector similarity search with multiple distance functions
|
|
946
|
-
- Document chunking and metadata filtering
|
|
947
|
-
- Performance optimization guides and troubleshooting
|
data/Rakefile
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
|
+
require "rbconfig"
|
|
4
5
|
require "rake/testtask"
|
|
5
6
|
require "rubocop/rake_task"
|
|
7
|
+
require "yard"
|
|
8
|
+
require "yard/rake/yardoc_task"
|
|
6
9
|
|
|
7
10
|
Rake::TestTask.new(:test) do |t|
|
|
8
11
|
t.libs << "test"
|
|
@@ -10,17 +13,89 @@ Rake::TestTask.new(:test) do |t|
|
|
|
10
13
|
t.test_files = FileList["test/**/*_test.rb"]
|
|
11
14
|
end
|
|
12
15
|
|
|
13
|
-
RuboCop::RakeTask.new
|
|
16
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
|
17
|
+
task.options = ["--parallel"]
|
|
18
|
+
end
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
YARD::Rake::YardocTask.new(:yard)
|
|
21
|
+
namespace :yard do
|
|
22
|
+
desc "Validate YARD documentation coverage"
|
|
23
|
+
task :validate do
|
|
24
|
+
require "open3"
|
|
25
|
+
|
|
26
|
+
stdout, stderr, status = Open3.capture3("bundle", "exec", "yard", "stats")
|
|
27
|
+
text = "#{stdout}\n#{stderr}"
|
|
28
|
+
puts text
|
|
29
|
+
abort("yard stats failed") unless status.success?
|
|
30
|
+
|
|
31
|
+
match = text.match(/([0-9]+(?:\.[0-9]+)?)%\s+documented/)
|
|
32
|
+
abort("Unable to determine YARD coverage") unless match
|
|
33
|
+
|
|
34
|
+
coverage = match[1].to_f
|
|
35
|
+
minimum = 99.0
|
|
36
|
+
if coverage < minimum
|
|
37
|
+
message = format(
|
|
38
|
+
"YARD coverage %<coverage>.2f%% is below %<minimum>.2f%%",
|
|
39
|
+
coverage: coverage,
|
|
40
|
+
minimum: minimum
|
|
41
|
+
)
|
|
42
|
+
abort(message)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
puts format("YARD coverage %.2f%%", coverage)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
namespace :rbs do
|
|
50
|
+
desc "Remove generated RBS prototype files"
|
|
51
|
+
task :clobber do
|
|
52
|
+
sh "rm -rf tmp/sig"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc "Generate disposable RBS prototypes into tmp/sig"
|
|
56
|
+
task :prototype do
|
|
57
|
+
sh "rm -rf tmp/sig"
|
|
58
|
+
sh "mkdir -p tmp/sig"
|
|
59
|
+
sh "bundle exec rbs prototype rb --out-dir=tmp/sig --base-dir=lib lib"
|
|
60
|
+
end
|
|
17
61
|
|
|
18
|
-
desc "
|
|
19
|
-
task :
|
|
20
|
-
|
|
21
|
-
|
|
62
|
+
desc "Validate curated RBS signatures with Steep"
|
|
63
|
+
task :validate do
|
|
64
|
+
sh "bundle exec steep check"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
desc "Open diff between curated and generated signatures"
|
|
68
|
+
task :diff do
|
|
69
|
+
sh "diff -ru sig tmp/sig || true"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc "Generate disposable RBS prototypes and validate curated signatures"
|
|
73
|
+
task check: %i[prototype validate]
|
|
22
74
|
end
|
|
23
75
|
|
|
76
|
+
namespace :examples do
|
|
77
|
+
desc "Validate Ruby example syntax without contacting providers"
|
|
78
|
+
task :syntax do
|
|
79
|
+
Dir["examples/**/*.rb"].sort.each do |file|
|
|
80
|
+
sh RbConfig.ruby, "-c", file
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc "Validate GitHub Actions workflows"
|
|
86
|
+
task :actionlint do
|
|
87
|
+
actionlint = if File.executable?(".tools/bin/actionlint")
|
|
88
|
+
".tools/bin/actionlint"
|
|
89
|
+
else
|
|
90
|
+
ENV.fetch("ACTIONLINT", "actionlint")
|
|
91
|
+
end
|
|
92
|
+
sh actionlint
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
desc "Run tests and linting"
|
|
97
|
+
task default: %w[test rubocop yard yard:validate rbs:validate examples:syntax]
|
|
98
|
+
|
|
24
99
|
desc "Console with gem loaded"
|
|
25
100
|
task :console do
|
|
26
101
|
require "bundler/setup"
|
data/Steepfile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
target :prescient do
|
|
4
|
+
signature 'sig'
|
|
5
|
+
library 'json'
|
|
6
|
+
library 'net-http'
|
|
7
|
+
|
|
8
|
+
check 'lib/prescient/version.rb'
|
|
9
|
+
check 'lib/prescient/errors.rb'
|
|
10
|
+
check 'lib/prescient.rb'
|
|
11
|
+
check 'lib/prescient/client.rb'
|
|
12
|
+
check 'lib/prescient/base.rb'
|
|
13
|
+
check 'lib/prescient/provider/openai.rb'
|
|
14
|
+
check 'lib/prescient/provider/ollama.rb'
|
|
15
|
+
check 'lib/prescient/provider/anthropic.rb'
|
|
16
|
+
check 'lib/prescient/provider/huggingface.rb'
|
|
17
|
+
end
|