braintrust 0.3.12__py3-none-any.whl → 0.3.13__py3-none-any.whl

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.
braintrust/bt_json.py CHANGED
@@ -2,27 +2,115 @@ import dataclasses
2
2
  import json
3
3
  from typing import Any, cast
4
4
 
5
+ # Try to import orjson for better performance
6
+ # If not available, we'll use standard json
7
+ try:
8
+ import orjson
9
+
10
+ _HAS_ORJSON = True
11
+ except ImportError:
12
+ _HAS_ORJSON = False
13
+
14
+
15
+ def _to_dict(obj: Any) -> Any:
16
+ """
17
+ Function-based default handler for non-JSON-serializable objects.
18
+
19
+ Handles:
20
+ - dataclasses
21
+ - Pydantic v2 BaseModel
22
+ - Pydantic v1 BaseModel
23
+ - Falls back to str() for unknown types
24
+ """
25
+ if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
26
+ return dataclasses.asdict(obj)
27
+
28
+ # Attempt to dump a Pydantic v2 `BaseModel`.
29
+ try:
30
+ return cast(Any, obj).model_dump()
31
+ except (AttributeError, TypeError):
32
+ pass
33
+
34
+ # Attempt to dump a Pydantic v1 `BaseModel`.
35
+ try:
36
+ return cast(Any, obj).dict()
37
+ except (AttributeError, TypeError):
38
+ pass
39
+
40
+ # When everything fails, try to return the string representation of the object
41
+ try:
42
+ return str(obj)
43
+ except Exception:
44
+ # If str() fails, return an error placeholder
45
+ return f"<non-serializable: {type(obj).__name__}>"
46
+
5
47
 
6
48
  class BraintrustJSONEncoder(json.JSONEncoder):
49
+ """
50
+ Custom JSON encoder for standard json library.
51
+
52
+ This is used as a fallback when orjson is not available or fails.
53
+ """
54
+
7
55
  def default(self, o: Any):
8
- if dataclasses.is_dataclass(o) and not isinstance(o, type):
9
- return dataclasses.asdict(o)
56
+ return _to_dict(o)
10
57
 
11
- # Attempt to dump a Pydantic v2 `BaseModel`.
12
- try:
13
- return cast(Any, o).model_dump()
14
- except (AttributeError, TypeError):
15
- pass
16
58
 
17
- # Attempt to dump a Pydantic v1 `BaseModel`.
59
+ def bt_dumps(obj, **kwargs) -> str:
60
+ """
61
+ Serialize obj to a JSON-formatted string.
62
+
63
+ Automatically uses orjson if available for better performance (3-5x faster),
64
+ with fallback to standard json library if orjson is not installed or fails.
65
+
66
+ Args:
67
+ obj: Object to serialize
68
+ **kwargs: Additional arguments (passed to json.dumps in fallback path)
69
+
70
+ Returns:
71
+ JSON string representation of obj
72
+ """
73
+ if _HAS_ORJSON:
74
+ # Try orjson first for better performance
18
75
  try:
19
- return cast(Any, o).dict()
20
- except (AttributeError, TypeError):
76
+ # pylint: disable=no-member # orjson is a C extension, pylint can't introspect it
77
+ return orjson.dumps( # type: ignore[possibly-unbound]
78
+ obj,
79
+ default=_to_dict,
80
+ # options match json.dumps behavior for bc
81
+ option=orjson.OPT_SORT_KEYS | orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NON_STR_KEYS, # type: ignore[possibly-unbound]
82
+ ).decode("utf-8")
83
+ except Exception:
84
+ # If orjson fails, fall back to standard json
21
85
  pass
22
86
 
23
- # When everything fails, just return the string representation of the object
24
- return str(o)
87
+ # Use standard json (either orjson not available or it failed)
88
+ # Use sort_keys=True for deterministic output (matches orjson OPT_SORT_KEYS)
89
+ return json.dumps(obj, cls=BraintrustJSONEncoder, allow_nan=False, sort_keys=True, **kwargs)
25
90
 
26
91
 
27
- def bt_dumps(obj, **kwargs) -> str:
28
- return json.dumps(obj, cls=BraintrustJSONEncoder, allow_nan=False, **kwargs)
92
+ def bt_loads(s: str, **kwargs) -> Any:
93
+ """
94
+ Deserialize s (a str containing a JSON document) to a Python object.
95
+
96
+ Automatically uses orjson if available for better performance (2-3x faster),
97
+ with fallback to standard json library if orjson is not installed or fails.
98
+
99
+ Args:
100
+ s: JSON string to deserialize
101
+ **kwargs: Additional arguments (passed to json.loads in fallback path)
102
+
103
+ Returns:
104
+ Python object representation of JSON string
105
+ """
106
+ if _HAS_ORJSON:
107
+ # Try orjson first for better performance
108
+ try:
109
+ # pylint: disable=no-member # orjson is a C extension, pylint can't introspect it
110
+ return orjson.loads(s) # type: ignore[possibly-unbound]
111
+ except Exception:
112
+ # If orjson fails, fall back to standard json
113
+ pass
114
+
115
+ # Use standard json (either orjson not available or it failed)
116
+ return json.loads(s, **kwargs)
braintrust/logger.py CHANGED
@@ -9,6 +9,7 @@ import inspect
9
9
  import io
10
10
  import json
11
11
  import logging
12
+ import math
12
13
  import os
13
14
  import sys
14
15
  import textwrap
@@ -53,7 +54,7 @@ from requests.adapters import HTTPAdapter
53
54
  from urllib3.util.retry import Retry
54
55
 
55
56
  from . import context, id_gen
56
- from .bt_json import bt_dumps
57
+ from .bt_json import bt_dumps, bt_loads
57
58
  from .db_fields import (
58
59
  ASYNC_SCORING_CONTROL_FIELD,
59
60
  AUDIT_METADATA_FIELD,
@@ -2471,7 +2472,15 @@ def _deep_copy_event(event: Mapping[str, Any]) -> Dict[str, Any]:
2471
2472
  # `json.dumps`. However, that runs at log upload time, while we want to
2472
2473
  # cut out all the references to user objects synchronously in this
2473
2474
  # function.
2474
- return {str(k): _deep_copy_object(v[k], depth + 1) for k in v}
2475
+ result = {}
2476
+ for k in v:
2477
+ try:
2478
+ key_str = str(k)
2479
+ except Exception:
2480
+ # If str() fails on the key, use a fallback representation
2481
+ key_str = f"<non-stringifiable-key: {type(k).__name__}>"
2482
+ result[key_str] = _deep_copy_object(v[k], depth + 1)
2483
+ return result
2475
2484
  elif isinstance(v, (List, Tuple, Set)):
2476
2485
  return [_deep_copy_object(x, depth + 1) for x in v]
2477
2486
  finally:
@@ -2491,7 +2500,14 @@ def _deep_copy_event(event: Mapping[str, Any]) -> Dict[str, Any]:
2491
2500
  return v
2492
2501
  elif isinstance(v, ReadonlyAttachment):
2493
2502
  return v.reference
2494
- elif isinstance(v, (int, float, str, bool)) or v is None:
2503
+ elif isinstance(v, float):
2504
+ # Handle NaN and Infinity for JSON compatibility
2505
+ if math.isnan(v):
2506
+ return "NaN"
2507
+ elif math.isinf(v):
2508
+ return "Infinity" if v > 0 else "-Infinity"
2509
+ return v
2510
+ elif isinstance(v, (int, str, bool)) or v is None:
2495
2511
  # Skip roundtrip for primitive types.
2496
2512
  return v
2497
2513
  else:
@@ -2500,7 +2516,7 @@ def _deep_copy_event(event: Mapping[str, Any]) -> Dict[str, Any]:
2500
2516
  # E.g. the original type could have a `__del__` method that alters
2501
2517
  # some shared internal state, and we need this deep copy to be
2502
2518
  # fully-independent from the original.
2503
- return json.loads(bt_dumps(v))
2519
+ return bt_loads(bt_dumps(v))
2504
2520
 
2505
2521
  return _deep_copy_object(event)
2506
2522
 
@@ -2523,7 +2539,7 @@ class ObjectIterator(Generic[T]):
2523
2539
  return value
2524
2540
 
2525
2541
 
2526
- INTERNAL_BTQL_LIMIT = 1000
2542
+ DEFAULT_FETCH_BATCH_SIZE = 1000
2527
2543
  MAX_BTQL_ITERATIONS = 10000
2528
2544
 
2529
2545
 
@@ -2550,7 +2566,7 @@ class ObjectFetcher(ABC, Generic[TMapping]):
2550
2566
  self._fetched_data: Optional[List[TMapping]] = None
2551
2567
  self._internal_btql = _internal_btql
2552
2568
 
2553
- def fetch(self) -> Iterator[TMapping]:
2569
+ def fetch(self, batch_size: Optional[int] = None) -> Iterator[TMapping]:
2554
2570
  """
2555
2571
  Fetch all records.
2556
2572
 
@@ -2563,9 +2579,10 @@ class ObjectFetcher(ABC, Generic[TMapping]):
2563
2579
  print(record)
2564
2580
  ```
2565
2581
 
2582
+ :param batch_size: The number of records to fetch per request. Defaults to 1000.
2566
2583
  :returns: An iterator over the records.
2567
2584
  """
2568
- return ObjectIterator(self._refetch)
2585
+ return ObjectIterator(lambda: self._refetch(batch_size=batch_size))
2569
2586
 
2570
2587
  def __iter__(self) -> Iterator[TMapping]:
2571
2588
  return self.fetch()
@@ -2584,8 +2601,9 @@ class ObjectFetcher(ABC, Generic[TMapping]):
2584
2601
  @abstractmethod
2585
2602
  def id(self) -> str: ...
2586
2603
 
2587
- def _refetch(self) -> List[TMapping]:
2604
+ def _refetch(self, batch_size: Optional[int] = None) -> List[TMapping]:
2588
2605
  state = self._get_state()
2606
+ limit = batch_size if batch_size is not None else DEFAULT_FETCH_BATCH_SIZE
2589
2607
  if self._fetched_data is None:
2590
2608
  cursor = None
2591
2609
  data = None
@@ -2610,7 +2628,7 @@ class ObjectFetcher(ABC, Generic[TMapping]):
2610
2628
  ],
2611
2629
  },
2612
2630
  "cursor": cursor,
2613
- "limit": INTERNAL_BTQL_LIMIT,
2631
+ "limit": limit,
2614
2632
  **(self._internal_btql or {}),
2615
2633
  },
2616
2634
  "use_columnstore": False,
@@ -3761,8 +3779,14 @@ class ReadonlyExperiment(ObjectFetcher[ExperimentEvent]):
3761
3779
  self._lazy_metadata.get()
3762
3780
  return self.state
3763
3781
 
3764
- def as_dataset(self) -> Iterator[_ExperimentDatasetEvent]:
3765
- return ExperimentDatasetIterator(self.fetch())
3782
+ def as_dataset(self, batch_size: Optional[int] = None) -> Iterator[_ExperimentDatasetEvent]:
3783
+ """
3784
+ Return the experiment's data as a dataset iterator.
3785
+
3786
+ :param batch_size: The number of records to fetch per request. Defaults to 1000.
3787
+ :returns: An iterator over the experiment data as dataset records.
3788
+ """
3789
+ return ExperimentDatasetIterator(self.fetch(batch_size=batch_size))
3766
3790
 
3767
3791
 
3768
3792
  _EXEC_COUNTER_LOCK = threading.Lock()
braintrust/test_logger.py CHANGED
@@ -716,6 +716,107 @@ def test_span_log_with_large_document_many_pages(with_memory_logger):
716
716
  assert logged_output["pages"][0]["lines"][0]["words"][0]["content"] == "word_0"
717
717
 
718
718
 
719
+ def test_span_log_handles_nan_gracefully(with_memory_logger):
720
+ """Test that span.log() handles NaN values by converting them to "NaN" string."""
721
+ logger = init_test_logger(__name__)
722
+
723
+ with logger.start_span(name="test_span") as span:
724
+ # Should NOT raise - should handle NaN gracefully
725
+ span.log(
726
+ input={"test": "input"},
727
+ output={"value": float("nan")},
728
+ )
729
+
730
+ # Verify the log was recorded with NaN handled appropriately
731
+ logs = with_memory_logger.pop()
732
+ assert len(logs) == 1
733
+ assert logs[0]["input"]["test"] == "input"
734
+ # NaN should be converted to "NaN" string for JSON compatibility
735
+ output_value = logs[0]["output"]["value"]
736
+ assert output_value == "NaN"
737
+
738
+
739
+ def test_span_log_handles_infinity_gracefully(with_memory_logger):
740
+ """Test that span.log() handles Infinity values by converting them to "Infinity"/"-Infinity" strings."""
741
+ logger = init_test_logger(__name__)
742
+
743
+ with logger.start_span(name="test_span") as span:
744
+ # Should NOT raise - should handle Infinity gracefully
745
+ span.log(
746
+ input={"test": "input"},
747
+ output={"value": float("inf"), "neg": float("-inf")},
748
+ )
749
+
750
+ # Verify the log was recorded with Infinity handled appropriately
751
+ logs = with_memory_logger.pop()
752
+ assert len(logs) == 1
753
+ assert logs[0]["input"]["test"] == "input"
754
+ # Infinity should be converted to string representations for JSON compatibility
755
+ assert logs[0]["output"]["value"] == "Infinity"
756
+ assert logs[0]["output"]["neg"] == "-Infinity"
757
+
758
+
759
+ def test_span_log_handles_unstringifiable_object_gracefully(with_memory_logger):
760
+ """Test that span.log() should handle objects with bad __str__ gracefully without raising.
761
+
762
+ This test currently FAILS - it demonstrates the desired behavior after the fix.
763
+ """
764
+ logger = init_test_logger(__name__)
765
+
766
+ class BadStrObject:
767
+ def __str__(self):
768
+ raise RuntimeError("Cannot convert to string!")
769
+
770
+ def __repr__(self):
771
+ raise RuntimeError("Cannot convert to repr!")
772
+
773
+ with logger.start_span(name="test_span") as span:
774
+ # Should NOT raise - should handle gracefully
775
+ span.log(
776
+ input={"test": "input"},
777
+ output={"result": BadStrObject()},
778
+ )
779
+
780
+ # Verify the log was recorded with a fallback representation
781
+ logs = with_memory_logger.pop()
782
+ assert len(logs) == 1
783
+ assert logs[0]["input"]["test"] == "input"
784
+ # The bad object should have been replaced with some error placeholder
785
+ assert "result" in logs[0]["output"]
786
+ output_str = str(logs[0]["output"]["result"])
787
+ # Should contain some indication of serialization failure
788
+ assert "error" in output_str.lower() or "serializ" in output_str.lower()
789
+
790
+
791
+ def test_span_log_handles_bad_dict_keys_gracefully(with_memory_logger):
792
+ """Test that span.log() should handle non-stringifiable dict keys gracefully.
793
+
794
+ This test currently FAILS - it demonstrates the desired behavior after the fix.
795
+ """
796
+ logger = init_test_logger(__name__)
797
+
798
+ class BadKey:
799
+ def __str__(self):
800
+ raise ValueError("Key cannot be stringified!")
801
+
802
+ def __repr__(self):
803
+ raise ValueError("Key cannot be stringified!")
804
+
805
+ with logger.start_span(name="test_span") as span:
806
+ # Should NOT raise - should handle gracefully
807
+ span.log(
808
+ input={"test": "input"},
809
+ output={BadKey(): "value"},
810
+ )
811
+
812
+ # Verify the log was recorded with the problematic key handled
813
+ logs = with_memory_logger.pop()
814
+ assert len(logs) == 1
815
+ assert logs[0]["input"]["test"] == "input"
816
+ # The output should exist but the bad key should be replaced
817
+ assert "output" in logs[0]
818
+
819
+
719
820
  def test_span_link_logged_out(with_memory_logger):
720
821
  simulate_logout()
721
822
  assert_logged_out()
@@ -2491,7 +2592,7 @@ class TestDatasetInternalBtql(TestCase):
2491
2592
 
2492
2593
  @patch("braintrust.logger.BraintrustState")
2493
2594
  def test_dataset_internal_btql_limit_not_overwritten(self, mock_state_class):
2494
- """Test that custom limit in _internal_btql is not overwritten by INTERNAL_BTQL_LIMIT."""
2595
+ """Test that custom limit in _internal_btql is not overwritten by DEFAULT_FETCH_BATCH_SIZE."""
2495
2596
  # Set up mock state
2496
2597
  mock_state = MagicMock()
2497
2598
  mock_state_class.return_value = mock_state
@@ -2538,7 +2639,7 @@ class TestDatasetInternalBtql(TestCase):
2538
2639
  call_args = mock_api_conn.post.call_args
2539
2640
  query_json = call_args[1]["json"]["query"]
2540
2641
 
2541
- # Verify that the custom limit is present (not overwritten by INTERNAL_BTQL_LIMIT)
2642
+ # Verify that the custom limit is present (not overwritten by DEFAULT_FETCH_BATCH_SIZE)
2542
2643
  self.assertEqual(query_json["limit"], custom_limit)
2543
2644
 
2544
2645
  # Verify that other _internal_btql fields are also present
@@ -2546,8 +2647,14 @@ class TestDatasetInternalBtql(TestCase):
2546
2647
 
2547
2648
  @patch("braintrust.logger.BraintrustState")
2548
2649
  def test_dataset_default_limit_when_not_specified(self, mock_state_class):
2549
- """Test that INTERNAL_BTQL_LIMIT is used when no custom limit is specified."""
2550
- from braintrust.logger import INTERNAL_BTQL_LIMIT, Dataset, LazyValue, ObjectMetadata, ProjectDatasetMetadata
2650
+ """Test that DEFAULT_FETCH_BATCH_SIZE is used when no custom limit is specified."""
2651
+ from braintrust.logger import (
2652
+ DEFAULT_FETCH_BATCH_SIZE,
2653
+ Dataset,
2654
+ LazyValue,
2655
+ ObjectMetadata,
2656
+ ProjectDatasetMetadata,
2657
+ )
2551
2658
 
2552
2659
  # Set up mock state
2553
2660
  mock_state = MagicMock()
@@ -2590,4 +2697,52 @@ class TestDatasetInternalBtql(TestCase):
2590
2697
  query_json = call_args[1]["json"]["query"]
2591
2698
 
2592
2699
  # Verify that the default limit is used
2593
- self.assertEqual(query_json["limit"], INTERNAL_BTQL_LIMIT)
2700
+ self.assertEqual(query_json["limit"], DEFAULT_FETCH_BATCH_SIZE)
2701
+
2702
+ @patch("braintrust.logger.BraintrustState")
2703
+ def test_dataset_custom_batch_size_in_fetch(self, mock_state_class):
2704
+ """Test that custom batch_size in fetch() is properly passed to BTQL query."""
2705
+ from braintrust.logger import Dataset, LazyValue, ObjectMetadata, ProjectDatasetMetadata
2706
+
2707
+ # Set up mock state
2708
+ mock_state = MagicMock()
2709
+ mock_state_class.return_value = mock_state
2710
+
2711
+ # Mock the API connection and response
2712
+ mock_api_conn = MagicMock()
2713
+ mock_state.api_conn.return_value = mock_api_conn
2714
+
2715
+ # Mock response object
2716
+ mock_response = MagicMock()
2717
+ mock_response.json.return_value = {
2718
+ "data": [{"id": "1", "input": "test1", "expected": "output1"}],
2719
+ "cursor": None,
2720
+ }
2721
+ mock_api_conn.post.return_value = mock_response
2722
+
2723
+ # Create dataset
2724
+ project_metadata = ObjectMetadata(id="test-project", name="test-project", full_info={})
2725
+ dataset_metadata = ObjectMetadata(id="test-dataset", name="test-dataset", full_info={})
2726
+ lazy_metadata = LazyValue(
2727
+ lambda: ProjectDatasetMetadata(project=project_metadata, dataset=dataset_metadata),
2728
+ use_mutex=False,
2729
+ )
2730
+
2731
+ dataset = Dataset(
2732
+ lazy_metadata=lazy_metadata,
2733
+ state=mock_state,
2734
+ )
2735
+
2736
+ # Trigger a fetch with custom batch_size
2737
+ custom_batch_size = 250
2738
+ list(dataset.fetch(batch_size=custom_batch_size))
2739
+
2740
+ # Verify the API was called
2741
+ mock_api_conn.post.assert_called_once()
2742
+
2743
+ # Get the actual call arguments
2744
+ call_args = mock_api_conn.post.call_args
2745
+ query_json = call_args[1]["json"]["query"]
2746
+
2747
+ # Verify that the custom batch_size is used
2748
+ self.assertEqual(query_json["limit"], custom_batch_size)
braintrust/version.py CHANGED
@@ -1,4 +1,4 @@
1
- VERSION = "0.3.12"
1
+ VERSION = "0.3.13"
2
2
 
3
3
  # this will be templated during the build
4
- GIT_COMMIT = "8c2b2995c2097f5699c101f4ab5e653ff40014a1"
4
+ GIT_COMMIT = "cef88a007fa60f4cd873f1d891a54ce5e173f3aa"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braintrust
3
- Version: 0.3.12
3
+ Version: 0.3.13
4
4
  Summary: SDK for integrating Braintrust
5
5
  Home-page: https://www.braintrust.dev
6
6
  Author: Braintrust
@@ -75,6 +75,20 @@ Install the library with pip.
75
75
  pip install braintrust
76
76
  ```
77
77
 
78
+ **Performance tip**: For 3-5x faster JSON serialization, install with the optional `performance` extra:
79
+
80
+ ```bash
81
+ pip install braintrust[performance]
82
+ ```
83
+
84
+ Or install `orjson` separately:
85
+
86
+ ```bash
87
+ pip install orjson
88
+ ```
89
+
90
+ The SDK automatically detects and uses orjson if available, with seamless fallback to standard json. See [ORJSON_OPTIMIZATION.md](ORJSON_OPTIMIZATION.md) for details.
91
+
78
92
  Then, run a simple experiment with the following code (replace `YOUR_API_KEY` with
79
93
  your Braintrust API key):
80
94
 
@@ -100,3 +114,7 @@ Eval(
100
114
  scores=[is_equal],
101
115
  )
102
116
  ```
117
+
118
+ # Performance Optimization
119
+
120
+ For 3-5x faster JSON serialization, install `orjson`. The SDK automatically detects and uses orjson if available, with seamless fallback to standard json.
@@ -2,7 +2,7 @@ braintrust/__init__.py,sha256=-NLWOaTdzVtQFu2TA0qYULbxP4pAdVdgqzZWosqL2eI,2092
2
2
  braintrust/_generated_types.py,sha256=X5UGdtWp9vnoyUd4PyY74xjWIoNRVqiqWHNpYd-v0to,80911
3
3
  braintrust/audit.py,sha256=Na3LJhpHj8Nd1az41HMQLLbHeWQkDZIOYHLLmVZdAdQ,467
4
4
  braintrust/aws.py,sha256=OBz_SRyopgpCDSNvETLypzGwTXk-bNLn-Eisevnjfwo,377
5
- braintrust/bt_json.py,sha256=NrpEpl7FkwgL3sSg_XoKCR8HYy9uEiBOfaJ4r7HkW2U,816
5
+ braintrust/bt_json.py,sha256=f2AkpVxT2LY3PZliJn-jH_1Qd9nujnSJcFronTdlsBE,3650
6
6
  braintrust/conftest.py,sha256=EZi9aU9g8O6XWwPP6EMv5OcDLkVwI5he_TTGM4jCMlQ,1598
7
7
  braintrust/context.py,sha256=ZXIOc3zXIXOzAopkuPIM9tq2prfsTjH0-e_FEEFTTJI,4235
8
8
  braintrust/db_fields.py,sha256=DBGFhfu9B3aLQI6cU6P2WGrdfCIs8AzDFRQEUBt9NCw,439
@@ -14,7 +14,7 @@ braintrust/gitutil.py,sha256=bEk38AlNtT-umtdCJ9lnSXlbKXsvjBOyTTsmzUKiVtM,5586
14
14
  braintrust/graph_util.py,sha256=lABIOMzxHf6E5LfDYfqa4OUR4uaW7xgUYNq5WGewD4w,5594
15
15
  braintrust/http_headers.py,sha256=9ZsDcsAKG04SGowsgchZktD6rG_oSTKWa8QyGUPA4xE,154
16
16
  braintrust/id_gen.py,sha256=PVkz-pS-9AzgmnAgpV-jgOFFo4hfl6e3IP9dVt6FouQ,1595
17
- braintrust/logger.py,sha256=qIElwbRqzT_3mrQ1XiS7razFxADjL6775eukwkiIhA0,209333
17
+ braintrust/logger.py,sha256=NTbxgXa_qsu7bwl4bUoLuj9HNDuaGQiDlu0-addkAQc,210560
18
18
  braintrust/merge_row_batch.py,sha256=NX4jRE9uuFB3Z7btrarQp_di84_NGTjvzpJhksn82W8,9882
19
19
  braintrust/oai.py,sha256=DW_i6bXXDprwZ_uwcdIE25MUxx5y5DDdqr0ucdIWP9I,33187
20
20
  braintrust/object.py,sha256=vYLyYWncsqLD00zffZUJwGTSkcJF9IIXmgIzrx3Np5c,632
@@ -34,7 +34,7 @@ braintrust/test_framework.py,sha256=fALvUefSmNOdQcEVgKHvdCOnPlUreZjhF5AiqfNLBPg,
34
34
  braintrust/test_framework2.py,sha256=pSEEmBIyszAiYnpEVvDZgJqIe3lQ3T807LmIuBqV98w,7235
35
35
  braintrust/test_helpers.py,sha256=-RrxDtyCfOOukhdN6tFSwr-Nmq7DrNQ7KO45hySqNZ0,13549
36
36
  braintrust/test_id_gen.py,sha256=mgArTyEBV-Xv21ARHPSHEPBsJshZrvIiPjBLNOKsAko,2490
37
- braintrust/test_logger.py,sha256=IgsfNUvztNqUd6NL4fh_IYsW44dkGxneiHsTxYSFozY,92846
37
+ braintrust/test_logger.py,sha256=Z3euqSHmc4kqsyKo838tkYGK8B1rTkIdxl7QZYR1CLk,98516
38
38
  braintrust/test_otel.py,sha256=janmEtu6dyFQL8N68WjRTS-65Kr5vSNSSwIaBWqXlyw,27243
39
39
  braintrust/test_queue.py,sha256=MdH6R9uSk_4akY4Db514Cpukwiy2RJ76Lqts1nQwZJY,8432
40
40
  braintrust/test_serializable_data_class.py,sha256=b04Ym64YtC6GJRGbKIN4J20RG1QN1FlnODwtEQh4sv0,1897
@@ -42,7 +42,7 @@ braintrust/test_span_components.py,sha256=UnF6ZL4k41XZ-CnfbjuqLeK4MZLtHTMdID3CMh
42
42
  braintrust/test_util.py,sha256=gyqe2JspRP7oXlp6ENztZe2fdRTOEMZMKpQi00y1DSc,4538
43
43
  braintrust/test_version.py,sha256=hk5JKjEFbNJ_ONc1VEkqHquflzre34RpFhCEYLTK8iA,1051
44
44
  braintrust/util.py,sha256=Ec6sRkQw5BckGrFjdA4YTyu_2BaKmHh4tWDwAi_ysOw,7227
45
- braintrust/version.py,sha256=i1ljewUfl4VA2SYv_e8CfC5C1RUiCNBL8mOBlRusJHk,118
45
+ braintrust/version.py,sha256=YyNQY-UJ0O_hVg8XR4VJGGwb-qChpEoYLN6jfWC4rEQ,118
46
46
  braintrust/xact_ids.py,sha256=bdyp88HjlyIkglgLSqYlCYscdSH6EWVyE14sR90Xl1s,658
47
47
  braintrust/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
48
  braintrust/cli/__main__.py,sha256=wCBKHGVmn3IT_yMXk5qfDwyI2SV2gf1tLr0NTxm9T8k,1519
@@ -109,8 +109,8 @@ braintrust/wrappers/claude_agent_sdk/__init__.py,sha256=CSXJWy-z2fHF7h4VJjLSnXJv
109
109
  braintrust/wrappers/claude_agent_sdk/_wrapper.py,sha256=uzElIOwwPmF_Y5fbWcKWEPC8HnSzW7byzpiuVKK0TXE,15613
110
110
  braintrust/wrappers/claude_agent_sdk/test_wrapper.py,sha256=0NmohdECudFvWtc-5PbANtTXzexkkwIJhGbujydDrT8,6826
111
111
  braintrust/wrappers/google_genai/__init__.py,sha256=PGFMuR3c4Gc3SUt24eP7z5AzdS2Dc1uF1d3QPCnLnuo,16018
112
- braintrust-0.3.12.dist-info/METADATA,sha256=LMIW6iDtc0F1i6QsqMWoUcJn6RREF0H9XJ0n6mHjYb4,3131
113
- braintrust-0.3.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
114
- braintrust-0.3.12.dist-info/entry_points.txt,sha256=Zpc0_09g5xm8as5jHqqFq7fhwO0xHSNct_TrEMONS7Q,60
115
- braintrust-0.3.12.dist-info/top_level.txt,sha256=hw1-y-UFMf60RzAr8x_eM7SThbIuWfQsQIbVvqSF83A,11
116
- braintrust-0.3.12.dist-info/RECORD,,
112
+ braintrust-0.3.13.dist-info/METADATA,sha256=cZM8kCyxf7v0SHVRVPHBElZcVIis1GcwcXTOea54WPo,3702
113
+ braintrust-0.3.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
114
+ braintrust-0.3.13.dist-info/entry_points.txt,sha256=Zpc0_09g5xm8as5jHqqFq7fhwO0xHSNct_TrEMONS7Q,60
115
+ braintrust-0.3.13.dist-info/top_level.txt,sha256=hw1-y-UFMf60RzAr8x_eM7SThbIuWfQsQIbVvqSF83A,11
116
+ braintrust-0.3.13.dist-info/RECORD,,