prescient 0.6.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/INTEGRATION_GUIDE.md +13 -1
- data/README.md +159 -20
- data/docker-compose.yml +22 -0
- data/examples/README.md +34 -0
- data/examples/web_search.rb +34 -0
- data/lib/prescient/api.rb +52 -0
- data/lib/prescient/cli.rb +135 -6
- data/lib/prescient/client.rb +26 -0
- data/lib/prescient/configuration_loader.rb +171 -0
- data/lib/prescient/errors.rb +12 -0
- data/lib/prescient/provider/openai.rb +0 -2
- data/lib/prescient/tool/search_api.rb +126 -0
- data/lib/prescient/tool/searxng.rb +124 -0
- data/lib/prescient/tool.rb +123 -0
- data/lib/prescient/version.rb +1 -1
- data/lib/prescient.rb +68 -0
- data/schema/prescient.configuration.schema.json +119 -0
- data/searxng/settings.yml +18 -0
- data/sig/prescient.rbs +52 -0
- metadata +11 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 176552789656cbda6e78951ab04eb0ecdfa9ffa53dcf88159bec5cdbf403cdef
|
|
4
|
+
data.tar.gz: 5173a6bdd607dc6aca121079ee6e81243a550575f4702c57133f637a31aac798
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb309c19aa67f200e806b227f392ff5181e5ef7bc1ea00a6f9a0d4cf5ebbce989c0d9b472885a1c2795efaa7789a8ed61f912453fd20594fba029f8c7bc126d4
|
|
7
|
+
data.tar.gz: 70187123148e3c0aad70f88fa469c91e3c55200c1c36b9452697109e2746cfe0fe70ef8358b766b3f99b967884aba4b846f3a8cb634c76fe9803ad45925d0e50
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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
|
+
|
|
5
27
|
## [0.6.0] - 2025-08-15
|
|
6
28
|
|
|
7
29
|
### Added
|
data/INTEGRATION_GUIDE.md
CHANGED
|
@@ -11,7 +11,7 @@ and [examples guide](examples/README.md).
|
|
|
11
11
|
|
|
12
12
|
```ruby
|
|
13
13
|
# Add to your Gemfile
|
|
14
|
-
gem 'prescient', '~> 0.
|
|
14
|
+
gem 'prescient', '~> 0.7.0'
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
### 2. Replace Existing AI Service
|
|
@@ -190,6 +190,8 @@ This makes the following routes available under `/prescient`:
|
|
|
190
190
|
| `GET` | `/v1/capabilities` | Provider capabilities |
|
|
191
191
|
| `GET` | `/v1/health` | Provider health |
|
|
192
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 |
|
|
193
195
|
| `POST` | `/v1/embeddings` | Single embedding |
|
|
194
196
|
| `POST` | `/v1/embeddings/batch` | Bounded batch embeddings |
|
|
195
197
|
|
|
@@ -202,6 +204,16 @@ curl -X POST http://localhost:3000/prescient/v1/generate \
|
|
|
202
204
|
-H "Authorization: Bearer ${PRESCIENT_API_TOKEN}" \
|
|
203
205
|
-H 'Content-Type: application/json' \
|
|
204
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}'
|
|
205
217
|
```
|
|
206
218
|
|
|
207
219
|
Responses include a request ID. Request bodies are size-limited, batch inputs
|
data/README.md
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/prescient)
|
|
4
4
|
[](https://www.ruby-lang.org/)
|
|
5
|
+
[](https://github.com/kanutocd/prescient/pkgs/container/prescient)
|
|
5
6
|
[](https://github.com/kanutocd/prescient/actions/workflows/ci.yml)
|
|
6
7
|
[](https://github.com/kanutocd/prescient/actions/workflows/security.yml)
|
|
7
8
|
[](LICENSE.txt)
|
|
8
9
|
|
|
9
|
-
Prescient is a boring AI provider
|
|
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
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
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
|
|
|
@@ -242,6 +245,8 @@ run Prescient::API.new(
|
|
|
242
245
|
Available endpoints include:
|
|
243
246
|
|
|
244
247
|
- **`POST /v1/generate`**
|
|
248
|
+
- **`POST /v1/search`**
|
|
249
|
+
- **`POST /v1/search/generate`**
|
|
245
250
|
- **`POST /v1/embeddings`**
|
|
246
251
|
- **`POST /v1/embeddings/batch`**
|
|
247
252
|
- **`GET /v1/providers`**
|
|
@@ -347,25 +352,139 @@ YAML values, then built-in defaults. Use `prescient config validate` to check a
|
|
|
347
352
|
configuration before running an operation, or `prescient config example` to
|
|
348
353
|
generate an annotated starter file.
|
|
349
354
|
|
|
350
|
-
|
|
351
|
-
|
|
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.
|
|
352
366
|
|
|
353
367
|
```yaml
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
type:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
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
|
|
363
376
|
```
|
|
364
377
|
|
|
378
|
+
Run the SearXNG-backed tool:
|
|
379
|
+
|
|
365
380
|
```bash
|
|
366
|
-
|
|
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"
|
|
367
441
|
```
|
|
368
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
|
+
)
|
|
460
|
+
```
|
|
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
|
+
|
|
369
488
|
### Programmatic Configuration
|
|
370
489
|
|
|
371
490
|
```ruby
|
|
@@ -602,6 +721,24 @@ client = Prescient.client(:customer_service)
|
|
|
602
721
|
response = client.generate_response("What's your return policy?")
|
|
603
722
|
```
|
|
604
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
|
+
|
|
605
742
|
### Template Placeholders
|
|
606
743
|
|
|
607
744
|
- `%{system_prompt}` - The system/role instruction
|
|
@@ -1132,6 +1269,8 @@ The included `docker-compose.yml` provides:
|
|
|
1132
1269
|
|
|
1133
1270
|
- **ollama**: Ollama AI service with persistent model storage
|
|
1134
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
|
|
1135
1274
|
- **redis**: Optional caching layer for embeddings
|
|
1136
1275
|
- **prescient-app**: Example Ruby application container
|
|
1137
1276
|
|
data/docker-compose.yml
CHANGED
|
@@ -91,6 +91,26 @@ services:
|
|
|
91
91
|
retries: 3
|
|
92
92
|
start_period: 30s
|
|
93
93
|
|
|
94
|
+
# Optional SearXNG instance for the external web-search example.
|
|
95
|
+
searxng:
|
|
96
|
+
image: searxng/searxng:latest
|
|
97
|
+
container_name: prescient-searxng
|
|
98
|
+
ports:
|
|
99
|
+
- "8080:8080"
|
|
100
|
+
environment:
|
|
101
|
+
- SEARXNG_BASE_URL=http://localhost:8080/
|
|
102
|
+
- SEARXNG_SECRET=${SEARXNG_SECRET:-prescient-development-secret}
|
|
103
|
+
volumes:
|
|
104
|
+
- ./searxng/settings.yml:/etc/searxng/settings.yml:ro
|
|
105
|
+
- searxng_cache:/var/cache/searxng
|
|
106
|
+
restart: unless-stopped
|
|
107
|
+
healthcheck:
|
|
108
|
+
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/')"]
|
|
109
|
+
interval: 30s
|
|
110
|
+
timeout: 10s
|
|
111
|
+
retries: 3
|
|
112
|
+
start_period: 30s
|
|
113
|
+
|
|
94
114
|
# Optional: Redis for caching embeddings (useful for development)
|
|
95
115
|
redis:
|
|
96
116
|
image: redis:7-alpine
|
|
@@ -145,6 +165,8 @@ volumes:
|
|
|
145
165
|
driver: local
|
|
146
166
|
redis_data:
|
|
147
167
|
driver: local
|
|
168
|
+
searxng_cache:
|
|
169
|
+
driver: local
|
|
148
170
|
|
|
149
171
|
networks:
|
|
150
172
|
default:
|
data/examples/README.md
CHANGED
|
@@ -19,6 +19,10 @@ bundle install
|
|
|
19
19
|
and similarity search.
|
|
20
20
|
- `rest_api.ru` — a tiny Rack-compatible application that mounts
|
|
21
21
|
`Prescient::API` and lists its endpoints at `/`.
|
|
22
|
+
- `web_search.rb` — explicit SearXNG tool invocation with normalized JSON output.
|
|
23
|
+
|
|
24
|
+
The same `web_search` capability can use SearchApi instead of SearXNG when the
|
|
25
|
+
tool is configured with `type: searchapi` and `SEARCHAPI_API_KEY`.
|
|
22
26
|
|
|
23
27
|
Run the REST API example with a Rack server such as `rackup`:
|
|
24
28
|
|
|
@@ -29,6 +33,17 @@ PRESCIENT_API_TOKEN=change-me BUNDLE_WITH=rack_example \
|
|
|
29
33
|
curl http://localhost:9292/
|
|
30
34
|
```
|
|
31
35
|
|
|
36
|
+
The endpoint catalog includes `POST /v1/search/generate`. With a configured
|
|
37
|
+
SearXNG tool and AI provider, call it explicitly to feed search results into
|
|
38
|
+
generation:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl -X POST http://localhost:9292/v1/search/generate \
|
|
42
|
+
-H 'Authorization: Bearer change-me' \
|
|
43
|
+
-H 'Content-Type: application/json' \
|
|
44
|
+
-d '{"query":"Ruby HTTP clients","provider":"openai","limit":5}'
|
|
45
|
+
```
|
|
46
|
+
|
|
32
47
|
Running `bundle exec ruby examples/rest_api.ru` directly prints the same
|
|
33
48
|
endpoint catalog without starting a server.
|
|
34
49
|
|
|
@@ -57,6 +72,25 @@ configuration. The scripts are demonstrations rather than isolated test
|
|
|
57
72
|
fixtures; they may make real provider requests when the configured service is
|
|
58
73
|
available.
|
|
59
74
|
|
|
75
|
+
The web-search example requires a reachable SearXNG instance:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
docker compose up -d searxng
|
|
79
|
+
SEARXNG_URL=http://localhost:8080 bundle exec ruby examples/web_search.rb "Ruby HTTP clients"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The example returns normalized search results directly by default. Opt in to
|
|
83
|
+
feeding those results to the configured AI provider with `--generate`:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
SEARXNG_URL=http://localhost:8080 PRESCIENT_PROVIDER=openai \
|
|
87
|
+
bundle exec ruby examples/web_search.rb --generate "Ruby HTTP clients"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Omit `--generate` to keep the search results direct. `PRESCIENT_PROVIDER` is
|
|
91
|
+
only used with `--generate` and may be omitted when the default provider is
|
|
92
|
+
configured.
|
|
93
|
+
|
|
60
94
|
See the [main README](../README.md) for configuration, fallback behavior,
|
|
61
95
|
prompt templates, context exclusions, embeddings, and the public API. Rails
|
|
62
96
|
applications can also use the [integration guide](../INTEGRATION_GUIDE.md),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative '../lib/prescient'
|
|
5
|
+
|
|
6
|
+
generate = ARGV.delete('--generate')
|
|
7
|
+
if ARGV.include?('--help')
|
|
8
|
+
puts 'Usage: ruby examples/web_search.rb [--generate] [QUERY]'
|
|
9
|
+
puts ' --generate Feed normalized search results to the configured AI provider'
|
|
10
|
+
puts ' PRESCIENT_PROVIDER Provider used with --generate (default: configured provider)'
|
|
11
|
+
exit
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
query = ARGV.empty? ? 'Ruby HTTP clients' : ARGV.join(' ')
|
|
15
|
+
|
|
16
|
+
Prescient.configure do |config|
|
|
17
|
+
config.add_tool(
|
|
18
|
+
:web_search,
|
|
19
|
+
Prescient::Tool::SearXNG,
|
|
20
|
+
url: ENV.fetch('SEARXNG_URL', 'http://localhost:8080'),
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
result = if generate
|
|
25
|
+
Prescient.search_and_generate(
|
|
26
|
+
query,
|
|
27
|
+
provider: ENV['PRESCIENT_PROVIDER']&.to_sym,
|
|
28
|
+
limit: 20,
|
|
29
|
+
)
|
|
30
|
+
else
|
|
31
|
+
Prescient.tool(:web_search).search(query, limit: 20)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
puts JSON.pretty_generate(result)
|
data/lib/prescient/api.rb
CHANGED
|
@@ -27,6 +27,8 @@ class Prescient::API
|
|
|
27
27
|
['GET', '/v1/capabilities'] => :capabilities_response,
|
|
28
28
|
['GET', '/v1/health'] => :health_response,
|
|
29
29
|
['POST', '/v1/generate'] => :generate_response,
|
|
30
|
+
['POST', '/v1/search'] => :search_response,
|
|
31
|
+
['POST', '/v1/search/generate'] => :search_generate_response,
|
|
30
32
|
['POST', '/v1/embeddings'] => :embeddings_response,
|
|
31
33
|
['POST', '/v1/embeddings/batch'] => :batch_embeddings_response,
|
|
32
34
|
}.freeze
|
|
@@ -88,6 +90,56 @@ class Prescient::API
|
|
|
88
90
|
json_response(200, result, request_id)
|
|
89
91
|
end
|
|
90
92
|
|
|
93
|
+
def search_generate_response(env, _query, request_id)
|
|
94
|
+
payload = request_payload(env)
|
|
95
|
+
query = required_string(payload, 'query')
|
|
96
|
+
tool = search_tool_name(payload)
|
|
97
|
+
fallback = search_fallback(payload)
|
|
98
|
+
limit = search_limit(payload)
|
|
99
|
+
|
|
100
|
+
result = Prescient.search_and_generate(
|
|
101
|
+
query,
|
|
102
|
+
tool: tool,
|
|
103
|
+
provider: payload['provider']&.to_sym,
|
|
104
|
+
limit: limit,
|
|
105
|
+
enable_fallback: fallback,
|
|
106
|
+
**generation_options(payload),
|
|
107
|
+
)
|
|
108
|
+
json_response(200, result, request_id)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def search_response(env, _query, request_id)
|
|
112
|
+
payload = request_payload(env)
|
|
113
|
+
query = required_string(payload, 'query')
|
|
114
|
+
tool_name = search_tool_name(payload)
|
|
115
|
+
tool = Prescient.tool(tool_name)
|
|
116
|
+
raise Prescient::ToolConfigurationError, "tool not configured: #{tool_name}" unless tool
|
|
117
|
+
|
|
118
|
+
result = tool.search(query, limit: search_limit(payload))
|
|
119
|
+
json_response(200, result, request_id)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def search_tool_name(payload)
|
|
123
|
+
value = payload.fetch('tool', 'web_search')
|
|
124
|
+
raise ArgumentError, 'tool must be a non-empty string' unless value.is_a?(String) && !value.empty?
|
|
125
|
+
|
|
126
|
+
value.to_sym
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def search_fallback(payload)
|
|
130
|
+
fallback = payload.key?('fallback') ? payload['fallback'] : true
|
|
131
|
+
raise ArgumentError, 'fallback must be boolean' unless [true, false].include?(fallback)
|
|
132
|
+
|
|
133
|
+
fallback
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def search_limit(payload)
|
|
137
|
+
limit = payload['limit']
|
|
138
|
+
raise ArgumentError, 'limit must be a positive integer' if limit && (!limit.is_a?(Integer) || !limit.positive?)
|
|
139
|
+
|
|
140
|
+
limit
|
|
141
|
+
end
|
|
142
|
+
|
|
91
143
|
def embeddings_response(env, _query, request_id)
|
|
92
144
|
payload = request_payload(env)
|
|
93
145
|
input = required_string(payload, 'input')
|