Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

## BTQL Safety

- Every BTQL query must include either:
- a timestamp filter (for example, `created >= NOW() - INTERVAL ...` or `created >= "<ts>"`), or
- a `root_span_id` filter.
- Do not run BTQL queries that lack both constraints.
- BTQL queries over `project_logs(...)` or the combined `project(...)` source must include a useful segment-elimination constraint:
- a selective range on `created`, `_xact_id`, or `_pagination_key`; or
- scoping to specific `root_span_id` or `id` values.
- This requirement does not apply to other object sources such as `project_functions(...)`, `project_prompts(...)`, `dataset(...)`, or `experiment(...)`.

## Tooling

Expand Down
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ reqwest = { version = "0.12.7", default-features = false, features = ["json", "r
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
serde_path_to_error = "0.1.20"
yaml_serde = "0.10"
toml = "0.8"
sha2 = "0.10.8"
strip-ansi-escapes = "0.2.0"
Expand Down
70 changes: 54 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,22 +135,60 @@ Remove-Item -Recurse -Force (Join-Path $env:APPDATA "bt") -ErrorAction SilentlyC

## Commands

| Command | Description |
| ------------- | ------------------------------------------------------------------ |
| `bt init` | Initialize `.bt/` config directory and link to a project |
| `bt login` | Log in to Braintrust or refresh an OAuth login |
| `bt logout` | Remove a saved Braintrust login |
| `bt switch` | Switch org and project context |
| `bt status` | Show current org and project context |
| `bt datasets` | Manage datasets and dataset pipelines |
| `bt eval` | Run eval files (Unix only) |
| `bt sql` | Run SQL queries against Braintrust |
| `bt view` | View logs, traces, and spans |
| `bt projects` | Manage projects (list, create, view, delete) |
| `bt datasets` | Manage remote datasets (list, create, update, view, delete) |
| `bt prompts` | Manage prompts (list, view, delete) |
| `bt sync` | Synchronize project logs between Braintrust and local NDJSON files |
| `bt update` | Update bt in-place |
| Command | Description |
| -------------- | ------------------------------------------------------------------ |
| `bt init` | Initialize `.bt/` config directory and link to a project |
| `bt login` | Log in to Braintrust or refresh an OAuth login |
| `bt logout` | Remove a saved Braintrust login |
| `bt switch` | Switch org and project context |
| `bt status` | Show current org and project context |
| `bt datasets` | Manage datasets and dataset pipelines |
| `bt eval` | Run eval files (Unix only) |
| `bt sql` | Run SQL queries against Braintrust |
| `bt view` | View logs, traces, and spans |
| `bt projects` | Manage projects (list, create, view, delete) |
| `bt datasets` | Manage remote datasets (list, create, update, view, delete) |
| `bt prompts` | Manage prompts (list, view, update, delete) |
| `bt functions` | Manage functions (list, view, invoke, update, push, pull, delete) |
| `bt tools` | Manage tools (list, view, invoke, update, delete) |
| `bt scorers` | Manage scorers (list, create, view, invoke, update, delete) |
| `bt sync` | Synchronize project logs between Braintrust and local NDJSON files |
| `bt update` | Update bt in-place |

## `bt scorers`

Create prompt-based LLM scorers or classifiers in the current project:

```bash
bt scorers create "Helpfulness" \
--model gpt-5.4-nano \
--messages @messages.json \
--choice-scores '{"A":1,"B":0}'

bt scorers create "Safety label" \
--model gpt-5.4-nano \
--messages @messages.json \
--classifications '["safe","unsafe"]' \
--allow-no-match
```

Use `--if-exists error|ignore|replace` to control slug conflicts. Text and structured input flags accept an inline value, `@PATH`, or `-` for stdin; only one flag per command may read stdin. Use `--template-format mustache|jinja|none`; `nunjucks` and `jinja2` are accepted aliases for Jinja. Model options include `--use-cache[=true|false]` and `--response-format text|json-object|<SOURCE>`; structured output accepts a full `response_format` JSON object inline or from `@PATH`.

Before writing a scorer, `bt` sends the complete candidate definition to Braintrust for validation. The backend applies the same model-parameter and replacement checks as the write and returns structured issues with normalization suggestions when available.

Update only the fields you specify, or use `--patch` for fields without dedicated flags:

```bash
bt scorers update helpfulness --messages @messages.json
bt scorers update helpfulness --model gpt-5.4-nano
bt functions update my-function --description "Updated"
bt tools update my-tool --patch @tool-patch.json
bt prompts update my-prompt --messages @messages.json
```

The API replaces `prompt_data` rather than merging it, so `bt` reads the current definition and sends a materialized replacement with your changes. A concurrent edit can therefore be overwritten.

For TypeScript and Python code scorers, use the Braintrust SDK and `bt functions push`.

## `bt eval`

Expand Down
27 changes: 27 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use std::fmt;

/// An expected error caused by command input rather than an internal failure.
#[derive(Debug)]
pub(crate) struct UserError {
source: Box<dyn std::error::Error + Send + Sync>,
}

impl From<anyhow::Error> for UserError {
fn from(error: anyhow::Error) -> Self {
Self {
source: error.into_boxed_dyn_error(),
}
}
}

impl fmt::Display for UserError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.source.fmt(formatter)
}
}

impl std::error::Error for UserError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.source.as_ref())
}
}
Loading
Loading