braintrust 0.0.93 → 0.0.95

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.
package/dist/index.js CHANGED
@@ -3647,12 +3647,14 @@ var require_pluralize = __commonJS({
3647
3647
  // src/index.ts
3648
3648
  var src_exports = {};
3649
3649
  __export(src_exports, {
3650
+ BaseExperiment: () => BaseExperiment,
3650
3651
  Dataset: () => Dataset,
3651
3652
  Eval: () => Eval,
3652
3653
  Experiment: () => Experiment,
3653
3654
  Logger: () => Logger,
3654
3655
  NOOP_SPAN: () => NOOP_SPAN,
3655
3656
  NoopSpan: () => NoopSpan,
3657
+ ReadonlyExperiment: () => ReadonlyExperiment,
3656
3658
  SpanImpl: () => SpanImpl,
3657
3659
  _internalGetGlobalState: () => _internalGetGlobalState,
3658
3660
  _internalSetInitialState: () => _internalSetInitialState,
@@ -7681,7 +7683,9 @@ async function getBaseBranchAncestor(remote = void 0) {
7681
7683
  if (git === null) {
7682
7684
  throw new Error("Not in a git repo");
7683
7685
  }
7684
- const { remote: remoteName, branch: baseBranch } = await getBaseBranch(remote);
7686
+ const { remote: remoteName, branch: baseBranch } = await getBaseBranch(
7687
+ remote
7688
+ );
7685
7689
  const isDirty = (await git.diffSummary()).files.length > 0;
7686
7690
  const head = isDirty ? "HEAD" : "HEAD^";
7687
7691
  try {
@@ -7999,6 +8003,21 @@ function getCurrentUnixTimestamp() {
7999
8003
  function isEmpty(a) {
8000
8004
  return a === void 0 || a === null;
8001
8005
  }
8006
+ var LazyValue = class {
8007
+ constructor(callable) {
8008
+ this.value = {
8009
+ hasComputed: false
8010
+ };
8011
+ this.callable = callable;
8012
+ }
8013
+ async get() {
8014
+ if (this.value.hasComputed) {
8015
+ return this.value.val;
8016
+ }
8017
+ this.value = { hasComputed: true, val: await this.callable() };
8018
+ return this.value.val;
8019
+ }
8020
+ };
8002
8021
 
8003
8022
  // src/logger.ts
8004
8023
  var NoopSpan = class {
@@ -8213,25 +8232,25 @@ function logFeedbackImpl(bgLogger, parentIds, {
8213
8232
  updateEvent = Object.fromEntries(
8214
8233
  Object.entries(updateEvent).filter(([_, v]) => !isEmpty(v))
8215
8234
  );
8216
- const trueParentIds = (async () => {
8217
- const { kind, ...ids } = await parentIds;
8235
+ const trueParentIds = new LazyValue(async () => {
8236
+ const { kind, ...ids } = await parentIds.get();
8218
8237
  return ids;
8219
- })();
8238
+ });
8220
8239
  if (Object.keys(updateEvent).length > 0) {
8221
- const record = (async () => {
8240
+ const record = new LazyValue(async () => {
8222
8241
  return {
8223
8242
  id,
8224
8243
  ...updateEvent,
8225
- ...await trueParentIds,
8244
+ ...await trueParentIds.get(),
8226
8245
  [AUDIT_SOURCE_FIELD]: source,
8227
8246
  [AUDIT_METADATA_FIELD]: metadata,
8228
8247
  [IS_MERGE_FIELD]: true
8229
8248
  };
8230
- })();
8249
+ });
8231
8250
  bgLogger.log([record]);
8232
8251
  }
8233
8252
  if (!isEmpty(comment)) {
8234
- const record = (async () => {
8253
+ const record = new LazyValue(async () => {
8235
8254
  return {
8236
8255
  id: v4_default(),
8237
8256
  created: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8243,11 +8262,11 @@ function logFeedbackImpl(bgLogger, parentIds, {
8243
8262
  comment: {
8244
8263
  text: comment
8245
8264
  },
8246
- ...await trueParentIds,
8265
+ ...await trueParentIds.get(),
8247
8266
  [AUDIT_SOURCE_FIELD]: source,
8248
8267
  [AUDIT_METADATA_FIELD]: metadata
8249
8268
  };
8250
- })();
8269
+ });
8251
8270
  bgLogger.log([record]);
8252
8271
  }
8253
8272
  }
@@ -8257,22 +8276,24 @@ var Logger = class {
8257
8276
  this.kind = "logger";
8258
8277
  this.lazyMetadata = lazyMetadata;
8259
8278
  this.logOptions = logOptions;
8260
- const logConn = this.getState().then((state) => state.logConn());
8279
+ const logConn = new LazyValue(
8280
+ () => this.getState().then((state) => state.logConn())
8281
+ );
8261
8282
  this.bgLogger = new BackgroundLogger(logConn);
8262
8283
  this.lastStartTime = getCurrentUnixTimestamp();
8263
8284
  }
8264
8285
  get org_id() {
8265
8286
  return (async () => {
8266
- return (await this.lazyMetadata).org_id;
8287
+ return (await this.lazyMetadata.get()).org_id;
8267
8288
  })();
8268
8289
  }
8269
8290
  get project() {
8270
8291
  return (async () => {
8271
- return (await this.lazyMetadata).project;
8292
+ return (await this.lazyMetadata.get()).project;
8272
8293
  })();
8273
8294
  }
8274
8295
  async getState() {
8275
- await this.lazyMetadata;
8296
+ await this.lazyMetadata.get();
8276
8297
  return _state;
8277
8298
  }
8278
8299
  /**
@@ -8347,7 +8368,7 @@ var Logger = class {
8347
8368
  startSpan(args) {
8348
8369
  const { name, ...argsRest } = args ?? {};
8349
8370
  return new SpanImpl({
8350
- parentIds: this.lazyParentIds(),
8371
+ parentIds: new LazyValue(() => this.lazyParentIds()),
8351
8372
  bgLogger: this.bgLogger,
8352
8373
  name: name ?? "root",
8353
8374
  ...argsRest
@@ -8365,7 +8386,11 @@ var Logger = class {
8365
8386
  * @param event.source (Optional) the source of the feedback. Must be one of "external" (default), "app", or "api".
8366
8387
  */
8367
8388
  logFeedback(event) {
8368
- logFeedbackImpl(this.bgLogger, this.lazyParentIds(), event);
8389
+ logFeedbackImpl(
8390
+ this.bgLogger,
8391
+ new LazyValue(() => this.lazyParentIds()),
8392
+ event
8393
+ );
8369
8394
  }
8370
8395
  /*
8371
8396
  * Flush any pending logs to the server.
@@ -8415,9 +8440,20 @@ var BackgroundLogger = class {
8415
8440
  }
8416
8441
  async flush_once(batchSize = DefaultBatchSize) {
8417
8442
  this.active_flush_resolved = false;
8418
- const itemPromises = this.items;
8443
+ const itemLazyValues = this.items;
8419
8444
  this.items = [];
8420
- const allItems = mergeRowBatch(await Promise.all(itemPromises)).reverse();
8445
+ const allItems = await (async () => {
8446
+ try {
8447
+ const itemPromises = itemLazyValues.map((x) => x.get());
8448
+ return mergeRowBatch(await Promise.all(itemPromises)).reverse();
8449
+ } catch (e) {
8450
+ console.warn(
8451
+ "Encountered error when constructing records to flush:\n",
8452
+ e
8453
+ );
8454
+ return [];
8455
+ }
8456
+ })();
8421
8457
  let postPromises = [];
8422
8458
  while (true) {
8423
8459
  const items = [];
@@ -8442,9 +8478,7 @@ var BackgroundLogger = class {
8442
8478
  for (let i = 0; i < NumRetries; i++) {
8443
8479
  const startTime = now();
8444
8480
  try {
8445
- return (await (await this.logConn).post_json("logs", itemsS)).map(
8446
- (res) => res.id
8447
- );
8481
+ return (await (await this.logConn.get()).post_json("logs", itemsS)).map((res) => res.id);
8448
8482
  } catch (e) {
8449
8483
  const retryingText = i + 1 === NumRetries ? "" : " Retrying";
8450
8484
  const errMsg = (() => {
@@ -8490,6 +8524,7 @@ function init(project, options = {}) {
8490
8524
  dataset,
8491
8525
  baseExperiment,
8492
8526
  isPublic,
8527
+ open,
8493
8528
  update,
8494
8529
  appUrl,
8495
8530
  apiKey,
@@ -8497,82 +8532,121 @@ function init(project, options = {}) {
8497
8532
  metadata,
8498
8533
  gitMetadataSettings
8499
8534
  } = options || {};
8500
- const lazyMetadata = (async () => {
8501
- await login({
8502
- orgName,
8503
- apiKey,
8504
- appUrl
8505
- });
8506
- const args = {
8507
- project_name: project,
8508
- org_id: _state.orgId
8509
- };
8510
- if (experiment) {
8511
- args["experiment_name"] = experiment;
8512
- }
8513
- if (description) {
8514
- args["description"] = description;
8535
+ if (open) {
8536
+ if (isEmpty(experiment)) {
8537
+ throw new Error("Cannot open an experiment without specifying its name");
8515
8538
  }
8516
8539
  if (update) {
8517
- args["update"] = update;
8540
+ throw new Error("Cannot open and update an experiment at the same time");
8518
8541
  }
8519
- let mergedGitMetadataSettings = {
8520
- ..._state.gitMetadataSettings || {
8521
- collect: "all"
8542
+ const lazyMetadata2 = new LazyValue(async () => {
8543
+ await login({
8544
+ orgName,
8545
+ apiKey,
8546
+ appUrl
8547
+ });
8548
+ const args = {
8549
+ project_name: project,
8550
+ org_name: _state.orgName,
8551
+ experiment_name: experiment
8552
+ };
8553
+ const response = await _state.apiConn().post_json("api/experiment/get", args);
8554
+ if (response.length === 0) {
8555
+ throw new Error(
8556
+ `Experiment ${experiment} not found in project ${project}.`
8557
+ );
8522
8558
  }
8523
- };
8524
- if (gitMetadataSettings) {
8525
- mergedGitMetadataSettings = mergeGitMetadataSettings(
8526
- mergedGitMetadataSettings,
8527
- gitMetadataSettings
8528
- );
8529
- }
8530
- const repoStatus2 = await isomorph_default.getRepoStatus(gitMetadataSettings);
8531
- if (repoStatus2) {
8532
- args["repo_info"] = repoStatus2;
8533
- }
8534
- if (baseExperiment) {
8535
- args["base_experiment"] = baseExperiment;
8536
- } else {
8537
- args["ancestor_commits"] = await isomorph_default.getPastNAncestors();
8538
- }
8539
- if (dataset !== void 0) {
8540
- args["dataset_id"] = dataset.id;
8541
- args["dataset_version"] = await dataset.version();
8542
- }
8543
- if (isPublic !== void 0) {
8544
- args["public"] = isPublic;
8545
- }
8546
- if (metadata) {
8547
- args["metadata"] = metadata;
8548
- }
8549
- let response = null;
8550
- while (true) {
8551
- try {
8552
- response = await _state.apiConn().post_json("api/experiment/register", args);
8553
- break;
8554
- } catch (e) {
8555
- if (args["base_experiment"] && `${"data" in e && e.data}`.includes("base experiment")) {
8556
- console.warn(`Base experiment ${args["base_experiment"]} not found.`);
8557
- delete args["base_experiment"];
8558
- } else {
8559
- throw e;
8559
+ const info = response[0];
8560
+ return {
8561
+ id: info.id,
8562
+ name: info.name,
8563
+ fullInfo: info
8564
+ };
8565
+ });
8566
+ return new ReadonlyExperiment(
8567
+ lazyMetadata2
8568
+ );
8569
+ }
8570
+ const lazyMetadata = new LazyValue(
8571
+ async () => {
8572
+ await login({
8573
+ orgName,
8574
+ apiKey,
8575
+ appUrl
8576
+ });
8577
+ const args = {
8578
+ project_name: project,
8579
+ org_id: _state.orgId
8580
+ };
8581
+ if (experiment) {
8582
+ args["experiment_name"] = experiment;
8583
+ }
8584
+ if (description) {
8585
+ args["description"] = description;
8586
+ }
8587
+ if (update) {
8588
+ args["update"] = update;
8589
+ }
8590
+ let mergedGitMetadataSettings = {
8591
+ ..._state.gitMetadataSettings || {
8592
+ collect: "all"
8560
8593
  }
8594
+ };
8595
+ if (gitMetadataSettings) {
8596
+ mergedGitMetadataSettings = mergeGitMetadataSettings(
8597
+ mergedGitMetadataSettings,
8598
+ gitMetadataSettings
8599
+ );
8561
8600
  }
8562
- }
8563
- return {
8564
- project: {
8565
- id: response.project.id,
8566
- name: response.project.name,
8567
- fullInfo: response.project
8568
- },
8569
- experiment: {
8570
- id: response.experiment.id,
8571
- name: response.experiment.name,
8572
- fullInfo: response.experiment
8601
+ const repoStatus2 = await isomorph_default.getRepoStatus(gitMetadataSettings);
8602
+ if (repoStatus2) {
8603
+ args["repo_info"] = repoStatus2;
8573
8604
  }
8574
- };
8575
- })();
8605
+ if (baseExperiment) {
8606
+ args["base_experiment"] = baseExperiment;
8607
+ } else {
8608
+ args["ancestor_commits"] = await isomorph_default.getPastNAncestors();
8609
+ }
8610
+ if (dataset !== void 0) {
8611
+ args["dataset_id"] = await dataset.id;
8612
+ args["dataset_version"] = await dataset.version();
8613
+ }
8614
+ if (isPublic !== void 0) {
8615
+ args["public"] = isPublic;
8616
+ }
8617
+ if (metadata) {
8618
+ args["metadata"] = metadata;
8619
+ }
8620
+ let response = null;
8621
+ while (true) {
8622
+ try {
8623
+ response = await _state.apiConn().post_json("api/experiment/register", args);
8624
+ break;
8625
+ } catch (e) {
8626
+ if (args["base_experiment"] && `${"data" in e && e.data}`.includes("base experiment")) {
8627
+ console.warn(
8628
+ `Base experiment ${args["base_experiment"]} not found.`
8629
+ );
8630
+ delete args["base_experiment"];
8631
+ } else {
8632
+ throw e;
8633
+ }
8634
+ }
8635
+ }
8636
+ return {
8637
+ project: {
8638
+ id: response.project.id,
8639
+ name: response.project.name,
8640
+ fullInfo: response.project
8641
+ },
8642
+ experiment: {
8643
+ id: response.experiment.id,
8644
+ name: response.experiment.name,
8645
+ fullInfo: response.experiment
8646
+ }
8647
+ };
8648
+ }
8649
+ );
8576
8650
  const ret = new Experiment(lazyMetadata, dataset);
8577
8651
  if (options.setCurrent ?? true) {
8578
8652
  _state.currentExperiment = ret;
@@ -8595,32 +8669,34 @@ function withLogger(callback, options = {}) {
8595
8669
  }
8596
8670
  function initDataset(project, options = {}) {
8597
8671
  const { dataset, description, version, appUrl, apiKey, orgName } = options || {};
8598
- const lazyMetadata = (async () => {
8599
- await login({
8600
- orgName,
8601
- apiKey,
8602
- appUrl
8603
- });
8604
- const args = {
8605
- org_id: _state.orgId,
8606
- project_name: project,
8607
- dataset_name: dataset,
8608
- description
8609
- };
8610
- const response = await _state.apiConn().post_json("api/dataset/register", args);
8611
- return {
8612
- project: {
8613
- id: response.project.id,
8614
- name: response.project.name,
8615
- fullInfo: response.project
8616
- },
8617
- dataset: {
8618
- id: response.dataset.id,
8619
- name: response.dataset.name,
8620
- fullInfo: response.dataset
8621
- }
8622
- };
8623
- })();
8672
+ const lazyMetadata = new LazyValue(
8673
+ async () => {
8674
+ await login({
8675
+ orgName,
8676
+ apiKey,
8677
+ appUrl
8678
+ });
8679
+ const args = {
8680
+ org_id: _state.orgId,
8681
+ project_name: project,
8682
+ dataset_name: dataset,
8683
+ description
8684
+ };
8685
+ const response = await _state.apiConn().post_json("api/dataset/register", args);
8686
+ return {
8687
+ project: {
8688
+ id: response.project.id,
8689
+ name: response.project.name,
8690
+ fullInfo: response.project
8691
+ },
8692
+ dataset: {
8693
+ id: response.dataset.id,
8694
+ name: response.dataset.name,
8695
+ fullInfo: response.dataset
8696
+ }
8697
+ };
8698
+ }
8699
+ );
8624
8700
  return new Dataset(lazyMetadata, version);
8625
8701
  }
8626
8702
  function withDataset(project, callback, options = {}) {
@@ -8640,46 +8716,48 @@ function initLogger(options = {}) {
8640
8716
  orgName,
8641
8717
  forceLogin
8642
8718
  } = options || {};
8643
- const lazyMetadata = (async () => {
8644
- await login({
8645
- orgName,
8646
- apiKey,
8647
- appUrl,
8648
- forceLogin
8649
- });
8650
- const org_id = _state.orgId;
8651
- if (projectId === void 0) {
8652
- const response = await _state.apiConn().post_json("api/project/register", {
8653
- project_name: projectName || GLOBAL_PROJECT,
8654
- org_id
8719
+ const lazyMetadata = new LazyValue(
8720
+ async () => {
8721
+ await login({
8722
+ orgName,
8723
+ apiKey,
8724
+ appUrl,
8725
+ forceLogin
8655
8726
  });
8656
- return {
8657
- org_id,
8658
- project: {
8659
- id: response.project.id,
8660
- name: response.project.name,
8661
- fullInfo: response.project
8662
- }
8663
- };
8664
- } else if (projectName === void 0) {
8665
- const response = await _state.apiConn().get_json("api/project", {
8666
- id: projectId
8667
- });
8668
- return {
8669
- org_id,
8670
- project: {
8671
- id: projectId,
8672
- name: response.name,
8673
- fullInfo: response.project
8674
- }
8675
- };
8676
- } else {
8677
- return {
8678
- org_id,
8679
- project: { id: projectId, name: projectName, fullInfo: {} }
8680
- };
8727
+ const org_id = _state.orgId;
8728
+ if (projectId === void 0) {
8729
+ const response = await _state.apiConn().post_json("api/project/register", {
8730
+ project_name: projectName || GLOBAL_PROJECT,
8731
+ org_id
8732
+ });
8733
+ return {
8734
+ org_id,
8735
+ project: {
8736
+ id: response.project.id,
8737
+ name: response.project.name,
8738
+ fullInfo: response.project
8739
+ }
8740
+ };
8741
+ } else if (projectName === void 0) {
8742
+ const response = await _state.apiConn().get_json("api/project", {
8743
+ id: projectId
8744
+ });
8745
+ return {
8746
+ org_id,
8747
+ project: {
8748
+ id: projectId,
8749
+ name: response.name,
8750
+ fullInfo: response.project
8751
+ }
8752
+ };
8753
+ } else {
8754
+ return {
8755
+ org_id,
8756
+ project: { id: projectId, name: projectName, fullInfo: {} }
8757
+ };
8758
+ }
8681
8759
  }
8682
- })();
8760
+ );
8683
8761
  const ret = new Logger(lazyMetadata, {
8684
8762
  asyncFlush
8685
8763
  });
@@ -8885,15 +8963,15 @@ function validateAndSanitizeExperimentLogPartialArgs(event) {
8885
8963
  }
8886
8964
  }
8887
8965
  function validateAndSanitizeExperimentLogFullArgs(event, hasDataset) {
8888
- if ("input" in event && event.input && "inputs" in event && event.inputs || !("input" in event) && !("inputs" in event)) {
8966
+ if ("input" in event && !isEmpty(event.input) && "inputs" in event && !isEmpty(event.inputs) || !("input" in event) && !("inputs" in event)) {
8889
8967
  throw new Error(
8890
8968
  "Exactly one of input or inputs (deprecated) must be specified. Prefer input."
8891
8969
  );
8892
8970
  }
8893
- if (!event.output) {
8971
+ if (isEmpty(event.output)) {
8894
8972
  throw new Error("output must be specified");
8895
8973
  }
8896
- if (!event.scores) {
8974
+ if (isEmpty(event.scores)) {
8897
8975
  throw new Error("scores must be specified");
8898
8976
  }
8899
8977
  if (hasDataset && event.datasetRecordId === void 0) {
@@ -8905,33 +8983,88 @@ function validateAndSanitizeExperimentLogFullArgs(event, hasDataset) {
8905
8983
  }
8906
8984
  return event;
8907
8985
  }
8908
- var Experiment = class {
8986
+ var ObjectFetcher = class {
8987
+ constructor(objectType, pinnedVersion) {
8988
+ this.objectType = objectType;
8989
+ this.pinnedVersion = pinnedVersion;
8990
+ this._fetchedData = void 0;
8991
+ }
8992
+ get id() {
8993
+ throw new Error("ObjectFetcher subclasses must have an 'id' attribute");
8994
+ }
8995
+ async getState() {
8996
+ throw new Error("ObjectFetcher subclasses must have a 'getState' method");
8997
+ }
8998
+ async *fetch() {
8999
+ const records = await this.fetchedData();
9000
+ for (const record of records) {
9001
+ yield record;
9002
+ }
9003
+ }
9004
+ [Symbol.iterator]() {
9005
+ return this.fetch();
9006
+ }
9007
+ async fetchedData() {
9008
+ if (this._fetchedData === void 0) {
9009
+ const state = await this.getState();
9010
+ const resp = await state.logConn().get(`object/${this.objectType}`, {
9011
+ id: await this.id,
9012
+ fmt: "json2",
9013
+ version: this.pinnedVersion
9014
+ });
9015
+ this._fetchedData = await resp.json();
9016
+ }
9017
+ return this._fetchedData || [];
9018
+ }
9019
+ clearCache() {
9020
+ this._fetchedData = void 0;
9021
+ }
9022
+ async version() {
9023
+ if (this.pinnedVersion !== void 0) {
9024
+ return this.pinnedVersion;
9025
+ } else {
9026
+ const fetchedData = await this.fetchedData();
9027
+ let maxVersion = void 0;
9028
+ for (const record of fetchedData) {
9029
+ const xactId = record[TRANSACTION_ID_FIELD];
9030
+ if (maxVersion === void 0 || (xactId ?? xactId > maxVersion)) {
9031
+ maxVersion = xactId;
9032
+ }
9033
+ }
9034
+ return maxVersion;
9035
+ }
9036
+ }
9037
+ };
9038
+ var Experiment = class extends ObjectFetcher {
8909
9039
  constructor(lazyMetadata, dataset) {
9040
+ super("experiment", void 0);
8910
9041
  // For type identification.
8911
9042
  this.kind = "experiment";
8912
9043
  this.lazyMetadata = lazyMetadata;
8913
9044
  this.dataset = dataset;
8914
- const logConn = this.getState().then((state) => state.logConn());
9045
+ const logConn = new LazyValue(
9046
+ () => this.getState().then((state) => state.logConn())
9047
+ );
8915
9048
  this.bgLogger = new BackgroundLogger(logConn);
8916
9049
  this.lastStartTime = getCurrentUnixTimestamp();
8917
9050
  }
8918
9051
  get id() {
8919
9052
  return (async () => {
8920
- return (await this.lazyMetadata).experiment.id;
9053
+ return (await this.lazyMetadata.get()).experiment.id;
8921
9054
  })();
8922
9055
  }
8923
9056
  get name() {
8924
9057
  return (async () => {
8925
- return (await this.lazyMetadata).experiment.name;
9058
+ return (await this.lazyMetadata.get()).experiment.name;
8926
9059
  })();
8927
9060
  }
8928
9061
  get project() {
8929
9062
  return (async () => {
8930
- return (await this.lazyMetadata).project;
9063
+ return (await this.lazyMetadata.get()).project;
8931
9064
  })();
8932
9065
  }
8933
9066
  async getState() {
8934
- await this.lazyMetadata;
9067
+ await this.lazyMetadata.get();
8935
9068
  return _state;
8936
9069
  }
8937
9070
  /**
@@ -8991,12 +9124,32 @@ var Experiment = class {
8991
9124
  startSpan(args) {
8992
9125
  const { name, ...argsRest } = args ?? {};
8993
9126
  return new SpanImpl({
8994
- parentIds: this.lazyParentIds(),
9127
+ parentIds: new LazyValue(() => this.lazyParentIds()),
8995
9128
  bgLogger: this.bgLogger,
8996
9129
  name: name ?? "root",
8997
9130
  ...argsRest
8998
9131
  });
8999
9132
  }
9133
+ async fetchBaseExperiment() {
9134
+ const state = await this.getState();
9135
+ const conn = state.apiConn();
9136
+ try {
9137
+ const resp = await conn.post("/api/base_experiment/get_id", {
9138
+ id: await this.id
9139
+ });
9140
+ const base = await resp.json();
9141
+ return {
9142
+ id: base["base_exp_id"],
9143
+ name: base["base_exp_name"]
9144
+ };
9145
+ } catch (e) {
9146
+ if (e instanceof FailedHTTPResponse && e.status === 400) {
9147
+ return null;
9148
+ } else {
9149
+ throw e;
9150
+ }
9151
+ }
9152
+ }
9000
9153
  /**
9001
9154
  * Summarize the experiment, including the scores (compared to the closest reference experiment) and metadata.
9002
9155
  *
@@ -9020,14 +9173,10 @@ var Experiment = class {
9020
9173
  let comparisonExperimentName = void 0;
9021
9174
  if (summarizeScores) {
9022
9175
  if (comparisonExperimentId === void 0) {
9023
- const conn = state.logConn();
9024
- const resp = await conn.get("/crud/base_experiments", {
9025
- id: await this.id
9026
- });
9027
- const base_experiments = await resp.json();
9028
- if (base_experiments.length > 0) {
9029
- comparisonExperimentId = base_experiments[0]["base_exp_id"];
9030
- comparisonExperimentName = base_experiments[0]["base_exp_name"];
9176
+ const baseExperiment = await this.fetchBaseExperiment();
9177
+ if (baseExperiment !== null) {
9178
+ comparisonExperimentId = baseExperiment.id;
9179
+ comparisonExperimentName = baseExperiment.name;
9031
9180
  }
9032
9181
  }
9033
9182
  if (comparisonExperimentId !== void 0) {
@@ -9065,7 +9214,11 @@ var Experiment = class {
9065
9214
  * @param event.source (Optional) the source of the feedback. Must be one of "external" (default), "app", or "api".
9066
9215
  */
9067
9216
  logFeedback(event) {
9068
- logFeedbackImpl(this.bgLogger, this.lazyParentIds(), event);
9217
+ logFeedbackImpl(
9218
+ this.bgLogger,
9219
+ new LazyValue(() => this.lazyParentIds()),
9220
+ event
9221
+ );
9069
9222
  }
9070
9223
  /**
9071
9224
  * Flush any pending rows to the server.
@@ -9083,6 +9236,40 @@ var Experiment = class {
9083
9236
  return this.id;
9084
9237
  }
9085
9238
  };
9239
+ var ReadonlyExperiment = class extends ObjectFetcher {
9240
+ constructor(lazyMetadata) {
9241
+ super("experiment", void 0);
9242
+ this.lazyMetadata = lazyMetadata;
9243
+ }
9244
+ get id() {
9245
+ return (async () => {
9246
+ return (await this.lazyMetadata.get()).id;
9247
+ })();
9248
+ }
9249
+ get name() {
9250
+ return (async () => {
9251
+ return (await this.lazyMetadata.get()).name;
9252
+ })();
9253
+ }
9254
+ async getState() {
9255
+ await this.lazyMetadata.get();
9256
+ return _state;
9257
+ }
9258
+ async *asDataset() {
9259
+ const records = this.fetch();
9260
+ for await (const record of records) {
9261
+ if (record.root_span_id !== record.span_id) {
9262
+ continue;
9263
+ }
9264
+ const { output, expected } = record;
9265
+ yield {
9266
+ input: record.input,
9267
+ expected: expected ?? output
9268
+ };
9269
+ }
9270
+ }
9271
+ };
9272
+ var executionCounter = 0;
9086
9273
  var SpanImpl = class _SpanImpl {
9087
9274
  // root_experiment should only be specified for a root span. parent_span
9088
9275
  // should only be specified for non-root spans.
@@ -9108,7 +9295,11 @@ var SpanImpl = class _SpanImpl {
9108
9295
  start: args.startTime ?? getCurrentUnixTimestamp()
9109
9296
  },
9110
9297
  context: { ...callerLocation },
9111
- span_attributes: { ...args.spanAttributes, name },
9298
+ span_attributes: {
9299
+ ...args.spanAttributes,
9300
+ name,
9301
+ exec_counter: executionCounter++
9302
+ },
9112
9303
  created: (/* @__PURE__ */ new Date()).toISOString()
9113
9304
  };
9114
9305
  this.parentIds = args.parentIds;
@@ -9146,18 +9337,18 @@ var SpanImpl = class _SpanImpl {
9146
9337
  if (sanitizedAndInternalData.metrics?.end) {
9147
9338
  this.loggedEndTime = sanitizedAndInternalData.metrics?.end;
9148
9339
  }
9149
- const parentIds = (async () => {
9150
- const { kind, ...ids } = await this.parentIds;
9340
+ const parentIds = new LazyValue(async () => {
9341
+ const { kind, ...ids } = await this.parentIds.get();
9151
9342
  return ids;
9152
- })();
9153
- const record = (async () => {
9343
+ });
9344
+ const record = new LazyValue(async () => {
9154
9345
  return {
9155
9346
  ...sanitizedAndInternalData,
9156
9347
  ...this.rowIds,
9157
- ...await parentIds,
9348
+ ...await parentIds.get(),
9158
9349
  [IS_MERGE_FIELD]: this.isMerge
9159
9350
  };
9160
- })();
9351
+ });
9161
9352
  this.bgLogger.log([record]);
9162
9353
  }
9163
9354
  logFeedback(event) {
@@ -9206,31 +9397,32 @@ var SpanImpl = class _SpanImpl {
9206
9397
  return this.end(args);
9207
9398
  }
9208
9399
  };
9209
- var Dataset = class {
9400
+ var Dataset = class extends ObjectFetcher {
9210
9401
  constructor(lazyMetadata, pinnedVersion) {
9211
- this._fetchedData = void 0;
9402
+ super("dataset", pinnedVersion);
9212
9403
  this.lazyMetadata = lazyMetadata;
9213
- this.pinnedVersion = pinnedVersion;
9214
- const logConn = this.getState().then((state) => state.logConn());
9404
+ const logConn = new LazyValue(
9405
+ () => this.getState().then((state) => state.logConn())
9406
+ );
9215
9407
  this.bgLogger = new BackgroundLogger(logConn);
9216
9408
  }
9217
9409
  get id() {
9218
9410
  return (async () => {
9219
- return (await this.lazyMetadata).dataset.id;
9411
+ return (await this.lazyMetadata.get()).dataset.id;
9220
9412
  })();
9221
9413
  }
9222
9414
  get name() {
9223
9415
  return (async () => {
9224
- return (await this.lazyMetadata).dataset.name;
9416
+ return (await this.lazyMetadata.get()).dataset.name;
9225
9417
  })();
9226
9418
  }
9227
9419
  get project() {
9228
9420
  return (async () => {
9229
- return (await this.lazyMetadata).project;
9421
+ return (await this.lazyMetadata.get()).project;
9230
9422
  })();
9231
9423
  }
9232
9424
  async getState() {
9233
- await this.lazyMetadata;
9425
+ await this.lazyMetadata.get();
9234
9426
  return _state;
9235
9427
  }
9236
9428
  /**
@@ -9261,7 +9453,7 @@ var Dataset = class {
9261
9453
  }
9262
9454
  }
9263
9455
  const rowId = id || v4_default();
9264
- const args = (async () => ({
9456
+ const args = new LazyValue(async () => ({
9265
9457
  id: rowId,
9266
9458
  inputs: input,
9267
9459
  output,
@@ -9269,18 +9461,18 @@ var Dataset = class {
9269
9461
  dataset_id: await this.id,
9270
9462
  created: (/* @__PURE__ */ new Date()).toISOString(),
9271
9463
  metadata
9272
- }))();
9464
+ }));
9273
9465
  this.bgLogger.log([args]);
9274
9466
  return rowId;
9275
9467
  }
9276
9468
  delete(id) {
9277
- const args = (async () => ({
9469
+ const args = new LazyValue(async () => ({
9278
9470
  id,
9279
9471
  project_id: (await this.project).id,
9280
9472
  dataset_id: await this.id,
9281
9473
  created: (/* @__PURE__ */ new Date()).toISOString(),
9282
9474
  _object_delete: true
9283
- }))();
9475
+ }));
9284
9476
  this.bgLogger.log([args]);
9285
9477
  return id;
9286
9478
  }
@@ -9316,81 +9508,6 @@ var Dataset = class {
9316
9508
  dataSummary
9317
9509
  };
9318
9510
  }
9319
- /**
9320
- * Fetch all records in the dataset.
9321
- *
9322
- * @example
9323
- * ```
9324
- * // Use an async iterator to fetch all records in the dataset.
9325
- * for await (const record of dataset.fetch()) {
9326
- * console.log(record);
9327
- * }
9328
- *
9329
- * // You can also iterate over the dataset directly.
9330
- * for await (const record of dataset) {
9331
- * console.log(record);
9332
- * }
9333
- * ```
9334
- *
9335
- * @returns An iterator over the dataset's records.
9336
- */
9337
- async *fetch() {
9338
- const records = await this.fetchedData();
9339
- for (const record of records) {
9340
- yield {
9341
- id: record.id,
9342
- input: record.input && JSON.parse(record.input),
9343
- output: record.input && JSON.parse(record.output),
9344
- metadata: record.metadata && JSON.parse(record.metadata)
9345
- };
9346
- }
9347
- this.clearCache();
9348
- }
9349
- /**
9350
- * Fetch all records in the dataset.
9351
- *
9352
- * @example
9353
- * ```
9354
- * // Use an async iterator to fetch all records in the dataset.
9355
- * for await (const record of dataset) {
9356
- * console.log(record);
9357
- * }
9358
- * ```
9359
- */
9360
- [Symbol.asyncIterator]() {
9361
- return this.fetch();
9362
- }
9363
- async fetchedData() {
9364
- if (this._fetchedData === void 0) {
9365
- const state = await this.getState();
9366
- const resp = await state.logConn().get("object/dataset", {
9367
- id: await this.id,
9368
- fmt: "json",
9369
- version: this.pinnedVersion
9370
- });
9371
- const text = await resp.text();
9372
- this._fetchedData = text.split("\n").filter((x) => x.trim() !== "").map((x) => JSON.parse(x));
9373
- }
9374
- return this._fetchedData || [];
9375
- }
9376
- clearCache() {
9377
- this._fetchedData = void 0;
9378
- }
9379
- async version() {
9380
- if (this.pinnedVersion !== void 0) {
9381
- return this.pinnedVersion;
9382
- } else {
9383
- const fetchedData = await this.fetchedData();
9384
- let maxVersion = void 0;
9385
- for (const record of fetchedData) {
9386
- const xactId = record[TRANSACTION_ID_FIELD];
9387
- if (maxVersion === void 0 || (xactId ?? xactId > maxVersion)) {
9388
- maxVersion = xactId;
9389
- }
9390
- }
9391
- return maxVersion;
9392
- }
9393
- }
9394
9511
  /**
9395
9512
  * Flush any pending rows to the server.
9396
9513
  */
@@ -9462,6 +9579,9 @@ var BarProgressReporter = class {
9462
9579
 
9463
9580
  // src/framework.ts
9464
9581
  var import_pluralize = __toESM(require_pluralize());
9582
+ function BaseExperiment(options = {}) {
9583
+ return { _type: "BaseExperiment", ...options };
9584
+ }
9465
9585
  function makeEvalName(projectName, experimentName) {
9466
9586
  let out = projectName;
9467
9587
  if (experimentName) {
@@ -9469,6 +9589,12 @@ function makeEvalName(projectName, experimentName) {
9469
9589
  }
9470
9590
  return out;
9471
9591
  }
9592
+ function initExperiment(projectName, options = {}) {
9593
+ return init(projectName, {
9594
+ ...options,
9595
+ setCurrent: false
9596
+ });
9597
+ }
9472
9598
  globalThis._evals = {};
9473
9599
  async function Eval(name, evaluator) {
9474
9600
  const evalName = makeEvalName(name, evaluator.experimentName);
@@ -9477,15 +9603,22 @@ async function Eval(name, evaluator) {
9477
9603
  }
9478
9604
  if (globalThis._lazy_load) {
9479
9605
  _evals[evalName] = { evalName, projectName: name, ...evaluator };
9480
- return;
9606
+ return {
9607
+ projectName: "_lazy_load",
9608
+ experimentName: "_lazy_load",
9609
+ projectUrl: "",
9610
+ experimentUrl: "",
9611
+ comparisonExperimentName: "",
9612
+ scores: {},
9613
+ metrics: {}
9614
+ };
9481
9615
  }
9482
9616
  const progressReporter = new BarProgressReporter();
9483
9617
  try {
9484
- const experiment = init(name, {
9618
+ const experiment = initExperiment(name, {
9485
9619
  experiment: evaluator.experimentName,
9486
9620
  metadata: evaluator.metadata,
9487
- isPublic: evaluator.isPublic,
9488
- setCurrent: false
9621
+ isPublic: evaluator.isPublic
9489
9622
  });
9490
9623
  try {
9491
9624
  const ret = await runEvaluator(
@@ -9526,10 +9659,37 @@ async function runEvaluator(experiment, evaluator, progressReporter, filters) {
9526
9659
  if (typeof evaluator.data === "string") {
9527
9660
  throw new Error("Unimplemented: string data paths");
9528
9661
  }
9529
- const dataResult = evaluator.data();
9530
- let data = null;
9662
+ let dataResult = typeof evaluator.data === "function" ? evaluator.data() : evaluator.data;
9663
+ if ("_type" in dataResult) {
9664
+ if (dataResult._type !== "BaseExperiment") {
9665
+ throw new Error("Invalid _type");
9666
+ }
9667
+ if (!experiment) {
9668
+ throw new Error(
9669
+ "Cannot use BaseExperiment() without connecting to Braintrust (you most likely set --no-send-logs)"
9670
+ );
9671
+ }
9672
+ let name = dataResult.name;
9673
+ if (isEmpty(name)) {
9674
+ const baseExperiment = await experiment.fetchBaseExperiment();
9675
+ if (!baseExperiment) {
9676
+ throw new Error("BaseExperiment() failed to fetch base experiment");
9677
+ }
9678
+ name = baseExperiment.name;
9679
+ }
9680
+ dataResult = initExperiment(evaluator.projectName, {
9681
+ experiment: name,
9682
+ open: true
9683
+ }).asDataset();
9684
+ }
9685
+ let data = [];
9531
9686
  if (dataResult instanceof Promise) {
9532
9687
  data = await dataResult;
9688
+ } else if (Symbol.asyncIterator in dataResult) {
9689
+ data = [];
9690
+ for await (const d of dataResult) {
9691
+ data.push(d);
9692
+ }
9533
9693
  } else {
9534
9694
  data = dataResult;
9535
9695
  }
@@ -9942,12 +10102,14 @@ var WrapperStream = class {
9942
10102
  configureNode();
9943
10103
  // Annotate the CommonJS export names for ESM import in node:
9944
10104
  0 && (module.exports = {
10105
+ BaseExperiment,
9945
10106
  Dataset,
9946
10107
  Eval,
9947
10108
  Experiment,
9948
10109
  Logger,
9949
10110
  NOOP_SPAN,
9950
10111
  NoopSpan,
10112
+ ReadonlyExperiment,
9951
10113
  SpanImpl,
9952
10114
  _internalGetGlobalState,
9953
10115
  _internalSetInitialState,