langsmithrb 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 +626 -0
- data/Rakefile +8 -0
- data/lib/langsmith/client.rb +1023 -0
- data/lib/langsmith/dataset.rb +177 -0
- data/lib/langsmith/evaluation.rb +101 -0
- data/lib/langsmith/feedback.rb +43 -0
- data/lib/langsmith/project.rb +40 -0
- data/lib/langsmith/run.rb +114 -0
- data/lib/langsmith/trace.rb +96 -0
- data/lib/langsmith/version.rb +6 -0
- data/lib/langsmith.rb +86 -0
- data/lib/langsmithrb.rb +4 -0
- metadata +211 -0
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
module Langsmith
|
6
|
+
class Dataset
|
7
|
+
attr_reader :id, :name, :description, :created_at, :tenant_id, :data
|
8
|
+
|
9
|
+
# Initialize a new Dataset instance
|
10
|
+
#
|
11
|
+
# @param client [Langsmith::Client] The LangSmith client
|
12
|
+
# @param data [Hash] Dataset data from the API
|
13
|
+
def initialize(client, data)
|
14
|
+
@client = client
|
15
|
+
@id = data[:id] || data["id"]
|
16
|
+
@name = data[:name] || data["name"]
|
17
|
+
@description = data[:description] || data["description"]
|
18
|
+
created_at_value = data[:created_at] || data["created_at"]
|
19
|
+
@created_at = created_at_value ? Time.parse(created_at_value) : nil
|
20
|
+
@tenant_id = data[:tenant_id] || data["tenant_id"]
|
21
|
+
@data = data
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create a new example in this dataset
|
25
|
+
#
|
26
|
+
# @param inputs [Hash] Input values for the example
|
27
|
+
# @param outputs [Hash, nil] Output values for the example (optional)
|
28
|
+
# @param metadata [Hash, nil] Additional metadata for the example (optional)
|
29
|
+
# @return [Langsmith::Example] The created example
|
30
|
+
def create_example(inputs:, outputs: nil, metadata: nil)
|
31
|
+
data = {
|
32
|
+
dataset_id: @id,
|
33
|
+
inputs: inputs
|
34
|
+
}
|
35
|
+
data[:outputs] = outputs if outputs
|
36
|
+
data[:metadata] = metadata if metadata
|
37
|
+
|
38
|
+
response = @client.post("/examples", data)
|
39
|
+
Example.new(@client, response)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get a specific example by ID
|
43
|
+
#
|
44
|
+
# @param example_id [String] ID of the example to get
|
45
|
+
# @return [Langsmith::Example] The requested example
|
46
|
+
def get_example(example_id:)
|
47
|
+
response = @client.get("/examples/#{example_id}")
|
48
|
+
Example.new(@client, response)
|
49
|
+
end
|
50
|
+
|
51
|
+
# List examples in this dataset
|
52
|
+
#
|
53
|
+
# @param limit [Integer] Maximum number of examples to return
|
54
|
+
# @param offset [Integer] Number of examples to skip
|
55
|
+
# @return [Array<Langsmith::Example>] List of examples in this dataset
|
56
|
+
def list_examples(limit: 100, offset: 0)
|
57
|
+
params = {
|
58
|
+
dataset_id: @id,
|
59
|
+
limit: limit,
|
60
|
+
offset: offset
|
61
|
+
}
|
62
|
+
response = @client.get("/examples", params)
|
63
|
+
response.map { |example_data| Example.new(@client, example_data) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create multiple examples in batch
|
67
|
+
#
|
68
|
+
# @param examples [Array<Hash>] Array of example data, each containing :inputs and optionally :outputs and :metadata
|
69
|
+
# @return [Array<Example>] The created examples
|
70
|
+
def create_examples_batch(examples:)
|
71
|
+
@client.create_examples_batch(dataset_id: @id, examples: examples)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create a new evaluation run on this dataset
|
75
|
+
#
|
76
|
+
# @param evaluator_name [String] Name of the evaluator to use
|
77
|
+
# @param run_ids [Array<String>] IDs of runs to evaluate
|
78
|
+
# @param metadata [Hash, nil] Additional metadata for the evaluation (optional)
|
79
|
+
# @return [Langsmith::Evaluation] The created evaluation run
|
80
|
+
def create_evaluation_run(evaluator_name:, run_ids:, metadata: nil)
|
81
|
+
data = {
|
82
|
+
dataset_id: @id,
|
83
|
+
evaluator_name: evaluator_name,
|
84
|
+
run_ids: run_ids
|
85
|
+
}
|
86
|
+
data[:metadata] = metadata if metadata
|
87
|
+
|
88
|
+
response = @client.post("/evaluations", data)
|
89
|
+
Evaluation.new(@client, response)
|
90
|
+
end
|
91
|
+
|
92
|
+
# List evaluation runs for this dataset
|
93
|
+
#
|
94
|
+
# @param limit [Integer] Maximum number of evaluation runs to return
|
95
|
+
# @param offset [Integer] Number of evaluation runs to skip
|
96
|
+
# @return [Array<Langsmith::Evaluation>] List of evaluation runs for this dataset
|
97
|
+
def list_evaluation_runs(limit: 100, offset: 0)
|
98
|
+
params = {
|
99
|
+
dataset_id: @id,
|
100
|
+
limit: limit,
|
101
|
+
offset: offset
|
102
|
+
}
|
103
|
+
response = @client.get("/evaluations", params)
|
104
|
+
response.map { |eval_data| Evaluation.new(@client, eval_data) }
|
105
|
+
end
|
106
|
+
|
107
|
+
# Update this dataset
|
108
|
+
#
|
109
|
+
# @param name [String, nil] New name for the dataset (optional)
|
110
|
+
# @param description [String, nil] New description for the dataset (optional)
|
111
|
+
# @return [Langsmith::Dataset] The updated dataset
|
112
|
+
def update(name: nil, description: nil)
|
113
|
+
data = {}
|
114
|
+
data[:name] = name if name
|
115
|
+
data[:description] = description if description
|
116
|
+
|
117
|
+
response = @client.patch("/datasets/#{@id}", data)
|
118
|
+
@name = response[:name] || response["name"] if name
|
119
|
+
@description = response[:description] || response["description"] if description
|
120
|
+
@data = response
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
# Delete this dataset
|
125
|
+
#
|
126
|
+
# @return [Boolean] True if successful
|
127
|
+
def delete
|
128
|
+
@client.delete("/datasets/#{@id}")
|
129
|
+
true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Example class to represent dataset examples
|
134
|
+
class Example
|
135
|
+
attr_reader :id, :dataset_id, :inputs, :outputs, :metadata, :created_at
|
136
|
+
|
137
|
+
def initialize(client, data)
|
138
|
+
@client = client
|
139
|
+
@id = data[:id] || data["id"]
|
140
|
+
@dataset_id = data[:dataset_id] || data["dataset_id"]
|
141
|
+
@inputs = data[:inputs] || data["inputs"]
|
142
|
+
@outputs = data[:outputs] || data["outputs"]
|
143
|
+
@metadata = data[:metadata] || data["metadata"]
|
144
|
+
created_at_value = data[:created_at] || data["created_at"]
|
145
|
+
@created_at = created_at_value ? Time.parse(created_at_value) : nil
|
146
|
+
@data = data
|
147
|
+
end
|
148
|
+
|
149
|
+
# Update this example
|
150
|
+
#
|
151
|
+
# @param inputs [Hash, nil] New input values (optional)
|
152
|
+
# @param outputs [Hash, nil] New output values (optional)
|
153
|
+
# @param metadata [Hash, nil] New metadata (optional)
|
154
|
+
# @return [Langsmith::Example] The updated example
|
155
|
+
def update(inputs: nil, outputs: nil, metadata: nil)
|
156
|
+
data = {}
|
157
|
+
data[:inputs] = inputs if inputs
|
158
|
+
data[:outputs] = outputs if outputs
|
159
|
+
data[:metadata] = metadata if metadata
|
160
|
+
|
161
|
+
response = @client.patch("/examples/#{@id}", data)
|
162
|
+
@inputs = response[:inputs] || response["inputs"] if inputs
|
163
|
+
@outputs = response[:outputs] || response["outputs"] if outputs
|
164
|
+
@metadata = response[:metadata] || response["metadata"] if metadata
|
165
|
+
@data = response
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
# Delete this example
|
170
|
+
#
|
171
|
+
# @return [Boolean] True if successful
|
172
|
+
def delete
|
173
|
+
@client.delete("/examples/#{@id}")
|
174
|
+
true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
module Langsmith
|
6
|
+
class Evaluation
|
7
|
+
attr_reader :id, :dataset_id, :evaluator_name, :status, :created_at, :run_ids, :metadata, :data
|
8
|
+
|
9
|
+
# Initialize a new Evaluation instance
|
10
|
+
#
|
11
|
+
# @param client [Langsmith::Client] The LangSmith client
|
12
|
+
# @param data [Hash] Evaluation data from the API
|
13
|
+
def initialize(client, data)
|
14
|
+
@client = client
|
15
|
+
@id = data[:id] || data["id"]
|
16
|
+
@dataset_id = data[:dataset_id] || data["dataset_id"]
|
17
|
+
@evaluator_name = data[:evaluator_name] || data["evaluator_name"]
|
18
|
+
@status = data[:status] || data["status"]
|
19
|
+
created_at_value = data[:created_at] || data["created_at"]
|
20
|
+
@created_at = created_at_value ? Time.parse(created_at_value) : nil
|
21
|
+
@run_ids = data[:run_ids] || data["run_ids"] || []
|
22
|
+
@metadata = data[:metadata] || data["metadata"] || {}
|
23
|
+
@data = data
|
24
|
+
end
|
25
|
+
|
26
|
+
# Get the results of this evaluation
|
27
|
+
#
|
28
|
+
# @param limit [Integer] Maximum number of results to return
|
29
|
+
# @param offset [Integer] Number of results to skip
|
30
|
+
# @return [Array<Hash>] Evaluation results
|
31
|
+
def results(limit: 100, offset: 0)
|
32
|
+
params = {
|
33
|
+
limit: limit,
|
34
|
+
offset: offset
|
35
|
+
}
|
36
|
+
@client.get("/evaluations/#{@id}/results", params)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the status of this evaluation
|
40
|
+
#
|
41
|
+
# @return [String] Current status of the evaluation
|
42
|
+
def refresh_status
|
43
|
+
response = @client.get("/evaluations/#{@id}")
|
44
|
+
@status = response[:status] || response["status"]
|
45
|
+
@data = response
|
46
|
+
@status
|
47
|
+
end
|
48
|
+
|
49
|
+
# Check if the evaluation is completed
|
50
|
+
#
|
51
|
+
# @return [Boolean] True if the evaluation is completed
|
52
|
+
def completed?
|
53
|
+
refresh_status == "complete"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Wait for the evaluation to complete
|
57
|
+
#
|
58
|
+
# @param timeout [Integer] Maximum time to wait in seconds
|
59
|
+
# @param interval [Integer] Time between status checks in seconds
|
60
|
+
# @return [Boolean] True if the evaluation completed within the timeout
|
61
|
+
def wait_for_completion(timeout: 300, interval: 5)
|
62
|
+
start_time = Time.now
|
63
|
+
|
64
|
+
while Time.now - start_time < timeout
|
65
|
+
return true if completed?
|
66
|
+
sleep(interval)
|
67
|
+
end
|
68
|
+
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Update this evaluation's metadata
|
73
|
+
#
|
74
|
+
# @param metadata [Hash] New metadata for the evaluation
|
75
|
+
# @return [Langsmith::Evaluation] The updated evaluation
|
76
|
+
def update_metadata(metadata:)
|
77
|
+
data = { metadata: metadata }
|
78
|
+
response = @client.patch("/evaluations/#{@id}", data)
|
79
|
+
@metadata = response[:metadata] || response["metadata"]
|
80
|
+
@data = response
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Cancel this evaluation
|
85
|
+
#
|
86
|
+
# @return [Boolean] True if successful
|
87
|
+
def cancel
|
88
|
+
@client.post("/evaluations/#{@id}/cancel")
|
89
|
+
refresh_status
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
# Delete this evaluation
|
94
|
+
#
|
95
|
+
# @return [Boolean] True if successful
|
96
|
+
def delete
|
97
|
+
@client.delete("/evaluations/#{@id}")
|
98
|
+
true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langsmith
|
4
|
+
class Feedback
|
5
|
+
attr_reader :id, :run_id, :key, :score, :comment, :created_at
|
6
|
+
|
7
|
+
# Initialize a new Feedback instance
|
8
|
+
#
|
9
|
+
# @param client [Langsmith::Client] The LangSmith client
|
10
|
+
# @param data [Hash] Feedback data from the API
|
11
|
+
def initialize(client, data)
|
12
|
+
@client = client
|
13
|
+
@id = data["id"]
|
14
|
+
@run_id = data["run_id"]
|
15
|
+
@key = data["key"]
|
16
|
+
@score = data["score"]
|
17
|
+
@comment = data["comment"]
|
18
|
+
@created_at = data["created_at"] ? Time.parse(data["created_at"]) : nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# Update this feedback
|
22
|
+
#
|
23
|
+
# @param score [Float] New feedback score
|
24
|
+
# @param comment [String] New feedback comment
|
25
|
+
# @return [Langsmith::Feedback] The updated feedback
|
26
|
+
def update(score: nil, comment: nil)
|
27
|
+
data = {}
|
28
|
+
data[:score] = score if score
|
29
|
+
data[:comment] = comment if comment
|
30
|
+
|
31
|
+
response = @client.patch("/feedback/#{@id}", data)
|
32
|
+
Langsmith::Feedback.new(@client, response)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Delete this feedback
|
36
|
+
#
|
37
|
+
# @return [Boolean] True if the feedback was deleted successfully
|
38
|
+
def delete
|
39
|
+
response = @client.delete("/feedback/#{@id}")
|
40
|
+
response["success"] == true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langsmith
|
4
|
+
class Project
|
5
|
+
attr_reader :id, :name, :description, :created_at, :tenant_id
|
6
|
+
|
7
|
+
# Initialize a new Project instance
|
8
|
+
#
|
9
|
+
# @param client [Langsmith::Client] The LangSmith client
|
10
|
+
# @param data [Hash] Project data from the API
|
11
|
+
def initialize(client, data)
|
12
|
+
@client = client
|
13
|
+
@id = data["id"]
|
14
|
+
@name = data["name"]
|
15
|
+
@description = data["description"]
|
16
|
+
@created_at = data["created_at"] ? Time.parse(data["created_at"]) : nil
|
17
|
+
@tenant_id = data["tenant_id"]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a new run in this project
|
21
|
+
#
|
22
|
+
# @param name [String] Name of the run
|
23
|
+
# @param run_type [String] Type of run (e.g., llm, chain, tool)
|
24
|
+
# @param inputs [Hash] Input values for the run
|
25
|
+
# @param extra [Hash] Additional metadata for the run
|
26
|
+
# @return [Langsmith::Run] The created run
|
27
|
+
def create_run(name:, run_type:, inputs: {}, extra: {})
|
28
|
+
@client.create_run(name: name, run_type: run_type, project_name: @name, inputs: inputs, extra: extra)
|
29
|
+
end
|
30
|
+
|
31
|
+
# List runs in this project
|
32
|
+
#
|
33
|
+
# @param run_type [String] Filter by run type
|
34
|
+
# @param limit [Integer] Maximum number of runs to return
|
35
|
+
# @return [Array<Langsmith::Run>] List of runs in this project
|
36
|
+
def list_runs(run_type: nil, limit: 100)
|
37
|
+
@client.list_runs(project_name: @name, run_type: run_type, limit: limit)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
module Langsmith
|
6
|
+
class Run
|
7
|
+
attr_reader :id, :name, :run_type, :start_time, :end_time, :status
|
8
|
+
attr_reader :inputs, :outputs, :error, :project_name, :trace_id, :parent_run_id
|
9
|
+
|
10
|
+
# Initialize a new Run instance
|
11
|
+
#
|
12
|
+
# @param client [Langsmith::Client] The LangSmith client
|
13
|
+
# @param data [Hash] Run data from the API
|
14
|
+
def initialize(client, data)
|
15
|
+
@client = client
|
16
|
+
@id = data["id"]
|
17
|
+
@name = data["name"]
|
18
|
+
@run_type = data["run_type"]
|
19
|
+
@start_time = data["start_time"] ? Time.parse(data["start_time"]) : Time.now
|
20
|
+
@end_time = data["end_time"] ? Time.parse(data["end_time"]) : nil
|
21
|
+
@status = data["status"] || "in_progress"
|
22
|
+
@inputs = data["inputs"] || {}
|
23
|
+
@outputs = data["outputs"] || {}
|
24
|
+
@error = data["error"]
|
25
|
+
@project_name = data["project_name"]
|
26
|
+
@trace_id = data["trace_id"]
|
27
|
+
@parent_run_id = data["parent_run_id"]
|
28
|
+
@extra = data["extra"] || {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Update the run with outputs and mark it as completed
|
32
|
+
#
|
33
|
+
# @param outputs [Hash] Output values from the run
|
34
|
+
# @return [Langsmith::Run] The updated run
|
35
|
+
def end(outputs: nil, error: nil)
|
36
|
+
end_time = Time.now
|
37
|
+
|
38
|
+
if error
|
39
|
+
@client.update_run(run_id: @id, end_time: end_time, error: error)
|
40
|
+
else
|
41
|
+
@client.update_run(run_id: @id, outputs: outputs, end_time: end_time)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create a child run for this run
|
46
|
+
#
|
47
|
+
# @param name [String] Name of the child run
|
48
|
+
# @param run_type [String] Type of the child run
|
49
|
+
# @param inputs [Hash] Input values for the child run
|
50
|
+
# @param extra [Hash] Additional metadata for the child run
|
51
|
+
# @return [Langsmith::Run] The created child run
|
52
|
+
def create_child_run(name:, run_type:, inputs: {}, extra: {})
|
53
|
+
child_extra = extra.merge(parent_run_id: @id, trace_id: @trace_id)
|
54
|
+
|
55
|
+
data = {
|
56
|
+
name: name,
|
57
|
+
run_type: run_type,
|
58
|
+
inputs: inputs,
|
59
|
+
extra: child_extra
|
60
|
+
}
|
61
|
+
data[:project_name] = @project_name if @project_name
|
62
|
+
|
63
|
+
response = @client.post("/runs", data)
|
64
|
+
Langsmith::Run.new(@client, response)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Add feedback to this run
|
68
|
+
#
|
69
|
+
# @param key [String] Feedback key (e.g., "correctness", "helpfulness")
|
70
|
+
# @param score [Float] Feedback score (typically 0.0 to 1.0)
|
71
|
+
# @param comment [String] Optional comment with the feedback
|
72
|
+
# @return [Langsmith::Feedback] The created feedback
|
73
|
+
def add_feedback(key:, score:, comment: nil)
|
74
|
+
@client.create_feedback(run_id: @id, key: key, score: score, comment: comment)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get all feedback for this run
|
78
|
+
#
|
79
|
+
# @return [Array<Langsmith::Feedback>] List of feedback for this run
|
80
|
+
def get_feedback
|
81
|
+
@client.get_feedback(run_id: @id)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get metadata from the run
|
85
|
+
#
|
86
|
+
# @param key [String] The metadata key to retrieve
|
87
|
+
# @return [Object] The metadata value
|
88
|
+
def get_metadata(key)
|
89
|
+
@extra[key]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Check if the run is completed
|
93
|
+
#
|
94
|
+
# @return [Boolean] True if the run is completed
|
95
|
+
def completed?
|
96
|
+
!@end_time.nil?
|
97
|
+
end
|
98
|
+
|
99
|
+
# Check if the run has an error
|
100
|
+
#
|
101
|
+
# @return [Boolean] True if the run has an error
|
102
|
+
def error?
|
103
|
+
!@error.nil?
|
104
|
+
end
|
105
|
+
|
106
|
+
# Get the duration of the run in seconds
|
107
|
+
#
|
108
|
+
# @return [Float] Duration in seconds, or nil if the run is not completed
|
109
|
+
def duration
|
110
|
+
return nil unless @end_time
|
111
|
+
@end_time - @start_time
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langsmith
|
4
|
+
class Trace
|
5
|
+
attr_reader :id, :name, :start_time, :end_time, :runs
|
6
|
+
|
7
|
+
# Initialize a new Trace instance
|
8
|
+
#
|
9
|
+
# @param client [Langsmith::Client] The LangSmith client
|
10
|
+
# @param trace_id [String] ID of the trace
|
11
|
+
def initialize(client, trace_id)
|
12
|
+
@client = client
|
13
|
+
@id = trace_id
|
14
|
+
@runs = []
|
15
|
+
refresh
|
16
|
+
end
|
17
|
+
|
18
|
+
# Refresh the trace data from the API
|
19
|
+
#
|
20
|
+
# @return [Langsmith::Trace] The updated trace
|
21
|
+
def refresh
|
22
|
+
response = @client.get("/traces/#{@id}")
|
23
|
+
@name = response["name"]
|
24
|
+
@start_time = response["start_time"] ? Time.parse(response["start_time"]) : nil
|
25
|
+
@end_time = response["end_time"] ? Time.parse(response["end_time"]) : nil
|
26
|
+
|
27
|
+
# Get all runs associated with this trace
|
28
|
+
runs_response = @client.get("/runs", { trace_id: @id })
|
29
|
+
@runs = runs_response.map { |run_data| Langsmith::Run.new(@client, run_data) }
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the root run of this trace
|
35
|
+
#
|
36
|
+
# @return [Langsmith::Run] The root run
|
37
|
+
def root_run
|
38
|
+
@runs.find { |run| run.parent_run_id.nil? }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get child runs of a specific run
|
42
|
+
#
|
43
|
+
# @param parent_run_id [String] ID of the parent run
|
44
|
+
# @return [Array<Langsmith::Run>] Child runs
|
45
|
+
def child_runs(parent_run_id)
|
46
|
+
@runs.select { |run| run.parent_run_id == parent_run_id }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Check if the trace is completed
|
50
|
+
#
|
51
|
+
# @return [Boolean] True if the trace is completed
|
52
|
+
def completed?
|
53
|
+
!@end_time.nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get the duration of the trace in seconds
|
57
|
+
#
|
58
|
+
# @return [Float] Duration in seconds, or nil if the trace is not completed
|
59
|
+
def duration
|
60
|
+
return nil unless @end_time && @start_time
|
61
|
+
@end_time - @start_time
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get a hierarchical representation of the trace
|
65
|
+
#
|
66
|
+
# @return [Hash] Hierarchical representation of the trace
|
67
|
+
def to_hierarchy
|
68
|
+
root = root_run
|
69
|
+
return {} unless root
|
70
|
+
|
71
|
+
build_hierarchy(root)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def build_hierarchy(run)
|
77
|
+
children = child_runs(run.id)
|
78
|
+
|
79
|
+
result = {
|
80
|
+
id: run.id,
|
81
|
+
name: run.name,
|
82
|
+
run_type: run.run_type,
|
83
|
+
start_time: run.start_time,
|
84
|
+
end_time: run.end_time,
|
85
|
+
status: run.status,
|
86
|
+
inputs: run.inputs,
|
87
|
+
outputs: run.outputs,
|
88
|
+
error: run.error
|
89
|
+
}
|
90
|
+
|
91
|
+
result[:children] = children.map { |child| build_hierarchy(child) } unless children.empty?
|
92
|
+
|
93
|
+
result
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/langsmith.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "langsmith/version"
|
5
|
+
|
6
|
+
module Langsmith
|
7
|
+
class << self
|
8
|
+
# @return [Logger]
|
9
|
+
attr_accessor :logger
|
10
|
+
# @return [Pathname]
|
11
|
+
attr_reader :root
|
12
|
+
# @return [String]
|
13
|
+
attr_accessor :api_key
|
14
|
+
# @return [String]
|
15
|
+
attr_accessor :api_url
|
16
|
+
end
|
17
|
+
|
18
|
+
module Errors
|
19
|
+
class BaseError < StandardError; end
|
20
|
+
class AuthenticationError < BaseError; end
|
21
|
+
class APIError < BaseError; end
|
22
|
+
class ResourceNotFoundError < BaseError; end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Colorizer
|
26
|
+
class << self
|
27
|
+
def red(str)
|
28
|
+
"\e[31m#{str}\e[0m"
|
29
|
+
end
|
30
|
+
|
31
|
+
def green(str)
|
32
|
+
"\e[32m#{str}\e[0m"
|
33
|
+
end
|
34
|
+
|
35
|
+
def yellow(str)
|
36
|
+
"\e[33m#{str}\e[0m"
|
37
|
+
end
|
38
|
+
|
39
|
+
def blue(str)
|
40
|
+
"\e[34m#{str}\e[0m"
|
41
|
+
end
|
42
|
+
|
43
|
+
def colorize_logger_msg(msg, severity)
|
44
|
+
return msg unless msg.is_a?(String)
|
45
|
+
|
46
|
+
return red(msg) if severity.to_sym == :ERROR
|
47
|
+
return yellow(msg) if severity.to_sym == :WARN
|
48
|
+
msg
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
LOGGER_OPTIONS = {
|
54
|
+
progname: "Langsmith.rb",
|
55
|
+
|
56
|
+
formatter: ->(severity, time, progname, msg) do
|
57
|
+
Logger::Formatter.new.call(
|
58
|
+
severity,
|
59
|
+
time,
|
60
|
+
"[#{progname}]",
|
61
|
+
Colorizer.colorize_logger_msg(msg, severity)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
}.freeze
|
65
|
+
|
66
|
+
# Default API URL for LangSmith
|
67
|
+
DEFAULT_API_URL = "https://api.smith.langchain.com".freeze
|
68
|
+
|
69
|
+
# Set default logger
|
70
|
+
self.logger ||= ::Logger.new($stdout, **LOGGER_OPTIONS)
|
71
|
+
|
72
|
+
# Set root path
|
73
|
+
@root = Pathname.new(__dir__)
|
74
|
+
|
75
|
+
# Set default API URL
|
76
|
+
@api_url = DEFAULT_API_URL
|
77
|
+
end
|
78
|
+
|
79
|
+
# Load the Langsmith components
|
80
|
+
require "langsmith/client"
|
81
|
+
require "langsmith/run"
|
82
|
+
require "langsmith/dataset"
|
83
|
+
require "langsmith/evaluation"
|
84
|
+
require "langsmith/feedback"
|
85
|
+
require "langsmith/project"
|
86
|
+
require "langsmith/trace"
|
data/lib/langsmithrb.rb
ADDED