feat(integrations): add TypeSafe instrumentation - #777
Conversation
Enable TypeSafe tracing before importing the provider: ```python import braintrust braintrust.auto_instrument() from typesafe_sdk import TypeSafeClient logger = braintrust.init_logger(project="my-project") client = TypeSafeClient() ``` For explicit client setup, wrap an existing sync or async client: ```python from braintrust.integrations.typesafe import wrap_typesafe from typesafe_sdk import TypeSafeClient client = wrap_typesafe(TypeSafeClient()) ``` Calls to `system_one` emit `question` spans with allowlisted state, question, answer, model, provider, and token usage fields. Both setup paths preserve TypeSafe return values and exceptions.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72055b70fc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".
| from braintrust.span_types import SpanTypeAttribute | ||
|
|
||
|
|
||
| _INSTRUMENTATION = "typesafe" |
There was a problem hiding this comment.
Use the standard TypeSafe origin name
All TypeSafe spans are emitted with the origin typesafe, while integration origins follow the <provider>-auto convention used by the other integrations. Consequently, source filters and cross-SDK dashboards looking for typesafe-auto will not match these spans; set this constant to typesafe-auto and update the corresponding assertion.
Useful? React with 👍 / 👎.
|
|
||
| def wrap_typesafe(client: Any) -> Any: | ||
| """Instrument a TypeSafe sync or async client in place.""" | ||
| TypeSafeIntegration.setup() |
There was a problem hiding this comment.
Limit manual wrapping to the supplied client
When an application calls wrap_typesafe() to trace one client while other TypeSafe clients exist, this invokes global setup and patches both client classes, so calls from every instance are traced despite the helper promising in-place instrumentation of the supplied client. Use the appropriate sync or async patcher's wrap_target() on client so explicit wrapping remains instance-scoped.
Useful? React with 👍 / 👎.
`wrap_typesafe(client)` now routes sync and async instances through their matching patchers. Explicit wrapping only traces the supplied client, while `setup_typesafe()` remains the package-wide instrumentation path. Set a default dummy TypeSafe credential so checked-in VCR cassettes can replay in environments without provider secrets.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5c622822b9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".
| from typesafe_sdk import AsyncTypeSafeClient | ||
|
|
||
| patcher = AsyncSystemOnePatcher if isinstance(client, AsyncTypeSafeClient) else SystemOnePatcher | ||
| return patcher.wrap_target(client) |
There was a problem hiding this comment.
Avoid double-wrapping globally instrumented clients
When setup_typesafe() or auto_instrument() has already patched TypeSafeClient.system_one, this new instance-scoped path calls wrap_target() on a client with no instance marker and wraps the already-wrapped inherited class method again. Applications that combine global setup with wrap_typesafe() therefore emit two nested spans for every request. Check the patch marker on the class method before applying the instance wrapper, as the OpenAI integration's manual wrapping path does.
Useful? React with 👍 / 👎.
https://www.braintrust.dev/app/Braintrust%20SDKs/object?object_type=project_logs&object_id=65b29c05-ac12-4829-ba2c-2a4ae7f834c5&id=6df448c8-32e0-4ec0-9ee5-124ed3ae5e4d
Auto instrumentation:
Explicit client instrumentation:
Generated span tree: