最近在做一个智能客服项目,客户对响应速度和回答的准确性要求很高。传统的基于规则或简单微调大模型的方法,要么不够灵活,要么“一本正经地胡说八道”,知识更新也麻烦。经过一番折腾,我们最终采用了 RAG(检索增强生成) 结合 微调 Qwen-VL 模型 的方案,效果拔群。今天就来分享一下我们的实战经验,希望能帮你少走弯路。

1. 为什么是RAG+微调?聊聊我们的技术选型

最开始我们评估了几种方案:

  • 纯规则引擎:响应快,但维护成本高,稍微复杂点的问题就歇菜,不适合开放域问答。
  • 纯大模型微调:把客服知识库全喂给模型(比如Qwen-VL),让它“记住”。优点是回答风格统一,能处理复杂逻辑。但缺点也很明显:知识更新需要重新训练,成本高;模型容易产生“幻觉”,编造不存在的信息;对于非常具体、细节的知识,记忆和召回能力有限。
  • RAG(检索增强生成):用户提问时,先从海量知识库中检索出最相关的文档片段,然后把“问题+相关片段”一起交给大模型生成答案。这相当于给模型配了一个“实时更新的外部知识库”,答案有据可查,极大减少了幻觉,也便于知识更新。

所以,我们选择了 RAG + 微调 的混合模式:

  • RAG 负责提供精准、最新的知识依据。
  • 微调 Qwen-VL 则让模型更好地理解我们的业务语境、话术风格,并具备处理多轮对话、复杂意图的能力。选择Qwen-VL是因为它不仅文本能力强,还原生支持视觉理解,为将来客服支持“图片描述商品问题”等场景预留了空间。

2. 动手第一步:构建高质量的知识库

RAG的效果,七八成取决于检索质量。如果检索出来的文档不相关,再强的模型也白搭。

我们的知识库来源包括:产品手册、FAQ文档、历史工单对话、政策文件等。处理流程如下:

  1. 文档加载与清洗:使用 langchain 的文档加载器,支持 .txt, .pdf, .docx, 网页等格式。清洗掉无关的页眉页脚、广告、特殊字符。

  2. 文本分割:这是关键!不能简单按段落或固定长度切分。我们采用“递归字符分割”,尽量保证语义完整性(比如一个完整的Q&A对不被切开),同时设置重叠窗口(overlap),避免答案被割裂。

    from langchain.text_splitter import RecursiveCharacterTextSplitter
    
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=500,  # 每个片段大约500字符
        chunk_overlap=50,  # 片段间重叠50字符,保证上下文连贯
        separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] # 按优先级分割
    )
    splits = text_splitter.split_documents(documents)
    
  3. 向量化与存储:将分割后的文本片段转换为向量(embeddings),存入向量数据库。我们对比了 text-embedding-ada-002bge-large-zh 等模型,最终选择了在中文场景表现优异的 bge-large-zh-v1.5。向量数据库用的是 Chroma,轻量且易用。

    from langchain.embeddings import HuggingFaceEmbeddings
    from langchain.vectorstores import Chroma
    
    embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh-v1.5")
    vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings, persist_directory="./chroma_db")
    vectorstore.persist() # 持久化保存
    

3. 让模型更懂业务:微调Qwen-VL

直接用开源Qwen-VL模型,它可能不太理解我们行业特有的术语和对话习惯。微调就是为了让它“入乡随俗”。

我们采用 LoRA(低秩适配) 进行高效微调,只需要训练极少的参数,就能达到不错的效果,节省大量计算资源。

关键步骤与参数:

  1. 准备训练数据:格式为多轮对话。我们从历史客服日志中清洗出高质量的对话对,并构造了一些针对难点问题的合成数据。

    [
      {
        "conversations": [
          {"role": "user", "content": "我的订单号是123456,为什么还没发货?"},
          {"role": "assistant", "content": "您好,已为您查询。订单123456目前处于【打包待出库】状态,预计今天下午发出,请您耐心等待。发货后会有短信通知哦。"}
        ]
      }
    ]
    
  2. 配置训练参数:使用 Qwen-VL-Chat 模型,基于 transformerspeft 库。

    from peft import LoraConfig, get_peft_model
    
    lora_config = LoraConfig(
        r=8,  # LoRA秩
        lora_alpha=32,
        target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 针对注意力模块
        lora_dropout=0.1,
        bias="none",
        task_type="CAUSAL_LM"
    )
    model = get_peft_model(model, lora_config) # 将原模型转换为LoRA模型
    
  3. 开始训练:设置学习率、批次大小,通常训练1-3个epoch即可看到明显效果。注意要冻结基础模型的大部分参数,只训练LoRA层。

4. 核心组装:将RAG与微调模型无缝集成

这是系统的“大脑”。流程是:用户提问 -> 检索相关文档 -> 组合提示词 -> 微调模型生成答案。

我们使用 langchain 来搭建这个流水线:

from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# 1. 加载微调后的模型和分词器
model_path = "./path/to/your/finetuned_qwen_vl"
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True, torch_dtype=torch.float16, device_map="auto")

# 2. 加载之前构建的向量数据库
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh-v1.5")
vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # 检索最相关的3个片段

# 3. 设计一个强大的提示词模板
prompt_template = """你是一个专业的客服助手。请严格根据以下提供的已知信息来回答问题。如果已知信息不足以回答问题,请直接说“根据现有资料,我无法回答该问题”,不要编造信息。

已知信息:
{context}

用户问题:
{question}

请用专业、友好的语气回答:"""
PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"])

# 4. 创建检索增强生成链
qa_chain = RetrievalQA.from_chain_type(
    llm=YourCustomLLM(model, tokenizer), # 需要将微调模型包装成LangChain的LLM接口
    chain_type="stuff", # 将检索到的所有文档“堆叠”后输入模型
    retriever=retriever,
    chain_type_kwargs={"prompt": PROMPT},
    return_source_documents=True # 返回参考来源,便于调试和展示
)

# 5. 提问!
question = "请问你们公司的退货政策是怎样的?"
result = qa_chain({"query": question})
print("回答:", result["result"])
print("参考来源:", result["source_documents"])

这里的关键是 YourCustomLLM,这是一个自定义包装类,负责调用我们微调好的Qwen-VL模型进行文本生成。

5. 性能、安全与那些踩过的坑

性能测试: 我们模拟了不同并发用户数(10, 50, 100)进行压力测试。主要瓶颈在:

  • 检索阶段:向量相似度计算。通过建立索引(如HNSW)和优化 k 值(不宜过大)来提速。
  • 生成阶段:模型推理。使用 vLLMTGI 等推理框架进行服务化部署和动态批处理,能极大提升吞吐量。响应时间平均能控制在1-3秒内,满足实时客服要求。

安全与隐私:

  • 数据隐私:所有客服对话和知识库数据在训练和推理过程中均在公司内网进行,不调用任何外部API。向量数据库也部署在私有环境。
  • 模型安全:在微调数据中加入了针对“拒绝回答无关问题”、“过滤敏感词”的指令数据,并在系统层面对用户输入和模型输出做了关键词过滤和审核。
  • 内容安全:在最终答案生成前,可以加入一层基于规则或轻量级分类器的安全检查,防止模型被“诱导”产生不当内容。

避坑指南:

  1. 冷启动延迟:首次启动加载模型和向量库较慢。解决方案是使用常驻内存的服务,或者为向量数据库启用持久化存储,避免每次重启全量加载。
  2. 检索不准:这是最常见问题。除了优化文本分割和嵌入模型,还可以尝试:
    • 混合检索:结合关键词检索(如BM25)和向量检索,取长补短。
    • 重排序(Re-ranking):用更精细的模型对初步检索出的Top N个结果进行重新排序,提升Top1的准确率。
  3. 多轮对话处理:简单的RAG是“一问一答”,会丢失历史上下文。我们的做法是将整个对话历史(或最近几轮)也作为输入的一部分,同时检索时,将当前问题和历史对话摘要一起作为查询条件,让模型能联系上下文。
  4. 知识更新:新增知识文档后,只需将其分割、向量化并插入向量数据库即可,无需重新训练模型,实现了知识的热更新。

6. 总结与未来展望

通过 RAG + 微调Qwen-VL 这套组合拳,我们构建的智能客服系统既拥有了实时、准确的知识查询能力,又具备了符合业务特性的语言风格和复杂推理能力。它不再是“人工智障”,而是一个真正能分担压力的智能助手。

未来还有不少优化方向:

  • 多模态扩展:利用Qwen-VL的视觉能力,用户可以直接发送产品故障图片,系统能描述图片内容并结合知识库给出排障建议。
  • 意图识别前置:在检索前,先用一个轻量级模型识别用户意图(如咨询、投诉、办理业务),从而路由到不同的知识子库或处理流程,更精准。
  • 自我迭代:将用户反馈“回答不准确”的案例自动收集,经过人工确认后,可以反向补充到知识库或微调数据中,让系统不断进化。

搭建过程虽然繁琐,但看到系统能准确、流畅地解决用户问题时,成就感满满。希望这篇笔记能为你提供一条清晰的实践路径。技术细节很多,建议先从一个小而准的知识库开始,跑通整个流程,再逐步扩展优化。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐