feat(trace): add advanced span filters - #728
Conversation
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: d95d83cc5c
ℹ️ 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".
d95d83c to
17dce78
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 17dce788f8
ℹ️ 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".
| else: | ||
| metadata_field = {"op": "ident", "name": ["metadata", *child_path]} | ||
| if value is None: | ||
| filters.append({"op": "isnull", "expr": metadata_field}) |
There was a problem hiding this comment.
Distinguish missing metadata from explicit nulls
When a null metadata filter is evaluated remotely, this fresh isnull branch also matches spans where the path is absent (the builder already relies on that behavior to include spans with a missing span_attributes.purpose), whereas local _metadata_matches explicitly rejects any absent key. Thus filters={"metadata": {"model": None}} returns different spans depending on whether LocalTrace finds a populated local cache. Reject null leaves or add an existence condition so the remote predicate preserves the local exact-match semantics.
Useful? React with 👍 / 👎.
1ce3164 to
10b2a3e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 10b2a3ea7d
ℹ️ 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".
| return (getattr(span, "error", None) is not None) == value | ||
|
|
||
| def to_btql(self, value: Any) -> list[dict[str, Any]]: | ||
| return [_btql_null_check("isnotnull" if value else "isnull", ["error"])] |
There was a problem hiding this comment.
Negate
isnull for error-presence filters
When filters={"has_error": True} reaches the remote path, this emits an isnotnull node, but BTQL represents non-null checks by applying the supported not node to an isnull expression. The backend therefore rejects this filter instead of returning errored spans; build not(isnull(error)) for the true branch.
Useful? React with 👍 / 👎.
| elif actual_value != expected_value: | ||
| return False |
There was a problem hiding this comment.
Compare metadata leaves without Python boolean coercion
When spans are served from the local cache, Python equality makes booleans equal to the corresponding integers, so metadata {"flag": true} matches filters={"metadata": {"flag": 1}} (and nested lists have the same issue). The remote BTQL comparison preserves the JSON value types, making results depend on whether the cache is populated; require compatible leaf types before comparing values.
Useful? React with 👍 / 👎.
10b2a3e to
46a12e1
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 46a12e12c6
ℹ️ 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".
| _SpanTypeFilter(), | ||
| _NameFilter(), | ||
| _HasErrorFilter(), | ||
| _MetadataFilter(), | ||
| _DurationFilter(), |
There was a problem hiding this comment.
Implement the advertised tags filter
When a caller uses the advertised filters={"tags": {"all": ["production"], "none": ["internal"]}} option, _FILTER_FIELDS is derived from this registry without a tags specification, so _normalize_span_filters() raises ValueError("Unsupported span filter fields: tags") instead of filtering spans. The commit description explicitly lists and demonstrates tags filtering, and cached spans now preserve tags, so add the corresponding typed field and local/BTQL filter implementation.
Useful? React with 👍 / 👎.
Add nested `filters` to `Trace.get_spans()` so callers can select spans by type, name,
error state, tags, metadata, and duration with matching local and BTQL semantics.
```python
spans = await trace.get_spans(
filters={
"span_type": ["tool"],
"name": ["search", "lookup"],
"has_error": True,
"metadata": {"model": "gpt-5"},
"duration": {"min": 0.5, "max": 10},
}
)
```
Preserve fields needed to evaluate these filters in the local span cache. Keep existing full-trace
and span-type caching, but execute advanced remote filters as fresh BTQL requests so partial or
empty results do not become stale snapshots:
```text
local buffered spans -> in-memory filtering
complete trace cache -> in-memory filtering
advanced remote filter -> fresh BTQL request
```
Keep `span_type=[...]` compatible on the public `Trace` API but emit a `DeprecationWarning`
directing callers to `filters={"span_type": [...]}`. Add runtime and type coverage for
validation and equivalent local and server filtering.
46a12e1 to
0ddb869
Compare
resolves https://linear.app/braintrustdata/issue/SDK-317/add-filters-on-traceget-spans-to-python-sdk
Add
filtersoption totrace.get_spans()so callers can filter spans. Currently we support filtering by span_type, name, has_error, tags, metadata, and duration.span_typepreviously was also a top level option ontrace.get_spans, I deprecated the top level option.Also did some caching refactors to make sure nothing broke. I tested some of it manually, but a lot of it was trusting the llm to take the wheel.