Metadata-Version: 2.4
Name: hugging-bay
Version: 0.4.10
Summary: Open model files with recorded SHA-256 and licence labels; request what is missing.
Author: Hugging Bay
License: MIT
Project-URL: Homepage, https://huggingbay.xyz/?ref=pypi
Project-URL: Documentation, https://huggingbay.xyz/redoc
Project-URL: Repository, https://github.com/barneywohl/hugging-bay
Project-URL: Bug Tracker, https://github.com/barneywohl/hugging-bay/issues
Project-URL: API Reference, https://huggingbay.xyz/openapi.json
Keywords: hugging-bay,bay-run,models,safety,gguf,local-llm,huggingface-alternative,llm,provenance,openapi,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24
Requires-Dist: cryptography>=41
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Requires-Dist: setuptools>=61; extra == "dev"
Requires-Dist: wheel>=0.43; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"

# hugging-bay

**Promise:** Find open model files with recorded SHA-256 and licence labels; verify downloaded bytes locally.<br>
**Install:** `python -m pip install hugging-bay`<br>
**MCP config:** `{"mcpServers":{"hugging-bay":{"url":"https://huggingbay.xyz/api/mcp"}}}` (Cursor; public reads need no account).<br>
**First call:** `Client().resolve("Qwen/Qwen3-0.6B")`<br>
**Ask:** Missing a model, file, tool or feature? Ask with request_feature (MCP) or POST https://huggingbay.xyz/api/v1/requests. No account. You get an id and can check its status. What shipped: https://huggingbay.xyz/changelog<br>
[Changelog](https://huggingbay.xyz/changelog?ref=pypi) · [API docs](https://huggingbay.xyz/redoc?ref=pypi)<br>
**Keywords:** `gguf`, `local-llm`, `huggingface-alternative`, `mcp`

## Usage

```python
from hugging_bay import Client

client = Client()  # defaults to https://huggingbay.xyz
# Optional auth: Client(token="sk-...") sends Authorization: Bearer <token>.
# The token is never printed in repr()/str() and never logged.
```

### 0. Get an API key (self-serve, optional)

Public discovery — search, resolve, download-plan, and hosted downloads — needs
**no token**. You only authenticate for account workflows (saves, collections,
watchlists). Minting a reader token is self-serve, with no signup and no prior
auth:

```python
from hugging_bay import Client

issued = Client().create_reader_token(display_name="my app", purpose="serving models")
token = issued["apiKey"]["token"]      # shown once — store it securely
client = Client(token=token)
print(client.me()["role"])             # validate the token
```

The full walkthrough (curl, JS/TS, MCP) is the one-screen quickstart at
`https://huggingbay.xyz/quickstart`. A `401`/`403` from any authorized endpoint
returns `next.href = /quickstart#get-a-key`, so there is no dead-end.

### 1. Find a commercial-safe model for a rig

```python
from hugging_bay import Client

client = Client()
result = client.find_model(
    task="coding",
    gpu="rtx4090-24",
    ctx=8192,
    device_family="nvidia",
    commercial=True,
    limit=3,
)
best = result["bestPick"]
print(best["repo"], "-> safe-to-run:", best["safety"]["verdict"])
```

### 2. Verify a source URL

```python
from hugging_bay import Client, HuggingBayError

client = Client()
try:
    resolved = client.resolve("Qwen/Qwen3-0.6B")
    print("resolved artifact:", resolved.get("artifactId") or resolved)
except HuggingBayError as err:
    print("could not resolve:", err.status, err.error)
```

### 3. Download a file and compare its SHA-256

```python
from hugging_bay import Client

client = Client()
artifact_id = "hf-model-qwen-qwen2-5-0-5b-instruct-gguf"

# The hosted download plan includes per-file hashes and byte sizes.
plan = client.download_plan(artifact_id)

# Stream one hosted file, verify its exact plan hash, and record the completed
# pull. This stops the Hugging Bay redirect, follows the short-lived signed GCS
# URL without forwarding the API bearer, and only returns after attestation.
file = next(row for row in plan["files"] if row["path"].endswith(".gguf"))
# Case-only filename twins are refused by default. Opt in to suffix names
# allocated from the sorted full plan; generated suffixes skip every planned
# basename. The destination you pass supplies the selected file's base name,
# and existing unrelated names may advance its suffix. `pathMapping` reports
# the actual local path.
result = client.download_file(
    artifact_id,
    file["path"],
    "./models/" + artifact_id + "/" + file["path"],
    sha256=file["sha256"],
    size_bytes=file["sizeBytes"],
    on_case_collision="suffix",
)
print(result.get("pathMapping"))
```

Read-only against production (GET only, no token, no downloads):

```python
from hugging_bay import Client

client = Client()  # https://huggingbay.xyz
page = client.search("embedding", limit=2, hosting="hosted")
pick = client.find_model("embeddings", hardware="8gb-laptop", limit=2)
resolved = client.resolve("Qwen/Qwen3-0.6B")
print(len(page["rows"]), pick.get("bestPick", {}).get("repo"), resolved.get("artifactId"))
```

Or run ``python examples/read_only.py`` (same host; optional ``HUGGING_BAY_ARTIFACT_ID``).

### 4. Diagnose a failing run

```python
from hugging_bay import Client

client = Client()
diagnosis = client.doctor(
    log_text="llama_model_load: error loading model: CUDA out of memory",
    runtime="llama.cpp",
)
if diagnosis["topFix"]:
    print("top fix:", diagnosis["topFix"])
for alt in diagnosis.get("smallerHostedAlternatives", []):
    print("smaller option:", alt)
```

### Discovery → execution with Bay Run

`BayRunClient` runs the mirrored weights live on Bay Run (`run.huggingbay.xyz`,
a separate origin). Take the `next_call` a catalog answer returns and run it —
the client mints the anonymous `$0` demo key for you and tolerates the edge's
plain-text `429 "Rate exceeded."`:

```python
from hugging_bay import Client, BayRunClient

runnable = Client().find_runnable(task="embeddings", limit=1)
# hugging-bay.find-runnable.v1: the handoff lives at the top-level `next_call`
# (or under `recommendation.execution`); there is no `candidates` list.
next_call = runnable.get("next_call") or runnable["recommendation"]["execution"]

with BayRunClient() as run:            # mints a $0 demo key on first call
    out = run.run_next_call(next_call, input="text to embed")
    print(out)

# Or call the OpenAI-compatible surface directly:
with BayRunClient() as run:
    run.embeddings("BAAI/bge-small-en-v1.5", "hello world")
    run.rerank("BAAI/bge-reranker-base", query="q", documents=["a", "b"])
    run.run_pin("pin_abc123", input="...")
```

## API surface

The new typed hosted methods require `BayRunClient(enable_hosted_api=True)`.
They are off by default. Existing execution methods keep their behavior.
Use `pins()`, `pin(id)`, `mint_pin(request)`,
`instant_models()`, `instant_classify(request)`, `instant_embed(request)`,
`instant_rerank(request)`, `instant_chat(request)`, and `coprocessor(request)`.
`auto_pins()` and `run_auto_pin(pin_id, request)` call the preview CPU auto-pin routes.
`BayRunMcpClient` maps MCP tool names `list_auto_pins` and `run_auto_pin` onto the same paths.
Request and response types are in `hugging_bay.bayrun_types`.

`provenance_keys()` reads published Ed25519 keys. `verify_receipt(receipt)`
checks a receipt locally with those keys. Unknown signers fail closed. Pin
keys can differ from published provenance keys, so Pin verification may need
an explicit trusted key document. Never use a receipt's embedded key as trust.
The standalone `hugging_bay.verify_receipt(receipt, keys)` works offline.
Verification authenticates signed claims. It does not prove model quality or
the unsigned decision. For raw-result binding, set `omit_raw_result=False`
when calling and pass `result=...` to verification.

With hosted methods enabled, the client retries `429` and `503 lane_cold`
once unless the server marks
the error `retriable: false`. It accepts `Retry-After` seconds or HTTP dates
and preserves the same body and idempotency key. A cold lane without a delay
gets a one second retry. Delays above `max_retry_after_seconds` (default 120)
raise `BayRunError` with the full `.retry_after` delay. Set
`honor_retry_after=False` to let your application schedule retries. When the
hosted flag is off, existing methods retain their 429/503 retry policy and
120 second cap.

See [the hosted API guide and five examples](https://github.com/barneywohl/hugging-bay/blob/main/docs/bay-run-clients.md).

| Method | Endpoint |
| --- | --- |
| `create_reader_token(display_name, purpose)` | `POST /api/account/reader-token` (self-serve, no auth) |
| `me()` | `GET /api/me` |
| `find_model(task, gpu, ctx, device_family, commercial, limit, *, hardware, custom_memory_gb)` | `GET /api/agents/find-model` |
| `artifact(id)` | `GET /api/v1/artifacts/{id}` |
| `safety(id)` | `GET /api/v1/artifacts/{id}/safety` |
| `bundle(id)` | `GET /api/v1/artifacts/{id}/bundle` |
| `passport(id)` | `GET /api/v1/artifacts/{id}/passport` |
| `resolve(repo_or_url)` | `GET /api/v1/resolve?repo=` |
| `download_plan(id)` | `GET /api/v1/artifacts/{id}/download-plan` |
| `download_file(id, path, destination, sha256, size_bytes, on_case_collision="error")` | GCS redirect + streamed SHA-256 verification + completion attestation; refuses case-folded path collisions by default, or returns deterministic `.caseN` destinations in `pathMapping` with `on_case_collision="suffix"` |
| `doctor(log_text, runtime)` | `POST /api/doctor` |
| `find_runnable(task, limit, commercial, **filters)` | `GET /api/agents/find-runnable` |
| `recipes()` / `recipe(slug)` | `GET /api/recipes[/{slug}]` |
| `verify_lock(lock_dict, root_dir)` | offline, stdlib only |
| `BayRunClient.free_key()` | `POST /v1/keys/free` (Bay Run) |
| `BayRunClient.models()` | `GET /v1/models` (Bay Run) |
| `BayRunClient.run_pin(pin_id, ...)` | `POST /v1/run/{pin_id}` (Bay Run) |
| `BayRunClient.embeddings / rerank / classify / chat(...)` | OpenAI-compatible (Bay Run) |
| `BayRunClient.run_next_call(next_call, input)` | executes a catalog `next_call` |

## Errors

Every Hugging Bay error is the **same typed envelope on every surface**:
`{error, code, status, message, requestId, cause?, next}`. Any response with
status `>= 400` raises `HuggingBayError`, which surfaces those fields directly —
`.error`/`.code` (machine code), `.message`, `.status`, `.request_id`, `.next`
(the `{action, href}` recovery step), `.retry_after_seconds`, and the raw `.body`.
GET/HEAD reads retry `429` and `503` once using the largest valid `Retry-After`
(seconds or HTTP-date) or JSON `retry_after_s` / `retryAfterSeconds`, plus
positive jitter. Delays beyond 60 seconds surface as errors with the full delay;
writes are single-shot. A successful `X-HB-Backoff: v1` response with
`Retry-After` paces the next read without replaying the success.
See [the backoff contract](../../docs/backoff-contract.md).

`resolve()` follows status-based error handling: a well-formed reference with no
catalog match raises status `404`; malformed/unsupported input raises `400`
(`invalid_query`). Bay Run errors raise `BayRunError`, which is tolerant of the
edge's non-JSON bodies and exposes `.retry_after`.

## License

MIT

## Public response note

Hugging Bay responses include a download note in `note.summary`. Current clients should read `note`, `downloadable`, and `downloadStatus`. Older clients that still need the previous response shape can opt in to it for one release; see the API reference.

Downloadable doesn't mean tested: this record says whether the exact file can be downloaded now, not that the model is complete or that it runs.

## Release dependency

The request-and-status sentence above is approved copy held on the c29 AEO-72/AEO-73 deployment gate. Verify the deployed `request_feature`, `get_request`, and public changelog/status path before package publication or directory copy uses it.

## Generated task client and resume

`client.api` exposes OpenAPI-generated task methods and typed dictionaries. `client.search`, `client.artifact`, `client.find_runnable`, `client.deployment_plan` and `client.download_plan` use these generated wire types. Deployment plans use `GET /api/agents/deployment-plan?artifact=id`; download plans keep their existing route.

Pass `resume=True` to `client.download_file` to retain interrupted bytes beside the destination. Retry the same call to continue. The SDK hashes the prefix again, validates the exact storage range, and checks the full SHA-256 before completion attestation and publication. A 200 response restarts the transfer. A crashed process can leave a lock; remove it only when no download is using it.

Run `python examples/read_only.py` for live GET-only reads. It never downloads model bytes or attests completion. See [the SDK workflow](../../scripts/sdk/README.md) for generation, drift checks and local packaging commands.

### Parallel catalog swarms (opt in)

`SwarmClient` is an opt-in parallel catalog client: share one instance across
threads, call `pin()` first, then use `resolve_many`, `find_runnable_many` and
`download_plans`. It provides bounded concurrency, single-flight pinned-answer
caching and shared Retry-After backoff. Server extensions remain disabled unless
`HUGGING_BAY_SWARM_FEATURES=1` is explicitly set. See [the swarm quickstart]
(../../docs/swarm-quickstart.md) for limits, NDJSON, snapshot scope and a
50-worker example.
