nabla-cli 0.1.0__tar.gz → 0.2.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.
- nabla_cli-0.2.0/PKG-INFO +236 -0
- nabla_cli-0.2.0/README.md +223 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/__init__.py +1 -1
- nabla_cli-0.2.0/nabla/__main__.py +8 -0
- nabla_cli-0.2.0/nabla/cli.py +439 -0
- nabla_cli-0.2.0/nabla/config.py +113 -0
- nabla_cli-0.2.0/nabla/samplegen.py +118 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/schemas/site_profile.schema.json +1 -1
- nabla_cli-0.2.0/nabla_cli.egg-info/PKG-INFO +236 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla_cli.egg-info/SOURCES.txt +6 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/pyproject.toml +1 -1
- nabla_cli-0.2.0/tests/test_cli_ux.py +332 -0
- nabla_cli-0.2.0/tests/test_config.py +179 -0
- nabla_cli-0.2.0/tests/test_samplegen.py +81 -0
- nabla_cli-0.1.0/PKG-INFO +0 -82
- nabla_cli-0.1.0/README.md +0 -69
- nabla_cli-0.1.0/nabla/cli.py +0 -121
- nabla_cli-0.1.0/nabla_cli.egg-info/PKG-INFO +0 -82
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/contract.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/induce.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/profile.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/prompt_recon.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/recorder.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/scanner.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla/schema_resolver.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla_cli.egg-info/dependency_links.txt +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla_cli.egg-info/entry_points.txt +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla_cli.egg-info/requires.txt +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/nabla_cli.egg-info/top_level.txt +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/setup.cfg +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/tests/test_e2e_profile.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/tests/test_induce.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/tests/test_prompt_recon.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/tests/test_recorder.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/tests/test_scanner.py +0 -0
- {nabla_cli-0.1.0 → nabla_cli-0.2.0}/tests/test_schema_resolver.py +0 -0
nabla_cli-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nabla-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Call-site harvester: scan OpenAI call sites, record real traffic, emit site profiles
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: jsonschema
|
|
9
|
+
Requires-Dist: pydantic>=2
|
|
10
|
+
Requires-Dist: openai>=1.40
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# nabla — OpenAI call-site harvester
|
|
15
|
+
|
|
16
|
+
`nabla` finds the OpenAI call sites in a Python repo, records real
|
|
17
|
+
(prompt, completion) traffic for one of them, and emits a single
|
|
18
|
+
self-contained **site profile** (`site_profile.json`) describing that
|
|
19
|
+
call site: prompt template + slots, resolved output JSON Schema,
|
|
20
|
+
sampling parameters, recorded examples, an inferred goal with difficulty
|
|
21
|
+
dimensions, and a cost/latency baseline.
|
|
22
|
+
|
|
23
|
+
The profile is the handoff artifact for training a small, cheap
|
|
24
|
+
replacement model for that one call site — everything downstream codes
|
|
25
|
+
against the profile, never against your repo.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install nabla-cli
|
|
31
|
+
# or, isolated:
|
|
32
|
+
pipx install nabla-cli
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python ≥ 3.11.
|
|
36
|
+
|
|
37
|
+
### `nabla` not found after `pip install`?
|
|
38
|
+
|
|
39
|
+
That means pip's script directory isn't on your PATH (pip prints a
|
|
40
|
+
yellow warning about this during install). Two fixes, either works:
|
|
41
|
+
|
|
42
|
+
- Run it as a module instead — always works, no PATH needed:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -m nabla scan path/to/repo
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- Or add pip's script directory to PATH. Find it with
|
|
49
|
+
`python -c "import sysconfig; print(sysconfig.get_path('scripts'))"`
|
|
50
|
+
and add that folder to your PATH (on Windows: Settings → search
|
|
51
|
+
"environment variables" → edit `Path` → add the folder, then open a
|
|
52
|
+
new terminal).
|
|
53
|
+
|
|
54
|
+
`pipx install nabla-cli` avoids the problem entirely if you have pipx.
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
Run from inside the target repo:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install nabla-cli
|
|
62
|
+
|
|
63
|
+
nabla init # one-time setup, once per machine
|
|
64
|
+
nabla scan
|
|
65
|
+
nabla record
|
|
66
|
+
nabla profile
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
No flags needed on the happy path: the repo defaults to the current
|
|
70
|
+
directory, `--site` is auto-picked when the repo has exactly one
|
|
71
|
+
supported call site (with more than one, pass `--site <symbol>` — the
|
|
72
|
+
names come from `scan`'s output), `record` writes
|
|
73
|
+
`<site>.recorded.jsonl`, and `profile` finds that file automatically by
|
|
74
|
+
the same convention.
|
|
75
|
+
|
|
76
|
+
If you don't pass `--samples`, `record` looks for
|
|
77
|
+
`<site>.samples.jsonl` and otherwise generates a handful of synthetic
|
|
78
|
+
inputs from the site's prompt template (saving them to that file so you
|
|
79
|
+
can edit them and rerun with realistic data). Profiles built on
|
|
80
|
+
generated inputs are marked `generated_samples` in their provenance —
|
|
81
|
+
fine for a first look, but prefer real sample data for a profile you
|
|
82
|
+
intend to hand off.
|
|
83
|
+
|
|
84
|
+
### `nabla init`
|
|
85
|
+
|
|
86
|
+
Interactive, run once per machine:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
$ nabla init
|
|
90
|
+
Pick a provider:
|
|
91
|
+
1) openai
|
|
92
|
+
2) groq
|
|
93
|
+
3) gemini
|
|
94
|
+
> 2
|
|
95
|
+
Paste your GROQ_API_KEY: ****************
|
|
96
|
+
Saved to ~/.nabla/config.json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
That's the only credential nabla needs. Base URLs, models, and request
|
|
100
|
+
pacing all come from a built-in preset for whichever provider you picked
|
|
101
|
+
— see [Providers](#providers).
|
|
102
|
+
|
|
103
|
+
### `nabla scan`
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
SYMBOL FILE KIND VERIFIABILITY FLAGS
|
|
107
|
+
extract_invoice app/invoice_extract.py create json_schema -
|
|
108
|
+
|
|
109
|
+
1 call site found.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `nabla record`
|
|
113
|
+
|
|
114
|
+
One progress line per sample, then a summary:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
nabla record: sample 1/6 -> 1 capture(s)
|
|
118
|
+
nabla record: sample 2/6 -> 1 capture(s)
|
|
119
|
+
...
|
|
120
|
+
nabla record: 6 examples -> extract_invoice.recorded.jsonl
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`--out` defaults to `<site>.recorded.jsonl` in the current directory (so
|
|
124
|
+
`profile` can find it by the same convention afterward) — pass `--out`
|
|
125
|
+
yourself only to pick a different name or location.
|
|
126
|
+
|
|
127
|
+
To record your own inputs instead of generated ones, pass `--samples`
|
|
128
|
+
(or create `<site>.samples.jsonl`, which is picked up automatically): a
|
|
129
|
+
JSONL file, one line per call to record. Each line is
|
|
130
|
+
`{"input_slots": {...}}`, where the keys match the site function's
|
|
131
|
+
keyword arguments (as reported by `scan`):
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{"input_slots": {"document_text": "Invoice INV-1001 total $250.00 due 2026-08-01", "locale_hint": "en-US"}}
|
|
135
|
+
{"input_slots": {"document_text": "Facture FR-552 montant 99,50 EUR", "locale_hint": "fr-FR"}}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
nabla runs the site's function once per line, in a subprocess, with its
|
|
139
|
+
OpenAI client rerouted through a local recording proxy.
|
|
140
|
+
|
|
141
|
+
### `nabla profile`
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
nabla profile: goal — "Extract structured invoice fields (total, currency,
|
|
145
|
+
due date) from free-form invoice or receipt text, normalizing
|
|
146
|
+
locale-specific number and date formats."
|
|
147
|
+
nabla profile: wrote extract_invoice.profile.json (site 4f2a9b1c3d8e…, supported=True, 6 examples)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`--out` defaults to `<site>.profile.json` in the current directory.
|
|
151
|
+
`--examples` is optional: `profile` looks for `<site>.recorded.jsonl` in
|
|
152
|
+
the current directory — the file `record` just wrote — and you only need
|
|
153
|
+
`--examples <path>` to point it at a different recording. The one LLM
|
|
154
|
+
call in this step is goal inference: it reads the reconstructed prompt
|
|
155
|
+
template, the resolved output schema, and the recorded examples, and
|
|
156
|
+
returns a goal description, implicit constraints, and difficulty
|
|
157
|
+
dimensions with cited evidence. That's the goal line printed above, and
|
|
158
|
+
it's what lands in the profile's `goal` block.
|
|
159
|
+
|
|
160
|
+
## Providers
|
|
161
|
+
|
|
162
|
+
`nabla init` supports three providers. Pick whichever you have a key
|
|
163
|
+
for — it doesn't need to be the provider your target repo already calls.
|
|
164
|
+
|
|
165
|
+
| Provider | Default model | Role |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `openai` | your repo's own model (goal inference uses `gpt-4o-mini`) | Real upstream — `record` calls OpenAI directly with your key, auth passthrough. |
|
|
168
|
+
| `groq` | `openai/gpt-oss-120b` | Stand-in oracle — `record` calls Groq instead of OpenAI. |
|
|
169
|
+
| `gemini` | `gemini-3.5-flash` | Stand-in oracle — `record` calls Gemini instead of OpenAI. |
|
|
170
|
+
|
|
171
|
+
When you pick `groq` or `gemini`, they only stand in for *generating* the
|
|
172
|
+
completions during `record`. The profile's cost/latency baseline always
|
|
173
|
+
reports numbers for the target repo's own model — the one your code
|
|
174
|
+
actually calls in production — never the stand-in's.
|
|
175
|
+
|
|
176
|
+
Gemini's free tier has a very low daily quota: expect `record` to pace
|
|
177
|
+
requests with multi-second delays and to still hit rate limits past a
|
|
178
|
+
handful of samples. `groq` or `openai` handle larger sample sets more
|
|
179
|
+
comfortably.
|
|
180
|
+
|
|
181
|
+
## What gets detected
|
|
182
|
+
|
|
183
|
+
`scan` classifies every `chat.completions.create` / `beta...parse` call
|
|
184
|
+
by verifiability: `json_schema` and `pydantic_parse` sites are supported
|
|
185
|
+
harvest targets; `tool_call`, `json_object_no_schema`, and `free_text`
|
|
186
|
+
sites are detected and reported but not harvested. Streaming,
|
|
187
|
+
multi-turn, vision, and `n>1` sites are flagged unsupported. Generic
|
|
188
|
+
wrapper functions are expanded to their callers; functions that own
|
|
189
|
+
their prompt template are kept as the site.
|
|
190
|
+
|
|
191
|
+
## Advanced: overrides
|
|
192
|
+
|
|
193
|
+
### Flags
|
|
194
|
+
|
|
195
|
+
Auto-detection is a default, not a requirement — the explicit flags still
|
|
196
|
+
work and take priority over it:
|
|
197
|
+
|
|
198
|
+
- `--site <symbol>` — skip site auto-pick; required once a repo has more
|
|
199
|
+
than one supported call site.
|
|
200
|
+
- `--out <path>` (`record`) — write recorded examples somewhere other than
|
|
201
|
+
`<site>.recorded.jsonl`.
|
|
202
|
+
- `--examples <path>` (`profile`) — read recorded examples from somewhere
|
|
203
|
+
other than `<site>.recorded.jsonl`.
|
|
204
|
+
- `--out <path>` (`profile`) — write the profile somewhere other than
|
|
205
|
+
`<site>.profile.json`.
|
|
206
|
+
|
|
207
|
+
### Environment variables
|
|
208
|
+
|
|
209
|
+
Everything `nabla init` sets up can also be overridden by hand. Precedence,
|
|
210
|
+
highest first:
|
|
211
|
+
|
|
212
|
+
1. An env var you set yourself
|
|
213
|
+
2. `~/.nabla/config.json` (written by `nabla init`)
|
|
214
|
+
3. The provider preset's built-in defaults
|
|
215
|
+
|
|
216
|
+
| Variable | Purpose |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `OPENAI_API_KEY` | Default upstream for `record` (auth passthrough). |
|
|
219
|
+
| `GROQ_API_KEY` / `GEMINI_API_KEY` | That provider's conventional key env, read if `~/.nabla/config.json` has no stored key. |
|
|
220
|
+
| `NABLA_PROVIDER` | Overrides the provider `nabla init` selected. |
|
|
221
|
+
| `NABLA_UPSTREAM_BASE_URL` | Record through any OpenAI-compatible stand-in oracle instead. |
|
|
222
|
+
| `NABLA_UPSTREAM_API_KEY` | Key for the stand-in oracle. |
|
|
223
|
+
| `NABLA_UPSTREAM_MODEL` | Rewrite the model for the stand-in only (the target repo's own model still lands in the profile). |
|
|
224
|
+
| `NABLA_RECORD_DELAY_MS` | Pacing between samples for rate-limited upstreams (default 0). |
|
|
225
|
+
| `NABLA_SAMPLE_TIMEOUT` | Per-sample subprocess timeout in seconds (default 300). |
|
|
226
|
+
| `NABLA_INDUCE_BASE_URL` / `NABLA_INDUCE_API_KEY_ENV` / `NABLA_INDUCE_MODEL` | Swap the goal-inference provider/model. |
|
|
227
|
+
|
|
228
|
+
## Notes
|
|
229
|
+
|
|
230
|
+
- `record` runs the site's enclosing function in a subprocess per sample,
|
|
231
|
+
with `OPENAI_BASE_URL` pointed at a local recording proxy. If the
|
|
232
|
+
target's tests mock the SDK, the proxy path reports zero traffic
|
|
233
|
+
explicitly instead of writing an empty profile.
|
|
234
|
+
- Recorded prompts/completions are real data from your repo and leave
|
|
235
|
+
the machine only via the goal-inference call. Review before sharing
|
|
236
|
+
profiles.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# nabla — OpenAI call-site harvester
|
|
2
|
+
|
|
3
|
+
`nabla` finds the OpenAI call sites in a Python repo, records real
|
|
4
|
+
(prompt, completion) traffic for one of them, and emits a single
|
|
5
|
+
self-contained **site profile** (`site_profile.json`) describing that
|
|
6
|
+
call site: prompt template + slots, resolved output JSON Schema,
|
|
7
|
+
sampling parameters, recorded examples, an inferred goal with difficulty
|
|
8
|
+
dimensions, and a cost/latency baseline.
|
|
9
|
+
|
|
10
|
+
The profile is the handoff artifact for training a small, cheap
|
|
11
|
+
replacement model for that one call site — everything downstream codes
|
|
12
|
+
against the profile, never against your repo.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install nabla-cli
|
|
18
|
+
# or, isolated:
|
|
19
|
+
pipx install nabla-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Requires Python ≥ 3.11.
|
|
23
|
+
|
|
24
|
+
### `nabla` not found after `pip install`?
|
|
25
|
+
|
|
26
|
+
That means pip's script directory isn't on your PATH (pip prints a
|
|
27
|
+
yellow warning about this during install). Two fixes, either works:
|
|
28
|
+
|
|
29
|
+
- Run it as a module instead — always works, no PATH needed:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python -m nabla scan path/to/repo
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- Or add pip's script directory to PATH. Find it with
|
|
36
|
+
`python -c "import sysconfig; print(sysconfig.get_path('scripts'))"`
|
|
37
|
+
and add that folder to your PATH (on Windows: Settings → search
|
|
38
|
+
"environment variables" → edit `Path` → add the folder, then open a
|
|
39
|
+
new terminal).
|
|
40
|
+
|
|
41
|
+
`pipx install nabla-cli` avoids the problem entirely if you have pipx.
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
Run from inside the target repo:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install nabla-cli
|
|
49
|
+
|
|
50
|
+
nabla init # one-time setup, once per machine
|
|
51
|
+
nabla scan
|
|
52
|
+
nabla record
|
|
53
|
+
nabla profile
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
No flags needed on the happy path: the repo defaults to the current
|
|
57
|
+
directory, `--site` is auto-picked when the repo has exactly one
|
|
58
|
+
supported call site (with more than one, pass `--site <symbol>` — the
|
|
59
|
+
names come from `scan`'s output), `record` writes
|
|
60
|
+
`<site>.recorded.jsonl`, and `profile` finds that file automatically by
|
|
61
|
+
the same convention.
|
|
62
|
+
|
|
63
|
+
If you don't pass `--samples`, `record` looks for
|
|
64
|
+
`<site>.samples.jsonl` and otherwise generates a handful of synthetic
|
|
65
|
+
inputs from the site's prompt template (saving them to that file so you
|
|
66
|
+
can edit them and rerun with realistic data). Profiles built on
|
|
67
|
+
generated inputs are marked `generated_samples` in their provenance —
|
|
68
|
+
fine for a first look, but prefer real sample data for a profile you
|
|
69
|
+
intend to hand off.
|
|
70
|
+
|
|
71
|
+
### `nabla init`
|
|
72
|
+
|
|
73
|
+
Interactive, run once per machine:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
$ nabla init
|
|
77
|
+
Pick a provider:
|
|
78
|
+
1) openai
|
|
79
|
+
2) groq
|
|
80
|
+
3) gemini
|
|
81
|
+
> 2
|
|
82
|
+
Paste your GROQ_API_KEY: ****************
|
|
83
|
+
Saved to ~/.nabla/config.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
That's the only credential nabla needs. Base URLs, models, and request
|
|
87
|
+
pacing all come from a built-in preset for whichever provider you picked
|
|
88
|
+
— see [Providers](#providers).
|
|
89
|
+
|
|
90
|
+
### `nabla scan`
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
SYMBOL FILE KIND VERIFIABILITY FLAGS
|
|
94
|
+
extract_invoice app/invoice_extract.py create json_schema -
|
|
95
|
+
|
|
96
|
+
1 call site found.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `nabla record`
|
|
100
|
+
|
|
101
|
+
One progress line per sample, then a summary:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
nabla record: sample 1/6 -> 1 capture(s)
|
|
105
|
+
nabla record: sample 2/6 -> 1 capture(s)
|
|
106
|
+
...
|
|
107
|
+
nabla record: 6 examples -> extract_invoice.recorded.jsonl
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`--out` defaults to `<site>.recorded.jsonl` in the current directory (so
|
|
111
|
+
`profile` can find it by the same convention afterward) — pass `--out`
|
|
112
|
+
yourself only to pick a different name or location.
|
|
113
|
+
|
|
114
|
+
To record your own inputs instead of generated ones, pass `--samples`
|
|
115
|
+
(or create `<site>.samples.jsonl`, which is picked up automatically): a
|
|
116
|
+
JSONL file, one line per call to record. Each line is
|
|
117
|
+
`{"input_slots": {...}}`, where the keys match the site function's
|
|
118
|
+
keyword arguments (as reported by `scan`):
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{"input_slots": {"document_text": "Invoice INV-1001 total $250.00 due 2026-08-01", "locale_hint": "en-US"}}
|
|
122
|
+
{"input_slots": {"document_text": "Facture FR-552 montant 99,50 EUR", "locale_hint": "fr-FR"}}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
nabla runs the site's function once per line, in a subprocess, with its
|
|
126
|
+
OpenAI client rerouted through a local recording proxy.
|
|
127
|
+
|
|
128
|
+
### `nabla profile`
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
nabla profile: goal — "Extract structured invoice fields (total, currency,
|
|
132
|
+
due date) from free-form invoice or receipt text, normalizing
|
|
133
|
+
locale-specific number and date formats."
|
|
134
|
+
nabla profile: wrote extract_invoice.profile.json (site 4f2a9b1c3d8e…, supported=True, 6 examples)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`--out` defaults to `<site>.profile.json` in the current directory.
|
|
138
|
+
`--examples` is optional: `profile` looks for `<site>.recorded.jsonl` in
|
|
139
|
+
the current directory — the file `record` just wrote — and you only need
|
|
140
|
+
`--examples <path>` to point it at a different recording. The one LLM
|
|
141
|
+
call in this step is goal inference: it reads the reconstructed prompt
|
|
142
|
+
template, the resolved output schema, and the recorded examples, and
|
|
143
|
+
returns a goal description, implicit constraints, and difficulty
|
|
144
|
+
dimensions with cited evidence. That's the goal line printed above, and
|
|
145
|
+
it's what lands in the profile's `goal` block.
|
|
146
|
+
|
|
147
|
+
## Providers
|
|
148
|
+
|
|
149
|
+
`nabla init` supports three providers. Pick whichever you have a key
|
|
150
|
+
for — it doesn't need to be the provider your target repo already calls.
|
|
151
|
+
|
|
152
|
+
| Provider | Default model | Role |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `openai` | your repo's own model (goal inference uses `gpt-4o-mini`) | Real upstream — `record` calls OpenAI directly with your key, auth passthrough. |
|
|
155
|
+
| `groq` | `openai/gpt-oss-120b` | Stand-in oracle — `record` calls Groq instead of OpenAI. |
|
|
156
|
+
| `gemini` | `gemini-3.5-flash` | Stand-in oracle — `record` calls Gemini instead of OpenAI. |
|
|
157
|
+
|
|
158
|
+
When you pick `groq` or `gemini`, they only stand in for *generating* the
|
|
159
|
+
completions during `record`. The profile's cost/latency baseline always
|
|
160
|
+
reports numbers for the target repo's own model — the one your code
|
|
161
|
+
actually calls in production — never the stand-in's.
|
|
162
|
+
|
|
163
|
+
Gemini's free tier has a very low daily quota: expect `record` to pace
|
|
164
|
+
requests with multi-second delays and to still hit rate limits past a
|
|
165
|
+
handful of samples. `groq` or `openai` handle larger sample sets more
|
|
166
|
+
comfortably.
|
|
167
|
+
|
|
168
|
+
## What gets detected
|
|
169
|
+
|
|
170
|
+
`scan` classifies every `chat.completions.create` / `beta...parse` call
|
|
171
|
+
by verifiability: `json_schema` and `pydantic_parse` sites are supported
|
|
172
|
+
harvest targets; `tool_call`, `json_object_no_schema`, and `free_text`
|
|
173
|
+
sites are detected and reported but not harvested. Streaming,
|
|
174
|
+
multi-turn, vision, and `n>1` sites are flagged unsupported. Generic
|
|
175
|
+
wrapper functions are expanded to their callers; functions that own
|
|
176
|
+
their prompt template are kept as the site.
|
|
177
|
+
|
|
178
|
+
## Advanced: overrides
|
|
179
|
+
|
|
180
|
+
### Flags
|
|
181
|
+
|
|
182
|
+
Auto-detection is a default, not a requirement — the explicit flags still
|
|
183
|
+
work and take priority over it:
|
|
184
|
+
|
|
185
|
+
- `--site <symbol>` — skip site auto-pick; required once a repo has more
|
|
186
|
+
than one supported call site.
|
|
187
|
+
- `--out <path>` (`record`) — write recorded examples somewhere other than
|
|
188
|
+
`<site>.recorded.jsonl`.
|
|
189
|
+
- `--examples <path>` (`profile`) — read recorded examples from somewhere
|
|
190
|
+
other than `<site>.recorded.jsonl`.
|
|
191
|
+
- `--out <path>` (`profile`) — write the profile somewhere other than
|
|
192
|
+
`<site>.profile.json`.
|
|
193
|
+
|
|
194
|
+
### Environment variables
|
|
195
|
+
|
|
196
|
+
Everything `nabla init` sets up can also be overridden by hand. Precedence,
|
|
197
|
+
highest first:
|
|
198
|
+
|
|
199
|
+
1. An env var you set yourself
|
|
200
|
+
2. `~/.nabla/config.json` (written by `nabla init`)
|
|
201
|
+
3. The provider preset's built-in defaults
|
|
202
|
+
|
|
203
|
+
| Variable | Purpose |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `OPENAI_API_KEY` | Default upstream for `record` (auth passthrough). |
|
|
206
|
+
| `GROQ_API_KEY` / `GEMINI_API_KEY` | That provider's conventional key env, read if `~/.nabla/config.json` has no stored key. |
|
|
207
|
+
| `NABLA_PROVIDER` | Overrides the provider `nabla init` selected. |
|
|
208
|
+
| `NABLA_UPSTREAM_BASE_URL` | Record through any OpenAI-compatible stand-in oracle instead. |
|
|
209
|
+
| `NABLA_UPSTREAM_API_KEY` | Key for the stand-in oracle. |
|
|
210
|
+
| `NABLA_UPSTREAM_MODEL` | Rewrite the model for the stand-in only (the target repo's own model still lands in the profile). |
|
|
211
|
+
| `NABLA_RECORD_DELAY_MS` | Pacing between samples for rate-limited upstreams (default 0). |
|
|
212
|
+
| `NABLA_SAMPLE_TIMEOUT` | Per-sample subprocess timeout in seconds (default 300). |
|
|
213
|
+
| `NABLA_INDUCE_BASE_URL` / `NABLA_INDUCE_API_KEY_ENV` / `NABLA_INDUCE_MODEL` | Swap the goal-inference provider/model. |
|
|
214
|
+
|
|
215
|
+
## Notes
|
|
216
|
+
|
|
217
|
+
- `record` runs the site's enclosing function in a subprocess per sample,
|
|
218
|
+
with `OPENAI_BASE_URL` pointed at a local recording proxy. If the
|
|
219
|
+
target's tests mock the SDK, the proxy path reports zero traffic
|
|
220
|
+
explicitly instead of writing an empty profile.
|
|
221
|
+
- Recorded prompts/completions are real data from your repo and leave
|
|
222
|
+
the machine only via the goal-inference call. Review before sharing
|
|
223
|
+
profiles.
|