从零构建多轮对话客服系统:大模型+意图识别+工单流转

2026-03-12 · 阅读约14分钟

大模型让智能客服从"关键词匹配"时代跨入了"真正理解语义"的时代。但"能聊天"和"能当客服"之间还有很大的差距。一个可用的客服系统需要处理:意图识别、知识库检索、多轮上下文管理、工单创建、人机协作转接。

这篇文章从工程角度,完整拆解一个基于大模型的多轮对话客服系统。

一、系统架构

                    用户消息
                       │
                ┌──────▼──────┐
                │   对话管理器  │ ← 维护会话状态和上下文
                └──────┬──────┘
                       │
              ┌────────▼────────┐
              │  意图识别 + 路由  │ ← Function Calling驱动
              └────────┬────────┘
                       │
        ┌──────────────┼──────────────┐
        │              │              │
  ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
  │ 知识库问答 │ │ 业务操作  │ │ 转人工    │
  │ (RAG检索) │ │ (查订单等)│ │ (工单系统)│
  └───────────┘ └───────────┘ └───────────┘

二、对话管理器

多轮对话的核心是上下文管理。每次用户发消息,系统需要把历史对话一起发给大模型。

import time
import json

class ConversationManager:
    def __init__(self, max_history=20, session_timeout=1800):
        self.sessions = {}  # {session_id: {messages, last_active, metadata}}
        self.max_history = max_history
        self.session_timeout = session_timeout  # 30分钟超时

    def get_session(self, session_id):
        """获取或创建会话"""
        now = time.time()

        # 检查超时
        if session_id in self.sessions:
            if now - self.sessions[session_id]["last_active"] > self.session_timeout:
                del self.sessions[session_id]  # 超时,清除旧会话

        if session_id not in self.sessions:
            self.sessions[session_id] = {
                "messages": [],
                "last_active": now,
                "metadata": {"turn_count": 0, "intent_history": []}
            }

        session = self.sessions[session_id]
        session["last_active"] = now
        return session

    def add_message(self, session_id, role, content):
        """添加消息到会话"""
        session = self.get_session(session_id)
        session["messages"].append({"role": role, "content": content})
        session["metadata"]["turn_count"] += 1

        # 保持历史长度在限制内
        if len(session["messages"]) > self.max_history:
            # 保留system消息 + 最近的N条
            system_msgs = [m for m in session["messages"] if m["role"] == "system"]
            recent_msgs = session["messages"][-self.max_history:]
            session["messages"] = system_msgs + recent_msgs

    def get_messages(self, session_id):
        """获取完整的消息历史(供大模型使用)"""
        session = self.get_session(session_id)
        return session["messages"]
会话超时非常重要。如果用户30分钟没说话再回来,大概率是新问题了。继续用旧上下文反而会干扰模型理解。

三、基于Function Calling的意图路由

传统客服用规则或分类模型做意图识别,准确率有限且维护成本高。大模型的Function Calling天然适合这个场景——让模型自己判断应该调用哪个"工具"。

CUSTOMER_SERVICE_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "search_knowledge_base",
            "description": "从知识库中搜索产品信息、使用教程、常见问题的答案。当用户提问关于产品功能、使用方法、故障排查等问题时使用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "用户问题的核心关键词,用于知识库检索"
                    },
                    "category": {
                        "type": "string",
                        "enum": ["产品功能", "使用教程", "故障排查", "退换货政策", "账户问题"],
                        "description": "问题所属分类"
                    }
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "query_order",
            "description": "查询用户的订单信息,包括订单状态、物流信息、支付信息等。",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "订单编号"
                    },
                    "phone": {
                        "type": "string",
                        "description": "下单时的手机号(当没有订单号时,用手机号查询)"
                    }
                }
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "create_ticket",
            "description": "创建工单,将问题转交给人工客服处理。当问题超出AI能力范围、用户明确要求转人工、或涉及退款/投诉等敏感操作时使用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "工单标题,简述用户问题"
                    },
                    "priority": {
                        "type": "string",
                        "enum": ["low", "medium", "high", "urgent"],
                        "description": "优先级。投诉和退款设为high,一般问题设为medium"
                    },
                    "summary": {
                        "type": "string",
                        "description": "问题摘要,包含关键信息供人工客服参考"
                    }
                },
                "required": ["title", "priority", "summary"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "direct_reply",
            "description": "直接回复用户。用于闲聊、打招呼、简单问题等不需要查询外部系统的场景。",
            "parameters": {
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string",
                        "description": "回复给用户的消息"
                    }
                },
                "required": ["message"]
            }
        }
    }
]

四、知识库检索集成

客服系统的知识库检索就是RAG(详见我的RAG实战文章)。这里重点讲客服场景下的特殊处理

def search_knowledge_base(query, category=None):
    """客服知识库检索"""
    # 1. 向量检索
    results = vector_db.search(
        query_embedding=embed(query),
        top_k=10,
        filter={"category": category} if category else None
    )

    # 2. 重排序
    reranked = reranker.rank(query, [r["content"] for r in results])

    # 3. 客服场景特殊处理:优先返回精确匹配的FAQ
    faq_results = [r for r in reranked if r["type"] == "faq" and r["score"] > 0.85]
    if faq_results:
        return faq_results[0]  # FAQ精确匹配,直接返回标准答案

    # 4. 无精确FAQ,返回Top-3文档供大模型综合回答
    return reranked[:3]

FAQ优先策略

客服场景下,很多问题有标准答案(如退换货政策、营业时间)。这类问题不应该让大模型自由发挥,而是直接返回预设的标准回答。

# FAQ数据结构
{
    "question": "退货流程是什么?",
    "answer": "退货流程如下:\n1. 在订单页面点击「申请退货」\n2. 选择退货原因并提交\n3. 等待审核(1-2个工作日)\n4. 审核通过后,按提示寄回商品\n5. 收到退货后3个工作日内退款\n\n注意:商品需保持完好,签收后7天内可申请退货。",
    "type": "faq",
    "category": "退换货政策"
}

五、完整的对话处理流程

from openai import OpenAI

class CustomerServiceBot:
    def __init__(self):
        self.client = OpenAI(api_key="xxx", base_url="https://api.deepseek.com")
        self.conv_manager = ConversationManager()
        self.system_prompt = """你是一个专业、友好的客服助手。

规则:
1. 回答要简洁准确,不要啰嗦
2. 不确定的信息不要编造,请调用知识库搜索
3. 涉及退款、投诉、账号安全等敏感问题,主动创建工单转人工
4. 用户情绪激动时,先安抚再解决问题
5. 不要透露你是AI,以客服身份回答"""

    def handle_message(self, session_id, user_message):
        """处理用户消息的主函数"""
        # 1. 添加system prompt(首次)
        session = self.conv_manager.get_session(session_id)
        if not session["messages"]:
            self.conv_manager.add_message(session_id, "system", self.system_prompt)

        # 2. 添加用户消息
        self.conv_manager.add_message(session_id, "user", user_message)

        # 3. 调用大模型(带Function Calling)
        messages = self.conv_manager.get_messages(session_id)
        response = self.client.chat.completions.create(
            model="deepseek-chat",
            messages=messages,
            tools=CUSTOMER_SERVICE_TOOLS,
            temperature=0.3  # 客服场景用低温度,保证稳定性
        )

        choice = response.choices[0]

        # 4. 处理工具调用
        if choice.message.tool_calls:
            tool_call = choice.message.tool_calls[0]
            func_name = tool_call.function.name
            func_args = json.loads(tool_call.function.arguments)

            # 执行对应的函数
            tool_result = self.execute_tool(func_name, func_args)

            # 把工具结果返回给模型,让模型组织最终回复
            self.conv_manager.add_message(session_id, "assistant",
                json.dumps({"tool_call": func_name, "args": func_args}))
            self.conv_manager.add_message(session_id, "tool", json.dumps(tool_result))

            # 再次调用模型生成最终回复
            final_response = self.client.chat.completions.create(
                model="deepseek-chat",
                messages=self.conv_manager.get_messages(session_id),
                temperature=0.3
            )
            reply = final_response.choices[0].message.content

        else:
            reply = choice.message.content

        # 5. 记录助手回复
        self.conv_manager.add_message(session_id, "assistant", reply)
        return reply

    def execute_tool(self, func_name, args):
        """执行工具调用"""
        if func_name == "search_knowledge_base":
            return search_knowledge_base(args["query"], args.get("category"))
        elif func_name == "query_order":
            return query_order_from_db(args.get("order_id"), args.get("phone"))
        elif func_name == "create_ticket":
            return create_support_ticket(args["title"], args["priority"], args["summary"])
        elif func_name == "direct_reply":
            return {"message": args["message"]}
        return {"error": "未知工具"}

六、人机协作:转人工机制

AI客服不可能解决所有问题。关键是知道什么时候该转人工

自动转人工的触发条件

def should_transfer_to_human(session):
    """判断是否应该转人工"""
    messages = session["messages"]
    metadata = session["metadata"]

    # 规则1: 用户主动要求
    last_msg = messages[-1]["content"] if messages else ""
    transfer_keywords = ["转人工", "找人工", "真人客服", "人工服务", "找你们经理"]
    if any(kw in last_msg for kw in transfer_keywords):
        return True, "用户主动要求转人工"

    # 规则2: 多轮未解决(连续3轮包含负面表达)
    negative_keywords = ["没用", "解决不了", "还是不行", "不对", "你听不懂"]
    recent = [m["content"] for m in messages[-6:] if m["role"] == "user"]
    negative_count = sum(1 for m in recent if any(kw in m for kw in negative_keywords))
    if negative_count >= 2:
        return True, "用户多轮表达不满,建议转人工"

    return False, ""

转接时的信息传递

转人工时,把AI的对话摘要一起传给人工客服,避免用户重复描述问题:

def create_handoff_summary(session):
    """生成转人工摘要"""
    messages = session["messages"]
    summary_prompt = f"""请根据以下客服对话记录,生成一份简洁的摘要供人工客服参考。
包含:用户的核心问题、已尝试的解决方案、用户情绪状态。

对话记录:
{json.dumps(messages[-10:], ensure_ascii=False)}"""

    response = llm.chat(summary_prompt)
    return response

七、监控与优化

关键指标

持续优化循环

  1. 收集AI无法回答的问题 → 补充知识库
  2. 收集用户负面反馈的对话 → 优化System Prompt
  3. 分析高频问题 → 制作标准FAQ
  4. 定期评估模型效果 → 考虑是否需要微调
不要追求100%的自动解决率。客服系统的核心是用户满意度,不是AI的自主率。该转人工的时候果断转,比让AI强行回答错误答案要好得多。
← 返回文章列表