braintrust 0.3.7__py3-none-any.whl → 0.3.9__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/_generated_types.py +52 -9
- braintrust/framework2.py +0 -1
- braintrust/generated_types.py +5 -1
- braintrust/logger.py +83 -40
- braintrust/test_logger.py +275 -20
- braintrust/version.py +2 -2
- {braintrust-0.3.7.dist-info → braintrust-0.3.9.dist-info}/METADATA +1 -1
- {braintrust-0.3.7.dist-info → braintrust-0.3.9.dist-info}/RECORD +11 -11
- {braintrust-0.3.7.dist-info → braintrust-0.3.9.dist-info}/WHEEL +0 -0
- {braintrust-0.3.7.dist-info → braintrust-0.3.9.dist-info}/entry_points.txt +0 -0
- {braintrust-0.3.7.dist-info → braintrust-0.3.9.dist-info}/top_level.txt +0 -0
braintrust/_generated_types.py
CHANGED
|
@@ -212,6 +212,17 @@ CallEvent = Union[
|
|
|
212
212
|
]
|
|
213
213
|
|
|
214
214
|
|
|
215
|
+
class ChatCompletionContentPartFileFile(TypedDict):
|
|
216
|
+
file_data: NotRequired[Optional[str]]
|
|
217
|
+
filename: NotRequired[Optional[str]]
|
|
218
|
+
file_id: NotRequired[Optional[str]]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class ChatCompletionContentPartFileWithTitle(TypedDict):
|
|
222
|
+
file: ChatCompletionContentPartFileFile
|
|
223
|
+
type: Literal['file']
|
|
224
|
+
|
|
225
|
+
|
|
215
226
|
class ChatCompletionContentPartImageWithTitleImageUrl(TypedDict):
|
|
216
227
|
url: str
|
|
217
228
|
detail: NotRequired[Optional[Union[Literal['auto'], Literal['low'], Literal['high']]]]
|
|
@@ -342,7 +353,7 @@ class ChatCompletionTool(TypedDict):
|
|
|
342
353
|
|
|
343
354
|
|
|
344
355
|
class CodeBundleRuntimeContext(TypedDict):
|
|
345
|
-
runtime: Literal['node', 'python']
|
|
356
|
+
runtime: Literal['node', 'python', 'browser']
|
|
346
357
|
version: str
|
|
347
358
|
|
|
348
359
|
|
|
@@ -570,7 +581,7 @@ class Data(CodeBundle):
|
|
|
570
581
|
|
|
571
582
|
|
|
572
583
|
class FunctionDataFunctionData1DataRuntimeContext(TypedDict):
|
|
573
|
-
runtime: Literal['node', 'python']
|
|
584
|
+
runtime: Literal['node', 'python', 'browser']
|
|
574
585
|
version: str
|
|
575
586
|
|
|
576
587
|
|
|
@@ -649,7 +660,7 @@ class FunctionIdFunctionId3(TypedDict):
|
|
|
649
660
|
|
|
650
661
|
|
|
651
662
|
class FunctionIdFunctionId4InlineContext(TypedDict):
|
|
652
|
-
runtime: Literal['node', 'python']
|
|
663
|
+
runtime: Literal['node', 'python', 'browser']
|
|
653
664
|
version: str
|
|
654
665
|
|
|
655
666
|
|
|
@@ -668,16 +679,16 @@ class FunctionIdFunctionId4(TypedDict):
|
|
|
668
679
|
FunctionIdRef = Mapping[str, Any]
|
|
669
680
|
|
|
670
681
|
|
|
671
|
-
FunctionObjectType = Literal['prompt', 'tool', 'scorer', 'task', 'agent']
|
|
682
|
+
FunctionObjectType = Literal['prompt', 'tool', 'scorer', 'task', 'agent', 'custom_view']
|
|
672
683
|
|
|
673
684
|
|
|
674
685
|
FunctionOutputType = Literal['completion', 'score', 'any']
|
|
675
686
|
|
|
676
687
|
|
|
677
|
-
FunctionTypeEnum = Literal['llm', 'scorer', 'task', 'tool']
|
|
688
|
+
FunctionTypeEnum = Literal['llm', 'scorer', 'task', 'tool', 'custom_view']
|
|
678
689
|
|
|
679
690
|
|
|
680
|
-
FunctionTypeEnumNullish = Literal['llm', 'scorer', 'task', 'tool']
|
|
691
|
+
FunctionTypeEnumNullish = Literal['llm', 'scorer', 'task', 'tool', 'custom_view']
|
|
681
692
|
|
|
682
693
|
|
|
683
694
|
class GitMetadataSettings(TypedDict):
|
|
@@ -1854,7 +1865,7 @@ class AnyModelParams(TypedDict):
|
|
|
1854
1865
|
function_call: NotRequired[Optional[Union[Literal['auto'], Literal['none'], AnyModelParamsFunctionCall]]]
|
|
1855
1866
|
n: NotRequired[Optional[float]]
|
|
1856
1867
|
stop: NotRequired[Optional[Sequence[str]]]
|
|
1857
|
-
reasoning_effort: NotRequired[Optional[Literal['minimal', 'low', 'medium', 'high']]]
|
|
1868
|
+
reasoning_effort: NotRequired[Optional[Literal['none', 'minimal', 'low', 'medium', 'high']]]
|
|
1858
1869
|
verbosity: NotRequired[Optional[Literal['low', 'medium', 'high']]]
|
|
1859
1870
|
top_k: NotRequired[Optional[float]]
|
|
1860
1871
|
stop_sequences: NotRequired[Optional[Sequence[str]]]
|
|
@@ -1894,7 +1905,11 @@ class AttachmentStatus(TypedDict):
|
|
|
1894
1905
|
"""
|
|
1895
1906
|
|
|
1896
1907
|
|
|
1897
|
-
ChatCompletionContentPart = Union[
|
|
1908
|
+
ChatCompletionContentPart = Union[
|
|
1909
|
+
ChatCompletionContentPartTextWithTitle,
|
|
1910
|
+
ChatCompletionContentPartImageWithTitle,
|
|
1911
|
+
ChatCompletionContentPartFileWithTitle,
|
|
1912
|
+
]
|
|
1898
1913
|
|
|
1899
1914
|
|
|
1900
1915
|
class ChatCompletionMessageParamChatCompletionMessageParam1(TypedDict):
|
|
@@ -1993,6 +2008,14 @@ class DatasetEvent(TypedDict):
|
|
|
1993
2008
|
Whether this span is a root span
|
|
1994
2009
|
"""
|
|
1995
2010
|
origin: NotRequired[Optional[ObjectReferenceNullish]]
|
|
2011
|
+
comments: NotRequired[Optional[Sequence[Any]]]
|
|
2012
|
+
"""
|
|
2013
|
+
Optional list of comments attached to this event
|
|
2014
|
+
"""
|
|
2015
|
+
audit_data: NotRequired[Optional[Sequence[Any]]]
|
|
2016
|
+
"""
|
|
2017
|
+
Optional list of audit entries attached to this event
|
|
2018
|
+
"""
|
|
1996
2019
|
|
|
1997
2020
|
|
|
1998
2021
|
class Experiment(TypedDict):
|
|
@@ -2075,7 +2098,7 @@ class ModelParamsModelParams(TypedDict):
|
|
|
2075
2098
|
function_call: NotRequired[Optional[Union[Literal['auto'], Literal['none'], ModelParamsModelParamsFunctionCall]]]
|
|
2076
2099
|
n: NotRequired[Optional[float]]
|
|
2077
2100
|
stop: NotRequired[Optional[Sequence[str]]]
|
|
2078
|
-
reasoning_effort: NotRequired[Optional[Literal['minimal', 'low', 'medium', 'high']]]
|
|
2101
|
+
reasoning_effort: NotRequired[Optional[Literal['none', 'minimal', 'low', 'medium', 'high']]]
|
|
2079
2102
|
verbosity: NotRequired[Optional[Literal['low', 'medium', 'high']]]
|
|
2080
2103
|
|
|
2081
2104
|
|
|
@@ -2327,6 +2350,14 @@ class ExperimentEvent(TypedDict):
|
|
|
2327
2350
|
Whether this span is a root span
|
|
2328
2351
|
"""
|
|
2329
2352
|
origin: NotRequired[Optional[ObjectReferenceNullish]]
|
|
2353
|
+
comments: NotRequired[Optional[Sequence[Any]]]
|
|
2354
|
+
"""
|
|
2355
|
+
Optional list of comments attached to this event
|
|
2356
|
+
"""
|
|
2357
|
+
audit_data: NotRequired[Optional[Sequence[Any]]]
|
|
2358
|
+
"""
|
|
2359
|
+
Optional list of audit entries attached to this event
|
|
2360
|
+
"""
|
|
2330
2361
|
|
|
2331
2362
|
|
|
2332
2363
|
class GraphNodeGraphNode7(TypedDict):
|
|
@@ -2437,6 +2468,18 @@ class ProjectLogsEvent(TypedDict):
|
|
|
2437
2468
|
"""
|
|
2438
2469
|
span_attributes: NotRequired[Optional[SpanAttributes]]
|
|
2439
2470
|
origin: NotRequired[Optional[ObjectReferenceNullish]]
|
|
2471
|
+
comments: NotRequired[Optional[Sequence[Any]]]
|
|
2472
|
+
"""
|
|
2473
|
+
Optional list of comments attached to this event
|
|
2474
|
+
"""
|
|
2475
|
+
audit_data: NotRequired[Optional[Sequence[Any]]]
|
|
2476
|
+
"""
|
|
2477
|
+
Optional list of audit entries attached to this event
|
|
2478
|
+
"""
|
|
2479
|
+
_async_scoring_state: NotRequired[Optional[Any]]
|
|
2480
|
+
"""
|
|
2481
|
+
The async scoring state for this event
|
|
2482
|
+
"""
|
|
2440
2483
|
|
|
2441
2484
|
|
|
2442
2485
|
class ProjectScore(TypedDict):
|
braintrust/framework2.py
CHANGED
braintrust/generated_types.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Auto-generated file (internal git SHA
|
|
1
|
+
"""Auto-generated file (internal git SHA 8e9c0a96b3cf291360978c17580f72f6817bd6c8) -- do not modify"""
|
|
2
2
|
|
|
3
3
|
from ._generated_types import (
|
|
4
4
|
Acl,
|
|
@@ -14,6 +14,8 @@ from ._generated_types import (
|
|
|
14
14
|
BraintrustModelParams,
|
|
15
15
|
CallEvent,
|
|
16
16
|
ChatCompletionContentPart,
|
|
17
|
+
ChatCompletionContentPartFileFile,
|
|
18
|
+
ChatCompletionContentPartFileWithTitle,
|
|
17
19
|
ChatCompletionContentPartImageWithTitle,
|
|
18
20
|
ChatCompletionContentPartText,
|
|
19
21
|
ChatCompletionContentPartTextWithTitle,
|
|
@@ -111,6 +113,8 @@ __all__ = [
|
|
|
111
113
|
"BraintrustModelParams",
|
|
112
114
|
"CallEvent",
|
|
113
115
|
"ChatCompletionContentPart",
|
|
116
|
+
"ChatCompletionContentPartFileFile",
|
|
117
|
+
"ChatCompletionContentPartFileWithTitle",
|
|
114
118
|
"ChatCompletionContentPartImageWithTitle",
|
|
115
119
|
"ChatCompletionContentPartText",
|
|
116
120
|
"ChatCompletionContentPartTextWithTitle",
|
braintrust/logger.py
CHANGED
|
@@ -459,22 +459,24 @@ class BraintrustState:
|
|
|
459
459
|
|
|
460
460
|
def copy_state(self, other: "BraintrustState"):
|
|
461
461
|
"""Copy login information from another BraintrustState instance."""
|
|
462
|
-
self.__dict__.update(
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
462
|
+
self.__dict__.update(
|
|
463
|
+
{
|
|
464
|
+
k: v
|
|
465
|
+
for (k, v) in other.__dict__.items()
|
|
466
|
+
if k
|
|
467
|
+
not in (
|
|
468
|
+
"current_experiment",
|
|
469
|
+
"current_logger",
|
|
470
|
+
"current_parent",
|
|
471
|
+
"current_span",
|
|
472
|
+
"_global_bg_logger",
|
|
473
|
+
"_override_bg_logger",
|
|
474
|
+
"_context_manager",
|
|
475
|
+
"_last_otel_setting",
|
|
476
|
+
"_context_manager_lock",
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
)
|
|
478
480
|
|
|
479
481
|
def login(
|
|
480
482
|
self,
|
|
@@ -750,7 +752,7 @@ def construct_logs3_data(items: Sequence[str]):
|
|
|
750
752
|
def _check_json_serializable(event):
|
|
751
753
|
try:
|
|
752
754
|
return bt_dumps(event)
|
|
753
|
-
except TypeError as e:
|
|
755
|
+
except (TypeError, ValueError) as e:
|
|
754
756
|
raise Exception(f"All logged values must be JSON-serializable: {event}") from e
|
|
755
757
|
|
|
756
758
|
|
|
@@ -2439,19 +2441,46 @@ def _deep_copy_event(event: Mapping[str, Any]) -> Dict[str, Any]:
|
|
|
2439
2441
|
Creates a deep copy of the given event. Replaces references to user objects
|
|
2440
2442
|
with placeholder strings to ensure serializability, except for `Attachment`
|
|
2441
2443
|
and `ExternalAttachment` objects, which are preserved and not deep-copied.
|
|
2442
|
-
"""
|
|
2443
2444
|
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2445
|
+
Handles circular references and excessive nesting depth to prevent
|
|
2446
|
+
RecursionError during serialization.
|
|
2447
|
+
"""
|
|
2448
|
+
# Maximum depth to prevent hitting Python's recursion limit
|
|
2449
|
+
# Python's default limit is ~1000, we use a conservative limit
|
|
2450
|
+
# to account for existing call stack usage from pytest, application code, etc.
|
|
2451
|
+
MAX_DEPTH = 200
|
|
2452
|
+
|
|
2453
|
+
# Track visited objects to detect circular references
|
|
2454
|
+
visited: set[int] = set()
|
|
2455
|
+
|
|
2456
|
+
def _deep_copy_object(v: Any, depth: int = 0) -> Any:
|
|
2457
|
+
# Check depth limit - use >= to stop before exceeding
|
|
2458
|
+
if depth >= MAX_DEPTH:
|
|
2459
|
+
return "<max depth exceeded>"
|
|
2460
|
+
|
|
2461
|
+
# Check for circular references in mutable containers
|
|
2462
|
+
# Use id() to track object identity
|
|
2463
|
+
if isinstance(v, (Mapping, List, Tuple, Set)):
|
|
2464
|
+
obj_id = id(v)
|
|
2465
|
+
if obj_id in visited:
|
|
2466
|
+
return "<circular reference>"
|
|
2467
|
+
visited.add(obj_id)
|
|
2468
|
+
try:
|
|
2469
|
+
if isinstance(v, Mapping):
|
|
2470
|
+
# Prevent dict keys from holding references to user data. Note that
|
|
2471
|
+
# `bt_json` already coerces keys to string, a behavior that comes from
|
|
2472
|
+
# `json.dumps`. However, that runs at log upload time, while we want to
|
|
2473
|
+
# cut out all the references to user objects synchronously in this
|
|
2474
|
+
# function.
|
|
2475
|
+
return {str(k): _deep_copy_object(v[k], depth + 1) for k in v}
|
|
2476
|
+
elif isinstance(v, (List, Tuple, Set)):
|
|
2477
|
+
return [_deep_copy_object(x, depth + 1) for x in v]
|
|
2478
|
+
finally:
|
|
2479
|
+
# Remove from visited set after processing to allow the same object
|
|
2480
|
+
# to appear in different branches of the tree
|
|
2481
|
+
visited.discard(obj_id)
|
|
2482
|
+
|
|
2483
|
+
if isinstance(v, Span):
|
|
2455
2484
|
return "<span>"
|
|
2456
2485
|
elif isinstance(v, Experiment):
|
|
2457
2486
|
return "<experiment>"
|
|
@@ -3278,17 +3307,17 @@ def _start_span_parent_args(
|
|
|
3278
3307
|
if parent:
|
|
3279
3308
|
assert parent_span_ids is None, "Cannot specify both parent and parent_span_ids"
|
|
3280
3309
|
parent_components = SpanComponentsV4.from_str(parent)
|
|
3281
|
-
assert (
|
|
3282
|
-
parent_object_type
|
|
3283
|
-
)
|
|
3310
|
+
assert parent_object_type == parent_components.object_type, (
|
|
3311
|
+
f"Mismatch between expected span parent object type {parent_object_type} and provided type {parent_components.object_type}"
|
|
3312
|
+
)
|
|
3284
3313
|
|
|
3285
3314
|
parent_components_object_id_lambda = _span_components_to_object_id_lambda(parent_components)
|
|
3286
3315
|
|
|
3287
3316
|
def compute_parent_object_id():
|
|
3288
3317
|
parent_components_object_id = parent_components_object_id_lambda()
|
|
3289
|
-
assert (
|
|
3290
|
-
parent_object_id.get()
|
|
3291
|
-
)
|
|
3318
|
+
assert parent_object_id.get() == parent_components_object_id, (
|
|
3319
|
+
f"Mismatch between expected span parent object id {parent_object_id.get()} and provided id {parent_components_object_id}"
|
|
3320
|
+
)
|
|
3292
3321
|
return parent_object_id.get()
|
|
3293
3322
|
|
|
3294
3323
|
arg_parent_object_id = LazyValue(compute_parent_object_id, use_mutex=False)
|
|
@@ -3896,8 +3925,8 @@ class SpanImpl(Span):
|
|
|
3896
3925
|
**{IS_MERGE_FIELD: self._is_merge},
|
|
3897
3926
|
)
|
|
3898
3927
|
|
|
3899
|
-
_check_json_serializable(partial_record)
|
|
3900
3928
|
serializable_partial_record = _deep_copy_event(partial_record)
|
|
3929
|
+
_check_json_serializable(serializable_partial_record)
|
|
3901
3930
|
if serializable_partial_record.get("metrics", {}).get("end") is not None:
|
|
3902
3931
|
self._logged_end_time = serializable_partial_record["metrics"]["end"]
|
|
3903
3932
|
|
|
@@ -4453,10 +4482,24 @@ def render_message(render: Callable[[str], str], message: PromptMessage):
|
|
|
4453
4482
|
if c["type"] == "text":
|
|
4454
4483
|
rendered_content.append({**c, "text": render(c["text"])})
|
|
4455
4484
|
elif c["type"] == "image_url":
|
|
4456
|
-
rendered_content.append(
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4485
|
+
rendered_content.append(
|
|
4486
|
+
{
|
|
4487
|
+
**c,
|
|
4488
|
+
"image_url": {**c["image_url"], "url": render(c["image_url"]["url"])},
|
|
4489
|
+
}
|
|
4490
|
+
)
|
|
4491
|
+
elif c["type"] == "file":
|
|
4492
|
+
rendered_content.append(
|
|
4493
|
+
{
|
|
4494
|
+
**c,
|
|
4495
|
+
"file": {
|
|
4496
|
+
**c["file"],
|
|
4497
|
+
"file_data": render(c["file"]["file_data"]),
|
|
4498
|
+
**({} if "file_id" not in c["file"] else {"file_id": render(c["file"]["file_id"])}),
|
|
4499
|
+
**({} if "filename" not in c["file"] else {"filename": render(c["file"]["filename"])}),
|
|
4500
|
+
},
|
|
4501
|
+
}
|
|
4502
|
+
)
|
|
4460
4503
|
else:
|
|
4461
4504
|
raise ValueError(f"Unknown content type: {c['type']}")
|
|
4462
4505
|
|
braintrust/test_logger.py
CHANGED
|
@@ -20,7 +20,7 @@ from braintrust import (
|
|
|
20
20
|
logger,
|
|
21
21
|
)
|
|
22
22
|
from braintrust.id_gen import OTELIDGenerator, get_id_generator
|
|
23
|
-
from braintrust.logger import _deep_copy_event, _extract_attachments, parent_context, render_mustache
|
|
23
|
+
from braintrust.logger import _deep_copy_event, _extract_attachments, parent_context, render_message, render_mustache
|
|
24
24
|
from braintrust.prompt import PromptChatBlock, PromptData, PromptMessage, PromptSchema
|
|
25
25
|
from braintrust.test_helpers import (
|
|
26
26
|
assert_dict_matches,
|
|
@@ -252,6 +252,32 @@ class TestLogger(TestCase):
|
|
|
252
252
|
self.assertIs(copy["output"]["attachmentList"][1], attachment2)
|
|
253
253
|
self.assertIs(copy["output"]["attachmentList"][3], attachment3)
|
|
254
254
|
|
|
255
|
+
def test_check_json_serializable_catches_circular_references(self):
|
|
256
|
+
"""Test that _check_json_serializable properly handles circular references.
|
|
257
|
+
|
|
258
|
+
After fix, _check_json_serializable should catch ValueError from circular
|
|
259
|
+
references and convert them to a more appropriate exception or handle them.
|
|
260
|
+
"""
|
|
261
|
+
from braintrust.logger import _check_json_serializable
|
|
262
|
+
|
|
263
|
+
# Create data with circular reference
|
|
264
|
+
data = {"a": "b"}
|
|
265
|
+
data["self"] = data
|
|
266
|
+
|
|
267
|
+
# Should either succeed (by handling circular refs) or raise a clear exception
|
|
268
|
+
# The error message should indicate the data is not serializable
|
|
269
|
+
try:
|
|
270
|
+
result = _check_json_serializable(data)
|
|
271
|
+
# If it succeeds, it should return a serialized string
|
|
272
|
+
self.assertIsInstance(result, str)
|
|
273
|
+
except Exception as e:
|
|
274
|
+
# If it raises an exception, it should mention serialization issue
|
|
275
|
+
error_msg = str(e).lower()
|
|
276
|
+
self.assertTrue(
|
|
277
|
+
"json-serializable" in error_msg or "circular" in error_msg,
|
|
278
|
+
f"Expected error message to mention serialization issue, got: {e}",
|
|
279
|
+
)
|
|
280
|
+
|
|
255
281
|
def test_prompt_build_with_structured_output_templating(self):
|
|
256
282
|
self.maxDiff = None
|
|
257
283
|
prompt = Prompt(
|
|
@@ -459,6 +485,46 @@ class TestLogger(TestCase):
|
|
|
459
485
|
with self.assertRaises(ValueError):
|
|
460
486
|
prompt.build(items=["only_one"], strict=True)
|
|
461
487
|
|
|
488
|
+
def test_render_message_with_file_content_parts(self):
|
|
489
|
+
"""Test render_message with mixed text, image, and file content parts including all file fields."""
|
|
490
|
+
message = PromptMessage(
|
|
491
|
+
role="user",
|
|
492
|
+
content=[
|
|
493
|
+
{"type": "text", "text": "Here is a {{item}}:"},
|
|
494
|
+
{"type": "image_url", "image_url": {"url": "{{image_url}}"}},
|
|
495
|
+
{
|
|
496
|
+
"type": "file",
|
|
497
|
+
"file": {
|
|
498
|
+
"file_data": "{{file_data}}",
|
|
499
|
+
"file_id": "{{file_id}}",
|
|
500
|
+
"filename": "{{filename}}",
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
rendered = render_message(
|
|
507
|
+
lambda template: template.replace("{{item}}", "document")
|
|
508
|
+
.replace("{{image_url}}", "https://example.com/image.png")
|
|
509
|
+
.replace("{{file_data}}", "base64data")
|
|
510
|
+
.replace("{{file_id}}", "file-456")
|
|
511
|
+
.replace("{{filename}}", "report.pdf"),
|
|
512
|
+
message,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
assert rendered["content"] == [
|
|
516
|
+
{"type": "text", "text": "Here is a document:"},
|
|
517
|
+
{"type": "image_url", "image_url": {"url": "https://example.com/image.png"}},
|
|
518
|
+
{
|
|
519
|
+
"type": "file",
|
|
520
|
+
"file": {
|
|
521
|
+
"file_data": "base64data",
|
|
522
|
+
"file_id": "file-456",
|
|
523
|
+
"filename": "report.pdf",
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
]
|
|
527
|
+
|
|
462
528
|
|
|
463
529
|
def test_noop_permalink_issue_1837():
|
|
464
530
|
# fixes issue #BRA-1837
|
|
@@ -471,6 +537,185 @@ def test_noop_permalink_issue_1837():
|
|
|
471
537
|
assert span.link() == "https://www.braintrust.dev/noop-span"
|
|
472
538
|
|
|
473
539
|
|
|
540
|
+
def test_span_log_with_simple_circular_reference(with_memory_logger):
|
|
541
|
+
"""Test that span.log() with simple circular reference works gracefully."""
|
|
542
|
+
logger = init_test_logger(__name__)
|
|
543
|
+
|
|
544
|
+
with logger.start_span(name="test_span") as span:
|
|
545
|
+
# Create simple circular reference
|
|
546
|
+
data = {"key": "value"}
|
|
547
|
+
data["self"] = data
|
|
548
|
+
|
|
549
|
+
# Should handle circular reference gracefully
|
|
550
|
+
span.log(
|
|
551
|
+
input={"test": "simple circular ref"},
|
|
552
|
+
output=data,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
# Verify the log was recorded with circular reference replaced by placeholder
|
|
556
|
+
logs = with_memory_logger.pop()
|
|
557
|
+
assert len(logs) == 1
|
|
558
|
+
|
|
559
|
+
logged_output = logs[0]["output"]
|
|
560
|
+
assert logged_output["key"] == "value"
|
|
561
|
+
# Circular reference should be replaced with a placeholder string
|
|
562
|
+
assert isinstance(logged_output["self"], str)
|
|
563
|
+
assert "circular" in logged_output["self"].lower()
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def test_span_log_with_nested_circular_reference(with_memory_logger):
|
|
567
|
+
"""Test that span.log() with nested circular reference works gracefully."""
|
|
568
|
+
logger = init_test_logger(__name__)
|
|
569
|
+
|
|
570
|
+
with logger.start_span(name="test_span") as span:
|
|
571
|
+
# Create nested structure with circular reference
|
|
572
|
+
page = {"page_number": 1, "content": "text"}
|
|
573
|
+
document = {"pages": [page]}
|
|
574
|
+
page["document"] = document
|
|
575
|
+
|
|
576
|
+
# Should handle circular reference gracefully
|
|
577
|
+
span.log(
|
|
578
|
+
input={"file": "test.pdf"},
|
|
579
|
+
output=document,
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Verify the log was recorded with nested circular reference handled
|
|
583
|
+
logs = with_memory_logger.pop()
|
|
584
|
+
assert len(logs) == 1
|
|
585
|
+
|
|
586
|
+
logged_output = logs[0]["output"]
|
|
587
|
+
assert len(logged_output["pages"]) == 1
|
|
588
|
+
assert logged_output["pages"][0]["page_number"] == 1
|
|
589
|
+
assert logged_output["pages"][0]["content"] == "text"
|
|
590
|
+
# Circular reference should be replaced with a placeholder
|
|
591
|
+
assert isinstance(logged_output["pages"][0]["document"], str)
|
|
592
|
+
assert "circular" in logged_output["pages"][0]["document"].lower()
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def test_span_log_with_deep_document_structure(with_memory_logger):
|
|
596
|
+
"""Test that span.log() with deeply nested document structure works gracefully."""
|
|
597
|
+
logger = init_test_logger(__name__)
|
|
598
|
+
|
|
599
|
+
with logger.start_span(name="test_span") as span:
|
|
600
|
+
# Create deeply nested document structure with circular reference
|
|
601
|
+
doc_data = {
|
|
602
|
+
"model_id": "document-model",
|
|
603
|
+
"content": "Document content",
|
|
604
|
+
"pages": [],
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
page = {
|
|
608
|
+
"page_number": 1,
|
|
609
|
+
"lines": [{"content": "Line 1"}],
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
# Create circular reference
|
|
613
|
+
page["document"] = doc_data
|
|
614
|
+
doc_data["pages"].append(page)
|
|
615
|
+
|
|
616
|
+
# Should handle circular reference gracefully
|
|
617
|
+
span.log(
|
|
618
|
+
input={"file": "test.pdf"},
|
|
619
|
+
output=doc_data,
|
|
620
|
+
metadata={"source": "document_processor"},
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# Verify the log was recorded with proper structure
|
|
624
|
+
logs = with_memory_logger.pop()
|
|
625
|
+
assert len(logs) == 1
|
|
626
|
+
|
|
627
|
+
logged_output = logs[0]["output"]
|
|
628
|
+
assert logged_output["model_id"] == "document-model"
|
|
629
|
+
assert logged_output["content"] == "Document content"
|
|
630
|
+
assert len(logged_output["pages"]) == 1
|
|
631
|
+
assert logged_output["pages"][0]["page_number"] == 1
|
|
632
|
+
assert len(logged_output["pages"][0]["lines"]) == 1
|
|
633
|
+
assert logged_output["pages"][0]["lines"][0]["content"] == "Line 1"
|
|
634
|
+
# Circular reference should be replaced with placeholder
|
|
635
|
+
assert isinstance(logged_output["pages"][0]["document"], str)
|
|
636
|
+
assert "circular" in logged_output["pages"][0]["document"].lower()
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def test_span_log_with_extremely_deep_nesting(with_memory_logger):
|
|
640
|
+
"""Test that span.log() with extremely deep nesting works gracefully."""
|
|
641
|
+
import sys
|
|
642
|
+
|
|
643
|
+
logger = init_test_logger(__name__)
|
|
644
|
+
|
|
645
|
+
with logger.start_span(name="test_span") as span:
|
|
646
|
+
recursion_limit = sys.getrecursionlimit()
|
|
647
|
+
|
|
648
|
+
# Create structure deeper than recursion limit
|
|
649
|
+
deeply_nested = {"level": 0}
|
|
650
|
+
current = deeply_nested
|
|
651
|
+
for i in range(1, recursion_limit + 100):
|
|
652
|
+
current["nested"] = {"level": i}
|
|
653
|
+
current = current["nested"]
|
|
654
|
+
|
|
655
|
+
# Should handle extremely deep nesting without RecursionError
|
|
656
|
+
span.log(
|
|
657
|
+
input={"test": "deep nesting"},
|
|
658
|
+
output=deeply_nested,
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
# Verify the log was recorded (may be truncated or have placeholder for deep nesting)
|
|
662
|
+
logs = with_memory_logger.pop()
|
|
663
|
+
assert len(logs) == 1
|
|
664
|
+
|
|
665
|
+
logged_output = logs[0]["output"]
|
|
666
|
+
assert logged_output["level"] == 0
|
|
667
|
+
# Either the structure is preserved up to a safe depth, or replaced with placeholder
|
|
668
|
+
assert "nested" in logged_output
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def test_span_log_with_large_document_many_pages(with_memory_logger):
|
|
672
|
+
"""Test that span.log() with large multi-page document works gracefully."""
|
|
673
|
+
logger = init_test_logger(__name__)
|
|
674
|
+
|
|
675
|
+
with logger.start_span(name="test_span") as span:
|
|
676
|
+
# Create realistic large document: 20 pages × 30 lines × 10 words
|
|
677
|
+
pages = []
|
|
678
|
+
for page_num in range(20):
|
|
679
|
+
lines = []
|
|
680
|
+
for line_num in range(30):
|
|
681
|
+
words = []
|
|
682
|
+
for word_num in range(10):
|
|
683
|
+
words.append(
|
|
684
|
+
{
|
|
685
|
+
"content": f"word_{word_num}",
|
|
686
|
+
"confidence": 0.98,
|
|
687
|
+
}
|
|
688
|
+
)
|
|
689
|
+
lines.append(
|
|
690
|
+
{
|
|
691
|
+
"content": f"line_{line_num}",
|
|
692
|
+
"words": words,
|
|
693
|
+
}
|
|
694
|
+
)
|
|
695
|
+
pages.append(
|
|
696
|
+
{
|
|
697
|
+
"page_number": page_num + 1,
|
|
698
|
+
"lines": lines,
|
|
699
|
+
}
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
# Should handle large document structure
|
|
703
|
+
span.log(
|
|
704
|
+
input={"file": "large_document.pdf"},
|
|
705
|
+
output={"pages": pages},
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
# Verify the log was recorded with full structure intact (no circular refs)
|
|
709
|
+
logs = with_memory_logger.pop()
|
|
710
|
+
assert len(logs) == 1
|
|
711
|
+
|
|
712
|
+
logged_output = logs[0]["output"]
|
|
713
|
+
assert len(logged_output["pages"]) == 20
|
|
714
|
+
assert len(logged_output["pages"][0]["lines"]) == 30
|
|
715
|
+
assert len(logged_output["pages"][0]["lines"][0]["words"]) == 10
|
|
716
|
+
assert logged_output["pages"][0]["lines"][0]["words"][0]["content"] == "word_0"
|
|
717
|
+
|
|
718
|
+
|
|
474
719
|
def test_span_link_logged_out(with_memory_logger):
|
|
475
720
|
simulate_logout()
|
|
476
721
|
assert_logged_out()
|
|
@@ -1762,7 +2007,6 @@ def test_parent_precedence_traced_baseline(with_memory_logger, with_simulate_log
|
|
|
1762
2007
|
assert top_log["span_id"] in (child_log.get("span_parents") or [])
|
|
1763
2008
|
|
|
1764
2009
|
|
|
1765
|
-
|
|
1766
2010
|
def test_parent_precedence_explicit_parent_overrides(with_memory_logger, with_simulate_login):
|
|
1767
2011
|
"""Test that explicit parent overrides current span."""
|
|
1768
2012
|
init_test_logger(__name__)
|
|
@@ -1800,6 +2044,7 @@ def reset_id_generator_state():
|
|
|
1800
2044
|
if original_env:
|
|
1801
2045
|
os.environ["BRAINTRUST_OTEL_COMPAT"] = original_env
|
|
1802
2046
|
|
|
2047
|
+
|
|
1803
2048
|
def test_otel_compatible_span_export_import():
|
|
1804
2049
|
"""Test that spans with OTEL-compatible IDs can be exported and imported correctly."""
|
|
1805
2050
|
from braintrust.span_identifier_v4 import SpanComponentsV4, SpanObjectTypeV3
|
|
@@ -1807,21 +2052,21 @@ def test_otel_compatible_span_export_import():
|
|
|
1807
2052
|
# Generate OTEL-compatible IDs
|
|
1808
2053
|
otel_gen = OTELIDGenerator()
|
|
1809
2054
|
trace_id = otel_gen.get_trace_id() # 32-char hex (16 bytes)
|
|
1810
|
-
span_id = otel_gen.get_span_id()
|
|
2055
|
+
span_id = otel_gen.get_span_id() # 16-char hex (8 bytes)
|
|
1811
2056
|
|
|
1812
2057
|
# Test that trace_id is 32 chars and span_id is 16 chars
|
|
1813
2058
|
assert len(trace_id) == 32
|
|
1814
2059
|
assert len(span_id) == 16
|
|
1815
|
-
assert all(c in
|
|
1816
|
-
assert all(c in
|
|
2060
|
+
assert all(c in "0123456789abcdef" for c in trace_id)
|
|
2061
|
+
assert all(c in "0123456789abcdef" for c in span_id)
|
|
1817
2062
|
|
|
1818
2063
|
# Create span components
|
|
1819
2064
|
components = SpanComponentsV4(
|
|
1820
2065
|
object_type=SpanObjectTypeV3.PROJECT_LOGS,
|
|
1821
|
-
object_id=
|
|
1822
|
-
row_id=
|
|
2066
|
+
object_id="test-project-id",
|
|
2067
|
+
row_id="test-row-id",
|
|
1823
2068
|
span_id=span_id,
|
|
1824
|
-
root_span_id=trace_id
|
|
2069
|
+
root_span_id=trace_id,
|
|
1825
2070
|
)
|
|
1826
2071
|
|
|
1827
2072
|
# Test export/import cycle
|
|
@@ -1856,14 +2101,15 @@ def test_span_with_otel_ids_export_import(reset_id_generator_state):
|
|
|
1856
2101
|
# Verify the span has OTEL-compatible IDs
|
|
1857
2102
|
assert len(span.span_id) == 16 # 8-byte hex
|
|
1858
2103
|
assert len(span.root_span_id) == 32 # 16-byte hex
|
|
1859
|
-
assert all(c in
|
|
1860
|
-
assert all(c in
|
|
2104
|
+
assert all(c in "0123456789abcdef" for c in span.span_id)
|
|
2105
|
+
assert all(c in "0123456789abcdef" for c in span.root_span_id)
|
|
1861
2106
|
|
|
1862
2107
|
# Export the span
|
|
1863
2108
|
exported = span.export()
|
|
1864
2109
|
|
|
1865
2110
|
# Parse it back
|
|
1866
2111
|
from braintrust.span_identifier_v4 import SpanComponentsV4
|
|
2112
|
+
|
|
1867
2113
|
imported = SpanComponentsV4.from_str(exported)
|
|
1868
2114
|
|
|
1869
2115
|
# Verify IDs are preserved exactly
|
|
@@ -1874,9 +2120,10 @@ def test_span_with_otel_ids_export_import(reset_id_generator_state):
|
|
|
1874
2120
|
def test_span_with_uuid_ids_share_root_span_id(reset_id_generator_state):
|
|
1875
2121
|
"""Test that UUID generators share span_id as root_span_id for backwards compatibility."""
|
|
1876
2122
|
import os
|
|
2123
|
+
|
|
1877
2124
|
# Ensure UUID generator is used (default behavior)
|
|
1878
|
-
if
|
|
1879
|
-
del os.environ[
|
|
2125
|
+
if "BRAINTRUST_OTEL_COMPAT" in os.environ:
|
|
2126
|
+
del os.environ["BRAINTRUST_OTEL_COMPAT"]
|
|
1880
2127
|
|
|
1881
2128
|
init_test_logger(__name__)
|
|
1882
2129
|
|
|
@@ -1901,7 +2148,7 @@ def test_parent_context_with_otel_ids(with_memory_logger, reset_id_generator_sta
|
|
|
1901
2148
|
original_root_span_id = parent_span.root_span_id
|
|
1902
2149
|
|
|
1903
2150
|
def is_hex(s):
|
|
1904
|
-
return all(c in
|
|
2151
|
+
return all(c in "0123456789abcdef" for c in s.lower())
|
|
1905
2152
|
|
|
1906
2153
|
assert is_hex(original_span_id)
|
|
1907
2154
|
assert is_hex(original_root_span_id)
|
|
@@ -1978,14 +2225,15 @@ def test_span_start_span_with_explicit_parent(with_memory_logger):
|
|
|
1978
2225
|
span3_log = next(l for l in logs if l.get("span_attributes", {}).get("name") == "span3")
|
|
1979
2226
|
|
|
1980
2227
|
# span3 should NOT have span2 as parent (would happen if it inherited from context)
|
|
1981
|
-
assert span2_span_id not in span3_log.get("span_parents", []),
|
|
2228
|
+
assert span2_span_id not in span3_log.get("span_parents", []), (
|
|
1982
2229
|
"span3 should not inherit from span2 context when explicit parent is provided"
|
|
2230
|
+
)
|
|
1983
2231
|
|
|
1984
2232
|
# span3 should inherit from root (the explicit parent)
|
|
1985
|
-
assert root_span_id in span3_log.get("span_parents", []),
|
|
2233
|
+
assert root_span_id in span3_log.get("span_parents", []), (
|
|
1986
2234
|
"span3 should have root_span_id in span_parents from explicit parent"
|
|
1987
|
-
|
|
1988
|
-
|
|
2235
|
+
)
|
|
2236
|
+
assert span3_log["root_span_id"] == root_root_span_id, "span3 should have root's root_span_id"
|
|
1989
2237
|
|
|
1990
2238
|
|
|
1991
2239
|
def test_span_start_span_inherits_from_self(with_memory_logger):
|
|
@@ -2011,8 +2259,9 @@ def test_span_start_span_inherits_from_self(with_memory_logger):
|
|
|
2011
2259
|
|
|
2012
2260
|
# Child should inherit parent's root_span_id and have parent_span_id in span_parents
|
|
2013
2261
|
assert child_log["root_span_id"] == parent_root_span_id
|
|
2014
|
-
assert parent_span_id in child_log.get("span_parents", []),
|
|
2262
|
+
assert parent_span_id in child_log.get("span_parents", []), (
|
|
2015
2263
|
"child should have parent_span_id in span_parents when no explicit parent is provided"
|
|
2264
|
+
)
|
|
2016
2265
|
|
|
2017
2266
|
|
|
2018
2267
|
def test_span_start_span_with_exported_span_parent(with_memory_logger):
|
|
@@ -2046,10 +2295,12 @@ def test_span_start_span_with_exported_span_parent(with_memory_logger):
|
|
|
2046
2295
|
|
|
2047
2296
|
# Child should inherit from exported_parent, not active_context
|
|
2048
2297
|
assert child_log["root_span_id"] == exported_parent_root_span_id
|
|
2049
|
-
assert exported_parent_span_id in child_log.get("span_parents", []),
|
|
2298
|
+
assert exported_parent_span_id in child_log.get("span_parents", []), (
|
|
2050
2299
|
"child should have exported_parent_span_id in span_parents"
|
|
2051
|
-
|
|
2300
|
+
)
|
|
2301
|
+
assert active_context_span_id not in child_log.get("span_parents", []), (
|
|
2052
2302
|
"child should NOT have active_context_span_id in span_parents"
|
|
2303
|
+
)
|
|
2053
2304
|
|
|
2054
2305
|
|
|
2055
2306
|
def test_get_exporter_returns_v3_by_default():
|
|
@@ -2082,6 +2333,7 @@ def test_experiment_export_respects_otel_compat_default():
|
|
|
2082
2333
|
exported = experiment.export()
|
|
2083
2334
|
|
|
2084
2335
|
from braintrust.span_identifier_v4 import SpanComponentsV4
|
|
2336
|
+
|
|
2085
2337
|
version = SpanComponentsV4.get_version(exported)
|
|
2086
2338
|
assert version == 3, f"Expected V3 encoding (version=3), got version={version}"
|
|
2087
2339
|
|
|
@@ -2094,6 +2346,7 @@ def test_experiment_export_respects_otel_compat_enabled():
|
|
|
2094
2346
|
exported = experiment.export()
|
|
2095
2347
|
|
|
2096
2348
|
from braintrust.span_identifier_v4 import SpanComponentsV4
|
|
2349
|
+
|
|
2097
2350
|
version = SpanComponentsV4.get_version(exported)
|
|
2098
2351
|
assert version == 4, f"Expected V4 encoding (version=4), got version={version}"
|
|
2099
2352
|
|
|
@@ -2106,6 +2359,7 @@ def test_logger_export_respects_otel_compat_default():
|
|
|
2106
2359
|
exported = test_logger.export()
|
|
2107
2360
|
|
|
2108
2361
|
from braintrust.span_identifier_v4 import SpanComponentsV4
|
|
2362
|
+
|
|
2109
2363
|
version = SpanComponentsV4.get_version(exported)
|
|
2110
2364
|
assert version == 3, f"Expected V3 encoding (version=3), got version={version}"
|
|
2111
2365
|
|
|
@@ -2118,6 +2372,7 @@ def test_logger_export_respects_otel_compat_enabled():
|
|
|
2118
2372
|
exported = test_logger.export()
|
|
2119
2373
|
|
|
2120
2374
|
from braintrust.span_identifier_v4 import SpanComponentsV4
|
|
2375
|
+
|
|
2121
2376
|
version = SpanComponentsV4.get_version(exported)
|
|
2122
2377
|
assert version == 4, f"Expected V4 encoding (version=4), got version={version}"
|
|
2123
2378
|
|
braintrust/version.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
braintrust/__init__.py,sha256=-NLWOaTdzVtQFu2TA0qYULbxP4pAdVdgqzZWosqL2eI,2092
|
|
2
|
-
braintrust/_generated_types.py,sha256=
|
|
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
5
|
braintrust/bt_json.py,sha256=NrpEpl7FkwgL3sSg_XoKCR8HYy9uEiBOfaJ4r7HkW2U,816
|
|
@@ -7,14 +7,14 @@ braintrust/conftest.py,sha256=_leQUhneeVovUhXeHdXiyC04bykrjWs75XJNvmXsR_g,1385
|
|
|
7
7
|
braintrust/context.py,sha256=ZXIOc3zXIXOzAopkuPIM9tq2prfsTjH0-e_FEEFTTJI,4235
|
|
8
8
|
braintrust/db_fields.py,sha256=DBGFhfu9B3aLQI6cU6P2WGrdfCIs8AzDFRQEUBt9NCw,439
|
|
9
9
|
braintrust/framework.py,sha256=Uf2DogzQG4suCcmABeQzc-5SWCO5tB2ic1OeQFZo_o4,57618
|
|
10
|
-
braintrust/framework2.py,sha256=
|
|
11
|
-
braintrust/generated_types.py,sha256=
|
|
10
|
+
braintrust/framework2.py,sha256=ck1-ybuEHf_ExEfveMGmQ02FDiSrIRsxJxTd64qlotM,15885
|
|
11
|
+
braintrust/generated_types.py,sha256=StMC58Mg_Imc4Z-xpLzqZql2E5K14b92FAnxj0aOrtw,4521
|
|
12
12
|
braintrust/git_fields.py,sha256=oT1h1nVkidGrIIamtIQfOpbuEzd1n-nup9bx54J_u0A,1480
|
|
13
13
|
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=
|
|
17
|
+
braintrust/logger.py,sha256=gODmFkQpS7wW3NCPlFvD7XHanW4ui2-5ld1n1MChrdc,209442
|
|
18
18
|
braintrust/merge_row_batch.py,sha256=NX4jRE9uuFB3Z7btrarQp_di84_NGTjvzpJhksn82W8,9882
|
|
19
19
|
braintrust/oai.py,sha256=8gUtISG8oIwgMRcFtlfDndzaF_md1Po_4_DFZcH_PkQ,33159
|
|
20
20
|
braintrust/object.py,sha256=vYLyYWncsqLD00zffZUJwGTSkcJF9IIXmgIzrx3Np5c,632
|
|
@@ -33,7 +33,7 @@ braintrust/span_types.py,sha256=UvuyjfL_Delao14TJs9IjnpYLcgDSodKJfAYju01aXQ,292
|
|
|
33
33
|
braintrust/test_framework.py,sha256=fALvUefSmNOdQcEVgKHvdCOnPlUreZjhF5AiqfNLBPg,14657
|
|
34
34
|
braintrust/test_helpers.py,sha256=-RrxDtyCfOOukhdN6tFSwr-Nmq7DrNQ7KO45hySqNZ0,13549
|
|
35
35
|
braintrust/test_id_gen.py,sha256=mgArTyEBV-Xv21ARHPSHEPBsJshZrvIiPjBLNOKsAko,2490
|
|
36
|
-
braintrust/test_logger.py,sha256=
|
|
36
|
+
braintrust/test_logger.py,sha256=UGRwDtGnoni5HrwTSHUsJQjM0njH77H1cJTVKNb40dw,92756
|
|
37
37
|
braintrust/test_otel.py,sha256=janmEtu6dyFQL8N68WjRTS-65Kr5vSNSSwIaBWqXlyw,27243
|
|
38
38
|
braintrust/test_queue.py,sha256=MdH6R9uSk_4akY4Db514Cpukwiy2RJ76Lqts1nQwZJY,8432
|
|
39
39
|
braintrust/test_serializable_data_class.py,sha256=b04Ym64YtC6GJRGbKIN4J20RG1QN1FlnODwtEQh4sv0,1897
|
|
@@ -41,7 +41,7 @@ braintrust/test_span_components.py,sha256=UnF6ZL4k41XZ-CnfbjuqLeK4MZLtHTMdID3CMh
|
|
|
41
41
|
braintrust/test_util.py,sha256=gyqe2JspRP7oXlp6ENztZe2fdRTOEMZMKpQi00y1DSc,4538
|
|
42
42
|
braintrust/test_version.py,sha256=hk5JKjEFbNJ_ONc1VEkqHquflzre34RpFhCEYLTK8iA,1051
|
|
43
43
|
braintrust/util.py,sha256=Ec6sRkQw5BckGrFjdA4YTyu_2BaKmHh4tWDwAi_ysOw,7227
|
|
44
|
-
braintrust/version.py,sha256=
|
|
44
|
+
braintrust/version.py,sha256=3QoSvR9upAkmscSlqZqj5iUZV0cddFtBgRVYdbEJU_0,117
|
|
45
45
|
braintrust/xact_ids.py,sha256=bdyp88HjlyIkglgLSqYlCYscdSH6EWVyE14sR90Xl1s,658
|
|
46
46
|
braintrust/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
47
|
braintrust/cli/__main__.py,sha256=wCBKHGVmn3IT_yMXk5qfDwyI2SV2gf1tLr0NTxm9T8k,1519
|
|
@@ -104,8 +104,8 @@ braintrust/wrappers/claude_agent_sdk/__init__.py,sha256=CSXJWy-z2fHF7h4VJjLSnXJv
|
|
|
104
104
|
braintrust/wrappers/claude_agent_sdk/_wrapper.py,sha256=uzElIOwwPmF_Y5fbWcKWEPC8HnSzW7byzpiuVKK0TXE,15613
|
|
105
105
|
braintrust/wrappers/claude_agent_sdk/test_wrapper.py,sha256=0NmohdECudFvWtc-5PbANtTXzexkkwIJhGbujydDrT8,6826
|
|
106
106
|
braintrust/wrappers/google_genai/__init__.py,sha256=PGFMuR3c4Gc3SUt24eP7z5AzdS2Dc1uF1d3QPCnLnuo,16018
|
|
107
|
-
braintrust-0.3.
|
|
108
|
-
braintrust-0.3.
|
|
109
|
-
braintrust-0.3.
|
|
110
|
-
braintrust-0.3.
|
|
111
|
-
braintrust-0.3.
|
|
107
|
+
braintrust-0.3.9.dist-info/METADATA,sha256=xEK83D4NuSxMV9ETcoBVzO0AqltIerG5NQP6z1galOk,2942
|
|
108
|
+
braintrust-0.3.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
109
|
+
braintrust-0.3.9.dist-info/entry_points.txt,sha256=Zpc0_09g5xm8as5jHqqFq7fhwO0xHSNct_TrEMONS7Q,60
|
|
110
|
+
braintrust-0.3.9.dist-info/top_level.txt,sha256=hw1-y-UFMf60RzAr8x_eM7SThbIuWfQsQIbVvqSF83A,11
|
|
111
|
+
braintrust-0.3.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|