bat-cli 0.1.0__py3-none-any.whl
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.
- add/__init__.py +3 -0
- add/client.py +16 -0
- bat_cli-0.1.0.dist-info/METADATA +231 -0
- bat_cli-0.1.0.dist-info/RECORD +47 -0
- bat_cli-0.1.0.dist-info/WHEEL +5 -0
- bat_cli-0.1.0.dist-info/entry_points.txt +2 -0
- bat_cli-0.1.0.dist-info/top_level.txt +8 -0
- build/__init__.py +3 -0
- build/build.py +79 -0
- cli.py +260 -0
- create/__init__.py +3 -0
- create/agent.py +312 -0
- create/templates/agent/.dockerignore +3 -0
- create/templates/agent/.env.template +4 -0
- create/templates/agent/.python-version +1 -0
- create/templates/agent/Dockerfile +37 -0
- create/templates/agent/Makefile +34 -0
- create/templates/agent/README.md +1 -0
- create/templates/agent/__main__.py +2 -0
- create/templates/agent/agent.json.template +12 -0
- create/templates/agent/agent.spec +45 -0
- create/templates/agent/config.yaml +1 -0
- create/templates/agent/llm_client.py.template +36 -0
- create/templates/agent/pyproject.toml.template +9 -0
- create/templates/agent/src/__init__.py +0 -0
- create/templates/agent/src/graph.py +50 -0
- create/templates/agent/src/llm_clients/__init__.py +0 -0
- create/templates/agent/tests/__init__.py +0 -0
- eval/__init__.py +1 -0
- eval/commands.py +562 -0
- eval/engine/__init__.py +1 -0
- eval/engine/adapter.py +251 -0
- eval/engine/bench_runner.py +149 -0
- eval/engine/contracts.py +115 -0
- eval/engine/eval_config.py +294 -0
- eval/engine/evaluator.py +85 -0
- eval/engine/metrics/__init__.py +1 -0
- eval/engine/metrics/llm_evaluators.py +383 -0
- eval/engine/metrics/metrics.py +135 -0
- eval/engine/metrics/qualitative_helpers.py +64 -0
- eval/engine/orchestrator.py +157 -0
- eval/engine/plotter.py +347 -0
- image_defaults.py +80 -0
- push/__init__.py +3 -0
- push/push.py +58 -0
- set/__init__.py +3 -0
- set/env.py +50 -0
add/__init__.py
ADDED
add/client.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from create.agent import _write_llm_clients
|
|
3
|
+
|
|
4
|
+
def add_clients_to_existing_agent(
|
|
5
|
+
agent_dir: Path,
|
|
6
|
+
*,
|
|
7
|
+
clients: list[str],
|
|
8
|
+
force: bool = False,
|
|
9
|
+
) -> list[Path]:
|
|
10
|
+
llm_clients_dir = agent_dir / "src" / "llm_clients"
|
|
11
|
+
if not llm_clients_dir.is_dir():
|
|
12
|
+
raise FileNotFoundError(
|
|
13
|
+
f"Directory '{llm_clients_dir}' not found. Run this command from an agent root containing src/llm_clients."
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
return _write_llm_clients(llm_clients_dir, clients=clients, force=force)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bat-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool to interact with BAT agents
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: typer>=0.12.3
|
|
8
|
+
Requires-Dist: bat-adk>=2026.06rc1
|
|
9
|
+
Requires-Dist: bat-adk[openai]
|
|
10
|
+
Requires-Dist: a2a-sdk>=1.0.0
|
|
11
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
12
|
+
Requires-Dist: typing-extensions>=4.12.0
|
|
13
|
+
Requires-Dist: matplotlib>=3.8
|
|
14
|
+
|
|
15
|
+
# bat-cli
|
|
16
|
+
|
|
17
|
+
A CLI tool for creating, building, and evaluating BAT agent projects.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Python and [uv](https://docs.astral.sh/uv/) installed
|
|
22
|
+
- Docker installed (required for `bat build` and `bat push`)
|
|
23
|
+
- For evaluation commands: an existing BAT agent root containing `agent.json`, `config.yaml`, and `src/graph.py`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Option A — build and install a standalone binary (Linux/macOS)
|
|
30
|
+
|
|
31
|
+
Run the helper script from the repo root:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bash cli/build_and_install.sh
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This will:
|
|
38
|
+
|
|
39
|
+
1. Sync `dev` and `packaging` dependency groups via `uv`.
|
|
40
|
+
2. Build a one-file executable with PyInstaller.
|
|
41
|
+
3. Move it to `~/.local/bin/bat` (uses `sudo` only when necessary).
|
|
42
|
+
|
|
43
|
+
Make sure `~/.local/bin` is in your `PATH`:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc
|
|
47
|
+
source ~/.bashrc
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then verify:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bat --help
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Option B — build manually
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv sync --group dev --group packaging
|
|
60
|
+
uv run pyinstaller --clean --noconfirm bat_cli.spec
|
|
61
|
+
# binary is at dist/bat (Linux/macOS) or dist/bat.exe (Windows)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
On **Windows**, copy `dist/bat.exe` to a folder on your `PATH` (e.g. `C:\tools\bat`) and open a new terminal.
|
|
65
|
+
|
|
66
|
+
> PyInstaller builds are OS-specific — build on each target OS.
|
|
67
|
+
|
|
68
|
+
### Option C — run without installing (development)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uv sync --group dev
|
|
72
|
+
uv run bat --help
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
All examples below show `bat ...`; replace with `uv run bat ...` when using this option.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Command Tree
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
bat
|
|
83
|
+
├── init
|
|
84
|
+
│ └── agent
|
|
85
|
+
│ ├── [name=default]
|
|
86
|
+
│ ├── --clients, -c
|
|
87
|
+
│ ├── --output-dir, -o
|
|
88
|
+
│ ├── --force, -f
|
|
89
|
+
│ ├── --port
|
|
90
|
+
│ ├── --model
|
|
91
|
+
│ └── --model-provider
|
|
92
|
+
├── add
|
|
93
|
+
│ └── client
|
|
94
|
+
│ ├── <clients>
|
|
95
|
+
│ └── --force, -f
|
|
96
|
+
├── set
|
|
97
|
+
│ └── env
|
|
98
|
+
│ ├── --port
|
|
99
|
+
│ ├── --model
|
|
100
|
+
│ ├── --model-provider
|
|
101
|
+
│ ├── --docker-registry
|
|
102
|
+
│ └── --repo
|
|
103
|
+
├── eval
|
|
104
|
+
│ ├── init
|
|
105
|
+
│ │ └── --force, -f
|
|
106
|
+
│ └── run
|
|
107
|
+
├── build
|
|
108
|
+
│ ├── --context, -C
|
|
109
|
+
│ ├── --docker-registry
|
|
110
|
+
│ ├── --repo
|
|
111
|
+
│ ├── --tag
|
|
112
|
+
│ ├── --version
|
|
113
|
+
│ └── --no-cache
|
|
114
|
+
└── push
|
|
115
|
+
├── --context, -C
|
|
116
|
+
├── --docker-registry
|
|
117
|
+
├── --repo
|
|
118
|
+
└── --tag
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Built-in help is available at every level:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bat --help
|
|
125
|
+
bat init agent --help
|
|
126
|
+
bat build --help
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Workflows
|
|
132
|
+
|
|
133
|
+
### 1. Create a new agent
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# default name
|
|
137
|
+
bat init agent
|
|
138
|
+
|
|
139
|
+
# custom name
|
|
140
|
+
bat init agent my_agent
|
|
141
|
+
|
|
142
|
+
# specific output directory
|
|
143
|
+
bat init agent my_agent --output-dir .
|
|
144
|
+
|
|
145
|
+
# pre-generate LLM clients
|
|
146
|
+
bat init agent my_agent --clients reformulator,planner,executor
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 2. Add clients to an existing agent
|
|
150
|
+
|
|
151
|
+
Run from the agent root (must contain `src/llm_clients/`):
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
bat add client planner,executor
|
|
155
|
+
|
|
156
|
+
# overwrite existing files
|
|
157
|
+
bat add client planner,executor --force
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 3. Update agent environment variables
|
|
161
|
+
|
|
162
|
+
Run from the agent root (updates or creates `.env`):
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
bat set env --port 8080 --model gpt-4.1-mini --model-provider openai
|
|
166
|
+
|
|
167
|
+
# also set Docker defaults for build/push
|
|
168
|
+
bat set env --docker-registry hub.bubbleran.com --repo orama/labs/my-agent
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 4. Build and push a Docker image
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
bat build --context ./my_agent --docker-registry hub.bubbleran.com --repo orama/labs/my-agent --tag latest
|
|
175
|
+
|
|
176
|
+
# no-cache build with version build-arg
|
|
177
|
+
bat build --context ./my_agent --repo orama/labs/my-agent --tag v1 --version 1.0.0 --no-cache
|
|
178
|
+
|
|
179
|
+
bat push --context ./my_agent --docker-registry hub.bubbleran.com --repo orama/labs/my-agent --tag latest
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
If `BAT_DOCKER_REGISTRY` and `BAT_DOCKER_REPO` are already set in `.env` or the shell, `--docker-registry` and `--repo` can be omitted.
|
|
183
|
+
|
|
184
|
+
**Precedence** (both `--docker-registry` / `--repo`):
|
|
185
|
+
|
|
186
|
+
1. CLI flag
|
|
187
|
+
2. Shell environment variable (`BAT_DOCKER_REGISTRY` / `BAT_DOCKER_REPO`)
|
|
188
|
+
3. `.env` file in the current directory
|
|
189
|
+
4. Hardcoded default (`default_registry` / `default-repository/<project-name>`)
|
|
190
|
+
|
|
191
|
+
### 5. Run evaluation
|
|
192
|
+
|
|
193
|
+
From an existing agent root:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# scaffold evaluation files
|
|
197
|
+
bat eval init
|
|
198
|
+
|
|
199
|
+
# run evaluation
|
|
200
|
+
bat eval run
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`eval init` creates:
|
|
204
|
+
|
|
205
|
+
- `eval/eval.yaml`
|
|
206
|
+
- `eval/input/tasks.json`
|
|
207
|
+
- `eval/output/`
|
|
208
|
+
|
|
209
|
+
Minimal `eval/eval.yaml`:
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
evaluation:
|
|
213
|
+
dataset: eval/input/tasks.json
|
|
214
|
+
output_dir: eval/output
|
|
215
|
+
k: 1
|
|
216
|
+
qualitative: true
|
|
217
|
+
save_attempts: false
|
|
218
|
+
|
|
219
|
+
judge:
|
|
220
|
+
provider: ollama
|
|
221
|
+
model: your-judge-model
|
|
222
|
+
base_url: http://localhost:11434
|
|
223
|
+
|
|
224
|
+
models:
|
|
225
|
+
- provider: openai
|
|
226
|
+
model: your-model-name
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`eval run` requires the agent virtual environment at `.venv/bin/python` (`.venv/Scripts/python.exe` on Windows).
|
|
230
|
+
|
|
231
|
+
For model that requires API_KEYS set it into the agent `.env` under <PROVIDER>\_API_KEY.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
cli.py,sha256=ATKa5kPdme4zS4wiOGlwSd9qRInxb0qqF7fCzXar1sk,8145
|
|
2
|
+
image_defaults.py,sha256=ItpLwN_gl7vVzM8Oyn_kxoAO-WtUFISBUYTHl2ZbheI,1807
|
|
3
|
+
add/__init__.py,sha256=KSwafPATDdeoEYHARrXDC6c4NGVdqfGvuAbFg8RbqWQ,95
|
|
4
|
+
add/client.py,sha256=dGEE7aw1FuALlLxOEGlIK4-EKAqMXYFNp_qtk60C09w,533
|
|
5
|
+
build/__init__.py,sha256=FB47dgFX4B5bacfCeEKA_7b0Bhpe0bgbNd8Zu7572kA,58
|
|
6
|
+
build/build.py,sha256=Zshd-Ymo8DjqnqvS8Fvbz3C4q28Mr0iePP4MdhxY2-U,2665
|
|
7
|
+
create/__init__.py,sha256=KABTrX_9ly8gWVlaYU1m8PeLErUv0WDt5O1JB0Z4a40,78
|
|
8
|
+
create/agent.py,sha256=IHpz8QL3iOBD_BOHBXT8rzC9U7BmfkbM5X9TGvnOlLw,9375
|
|
9
|
+
create/templates/agent/.dockerignore,sha256=TK0TPU8KbPS7oV8bi7rkjWpV8guU91SHxRqTzGpa9Z8,26
|
|
10
|
+
create/templates/agent/.env.template,sha256=7BKZIauBq77d5fMBoK1ePHqByipmk0RHl4D9utaNZTs,126
|
|
11
|
+
create/templates/agent/.python-version,sha256=MzB9pWrxP3kVhFGP7y5JZBGAv78u97LSVsmrb61WT4A,4
|
|
12
|
+
create/templates/agent/Dockerfile,sha256=rNbaEv6AG5UTFpZtmI4KDqYtV7Q_r3UM_zclnlmW3tk,716
|
|
13
|
+
create/templates/agent/Makefile,sha256=CgAhJ2nbkPbjmUv4QIikjPsZDXUmVntIz-bKO_xBaBc,911
|
|
14
|
+
create/templates/agent/README.md,sha256=I1MrCvRD2c7ER4PcoR5m11wNq6VL9CDYyhFgqSf0G44,20
|
|
15
|
+
create/templates/agent/__main__.py,sha256=bXSrkOUtyAeb_mmaDAoFcc1iMOHbGy8CiUWttFu6cnQ,53
|
|
16
|
+
create/templates/agent/agent.json.template,sha256=nprO37SgOJoyFVOZgxHDLPu43HlrI4pjxq6KNi2CqHw,236
|
|
17
|
+
create/templates/agent/agent.spec,sha256=YQU-Qtv4I_1Knp7sZLVvdxy3O0PcxwOt4FtrRJ_CWYg,661
|
|
18
|
+
create/templates/agent/config.yaml,sha256=5PnGziRDshBnIHPn5RiuvA1wa0SRgv1mAHwZpdEg_LQ,19
|
|
19
|
+
create/templates/agent/llm_client.py.template,sha256=fAuIqVv5Fs8wlnyh0ZZsiiQgxsE859Kay82FHVXmiEw,974
|
|
20
|
+
create/templates/agent/pyproject.toml.template,sha256=JUmq3CQhgEAcGv8GRokV5hPKNU2OP5KTLi2zKYyfiRc,196
|
|
21
|
+
create/templates/agent/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
create/templates/agent/src/graph.py,sha256=CYHb2EIBRkT9QA7eV2HHSg3dHJWfBmx-DvR3IKyzJZg,1114
|
|
23
|
+
create/templates/agent/src/llm_clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
create/templates/agent/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
eval/__init__.py,sha256=Mx15-MKFaDtmsc9ubisr-wXH0Hs57YX0QTr7183xz0g,46
|
|
26
|
+
eval/commands.py,sha256=FuVFrWC-rdU90Rame1MaQVXrgLOAr2nyplndeYuUB9U,18361
|
|
27
|
+
eval/engine/__init__.py,sha256=8fEKHuy3Ud4ep4fw762uu51DV1SBj1xlonolmNcQbeY,56
|
|
28
|
+
eval/engine/adapter.py,sha256=yynP98FRNx1LZgJme92cnCu6tE1sSBtd0mVgfoVRVIo,9303
|
|
29
|
+
eval/engine/bench_runner.py,sha256=IhcB_cBTxEL0u05jFvcHB4P7Na_2lzU9lo3kJrX1Aoo,5130
|
|
30
|
+
eval/engine/contracts.py,sha256=DAHpsD2x8x5_zuMQS9nKix8p4e9DltqMque26_7P9ug,3255
|
|
31
|
+
eval/engine/eval_config.py,sha256=9S6LV_Wbk7Sry0nRXP9S8fs7yNMYkKNSoSamfDQM0Wc,9559
|
|
32
|
+
eval/engine/evaluator.py,sha256=XZ2dmZS_VhjGTsISRf3twU3Iz9okDNU_PziCk1qqcE8,2877
|
|
33
|
+
eval/engine/orchestrator.py,sha256=PrCDmEyngI0dciPXS9-qAUcPbqNgt3dTQ-I7CVJdweo,5330
|
|
34
|
+
eval/engine/plotter.py,sha256=xdw1ppMOfI98yz5vEN4tZhQs8E-vnI7ORk-MKfjwiDU,15462
|
|
35
|
+
eval/engine/metrics/__init__.py,sha256=JAVJTRLC2M2Hv41BcImXk_ZYgDb_z3JEHxkepXtce90,48
|
|
36
|
+
eval/engine/metrics/llm_evaluators.py,sha256=Q9qy3usexnjzB8RZW-Mku63s_Ot7Jf1pZsmF_inGLNk,18080
|
|
37
|
+
eval/engine/metrics/metrics.py,sha256=U3IAKQ2H_dvVGQArWS8OwuAcNBhcfVbjMXRydiz5VGc,4877
|
|
38
|
+
eval/engine/metrics/qualitative_helpers.py,sha256=OYNehel5l8BAmbhXH4UxxQMdJC7cumK6kkFNG0HNIIA,2404
|
|
39
|
+
push/__init__.py,sha256=2zzcgnvuK-xfH2ArR4jQHyIjIFUwvyNx2hEPD1zb4f0,55
|
|
40
|
+
push/push.py,sha256=3cqwGL_JJc-cugM7QBTLr9Ud3-fen2TSJy4KHbG8CCs,1953
|
|
41
|
+
set/__init__.py,sha256=e57CrZacOKMkzwII1VdYhxvBWF3Tt02_qw7c0BJ-Dt8,62
|
|
42
|
+
set/env.py,sha256=mAuNfhXyji0ij0H2qdc-aIlPqYSE5lQlxpJ5IKF4UHc,1656
|
|
43
|
+
bat_cli-0.1.0.dist-info/METADATA,sha256=JU4kguzwW8wiF3NAKz3FZIPQ_3WIfMIZzm9Za2CmI60,5266
|
|
44
|
+
bat_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
45
|
+
bat_cli-0.1.0.dist-info/entry_points.txt,sha256=xsln8lLso51-g1XbUBxmAaFCNS1Hxlf611qrMLKYoxs,33
|
|
46
|
+
bat_cli-0.1.0.dist-info/top_level.txt,sha256=JE51f0xnE-1CGdjBjVh7QrmCww9GcHSj7EjzzaoBi8E,50
|
|
47
|
+
bat_cli-0.1.0.dist-info/RECORD,,
|
build/__init__.py
ADDED
build/build.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from image_defaults import resolve_registry, resolve_repo_name
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_image(
|
|
10
|
+
context: Path = typer.Option(
|
|
11
|
+
Path("."),
|
|
12
|
+
"--context",
|
|
13
|
+
"-C",
|
|
14
|
+
help="Directory used as docker build context and to infer the default repository name.",
|
|
15
|
+
),
|
|
16
|
+
docker_registry: str | None = typer.Option(
|
|
17
|
+
None,
|
|
18
|
+
"--docker-registry",
|
|
19
|
+
help=(
|
|
20
|
+
"Docker registry hostname. Precedence: --docker-registry > "
|
|
21
|
+
"BAT_DOCKER_REGISTRY env var (or .env in current directory) > default_registry."
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
repo: str | None = typer.Option(
|
|
25
|
+
None,
|
|
26
|
+
"--repo",
|
|
27
|
+
help=(
|
|
28
|
+
"Image repository path. Precedence: --repo > BAT_DOCKER_REPO env var "
|
|
29
|
+
"(or .env in current directory) > default-repository/<project-name>."
|
|
30
|
+
),
|
|
31
|
+
),
|
|
32
|
+
tag: str = typer.Option(
|
|
33
|
+
"latest",
|
|
34
|
+
"--tag",
|
|
35
|
+
help="Image tag.",
|
|
36
|
+
),
|
|
37
|
+
version: str | None = typer.Option(
|
|
38
|
+
None,
|
|
39
|
+
"--version",
|
|
40
|
+
help="Optional VERSION build arg passed to docker build.",
|
|
41
|
+
),
|
|
42
|
+
no_cache: bool = typer.Option(
|
|
43
|
+
False,
|
|
44
|
+
"--no-cache",
|
|
45
|
+
help="Disable docker layer cache during build.",
|
|
46
|
+
),
|
|
47
|
+
) -> None:
|
|
48
|
+
context_dir = context.resolve()
|
|
49
|
+
if not context_dir.is_dir():
|
|
50
|
+
typer.secho(f"Context directory not found: {context_dir}", fg=typer.colors.RED, err=True)
|
|
51
|
+
raise typer.Exit(code=1)
|
|
52
|
+
|
|
53
|
+
dockerfile_path = context_dir / "Dockerfile"
|
|
54
|
+
if not dockerfile_path.is_file():
|
|
55
|
+
typer.secho(f"Dockerfile not found in context: {dockerfile_path}", fg=typer.colors.RED, err=True)
|
|
56
|
+
raise typer.Exit(code=1)
|
|
57
|
+
|
|
58
|
+
resolved_registry = resolve_registry(context_dir, docker_registry)
|
|
59
|
+
resolved_repo = resolve_repo_name(context_dir, repo)
|
|
60
|
+
image = f"{resolved_registry}/{resolved_repo}:{tag}"
|
|
61
|
+
|
|
62
|
+
command = ["docker", "build"]
|
|
63
|
+
if no_cache:
|
|
64
|
+
command.append("--no-cache")
|
|
65
|
+
if version:
|
|
66
|
+
command.extend(["--build-arg", f"VERSION={version}"])
|
|
67
|
+
command.extend(["--tag", image, "."])
|
|
68
|
+
|
|
69
|
+
typer.echo(f"Building Docker image: {image}")
|
|
70
|
+
try:
|
|
71
|
+
subprocess.run(command, check=True, cwd=context_dir)
|
|
72
|
+
except FileNotFoundError as exc:
|
|
73
|
+
typer.secho("Docker executable not found in PATH.", fg=typer.colors.RED, err=True)
|
|
74
|
+
raise typer.Exit(code=1) from exc
|
|
75
|
+
except subprocess.CalledProcessError as exc:
|
|
76
|
+
typer.secho("Docker build failed.", fg=typer.colors.RED, err=True)
|
|
77
|
+
raise typer.Exit(code=exc.returncode or 1) from exc
|
|
78
|
+
|
|
79
|
+
typer.secho(f"Docker image built successfully: {image}", fg=typer.colors.GREEN)
|
cli.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import typer
|
|
7
|
+
from typer.core import TyperGroup
|
|
8
|
+
|
|
9
|
+
from add.client import add_clients_to_existing_agent
|
|
10
|
+
from build.build import build_image
|
|
11
|
+
from create.agent import create_agent_scaffold
|
|
12
|
+
from eval.commands import eval_init, eval_plot, eval_run, eval_show
|
|
13
|
+
from push.push import push_image
|
|
14
|
+
from set.env import set_env_values
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_BANNER_COLORS = (51, 45, 39, 63, 99, 135)
|
|
18
|
+
|
|
19
|
+
_FALLBACK_BANNER = r"""
|
|
20
|
+
____ _ _____ ____ _ ___
|
|
21
|
+
| __ ) / \|_ _| / ___| | |_ _|
|
|
22
|
+
| _ \ / _ \ | | | | | | | |
|
|
23
|
+
| |_) / ___ \| | | |___| |___ | |
|
|
24
|
+
|____/_/ \_\_| \____|_____|___|
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_BANNER_MOTD = """
|
|
28
|
+
Welcome to BubbleRAN Agentic Toolkit CLI tool.
|
|
29
|
+
|
|
30
|
+
Scaffold, build, push, and evaluate BAT agents from one place.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _figlet_banner(text: str) -> str | None:
|
|
36
|
+
if shutil.which("figlet") is None:
|
|
37
|
+
return None
|
|
38
|
+
try:
|
|
39
|
+
return subprocess.check_output(
|
|
40
|
+
["figlet", "-f", "standard", text],
|
|
41
|
+
text=True,
|
|
42
|
+
stderr=subprocess.DEVNULL,
|
|
43
|
+
)
|
|
44
|
+
except (OSError, subprocess.SubprocessError):
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _colorize_line(line: str) -> str:
|
|
49
|
+
n = len(line)
|
|
50
|
+
if n == 0:
|
|
51
|
+
return ""
|
|
52
|
+
palette = _BANNER_COLORS
|
|
53
|
+
pieces = []
|
|
54
|
+
for i, ch in enumerate(line):
|
|
55
|
+
idx = min(int(i * len(palette) / n), len(palette) - 1)
|
|
56
|
+
pieces.append(f"\033[38;5;{palette[idx]}m{ch}")
|
|
57
|
+
pieces.append("\033[0m")
|
|
58
|
+
return "".join(pieces)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _render_banner() -> str:
|
|
62
|
+
art = _figlet_banner("BAT-CLI") or _FALLBACK_BANNER
|
|
63
|
+
colored = "\n".join(_colorize_line(line) for line in art.splitlines())
|
|
64
|
+
return f"{colored}\n{_BANNER_MOTD}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class BannerGroup(TyperGroup):
|
|
68
|
+
def format_help(self, ctx, formatter):
|
|
69
|
+
click.echo(_render_banner().rstrip("\n") + "\n", nl=False)
|
|
70
|
+
super().format_help(ctx, formatter)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
app = typer.Typer(
|
|
74
|
+
cls=BannerGroup
|
|
75
|
+
)
|
|
76
|
+
init_app = typer.Typer(help="Create new BAT resources.")
|
|
77
|
+
add_app = typer.Typer(help="Add new components to existing BAT agents.")
|
|
78
|
+
set_app = typer.Typer(help="Set configuration values for existing BAT agents.")
|
|
79
|
+
eval_app = typer.Typer(help="Run local evaluation workflows for existing BAT agents.")
|
|
80
|
+
|
|
81
|
+
app.add_typer(init_app, name="init")
|
|
82
|
+
app.add_typer(add_app, name="add")
|
|
83
|
+
app.add_typer(set_app, name="set")
|
|
84
|
+
app.add_typer(eval_app, name="eval")
|
|
85
|
+
app.command("build", help="Build the Docker image for the agent.")(build_image)
|
|
86
|
+
app.command("push", help="Push the Docker image to a registry.")(push_image)
|
|
87
|
+
eval_app.command("init", help="Initialize local evaluation scaffold.")(eval_init)
|
|
88
|
+
eval_app.command("run", help="Run evaluation using eval/eval.yaml.")(eval_run)
|
|
89
|
+
eval_app.command("show", help="Show the resolved evaluation configuration.")(eval_show)
|
|
90
|
+
eval_app.command("plot", help="Generate metric charts from an evaluation output folder.")(eval_plot)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _parse_clients_option(raw_clients: str | None) -> list[str] | None:
|
|
94
|
+
if raw_clients is None:
|
|
95
|
+
return None
|
|
96
|
+
parsed_clients = [client.strip() for client in raw_clients.split(",") if client.strip()]
|
|
97
|
+
if not parsed_clients:
|
|
98
|
+
raise typer.BadParameter("Provide at least one client name, for example: reformulator,planner,executor")
|
|
99
|
+
return parsed_clients
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@init_app.command("agent")
|
|
103
|
+
def create_new_agent(
|
|
104
|
+
name: str = typer.Argument("default", help="Name of the agent directory to create."),
|
|
105
|
+
clients: str | None = typer.Option(
|
|
106
|
+
None,
|
|
107
|
+
"--clients",
|
|
108
|
+
"-c",
|
|
109
|
+
help="Optional comma-separated LLM client names to generate, for example: reformulator,planner,executor",
|
|
110
|
+
),
|
|
111
|
+
output_dir: Path = typer.Option(
|
|
112
|
+
Path("."),
|
|
113
|
+
"--output-dir",
|
|
114
|
+
"-o",
|
|
115
|
+
help="Directory where the agent folder will be created.",
|
|
116
|
+
),
|
|
117
|
+
force: bool = typer.Option(
|
|
118
|
+
False,
|
|
119
|
+
"--force",
|
|
120
|
+
"-f",
|
|
121
|
+
help="Overwrite existing files when the target directory exists. Use with caution as this will delete existing files in the target directory.",
|
|
122
|
+
),
|
|
123
|
+
port: int = typer.Option(
|
|
124
|
+
9900,
|
|
125
|
+
"--port",
|
|
126
|
+
help="Port value written to .env.",
|
|
127
|
+
),
|
|
128
|
+
model: str = typer.Option(
|
|
129
|
+
"gpt-4o-mini",
|
|
130
|
+
"--model",
|
|
131
|
+
help="Model value written to .env.",
|
|
132
|
+
),
|
|
133
|
+
model_provider: str = typer.Option(
|
|
134
|
+
"openai",
|
|
135
|
+
"--model-provider",
|
|
136
|
+
"--model_provider",
|
|
137
|
+
help="Model provider value written to .env.",
|
|
138
|
+
),
|
|
139
|
+
) -> None:
|
|
140
|
+
target_dir = output_dir / name
|
|
141
|
+
parsed_clients = _parse_clients_option(clients)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
created_files = create_agent_scaffold(
|
|
145
|
+
target_dir,
|
|
146
|
+
force=force,
|
|
147
|
+
clients=parsed_clients,
|
|
148
|
+
port=port,
|
|
149
|
+
model=model,
|
|
150
|
+
model_provider=model_provider,
|
|
151
|
+
)
|
|
152
|
+
except FileExistsError as exc:
|
|
153
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
154
|
+
raise typer.Exit(code=1) from exc
|
|
155
|
+
|
|
156
|
+
typer.secho(f"Created BAT agent skeleton in: {target_dir.resolve()}", fg=typer.colors.GREEN)
|
|
157
|
+
typer.echo(f"Files written: {len(created_files)}")
|
|
158
|
+
|
|
159
|
+
@add_app.command("client")
|
|
160
|
+
def add_new_client(
|
|
161
|
+
clients: str = typer.Argument(
|
|
162
|
+
...,
|
|
163
|
+
help="Comma-separated LLM client names to generate, for example: reformulator,planner,executor",
|
|
164
|
+
),
|
|
165
|
+
force: bool = typer.Option(
|
|
166
|
+
False,
|
|
167
|
+
"--force",
|
|
168
|
+
"-f",
|
|
169
|
+
help="Overwrite existing client files if they already exist. Use with caution as this will delete existing client files with the same name.",
|
|
170
|
+
),
|
|
171
|
+
) -> None:
|
|
172
|
+
current_dir = Path.cwd()
|
|
173
|
+
llm_clients_dir = current_dir / "src" / "llm_clients"
|
|
174
|
+
if not llm_clients_dir.is_dir():
|
|
175
|
+
typer.secho(
|
|
176
|
+
"Current directory must contain src/llm_clients. Run this command from the root of an existing agent.",
|
|
177
|
+
fg=typer.colors.RED,
|
|
178
|
+
err=True,
|
|
179
|
+
)
|
|
180
|
+
raise typer.Exit(code=1)
|
|
181
|
+
|
|
182
|
+
parsed_clients = _parse_clients_option(clients) or []
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
created_files = add_clients_to_existing_agent(current_dir, clients=parsed_clients, force=force)
|
|
186
|
+
except FileNotFoundError as exc:
|
|
187
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
188
|
+
raise typer.Exit(code=1) from exc
|
|
189
|
+
|
|
190
|
+
typer.secho(f"Updated LLM clients in: {llm_clients_dir.resolve()}", fg=typer.colors.GREEN)
|
|
191
|
+
typer.echo(f"Files written: {len(created_files)}")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@set_app.command("env")
|
|
195
|
+
def set_agent_env(
|
|
196
|
+
port: int | None = typer.Option(
|
|
197
|
+
None,
|
|
198
|
+
"--port",
|
|
199
|
+
help="Set PORT in .env.",
|
|
200
|
+
),
|
|
201
|
+
model: str | None = typer.Option(
|
|
202
|
+
None,
|
|
203
|
+
"--model",
|
|
204
|
+
help="Set MODEL in .env.",
|
|
205
|
+
),
|
|
206
|
+
model_provider: str | None = typer.Option(
|
|
207
|
+
None,
|
|
208
|
+
"--model-provider",
|
|
209
|
+
"--model_provider",
|
|
210
|
+
help="Set MODEL_PROVIDER in .env.",
|
|
211
|
+
),
|
|
212
|
+
docker_registry: str | None = typer.Option(
|
|
213
|
+
None,
|
|
214
|
+
"--docker-registry",
|
|
215
|
+
help="Set BAT_DOCKER_REGISTRY in .env for build/push defaults.",
|
|
216
|
+
),
|
|
217
|
+
repo: str | None = typer.Option(
|
|
218
|
+
None,
|
|
219
|
+
"--repo",
|
|
220
|
+
help="Set BAT_DOCKER_REPO in .env for build/push defaults.",
|
|
221
|
+
),
|
|
222
|
+
) -> None:
|
|
223
|
+
current_dir = Path.cwd()
|
|
224
|
+
env = current_dir / ".env"
|
|
225
|
+
if not env.is_file():
|
|
226
|
+
typer.secho(
|
|
227
|
+
"Current directory must contain .env. Run this command from the root of an existing agent.",
|
|
228
|
+
fg=typer.colors.RED,
|
|
229
|
+
err=True,
|
|
230
|
+
)
|
|
231
|
+
raise typer.Exit(code=1)
|
|
232
|
+
|
|
233
|
+
if all(value is None for value in [port, model, model_provider, docker_registry, repo]):
|
|
234
|
+
typer.secho(
|
|
235
|
+
"Provide at least one option to set: --port, --model, --model-provider, --docker-registry, --repo",
|
|
236
|
+
fg=typer.colors.RED,
|
|
237
|
+
err=True,
|
|
238
|
+
)
|
|
239
|
+
raise typer.Exit(code=1)
|
|
240
|
+
|
|
241
|
+
env_path, updated_keys = set_env_values(
|
|
242
|
+
current_dir,
|
|
243
|
+
port=port,
|
|
244
|
+
model=model,
|
|
245
|
+
model_provider=model_provider,
|
|
246
|
+
docker_registry=docker_registry,
|
|
247
|
+
repo=repo,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
typer.secho(f"Updated env file: {env_path.resolve()}", fg=typer.colors.GREEN)
|
|
251
|
+
typer.echo(f"Keys updated: {', '.join(updated_keys)}")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def main() -> None:
|
|
256
|
+
app()
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
main()
|
create/__init__.py
ADDED