langsmithrb_rails 0.1.0 → 0.1.1

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -0
  3. data/.rspec_status +82 -0
  4. data/CHANGELOG.md +25 -0
  5. data/Gemfile +20 -0
  6. data/Gemfile.lock +321 -0
  7. data/LICENSE +21 -0
  8. data/README.md +268 -0
  9. data/Rakefile +10 -0
  10. data/langsmithrb_rails-0.1.0.gem +0 -0
  11. data/langsmithrb_rails.gemspec +45 -0
  12. data/lib/generators/langsmithrb_rails/buffer/buffer_generator.rb +94 -0
  13. data/lib/generators/langsmithrb_rails/buffer/templates/create_langsmith_run_buffers.rb +29 -0
  14. data/lib/generators/langsmithrb_rails/buffer/templates/flush_buffer_job.rb +40 -0
  15. data/lib/generators/langsmithrb_rails/buffer/templates/langsmith.rake +71 -0
  16. data/lib/generators/langsmithrb_rails/buffer/templates/langsmith_run_buffer.rb +70 -0
  17. data/lib/generators/langsmithrb_rails/buffer/templates/migration.rb +28 -0
  18. data/lib/generators/langsmithrb_rails/ci/ci_generator.rb +37 -0
  19. data/lib/generators/langsmithrb_rails/ci/templates/langsmith-evals.yml +85 -0
  20. data/lib/generators/langsmithrb_rails/ci/templates/langsmith_export_summary.rb +81 -0
  21. data/lib/generators/langsmithrb_rails/demo/demo_generator.rb +81 -0
  22. data/lib/generators/langsmithrb_rails/demo/templates/chat_controller.js +88 -0
  23. data/lib/generators/langsmithrb_rails/demo/templates/chat_controller.rb +58 -0
  24. data/lib/generators/langsmithrb_rails/demo/templates/chat_message.rb +24 -0
  25. data/lib/generators/langsmithrb_rails/demo/templates/create_chat_messages.rb +19 -0
  26. data/lib/generators/langsmithrb_rails/demo/templates/index.html.erb +180 -0
  27. data/lib/generators/langsmithrb_rails/demo/templates/llm_service.rb +165 -0
  28. data/lib/generators/langsmithrb_rails/evals/evals_generator.rb +52 -0
  29. data/lib/generators/langsmithrb_rails/evals/templates/checks/correctness.rb +71 -0
  30. data/lib/generators/langsmithrb_rails/evals/templates/checks/llm_graded.rb +137 -0
  31. data/lib/generators/langsmithrb_rails/evals/templates/datasets/sample.yml +60 -0
  32. data/lib/generators/langsmithrb_rails/evals/templates/langsmith_evals.rake +255 -0
  33. data/lib/generators/langsmithrb_rails/evals/templates/targets/http.rb +120 -0
  34. data/lib/generators/langsmithrb_rails/evals/templates/targets/ruby.rb +136 -0
  35. data/lib/generators/langsmithrb_rails/install/install_generator.rb +35 -0
  36. data/lib/generators/langsmithrb_rails/install/templates/config.yml +45 -0
  37. data/lib/generators/langsmithrb_rails/install/templates/initializer.rb +34 -0
  38. data/lib/generators/langsmithrb_rails/privacy/privacy_generator.rb +39 -0
  39. data/lib/generators/langsmithrb_rails/privacy/templates/custom_redactor.rb +132 -0
  40. data/lib/generators/langsmithrb_rails/privacy/templates/privacy.yml +88 -0
  41. data/lib/generators/langsmithrb_rails/privacy/templates/privacy_initializer.rb +41 -0
  42. data/lib/generators/langsmithrb_rails/tracing/templates/langsmith_traced.rb +146 -0
  43. data/lib/generators/langsmithrb_rails/tracing/templates/langsmith_traced_job.rb +151 -0
  44. data/lib/generators/langsmithrb_rails/tracing/templates/request_tracing.rb +117 -0
  45. data/lib/generators/langsmithrb_rails/tracing/tracing_generator.rb +78 -0
  46. data/lib/langsmithrb_rails/client.rb +77 -0
  47. data/lib/langsmithrb_rails/config.rb +72 -0
  48. data/lib/langsmithrb_rails/generators/langsmithrb_rails/langsmith_generator.rb +61 -0
  49. data/lib/langsmithrb_rails/generators/langsmithrb_rails/templates/langsmith_initializer.rb +22 -0
  50. data/lib/langsmithrb_rails/langsmith.rb +35 -0
  51. data/lib/langsmithrb_rails/railtie.rb +33 -0
  52. data/lib/langsmithrb_rails/redactor.rb +76 -0
  53. data/lib/langsmithrb_rails/version.rb +5 -0
  54. data/lib/langsmithrb_rails.rb +31 -0
  55. metadata +59 -6
data/README.md ADDED
@@ -0,0 +1,268 @@
1
+ # Langsmith Rails
2
+
3
+ LangsmithrbRails provides seamless integration with [LangSmith](https://smith.langchain.com/) for your Rails applications. LangSmith is a platform for debugging, testing, evaluating, and monitoring LLM applications.
4
+
5
+ This gem makes it easy to add LangSmith tracing to your Rails application, allowing you to monitor and debug your LLM operations. It provides a comprehensive set of features including request tracing, PII redaction, local buffering, evaluation frameworks, and more.
6
+
7
+ ## Features
8
+
9
+ - 🔍 **Request Tracing**: Automatically trace HTTP requests with middleware
10
+ - 🧩 **Service & Job Tracing**: Trace your services and background jobs
11
+ - 🔒 **PII Redaction**: Automatically redact sensitive information
12
+ - 💾 **Local Buffering**: Store traces locally and send them in batches
13
+ - 📊 **Evaluations**: Evaluate LLM responses with customizable criteria
14
+ - 🔄 **CI Integration**: Run evaluations in your CI pipeline
15
+ - 🚀 **Demo Application**: Get started quickly with a sample application
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'langsmithrb_rails'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ ```bash
28
+ $ bundle install
29
+ ```
30
+
31
+ Or install it yourself as:
32
+
33
+ ```bash
34
+ $ gem install langsmithrb_rails
35
+ ```
36
+
37
+ ## Setup
38
+
39
+ After installing the gem, run the install generator to set up LangSmith in your Rails application:
40
+
41
+ ```bash
42
+ $ rails generate langsmithrb_rails:install
43
+ ```
44
+
45
+ This will:
46
+
47
+ 1. Create a LangSmith initializer at `config/initializers/langsmith.rb`
48
+ 2. Generate a YAML configuration file at `config/langsmith.yml`
49
+ 3. Display post-install instructions
50
+
51
+ ## Configuration
52
+
53
+ LangSmith is configured through environment variables and a YAML configuration file:
54
+
55
+ ```
56
+ # Required
57
+ LANGSMITH_API_KEY=your_api_key
58
+
59
+ # Optional
60
+ LANGSMITH_PROJECT=your_project_name
61
+ LANGSMITH_SAMPLING=1.0 # Sampling rate (0.0 to 1.0)
62
+ LANGSMITH_ENABLED=true # Enable/disable tracing
63
+ ```
64
+
65
+ You can get your API key from [LangSmith](https://smith.langchain.com/).
66
+
67
+ The YAML configuration file (`config/langsmith.yml`) allows for environment-specific settings:
68
+
69
+ ```yaml
70
+ default: &default
71
+ api_key: <%= ENV.fetch("LANGSMITH_API_KEY", nil) %>
72
+ project_name: <%= ENV.fetch("LANGSMITH_PROJECT", nil) %>
73
+ sampling_rate: <%= ENV.fetch("LANGSMITH_SAMPLING", 1.0).to_f %>
74
+ enabled: <%= ENV.fetch("LANGSMITH_ENABLED", "true") == "true" %>
75
+ redact_pii: true
76
+ timeout: 5
77
+
78
+ development:
79
+ <<: *default
80
+
81
+ test:
82
+ <<: *default
83
+
84
+ production:
85
+ <<: *default
86
+ sampling_rate: <%= ENV.fetch("LANGSMITH_SAMPLING", 0.1).to_f %>
87
+ ```
88
+
89
+ You can also configure LangSmith programmatically in your initializer:
90
+
91
+ ```ruby
92
+ LangsmithrbRails.configure do |config|
93
+ config.enabled = true # Enable LangSmith tracing
94
+ config.api_key = "your_api_key" # Your LangSmith API key
95
+ config.project_name = "your_project" # Optional project name
96
+ config.sampling_rate = 0.1 # Sample 10% of traces
97
+ config.redact_pii = true # Redact PII from traces
98
+ end
99
+ ```
100
+
101
+ ## Generators
102
+
103
+ LangsmithrbRails provides several generators to help you set up different features:
104
+
105
+ ### Install Generator
106
+
107
+ ```bash
108
+ $ rails generate langsmithrb_rails:install
109
+ ```
110
+
111
+ Sets up the basic configuration for LangSmith.
112
+
113
+ ### Tracing Generator
114
+
115
+ ```bash
116
+ $ rails generate langsmithrb_rails:tracing
117
+ ```
118
+
119
+ Adds middleware and concerns for request-level tracing:
120
+
121
+ - Creates a middleware for tracing HTTP requests
122
+ - Adds a concern for tracing services
123
+ - Adds a concern for tracing background jobs
124
+ - Updates your application configuration
125
+
126
+ ### Buffer Generator
127
+
128
+ ```bash
129
+ $ rails generate langsmithrb_rails:buffer
130
+ ```
131
+
132
+ Sets up local buffering for traces:
133
+
134
+ - Creates a migration for the buffer table
135
+ - Adds a model for the buffer
136
+ - Adds a job for flushing the buffer
137
+ - Adds rake tasks for manual flushing
138
+
139
+ ### Evals Generator
140
+
141
+ ```bash
142
+ $ rails generate langsmithrb_rails:evals
143
+ ```
144
+
145
+ Adds an evaluation framework for LLM responses:
146
+
147
+ - Creates sample datasets for evaluation
148
+ - Adds evaluation checks (correctness, LLM-graded)
149
+ - Adds evaluation targets (HTTP, Ruby)
150
+ - Adds rake tasks for running evaluations
151
+
152
+ ### CI Generator
153
+
154
+ ```bash
155
+ $ rails generate langsmithrb_rails:ci
156
+ ```
157
+
158
+ Sets up CI integration for LangSmith evaluations:
159
+
160
+ - Creates a GitHub Actions workflow
161
+ - Adds a script for generating evaluation summaries
162
+ - Configures PR comments with evaluation results
163
+
164
+ ### Privacy Generator
165
+
166
+ ```bash
167
+ $ rails generate langsmithrb_rails:privacy
168
+ ```
169
+
170
+ Adds privacy features for LangSmith traces:
171
+
172
+ - Creates a custom redactor for PII
173
+ - Adds a privacy initializer
174
+ - Generates a privacy configuration file
175
+
176
+ ### Demo Generator
177
+
178
+ ```bash
179
+ $ rails generate langsmithrb_rails:demo
180
+ ```
181
+
182
+ Adds a demo application with LangSmith tracing:
183
+
184
+ - Creates a chat interface with LLM integration
185
+ - Adds a service for interacting with LLMs
186
+ - Configures tracing for all LLM operations
187
+
188
+ ## Usage
189
+
190
+ ### Basic Tracing
191
+
192
+ Once configured, you can trace your code using the `LangsmithTraced` concern:
193
+
194
+ ```ruby
195
+ class MyService
196
+ include LangsmithTraced
197
+
198
+ def process(input)
199
+ langsmith_trace("my_operation", inputs: { input: input }) do |run|
200
+ # Your code here
201
+ result = do_something(input)
202
+
203
+ # Record the output
204
+ run.outputs = { result: result }
205
+
206
+ result
207
+ end
208
+ end
209
+ end
210
+ ```
211
+
212
+ ### Tracing Background Jobs
213
+
214
+ For background jobs, use the `LangsmithTracedJob` concern:
215
+
216
+ ```ruby
217
+ class MyJob < ApplicationJob
218
+ include LangsmithTracedJob
219
+
220
+ def perform(args)
221
+ # Job is automatically traced
222
+ # ...
223
+ end
224
+ end
225
+ ```
226
+
227
+ ### Local Buffering
228
+
229
+ If you've set up buffering, traces will be stored locally and sent in batches. You can manually flush the buffer:
230
+
231
+ ```bash
232
+ $ rails langsmith:flush
233
+ ```
234
+
235
+ ### Running Evaluations
236
+
237
+ To run an evaluation:
238
+
239
+ ```bash
240
+ $ rails langsmith:eval[sample,http,my_experiment]
241
+ ```
242
+
243
+ Where:
244
+ - `sample` is the dataset name
245
+ - `http` is the target name
246
+ - `my_experiment` is the experiment name
247
+
248
+ ### Comparing Experiments
249
+
250
+ To compare two experiments:
251
+
252
+ ```bash
253
+ $ rails langsmith:compare[exp_a,exp_b]
254
+ ```
255
+
256
+ ## Development
257
+
258
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
259
+
260
+ To install this gem onto your local machine, run `bundle exec rake install`.
261
+
262
+ ## Contributing
263
+
264
+ Bug reports and pull requests are welcome on GitHub at https://github.com/protocolgrid/langsmithrb_rails.
265
+
266
+ ## License
267
+
268
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: [:spec, :rubocop]
Binary file
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/langsmithrb_rails/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "langsmithrb_rails"
7
+ spec.version = LangsmithrbRails::VERSION
8
+ spec.authors = ["Protocol Grid"]
9
+ spec.email = ["info@protocolgrid.com"]
10
+
11
+ spec.summary = "Rails integration for LangSmith tracing and monitoring"
12
+ spec.description = "Seamlessly integrate LangSmith tracing and monitoring into your Rails applications"
13
+ spec.homepage = "https://github.com/cdaviis/langsmithrb_rails"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/cdaviis/langsmithrb_rails"
19
+ spec.metadata["changelog_uri"] = "https://github.com/cdaviis/langsmithrb_rails/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Dependencies
33
+ spec.add_dependency "langsmithrb", "~> 0.1.0"
34
+ spec.add_dependency "rails", ">= 6.0.0"
35
+
36
+ # Development dependencies
37
+ spec.add_development_dependency "rspec", "~> 3.0"
38
+ spec.add_development_dependency "rspec-rails", "~> 6.0"
39
+ spec.add_development_dependency "generator_spec", "~> 0.9"
40
+ spec.add_development_dependency "yard", "~> 0.9"
41
+ spec.add_development_dependency "rubocop", "~> 1.50"
42
+ spec.add_development_dependency "rubocop-rspec", "~> 2.22"
43
+ spec.add_development_dependency "rubocop-rails", "~> 2.19"
44
+ spec.add_development_dependency "simplecov", "~> 0.22.0"
45
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LangsmithrbRails
4
+ module Generators
5
+ # Generator for adding LangSmith buffer to Rails applications
6
+ class BufferGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ desc "Adds LangSmith buffer to your Rails application"
10
+
11
+ def create_migration
12
+ timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
13
+ template "migration.rb", "db/migrate/#{timestamp}_create_langsmith_run_buffers.rb"
14
+ end
15
+
16
+ def create_model
17
+ template "langsmith_run_buffer.rb", "app/models/langsmith_run_buffer.rb"
18
+ end
19
+
20
+ def create_job
21
+ template "flush_buffer_job.rb", "app/jobs/langsmith/flush_buffer_job.rb"
22
+ end
23
+
24
+ def create_rake_task
25
+ template "langsmith.rake", "lib/tasks/langsmith.rake"
26
+ end
27
+
28
+ def update_config
29
+ initializer_path = "config/initializers/langsmith.rb"
30
+
31
+ if File.exist?(initializer_path)
32
+ buffer_config = "\n# Enable buffer for LangSmith traces\nLangsmithrbRails::Config.set(:use_buffer, true)\n"
33
+
34
+ # Check if buffer config is already present
35
+ if File.read(initializer_path).include?("use_buffer")
36
+ say_status :skip, "Buffer config already present in initializer", :yellow
37
+ else
38
+ append_to_file initializer_path, buffer_config
39
+ say_status :append, "Added buffer config to initializer", :green
40
+ end
41
+ else
42
+ say_status :error, "Could not find langsmith initializer", :red
43
+ say_status :create, "Creating initializer with buffer config", :green
44
+
45
+ create_file initializer_path do
46
+ <<~RUBY
47
+ # frozen_string_literal: true
48
+
49
+ # LangSmith Rails Initializer
50
+
51
+ # Configure LangSmith Rails
52
+ LangsmithrbRails.configure do |config|
53
+ # Whether LangSmith tracing is enabled
54
+ config.enabled = ENV["LANGSMITH_API_KEY"].present?
55
+
56
+ # LangSmith API key
57
+ config.api_key = ENV["LANGSMITH_API_KEY"]
58
+
59
+ # LangSmith project name
60
+ config.project_name = ENV["LANGSMITH_PROJECT"]
61
+ end
62
+
63
+ # Enable buffer for LangSmith traces
64
+ LangsmithrbRails::Config.set(:use_buffer, true)
65
+ RUBY
66
+ end
67
+ end
68
+ end
69
+
70
+ def create_directories
71
+ empty_directory "app/jobs/langsmith"
72
+ end
73
+
74
+ def display_post_install_message
75
+ say "\n"
76
+ say "LangSmith buffer has been added to your Rails application! 🎉", :green
77
+ say "\n"
78
+ say "Next steps:", :yellow
79
+ say " 1. Run the migration to create the buffer table:", :yellow
80
+ say " bin/rails db:migrate", :yellow
81
+ say " 2. Set up a job to flush the buffer periodically:", :yellow
82
+ say " # In config/sidekiq.yml or equivalent", :yellow
83
+ say " :schedule:", :yellow
84
+ say " langsmith_flush_buffer:", :yellow
85
+ say " cron: '*/5 * * * *' # Every 5 minutes", :yellow
86
+ say " class: LangsmithFlushBufferJob", :yellow
87
+ say "\n"
88
+ say "You can also flush the buffer manually:", :yellow
89
+ say " bin/rails langsmith:flush", :yellow
90
+ say "\n"
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration for creating the langsmith_run_buffers table
4
+ class CreateLangsmithRunBuffers < ActiveRecord::Migration[7.0]
5
+ def change
6
+ create_table :langsmith_run_buffers do |t|
7
+ t.string :name, null: false
8
+ t.string :run_type, null: false
9
+ t.string :status, null: false, default: "pending"
10
+ t.string :request_id
11
+ t.string :user_ref
12
+ t.string :run_id
13
+ t.string :parent_run_id
14
+ t.datetime :started_at
15
+ t.datetime :ended_at
16
+ t.json :meta, default: {}
17
+ t.json :payload, null: false
18
+ t.text :error
19
+ t.integer :retry_count, default: 0
20
+
21
+ t.timestamps
22
+ end
23
+
24
+ add_index :langsmith_run_buffers, :status
25
+ add_index :langsmith_run_buffers, :run_id
26
+ add_index :langsmith_run_buffers, :request_id
27
+ add_index :langsmith_run_buffers, :retry_count
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langsmith
4
+ # Job for flushing the LangSmith buffer
5
+ class FlushBufferJob < ApplicationJob
6
+ queue_as :langsmith
7
+
8
+ # Maximum number of traces to process in a single job
9
+ BATCH_SIZE = 100
10
+
11
+ # Process the job
12
+ # @param retry_failed [Boolean] Whether to retry failed traces
13
+ def perform(retry_failed: false)
14
+ # Get pending traces
15
+ traces = LangsmithRunBuffer.pending.limit(BATCH_SIZE)
16
+ process_traces(traces)
17
+
18
+ # Get failed traces if requested
19
+ if retry_failed
20
+ traces = LangsmithRunBuffer.ready_to_retry.limit(BATCH_SIZE)
21
+ process_traces(traces)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # Process a batch of traces
28
+ # @param traces [ActiveRecord::Relation] Traces to process
29
+ def process_traces(traces)
30
+ traces.find_each do |trace|
31
+ begin
32
+ trace.send_to_langsmith
33
+ rescue => e
34
+ trace.mark_failed(e.message)
35
+ Rails.logger.error("Failed to send trace #{trace.id}: #{e.message}")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :langsmith do
4
+ desc "Flush the LangSmith buffer"
5
+ task flush: :environment do
6
+ puts "Flushing LangSmith buffer..."
7
+
8
+ # Get counts before
9
+ pending_count = LangsmithRunBuffer.pending.count
10
+ failed_count = LangsmithRunBuffer.ready_to_retry.count
11
+
12
+ # Process pending traces
13
+ Langsmith::FlushBufferJob.new.perform(retry_failed: true)
14
+
15
+ # Get counts after
16
+ pending_after = LangsmithRunBuffer.pending.count
17
+ failed_after = LangsmithRunBuffer.ready_to_retry.count
18
+ sent_count = pending_count + failed_count - pending_after - failed_after
19
+
20
+ puts "Sent #{sent_count} traces to LangSmith"
21
+ puts "#{pending_after} traces still pending"
22
+ puts "#{failed_after} traces failed"
23
+ end
24
+
25
+ desc "Replay traces from a file"
26
+ task :replay, [:file] => :environment do |_t, args|
27
+ file_path = args[:file]
28
+
29
+ unless file_path && File.exist?(file_path)
30
+ puts "Error: File not found or not specified"
31
+ puts "Usage: rake langsmith:replay[path/to/file.json]"
32
+ exit 1
33
+ end
34
+
35
+ puts "Replaying traces from #{file_path}..."
36
+
37
+ # Load the file
38
+ traces = JSON.parse(File.read(file_path))
39
+
40
+ # Create buffer entries
41
+ traces.each do |trace|
42
+ LangsmithRunBuffer.create!(
43
+ name: trace["name"] || "replayed_trace",
44
+ run_type: trace["run_type"] || "llm",
45
+ status: "pending",
46
+ request_id: trace["request_id"],
47
+ user_ref: trace["user_ref"],
48
+ run_id: trace["run_id"],
49
+ parent_run_id: trace["parent_run_id"],
50
+ started_at: trace["started_at"],
51
+ ended_at: trace["ended_at"],
52
+ meta: trace["meta"] || {},
53
+ payload: trace,
54
+ error: nil,
55
+ retry_count: 0
56
+ )
57
+ end
58
+
59
+ puts "Created #{traces.size} buffer entries"
60
+ puts "Run rake langsmith:flush to send them to LangSmith"
61
+ end
62
+
63
+ desc "Clean up old sent traces"
64
+ task cleanup: :environment do
65
+ # Delete traces that were sent more than 7 days ago
66
+ cutoff_date = 7.days.ago
67
+ count = LangsmithRunBuffer.sent.where("updated_at < ?", cutoff_date).delete_all
68
+
69
+ puts "Deleted #{count} old traces from the buffer"
70
+ end
71
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Model for buffering LangSmith traces
4
+ class LangsmithRunBuffer < ApplicationRecord
5
+ # Validations
6
+ validates :name, presence: true
7
+ validates :run_type, presence: true
8
+ validates :status, presence: true
9
+
10
+ # Scopes
11
+ scope :pending, -> { where(status: "pending") }
12
+ scope :failed, -> { where(status: "failed") }
13
+ scope :sent, -> { where(status: "sent") }
14
+ scope :ready_to_retry, -> { where(status: "failed").where("retry_count < ?", 5) }
15
+
16
+ # Send the trace to LangSmith
17
+ # @return [Boolean] Whether the trace was sent successfully
18
+ def send_to_langsmith
19
+ client = LangsmithrbRails::Client.new
20
+
21
+ if run_id.present?
22
+ # This is an update operation
23
+ response = client.update_run(run_id, payload)
24
+ else
25
+ # This is a create operation
26
+ response = client.create_run(payload)
27
+
28
+ # Store the run ID if available
29
+ if response&.dig(:status) == 200 && response&.dig(:body, "id").present?
30
+ update(run_id: response.dig(:body, "id"))
31
+ end
32
+ end
33
+
34
+ if response&.dig(:status) == 200
35
+ update(status: "sent")
36
+ true
37
+ else
38
+ increment!(:retry_count)
39
+ update(
40
+ status: "failed",
41
+ error: response&.dig(:error) || "Unknown error"
42
+ )
43
+ false
44
+ end
45
+ rescue => e
46
+ increment!(:retry_count)
47
+ update(
48
+ status: "failed",
49
+ error: e.message
50
+ )
51
+ false
52
+ end
53
+
54
+ # Mark the trace as failed
55
+ # @param error [String] Error message
56
+ # @return [Boolean] Whether the update was successful
57
+ def mark_failed(error)
58
+ increment!(:retry_count)
59
+ update(
60
+ status: "failed",
61
+ error: error
62
+ )
63
+ end
64
+
65
+ # Mark the trace as sent
66
+ # @return [Boolean] Whether the update was successful
67
+ def mark_sent
68
+ update(status: "sent")
69
+ end
70
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateLangsmithRunBuffers < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :langsmith_run_buffers do |t|
6
+ t.string :name, null: false
7
+ t.string :run_type, null: false
8
+ t.string :status, null: false, default: "pending"
9
+ t.string :request_id
10
+ t.string :user_ref
11
+ t.string :run_id
12
+ t.string :parent_run_id
13
+ t.datetime :started_at
14
+ t.datetime :ended_at
15
+ t.jsonb :meta
16
+ t.jsonb :payload
17
+ t.text :error
18
+ t.integer :retry_count, null: false, default: 0
19
+
20
+ t.timestamps
21
+ end
22
+
23
+ add_index :langsmith_run_buffers, :status
24
+ add_index :langsmith_run_buffers, :request_id
25
+ add_index :langsmith_run_buffers, :run_id
26
+ add_index :langsmith_run_buffers, :parent_run_id
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LangsmithrbRails
4
+ module Generators
5
+ # Generator for adding CI integration for LangSmith evaluations
6
+ class CiGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ desc "Adds CI integration for LangSmith evaluations"
10
+
11
+ def create_github_workflow
12
+ empty_directory ".github/workflows"
13
+ template "langsmith-evals.yml", ".github/workflows/langsmith-evals.yml"
14
+ end
15
+
16
+ def create_ci_script
17
+ empty_directory "script/ci"
18
+ template "langsmith_export_summary.rb", "script/ci/langsmith_export_summary.rb"
19
+ chmod "script/ci/langsmith_export_summary.rb", 0755
20
+ end
21
+
22
+ def display_post_install_message
23
+ say "\n"
24
+ say "LangSmith CI integration has been added to your Rails application! 🎉", :green
25
+ say "\n"
26
+ say "This adds:", :yellow
27
+ say " 1. GitHub Actions workflow for running evaluations", :yellow
28
+ say " 2. Script for generating evaluation summaries for PR comments", :yellow
29
+ say "\n"
30
+ say "To customize the CI configuration:", :yellow
31
+ say " 1. Edit .github/workflows/langsmith-evals.yml", :yellow
32
+ say " 2. Configure the evaluation dataset and target in the workflow", :yellow
33
+ say "\n"
34
+ end
35
+ end
36
+ end
37
+ end