Metadata-Version: 2.4
Name: nabla-cli
Version: 0.1.1
Summary: Call-site harvester: scan OpenAI call sites, record real traffic, emit site profiles
License: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: jsonschema
Requires-Dist: pydantic>=2
Requires-Dist: openai>=1.40
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"

# nabla — OpenAI call-site harvester

`nabla` finds the OpenAI call sites in a Python repo, records real
(prompt, completion) traffic for one of them, and emits a single
self-contained **site profile** (`site_profile.json`) describing that
call site: prompt template + slots, resolved output JSON Schema,
sampling parameters, recorded examples, an inferred goal with difficulty
dimensions, and a cost/latency baseline.

The profile is the handoff artifact for training a small, cheap
replacement model for that one call site — everything downstream codes
against the profile, never against your repo.

## Install

```bash
pip install nabla-cli
# or, isolated:
pipx install nabla-cli
```

Requires Python ≥ 3.11.

### `nabla` not found after `pip install`?

That means pip's script directory isn't on your PATH (pip prints a
yellow warning about this during install). Two fixes, either works:

- Run it as a module instead — always works, no PATH needed:

  ```bash
  python -m nabla scan path/to/repo
  ```

- Or add pip's script directory to PATH. Find it with
  `python -c "import sysconfig; print(sysconfig.get_path('scripts'))"`
  and add that folder to your PATH (on Windows: Settings → search
  "environment variables" → edit `Path` → add the folder, then open a
  new terminal).

`pipx install nabla-cli` avoids the problem entirely if you have pipx.

## Quickstart

```bash
# 1. Find call sites (pure static analysis — no network, no API key)
nabla scan path/to/repo

# 2. Record real traffic for one site, driven by sample inputs
#    (JSONL lines of {"input_slots": {...}} matching the site's function kwargs)
nabla record path/to/repo --site my_function --samples samples.jsonl --out recorded.jsonl

# 3. Assemble + validate the profile (one LLM call for goal inference)
nabla profile path/to/repo --site my_function --examples recorded.jsonl --out site_profile.json
```

## What gets detected

`scan` classifies every `chat.completions.create` / `beta...parse` call
by verifiability: `json_schema` and `pydantic_parse` sites are supported
harvest targets; `tool_call`, `json_object_no_schema`, and `free_text`
sites are detected and reported but not harvested. Streaming,
multi-turn, vision, and `n>1` sites are flagged unsupported. Generic
wrapper functions are expanded to their callers; functions that own
their prompt template are kept as the site.

## Environment variables

| Variable | Purpose |
|---|---|
| `OPENAI_API_KEY` | Default upstream for `record` (auth passthrough). |
| `NABLA_UPSTREAM_BASE_URL` | Record through any OpenAI-compatible stand-in oracle instead. |
| `NABLA_UPSTREAM_API_KEY` | Key for the stand-in oracle. |
| `NABLA_UPSTREAM_MODEL` | Rewrite the model for the stand-in only (the target repo's own model still lands in the profile). |
| `NABLA_RECORD_DELAY_MS` | Pacing between samples for rate-limited upstreams (default 0). |
| `NABLA_SAMPLE_TIMEOUT` | Per-sample subprocess timeout in seconds (default 300). |
| `GEMINI_API_KEY` | Key for the default goal-inference provider (Gemini free tier). |
| `NABLA_INDUCE_BASE_URL` / `NABLA_INDUCE_API_KEY_ENV` / `NABLA_INDUCE_MODEL` | Swap the goal-inference provider/model. |

## Notes

- `record` runs the site's enclosing function in a subprocess per sample,
  with `OPENAI_BASE_URL` pointed at a local recording proxy. If the
  target's tests mock the SDK, the proxy path reports zero traffic
  explicitly instead of writing an empty profile.
- Recorded prompts/completions are real data from your repo and leave
  the machine only via the goal-inference call. Review before sharing
  profiles.
