graph-agent 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/.github/workflows/ci.yml +50 -0
- data/.github/workflows/release.yml +49 -0
- data/.gitignore +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +126 -0
- data/CHANGELOG.md +26 -0
- data/CLAUDE.md +128 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +94 -0
- data/LICENSE +21 -0
- data/Makefile +114 -0
- data/README.md +464 -0
- data/Rakefile +15 -0
- data/docs/README.md +55 -0
- data/docs/api_reference.md +832 -0
- data/docs/concepts.md +216 -0
- data/docs/edges.md +265 -0
- data/docs/error_handling.md +241 -0
- data/docs/human_in_the_loop.md +231 -0
- data/docs/persistence.md +276 -0
- data/docs/quickstart.md +154 -0
- data/docs/send_and_command.md +218 -0
- data/docs/state.md +181 -0
- data/docs/streaming.md +172 -0
- data/graph-agent.gemspec +48 -0
- data/lib/graph_agent/channels/base_channel.rb +52 -0
- data/lib/graph_agent/channels/binary_operator_aggregate.rb +56 -0
- data/lib/graph_agent/channels/ephemeral_value.rb +59 -0
- data/lib/graph_agent/channels/last_value.rb +49 -0
- data/lib/graph_agent/channels/topic.rb +58 -0
- data/lib/graph_agent/checkpoint/base_saver.rb +38 -0
- data/lib/graph_agent/checkpoint/in_memory_saver.rb +145 -0
- data/lib/graph_agent/constants.rb +9 -0
- data/lib/graph_agent/errors.rb +41 -0
- data/lib/graph_agent/graph/compiled_state_graph.rb +362 -0
- data/lib/graph_agent/graph/conditional_edge.rb +57 -0
- data/lib/graph_agent/graph/edge.rb +23 -0
- data/lib/graph_agent/graph/mermaid_visualizer.rb +154 -0
- data/lib/graph_agent/graph/message_graph.rb +18 -0
- data/lib/graph_agent/graph/node.rb +61 -0
- data/lib/graph_agent/graph/state_graph.rb +197 -0
- data/lib/graph_agent/reducers.rb +34 -0
- data/lib/graph_agent/state/schema.rb +54 -0
- data/lib/graph_agent/types/cache_policy.rb +12 -0
- data/lib/graph_agent/types/command.rb +26 -0
- data/lib/graph_agent/types/interrupt.rb +28 -0
- data/lib/graph_agent/types/retry_policy.rb +42 -0
- data/lib/graph_agent/types/send.rb +26 -0
- data/lib/graph_agent/types/state_snapshot.rb +28 -0
- data/lib/graph_agent/version.rb +5 -0
- data/lib/graph_agent.rb +29 -0
- metadata +158 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "mermaid_visualizer"
|
|
4
|
+
|
|
5
|
+
module GraphAgent
|
|
6
|
+
module Graph
|
|
7
|
+
class StateGraph
|
|
8
|
+
attr_reader :schema, :nodes, :edges, :branches, :waiting_edges
|
|
9
|
+
|
|
10
|
+
def initialize(schema = nil, input_schema: nil, output_schema: nil)
|
|
11
|
+
@schema = _normalize_schema(schema)
|
|
12
|
+
@input_schema = input_schema ? _normalize_schema(input_schema) : nil
|
|
13
|
+
@output_schema = output_schema ? _normalize_schema(output_schema) : nil
|
|
14
|
+
@nodes = {}
|
|
15
|
+
@edges = Set.new
|
|
16
|
+
@branches = Hash.new { |h, k| h[k] = {} }
|
|
17
|
+
@waiting_edges = Set.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_node(name, action = nil, metadata: nil, retry_policy: nil, cache_policy: nil, &block)
|
|
21
|
+
action = block if block && action.nil?
|
|
22
|
+
name = _get_node_name(name, action)
|
|
23
|
+
|
|
24
|
+
raise InvalidGraphError.new("Node action must be provided") if action.nil?
|
|
25
|
+
raise InvalidGraphError.new("Node '#{name}' already exists") if @nodes.key?(name)
|
|
26
|
+
raise InvalidGraphError.new("Node name '#{name}' is reserved") if [END_NODE.to_s, START.to_s].include?(name)
|
|
27
|
+
|
|
28
|
+
@nodes[name] = Node.new(
|
|
29
|
+
name, action,
|
|
30
|
+
metadata: metadata,
|
|
31
|
+
retry_policy: retry_policy,
|
|
32
|
+
cache_policy: cache_policy
|
|
33
|
+
)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def add_edge(start_key, end_key)
|
|
38
|
+
if start_key.is_a?(Array)
|
|
39
|
+
targets = start_key.map(&:to_s)
|
|
40
|
+
@waiting_edges.add([targets, end_key.to_s])
|
|
41
|
+
return self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
start_key = start_key.to_s
|
|
45
|
+
end_key = end_key.to_s
|
|
46
|
+
|
|
47
|
+
raise InvalidGraphError.new("END cannot be a start node") if start_key == END_NODE.to_s
|
|
48
|
+
raise InvalidGraphError.new("START cannot be an end node") if end_key == START.to_s
|
|
49
|
+
|
|
50
|
+
@edges.add(Edge.new(start_key, end_key))
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_conditional_edges(source, path, path_map = nil)
|
|
55
|
+
source = source.to_s
|
|
56
|
+
name = _branch_name(path)
|
|
57
|
+
|
|
58
|
+
if @branches[source].key?(name)
|
|
59
|
+
raise InvalidGraphError.new("Branch '#{name}' already exists for node '#{source}'")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@branches[source][name] = ConditionalEdge.new(source, path, path_map: path_map)
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def add_sequence(nodes)
|
|
67
|
+
node_names = nodes.map do |node|
|
|
68
|
+
if node.is_a?(Array)
|
|
69
|
+
name, action = node
|
|
70
|
+
add_node(name, action)
|
|
71
|
+
name.to_s
|
|
72
|
+
elsif node.respond_to?(:call)
|
|
73
|
+
name = _get_node_name(nil, node)
|
|
74
|
+
add_node(name, node)
|
|
75
|
+
name
|
|
76
|
+
else
|
|
77
|
+
node.to_s
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
node_names.each_cons(2) { |a, b| add_edge(a, b) }
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def set_entry_point(node_name)
|
|
86
|
+
add_edge(START, node_name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def set_finish_point(node_name)
|
|
90
|
+
add_edge(node_name, END_NODE)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def set_conditional_entry_point(path, path_map = nil)
|
|
94
|
+
add_conditional_edges(START, path, path_map)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def compile(checkpointer: nil, interrupt_before: nil, interrupt_after: nil, debug: false)
|
|
98
|
+
validate!
|
|
99
|
+
|
|
100
|
+
CompiledStateGraph.new(
|
|
101
|
+
builder: self,
|
|
102
|
+
checkpointer: checkpointer,
|
|
103
|
+
interrupt_before: interrupt_before || [],
|
|
104
|
+
interrupt_after: interrupt_after || [],
|
|
105
|
+
debug: debug
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Generate a Mermaid diagram representation of the graph
|
|
110
|
+
def to_mermaid(options = {})
|
|
111
|
+
MermaidVisualizer.render(self, options)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Print Mermaid diagram to stdout
|
|
115
|
+
def print_mermaid(options = {})
|
|
116
|
+
puts to_mermaid(options)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def _normalize_schema(schema)
|
|
122
|
+
case schema
|
|
123
|
+
when State::Schema then schema
|
|
124
|
+
when Hash then _schema_from_hash(schema)
|
|
125
|
+
when nil then nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def _schema_from_hash(hash)
|
|
130
|
+
s = State::Schema.new
|
|
131
|
+
hash.each do |name, opts|
|
|
132
|
+
opts.is_a?(Hash) ? s.field(name, **opts) : s.field(name)
|
|
133
|
+
end
|
|
134
|
+
s
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def _get_node_name(name, action)
|
|
138
|
+
return name.to_s if name
|
|
139
|
+
|
|
140
|
+
if action.respond_to?(:name) && !action.name.nil? && !action.name.empty?
|
|
141
|
+
action.name.split("::").last.gsub(/[^a-zA-Z0-9_]/, "_")
|
|
142
|
+
else
|
|
143
|
+
raise InvalidGraphError.new("Node name must be provided")
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def _branch_name(path)
|
|
148
|
+
if path.respond_to?(:name) && path.name && !path.name.empty?
|
|
149
|
+
path.name
|
|
150
|
+
else
|
|
151
|
+
"condition_#{@branches.values.sum(&:size)}"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def validate!
|
|
156
|
+
_validate_entry_point!
|
|
157
|
+
_validate_edges!
|
|
158
|
+
_validate_outgoing_edges!
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def _validate_entry_point!
|
|
162
|
+
entry_edges = @edges.any? { |e| e.source == START.to_s }
|
|
163
|
+
entry_branches = @branches[START.to_s]&.any?
|
|
164
|
+
|
|
165
|
+
return if entry_edges || entry_branches
|
|
166
|
+
|
|
167
|
+
raise InvalidGraphError.new("Graph must have an entry point. Use set_entry_point or add_edge(START, ...)")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def _validate_edges!
|
|
171
|
+
sentinel = [START.to_s, END_NODE.to_s]
|
|
172
|
+
@edges.each do |edge|
|
|
173
|
+
next if sentinel.include?(edge.source)
|
|
174
|
+
|
|
175
|
+
unless @nodes.key?(edge.source)
|
|
176
|
+
raise InvalidGraphError.new("Edge references unknown source node '#{edge.source}'")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
next if edge.target == END_NODE.to_s
|
|
180
|
+
|
|
181
|
+
unless @nodes.key?(edge.target)
|
|
182
|
+
raise InvalidGraphError.new("Edge references unknown target node '#{edge.target}'")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def _validate_outgoing_edges!
|
|
188
|
+
@nodes.each_key do |name|
|
|
189
|
+
has_outgoing = @edges.any? { |e| e.source == name } ||
|
|
190
|
+
(@branches.key?(name) && !@branches[name].empty?)
|
|
191
|
+
|
|
192
|
+
raise InvalidGraphError.new("Node '#{name}' has no outgoing edges") unless has_outgoing
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphAgent
|
|
4
|
+
module Reducers
|
|
5
|
+
ADD = ->(a, b) { a + b }
|
|
6
|
+
MERGE = ->(a, b) { a.merge(b) }
|
|
7
|
+
REPLACE = ->(_, b) { b }
|
|
8
|
+
APPEND = ->(a, b) { Array(a) + Array(b) }
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def add_messages(existing, new_messages)
|
|
13
|
+
existing = Array(existing)
|
|
14
|
+
new_messages = Array(new_messages)
|
|
15
|
+
|
|
16
|
+
existing_by_id = {}
|
|
17
|
+
existing.each_with_index do |msg, idx|
|
|
18
|
+
if msg.is_a?(Hash) && msg[:id]
|
|
19
|
+
existing_by_id[msg[:id]] = idx
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
result = existing.dup
|
|
24
|
+
new_messages.each do |msg|
|
|
25
|
+
if msg.is_a?(Hash) && msg[:id] && existing_by_id.key?(msg[:id])
|
|
26
|
+
result[existing_by_id[msg[:id]]] = msg
|
|
27
|
+
else
|
|
28
|
+
result << msg
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphAgent
|
|
4
|
+
module State
|
|
5
|
+
class Schema
|
|
6
|
+
attr_reader :fields
|
|
7
|
+
|
|
8
|
+
Field = Data.define(:name, :type, :reducer, :default)
|
|
9
|
+
|
|
10
|
+
def initialize(&block)
|
|
11
|
+
@fields = {}
|
|
12
|
+
instance_eval(&block) if block
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def field(name, type: nil, reducer: nil, default: nil)
|
|
16
|
+
@fields[name.to_sym] = Field.new(
|
|
17
|
+
name: name.to_sym,
|
|
18
|
+
type: type,
|
|
19
|
+
reducer: reducer,
|
|
20
|
+
default: default
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initial_state
|
|
25
|
+
@fields.transform_values do |f|
|
|
26
|
+
if f.default.nil?
|
|
27
|
+
nil
|
|
28
|
+
elsif f.default.respond_to?(:dup)
|
|
29
|
+
begin
|
|
30
|
+
f.default.dup
|
|
31
|
+
rescue TypeError
|
|
32
|
+
f.default
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
f.default
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def apply(state, updates)
|
|
41
|
+
updates.each do |key, value|
|
|
42
|
+
key = key.to_sym
|
|
43
|
+
f = @fields[key]
|
|
44
|
+
if f&.reducer
|
|
45
|
+
state[key] = f.reducer.call(state[key], value)
|
|
46
|
+
else
|
|
47
|
+
state[key] = value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
state
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphAgent
|
|
4
|
+
class Command
|
|
5
|
+
PARENT = :__parent__
|
|
6
|
+
|
|
7
|
+
attr_reader :graph, :update, :resume, :goto
|
|
8
|
+
|
|
9
|
+
def initialize(graph: nil, update: nil, resume: nil, goto: [])
|
|
10
|
+
@graph = graph
|
|
11
|
+
@update = update
|
|
12
|
+
@resume = resume
|
|
13
|
+
@goto = Array(goto)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
parts = []
|
|
18
|
+
parts << "graph=#{graph.inspect}" if graph
|
|
19
|
+
parts << "update=#{update.inspect}" if update
|
|
20
|
+
parts << "resume=#{resume.inspect}" if resume
|
|
21
|
+
parts << "goto=#{goto.inspect}" unless goto.empty?
|
|
22
|
+
"Command(#{parts.join(", ")})"
|
|
23
|
+
end
|
|
24
|
+
alias inspect to_s
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module GraphAgent
|
|
6
|
+
class Interrupt
|
|
7
|
+
attr_reader :value, :id
|
|
8
|
+
|
|
9
|
+
def initialize(value, id: nil)
|
|
10
|
+
@value = value
|
|
11
|
+
@id = id || SecureRandom.hex(16)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def ==(other)
|
|
15
|
+
other.is_a?(Interrupt) && id == other.id && value == other.value
|
|
16
|
+
end
|
|
17
|
+
alias eql? ==
|
|
18
|
+
|
|
19
|
+
def hash
|
|
20
|
+
[id, value].hash
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
"Interrupt(value=#{value.inspect}, id=#{id.inspect})"
|
|
25
|
+
end
|
|
26
|
+
alias inspect to_s
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphAgent
|
|
4
|
+
class RetryPolicy
|
|
5
|
+
attr_reader :initial_interval, :backoff_factor, :max_interval,
|
|
6
|
+
:max_attempts, :jitter, :retry_on
|
|
7
|
+
|
|
8
|
+
def initialize(
|
|
9
|
+
initial_interval: 0.5,
|
|
10
|
+
backoff_factor: 2.0,
|
|
11
|
+
max_interval: 128.0,
|
|
12
|
+
max_attempts: 3,
|
|
13
|
+
jitter: true,
|
|
14
|
+
retry_on: StandardError
|
|
15
|
+
)
|
|
16
|
+
@initial_interval = initial_interval
|
|
17
|
+
@backoff_factor = backoff_factor
|
|
18
|
+
@max_interval = max_interval
|
|
19
|
+
@max_attempts = max_attempts
|
|
20
|
+
@jitter = jitter
|
|
21
|
+
@retry_on = retry_on
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def should_retry?(error)
|
|
25
|
+
case @retry_on
|
|
26
|
+
when Proc
|
|
27
|
+
@retry_on.call(error)
|
|
28
|
+
when Array
|
|
29
|
+
@retry_on.any? { |klass| error.is_a?(klass) }
|
|
30
|
+
else
|
|
31
|
+
error.is_a?(@retry_on)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def interval_for(attempt)
|
|
36
|
+
interval = @initial_interval * (@backoff_factor**attempt)
|
|
37
|
+
interval = [interval, @max_interval].min
|
|
38
|
+
interval += rand * interval * 0.1 if @jitter
|
|
39
|
+
interval
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphAgent
|
|
4
|
+
class Send
|
|
5
|
+
attr_reader :node, :arg
|
|
6
|
+
|
|
7
|
+
def initialize(node, arg)
|
|
8
|
+
@node = node.to_s
|
|
9
|
+
@arg = arg
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ==(other)
|
|
13
|
+
other.is_a?(Send) && node == other.node && arg == other.arg
|
|
14
|
+
end
|
|
15
|
+
alias eql? ==
|
|
16
|
+
|
|
17
|
+
def hash
|
|
18
|
+
[node, arg].hash
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
"Send(node=#{node.inspect}, arg=#{arg.inspect})"
|
|
23
|
+
end
|
|
24
|
+
alias inspect to_s
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphAgent
|
|
4
|
+
class StateSnapshot
|
|
5
|
+
attr_reader :values, :next_nodes, :config, :metadata,
|
|
6
|
+
:created_at, :parent_config, :tasks, :interrupts
|
|
7
|
+
|
|
8
|
+
def initialize(
|
|
9
|
+
values:,
|
|
10
|
+
next_nodes: [],
|
|
11
|
+
config: {},
|
|
12
|
+
metadata: nil,
|
|
13
|
+
created_at: nil,
|
|
14
|
+
parent_config: nil,
|
|
15
|
+
tasks: [],
|
|
16
|
+
interrupts: []
|
|
17
|
+
)
|
|
18
|
+
@values = values
|
|
19
|
+
@next_nodes = Array(next_nodes)
|
|
20
|
+
@config = config
|
|
21
|
+
@metadata = metadata
|
|
22
|
+
@created_at = created_at
|
|
23
|
+
@parent_config = parent_config
|
|
24
|
+
@tasks = Array(tasks)
|
|
25
|
+
@interrupts = Array(interrupts)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/graph_agent.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "graph_agent/version"
|
|
4
|
+
require_relative "graph_agent/constants"
|
|
5
|
+
require_relative "graph_agent/errors"
|
|
6
|
+
require_relative "graph_agent/reducers"
|
|
7
|
+
require_relative "graph_agent/state/schema"
|
|
8
|
+
require_relative "graph_agent/channels/base_channel"
|
|
9
|
+
require_relative "graph_agent/channels/last_value"
|
|
10
|
+
require_relative "graph_agent/channels/binary_operator_aggregate"
|
|
11
|
+
require_relative "graph_agent/channels/ephemeral_value"
|
|
12
|
+
require_relative "graph_agent/channels/topic"
|
|
13
|
+
require_relative "graph_agent/types/send"
|
|
14
|
+
require_relative "graph_agent/types/command"
|
|
15
|
+
require_relative "graph_agent/types/retry_policy"
|
|
16
|
+
require_relative "graph_agent/types/cache_policy"
|
|
17
|
+
require_relative "graph_agent/types/interrupt"
|
|
18
|
+
require_relative "graph_agent/types/state_snapshot"
|
|
19
|
+
require_relative "graph_agent/checkpoint/base_saver"
|
|
20
|
+
require_relative "graph_agent/checkpoint/in_memory_saver"
|
|
21
|
+
require_relative "graph_agent/graph/node"
|
|
22
|
+
require_relative "graph_agent/graph/edge"
|
|
23
|
+
require_relative "graph_agent/graph/conditional_edge"
|
|
24
|
+
require_relative "graph_agent/graph/state_graph"
|
|
25
|
+
require_relative "graph_agent/graph/compiled_state_graph"
|
|
26
|
+
require_relative "graph_agent/graph/message_graph"
|
|
27
|
+
|
|
28
|
+
module GraphAgent
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: graph-agent
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- GraphAgent Contributors
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: concurrent-ruby
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.12'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.12'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rubocop
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.50'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.50'
|
|
68
|
+
description: Ruby port of LangGraph - build stateful, multi-actor applications with
|
|
69
|
+
LLMs using a graph-based workflow engine with Pregel execution model
|
|
70
|
+
email:
|
|
71
|
+
- richard.sun@ai-firstly.com
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".github/workflows/ci.yml"
|
|
77
|
+
- ".github/workflows/release.yml"
|
|
78
|
+
- ".gitignore"
|
|
79
|
+
- ".rspec"
|
|
80
|
+
- ".rubocop.yml"
|
|
81
|
+
- CHANGELOG.md
|
|
82
|
+
- CLAUDE.md
|
|
83
|
+
- Gemfile
|
|
84
|
+
- Gemfile.lock
|
|
85
|
+
- LICENSE
|
|
86
|
+
- Makefile
|
|
87
|
+
- README.md
|
|
88
|
+
- Rakefile
|
|
89
|
+
- docs/README.md
|
|
90
|
+
- docs/api_reference.md
|
|
91
|
+
- docs/concepts.md
|
|
92
|
+
- docs/edges.md
|
|
93
|
+
- docs/error_handling.md
|
|
94
|
+
- docs/human_in_the_loop.md
|
|
95
|
+
- docs/persistence.md
|
|
96
|
+
- docs/quickstart.md
|
|
97
|
+
- docs/send_and_command.md
|
|
98
|
+
- docs/state.md
|
|
99
|
+
- docs/streaming.md
|
|
100
|
+
- graph-agent.gemspec
|
|
101
|
+
- lib/graph_agent.rb
|
|
102
|
+
- lib/graph_agent/channels/base_channel.rb
|
|
103
|
+
- lib/graph_agent/channels/binary_operator_aggregate.rb
|
|
104
|
+
- lib/graph_agent/channels/ephemeral_value.rb
|
|
105
|
+
- lib/graph_agent/channels/last_value.rb
|
|
106
|
+
- lib/graph_agent/channels/topic.rb
|
|
107
|
+
- lib/graph_agent/checkpoint/base_saver.rb
|
|
108
|
+
- lib/graph_agent/checkpoint/in_memory_saver.rb
|
|
109
|
+
- lib/graph_agent/constants.rb
|
|
110
|
+
- lib/graph_agent/errors.rb
|
|
111
|
+
- lib/graph_agent/graph/compiled_state_graph.rb
|
|
112
|
+
- lib/graph_agent/graph/conditional_edge.rb
|
|
113
|
+
- lib/graph_agent/graph/edge.rb
|
|
114
|
+
- lib/graph_agent/graph/mermaid_visualizer.rb
|
|
115
|
+
- lib/graph_agent/graph/message_graph.rb
|
|
116
|
+
- lib/graph_agent/graph/node.rb
|
|
117
|
+
- lib/graph_agent/graph/state_graph.rb
|
|
118
|
+
- lib/graph_agent/reducers.rb
|
|
119
|
+
- lib/graph_agent/state/schema.rb
|
|
120
|
+
- lib/graph_agent/types/cache_policy.rb
|
|
121
|
+
- lib/graph_agent/types/command.rb
|
|
122
|
+
- lib/graph_agent/types/interrupt.rb
|
|
123
|
+
- lib/graph_agent/types/retry_policy.rb
|
|
124
|
+
- lib/graph_agent/types/send.rb
|
|
125
|
+
- lib/graph_agent/types/state_snapshot.rb
|
|
126
|
+
- lib/graph_agent/version.rb
|
|
127
|
+
homepage: https://github.com/ai-firstly/graph-agent
|
|
128
|
+
licenses:
|
|
129
|
+
- MIT
|
|
130
|
+
metadata:
|
|
131
|
+
allowed_push_host: https://rubygems.org
|
|
132
|
+
homepage_uri: https://github.com/ai-firstly/graph-agent
|
|
133
|
+
source_code_uri: https://github.com/ai-firstly/graph-agent
|
|
134
|
+
changelog_uri: https://github.com/ai-firstly/graph-agent/blob/main/CHANGELOG.md
|
|
135
|
+
documentation_uri: https://rubydoc.info/gems/graph-agent
|
|
136
|
+
bug_tracker_uri: https://github.com/ai-firstly/graph-agent/issues
|
|
137
|
+
rubygems_mfa_required: 'true'
|
|
138
|
+
rdoc_options: []
|
|
139
|
+
require_paths:
|
|
140
|
+
- lib
|
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 3.1.0
|
|
146
|
+
- - "<"
|
|
147
|
+
- !ruby/object:Gem::Version
|
|
148
|
+
version: '5.0'
|
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
|
+
requirements:
|
|
151
|
+
- - ">="
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: '0'
|
|
154
|
+
requirements: []
|
|
155
|
+
rubygems_version: 3.6.9
|
|
156
|
+
specification_version: 4
|
|
157
|
+
summary: A Ruby framework for building stateful, multi-actor agent workflows
|
|
158
|
+
test_files: []
|