Skip to content

Conversation

@clutchski
Copy link
Collaborator

@clutchski clutchski commented Jan 14, 2026

Adds a new auto_instrument() function that detects installed AI/ML libraries and automatically instruments them for tracing.

Supported integrations:

  • OpenAI (patch_openai/unpatch_openai)
  • Anthropic (patch_anthropic/unpatch_anthropic)
  • LiteLLM (patch_litellm/unpatch_litellm)
  • Pydantic AI (setup_pydantic_ai)
  • Google GenAI (setup_genai)
  • Agno (setup_agno)
  • Claude Agent SDK (setup_claude_agent_sdk)
  • DSPy (patch_dspy/unpatch_dspy)

Usage:

import braintrust
braintrust.auto_instrument()

@clutchski clutchski marked this pull request as ready for review January 14, 2026 16:34
@clutchski clutchski requested review from ibolmo and knjiang January 14, 2026 16:35
Copy link
Contributor

@knjiang knjiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly questions around maintainablility and discovery of auto-instrumentation.

code LGTM

except ImportError as e:
logger.error(f"Failed to import Claude Agent SDK: {e}")
logger.error("claude-agent-sdk is not installed. Please install it with: pip install claude-agent-sdk")
except ImportError:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont see tests for claude_agent_sdk and others, i think we can probably add them in as well for test_auto.
i'm wnodering if there's an a way we can also enforce testing auto instrumentation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know about enforcement but I added the rest!

Copy link
Contributor

@manugoyal manugoyal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks cool to me

logger = braintrust.init_logger(project="auto-instrument-demo")

# Now import and use AI libraries normally - all calls are traced!
# IMPORTANT: Import AI libraries AFTER calling auto_instrument()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this requirement really necessary? I think module imports are shared objects,
so if you modify a module in one scope, it'll modify all imports of that module.
E.g.

In [1]: def foo():
   ...:     import sys
   ...:     setattr(sys, 'manuattr', 'foo')
   ...:

In [2]: import sys

In [3]: getattr(sys, 'manuattr')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 1
----> 1 getattr(sys, 'manuattr')

AttributeError: module 'sys' has no attribute 'manuattr'

In [4]: foo()

In [5]: getattr(sys, 'manuattr')
Out[5]: 'foo'

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  - import openai; openai.OpenAI() - works regardless of order (looks up attribute dynamically)
  - from openai import OpenAI; OpenAI() - only works if imported AFTER patching (creates local binding)

try:
from braintrust.oai import patch_openai

patch_openai()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like patch_openai swallows import errors, so if we're trying to detect
whether the patch succeeded, maybe patch_openai should return the boolean
directly?

@clutchski clutchski requested a review from ankrgyl as a code owner January 22, 2026 21:18
clutchski and others added 3 commits January 22, 2026 13:45
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
DSPy uses litellm which uses httpx. The standalone VCR in test_utils
doesn't capture httpx requests (only requests/urllib3). The pytest-vcr
plugin has httpx support, so span verification is done in test_dspy.py.

This test now focuses on patching behavior only - verifying that
auto_instrument/auto_uninstrument work correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
logger.error(f"Failed to import Agno: {e}")
logger.error("Agno is not installed. Please install it with: pip install agno")
except ImportError:
# Not installed - this is expected when using auto_instrument()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe have the logging conditional if and only if not using auto instrumentation

Example:
```python
import braintrust
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd rather have each register itself with the library, and then have this iterate through registered instrumentations. This allows 3rd (or first) party libraries to register themselves as well (we'll need this for langchain-py and adk).

package_dir={"": "src"},
packages=setuptools.find_packages(where="src"),
package_data={"braintrust": ["py.typed"]},
package_data={"braintrust": ["py.typed", "wrappers/cassettes/*.yaml"]},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we be including the cassettes? :O

def _apply_openai_wrapper(client):
"""Apply tracing wrapper to an OpenAI client instance in-place."""
wrapped = wrap_openai(client)
for attr in ("chat", "responses", "embeddings", "moderations", "beta"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be using wrapt library to override class methods. i fear about the loss of types and other idiosyncratic pythonic things (signature/introspection, binding, ...) with this setattr approach

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea i just didn't want to add a dependency without really making sur eit was worht itm but we can try. we did this at dd as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i think wrapt is very robust and its used in opentelemetry as well. so i think it's worth the bytes plus it's already in the build 😂
image

return results


def _uninstrument_openai() -> bool:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

each wrapper should have this rather than in this massive auto.py file

_internal_with_custom_background_logger, # noqa: F401 # type: ignore[reportUnusedImport]
)
from .oai import (
patch_openai, # noqa: F401 # type: ignore[reportUnusedImport]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we should expose these

@ibolmo ibolmo requested a review from knjiang January 23, 2026 22:55
Copy link
Collaborator

@ibolmo ibolmo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

woops forgot to block until my questions/suggestions are reviewed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants