[build-system] requires = ["setuptools>=82.0.1"] build-backend = "_build_hooks" backend-path = ["."] [project] name = "braintrust" dynamic = ["version"] description = "SDK for integrating Braintrust" readme = "README.md" license = "MIT" requires-python = ">=3.10.0" authors = [ { name = "Braintrust", email = "sdk@braintrustdata.com" }, ] classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.15", "Operating System :: OS Independent", ] dependencies = [ "certifi>=2026.10.19", "requests", "chevron", "tqdm", "exceptiongroup>=1.2.0", "jsonschema", "packaging", "sseclient-py", "python-slugify", "typing_extensions>=4.1.0", "wrapt", ] [project.urls] Homepage = "https://www.braintrust.dev" "Source Code" = "https://github.com/braintrustdata/braintrust-sdk-python" "Bug Tracker" = "https://github.com/braintrustdata/braintrust-sdk-python/issues" [project.scripts] braintrust = "braintrust.cli.__main__:main" [project.entry-points.pytest11] braintrust = "braintrust.wrappers.pytest_plugin.plugin" [project.entry-points."harbor.plugins"] braintrust = "braintrust.integrations.harbor:HarborPlugin" [project.optional-dependencies] cli = ["boto3", "python-dotenv", "uv>=0.13.4", "starlette", "uvicorn"] # TODO: remove the doc extra in the next major version. doc = [] openai-agents = ["openai-agents"] otel = [ "opentelemetry-api", "opentelemetry-sdk", "opentelemetry-exporter-otlp-proto-http", ] performance = [ "orjson; platform_python_implementation != 'PyPy'", ] # Deprecated: Temporal is now available via braintrust.auto_instrument(); # keep this extra as a compatibility alias until a future major release. temporal = [ "temporalio>=1.19.0; python_version>='3.10'", ] all = [ "boto3", "openai-agents", "opentelemetry-api", "opentelemetry-exporter-otlp-proto-http", "opentelemetry-sdk", "orjson; platform_python_implementation != 'PyPy'", "python-dotenv", "starlette", "temporalio>=1.19.0; python_version>='3.10'", "uv>=0.13.4", "uvicorn", ] [tool.setuptools.dynamic] version = {attr = "braintrust.version.VERSION"} [tool.setuptools.cmdclass] build_py = "_build_hooks.BuildPy" [tool.setuptools.packages.find] where = ["src"] exclude = ["braintrust.type_tests*", "braintrust.fixtures*"] [tool.setuptools.package-data] braintrust = ["py.typed"] [tool.setuptools.exclude-package-data] braintrust = ["fixtures/*", "type_tests/*"] # --------------------------------------------------------------------------- # Dependency groups (PEP 735) # # Session auxiliary deps and infrastructure. These participate in # ``uv lock`` and get a fully-resolved lockfile. # # Provider *matrix* versions live in ``[tool.braintrust.matrix]`` below # because declaring every version combination as dependency-group conflicts # creates ~1.5 M resolution forks and makes ``uv lock`` infeasible. # --------------------------------------------------------------------------- [dependency-groups] # -- Base test deps (all sessions include this) -------------------------------- test = [ "pytest==9.1.1", "pytest-asyncio==1.3.0", "pytest-vcr==1.0.2", ] # -- Session-specific auxiliary deps ------------------------------------------- test-openai-http2 = [ {include-group = "test"}, "h2==4.4.1", # OpenAI 3 no longer installs httpx transitively, but this session exercises # the integration's HTTP/2 behavior with an explicit httpx transport. "httpx==0.28.1", ] test-openai-ddtrace = [ {include-group = "test"}, "ddtrace==4.12.0", ] test-openai-agents = [ {include-group = "test"}, # openai is an auxiliary dep (matrix dep is openai-agents); pin here. "openai==2.31.0", ] test-litellm = [ {include-group = "test"}, # litellm requires openai<=1.99.9 (ResponseTextConfig removal in 1.100.0) "openai==1.99.9", "fastapi==0.135.3", # litellm 1.106.0 imports mcp on every tool call but only declares it in # the mcp/proxy extras (https://github.com/BerriAI/litellm/issues/46045). "mcp>=2.2.0,<3", "orjson==3.11.8; python_version<'3.15'", # 3.11.8 has no cp315 wheels and its sdist fails on current stable Rust. "orjson==3.12.0; python_version>='3.15'", ] test-dspy = [ # DSPy 2.6 requires Pillow to format image inputs but does not install it. "pillow==12.3.0", ] test-agno = [ {include-group = "test"}, "openai==2.31.0", "fastapi==0.135.3", ] test-agentscope = [ {include-group = "test"}, "openai==2.31.0", ] # Compatibility constraints for older provider versions. These groups are # used as pip constraint files, so they do not install optional packages that # the provider itself does not require. test-mcp-v1 = [ "mcp==1.28.1", ] # SQLAlchemy 2.1.0 ships only an sdist whose pyproject declares a duplicate # normalized extra (`mssql-pymssql`), which uv refuses to build. test-sqlalchemy-2-0 = [ "sqlalchemy<2.1", ] test-pydantic-ai-otel-events = [ "opentelemetry-api==1.39.1", # genai-prices 0.1 returns fields that older Pydantic AI RequestUsage models cannot accept. "genai-prices==0.0.73", ] test-strands = [ {include-group = "test"}, "openai==2.32.0", ] test-pydantic-ai-logfire = [ {include-group = "test"}, "logfire==4.39.0", ] test-pydantic-ai-logfire-constraints = [ "logfire==4.39.0", ] test-langchain = [ {include-group = "test"}, "langchain-openai==1.1.14", "langchain-anthropic==1.4.6", "langgraph==1.1.6", ] test-deepagents = [ {include-group = "test"}, "langchain-openai==1.1.14", ] test-livekit-agents = [ {include-group = "test"}, # Floors keep the lock on the matrix ``latest`` line; without them uv keeps # older 1.3.x plugins, which pull livekit-agents back to 1.3.x. "livekit-plugins-openai>=1.8.8", "livekit-plugins-silero>=1.8.8", ] test-livekit-agents-1-3 = [ {include-group = "test"}, # Pin the agents package so the plugins do not pull a newer 1.3.x. "livekit-agents==1.3.1", "livekit-plugins-openai==1.3.1", "livekit-plugins-silero==1.3.1", # livekit-agents 1.3.x imports opentelemetry.sdk._logs.LogData, removed in newer SDKs. # This is a LiveKit runtime compatibility constraint, not a Braintrust OTel integration dependency. "opentelemetry-sdk<1.39", "opentelemetry-api<1.39", "opentelemetry-exporter-otlp<1.39", ] test-pipecat = [ {include-group = "test"}, # pipecat-ai 1.3.0 imports websockets but does not install it transitively. "websockets==15.0.1", ] test-crewai = [ {include-group = "test"}, # CrewAI's no-network smoke test forces the LiteLLM fallback path via # ``is_litellm=True`` + ``mock_response``. Keep this on a recent LiteLLM # so transitive security fixes (for example python-dotenv and aiohttp) are # present in the locked test environment. "litellm==1.88.6", ] test-llamaindex = [ {include-group = "test"}, "llama-index-llms-openai", "llama-index-embeddings-openai", ] test-llamaindex-0-13 = [ {include-group = "test"}, "llama-index-core==0.13.0", "llama-index-llms-openai==0.5.0", "llama-index-embeddings-openai==0.5.0", ] test-btx = [ {include-group = "test"}, "pyyaml", ] test-instructor = [ {include-group = "test"}, "jiter<0.11", "openai==1.99.9", ] test-huggingface-hub = [ {include-group = "test"}, # numpy is required by huggingface_hub.InferenceClient.feature_extraction # but is not declared as an install_requires dep upstream. "numpy", # huggingface_hub<1.0 implements AsyncInferenceClient on top of aiohttp; # newer versions ship with httpx already. Pinning aiohttp here keeps the # 0.32.0 floor working without leaking the requirement onto the rest of # the test environment. "aiohttp", ] test-transformers = [ {include-group = "test"}, # Local pipeline execution is intentionally isolated from other provider # sessions because PyTorch is a large optional runtime dependency. "torch", "sentencepiece", ] test-cli = [ {include-group = "test"}, "boto3", "httpx==0.28.1", "python-dotenv", "starlette", "uv>=0.13.4", "uvicorn", ] test-types = [ {include-group = "test"}, "autoevals==0.2.0", "pyright==1.1.408", "mypy==1.20.0", ] # -- Lint deps ----------------------------------------------------------------- lint = [ "pylint==4.1.2", "pyperf==2.10.0", # Packages below are needed so pylint can resolve every SDK import. # Versions are unpinned (unless already constrained) so the resolver # can find mutually compatible transitive deps. "agentscope<2", "agno", "ai>=0.4.0; python_version>='3.12'", "anthropic", "autogen-agentchat", "autogen-ext[openai]", "claude-agent-sdk", "cohere", "autoevals", "braintrust-core", # Do not install crewai in the shared lint environment: current releases # transitively pin vulnerable chromadb versions. CrewAI integration tests # install the provider package via the nox matrix instead. # onnxruntime 1.24.3 dropped cp310 wheels. Constrain the transitive # resolution on Python 3.10 so pylint can still build its environment. "onnxruntime<1.24; python_version<'3.11'", # Python 3.15 markers below: these pull dependencies with no cp315 wheels # whose sdists cannot build yet (fastuuid via litellm, jiter<0.11 via # instructor, livekit-blingfire via livekit-agents). "dspy; python_version<'3.15'", "google-adk", "google-genai", "google-cloud-discoveryengine", "instructor; python_version<'3.15'", "litellm>=1.104.2,!=1.107.0; python_version<'3.15'", # Fork the resolution so 3.15 is not stuck on instructor's jiter<0.11. "jiter>=0.11; python_version>='3.15'", "livekit-agents>=1.8.5; python_version<'3.15'", "livekit-plugins-openai; python_version<'3.15'", # Keep these available to pylint without installing CrewAI (whose # transitive chromadb versions are unsuitable for shared CI). "opentelemetry-instrumentation-openai==0.59.1", "langsmith==0.8.18", "langchain-core", "langchain-openai", "langchain-anthropic", "llama-index-core", "llama-index-llms-openai", "llama-index-embeddings-openai", # pydantic-ai's matrix latest requires Python 3.11+, so pylint skips it on # 3.10. Use slim to resolve pydantic_ai imports without optional extras. "pydantic-ai-slim>=2.53.0; python_version >= '3.11'", # Keep the lint environment's optional SDK dependencies lock-resolved # instead of installing the unconstrained [all] extra separately. "opentelemetry-api==1.39.1", "opentelemetry-exporter-otlp-proto-http==1.39.1", "opentelemetry-sdk==1.39.1", "orjson", {include-group = "test-cli"}, ] # -- Build and code-generation deps -------------------------------------------- build = [ "setuptools>=82.0.1", ] api-codegen = [ "datamodel-code-generator==0.81.0", "ruff==0.15.21", ] # -- Dev deps (superset for local development) --------------------------------- dev = [ {include-group = "test"}, {include-group = "build"}, {include-group = "api-codegen"}, "nox==2026.2.9", "pre-commit==4.6.2", "pylint==4.1.2", "pyperf==2.10.0", ] # --------------------------------------------------------------------------- # UV package sources # --------------------------------------------------------------------------- [tool.uv.sources] torch = { index = "pytorch-cpu" } [[tool.uv.index]] name = "pytorch-cpu" url = "https://download.pytorch.org/whl/cpu" explicit = true # --------------------------------------------------------------------------- # UV conflicts — auxiliary session groups that pin different versions of the # same package cannot coexist in a single resolution. # --------------------------------------------------------------------------- [tool.uv] exclude-newer = "2026-10-20T19:24:36Z" conflicts = [ # Groups that pin or transitively require conflicting openai versions. [ {group = "test-openai-agents"}, {group = "test-litellm"}, {group = "test-crewai"}, {group = "test-agno"}, {group = "test-agentscope"}, {group = "test-strands"}, {group = "test-langchain"}, {group = "test-deepagents"}, {group = "test-llamaindex"}, {group = "test-llamaindex-0-13"}, {group = "test-instructor"}, {group = "test-livekit-agents"}, {group = "test-livekit-agents-1-3"}, {group = "lint"}, ], # opentelemetry-sdk version conflicts (google-adk/livekit vs logfire). [ {group = "lint"}, {group = "test-pydantic-ai-logfire"}, {group = "test-livekit-agents"}, {group = "test-livekit-agents-1-3"}, ], [ {group = "test-pydantic-ai-otel-events"}, {group = "test-livekit-agents"}, {group = "test-livekit-agents-1-3"}, ], [ {group = "lint"}, {group = "test-pydantic-ai-otel-events"}, ], [ {group = "test-pydantic-ai-logfire-constraints"}, {group = "test-livekit-agents"}, {group = "test-livekit-agents-1-3"}, ], [ {group = "test-llamaindex"}, {group = "test-llamaindex-0-13"}, ], # mcp 1.x constraints for old provider versions vs litellm's mcp 2.x. [ {group = "test-litellm"}, {group = "test-mcp-v1"}, ], ] # --------------------------------------------------------------------------- # Provider version matrix # # This is a custom metadata table — NOT dependency-groups — so it does not # participate in ``uv lock`` (which would be infeasible with ~1.5 M conflict # forks). The noxfile reads these at import time. # # Each key is a provider prefix. The value is a dict mapping human-readable # version tags to the pip requirement string. "latest" is pinned to an # explicit version and bumped by the weekly dependency-update workflow. # --------------------------------------------------------------------------- [tool.braintrust.matrix] [tool.braintrust.matrix.ai-sdk] latest = "ai[openai]==0.8.0" "0.4.0" = "ai[openai]==0.4.0" [tool.braintrust.matrix.openai] latest = "openai==3.35.1" "1.92.0" = "openai==1.92.0" "1.77.0" = "openai==1.77.0" "1.71.0" = "openai==1.71.0" [tool.braintrust.matrix.anthropic] latest = "anthropic==1.15.0" "0.48.0" = "anthropic==0.48.0" [tool.braintrust.matrix.cohere] latest = "cohere==7.2.0" "5.0.0" = "cohere==5.0.0" [tool.braintrust.matrix.openai-agents] latest = "openai-agents==0.24.0" "0.13.6" = "openai-agents==0.13.6" "0.0.19" = "openai-agents==0.0.19" [tool.braintrust.matrix.litellm] # 1.92.0 only publishes Linux wheels through CPython 3.13, but the test # matrix also covers Windows and CPython 3.14. latest = "litellm==1.106.0" "1.74.0" = "litellm==1.74.0" [tool.braintrust.matrix.livekit-agents] latest = "livekit-agents==1.8.8" "1.3.1" = "livekit-agents==1.3.1" [tool.braintrust.matrix.pipecat-ai] latest = "pipecat-ai==1.12.0" "1.3.0" = "pipecat-ai==1.3.0" [tool.braintrust.matrix.claude-agent-sdk] latest = "claude-agent-sdk==0.2.168" # The integration still supports >=0.1.10, but 0.1.9-0.1.17 were deleted from # PyPI, so 0.1.18 is the oldest installable version we can test against. "0.1.18" = "claude-agent-sdk==0.1.18" [tool.braintrust.matrix.cursor-sdk] # 1.0.25 fixes bridge callback tokens that could be parsed as CLI flags while # retaining per-turn usage, sync/async consumption, multimodal, and custom tools. latest = "cursor-sdk==1.0.38" "1.0.25" = "cursor-sdk==1.0.25" [tool.braintrust.matrix.agno] latest = "agno==3.1.4" "2.4.0" = "agno==2.4.0" "2.1.0" = "agno==2.1.0" [tool.braintrust.matrix.agentscope] latest = "agentscope==2.0.10" "1.0.0" = "agentscope==1.0.0" [tool.braintrust.matrix.autogen-agentchat] latest = "autogen-agentchat==0.7.5" "0.7.0" = "autogen-agentchat==0.7.0" [tool.braintrust.matrix.autogen-ext] latest = "autogen-ext[openai]==0.7.5" "0.7.0" = "autogen-ext[openai]==0.7.0" [tool.braintrust.matrix.strands-agents] latest = "strands-agents==1.61.1" "1.20.0" = "strands-agents==1.20.0" [tool.braintrust.matrix.pydantic-ai-integration] latest = "pydantic-ai==2.57.0" "1.10.0" = "pydantic-ai==1.10.0" [tool.braintrust.matrix.pydantic-ai-wrap-openai] latest = "pydantic-ai==2.57.0" "1.0.1" = "pydantic-ai==1.0.1" "0.1.9" = "pydantic-ai==0.1.9" [tool.braintrust.matrix.autoevals] latest = "autoevals==0.4.0" "0.0.129" = "autoevals==0.0.129" [tool.braintrust.matrix.instructor] latest = "instructor==1.17.0" "1.11.0" = "instructor==1.11.0" [tool.braintrust.matrix.google-genai] latest = "google-genai==2.31.0" "1.75.0" = "google-genai==1.75.0" "1.30.0" = "google-genai==1.30.0" [tool.braintrust.matrix.google-cloud-discoveryengine] latest = "google-cloud-discoveryengine==0.20.5" [tool.braintrust.matrix.dspy] latest = "dspy==3.4.1" "2.6.0" = "dspy==2.6.0" [tool.braintrust.matrix.deepagents] latest = "deepagents==0.7.28" "0.6.0" = "deepagents==0.6.0" [tool.braintrust.matrix.crewai] # 1.13.0 is the first release with the full causal-id surface (event_id / # parent_event_id / started_event_id on BaseEvent) plus the ``usage`` field on # LLMCallCompletedEvent that the Braintrust CrewAI integration depends on. latest = "crewai==1.15.28" "1.13.0" = "crewai==1.13.0" [tool.braintrust.matrix.google-adk] latest = "google-adk[mcp]==2.12.0" "2.6.3" = "google-adk[mcp]==2.6.3" "1.14.1" = "google-adk[mcp]==1.14.1" [tool.braintrust.matrix.langchain-core] latest = "langchain-core==1.6.12" "0.3.28" = "langchain-core==0.3.28" [tool.braintrust.matrix.llama-index-core] latest = "llama-index-core==0.14.25" "0.13.0" = "llama-index-core==0.13.0" [tool.braintrust.matrix.openrouter] latest = "openrouter==1.3.66" "0.6.0" = "openrouter==0.6.0" [tool.braintrust.matrix.mistralai] latest = "mistralai==3.2.0" "2.10.1" = "mistralai==2.10.1" "1.12.4" = "mistralai==1.12.4" [tool.braintrust.matrix.typesafe-sdk] latest = "typesafe-sdk==0.7.5" "0.6.0" = "typesafe-sdk==0.6.0" [tool.braintrust.matrix.huggingface-hub] # Floor pinned to 0.32.0: the earliest release that exposes the # ``provider="auto"`` routing mode the integration relies on for multi- # provider use, while still keeping the same chat_completion / # text_generation / feature_extraction / sentence_similarity method names. latest = "huggingface-hub==2.2.0" "0.32.0" = "huggingface-hub==0.32.0" [tool.braintrust.matrix.transformers] latest = "transformers==5.20.1" "4.42.0" = "transformers==4.42.0" [tool.braintrust.matrix.temporalio] latest = "temporalio==1.34.0" "1.20.0" = "temporalio==1.20.0" "1.19.0" = "temporalio==1.19.0" [tool.braintrust.matrix.harbor] latest = "harbor==0.24.0" # 0.16.0 is the oldest supported release: it is the first with # TrialEvent.AGENT_END, which the lifecycle state machine subscribes to. Harbor # gained its plugin system in 0.13.2, so 0.13.2-0.15.0 can load the plugin but # cannot report the end of the agent phase. "0.16.0" = "harbor==0.16.0" [tool.braintrust.matrix.pytest-matrix] # Canonical pytest pin. The matching entry in [dependency-groups].test is # kept in sync by py/scripts/sync-pytest-pin.py (enforced by pre-commit). latest = "pytest==9.1.1" "8.4.2" = "pytest==8.4.2" [tool.braintrust.matrix.braintrust-core] latest = "braintrust-core==0.0.59" [tool.braintrust.matrix.boto3] latest = "boto3==1.43.114" "1.34.116" = "boto3==1.34.116" [tool.braintrust.matrix.botocore] latest = "botocore==1.43.114" "1.34.116" = "botocore==1.34.116" # OpenTelemetry api/sdk/exporter release in lockstep; keep their keys in sync. # 1.45.0 moved OTLP HTTP exporter headers onto an internal client, so keep a # pre-1.45 version covered alongside latest. 1.16.0 is the oldest release that # imports without pkg_resources; test_otel skips it on Python 3.14 (protobuf<5). [tool.braintrust.matrix.opentelemetry-api] latest = "opentelemetry-api==1.45.1" "1.44.0" = "opentelemetry-api==1.44.0" "1.16.0" = "opentelemetry-api==1.16.0" [tool.braintrust.matrix.opentelemetry-sdk] latest = "opentelemetry-sdk==1.45.1" "1.44.0" = "opentelemetry-sdk==1.44.0" "1.16.0" = "opentelemetry-sdk==1.16.0" [tool.braintrust.matrix.opentelemetry-exporter-otlp-proto-http] latest = "opentelemetry-exporter-otlp-proto-http==1.45.1" "1.44.0" = "opentelemetry-exporter-otlp-proto-http==1.44.0" "1.16.0" = "opentelemetry-exporter-otlp-proto-http==1.16.0" # --------------------------------------------------------------------------- # Vendor packages — optional third-party packages the SDK can work without. # Keys are matrix keys; values are Python import names. The noxfile uses this # table to: # - pylint: install each at its matrix ``latest`` pin so import checks pass # - test_core: verify none are importable in the minimal environment # Packages that only need lint coverage but not test_core gating (e.g. # pydantic-ai, langchain-core) belong in the ``lint`` dependency group instead. # --------------------------------------------------------------------------- # --------------------------------------------------------------------------- # Cassette directory mapping — maps integration directory names under # ``src/braintrust/integrations/`` to the matrix key(s) whose version tags # populate their ``cassettes//`` subdirectories. # # Used by ``scripts/check-stale-cassettes.py`` to detect orphaned cassette # directories when versions are dropped from the matrix. # --------------------------------------------------------------------------- [tool.braintrust.cassette-dirs] adk = ["google-adk"] ai_sdk = ["ai-sdk"] agentscope = ["agentscope"] agno = ["agno"] autogen = ["autogen-agentchat"] anthropic = ["anthropic"] bedrock_runtime = ["boto3", "botocore"] cohere = ["cohere"] claude_agent_sdk = ["claude-agent-sdk"] cursor_sdk = ["cursor-sdk"] crewai = ["crewai"] dspy = ["dspy"] google_genai = ["google-genai"] google_discoveryengine = ["google-cloud-discoveryengine"] huggingface_hub = ["huggingface-hub"] harbor = ["harbor"] instructor = ["instructor"] langchain = ["langchain-core", "deepagents"] litellm = ["litellm"] livekit_agents = ["livekit-agents"] llamaindex = ["llama-index-core"] mistral = ["mistralai"] openai = ["openai"] openai_agents = ["openai-agents"] openrouter = ["openrouter"] pipecat = ["pipecat-ai"] pydantic_ai = ["pydantic-ai-integration", "pydantic-ai-wrap-openai"] strands = ["strands-agents"] typesafe = ["typesafe-sdk"] [tool.braintrust.vendor-packages] ai-sdk = "ai" claude-agent-sdk = "claude_agent_sdk" agno = "agno" agentscope = "agentscope" autogen-agentchat = "autogen_agentchat" autogen-ext = "autogen_ext" anthropic = "anthropic" cohere = "cohere" autoevals = "autoevals" braintrust-core = "braintrust_core" boto3 = "boto3" harbor = "harbor" botocore = "botocore" crewai = "crewai" cursor-sdk = "cursor_sdk" dspy = "dspy" google-adk = "google.adk" google-genai = "google.genai" google-cloud-discoveryengine = "google.cloud.discoveryengine_v1" instructor = "instructor" litellm = "litellm" livekit-agents = "livekit.agents" mistralai = "mistralai" huggingface-hub = "huggingface_hub" openai = "openai" openai-agents = "agents" openrouter = "openrouter" opentelemetry-api = "opentelemetry" opentelemetry-sdk = "opentelemetry.sdk" opentelemetry-exporter-otlp-proto-http = "opentelemetry.exporter.otlp.proto.http" pipecat-ai = "pipecat" pydantic-ai-integration = "pydantic_ai" strands-agents = "strands" temporalio = "temporalio" transformers = "transformers" typesafe-sdk = "typesafe_sdk"