braintrust 0.3.10__py3-none-any.whl → 0.3.12__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.
@@ -10,6 +10,7 @@ from ..logger import BraintrustState
10
10
  ORIGIN_HEADER = "origin"
11
11
  BRAINTRUST_AUTH_TOKEN_HEADER = "x-bt-auth-token"
12
12
  BRAINTRUST_ORG_NAME_HEADER = "x-bt-org-name"
13
+ BRAINTRUST_PROJECT_ID_HEADER = "x-bt-project-id"
13
14
 
14
15
 
15
16
  @dataclass
@@ -17,6 +18,7 @@ class RequestContext:
17
18
  app_origin: Optional[str]
18
19
  token: Optional[str]
19
20
  org_name: Optional[str]
21
+ project_id: Optional[str]
20
22
  state: Optional[BraintrustState]
21
23
 
22
24
 
@@ -56,6 +58,7 @@ class AuthorizationMiddleware(BaseHTTPMiddleware):
56
58
  app_origin=extract_allowed_origin(request.headers.get(ORIGIN_HEADER)),
57
59
  token=None,
58
60
  org_name=request.headers.get(BRAINTRUST_ORG_NAME_HEADER),
61
+ project_id=request.headers.get(BRAINTRUST_PROJECT_ID_HEADER),
59
62
  state=None,
60
63
  )
61
64
 
@@ -18,6 +18,7 @@ ALLOWED_HEADERS = [
18
18
  "x-bt-auth-token",
19
19
  "x-bt-parent",
20
20
  "x-bt-org-name",
21
+ "x-bt-project-id",
21
22
  "x-bt-stream-fmt",
22
23
  "x-bt-use-cache",
23
24
  "x-stainless-os",
@@ -196,7 +196,7 @@ async def run_eval(request: Request) -> Union[JSONResponse, StreamingResponse]:
196
196
  "state": state,
197
197
  "scores": evaluator.scores
198
198
  + [
199
- make_scorer(state, score["name"], score["function_id"])
199
+ make_scorer(state, score["name"], score["function_id"], ctx.project_id)
200
200
  for score in eval_data.get("scores", [])
201
201
  ],
202
202
  "stream": stream_fn,
@@ -305,7 +305,7 @@ def snake_to_camel(snake_str: str) -> str:
305
305
  return components[0] + "".join(x.title() for x in components[1:]) if components else snake_str
306
306
 
307
307
 
308
- def make_scorer(state: BraintrustState, name: str, score: FunctionId) -> EvalScorer[Any, Any]:
308
+ def make_scorer(state: BraintrustState, name: str, score: FunctionId, project_id: Optional[str] = None) -> EvalScorer[Any, Any]:
309
309
  def scorer_fn(input, output, expected, metadata):
310
310
  request = {
311
311
  **score,
@@ -315,7 +315,10 @@ def make_scorer(state: BraintrustState, name: str, score: FunctionId) -> EvalSco
315
315
  "mode": "auto",
316
316
  "strict": True,
317
317
  }
318
- result = state.proxy_conn().post("function/invoke", json=request, headers={"Accept": "application/json"})
318
+ headers = {"Accept": "application/json"}
319
+ if project_id:
320
+ headers["x-bt-project-id"] = project_id
321
+ result = state.proxy_conn().post("function/invoke", json=request, headers=headers)
319
322
  result.raise_for_status()
320
323
  data = result.json()
321
324
  return data
@@ -4,7 +4,6 @@ from pathlib import Path
4
4
  from typing import Any
5
5
 
6
6
  import pytest
7
-
8
7
  from braintrust.framework import _evals
9
8
  from braintrust.test_helpers import has_devserver_installed
10
9
 
@@ -28,9 +27,8 @@ def client():
28
27
  pytest.skip("Devserver dependencies not installed (requires .[cli])")
29
28
 
30
29
  # Import CLI dependencies inside the fixture
31
- from starlette.testclient import TestClient
32
-
33
30
  from braintrust.devserver.server import create_app
31
+ from starlette.testclient import TestClient
34
32
 
35
33
  # Use the real simple_eval.py example
36
34
  eval_file = Path(__file__).parent.parent.parent.parent / "examples" / "evals" / "simple_eval.py"
@@ -39,8 +37,8 @@ def client():
39
37
  _evals.clear()
40
38
 
41
39
  # Load the eval file to register evaluators (but don't run them)
42
- spec = __import__('importlib.util').util.spec_from_file_location("simple_eval", str(eval_file))
43
- module = __import__('importlib.util').util.module_from_spec(spec)
40
+ spec = __import__("importlib.util").util.spec_from_file_location("simple_eval", str(eval_file))
41
+ module = __import__("importlib.util").util.module_from_spec(spec)
44
42
 
45
43
  # Get evaluators from the module without executing Eval()
46
44
  # We need to parse the file and extract the Evaluator definition
@@ -134,6 +132,7 @@ def parse_sse_events(response_text: str) -> list[dict[str, Any]]:
134
132
  return events
135
133
 
136
134
 
135
+ @pytest.mark.skip
137
136
  @pytest.mark.vcr
138
137
  def test_eval_sse_streaming(client, api_key, org_name):
139
138
  """
braintrust/oai.py CHANGED
@@ -845,7 +845,7 @@ def _parse_metrics_from_usage(usage: Any) -> Dict[str, Any]:
845
845
 
846
846
 
847
847
  def _is_numeric(v):
848
- return isinstance(v, (int, float, complex))
848
+ return isinstance(v, (int, float, complex)) and not isinstance(v, bool)
849
849
 
850
850
 
851
851
  def prettify_params(params: Dict[str, Any]) -> Dict[str, Any]:
braintrust/version.py CHANGED
@@ -1,4 +1,4 @@
1
- VERSION = "0.3.10"
1
+ VERSION = "0.3.12"
2
2
 
3
3
  # this will be templated during the build
4
- GIT_COMMIT = "160c352d87c121ef1ea43e49831453b9e8d4d0f8"
4
+ GIT_COMMIT = "8c2b2995c2097f5699c101f4ab5e653ff40014a1"
@@ -0,0 +1,144 @@
1
+ """
2
+ Tests to ensure wrap_openai works correctly with OpenRouter.
3
+
4
+ OpenRouter is a popular API gateway that provides access to multiple LLM providers
5
+ through an OpenAI-compatible interface. This test validates that our wrapper handles
6
+ OpenRouter-specific response fields correctly (e.g., boolean `is_byok` in usage).
7
+ """
8
+
9
+ import os
10
+ import time
11
+
12
+ import pytest
13
+ from braintrust import logger, wrap_openai
14
+ from braintrust.test_helpers import init_test_logger
15
+ from braintrust.wrappers.test_utils import assert_metrics_are_valid
16
+ from openai import AsyncOpenAI, OpenAI
17
+
18
+ PROJECT_NAME = "test-openrouter"
19
+ TEST_MODEL = "openai/gpt-4o-mini"
20
+
21
+
22
+ @pytest.fixture(scope="module")
23
+ def vcr_config():
24
+ return {
25
+ "filter_headers": [
26
+ "authorization",
27
+ ]
28
+ }
29
+
30
+
31
+ @pytest.fixture
32
+ def memory_logger():
33
+ init_test_logger(PROJECT_NAME)
34
+ with logger._internal_with_memory_background_logger() as bgl:
35
+ yield bgl
36
+
37
+
38
+ def _get_client():
39
+ return OpenAI(
40
+ base_url="https://openrouter.ai/api/v1",
41
+ api_key=os.environ.get("OPENROUTER_API_KEY"),
42
+ )
43
+
44
+
45
+ def _get_async_client():
46
+ return AsyncOpenAI(
47
+ base_url="https://openrouter.ai/api/v1",
48
+ api_key=os.environ.get("OPENROUTER_API_KEY"),
49
+ )
50
+
51
+
52
+ @pytest.mark.vcr
53
+ def test_openrouter_chat_completion_sync(memory_logger):
54
+ assert not memory_logger.pop()
55
+
56
+ client = wrap_openai(_get_client())
57
+
58
+ start = time.time()
59
+ response = client.chat.completions.create(
60
+ model=TEST_MODEL,
61
+ messages=[{"role": "user", "content": "What is 2+2? Reply with just the number."}],
62
+ max_tokens=10,
63
+ )
64
+ end = time.time()
65
+
66
+ assert response
67
+ assert response.choices[0].message.content
68
+ assert "4" in response.choices[0].message.content
69
+
70
+ spans = memory_logger.pop()
71
+ assert len(spans) == 1
72
+ span = spans[0]
73
+
74
+ metrics = span["metrics"]
75
+ assert_metrics_are_valid(metrics, start, end)
76
+
77
+ # Ensure no boolean values in metrics (the original bug with is_byok)
78
+ for key, value in metrics.items():
79
+ assert not isinstance(value, bool), f"Metric {key} should not be a boolean"
80
+
81
+
82
+ @pytest.mark.vcr
83
+ @pytest.mark.asyncio
84
+ async def test_openrouter_chat_completion_async(memory_logger):
85
+ """Test that wrap_openai works with OpenRouter's async client."""
86
+ assert not memory_logger.pop()
87
+
88
+ client = wrap_openai(_get_async_client())
89
+
90
+ start = time.time()
91
+ response = await client.chat.completions.create(
92
+ model=TEST_MODEL,
93
+ messages=[{"role": "user", "content": "What is 3+3? Reply with just the number."}],
94
+ max_tokens=10,
95
+ )
96
+ end = time.time()
97
+
98
+ assert response
99
+ assert response.choices[0].message.content
100
+ assert "6" in response.choices[0].message.content
101
+
102
+ spans = memory_logger.pop()
103
+ assert len(spans) == 1
104
+ span = spans[0]
105
+
106
+ metrics = span["metrics"]
107
+ assert_metrics_are_valid(metrics, start, end)
108
+
109
+ for key, value in metrics.items():
110
+ assert not isinstance(value, bool), f"Metric {key} should not be a boolean"
111
+
112
+
113
+ @pytest.mark.vcr
114
+ def test_openrouter_streaming_sync(memory_logger):
115
+ """Test that wrap_openai works with OpenRouter's streaming responses."""
116
+ assert not memory_logger.pop()
117
+
118
+ client = wrap_openai(_get_client())
119
+
120
+ start = time.time()
121
+ chunks = []
122
+ stream = client.chat.completions.create(
123
+ model=TEST_MODEL,
124
+ messages=[{"role": "user", "content": "What is 5+5? Reply with just the number."}],
125
+ max_tokens=10,
126
+ stream=True,
127
+ )
128
+ for chunk in stream:
129
+ chunks.append(chunk)
130
+ end = time.time()
131
+
132
+ assert chunks
133
+ content = "".join(c.choices[0].delta.content or "" for c in chunks if c.choices)
134
+ assert "10" in content
135
+
136
+ spans = memory_logger.pop()
137
+ assert len(spans) == 1
138
+ span = spans[0]
139
+
140
+ metrics = span["metrics"]
141
+ assert_metrics_are_valid(metrics, start, end)
142
+
143
+ for key, value in metrics.items():
144
+ assert not isinstance(value, bool), f"Metric {key} should not be a boolean"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braintrust
3
- Version: 0.3.10
3
+ Version: 0.3.12
4
4
  Summary: SDK for integrating Braintrust
5
5
  Home-page: https://www.braintrust.dev
6
6
  Author: Braintrust
@@ -16,7 +16,7 @@ braintrust/http_headers.py,sha256=9ZsDcsAKG04SGowsgchZktD6rG_oSTKWa8QyGUPA4xE,15
16
16
  braintrust/id_gen.py,sha256=PVkz-pS-9AzgmnAgpV-jgOFFo4hfl6e3IP9dVt6FouQ,1595
17
17
  braintrust/logger.py,sha256=qIElwbRqzT_3mrQ1XiS7razFxADjL6775eukwkiIhA0,209333
18
18
  braintrust/merge_row_batch.py,sha256=NX4jRE9uuFB3Z7btrarQp_di84_NGTjvzpJhksn82W8,9882
19
- braintrust/oai.py,sha256=8gUtISG8oIwgMRcFtlfDndzaF_md1Po_4_DFZcH_PkQ,33159
19
+ braintrust/oai.py,sha256=DW_i6bXXDprwZ_uwcdIE25MUxx5y5DDdqr0ucdIWP9I,33187
20
20
  braintrust/object.py,sha256=vYLyYWncsqLD00zffZUJwGTSkcJF9IIXmgIzrx3Np5c,632
21
21
  braintrust/parameters.py,sha256=E1NJGJZ4Qjhp2h4Rghrh4qdE0FiaXLGPapS_iAk8es8,5946
22
22
  braintrust/prompt.py,sha256=c7UR-UeJ8Hf-DGyFRyUxSgXEOkZFhVd9-IhvyPhy1xI,1938
@@ -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=SJg7EMz9LRoqG1nnPiMC44laB2sXCOxz83gnpK4Ee5g,118
45
+ braintrust/version.py,sha256=i1ljewUfl4VA2SYv_e8CfC5C1RUiCNBL8mOBlRusJHk,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
@@ -58,16 +58,16 @@ braintrust/contrib/__init__.py,sha256=Dh-0yGnjLhjK34QTV8ZRnjRIXbLu3q8C1h9GQsRKP6
58
58
  braintrust/contrib/temporal/__init__.py,sha256=kyV2FwryszPGb6sR5H3xR9FijFbJslDC3k-d7RNZufE,16519
59
59
  braintrust/contrib/temporal/test_temporal.py,sha256=UJxogj9z6qXgVCPQ_FiUSIfJXvz2LMsfLKMgrboyLw0,18491
60
60
  braintrust/devserver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
- braintrust/devserver/auth.py,sha256=rRHNe31dbCKt8jSRsIx0DjyRqi98PZ5LtJ-ScGu2TfE,2683
61
+ braintrust/devserver/auth.py,sha256=ZCtfLI16u35elXx6vDHGJTcpEV8wJVVfJALuuVHZxW0,2840
62
62
  braintrust/devserver/cache.py,sha256=UPl_Co4D4WqWK8ly8lozkLBEk609qlXiF5DMrOWpTHw,1804
63
- braintrust/devserver/cors.py,sha256=7spdtvhXHEJ6FLIQQygwi1S7D52PPTY2RteG8sw7YQw,5568
63
+ braintrust/devserver/cors.py,sha256=Tp0C3KuR0jEbLcjnBA0HyEgzesW14OybYI-xqxHfMzk,5591
64
64
  braintrust/devserver/dataset.py,sha256=93iWWvmuWQut2uG2AJD63Q8fPZGA0Nc9y_r6X_XJRFM,2315
65
65
  braintrust/devserver/eval_hooks.py,sha256=T5LWmAZjqa8kiXDKXYZlEsx_VhbIoiv8_X10s2v9C7M,1767
66
66
  braintrust/devserver/schemas.py,sha256=q9_I1DoaF5clLmMCkQvpB1DvFhy2GE1dZCumAc0S76M,9098
67
- braintrust/devserver/server.py,sha256=8lU0WNweoGM9sK8IiKQj4RtnNupKVlNEBZpbcXP07fc,12455
67
+ braintrust/devserver/server.py,sha256=vmpLYR8OihAINpygkvPPNbg0t4M1OPeCb8rSco6QY9s,12606
68
68
  braintrust/devserver/test_cached_login.py,sha256=8RxzS3UuBeGx2L5j4mzXSQnJzF7pL0d4AhS3MNzyqi8,3741
69
69
  braintrust/devserver/test_lru_cache.py,sha256=5YYJ5uFj7k4Z4PQQ-UOV7bLP5zBYVo-5jV5_hpthtgM,4164
70
- braintrust/devserver/test_server_integration.py,sha256=Nq6OyEd5UT4VH4XHMRB8cJ2KUXzzHtDeWGGwr4WLsYA,6695
70
+ braintrust/devserver/test_server_integration.py,sha256=ygIYDsTCSqwa_rzQDtdX0mjkXQVy7qyJjntdoCgtjKM,6711
71
71
  braintrust/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
72
  braintrust/functions/constants.py,sha256=g_EDiSrfCltHr5QaQMQzJ3qy3D29X-11LanDqlicqB0,23
73
73
  braintrust/functions/invoke.py,sha256=IGs_Zz3UbuKJqpg7O1sVcVIOGTD7QfcTXN2Ep83CqUY,8651
@@ -96,6 +96,7 @@ braintrust/wrappers/test_dspy.py,sha256=x5vXu-NoFFjWGIcvFvXICVsDasQEQzPuSlxgzbHJ
96
96
  braintrust/wrappers/test_google_genai.py,sha256=20e4DqLUZmcpVGircj7vN0IxsS5FVjvveqOu0JNZ5jo,17596
97
97
  braintrust/wrappers/test_litellm.py,sha256=sNId6qnbQibT66aB_lsxX_HlaPF6GoMFaawgnF1A2a4,21428
98
98
  braintrust/wrappers/test_openai.py,sha256=UqKtoIlZlqiLC6x7Rbv9zROgDdE5zvXZObbFMKpmifw,60237
99
+ braintrust/wrappers/test_openrouter.py,sha256=9wIYuwObFkADH8h5pzHFB4C9TcL_HTeq1KlODYhSMLs,3987
99
100
  braintrust/wrappers/test_pydantic_ai.py,sha256=uqWbk1D8iX3q9qh_Q8PkopNUfPQCBCvOSa4KkpuIqqE,5057
100
101
  braintrust/wrappers/test_utils.py,sha256=Qz7LYG5V0DK2KuTJ_YLGpO_Zr_LJFfJgZX_Ps8tlM_c,505
101
102
  braintrust/wrappers/agno/__init__.py,sha256=V0dnEhu2U0FabyEmwDbWv5x6rP1rdI8z9JLAB_8XuXk,2416
@@ -108,8 +109,8 @@ braintrust/wrappers/claude_agent_sdk/__init__.py,sha256=CSXJWy-z2fHF7h4VJjLSnXJv
108
109
  braintrust/wrappers/claude_agent_sdk/_wrapper.py,sha256=uzElIOwwPmF_Y5fbWcKWEPC8HnSzW7byzpiuVKK0TXE,15613
109
110
  braintrust/wrappers/claude_agent_sdk/test_wrapper.py,sha256=0NmohdECudFvWtc-5PbANtTXzexkkwIJhGbujydDrT8,6826
110
111
  braintrust/wrappers/google_genai/__init__.py,sha256=PGFMuR3c4Gc3SUt24eP7z5AzdS2Dc1uF1d3QPCnLnuo,16018
111
- braintrust-0.3.10.dist-info/METADATA,sha256=hJlOuBcUfgMXQhHh3nPhixLRZjZpcpcu8VX7YCFXNBE,3131
112
- braintrust-0.3.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
113
- braintrust-0.3.10.dist-info/entry_points.txt,sha256=Zpc0_09g5xm8as5jHqqFq7fhwO0xHSNct_TrEMONS7Q,60
114
- braintrust-0.3.10.dist-info/top_level.txt,sha256=hw1-y-UFMf60RzAr8x_eM7SThbIuWfQsQIbVvqSF83A,11
115
- braintrust-0.3.10.dist-info/RECORD,,
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,,