ebaml 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ebaml-0.1.0/LICENSE +21 -0
- ebaml-0.1.0/PKG-INFO +450 -0
- ebaml-0.1.0/README.md +422 -0
- ebaml-0.1.0/pyproject.toml +280 -0
- ebaml-0.1.0/pyproject.toml.orig +280 -0
- ebaml-0.1.0/src/baml_sdk/.baml-generator-output-root +1 -0
- ebaml-0.1.0/src/baml_sdk/.baml-generator-output.json +551 -0
- ebaml-0.1.0/src/baml_sdk/.gitignore +3 -0
- ebaml-0.1.0/src/baml_sdk/__init__.py +822 -0
- ebaml-0.1.0/src/baml_sdk/__init__.pyi +746 -0
- ebaml-0.1.0/src/baml_sdk/_baml_sources.py +31 -0
- ebaml-0.1.0/src/baml_sdk/_function_registry.py +125 -0
- ebaml-0.1.0/src/baml_sdk/_inlinedbaml.py +25 -0
- ebaml-0.1.0/src/baml_sdk/_invocation_types.py +34 -0
- ebaml-0.1.0/src/baml_sdk/_invocation_types.pyi +73 -0
- ebaml-0.1.0/src/baml_sdk/_typemap.py +584 -0
- ebaml-0.1.0/src/baml_sdk/ai/__init__.py +473 -0
- ebaml-0.1.0/src/baml_sdk/ai/__init__.pyi +391 -0
- ebaml-0.1.0/src/baml_sdk/ai/clients/__init__.py +276 -0
- ebaml-0.1.0/src/baml_sdk/ai/clients/__init__.pyi +309 -0
- ebaml-0.1.0/src/baml_sdk/ai/content/__init__.py +139 -0
- ebaml-0.1.0/src/baml_sdk/ai/content/__init__.pyi +121 -0
- ebaml-0.1.0/src/baml_sdk/ai/errors/__init__.py +297 -0
- ebaml-0.1.0/src/baml_sdk/ai/errors/__init__.pyi +218 -0
- ebaml-0.1.0/src/baml_sdk/ai/events/__init__.py +297 -0
- ebaml-0.1.0/src/baml_sdk/ai/events/__init__.pyi +237 -0
- ebaml-0.1.0/src/baml_sdk/ai/internal/__init__.py +467 -0
- ebaml-0.1.0/src/baml_sdk/ai/internal/__init__.pyi +645 -0
- ebaml-0.1.0/src/baml_sdk/ai/mcp/__init__.py +77 -0
- ebaml-0.1.0/src/baml_sdk/ai/mcp/__init__.pyi +120 -0
- ebaml-0.1.0/src/baml_sdk/ai/stream/__init__.py +336 -0
- ebaml-0.1.0/src/baml_sdk/ai/stream/__init__.pyi +453 -0
- ebaml-0.1.0/src/baml_sdk/ai/tools/__init__.py +146 -0
- ebaml-0.1.0/src/baml_sdk/ai/tools/__init__.pyi +165 -0
- ebaml-0.1.0/src/baml_sdk/ai/wire/__init__.py +280 -0
- ebaml-0.1.0/src/baml_sdk/ai/wire/__init__.pyi +459 -0
- ebaml-0.1.0/src/baml_sdk/baml/__init__.py +837 -0
- ebaml-0.1.0/src/baml_sdk/baml/__init__.pyi +4768 -0
- ebaml-0.1.0/src/baml_sdk/baml/crypto/__init__.py +277 -0
- ebaml-0.1.0/src/baml_sdk/baml/crypto/__init__.pyi +153 -0
- ebaml-0.1.0/src/baml_sdk/baml/csv/__init__.py +1076 -0
- ebaml-0.1.0/src/baml_sdk/baml/csv/__init__.pyi +1362 -0
- ebaml-0.1.0/src/baml_sdk/baml/env/__init__.py +138 -0
- ebaml-0.1.0/src/baml_sdk/baml/env/__init__.pyi +145 -0
- ebaml-0.1.0/src/baml_sdk/baml/errors/__init__.py +275 -0
- ebaml-0.1.0/src/baml_sdk/baml/errors/__init__.pyi +161 -0
- ebaml-0.1.0/src/baml_sdk/baml/fs/__init__.py +358 -0
- ebaml-0.1.0/src/baml_sdk/baml/fs/__init__.pyi +414 -0
- ebaml-0.1.0/src/baml_sdk/baml/future/__init__.py +182 -0
- ebaml-0.1.0/src/baml_sdk/baml/future/__init__.pyi +244 -0
- ebaml-0.1.0/src/baml_sdk/baml/glob/__init__.py +98 -0
- ebaml-0.1.0/src/baml_sdk/baml/glob/__init__.pyi +93 -0
- ebaml-0.1.0/src/baml_sdk/baml/hash/__init__.py +63 -0
- ebaml-0.1.0/src/baml_sdk/baml/hash/__init__.pyi +43 -0
- ebaml-0.1.0/src/baml_sdk/baml/host/__init__.py +55 -0
- ebaml-0.1.0/src/baml_sdk/baml/host/__init__.pyi +85 -0
- ebaml-0.1.0/src/baml_sdk/baml/http/__init__.py +398 -0
- ebaml-0.1.0/src/baml_sdk/baml/http/__init__.pyi +602 -0
- ebaml-0.1.0/src/baml_sdk/baml/io/__init__.py +88 -0
- ebaml-0.1.0/src/baml_sdk/baml/io/__init__.pyi +133 -0
- ebaml-0.1.0/src/baml_sdk/baml/iter/__init__.py +485 -0
- ebaml-0.1.0/src/baml_sdk/baml/iter/__init__.pyi +281 -0
- ebaml-0.1.0/src/baml_sdk/baml/json/__init__.py +898 -0
- ebaml-0.1.0/src/baml_sdk/baml/json/__init__.pyi +850 -0
- ebaml-0.1.0/src/baml_sdk/baml/media/__init__.py +46 -0
- ebaml-0.1.0/src/baml_sdk/baml/media/__init__.pyi +45 -0
- ebaml-0.1.0/src/baml_sdk/baml/net/__init__.py +140 -0
- ebaml-0.1.0/src/baml_sdk/baml/net/__init__.pyi +211 -0
- ebaml-0.1.0/src/baml_sdk/baml/ops/__init__.py +68 -0
- ebaml-0.1.0/src/baml_sdk/baml/ops/__init__.pyi +203 -0
- ebaml-0.1.0/src/baml_sdk/baml/panics/__init__.py +356 -0
- ebaml-0.1.0/src/baml_sdk/baml/panics/__init__.pyi +121 -0
- ebaml-0.1.0/src/baml_sdk/baml/random/__init__.py +121 -0
- ebaml-0.1.0/src/baml_sdk/baml/random/__init__.pyi +124 -0
- ebaml-0.1.0/src/baml_sdk/baml/regex/__init__.py +295 -0
- ebaml-0.1.0/src/baml_sdk/baml/regex/__init__.pyi +378 -0
- ebaml-0.1.0/src/baml_sdk/baml/sap/__init__.py +193 -0
- ebaml-0.1.0/src/baml_sdk/baml/sap/__init__.pyi +194 -0
- ebaml-0.1.0/src/baml_sdk/baml/spawn/__init__.py +191 -0
- ebaml-0.1.0/src/baml_sdk/baml/spawn/__init__.pyi +194 -0
- ebaml-0.1.0/src/baml_sdk/baml/sys/__init__.py +283 -0
- ebaml-0.1.0/src/baml_sdk/baml/sys/__init__.pyi +293 -0
- ebaml-0.1.0/src/baml_sdk/baml/time/__init__.py +661 -0
- ebaml-0.1.0/src/baml_sdk/baml/time/__init__.pyi +2048 -0
- ebaml-0.1.0/src/baml_sdk/baml/toml/__init__.py +187 -0
- ebaml-0.1.0/src/baml_sdk/baml/toml/__init__.pyi +187 -0
- ebaml-0.1.0/src/baml_sdk/baml/ws/__init__.py +108 -0
- ebaml-0.1.0/src/baml_sdk/baml/ws/__init__.pyi +163 -0
- ebaml-0.1.0/src/baml_sdk/baml/yaml/__init__.py +140 -0
- ebaml-0.1.0/src/baml_sdk/baml/yaml/__init__.pyi +129 -0
- ebaml-0.1.0/src/baml_sdk/experimental/__init__.py +26 -0
- ebaml-0.1.0/src/baml_sdk/experimental/__init__.pyi +28 -0
- ebaml-0.1.0/src/baml_sdk/py.typed +0 -0
- ebaml-0.1.0/src/baml_sdk/reflect/__init__.py +681 -0
- ebaml-0.1.0/src/baml_sdk/reflect/__init__.pyi +962 -0
- ebaml-0.1.0/src/baml_sdk/reflect/class_/__init__.py +131 -0
- ebaml-0.1.0/src/baml_sdk/reflect/class_/__init__.pyi +180 -0
- ebaml-0.1.0/src/baml_sdk/reflect/enum/__init__.py +44 -0
- ebaml-0.1.0/src/baml_sdk/reflect/enum/__init__.pyi +41 -0
- ebaml-0.1.0/src/baml_sdk/reflect/errors/__init__.py +122 -0
- ebaml-0.1.0/src/baml_sdk/reflect/errors/__init__.pyi +67 -0
- ebaml-0.1.0/src/baml_sdk/reflect/function/__init__.py +46 -0
- ebaml-0.1.0/src/baml_sdk/reflect/function/__init__.pyi +39 -0
- ebaml-0.1.0/src/baml_sdk/reflect/interface/__init__.py +55 -0
- ebaml-0.1.0/src/baml_sdk/reflect/interface/__init__.pyi +64 -0
- ebaml-0.1.0/src/baml_sdk/vendor/__init__.py +47 -0
- ebaml-0.1.0/src/baml_sdk/vendor/__init__.pyi +33 -0
- ebaml-0.1.0/src/baml_sdk/vendor/anthropic/__init__.py +143 -0
- ebaml-0.1.0/src/baml_sdk/vendor/anthropic/__init__.pyi +159 -0
- ebaml-0.1.0/src/baml_sdk/vendor/anthropic/internal/__init__.py +774 -0
- ebaml-0.1.0/src/baml_sdk/vendor/anthropic/internal/__init__.pyi +787 -0
- ebaml-0.1.0/src/baml_sdk/vendor/assert_/__init__.py +69 -0
- ebaml-0.1.0/src/baml_sdk/vendor/assert_/__init__.pyi +210 -0
- ebaml-0.1.0/src/baml_sdk/vendor/aws/__init__.py +159 -0
- ebaml-0.1.0/src/baml_sdk/vendor/aws/__init__.pyi +87 -0
- ebaml-0.1.0/src/baml_sdk/vendor/aws/internal/__init__.py +1430 -0
- ebaml-0.1.0/src/baml_sdk/vendor/aws/internal/__init__.pyi +1197 -0
- ebaml-0.1.0/src/baml_sdk/vendor/claude_code/__init__.py +74 -0
- ebaml-0.1.0/src/baml_sdk/vendor/claude_code/__init__.pyi +60 -0
- ebaml-0.1.0/src/baml_sdk/vendor/claude_code/internal/__init__.py +247 -0
- ebaml-0.1.0/src/baml_sdk/vendor/claude_code/internal/__init__.pyi +262 -0
- ebaml-0.1.0/src/baml_sdk/vendor/google/__init__.py +177 -0
- ebaml-0.1.0/src/baml_sdk/vendor/google/__init__.pyi +165 -0
- ebaml-0.1.0/src/baml_sdk/vendor/google/internal/__init__.py +1111 -0
- ebaml-0.1.0/src/baml_sdk/vendor/google/internal/__init__.pyi +1141 -0
- ebaml-0.1.0/src/baml_sdk/vendor/openai/__init__.py +485 -0
- ebaml-0.1.0/src/baml_sdk/vendor/openai/__init__.pyi +669 -0
- ebaml-0.1.0/src/baml_sdk/vendor/openai/internal/__init__.py +2248 -0
- ebaml-0.1.0/src/baml_sdk/vendor/openai/internal/__init__.pyi +2155 -0
- ebaml-0.1.0/src/baml_sdk/vendor/openai/internal/decisions/__init__.py +207 -0
- ebaml-0.1.0/src/baml_sdk/vendor/openai/internal/decisions/__init__.pyi +182 -0
- ebaml-0.1.0/src/baml_sdk/vendor/testing/__init__.py +580 -0
- ebaml-0.1.0/src/baml_sdk/vendor/testing/__init__.pyi +667 -0
- ebaml-0.1.0/src/baml_sdk/vendor/trace/__init__.py +304 -0
- ebaml-0.1.0/src/baml_sdk/vendor/trace/__init__.pyi +321 -0
- ebaml-0.1.0/src/baml_sdk/vendor/typesafeai/__init__.py +74 -0
- ebaml-0.1.0/src/baml_sdk/vendor/typesafeai/__init__.pyi +56 -0
- ebaml-0.1.0/src/baml_sdk/vendor/typesafeai/internal/__init__.py +205 -0
- ebaml-0.1.0/src/baml_sdk/vendor/typesafeai/internal/__init__.pyi +175 -0
- ebaml-0.1.0/src/baml_sdk/vendor/vercel/__init__.py +104 -0
- ebaml-0.1.0/src/baml_sdk/vendor/vercel/__init__.pyi +93 -0
- ebaml-0.1.0/src/baml_sdk/vendor/vercel/internal/__init__.py +336 -0
- ebaml-0.1.0/src/baml_sdk/vendor/vercel/internal/__init__.pyi +294 -0
- ebaml-0.1.0/src/ebaml/__init__.py +58 -0
- ebaml-0.1.0/src/ebaml/benchmark.py +378 -0
- ebaml-0.1.0/src/ebaml/core/__init__.py +1 -0
- ebaml-0.1.0/src/ebaml/core/custom_types.py +128 -0
- ebaml-0.1.0/src/ebaml/core/exceptions.py +115 -0
- ebaml-0.1.0/src/ebaml/graphs/__init__.py +1 -0
- ebaml-0.1.0/src/ebaml/graphs/ebm_graph.py +560 -0
- ebaml-0.1.0/src/ebaml/graphs/graph_text.py +203 -0
- ebaml-0.1.0/src/ebaml/llm/__init__.py +1 -0
- ebaml-0.1.0/src/ebaml/llm/descriptors.py +456 -0
- ebaml-0.1.0/src/ebaml/llm/options.py +164 -0
- ebaml-0.1.0/src/ebaml/llm/provider_errors.py +301 -0
- ebaml-0.1.0/src/ebaml/llm/retry.py +73 -0
- ebaml-0.1.0/src/ebaml/llm/sdk_setup.py +326 -0
- ebaml-0.1.0/src/ebaml/local/__init__.py +1 -0
- ebaml-0.1.0/src/ebaml/local/fact_text.py +276 -0
- ebaml-0.1.0/src/ebaml/local/facts.py +550 -0
- ebaml-0.1.0/src/ebaml/py.typed +0 -0
ebaml-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Johnny Godoy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ebaml-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ebaml
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: BAML for Explainable Boosting Machines: talk to your EBM.
|
|
5
|
+
Keywords: baml,llm,explainable-ai,explainable-boosting-machine,interpretml
|
|
6
|
+
Author: Johnny Godoy
|
|
7
|
+
Author-email: Johnny Godoy <johnny.godoy@ing.uchile.cl>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Dist: attrs>=26.1.0
|
|
17
|
+
Requires-Dist: baml-bridge>=0.23.0
|
|
18
|
+
Requires-Dist: beartype>=0.23.1
|
|
19
|
+
Requires-Dist: interpret>=0.7.8
|
|
20
|
+
Requires-Dist: orjson>=3.13.0
|
|
21
|
+
Requires-Dist: structlog>=26.1.0
|
|
22
|
+
Requires-Python: >=3.13
|
|
23
|
+
Project-URL: Documentation, https://johnny-godoy.github.io/EBaMl/
|
|
24
|
+
Project-URL: Source, https://github.com/johnny-godoy/EBaMl
|
|
25
|
+
Project-URL: Issues, https://github.com/johnny-godoy/EBaMl/issues
|
|
26
|
+
Project-URL: Changelog, https://github.com/johnny-godoy/EBaMl/blob/main/CHANGELOG.md
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# EBaMl
|
|
30
|
+
|
|
31
|
+
<!-- --8<-- [start:intro] -->
|
|
32
|
+
BAML for Explainable Boosting Machines. Python extracts the graphs from an EBM, and BAML holds the prompts, the LLM
|
|
33
|
+
clients and the chain-of-thought pipelines.
|
|
34
|
+
|
|
35
|
+
EBaMl is based on [TalkToEBM](https://github.com/interpretml/TalkToEBM) and adds local
|
|
36
|
+
explanations (why did the model predict this for this input?) computed exactly and put in words for a layperson or a
|
|
37
|
+
data scientist, a benchmark that scores answers automatically, a configurable LLM provider, token budgets and typed,
|
|
38
|
+
structured logging.
|
|
39
|
+
|
|
40
|
+
LLM calls go through [OpenRouter](https://openrouter.ai)'s free router (`openrouter/free`), so no paid API key is needed.
|
|
41
|
+
|
|
42
|
+
<!-- --8<-- [end:intro] -->
|
|
43
|
+
## Supported models
|
|
44
|
+
|
|
45
|
+
<!-- --8<-- [start:supported-models] -->
|
|
46
|
+
| Feature | Binary classifier | Regressor | Multiclass classifier | Interactions |
|
|
47
|
+
| --- | :---: | :---: | :---: | :---: |
|
|
48
|
+
| Graph descriptions (`describe_graph`) | ✅ | ✅ | ❌ | ⚠️ |
|
|
49
|
+
| Model summary (`describe_ebm`) | ✅ | ✅ | ❌ | ⚠️ |
|
|
50
|
+
| Graph text (`EBMGraph`) | ✅ | ✅ | ❌ | ⚠️ |
|
|
51
|
+
| Local explanations (`explain_instance`) | ✅ | ❌ | ❌ | ❌ |
|
|
52
|
+
|
|
53
|
+
✅ supported. ⚠️ partly supported: the main effects are described, and the interaction terms are skipped without a
|
|
54
|
+
warning. ❌ not supported yet. Full support is on the
|
|
55
|
+
[roadmap](https://github.com/johnny-godoy/EBaMl/blob/main/ROADMAP.md).
|
|
56
|
+
<!-- --8<-- [end:supported-models] -->
|
|
57
|
+
## Setup
|
|
58
|
+
|
|
59
|
+
<!-- --8<-- [start:setup] -->
|
|
60
|
+
### Requirements
|
|
61
|
+
|
|
62
|
+
- Python 3.13 or later and [uv](https://docs.astral.sh/uv/getting-started/installation/).
|
|
63
|
+
- The BAML toolchain 0.23 or later (step 1).
|
|
64
|
+
- An API key for an LLM provider. The default is [OpenRouter](https://openrouter.ai)'s free router, which needs no
|
|
65
|
+
paid plan. You can use another provider instead, see "Choosing the model".
|
|
66
|
+
|
|
67
|
+
### 1. Install the BAML toolchain
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
curl -fsSL https://pkg.boundaryml.com/install.sh | sh -s
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The installer prints a line to add to your shell profile. Add it, then open a new shell:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
. "$HOME/.baml/env"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Check it:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
baml --version # toolchain 0.23 or later
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If `baml` resolves to an older install (Homebrew `baml`, or `baml-py` in a virtualenv), remove it or put `~/.baml/bin`
|
|
86
|
+
first on `PATH`. The old CLIs have no `describe`, `generate add` or `agent` commands.
|
|
87
|
+
|
|
88
|
+
If a command stops with "the BAML agent skill is required", run `baml agent install` and retry.
|
|
89
|
+
|
|
90
|
+
### 2. Get the code and install the Python dependencies
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone <repository-url> EBaMl
|
|
94
|
+
cd EBaMl
|
|
95
|
+
uv sync
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Set the API key of your LLM provider
|
|
99
|
+
|
|
100
|
+
For the default provider, OpenRouter:
|
|
101
|
+
|
|
102
|
+
1. Create an account at <https://openrouter.ai> and open <https://openrouter.ai/settings/keys>.
|
|
103
|
+
2. Create a key. Keys start with `sk-or-`.
|
|
104
|
+
3. Export it in the shell you run the code from:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
export OPENROUTER_API_KEY=sk-or-...
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The key is read from the environment when a request is sent. It is never written to a `.baml` file.
|
|
111
|
+
|
|
112
|
+
Using another provider? Set its variable instead (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, ...) and pass `model=` as
|
|
113
|
+
described in "Choosing the model". Local models (Ollama) need no key.
|
|
114
|
+
|
|
115
|
+
### 4. Generate the Python SDK
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
baml generate
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This writes the `src/baml_sdk/` package from `baml_src/`, next to `ebaml`, so it imports wherever the project is
|
|
122
|
+
installed. Rerun it every time a `.baml` file changes, including when you change the provider.
|
|
123
|
+
|
|
124
|
+
If `src/baml_sdk/` does not exist, `ebaml.llm.descriptors` runs `baml generate` when you import it. If the package was
|
|
125
|
+
generated by another BAML toolchain than the one that `baml-bridge` expects (a version skew), the import runs
|
|
126
|
+
`baml toolchain use <version>` and `baml generate`. If the agent skill check stops `baml generate`, the import runs it
|
|
127
|
+
again with `--agent-skill-check off`. If none of this works, `SDKUnavailableError` lists the steps that fix the cause.
|
|
128
|
+
|
|
129
|
+
### 5. Check the setup
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
OPENROUTER_API_KEY=unused baml test -x root::Live # offline tests on a scripted client, no network
|
|
133
|
+
baml run main # prints "hello from baml"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
These two do not call an LLM, so they pass without a working key or provider. They check the toolchain and the SDK.
|
|
137
|
+
|
|
138
|
+
### 6. Live test (optional)
|
|
139
|
+
|
|
140
|
+
Makes real requests to your provider (9 on the OpenRouter free router, so it can be slow or rate limited). It does
|
|
141
|
+
nothing without `OPENROUTER_API_KEY`, so with another provider run the example under "Usage" instead.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
OPENROUTER_API_KEY=sk-or-... uv run python scripts/live_openrouter.py
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
It fits a small EBM, runs the three-turn chain directly and checks that the provider reports token usage, then runs
|
|
148
|
+
`describe_graph` and `describe_ebm`. It exits non-zero on the first failed assertion.
|
|
149
|
+
|
|
150
|
+
### Troubleshooting
|
|
151
|
+
|
|
152
|
+
| Symptom | Cause and fix |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `baml: command not found` | The profile line from step 1 is not loaded. Open a new shell or run `. "$HOME/.baml/env"`. |
|
|
155
|
+
| `unknown command: describe` (or `generate add`, `agent`) | An older `baml` is first on `PATH`. See step 1. |
|
|
156
|
+
| `SDKUnavailableError`, or `ModuleNotFoundError: baml_sdk` | The SDK is not generated or has the wrong version, and `ebaml` could not fix it. The message of the error lists the steps. Run `baml generate` (step 4). |
|
|
157
|
+
| `MissingAPIKeyError` | The key of the provider is not exported in this shell. The message names the variable. See "Choosing the model" for the variable of each provider. |
|
|
158
|
+
| `APIKeyRejectedError` | The provider rejected the key. Check the key, its account and its credit. |
|
|
159
|
+
| `RateLimitedError` | The provider limited the rate. Lower the concurrency, `describe_ebm(ebm, max_concurrency=2)`, or pin a paid model. |
|
|
160
|
+
| `ProviderUnreachableError` | A request did not reach the provider. Check the network, `HTTPS_PROXY` and `base_url`. |
|
|
161
|
+
|
|
162
|
+
<!-- --8<-- [end:setup] -->
|
|
163
|
+
## Usage
|
|
164
|
+
|
|
165
|
+
<!-- --8<-- [start:usage] -->
|
|
166
|
+
```python
|
|
167
|
+
from interpret.glassbox import ExplainableBoostingClassifier
|
|
168
|
+
|
|
169
|
+
from ebaml.llm.descriptors import describe_ebm, describe_graph
|
|
170
|
+
from ebaml.llm.options import PromptOptions
|
|
171
|
+
|
|
172
|
+
ebm = ExplainableBoostingClassifier().fit(X, y)
|
|
173
|
+
|
|
174
|
+
print(describe_graph(ebm, feature_index=0))
|
|
175
|
+
print(describe_ebm(ebm, num_sentences=30, prompt=PromptOptions(dataset_description="...")))
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
A graph can be turned into text with `EBMGraph.to_text` and back with `EBMGraph.from_text`, which also reads a graph
|
|
179
|
+
embedded in a longer prompt. It needs the confidence bounds, and it only reads continuous features.
|
|
180
|
+
|
|
181
|
+
Three keyword-only arguments set the options. `graph=GraphOptions(...)` sets the options of `EBMGraph.from_ebm` and
|
|
182
|
+
`EBMGraph.to_text`, for example `max_tokens`. `prompt=PromptOptions(...)` sets the descriptions that the prompts use
|
|
183
|
+
(`expert_description`, `graph_description`, `dataset_description` and `task_description`). `model=` selects the LLM, see
|
|
184
|
+
"Choosing the model". The classes are in `ebaml.llm.options`. Token usage reported by the provider is logged at `INFO` on the `ebaml.llm.descriptors` logger.
|
|
185
|
+
|
|
186
|
+
### Explaining one prediction
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from ebaml.llm.descriptors import describe_ebm, explain_instance
|
|
190
|
+
|
|
191
|
+
ebm = ExplainableBoostingClassifier(interactions=0).fit(X, y) # binary classifier, no interactions
|
|
192
|
+
global_explanation = describe_ebm(ebm) # computed once, prepended to every local prompt
|
|
193
|
+
|
|
194
|
+
explanation = explain_instance(ebm, X.iloc[0].to_dict(), global_explanation, audience="layperson")
|
|
195
|
+
print(explanation.counterfactual) # what would have to change to flip the prediction
|
|
196
|
+
print(explanation.sufficient_reason) # which inputs settle the prediction on their own
|
|
197
|
+
print(explanation.completions) # how certain the outcome is as inputs become known
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The facts are computed exactly from the scores and confidence intervals of the EBM (`ebaml.local`); the LLM only puts
|
|
201
|
+
them in words. They are the three queries of [arXiv:2010.12265](https://arxiv.org/abs/2010.12265): minimum change
|
|
202
|
+
required, minimum sufficient reason and count completions. `audience="layperson"` (default) gives no model numbers;
|
|
203
|
+
`audience="data_scientist"` quotes scores, probabilities and confidence intervals. It supports binary classifiers
|
|
204
|
+
without interactions, and `explanation.facts` holds the numbers behind the text. The "Local explanations" page of the documentation explains what each explanation means, the options and the limits,
|
|
205
|
+
and `notebooks/local_explanations.py` walks through them on the Titanic data.
|
|
206
|
+
|
|
207
|
+
### Errors
|
|
208
|
+
|
|
209
|
+
Every error that `ebaml` raises on purpose derives from `ebaml.EBaMlError`. Use `except EBaMlError` to catch the errors
|
|
210
|
+
of `ebaml` apart from the errors of other code. Each error for a bad value also derives from `ValueError`, so an
|
|
211
|
+
`except ValueError` catches it too.
|
|
212
|
+
|
|
213
|
+
| Error | `ValueError` | Raised when |
|
|
214
|
+
| --- | --- | --- |
|
|
215
|
+
| `InvalidOptionError` | yes | An option is not one of its valid values, for example an unknown `Normalization`. |
|
|
216
|
+
| `OptionRangeError` | yes | A number option is outside its range, for example `confidence_level=1`. |
|
|
217
|
+
| `InvalidInstanceError` | yes | The instance to explain lacks a value, or has a category that the EBM did not see. |
|
|
218
|
+
| `UnsupportedModelError` | yes | A local explanation gets an EBM with interactions, or one that is not a binary classifier. |
|
|
219
|
+
| `GraphTextError` | yes | `EBMGraph.from_text` cannot read the text of a graph. |
|
|
220
|
+
| `EBMGraphError` | no | The base class of the errors of a graph. |
|
|
221
|
+
| `EBMGraphShapeError` | yes | The bins of a graph do not fit its scores, or a boolean format gets more than two bins. |
|
|
222
|
+
| `EBMGraphTokenLimitError` | no | The text of a graph is over the token budget, and its format cannot be simplified. |
|
|
223
|
+
| `EBMGraphSimplificationError` | no | The text of a graph is still over the token budget at the largest simplification. |
|
|
224
|
+
| `SDKUnavailableError` | no | `baml_sdk` is missing, and `ebaml` cannot run `baml generate`. The error also derives from `ImportError`. |
|
|
225
|
+
| `LLMRequestError` | no | The base class of the errors of a provider. Its message lists the steps that fix the cause. |
|
|
226
|
+
| `MissingAPIKeyError` | no | A provider needs an API key and the environment has none. |
|
|
227
|
+
| `APIKeyRejectedError` | no | A provider answers a request with "unauthorized" or "forbidden". |
|
|
228
|
+
| `RateLimitedError` | no | A provider limits the rate of the requests. |
|
|
229
|
+
| `ProviderUnreachableError` | no | A request does not reach the provider. |
|
|
230
|
+
|
|
231
|
+
`EBMGraphShapeError`, `EBMGraphTokenLimitError` and `EBMGraphSimplificationError` derive from `EBMGraphError`. The four provider errors derive from `LLMRequestError`. All classes are in `ebaml.core.exceptions`, and `ebaml` exports
|
|
232
|
+
them too.
|
|
233
|
+
|
|
234
|
+
<!-- --8<-- [end:usage] -->
|
|
235
|
+
## How it works
|
|
236
|
+
|
|
237
|
+
| Where | What |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `src/ebaml/core/` | Shared kernel: types and errors. It imports no other part of `ebaml`. |
|
|
240
|
+
| `src/ebaml/graphs/` | Graph extraction and simplification (numpy, `interpret`), and the text the LLM reads. |
|
|
241
|
+
| `src/ebaml/llm/` | The edge: the calls into BAML (`descriptors.py`), their options and the retry after a cut-off reply. |
|
|
242
|
+
| `baml_src/talk_to_ebm.baml` | Prompts, the default LLM clients, the three-turn description chain, the summary, `describe_ebm`, token accounting. |
|
|
243
|
+
| `baml_src/model_spec.baml` | The model of a call: `ModelSpec` and one client builder for each provider. |
|
|
244
|
+
| `src/ebaml/local/`, `baml_src/local_explanations.baml` | Local explanations: the facts of one prediction computed deterministically, their rendering as JSON texts, and the prompts that put them in words. |
|
|
245
|
+
| `baml_src/talk_to_ebm_test.baml`, `baml_src/model_spec_test.baml` | Offline tests on a scripted client. |
|
|
246
|
+
| `baml.toml` | Generator config for the Python SDK. |
|
|
247
|
+
| `src/ebaml/benchmark.py`, `benchmark/` | The benchmark: loading, answer parsing and scoring, and the data and runner (see [Benchmark](#benchmark)). |
|
|
248
|
+
| `notebooks/` | Example notebooks, written with marimo. |
|
|
249
|
+
| `docs/`, `zensical.toml` | The documentation, built with Zensical (see [Development](#development)). |
|
|
250
|
+
| `tests/unit/` | Python unit tests (100% coverage required). `scripts/live_openrouter.py` is the optional live test. |
|
|
251
|
+
| `scripts/` | Maintenance scripts, see [scripts/README.md](scripts/README.md). |
|
|
252
|
+
| `.github/` | CI workflow and the scripts that turn failures into reports. |
|
|
253
|
+
| `src/baml_sdk/` | Generated. Do not edit. |
|
|
254
|
+
|
|
255
|
+
<!-- --8<-- [start:behaviour] -->
|
|
256
|
+
- **Model:** `openrouter/free` picks a free model for each request, so the model can change between calls and between
|
|
257
|
+
the turns of one conversation. Quality and speed vary with it.
|
|
258
|
+
- **Retries:** transient failures (rate limits, network errors, 5xx) are retried up to 3 times. Requests the provider
|
|
259
|
+
rejects are not retried.
|
|
260
|
+
- **Concurrency:** `describe_ebm` describes features concurrently, at most `max_concurrency` at a time (default 8).
|
|
261
|
+
Free models are rate limited, so lower it (`describe_ebm(ebm, max_concurrency=2)`) if you see rate-limit errors.
|
|
262
|
+
- **Token budget:** there is no tokenizer before a call, so the check uses a rough estimate (about 3 characters per
|
|
263
|
+
token). Going a little over `max_tokens` only warns. Going more than `overbudget_percent` (default 50) over makes
|
|
264
|
+
`to_text` simplify the graph, or raise if it cannot. The provider's own error is the real limit. The functions in
|
|
265
|
+
`ebaml.llm.descriptors` pass the BAML check. A direct call of `EBMGraph.to_text` checks nothing, unless you pass
|
|
266
|
+
`check_budget`, for example `baml_sdk.check_token_budget`.
|
|
267
|
+
|
|
268
|
+
<!-- --8<-- [end:behaviour] -->
|
|
269
|
+
## Notebook
|
|
270
|
+
|
|
271
|
+
[`notebooks/titanic.py`](notebooks/titanic.py) is a [marimo](https://marimo.io) notebook that ports TalkToEBM's example.
|
|
272
|
+
It trains an EBM on the Titanic dataset (downloaded from OpenML), asks the LLM to describe a graph and the whole model,
|
|
273
|
+
and shows the graph and text API (`from_ebm`, `simplify`, `to_text`, `from_text`). The LLM cells run only when you press
|
|
274
|
+
their button. They use the default model, which needs `OPENROUTER_API_KEY`. See "Choosing the model" for other models.
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
uv run marimo edit notebooks/titanic.py # interactive
|
|
278
|
+
uv run marimo run notebooks/titanic.py # as an app
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
[`notebooks/local_explanations.py`](notebooks/local_explanations.py) is the place to learn the local explanations: pick a
|
|
282
|
+
passenger, see the facts with no API key, move the confidence slider, check the guarantees against random completions,
|
|
283
|
+
compare what the LLM reads for a layperson and a data scientist, and (with a key) get the explanations in words.
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
uv run marimo edit notebooks/local_explanations.py
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Benchmark
|
|
290
|
+
|
|
291
|
+
The benchmark follows [TalkToEBM's benchmark](https://github.com/interpretml/TalkToEBM/tree/main/benchmark) that checks how well a
|
|
292
|
+
model reads EBM graphs: function recognition (easy and hard), the largest jump, monotonicity, reading a value and the
|
|
293
|
+
widest confidence interval. It makes real requests, and it asks two LLM turns per question (6 tasks, 480 questions in
|
|
294
|
+
all). The default model needs `OPENROUTER_API_KEY`. The flag `--model provider/model` selects another model.
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
uv run python benchmark/run.py --task monotonicity --limit 10 --output results.json
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
See [benchmark/README.md](benchmark/README.md) for the tasks, the scoring and what it leaves out.
|
|
301
|
+
|
|
302
|
+
## Choosing the model
|
|
303
|
+
|
|
304
|
+
<!-- --8<-- [start:changing-model] -->
|
|
305
|
+
The default model is OpenRouter's free router, `openrouter/free`. It needs `OPENROUTER_API_KEY`. The `model` argument
|
|
306
|
+
of `describe_graph`, `describe_ebm` and `explain_instance` selects another model for one call. EBaMl sends every LLM
|
|
307
|
+
request of that call to that model. You do not change the package.
|
|
308
|
+
|
|
309
|
+
### Quick start
|
|
310
|
+
|
|
311
|
+
Pass the shorthand `"provider/model"`:
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
from ebaml.llm.descriptors import describe_ebm
|
|
315
|
+
|
|
316
|
+
print(describe_ebm(ebm, model="anthropic/claude-haiku-4-5"))
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Pass `ModelOptions` to set more:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from ebaml.llm.options import ModelOptions
|
|
323
|
+
|
|
324
|
+
model = ModelOptions(provider="anthropic", model="claude-haiku-4-5", temperature=0.2, max_output_tokens=8000)
|
|
325
|
+
print(describe_ebm(ebm, model=model))
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
EBaMl splits the shorthand at the first `/`. The text `"openrouter/openai/gpt-4o-mini"` selects the provider
|
|
329
|
+
`openrouter` and the model `openai/gpt-4o-mini`. EBaMl checks the provider before the first paid request. It raises
|
|
330
|
+
`InvalidOptionError` if the provider is not in the table below.
|
|
331
|
+
|
|
332
|
+
### Providers
|
|
333
|
+
|
|
334
|
+
The provider is a BAML provider prefix. Set the environment variable of the key in the shell that runs Python.
|
|
335
|
+
|
|
336
|
+
| Provider | Default key variable | Notes |
|
|
337
|
+
| --- | --- | --- |
|
|
338
|
+
| `openrouter` | `OPENROUTER_API_KEY` | The model name is the OpenRouter name, for example `openai/gpt-4o-mini`. |
|
|
339
|
+
| `openai` | `OPENAI_API_KEY` | |
|
|
340
|
+
| `anthropic` | `ANTHROPIC_API_KEY` | |
|
|
341
|
+
| `google` | `GOOGLE_API_KEY` | The Gemini API. |
|
|
342
|
+
| `ollama` | None | A local server. `base_url` defaults to `http://localhost:11434/v1`. |
|
|
343
|
+
| `openai-generic` | None | Any OpenAI-compatible server. `base_url` is required. Set `api_key_env` if the server needs a key. |
|
|
344
|
+
| `azure` | `AZURE_OPENAI_API_KEY` | The model name is the name of the deployment. Set `base_url` to the deployment, or set `AZURE_OPENAI_ENDPOINT`. |
|
|
345
|
+
| `vertex` | None | Google Cloud credentials (`GOOGLE_APPLICATION_CREDENTIALS` or `gcloud auth`). The project comes from `GOOGLE_CLOUD_PROJECT`. |
|
|
346
|
+
| `bedrock` | None | The AWS credentials of the machine. The region comes from `AWS_REGION`. `api_key_env` has no effect. |
|
|
347
|
+
|
|
348
|
+
Run `baml describe <client>.new`, for example `baml describe aws.BedrockClient.new`, to see all settings of a client.
|
|
349
|
+
|
|
350
|
+
### Options
|
|
351
|
+
|
|
352
|
+
| Field | Meaning | Default |
|
|
353
|
+
| --- | --- | --- |
|
|
354
|
+
| `provider` | The BAML provider prefix from the table. | Required. |
|
|
355
|
+
| `model` | The name of the model at the provider. | Required. |
|
|
356
|
+
| `api_key_env` | The name of the environment variable that holds the key. | The variable of the provider. |
|
|
357
|
+
| `base_url` | The address of the server. | The address of the provider. |
|
|
358
|
+
| `temperature` | The sampling temperature. | 0.7 (0.0 in the benchmark). |
|
|
359
|
+
| `max_output_tokens` | The most tokens of one reply. | See below. |
|
|
360
|
+
| `headers` | Headers to add to each request. | None. |
|
|
361
|
+
|
|
362
|
+
A field that you set wins over the default of the call. The output limit has two defaults:
|
|
363
|
+
|
|
364
|
+
| Calls | `max_output_tokens` |
|
|
365
|
+
| --- | --- |
|
|
366
|
+
| Turn 1 of a graph description, the draft of the summary, and the two turns of the benchmark | 8000 |
|
|
367
|
+
| All other calls, including the local explanations | 6000 |
|
|
368
|
+
|
|
369
|
+
### Examples
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
# OpenRouter, a pinned model (the free router picks a new model for each request)
|
|
373
|
+
describe_ebm(ebm, model="openrouter/openai/gpt-4o-mini")
|
|
374
|
+
|
|
375
|
+
# OpenAI
|
|
376
|
+
describe_ebm(ebm, model="openai/gpt-4o-mini")
|
|
377
|
+
|
|
378
|
+
# Google Gemini
|
|
379
|
+
describe_ebm(ebm, model="google/gemini-2.5-flash")
|
|
380
|
+
|
|
381
|
+
# A local model with Ollama. It needs no key.
|
|
382
|
+
describe_ebm(ebm, model="ollama/llama3.1")
|
|
383
|
+
|
|
384
|
+
# A company gateway with an OpenAI-compatible API
|
|
385
|
+
gateway = ModelOptions(
|
|
386
|
+
provider="openai-generic",
|
|
387
|
+
model="my-model",
|
|
388
|
+
base_url="https://llm.example.com/v1",
|
|
389
|
+
api_key_env="MY_GATEWAY_KEY",
|
|
390
|
+
headers={"x-team": "ebm"},
|
|
391
|
+
)
|
|
392
|
+
describe_ebm(ebm, model=gateway)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The benchmark has the flag `--model`:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
uv run python benchmark/run.py --model anthropic/claude-haiku-4-5 --limit 10
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Keys
|
|
402
|
+
|
|
403
|
+
Put the key in an environment variable, in the shell or with `os.environ`. `ModelOptions` holds the name of the
|
|
404
|
+
variable, never the key, so the key does not appear in a `repr`, a log line or a traceback. EBaMl reads the variable
|
|
405
|
+
only when it sends a request.
|
|
406
|
+
|
|
407
|
+
### Limits
|
|
408
|
+
|
|
409
|
+
- One call uses one model. The turns of a call cannot use different models.
|
|
410
|
+
- Python cannot set up fallback chains or round robin. A BAML client object cannot cross from Python to BAML, so
|
|
411
|
+
Python sends plain data and BAML builds the client. A BAML client declaration can use `ai.clients.Fallback`: see
|
|
412
|
+
"Change the default model" in `CONTRIBUTING.md`.
|
|
413
|
+
- A reasoning model counts its hidden tokens in `max_output_tokens`. Keep the limit large, or the reply can end before
|
|
414
|
+
the answer.
|
|
415
|
+
- Quality, speed and cost depend on the model. Small models can ignore the number of sentences that you ask for.
|
|
416
|
+
|
|
417
|
+
Every client has the retry policy of the default clients: EBaMl retries a transient failure up to 3 times.
|
|
418
|
+
<!-- --8<-- [end:changing-model] -->
|
|
419
|
+
|
|
420
|
+
## Development
|
|
421
|
+
|
|
422
|
+
<!-- --8<-- [start:development] -->
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
baml fmt # format .baml files
|
|
426
|
+
baml check # compile check
|
|
427
|
+
baml test -x root::Live # run the offline BAML tests
|
|
428
|
+
baml generate # regenerate src/baml_sdk/
|
|
429
|
+
uv run pytest # Python unit tests, fails under 100% coverage
|
|
430
|
+
uv run pytest README.md docs src --no-cov # the code examples of the docs and the docstrings
|
|
431
|
+
prek run --all-files # every hook: formatters, ruff, vulture, pyrefly, baml fmt, ...
|
|
432
|
+
uv run python scripts/mutation.py # mutation testing of all modules, fails if any mutant survives
|
|
433
|
+
uv run python scripts/mutation.py --base origin/main # the same, but only for modules changed since main
|
|
434
|
+
uv run python scripts/mutation.py --module src/ebaml/local/facts.py # the same, but only for one module
|
|
435
|
+
uv run python -m scripts.check_ignores # every lint/type/test workaround needs a reason written next to it
|
|
436
|
+
uv run python -m scripts.compile_ignores # all workarounds and their reasons as one Markdown report to audit
|
|
437
|
+
uv run --group docs python -m scripts.build_docs # build the docs; fails on a broken link or a docstring warning
|
|
438
|
+
uv run --group docs python -m scripts.build_docs serve # preview the docs (exports the notebooks, so the first run is slow)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
CI (`.github/workflows/ci.yml`) runs prek, secret and security scans (trufflehog, semgrep, trivy), `baml check` and
|
|
442
|
+
`baml test`, pytest with 100% coverage, the code examples of the documentation, and mutation testing of the changed modules with a 100% score. A weekly run tests all modules, one job for each module. Lint and type workarounds
|
|
443
|
+
(`# noqa`, `# type: ignore`, skipped tests, ...) must carry a written reason next to them.
|
|
444
|
+
|
|
445
|
+
The `python` code blocks of `README.md` and `docs/*.md` run in order, and the blocks of one file share their variables.
|
|
446
|
+
The `Examples` sections of the docstrings run as doctests. `tests/docs/conftest.py` gives each Markdown file a small
|
|
447
|
+
data set as `X` and `y`, and it replaces the LLM calls with fakes, so the examples send no request. Put setup that the
|
|
448
|
+
reader does not need in an `<!-- invisible-code-block: python -->` comment. To skip a block, put
|
|
449
|
+
`<!-- skip: next -->` above it, and write the reason in the text.
|
|
450
|
+
<!-- --8<-- [end:development] -->
|