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.
- checksums.yaml +4 -4
- data/.rspec +3 -0
- data/.rspec_status +161 -0
- data/CHANGELOG.md +38 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +321 -0
- data/LICENSE +21 -0
- data/README.md +421 -0
- data/Rakefile +10 -0
- data/langsmithrb_rails-0.1.0.gem +0 -0
- data/langsmithrb_rails-0.1.1.gem +0 -0
- data/langsmithrb_rails.gemspec +45 -0
- data/lib/generators/langsmithrb_rails/buffer/buffer_generator.rb +94 -0
- data/lib/generators/langsmithrb_rails/buffer/templates/create_langsmith_run_buffers.rb +29 -0
- data/lib/generators/langsmithrb_rails/buffer/templates/flush_buffer_job.rb +40 -0
- data/lib/generators/langsmithrb_rails/buffer/templates/langsmith.rake +71 -0
- data/lib/generators/langsmithrb_rails/buffer/templates/langsmith_run_buffer.rb +70 -0
- data/lib/generators/langsmithrb_rails/buffer/templates/migration.rb +28 -0
- data/lib/generators/langsmithrb_rails/ci/ci_generator.rb +37 -0
- data/lib/generators/langsmithrb_rails/ci/templates/langsmith-evals.yml +85 -0
- data/lib/generators/langsmithrb_rails/ci/templates/langsmith_export_summary.rb +81 -0
- data/lib/generators/langsmithrb_rails/demo/demo_generator.rb +81 -0
- data/lib/generators/langsmithrb_rails/demo/templates/chat_controller.js +88 -0
- data/lib/generators/langsmithrb_rails/demo/templates/chat_controller.rb +58 -0
- data/lib/generators/langsmithrb_rails/demo/templates/chat_message.rb +24 -0
- data/lib/generators/langsmithrb_rails/demo/templates/create_chat_messages.rb +19 -0
- data/lib/generators/langsmithrb_rails/demo/templates/index.html.erb +180 -0
- data/lib/generators/langsmithrb_rails/demo/templates/llm_service.rb +165 -0
- data/lib/generators/langsmithrb_rails/evals/evals_generator.rb +52 -0
- data/lib/generators/langsmithrb_rails/evals/templates/checks/correctness.rb +71 -0
- data/lib/generators/langsmithrb_rails/evals/templates/checks/llm_graded.rb +137 -0
- data/lib/generators/langsmithrb_rails/evals/templates/datasets/sample.yml +60 -0
- data/lib/generators/langsmithrb_rails/evals/templates/langsmith_evals.rake +255 -0
- data/lib/generators/langsmithrb_rails/evals/templates/targets/http.rb +120 -0
- data/lib/generators/langsmithrb_rails/evals/templates/targets/ruby.rb +136 -0
- data/lib/generators/langsmithrb_rails/install/install_generator.rb +35 -0
- data/lib/generators/langsmithrb_rails/install/templates/config.yml +45 -0
- data/lib/generators/langsmithrb_rails/install/templates/initializer.rb +34 -0
- data/lib/generators/langsmithrb_rails/privacy/privacy_generator.rb +39 -0
- data/lib/generators/langsmithrb_rails/privacy/templates/custom_redactor.rb +132 -0
- data/lib/generators/langsmithrb_rails/privacy/templates/privacy.yml +88 -0
- data/lib/generators/langsmithrb_rails/privacy/templates/privacy_initializer.rb +41 -0
- data/lib/generators/langsmithrb_rails/tracing/templates/langsmith_traced.rb +146 -0
- data/lib/generators/langsmithrb_rails/tracing/templates/langsmith_traced_job.rb +151 -0
- data/lib/generators/langsmithrb_rails/tracing/templates/request_tracing.rb +117 -0
- data/lib/generators/langsmithrb_rails/tracing/tracing_generator.rb +78 -0
- data/lib/langsmithrb_rails/client.rb +292 -0
- data/lib/langsmithrb_rails/config.rb +169 -0
- data/lib/langsmithrb_rails/evaluation/evaluator.rb +178 -0
- data/lib/langsmithrb_rails/evaluation/llm_evaluator.rb +154 -0
- data/lib/langsmithrb_rails/evaluation/string_evaluator.rb +158 -0
- data/lib/langsmithrb_rails/evaluation.rb +76 -0
- data/lib/langsmithrb_rails/generators/langsmithrb_rails/langsmith_generator.rb +61 -0
- data/lib/langsmithrb_rails/generators/langsmithrb_rails/templates/langsmith_initializer.rb +22 -0
- data/lib/langsmithrb_rails/langsmith.rb +35 -0
- data/lib/langsmithrb_rails/otel/exporter.rb +120 -0
- data/lib/langsmithrb_rails/otel.rb +135 -0
- data/lib/langsmithrb_rails/railtie.rb +33 -0
- data/lib/langsmithrb_rails/redactor.rb +76 -0
- data/lib/langsmithrb_rails/run_trees.rb +157 -0
- data/lib/langsmithrb_rails/version.rb +5 -0
- data/lib/langsmithrb_rails/wrappers/anthropic.rb +146 -0
- data/lib/langsmithrb_rails/wrappers/base.rb +81 -0
- data/lib/langsmithrb_rails/wrappers/llm.rb +151 -0
- data/lib/langsmithrb_rails/wrappers/openai.rb +193 -0
- data/lib/langsmithrb_rails/wrappers.rb +41 -0
- data/lib/langsmithrb_rails.rb +151 -0
- data/pkg/langsmithrb_rails-0.3.0.gem +0 -0
- 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
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
|