braintrust 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be1db72d5636027cfb0b1744e0c30ac9b7169b7f898d4a911639d9f437e0654a
4
- data.tar.gz: 725708134bfa2a8d12a3fb0c74e9a54f97afe2f249ed6e3d26184cc5c97a3775
3
+ metadata.gz: 3330f067e867f2d0d81ea12db80bb2dbf83d9ca447ef819e2bff7c97138e7f9e
4
+ data.tar.gz: f2e4f8e34bfe2292d5b3883923d4b638de89b6c930144c02a4c7941b831ca7e6
5
5
  SHA512:
6
- metadata.gz: 85cda56b35738299973d2d58a001c9cb746c168437de56e9cf443a2787728dc98014b2b63cf5d6f0d781b8d5c443feedc42dab5abaae2827b60f1c80eb38329a
7
- data.tar.gz: e99f24f4cfabbffbdc21822e35145794ccdf29bd22d2e47889dfaab30b4428b49ae173c3ad3e12c4fc7169e3a61ed493670c55bacacd25a800c5df28dc68269f
6
+ metadata.gz: feb0969a48253f3729b9f38d243abcb6f0367176b9cddf9df112be9e415f8b5dadf90ca50588bd2e09be84a4efb406f40aa13b4ccab88310bba1a73e993205e6
7
+ data.tar.gz: 67e9c6163c4b20944953e7063286f98b2e079e424fd80991376110864971f251cb1ca6a663a99d923d9cd92d74be17030dba90a9ba5619f610d307d4075aa38f
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Braintrust Ruby SDK
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/braintrust.svg)](https://badge.fury.io/rb/braintrust)
4
+ [![Documentation](https://img.shields.io/badge/docs-rubydoc.info-blue.svg)](https://rubydoc.info/gems/braintrust)
4
5
  ![Beta](https://img.shields.io/badge/status-beta-yellow)
5
6
 
6
7
  ## Overview
@@ -46,46 +47,24 @@ export BRAINTRUST_API_KEY="your-api-key"
46
47
  ```ruby
47
48
  require "braintrust"
48
49
 
49
- # Initialize Braintrust
50
50
  Braintrust.init
51
51
 
52
- # Simple food classifier (the code being evaluated)
53
- def classify_food(input)
54
- fruit = %w[apple banana strawberry orange grape mango]
55
- vegetable = %w[carrot broccoli spinach potato tomato cucumber]
56
-
57
- input_lower = input.downcase
58
- return "fruit" if fruit.any? { |f| input_lower.include?(f) }
59
- return "vegetable" if vegetable.any? { |v| input_lower.include?(v) }
60
- "unknown"
61
- end
52
+ # Define task to evaluate
53
+ task = ->(input) { input.include?("a") ? "fruit" : "vegetable" }
62
54
 
63
- # Run an evaluation
64
- result = Braintrust::Eval.run(
55
+ # Run evaluation
56
+ Braintrust::Eval.run(
65
57
  project: "my-project",
66
- experiment: "food-classifier-eval",
67
-
68
- # Test cases
58
+ experiment: "food-classifier",
69
59
  cases: [
70
60
  {input: "apple", expected: "fruit"},
71
- {input: "carrot", expected: "vegetable"},
72
- {input: "banana", expected: "fruit"},
73
- {input: "broccoli", expected: "vegetable"}
61
+ {input: "carrot", expected: "vegetable"}
74
62
  ],
75
-
76
- # Task to evaluate
77
- task: ->(input) { classify_food(input) },
78
-
79
- # Scorers to judge output quality
63
+ task: task,
80
64
  scorers: [
81
- Braintrust::Eval.scorer("exact_match") { |input, expected, output|
82
- (output == expected) ? 1.0 : 0.0
83
- }
65
+ ->(input, expected, output) { output == expected ? 1.0 : 0.0 }
84
66
  ]
85
67
  )
86
-
87
- # View results
88
- puts "View results at: #{result.permalink}"
89
68
  ```
90
69
 
91
70
  ### Tracing
@@ -128,20 +107,15 @@ puts "View trace in Braintrust!"
128
107
  require "braintrust"
129
108
  require "openai"
130
109
 
131
- # Initialize Braintrust
132
110
  Braintrust.init
133
111
 
134
- # Create OpenAI client
135
112
  client = OpenAI::Client.new(api_key: ENV["OPENAI_API_KEY"])
136
113
 
137
- # Wrap the client with Braintrust tracing
138
114
  Braintrust::Trace::OpenAI.wrap(client)
139
115
 
140
- # Create a root span to capture the operation
141
116
  tracer = OpenTelemetry.tracer_provider.tracer("openai-app")
142
117
  root_span = nil
143
118
 
144
- # Make a chat completion request (automatically traced!)
145
119
  response = tracer.in_span("chat-completion") do |span|
146
120
  root_span = span
147
121
 
@@ -157,10 +131,8 @@ end
157
131
 
158
132
  puts "Response: #{response.choices[0].message.content}"
159
133
 
160
- # View the trace
161
134
  puts "View trace at: #{Braintrust::Trace.permalink(root_span)}"
162
135
 
163
- # Shutdown to flush spans
164
136
  OpenTelemetry.tracer_provider.shutdown
165
137
  ```
166
138
 
@@ -16,15 +16,20 @@ module Braintrust
16
16
 
17
17
  # Create a Config from environment variables, with option overrides
18
18
  # Passed-in options take priority over ENV vars
19
- def self.from_env(**options)
20
- defaults = {
21
- api_key: ENV["BRAINTRUST_API_KEY"],
22
- org_name: ENV["BRAINTRUST_ORG_NAME"],
23
- default_project: ENV["BRAINTRUST_DEFAULT_PROJECT"],
24
- app_url: ENV["BRAINTRUST_APP_URL"] || "https://www.braintrust.dev",
25
- api_url: ENV["BRAINTRUST_API_URL"] || "https://api.braintrust.dev"
26
- }
27
- new(**defaults.merge(options))
19
+ # @param api_key [String, nil] Braintrust API key (overrides BRAINTRUST_API_KEY env var)
20
+ # @param org_name [String, nil] Organization name (overrides BRAINTRUST_ORG_NAME env var)
21
+ # @param default_project [String, nil] Default project (overrides BRAINTRUST_DEFAULT_PROJECT env var)
22
+ # @param app_url [String, nil] App URL (overrides BRAINTRUST_APP_URL env var)
23
+ # @param api_url [String, nil] API URL (overrides BRAINTRUST_API_URL env var)
24
+ # @return [Config] the created config
25
+ def self.from_env(api_key: nil, org_name: nil, default_project: nil, app_url: nil, api_url: nil)
26
+ new(
27
+ api_key: api_key || ENV["BRAINTRUST_API_KEY"],
28
+ org_name: org_name || ENV["BRAINTRUST_ORG_NAME"],
29
+ default_project: default_project || ENV["BRAINTRUST_DEFAULT_PROJECT"],
30
+ app_url: app_url || ENV["BRAINTRUST_APP_URL"] || "https://www.braintrust.dev",
31
+ api_url: api_url || ENV["BRAINTRUST_API_URL"] || "https://api.braintrust.dev"
32
+ )
28
33
  end
29
34
  end
30
35
  end
@@ -5,21 +5,23 @@ module Braintrust
5
5
  # Result represents the outcome of an evaluation run
6
6
  # Contains experiment metadata, errors, and timing information
7
7
  class Result
8
- attr_reader :experiment_id, :experiment_name, :project_id,
8
+ attr_reader :experiment_id, :experiment_name, :project_id, :project_name,
9
9
  :permalink, :errors, :duration
10
10
 
11
11
  # Create a new result
12
12
  # @param experiment_id [String] The experiment ID
13
13
  # @param experiment_name [String] The experiment name
14
14
  # @param project_id [String] The project ID
15
+ # @param project_name [String] The project name
15
16
  # @param permalink [String] Link to view the experiment in Braintrust UI
16
17
  # @param errors [Array<String>] List of errors that occurred
17
18
  # @param duration [Float] Duration in seconds
18
- def initialize(experiment_id:, experiment_name:, project_id:,
19
+ def initialize(experiment_id:, experiment_name:, project_id:, project_name:,
19
20
  permalink:, errors:, duration:)
20
21
  @experiment_id = experiment_id
21
22
  @experiment_name = experiment_name
22
23
  @project_id = project_id
24
+ @project_name = project_name
23
25
  @permalink = permalink
24
26
  @errors = errors
25
27
  @duration = duration
@@ -42,9 +44,10 @@ module Braintrust
42
44
  def to_s
43
45
  [
44
46
  "Experiment: #{experiment_name}",
47
+ "Project: #{project_name}",
45
48
  "ID: #{experiment_id}",
46
49
  "Link: #{permalink}",
47
- "Duration: #{duration.round(2)}s",
50
+ "Duration: #{duration.round(4)}s",
48
51
  "Errors: #{errors.length}"
49
52
  ].join("\n")
50
53
  end
@@ -137,6 +137,7 @@ module Braintrust
137
137
  experiment_id: experiment_id,
138
138
  experiment_name: experiment_name,
139
139
  project_id: project_id,
140
+ project_name: project_name,
140
141
  permalink: permalink,
141
142
  errors: errors,
142
143
  duration: duration
@@ -146,6 +147,7 @@ module Braintrust
146
147
  # Print result summary to stdout
147
148
  # @param result [Result] The evaluation result
148
149
  def print_result(result)
150
+ puts "=" * 60
149
151
  puts result
150
152
  end
151
153
 
@@ -295,7 +297,7 @@ module Braintrust
295
297
  # @param scorers [Array<Scorer>] The scorers
296
298
  # @param errors [Array<String>] Error collection array
297
299
  # @param tracer [Tracer] OpenTelemetry tracer
298
- # @param parent_attr [String] Parent attribute (experiment_id:project/exp_id)
300
+ # @param parent_attr [String] Parent attribute (experiment_id:exp_id)
299
301
  def run_case(test_case, task, scorers, errors, tracer, parent_attr)
300
302
  # Create eval span (parent)
301
303
  tracer.in_span("eval") do |eval_span|
@@ -11,7 +11,38 @@ module Braintrust
11
11
  @mutex = Mutex.new
12
12
  @global_state = nil
13
13
 
14
- def initialize(api_key: nil, org_name: nil, org_id: nil, default_project: nil, app_url: nil, api_url: nil, proxy_url: nil, logged_in: false)
14
+ # Create a State from environment variables with option overrides
15
+ # @param api_key [String, nil] Braintrust API key (overrides BRAINTRUST_API_KEY env var)
16
+ # @param org_name [String, nil] Organization name (overrides BRAINTRUST_ORG_NAME env var)
17
+ # @param default_project [String, nil] Default project (overrides BRAINTRUST_DEFAULT_PROJECT env var)
18
+ # @param app_url [String, nil] App URL (overrides BRAINTRUST_APP_URL env var)
19
+ # @param api_url [String, nil] API URL (overrides BRAINTRUST_API_URL env var)
20
+ # @param blocking_login [Boolean] whether to block and login synchronously (default: false)
21
+ # @param enable_tracing [Boolean] whether to enable OpenTelemetry tracing (default: true)
22
+ # @param tracer_provider [TracerProvider, nil] Optional tracer provider to use
23
+ # @return [State] the created state
24
+ def self.from_env(api_key: nil, org_name: nil, default_project: nil, app_url: nil, api_url: nil, blocking_login: false, enable_tracing: true, tracer_provider: nil)
25
+ require_relative "config"
26
+ config = Config.from_env(
27
+ api_key: api_key,
28
+ org_name: org_name,
29
+ default_project: default_project,
30
+ app_url: app_url,
31
+ api_url: api_url
32
+ )
33
+ new(
34
+ api_key: config.api_key,
35
+ org_name: config.org_name,
36
+ default_project: config.default_project,
37
+ app_url: config.app_url,
38
+ api_url: config.api_url,
39
+ blocking_login: blocking_login,
40
+ enable_tracing: enable_tracing,
41
+ tracer_provider: tracer_provider
42
+ )
43
+ end
44
+
45
+ def initialize(api_key: nil, org_name: nil, org_id: nil, default_project: nil, app_url: nil, api_url: nil, proxy_url: nil, blocking_login: false, enable_tracing: true, tracer_provider: nil)
15
46
  # Instance-level mutex for thread-safe login
16
47
  @login_mutex = Mutex.new
17
48
  raise ArgumentError, "api_key is required" if api_key.nil? || api_key.empty?
@@ -23,7 +54,20 @@ module Braintrust
23
54
  @app_url = app_url || "https://www.braintrust.dev"
24
55
  @api_url = api_url
25
56
  @proxy_url = proxy_url
26
- @logged_in = logged_in
57
+ @logged_in = false
58
+
59
+ # Perform login after state setup
60
+ if blocking_login
61
+ login
62
+ else
63
+ login_in_thread
64
+ end
65
+
66
+ # Setup tracing if requested
67
+ if enable_tracing
68
+ require_relative "trace"
69
+ Trace.setup(self, tracer_provider)
70
+ end
27
71
  end
28
72
 
29
73
  # Thread-safe global state getter
@@ -15,6 +15,35 @@ end
15
15
 
16
16
  module Braintrust
17
17
  module Trace
18
+ # Set up OpenTelemetry tracing with Braintrust
19
+ # @param state [State] Braintrust state
20
+ # @param tracer_provider [TracerProvider, nil] Optional tracer provider
21
+ # @return [void]
22
+ def self.setup(state, tracer_provider = nil)
23
+ if tracer_provider
24
+ # Use the explicitly provided tracer provider
25
+ # DO NOT set as global - user is managing it themselves
26
+ Log.debug("Using explicitly provided OpenTelemetry tracer provider")
27
+ else
28
+ # Check if global tracer provider is already a real TracerProvider
29
+ current_provider = OpenTelemetry.tracer_provider
30
+
31
+ if current_provider.is_a?(OpenTelemetry::SDK::Trace::TracerProvider)
32
+ # Use existing provider
33
+ Log.debug("Using existing OpenTelemetry tracer provider")
34
+ tracer_provider = current_provider
35
+ else
36
+ # Create new provider and set as global
37
+ tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new
38
+ OpenTelemetry.tracer_provider = tracer_provider
39
+ Log.debug("Created OpenTelemetry tracer provider")
40
+ end
41
+ end
42
+
43
+ # Enable Braintrust tracing (adds span processor)
44
+ enable(tracer_provider, state: state)
45
+ end
46
+
18
47
  def self.enable(tracer_provider, state: nil, exporter: nil)
19
48
  state ||= Braintrust.current_state
20
49
  raise Error, "No state available" unless state
@@ -94,14 +123,8 @@ module Braintrust
94
123
 
95
124
  # Build the permalink URL based on parent type
96
125
  if parent_type == "experiment_id"
97
- # For experiments: {app_url}/app/{org}/p/{project}/experiments/{experiment_id}?r={trace_id}&s={span_id}
98
- project_name, experiment_id = parent_id.split("/", 2)
99
- unless project_name && experiment_id
100
- Log.error("Invalid experiment parent format: #{parent_id}")
101
- return ""
102
- end
103
-
104
- "#{app_url}/app/#{org_name}/p/#{project_name}/experiments/#{experiment_id}?r=#{trace_id}&s=#{span_id}"
126
+ # For experiments: {app_url}/app/{org}/object?object_type=experiment&object_id={experiment_id}&r={trace_id}&s={span_id}
127
+ "#{app_url}/app/#{org_name}/object?object_type=experiment&object_id=#{parent_id}&r=#{trace_id}&s=#{span_id}"
105
128
  else
106
129
  # For projects: {app_url}/app/{org}/p/{project}/logs?r={trace_id}&s={span_id}
107
130
  # parent_type is typically "project_name"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Braintrust
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
data/lib/braintrust.rb CHANGED
@@ -15,7 +15,7 @@ require_relative "braintrust/eval"
15
15
  # @example Initialize with global state
16
16
  # Braintrust.init(
17
17
  # api_key: ENV['BRAINTRUST_API_KEY'],
18
- # project: "my-project"
18
+ # default_project: "my-project"
19
19
  # )
20
20
  #
21
21
  # @example Initialize with explicit state
@@ -27,42 +27,31 @@ module Braintrust
27
27
  class Error < StandardError; end
28
28
 
29
29
  # Initialize Braintrust SDK
30
- # Creates a State from config (ENV + options) and optionally sets it as global
31
30
  #
32
- # By default, kicks off an async background login that retries indefinitely.
33
- # Use blocking_login: true to login synchronously before returning.
34
- #
35
- # @param set_global [Boolean] whether to set as global state (default: true)
36
- # @param blocking_login [Boolean] whether to block and login synchronously (default: false, which starts async login)
37
- # @param tracing [Boolean] whether to enable OpenTelemetry tracing (default: true)
38
- # @param tracer_provider [TracerProvider, nil] Optional tracer provider to use instead of creating one
39
31
  # @param api_key [String, nil] Braintrust API key (overrides BRAINTRUST_API_KEY env var)
40
32
  # @param org_name [String, nil] Organization name (overrides BRAINTRUST_ORG_NAME env var)
41
33
  # @param default_project [String, nil] Default project for spans (overrides BRAINTRUST_DEFAULT_PROJECT env var, format: "project_name:my-project" or "project_id:uuid")
42
34
  # @param app_url [String, nil] App URL (overrides BRAINTRUST_APP_URL env var, default: https://www.braintrust.dev)
43
35
  # @param api_url [String, nil] API URL (overrides BRAINTRUST_API_URL env var, default: https://api.braintrust.dev)
36
+ # @param set_global [Boolean] Whether to set as global state (default: true)
37
+ # @param blocking_login [Boolean] Whether to block and login synchronously (default: false - async background login)
38
+ # @param enable_tracing [Boolean] Whether to enable OpenTelemetry tracing (default: true)
39
+ # @param tracer_provider [TracerProvider, nil] Optional tracer provider to use instead of creating one
44
40
  # @return [State] the created state
45
- def self.init(set_global: true, blocking_login: false, tracing: true, tracer_provider: nil, **options)
46
- config = Config.from_env(**options)
47
- state = State.new(
48
- api_key: config.api_key,
49
- org_name: config.org_name,
50
- default_project: config.default_project,
51
- app_url: config.app_url,
52
- api_url: config.api_url
41
+ def self.init(api_key: nil, org_name: nil, default_project: nil, app_url: nil, api_url: nil, set_global: true, blocking_login: false, enable_tracing: true, tracer_provider: nil)
42
+ state = State.from_env(
43
+ api_key: api_key,
44
+ org_name: org_name,
45
+ default_project: default_project,
46
+ app_url: app_url,
47
+ api_url: api_url,
48
+ blocking_login: blocking_login,
49
+ enable_tracing: enable_tracing,
50
+ tracer_provider: tracer_provider
53
51
  )
54
52
 
55
53
  State.global = state if set_global
56
54
 
57
- # Login: either blocking (synchronous) or async (background thread with retries)
58
- if blocking_login
59
- state.login
60
- else
61
- state.login_in_thread # Default: async background login
62
- end
63
-
64
- setup_tracing(state, tracer_provider) if tracing
65
-
66
55
  state
67
56
  end
68
57
 
@@ -71,40 +60,4 @@ module Braintrust
71
60
  def self.current_state
72
61
  State.global
73
62
  end
74
-
75
- class << self
76
- private
77
-
78
- # Set up OpenTelemetry tracing with Braintrust
79
- # @param state [State] Braintrust state
80
- # @param explicit_provider [TracerProvider, nil] Optional explicit tracer provider
81
- # @return [void]
82
- def setup_tracing(state, explicit_provider = nil)
83
- require "opentelemetry/sdk"
84
-
85
- if explicit_provider
86
- # Use the explicitly provided tracer provider
87
- # DO NOT set as global - user is managing it themselves
88
- Log.debug("Using explicitly provided OpenTelemetry tracer provider")
89
- tracer_provider = explicit_provider
90
- else
91
- # Check if global tracer provider is already a real TracerProvider
92
- current_provider = OpenTelemetry.tracer_provider
93
-
94
- if current_provider.is_a?(OpenTelemetry::SDK::Trace::TracerProvider)
95
- # Use existing provider
96
- Log.debug("Using existing OpenTelemetry tracer provider")
97
- tracer_provider = current_provider
98
- else
99
- # Create new provider and set as global
100
- tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new
101
- OpenTelemetry.tracer_provider = tracer_provider
102
- Log.debug("Created OpenTelemetry tracer provider")
103
- end
104
- end
105
-
106
- # Enable Braintrust tracing (adds span processor)
107
- Trace.enable(tracer_provider, state: state)
108
- end
109
- end
110
63
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: braintrust
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Braintrust
@@ -149,8 +149,35 @@ dependencies:
149
149
  - - "~>"
150
150
  - !ruby/object:Gem::Version
151
151
  version: '2.5'
152
- description: OpenTelemetry-based SDK for Braintrust with tracing, OpenAI integration,
153
- and evals
152
+ - !ruby/object:Gem::Dependency
153
+ name: yard
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '0.9'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '0.9'
166
+ - !ruby/object:Gem::Dependency
167
+ name: kramdown
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '2.0'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '2.0'
180
+ description: 'Braintrust Ruby SDK for evals, tracing and more. '
154
181
  email:
155
182
  - info@braintrust.dev
156
183
  executables: []
@@ -184,7 +211,7 @@ licenses:
184
211
  metadata:
185
212
  homepage_uri: https://github.com/braintrustdata/braintrust-sdk-ruby
186
213
  source_code_uri: https://github.com/braintrustdata/braintrust-sdk-ruby
187
- changelog_uri: https://github.com/braintrustdata/braintrust-sdk-ruby/blob/main/CHANGELOG.md
214
+ changelog_uri: https://github.com/braintrustdata/braintrust-sdk-ruby/releases
188
215
  rdoc_options: []
189
216
  require_paths:
190
217
  - lib