hanzo-train 0.1.1 drives the training wire at /v1/train/clients.

This commit is contained in:
2026-09-28 22:11:20 -07:00
parent 875469edc7
commit 4dac77fe06
3 changed files with 14 additions and 14 deletions
+5 -5
View File
@@ -1,6 +1,6 @@
"""Tinker-shaped client for the Hanzo Engine training API.
"""Tinker-shaped client for Hanzo's training API, ``/v1/train/clients``.
The engine exposes a small LoRA training surface under ``/v1/training``. This
api.hanzo.ai and a self-hosted engine both serve it there. This
client mirrors the shapes of Thinking Machines' ``tinker`` SDK -- so training
loops written against tinker port across unchanged -- using Hanzo-canonical
field names and a single synchronous HTTP path (httpx, no retries).
@@ -208,7 +208,7 @@ class ServiceClient:
cfg = lora_config or LoraConfig()
data = self._request(
"POST",
"/v1/training/clients",
"/v1/train/clients",
{
"base_model": base_model,
"lora_config": {
@@ -224,7 +224,7 @@ class ServiceClient:
return tc
def list_training_clients(self) -> list[TrainingClientInfo]:
data = self._request("GET", "/v1/training/clients")
data = self._request("GET", "/v1/train/clients")
return [_info(c) for c in data["clients"]]
def close(self) -> None:
@@ -240,7 +240,7 @@ class TrainingClient:
self._poll_interval = poll_interval
def _path(self, suffix: str = "") -> str:
return f"/v1/training/clients/{self.id}{suffix}"
return f"/v1/train/clients/{self.id}{suffix}"
def forward_backward(
self, data: Iterable[Datum | dict[str, Any]]
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "hanzo-train"
version = "0.1.0"
version = "0.1.1"
description = "Hanzo Train — Tinker-shaped client for the Hanzo Engine training API"
readme = "README.md"
license = { text = "Apache-2.0" }
+8 -8
View File
@@ -58,7 +58,7 @@ def test_create_defaults_and_no_wait() -> None:
assert tc.id == "tc-abc"
assert seen["method"] == "POST"
assert seen["path"] == "/v1/training/clients"
assert seen["path"] == "/v1/train/clients"
assert seen["body"]["base_model"] == "m"
lora = seen["body"]["lora_config"]
assert lora["rank"] == 16
@@ -132,7 +132,7 @@ def test_forward_backward_prompt_form() -> None:
tc = TrainingClient(service(handler), "tc-abc")
out = tc.forward_backward([{"prompt": "2+2=", "completion": "4"}]).result()
assert seen["path"] == "/v1/training/clients/tc-abc/forward_backward"
assert seen["path"] == "/v1/train/clients/tc-abc/forward_backward"
assert seen["body"]["data"] == [{"prompt": "2+2=", "completion": "4"}]
assert out.loss == 1.5
assert out.num_tokens == 3
@@ -194,7 +194,7 @@ def test_optim_step_explicit_params() -> None:
tc = TrainingClient(service(handler), "tc-abc")
res = tc.optim_step(AdamParams(lr=1e-4)).result()
assert seen["path"] == "/v1/training/clients/tc-abc/optim_step"
assert seen["path"] == "/v1/train/clients/tc-abc/optim_step"
assert seen["body"]["adam_params"] == {
"lr": 1e-4,
"beta1": 0.9,
@@ -232,7 +232,7 @@ def test_sample_prompt_form() -> None:
prompt="2+2=", sampling_params=SamplingParams(max_tokens=8, temperature=0.0), num_samples=1
).result()
assert seen["path"] == "/v1/training/clients/tc-abc/sample"
assert seen["path"] == "/v1/train/clients/tc-abc/sample"
assert seen["body"]["prompt"] == "2+2="
assert "tokens" not in seen["body"]
assert seen["body"]["num_samples"] == 1
@@ -284,7 +284,7 @@ def test_save_weights_and_alias() -> None:
tc = TrainingClient(service(handler), "tc-abc")
saved = tc.save_weights_and_get_sampling_client(name="my-adapter").result()
assert seen["path"] == "/v1/training/clients/tc-abc/save_weights"
assert seen["path"] == "/v1/train/clients/tc-abc/save_weights"
assert seen["body"] == {"name": "my-adapter"}
assert saved.path == "/data/adapters/my-adapter"
assert saved.format == "peft"
@@ -297,7 +297,7 @@ def test_save_weights_and_alias() -> None:
def test_get_info() -> None:
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "GET"
assert request.url.path == "/v1/training/clients/tc-abc"
assert request.url.path == "/v1/train/clients/tc-abc"
return httpx.Response(
200,
json=info_json(
@@ -325,7 +325,7 @@ def test_get_info() -> None:
def test_list_training_clients() -> None:
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "GET"
assert request.url.path == "/v1/training/clients"
assert request.url.path == "/v1/train/clients"
return httpx.Response(
200,
json={
@@ -355,7 +355,7 @@ def test_delete() -> None:
assert result is None
assert seen["method"] == "DELETE"
assert seen["path"] == "/v1/training/clients/tc-abc"
assert seen["path"] == "/v1/train/clients/tc-abc"
def test_error_404() -> None: