flow_nodes 0.1.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 +7 -0
- data/.qlty.yml +40 -0
- data/.rspec +3 -0
- data/.rubocop.yml +53 -0
- data/CHANGELOG.md +59 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +315 -0
- data/Rakefile +12 -0
- data/examples/advanced_workflow.rb +299 -0
- data/examples/batch_processing.rb +108 -0
- data/examples/chatbot.rb +91 -0
- data/examples/llm_calendar_parser.rb +429 -0
- data/examples/llm_content_processor.rb +603 -0
- data/examples/llm_document_analyzer.rb +276 -0
- data/examples/simple_llm_example.rb +166 -0
- data/examples/workflow.rb +158 -0
- data/lib/flow_nodes/async_batch_flow.rb +16 -0
- data/lib/flow_nodes/async_batch_node.rb +15 -0
- data/lib/flow_nodes/async_flow.rb +49 -0
- data/lib/flow_nodes/async_node.rb +48 -0
- data/lib/flow_nodes/async_parallel_batch_flow.rb +17 -0
- data/lib/flow_nodes/async_parallel_batch_node.rb +18 -0
- data/lib/flow_nodes/base_node.rb +117 -0
- data/lib/flow_nodes/batch_flow.rb +16 -0
- data/lib/flow_nodes/batch_node.rb +15 -0
- data/lib/flow_nodes/conditional_transition.rb +17 -0
- data/lib/flow_nodes/flow.rb +65 -0
- data/lib/flow_nodes/node.rb +54 -0
- data/lib/flow_nodes/version.rb +5 -0
- data/lib/flow_nodes.rb +20 -0
- data/sig/flow_nodes.rbs +4 -0
- metadata +82 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# A node designed for asynchronous execution.
|
|
5
|
+
class AsyncNode < Node
|
|
6
|
+
# Runs the node asynchronously. Use with `AsyncFlow` to chain successors.
|
|
7
|
+
def run_async(s)
|
|
8
|
+
warn("Node won't run successors. Use AsyncFlow.") unless @successors.empty?
|
|
9
|
+
_run_async(s)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def _run(_s)
|
|
13
|
+
raise "Use run_async for AsyncNode."
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def prep_async(_s) = nil
|
|
19
|
+
def exec_async(_p) = nil
|
|
20
|
+
def post_async(_s, _p, _e) = nil
|
|
21
|
+
|
|
22
|
+
def exec_fallback_async(p, exc)
|
|
23
|
+
exec_fallback(p, exc)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def _exec_async(p)
|
|
27
|
+
last_exception = nil
|
|
28
|
+
@max_retries.times do |i|
|
|
29
|
+
@current_retry = i
|
|
30
|
+
begin
|
|
31
|
+
return exec_async(p)
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
last_exception = e
|
|
34
|
+
sleep @wait if @wait.positive? && i < @max_retries - 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
exec_fallback_async(p, last_exception)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def _run_async(s)
|
|
41
|
+
prepared_params = prep_async(s)
|
|
42
|
+
actual_params = prepared_params || @params
|
|
43
|
+
result = _exec_async(actual_params)
|
|
44
|
+
post_async(s, actual_params, result)
|
|
45
|
+
result
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# An async flow that processes a batch of items in parallel using threads.
|
|
5
|
+
class AsyncParallelBatchFlow < AsyncFlow
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def _run_async(s)
|
|
9
|
+
batch_params = prep_async(s) || []
|
|
10
|
+
threads = batch_params.map do |item_params|
|
|
11
|
+
Thread.new { _orch_async(s, params: @params.merge(item_params)) }
|
|
12
|
+
end
|
|
13
|
+
threads.map(&:value)
|
|
14
|
+
post_async(s, batch_params, nil)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# An async node that processes a batch of items in parallel using threads.
|
|
5
|
+
class AsyncParallelBatchNode < AsyncNode
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
# @note This uses standard Ruby threads and is subject to the Global VM Lock (GVL).
|
|
9
|
+
# It is best suited for I/O-bound tasks, not for parallelizing CPU-bound work.
|
|
10
|
+
def _exec_async(items)
|
|
11
|
+
return [] if items.nil?
|
|
12
|
+
|
|
13
|
+
items_array = items.is_a?(Array) ? items : [items]
|
|
14
|
+
threads = items_array.map { |item| Thread.new { super(item) } }
|
|
15
|
+
threads.map(&:value)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# Base class for all nodes in a flow. Defines the core API for connecting
|
|
5
|
+
# nodes and executing logic.
|
|
6
|
+
class BaseNode
|
|
7
|
+
# @return [Hash] parameters passed to the node during execution.
|
|
8
|
+
attr_accessor :params
|
|
9
|
+
|
|
10
|
+
# @return [Hash<String, BaseNode>] a hash mapping action names to successor nodes.
|
|
11
|
+
attr_accessor :successors
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@params = {}
|
|
15
|
+
@successors = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Creates a deep copy of the node. This is critical for ensuring that each
|
|
19
|
+
# execution of a flow operates on its own isolated set of node instances,
|
|
20
|
+
# preventing state bleed.
|
|
21
|
+
#
|
|
22
|
+
# @param other [BaseNode] The original node being duplicated.
|
|
23
|
+
def initialize_copy(other)
|
|
24
|
+
super
|
|
25
|
+
@params = Marshal.load(Marshal.dump(other.params))
|
|
26
|
+
# Successors are other nodes. The orchestration loop handles duplicating them
|
|
27
|
+
# as they are traversed. A shallow copy of the hash is sufficient here.
|
|
28
|
+
@successors = other.successors.dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Sets the parameters for the node. To ensure thread safety and prevent
|
|
32
|
+
# state bleed, the parameters are deep-copied.
|
|
33
|
+
#
|
|
34
|
+
# @param p [Hash] The parameters to set.
|
|
35
|
+
def set_params(p)
|
|
36
|
+
@params = Marshal.load(Marshal.dump(p || {}))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Connects this node to a successor for a given action.
|
|
40
|
+
#
|
|
41
|
+
# @param node [BaseNode] The successor node.
|
|
42
|
+
# @param action [String] The action name that triggers the transition.
|
|
43
|
+
# @return [BaseNode] The successor node.
|
|
44
|
+
def nxt(node, action = "default")
|
|
45
|
+
warn("Overwriting successor for action '#{action}'") if @successors.key?(action)
|
|
46
|
+
@successors[action] = node
|
|
47
|
+
node
|
|
48
|
+
end
|
|
49
|
+
alias next nxt
|
|
50
|
+
|
|
51
|
+
# Defines the default transition to the next node.
|
|
52
|
+
# @param other [BaseNode] The node to transition to.
|
|
53
|
+
def >>(other)
|
|
54
|
+
nxt(other)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Creates a conditional transition to a successor node.
|
|
58
|
+
# @param action [String, Symbol] The action that triggers this transition.
|
|
59
|
+
# @return [ConditionalTransition] An object to define the target node.
|
|
60
|
+
def -(other)
|
|
61
|
+
raise TypeError, "Action must be a String or Symbol" unless other.is_a?(String) || other.is_a?(Symbol)
|
|
62
|
+
|
|
63
|
+
ConditionalTransition.new(self, other.to_s)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Executes the main logic of the node.
|
|
67
|
+
# This is intended to be overridden by subclasses.
|
|
68
|
+
#
|
|
69
|
+
# @param _p [Hash] The parameters for execution.
|
|
70
|
+
# @return [String, Symbol, nil] The result action to determine the next node in a flow.
|
|
71
|
+
def exec(_p)
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Runs the full lifecycle of the node: prep, exec, and post.
|
|
76
|
+
# If not part of a Flow, successors will not be executed.
|
|
77
|
+
#
|
|
78
|
+
# @param state [Object] An optional shared state object passed through the flow.
|
|
79
|
+
def run(state)
|
|
80
|
+
warn("Node won't run successors. Use Flow.") unless @successors.empty?
|
|
81
|
+
_run(state)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
protected
|
|
85
|
+
|
|
86
|
+
# Pre-execution hook. Can be used to prepare data.
|
|
87
|
+
# @param _state [Object] The shared state object.
|
|
88
|
+
def prep(_state)
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Post-execution hook. Can be used for cleanup or logging.
|
|
93
|
+
# @param _state [Object] The shared state object.
|
|
94
|
+
# @param _params [Hash] The parameters used in execution.
|
|
95
|
+
# @param _result [Object] The value returned by `exec`.
|
|
96
|
+
def post(_state, _params, _result)
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Internal execution wrapper.
|
|
101
|
+
# @param p [Hash] The parameters for execution.
|
|
102
|
+
def _exec(p)
|
|
103
|
+
exec(p)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Internal run lifecycle.
|
|
107
|
+
# @param s [Object] The shared state object.
|
|
108
|
+
def _run(s)
|
|
109
|
+
prepared_params = prep(s)
|
|
110
|
+
# Use the node's params if prep returns nil
|
|
111
|
+
params_to_use = prepared_params || @params
|
|
112
|
+
execution_result = _exec(params_to_use)
|
|
113
|
+
post(s, prepared_params, execution_result)
|
|
114
|
+
execution_result
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# A flow that processes a batch of items sequentially.
|
|
5
|
+
class BatchFlow < Flow
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def _run(s)
|
|
9
|
+
batch_params = prep(s) || []
|
|
10
|
+
batch_params.each do |item_params|
|
|
11
|
+
_orch(@params.merge(item_params))
|
|
12
|
+
end
|
|
13
|
+
post(s, batch_params, nil)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# A node that processes a batch of items sequentially.
|
|
5
|
+
class BatchNode < Node
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def _exec(items)
|
|
9
|
+
return [] if items.nil?
|
|
10
|
+
|
|
11
|
+
items_array = items.is_a?(Array) ? items : [items]
|
|
12
|
+
items_array.map { |item| super(item) }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# Represents a pending conditional transition from one node to another.
|
|
5
|
+
class ConditionalTransition
|
|
6
|
+
def initialize(source_node, action)
|
|
7
|
+
@source_node = source_node
|
|
8
|
+
@action = action
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Completes the transition by connecting the source node to the target.
|
|
12
|
+
# @param target_node [BaseNode] The node to transition to.
|
|
13
|
+
def >>(other)
|
|
14
|
+
@source_node.nxt(other, @action)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# Orchestrates a sequence of connected nodes, managing state and transitions.
|
|
5
|
+
class Flow < BaseNode
|
|
6
|
+
attr_accessor :start_node
|
|
7
|
+
|
|
8
|
+
def initialize(start: nil)
|
|
9
|
+
super()
|
|
10
|
+
@start_node = start
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Sets the starting node of the flow.
|
|
14
|
+
# @param node [BaseNode] The node to start the flow with.
|
|
15
|
+
# @return [BaseNode] The starting node.
|
|
16
|
+
def start(node)
|
|
17
|
+
@start_node = node
|
|
18
|
+
node
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protected
|
|
22
|
+
|
|
23
|
+
# Main orchestration logic that walks through the node graph.
|
|
24
|
+
def _orch(initial_params)
|
|
25
|
+
raise "Flow has no start node" unless @start_node
|
|
26
|
+
|
|
27
|
+
current_node = @start_node.dup
|
|
28
|
+
current_params = initial_params
|
|
29
|
+
|
|
30
|
+
loop do
|
|
31
|
+
# Merge the node's own params with the incoming params from the flow.
|
|
32
|
+
# The flow's params take precedence.
|
|
33
|
+
merged_params = current_node.params.merge(current_params || {})
|
|
34
|
+
current_node.set_params(merged_params)
|
|
35
|
+
current_params = merged_params # Ensure current_params is updated for the next iteration
|
|
36
|
+
|
|
37
|
+
action = current_node._run(current_node.params)
|
|
38
|
+
|
|
39
|
+
# If the node returns a symbol, it's an action to determine the next node.
|
|
40
|
+
current_node = get_next_node(current_node, action)&.dup
|
|
41
|
+
break unless current_node
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _run(s)
|
|
46
|
+
prepared_params = prep(s)
|
|
47
|
+
result = _orch(prepared_params || @params)
|
|
48
|
+
post(s, prepared_params, result)
|
|
49
|
+
result
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Determines the next node based on the result of the current node.
|
|
53
|
+
# For routing to work predictably, the return value of a node's `exec` method
|
|
54
|
+
# should be a String or Symbol that matches a defined successor action.
|
|
55
|
+
def get_next_node(current_node, action)
|
|
56
|
+
action_key = action.nil? || action == "" ? "default" : action.to_s
|
|
57
|
+
successor = current_node.successors[action_key]
|
|
58
|
+
|
|
59
|
+
if !successor && !current_node.successors.empty?
|
|
60
|
+
warn("Flow ends: action '#{action_key}' not found in successors: #{current_node.successors.keys.inspect}")
|
|
61
|
+
end
|
|
62
|
+
successor
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlowNodes
|
|
4
|
+
# A node with built-in retry logic.
|
|
5
|
+
class Node < BaseNode
|
|
6
|
+
attr_reader :max_retries, :wait, :current_retry
|
|
7
|
+
|
|
8
|
+
def initialize(max_retries: 1, wait: 0)
|
|
9
|
+
super()
|
|
10
|
+
@max_retries = max_retries
|
|
11
|
+
@wait = wait
|
|
12
|
+
@current_retry = 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Public method to execute the node's logic with retries.
|
|
16
|
+
# This is the entry point for a flow to run a node.
|
|
17
|
+
def _run(s)
|
|
18
|
+
prepared_params = prep(s)
|
|
19
|
+
actual_params = prepared_params || @params
|
|
20
|
+
execution_result = _exec(actual_params)
|
|
21
|
+
post(s, actual_params, execution_result)
|
|
22
|
+
execution_result
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
# Internal execution logic with retries.
|
|
28
|
+
# @note If your `exec` method performs actions with side effects (e.g., API calls,
|
|
29
|
+
# database writes), ensure they are idempotent. Retries will re-execute the logic,
|
|
30
|
+
# which could cause unintended repeated effects if not designed carefully.
|
|
31
|
+
def _exec(p)
|
|
32
|
+
last_exception = nil
|
|
33
|
+
@max_retries.times do |i|
|
|
34
|
+
@current_retry = i
|
|
35
|
+
begin
|
|
36
|
+
return exec(p)
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
last_exception = e
|
|
39
|
+
sleep @wait if @wait.positive? && i < @max_retries - 1
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
exec_fallback(p, last_exception)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Fallback method called after all retries have been exhausted.
|
|
46
|
+
# The default behavior is to re-raise the last exception.
|
|
47
|
+
#
|
|
48
|
+
# @param _params [Hash] The parameters that caused the failure.
|
|
49
|
+
# @param exception [Exception] The last exception that was caught.
|
|
50
|
+
def exec_fallback(_params, exception)
|
|
51
|
+
raise exception
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/flow_nodes.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "flow_nodes/version"
|
|
4
|
+
require_relative "flow_nodes/base_node"
|
|
5
|
+
require_relative "flow_nodes/conditional_transition"
|
|
6
|
+
require_relative "flow_nodes/node"
|
|
7
|
+
require_relative "flow_nodes/batch_node"
|
|
8
|
+
require_relative "flow_nodes/flow"
|
|
9
|
+
require_relative "flow_nodes/batch_flow"
|
|
10
|
+
require_relative "flow_nodes/async_node"
|
|
11
|
+
require_relative "flow_nodes/async_batch_node"
|
|
12
|
+
require_relative "flow_nodes/async_parallel_batch_node"
|
|
13
|
+
require_relative "flow_nodes/async_flow"
|
|
14
|
+
require_relative "flow_nodes/async_batch_flow"
|
|
15
|
+
require_relative "flow_nodes/async_parallel_batch_flow"
|
|
16
|
+
|
|
17
|
+
# FlowNodes is a minimalist, graph-based framework for building complex workflows
|
|
18
|
+
# and agentic systems in Ruby. It is a port of the Python PocketFlow library.
|
|
19
|
+
module FlowNodes
|
|
20
|
+
end
|
data/sig/flow_nodes.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: flow_nodes
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- RJ Robinson
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-07-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: FlowNodes is a Ruby port of PocketFlow, the Python framework created
|
|
14
|
+
by The Pocket. It brings the power and simplicity of PocketFlow's graph-based architecture
|
|
15
|
+
to the Ruby ecosystem. Build powerful LLM applications like Agents, Workflows, and
|
|
16
|
+
RAG systems with minimal code and maximum expressiveness.
|
|
17
|
+
email:
|
|
18
|
+
- rj@trainual.com
|
|
19
|
+
executables: []
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- ".qlty.yml"
|
|
24
|
+
- ".rspec"
|
|
25
|
+
- ".rubocop.yml"
|
|
26
|
+
- CHANGELOG.md
|
|
27
|
+
- CODE_OF_CONDUCT.md
|
|
28
|
+
- LICENSE.txt
|
|
29
|
+
- README.md
|
|
30
|
+
- Rakefile
|
|
31
|
+
- examples/advanced_workflow.rb
|
|
32
|
+
- examples/batch_processing.rb
|
|
33
|
+
- examples/chatbot.rb
|
|
34
|
+
- examples/llm_calendar_parser.rb
|
|
35
|
+
- examples/llm_content_processor.rb
|
|
36
|
+
- examples/llm_document_analyzer.rb
|
|
37
|
+
- examples/simple_llm_example.rb
|
|
38
|
+
- examples/workflow.rb
|
|
39
|
+
- lib/flow_nodes.rb
|
|
40
|
+
- lib/flow_nodes/async_batch_flow.rb
|
|
41
|
+
- lib/flow_nodes/async_batch_node.rb
|
|
42
|
+
- lib/flow_nodes/async_flow.rb
|
|
43
|
+
- lib/flow_nodes/async_node.rb
|
|
44
|
+
- lib/flow_nodes/async_parallel_batch_flow.rb
|
|
45
|
+
- lib/flow_nodes/async_parallel_batch_node.rb
|
|
46
|
+
- lib/flow_nodes/base_node.rb
|
|
47
|
+
- lib/flow_nodes/batch_flow.rb
|
|
48
|
+
- lib/flow_nodes/batch_node.rb
|
|
49
|
+
- lib/flow_nodes/conditional_transition.rb
|
|
50
|
+
- lib/flow_nodes/flow.rb
|
|
51
|
+
- lib/flow_nodes/node.rb
|
|
52
|
+
- lib/flow_nodes/version.rb
|
|
53
|
+
- sig/flow_nodes.rbs
|
|
54
|
+
homepage: https://github.com/rjrobinson/flow_nodes
|
|
55
|
+
licenses:
|
|
56
|
+
- MIT
|
|
57
|
+
metadata:
|
|
58
|
+
allowed_push_host: https://rubygems.org
|
|
59
|
+
homepage_uri: https://github.com/rjrobinson/flow_nodes
|
|
60
|
+
source_code_uri: https://github.com/rjrobinson/flow_nodes
|
|
61
|
+
changelog_uri: https://github.com/rjrobinson/flow_nodes/blob/main/CHANGELOG.md
|
|
62
|
+
rubygems_mfa_required: 'true'
|
|
63
|
+
post_install_message:
|
|
64
|
+
rdoc_options: []
|
|
65
|
+
require_paths:
|
|
66
|
+
- lib
|
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: 3.1.0
|
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '0'
|
|
77
|
+
requirements: []
|
|
78
|
+
rubygems_version: 3.5.22
|
|
79
|
+
signing_key:
|
|
80
|
+
specification_version: 4
|
|
81
|
+
summary: A Ruby port of PocketFlow, the minimalist LLM framework.
|
|
82
|
+
test_files: []
|