AI Agent 智能体与MCP开发实践第7章案例实现--基于Qwen3的多Agent智能客服实战

7.1 分层Qwen3的多Agent智能客服实战
分层Agent架构的核心设计理念在于采用多个大模型分别承担不同任务,以实现专业化的分工与高效协作。从技术层面细致审视,构建一个高效、智能且贴合跨境电商业务特性的智能客服架构,已然成为企业提升服务质量、增强客户粘性、拓展全球市场的关键所在。而一个基础的跨境电商智能客服架构,其核心能力之一便是精准且高效地对用户意图进行分类。如下图所示。

7.2 搭建具备商业问答的交流客服Agent
7.2.1 基于Qwen3的多语种智能客服基座模型简介
接下来我们将以Qwen3-1.7B这一模型作为智能客服的基座模型展开演示。(注意:使用git clone https://www.modelscope.cn/Qwen/Qwen3-1.7B.git命令完成模型下载)
以下便是基于Qwen3-1.7B模型构建基本智能客服基座模型的核心代码示例:

7.2.2 真实客服数据集介绍与使用详解
为了让原有的基座模型了解和掌握基本的客服交流方式和客服基本工作流程,下面我们准备了一份基于真实客服数据打造的客服交流数据集,如下所示:这里是我们收集的来自于真实客服数据的人工服务数据,其中每行是一个完整的交流内容,以空格进行分割,而且每行直接也进行了分词处理。 在具体使用上,由于我们收集的原始客服数据是基于txt文本格式的,而并不适合前面我们所选择使用的基座模型,因此在我们还要首先对其格式进行调整。 适配于前面所选择的基座模型,我们在这里提供了一个对数据集格式进行调整的代码。


7.2.3 使用LoRA微调基座模型
我们将使用LoRA微调框架,微调后的模型保存在qwen3_lora_finetuned文件夹下,具体运行智能客服微调的完整代码如下图所示:

7.2.4 使用微调后的智能客服基座模型完成推理
基于微调后的基座模型进行推理的代码如下所示。
import json
import torch
import safetensors.torch as safetorch
from tqdm import tqdm
from datasets import Dataset
from transformers import (
TrainingArguments,
Trainer,
DataCollatorForSeq2Seq
)
from peft import LoraConfig, get_peft_model
from modelscope import AutoModelForCausalLM, AutoTokenizer
# 配置参数
model_name = "Qwen/Qwen3-1.7B"
output_dir = "qwen_lora_finetuned"
max_length = 384 # 根据显存调整
# 初始化模型和分词器
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto",
use_cache=False # 梯度检查点需要关闭cache
)
print("base_model load successful!")
from peft import PeftModel
model = PeftModel.from_pretrained(model, "qwen_lora_finetuned")
messages = [
{"role": "user", "content": "我没收到货呢所以就想知道是哪里的问题?"}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
generated_ids = model.generate(
**model_inputs,
max_new_tokens=max_length
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response)
运行结果如下图所示:

7.3 给交流客服Agent注入垂直领域知识
7.3.1 基于BM25算法的RAG实战
假如我们有一系列的文档Doc,现在要查询问题Query。BM25的思想是,对Query进行语素解析,生成语素Q;然后对于每个搜索文档D_i计算每个语素Q_i与文档D_j的相关性,最后将所有的语素Q_i与D_j进行加权求和,从而最终计算出Query与D_j的相似性得分。将BM25算法总结如下:

实现代码如下图所示:
from modelscope import AutoModelForCausalLM, AutoTokenizer
import torch
# 模型加载(单例模式优化)
model_name = "Qwen/Qwen3-1.7B"
device = "cuda" if torch.cuda.is_available() else "cpu"
def get_model_tokenizer():
if not hasattr(get_model_tokenizer, "model"):
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype="auto",
device_map=device,
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
get_model_tokenizer.model = model
get_model_tokenizer.tokenizer = tokenizer
return get_model_tokenizer.model, get_model_tokenizer.tokenizer
def generate_response(prompt):
model, tokenizer = get_model_tokenizer()
inputs = tokenizer([prompt], return_tensors="pt").to(device)
generated_ids = model.generate(
**inputs,
max_new_tokens=312
)
# 过滤输入部分并解码 [[6]]
output_ids = generated_ids[0][len(inputs.input_ids[0]):]
return tokenizer.decode(output_ids, skip_special_tokens=True).strip()
def customer_chat(query, informations):
# 构建RAG增强Prompt
system_prompt = (
f"""
你是一个跨境智能客服服务器,你现在需要回答用户的提问,使用如下知识和内容:'{informations}'
1、你无需思考,只需要按提供给你的知识回答即可!
2、你在回答问题时只能从给你的知识和内容中进查找,如果找不到对应的内容,就必须回答'找不到'。
3、你不能回答给你的参考资料之外的内容。
"""
)
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": query}
]
tokenizer = get_model_tokenizer()[1]
prompt = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
# 生成完整响应
return generate_response(prompt)
from rank_bm25 import BM25Okapi
#query是需要查询的文本,documents为文本库,top_n为返回最接近的n条文本内容
def get_top_n_sim_text(query: str, documents,top_n = 3):
tokenized_corpus = []
for doc in documents:
text = []
for char in doc:
text.append(char)
tokenized_corpus.append(text)
bm25 = BM25Okapi(tokenized_corpus)
tokenized_query = [char for char in query]
#doc_scores = bm25.get_scores(tokenized_query) # array([0., 0.93729472, 0.])
results = bm25.get_top_n(tokenized_query, tokenized_corpus, n=top_n)
results = ["".join(res) for res in results]
return results
def split_text_by_word_count(text, max_word_count):
"""
将文本按字数分段
参数:
text (str): 要分段的文本内容
max_word_count (int): 每段的最大字数
返回:
list: 分段后的文本列表
"""
words = text.split() # 按空格分割单词
segments = []
current_segment = []
current_word_count = 0
for word in words:
# 添加当前单词到当前段,并更新字数统计
current_segment.append(word)
current_word_count += len(word) + 1 # +1 是为了考虑单词之间的空格
# 如果当前段字数超过限制,则结束当前段并开始新段
if current_word_count > max_word_count:
# 如果当前段只有一个单词且超过限制,则单独处理(防止长单词无法放入)
if len(current_segment) == 1:
segments.append(current_segment[0])
current_segment = []
current_word_count = 0
else:
# 回退到上一个单词,完成当前段
segments.append(' '.join(current_segment[:-1]))
current_segment = [word]
current_word_count = len(word) + 1
# 添加最后一个段
if current_segment:
segments.append(' '.join(current_segment))
return segments
# 使用示例
if __name__ == "__main__":
with open("phone.txt", "r", encoding="utf-8") as f:
content = f.read()
# 模拟RAG检索到的信息
queries = [
"手机的地域范围是哪里?",
"我在什么情况下可以免费手机更换?",
"手机的质保期是多久?"
]
for query in queries:
informations = split_text_by_word_count(content, max_word_count=128)
bm25_results = get_top_n_sim_text(query, informations)
response = customer_chat(query, bm25_results)
print(f"Q: {query}\nA: {response}\n{'=' * 40}")
运行结果如下:


7.3.2 基于Embedding向量排序的RAG实战
首先,执行pip install langchain_huggingface,此代码主要包括文本拆分、向量计算和排序的方法,具体实现代码如下:
import numpy as np
from langchain_huggingface import HuggingFaceEmbeddings
import torch
class EmbeddingModel:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.initialize()
return cls._instance
def initialize(self):
device = "cuda" if torch.cuda.is_available() else "cpu"
self.embeddings = HuggingFaceEmbeddings(
model_name="TencentBAC/Conan-embedding-v1",
model_kwargs={'device': device},
encode_kwargs={'normalize_embeddings': True},
cache_folder="./embeddings_cache"
)
def embed_query(self, query):
embedding = self.embeddings.embed_query(query)
return np.array(embedding)
def embed_querys(self, query_list):
embedding_list = []
for query in query_list:
embedding = self.embed_query(query)
embedding_list.append(embedding)
return np.array(embedding_list)
def search_similar(self, query, text_list):
"""
查找与查询最相似的文本列表,按相似度降序排列
参数:
query (str): 输入查询
embedding_list (np.array): 预计算的嵌入向量列表(n_samples, embedding_dim)
text_list (list): 与嵌入对应的原始文本列表
返回:
tuple: (排序后的文本列表, 对应的相似度分数列表)
"""
# 1. 生成查询嵌入
query_emb = self.embed_query(query) # [[3]] 使用Hugging Face嵌入模型
embedding_list = self.embed_querys(text_list)
# 2. 计算余弦相似度(通过点积实现,因已归一化)
similarities = np.dot(embedding_list, query_emb)
# 3. 按相似度排序
sorted_indices = np.argsort(similarities)[::-1] # 降序排列
# 4. 返回排序结果
sorted_texts = [text_list[i] for i in sorted_indices]
sorted_scores = similarities[sorted_indices]
return sorted_texts, sorted_scores
def split_text_by_word_count(self,text, max_word_count):
"""
将文本按字数分段
参数:
text (str): 要分段的文本内容
max_word_count (int): 每段的最大字数
返回:
list: 分段后的文本列表
"""
words = text.split() # 按空格分割单词
segments = []
current_segment = []
current_word_count = 0
for word in words:
# 添加当前单词到当前段,并更新字数统计
current_segment.append(word)
current_word_count += len(word) + 1 # +1 是为了考虑单词之间的空格
# 如果当前段字数超过限制,则结束当前段并开始新段
if current_word_count > max_word_count:
# 如果当前段只有一个单词且超过限制,则单独处理(防止长单词无法放入)
if len(current_segment) == 1:
segments.append(current_segment[0])
current_segment = []
current_word_count = 0
else:
# 回退到上一个单词,完成当前段
segments.append(' '.join(current_segment[:-1]))
current_segment = [word]
current_word_count = len(word) + 1
# 添加最后一个段
if current_segment:
segments.append(' '.join(current_segment))
return segments
if __name__ == '__main__':
embedding_model = EmbeddingModel()
informations = [
"手机的地域范围是哪里?",
"我在什么情况下可以免费手机更换?",
"手机的质保期是多久?"
]
# 查询示例
query = "手机保修多久?"
sorted_texts, sorted_scores = embedding_model.search_similar(query, informations)
print("排序结果:")
for text, score in zip(sorted_texts, sorted_scores):
print(f"相似度 {score:.4f} : {text}")
运行结果如下:

7.4 搭建基于Qwen3的调度Agent
MCP 具备强大的功能整合与灵活调度能力,它如同一个智能“百宝箱”,能够高效串联起各个功能模块,让数据在模块之间顺畅刘彤,功能得以协同运作。具体实现如下图所示:“

import datetime
from mcp.server.fastmcp import FastMCP
# 创建服务实例,为智能客服工具集的运行搭建舞台
mcp = FastMCP("DemoServer")
@mcp.tool()
def fetch_logistics_information(logistics_number: str) -> dict:
"""
该函数犹如一位专业的物流信息侦探,根据用户提供的物流单号,向物流服务系统发起精准的查询请求。
它不放过物流运输过程中的任何一个关键节点,包括包裹揽收、中转、派送等环节,详细获取并返回这些环节的具体状态、时间、地点以及操作人员等数据。
通过这些丰富的信息,用户能够全面、深入地了解货物的实时运输动态,仿佛亲眼目睹货物在运输途中的每一步。
参数:
logistics_number (str): 用户提供的物流单号,是查询物流信息的关键钥匙。
返回:
dict: 包含详细物流信息的字典,结构清晰,方便用户查看与理解。
"""
# 模拟从物流服务系统获取数据的过程,实际应用中会调用真实的物流接口
logistics_data = {
"logistics_number": logistics_number,
"status": "In Transit",
"current_location": "City X, Country Y",
"last_update_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"events": [
{
"event_type": "Pickup",
"time": "2024-07-10 09:00:00",
"location": "Warehouse A, City Z, Country W",
"operator": "Courier John"
},
{
"event_type": "Transit",
"time": "2024-07-11 14:30:00",
"location": "Transit Hub B, City X, Country Y",
"operator": "Operator Lisa"
}
]
}
return logistics_data
@mcp.tool()
def record_user_complaint(complaint_details: str) -> str:
"""
此函数是用户投诉的“忠诚记录者”,负责接收并妥善记录用户发起的投诉信息。
它将用户投诉的核心内容、关联业务细节、用户基础信息以及投诉发生的时间等关键要素,以结构化数据形式精心存储至系统数据库或指定存储介质。
这一举措为后续的投诉处理、分析、反馈等流程提供了完整、准确的数据基础,助力企业能够迅速响应用户诉求,采取有效措施提升服务质量与用户满意度。
在跨境业务中,常见的投诉涉及价格争议、物流时间过长、商品外观不符、包装牢固程度等问题,而此函数能够全面覆盖这些情况,确保每一个投诉都能得到妥善处理。
参数:
complaint_details (str): 用户投诉的核心内容,详细描述了用户遇到的问题和不满。
返回:
str: 告知用户投诉已成功记录的提示信息,让用户感受到企业的重视与关怀。
"""
# 模拟将投诉信息存储到数据库的过程,实际应用中会使用数据库操作语句
complaint_record = {
"complaint_details": complaint_details,
"complaint_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status": "Received"
}
# 这里只是模拟存储操作,实际应用中会调用数据库接口进行真实存储
print(f"Complaint record stored: {complaint_record}")
return "Your complaint has been recorded. We will investigate and get back to you as soon as possible. Thank you for your feedback!"
@mcp.tool()
def customer_chat(query: str) -> dict:
"""
该函数是智能客服的“智慧大脑”,专门处理普通用户发起的各类咨询服务请求。
它如同一位经验丰富的客服专家,能够接收用户输入的咨询问题、关联业务场景信息(如有)以及用户基础标识,然后调用相应的知识库、业务规则引擎或对接外部专业接口,对用户咨询进行智能分析与精准应答。
同时,它还会详细记录咨询全过程数据,以便后续进行服务质量评估、问题溯源及知识库优化,为用户提供高效、准确且个性化的咨询服务体验。
参数:
query (str): 用户提出的咨询问题文本,需清晰描述用户想要了解的信息,如产品功能、服务流程、政策规定等。
返回:
dict: 包含应答内容和相关信息的字典,结构清晰,便于前端展示和用户理解。
"""
# 模拟知识库查询和应答生成的过程,实际应用中会调用知识库接口和业务规则引擎
knowledge_base = {
"product_feature": "This product has advanced features such as [feature 1], [feature 2], and [feature 3].",
"return_policy": "Our return policy allows returns within [X] days of purchase, with the product in its original condition."
}
# 简单模拟根据查询内容从知识库获取应答的逻辑
if "feature" in query.lower():
answer = knowledge_base["product_feature"]
elif "return" in query.lower():
answer = knowledge_base["return_policy"]
else:
answer = "We are sorry, but we couldn't find a direct answer to your question. Our team will review your query and get back to you shortly."
# 记录咨询信息(模拟)
consultation_record = {
"query": query,
"answer": answer,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
print(f"Consultation record stored: {consultation_record}")
return {
"answer": answer,
"response_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status": "success"
}
if __name__ == "__main__":
# 使用标准输入输出协议运行服务,让智能客服工具集正式投入工作
mcp.run(transport="stdio")
7.4.2 基于在线Qwen3的客户意图识别与工具调度Agent
本部分将完成基于Qwen3的客户意图识别与工具调度Agent。这个Agent如同智能客服系统中的“智慧指挥官”,能够巧妙协调大模型与工具集,实现高效、精准的服务,代码如下所示:
'''
MCP 客户端程序
通过 stdio 协议与 MCP 服务器进行无缝通信
精准获取 MCP 服务器的工具清单和调用参数
并灵活调用工具实现 MCP 服务中丰富多样的函数功能
'''
from openai import OpenAI
from mcp import ClientSession, StdioServerParameters # 从 mcp 模块导入关键类,为通信与会话管理提供支持
from mcp.client.stdio import stdio_client # 从 mcp.client.stdio 模块导入通信函数,搭建客户端与服务器之间的桥梁
import sys # 导入 sys 模块,用于处理命令行参数,增强程序的灵活性
import json # 导入 json 模块,用于高效处理 JSON 数据,实现数据的结构化存储与传输
import os # 导入 os 模块,用于处理文件路径和环境变量,确保程序在不同环境下的稳定运行
# 定义一个功能强大的函数,用于将 tools 工具中的 JSON 数据转换为清晰易读的格式
def transform_json(tools):
s = "MCP 服务器提供的工具如下:\n"
for tool in tools: # 遍历工具列表,逐一解析每个工具的信息
s = s + f"""
工具名称: {tool.name},
工具描述: {tool.description},
- 输入参数标题: {tool.inputSchema['title']},
- 输入参数属性: {json.dumps(tool.inputSchema['properties'], indent=4, ensure_ascii=False)},
"""
return s
# 定义与 DeepSeek 大模型交互的核心函数,实现用户意图识别与工具调用命令生成
def ask_llm_deepseek(question, tools_list):
"""
该函数通过精心设计的系统提示(system prompt),引导 DeepSeek 大模型根据用户问题和工具描述,生成符合要求的工具调用命令。
参数:
question (str): 用户提出的问题,是模型理解用户需求的关键依据。
tools_list (str): 包含工具详细描述的字符串,为模型提供工具集的相关信息,辅助其做出正确的工具选择。
返回:
tuple: 包含生成的 JSON 格式工具调用命令文本和 OpenAI 客户端对象的元组,便于后续处理与调用。
"""
system_prompt = tools_list + '\n 根据以上描述,用户要求: %s ,请生成一个工具调用命令,要求以 json 格式输出{"tool": 工具名, "tool_input": 参数字典},只输出 json,不要输出其他内容' % (
question)
# 创建 OpenAI 客户端,配置 API 密钥和基础 URL,确保与 DeepSeek 模型的正常通信
client = OpenAI(
api_key="sk-282074c41d594514aee6fd6f179ed292", # 实际应用中,应从安全的环境变量或配置文件中获取 API 密钥
base_url="https://api.deepseek.com/beta",
)
# 调用 DeepSeek 模型生成响应
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": system_prompt}, # 系统提示,为模型设定任务背景和要求
{"role": "user", "content": "Hello"}, # 示例用户消息,可根据实际情况调整或移除
],
max_tokens=1024, # 限制生成文本的最大长度,避免响应过长
temperature=0.99, # 控制生成文本的随机性和创造性,数值越高,输出越多样
stream=False # 禁用流式输出,一次性获取完整响应
)
generated_text = response.choices[0].message.content # 提取生成的文本内容
return generated_text, client # 返回生成的文本和客户端对象,便于后续调用其他函数
# 定义与 DeepSeek 模型进行简单交互的函数,用于获取直接的回答
def llm_deepseek(question, client):
"""
该函数直接调用 DeepSeek 模型,根据用户问题生成回答。
参数:
question (str): 用户提出的问题,是模型生成回答的依据。
client (OpenAI): OpenAI 客户端对象,用于与 DeepSeek 模型进行通信。
返回:
str: 模型生成的回答文本。
"""
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "user", "content": question}, # 用户消息,包含用户提出的问题
],
max_tokens=1024, # 限制生成文本的最大长度
temperature=0.99, # 控制生成文本的随机性和创造性
stream=False # 禁用流式输出
)
generated_text = response.choices[0].message.content # 提取生成的文本内容
return generated_text
# 获取服务器脚本路径,为启动服务器进程做准备
server_script_path = os.path.join(os.path.dirname(__file__), "base_customer_mcp_tools.py")
# 定义服务器参数,配置服务器进程的运行环境
server_params = StdioServerParameters(
command="python", # 运行命令,指定使用 Python 解释器
args=[server_script_path], # 服务器脚本路径,指向包含工具实现的脚本文件
env=None # 可选的环境变量,用于配置服务器进程的运行环境
)
# 定义异步运行函数,处理用户查询并调用相应工具
async def run(question="你是谁?"):
"""
该异步函数是整个智能客服流程的核心,负责处理用户查询、调用大模型进行意图识别、调度工具并生成最终回答。
参数:
question (str, optional): 用户提出的问题,默认为 "你是谁?"。
"""
# 建立与 MCP 服务器的连接
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 初始化连接,确保客户端与服务器之间的通信正常
await session.initialize()
# 列出可用工具,获取工具的详细信息
tools = await session.list_tools()
# 将工具信息转换为易读的格式
s = transform_json(tools.tools) + "\n"
# 调用大模型进行意图识别和工具调用命令生成
response, client = ask_llm_deepseek(question, s)
# 清理生成的 JSON 字符串中的 Markdown 标记,确保格式正确
response = response.strip().replace('```json', '').replace('```', '').strip()
# 将清理后的字符串解析为 JSON 对象
mtools = json.loads(response) # 现在 response 是纯 JSON 字符串
print("解析后的工具调用命令: --> ", mtools)
# 检查生成的命令中是否包含工具名称
if 'tool' in mtools:
tool_name = mtools['tool'] # 获取工具名称
tool_input = mtools['tool_input'] # 获取工具输入参数
# 调用指定的工具,执行相应的任务
print("正在调用工具:", tool_name, "输入参数:", tool_input)
ret = await session.call_tool(tool_name, tool_input) # 异步调用工具
if ret:
try:
# 尝试将工具返回的结果解析为 JSON 对象
r = json.loads(ret.content[0].text)
except:
# 如果解析失败,则直接使用返回的文本内容
r = ret.content[0].text
print("工具返回结果:", r)
# 根据工具返回结果和用户问题,生成最终回答
questions = f"用户的问题是{question},根据{tool_name}的返回结果为:{r},根据以上信息,回答问题。"
r = llm_deepseek(questions, client) # 调用大模型生成回答
print("最终回答:", r)
else:
# 如果没有生成有效的工具调用命令,则直接调用大模型回答用户问题
r = llm_deepseek(question, client)
print("直接回答:", r)
if __name__ == "__main__":
import asyncio
# 定义一组测试问题,用于验证智能客服系统的功能
questions = [
'帮我查一下订单,订单编号是 5241368562',
'我觉得你们家商品价格太高!',
"手机的质保是多久?",
]
# 遍历测试问题,逐一处理并输出结果
for question in questions:
asyncio.run(run(question))
7.5 本章小结
在本章中,我们讲解了基于分层Agent架构的跨境电商智能客服实战案例,通过微调本地化大模型作为客服基座模型,伴随MCP功能模块的添加,我们的跨境电商智能客服基本上实现了客户问询、订单查询以及投诉记录等功能。然而,这些基础能力的整合仅是智能客服进化的起点,其核心价值在于如何通过动态服务编排与业务数据反哺,将功能模块转化为可自主优化的“有机生命体”。
更多推荐


所有评论(0)