prospector_engine 0.1.1 → 0.2.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/README.md +79 -57
- data/app/controllers/prospector/run_reclassifications_controller.rb +1 -1
- data/lib/generators/prospector/install/templates/prospector.rb +3 -2
- data/lib/prospector/CLAUDE.md +2 -2
- data/lib/prospector/classification/runner.rb +8 -9
- data/lib/prospector/configuration.rb +13 -1
- data/lib/prospector/engine.rb +10 -2
- data/lib/prospector/keywords/generator.rb +2 -1
- data/lib/prospector/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6ff4fed78e9e8c81ec50623a7e2cbe0076b0f890f3c3fec3561551585de0513
|
|
4
|
+
data.tar.gz: 584620ecbee56ffdf94520638a5b7ab409e13c9fc264dd934a44d8e798bf4275
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7bbc93fdaca17a89d0ba229fbd23bba95eef79cd723b288ec872162eab5801bc9bc4fa0f93506245b1e7a796ded93523c0d237d61d5463c60c603644cb7450e6
|
|
7
|
+
data.tar.gz: ac7828b1928c11db81a56079fa6b65442a8b85404a214d147283e02a69c4f9109b3fed4568da47548e1e9b758dc415206339ef3e19abeecde10d4e9588d7ec09
|
data/README.md
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
# Prospector
|
|
2
2
|
|
|
3
|
-
A Rails engine for discovering businesses from multiple sources with AI-powered keyword generation and classification.
|
|
3
|
+
A Rails engine for discovering businesses from multiple sources with AI-powered keyword generation, contact enrichment, and classification.
|
|
4
4
|
|
|
5
|
-
Prospector handles the full discovery pipeline: generate search keywords with AI, fetch business listings from external APIs, classify results for domain relevance, and present an admin review interface -- all inside your existing Rails app.
|
|
5
|
+
Prospector handles the full discovery pipeline: generate search keywords with AI, fetch business listings from external APIs, scrape websites for contact info, classify results for domain relevance, and present an admin review interface -- all inside your existing Rails app.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Multi-source adapters** -- Ships with Google Places. Pluggable interface for adding Yelp, Bing,
|
|
9
|
+
- **Multi-source adapters** -- Ships with Google Places. Pluggable interface for adding Yelp, Bing, or custom sources.
|
|
10
|
+
- **Contact enrichment** -- Automatically scrapes candidate websites for email and social links (Facebook, Instagram, LinkedIn, TikTok, YouTube). SSRF-protected with IP validation on every redirect hop.
|
|
10
11
|
- **AI keyword generation** -- On-demand LLM-powered keyword generation for any business domain. Keywords are stored and reused across runs.
|
|
11
12
|
- **AI classification** -- Automatically classifies discovered businesses for domain relevance. Non-relevant results are auto-rejected.
|
|
12
|
-
- **Flexible geography** -- Search by metro area, city, coordinates + radius, ZIP code, or bounding box.
|
|
13
|
-
- **Admin UI** -- Mountable admin interface with self-contained
|
|
13
|
+
- **Flexible geography** -- Search by metro area, city, coordinates + radius, ZIP code, or bounding box. 42 preloaded US metro areas available as presets.
|
|
14
|
+
- **Admin UI** -- Mountable admin interface with self-contained dark theme. Review candidates, approve/reject, bulk approve, trigger reclassification.
|
|
14
15
|
- **Keyword management** -- Admin UI for viewing, adding, toggling, and AI-generating search keywords per category.
|
|
15
16
|
- **Background jobs** -- Fetch, classify, and bulk approve jobs integrate with your existing queue (Solid Queue, Sidekiq, etc.).
|
|
16
|
-
- **Turbo Streams** -- Real-time updates when Turbo is present. Gracefully degrades without it.
|
|
17
|
-
- **
|
|
17
|
+
- **Turbo Streams** -- Real-time progress updates when Turbo is present. Gracefully degrades without it.
|
|
18
|
+
- **Instrumentation** -- ActiveSupport::Notifications events for run status changes and candidate approvals.
|
|
18
19
|
|
|
19
20
|
## Requirements
|
|
20
21
|
|
|
@@ -51,11 +52,20 @@ Edit `config/initializers/prospector.rb`:
|
|
|
51
52
|
|
|
52
53
|
```ruby
|
|
53
54
|
Prospector.configure do |config|
|
|
54
|
-
# Required: domain slug for keyword generation and classification context
|
|
55
|
+
# Required: domain slug for keyword generation and classification context.
|
|
55
56
|
config.domain = "motorcycle_services"
|
|
56
57
|
|
|
57
|
-
# Required:
|
|
58
|
-
|
|
58
|
+
# Required: admin authentication.
|
|
59
|
+
config.authenticate_admin_with do |controller|
|
|
60
|
+
controller.current_user&.admin?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Classifier class (must be set before classification runs).
|
|
64
|
+
# Can be a class constant or a string for lazy resolution.
|
|
65
|
+
config.classifier = MotorcycleClassifier
|
|
66
|
+
# config.classifier = "MotorcycleClassifier"
|
|
67
|
+
|
|
68
|
+
# Called when an admin approves a candidate (optional).
|
|
59
69
|
# Runs AFTER the candidate status is committed (safe to enqueue jobs).
|
|
60
70
|
config.on_approve do |candidate|
|
|
61
71
|
data = candidate.normalized_data
|
|
@@ -67,26 +77,16 @@ Prospector.configure do |config|
|
|
|
67
77
|
zip_code: data["zip_code"],
|
|
68
78
|
phone_number: data["phone_number"],
|
|
69
79
|
website: data["website"],
|
|
80
|
+
email: candidate.email,
|
|
70
81
|
latitude: data["latitude"],
|
|
71
82
|
longitude: data["longitude"],
|
|
72
83
|
service_types: candidate.llm_categories,
|
|
73
|
-
|
|
84
|
+
facebook_url: candidate.facebook_url,
|
|
85
|
+
instagram_url: candidate.instagram_url
|
|
74
86
|
)
|
|
75
87
|
end
|
|
76
88
|
|
|
77
|
-
# Required: admin authentication.
|
|
78
|
-
# Receives the controller instance.
|
|
79
|
-
config.authenticate_admin_with do |controller|
|
|
80
|
-
controller.current_user&.admin?
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Required: classifier class (inherits LlmClassifier::Classifier).
|
|
84
|
-
# Defines categories, model, and classification rules for your domain.
|
|
85
|
-
# See "Defining a Classifier" below.
|
|
86
|
-
config.classifier = MotorcycleClassifier
|
|
87
|
-
|
|
88
89
|
# Optional: check for duplicates before creating candidates.
|
|
89
|
-
# Return true to skip the candidate.
|
|
90
90
|
config.duplicate_check do |source_uid:, name:, **|
|
|
91
91
|
Business.exists?(["import_metadata->>'place_id' = ?", source_uid])
|
|
92
92
|
end
|
|
@@ -94,14 +94,17 @@ Prospector.configure do |config|
|
|
|
94
94
|
# Optional: default source adapter (default: :google_places)
|
|
95
95
|
config.default_source = :google_places
|
|
96
96
|
|
|
97
|
-
# Optional:
|
|
98
|
-
|
|
97
|
+
# Optional: model override for classification and keyword generation (a bare ruby_llm model id).
|
|
98
|
+
# Defaults to nil: classification uses the classifier's `model`, keyword generation uses RubyLLM's default.
|
|
99
|
+
# config.default_classifier_model = "claude-opus-4-6"
|
|
99
100
|
|
|
100
101
|
# Optional: job queue name (default: :default)
|
|
101
102
|
config.queue_name = :prospector
|
|
102
103
|
end
|
|
103
104
|
```
|
|
104
105
|
|
|
106
|
+
**Note:** `validate!` only runs in production. In development, missing `domain` or `authenticate_admin_with` will cause runtime errors rather than startup failures.
|
|
107
|
+
|
|
105
108
|
Set the required environment variables:
|
|
106
109
|
|
|
107
110
|
```bash
|
|
@@ -116,20 +119,21 @@ ANTHROPIC_API_KEY=your_anthropic_api_key # or whatever ruby_llm needs
|
|
|
116
119
|
Visit `/prospector` in your browser. The admin interface provides:
|
|
117
120
|
|
|
118
121
|
- **Runs** -- Create, monitor, retry, restart, and cancel discovery runs
|
|
119
|
-
- **Candidates** -- Review discovered businesses, approve or reject individually or in bulk
|
|
122
|
+
- **Candidates** -- Review discovered businesses with contact info, approve or reject individually or in bulk
|
|
120
123
|
- **Keywords** -- View, add, toggle, and AI-generate search keywords per category
|
|
121
124
|
|
|
122
125
|
### Creating a Run
|
|
123
126
|
|
|
124
127
|
1. Click "New Run" in the admin UI
|
|
125
128
|
2. Select a geography type (metro area, city, coordinates, ZIP code, or bounding box)
|
|
126
|
-
3.
|
|
127
|
-
4.
|
|
129
|
+
3. For metro areas, optionally select from 42 preloaded US metros or enter a custom one
|
|
130
|
+
4. Fill in the geography details
|
|
131
|
+
5. Click "Start Run"
|
|
128
132
|
|
|
129
133
|
The run progresses through these stages automatically:
|
|
130
134
|
|
|
131
135
|
```
|
|
132
|
-
pending -> running (
|
|
136
|
+
pending -> running (fetch + enrich) -> classifying (AI) -> completed
|
|
133
137
|
```
|
|
134
138
|
|
|
135
139
|
### Defining a Classifier
|
|
@@ -143,9 +147,8 @@ class MotorcycleClassifier < LlmClassifier::Classifier
|
|
|
143
147
|
:detailing, :training, :towing, :insurance, :touring,
|
|
144
148
|
:clubs, :parts
|
|
145
149
|
|
|
146
|
-
model "
|
|
150
|
+
model "claude-opus-4-6"
|
|
147
151
|
multi_label true
|
|
148
|
-
require_categories true # fail when no categories match (auto-reject)
|
|
149
152
|
|
|
150
153
|
system_prompt <<~PROMPT
|
|
151
154
|
You are classifying businesses for the motorcycle services domain.
|
|
@@ -164,13 +167,11 @@ class MotorcycleClassifier < LlmClassifier::Classifier
|
|
|
164
167
|
end
|
|
165
168
|
```
|
|
166
169
|
|
|
167
|
-
|
|
170
|
+
The classifier receives a hash with `name`, `address`, `website`, `description`, and `source_types` (comma-joined string) keys.
|
|
168
171
|
|
|
169
|
-
|
|
170
|
-
config.classifier = MotorcycleClassifier
|
|
171
|
-
```
|
|
172
|
+
Use a bare model id that ruby_llm recognizes (`claude-opus-4-6`, not `anthropic:claude-opus-4-6`) and that supports structured outputs, since llm_classifier 0.3 sends every request with a JSON schema.
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
Candidates the classifier assigns no categories are auto-rejected with reason `no_relevant_categories`. A failed classification (API error, unknown model, unparseable response) is recorded in `metadata["llm_error"]`, and the candidate stays pending so the next reclassification retries it. Leave `require_categories` off: with it on, llm_classifier reports "no categories" as a failure, so irrelevant businesses are counted as errors instead of being rejected.
|
|
174
175
|
|
|
175
176
|
### Programmatic Usage
|
|
176
177
|
|
|
@@ -195,13 +196,19 @@ Prospector::Keyword.create!(
|
|
|
195
196
|
# Approve a candidate programmatically
|
|
196
197
|
candidate = Prospector::Candidate.find(id)
|
|
197
198
|
candidate.approve! # fires the on_approve callback
|
|
199
|
+
|
|
200
|
+
# Access enriched contact info
|
|
201
|
+
candidate.email # => "info@example.com"
|
|
202
|
+
candidate.facebook_url # => "https://facebook.com/example"
|
|
203
|
+
candidate.instagram_url # => "https://instagram.com/example"
|
|
198
204
|
```
|
|
199
205
|
|
|
200
206
|
### Geography Types
|
|
201
207
|
|
|
202
208
|
```ruby
|
|
203
|
-
# Metro area (text search)
|
|
204
|
-
Prospector::Geography::MetroArea.new(name: "San Francisco", primary_state: "CA")
|
|
209
|
+
# Metro area (text search) -- 42 US metros available as presets
|
|
210
|
+
Prospector::Geography::MetroArea.new(name: "San Francisco Bay Area", primary_state: "CA")
|
|
211
|
+
Prospector::Geography::MetroArea::PRELOADED # => [{name: "Atlanta", primary_state: "GA"}, ...]
|
|
205
212
|
|
|
206
213
|
# City (text search)
|
|
207
214
|
Prospector::Geography::City.new(city: "Austin", state: "TX")
|
|
@@ -226,8 +233,6 @@ class MyApp::YelpAdapter < Prospector::Sources::Base
|
|
|
226
233
|
def self.adapter_key = "yelp"
|
|
227
234
|
|
|
228
235
|
def fetch(geography:, keywords:)
|
|
229
|
-
# Call the Yelp API for each keyword + geography combination.
|
|
230
|
-
# Return an Array of Prospector::Sources::Result.
|
|
231
236
|
keywords.flat_map do |keyword|
|
|
232
237
|
search_yelp(keyword, geography).map do |biz|
|
|
233
238
|
Prospector::Sources::Result.new(
|
|
@@ -260,8 +265,6 @@ Prospector.configure do |config|
|
|
|
260
265
|
end
|
|
261
266
|
```
|
|
262
267
|
|
|
263
|
-
Then select "Yelp" as the source when creating a run.
|
|
264
|
-
|
|
265
268
|
## Database Tables
|
|
266
269
|
|
|
267
270
|
Prospector creates four tables, all prefixed with `prospector_`:
|
|
@@ -269,7 +272,7 @@ Prospector creates four tables, all prefixed with `prospector_`:
|
|
|
269
272
|
| Table | Purpose |
|
|
270
273
|
|-------|---------|
|
|
271
274
|
| `prospector_runs` | Discovery runs with status, geography, and progress counters |
|
|
272
|
-
| `prospector_candidates` | Discovered businesses pending review |
|
|
275
|
+
| `prospector_candidates` | Discovered businesses with contact info (email, social links) pending review |
|
|
273
276
|
| `prospector_classification_runs` | Tracks AI reclassification operations |
|
|
274
277
|
| `prospector_keywords` | Stored search keywords per domain and category |
|
|
275
278
|
|
|
@@ -284,33 +287,52 @@ Prospector::Run (state machine)
|
|
|
284
287
|
| |-- Pipeline::Orchestrator
|
|
285
288
|
| | |-- Pipeline::Normalizer (address parsing)
|
|
286
289
|
| | |-- Duplicate checking (via config.duplicate_check)
|
|
287
|
-
| |
|
|
290
|
+
| | |-- Creates Prospector::Candidate records
|
|
291
|
+
| | '-- Enrichment::ContactScraper (email + social links)
|
|
288
292
|
| '-- Enqueues ClassifyJob
|
|
289
293
|
|
|
|
290
294
|
|-- ClassifyJob
|
|
291
295
|
| '-- Classification::Runner
|
|
292
|
-
| |-- Calls LLM via
|
|
296
|
+
| |-- Calls LLM via llm_classifier
|
|
293
297
|
| |-- Stores categories, confidence, reasoning in metadata
|
|
294
|
-
|
|
|
298
|
+
| |-- Auto-rejects candidates with no relevant categories
|
|
299
|
+
| '-- Records failed classifications as llm_error, leaves them pending
|
|
295
300
|
|
|
|
296
301
|
'-- Admin UI
|
|
297
|
-
|--
|
|
298
|
-
|--
|
|
299
|
-
|
|
302
|
+
|-- Runs: create, monitor, retry, restart, cancel
|
|
303
|
+
|-- Candidates: review, approve, reject, restore, bulk approve
|
|
304
|
+
|-- Keywords: view, add, toggle active, AI-generate
|
|
305
|
+
'-- Reclassification with model override
|
|
300
306
|
```
|
|
301
307
|
|
|
302
308
|
## Run States
|
|
303
309
|
|
|
304
310
|
```
|
|
305
311
|
pending --> running --> classifying --> completed
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
^
|
|
310
|
-
|
|
|
311
|
-
|
|
312
|
+
| | |
|
|
313
|
+
v v v
|
|
314
|
+
failed cancelled
|
|
315
|
+
^ ^
|
|
316
|
+
|____________|
|
|
317
|
+
(retry/restart)
|
|
312
318
|
```
|
|
313
319
|
|
|
320
|
+
- `cancellable?` -- pending, running, or classifying
|
|
321
|
+
- `retryable?` -- failed only
|
|
322
|
+
- `restartable?` -- completed, failed, or cancelled
|
|
323
|
+
- `restart!` destroys pending/rejected candidates and resets all counters
|
|
324
|
+
- `reset_for_retry!` resets status and error fields only
|
|
325
|
+
|
|
326
|
+
## Instrumentation
|
|
327
|
+
|
|
328
|
+
Prospector emits ActiveSupport::Notifications events:
|
|
329
|
+
|
|
330
|
+
| Event | Payload |
|
|
331
|
+
|-------|---------|
|
|
332
|
+
| `prospector.run.status_changed` | `run`, `status`, `previous_status` |
|
|
333
|
+
| `prospector.candidate.approved` | `candidate`, `record` |
|
|
334
|
+
| `prospector.candidate.rejected` | `candidate`, `reason` |
|
|
335
|
+
|
|
314
336
|
## Development
|
|
315
337
|
|
|
316
338
|
The gem ships with a devcontainer for containerized development:
|
|
@@ -320,10 +342,10 @@ The gem ships with a devcontainer for containerized development:
|
|
|
320
342
|
docker compose -f .devcontainer/compose.yaml up -d --build
|
|
321
343
|
|
|
322
344
|
# Run tests (inside the container)
|
|
323
|
-
docker exec -w /workspaces/
|
|
345
|
+
docker exec -w /workspaces/prospector_engine prospector_engine-app-1 bundle exec rake test
|
|
324
346
|
|
|
325
347
|
# Run a single test file
|
|
326
|
-
docker exec -w /workspaces/
|
|
348
|
+
docker exec -w /workspaces/prospector_engine prospector_engine-app-1 bundle exec ruby -Itest test/models/run_test.rb
|
|
327
349
|
```
|
|
328
350
|
|
|
329
351
|
The test suite uses a dummy Rails app (`test/dummy/`) with PostgreSQL.
|
|
@@ -12,7 +12,7 @@ module Prospector
|
|
|
12
12
|
|
|
13
13
|
def build_classification_run(run)
|
|
14
14
|
run.classification_runs.new(
|
|
15
|
-
ai_model: params[:ai_model] || Prospector.config.
|
|
15
|
+
ai_model: params[:ai_model].presence || Prospector.config.resolved_classifier_model,
|
|
16
16
|
include_approved: params[:include_approved] == "1",
|
|
17
17
|
actor_id: respond_to?(:current_user) ? current_user&.id : nil
|
|
18
18
|
)
|
|
@@ -24,8 +24,9 @@ Prospector.configure do |config|
|
|
|
24
24
|
# Optional: default source adapter (default: :google_places)
|
|
25
25
|
# config.default_source = :google_places
|
|
26
26
|
|
|
27
|
-
# Optional:
|
|
28
|
-
#
|
|
27
|
+
# Optional: model override for classification and keyword generation (a bare ruby_llm model id)
|
|
28
|
+
# Defaults to nil: classification uses the classifier's `model`, keyword generation uses RubyLLM's default
|
|
29
|
+
# config.default_classifier_model = "claude-opus-4-6"
|
|
29
30
|
|
|
30
31
|
# Optional: duplicate check hook
|
|
31
32
|
# config.duplicate_check do |source_uid:, name:, **|
|
data/lib/prospector/CLAUDE.md
CHANGED
|
@@ -39,7 +39,7 @@ Key methods: `to_query_string` (for text search), `to_coordinate_restriction` (f
|
|
|
39
39
|
|
|
40
40
|
## Classification (`classification/`)
|
|
41
41
|
|
|
42
|
-
- `Runner` - Iterates unprocessed candidates, calls
|
|
42
|
+
- `Runner` - Iterates unprocessed candidates, calls the configured `LlmClassifier::Classifier`, stores results in candidate metadata. Auto-rejects candidates with empty categories. A failed `Result` (or raised error) is stored as `llm_error` and counted as an error, never a rejection; the candidate stays `ai_unprocessed` so it is retried.
|
|
43
43
|
|
|
44
44
|
## Keywords (`keywords/`)
|
|
45
45
|
|
|
@@ -47,6 +47,6 @@ Key methods: `to_query_string` (for text search), `to_coordinate_restriction` (f
|
|
|
47
47
|
|
|
48
48
|
## Other Files
|
|
49
49
|
|
|
50
|
-
- `configuration.rb` - `Prospector.configure` DSL. Required keys: domain, on_approve, authenticate_admin_with.
|
|
50
|
+
- `configuration.rb` - `Prospector.configure` DSL. Required keys: domain, on_approve, authenticate_admin_with. `default_classifier_model` defaults to nil so the classifier's own `model` applies; `resolved_classifier_model` reports the model that will actually run.
|
|
51
51
|
- `engine.rb` - Rails::Engine with isolate_namespace.
|
|
52
52
|
- `error.rb` - Error hierarchy: Error, ConfigurationError, AdapterError, MissingApiKeyError, ClassificationError.
|
|
@@ -34,25 +34,24 @@ module Prospector
|
|
|
34
34
|
|
|
35
35
|
def classify_candidate(candidate)
|
|
36
36
|
result = invoke_classifier(candidate)
|
|
37
|
+
return record_error(candidate, result.error) if result.failure?
|
|
38
|
+
|
|
37
39
|
store_result(candidate, result)
|
|
38
40
|
auto_reject_if_empty(candidate, result)
|
|
39
41
|
@processed += 1
|
|
40
42
|
rescue => e
|
|
41
|
-
|
|
42
|
-
@errors += 1
|
|
43
|
+
record_error(candidate, e.message)
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
def invoke_classifier(candidate)
|
|
46
|
-
classifier =
|
|
47
|
+
classifier = Prospector.config.classifier_class
|
|
47
48
|
input = build_input(candidate)
|
|
48
49
|
classifier.classify(input, model: @model)
|
|
49
50
|
end
|
|
50
51
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
raw.is_a?(String) ? raw.constantize : raw
|
|
52
|
+
def record_error(candidate, message)
|
|
53
|
+
candidate.update!(metadata: candidate.metadata.merge("llm_error" => message))
|
|
54
|
+
@errors += 1
|
|
56
55
|
end
|
|
57
56
|
|
|
58
57
|
def build_input(candidate)
|
|
@@ -66,7 +65,7 @@ module Prospector
|
|
|
66
65
|
end
|
|
67
66
|
|
|
68
67
|
def store_result(candidate, result)
|
|
69
|
-
candidate.update!(metadata: candidate.metadata.merge(
|
|
68
|
+
candidate.update!(metadata: candidate.metadata.except("llm_error").merge(
|
|
70
69
|
"llm_categories" => result.categories,
|
|
71
70
|
"llm_confidence" => result.confidence,
|
|
72
71
|
"llm_reasoning" => result.reasoning,
|
|
@@ -12,7 +12,7 @@ module Prospector
|
|
|
12
12
|
def initialize
|
|
13
13
|
@domain = nil
|
|
14
14
|
@default_source = :google_places
|
|
15
|
-
@default_classifier_model =
|
|
15
|
+
@default_classifier_model = nil
|
|
16
16
|
@classifier = nil
|
|
17
17
|
@turbo_enabled = defined?(Turbo::Broadcastable)
|
|
18
18
|
@queue_name = :default
|
|
@@ -48,6 +48,18 @@ module Prospector
|
|
|
48
48
|
adapter.is_a?(String) ? adapter.constantize : adapter
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
def classifier_class
|
|
52
|
+
raise ConfigurationError, "Prospector.config.classifier must be set" unless classifier
|
|
53
|
+
|
|
54
|
+
classifier.is_a?(String) ? classifier.constantize : classifier
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def resolved_classifier_model
|
|
58
|
+
require "ruby_llm"
|
|
59
|
+
|
|
60
|
+
default_classifier_model || classifier_class.model || RubyLLM.config.default_model
|
|
61
|
+
end
|
|
62
|
+
|
|
51
63
|
def validate!
|
|
52
64
|
raise ConfigurationError, "Prospector.config.domain must be set" if domain.blank?
|
|
53
65
|
raise ConfigurationError, "Prospector.config.authenticate_admin_with must be set" unless @authenticate_admin_block
|
data/lib/prospector/engine.rb
CHANGED
|
@@ -2,8 +2,16 @@ module Prospector
|
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
3
|
isolate_namespace Prospector
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
initializer "prospector.autoload", before: :set_autoload_paths do |app|
|
|
6
|
+
lib_path = root.join("lib")
|
|
7
|
+
app.config.autoload_paths << lib_path
|
|
8
|
+
app.config.eager_load_paths << lib_path
|
|
9
|
+
|
|
10
|
+
Rails.autoloaders.main.ignore(
|
|
11
|
+
root.join("lib/prospector/version.rb"),
|
|
12
|
+
root.join("lib/generators")
|
|
13
|
+
)
|
|
14
|
+
end
|
|
7
15
|
|
|
8
16
|
initializer "prospector.configuration" do
|
|
9
17
|
Prospector.config.validate! if Rails.env.production?
|
|
@@ -30,6 +30,7 @@ module Prospector
|
|
|
30
30
|
|
|
31
31
|
chat = RubyLLM.chat(model: Prospector.config.default_classifier_model)
|
|
32
32
|
response = chat.ask(prompt_for(category))
|
|
33
|
+
@generated_by = chat.model.id
|
|
33
34
|
parse_keywords(response.content)
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -63,7 +64,7 @@ module Prospector
|
|
|
63
64
|
) do |record|
|
|
64
65
|
record.source = "llm"
|
|
65
66
|
record.metadata = {
|
|
66
|
-
"generated_by" =>
|
|
67
|
+
"generated_by" => @generated_by,
|
|
67
68
|
"generated_at" => Time.current.iso8601
|
|
68
69
|
}
|
|
69
70
|
end
|
data/lib/prospector/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prospector_engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AxiumFoundry
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -42,16 +42,16 @@ dependencies:
|
|
|
42
42
|
name: llm_classifier
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: 0.
|
|
47
|
+
version: 0.3.0
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: 0.
|
|
54
|
+
version: 0.3.0
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: minitest
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -178,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
178
178
|
- !ruby/object:Gem::Version
|
|
179
179
|
version: '0'
|
|
180
180
|
requirements: []
|
|
181
|
-
rubygems_version: 3.
|
|
181
|
+
rubygems_version: 3.5.22
|
|
182
182
|
signing_key:
|
|
183
183
|
specification_version: 4
|
|
184
184
|
summary: Business discovery from multiple sources with AI classification
|