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.
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Prescient
2
2
 
3
- Prescient provides a unified interface for AI providers including Ollama (local), Anthropic Claude, OpenAI GPT, and HuggingFace models. Built for prescient applications that need AI predictions with provider switching, error handling, and fallback mechanisms.
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.1, nomic-embed-text, etc.)
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 3 (Haiku, Sonnet, Opus)
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**: GPT-3.5, GPT-4, text-embedding-3-small/large
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.1:8b
157
+ OLLAMA_CHAT_MODEL=llama3.2:3b
70
158
 
71
159
  # Anthropic
72
160
  ANTHROPIC_API_KEY=your_api_key
73
- ANTHROPIC_MODEL=claude-3-haiku-20240307
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-3.5-turbo
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=microsoft/DialoGPT-medium
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::Provider,
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.1:8b'
190
+ chat_model: 'llama3.2:3b'
103
191
  )
104
192
 
105
193
  # Add Anthropic
106
- config.add_provider(:anthropic, Prescient::Anthropic::Provider,
194
+ config.add_provider(:anthropic, Prescient::Provider::Anthropic,
107
195
  api_key: ENV['ANTHROPIC_API_KEY'],
108
- model: 'claude-3-haiku-20240307'
196
+ model: 'claude-sonnet-4-20250514'
109
197
  )
110
198
 
111
199
  # Add OpenAI
112
- config.add_provider(:openai, Prescient::OpenAI::Provider,
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-3.5-turbo'
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-3.5-turbo'
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-3-haiku-20240307'
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.1:8b'
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
- - Only available (healthy) providers are tried during fallback
162
- - If no fallback providers are configured, all available providers are tried as fallbacks
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, ...] (768-dimensional vector)
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
- [:ollama, :anthropic, :openai, :huggingface].each do |provider|
337
+ Prescient.configuration.providers.keys.each do |provider|
244
338
  health = Prescient.health_check(provider: provider)
245
339
  puts "#{provider}: #{health[:status]}"
246
- puts "Ready: #{health[:ready]}" if health[:ready]
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-3.5-turbo',
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: '%{ name } by %{ brand }: %{ description } - $%{ price } (%{ category })',
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: '%{ product_name } - %{ rating }/5 stars: "%{ review_text }"',
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(:default)
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 integrates seamlessly with PostgreSQL's pgvector extension for storing and searching embeddings:
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-compose up -d postgres
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', 768, vector_str, 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-compose up -d postgres ollama
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::BaseProvider
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] # => "Prescient::Ollama::Provider"
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`, `list_models`
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.1:8b` | 8GB | 4.7GB | Chat model (8B parameters) |
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.1:8b` and upgrade based on your hardware capabilities and performance needs.
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-compose up -d ollama
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-compose up ollama-init
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.1:8b
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.1:8b"}'
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.1:7b ruby examples/custom_contexts.rb
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.1:8b
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.1:8b", "prompt": "Hello", "stream": false}'
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 rspec
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
- desc "Run tests and linting"
16
- task default: %w[test rubocop]
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 "Run tests with coverage"
19
- task :coverage do
20
- ENV['COVERAGE'] = 'true'
21
- Rake::Task[:test].execute
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