qwen-2.5-1.5b-de-smb-support-agent
Public reference adapter for a brand-aligned, domain-trained German
customer-support agent — the kind of fine-tune I deliver to
freelance clients: replaces a generic chatbot, runs on the customer's
own GPU, brings Tier-1 resolution time from 10+ min to under
2 min, and emits structured JSON the rest of the stack can trust.
The training data in this public version is synthetic (generated
against a fictional German DTC brand so the full adapter + data +
harness can be open-sourced without exposing real client tickets). On
a real engagement the same recipe runs against your brand voice, your
policies, and 2,000-10,000 of your actual historical support tickets.
Intended use
- Specialised customer-support agents. Fine-tune on 2,000-10,000
historical support tickets so the model speaks the brand voice and
enforces policy compliance. Resolution time drops from 10+ min
to under 2 min on Tier-1 queries; small support teams scale
without hiring.
- Catalog- and policy-aware Q&A. Agent grounds answers in the
customer's actual product catalog and policy SOPs instead of
inventing them.
- GDPR-clean by default. Training data, adapter weights, and
inference all run on customer infrastructure (or a private cloud
endpoint). No conversation history reaches a US frontier-API vendor.
- Drop-in component. The agent in the companion repo
(
agent/graph.py) wraps the fine-tuned model in a 3-node LangGraph
pipeline: generate -> validate -> route. The router sends to a
human queue on escalate_to_human=true or invalid output; otherwise
it returns the response.
Output schema
1class SupportResponse(BaseModel):
2 response_text: str
3 escalate_to_human: bool
4 policies_referenced: list[str]
5 intent_category: Literal[
6 "return", "shipping", "sizing", "product_care",
7 "complaint", "b2b", "general",
8 ]
Training data
- N: 75 train + 16 eval synthetic tickets (fictional German DTC brand)
- Generated by: Claude Opus, sampled across 7 intent categories
× 7 customer tones × 50+ concrete scenarios
(
scripts/synth_smb.py)
- Validated by:
SupportResponse.model_validate() at ingest;
parse failures dropped
- Brand profile + policies + escalation triggers encoded in
schema.BRAND as one swappable Pydantic-typed dict — on a
client engagement, this is the file that gets replaced with yours
Training setup
- Base:
Qwen/Qwen2.5-1.5B-Instruct (Apache 2.0, no gating)
- Method: LoRA (PEFT) via TRL
SFTTrainer, conversational chat format
- LoRA config: r=32, alpha=64, dropout=0.05, target_modules =
attention + MLP projections (q/k/v/o, gate/up/down)
- Optimiser: AdamW (torch), cosine schedule, warmup ratio 0.03,
learning rate 4e-4
- Batch: 4 per device × 4 grad-accum × bf16 ×
gradient checkpointing
- Epochs: 8 (40 optimisation steps)
- Max seq len: 2048
- Hardware: single NVIDIA A40 (48 GB) on RunPod
- Wall time: 5 minutes 28 seconds
- GPU cost: about USD 0.04 for the training run
Evaluation
Computed on the 16-ticket held-out eval split via scripts/eval.py:
| Metric | Base Qwen2.5-1.5B | + LoRA adapter |
|---|
| Schema-valid JSON output | 44% | 94% |
| Structured-field F1 (escalate / intent / policies) | 0.22 | 0.80 |
Plus a Claude-Opus-as-judge pass on the free-form response_text
(scripts/eval_judge.py):
| Judge dimension | Base | + LoRA |
|---|
| Brand voice (1-5) | 1.4 | 2.0 |
| Policy compliance (% pass) | 38% | 6% |
| Resolution quality (% pass) | 6% | 6% |
The fine-tune locks down the schema layer (parse + structured
field F1) at this small data scale — the precondition for any
downstream wrapper. The free-form response_text quality starts to
shift in brand voice but is not at production grade on 75 synthetic
samples; the same recipe on 2,000-10,000 real customer tickets is
what closes the policy + resolution gap.
How to use
1from peft import PeftModel
2from transformers import AutoModelForCausalLM, AutoTokenizer
3
4base = "Qwen/Qwen2.5-1.5B-Instruct"
5adapter = "renezander030/qwen-2.5-1.5b-de-smb-support-agent"
6
7tok = AutoTokenizer.from_pretrained(base)
8mdl = AutoModelForCausalLM.from_pretrained(base, device_map="auto")
9mdl = PeftModel.from_pretrained(mdl, adapter).merge_and_unload()
10
11# The adapter learned the system prompt rendered by schema.render_system().
12# For inference, use the same prompt; see schema.py + agent/nodes.py.
13# (This system prompt ships with the public synthetic brand; on a real
14# client engagement it is replaced with yours.)
15SYSTEM = (
16 "Du bist Maria im Kundenservice eines deutschen "
17 "DTC-Familienbetriebs (Leder-Manufaktur seit 1987). "
18 "Sie-Form, persönlich-professionell, signiert mit Vorname. "
19 "Antworte AUSSCHLIESSLICH als JSON nach Schema SupportResponse."
20)
21messages = [
22 {"role": "system", "content": SYSTEM},
23 {"role": "user", "content": "Wo bleibt meine Bestellung?"},
24]
25prompt = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
26inputs = tok(prompt, return_tensors="pt").to(mdl.device)
27out = mdl.generate(**inputs, max_new_tokens=768, do_sample=False)
28print(tok.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
Deployment notes
- Footprint: 140 MB safetensors adapter + 11 MB tokenizer; ships as a
one-folder plugin on top of any Qwen2.5-1.5B host
- 4-bit (bitsandbytes nf4) brings the merged inference footprint to
~1 GB on a 24 GB consumer GPU; on A40 / A100 the bf16 path is faster
- For high-throughput batch ingestion swap
transformers.generate()
for vLLM with the LoRA adapter loaded; expect 5-10x throughput
- Pydantic validation at the boundary makes downstream pipelines
fail-fast on schema drift
Limitations
- 75 synthetic training tickets is intentionally small for a public
proof; client engagements use 2k-10k real tickets. At 75 the
schema layer locks in but free-form policy compliance does not.
- Synthetic share is 100% in this open-source dataset; real client
data closes the brand-voice + policy-recall gap.
intent_category is collapsed to 7 buckets; niche intents snap to
general.
- No calibration: judge scores are heuristics, not probabilities.
Work with me
This adapter is a public reference of the recipe I deliver to clients:
domain-specific LLMs, running on their own GPU, GDPR-clean, with
structured-output contracts the rest of the stack can trust.
If you need one of the following, I can build it:
- a fine-tune on your own data (customer support, legal, finance, medical, technical)
- a private LLM deployment on your infrastructure, or a dedicated cloud GPU endpoint
- a structured-output agent pipeline (LangGraph, Pydantic-validated, human-in-the-loop routing)
- an evaluation harness that tells you when the model is actually good enough to ship
Two ways to engage:
- Upwork — contract-ready, vetted, pay-as-you-go: https://www.upwork.com/freelancers/reneza
- Direct — for longer engagements, retainers, or a quick conversation: https://renezander.com
License
Adapter weights: Apache 2.0 (matches the Qwen2.5 base).
Training scripts in the companion repo: MIT.
Citation
1@misc{zander2026qwen15bdesmbsupport,
2 author = {Zander, Rene},
3 title = {qwen-2.5-1.5b-de-smb-support-agent: a LoRA adapter
4 for German SMB customer-support agents,
5 trained on synthetic DTC support tickets},
6 year = {2026},
7 howpublished = {HuggingFace Hub},
8 url = {https://huggingface.co/renezander030/qwen-2.5-1.5b-de-smb-support-agent},
9}