Class: Ask::Graph
- Inherits:
-
Object
- Object
- Ask::Graph
- Defined in:
- lib/ask/graph.rb,
lib/ask/graph/runner.rb,
lib/ask/graph/context.rb,
lib/ask/graph/version.rb
Overview
Define durable workflow graphs with named steps, conditional routing, parallel execution, sub-graph composition, human-in-the-loop approval, per-item checkpointing loops, step timeouts, retry policies, and lifecycle hooks.
Defined Under Namespace
Classes: Context, Paused, Runner, StepFailed, StepTimeout, WorkflowTimeout
Constant Summary collapse
- VERSION =
"0.7.1"
Instance Attribute Summary collapse
-
#context ⇒ Object
readonly
Returns the value of attribute context.
-
#runner ⇒ Object
readonly
Returns the value of attribute runner.
Class Method Summary collapse
-
.after_step(method_name) ⇒ Object
Register a callback to run after every successful step.
-
.approve(klass, **opts) ⇒ Object
Declare a human-in-the-loop step.
-
.before_step(method_name) ⇒ Object
Register a callback to run before every step.
-
.call(input = nil, storage: nil) ⇒ Object
Create an instance and run it.
-
.declarations ⇒ Object
All step declarations collected across the class hierarchy.
-
.inherited(subclass) ⇒ Object
Inherited hook — copies declarations to subclasses.
-
.lifecycle_hooks ⇒ Object
--- Lifecycle hooks ---.
-
.on_failure(method_name) ⇒ Object
Register a callback to run when a step fails.
-
.run ⇒ Object
Create an instance and run it.
-
.step(klass, **opts) ⇒ Object
Declare a sequential step.
-
.step_timeout(seconds = :not_set) ⇒ Integer?
(also: default_step_timeout)
Set or get the default timeout (in seconds) for each step in this graph.
-
.steps(*klasses, **opts) ⇒ Object
Declare parallel steps — all run simultaneously.
-
.storage(store = :not_set) ⇒ Object?
Set or get the default storage backend for checkpoint data.
-
.storage=(store) ⇒ Object
Assignment form of #storage — convenient in initializers: Ask::Graph.storage = RedisPool.new.
-
.workflow_timeout(seconds = :not_set) ⇒ Integer?
(also: default_workflow_timeout)
Set or get the total runtime cap (in seconds) for the entire graph.
Instance Method Summary collapse
- #call ⇒ Object (also: #run)
-
#initialize(input = nil, storage: nil) ⇒ Graph
constructor
A new instance of Graph.
- #resume(input:) ⇒ Object
Constructor Details
#initialize(input = nil, storage: nil) ⇒ Graph
Returns a new instance of Graph.
284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/ask/graph.rb', line 284 def initialize(input = nil, storage: nil) @input = input store = storage || self.class.storage || Ask::State::Memory.new hooks = self.class.lifecycle_hooks @runner = Runner.new(self.class.declarations, storage: store, hooks: hooks, graph_instance: self, step_timeout: self.class.step_timeout, workflow_timeout: self.class.workflow_timeout) @context = nil end |
Instance Attribute Details
#context ⇒ Object (readonly)
Returns the value of attribute context.
298 299 300 |
# File 'lib/ask/graph.rb', line 298 def context @context end |
#runner ⇒ Object (readonly)
Returns the value of attribute runner.
297 298 299 |
# File 'lib/ask/graph.rb', line 297 def runner @runner end |
Class Method Details
.after_step(method_name) ⇒ Object
Register a callback to run after every successful step.
192 193 194 |
# File 'lib/ask/graph.rb', line 192 def after_step(method_name) lifecycle_hooks[:after_step] << method_name end |
.approve(klass, **opts) ⇒ Object
Declare a human-in-the-loop step. Runs the step, then pauses and waits for external input via #resume.
238 239 240 |
# File 'lib/ask/graph.rb', line 238 def approve(klass, **opts) declarations << build_declaration(:approve, klass, opts) end |
.before_step(method_name) ⇒ Object
Register a callback to run before every step.
186 187 188 |
# File 'lib/ask/graph.rb', line 186 def before_step(method_name) lifecycle_hooks[:before_step] << method_name end |
.call(input = nil, storage: nil) ⇒ Object
Create an instance and run it.
When input is a Context, the graph is run as a sub-graph:
the context data is exported, the graph runs with it, and results
are merged back into the original context automatically.
257 258 259 260 261 262 263 264 265 |
# File 'lib/ask/graph.rb', line 257 def call(input = nil, storage: nil) if input.is_a?(Context) ctx = new(input.export_data, storage: storage).call input.import(ctx) ctx else new(input, storage: storage).call end end |
.declarations ⇒ Object
All step declarations collected across the class hierarchy.
64 65 66 |
# File 'lib/ask/graph.rb', line 64 def declarations @declarations ||= [] end |
.inherited(subclass) ⇒ Object
Inherited hook — copies declarations to subclasses.
69 70 71 72 73 74 |
# File 'lib/ask/graph.rb', line 69 def inherited(subclass) super subclass.instance_variable_set(:@declarations, declarations.dup) subclass.instance_variable_set(:@lifecycle_hooks, lifecycle_hooks.transform_values(&:dup)) end |
.lifecycle_hooks ⇒ Object
--- Lifecycle hooks ---
180 181 182 |
# File 'lib/ask/graph.rb', line 180 def lifecycle_hooks @lifecycle_hooks ||= { before_step: [], after_step: [], on_failure: [] } end |
.on_failure(method_name) ⇒ Object
Register a callback to run when a step fails.
198 199 200 |
# File 'lib/ask/graph.rb', line 198 def on_failure(method_name) lifecycle_hooks[:on_failure] << method_name end |
.run ⇒ Object
Create an instance and run it.
When input is a Context, the graph is run as a sub-graph:
the context data is exported, the graph runs with it, and results
are merged back into the original context automatically.
266 267 268 269 270 271 272 273 274 |
# File 'lib/ask/graph.rb', line 266 def call(input = nil, storage: nil) if input.is_a?(Context) ctx = new(input.export_data, storage: storage).call input.import(ctx) ctx else new(input, storage: storage).call end end |
.step(klass, **opts) ⇒ Object
Declare a sequential step.
210 211 212 |
# File 'lib/ask/graph.rb', line 210 def step(klass, **opts) declarations << build_declaration(:step, klass, opts) end |
.step_timeout(seconds = :not_set) ⇒ Integer? Also known as: default_step_timeout
Set or get the default timeout (in seconds) for each step in this graph.
Steps without an explicit timeout: option inherit this value.
Setting on Ask::Graph itself applies to all graphs that don't set
their own default — a global default.
95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/ask/graph.rb', line 95 def step_timeout(seconds = :not_set) if seconds == :not_set if instance_variable_defined?(:@step_timeout) @step_timeout elsif superclass.respond_to?(:step_timeout) superclass.step_timeout end else @step_timeout = seconds end end |
.steps(*klasses, **opts) ⇒ Object
Declare parallel steps — all run simultaneously.
219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/ask/graph.rb', line 219 def steps(*klasses, **opts) declarations << { classes: klasses, type: :steps, name: klasses.map { |k| k.name || k.to_s }.join(", "), if: opts[:if], unless: opts[:unless], description: opts[:description], timeout: opts[:timeout], retry: opts[:retry] } end |
.storage(store = :not_set) ⇒ Object?
Set or get the default storage backend for checkpoint data.
The storage object must respond to #set(key, value) and #get(key).
Setting on Ask::Graph itself applies to all graphs that don't set their own storage — a global default.
160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/ask/graph.rb', line 160 def storage(store = :not_set) if store == :not_set if instance_variable_defined?(:@storage) @storage elsif superclass.respond_to?(:storage) superclass.storage end else @storage = store end end |
.storage=(store) ⇒ Object
174 175 176 |
# File 'lib/ask/graph.rb', line 174 def storage=(store) @storage = store end |
.workflow_timeout(seconds = :not_set) ⇒ Integer? Also known as: default_workflow_timeout
Set or get the total runtime cap (in seconds) for the entire graph. The whole workflow aborts with WorkflowTimeout if it exceeds this limit, regardless of individual step timeouts. Setting on Ask::Graph itself applies to all graphs that don't set their own default — a global default.
126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/ask/graph.rb', line 126 def workflow_timeout(seconds = :not_set) if seconds == :not_set if instance_variable_defined?(:@workflow_timeout) @workflow_timeout elsif superclass.respond_to?(:workflow_timeout) superclass.workflow_timeout end else @workflow_timeout = seconds end end |
Instance Method Details
#call ⇒ Object Also known as: run
300 301 302 303 304 305 306 307 |
# File 'lib/ask/graph.rb', line 300 def call(*) @context = Context.new(self, @input) @runner.run(@context) @context rescue => e raise unless e.is_a?(Paused) @context end |
#resume(input:) ⇒ Object
310 311 312 313 314 |
# File 'lib/ask/graph.rb', line 310 def resume(input:) @context.resume_input = input.to_s @runner.run(@context) @context end |