What it produces
A JSON object with exactly these 10 top-level keys:
project, requirements, components, relationships, circuit, fabrication, instructions, appearance, image_generation_prompt, sourcing
. It encodes real design decisions — a pin-level circuit netlist,
phase-ordered build instructions, and a sourcing table whose line items sum to a stated total. The
schema is
schema.json in this repo; a
Product pydantic model + semantic gates (
validate.py) can
re-check any output.
Usage
This adapter ships a chat template that defaults to no-think — the model outputs JSON directly,
no special flags needed. (Qwen3.5's stock template opens a <think> reasoning block by default, which
would make every response fail to parse; the bundled template flips that default. Pass
enable_thinking=True only if you deliberately want reasoning.) Use the same system prompt shape
(10 keys, "output only JSON") the model was trained with.
import torchfrom unsloth import FastVisionModel model, tokenizer = FastVisionModel.from_pretrained("caid-technologies/partipro-base-lora", load_in_4bit=False)FastVisionModel.for_inference(model) messages = [ {"role": "system", "content": [{"type": "text", "text": SYSTEM_PROMPT}]}, {"role": "user", "content": [{"type": "text", "text": "Design a USB-powered desk lamp with touch dimming."}]},]# No-think is the default in the bundled template. enable_thinking=False is optional (belt-and-suspenders).text = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)inputs = tokenizer(text=text, return_tensors="pt").to("cuda")out = model.generate(**inputs, max_new_tokens=13000, do_sample=False, repetition_penalty=1.1)print(tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
For production serving, use schema-constrained decoding. Under vLLM, pass schema.json as a
guided_json constraint — this lifts schema-validity dramatically (see Evaluation) and makes malformed
JSON structurally impossible rather than merely unlikely. Note that when serving base + this adapter
through vLLM, the engine applies the base chat template, which defaults to thinking mode — pass
chat_template_kwargs={"enable_thinking": false} (or serve the merged model, which bundles the no-think
template). Then re-validate with validate.py and retry on gate failures.
Evaluation
Measured with vLLM on 60 held-out in-distribution examples (15 products the model never trained
on × 4 input variants: prompt only / +brief / +sketch / +brief+render), a 42-prompt out-of-distribution
suite, and a sampling-robustness matrix. Strict parse = raw json.loads, no salvage (the deployment
bar). Every output scored against the real schema (schema.py) and semantic gates (validate.py). The
eval ran base + this LoRA; because the vision tower is frozen, these numbers also describe the merged model.
In-distribution (60 held-out)
Table with columns: Metric, Fine-tuned — free, Fine-tuned — guided_json, Base Qwen3.5-9B| Metric | Fine-tuned — free | Fine-tuned — guided_json | Base Qwen3.5-9B |
|---|
| Strict JSON parse | 0.85 | 0.83 | 0.97 |
Schema-valid (Product) | 0.67 | 0.83 | 0.00 |
| Gates-clean (pins / sourcing math / phase order) | 0.40 | 0.48 | 0.00 |
| Prompt-echo fidelity |
The base model emits parseable JSON (0.97) but it is never a valid blueprint (schema 0.00) — right
braces, wrong content. The fine-tune produces a schema-valid blueprint 67% of the time under free
decoding and 83% under schema-constrained decoding. Quality scales with context: the strongest
variant (prompt + brief + render image) reaches 0.73 schema-valid / 0.93 echo under free decoding.
Out-of-distribution (42 fresh prompts)
Table with columns: kind, n, parse, schema (free)| kind | n | parse | schema (free) |
|---|
| realistic paraphrase | 10 | 0.90 | 0.70 |
| new domain | 10 | 0.90 | 0.70 |
| vague / underspecified | 5 | 0.80 | 0.40 |
| image-grounded | 6 | 0.83 | 0.50 |
| adversarial |
Across the realistic OOD rows (paraphrase + new-domain + vague + image), schema-valid is 0.61 under
free decoding and 0.97 under guided_json — constrained decoding is the difference between a demo and
a deployable endpoint on unseen inputs. Adversarial rows (impossible physics, contradictory constraints,
prompt-injection) are the weakest, as intended: the model tries to comply and sometimes breaks.
Sampling robustness
At temperature=0.7 (30 generations, 3 seeds): strict parse holds at 0.97, schema-valid 0.57 —
sampling does not break the JSON structure.
Reading these numbers for deployment
- Serve with
guided_json. Passing the blueprint schema as a vLLM structured-output constraint lifts
schema-valid from 0.61 → 0.97 on unseen prompts (and 0.67 → 0.83 in-distribution). This is the
intended serving path, not an optional extra.
- Parse failures are truncation, not malformed output. 10 of 126 free generations hit the 13,000-token
cap; there were 0 thinking-leaks, 0 markdown fences, 1 trailing-data. Raising the token budget and/or
repetition_penalty (≥1.1) recovers most of these and lifts parse toward ~0.95.
- Gates-clean (~0.40–0.48) is the honest quality ceiling. Roughly half of outputs have at least one
semantic issue a human wouldn't catch at a glance (a pin on the wrong net, sourcing totals off, a phase
mis-ordered). Constrained decoding does not fix these. Always re-run outputs through
validate.py
and regenerate on failure — this is the intended workflow.
Training data
150 synthetic records, each validated by the same schema + semantic gates before inclusion
(pin-checked circuits, sourcing arithmetic, phase-ordered instructions). Records were authored by
Claude Sonnet 5 and DeepSeek-V4-Pro; concept images (render + hand-drawn sketch per record) by
gpt-image-1. Four input variants per record (prompt only; prompt + brief; prompt + sketch;
prompt + brief + render) teach the model to read documents and images, not just bare prompts.
- LoRA: r=16, α=16, bf16, language+attention+MLP layers (vision encoder frozen), 3 epochs, max seq 16384.
- Base model: Qwen/Qwen3.5-9B (Apache-2.0).
Limitations
- English prompts only; maker/electronics/hardware domain. Off-task prompts still tend to produce a
blueprint (parse 1.00, schema 0.75 on the off-task suite) — it does not reliably refuse.
- Outputs are plausible engineering, not verified builds — the gates check internal consistency
(valid pins, correct arithmetic, ordered phases), not real-world electrical safety or buildability.
Review before you solder.
- ~half of outputs have a semantic gate issue (see Evaluation) — treat every output as a draft to
re-validate with
validate.py, not a finished spec.
- Long outputs can truncate at the token cap (10/126 in eval); use
repetition_penalty≥1.1 and a
generous max_new_tokens (≥13k), or constrained decoding.
- Synthetic training data inherits the generators' biases. Adversarial/contradictory requests
(impossible physics, negative budgets) may yield confidently wrong blueprints (parse 0.43 on the
adversarial suite).
- LoRA adapter (~165 MB); requires
Qwen/Qwen3.5-9B as the base (auto-downloaded on load) and is tied
to that model at this revision. It defaults to no-think via a patched chat template; pass
enable_thinking=True only if you want reasoning.
License
Apache-2.0, inheriting the base model's license. The adapter weights are released under the same terms.