langgraphrb_rails 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/README.md +816 -0
- data/Rakefile +23 -0
- data/app/assets/javascripts/langgraphrb_rails.js +153 -0
- data/app/assets/stylesheets/langgraphrb_rails.css +95 -0
- data/lib/generators/langgraph_rb/compatibility.rb +71 -0
- data/lib/generators/langgraph_rb/controller/templates/controller.rb +54 -0
- data/lib/generators/langgraph_rb/controller/templates/view.html.erb +101 -0
- data/lib/generators/langgraph_rb/controller_generator.rb +39 -0
- data/lib/generators/langgraph_rb/graph/templates/graph.rb +68 -0
- data/lib/generators/langgraph_rb/graph_generator.rb +23 -0
- data/lib/generators/langgraph_rb/install/templates/README +45 -0
- data/lib/generators/langgraph_rb/install/templates/example_graph.rb +89 -0
- data/lib/generators/langgraph_rb/install/templates/initializer.rb +35 -0
- data/lib/generators/langgraph_rb/install/templates/langgraph_rb.yml +45 -0
- data/lib/generators/langgraph_rb/install_generator.rb +34 -0
- data/lib/generators/langgraph_rb/job/templates/job.rb +38 -0
- data/lib/generators/langgraph_rb/job_generator.rb +27 -0
- data/lib/generators/langgraph_rb/model/templates/migration.rb +12 -0
- data/lib/generators/langgraph_rb/model/templates/model.rb +15 -0
- data/lib/generators/langgraph_rb/model_generator.rb +34 -0
- data/lib/generators/langgraph_rb/task/templates/task.rake +58 -0
- data/lib/generators/langgraph_rb/task_generator.rb +23 -0
- data/lib/generators/langgraphrb_rails/compatibility.rb +71 -0
- data/lib/generators/langgraphrb_rails/controller/templates/controller.rb +30 -0
- data/lib/generators/langgraphrb_rails/controller/templates/view.html.erb +112 -0
- data/lib/generators/langgraphrb_rails/controller_generator.rb +29 -0
- data/lib/generators/langgraphrb_rails/graph/templates/graph.rb +14 -0
- data/lib/generators/langgraphrb_rails/graph/templates/node.rb +16 -0
- data/lib/generators/langgraphrb_rails/graph_generator.rb +48 -0
- data/lib/generators/langgraphrb_rails/install/templates/config.yml +30 -0
- data/lib/generators/langgraphrb_rails/install/templates/example_graph.rb +44 -0
- data/lib/generators/langgraphrb_rails/install/templates/initializer.rb +27 -0
- data/lib/generators/langgraphrb_rails/install_generator.rb +35 -0
- data/lib/generators/langgraphrb_rails/jobs/templates/run_job.rb +45 -0
- data/lib/generators/langgraphrb_rails/jobs_generator.rb +34 -0
- data/lib/generators/langgraphrb_rails/model/templates/migration.rb +20 -0
- data/lib/generators/langgraphrb_rails/model/templates/model.rb +14 -0
- data/lib/generators/langgraphrb_rails/model_generator.rb +30 -0
- data/lib/generators/langgraphrb_rails/persistence/templates/create_langgraph_runs.rb +18 -0
- data/lib/generators/langgraphrb_rails/persistence/templates/langgraph_run.rb +56 -0
- data/lib/generators/langgraphrb_rails/persistence_generator.rb +28 -0
- data/lib/generators/langgraphrb_rails/task/templates/task.rake +30 -0
- data/lib/generators/langgraphrb_rails/task_generator.rb +17 -0
- data/lib/generators/langgraphrb_rails/tracing/templates/traced.rb +45 -0
- data/lib/generators/langgraphrb_rails/tracing_generator.rb +63 -0
- data/lib/langgraphrb_rails/configuration.rb +47 -0
- data/lib/langgraphrb_rails/engine.rb +20 -0
- data/lib/langgraphrb_rails/helper.rb +141 -0
- data/lib/langgraphrb_rails/middleware/streaming.rb +77 -0
- data/lib/langgraphrb_rails/railtie.rb +55 -0
- data/lib/langgraphrb_rails/stores/active_record.rb +51 -0
- data/lib/langgraphrb_rails/stores/redis.rb +57 -0
- data/lib/langgraphrb_rails/test_helper.rb +126 -0
- data/lib/langgraphrb_rails/version.rb +28 -0
- data/lib/langgraphrb_rails.rb +111 -0
- data/lib/tasks/langgraphrb_rails_tasks.rake +62 -0
- metadata +217 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Nodes::<%= node_class_name(@node_name) %> < LangGraphRB::Node
|
4
|
+
# Include LangSmith tracing if available
|
5
|
+
include LangsmithTraced if defined?(LangsmithTraced)
|
6
|
+
|
7
|
+
def call(context:, state:)
|
8
|
+
# TODO: Implement <%= @node_name %> logic
|
9
|
+
|
10
|
+
# Example:
|
11
|
+
# result = "Processed by <%= @node_name %>"
|
12
|
+
|
13
|
+
# Return the updated state
|
14
|
+
ok state: state.merge("<%= @node_name %>_result" => "Processed")
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require_relative 'compatibility'
|
3
|
+
|
4
|
+
module LanggraphrbRails
|
5
|
+
module Generators
|
6
|
+
class GraphGenerator < Rails::Generators::NamedBase
|
7
|
+
include LanggraphrbRails::Generators::Compatibility
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
class_option :nodes, type: :string, desc: "Comma-separated list of node names to create"
|
11
|
+
|
12
|
+
desc "Creates a new LanggraphRB graph and associated nodes"
|
13
|
+
|
14
|
+
def create_graph_file
|
15
|
+
template "graph.rb", "app/langgraphs/#{file_name}_graph.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_node_files
|
19
|
+
return unless options[:nodes].present?
|
20
|
+
|
21
|
+
nodes = options[:nodes].split(',').map(&:strip)
|
22
|
+
nodes.each do |node_name|
|
23
|
+
@node_name = node_name
|
24
|
+
template "node.rb", "app/langgraphs/nodes/#{node_name}.rb"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def node_class_name(node_name)
|
31
|
+
node_name.camelize
|
32
|
+
end
|
33
|
+
|
34
|
+
def node_connections
|
35
|
+
return [] unless options[:nodes].present?
|
36
|
+
|
37
|
+
nodes = options[:nodes].split(',').map(&:strip)
|
38
|
+
nodes.map.with_index do |node, index|
|
39
|
+
if index == nodes.length - 1
|
40
|
+
"node :#{node}, terminal: true"
|
41
|
+
else
|
42
|
+
"node :#{node}, to: :#{nodes[index + 1]}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
default: &default
|
2
|
+
store:
|
3
|
+
adapter: memory
|
4
|
+
options:
|
5
|
+
ttl: 3600
|
6
|
+
job:
|
7
|
+
queue: langgraphrb
|
8
|
+
max_retries: 3
|
9
|
+
error:
|
10
|
+
policy: retry
|
11
|
+
max_retries: 3
|
12
|
+
|
13
|
+
development:
|
14
|
+
<<: *default
|
15
|
+
store:
|
16
|
+
adapter: memory
|
17
|
+
|
18
|
+
test:
|
19
|
+
<<: *default
|
20
|
+
store:
|
21
|
+
adapter: memory
|
22
|
+
|
23
|
+
production:
|
24
|
+
<<: *default
|
25
|
+
store:
|
26
|
+
adapter: redis
|
27
|
+
options:
|
28
|
+
url: <%= ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') %>
|
29
|
+
namespace: langgraphrb
|
30
|
+
ttl: 86400 # 24 hours
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Example graph demonstrating LanggraphRB integration with Rails
|
4
|
+
class ExampleGraph
|
5
|
+
include LangGraphRB::Graph
|
6
|
+
|
7
|
+
# Define nodes and edges
|
8
|
+
node :start, to: :process
|
9
|
+
node :process, to: :finish
|
10
|
+
node :finish, terminal: true
|
11
|
+
|
12
|
+
# You can also define conditional edges:
|
13
|
+
# node :process do |state|
|
14
|
+
# if state["needs_review"]
|
15
|
+
# :review
|
16
|
+
# else
|
17
|
+
# :finish
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Define your nodes in app/langgraphs/nodes/
|
23
|
+
# Example:
|
24
|
+
#
|
25
|
+
# class Nodes::Start < LangGraphRB::Node
|
26
|
+
# def call(context:, state:)
|
27
|
+
# # Initialize state with input
|
28
|
+
# ok state: state.merge("message" => "Processing started")
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# class Nodes::Process < LangGraphRB::Node
|
33
|
+
# def call(context:, state:)
|
34
|
+
# # Process the input
|
35
|
+
# ok state: state.merge("processed" => true)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# class Nodes::Finish < LangGraphRB::Node
|
40
|
+
# def call(context:, state:)
|
41
|
+
# # Finalize and return result
|
42
|
+
# ok state: state.merge("completed" => true), terminal: true
|
43
|
+
# end
|
44
|
+
# end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# LanggraphrbRails configuration
|
4
|
+
LanggraphrbRails.configure do |config|
|
5
|
+
# Configure the store adapter (memory, redis, active_record)
|
6
|
+
# config.store_adapter = Rails.env.production? ? :redis : :memory
|
7
|
+
|
8
|
+
# Configure the store options
|
9
|
+
# config.store_options = {
|
10
|
+
# ttl: 3600, # TTL in seconds (for Redis and ActiveRecord stores)
|
11
|
+
# namespace: 'langgraphrb' # Namespace for Redis keys
|
12
|
+
# }
|
13
|
+
|
14
|
+
# Configure error handling
|
15
|
+
# config.on_error = :retry # Options: :retry, :fail
|
16
|
+
# config.max_retries = 3
|
17
|
+
|
18
|
+
# Configure job queue
|
19
|
+
# config.job_queue = :langgraphrb
|
20
|
+
end
|
21
|
+
|
22
|
+
# Load environment-specific configuration
|
23
|
+
config_file = Rails.root.join('config', 'langgraphrb_rails.yml')
|
24
|
+
if File.exist?(config_file)
|
25
|
+
config = YAML.load(ERB.new(File.read(config_file)).result)[Rails.env]
|
26
|
+
LanggraphrbRails.configure_from_hash(config) if config
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require_relative 'compatibility'
|
3
|
+
|
4
|
+
module LanggraphrbRails
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include LanggraphrbRails::Generators::Compatibility
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
desc "Creates a LanggraphRB initializer and configuration file for your application"
|
11
|
+
|
12
|
+
def create_initializer_file
|
13
|
+
template "initializer.rb", "config/initializers/langgraphrb_rails.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_config_file
|
17
|
+
template "config.yml", "config/langgraphrb_rails.yml"
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_graphs_directory
|
21
|
+
empty_directory "app/langgraphs"
|
22
|
+
empty_directory "app/langgraphs/nodes"
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_graphs_to_autoload_paths
|
26
|
+
application "config.autoload_paths += %W(#{Rails.root}/app/langgraphs)"
|
27
|
+
application "config.autoload_paths += %W(#{Rails.root}/app/langgraphs/nodes)"
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_example_graph
|
31
|
+
template "example_graph.rb", "app/langgraphs/example_graph.rb"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langgraph
|
4
|
+
class RunJob < ApplicationJob
|
5
|
+
queue_as { LanggraphrbRails.configuration.job_queue || :default }
|
6
|
+
|
7
|
+
# Include LangSmith tracing if available
|
8
|
+
include LangsmithTracedJob if defined?(LangsmithTracedJob)
|
9
|
+
|
10
|
+
# Retry options
|
11
|
+
retry_on StandardError, wait: :exponentially_longer, attempts: 3
|
12
|
+
|
13
|
+
def perform(run_id)
|
14
|
+
# Find the run record
|
15
|
+
run = LanggraphRun.find_by(id: run_id)
|
16
|
+
return unless run
|
17
|
+
|
18
|
+
# Mark as running
|
19
|
+
run.running!
|
20
|
+
|
21
|
+
begin
|
22
|
+
# Resume the graph execution
|
23
|
+
result = LangGraphRB.resume!(run_id)
|
24
|
+
|
25
|
+
# Update the run record with the result
|
26
|
+
if result[:terminal]
|
27
|
+
run.succeeded!(result[:state])
|
28
|
+
else
|
29
|
+
# If not terminal, update state and re-queue
|
30
|
+
run.update(
|
31
|
+
state: result[:state],
|
32
|
+
current_node: result[:current_node]
|
33
|
+
)
|
34
|
+
|
35
|
+
# Re-queue the job to continue processing
|
36
|
+
self.class.perform_later(run_id)
|
37
|
+
end
|
38
|
+
rescue => e
|
39
|
+
# Handle errors
|
40
|
+
run.failed!(e.message)
|
41
|
+
raise # Re-raise to trigger retry mechanism
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require_relative 'compatibility'
|
3
|
+
|
4
|
+
module LanggraphrbRails
|
5
|
+
module Generators
|
6
|
+
class JobsGenerator < Rails::Generators::NamedBase
|
7
|
+
include LanggraphrbRails::Generators::Compatibility
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
desc "Creates background jobs for LanggraphRB graph processing"
|
11
|
+
|
12
|
+
def create_run_job_file
|
13
|
+
template "run_job.rb", "app/jobs/langgraph/run_job.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_jobs_directory
|
17
|
+
empty_directory "app/jobs/langgraph"
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_job_configuration
|
21
|
+
initializer_path = "config/initializers/langgraphrb_rails.rb"
|
22
|
+
|
23
|
+
if File.exist?(Rails.root.join(initializer_path))
|
24
|
+
inject_into_file initializer_path, after: "LanggraphrbRails.configure do |config|\n" do
|
25
|
+
<<-RUBY
|
26
|
+
# Configure job queue for LanggraphRB jobs
|
27
|
+
config.job_queue = :langgraphrb
|
28
|
+
RUBY
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Create<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
|
+
def change
|
3
|
+
create_table :<%= table_name %> do |t|
|
4
|
+
<% attributes.each do |attribute| -%>
|
5
|
+
<% if attribute.type == :references -%>
|
6
|
+
t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
|
7
|
+
<% elsif attribute.type == :json || attribute.type == :jsonb -%>
|
8
|
+
t.<%= attribute.type %> :<%= attribute.name %>, default: {}<%= attribute.inject_options %>
|
9
|
+
<% else -%>
|
10
|
+
t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
|
11
|
+
<% end -%>
|
12
|
+
<% end -%>
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
<% attributes_with_index = attributes.select { |a| a.has_index? || a.reference? } -%>
|
16
|
+
<% attributes_with_index.each do |attribute| -%>
|
17
|
+
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
|
18
|
+
<% end -%>
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= class_name %> < ApplicationRecord
|
4
|
+
# Add validations, associations, and methods as needed
|
5
|
+
<% if attributes.map(&:name).include?('thread_id') -%>
|
6
|
+
validates :thread_id, presence: true, uniqueness: true
|
7
|
+
<% end -%>
|
8
|
+
<% if attributes.map(&:name).include?('state') -%>
|
9
|
+
# Ensure state is always a hash
|
10
|
+
def state
|
11
|
+
super || {}
|
12
|
+
end
|
13
|
+
<% end -%>
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
require 'rails/generators/base'
|
3
|
+
require_relative 'compatibility'
|
4
|
+
|
5
|
+
module LanggraphrbRails
|
6
|
+
module Generators
|
7
|
+
class ModelGenerator < Rails::Generators::NamedBase
|
8
|
+
include Rails::Generators::Migration
|
9
|
+
include LanggraphrbRails::Generators::Compatibility
|
10
|
+
|
11
|
+
source_root File.expand_path('templates', __dir__)
|
12
|
+
|
13
|
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
14
|
+
|
15
|
+
desc "Creates a model for LanggraphRB state persistence"
|
16
|
+
|
17
|
+
def create_model_file
|
18
|
+
template "model.rb", "app/models/#{file_name}.rb"
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_migration_file
|
22
|
+
migration_template "migration.rb", "db/migrate/create_#{table_name}.rb"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.next_migration_number(dirname)
|
26
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateLanggraphRuns < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
|
+
def change
|
3
|
+
create_table :langgraph_runs do |t|
|
4
|
+
t.string :thread_id, null: false, index: true
|
5
|
+
t.string :graph, null: false
|
6
|
+
t.string :current_node
|
7
|
+
t.integer :status, default: 0, null: false
|
8
|
+
t.jsonb :state, default: {}
|
9
|
+
t.jsonb :context, default: {}
|
10
|
+
t.text :error
|
11
|
+
t.datetime :expires_at
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index :langgraph_runs, :status
|
16
|
+
add_index :langgraph_runs, :expires_at
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LanggraphRun < ApplicationRecord
|
4
|
+
# Status enum
|
5
|
+
enum status: {
|
6
|
+
queued: 0,
|
7
|
+
running: 1,
|
8
|
+
succeeded: 2,
|
9
|
+
failed: 3,
|
10
|
+
canceled: 4
|
11
|
+
}
|
12
|
+
|
13
|
+
# Validations
|
14
|
+
validates :thread_id, presence: true
|
15
|
+
validates :graph, presence: true
|
16
|
+
validates :status, presence: true
|
17
|
+
|
18
|
+
# Scopes
|
19
|
+
scope :active, -> { where(status: [:queued, :running]) }
|
20
|
+
scope :completed, -> { where(status: [:succeeded, :failed, :canceled]) }
|
21
|
+
scope :recent, -> { order(created_at: :desc) }
|
22
|
+
|
23
|
+
# Callbacks
|
24
|
+
before_create :set_default_status
|
25
|
+
|
26
|
+
# Methods
|
27
|
+
def running!
|
28
|
+
update(status: :running)
|
29
|
+
end
|
30
|
+
|
31
|
+
def succeeded!(final_state = nil)
|
32
|
+
update(status: :succeeded, state: final_state || state)
|
33
|
+
end
|
34
|
+
|
35
|
+
def failed!(error_message = nil)
|
36
|
+
update(status: :failed, error: error_message)
|
37
|
+
end
|
38
|
+
|
39
|
+
def canceled!
|
40
|
+
update(status: :canceled)
|
41
|
+
end
|
42
|
+
|
43
|
+
def active?
|
44
|
+
queued? || running?
|
45
|
+
end
|
46
|
+
|
47
|
+
def completed?
|
48
|
+
succeeded? || failed? || canceled?
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def set_default_status
|
54
|
+
self.status ||= :queued
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require 'rails/generators/active_record'
|
3
|
+
require_relative 'compatibility'
|
4
|
+
|
5
|
+
module LanggraphrbRails
|
6
|
+
module Generators
|
7
|
+
class PersistenceGenerator < Rails::Generators::Base
|
8
|
+
include Rails::Generators::Migration
|
9
|
+
include LanggraphrbRails::Generators::Compatibility
|
10
|
+
|
11
|
+
source_root File.expand_path('templates', __dir__)
|
12
|
+
|
13
|
+
desc "Creates a LanggraphRun model and migration for persistence"
|
14
|
+
|
15
|
+
def create_model_file
|
16
|
+
template "langgraph_run.rb", "app/models/langgraph_run.rb"
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_migration_file
|
20
|
+
migration_template "create_langgraph_runs.rb", "db/migrate/create_langgraph_runs.rb"
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.next_migration_number(dirname)
|
24
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
namespace :langgraph_<%= file_name %> do
|
2
|
+
desc "Process pending <%= class_name %> graph runs"
|
3
|
+
task process: :environment do
|
4
|
+
# Find queued runs for this graph
|
5
|
+
runs = LanggraphRun.where(graph: "<%= class_name %>Graph", status: :queued)
|
6
|
+
|
7
|
+
puts "Processing #{runs.count} pending <%= class_name %> graph runs..."
|
8
|
+
|
9
|
+
runs.each do |run|
|
10
|
+
puts "Queueing run ##{run.id}..."
|
11
|
+
Langgraph::RunJob.perform_later(run.id)
|
12
|
+
end
|
13
|
+
|
14
|
+
puts "Done."
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Clean up expired <%= class_name %> graph runs"
|
18
|
+
task cleanup: :environment do
|
19
|
+
# Find expired runs
|
20
|
+
expired = LanggraphRun.where(graph: "<%= class_name %>Graph")
|
21
|
+
.where("expires_at < ?", Time.current)
|
22
|
+
|
23
|
+
count = expired.count
|
24
|
+
puts "Cleaning up #{count} expired <%= class_name %> graph runs..."
|
25
|
+
|
26
|
+
expired.destroy_all
|
27
|
+
|
28
|
+
puts "Removed #{count} expired runs."
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require_relative 'compatibility'
|
3
|
+
|
4
|
+
module LanggraphrbRails
|
5
|
+
module Generators
|
6
|
+
class TaskGenerator < Rails::Generators::NamedBase
|
7
|
+
include LanggraphrbRails::Generators::Compatibility
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
desc "Creates rake tasks for LanggraphRB graph processing"
|
11
|
+
|
12
|
+
def create_task_file
|
13
|
+
template "task.rake", "lib/tasks/langgraph_#{file_name}.rake"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Include this concern in your LangGraphRB nodes to enable LangSmith tracing
|
4
|
+
module LangsmithTraced
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Trace a block of code with LangSmith
|
8
|
+
def trace(name:, type: "chain", meta: {})
|
9
|
+
return yield unless defined?(LangsmithrbRails) && LangsmithrbRails.configured?
|
10
|
+
|
11
|
+
tracer = LangsmithrbRails.tracer
|
12
|
+
|
13
|
+
# Create a new trace
|
14
|
+
tracer.trace(name: name, type: type, meta: meta) do |run|
|
15
|
+
# Execute the block and capture the result
|
16
|
+
result = yield
|
17
|
+
|
18
|
+
# Add the output to the trace
|
19
|
+
run.add_output(result)
|
20
|
+
|
21
|
+
# Return the result
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Override the call method to automatically trace node execution
|
27
|
+
def call_with_tracing(context:, state:)
|
28
|
+
return call_without_tracing(context: context, state: state) unless defined?(LangsmithrbRails) && LangsmithrbRails.configured?
|
29
|
+
|
30
|
+
# Get the node name from the class
|
31
|
+
node_name = self.class.name.demodulize.underscore
|
32
|
+
|
33
|
+
# Create a trace for this node
|
34
|
+
trace(name: node_name, type: "node", meta: { context: context, input_state: state }) do
|
35
|
+
# Call the original method
|
36
|
+
call_without_tracing(context: context, state: state)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
included do
|
41
|
+
# Alias the original call method
|
42
|
+
alias_method :call_without_tracing, :call
|
43
|
+
alias_method :call, :call_with_tracing
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require_relative 'compatibility'
|
3
|
+
|
4
|
+
module LanggraphrbRails
|
5
|
+
module Generators
|
6
|
+
class TracingGenerator < Rails::Generators::Base
|
7
|
+
include LanggraphrbRails::Generators::Compatibility
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
desc "Creates LangSmith tracing integration for LanggraphRB"
|
11
|
+
|
12
|
+
def check_langsmithrb_rails
|
13
|
+
unless gem_available?('langsmithrb_rails')
|
14
|
+
say "The langsmithrb_rails gem is required for tracing. Please add it to your Gemfile.", :red
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_concern_file
|
20
|
+
template "tracing/traced.rb", "app/models/concerns/langsmith_traced.rb"
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_initializer
|
24
|
+
if File.exist?('config/initializers/langgraphrb_rails.rb')
|
25
|
+
inject_into_file 'config/initializers/langgraphrb_rails.rb', after: "LanggraphrbRails.configure do |config|\n" do
|
26
|
+
<<-RUBY
|
27
|
+
# Configure LangSmith tracing
|
28
|
+
if defined?(LangsmithrbRails) && ENV['LANGSMITH_API_KEY'].present?
|
29
|
+
config.observers << LangsmithrbRails::Observer.new(
|
30
|
+
api_key: ENV['LANGSMITH_API_KEY'],
|
31
|
+
project_name: ENV.fetch('LANGSMITH_PROJECT', 'langgraph-rails')
|
32
|
+
)
|
33
|
+
end
|
34
|
+
RUBY
|
35
|
+
end
|
36
|
+
else
|
37
|
+
say "Initializer not found. Please run the install generator first.", :red
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_environment_variables
|
42
|
+
append_to_file '.env.sample' do
|
43
|
+
<<-ENV
|
44
|
+
|
45
|
+
# LangSmith Tracing (optional)
|
46
|
+
# LANGSMITH_API_KEY=your_api_key_here
|
47
|
+
# LANGSMITH_PROJECT=your_project_name
|
48
|
+
ENV
|
49
|
+
end unless File.exist?('.env.sample')
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def gem_available?(name)
|
55
|
+
Gem::Specification.find_by_name(name)
|
56
|
+
rescue Gem::LoadError
|
57
|
+
false
|
58
|
+
rescue
|
59
|
+
Gem.available?(name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module LanggraphrbRails
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :store_adapter, :store_options
|
4
|
+
attr_accessor :observers
|
5
|
+
attr_accessor :job_queue, :on_error, :max_retries
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@store_adapter = :memory
|
9
|
+
@store_options = {}
|
10
|
+
@observers = []
|
11
|
+
@job_queue = :default
|
12
|
+
@on_error = :retry
|
13
|
+
@max_retries = 3
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def configuration
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure
|
23
|
+
yield(configuration) if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def configure_store
|
27
|
+
yield(store_config = {}) if block_given?
|
28
|
+
|
29
|
+
configuration.store_adapter = store_config[:adapter] if store_config[:adapter]
|
30
|
+
configuration.store_options = store_config[:options] if store_config[:options]
|
31
|
+
end
|
32
|
+
|
33
|
+
def configure_observers
|
34
|
+
yield(configuration.observers) if block_given?
|
35
|
+
end
|
36
|
+
|
37
|
+
def store
|
38
|
+
case configuration.store_adapter.to_sym
|
39
|
+
when :redis
|
40
|
+
require_relative 'stores/redis'
|
41
|
+
LanggraphrbRails::Stores::RedisStore.new(configuration.store_options)
|
42
|
+
else
|
43
|
+
LangGraphRB::Stores::InMemoryStore.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module LanggraphrbRails
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace LanggraphrbRails
|
4
|
+
|
5
|
+
# Add generators
|
6
|
+
config.app_generators do |g|
|
7
|
+
g.templates.unshift File.expand_path('../../generators/langgraph_rb/templates', __dir__)
|
8
|
+
end
|
9
|
+
|
10
|
+
initializer "langgraphrb_rails.setup" do |app|
|
11
|
+
# Load any initializers or configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
initializer "langgraphrb_rails.helpers" do |app|
|
15
|
+
ActiveSupport.on_load(:action_view) do
|
16
|
+
include LanggraphrbRails::Helper
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|