Skip to content

Commit f0b3b62

Browse files
authored
fix(pydantic_ai): support moved tool manager module (#224)
* 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. * 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. * 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. * 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. * fix(packaging): skip temporal extra on free-threaded Windows * chore(ci): bump mise action to v4.0.1 * fix(ci): use uv binary in build install check * refactor(ci): split smoke and nox matrix jobs * fix(ci): use active venv for smoke import check
1 parent 351b89c commit f0b3b62

10 files changed

Lines changed: 72 additions & 30 deletions

File tree

.github/workflows/adk-py-test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
1717

1818
- name: Set up mise
19-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
19+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
2020
with:
2121
cache: true
2222
experimental: true

.github/workflows/checks.yaml

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
with:
1818
fetch-depth: 0 # Fetch full history for proper diff
1919
- name: Set up mise
20-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
20+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
2121
with:
2222
cache: true
2323
experimental: true
@@ -33,21 +33,20 @@ jobs:
3333
- name: Ensure SHA pinned actions
3434
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@70c4af2ed5282c51ba40566d026d6647852ffa3e # v5.0.1
3535

36-
build:
36+
smoke:
3737
runs-on: ${{ matrix.os }}
38-
timeout-minutes: 30
38+
timeout-minutes: 20
3939

4040
strategy:
4141
fail-fast: false
4242
matrix:
4343
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
4444
os: [ubuntu-latest, windows-latest]
45-
shard: [0, 1]
4645

4746
steps:
4847
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
4948
- name: Set up mise
50-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
49+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
5150
with:
5251
cache: true
5352
experimental: true
@@ -59,10 +58,33 @@ jobs:
5958
run: |
6059
# This is already done by make install-dev, but we're keeping this as a separate step
6160
# to explicitly verify that installation works
62-
mise exec python@${{ matrix.python-version }} -- python -m uv pip install -e ./py[all]
61+
mise exec python@${{ matrix.python-version }} -- uv pip install -e ./py[all]
6362
- name: Test whether the Python SDK can be imported
6463
run: |
65-
mise exec python@${{ matrix.python-version }} -- python -c 'import braintrust'
64+
mise exec python@${{ matrix.python-version }} -- uv run --active --no-project python -c 'import braintrust'
65+
66+
nox:
67+
runs-on: ${{ matrix.os }}
68+
timeout-minutes: 30
69+
70+
strategy:
71+
fail-fast: false
72+
matrix:
73+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
74+
os: [ubuntu-latest, windows-latest]
75+
shard: [0, 1]
76+
77+
steps:
78+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
79+
- name: Set up mise
80+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
81+
with:
82+
cache: true
83+
experimental: true
84+
install_args: python@${{ matrix.python-version }}
85+
- name: Install dependencies
86+
run: |
87+
mise exec python@${{ matrix.python-version }} -- make -C py install-dev
6688
- name: Run nox tests (shard ${{ matrix.shard }}/2)
6789
shell: bash
6890
run: |
@@ -75,13 +97,15 @@ jobs:
7597
uses: ./.github/workflows/langchain-py-test.yaml
7698

7799
upload-wheel:
78-
needs: build
100+
needs:
101+
- smoke
102+
- nox
79103
runs-on: ubuntu-latest
80104
timeout-minutes: 10
81105
steps:
82106
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
83107
- name: Set up mise
84-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
108+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
85109
with:
86110
cache: true
87111
experimental: true
@@ -100,7 +124,8 @@ jobs:
100124
needs:
101125
- lint
102126
- ensure-pinned-actions
103-
- build
127+
- smoke
128+
- nox
104129
- adk-py
105130
- langchain-py
106131
- upload-wheel
@@ -124,7 +149,8 @@ jobs:
124149
125150
check_result "lint" "${{ needs.lint.result }}"
126151
check_result "ensure-pinned-actions" "${{ needs.ensure-pinned-actions.result }}"
127-
check_result "build" "${{ needs.build.result }}"
152+
check_result "smoke" "${{ needs.smoke.result }}"
153+
check_result "nox" "${{ needs.nox.result }}"
128154
check_result "adk-py" "${{ needs.adk-py.result }}"
129155
check_result "langchain-py" "${{ needs.langchain-py.result }}"
130156
check_result "upload-wheel" "${{ needs.upload-wheel.result }}"

.github/workflows/langchain-py-test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
1313

1414
- name: Set up mise
15-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
15+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
1616
with:
1717
cache: true
1818
experimental: true

.github/workflows/publish-py-sdk.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
ref: ${{ github.event.inputs.ref }}
3939
fetch-depth: 0
4040
- name: Set up mise
41-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
41+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
4242
with:
4343
cache: true
4444
experimental: true
@@ -71,7 +71,7 @@ jobs:
7171
ref: ${{ env.COMMIT_SHA }}
7272
fetch-depth: 0
7373
- name: Set up mise
74-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
74+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
7575
with:
7676
cache: true
7777
experimental: true

.github/workflows/test-publish-py-sdk.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
ref: ${{ github.event.inputs.ref }}
4343
fetch-depth: 0
4444
- name: Set up mise
45-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
45+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
4646
with:
4747
cache: true
4848
experimental: true
@@ -91,7 +91,7 @@ jobs:
9191
ref: ${{ env.COMMIT_SHA }}
9292
fetch-depth: 0
9393
- name: Set up mise
94-
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
94+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
9595
with:
9696
cache: true
9797
experimental: true

py/noxfile.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ def _pinned_python_version():
8686
OPENAI_VERSIONS = (LATEST, "1.77.0", "1.71", "1.91", "1.92")
8787
OPENAI_AGENTS_VERSIONS = (LATEST, "0.0.19")
8888
# litellm latest requires Python >= 3.10
89-
# Pin litellm because 1.82.7-1.82.8 are compromised: https://github.com/BerriAI/litellm/issues/24512
90-
LITELLM_VERSIONS = ("1.82.0", "1.74.0")
89+
# Pin litellm to a version without the 1.82.7-1.82.8 compromise and with the
90+
# OIDC userinfo cache key collision fix from 1.83.0+
91+
LITELLM_VERSIONS = ("1.83.0", "1.74.0")
9192
# CLI bundling started in 0.1.10 - older versions require external Claude Code installation
9293
CLAUDE_AGENT_SDK_VERSIONS = (LATEST, "0.1.10")
9394
# Keep LATEST for newest API coverage, and pin 2.4.0 to cover the 2.4 -> 2.5 breaking change

py/requirements-optional.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ langchain-anthropic==1.4.0
77
langchain-core==1.2.22
88
langchain-openai==1.1.12
99
langsmith==0.7.12
10-
litellm==1.82.0
10+
litellm==1.83.0
1111
openai==2.24.0
1212
openrouter==0.7.11
1313
pydantic_ai==1.66.0

py/setup.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import sysconfig
23

34
import setuptools
45

@@ -27,14 +28,24 @@
2728
"wrapt",
2829
]
2930

31+
is_free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
32+
is_windows = os.name == "nt"
33+
34+
# orjson is not compatible with PyPy or free-threaded Python, so only expose it
35+
# for standard CPython builds where it is supported.
36+
performance_require = [] if is_free_threaded else ["orjson; platform_python_implementation != 'PyPy'"]
37+
38+
# temporalio does not currently install cleanly on Windows free-threaded Python,
39+
# so leave the optional integration available everywhere else.
40+
temporal_require = [] if is_free_threaded and is_windows else ["temporalio>=1.19.0; python_version>='3.10'"]
41+
3042
extras_require = {
31-
"cli": ["boto3", "psycopg2-binary", "uv", "starlette", "uvicorn"],
43+
"cli": ["boto3", "uv", "starlette", "uvicorn"],
3244
"doc": ["pydoc-markdown"],
3345
"openai-agents": ["openai-agents"],
3446
"otel": ["opentelemetry-api", "opentelemetry-sdk", "opentelemetry-exporter-otlp-proto-http"],
35-
# orjson is not compatible with PyPy, so we exclude it for that platform
36-
"performance": ["orjson; platform_python_implementation != 'PyPy'"],
37-
"temporal": ["temporalio>=1.19.0; python_version>='3.10'"],
47+
"performance": performance_require,
48+
"temporal": temporal_require,
3849
}
3950

4051
extras_require["all"] = sorted({package for packages in extras_require.values() for package in packages})

py/src/braintrust/integrations/pydantic_ai/patchers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,18 @@ class StreamedResponseSyncStartProducerPatcher(FunctionWrapperPatcher):
134134

135135
class _ToolManagerExecuteFunctionToolPatcher(FunctionWrapperPatcher):
136136
name = "pydantic_ai.tool_manager.execute_function_tool"
137-
target_module = "pydantic_ai._tool_manager"
137+
# Regression compatibility note: pydantic_ai 1.78.0 moved ToolManager out
138+
# of the private ``pydantic_ai._tool_manager`` module into
139+
# ``pydantic_ai.tool_manager``. ``pydantic_ai._agent_graph.ToolManager`` is
140+
# a stable alias in both the old and new layouts, so patch that seam.
141+
target_module = "pydantic_ai._agent_graph"
138142
target_path = "ToolManager._execute_function_tool_call"
139143
wrapper = _tool_manager_execute_function_tool_wrapper
140144

141145

142146
class _ToolManagerCallFunctionToolPatcher(FunctionWrapperPatcher):
143147
name = "pydantic_ai.tool_manager.call_function_tool"
144-
target_module = "pydantic_ai._tool_manager"
148+
target_module = "pydantic_ai._agent_graph"
145149
target_path = "ToolManager._call_function_tool"
146150
wrapper = _tool_manager_call_function_tool_wrapper
147151
superseded_by = (_ToolManagerExecuteFunctionToolPatcher,)

py/src/braintrust/integrations/pydantic_ai/test_pydantic_ai_integration.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,7 +2248,7 @@ def test_wrap_model_classes_is_deprecated(monkeypatch):
22482248

22492249

22502250
def test_setup_pydantic_ai_is_idempotent_across_new_patch_points():
2251-
import pydantic_ai._tool_manager as tool_manager_module
2251+
import pydantic_ai._agent_graph as agent_graph_module
22522252
import pydantic_ai.direct as direct_module
22532253
from braintrust.integrations.pydantic_ai.integration import PydanticAIIntegration
22542254
from pydantic_ai.agent.abstract import AbstractAgent
@@ -2257,15 +2257,15 @@ def test_setup_pydantic_ai_is_idempotent_across_new_patch_points():
22572257
prepare_model = direct_module.__dict__["_prepare_model"]
22582258
tool_method_name = (
22592259
"_execute_function_tool_call"
2260-
if "_execute_function_tool_call" in tool_manager_module.ToolManager.__dict__
2260+
if "_execute_function_tool_call" in agent_graph_module.ToolManager.__dict__
22612261
else "_call_function_tool"
22622262
)
2263-
tool_method = tool_manager_module.ToolManager.__dict__[tool_method_name]
2263+
tool_method = agent_graph_module.ToolManager.__dict__[tool_method_name]
22642264

22652265
assert PydanticAIIntegration.setup() is True
22662266
assert AbstractAgent.__dict__["run"] is run
22672267
assert direct_module.__dict__["_prepare_model"] is prepare_model
2268-
assert tool_manager_module.ToolManager.__dict__[tool_method_name] is tool_method
2268+
assert agent_graph_module.ToolManager.__dict__[tool_method_name] is tool_method
22692269

22702270

22712271
def test_serialize_content_part_with_binary_content():

0 commit comments

Comments
 (0)