From 576f51c0791be7e5543ad0e32e8d04d7c3820e8f Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 09:23:21 -0400 Subject: [PATCH 1/9] fix(pydantic_ai): support moved tool manager module Patch ToolManager through pydantic_ai._agent_graph so the integration keeps working after pydantic_ai 1.78.0 moved the implementation out of pydantic_ai._tool_manager. Update the idempotence test to assert against the same stable alias while keeping the existing VCR-backed tool execution regression coverage in place. --- py/src/braintrust/integrations/pydantic_ai/patchers.py | 8 ++++++-- .../pydantic_ai/test_pydantic_ai_integration.py | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/py/src/braintrust/integrations/pydantic_ai/patchers.py b/py/src/braintrust/integrations/pydantic_ai/patchers.py index 0335fcd7..fd164451 100644 --- a/py/src/braintrust/integrations/pydantic_ai/patchers.py +++ b/py/src/braintrust/integrations/pydantic_ai/patchers.py @@ -134,14 +134,18 @@ class StreamedResponseSyncStartProducerPatcher(FunctionWrapperPatcher): class _ToolManagerExecuteFunctionToolPatcher(FunctionWrapperPatcher): name = "pydantic_ai.tool_manager.execute_function_tool" - target_module = "pydantic_ai._tool_manager" + # Regression compatibility note: pydantic_ai 1.78.0 moved ToolManager out + # of the private ``pydantic_ai._tool_manager`` module into + # ``pydantic_ai.tool_manager``. ``pydantic_ai._agent_graph.ToolManager`` is + # a stable alias in both the old and new layouts, so patch that seam. + target_module = "pydantic_ai._agent_graph" target_path = "ToolManager._execute_function_tool_call" wrapper = _tool_manager_execute_function_tool_wrapper class _ToolManagerCallFunctionToolPatcher(FunctionWrapperPatcher): name = "pydantic_ai.tool_manager.call_function_tool" - target_module = "pydantic_ai._tool_manager" + target_module = "pydantic_ai._agent_graph" target_path = "ToolManager._call_function_tool" wrapper = _tool_manager_call_function_tool_wrapper superseded_by = (_ToolManagerExecuteFunctionToolPatcher,) diff --git a/py/src/braintrust/integrations/pydantic_ai/test_pydantic_ai_integration.py b/py/src/braintrust/integrations/pydantic_ai/test_pydantic_ai_integration.py index 8c29f8d9..38369d14 100644 --- a/py/src/braintrust/integrations/pydantic_ai/test_pydantic_ai_integration.py +++ b/py/src/braintrust/integrations/pydantic_ai/test_pydantic_ai_integration.py @@ -2248,7 +2248,7 @@ def test_wrap_model_classes_is_deprecated(monkeypatch): def test_setup_pydantic_ai_is_idempotent_across_new_patch_points(): - import pydantic_ai._tool_manager as tool_manager_module + import pydantic_ai._agent_graph as agent_graph_module import pydantic_ai.direct as direct_module from braintrust.integrations.pydantic_ai.integration import PydanticAIIntegration from pydantic_ai.agent.abstract import AbstractAgent @@ -2257,15 +2257,15 @@ def test_setup_pydantic_ai_is_idempotent_across_new_patch_points(): prepare_model = direct_module.__dict__["_prepare_model"] tool_method_name = ( "_execute_function_tool_call" - if "_execute_function_tool_call" in tool_manager_module.ToolManager.__dict__ + if "_execute_function_tool_call" in agent_graph_module.ToolManager.__dict__ else "_call_function_tool" ) - tool_method = tool_manager_module.ToolManager.__dict__[tool_method_name] + tool_method = agent_graph_module.ToolManager.__dict__[tool_method_name] assert PydanticAIIntegration.setup() is True assert AbstractAgent.__dict__["run"] is run assert direct_module.__dict__["_prepare_model"] is prepare_model - assert tool_manager_module.ToolManager.__dict__[tool_method_name] is tool_method + assert agent_graph_module.ToolManager.__dict__[tool_method_name] is tool_method def test_serialize_content_part_with_binary_content(): From c3fba3499ddbf832681c44eda303a6fe139b4c75 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 10:04:39 -0400 Subject: [PATCH 2/9] fix(packaging): drop psycopg2-binary from extras Remove psycopg2-binary from the cli extra so braintrust[all] no longer pulls it in. The package is not used by the SDK codebase and it breaks Windows free-threaded 3.13/3.14 installs because upstream does not publish cp313t/cp314t wheels. --- py/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/setup.py b/py/setup.py index ee387304..c584158d 100644 --- a/py/setup.py +++ b/py/setup.py @@ -28,7 +28,7 @@ ] extras_require = { - "cli": ["boto3", "psycopg2-binary", "uv", "starlette", "uvicorn"], + "cli": ["boto3", "uv", "starlette", "uvicorn"], "doc": ["pydoc-markdown"], "openai-agents": ["openai-agents"], "otel": ["opentelemetry-api", "opentelemetry-sdk", "opentelemetry-exporter-otlp-proto-http"], From 288475716332e305bd405bed24ef2807b89d4cb4 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 10:10:31 -0400 Subject: [PATCH 3/9] chore(litellm): upgrade pinned version to 1.83.0 Update the optional requirements and LiteLLM test matrix to use 1.83.0. This picks up the security fix for the OIDC userinfo cache key collision CVE while still avoiding the compromised 1.82.7 and 1.82.8 releases. --- py/noxfile.py | 5 +++-- py/requirements-optional.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/py/noxfile.py b/py/noxfile.py index 05dcf9c1..89aa9082 100644 --- a/py/noxfile.py +++ b/py/noxfile.py @@ -86,8 +86,9 @@ def _pinned_python_version(): OPENAI_VERSIONS = (LATEST, "1.77.0", "1.71", "1.91", "1.92") OPENAI_AGENTS_VERSIONS = (LATEST, "0.0.19") # litellm latest requires Python >= 3.10 -# Pin litellm because 1.82.7-1.82.8 are compromised: https://github.com/BerriAI/litellm/issues/24512 -LITELLM_VERSIONS = ("1.82.0", "1.74.0") +# Pin litellm to a version without the 1.82.7-1.82.8 compromise and with the +# OIDC userinfo cache key collision fix from 1.83.0+ +LITELLM_VERSIONS = ("1.83.0", "1.74.0") # CLI bundling started in 0.1.10 - older versions require external Claude Code installation CLAUDE_AGENT_SDK_VERSIONS = (LATEST, "0.1.10") # Keep LATEST for newest API coverage, and pin 2.4.0 to cover the 2.4 -> 2.5 breaking change diff --git a/py/requirements-optional.txt b/py/requirements-optional.txt index 53a423c7..0ef60446 100644 --- a/py/requirements-optional.txt +++ b/py/requirements-optional.txt @@ -7,7 +7,7 @@ langchain-anthropic==1.4.0 langchain-core==1.2.22 langchain-openai==1.1.12 langsmith==0.7.12 -litellm==1.82.0 +litellm==1.83.0 openai==2.24.0 openrouter==0.7.11 pydantic_ai==1.66.0 From 27942558ea5c747922ec9258d7c922aa152c41d8 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 10:15:00 -0400 Subject: [PATCH 4/9] fix(packaging): skip orjson on free-threaded Python Exclude orjson from the performance extra when building on free-threaded interpreters. orjson 3.11.8 does not support free-threaded Python, which breaks braintrust[all] installation on Windows 3.13t and 3.14t runners. --- py/setup.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/py/setup.py b/py/setup.py index c584158d..53a296ff 100644 --- a/py/setup.py +++ b/py/setup.py @@ -1,4 +1,5 @@ import os +import sysconfig import setuptools @@ -27,13 +28,18 @@ "wrapt", ] +is_free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) + +# orjson is not compatible with PyPy or free-threaded Python, so only expose it +# for standard CPython builds where it is supported. +performance_require = [] if is_free_threaded else ["orjson; platform_python_implementation != 'PyPy'"] + extras_require = { "cli": ["boto3", "uv", "starlette", "uvicorn"], "doc": ["pydoc-markdown"], "openai-agents": ["openai-agents"], "otel": ["opentelemetry-api", "opentelemetry-sdk", "opentelemetry-exporter-otlp-proto-http"], - # orjson is not compatible with PyPy, so we exclude it for that platform - "performance": ["orjson; platform_python_implementation != 'PyPy'"], + "performance": performance_require, "temporal": ["temporalio>=1.19.0; python_version>='3.10'"], } From 1370d6036b0cc09ca0f8ddf3cbaafe347a3f67b3 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 10:53:19 -0400 Subject: [PATCH 5/9] fix(packaging): skip temporal extra on free-threaded Windows --- py/setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/py/setup.py b/py/setup.py index 53a296ff..3055a91a 100644 --- a/py/setup.py +++ b/py/setup.py @@ -29,18 +29,23 @@ ] is_free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) +is_windows = os.name == "nt" # orjson is not compatible with PyPy or free-threaded Python, so only expose it # for standard CPython builds where it is supported. performance_require = [] if is_free_threaded else ["orjson; platform_python_implementation != 'PyPy'"] +# temporalio does not currently install cleanly on Windows free-threaded Python, +# so leave the optional integration available everywhere else. +temporal_require = [] if is_free_threaded and is_windows else ["temporalio>=1.19.0; python_version>='3.10'"] + extras_require = { "cli": ["boto3", "uv", "starlette", "uvicorn"], "doc": ["pydoc-markdown"], "openai-agents": ["openai-agents"], "otel": ["opentelemetry-api", "opentelemetry-sdk", "opentelemetry-exporter-otlp-proto-http"], "performance": performance_require, - "temporal": ["temporalio>=1.19.0; python_version>='3.10'"], + "temporal": temporal_require, } extras_require["all"] = sorted({package for packages in extras_require.values() for package in packages}) From ba72f42a1897615ea89ed038a46f34ae78133284 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 11:21:56 -0400 Subject: [PATCH 6/9] chore(ci): bump mise action to v4.0.1 --- .github/workflows/adk-py-test.yaml | 2 +- .github/workflows/checks.yaml | 6 +++--- .github/workflows/langchain-py-test.yaml | 2 +- .github/workflows/publish-py-sdk.yaml | 4 ++-- .github/workflows/test-publish-py-sdk.yaml | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/adk-py-test.yaml b/.github/workflows/adk-py-test.yaml index 2650f60f..adbfa294 100644 --- a/.github/workflows/adk-py-test.yaml +++ b/.github/workflows/adk-py-test.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index b4336914..8d5eb273 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -17,7 +17,7 @@ jobs: with: fetch-depth: 0 # Fetch full history for proper diff - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true @@ -47,7 +47,7 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true @@ -81,7 +81,7 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true diff --git a/.github/workflows/langchain-py-test.yaml b/.github/workflows/langchain-py-test.yaml index 54ac8df8..c49495f6 100644 --- a/.github/workflows/langchain-py-test.yaml +++ b/.github/workflows/langchain-py-test.yaml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true diff --git a/.github/workflows/publish-py-sdk.yaml b/.github/workflows/publish-py-sdk.yaml index d729cf0b..7a88c204 100644 --- a/.github/workflows/publish-py-sdk.yaml +++ b/.github/workflows/publish-py-sdk.yaml @@ -38,7 +38,7 @@ jobs: ref: ${{ github.event.inputs.ref }} fetch-depth: 0 - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true @@ -71,7 +71,7 @@ jobs: ref: ${{ env.COMMIT_SHA }} fetch-depth: 0 - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true diff --git a/.github/workflows/test-publish-py-sdk.yaml b/.github/workflows/test-publish-py-sdk.yaml index 3e41a430..9c418ee4 100644 --- a/.github/workflows/test-publish-py-sdk.yaml +++ b/.github/workflows/test-publish-py-sdk.yaml @@ -42,7 +42,7 @@ jobs: ref: ${{ github.event.inputs.ref }} fetch-depth: 0 - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true @@ -91,7 +91,7 @@ jobs: ref: ${{ env.COMMIT_SHA }} fetch-depth: 0 - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 with: cache: true experimental: true From 7361beaae45088a7037fe51a17739b0acb47d54f Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 11:27:58 -0400 Subject: [PATCH 7/9] fix(ci): use uv binary in build install check --- .github/workflows/checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 8d5eb273..1b5b3ef3 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -59,7 +59,7 @@ jobs: run: | # This is already done by make install-dev, but we're keeping this as a separate step # to explicitly verify that installation works - mise exec python@${{ matrix.python-version }} -- python -m uv pip install -e ./py[all] + mise exec python@${{ matrix.python-version }} -- uv pip install -e ./py[all] - name: Test whether the Python SDK can be imported run: | mise exec python@${{ matrix.python-version }} -- python -c 'import braintrust' From 6f188d743e3838b46101ea0420755d02c5d249b9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 11:42:38 -0400 Subject: [PATCH 8/9] refactor(ci): split smoke and nox matrix jobs --- .github/workflows/checks.yaml | 40 +++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 1b5b3ef3..5bcfb746 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -33,16 +33,15 @@ jobs: - name: Ensure SHA pinned actions uses: zgosalvez/github-actions-ensure-sha-pinned-actions@70c4af2ed5282c51ba40566d026d6647852ffa3e # v5.0.1 - build: + smoke: runs-on: ${{ matrix.os }} - timeout-minutes: 30 + timeout-minutes: 20 strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] os: [ubuntu-latest, windows-latest] - shard: [0, 1] steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -62,7 +61,30 @@ jobs: mise exec python@${{ matrix.python-version }} -- uv pip install -e ./py[all] - name: Test whether the Python SDK can be imported run: | - mise exec python@${{ matrix.python-version }} -- python -c 'import braintrust' + mise exec python@${{ matrix.python-version }} -- uv run --active --no-sync python -c 'import braintrust' + + nox: + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + os: [ubuntu-latest, windows-latest] + shard: [0, 1] + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Set up mise + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 + with: + cache: true + experimental: true + install_args: python@${{ matrix.python-version }} + - name: Install dependencies + run: | + mise exec python@${{ matrix.python-version }} -- make -C py install-dev - name: Run nox tests (shard ${{ matrix.shard }}/2) shell: bash run: | @@ -75,7 +97,9 @@ jobs: uses: ./.github/workflows/langchain-py-test.yaml upload-wheel: - needs: build + needs: + - smoke + - nox runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -100,7 +124,8 @@ jobs: needs: - lint - ensure-pinned-actions - - build + - smoke + - nox - adk-py - langchain-py - upload-wheel @@ -124,7 +149,8 @@ jobs: check_result "lint" "${{ needs.lint.result }}" check_result "ensure-pinned-actions" "${{ needs.ensure-pinned-actions.result }}" - check_result "build" "${{ needs.build.result }}" + check_result "smoke" "${{ needs.smoke.result }}" + check_result "nox" "${{ needs.nox.result }}" check_result "adk-py" "${{ needs.adk-py.result }}" check_result "langchain-py" "${{ needs.langchain-py.result }}" check_result "upload-wheel" "${{ needs.upload-wheel.result }}" From 1b455e5aab81b0aec5faa0eb413ffb0c6f0bf5f2 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 11:54:49 -0400 Subject: [PATCH 9/9] fix(ci): use active venv for smoke import check --- .github/workflows/checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 5bcfb746..ca7844bf 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -61,7 +61,7 @@ jobs: mise exec python@${{ matrix.python-version }} -- uv pip install -e ./py[all] - name: Test whether the Python SDK can be imported run: | - mise exec python@${{ matrix.python-version }} -- uv run --active --no-sync python -c 'import braintrust' + mise exec python@${{ matrix.python-version }} -- uv run --active --no-project python -c 'import braintrust' nox: runs-on: ${{ matrix.os }}