Class: Braintrust::State

Inherits:
Object
  • Object
show all
Defined in:
lib/braintrust/state.rb

Overview

State object that holds Braintrust configuration Thread-safe global state management

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#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, config: nil, exporter: nil) ⇒ State

Create a State object directly with explicit parameters

Parameters:

  • api_key (String) (defaults to: nil)

    Braintrust API key (required)

  • org_name (String, nil) (defaults to: nil)

    Organization name

  • org_id (String, nil) (defaults to: nil)

    Organization ID (if provided, skips login - useful for testing)

  • default_project (String, nil) (defaults to: nil)

    Default project name

  • app_url (String, nil) (defaults to: nil)

    App URL (default: www.braintrust.dev)

  • api_url (String, nil) (defaults to: nil)

    API URL

  • proxy_url (String, nil) (defaults to: nil)

    Proxy URL

  • blocking_login (Boolean) (defaults to: false)

    Login synchronously (default: false)

  • enable_tracing (Boolean) (defaults to: true)

    Enable OpenTelemetry tracing (default: true)

  • tracer_provider (TracerProvider, nil) (defaults to: nil)

    Optional tracer provider

  • config (Config, nil) (defaults to: nil)

    Optional config object

  • exporter (Exporter, nil) (defaults to: nil)

    Optional exporter for testing

Raises:

  • (ArgumentError)


66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/braintrust/state.rb', line 66

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, config: nil, exporter: nil)
  # Instance-level mutex for thread-safe login
  @login_mutex = Mutex.new
  raise ArgumentError, "api_key is required" if api_key.nil? || api_key.empty?

  @api_key = api_key
  @org_name = org_name
  @org_id = org_id
  @default_project = default_project
  @app_url = app_url || "https://www.braintrust.dev"
  @api_url = api_url || "https://api.braintrust.dev"
  @proxy_url = proxy_url
  @config = config

  # If org_id is provided, we're already "logged in" (useful for testing)
  # Otherwise, perform login to discover org info
  if org_id
    @logged_in = true
  elsif 
    @logged_in = false
    
  else
    @logged_in = false
    
  end

  # Setup tracing if requested
  if enable_tracing
    require_relative "trace"
    Trace.setup(self, tracer_provider, exporter: exporter)

    # Propagate tracer_provider to Contrib if loaded (soft dependency check)
    if defined?(Braintrust::Contrib)
      Braintrust::Contrib.init(tracer_provider: tracer_provider)
    end
  end
end

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def api_key
  @api_key
end

#api_urlObject (readonly)

Returns the value of attribute api_url.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def api_url
  @api_url
end

#app_urlObject (readonly)

Returns the value of attribute app_url.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def app_url
  @app_url
end

#configObject (readonly)

Returns the value of attribute config.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def config
  @config
end

#default_projectObject (readonly)

Returns the value of attribute default_project.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def default_project
  @default_project
end

#logged_inObject (readonly)

Returns the value of attribute logged_in.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def logged_in
  @logged_in
end

#org_idObject (readonly)

Returns the value of attribute org_id.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def org_id
  @org_id
end

#org_nameObject (readonly)

Returns the value of attribute org_name.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def org_name
  @org_name
end

#proxy_urlObject (readonly)

Returns the value of attribute proxy_url.



9
10
11
# File 'lib/braintrust/state.rb', line 9

def proxy_url
  @proxy_url
end

Class Method Details

.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, filter_ai_spans: nil, span_filter_funcs: nil, exporter: nil) ⇒ State

Create a State from environment variables with option overrides

Parameters:

  • api_key (String, nil) (defaults to: nil)

    Braintrust API key (overrides BRAINTRUST_API_KEY env var)

  • org_name (String, nil) (defaults to: nil)

    Organization name (overrides BRAINTRUST_ORG_NAME env var)

  • default_project (String, nil) (defaults to: nil)

    Default project (overrides BRAINTRUST_DEFAULT_PROJECT env var)

  • app_url (String, nil) (defaults to: nil)

    App URL (overrides BRAINTRUST_APP_URL env var)

  • api_url (String, nil) (defaults to: nil)

    API URL (overrides BRAINTRUST_API_URL env var)

  • blocking_login (Boolean) (defaults to: false)

    whether to block and login synchronously (default: false)

  • enable_tracing (Boolean) (defaults to: true)

    whether to enable OpenTelemetry tracing (default: true)

  • tracer_provider (TracerProvider, nil) (defaults to: nil)

    Optional tracer provider to use

  • filter_ai_spans (Boolean, nil) (defaults to: nil)

    Enable AI span filtering

  • span_filter_funcs (Array<Proc>, nil) (defaults to: nil)

    Custom span filter functions

  • exporter (Exporter, nil) (defaults to: nil)

    Optional exporter override (for testing)

Returns:

  • (State)

    the created state



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/braintrust/state.rb', line 27

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, filter_ai_spans: nil, span_filter_funcs: nil, exporter: nil)
  require_relative "config"
  config = Config.from_env(
    api_key: api_key,
    org_name: org_name,
    default_project: default_project,
    app_url: app_url,
    api_url: api_url,
    filter_ai_spans: filter_ai_spans,
    span_filter_funcs: span_filter_funcs
  )
  new(
    api_key: config.api_key,
    org_name: config.org_name,
    default_project: config.default_project,
    app_url: config.app_url,
    api_url: config.api_url,
    blocking_login: ,
    enable_tracing: enable_tracing,
    tracer_provider: tracer_provider,
    config: config,
    exporter: exporter
  )
end

.globalObject

Thread-safe global state getter



105
106
107
# File 'lib/braintrust/state.rb', line 105

def self.global
  @mutex.synchronize { @global_state }
end

.global=(state) ⇒ Object

Thread-safe global state setter



110
111
112
# File 'lib/braintrust/state.rb', line 110

def self.global=(state)
  @mutex.synchronize { @global_state = state }
end

Instance Method Details

#loginself

Login to Braintrust API and update state with org info Makes synchronous HTTP request via API::Auth Updates @org_id, @org_name, @api_url, @proxy_url, @logged_in Idempotent: returns early if already logged in Thread-safe: protected by mutex

Returns:

  • (self)


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/braintrust/state.rb', line 120

def 
  @login_mutex.synchronize do
    # Return early if already logged in
    return self if @logged_in

    result = API::Internal::Auth.(
      api_key: @api_key,
      app_url: @app_url,
      org_name: @org_name
    )

    # Update state with org info
    @org_id = result.org_id
    @org_name = result.org_name
    @api_url = result.api_url
    @proxy_url = result.proxy_url
    @logged_in = true

    self
  end
end

#login_in_threadself

Login to Braintrust API in a background thread with retry logic Retries indefinitely with exponential backoff until success Idempotent: returns early if already logged in Thread-safe: login method is protected by mutex

Returns:

  • (self)


157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/braintrust/state.rb', line 157

def 
  # Return early if already logged in (without spawning thread)
  return self if @logged_in

  @login_thread = Thread.new do
    retry_count = 0
    max_delay = 5.0

    loop do
      Log.debug("Background login attempt #{retry_count + 1}")
      
      Log.debug("Background login succeeded")
      break
    rescue => e
      retry_count += 1
      delay = [0.001 * 2**(retry_count - 1), max_delay].min
      Log.debug("Background login failed (attempt #{retry_count}): #{e.message}. Retrying in #{delay}s...")
      sleep delay
    end
  end

  self
end

Generate a permalink URL to view an object in the Braintrust UI This is for the /object endpoint (experiments, datasets, etc.) For trace span permalinks, use Trace.permalink instead.

Parameters:

  • object_type (String)

    Type of object (e.g., “experiment”, “dataset”)

  • object_id (String)

    Object UUID

Returns:

  • (String)

    Permalink URL



148
149
150
# File 'lib/braintrust/state.rb', line 148

def object_permalink(object_type:, object_id:)
  "#{@app_url}/app/#{@org_name}/object?object_type=#{object_type}&object_id=#{object_id}"
end

#validateself

Validate state is properly configured Raises ArgumentError if state is invalid

Returns:

  • (self)

Raises:

  • (ArgumentError)


192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/braintrust/state.rb', line 192

def validate
  raise ArgumentError, "api_key is required" if @api_key.nil? || @api_key.empty?
  raise ArgumentError, "api_url is required" if @api_url.nil? || @api_url.empty?
  raise ArgumentError, "app_url is required" if @app_url.nil? || @app_url.empty?

  # If logged_in is true, org_id and org_name should be present
  if @logged_in
    raise ArgumentError, "org_id is required when logged_in is true" if @org_id.nil? || @org_id.empty?
    raise ArgumentError, "org_name is required when logged_in is true" if @org_name.nil? || @org_name.empty?
  end

  self
end

#wait_for_login(timeout = nil) ⇒ self

Wait for background login thread to complete (for testing)

Parameters:

  • timeout (Numeric, nil) (defaults to: nil)

    Optional timeout in seconds

Returns:

  • (self)


184
185
186
187
# File 'lib/braintrust/state.rb', line 184

def (timeout = nil)
  @login_thread&.join(timeout)
  self
end