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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e754e44af2ba6797161e333fc790c36f3a5e8b79e30deb4dc5eef78ddb820c1
4
- data.tar.gz: cf8338a50a7dfb29f2814b250dea974d69306c1e5c3ae7d69272663b4cd8cd43
3
+ metadata.gz: f6ff4fed78e9e8c81ec50623a7e2cbe0076b0f890f3c3fec3561551585de0513
4
+ data.tar.gz: 584620ecbee56ffdf94520638a5b7ab409e13c9fc264dd934a44d8e798bf4275
5
5
  SHA512:
6
- metadata.gz: dc08f57c755577e9589a729e671d21b3729dad5a81a6e0b8129ffe432eb07edaaaba8a0ad755827c45247616f2a11517a0750ed295d5bdb498c22461401b6648
7
- data.tar.gz: 22026bf0d973173b0c22d4b3c4648a0ac9957b83ae8dd164dab0cee6e3256bb3aa29f64a101207ed3f8084d1416337713df738835d6e1416abba16d16dfe8113
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: default AI model for classification and keyword generation
98
- config.default_classifier_model = "anthropic:claude-sonnet-4-20250514"
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 "anthropic:claude-sonnet-4-20250514"
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
- | '-- Auto-rejects candidates with no relevant categories
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.default_classifier_model,
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: default AI model for classification (passed to classifier)
28
- # config.default_classifier_model = "anthropic:claude-sonnet-4-20250514"
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:, **|
@@ -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 LLM via ruby_llm, stores results in candidate metadata. Auto-rejects candidates with empty categories.
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
- candidate.update!(metadata: candidate.metadata.merge("llm_error" => e.message))
42
- @errors += 1
43
+ record_error(candidate, e.message)
43
44
  end
44
45
 
45
46
  def invoke_classifier(candidate)
46
- classifier = resolve_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 resolve_classifier
52
- raw = Prospector.config.classifier
53
- raise ConfigurationError, "Prospector.config.classifier must be set" unless raw
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 = "anthropic:claude-sonnet-4-20250514"
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" => Prospector.config.default_classifier_model,
67
+ "generated_by" => @generated_by,
67
68
  "generated_at" => Time.current.iso8601
68
69
  }
69
70
  end
@@ -1,3 +1,3 @@
1
1
  module Prospector
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
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.1.2
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-04-06 00:00:00.000000000 Z
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.2.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.2.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.4.20
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