prospector_engine 0.1.2 → 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 +10 -5
- 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/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
|
@@ -94,8 +94,9 @@ 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
|
|
@@ -146,9 +147,8 @@ class MotorcycleClassifier < LlmClassifier::Classifier
|
|
|
146
147
|
:detailing, :training, :towing, :insurance, :touring,
|
|
147
148
|
:clubs, :parts
|
|
148
149
|
|
|
149
|
-
model "
|
|
150
|
+
model "claude-opus-4-6"
|
|
150
151
|
multi_label true
|
|
151
|
-
require_categories true
|
|
152
152
|
|
|
153
153
|
system_prompt <<~PROMPT
|
|
154
154
|
You are classifying businesses for the motorcycle services domain.
|
|
@@ -169,6 +169,10 @@ end
|
|
|
169
169
|
|
|
170
170
|
The classifier receives a hash with `name`, `address`, `website`, `description`, and `source_types` (comma-joined string) keys.
|
|
171
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.
|
|
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.
|
|
175
|
+
|
|
172
176
|
### Programmatic Usage
|
|
173
177
|
|
|
174
178
|
```ruby
|
|
@@ -291,7 +295,8 @@ Prospector::Run (state machine)
|
|
|
291
295
|
| '-- Classification::Runner
|
|
292
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
302
|
|-- Runs: create, monitor, retry, restart, cancel
|
|
@@ -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
|
|
@@ -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
|