programasweights 0.4.4__tar.gz → 0.4.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- programasweights-0.4.5/.github/workflows/test.yml +82 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/CHANGELOG.md +14 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/PKG-INFO +16 -3
- {programasweights-0.4.4 → programasweights-0.4.5}/PYPI_README.md +14 -1
- {programasweights-0.4.4 → programasweights-0.4.5}/README.md +14 -1
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/api-reference/python-sdk.md +90 -5
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/__init__.py +45 -22
- programasweights-0.4.5/programasweights/_program_reference.py +61 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/client.py +11 -7
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/convert_peft_to_paw.py +12 -14
- programasweights-0.4.5/programasweights/errors.py +79 -0
- programasweights-0.4.5/programasweights/local_program.py +196 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/pyproject.toml +1 -1
- programasweights-0.4.5/tests/test_api_errors.py +222 -0
- programasweights-0.4.5/tests/test_compile_timeouts.py +129 -0
- programasweights-0.4.5/tests/test_local_program.py +676 -0
- programasweights-0.4.4/.github/workflows/test.yml +0 -43
- {programasweights-0.4.4 → programasweights-0.4.5}/.gitignore +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/.readthedocs.yaml +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/AGENTS.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/LICENSE +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/adr/001-llama-cpp-over-pytorch.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/adr/002-q4_0-adapter-format.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/adr/003-single-spec-field.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/adr/004-compiler-naming.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/adr/005-vllm-hidden-states.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/adr/006-email-api-key-auth.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/advanced/adrs.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/advanced/architecture.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/api-reference/cli.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/api-reference/rest-api.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/architecture.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/case-studies/alien-taboo.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/case-studies/log-monitoring.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/case-studies/semantic-search.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/case-studies/site-navigation.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/case-studies/tool-calling.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/getting-started/first-program.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/getting-started/installation.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/getting-started/naming-programs.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/guide/browser-inference.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/guide/how-it-works.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/guide/local-inference.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/guide/writing-good-specs.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/hub/browsing-programs.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/hub/feedback-cases.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/hub/publishing-programs.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/index.md +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/docs/requirements.txt +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/examples/flask_app.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/examples/jupyter_notebook.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/examples/langchain_integration.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/examples/paw_monitor.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/examples/replace_openai.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/mkdocs.yml +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/_output.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/artifacts.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/cache.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/cli.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/compiler/__init__.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/compiler/dummy.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/config.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/paw_format.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/runtime/__init__.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/runtime/interpreter.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/runtime/interpreter_onnx.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/programasweights/runtime_llamacpp.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/tests/test_base_interpreter.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/tests/test_cli_auth.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/tests/test_desktop_sdk.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/tests/test_offline_cache.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/tests/test_runtime_registry_sdk.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/tests/test_sdk.py +0 -0
- {programasweights-0.4.4 → programasweights-0.4.5}/tests/test_sdk.sh +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install (hermetic deps only)
|
|
24
|
+
# Install httpx + pytest and the package itself without pulling the heavy
|
|
25
|
+
# llama-cpp-python build. Runtime tests inject a fake llama_cpp module,
|
|
26
|
+
# so CI needs neither the native extension nor a model download.
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
python -m pip install httpx pytest
|
|
30
|
+
python -m pip install -e . --no-deps
|
|
31
|
+
|
|
32
|
+
- name: Run hermetic tests
|
|
33
|
+
# Scoped to tests that need no network, no model download, and no
|
|
34
|
+
# PAW_API_KEY. Auth tests (@needs_auth) auto-skip without a key; the
|
|
35
|
+
# network/model-download tests in test_sdk.py are excluded here and can
|
|
36
|
+
# be run separately against a live server.
|
|
37
|
+
run: |
|
|
38
|
+
pytest \
|
|
39
|
+
tests/test_api_errors.py \
|
|
40
|
+
tests/test_compile_timeouts.py \
|
|
41
|
+
tests/test_local_program.py \
|
|
42
|
+
tests/test_base_interpreter.py \
|
|
43
|
+
tests/test_cli_auth.py \
|
|
44
|
+
tests/test_desktop_sdk.py \
|
|
45
|
+
tests/test_runtime_registry_sdk.py \
|
|
46
|
+
tests/test_sdk.py::TestInstallAndImport
|
|
47
|
+
|
|
48
|
+
local-files-windows:
|
|
49
|
+
runs-on: windows-latest
|
|
50
|
+
strategy:
|
|
51
|
+
fail-fast: false
|
|
52
|
+
matrix:
|
|
53
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v4
|
|
56
|
+
- uses: actions/setup-python@v5
|
|
57
|
+
with:
|
|
58
|
+
python-version: ${{ matrix.python-version }}
|
|
59
|
+
- name: Install hermetic test dependencies
|
|
60
|
+
run: |
|
|
61
|
+
python -m pip install httpx pytest
|
|
62
|
+
python -m pip install -e . --no-deps
|
|
63
|
+
- name: Test Windows local paths, cache locks, and compile errors
|
|
64
|
+
run: python -m pytest tests/test_api_errors.py tests/test_compile_timeouts.py tests/test_local_program.py --junitxml=test-results.xml
|
|
65
|
+
- name: Annotate Windows test failures
|
|
66
|
+
if: failure()
|
|
67
|
+
shell: python
|
|
68
|
+
run: |
|
|
69
|
+
from pathlib import Path
|
|
70
|
+
import xml.etree.ElementTree as ET
|
|
71
|
+
|
|
72
|
+
report = Path("test-results.xml")
|
|
73
|
+
if report.exists():
|
|
74
|
+
for case in ET.parse(report).iter("testcase"):
|
|
75
|
+
for result in case:
|
|
76
|
+
if result.tag in {"failure", "error"}:
|
|
77
|
+
# GitHub truncates annotations, so retain the actual
|
|
78
|
+
# exception at the end of long pytest tracebacks.
|
|
79
|
+
detail = result.text or result.get("message", "")
|
|
80
|
+
message = f"{case.get('name')}: {detail[-3500:]}"
|
|
81
|
+
message = message.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
|
|
82
|
+
print(f"::error::{message}")
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.5 (2026-09-10)
|
|
4
|
+
|
|
5
|
+
- Expose structured compile API failures as `paw.APIError`, compatible with
|
|
6
|
+
`httpx.HTTPStatusError`, preserving server code, message, request ID, and the
|
|
7
|
+
original response. Transport errors propagate unchanged; no automatic retries.
|
|
8
|
+
- Allow synchronous compilation a 2,400-second read timeout while keeping
|
|
9
|
+
connect, write, and pool timeouts at 120 seconds. Async submission stays at
|
|
10
|
+
30 seconds; precheck, status, and cancellation stay at 10 seconds.
|
|
11
|
+
- Load current GGUF ZIP `.paw` files through `paw.function` using explicit
|
|
12
|
+
local paths or `Path` objects. Validated imports use a separate SHA-256 cache
|
|
13
|
+
without replacing Hub caches or source files; invalid local files never fall
|
|
14
|
+
back to Hub lookup. Shared base-model assets may still need downloading unless
|
|
15
|
+
offline mode is requested. Legacy tensor-format `.paw` files are unsupported.
|
|
16
|
+
|
|
3
17
|
## 0.4.4 (2026-07-18)
|
|
4
18
|
|
|
5
19
|
- Add desktop preparation and cache inspection APIs with structured progress:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: programasweights
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
4
4
|
Summary: Compile natural language specifications into neural programs that run locally via llama.cpp.
|
|
5
5
|
Project-URL: Homepage, https://programasweights.com
|
|
6
6
|
Project-URL: Repository, https://github.com/programasweights/programasweights-python
|
|
@@ -102,6 +102,19 @@ finetune compiles can be queued with
|
|
|
102
102
|
`paw.compile_async(spec, compiler="paw-ft-bs48")`; an explicit finetune
|
|
103
103
|
compiler is required.
|
|
104
104
|
|
|
105
|
+
Load a saved current GGUF ZIP `.paw` bundle directly (SDK 0.4.5+):
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from pathlib import Path
|
|
109
|
+
fn = paw.function(Path("classifier.paw"))
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Local files are validated into a separate SHA-256 cache without changing the
|
|
113
|
+
source or falling back to Hub lookup. Runtime metadata or the shared base model
|
|
114
|
+
may still download; `offline=True` prohibits those requests. Legacy tensor-format
|
|
115
|
+
`.paw` files are unsupported. Local-file inputs are supported by `function`,
|
|
116
|
+
not `prepare_program` or `is_offline_ready`.
|
|
117
|
+
|
|
105
118
|
Advanced adapter-free inference is available with
|
|
106
119
|
`paw.function(None, interpreter="gpt2")`; see the Python API reference for
|
|
107
120
|
its intentionally strict semantics.
|
|
@@ -179,4 +192,4 @@ paw login
|
|
|
179
192
|
|
|
180
193
|
## License
|
|
181
194
|
|
|
182
|
-
MIT
|
|
195
|
+
MIT
|
|
@@ -71,6 +71,19 @@ finetune compiles can be queued with
|
|
|
71
71
|
`paw.compile_async(spec, compiler="paw-ft-bs48")`; an explicit finetune
|
|
72
72
|
compiler is required.
|
|
73
73
|
|
|
74
|
+
Load a saved current GGUF ZIP `.paw` bundle directly (SDK 0.4.5+):
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from pathlib import Path
|
|
78
|
+
fn = paw.function(Path("classifier.paw"))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Local files are validated into a separate SHA-256 cache without changing the
|
|
82
|
+
source or falling back to Hub lookup. Runtime metadata or the shared base model
|
|
83
|
+
may still download; `offline=True` prohibits those requests. Legacy tensor-format
|
|
84
|
+
`.paw` files are unsupported. Local-file inputs are supported by `function`,
|
|
85
|
+
not `prepare_program` or `is_offline_ready`.
|
|
86
|
+
|
|
74
87
|
Advanced adapter-free inference is available with
|
|
75
88
|
`paw.function(None, interpreter="gpt2")`; see the Python API reference for
|
|
76
89
|
its intentionally strict semantics.
|
|
@@ -148,4 +161,4 @@ paw login
|
|
|
148
161
|
|
|
149
162
|
## License
|
|
150
163
|
|
|
151
|
-
MIT
|
|
164
|
+
MIT
|
|
@@ -71,6 +71,19 @@ finetune compiles can be queued with
|
|
|
71
71
|
`paw.compile_async(spec, compiler="paw-ft-bs48")`; an explicit finetune
|
|
72
72
|
compiler is required.
|
|
73
73
|
|
|
74
|
+
Load a saved current GGUF ZIP `.paw` bundle directly (SDK 0.4.5+):
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from pathlib import Path
|
|
78
|
+
fn = paw.function(Path("classifier.paw"))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Local files are validated into a separate SHA-256 cache without changing the
|
|
82
|
+
source or falling back to Hub lookup. Runtime metadata or the shared base model
|
|
83
|
+
may still download; `offline=True` prohibits those requests. Legacy tensor-format
|
|
84
|
+
`.paw` files are unsupported. Local-file inputs are supported by `function`,
|
|
85
|
+
not `prepare_program` or `is_offline_ready`.
|
|
86
|
+
|
|
74
87
|
Advanced adapter-free inference is available with
|
|
75
88
|
`paw.function(None, interpreter="gpt2")`; see the
|
|
76
89
|
[Python API reference](docs/api-reference/python-sdk.md#advanced-adapter-free-base-interpreter)
|
|
@@ -149,4 +162,4 @@ paw login
|
|
|
149
162
|
|
|
150
163
|
## License
|
|
151
164
|
|
|
152
|
-
MIT
|
|
165
|
+
MIT
|
|
@@ -28,15 +28,17 @@ fn = paw.function(
|
|
|
28
28
|
)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Loads a compiled program and returns a callable.
|
|
31
|
+
Loads a compiled program and returns a callable. Hub references download the
|
|
32
|
+
program and base model on first use; local `.paw` files supply the program
|
|
33
|
+
bundle directly. Required runtime metadata and base models are cached for reuse.
|
|
32
34
|
|
|
33
35
|
| Parameter | Description |
|
|
34
36
|
|-----------|-------------|
|
|
35
|
-
| `program_id` | Required. A `Program` object, hash ID (e.g. `a6b454023d41ac9ca845`), slug (e.g. `da03/my-classifier`),
|
|
37
|
+
| `program_id` | Required. A `Program` object, hash ID (e.g. `a6b454023d41ac9ca845`), slug (e.g. `da03/my-classifier`), official shorthand (e.g. `email-triage`), or local `.paw` path (see below). A `Program` resolves by immutable `id`, not its mutable slug. |
|
|
36
38
|
| `n_ctx` | Context length for the local runtime (default `2048`). |
|
|
37
39
|
| `n_gpu_layers` | GPU layers to offload (`0` = CPU-only, `-1` = all). The default is `-1`, or `PAW_GPU_LAYERS` when set. |
|
|
38
40
|
| `verbose` | Enable verbose logging (default `False`). |
|
|
39
|
-
| `offline` |
|
|
41
|
+
| `offline` | Use only local files/cache and make zero network calls; fail if required validated assets are missing. `PAW_OFFLINE=1` has the same effect. |
|
|
40
42
|
| `interpreter` | Advanced adapter-free mode only. Must be passed by keyword and only when `program_id` is explicitly `None`. Supported values are `Qwen/Qwen3-0.6B` and `gpt2`. |
|
|
41
43
|
|
|
42
44
|
The returned callable:
|
|
@@ -54,7 +56,7 @@ output: str = fn(input_text, max_tokens=None, temperature=0.0)
|
|
|
54
56
|
**Context limits:** Spec + input + output share a ~2048 token window. Inputs that exceed it will error. `max_tokens` defaults to `None`: generation runs until EOS or the context limit.
|
|
55
57
|
|
|
56
58
|
Compiled mode is strict: the adapter, prompt template, matching metadata,
|
|
57
|
-
runtime manifest, and runtime-compatible base-model file must all validate. Version 0.4.
|
|
59
|
+
runtime manifest, and runtime-compatible base-model file must all validate. Version 0.4.5
|
|
58
60
|
accepts runtime manifest version 1 with `adapter_format="gguf_lora"`.
|
|
59
61
|
Built-in models are checked against pinned size/SHA-256 metadata and GGUF
|
|
60
62
|
magic. Historical manifests for those known runtime IDs are normalized to the
|
|
@@ -62,6 +64,45 @@ same canonical integrity metadata, so missing server-side checksum fields
|
|
|
62
64
|
cannot weaken validation. Missing or failed adapters raise an error; the SDK
|
|
63
65
|
never silently falls back to an unadapted base model.
|
|
64
66
|
|
|
67
|
+
### Loading a local `.paw` file
|
|
68
|
+
|
|
69
|
+
Version 0.4.5 adds local-file inputs to `paw.function`:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from pathlib import Path
|
|
73
|
+
|
|
74
|
+
fn = paw.function(Path("classifier.paw"))
|
|
75
|
+
# With the required runtime metadata and base model already available locally:
|
|
76
|
+
fn = paw.function("./classifier.paw", offline=True)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use a current GGUF ZIP `.paw` bundle, such as one downloaded from a hosted
|
|
80
|
+
compile. It must contain `meta.json`, `adapter.gguf`, and `prompt_template.txt`,
|
|
81
|
+
with only `pseudo_program.txt` allowed as an optional extra; serialized native
|
|
82
|
+
prefix state is not accepted from archives. Local inputs are selected
|
|
83
|
+
deterministically: a `Path`/`os.PathLike`
|
|
84
|
+
object, an explicit path such as `./classifier.paw` or an absolute path, or a
|
|
85
|
+
string ending in `.paw` (case-insensitive). Ordinary IDs and slugs such as
|
|
86
|
+
`da03/my-classifier` keep their existing Hub behavior even if a matching local
|
|
87
|
+
file exists. Use `Path(...)` or an explicit path for a filename without the
|
|
88
|
+
`.paw` suffix. URL inputs are unsupported.
|
|
89
|
+
|
|
90
|
+
The bundle is validated and imported under
|
|
91
|
+
`PAW_CACHE_DIR/local_programs/<archive-sha256>` (default cache root:
|
|
92
|
+
`~/.cache/programasweights`). Its source file is unchanged, and its metadata
|
|
93
|
+
cannot replace a Hub program-ID or slug cache. Missing or invalid local files
|
|
94
|
+
raise an error without falling back to a Hub lookup or program download.
|
|
95
|
+
|
|
96
|
+
A local program does not necessarily make the first load fully offline:
|
|
97
|
+
the existing runtime policy may fetch required runtime metadata from PAW and
|
|
98
|
+
download the shared base model. Pass `offline=True` or set `PAW_OFFLINE=1`
|
|
99
|
+
to prohibit all network access. Historical `PAW\x02` tensor containers,
|
|
100
|
+
including output from the legacy `convert_peft_to_paw` module, are not supported
|
|
101
|
+
by this loader; it does not convert PEFT tensors to GGUF.
|
|
102
|
+
|
|
103
|
+
Only `paw.function` gains local-file inputs. `prepare_program` and
|
|
104
|
+
`is_offline_ready` continue to accept Hub program references.
|
|
105
|
+
|
|
65
106
|
### Advanced: adapter-free base interpreter
|
|
66
107
|
|
|
67
108
|
Pass explicit `None` plus an interpreter to run the supported base GGUF without a compiled PAW program:
|
|
@@ -155,11 +196,24 @@ Compiles a natural language spec on the server. Returns a `Program` object.
|
|
|
155
196
|
|-----------|-------------|
|
|
156
197
|
| `id` | Hash-based program identifier. Use with `paw.function(program.id)`. |
|
|
157
198
|
| `slug` | Full slug handle (e.g. `da03/my-classifier`) if one was created, `None` otherwise. |
|
|
158
|
-
| `status` | `"ready"` on success
|
|
199
|
+
| `status` | Status returned by the server, normally `"ready"` on success. HTTP errors raise `APIError` instead of returning a failed `Program`. |
|
|
159
200
|
| `compiler_snapshot` | Exact compiler version used. |
|
|
160
201
|
| `timings` | Timing metadata from the server. |
|
|
161
202
|
| `error` | Error message when compilation fails. |
|
|
162
203
|
|
|
204
|
+
### Compile timeouts
|
|
205
|
+
|
|
206
|
+
Synchronous `compile` uses `httpx.Timeout(120.0, read=2400.0)`: connect, write,
|
|
207
|
+
and pool waits remain 120 seconds; the read timeout is 2,400 seconds. This
|
|
208
|
+
allows for the origin's 1,900-second provider wait plus up to 330 seconds of
|
|
209
|
+
artifact finalization. It is a timeout while waiting for response data, **not a
|
|
210
|
+
40-minute total deadline or guarantee**; upstream services may fail earlier.
|
|
211
|
+
The same setting applies to the compile step of `compile_and_load`.
|
|
212
|
+
|
|
213
|
+
Async submission retains a 30-second timeout. Precheck, status polling, and
|
|
214
|
+
cancellation each retain a 10-second timeout. For long finetunes, prefer the
|
|
215
|
+
explicit async workflow below so you retain a job ID for later status checks.
|
|
216
|
+
|
|
163
217
|
## Long-running compile jobs
|
|
164
218
|
|
|
165
219
|
The asynchronous compile endpoint is available through both `PAWClient` and top-level helpers:
|
|
@@ -183,6 +237,37 @@ Status and cancellation requests must use the same authenticated account as
|
|
|
183
237
|
submission. Anonymous jobs are bound to the validated client IP that submitted
|
|
184
238
|
them.
|
|
185
239
|
|
|
240
|
+
### Compile API errors
|
|
241
|
+
|
|
242
|
+
`compile`, `precheck_compile`, `compile_async`, `get_compile_status`, and
|
|
243
|
+
`cancel_compile` raise `paw.APIError` for HTTP 4xx/5xx responses. It is a subclass
|
|
244
|
+
of `httpx.HTTPStatusError`, so existing handlers continue to work. When supplied
|
|
245
|
+
by the server, `code`, `message`, and `request_id` are available as attributes
|
|
246
|
+
and included in the exception text. Missing fields are `None`; the original
|
|
247
|
+
`request` and `response` remain available, including response headers and body.
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
try:
|
|
251
|
+
job = paw.compile_async(SPEC, compiler="paw-ft-bs48")
|
|
252
|
+
except paw.APIError as error:
|
|
253
|
+
print(error.code, error.message, error.request_id)
|
|
254
|
+
# error.response.status_code and error.response.headers are unchanged.
|
|
255
|
+
raise
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
For example, a `durable_queue_unavailable` 503 reports that durable Redis must
|
|
259
|
+
be healthy before async compilation can proceed. That rejection occurs before
|
|
260
|
+
the job is accepted; the caller can submit again after service recovery.
|
|
261
|
+
The SDK does not automatically retry compilation requests: other failures may
|
|
262
|
+
occur after a job has already been recorded. Invalid/non-JSON error bodies
|
|
263
|
+
retain the ordinary HTTP error description rather than displaying raw content.
|
|
264
|
+
|
|
265
|
+
Transport errors such as `httpx.ReadTimeout` propagate unchanged, rather than
|
|
266
|
+
becoming `APIError`. A timeout does not prove the server rejected or cancelled
|
|
267
|
+
the work, so the SDK does not automatically resubmit it. Both `paw.compile`
|
|
268
|
+
and `paw.compile_and_load` propagate these errors; `compile_and_load` does not
|
|
269
|
+
attempt to load a function when compilation raises.
|
|
270
|
+
|
|
186
271
|
## `paw.compile_and_load`
|
|
187
272
|
|
|
188
273
|
```python
|
|
@@ -27,7 +27,7 @@ try:
|
|
|
27
27
|
from importlib.metadata import version as _meta_version
|
|
28
28
|
__version__ = _meta_version("programasweights")
|
|
29
29
|
except Exception:
|
|
30
|
-
__version__ = "0.4.
|
|
30
|
+
__version__ = "0.4.5"
|
|
31
31
|
|
|
32
32
|
from ._output import ProgressCallback, ProgressEvent, report_progress
|
|
33
33
|
from .cache import CachedProgram
|
|
@@ -39,6 +39,7 @@ from .client import (
|
|
|
39
39
|
Program,
|
|
40
40
|
)
|
|
41
41
|
from .config import get_api_url, get_api_key, set_api_key
|
|
42
|
+
from .errors import APIError
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
def compile(
|
|
@@ -400,18 +401,22 @@ def function(
|
|
|
400
401
|
):
|
|
401
402
|
"""Load a compiled program, or explicitly load a bare base interpreter.
|
|
402
403
|
|
|
403
|
-
|
|
404
|
-
|
|
404
|
+
Hub references download the .paw bundle on first use; local paths supply
|
|
405
|
+
it directly. Required runtime metadata and base models may still download
|
|
406
|
+
unless offline mode is enabled. Subsequent calls reuse validated caches.
|
|
405
407
|
|
|
406
408
|
Args:
|
|
407
409
|
program_id: Program ID (str), slug (``da03/my-program``), pinned version
|
|
408
|
-
(``da03/my-program@v3``),
|
|
410
|
+
(``da03/my-program@v3``), a ``Program`` object from compile(), or a
|
|
411
|
+
local GGUF-based .paw bundle. PathLike objects and explicit path
|
|
412
|
+
strings (including strings ending in .paw) select local files.
|
|
409
413
|
n_ctx: Context window size for llama.cpp.
|
|
410
414
|
n_gpu_layers: GPU layers (-1 = all GPU, 0 = CPU only). Defaults to -1
|
|
411
415
|
(auto-uses Metal/CUDA if available, safe fallback to CPU).
|
|
412
416
|
Set ``PAW_GPU_LAYERS=0`` env var to force CPU-only.
|
|
413
417
|
verbose: Print llama.cpp debug output.
|
|
414
|
-
offline:
|
|
418
|
+
offline: Prohibit network access. Local bundles may be imported, but
|
|
419
|
+
their runtime and base model must already be available locally.
|
|
415
420
|
Also set via ``PAW_OFFLINE=1`` env var.
|
|
416
421
|
interpreter: Advanced adapter-free mode. This is only valid when
|
|
417
422
|
``program_id`` is explicitly ``None``. Initially supported values
|
|
@@ -427,6 +432,8 @@ def function(
|
|
|
427
432
|
|
|
428
433
|
>>> fn = paw.function("da03/my-program@v2") # pinned version
|
|
429
434
|
|
|
435
|
+
>>> fn = paw.function("./classifier.paw") # local bundle
|
|
436
|
+
|
|
430
437
|
>>> base = paw.function(None, interpreter="gpt2")
|
|
431
438
|
"""
|
|
432
439
|
import os
|
|
@@ -452,7 +459,12 @@ def function(
|
|
|
452
459
|
offline=offline,
|
|
453
460
|
)
|
|
454
461
|
|
|
455
|
-
|
|
462
|
+
from ._program_reference import local_program_path
|
|
463
|
+
|
|
464
|
+
local_path = local_program_path(program_id)
|
|
465
|
+
program_reference = (
|
|
466
|
+
_coerce_program_reference(program_id) if local_path is None else None
|
|
467
|
+
)
|
|
456
468
|
if program_reference == "":
|
|
457
469
|
raise ValueError(
|
|
458
470
|
"program_id cannot be an empty string; pass explicit None with "
|
|
@@ -464,25 +476,35 @@ def function(
|
|
|
464
476
|
"program_id=None to request adapter-free base mode."
|
|
465
477
|
)
|
|
466
478
|
|
|
467
|
-
|
|
479
|
+
if local_path is not None:
|
|
480
|
+
from .local_program import import_local_program
|
|
468
481
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
from .
|
|
482
|
+
# Validate explicit local input before importing the native runtime.
|
|
483
|
+
# A missing/corrupt file is never retried as a Hub ID or slug.
|
|
484
|
+
program_dir = import_local_program(local_path)
|
|
485
|
+
from .runtime_llamacpp import PawFunction
|
|
486
|
+
else:
|
|
487
|
+
# Preserve the existing Hub path's fail-fast dependency check before
|
|
488
|
+
# resolving slugs or downloading assets.
|
|
489
|
+
from .runtime_llamacpp import PawFunction
|
|
477
490
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
491
|
+
resolved_id = _resolve_program_id(program_reference, offline=offline)
|
|
492
|
+
if offline and not cache.has_valid_program_assets(resolved_id):
|
|
493
|
+
raise RuntimeError(
|
|
494
|
+
f"Program {resolved_id} is not fully cached; offline mode "
|
|
495
|
+
"prohibits program downloads."
|
|
496
|
+
)
|
|
497
|
+
if not cache.has_valid_program_assets(resolved_id):
|
|
498
|
+
from .client import PAWClient
|
|
499
|
+
|
|
500
|
+
client = PAWClient(api_url=get_api_url(), api_key=get_api_key())
|
|
501
|
+
client.download_paw(resolved_id)
|
|
502
|
+
if not cache.has_valid_program_assets(resolved_id):
|
|
503
|
+
raise RuntimeError(
|
|
504
|
+
f"Program {resolved_id} is missing valid compiled assets."
|
|
505
|
+
)
|
|
506
|
+
program_dir = cache.get_program_dir(resolved_id)
|
|
484
507
|
|
|
485
|
-
program_dir = cache.get_program_dir(resolved_id)
|
|
486
508
|
return PawFunction(
|
|
487
509
|
program_dir,
|
|
488
510
|
n_ctx=n_ctx,
|
|
@@ -616,6 +638,7 @@ def list_compilers() -> list[dict]:
|
|
|
616
638
|
|
|
617
639
|
|
|
618
640
|
__all__ = [
|
|
641
|
+
"APIError",
|
|
619
642
|
"CachedProgram",
|
|
620
643
|
"CompileCancellation",
|
|
621
644
|
"CompileJob",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Deterministic local-file intent, independent of filesystem contents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_WINDOWS_DRIVE = re.compile(r"^[A-Za-z]:")
|
|
11
|
+
_URI_SCHEME = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def local_program_path(reference: object) -> Path | None:
|
|
15
|
+
"""Return an explicit local path, or None for an ID/slug/Program.
|
|
16
|
+
|
|
17
|
+
Path-like objects always mean files. Strings select files by syntax, never
|
|
18
|
+
by existence; in particular, an ``owner/slug`` remains a Hub reference.
|
|
19
|
+
"""
|
|
20
|
+
is_pathlike = isinstance(reference, os.PathLike)
|
|
21
|
+
if is_pathlike:
|
|
22
|
+
value = os.fspath(reference)
|
|
23
|
+
if not isinstance(value, str):
|
|
24
|
+
raise TypeError("Local program paths must be text, not bytes.")
|
|
25
|
+
elif isinstance(reference, str):
|
|
26
|
+
value = reference
|
|
27
|
+
else:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
if "\x00" in value:
|
|
31
|
+
raise ValueError("Program references cannot contain null bytes.")
|
|
32
|
+
windows_drive = bool(_WINDOWS_DRIVE.match(value))
|
|
33
|
+
if not is_pathlike and not windows_drive and _URI_SCHEME.match(value):
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"Program URLs are not supported. Download the .paw bundle first "
|
|
36
|
+
"and pass its local path."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
explicit_path = value.startswith(
|
|
40
|
+
("./", "../", "/", "~/", "\\", ".\\", "..\\", "~\\")
|
|
41
|
+
)
|
|
42
|
+
if not (
|
|
43
|
+
is_pathlike or windows_drive or explicit_path
|
|
44
|
+
or value.lower().endswith(".paw")
|
|
45
|
+
):
|
|
46
|
+
return None
|
|
47
|
+
if not value:
|
|
48
|
+
raise ValueError("Local program path cannot be empty.")
|
|
49
|
+
|
|
50
|
+
# A Windows-looking string must never be sent to the Hub on another OS.
|
|
51
|
+
# Path objects, however, explicitly name a native path and may contain
|
|
52
|
+
# characters that would have a different meaning on another platform.
|
|
53
|
+
windows_path = windows_drive or value.startswith(
|
|
54
|
+
("\\", ".\\", "..\\", "~\\")
|
|
55
|
+
)
|
|
56
|
+
if not is_pathlike and os.name != "nt" and windows_path:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"This is a Windows filesystem path, which cannot be opened on "
|
|
59
|
+
"this platform. Pass a native local path instead."
|
|
60
|
+
)
|
|
61
|
+
return Path(value).expanduser()
|
|
@@ -21,6 +21,7 @@ import httpx
|
|
|
21
21
|
|
|
22
22
|
from . import config
|
|
23
23
|
from ._output import ProgressCallback, report_progress
|
|
24
|
+
from .errors import raise_for_api_status
|
|
24
25
|
|
|
25
26
|
MAX_PAW_ARCHIVE_BYTES = 256 * 1024 * 1024
|
|
26
27
|
MAX_PAW_ARCHIVE_MEMBERS = 256
|
|
@@ -170,7 +171,7 @@ class PAWClient:
|
|
|
170
171
|
Program with id, slug, status, and timings.
|
|
171
172
|
|
|
172
173
|
Raises:
|
|
173
|
-
|
|
174
|
+
APIError: On API errors; also caught by httpx.HTTPStatusError.
|
|
174
175
|
"""
|
|
175
176
|
body = self._compile_body(
|
|
176
177
|
spec,
|
|
@@ -186,9 +187,12 @@ class PAWClient:
|
|
|
186
187
|
f"{self._api_url}/api/v1/compile",
|
|
187
188
|
json=body,
|
|
188
189
|
headers=self._headers(),
|
|
189
|
-
|
|
190
|
+
# Allow a long response wait (1900s provider + 330s finalization)
|
|
191
|
+
# without extending connect/write/pool timeouts beyond 120s.
|
|
192
|
+
# This is a read timeout, not a total deadline; prefer compile_async().
|
|
193
|
+
timeout=httpx.Timeout(120.0, read=2400.0),
|
|
190
194
|
)
|
|
191
|
-
resp
|
|
195
|
+
raise_for_api_status(resp)
|
|
192
196
|
data = resp.json()
|
|
193
197
|
|
|
194
198
|
return Program(
|
|
@@ -221,7 +225,7 @@ class PAWClient:
|
|
|
221
225
|
headers=self._headers(),
|
|
222
226
|
timeout=10.0,
|
|
223
227
|
)
|
|
224
|
-
resp
|
|
228
|
+
raise_for_api_status(resp)
|
|
225
229
|
return cast(CompilePrecheck, resp.json())
|
|
226
230
|
|
|
227
231
|
def compile_async(
|
|
@@ -260,7 +264,7 @@ class PAWClient:
|
|
|
260
264
|
headers=self._headers(),
|
|
261
265
|
timeout=30.0,
|
|
262
266
|
)
|
|
263
|
-
resp
|
|
267
|
+
raise_for_api_status(resp)
|
|
264
268
|
return cast(CompileJob, resp.json())
|
|
265
269
|
|
|
266
270
|
def get_compile_status(self, job_id: str) -> CompileStatus:
|
|
@@ -270,7 +274,7 @@ class PAWClient:
|
|
|
270
274
|
headers=self._headers(),
|
|
271
275
|
timeout=10.0,
|
|
272
276
|
)
|
|
273
|
-
resp
|
|
277
|
+
raise_for_api_status(resp)
|
|
274
278
|
return cast(CompileStatus, resp.json())
|
|
275
279
|
|
|
276
280
|
def cancel_compile(self, job_id: str) -> CompileCancellation:
|
|
@@ -283,7 +287,7 @@ class PAWClient:
|
|
|
283
287
|
headers=self._headers(),
|
|
284
288
|
timeout=10.0,
|
|
285
289
|
)
|
|
286
|
-
resp
|
|
290
|
+
raise_for_api_status(resp)
|
|
287
291
|
return cast(CompileCancellation, resp.json())
|
|
288
292
|
|
|
289
293
|
def resolve_slug(self, slug: str) -> str:
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Legacy PEFT/LoRA converter for the PAW-v2 tensor format.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
The resulting .paw file can be:
|
|
10
|
-
- Shared on the Program Hub
|
|
11
|
-
- Loaded with paw.function("output.paw")
|
|
12
|
-
- Distributed as a single file
|
|
4
|
+
This module writes the historical PAW\\x02 tensor container, not the current
|
|
5
|
+
GGUF ZIP .paw bundle. Its output is NOT supported by the modern public
|
|
6
|
+
paw.function local-file loader. It does not convert PEFT weights to GGUF, and
|
|
7
|
+
from_peft is not exported as paw.from_peft. Use a current hosted-compile bundle
|
|
8
|
+
for the public loader; this legacy converter is retained for older workflows.
|
|
13
9
|
"""
|
|
14
10
|
|
|
15
11
|
import json
|
|
@@ -34,7 +30,9 @@ def from_peft(
|
|
|
34
30
|
interpreter_model: Optional[str] = None,
|
|
35
31
|
) -> str:
|
|
36
32
|
"""
|
|
37
|
-
Convert a PEFT/LoRA adapter to .paw format.
|
|
33
|
+
Convert a PEFT/LoRA adapter to the legacy, non-GGUF .paw tensor format.
|
|
34
|
+
|
|
35
|
+
The output cannot be loaded by the modern public paw.function API.
|
|
38
36
|
|
|
39
37
|
Args:
|
|
40
38
|
adapter_path: Path to PEFT adapter directory (contains adapter_config.json + adapter_model.safetensors)
|
|
@@ -51,7 +49,7 @@ def from_peft(
|
|
|
51
49
|
Path to saved .paw file
|
|
52
50
|
|
|
53
51
|
Example:
|
|
54
|
-
>>>
|
|
52
|
+
>>> from programasweights.convert_peft_to_paw import from_peft
|
|
55
53
|
>>>
|
|
56
54
|
>>> # After training with PEFT:
|
|
57
55
|
>>> # from peft import get_peft_model, LoraConfig
|
|
@@ -59,8 +57,8 @@ def from_peft(
|
|
|
59
57
|
>>> # trainer.train()
|
|
60
58
|
>>> # model.save_pretrained("my_adapter/")
|
|
61
59
|
>>>
|
|
62
|
-
>>> # Convert
|
|
63
|
-
>>>
|
|
60
|
+
>>> # Convert for a legacy tensor-format consumer, not paw.function:
|
|
61
|
+
>>> from_peft(
|
|
64
62
|
... "my_adapter/",
|
|
65
63
|
... "sentiment.paw",
|
|
66
64
|
... spec="Classify text sentiment as positive, negative, or neutral",
|