langsmithrb_rails 0.1.0 โ†’ 0.3.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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -0
  3. data/.rspec_status +161 -0
  4. data/CHANGELOG.md +38 -0
  5. data/Gemfile +20 -0
  6. data/Gemfile.lock +321 -0
  7. data/LICENSE +21 -0
  8. data/README.md +421 -0
  9. data/Rakefile +10 -0
  10. data/langsmithrb_rails-0.1.0.gem +0 -0
  11. data/langsmithrb_rails-0.1.1.gem +0 -0
  12. data/langsmithrb_rails.gemspec +45 -0
  13. data/lib/generators/langsmithrb_rails/buffer/buffer_generator.rb +94 -0
  14. data/lib/generators/langsmithrb_rails/buffer/templates/create_langsmith_run_buffers.rb +29 -0
  15. data/lib/generators/langsmithrb_rails/buffer/templates/flush_buffer_job.rb +40 -0
  16. data/lib/generators/langsmithrb_rails/buffer/templates/langsmith.rake +71 -0
  17. data/lib/generators/langsmithrb_rails/buffer/templates/langsmith_run_buffer.rb +70 -0
  18. data/lib/generators/langsmithrb_rails/buffer/templates/migration.rb +28 -0
  19. data/lib/generators/langsmithrb_rails/ci/ci_generator.rb +37 -0
  20. data/lib/generators/langsmithrb_rails/ci/templates/langsmith-evals.yml +85 -0
  21. data/lib/generators/langsmithrb_rails/ci/templates/langsmith_export_summary.rb +81 -0
  22. data/lib/generators/langsmithrb_rails/demo/demo_generator.rb +81 -0
  23. data/lib/generators/langsmithrb_rails/demo/templates/chat_controller.js +88 -0
  24. data/lib/generators/langsmithrb_rails/demo/templates/chat_controller.rb +58 -0
  25. data/lib/generators/langsmithrb_rails/demo/templates/chat_message.rb +24 -0
  26. data/lib/generators/langsmithrb_rails/demo/templates/create_chat_messages.rb +19 -0
  27. data/lib/generators/langsmithrb_rails/demo/templates/index.html.erb +180 -0
  28. data/lib/generators/langsmithrb_rails/demo/templates/llm_service.rb +165 -0
  29. data/lib/generators/langsmithrb_rails/evals/evals_generator.rb +52 -0
  30. data/lib/generators/langsmithrb_rails/evals/templates/checks/correctness.rb +71 -0
  31. data/lib/generators/langsmithrb_rails/evals/templates/checks/llm_graded.rb +137 -0
  32. data/lib/generators/langsmithrb_rails/evals/templates/datasets/sample.yml +60 -0
  33. data/lib/generators/langsmithrb_rails/evals/templates/langsmith_evals.rake +255 -0
  34. data/lib/generators/langsmithrb_rails/evals/templates/targets/http.rb +120 -0
  35. data/lib/generators/langsmithrb_rails/evals/templates/targets/ruby.rb +136 -0
  36. data/lib/generators/langsmithrb_rails/install/install_generator.rb +35 -0
  37. data/lib/generators/langsmithrb_rails/install/templates/config.yml +45 -0
  38. data/lib/generators/langsmithrb_rails/install/templates/initializer.rb +34 -0
  39. data/lib/generators/langsmithrb_rails/privacy/privacy_generator.rb +39 -0
  40. data/lib/generators/langsmithrb_rails/privacy/templates/custom_redactor.rb +132 -0
  41. data/lib/generators/langsmithrb_rails/privacy/templates/privacy.yml +88 -0
  42. data/lib/generators/langsmithrb_rails/privacy/templates/privacy_initializer.rb +41 -0
  43. data/lib/generators/langsmithrb_rails/tracing/templates/langsmith_traced.rb +146 -0
  44. data/lib/generators/langsmithrb_rails/tracing/templates/langsmith_traced_job.rb +151 -0
  45. data/lib/generators/langsmithrb_rails/tracing/templates/request_tracing.rb +117 -0
  46. data/lib/generators/langsmithrb_rails/tracing/tracing_generator.rb +78 -0
  47. data/lib/langsmithrb_rails/client.rb +292 -0
  48. data/lib/langsmithrb_rails/config.rb +169 -0
  49. data/lib/langsmithrb_rails/evaluation/evaluator.rb +178 -0
  50. data/lib/langsmithrb_rails/evaluation/llm_evaluator.rb +154 -0
  51. data/lib/langsmithrb_rails/evaluation/string_evaluator.rb +158 -0
  52. data/lib/langsmithrb_rails/evaluation.rb +76 -0
  53. data/lib/langsmithrb_rails/generators/langsmithrb_rails/langsmith_generator.rb +61 -0
  54. data/lib/langsmithrb_rails/generators/langsmithrb_rails/templates/langsmith_initializer.rb +22 -0
  55. data/lib/langsmithrb_rails/langsmith.rb +35 -0
  56. data/lib/langsmithrb_rails/otel/exporter.rb +120 -0
  57. data/lib/langsmithrb_rails/otel.rb +135 -0
  58. data/lib/langsmithrb_rails/railtie.rb +33 -0
  59. data/lib/langsmithrb_rails/redactor.rb +76 -0
  60. data/lib/langsmithrb_rails/run_trees.rb +157 -0
  61. data/lib/langsmithrb_rails/version.rb +5 -0
  62. data/lib/langsmithrb_rails/wrappers/anthropic.rb +146 -0
  63. data/lib/langsmithrb_rails/wrappers/base.rb +81 -0
  64. data/lib/langsmithrb_rails/wrappers/llm.rb +151 -0
  65. data/lib/langsmithrb_rails/wrappers/openai.rb +193 -0
  66. data/lib/langsmithrb_rails/wrappers.rb +41 -0
  67. data/lib/langsmithrb_rails.rb +151 -0
  68. data/pkg/langsmithrb_rails-0.3.0.gem +0 -0
  69. metadata +74 -7
data/README.md ADDED
@@ -0,0 +1,421 @@
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
+ - ๐Ÿ”Œ **LLM Provider Wrappers**: Trace OpenAI, Anthropic, and other LLM providers
17
+ - ๐ŸŒณ **Advanced Tracing**: Hierarchical run trees for complex workflows
18
+ - ๐Ÿ“ก **OpenTelemetry Integration**: Distributed tracing with OpenTelemetry
19
+ - ๐Ÿงช **Enhanced Evaluation Framework**: String and LLM-based evaluators
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'langsmithrb_rails'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ ```bash
32
+ $ bundle install
33
+ ```
34
+
35
+ Or install it yourself as:
36
+
37
+ ```bash
38
+ $ gem install langsmithrb_rails
39
+ ```
40
+
41
+ ## Setup
42
+
43
+ After installing the gem, run the install generator to set up LangSmith in your Rails application:
44
+
45
+ ```bash
46
+ $ rails generate langsmithrb_rails:install
47
+ ```
48
+
49
+ This will:
50
+
51
+ 1. Create a LangSmith initializer at `config/initializers/langsmith.rb`
52
+ 2. Generate a YAML configuration file at `config/langsmith.yml`
53
+ 3. Display post-install instructions
54
+
55
+ ## Configuration
56
+
57
+ LangSmith is configured through environment variables and a YAML configuration file:
58
+
59
+ ```
60
+ # Required
61
+ LANGSMITH_API_KEY=your_api_key
62
+
63
+ # Optional
64
+ LANGSMITH_PROJECT=your_project_name
65
+ LANGSMITH_SAMPLING=1.0 # Sampling rate (0.0 to 1.0)
66
+ LANGSMITH_ENABLED=true # Enable/disable tracing
67
+ ```
68
+
69
+ You can get your API key from [LangSmith](https://smith.langchain.com/).
70
+
71
+ The YAML configuration file (`config/langsmith.yml`) allows for environment-specific settings:
72
+
73
+ ```yaml
74
+ default: &default
75
+ api_key: <%= ENV.fetch("LANGSMITH_API_KEY", nil) %>
76
+ project_name: <%= ENV.fetch("LANGSMITH_PROJECT", nil) %>
77
+ sampling_rate: <%= ENV.fetch("LANGSMITH_SAMPLING", 1.0).to_f %>
78
+ enabled: <%= ENV.fetch("LANGSMITH_ENABLED", "true") == "true" %>
79
+ redact_pii: true
80
+ timeout: 5
81
+
82
+ development:
83
+ <<: *default
84
+
85
+ test:
86
+ <<: *default
87
+
88
+ production:
89
+ <<: *default
90
+ sampling_rate: <%= ENV.fetch("LANGSMITH_SAMPLING", 0.1).to_f %>
91
+ ```
92
+
93
+ You can also configure LangSmith programmatically in your initializer:
94
+
95
+ ```ruby
96
+ LangsmithrbRails.configure do |config|
97
+ config.enabled = true # Enable LangSmith tracing
98
+ config.api_key = "your_api_key" # Your LangSmith API key
99
+ config.project_name = "your_project" # Optional project name
100
+ config.sampling_rate = 0.1 # Sample 10% of traces
101
+ config.redact_pii = true # Redact PII from traces
102
+
103
+ # Advanced tracing options
104
+ config.trace_all = true # Trace all operations
105
+ config.trace_level = "info" # Trace level (debug, info, warn, error, fatal)
106
+
107
+ # OpenTelemetry options
108
+ config.otel_enabled = false # Enable OpenTelemetry integration
109
+ config.otel_service_name = "my-rails-app" # Service name for OpenTelemetry
110
+
111
+ # Evaluation options
112
+ config.evaluation_enabled = true # Enable evaluation framework
113
+
114
+ # Logging options
115
+ config.log_level = "info" # Log level (debug, info, warn, error, fatal)
116
+ config.log_to_stdout = true # Log to STDOUT
117
+ end
118
+ ```
119
+
120
+ ## Generators
121
+
122
+ LangsmithrbRails provides several generators to help you set up different features:
123
+
124
+ ### Install Generator
125
+
126
+ ```bash
127
+ $ rails generate langsmithrb_rails:install
128
+ ```
129
+
130
+ Sets up the basic configuration for LangSmith.
131
+
132
+ ### Tracing Generator
133
+
134
+ ```bash
135
+ $ rails generate langsmithrb_rails:tracing
136
+ ```
137
+
138
+ Adds middleware and concerns for request-level tracing:
139
+
140
+ - Creates a middleware for tracing HTTP requests
141
+ - Adds a concern for tracing services
142
+ - Adds a concern for tracing background jobs
143
+ - Updates your application configuration
144
+
145
+ ### Buffer Generator
146
+
147
+ ```bash
148
+ $ rails generate langsmithrb_rails:buffer
149
+ ```
150
+
151
+ Sets up local buffering for traces:
152
+
153
+ - Creates a migration for the buffer table
154
+ - Adds a model for the buffer
155
+ - Adds a job for flushing the buffer
156
+ - Adds rake tasks for manual flushing
157
+
158
+ ### Evals Generator
159
+
160
+ ```bash
161
+ $ rails generate langsmithrb_rails:evals
162
+ ```
163
+
164
+ Adds an evaluation framework for LLM responses:
165
+
166
+ - Creates sample datasets for evaluation
167
+ - Adds evaluation checks (correctness, LLM-graded)
168
+ - Adds evaluation targets (HTTP, Ruby)
169
+ - Adds rake tasks for running evaluations
170
+
171
+ ### CI Generator
172
+
173
+ ```bash
174
+ $ rails generate langsmithrb_rails:ci
175
+ ```
176
+
177
+ Sets up CI integration for LangSmith evaluations:
178
+
179
+ - Creates a GitHub Actions workflow
180
+ - Adds a script for generating evaluation summaries
181
+ - Configures PR comments with evaluation results
182
+
183
+ ### Privacy Generator
184
+
185
+ ```bash
186
+ $ rails generate langsmithrb_rails:privacy
187
+ ```
188
+
189
+ Adds privacy features for LangSmith traces:
190
+
191
+ - Creates a custom redactor for PII
192
+ - Adds a privacy initializer
193
+ - Generates a privacy configuration file
194
+
195
+ ### Demo Generator
196
+
197
+ ```bash
198
+ $ rails generate langsmithrb_rails:demo
199
+ ```
200
+
201
+ Adds a demo application with LangSmith tracing:
202
+
203
+ - Creates a chat interface with LLM integration
204
+ - Adds a service for interacting with LLMs
205
+ - Configures tracing for all LLM operations
206
+
207
+ ## Usage
208
+
209
+ ### Basic Tracing
210
+
211
+ Once configured, you can trace your code using the `LangsmithTraced` concern:
212
+
213
+ ```ruby
214
+ class MyService
215
+ include LangsmithTraced
216
+
217
+ def process(input)
218
+ langsmith_trace("my_operation", inputs: { input: input }) do |run|
219
+ # Your code here
220
+ result = do_something(input)
221
+
222
+ # Record the output
223
+ run.outputs = { result: result }
224
+
225
+ result
226
+ end
227
+ end
228
+ end
229
+ ```
230
+
231
+ ### LLM Provider Wrappers
232
+
233
+ You can wrap LLM providers to automatically trace all operations:
234
+
235
+ ```ruby
236
+ # Wrap an OpenAI client
237
+ require "openai"
238
+ openai_client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"])
239
+ traced_client = LangsmithrbRails.wrap(openai_client, provider: :openai, project_name: "my-project")
240
+
241
+ # Use the traced client normally
242
+ response = traced_client.chat(
243
+ parameters: {
244
+ model: "gpt-4",
245
+ messages: [{ role: "user", content: "Hello, world!" }]
246
+ }
247
+ )
248
+
249
+ # Wrap an Anthropic client
250
+ require "anthropic"
251
+ anthropic_client = Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
252
+ traced_client = LangsmithrbRails.wrap(anthropic_client, provider: :anthropic)
253
+
254
+ # Use the traced client normally
255
+ response = traced_client.messages(
256
+ model: "claude-3-opus-20240229",
257
+ max_tokens: 1024,
258
+ messages: [{ role: "user", content: "Hello, world!" }]
259
+ )
260
+
261
+ # Wrap any LLM client
262
+ custom_client = MyCustomLLM.new
263
+ traced_client = LangsmithrbRails.wrap(custom_client, provider: :llm)
264
+
265
+ # Make any method traceable
266
+ LangsmithrbRails.traceable(object, :method_name, run_name: "my-run")
267
+ ```
268
+
269
+ ### Tracing Background Jobs
270
+
271
+ For background jobs, use the `LangsmithTracedJob` concern:
272
+
273
+ ```ruby
274
+ class MyJob < ApplicationJob
275
+ include LangsmithTracedJob
276
+
277
+ def perform(args)
278
+ # Job is automatically traced
279
+ # ...
280
+ end
281
+ end
282
+ ```
283
+
284
+ ### Advanced Tracing with Run Trees
285
+
286
+ For complex workflows, use run trees to create hierarchical traces:
287
+
288
+ ```ruby
289
+ LangsmithrbRails.run(name: "parent-operation", run_type: "chain", inputs: { query: "Hello" }) do |parent_run|
290
+ # Do some work in the parent run
291
+ intermediate_result = process_query(parent_run.inputs[:query])
292
+
293
+ # Create a child run
294
+ child_run = parent_run.create_child(
295
+ name: "child-operation",
296
+ run_type: "llm",
297
+ inputs: { prompt: intermediate_result }
298
+ )
299
+
300
+ # Do some work in the child run
301
+ result = call_llm(child_run.inputs[:prompt])
302
+
303
+ # End the child run with outputs
304
+ child_run.end(outputs: { response: result })
305
+
306
+ # Return the final result
307
+ result
308
+ end
309
+ ```
310
+
311
+ ### Local Buffering
312
+
313
+ If you've set up buffering, traces will be stored locally and sent in batches. You can manually flush the buffer:
314
+
315
+ ```bash
316
+ $ rails langsmith:flush
317
+ ```
318
+
319
+ ### OpenTelemetry Integration
320
+
321
+ You can enable OpenTelemetry integration for distributed tracing:
322
+
323
+ ```ruby
324
+ # Initialize OpenTelemetry
325
+ LangsmithrbRails::OTEL.init(
326
+ service_name: "my-rails-app",
327
+ service_version: "1.0.0"
328
+ )
329
+
330
+ # Trace an operation with OpenTelemetry
331
+ LangsmithrbRails::OTEL.trace("my-operation", attributes: { key: "value" }) do
332
+ # Your code here
333
+ result = do_something()
334
+ result
335
+ end
336
+
337
+ # Trace an LLM operation
338
+ LangsmithrbRails::OTEL.trace_llm(
339
+ "llm-operation",
340
+ inputs: { prompt: "Hello, world!" },
341
+ run_type: "llm",
342
+ project_name: "my-project",
343
+ tags: ["tag1", "tag2"]
344
+ ) do
345
+ # Your LLM code here
346
+ response = llm.generate("Hello, world!")
347
+ response
348
+ end
349
+ ```
350
+
351
+ ### Running Evaluations
352
+
353
+ To run an evaluation:
354
+
355
+ ```bash
356
+ $ rails langsmith:eval[sample,http,my_experiment]
357
+ ```
358
+
359
+ Where:
360
+ - `sample` is the dataset name
361
+ - `http` is the target name
362
+ - `my_experiment` is the experiment name
363
+
364
+ ### Enhanced Evaluation Framework
365
+
366
+ Use the enhanced evaluation framework to evaluate LLM responses:
367
+
368
+ ```ruby
369
+ # Create a string evaluator
370
+ string_evaluator = LangsmithrbRails.evaluator(
371
+ :string,
372
+ match_type: :exact,
373
+ case_sensitive: false,
374
+ project_name: "my-project"
375
+ )
376
+
377
+ # Evaluate a prediction against a reference
378
+ result = string_evaluator.evaluate("Hello, world!", "hello, world!")
379
+ # => { score: 1.0, metadata: { match: true, match_type: "exact", case_sensitive: false } }
380
+
381
+ # Create an LLM-based evaluator
382
+ llm_client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"])
383
+ llm_evaluator = LangsmithrbRails.evaluator(
384
+ :llm,
385
+ llm: llm_client,
386
+ criteria: "Evaluate the response for accuracy and relevance.",
387
+ project_name: "my-project"
388
+ )
389
+
390
+ # Evaluate a run
391
+ result = llm_evaluator.evaluate_run("run-id")
392
+
393
+ # Evaluate a dataset
394
+ results = LangsmithrbRails::Evaluation.evaluate_dataset(
395
+ "dataset-id",
396
+ [string_evaluator, llm_evaluator],
397
+ experiment_name: "my-experiment"
398
+ )
399
+ ```
400
+
401
+ ### Comparing Experiments
402
+
403
+ To compare two experiments:
404
+
405
+ ```bash
406
+ $ rails langsmith:compare[exp_a,exp_b]
407
+ ```
408
+
409
+ ## Development
410
+
411
+ 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.
412
+
413
+ To install this gem onto your local machine, run `bundle exec rake install`.
414
+
415
+ ## Contributing
416
+
417
+ Bug reports and pull requests are welcome on GitHub at https://github.com/protocolgrid/langsmithrb_rails.
418
+
419
+ ## License
420
+
421
+ 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
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