Metadata-Version: 2.4
Name: rescale-ai-client
Version: 0.3.4
Summary: Standalone HTTP client for hosted and local Rescale AI inference servers
Project-URL: Repository, https://github.com/rescale/rescale-ai-client
Author: Rescale
License-Expression: Apache-2.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Requires-Dist: cryptography>=42
Requires-Dist: numpy>=1.24
Requires-Dist: pydantic>=2.7
Requires-Dist: python-multipart>=0.0.27
Provides-Extra: cma
Requires-Dist: cma>=3.2; extra == 'cma'
Requires-Dist: scipy>=1.10; extra == 'cma'
Provides-Extra: dev
Requires-Dist: datamodel-code-generator>=0.31.2; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: scipy>=1.10; extra == 'dev'
Provides-Extra: jupyter
Requires-Dist: jupyter-bokeh>=4.0; extra == 'jupyter'
Requires-Dist: panel>=1.0; extra == 'jupyter'
Requires-Dist: pyvista>=0.40; extra == 'jupyter'
Requires-Dist: trame>=2.0; extra == 'jupyter'
Provides-Extra: mesh
Requires-Dist: pyvista>=0.40; extra == 'mesh'
Provides-Extra: optimization
Requires-Dist: scipy>=1.10; extra == 'optimization'
Provides-Extra: ssh
Requires-Dist: paramiko>=3.4; extra == 'ssh'
Description-Content-Type: text/markdown

# rescale-ai-client

Standalone Python client for [Rescale AI](https://rescale.com) hosted and local inference servers.
Communicates over the shared HTTP API. No `rescale_ai` package dependency.

## Installation

```bash
pip install rescale-ai-client
```

For mesh loading, prediction plotting, or VTK mesh helpers (`.vtp` and `.vtm`):

```bash
pip install "rescale-ai-client[mesh]"
```

For interactive 3D notebook plots:

```bash
pip install "rescale-ai-client[mesh,jupyter]"
```

This extra also installs `jupyter_bokeh`, which Panel needs for interactive rendering
in VSCode notebooks.

For development (do not run unless making local changes to the `rescale-ai-client` library)

```bash
pip install -e ".[dev]"
```

## License Request

```bash
uvx rescale-ai-client request-license --email user@customer.com
```

Writes `license-request.json` for the current user and machine.

## License Registration

```bash
uvx rescale-ai-client register
```

Registers the issued license for the current user. Without a path, the command
looks for a valid license in `~/Downloads`, then the current directory, then
prompts for the absolute path to the license file.

You can also pass the license path explicitly:

```bash
uvx rescale-ai-client register <path_to_license_file>
```

## Quickstart

### Launch Local Jupyter Notebook:

```
cd <path_to_workspace>/rescale-ai-client
source .venv/bin/activate
python -m ensurepip --upgrade
python -m pip install --upgrade pip
pip install uv
jupyter notebook
```

See `example/notebooks/quickstart.ipynb` for an example Jupyter notebook to get started. The directory contains other more advanced examples. 

### Retrieve Server-Available Models:

```python
from rescale_ai_client import InferenceClient

client = InferenceClient.connect(base_url="http://127.0.0.1:65432") # Connects to local Inference Runner. Modify to connect to remote Host & Port
catalog = client.list_models()

model = catalog.model("MyModel").latest()
prediction = client.run_inference(
    "/path/to/case.vtm",
    parameters={"mach": 0.82},
    model_name=model.model_name,
    version_number=model.version_number,
)

print(prediction.global_predictions)
prediction.save("/tmp/result.vtm")
```

### No-mesh Core Install, Summary-Only Inference:

```python
from rescale_ai_client import InferenceClient

client = InferenceClient.connect(base_url="http://127.0.0.1:65432") # Connects to local Inference Runner. Modify to connect to remote Host & Port
summary = client.run_inference_summary(
    "/path/to/case.vtm",
    parameters={"mach": 0.82},
    model_name="MyModel",
    version_number=7,
)

print(summary.global_predictions)
print(summary.prediction_id)
```

### Offline Install Bundle:

This installation path used when downloading an inference bundle directly from the Web UI.

```python
from rescale_ai_client import InferenceClient

client = InferenceClient.connect(base_url="http://127.0.0.1:65432") # Connects to local Inference Runner. Modify to connect to remote Host & Port

installed = client.install_bundle(
    "/path/to/MyModel_v0_local_inference.zip"
)

prediction = client.run_inference(
    "/path/to/case.vtm",
    parameters={"mach": 0.82},
    model_version_path=installed.model_version_path,
)

print(installed.model_name, installed.version_number)
print(prediction.global_predictions)
```

An ensemble bundle loads as `LoadedEnsemble` and is selected by its immutable
ensemble identity. Members execute sequentially, and uncertainty arrays can be
requested alongside the ensemble mean:

```python
from rescale_ai_client import EnsembleLocator, InferenceClient

client = InferenceClient.connect()
installed = client.install_bundle("/path/to/ensemble.zip")

prediction = client.run_inference(
    "/path/to/case.vtm",
    model=EnsembleLocator(ensemble_id=installed.ensemble.ensemble_id),
    include_standard_deviation=True,
    ensemble_node_output="mean",
)

for name, distribution in prediction.ensemble_global_predictions.items():
    print(name, distribution.mean, distribution.standard_deviation)
    print(distribution.member_predictions)
```

Global ensemble outputs always include their mean, standard deviation, and
member-value vector. Node arrays default to the ensemble's primary member;
set `ensemble_node_output="mean"` to generate mean node predictions. Node
standard-deviation arrays are only generated when mean node output and
`include_standard_deviation=True` are both requested.

### `extensions` for Optional Future Data

Most JSON request methods accept an optional `extensions={...}` payload. This is the additive
escape hatch for future server features that should not force a client upgrade.

#### Example: Send Optional Request Hints During Prediction:

```python
from rescale_ai_client import InferenceClient

client = InferenceClient.connect(base_url="http://127.0.0.1:65432") # Connects to local Inference Runner. Modify to connect to remote Host & Port

summary = client.run_inference_summary(
    "/path/to/case.vtm",
    parameters={"mach": 0.82},
    model_name="MyModel",
    version_number=7,
    extensions={
        "rescale": {
            "ui_session": "demo-123",
            "include_debug": True,
        }
    },
)

print(summary.extensions)
print(summary.warnings)
```

#### Example: Read Capability-Style or Server-Added Metadata from Typed Responses:

```python
from rescale_ai_client import InferenceClient

client = InferenceClient.connect(base_url="http://127.0.0.1:65432")

server_info = client.server_info()
print(server_info.capabilities)

metadata = client.get_model_version_metadata(
    model_name="MyModel",
    version_number=7,
)
print(metadata.extensions)
print(metadata.warnings)
```

### Run a Saved System

Saved systems created in the Design Studio are available through the local
inference server. Inputs may be partial; omitted values use the defaults stored
in the system definition. Passing the catalog hash prevents accidentally
running a definition that changed after it was inspected:

```python
from rescale_ai_client import InferenceClient

client = InferenceClient.connect()
catalog = client.list_systems()
system = catalog.system("booth")

result = client.run_system(
    system.system_id,
    {"x1": 1.0, "x2": 3.0},
    expected_system_hash=system.system_hash,
)

print(result.outputs)
print(result.resolved_models)
```

`get_system(system_id)` returns the declared inputs, outputs, components, and
validation state. A run records the exact model versions resolved at its start,
including models configured as `latest`. System catalog and execution endpoints
are intentionally available only through a loopback connection (direct local
access or an SSH tunnel).

Design rules:

- `extensions` are optional and additive
- unknown request extensions are ignored by the server
- unknown response extensions are ignored by clients
- if a field becomes core contract, it should graduate into a typed top-level field

## Optimization

The `rescale_ai_client.optimization` package turns mesh inference into a
design-optimization loop. Define an `Objective` (built-ins cover global scalar
outputs, point-field reductions, and multi-model chains) and hand it to a search
driver. Every driver minimizes `ObjectiveResult.value` (use `maximize=True` on
the built-in objectives to flip the sign) and returns a result carrying the full
evaluation `history`, which the `plot_*` helpers can render.

Install the extra:

```bash
pip install "rescale-ai-client[optimization]"   # adds SciPy
pip install "rescale-ai-client[cma]"             # also adds CMA-ES support
```

Available drivers:

| Driver | Kind | Use when |
| --- | --- | --- |
| `minimize_scipy` | Local (L-BFGS-B / SLSQP / TNC) | A good starting point is known and the response is smooth |
| `sqp_minimize` | Local SQP (SLSQP / trust-constr) with nonlinear constraints | The problem has constraints (e.g. `max_stress <= 500`); warm-start from a global driver |
| `differential_evolution` | Global, population | Multi-modal landscape, evaluations are cheap |
| `random_search` | Baseline sampling | A quick lower bound / sanity check |
| `bayesian_optimization` | Global, GP surrogate + Expected Improvement | Each evaluation is an expensive inference call (most sample-efficient) |
| `cma_es` | Global evolution strategy (needs `[cma]`) | Noisy or ill-conditioned, multi-modal objectives |

```python
from rescale_ai_client import InferenceClient
from rescale_ai_client.optimization import (
    GlobalOutputObjective,
    bayesian_optimization,
    cma_es,
    minimize_scipy,
    random_search,
)

client = InferenceClient.connect(base_url="http://127.0.0.1:65432")
obj = GlobalOutputObjective(
    client,
    model_name="MyModel",
    version=0,
    vtp="/path/to/case.vtm",
    output_name="Cd",
    maximize=False,
)
bounds = {"AngleBeta1": (10.0, 35.0), "RPM": (25000.0, 32000.0)}

# Sample-efficient global search — best for expensive inference objectives.
result = bayesian_optimization(obj, bounds=bounds, n_calls=25, seed=0, verbose=True)
print(result.x, result.fun)

# Other drivers share the same call/return shape:
# minimize_scipy(obj, x0={...}, bounds=bounds)
# cma_es(obj, x0={...}, bounds=bounds)            # requires the [cma] extra
# random_search(obj, bounds=bounds, n_calls=50)
```

### Constrained optimization with SQP

`sqp_minimize` adds nonlinear constraints. Each `Constraint` receives both the
parameters and the **cached** `ObjectiveResult` at that point, so a constraint on
a model output reuses the objective's inference (no extra call):

```python
from rescale_ai_client.optimization import Constraint, sqp_minimize

constraints = [
    # Keep a predicted global output under a limit (read from the cached result).
    Constraint(
        fn=lambda params, result: result.info["global_outputs"]["max_stress"],
        upper=500.0,
        name="max_stress",
    ),
    # A purely geometric/parameter constraint ignores the result argument.
    Constraint(fn=lambda params, result: params["t1"] - params["t2"], lower=0.0),
]

result = sqp_minimize(
    obj,
    x0={"AngleBeta1": 20.0, "RPM": 29607.0},
    bounds=bounds,
    constraints=constraints,
    method="SLSQP",          # or "trust-constr" for harder problems
    verbose=True,
)
print(result.x, result.fun, result.feasible, result.constraint_values)
```

Because SQP estimates gradients by finite differences, it's most effective on a
smooth response and warm-started from a global driver's best point.

## API

### `InferenceClient.connect(*, base_url=None, host=None, port=None, url=None, timeout=600.0)`

Factory method. If omitted, defaults to local loopback at `http://127.0.0.1:65432`. `base_url` first. `host` / `port` and `url` remain convenience inputs. Scheme-less non-local endpoints default to HTTPS; explicit HTTP for non-local endpoints raises an `InsecureConnectionWarning` and stores the message on `client.insecure_transport_warning`.

// Give example here: connecting to my most recent Workstation
### `InferenceClient.connect_via_ssh(*, ssh_user_host, remote_port=65432, ...)`

SSH tunnel helper. `ssh_user_host` required. If `remote_port` is omitted, defaults to `65432`.

### `run_inference(vtp, parameters=None, *, model=None, model_version_path=None, model_name=None, model_uuid=None, version_number=None, ...)`

Canonical Python entrypoint. `model=` accepts either `ModelVersionLocator` or `EnsembleLocator`. The `vtp` argument name is kept for compatibility, but path inputs may be `.vtp` or `.vtm`; `.vtm` paths are sent to the inference server so block structure is preserved. Same shape for hosted and local servers. CamelCase aliases still work, but new code should use snake_case.
JSON request methods also accept `extensions=...` for optional future payloads.

### `run_inference_summary(...)`

Lightweight entrypoint for core installs without the optional mesh dependencies. Returns typed prediction metadata plus global scalar outputs without constructing a `pyvista` mesh.

### `list_models(...)`

Returns a typed `ListModelsResult` with helpers like `.model("name")`, `.get("name", version_number=7)`, and `.model("name").latest()`.

### `list_systems()` / `get_system(system_id)`

Lists saved Design Studio systems or returns one typed `SystemDescription`.

### `run_system(system_id, inputs=None, *, expected_system_hash=None, require_convergence=False, extensions=None)`

Runs a saved system on the local inference server and returns a typed
`SystemRunResult` containing scalar outputs, resolved model versions, iteration
diagnostics, warnings, and reproducibility hashes.

### `inspect_bundle(...)` / `install_bundle(...)`

Inspect, upload, and install an offline local-inference bundle zip into the local installer. `install_bundle(...)` is idempotent for identical content. Ensemble IDs are immutable, so installing different content under an existing ensemble ID is rejected.

### `get_model_version_metadata(version_number, *, model_name=None, model_uuid=None, ...)`

Fetches typed metadata for one model version.

### `get_loaded_model()`

Returns the server's current active-model bookkeeping status.

### `server_info()`

Returns typed inference server capability and compatibility metadata:

- `api_version`
- `supported_api_versions`
- `schema_revision`
- `server_build`
- `capabilities`

### `Client`

Compatibility alias for one release. Use `InferenceClient` for new code.

## Advanced

### Protocol Sync
The committed wire DTOs under `src/rescale_ai_client/_generated/` are generated from the
closed-source inference server OpenAPI schema snapshot at
`openapi/inference-server.openapi.json`.

Update flow:

```bash
# from the closed-source rescale_ai repo
PYTHONPATH=/path/to/rescale_ai \
  /path/to/rescale_ai/.venv/bin/python \
  - <<'PY'
from pathlib import Path
from rescale_ai.inference.server.openapi_compat import export_openapi_schema

export_openapi_schema(
    Path("/path/to/rescale-ai-client/openapi/inference-server.openapi.json")
)
PY

# from this repo
python scripts/generate_protocol_models.py
python scripts/generate_protocol_models.py --check
```

### Releasing

Releases are published by GitHub Actions with `uv build` and `uv publish`.

#### One-Time Setup:

1. In GitHub, create an environment named `pypi` under **Settings -> Environments**.
2. In PyPI, add a trusted publisher for:
   - Owner: `rescale`
   - Repository: `rescale-ai-client`
   - Workflow: `release.yml`
   - Environment: `pypi`

No PyPI API token needs to be stored in GitHub secrets for this workflow. PyPI issues
a short-lived publishing credential to the tagged GitHub Actions run through OIDC.

#### Publish a Release:

```bash
uv version 0.1.1
git commit -am "Release 0.1.1"
git tag -a v0.1.1 -m v0.1.1
git push origin main
git push origin v0.1.1
```

The workflow runs tests, verifies generated protocol models, builds both wheel and
source distributions, smoke-tests both artifacts, and publishes to PyPI.

## License

Apache-2.0
