programasweights 0.2.7__tar.gz → 0.2.8__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.
Files changed (62) hide show
  1. {programasweights-0.2.7 → programasweights-0.2.8}/AGENTS.md +8 -8
  2. {programasweights-0.2.7 → programasweights-0.2.8}/PKG-INFO +1 -1
  3. {programasweights-0.2.7 → programasweights-0.2.8}/docs/adr/001-llama-cpp-over-pytorch.md +1 -1
  4. {programasweights-0.2.7 → programasweights-0.2.8}/docs/api-reference/cli.md +1 -1
  5. {programasweights-0.2.7 → programasweights-0.2.8}/docs/api-reference/python-sdk.md +1 -1
  6. {programasweights-0.2.7 → programasweights-0.2.8}/docs/api-reference/rest-api.md +10 -2
  7. {programasweights-0.2.7 → programasweights-0.2.8}/docs/architecture.md +1 -1
  8. programasweights-0.2.8/docs/case-studies/log-monitoring.md +132 -0
  9. programasweights-0.2.8/docs/case-studies/tool-calling.md +477 -0
  10. programasweights-0.2.8/docs/guide/writing-good-specs.md +11 -0
  11. {programasweights-0.2.7 → programasweights-0.2.8}/mkdocs.yml +2 -0
  12. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/__init__.py +12 -2
  13. programasweights-0.2.8/programasweights/_output.py +34 -0
  14. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/cache.py +7 -4
  15. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/client.py +10 -0
  16. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/runtime_llamacpp.py +4 -0
  17. {programasweights-0.2.7 → programasweights-0.2.8}/pyproject.toml +1 -1
  18. programasweights-0.2.7/docs/guide/writing-good-specs.md +0 -36
  19. {programasweights-0.2.7 → programasweights-0.2.8}/.gitignore +0 -0
  20. {programasweights-0.2.7 → programasweights-0.2.8}/.readthedocs.yaml +0 -0
  21. {programasweights-0.2.7 → programasweights-0.2.8}/CHANGELOG.md +0 -0
  22. {programasweights-0.2.7 → programasweights-0.2.8}/LICENSE +0 -0
  23. {programasweights-0.2.7 → programasweights-0.2.8}/PYPI_README.md +0 -0
  24. {programasweights-0.2.7 → programasweights-0.2.8}/README.md +0 -0
  25. {programasweights-0.2.7 → programasweights-0.2.8}/docs/adr/002-q4_0-adapter-format.md +0 -0
  26. {programasweights-0.2.7 → programasweights-0.2.8}/docs/adr/003-single-spec-field.md +0 -0
  27. {programasweights-0.2.7 → programasweights-0.2.8}/docs/adr/004-compiler-naming.md +0 -0
  28. {programasweights-0.2.7 → programasweights-0.2.8}/docs/adr/005-vllm-hidden-states.md +0 -0
  29. {programasweights-0.2.7 → programasweights-0.2.8}/docs/adr/006-email-api-key-auth.md +0 -0
  30. {programasweights-0.2.7 → programasweights-0.2.8}/docs/advanced/adrs.md +0 -0
  31. {programasweights-0.2.7 → programasweights-0.2.8}/docs/advanced/architecture.md +0 -0
  32. {programasweights-0.2.7 → programasweights-0.2.8}/docs/case-studies/semantic-search.md +0 -0
  33. {programasweights-0.2.7 → programasweights-0.2.8}/docs/case-studies/site-navigation.md +0 -0
  34. {programasweights-0.2.7 → programasweights-0.2.8}/docs/getting-started/first-program.md +0 -0
  35. {programasweights-0.2.7 → programasweights-0.2.8}/docs/getting-started/installation.md +0 -0
  36. {programasweights-0.2.7 → programasweights-0.2.8}/docs/getting-started/naming-programs.md +0 -0
  37. {programasweights-0.2.7 → programasweights-0.2.8}/docs/guide/browser-inference.md +0 -0
  38. {programasweights-0.2.7 → programasweights-0.2.8}/docs/guide/how-it-works.md +0 -0
  39. {programasweights-0.2.7 → programasweights-0.2.8}/docs/guide/local-inference.md +0 -0
  40. {programasweights-0.2.7 → programasweights-0.2.8}/docs/hub/browsing-programs.md +0 -0
  41. {programasweights-0.2.7 → programasweights-0.2.8}/docs/hub/feedback-cases.md +0 -0
  42. {programasweights-0.2.7 → programasweights-0.2.8}/docs/hub/publishing-programs.md +0 -0
  43. {programasweights-0.2.7 → programasweights-0.2.8}/docs/index.md +0 -0
  44. {programasweights-0.2.7 → programasweights-0.2.8}/docs/requirements.txt +0 -0
  45. {programasweights-0.2.7 → programasweights-0.2.8}/examples/flask_app.py +0 -0
  46. {programasweights-0.2.7 → programasweights-0.2.8}/examples/jupyter_notebook.py +0 -0
  47. {programasweights-0.2.7 → programasweights-0.2.8}/examples/langchain_integration.py +0 -0
  48. {programasweights-0.2.7 → programasweights-0.2.8}/examples/paw_monitor.py +0 -0
  49. {programasweights-0.2.7 → programasweights-0.2.8}/examples/replace_openai.py +0 -0
  50. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/artifacts.py +0 -0
  51. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/cli.py +0 -0
  52. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/compiler/__init__.py +0 -0
  53. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/compiler/dummy.py +0 -0
  54. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/config.py +0 -0
  55. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/convert_peft_to_paw.py +0 -0
  56. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/paw_format.py +0 -0
  57. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/runtime/__init__.py +0 -0
  58. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/runtime/interpreter.py +0 -0
  59. {programasweights-0.2.7 → programasweights-0.2.8}/programasweights/runtime/interpreter_onnx.py +0 -0
  60. {programasweights-0.2.7 → programasweights-0.2.8}/tests/test_offline_cache.py +0 -0
  61. {programasweights-0.2.7 → programasweights-0.2.8}/tests/test_sdk.py +0 -0
  62. {programasweights-0.2.7 → programasweights-0.2.8}/tests/test_sdk.sh +0 -0
@@ -78,6 +78,11 @@ Output: delete
78
78
  - **Include examples from your actual data**: Examples outperform prose-only descriptions.
79
79
  - **Debug failures before sweeping**: Look at specific failing examples and understand WHY before trying many variants.
80
80
 
81
+ ## Context Window
82
+
83
+ - Spec + input + output share a ~2048 token context window. Inputs that exceed it will error.
84
+ - `max_tokens` defaults to `None`: generation runs until EOS or the context limit.
85
+
81
86
  ## Chaining Functions
82
87
 
83
88
  Multiple PAW functions can be composed for multi-step tasks:
@@ -93,11 +98,7 @@ if label != "other":
93
98
 
94
99
  Chain them with regular Python logic.
95
100
 
96
- ## Case Studies
97
-
98
- Production examples with iterative spec-tuning walkthroughs: [site navigation](https://programasweights.readthedocs.io/en/latest/case-studies/site-navigation/) (5-program pipeline, Cmd+K helper) and [semantic search](https://programasweights.readthedocs.io/en/latest/case-studies/semantic-search/) (reranking without a vector database).
99
-
100
- ## Event-Driven Monitoring
101
+ ## Log Monitoring
101
102
 
102
103
  PAW functions can classify log output. Compile once with examples from your specific logs, then reuse the function locally forever:
103
104
 
@@ -199,8 +200,7 @@ paw.login()
199
200
  - **Cache**: `~/.cache/programasweights/`. Override with `PAW_CACHE_DIR`.
200
201
  - **Offline** after first download.
201
202
 
202
- ## Limits
203
+ ## Case Studies
203
204
 
204
- - Spec + input + output share a ~2048 token context window. Inputs that exceed it will error.
205
- - `max_tokens` defaults to `None`: generation runs until EOS or the context limit.
205
+ Detailed walkthroughs of building production systems with PAW, including what we tried and what we learned: [log monitoring](https://programasweights.readthedocs.io/en/latest/case-studies/log-monitoring/), [site navigation](https://programasweights.readthedocs.io/en/latest/case-studies/site-navigation/), [semantic search](https://programasweights.readthedocs.io/en/latest/case-studies/semantic-search/), [tool calling](https://programasweights.readthedocs.io/en/latest/case-studies/tool-calling/).
206
206
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programasweights
3
- Version: 0.2.7
3
+ Version: 0.2.8
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
@@ -17,5 +17,5 @@ Replace the PyTorch runtime with llama-cpp-python (~80MB install). Use GGUF mode
17
17
  - Inference uses Metal (Mac), CPU (Linux/Windows) — no GPU required
18
18
  - Must pre-render chat templates server-side (no transformers tokenizer on client)
19
19
  - .paw format must change from v2 (safetensors) to v3 (GGUF adapter)
20
- - Base model is downloaded once (~623MB for Q6_K) and shared across all functions
20
+ - Base model is downloaded once (~594 MB for Q6_K) and shared across all functions
21
21
  - Per-function adapter download is ~23MB (Q4_0, confirmed lossless at 4096-scale eval)
@@ -57,7 +57,7 @@ paw rename <program_id> <new-slug> [--json]
57
57
  | Argument | Description |
58
58
  |----------|-------------|
59
59
  | `program_id` | Program hash ID or current slug. |
60
- | `new-slug` | New slug name (2-50 chars, lowercase alphanumeric and hyphens). |
60
+ | `new-slug` | New slug name (2-50 chars, lowercase alphanumeric and hyphens). Pass `""` to remove. |
61
61
 
62
62
  ## `paw info`
63
63
 
@@ -41,7 +41,7 @@ output: str = fn(input_text, max_tokens=None, temperature=0.0)
41
41
  | `max_tokens` | Maximum tokens to generate. `None` (default) = use all remaining context window. |
42
42
  | `temperature` | Sampling temperature (default `0.0`). |
43
43
 
44
- **Context limits:** Spec + input + output share a ~2048 token window. If input + output together exceed the window, generation will error. For short inputs, high `max_tokens` is safe (stops at EOS). For long inputs, set `max_tokens` conservatively.
44
+ **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.
45
45
 
46
46
  ## `paw.compile`
47
47
 
@@ -34,7 +34,11 @@ Compile a specification.
34
34
  | `name` | string | No | Display title. Auto-generated if omitted. |
35
35
  | `tags` | string[] | No | Tags for discovery. |
36
36
 
37
- **Response (JSON):** includes `job_id`, `status`, `program_id`, `slug` (if created), `pseudo_program`, and `timings`.
37
+ **Response:**
38
+ - **202 Accepted** on success — JSON with `job_id`, `status`, `program_id`, `slug` (if created), `pseudo_program`, and `timings`
39
+ - **400** — unknown compiler
40
+ - **422** — spec too short, too long, or token limit exceeded
41
+ - **500** — compilation failed (server error)
38
42
 
39
43
  ### `POST /infer`
40
44
 
@@ -88,7 +92,11 @@ Owner-only fields: `public`, `name`, `tags`. Any authenticated user can set thei
88
92
 
89
93
  ### `GET /programs/{id_or_slug}/download`
90
94
 
91
- Redirects to the Hugging Face CDN for the `.paw` artifact. Private programs return 404 to non-owners.
95
+ Downloads the `.paw` artifact. Returns one of:
96
+ - **302** redirect to HuggingFace CDN (program already uploaded)
97
+ - **200** file from server (freshly compiled, not yet on HF)
98
+ - **202 Accepted** with `Retry-After` header (program assets still generating, retry after the indicated seconds)
99
+ - **404** if program not found or private and not owned by the requester
92
100
 
93
101
  ### `GET /programs/resolve/{slug}`
94
102
 
@@ -31,7 +31,7 @@ vLLM multi-LoRA serving on GPU 2 (or GPU 1 in 2-GPU config). Each compiled progr
31
31
 
32
32
  ## Inference (Client-side SDK)
33
33
 
34
- llama-cpp-python loads the base GGUF model (623MB, downloaded once) and hot-swaps Q4_0 adapters per function call. No PyTorch or transformers required.
34
+ llama-cpp-python loads the base GGUF model (594 MB, downloaded once) and hot-swaps Q4_0 adapters per function call. No PyTorch or transformers required.
35
35
 
36
36
  ## GPU Assignment
37
37
 
@@ -0,0 +1,132 @@
1
+ # Event-Driven Log Monitoring
2
+
3
+ Long-running processes -- training runs, deployments, data pipelines -- produce thousands of log lines. You want to know when something important happens (checkpoint saved, error, completion) without watching the terminal. PAW lets you compile a classifier that runs locally and alerts on the lines that matter.
4
+
5
+ **Full tool:** [examples/paw_monitor.py](https://github.com/programasweights/programasweights-python/blob/main/examples/paw_monitor.py) on GitHub.
6
+
7
+ ## How we built it
8
+
9
+ ### Attempt 1: Keyword matching
10
+
11
+ The obvious first approach: grep for "error", "fail", "complete".
12
+
13
+ **Result:** Too noisy. "error" matches "error_count=0" (routine metric). "complete" matches "batch complete" (every 10 seconds). Missing important lines like "Traceback" or "CUDA out of memory" that don't contain the keywords.
14
+
15
+ **Lesson:** The whole reason to use PAW is that keyword matching can't express "important enough to interrupt me."
16
+
17
+ ### Attempt 2: One-shot description
18
+
19
+ ```
20
+ Classify if this log line is important enough to alert on. Return ALERT or QUIET.
21
+ ```
22
+
23
+ **Result:** Too vague. The model alerted on everything that looked unusual, including routine debug output. "Important" means different things in different contexts.
24
+
25
+ **Lesson:** Don't rely on abstract instructions. The model needs concrete examples of what ALERT and QUIET look like in YOUR specific logs.
26
+
27
+ ### Attempt 3: Example-based spec
28
+
29
+ ```
30
+ Classify log lines. Return ONLY one word: ALERT or QUIET.
31
+
32
+ Input: [step 100] loss=0.05 lr=0.0001
33
+ Output: QUIET
34
+
35
+ Input: [Checkpoint] Saved model at step 1000
36
+ Output: ALERT
37
+
38
+ Input: Traceback (most recent call last):
39
+ Output: ALERT
40
+
41
+ Input: Training complete. Final loss: 0.11
42
+ Output: ALERT
43
+ ```
44
+
45
+ **Result:** This worked. The model learned the boundary between routine metrics (QUIET) and significant events (ALERT) from the examples. Adding 3-5 examples from actual logs was the key.
46
+
47
+ **Lesson:** Include examples from your actual data. 3-5 representative examples consistently outperform prose-only descriptions.
48
+
49
+ ### Refinement: Developer feedback
50
+
51
+ A developer using PAW for monitoring diffusion model training shared their experience. Key insights:
52
+
53
+ - **Positive instructions work better than negative ones.** "Output ALERT only if X, Y, or Z" is more reliable than "Don't alert on routine output."
54
+ - **The spec should match the monitoring context.** Different training phases produce different log patterns. Examples should cover the specific domain.
55
+ - **Stall detection needs a separate mechanism.** PAW classifies what it sees -- it can't detect the absence of output. The monitoring tool needs a timer for "no new output in N minutes."
56
+
57
+ ## The solution
58
+
59
+ ### The spec
60
+
61
+ ```python
62
+ program = paw.compile("""
63
+ Classify log lines. Return ONLY one word: ALERT or QUIET.
64
+
65
+ Input: [step 100] loss=0.05 lr=0.0001
66
+ Output: QUIET
67
+
68
+ Input: [Checkpoint] Saved model at step 1000
69
+ Output: ALERT
70
+
71
+ Input: Traceback (most recent call last):
72
+ Output: ALERT
73
+
74
+ Input: Training complete. Final loss: 0.11
75
+ Output: ALERT
76
+ """)
77
+
78
+ fn = paw.function(program.id)
79
+ ```
80
+
81
+ Compile once, save the program ID, reuse forever. The function runs locally with no internet after the first download.
82
+
83
+ ### The monitoring loop
84
+
85
+ ```python
86
+ import time
87
+
88
+ fn = paw.function("your-program-id")
89
+
90
+ last_size = 0
91
+ while True:
92
+ size = os.path.getsize(log_file)
93
+ if size > last_size:
94
+ with open(log_file) as f:
95
+ f.seek(last_size)
96
+ new_text = f.read()
97
+ last_size = size
98
+
99
+ # Truncate to last ~1000 chars to fit context window
100
+ chunk = new_text[-1000:] if len(new_text) > 1000 else new_text
101
+ result = fn(chunk)
102
+ if result.strip() == "ALERT":
103
+ send_notification(chunk)
104
+
105
+ time.sleep(10)
106
+ ```
107
+
108
+ ### Full tool
109
+
110
+ The complete [paw_monitor.py](https://github.com/programasweights/programasweights-python/blob/main/examples/paw_monitor.py) adds:
111
+
112
+ - **File watching** with `seek()` to only process new content
113
+ - **Input truncation** to fit the ~2048 token context window
114
+ - **Stall detection** -- alerts if no new output for a configurable timeout
115
+ - **`--focus` and `--ignore`** flags to guide what the classifier pays attention to
116
+ - **`--local` flag** to run entirely offline after first compile
117
+ - **`--json` output** for integration with other tools
118
+
119
+ ## Adapting this for your use case
120
+
121
+ 1. **Collect 5-10 example log lines** from your actual process -- a mix of routine output and important events
122
+ 2. **Write a spec** with `Input: ... Output: ALERT/QUIET` pairs
123
+ 3. **Test with real logs** -- pipe a log file through the classifier and check which lines it flags
124
+ 4. **Iterate** -- if it alerts too much, add more QUIET examples. If it misses things, add ALERT examples.
125
+ 5. **Save the program ID** -- compile once, monitor forever
126
+
127
+ ## Takeaways
128
+
129
+ - **Examples beat descriptions** for teaching the model your specific log patterns.
130
+ - **Compile once, run forever.** The compiled function is cached locally and needs no internet.
131
+ - **PAW classifies what it sees** -- combine with a timer for stall detection.
132
+ - **Iterate with real data.** The first spec is rarely perfect. Test, check failures, adjust examples.
@@ -0,0 +1,477 @@
1
+ # Tool Calling with a 10-Function Pipeline
2
+
3
+ Large language models are increasingly expected to call tools — pick the right API, extract parameters, chain multi-step flows, and know when NOT to act. This normally requires large models (70B+). We used PAW to build a tool-calling system from 10 tiny compiled functions running on a 0.6B interpreter, and tested it on the [ToolCall-15](https://github.com/stevibe/ToolCall-15) benchmark.
4
+
5
+ The entire pipeline was built in a single Cursor session by an AI coding agent (Claude Opus 4.6) that read PAW's [AGENTS.md](https://programasweights.com/AGENTS.md) and the benchmark source.
6
+
7
+ **Result:** 93% (28/30 points) on ToolCall-15, with 14 out of 15 scenarios passing.
8
+
9
+ !!! warning "Benchmark leakage"
10
+ The AI agent that built this pipeline had access to the benchmark source code, including the 15 scenario definitions and scoring logic. This means the specs and heuristics were likely tailored to these specific test cases. We report 93% as a proof of concept for the architecture, not a generalizable accuracy claim. A principled evaluation would test on held-out scenarios the builder has never seen.
11
+
12
+ ## The benchmark: ToolCall-15
13
+
14
+ [ToolCall-15](https://github.com/stevibe/ToolCall-15) (by [stevibe](https://x.com/stevibe)) is a visual benchmark for LLM tool use. It runs 15 hand-picked scenarios through an OpenAI-compatible chat completions interface, scores each deterministically, and renders results in a live dashboard.
15
+
16
+ **12 available tools** (given to every scenario):
17
+
18
+ `web_search`, `get_weather`, `calculator`, `send_email`, `search_files`, `read_file`, `create_calendar_event`, `get_contacts`, `translate_text`, `get_stock_price`, `set_reminder`, `run_code`
19
+
20
+ **15 scenarios across 5 categories** (3 each, max 6 points per category):
21
+
22
+ | Category | Scenarios | What it tests |
23
+ |---|---|---|
24
+ | A: Tool Selection | TC-01 Weather in Berlin, TC-02 AAPL stock price, TC-03 "Let Sarah know..." | Pick the right tool, resist distractors, infer implicit chains |
25
+ | B: Parameter Precision | TC-04 Tokyo in Fahrenheit, TC-05 "Next Monday 9:30am", TC-06 Translate to both Spanish & Japanese | Pass correct arguments, parse dates, handle multi-value |
26
+ | C: Multi-Step Chains | TC-07 Find report → read → email total, TC-08 Check weather → if raining → set reminder, TC-09 Weather AND stock price | Thread data across steps, branch conditionally, run in parallel |
27
+ | D: Restraint & Refusal | TC-10 "When did WWII end?", TC-11 "15% of 200", TC-12 "Delete my emails" | Don't use tools for trivial knowledge, refuse impossible requests |
28
+ | E: Error Recovery | TC-13 Empty search results → retry, TC-14 Stock API rate limited, TC-15 Search population → calculate 2% | Handle errors gracefully, maintain data integrity |
29
+
30
+ Scoring: pass = 2 points, partial = 1, fail = 0. Final score = average of 5 category percentages.
31
+
32
+ ## The approach: decompose into specialists
33
+
34
+ The key insight is the same one from our [site navigation](site-navigation.md) case study: **split the task into small, focused functions.** A single monolithic prompt trying to handle tool selection, parameter extraction, multi-step reasoning, and restraint all at once would fail. Instead, each PAW function handles one decision.
35
+
36
+ The 10 compiled functions divide into three groups:
37
+
38
+ **Routing (which tool?):**
39
+
40
+ | Function | Spec (abbreviated) | Output |
41
+ |---|---|---|
42
+ | `tc15-needs-tool` | "Does this request need an external tool, or can it be answered directly?" | YES / NO |
43
+ | `tc15-tool-router` | "Which tool should be called?" with 10 examples covering all 12 tools + NONE | One of 13 labels |
44
+ | `tc15-impossible-check` | "Is this request something no available tool can fulfill?" | IMPOSSIBLE / POSSIBLE |
45
+ | `tc15-second-tool` | "After the first tool, is a second tool needed?" | Tool name or NONE |
46
+
47
+ **Extraction (what parameters?):**
48
+
49
+ | Function | Spec (abbreviated) | Output |
50
+ |---|---|---|
51
+ | `tc15-extract-location` | "Extract the city name from a weather request" | City name |
52
+ | `tc15-extract-ticker` | "Extract the stock ticker symbol" | Ticker (e.g. AAPL) |
53
+ | `tc15-extract-units` | "Extract temperature units, default celsius" | fahrenheit / celsius |
54
+ | `tc15-extract-search-query` | "Extract the file search query" | Search string |
55
+ | `tc15-extract-person` | "Extract the person's name" | Name |
56
+ | `tc15-extract-translate` | "Extract source_lang, target_lang, text" | Pipe-separated triple |
57
+
58
+ **Not compiled (handled by code):**
59
+
60
+ Some tasks are better handled by regular code than by a neural function:
61
+
62
+ - **Date/time parsing** (regex): "next Monday at 9:30am" → `2026-03-23T09:30`
63
+ - **Direct answers** (lookup table): "15% of 200" → `30`
64
+ - **Response synthesis** (template): format tool results into natural language
65
+ - **Protocol formatting**: build OpenAI-compatible `tool_calls` JSON
66
+
67
+ This is a general design principle: **use PAW for fuzzy judgment, use code for structured logic.**
68
+
69
+ ## The full specs
70
+
71
+ Here are the actual specs the agent compiled. Each is a standalone PAW function.
72
+
73
+ ### tc15-tool-router
74
+
75
+ ```
76
+ Classify which tool should be called for a user request.
77
+ Output EXACTLY one of these tool names:
78
+ web_search, get_weather, calculator, send_email, search_files,
79
+ read_file, create_calendar_event, get_contacts, translate_text,
80
+ get_stock_price, set_reminder, run_code, NONE.
81
+ Output NONE if the user's question can be answered from general
82
+ knowledge without any tool.
83
+
84
+ Input: What's the weather in Berlin?
85
+ Output: get_weather
86
+
87
+ Input: What is the price of AAPL stock?
88
+ Output: get_stock_price
89
+
90
+ Input: What year did WWII end?
91
+ Output: NONE
92
+
93
+ Input: What is 15% of 200?
94
+ Output: NONE
95
+
96
+ Input: Delete all my emails
97
+ Output: NONE
98
+
99
+ Input: Translate hello to Spanish
100
+ Output: translate_text
101
+
102
+ Input: Find the Q3 budget report
103
+ Output: search_files
104
+
105
+ Input: Send an email to Sarah
106
+ Output: get_contacts
107
+
108
+ Input: Schedule a meeting for Monday
109
+ Output: create_calendar_event
110
+
111
+ Input: Remind me to bring an umbrella
112
+ Output: set_reminder
113
+ ```
114
+
115
+ ### tc15-needs-tool
116
+
117
+ ```
118
+ Determine if a user request needs an external tool or can be
119
+ answered directly. Output YES if the request needs real-time data,
120
+ file access, sending messages, scheduling, or looking up current
121
+ information. Output NO if it's basic knowledge, simple math, or
122
+ an impossible request with no available tool.
123
+
124
+ Input: What's the weather?
125
+ Output: YES
126
+
127
+ Input: What year did WWII end?
128
+ Output: NO
129
+
130
+ Input: What is 15% of 200?
131
+ Output: NO
132
+
133
+ Input: Delete all my emails
134
+ Output: NO
135
+
136
+ Input: What's AAPL stock price?
137
+ Output: YES
138
+
139
+ Input: Find the budget report
140
+ Output: YES
141
+ ```
142
+
143
+ ### tc15-extract-location
144
+
145
+ ```
146
+ Extract the location/city name from a weather or location request.
147
+ Output just the city or location name, nothing else.
148
+
149
+ Input: What's the weather in Berlin?
150
+ Output: Berlin
151
+
152
+ Input: Temperature in Tokyo in Fahrenheit
153
+ Output: Tokyo
154
+
155
+ Input: How's the weather in Paris right now?
156
+ Output: Paris
157
+
158
+ Input: London weather forecast
159
+ Output: London
160
+ ```
161
+
162
+ ### tc15-extract-ticker
163
+
164
+ ```
165
+ Extract the stock ticker symbol from a stock price request.
166
+ Output just the uppercase ticker symbol.
167
+
168
+ Input: What is the price of AAPL stock?
169
+ Output: AAPL
170
+
171
+ Input: MSFT stock price
172
+ Output: MSFT
173
+
174
+ Input: How much is Apple stock?
175
+ Output: AAPL
176
+
177
+ Input: Microsoft stock price?
178
+ Output: MSFT
179
+ ```
180
+
181
+ ### tc15-extract-units
182
+
183
+ ```
184
+ Extract temperature units from a weather request.
185
+ Output 'fahrenheit' if Fahrenheit is mentioned, otherwise
186
+ output 'celsius'.
187
+
188
+ Input: Temperature in Tokyo in Fahrenheit
189
+ Output: fahrenheit
190
+
191
+ Input: Weather in Berlin
192
+ Output: celsius
193
+
194
+ Input: Paris weather in F
195
+ Output: fahrenheit
196
+
197
+ Input: London temperature celsius
198
+ Output: celsius
199
+ ```
200
+
201
+ ### tc15-extract-search-query
202
+
203
+ ```
204
+ Extract the file search query from a user request about finding
205
+ documents or files. Output a concise search query string.
206
+
207
+ Input: Find the Q3 budget report
208
+ Output: Q3 budget report
209
+
210
+ Input: Find the Johnson proposal document
211
+ Output: Johnson proposal
212
+
213
+ Input: Search for the annual review
214
+ Output: annual review
215
+ ```
216
+
217
+ ### tc15-extract-person
218
+
219
+ ```
220
+ Extract the person's name from a request about contacting or
221
+ looking up someone. Output just the person's name.
222
+
223
+ Input: I need to let Sarah know about the meeting
224
+ Output: Sarah
225
+
226
+ Input: Send an email to John
227
+ Output: John
228
+
229
+ Input: Contact my manager
230
+ Output: manager
231
+
232
+ Input: Email the report to Jamie
233
+ Output: Jamie
234
+ ```
235
+
236
+ ### tc15-extract-translate
237
+
238
+ ```
239
+ Extract translation parameters from a translate request.
240
+ Output in the format: source_lang|target_lang|text.
241
+ If multiple target languages, output one line per target language.
242
+
243
+ Input: Translate hello to Spanish
244
+ Output: English|Spanish|hello
245
+
246
+ Input: Translate Where is the hospital from English to Japanese
247
+ Output: English|Japanese|Where is the hospital
248
+ ```
249
+
250
+ ### tc15-impossible-check
251
+
252
+ ```
253
+ Determine if a request asks for something that cannot be done
254
+ with standard tools (web search, weather, calculator, email,
255
+ file search/read, calendar, contacts, translate, stocks,
256
+ reminders, code execution).
257
+ Output IMPOSSIBLE if no tool can fulfill it (like deleting
258
+ emails, modifying files, accessing databases, etc).
259
+ Output POSSIBLE otherwise.
260
+
261
+ Input: Delete all my emails from last month
262
+ Output: IMPOSSIBLE
263
+
264
+ Input: What's the weather?
265
+ Output: POSSIBLE
266
+
267
+ Input: Hack into the server
268
+ Output: IMPOSSIBLE
269
+
270
+ Input: Send an email
271
+ Output: POSSIBLE
272
+ ```
273
+
274
+ ### tc15-second-tool
275
+
276
+ ```
277
+ Given a user request that may need multiple tools, identify if
278
+ a SECOND tool is needed after the first. Output the second tool
279
+ name or NONE.
280
+
281
+ Input: Weather in London and MSFT stock price
282
+ Output: get_stock_price
283
+
284
+ Input: Find the report and email it to my manager
285
+ Output: read_file
286
+
287
+ Input: Check weather, if raining remind me about umbrella
288
+ Output: set_reminder
289
+
290
+ Input: What's the weather in Berlin?
291
+ Output: NONE
292
+
293
+ Input: Let Sarah know the meeting moved to 3pm
294
+ Output: send_email
295
+ ```
296
+
297
+ ## Pipeline architecture
298
+
299
+ The 10 PAW functions are orchestrated by a FastAPI proxy server that speaks the OpenAI `/v1/chat/completions` protocol. Here's the flow:
300
+
301
+ ```
302
+ User message arrives at proxy
303
+
304
+ ├─ detect_parallel_tools (regex)
305
+ │ "weather AND stock price" → call both tools in parallel
306
+ │ └─ if match: extract params for each → return tool_calls → done
307
+
308
+ ├─ tc15-needs-tool → "Does this need a tool?"
309
+ │ └─ NO → tc15-impossible-check → explain refusal or answer directly
310
+
311
+ ├─ tc15-tool-router → "Which tool?"
312
+ │ └─ NONE → answer from knowledge (lookup table or simple math)
313
+
314
+ ├─ extract_params_for_tool(tool_name):
315
+ │ ├─ get_weather → tc15-extract-location + tc15-extract-units
316
+ │ ├─ get_stock_price → tc15-extract-ticker
317
+ │ ├─ search_files → tc15-extract-search-query
318
+ │ ├─ get_contacts → tc15-extract-person
319
+ │ ├─ translate_text → tc15-extract-translate (may produce 2 calls)
320
+ │ ├─ calendar_event → regex date/time parsing
321
+ │ └─ set_reminder → regex date/time parsing
322
+
323
+ └─ Return tool_calls in OpenAI JSON format
324
+
325
+ ──── On follow-up turns (tool results come back) ────
326
+
327
+ ├─ decide_follow_up_tool:
328
+ │ ├─ Error result? → explain to user
329
+ │ ├─ Empty results? → retry with broader query
330
+ │ ├─ tc15-second-tool → "Is another tool needed?"
331
+ │ │ └─ Chain logic: search → read → contacts → send_email
332
+ │ └─ No more tools → synthesize_response from all results
333
+
334
+ └─ Return either more tool_calls or final text response
335
+ ```
336
+
337
+ The proxy handles multi-turn conversations by tracking which tools have already been called and threading data between steps (e.g., using a `file_id` from a search result in a subsequent `read_file` call).
338
+
339
+ ## How it was built
340
+
341
+ A single prompt to Cursor (Claude Opus 4.6):
342
+
343
+ > "Build a PAW-powered proxy for the ToolCall-15 benchmark. Read AGENTS.md for PAW usage. Analyze the benchmark scenarios and create specialized PAW functions for tool routing and parameter extraction. Wire them into an OpenAI-compatible endpoint."
344
+
345
+ The agent:
346
+
347
+ 1. Read the benchmark methodology and all 15 scenario definitions
348
+ 2. Identified which decisions need fuzzy judgment (PAW) vs. structured logic (code)
349
+ 3. Designed and compiled 10 PAW functions with specs tailored to the scenarios
350
+ 4. Built a FastAPI proxy server (~700 lines) with conversation state management
351
+ 5. Ran the benchmark, debugged failures, and iterated
352
+
353
+ ### Iteration 1: 70% (21/30)
354
+
355
+ The first run had several failures:
356
+
357
+ - **TC-01, TC-02 (Tool Selection): Infinite loop.** After calling the correct tool and receiving results, the proxy called the same tool again instead of synthesizing a response. Root cause: the conversation state handler didn't properly detect "I already have results, stop calling tools."
358
+ - **TC-08 (Conditional Branching): Wrong first tool.** The router sent "check weather, if raining set reminder" to `create_calendar_event` instead of `get_weather`. The tool-router spec didn't have an example for conditional chains.
359
+ - **TC-15 (Data Integrity): Wrong second tool.** After `web_search` returned Iceland's population, the proxy called `get_stock_price("Iceland")` instead of `calculator`. The `tc15-second-tool` function didn't have enough examples for search-then-calculate chains.
360
+ - **TC-07 (Multi-Step Chain): Partial.** The 4-step chain (search → read → contacts → email) partially worked but didn't thread data correctly between all steps.
361
+
362
+ ### Iteration 2: fix the orchestration
363
+
364
+ The agent fixed the proxy code (not the PAW specs):
365
+
366
+ - Added proper conversation state tracking to detect when tool results are present
367
+ - Added routing overrides for common patterns (conditional weather checks, search-then-calculate)
368
+ - Fixed data threading in multi-step chains (extracting `file_id` from search results, email from contacts)
369
+ - Recompiled 2 functions that had HuggingFace download failures
370
+
371
+ ### Final result: 93% (28/30)
372
+
373
+ | Category | Score | Details |
374
+ |---|---|---|
375
+ | A: Tool Selection | 100% (6/6) | TC-01 pass, TC-02 pass, TC-03 pass |
376
+ | B: Parameter Precision | 100% (6/6) | TC-04 pass, TC-05 pass, TC-06 pass |
377
+ | C: Multi-Step Chains | 100% (6/6) | TC-07 pass, TC-08 pass, TC-09 pass |
378
+ | D: Restraint & Refusal | 100% (6/6) | TC-10 pass, TC-11 pass, TC-12 pass |
379
+ | E: Error Recovery | 67% (4/6) | TC-13 **fail**, TC-14 pass, TC-15 pass |
380
+
381
+ **14 out of 15 scenarios passed.** The single failure:
382
+
383
+ ### Why TC-13 failed
384
+
385
+ **TC-13 (Empty Results Retry):** The user asks "Find the Johnson proposal document." The proxy correctly calls `search_files(query="Johnson proposal")`, gets empty results, but instead of retrying with a broader query like `"Johnson"`, it immediately responds "No results were found."
386
+
387
+ **Root cause:** The proxy has loop-prevention logic that blocks re-calling a tool already in `tools_called`. This prevents infinite loops (which was a real problem in iteration 1) but also prevents the legitimate retry pattern where you call the same tool with different parameters. The agent's fix for the infinite loop was too aggressive — it stopped all same-tool retries.
388
+
389
+ This is an **architectural limitation**, not a PAW function limitation. The `tc15-extract-search-query` function works fine. The issue is in the orchestration code's retry policy.
390
+
391
+ ## Contamination disclosure
392
+
393
+ The AI agent that built this pipeline had full access to:
394
+
395
+ - The 15 scenario definitions (user messages, expected tools, scoring logic)
396
+ - The mocked tool responses
397
+ - The benchmark source code
398
+
399
+ This means:
400
+
401
+ 1. The specs were tuned for these 15 specific scenarios (e.g., the tool-router examples map closely to the actual test inputs)
402
+ 2. The routing overrides in the proxy code target specific scenario patterns
403
+ 3. The direct-answer lookup table contains exact answers for TC-10 and TC-11
404
+
405
+ **What this proves:** The architecture works — 10 small PAW specialists + Python glue can implement a tool-calling system that scores 93% on a multi-category benchmark.
406
+
407
+ **What this does NOT prove:** That these exact specs would generalize to unseen tool-calling scenarios. A principled evaluation would compile specs without seeing the test cases, then evaluate on held-out scenarios.
408
+
409
+ ## Full code
410
+
411
+ ### compile_functions.py
412
+
413
+ The compilation script that creates all 10 functions:
414
+
415
+ ```python
416
+ import json, sys
417
+ import programasweights as paw
418
+
419
+ COMPILER = "paw-4b-qwen3-0.6b"
420
+
421
+ FUNCTIONS_TO_COMPILE = [
422
+ {"slug": "tc15-tool-router", "spec": "Classify which tool..."},
423
+ {"slug": "tc15-needs-tool", "spec": "Determine if request needs tool..."},
424
+ {"slug": "tc15-extract-location", "spec": "Extract city name..."},
425
+ {"slug": "tc15-extract-ticker", "spec": "Extract stock ticker..."},
426
+ {"slug": "tc15-extract-units", "spec": "Extract temperature units..."},
427
+ {"slug": "tc15-extract-search-query","spec": "Extract file search query..."},
428
+ {"slug": "tc15-extract-person", "spec": "Extract person's name..."},
429
+ {"slug": "tc15-extract-translate", "spec": "Extract translation parameters..."},
430
+ {"slug": "tc15-impossible-check", "spec": "Is this request impossible?..."},
431
+ {"slug": "tc15-second-tool", "spec": "Is a second tool needed?..."},
432
+ ]
433
+
434
+ for fn_def in FUNCTIONS_TO_COMPILE:
435
+ program = paw.compile(fn_def["spec"], compiler=COMPILER)
436
+ print(f"{fn_def['slug']}: {program.id}")
437
+ ```
438
+
439
+ ### server.py (key routing logic)
440
+
441
+ The proxy server's main decision flow:
442
+
443
+ ```python
444
+ @app.post("/v1/chat/completions")
445
+ async def chat_completions(request: ChatRequest):
446
+ messages = [m.model_dump() for m in request.messages]
447
+ user_msg = ""
448
+ for msg in messages:
449
+ if msg.get("role") == "user":
450
+ user_msg = msg.get("content", "")
451
+
452
+ # If we already have tool results, decide: more tools or synthesize?
453
+ if any(m.get("role") == "tool" for m in messages):
454
+ follow_up = decide_follow_up_tool(user_msg, messages)
455
+ if follow_up:
456
+ return make_response(tool_calls=follow_up)
457
+ return make_response(content=synthesize_response(user_msg, messages))
458
+
459
+ # First turn: should we use tools at all?
460
+ tool_calls = build_tool_calls_for_user(user_msg, messages)
461
+ if tool_calls:
462
+ return make_response(tool_calls=tool_calls)
463
+
464
+ # No tools needed — answer directly
465
+ return make_response(content=generate_direct_answer(user_msg))
466
+ ```
467
+
468
+ The full source code is available in the [paw-proxy directory](https://github.com/stevibe/ToolCall-15/tree/main/paw-proxy) of the ToolCall-15 repository.
469
+
470
+ ## Takeaways
471
+
472
+ - **Decomposition works.** 10 small specialists beat one monolithic prompt. Each function handles one decision — "which tool?", "what city?", "is this impossible?" — and does it well.
473
+ - **PAW + code hybrid.** Fuzzy judgment (tool routing, entity extraction, intent classification) goes to PAW. Structured logic (date parsing, JSON formatting, conversation state) stays in Python. Neither alone would work.
474
+ - **An AI agent can build the whole thing.** From analyzing the benchmark to compiling functions to writing the proxy server — one Cursor session, one prompt.
475
+ - **Loop prevention vs. retry is hard.** The single failure (TC-13) came from the tension between preventing infinite tool loops and allowing legitimate retries with different parameters. This is an orchestration problem, not a model problem.
476
+ - **0.6B is enough for focused tasks.** Tool routing across 12 options, entity extraction, yes/no classification — none of these need a large model when the spec is precise and examples are clear.
477
+ - **Always evaluate on unseen data.** Our 93% includes benchmark leakage. The architecture is sound, but the specific accuracy is optimistic.
@@ -0,0 +1,11 @@
1
+ # Writing Good Specs
2
+
3
+ The canonical spec-writing guide lives in [AGENTS.md](https://programasweights.com/agents) — the same document that AI coding assistants read when integrating PAW.
4
+
5
+ See the **"Writing Good Specs"** section there for:
6
+
7
+ - The #1 practice: iterate with test cases
8
+ - Spec-tuning tips (role naming, output constraints, positive vs negative instructions, field ordering, and more)
9
+ - Example specs with `Input: ... Output: ...` format
10
+
11
+ You can also copy the full AGENTS.md from the [AI Agents page](https://programasweights.com/agents) and save it as `AGENTS.md` in your project root — AI assistants will read it automatically.
@@ -52,8 +52,10 @@ nav:
52
52
  - Publishing Programs: hub/publishing-programs.md
53
53
  - Feedback & Cases: hub/feedback-cases.md
54
54
  - Case Studies:
55
+ - Log Monitoring: case-studies/log-monitoring.md
55
56
  - Site Navigation: case-studies/site-navigation.md
56
57
  - Semantic Search: case-studies/semantic-search.md
58
+ - Tool Calling: case-studies/tool-calling.md
57
59
  - Advanced:
58
60
  - Architecture: advanced/architecture.md
59
61
  - Architecture Decision Records: advanced/adrs.md
@@ -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.2.7"
30
+ __version__ = "0.2.8"
31
31
 
32
32
  from .config import get_api_url, get_api_key, set_api_key
33
33
 
@@ -68,8 +68,15 @@ def compile(
68
68
  '{"name": "Alice"}'
69
69
  """
70
70
  from .client import PAWClient
71
+ from ._output import status
72
+ status("Compiling...")
71
73
  client = PAWClient(api_url=get_api_url(), api_key=get_api_key())
72
- return client.compile(spec, compiler=compiler, name=name, tags=tags, public=public, slug=slug)
74
+ result = client.compile(spec, compiler=compiler, name=name, tags=tags, public=public, slug=slug)
75
+ label = f"{result.id}"
76
+ if result.slug:
77
+ label += f" ({result.slug})"
78
+ status(f"Compiled: {label}")
79
+ return result
73
80
 
74
81
 
75
82
  def function(
@@ -111,12 +118,15 @@ def function(
111
118
  if n_gpu_layers is None:
112
119
  n_gpu_layers = int(os.environ.get("PAW_GPU_LAYERS", "0"))
113
120
 
121
+ from ._output import status
122
+
114
123
  resolved_id = program_id
115
124
  if not re.fullmatch(r"[a-f0-9]{16,64}", program_id):
116
125
  cached = get_cached_slug(program_id)
117
126
  if cached:
118
127
  resolved_id = cached
119
128
  else:
129
+ status(f"Resolving {program_id}...")
120
130
  from .client import PAWClient
121
131
  client = PAWClient(api_url=get_api_url(), api_key=get_api_key())
122
132
  resolved_id = client.resolve_slug(program_id)
@@ -0,0 +1,34 @@
1
+ """User-facing status messages for the PAW SDK.
2
+
3
+ All messages go to stderr so they don't pollute stdout when users pipe output.
4
+ Set PAW_QUIET=1 to suppress all status messages.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import sys
10
+
11
+
12
+ def _quiet() -> bool:
13
+ return os.environ.get("PAW_QUIET", "").strip() in ("1", "true", "yes")
14
+
15
+
16
+ def status(msg: str, **kwargs) -> None:
17
+ """Print a status message to stderr."""
18
+ if _quiet():
19
+ return
20
+ print(msg, file=sys.stderr, flush=True, **kwargs)
21
+
22
+
23
+ def status_inline(msg: str) -> None:
24
+ """Print a status message inline (no newline) for progress updates."""
25
+ if _quiet():
26
+ return
27
+ print(f"\r{msg}", end="", file=sys.stderr, flush=True)
28
+
29
+
30
+ def status_end() -> None:
31
+ """End an inline status line."""
32
+ if _quiet():
33
+ return
34
+ print(file=sys.stderr, flush=True)
@@ -54,9 +54,10 @@ def get_base_model_path(interpreter: str = "Qwen/Qwen3-0.6B") -> Path:
54
54
  f"Available: {list(INTERPRETER_TO_GGUF.keys())}"
55
55
  )
56
56
 
57
- print(f"Downloading base model {gguf_name} (one-time download)...")
57
+ from ._output import status
58
+ status(f"Downloading interpreter {gguf_name} (one-time download)...")
58
59
  _download_file(url, gguf_path)
59
- print(f"Saved to {gguf_path}")
60
+ status(f"Saved to {gguf_path}")
60
61
  return gguf_path
61
62
 
62
63
 
@@ -87,8 +88,10 @@ def _download_file(url: str, dest: Path):
87
88
  if total > 0:
88
89
  pct = downloaded / total * 100
89
90
  mb = downloaded / 1024 / 1024
90
- print(f"\r {mb:.1f} MB ({pct:.0f}%)", end="", flush=True)
91
- print()
91
+ from ._output import status_inline, status_end
92
+ status_inline(f" {mb:.1f} MB ({pct:.0f}%)")
93
+ from ._output import status_end
94
+ status_end()
92
95
  os.replace(str(tmp), str(dest))
93
96
  except BaseException:
94
97
  tmp.unlink(missing_ok=True)
@@ -114,9 +114,13 @@ class PAWClient:
114
114
  if (program_dir / "prompt_template.txt").exists():
115
115
  return program_dir
116
116
 
117
+ from ._output import status
118
+
119
+ status(f"Downloading program {program_id[:12]}...")
117
120
  max_wait = 30
118
121
  elapsed = 0
119
122
  resp = None
123
+ waiting_logged = False
120
124
  while elapsed < max_wait:
121
125
  resp = httpx.get(
122
126
  f"{self._api_url}/api/v1/programs/{program_id}/download",
@@ -125,6 +129,9 @@ class PAWClient:
125
129
  follow_redirects=True,
126
130
  )
127
131
  if resp.status_code == 202:
132
+ if not waiting_logged:
133
+ status("Waiting for program to be ready...")
134
+ waiting_logged = True
128
135
  retry_after = int(resp.headers.get("Retry-After", "3"))
129
136
  time.sleep(retry_after)
130
137
  elapsed += retry_after
@@ -137,6 +144,9 @@ class PAWClient:
137
144
  if "not found" in detail.lower():
138
145
  raise RuntimeError(f"Program {program_id} not found on server.")
139
146
  if elapsed < max_wait - 3:
147
+ if not waiting_logged:
148
+ status("Waiting for program to be ready...")
149
+ waiting_logged = True
140
150
  time.sleep(3)
141
151
  elapsed += 3
142
152
  continue
@@ -51,9 +51,12 @@ class PawFunction:
51
51
  raise FileNotFoundError(f"No prompt_template.txt in {program_dir}")
52
52
  self._template = template_path.read_text()
53
53
 
54
+ from ._output import status
54
55
  interpreter = self._meta.get("interpreter", "Qwen/Qwen3-0.6B")
55
56
  base_model_path = cache.get_base_model_path(interpreter)
56
57
 
58
+ status("Loading interpreter...")
59
+
57
60
  if not verbose:
58
61
  import os as _os, sys as _sys
59
62
  _stderr_fd = _sys.stderr.fileno()
@@ -103,6 +106,7 @@ class PawFunction:
103
106
  self._n_prefix = len(self._prefix_tokens)
104
107
 
105
108
  self._load_or_eval_prefix()
109
+ status("Ready.")
106
110
 
107
111
  def _apply_adapter(self, scale: float):
108
112
  """Apply LoRA adapter, compatible with both old and new llama.cpp API."""
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "programasweights"
7
- version = "0.2.7"
7
+ version = "0.2.8"
8
8
  description = "Compile natural language specifications into neural programs that run locally via llama.cpp."
9
9
  readme = "PYPI_README.md"
10
10
  requires-python = ">=3.9"
@@ -1,36 +0,0 @@
1
- # Writing Good Specs
2
-
3
- A specification is a **self-contained** natural-language description of what the function should do. There is no separate “examples” API field: everything the compiler sees must live in that single spec string.
4
-
5
- ## What to include
6
-
7
- Write clearly enough that someone unfamiliar with your project could implement the behavior from the text alone.
8
-
9
- ### Good practices
10
-
11
- - **Output shape:** Say how results should look. Examples: “return a JSON list of strings”, “respond with only `positive` or `negative`”, “output valid CSV with a header row”.
12
- - **Edge cases:** State behavior for empty input, missing data, or failure modes. Example: “if no emails are found, return an empty list”.
13
- - **Ambiguity:** Explain how to handle unclear inputs (e.g., multiple interpretations, partial data).
14
- - **Length:** Keep the spec under **8000 characters**; the API enforces this limit.
15
-
16
- ### Examples inside the spec
17
-
18
- You may embed example pairs directly in the spec text, for example:
19
-
20
- ```text
21
- Examples:
22
- Input: hello
23
- Output: greeting
24
- ```
25
-
26
- Use a consistent pattern so the compiler can treat them as demonstrations of desired behavior.
27
-
28
- ## What to avoid
29
-
30
- - **Vague goals:** Phrases like “do something useful” or “be smart about it” do not constrain behavior.
31
- - **Excessive length:** Long essays dilute the task definition and hit limits unnecessarily.
32
- - **Contradictions:** Conflicting instructions produce unreliable pseudo-programs and weaker adapters.
33
-
34
- ## How the compiler uses your spec
35
-
36
- The compiler **generates a pseudo-program** from your spec. That artifact often **rephrases and expands** the spec with structured examples. You can inspect what was produced in the playground under **“View compiled program internals”** to verify alignment with your intent.