Regex and structural search over Braintrust logs, working around BTQL's lack of regex.
Project description
braintrust-grep
Tool for regex and structural search over Braintrust logs.
Braintrust's query language (BTQL) has no regex in log filters, and its
LIKE/substring operator can silently match nothing on structured (JSON-object)
fields.
So the reliable pattern I've found is: prefilter server-side with MATCH, pull
logs scoped to a created window, then match locally in Python.
braintrust-grep does this for you while also handling the rate limit, query timeout, gzip redirects,
pagination, and span deep-linking for you.
I built this while hunting down hallucinations and other discrepancies in an 'AI' (LLM) powered extraction pipeline.
Install
You can install on Github today, PyPi package coming soon.
As a CLI tool
uv tool install git+https://github.com/aflansburg/braintrust-grep
# …or run it once without installing:
uvx --from git+https://github.com/aflansburg/braintrust-grep btgrep --help
As a library:
uv add "braintrust-grep @ git+https://github.com/aflansburg/braintrust-grep"
Then set your credentials:
export BRAINTRUST_API_KEY=... # required
export BRAINTRUST_ORG_NAME=... # optional, for deep-links !!!! Case Sensitive it seems !!!!
Once published to PyPI this becomes
uv tool install braintrust-grep/uvx braintrust-grep. Thebraintrust-grepcommand is an alias forbtgrep, souvx braintrust-grepresolves without--from.
Quickstart (CLI)
# Find spans whose output matches a regex, last 14 days (shorter time intervals are more ideal: 1h, 3h, etc.):
btgrep search -p my-project --since 14d --regex 'output=timeout|refused'
# AND several field-scoped predicates (regex + "key empty or missing"):
btgrep search -p my-project --since 7d \
--match-fulltext output=evidence \
--regex 'input.raw_text=user_\d+' \
--empty output.result
Subcommands stream JSONL on stdin/stdout and diagnostics go to stderr.
It's a subcommand pattern we decided to support common in pipeline tooling like jq or the OG grep | sed | awk.
btgrep search -p my-project --since 14d --regex 'output=foo' \
| btgrep enrich -p my-project --root-field input.doc_id --root-field input.patient_id \
| btgrep export -o out.csv -p my-project --org MyOrg # project id auto-resolved
You can write up a declarative spec (copy one from examples/ and
fill in your project/org/patterns):
btgrep hunt --spec examples/find_errors.yaml -o out.csv
Gnarly regex? Put it in a file and reference it with @:
--regex 'output=@pattern.re' (no shell-quoting pain).
Python API
from braintrust_grep import (
BtqlClient, ClientOptions, resolve_project_id, search, enrich,
AllOf, Regex, MetadataObjectEmpty, resolve_window,
)
client = BtqlClient(ClientOptions.from_env())
pid = resolve_project_id("my-project")
window = resolve_window("14d", "now")
predicate = AllOf(
Regex("output", r'"symptoms"\s*:\s*\['),
MetadataObjectEmpty("input.raw_text", keys=("pages", "documentdata"),
marker="=== METADATA ==="),
)
matches = list(search(
client, pid, window=window, predicate=predicate,
match=[("output", "evidence"), ("output", "symptoms")], # server MATCH prefilter
))
enriched = enrich(client, matches, project_id=pid,
root_fields=["input.doc_id", "input.patient_id"])
Predicates
Regex(field, pattern, flags): regex over a dotted field (non-strings are JSON-serialized first, so you can match structure).field="*"matches the whole row.EmptyOrMissing(field): true when the key is absent or the value is""/[]/{}/null. (Regex can't express "missing".)MetadataObjectEmpty(field, keys, marker=None): parses JSON found infield— a JSON column, a JSON string, or a block embedded in text after an optionalmarker(e.g. the field may be raw text with a preceding"=== METADATA ===") — and matches when every named key is empty-or-missing. Schema-agnostic: setfield/keys/markerto your own.AllOf/AnyOf/Not: compose them.
Constraints & gotchas (why this library exists)
- 30-second query timeout. A single query over 30 seconds will return HTTP 504. Fast-path to staying under that:
created-range +MATCH+ cursor pagination. Wideid IN/root_span_id IN/LIKE/span-name scans will blow your rate limit budget. Enrichment uses narrow time-bucketedINbatches. LIKEis blind to structured fields.output LIKE '%x%'returns 0 whenoutputis a JSON object. UseMATCH. The query builder warns if youLIKEa structured field.- Rate limit ~20 requests / 60s per org (HTTP 429). The client paces itself and honors
Retry-After; retries408/429/500/502/503/504with capped backoff. - Large results 303-redirect to a gzipped object: handled transparently.
- Per-span payloads up to 20 MB (
raw_textcan be a whole document). The scan selects only the columns your predicates read. Neverselect: *. - Row
id≠span_id. Deep-links needr=<root_span_id>&s=<span_id>, not the row id.span_deeplink()takes care of this for you.
Configuration (env)
| Var | Purpose |
|---|---|
BRAINTRUST_API_KEY |
Auth (required) |
BRAINTRUST_ORG_NAME |
Org slug for deep-links |
BRAINTRUST_BTQL_URL |
Override the BTQL endpoint |
ClientOptions exposes min_request_interval, max_retries, page_size,
retry_statuses, backoff knobs, etc. .env is loaded automatically if
python-dotenv is installed (never required).
Development
uv sync --extra dev # install with dev + test deps
uv run ruff check . && uv run ruff format --check .
Running the tests
The suite is offline. No Braintrust credentials or network required. HTTP is
faked with httpx.MockTransport and time with a FakeClock, so it runs in well
under a second.
uv run pytest # all tests
uv run pytest --cov # with a coverage report
uv run pytest -v # verbose (list each test)
uv run pytest tests/test_client.py # one file
uv run pytest -k "retry or gzip" # tests matching an expression
uv run pytest -x -q # stop at first failure, quiet
TBD
Async client; parallel bucket fetching (the rate limit makes concurrency
low-value); result caching/resume; non-project_logs sources (experiments,
datasets); a custom-predicate plugin system beyond --spec; an interactive TUI.
Contributing
Issues and PRs welcome. Please:
- Fork and branch off
main. uv sync --extra dev, then keepruff check,ruff format, andpytestgreen.- Add a test for any behavior change (the suite is offline — mock HTTP with
httpx.MockTransportand time withFakeClock; seetests/conftest.py). - Open a PR describing the change and, for a bug fix, the failing case it covers.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file braintrust_grep-0.1.0.tar.gz.
File metadata
- Download URL: braintrust_grep-0.1.0.tar.gz
- Upload date:
- Size: 30.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d54c290b3c6ab1c67b0a3f22e9d4b4caec5afe6006b8532d10ca4650e4d2543
|
|
| MD5 |
3dcddadcc40c928936092cc0b7696247
|
|
| BLAKE2b-256 |
a205466bedf570d0c8cdd9b2fe1ae5e026c22e8f8ee6fbda9601c54c21cac544
|
Provenance
The following attestation bundles were made for braintrust_grep-0.1.0.tar.gz:
Publisher:
publish.yml on aflansburg/braintrust-grep
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
braintrust_grep-0.1.0.tar.gz -
Subject digest:
8d54c290b3c6ab1c67b0a3f22e9d4b4caec5afe6006b8532d10ca4650e4d2543 - Sigstore transparency entry: 2176333509
- Sigstore integration time:
-
Permalink:
aflansburg/braintrust-grep@506860172ec70d4c23d6793d0394f0a1dc0eeac8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/aflansburg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@506860172ec70d4c23d6793d0394f0a1dc0eeac8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file braintrust_grep-0.1.0-py3-none-any.whl.
File metadata
- Download URL: braintrust_grep-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6add100403331e7e74f68c0d1cb8ac7c2bfde314ece6e304e437b922bd10697
|
|
| MD5 |
428f5e380f5ff3783cda45126a33968b
|
|
| BLAKE2b-256 |
403832ee4ec39c667163b0c311d73a2b8b2de226e98d2c890e91fdd94f91bec6
|
Provenance
The following attestation bundles were made for braintrust_grep-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on aflansburg/braintrust-grep
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
braintrust_grep-0.1.0-py3-none-any.whl -
Subject digest:
d6add100403331e7e74f68c0d1cb8ac7c2bfde314ece6e304e437b922bd10697 - Sigstore transparency entry: 2176333695
- Sigstore integration time:
-
Permalink:
aflansburg/braintrust-grep@506860172ec70d4c23d6793d0394f0a1dc0eeac8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/aflansburg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@506860172ec70d4c23d6793d0394f0a1dc0eeac8 -
Trigger Event:
release
-
Statement type: