Class: RubyLLM::ResearchJob
- Inherits:
-
Object
- Object
- RubyLLM::ResearchJob
- Includes:
- Support::Inspectable
- Defined in:
- lib/ruby_llm/research_job.rb
Overview
A hosted, single-turn research task. RubyLLM.research_later returns a job immediately; #wait polls it and #message returns its report. The provider's agent identity is separate from an inference model.
job = RubyLLM.research_later(question, provider: provider, agent: agent_id)
job.wait
puts job..content
Defined Under Namespace
Classes: Error, InterruptedError, TimeoutError
Constant Summary
Constants included from Support::Inspectable
Support::Inspectable::TRUNCATE_AT
Instance Attribute Summary collapse
-
#agent ⇒ Object
readonly
The hosted agent's identity, separate from a model ID.
-
#cancellation_error ⇒ Object
readonly
The error from an unsuccessful automatic cancellation attempt, if any.
-
#error ⇒ Object
readonly
The provider's failure explanation, if any.
-
#id ⇒ Object
readonly
The provider-assigned job ID.
-
#provider ⇒ Object
readonly
The provider slug.
-
#raw ⇒ Object
readonly
The original provider response from submission or the latest poll.
-
#status ⇒ Object
readonly
The normalized state:
:pending,:completed,:incomplete,:failed, or:cancelled. -
#tokens ⇒ Object
readonly
Returns the provider-reported task usage.
Class Method Summary collapse
-
.find(id, provider:, context: nil) ⇒ Object
Retrieves an existing task by ID without submitting another one.
-
.research(prompt, timeout: 600, interval: 5, **options) ⇒ Object
Runs a research task and returns its Message.
-
.research_later(prompt, provider:, agent:, with: nil, provider_tools: nil, context: nil, provider_options: {}, metadata: nil) ⇒ Object
Submits one research task without waiting.
-
.validate_polling_options(timeout, interval) ⇒ Object
:nodoc:.
Instance Method Summary collapse
-
#cancel(timeout: 5) ⇒ Object
Requests cancellation and returns self.
-
#cancelled? ⇒ Boolean
Returns whether the provider confirmed cancellation.
-
#completed? ⇒ Boolean
Returns whether the task finished with a complete report.
-
#cost ⇒ Object
Returns the reported cost, or unknown cost when the provider supplies no price.
-
#done? ⇒ Boolean
Returns whether the task reached any terminal state.
-
#failed? ⇒ Boolean
Returns whether the provider reported failure.
-
#incomplete? ⇒ Boolean
Returns whether the provider stopped before completing the report.
-
#initialize(id:, provider:, agent:, protocol:, **state) ⇒ ResearchJob
constructor
:nodoc:.
-
#message ⇒ Object
Returns the report, or
nilwhile pending. -
#pending? ⇒ Boolean
Returns whether the task is waiting or running.
-
#refresh(timeout: nil) ⇒ Object
Fetches the latest state and returns self.
-
#wait(timeout: 600, interval: 5) ⇒ Object
Polls until a terminal state and returns self.
Methods included from Support::Inspectable
#full_inspect, #inspect, #pretty_print
Constructor Details
#initialize(id:, provider:, agent:, protocol:, **state) ⇒ ResearchJob
:nodoc:
121 122 123 124 125 126 127 |
# File 'lib/ruby_llm/research_job.rb', line 121 def initialize(id:, provider:, agent:, protocol:, **state) # :nodoc: @id = id @provider = provider.to_sym @agent = agent @protocol = protocol apply_state(state) end |
Instance Attribute Details
#agent ⇒ Object (readonly)
The hosted agent's identity, separate from a model ID.
53 54 55 |
# File 'lib/ruby_llm/research_job.rb', line 53 def agent @agent end |
#cancellation_error ⇒ Object (readonly)
The error from an unsuccessful automatic cancellation attempt, if any.
66 67 68 |
# File 'lib/ruby_llm/research_job.rb', line 66 def cancellation_error @cancellation_error end |
#error ⇒ Object (readonly)
The provider's failure explanation, if any.
60 61 62 |
# File 'lib/ruby_llm/research_job.rb', line 60 def error @error end |
#id ⇒ Object (readonly)
The provider-assigned job ID.
47 48 49 |
# File 'lib/ruby_llm/research_job.rb', line 47 def id @id end |
#provider ⇒ Object (readonly)
The provider slug.
50 51 52 |
# File 'lib/ruby_llm/research_job.rb', line 50 def provider @provider end |
#raw ⇒ Object (readonly)
The original provider response from submission or the latest poll.
63 64 65 |
# File 'lib/ruby_llm/research_job.rb', line 63 def raw @raw end |
#status ⇒ Object (readonly)
The normalized state: :pending, :completed, :incomplete,
:failed, or :cancelled.
57 58 59 |
# File 'lib/ruby_llm/research_job.rb', line 57 def status @status end |
#tokens ⇒ Object (readonly)
Returns the provider-reported task usage. Unreported fields are nil.
197 198 199 |
# File 'lib/ruby_llm/research_job.rb', line 197 def tokens @tokens end |
Class Method Details
.find(id, provider:, context: nil) ⇒ Object
Retrieves an existing task by ID without submitting another one. Use the same provider configuration that created the task.
110 111 112 113 |
# File 'lib/ruby_llm/research_job.rb', line 110 def self.find(id, provider:, context: nil) config = context&.config || RubyLLM.config Provider.resolve!(provider).new(config).find_research_job(id) end |
.research(prompt, timeout: 600, interval: 5, **options) ⇒ Object
Runs a research task and returns its Message. On a timeout or
interrupt, attempts to cancel the remote task before raising an
error retaining the job. timeout: and interval: are seconds.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/ruby_llm/research_job.rb', line 88 def self.research(prompt, timeout: 600, interval: 5, **) (timeout, interval) job = research_later(prompt, **) job.wait(timeout:, interval:). rescue Interrupt => e raise unless job job.send(:attempt_cancellation) raise InterruptedError.new("Research interrupted (job #{job.id})", job:), cause: e rescue StandardError => e job ||= e.job if e.is_a?(Error) raise unless job job.send(:attempt_cancellation) raise if e.is_a?(Error) response = e.response if e.respond_to?(:response) raise Error.new("Research failed: #{e.} (job #{job.id})", job:, response:), cause: e end |
.research_later(prompt, provider:, agent:, with: nil, provider_tools: nil, context: nil, provider_options: {}, metadata: nil) ⇒ Object
Submits one research task without waiting. provider: and agent:
are required; with: attaches documents or images where supported.
provider_tools: accepts an array of aliases or a Hash of aliases
and their options, as on Chat#with_provider_tools.
72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/ruby_llm/research_job.rb', line 72 def self.research_later(prompt, provider:, agent:, with: nil, provider_tools: nil, context: nil, provider_options: {}, metadata: nil) config = context&.config || RubyLLM.config instance = Provider.resolve!(provider).new(config) payload = { provider: instance.slug, agent:, prompt:, metadata: } RubyLLM.instrument('research_job.ruby_llm', payload, config:) do |event| job = instance.research_later(prompt, agent:, with:, provider_tools:, provider_options:) event[:job_id] = job.id event[:status] = job.status job end end |
.validate_polling_options(timeout, interval) ⇒ Object
:nodoc:
115 116 117 118 119 |
# File 'lib/ruby_llm/research_job.rb', line 115 def self.(timeout, interval) # :nodoc: return if [timeout, interval].all? { |value| value.is_a?(Numeric) && value.positive? && value.finite? } raise ArgumentError, 'Research timeout and interval must be positive finite numbers' end |
Instance Method Details
#cancel(timeout: 5) ⇒ Object
Requests cancellation and returns self. Only the provider's response can confirm cancellation; this does not delete stored task data.
178 179 180 181 182 183 184 185 |
# File 'lib/ruby_llm/research_job.rb', line 178 def cancel(timeout: 5) self.class.(timeout, 1) unless done? state = request_with_timeout(timeout) { @protocol.cancel_research_job(self, timeout:) } apply_state(state) end self end |
#cancelled? ⇒ Boolean
Returns whether the provider confirmed cancellation.
145 |
# File 'lib/ruby_llm/research_job.rb', line 145 def cancelled? = status == :cancelled |
#completed? ⇒ Boolean
Returns whether the task finished with a complete report.
136 |
# File 'lib/ruby_llm/research_job.rb', line 136 def completed? = status == :completed |
#cost ⇒ Object
Returns the reported cost, or unknown cost when the provider supplies no price. Agent IDs are never used to look up model token prices.
201 202 203 |
# File 'lib/ruby_llm/research_job.rb', line 201 def cost Cost.from_h({ total: tokens.reported_cost }.compact, tokens:) end |
#done? ⇒ Boolean
Returns whether the task reached any terminal state.
133 |
# File 'lib/ruby_llm/research_job.rb', line 133 def done? = !pending? |
#failed? ⇒ Boolean
Returns whether the provider reported failure.
142 |
# File 'lib/ruby_llm/research_job.rb', line 142 def failed? = status == :failed |
#incomplete? ⇒ Boolean
Returns whether the provider stopped before completing the report.
139 |
# File 'lib/ruby_llm/research_job.rb', line 139 def incomplete? = status == :incomplete |
#message ⇒ Object
Returns the report, or nil while pending. An incomplete report has
Message#finish_reason :max_tokens. Raises Error for failed or
cancelled tasks.
190 191 192 193 194 |
# File 'lib/ruby_llm/research_job.rb', line 190 def raise Error.new("Research #{status}: #{error} (job #{id})", job: self) if failed? || cancelled? @message end |
#pending? ⇒ Boolean
Returns whether the task is waiting or running.
130 |
# File 'lib/ruby_llm/research_job.rb', line 130 def pending? = status == :pending |
#refresh(timeout: nil) ⇒ Object
Fetches the latest state and returns self. Does nothing after the
task finishes. timeout: limits this request in seconds.
149 150 151 152 153 154 155 156 |
# File 'lib/ruby_llm/research_job.rb', line 149 def refresh(timeout: nil) self.class.(timeout, 1) unless timeout.nil? unless done? state = request_with_timeout(timeout) { @protocol.refresh_research_job(self, timeout:) } apply_state(state) end self end |
#wait(timeout: 600, interval: 5) ⇒ Object
Polls until a terminal state and returns self. A timeout leaves the independent task running and raises TimeoutError with this job. Incomplete reports remain available through #message.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/ruby_llm/research_job.rb', line 161 def wait(timeout: 600, interval: 5) self.class.(timeout, interval) deadline = monotonic_time + timeout until done? remaining = deadline - monotonic_time raise TimeoutError.new("Research timed out (job #{id})", job: self) unless remaining.positive? refresh(timeout: remaining) sleep [interval, deadline - monotonic_time].min if pending? && monotonic_time < deadline end raise Error.new("Research #{status}: #{error} (job #{id})", job: self) if failed? || cancelled? self end |