prescient 0.5.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53a88a152c9fb689789c06dc11612905e30d76e997dbdcc1ca9906870bb01899
4
- data.tar.gz: 4dba29dd0ad5ae0edbccd0f9d50d8e1eeb8815ba1aeadda838b327ec124111d6
3
+ metadata.gz: 176552789656cbda6e78951ab04eb0ecdfa9ffa53dcf88159bec5cdbf403cdef
4
+ data.tar.gz: 5173a6bdd607dc6aca121079ee6e81243a550575f4702c57133f637a31aac798
5
5
  SHA512:
6
- metadata.gz: 0beee5d0fc6ca63e1d2f881055402a18ab374d8ec71f17c6e706bba7a0ec8f565bbd77ff4f1b337f100597d357a07ffcc45b37e080429199603ce3147e292579
7
- data.tar.gz: 2d8214bfb8d5f61536e5c79462d560cd4d6f83215dae54cffed821fe0f23d2959adbe02ec94f522d69bd2e04adb37b52087ba364752a98e093e6605ce204abfb
6
+ metadata.gz: fb309c19aa67f200e806b227f392ff5181e5ef7bc1ea00a6f9a0d4cf5ebbce989c0d9b472885a1c2795efaa7789a8ed61f912453fd20594fba029f8c7bc126d4
7
+ data.tar.gz: 70187123148e3c0aad70f88fa469c91e3c55200c1c36b9452697109e2746cfe0fe70ef8358b766b3f99b967884aba4b846f3a8cb634c76fe9803ad45925d0e50
data/.dockerignore ADDED
@@ -0,0 +1,18 @@
1
+ .git
2
+ .github
3
+ .ignoreme
4
+ .bundle
5
+ .yardoc
6
+ _yardoc
7
+ coverage
8
+ doc
9
+ tmp
10
+ test
11
+ spec
12
+ examples/*.rb
13
+ *.gem
14
+ .env*
15
+ AGENTS.md
16
+ Dockerfile.example
17
+ docker-compose.yml
18
+ docker-compose.api.yml
data/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## [0.7.0] - 2025-08-17
6
+
7
+ ### Added
8
+
9
+ - Added an explicit external-tool contract with a configurable SearXNG web-search adapter.
10
+ - Added YAML/schema configuration for tool registration, environment references, bounded requests, and normalized search results.
11
+ - Added `prescient search` and an annotated `web_search` configuration example.
12
+ - Added an optional development SearXNG service to `docker-compose.yml` for runnable web-search examples.
13
+ - Added opt-in search-to-provider context assembly through `Prescient.search_and_generate` and `prescient search --generate`.
14
+ - Added `POST /v1/search/generate` for REST API consumers to opt in to search-result context.
15
+ - Added `POST /v1/search` for normalized raw external-tool results without AI generation.
16
+ - Added SearchApi as a hosted web-search adapter with engine, location, language,
17
+ country, API-key, timeout, and result-limit configuration.
18
+ - Added capability groups for ordered tool-adapter fallback on transient
19
+ connection and rate-limit failures.
20
+ - Documented SearXNG and SearchApi setup and CLI usage side by side.
21
+ - Added explicit `--generate` opt-in behavior to the web-search example.
22
+ - Added `SEARXNG_URL` environment defaults for automatic `web_search` registration.
23
+ - Expanded `prescient config example` with documented SearXNG settings,
24
+ environment references, custom tool names, and direct-versus-generated usage.
25
+ - Organized CLI help into global and search-specific option sections.
26
+
27
+ ## [0.6.0] - 2025-08-15
28
+
29
+ ### Added
30
+
31
+ - Added a dependency-free Rack-compatible REST API with generation, embeddings,
32
+ bounded batch embeddings, provider/model discovery, capabilities, health,
33
+ liveness, readiness, version, request IDs, authentication hooks, and JSON
34
+ error envelopes.
35
+ - Added a small Rack example that lists the REST API endpoints and delegates
36
+ requests to `Prescient::API`.
37
+ - Added an optional `rack_example` bundle group with Rack, Rackup, and Puma for
38
+ running the example application without adding web-server dependencies to
39
+ the library.
40
+ - Added a non-root, healthchecked Docker image and Compose example for the REST
41
+ API, with optional GHCR publication on version tags.
42
+ - Documented mounting `Prescient::API` in Rails routes with its endpoint
43
+ catalog and authentication example.
44
+ - Made the CLI and REST API optional lazy-loaded entry points so library users
45
+ requiring only `prescient` do not load either interface eagerly.
46
+
5
47
  ## [0.5.0] - 2025-08-14
6
48
 
7
49
  ### Added
data/Dockerfile ADDED
@@ -0,0 +1,45 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM ruby:3.3-alpine AS builder
4
+
5
+ WORKDIR /app
6
+
7
+ RUN apk add --no-cache build-base git
8
+
9
+ ENV BUNDLE_WITH=rack_example \
10
+ BUNDLE_WITHOUT=development:test \
11
+ BUNDLE_PATH=/usr/local/bundle
12
+
13
+ COPY Gemfile prescient.gemspec ./
14
+ COPY lib ./lib
15
+
16
+ RUN bundle install --jobs 4 --retry 3
17
+
18
+ FROM ruby:3.3-alpine
19
+
20
+ WORKDIR /app
21
+
22
+ RUN apk add --no-cache curl tzdata && \
23
+ addgroup -S -g 1000 prescient && \
24
+ adduser -S -u 1000 -G prescient prescient
25
+
26
+ ENV BUNDLE_WITH=rack_example \
27
+ BUNDLE_WITHOUT=development:test \
28
+ BUNDLE_PATH=/usr/local/bundle \
29
+ GEM_HOME=/usr/local/bundle/ruby/3.3.0 \
30
+ GEM_PATH=/usr/local/bundle/ruby/3.3.0 \
31
+ PATH=/usr/local/bundle/ruby/3.3.0/bin:/usr/local/bundle/bin:$PATH \
32
+ RACK_ENV=production
33
+
34
+ COPY --from=builder /usr/local/bundle /usr/local/bundle
35
+ COPY --chown=prescient:prescient lib ./lib
36
+ COPY --chown=prescient:prescient examples/rest_api.ru ./examples/rest_api.ru
37
+
38
+ USER prescient
39
+
40
+ EXPOSE 9292
41
+
42
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
43
+ CMD curl --fail --silent http://127.0.0.1:9292/healthz || exit 1
44
+
45
+ CMD ["rackup", "-s", "puma", "-o", "0.0.0.0", "-p", "9292", "/app/examples/rest_api.ru"]
data/INTEGRATION_GUIDE.md CHANGED
@@ -11,9 +11,7 @@ and [examples guide](examples/README.md).
11
11
 
12
12
  ```ruby
13
13
  # Add to your Gemfile
14
- gem 'prescient', path: './prescient_gem' # Local development
15
- # OR when published:
16
- # gem 'prescient', '~> 0.5.0'
14
+ gem 'prescient', '~> 0.7.0'
17
15
  ```
18
16
 
19
17
  ### 2. Replace Existing AI Service
@@ -111,6 +109,40 @@ Prescient.configure do |config|
111
109
  chat_model: ENV.fetch('HUGGINGFACE_CHAT_MODEL', 'google/gemma-2-2b-it')
112
110
  )
113
111
  end
112
+
113
+ # Google Gemini
114
+ if ENV['GEMINI_API_KEY'].present?
115
+ config.add_provider(:gemini, Prescient::Provider::Gemini,
116
+ api_key: ENV['GEMINI_API_KEY'],
117
+ embedding_model: ENV.fetch('GEMINI_EMBEDDING_MODEL', 'gemini-embedding-001'),
118
+ chat_model: ENV.fetch('GEMINI_CHAT_MODEL', 'gemini-2.5-flash')
119
+ )
120
+ end
121
+
122
+ # Mistral
123
+ if ENV['MISTRAL_API_KEY'].present?
124
+ config.add_provider(:mistral, Prescient::Provider::Mistral,
125
+ api_key: ENV['MISTRAL_API_KEY'],
126
+ embedding_model: ENV.fetch('MISTRAL_EMBEDDING_MODEL', 'mistral-embed'),
127
+ chat_model: ENV.fetch('MISTRAL_CHAT_MODEL', 'mistral-large-latest')
128
+ )
129
+ end
130
+
131
+ # DeepSeek supports generation, but not embeddings.
132
+ if ENV['DEEPSEEK_API_KEY'].present?
133
+ config.add_provider(:deepseek, Prescient::Provider::DeepSeek,
134
+ api_key: ENV['DEEPSEEK_API_KEY'],
135
+ chat_model: ENV.fetch('DEEPSEEK_CHAT_MODEL', 'deepseek-v4-flash')
136
+ )
137
+ end
138
+
139
+ # xAI supports generation, but not embeddings.
140
+ if ENV['XAI_API_KEY'].present?
141
+ config.add_provider(:xai, Prescient::Provider::XAI,
142
+ api_key: ENV['XAI_API_KEY'],
143
+ chat_model: ENV.fetch('XAI_CHAT_MODEL', 'grok-4.5')
144
+ )
145
+ end
114
146
  end
115
147
 
116
148
  # Set default provider for Rails
@@ -128,7 +160,68 @@ Configuration precedence is CLI overrides, environment defaults and
128
160
  references, YAML values, then built-in defaults. The generated
129
161
  `prescient config example` file includes the current JSON Schema URL.
130
162
 
131
- ### 4. Update Environment Variables
163
+ ### 4. Mount the REST API
164
+
165
+ `Prescient::API` is Rack-compatible and can be mounted directly in a Rails
166
+ route set. The API keeps provider execution on `Prescient::Client` and exposes
167
+ only generic operations:
168
+
169
+ ```ruby
170
+ # config/routes.rb
171
+ prescient_api = Prescient::API.new(
172
+ authentication: lambda { |env|
173
+ expected_token = ENV.fetch('PRESCIENT_API_TOKEN', nil)
174
+ expected_token && env['HTTP_AUTHORIZATION'] == "Bearer #{expected_token}"
175
+ }
176
+ )
177
+
178
+ mount prescient_api => '/prescient', as: :prescient_api
179
+ ```
180
+
181
+ This makes the following routes available under `/prescient`:
182
+
183
+ | Method | Path | Purpose |
184
+ | --- | --- | --- |
185
+ | `GET` | `/healthz` | Liveness check |
186
+ | `GET` | `/readyz` | Readiness check |
187
+ | `GET` | `/v1/version` | Library and API versions |
188
+ | `GET` | `/v1/providers` | Configured providers |
189
+ | `GET` | `/v1/models` | Available models, optionally filtered by provider |
190
+ | `GET` | `/v1/capabilities` | Provider capabilities |
191
+ | `GET` | `/v1/health` | Provider health |
192
+ | `POST` | `/v1/generate` | Text generation |
193
+ | `POST` | `/v1/search` | Normalized external-tool search |
194
+ | `POST` | `/v1/search/generate` | Search with opt-in AI generation |
195
+ | `POST` | `/v1/embeddings` | Single embedding |
196
+ | `POST` | `/v1/embeddings/batch` | Bounded batch embeddings |
197
+
198
+ For example:
199
+
200
+ ```bash
201
+ curl http://localhost:3000/prescient/healthz
202
+
203
+ curl -X POST http://localhost:3000/prescient/v1/generate \
204
+ -H "Authorization: Bearer ${PRESCIENT_API_TOKEN}" \
205
+ -H 'Content-Type: application/json' \
206
+ -d '{"prompt":"Explain Ruby fibers"}'
207
+
208
+ curl -X POST http://localhost:3000/prescient/v1/search/generate \
209
+ -H "Authorization: Bearer ${PRESCIENT_API_TOKEN}" \
210
+ -H 'Content-Type: application/json' \
211
+ -d '{"query":"Ruby HTTP clients","provider":"openai","limit":5}'
212
+
213
+ curl -X POST http://localhost:3000/prescient/v1/search \
214
+ -H "Authorization: Bearer ${PRESCIENT_API_TOKEN}" \
215
+ -H 'Content-Type: application/json' \
216
+ -d '{"query":"Ruby HTTP clients","limit":5}'
217
+ ```
218
+
219
+ Responses include a request ID. Request bodies are size-limited, batch inputs
220
+ are bounded, and JSON errors do not expose raw provider response bodies. Keep
221
+ the API behind the application’s normal TLS, authentication, rate-limiting,
222
+ and observability controls.
223
+
224
+ ### 5. Update Environment Variables
132
225
 
133
226
  ```bash
134
227
  # .env or environment configuration
@@ -151,9 +244,27 @@ ANTHROPIC_MODEL=claude-sonnet-4-20250514
151
244
  HUGGINGFACE_API_KEY=your_huggingface_api_key
152
245
  HUGGINGFACE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
153
246
  HUGGINGFACE_CHAT_MODEL=google/gemma-2-2b-it
247
+
248
+ # Google Gemini
249
+ GEMINI_API_KEY=your_gemini_api_key
250
+ GEMINI_EMBEDDING_MODEL=gemini-embedding-001
251
+ GEMINI_CHAT_MODEL=gemini-2.5-flash
252
+
253
+ # Mistral
254
+ MISTRAL_API_KEY=your_mistral_api_key
255
+ MISTRAL_EMBEDDING_MODEL=mistral-embed
256
+ MISTRAL_CHAT_MODEL=mistral-large-latest
257
+
258
+ # DeepSeek (generation only)
259
+ DEEPSEEK_API_KEY=your_deepseek_api_key
260
+ DEEPSEEK_CHAT_MODEL=deepseek-v4-flash
261
+
262
+ # xAI (generation only)
263
+ XAI_API_KEY=your_xai_api_key
264
+ XAI_CHAT_MODEL=grok-4.5
154
265
  ```
155
266
 
156
- ### 5. Update Controllers
267
+ ### 6. Update Controllers
157
268
 
158
269
  **Before:**
159
270
 
@@ -199,7 +310,7 @@ class Api::V1::AiQueriesController < ApplicationController
199
310
  end
200
311
  ```
201
312
 
202
- ### 6. Health Check Integration
313
+ ### 7. Health Check Integration
203
314
 
204
315
  ```ruby
205
316
  # app/controllers/api/v1/system/health_controller.rb
@@ -232,7 +343,7 @@ class Api::V1::System::HealthController < ApplicationController
232
343
  }
233
344
 
234
345
  # Check backup providers
235
- backup_providers = [:openai, :anthropic, :huggingface] - [primary_provider]
346
+ backup_providers = %i[openai anthropic huggingface gemini mistral deepseek xai] - [primary_provider]
236
347
  providers[:backups] = backup_providers.map do |provider|
237
348
  {
238
349
  name: provider,
@@ -252,7 +363,7 @@ class Api::V1::System::HealthController < ApplicationController
252
363
  end
253
364
  ```
254
365
 
255
- ### 7. Migration Strategy
366
+ ### 8. Migration Strategy
256
367
 
257
368
  1. **Phase 1: Side-by-side deployment**
258
369
 
@@ -271,7 +382,7 @@ end
271
382
  - Update all controllers to use AIService
272
383
  - Clean up unused code
273
384
 
274
- ### 8. Testing Updates
385
+ ### 9. Testing Updates
275
386
 
276
387
  ```ruby
277
388
  # test/services/ai_service_test.rb
@@ -296,7 +407,7 @@ class AIServiceTest < ActiveSupport::TestCase
296
407
  end
297
408
  ```
298
409
 
299
- ### 9. Monitoring and Logging
410
+ ### 10. Monitoring and Logging
300
411
 
301
412
  ```ruby
302
413
  # config/initializers/prescient_monitoring.rb
@@ -320,7 +431,7 @@ end
320
431
  PrescientMonitoring.setup! if Rails.env.production?
321
432
  ```
322
433
 
323
- ### 10. Performance Optimization
434
+ ### 11. Performance Optimization
324
435
 
325
436
  ```ruby
326
437
  # app/services/ai_service.rb (enhanced)
data/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/prescient?logo=rubygems&logoColor=white)](https://rubygems.org/gems/prescient)
4
4
  [![Requires Ruby 3.1+](https://img.shields.io/badge/Requires-Ruby%203.1%2B-CC342D?logo=ruby&logoColor=white)](https://www.ruby-lang.org/)
5
+ [![Docker](https://img.shields.io/badge/Docker-GHCR-2496ED?logo=docker&logoColor=white)](https://github.com/kanutocd/prescient/pkgs/container/prescient)
5
6
  [![CI](https://github.com/kanutocd/prescient/actions/workflows/ci.yml/badge.svg)](https://github.com/kanutocd/prescient/actions/workflows/ci.yml)
6
7
  [![Security](https://img.shields.io/github/actions/workflow/status/kanutocd/prescient/security.yml?branch=main&event=push&label=Security)](https://github.com/kanutocd/prescient/actions/workflows/security.yml)
7
8
  [![License](https://img.shields.io/badge/License-MIT-22C55E)](LICENSE.txt)
8
9
 
9
- 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, Hugging Face, Google Gemini, Mistral, DeepSeek, or xAI. Prescient handles provider selection, retries, health checks, and fallback.
10
+ Prescient is a boring AI provider gateway implemented in Ruby. Configure your AI providers once, then use them through a consistent Ruby API, CLI, or REST API. Prescient handles provider selection, retries, health checks, and fallback across configured providers, including OpenAI, Anthropic, Ollama, Hugging Face, Google Gemini, Mistral, DeepSeek, and xAI.
10
11
 
11
12
  For focused guidance, see the **[examples guide](https://github.com/kanutocd/prescient/tree/main/examples)**,
12
13
  **[Rails integration guide](https://github.com/kanutocd/prescient/blob/main/INTEGRATION_GUIDE.md)**, and
@@ -14,13 +15,15 @@ For focused guidance, see the **[examples guide](https://github.com/kanutocd/pre
14
15
 
15
16
  ## Features
16
17
 
17
- - **Unified Interface**: Single API for multiple AI providers
18
- - **Local and Cloud Support**: Ollama for local/private deployments, cloud APIs for scale
19
- - **Embedding Generation**: Vector embeddings for semantic search and AI applications
20
- - **Text Completion**: Chat completions with context support
21
- - **Error Handling**: Robust error handling with automatic retries
22
- - **Health Monitoring**: Built-in health checks for all providers
23
- - **Flexible Configuration**: YAML, environment variable, and programmatic configuration
18
+ - **Provider abstraction** One consistent interface across supported AI providers
19
+ - **Multiple interfaces** Ruby API, CLI, and Rack-compatible REST API
20
+ - **Text and embeddings** Generate responses and embeddings with provider/model selection
21
+ - **Reliability controls** Retries, health checks, and fallback across configured providers
22
+ - **Declarative configuration** Versioned YAML, environment references, and JSON Schema validation
23
+ - **Prompt and context customization** Configurable prompt templates and context formatting
24
+ - **External tools** — Explicit, normalized web-search integration with SearXNG and SearchApi
25
+ - **Local and cloud support** — Ollama alongside hosted providers
26
+ - **Optional integrations** — Docker deployment and pgvector support without making either mandatory
24
27
 
25
28
  ## Supported Providers
26
29
 
@@ -223,6 +226,64 @@ The generated file points YAML language servers at the latest schema on the
223
226
  main branch. Pin the schema URL to a release tag when reproducible tooling is
224
227
  required.
225
228
 
229
+ ## REST API
230
+
231
+ `Prescient::API` is a dependency-free Rack-compatible application. Mount it in
232
+ the web server of your choice without making HTTP a requirement for library
233
+ users. The API and CLI files are loaded lazily; `Prescient::API` autoloads on
234
+ first reference, or you can require it explicitly:
235
+
236
+ ```ruby
237
+ require 'prescient'
238
+ require 'prescient/api'
239
+
240
+ run Prescient::API.new(
241
+ authentication: ->(env) { env['HTTP_AUTHORIZATION'] == "Bearer #{ENV['PRESCIENT_API_TOKEN']}" }
242
+ )
243
+ ```
244
+
245
+ Available endpoints include:
246
+
247
+ - **`POST /v1/generate`**
248
+ - **`POST /v1/search`**
249
+ - **`POST /v1/search/generate`**
250
+ - **`POST /v1/embeddings`**
251
+ - **`POST /v1/embeddings/batch`**
252
+ - **`GET /v1/providers`**
253
+ - **`GET /v1/models`**
254
+ - **`GET /v1/capabilities`**
255
+ - **`GET /v1/health`**
256
+ - **`GET /v1/version`**
257
+ - **`GET /healthz`**
258
+ - **`GET /readyz`**
259
+
260
+ Responses include:
261
+
262
+ - a request ID
263
+ - a generic JSON error envelope that never exposes raw provider response bodies.
264
+
265
+ ## Docker
266
+
267
+ Build and run the REST API image as a non-root container:
268
+
269
+ ```bash
270
+ docker build -t prescient:local .
271
+ docker run --rm -p 9292:9292 \
272
+ -e PRESCIENT_API_TOKEN=change-me \
273
+ prescient:local
274
+ ```
275
+
276
+ The image exposes port `9292`, includes a `/healthz` healthcheck, supports a
277
+ read-only filesystem, and does not bundle PostgreSQL, Redis, or a worker. The
278
+ Compose example provides the same setup:
279
+
280
+ ```bash
281
+ PRESCIENT_API_TOKEN=change-me docker compose -f docker-compose.api.yml up --build
282
+ ```
283
+
284
+ Tagged releases publish to GHCR as:
285
+ `ghcr.io/kanutocd/prescient:<version>`.
286
+
226
287
  ## Configuration
227
288
 
228
289
  ### Environment Variables
@@ -291,25 +352,139 @@ YAML values, then built-in defaults. Use `prescient config validate` to check a
291
352
  configuration before running an operation, or `prescient config example` to
292
353
  generate an annotated starter file.
293
354
 
294
- Prompt templates can also be configured per provider. Use the YAML mapping for
295
- multiline templates, or pass a template file to a single CLI operation:
355
+ ### External Tools
356
+
357
+ External tools are opt-in capability adapters, separate from AI providers.
358
+ Supported web-search adapters are [SearXNG](https://searxng.org/) and
359
+ [SearchApi](https://www.searchapi.io/):
360
+
361
+ #### SearXNG
362
+
363
+ Setting `SEARXNG_URL` registers the default `web_search` tool for CLI and Ruby
364
+ environment-based configuration. YAML or programmatic configuration can be
365
+ used when more control is needed.
296
366
 
297
367
  ```yaml
298
- providers:
299
- openai:
300
- type: openai
301
- api_key_env: OPENAI_API_KEY
302
- chat_model: gpt-4.1-mini
303
- prompt_templates:
304
- system_prompt: You are a concise assistant.
305
- no_context_template: "%{system_prompt}\n\nUser: %{query}"
306
- with_context_template: "%{system_prompt}\n\nContext:\n%{context}\n\nUser: %{query}"
368
+ tools:
369
+ web_search:
370
+ type: searxng
371
+ url_env: SEARXNG_URL
372
+ language: en
373
+ categories: [general, news]
374
+ timeout: 5
375
+ max_results: 5
307
376
  ```
308
377
 
378
+ Run the SearXNG-backed tool:
379
+
309
380
  ```bash
310
- prescient generate --prompt-templates-file prompts.yml "Summarize this"
381
+ docker compose up -d searxng
382
+ SEARXNG_URL=http://localhost:8080 bundle exec prescient search \
383
+ --format json "Ruby HTTP clients"
384
+ ```
385
+
386
+ #### SearchApi
387
+
388
+ [SearchApi](https://www.searchapi.io/) uses a hosted Google search engine and
389
+ requires an API key:
390
+
391
+ ```yaml
392
+ tools:
393
+ web_search:
394
+ type: searchapi
395
+ api_key_env: SEARCHAPI_API_KEY
396
+ engine: google
397
+ location: New York
398
+ hl: en
399
+ gl: us
400
+ timeout: 10
401
+ max_results: 5
402
+ ```
403
+
404
+ The `engine` value can select another SearchApi web or product engine when its
405
+ response uses `organic_results`, such as `bing`, `yahoo`, `yandex`,
406
+ `amazon_search`, or `walmart_search`.
407
+
408
+ Adapters can be grouped under one logical capability for ordered fallback:
409
+
410
+ ```yaml
411
+ tools:
412
+ web_search:
413
+ adapters:
414
+ - type: searxng
415
+ url_env: SEARXNG_URL
416
+ - type: searchapi
417
+ api_key_env: SEARCHAPI_API_KEY
418
+ engine: google
419
+ ```
420
+
421
+ Adapters are tried in order. Fallback is limited to transient connection and
422
+ rate-limit failures; invalid configuration, authentication failures, and
423
+ malformed responses are not retried with another adapter.
424
+
425
+ Run the SearchApi-backed tool:
426
+
427
+ ```bash
428
+ SEARCHAPI_API_KEY=your-key bundle exec prescient search \
429
+ --config prescient.yml --format json "Ruby HTTP clients"
430
+ ```
431
+
432
+ Invoke a configured tool explicitly from Ruby or the CLI:
433
+
434
+ ```ruby
435
+ result = Prescient.tool(:web_search).search('Ruby HTTP clients', limit: 3)
436
+ ```
437
+
438
+ ```bash
439
+ bundle exec prescient search --config prescient.yml \
440
+ --tool web_search --format json "Ruby HTTP clients"
441
+ ```
442
+
443
+ Results use a normalized envelope containing `tool`, `query`, `source`, and
444
+ `results` entries with `title`, `url`, `snippet`, and `source`. Requests have
445
+ bounded query length, timeout, result count, and response size. Tool execution
446
+ is explicit; Prescient does not autonomously invoke tools, and tool endpoints
447
+ are not exposed as raw tool endpoints through `Prescient::API`; the API exposes
448
+ the explicit combined search-and-generation operation below. Other adapters can
449
+ implement the same contract without changing provider integrations.
450
+
451
+ Search results are not sent to an AI provider by default. Opt in when you want
452
+ the normalized results assembled as generation context:
453
+
454
+ ```ruby
455
+ response = Prescient.search_and_generate(
456
+ 'Ruby HTTP clients',
457
+ tool: :web_search,
458
+ provider: :openai,
459
+ )
311
460
  ```
312
461
 
462
+ The CLI exposes the same opt-in behavior with `--generate`:
463
+
464
+ ```bash
465
+ prescient search --generate --provider openai "Ruby HTTP clients"
466
+ ```
467
+
468
+ The REST API exposes the same opt-in behavior:
469
+
470
+ Raw normalized search results are available without generation:
471
+
472
+ ```bash
473
+ curl -X POST http://localhost:9292/v1/search \
474
+ -H 'Content-Type: application/json' \
475
+ -d '{"query":"Ruby HTTP clients","limit":5}'
476
+ ```
477
+
478
+ ```bash
479
+ curl -X POST http://localhost:9292/v1/search/generate \
480
+ -H 'Content-Type: application/json' \
481
+ -d '{"query":"Ruby HTTP clients","provider":"openai","limit":5}'
482
+ ```
483
+
484
+ Use `fallback: false` to disable provider fallback for the request. The
485
+ response is the normalized AI provider response; omit this endpoint and use
486
+ `POST /v1/generate` when search context is not wanted.
487
+
313
488
  ### Programmatic Configuration
314
489
 
315
490
  ```ruby
@@ -546,6 +721,24 @@ client = Prescient.client(:customer_service)
546
721
  response = client.generate_response("What's your return policy?")
547
722
  ```
548
723
 
724
+ Templates can also be configured in YAML or overridden for one CLI operation:
725
+
726
+ ```yaml
727
+ providers:
728
+ openai:
729
+ type: openai
730
+ api_key_env: OPENAI_API_KEY
731
+ chat_model: gpt-4.1-mini
732
+ prompt_templates:
733
+ system_prompt: You are a concise assistant.
734
+ no_context_template: "%{system_prompt}\n\nUser: %{query}"
735
+ with_context_template: "%{system_prompt}\n\nContext:\n%{context}\n\nUser: %{query}"
736
+ ```
737
+
738
+ ```bash
739
+ prescient generate --prompt-templates-file prompts.yml "Summarize this"
740
+ ```
741
+
549
742
  ### Template Placeholders
550
743
 
551
744
  - `%{system_prompt}` - The system/role instruction
@@ -978,6 +1171,30 @@ puts info[:options] # => { ... } (excluding sensitive data)
978
1171
  - Research-friendly
979
1172
  - Free tier available
980
1173
 
1174
+ ### Google Gemini
1175
+
1176
+ - Text generation and embeddings
1177
+ - Google AI API integration
1178
+ - Model discovery through the Gemini models endpoint
1179
+
1180
+ ### Mistral
1181
+
1182
+ - Text generation and embeddings
1183
+ - OpenAI-compatible API style
1184
+ - Model discovery through the Mistral models endpoint
1185
+
1186
+ ### DeepSeek
1187
+
1188
+ - Text generation
1189
+ - OpenAI-compatible API style
1190
+ - No embedding support
1191
+
1192
+ ### xAI
1193
+
1194
+ - Text generation
1195
+ - OpenAI-compatible API style
1196
+ - No embedding support
1197
+
981
1198
  ## Docker Setup (Recommended for Ollama)
982
1199
 
983
1200
  The easiest way to get started with Prescient and Ollama is using Docker Compose:
@@ -1052,6 +1269,8 @@ The included `docker-compose.yml` provides:
1052
1269
 
1053
1270
  - **ollama**: Ollama AI service with persistent model storage
1054
1271
  - **ollama-init**: Automatically pulls required models on startup
1272
+ - **searxng**: Optional SearXNG web-search service with JSON output enabled
1273
+ - **postgres**: Optional PostgreSQL database with pgvector support
1055
1274
  - **redis**: Optional caching layer for embeddings
1056
1275
  - **prescient-app**: Example Ruby application container
1057
1276
 
@@ -1210,10 +1429,10 @@ OPENAI_API_KEY=... \
1210
1429
  bundle exec ruby -Itest test/prescient/live_provider_smoke_test.rb
1211
1430
  ```
1212
1431
 
1213
- Supported provider names are `ollama`, `anthropic`, `openai`, and
1214
- `huggingface`. The corresponding provider environment variables and model
1215
- overrides are honored. These tests are never live unless both opt-in
1216
- variables are set.
1432
+ Supported provider names are `ollama`, `anthropic`, `openai`, `huggingface`,
1433
+ `gemini`, `mistral`, `deepseek`, and `xai`. The corresponding provider
1434
+ environment variables and model overrides are honored. These tests are never
1435
+ live unless both opt-in variables are set.
1217
1436
 
1218
1437
  ### RBS and Steep
1219
1438
 
@@ -0,0 +1,21 @@
1
+ services:
2
+ prescient-api:
3
+ build:
4
+ context: .
5
+ dockerfile: Dockerfile
6
+ image: prescient:local
7
+ ports:
8
+ - "9292:9292"
9
+ environment:
10
+ PRESCIENT_API_TOKEN: ${PRESCIENT_API_TOKEN:?set PRESCIENT_API_TOKEN}
11
+ OLLAMA_URL: ${OLLAMA_URL:-http://host.docker.internal:11434}
12
+ read_only: true
13
+ tmpfs:
14
+ - /tmp
15
+ restart: unless-stopped
16
+ healthcheck:
17
+ test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:9292/healthz"]
18
+ interval: 30s
19
+ timeout: 5s
20
+ retries: 3
21
+ start_period: 10s