Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@

All notable changes to this project will be documented in this file.

## Unreleased

### Added

- Hosted auto-label support, matching the Roboflow MCP tools:
- `Workspace.autolabel_models()` — list the foundation-model catalog
(`gpt-6-astra-boxes`, `sam3-rle`, `gemini-boxes`, ...) with availability,
guidance and credits per image.
- `Project.autolabel_preview(model, image, ontology=...)` — free single-image
preview to compare models before starting a job. `image` accepts an HTTPS
URL, a local file path or a base64 string.
- `Project.autolabel(batch_id, model, model_type="foundational" | "roboflow", ...)`
— start a job over a batch; returns `{jobId, annotationJobId}`. The
`ontology` is keyed by prompt (`{"kitten": "cat", "tabby": "cat"}`), so
several prompts can share one output class. `preserve_existing_annotations=True`
keeps annotations already on the images (the server default replaces them).
- `Project.autolabel_job(job_id)` / `Workspace.autolabel_job(job_id)` — poll
per-subjob progress.
- `roboflow autolabel models | preview | start | job` CLI commands.
`start --preserve-existing` mirrors the SDK flag; `job -p ws/project`
resolves the workspace the same way `start` does.

## 1.4.1

### Added
Expand Down
39 changes: 39 additions & 0 deletions CLI-COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,44 @@ project.accept_annotation_job_images(
)
```

### Auto-label a batch with a foundation model

```bash
roboflow autolabel models
roboflow autolabel preview -p my-project -m sam3-rle --image https://example.com/sample.jpg \
--class cat --class dog
roboflow autolabel start -p my-project --batch-id <batch-id> -m gpt-6-astra-boxes \
--ontology '{"a cat": "cat", "a dog": "dog"}' --confidence 0.5 --reviewer b@co.com
roboflow autolabel start -p my-project --batch-id <batch-id> -m my-project/3 --model-type roboflow
roboflow autolabel start -p my-project --batch-id <batch-id> -m sam3-rle --preserve-existing
roboflow autolabel job <job-id>
roboflow autolabel job <job-id> -p other-workspace/my-project
```

`models` lists the catalog for the workspace (id, availability, credits per
image, default). `preview` runs one image through a model for free so you can
compare candidates before spending credits. `start` creates the job and prints
`jobId` and `annotationJobId`; poll it with `job`. Pass the ontology either as
repeated `--class` flags or as `--ontology` JSON. The ontology is keyed by
**prompt**, not by class: `'{"kitten": "cat", "tabby": "cat"}'` labels whatever
matches either prompt as class `cat`. That direction is what lets several
prompts share one output class. JSON options also accept a curl-style file
reference (`--ontology @ontology.json`).
`--image` accepts an HTTPS URL or a local file. By default a job replaces the
annotations already on the batch images; `--preserve-existing` keeps them and
only adds new ones. `job` looks the id up in your default workspace, so when
the job was started with a `workspace/project` shorthand pass the same `-p` to
`job`.

The same operations are available in Python:

```python
models = workspace.autolabel_models()["models"]
preview = project.autolabel_preview("sam3-rle", "sample.jpg", ontology={"cat": "cat"})
job = project.autolabel("batch-id", model="gpt-6-astra-boxes", ontology={"a cat": "cat"})
project.autolabel_job(job["jobId"])["status"]
```

### RFDM devices (v2 deployments)

Workspace-scoped device management — backed by the external Deployments API
Expand Down Expand Up @@ -457,6 +495,7 @@ Version numbers are always numeric — that's how `x/y` is disambiguated between
| `workflow` | Manage workflows |
| `folder` | Manage workspace folders |
| `annotation` | Annotation batches and jobs |
| `autolabel` | Auto-label batches with hosted foundation or Roboflow models |
| `asynctasks` | Inspect async background tasks (e.g. project forks) |
| `trash` | List items in Trash |
| `universe` | Search Roboflow Universe |
Expand Down
123 changes: 123 additions & 0 deletions roboflow/adapters/rfapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,11 @@ def _annotation_pagination_params(api_key, *, limit, after=None, show_empty=None


def _annotation_administration_response(response):
return _json_response_or_raise(response)


def _json_response_or_raise(response):
"""Return the JSON body of a 2xx response; raise ``RoboflowError`` with the HTTP status otherwise."""
if not 200 <= response.status_code < 300:
message = response.text
try:
Expand All @@ -1323,6 +1328,124 @@ def _annotation_administration_response(response):
return response.json()


# ---------------------------------------------------------------------------
# Hosted auto-label endpoints
# ---------------------------------------------------------------------------


def _autolabel_response(response):
return _json_response_or_raise(response)


def list_autolabel_models(api_key, workspace_url):
"""Fetch the foundation-model catalog for hosted auto-labeling.

Calls ``GET /:workspace/autolabel/models``. Returns ``{models: [...]}``
where each entry carries ``id``, ``name``, ``guidance``, ``ontologyFormat``,
``creditsPerImage``, ``isDefault`` and per-workspace ``available`` (with an
``unavailableReason`` when the plan blocks a model).
"""
response = requests.get(
f"{API_URL}/{workspace_url}/autolabel/models",
params={"api_key": api_key},
)
return _autolabel_response(response)


def preview_autolabel(
api_key,
workspace_url,
project_url,
*,
model_type,
image,
ontology=None,
confidence_threshold=None,
):
"""Preview one image with a foundation model before starting a job.

Calls ``POST /:workspace/:project/autolabel/preview``. Free: no job is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not totally true, we charge for third-party APIs.

created and no credits are spent. ``image`` is
``{"type": "url" | "base64", "value": ...}`` and ``ontology`` is keyed by
prompt: ``{"kitten": "cat"}`` labels prompt matches as class ``cat``.
Returns
``{model, predictions, summary, blockErrors?}``.
"""
payload = {"modelType": model_type, "image": image}
if ontology is not None:
payload["ontology"] = ontology
if confidence_threshold is not None:
payload["confidenceThreshold"] = confidence_threshold
response = requests.post(
f"{API_URL}/{workspace_url}/{project_url}/autolabel/preview",
params={"api_key": api_key},
json=payload,
)
return _autolabel_response(response)


def start_autolabel_job(
api_key,
workspace_url,
project_url,
*,
batch_id,
model_type,
ontology=None,
num_images_to_label=None,
default_confidence=None,
confidence_thresholds=None,
run_nms=None,
reviewer_email=None,
model_options=None,
preserve_existing_annotations=None,
):
"""Start a hosted auto-label job over a batch.

Calls ``POST /:workspace/:project/autolabel``. ``model_type`` is sent
as-is: a catalog id from ``list_autolabel_models`` (for example
``gpt-6-astra-boxes`` or ``sam3-rle``) or ``custom_roboflow`` with the
Roboflow model id in ``model_options["modelId"]``. ``ontology`` is keyed
by prompt: ``{"kitten": "cat"}`` labels prompt matches as class ``cat``.
The backend fans
``default_confidence`` out across the ontology when
``confidence_thresholds`` is omitted and defaults ``num_images_to_label``
to the whole batch. ``preserve_existing_annotations`` keeps annotations
already on the images and only adds new ones; the server default (False)
replaces them. Returns ``{jobId, annotationJobId, message}``.
"""
payload = {"batchId": batch_id, "modelType": model_type}
optional = {
"ontology": ontology,
"numImagesToLabel": num_images_to_label,
"defaultConfidence": default_confidence,
"confidenceThresholds": confidence_thresholds,
"runNMS": run_nms,
"reviewerEmail": reviewer_email,
"modelOptions": model_options,
"preserveExistingAnnotations": preserve_existing_annotations,
}
payload.update({key: value for key, value in optional.items() if value is not None})
response = requests.post(
f"{API_URL}/{workspace_url}/{project_url}/autolabel",
params={"api_key": api_key},
json=payload,
)
return _autolabel_response(response)


def get_autolabel_job(api_key, workspace_url, job_id):
"""Fetch per-subjob status and progress for a hosted auto-label job.

Calls ``GET /:workspace/autolabel/jobs/:jobId``.
"""
response = requests.get(
f"{API_URL}/{workspace_url}/autolabel/jobs/{job_id}",
params={"api_key": api_key},
)
return _autolabel_response(response)


# ---------------------------------------------------------------------------
# Phase 2: Folder (project group) endpoints
# ---------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions roboflow/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def _walk(group: Any, prefix: str = "") -> None:
from roboflow.cli.handlers.api_key import api_key_app # noqa: E402
from roboflow.cli.handlers.asynctasks import asynctasks_app # noqa: E402
from roboflow.cli.handlers.auth import auth_app # noqa: E402
from roboflow.cli.handlers.autolabel import autolabel_app # noqa: E402
from roboflow.cli.handlers.batch import batch_app # noqa: E402
from roboflow.cli.handlers.completion import completion_app # noqa: E402
from roboflow.cli.handlers.deployment import deployment_app # noqa: E402
Expand All @@ -213,6 +214,7 @@ def _walk(group: Any, prefix: str = "") -> None:
app.add_typer(api_key_app, name="api-key")
app.add_typer(asynctasks_app, name="asynctasks")
app.add_typer(auth_app, name="auth")
app.add_typer(autolabel_app, name="autolabel")
app.add_typer(batch_app, name="batch")
app.add_typer(completion_app, name="completion")
app.add_typer(deployment_app, name="deployment")
Expand Down
28 changes: 28 additions & 0 deletions roboflow/cli/_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,31 @@ def resolve_ws_and_key(args) -> Optional[Tuple[str, str]]:
return None

return ws, api_key


def resolve_project_context(args) -> Optional[Tuple[str, str, str]]:
"""Resolve API key, workspace and project from CLI args.

Parses ``args.project`` (any ``resolve_resource`` shorthand, honouring
``args.workspace`` as an override) and loads the API key for that
workspace. Returns ``(api_key, workspace_url, project_slug)`` or ``None``
after calling ``output_error`` on failure; a missing key exits with the
auth code (2), matching ``resolve_ws_and_key``.
"""
from roboflow.cli._output import output_error
from roboflow.config import load_roboflow_api_key

try:
workspace, project, _version = resolve_resource(
args.project, workspace_override=getattr(args, "workspace", None)
)
except ValueError as exc:
output_error(args, str(exc))
return None

api_key = getattr(args, "api_key", None) or load_roboflow_api_key(workspace)
if not api_key:
output_error(args, "No API key found.", hint="Set ROBOFLOW_API_KEY or run 'roboflow auth login'.", exit_code=2)
return None

return api_key, workspace, project
15 changes: 2 additions & 13 deletions roboflow/cli/handlers/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,20 +465,9 @@ def job_delete_annotations(


def _resolve_project_context(args: Any) -> Optional[tuple[str, str, str]]:
from roboflow.cli._output import output_error
from roboflow.cli._resolver import resolve_resource
from roboflow.config import load_roboflow_api_key
from roboflow.cli._resolver import resolve_project_context

try:
workspace, project, _version = resolve_resource(args.project, workspace_override=args.workspace)
except ValueError as exc:
output_error(args, str(exc))
return None
api_key = args.api_key or load_roboflow_api_key(workspace)
if not api_key:
output_error(args, "No API key found.", hint="Set ROBOFLOW_API_KEY or run 'roboflow auth login'.", exit_code=2)
return None
return api_key, workspace, project
return resolve_project_context(args)


def _call(args: Any, operation: Callable[[str, str, str], Any]) -> Any:
Expand Down
Loading
Loading