Metadata-Version: 2.5
Name: agentbuild
Version: 0.0.3
Summary: Tools to make agent research easier.
Author: trducng
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.11
Requires-Dist: diskcache>=5.6
Requires-Dist: ipython>=8
Provides-Extra: anthropic
Requires-Dist: anthropic>=2; extra == 'anthropic'
Provides-Extra: gemini
Requires-Dist: google-genai>=2; extra == 'gemini'
Provides-Extra: openai
Requires-Dist: openai>=3; extra == 'openai'
Description-Content-Type: text/markdown

# agentbuild

Tools to make agent research easier.

## Install

```bash
pip install agentbuild
```

## Jupyter: `%load_cells`

Copy the code of cells from another notebook into the current cell, the way `%load` does for
files.

```python
%load_ext agentbuild.jupyter

%load_cells other.ipynb                            # list the notebook's cells, IDs and titles
%load_cells other.ipynb --cells 7f3a9c21 b2e4d0aa  # copy those cells into this cell
%load_cells "my export.ipynb" -c "Import Libraries" 7f3a
%load_cells other.ipynb -c 7f3a9c21 --run          # run those cells instead of copying them
%load_cells other.ipynb -c 7f3a9c21 --print        # print their code instead of copying it
```

Each cell after `--cells` (or `-c`) can be:

- a cell ID: the Jupyter cell ID, or for notebooks exported from Databricks, the Databricks cell ID
  (`nuid`);
- the start of a cell ID, such as the 8 characters the listing shows;
- a Databricks cell title, matched exactly or ignoring case. Quote titles that contain spaces.

If a value matches more than one cell, the magic stops with an error instead of guessing.

Running the magic with `--cells` replaces the current cell with a commented-out copy of the magic
line, followed by the code of the chosen cells in the order given. Run the cell again to execute
that code. Relative paths are resolved from the kernel's working directory, which is normally the
notebook's folder.

- `--run` runs the chosen cells' code in the current session instead of copying it. Magics in
  that code, such as `%pip`, work as they would in a cell.
- `--print` prints the code instead of replacing the current cell, for notebook apps that can't
  replace a cell's contents. This is the default on Databricks, whose notebooks can't (it's
  detected through the `DATABRICKS_RUNTIME_VERSION` environment variable).

To load the extension in every session, add this to
`~/.ipython/profile_default/ipython_config.py`:

```python
c.InteractiveShellApp.extensions = ["agentbuild.jupyter"]
```

## LLM calls: `agentbuild.leanllm`

One small client for OpenAI, Anthropic, Gemini and any OpenAI-compatible server (Azure OpenAI,
Ollama, vLLM, OpenRouter), with retries, failover, load balancing and an on-disk response cache.
Install the SDKs of the providers you use:

```bash
pip install "agentbuild[openai,anthropic,gemini]"
```

Keys come from `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` and `GEMINI_API_KEY`.

```python
from agentbuild.leanllm import LLM, Deployment

reply = LLM("openai/gpt-4o-mini").chat("Summarize this chunk: ...")
print(reply.text)

llm = LLM(
    route=[
        # Tier 1: two deployments of one model share the load
        [
            "openai/gpt-4o-mini",
            Deployment(
                "openai/gpt-4o-mini",
                base_url="https://my-azure.openai.azure.com/openai/v1",
                api_key_env="AZURE_OPENAI_KEY",
            ),
        ],
        # Later tiers are used only when every deployment above has failed
        ["anthropic/claude-haiku-4-5"],
        ["gemini/gemini-2.5-flash"],
    ],
    cache="./.llm_cache",  # None disables caching
    retries=2,  # per deployment, before moving on
    timeout=120,  # seconds per request
    cooldown=60,  # seconds a failing deployment is skipped
)
reply = llm.chat(
    [
        {"role": "system", "content": "Answer only from the context."},
        {"role": "user", "content": prompt},
    ],
    temperature=0,
    max_tokens=500,
)
reply.text, reply.finish_reason, reply.served_by, reply.cached, reply.usage, reply.attempts

vectors = LLM("openai/text-embedding-3-small", cache="./.llm_cache").embed(chunks)
```

- A deployment's key can also be given directly, as `Deployment(..., api_key=key)`; it is kept
  out of reprs and logs.
- For an Azure OpenAI resource that cannot use the v1 endpoint yet, pass the same values as
  `openai.AzureOpenAI`: `Deployment("openai/<deployment name>", base_url=<azure_endpoint>,
  api_version="2024-10-21", api_key=key)`.
- `await llm.achat(...)` and `await llm.aembed(...)` behave the same, asynchronously.
- `cache=False` skips the cache for one call; `cache="refresh"` calls the provider and overwrites
  the entry. A cached answer comes back even at a temperature above 0. Embeddings are cached one
  text at a time, so only new texts are sent. Keep the cache on a local disk: on Databricks, not
  under `/dbfs` or `/Volumes`.
- `provider_options={"gemini": {"thinking_config": {"thinking_budget": 0}}}` passes options to
  one provider's API and is ignored by the others. Gemini 2.5 models think by default, and
  thinking counts against `max_tokens`.
- A `BadRequest` (400, 422) is raised at once, with no failover. When every deployment fails,
  `AllProvidersFailed.attempts` lists each try: `a.deployment`, `a.error`, `a.duration_s`.
- Each attempt is logged on the `agentbuild.leanllm` logger; `on_attempt=callback` receives it too.

See [docs/leanllm_submodule.md](docs/leanllm_submodule.md) for the full behaviour.

## Development

Requires [uv](https://docs.astral.sh/uv/) and `make`.

```bash
make install       # install the package + dev tools, set up git hooks
make help          # list all commands
```

Common commands:

```bash
make test          # run tests
make test-matrix   # run tests on Python 3.11-3.15 (override with PYTHONS="3.11 3.12")
make cov           # run tests with a coverage report
make lint          # lint + format check
make format        # auto-fix lint issues and format
make check         # run the same checks as CI
```

## Release

```bash
make publish       # build and upload to PyPI (needs a PyPI token)
```
