Instructions to use sanidhya1910/northwind-support-lora with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use sanidhya1910/northwind-support-lora with PEFT:
from peft import PeftModel from transformers import AutoModelForCausalLM base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-4B") model = PeftModel.from_pretrained(base_model, "sanidhya1910/northwind-support-lora") - Notebooks
- Google Colab
- Kaggle
Northwind Support LoRA (Qwen3-4B)
A LoRA adapter that moves a 992-token customer-support system prompt β a 20-entry policy handbook plus a strict JSON output contract β into the weights, so inference runs on a one-line system prompt instead.
Trained on a synthetic dataset generated from scratch by a seeded, rule-based generator β no teacher model, no API key, reproducible byte-for-byte. Everything needed to judge the result is on this page: the four-arm comparison, the training setup, and the limitations.
β οΈ This adapter encodes the policies of Northwind Outdoors, a fictional retailer. It is a methodology demonstration, not a usable support agent for any real company. See Limitations.
What it does
Given a customer message, it replies in 2-4 policy-grounded sentences and appends exactly one machine-readable ticket:
Clearance and final-sale items are sold as-is, so the Ridgeline rain shell on NW-771904 can't be
returned or exchanged. I know that's not the answer you wanted; it's called out at checkout.
```json
{"intent": "return_refund", "priority": "P3", "escalate": false, "policy_ids": ["RET-02"], "next_action": "answer_only"}
```
Results
200 held-out conversations, greedy decoding, rule-based scoring against gold labels derived by rule (no LLM judge). Customer phrasings in the test set never appear in training.
| Metric | base + full prompt (992 tok) | + 2-shot | base + one-line prompt | this adapter + one-line |
|---|---|---|---|---|
| Prompt tokens (mean) | 1078.7 | 1394.7 | 98.7 | 98.7 |
| Schema-valid JSON % | 97.0 | 99.5 | 1.0 | 98.5 |
| Intent accuracy % | 64.0 | 70.5 | 3.0 | 98.5 |
| Priority accuracy % | 63.0 | 62.5 | 2.5 | 95.0 |
| Next-action accuracy % | 49.0 | 55.0 | 2.5 | 88.5 |
| Escalation F1 | 70.0 | 69.2 | 18.5 | 90.3 |
| Escalation recall | 58.3 | 56.2 | 10.4 | 87.5 |
| Policy-citation F1 | 58.6 | 65.1 | 8.3 | 90.5 |
| Prose copied verbatim from training % | 0.0 | 0.0 | 0.0 | 48.0 |
| Seconds / example | 2.93 | 2.43 | 3.92 | 1.47 |
91% fewer prompt tokens on every request, while beating the prompted baseline on every accuracy
metric. The base + one-line prompt column is the ablation: without the adapter, the base model
has no idea the policy IDs or the ticket format exist.
The most useful gap is escalation recall. Both prompted arms are precise (~90%) but miss ~43% of cases that require a human β injury, fraud, chargebacks, legal threats. The adapter reaches 87.5%.
Usage
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
BASE = "Qwen/Qwen3-4B"
tok = AutoTokenizer.from_pretrained(BASE)
model = AutoModelForCausalLM.from_pretrained(BASE, dtype="bfloat16", device_map="auto")
model = PeftModel.from_pretrained(model, "blackburn1910/northwind-support-lora")
messages = [
{"role": "system", "content": "You are the customer support assistant for Northwind Outdoors."},
{"role": "user", "content": "I want to return the rain shell I bought on clearance, order NW-771904."},
]
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True, enable_thinking=False)
out = model.generate(**tok(text, return_tensors="pt", add_special_tokens=False).to(model.device),
max_new_tokens=300, do_sample=False)
print(tok.decode(out[0], skip_special_tokens=True))
The system prompt must be exactly You are the customer support assistant for Northwind Outdoors.
β that is what it was trained against. Qwen3 must run in non-thinking mode (enable_thinking=False).
Training
| Base | Qwen/Qwen3-4B (Apache-2.0) |
| Method | LoRA r=16, Ξ±=32, dropout 0.05, on all attention + MLP projections |
| Data | 1,400 synthetic conversations β 1,938 assistant-turn examples |
| Objective | causal LM, loss masked to assistant turns only |
| Schedule | 3 epochs, 366 steps, lr 2e-4 cosine, effective batch 16, bf16 |
| Loss | train 3.5376 β 0.0246; eval 0.0626 β 0.0345 β 0.0335 (ppl 1.034) |
| Hardware | 1Γ RTX 5070 Ti (16 GB), ~1h50m |
Training data is generated offline by a seeded, rule-based generator β no teacher model and no API key β so it reproduces byte-for-byte.
Limitations
- It has learned one fictional company's handbook, not customer support. Point it at a different policy set and it will be confidently wrong, in the way a prompted model would not be β the prompted baseline simply follows whatever prompt you give it. That trade is the whole point of the experiment, and it cuts both ways.
- 48% of its replies reproduce a training answer verbatim (after normalising order IDs and numbers), against 0% for the prompted baseline. The training targets are template-generated, so the adapter has substantially memorised the response bank. Read the accuracy figures as evidence about format and policy fidelity, not about writing quality.
- Prose quality is not evaluated. No human raters, no judge model β deliberately, so every reported number is rule-checkable.
- Test phrasings are unseen but come from the same generator as training. This measures generalisation across wording, not across distribution. Real ticket text has a tail of weirdness this data does not.
- Single seed, single base model. Differences of a point or two are noise.
- Not safety-tuned for production use. The escalation rules here are deliberately crisp; real escalation policy is fuzzier and would need human-labelled data.
License
Apache-2.0, matching the Qwen3-4B base model.
- Downloads last month
- 17