Chapter 10

实战:微调中文客服助手

把前九章串起来:从整理客服对话数据,到用 QLoRA 微调 Qwen,再导出 GGUF 用 Ollama 本地部署,一条龙走通。

10.1 目标与技术选型

我们要做一个电商场景的中文客服助手:懂业务话术、能回答退换货/物流/发票等常见问题、语气统一。选型如下:

基础模型
unsloth/Qwen2.5-7B-Instruct——中文强、指令跟随好、社区支持完善。
微调方法
QLoRA(4bit 加载 + LoRA),一张 16GB 显卡即可,成本低。
数据规模
1000-3000 条高质量客服"问-答"对,覆盖主要业务场景。
部署目标
导出 GGUF(q4_k_m),用 Ollama 在本地/内网服务器跑,接入客服后台。
评估方式
预留 50 条测试问题,微调前后并肩对比 + 人工验收。
chat template
qwen-2.5,训练与推理必须一致。
迭代周期
数据→训练→评估→补数据,循环 2-3 轮收敛到满意质量。

10.2 步骤一:准备数据

import json
from datasets import Dataset
from unsloth.chat_templates import get_chat_template

# data.jsonl 每行: {"q": "用户问题", "a": "客服回答"}
rows = [json.loads(l) for l in open("data.jsonl", encoding="utf-8")]
ds = Dataset.from_list(rows)

SYS = "你是「云购商城」的官方客服助手,回答需专业、礼貌、简洁,只依据商城政策作答。"

def to_text(ex):
    msgs = [
        {"role": "system",    "content": SYS},
        {"role": "user",      "content": ex["q"]},
        {"role": "assistant", "content": ex["a"]},
    ]
    return {"text": tokenizer.apply_chat_template(
        msgs, tokenize=False, add_generation_prompt=False)}

10.3 步骤二:加载 Qwen 并注入 LoRA

from unsloth import FastLanguageModel

max_seq_length = 2048
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Qwen2.5-7B-Instruct",
    max_seq_length = max_seq_length,
    dtype = None,
    load_in_4bit = True,
)
tokenizer = get_chat_template(tokenizer, chat_template="qwen-2.5")
ds = ds.map(to_text)

model = FastLanguageModel.get_peft_model(
    model, r=16,
    target_modules=["q_proj","k_proj","v_proj","o_proj",
                    "gate_proj","up_proj","down_proj"],
    lora_alpha=16, lora_dropout=0, bias="none",
    use_gradient_checkpointing="unsloth", random_state=3407,
)

10.4 步骤三:QLoRA 训练

from trl import SFTTrainer, SFTConfig
from unsloth.chat_templates import train_on_responses_only

split = ds.train_test_split(test_size=0.05, seed=3407)

trainer = SFTTrainer(
    model=model, tokenizer=tokenizer,
    train_dataset=split["train"], eval_dataset=split["test"],
    dataset_text_field="text", max_seq_length=max_seq_length, packing=False,
    args=SFTConfig(
        per_device_train_batch_size=2, gradient_accumulation_steps=4,
        warmup_ratio=0.03, num_train_epochs=2, learning_rate=2e-4,
        logging_steps=5, optim="adamw_8bit", weight_decay=0.01,
        lr_scheduler_type="linear", seed=3407, bf16=True,
        save_steps=50, output_dir="cs_out",
    ),
)
# 只对客服回答算 loss
trainer = train_on_responses_only(
    trainer,
    instruction_part="<|im_start|>user\n",
    response_part="<|im_start|>assistant\n",
)
trainer.train()

10.5 步骤四:评估

FastLanguageModel.for_inference(model)

def reply(q):
    msgs=[{"role":"system","content":SYS},{"role":"user","content":q}]
    ids=tokenizer.apply_chat_template(msgs,tokenize=True,
        add_generation_prompt=True,return_tensors="pt").to("cuda")
    out=model.generate(input_ids=ids,max_new_tokens=256,
        temperature=0.3,top_p=0.9,do_sample=True)
    return tokenizer.decode(out[0][ids.shape[-1]:],skip_special_tokens=True)

for q in ["我买的鞋子小了能换吗?", "发票怎么开?", "下单三天了还没发货"]:
    print("Q:", q, "\nA:", reply(q), "\n---")
评估要点

重点看:是否遵守商城政策口径、是否礼貌简洁、有没有编造不存在的规则(幻觉)、拒答边界是否合理。发现问题→补对应场景数据→重训。

10.6 步骤五:导出 GGUF + Ollama 部署

# 导出 GGUF(q4_k_m 均衡)
model.save_pretrained_gguf("cs_gguf", tokenizer,
                          quantization_method="q4_k_m")

写一个 Ollama Modelfile 指向导出的 gguf 并内置 system 提示:

# Modelfile
FROM ./cs_gguf/unsloth.Q4_K_M.gguf

SYSTEM """你是「云购商城」的官方客服助手,回答需专业、礼貌、简洁,只依据商城政策作答。"""

PARAMETER temperature 0.3
PARAMETER top_p 0.9
# 构建并运行
$ ollama create cs-bot -f Modelfile
$ ollama run cs-bot "我的订单可以取消吗?"

# 或走 HTTP API 接入客服后台
$ curl http://localhost:11434/api/chat -d '{
  "model": "cs-bot",
  "messages": [{"role":"user","content":"运费怎么算?"}]
}'

10.7 上线 Checklist

合规与安全提醒

客服会直接面向用户,务必加入:敏感词/越权请求的拒答规则、不承诺无法兑现的政策、金额/时效等关键信息以后台真实数据为准(可结合 RAG 而非全靠微调记忆)。

10.8 收官

至此你已走通 Unsloth 微调的完整链路:理解原理 → LoRA/QLoRA → 加载模型 → 备数据 → 配置训练 → 训练监控 → 推理评估 → 导出部署 → 进阶对齐 → 端到端实战

继续深入

推理上线可配合《vLLM 高性能推理》做高并发服务;系统化微调理论见《LLM 微调实战》;Python/数据基础见《Python AI 开发手册》。