第 78 章

案例一:企业级代码审查系统(Plugin + Hook + LSP + CI/CD 完整架构)

第七十八章:构建 AI 客服系统:意图识别、知识库问答与人工兜底

78.1 AI 客服系统的架构全景

AI 客服系统是企业部署 Claude 最成熟、ROI 最清晰的场景之一。一个完整的 AI 客服系统不是单一的"问答机器人",而是一个由多个协同模块组成的智能管道:

用户消息输入
       ↓
[预处理层] 语言检测 → 消息清洗 → 会话上下文拼接
       ↓
[意图识别层] 意图分类 → 实体提取 → 置信度评估
       ↓
[路由层] 规则路由 / 模型路由 → 决定处理路径
    ↙        ↓          ↘
[FAQ回答]  [知识库问答]  [人工升级]
    ↓           ↓            ↓
[后处理层] 响应格式化 → 品牌声音检查 → 发送
       ↓
[反馈循环] 用户评价 → 日志记录 → 持续优化

本章将逐层拆解这个架构,提供可直接用于生产的代码实现。

78.2 意图识别(Intent Recognition)

78.2.1 意图分类体系设计

意图分类是整个系统的路由核心。设计意图体系时需要考虑:

from anthropic import Anthropic
import json

client = Anthropic()

# 意图分类体系定义
INTENT_TAXONOMY = {
    "account_issues": {
        "description": "账户相关问题",
        "subtypes": {
            "login_problem": "登录/密码问题",
            "account_verification": "账户验证/实名认证",
            "account_suspension": "账户封禁/限制",
            "account_deletion": "注销账户"
        },
        "auto_handle_threshold": 0.85
    },
    "billing": {
        "description": "账单/支付问题",
        "subtypes": {
            "payment_failed": "支付失败",
            "refund_request": "退款申请",
            "invoice_request": "发票申请",
            "subscription_inquiry": "订阅咨询"
        },
        "auto_handle_threshold": 0.80  # 账单问题需更高置信度
    },
    "product_inquiry": {
        "description": "产品功能咨询",
        "subtypes": {
            "feature_question": "功能使用疑问",
            "pricing_inquiry": "定价咨询",
            "compatibility": "兼容性问题",
            "feature_request": "功能建议"
        },
        "auto_handle_threshold": 0.75
    },
    "technical_support": {
        "description": "技术故障报告",
        "subtypes": {
            "bug_report": "Bug 报告",
            "performance_issue": "性能问题",
            "integration_help": "集成帮助",
            "api_issue": "API 问题"
        },
        "auto_handle_threshold": 0.70  # 技术问题复杂度高,降低自动处理阈值
    },
    "complaint": {
        "description": "投诉/不满",
        "subtypes": {
            "service_complaint": "服务投诉",
            "product_complaint": "产品投诉",
            "escalation_request": "要求升级人工"
        },
        "auto_handle_threshold": 0.0  # 投诉始终升级人工
    }
}

INTENT_CLASSIFIER_SYSTEM = f"""你是一个精确的客服意图分类器。

意图分类体系:
{json.dumps(INTENT_TAXONOMY, ensure_ascii=False, indent=2)}

分类规则:
1. 识别用户消息的主要意图和子意图
2. 如果消息包含多个意图,按重要性排序,最多识别 2 个
3. 提取关键实体(产品名称、订单号、金额等)
4. 评估用户的情绪状态(neutral/frustrated/urgent/angry)

输出 JSON 格式:
{{
  "primary_intent": "账单/refund_request",
  "secondary_intent": null,
  "entities": {{"order_id": "ORD-12345", "amount": "299元"}},
  "emotion": "frustrated",
  "confidence": 0.92,
  "requires_context": false,
  "raw_issue": "用户想要退款"
}}"""

def classify_intent(user_message: str, conversation_history: list = None) -> dict:
    """
    分类用户意图
    conversation_history: 最近 N 轮对话历史(用于上下文理解)
    """
    messages = []
    
    # 添加对话历史作为上下文
    if conversation_history:
        history_text = "\n".join([
            f"用户:{turn['user']}\n客服:{turn['assistant']}"
            for turn in conversation_history[-3:]  # 最近3轮
        ])
        messages.append({
            "role": "user",
            "content": f"对话历史:\n{history_text}\n\n当前消息:{user_message}"
        })
    else:
        messages.append({"role": "user", "content": user_message})
    
    response = client.messages.create(
        model="claude-haiku-4-5",  # 使用轻量模型,意图分类不需要最强模型
        max_tokens=300,
        system=INTENT_CLASSIFIER_SYSTEM,
        messages=messages
    )
    
    try:
        return json.loads(response.content[0].text)
    except json.JSONDecodeError:
        return {
            "primary_intent": "unknown",
            "confidence": 0.0,
            "error": "分类失败"
        }

78.2.2 情绪检测与升级触发

ESCALATION_TRIGGERS = {
    "explicit_request": ["要人工", "转人工", "找真人", "speak to human", "human agent"],
    "emotion_threshold": "angry",  # 检测到愤怒情绪时升级
    "intent_always_escalate": ["complaint", "escalation_request"],
    "failed_attempts": 3,  # 连续 3 次未解决自动升级
    "high_value_keywords": ["法律", "律师", "投诉到工商", "媒体曝光", "退款不受理"]
}

def should_escalate(
    intent_result: dict,
    conversation_history: list,
    failed_attempts: int
) -> dict:
    """
    判断是否需要升级到人工客服
    """
    reasons = []
    
    # 检查意图是否总是需要升级
    primary = intent_result.get("primary_intent", "")
    for always_escalate in ESCALATION_TRIGGERS["intent_always_escalate"]:
        if always_escalate in primary:
            reasons.append(f"意图类型需要人工处理:{always_escalate}")
    
    # 检查情绪
    if intent_result.get("emotion") == "angry":
        reasons.append("用户情绪激动")
    
    # 检查置信度
    confidence = intent_result.get("confidence", 0)
    intent_category = primary.split("/")[0] if "/" in primary else primary
    threshold = INTENT_TAXONOMY.get(intent_category, {}).get("auto_handle_threshold", 0.8)
    
    if confidence < threshold:
        reasons.append(f"置信度 {confidence:.2f} 低于阈值 {threshold}")
    
    # 检查失败次数
    if failed_attempts >= ESCALATION_TRIGGERS["failed_attempts"]:
        reasons.append(f"已尝试 {failed_attempts} 次未解决")
    
    # 检查高风险关键词
    latest_message = ""
    if conversation_history:
        latest_message = conversation_history[-1].get("user", "")
    
    for keyword in ESCALATION_TRIGGERS["high_value_keywords"]:
        if keyword in latest_message:
            reasons.append(f"检测到高风险关键词:{keyword}")
    
    return {
        "should_escalate": len(reasons) > 0,
        "reasons": reasons,
        "priority": "high" if "情绪激动" in reasons or "高风险" in str(reasons) else "normal"
    }

78.3 知识库问答(Knowledge Base Q&A)

78.3.1 知识库架构

AI 客服的知识库通常由三类内容组成:

KNOWLEDGE_BASE_STRUCTURE = {
    "faq": {
        "description": "常见问题与标准答案",
        "format": "问题-答案对",
        "update_frequency": "weekly",
        "retrieval_method": "semantic_search"
    },
    "product_docs": {
        "description": "产品文档、操作手册",
        "format": "结构化文档",
        "update_frequency": "per_release",
        "retrieval_method": "hybrid_search"
    },
    "policy_docs": {
        "description": "退款政策、服务条款、隐私政策",
        "format": "条款文档",
        "update_frequency": "monthly",
        "retrieval_method": "keyword_search"
    },
    "case_history": {
        "description": "历史成功解决的客服案例",
        "format": "案例库",
        "update_frequency": "continuous",
        "retrieval_method": "semantic_search"
    }
}

78.3.2 基于检索增强的问答

class CustomerServiceKnowledgeBase:
    """客服知识库(简化实现,生产环境应接入向量数据库)"""
    
    def __init__(self):
        self.faqs = []
        self.policies = {}
        self.product_docs = []
    
    def search(self, query: str, top_k: int = 5) -> list:
        """
        检索相关知识库内容
        生产环境应替换为向量数据库检索
        """
        # 这里展示接口设计,实际应用应调用 Pinecone/Qdrant 等
        results = []
        
        # FAQ 语义搜索(简化版)
        for faq in self.faqs:
            relevance = self._simple_relevance(query, faq["question"])
            if relevance > 0.5:
                results.append({
                    "type": "faq",
                    "content": f"Q: {faq['question']}\nA: {faq['answer']}",
                    "relevance": relevance,
                    "metadata": faq.get("metadata", {})
                })
        
        # 政策文档关键词匹配
        query_lower = query.lower()
        for policy_name, policy_content in self.policies.items():
            keywords = policy_content.get("keywords", [])
            if any(kw in query_lower for kw in keywords):
                results.append({
                    "type": "policy",
                    "content": policy_content["content"],
                    "relevance": 0.8,
                    "metadata": {"policy": policy_name}
                })
        
        # 按相关度排序
        results.sort(key=lambda x: x["relevance"], reverse=True)
        return results[:top_k]
    
    def _simple_relevance(self, query: str, text: str) -> float:
        """简化的相关度计算(生产环境应使用嵌入向量)"""
        query_words = set(query.lower().split())
        text_words = set(text.lower().split())
        overlap = query_words & text_words
        return len(overlap) / (len(query_words) + 1)


def answer_with_knowledge_base(
    user_query: str,
    knowledge_base: CustomerServiceKnowledgeBase,
    company_name: str,
    intent_result: dict
) -> dict:
    """
    基于知识库的客服问答
    """
    # 检索相关知识
    relevant_docs = knowledge_base.search(user_query, top_k=5)
    
    if not relevant_docs:
        return {
            "answer": None,
            "confidence": 0.0,
            "source": "no_knowledge",
            "should_escalate": True
        }
    
    # 构建上下文
    context_text = "\n\n".join([
        f"[{doc['type'].upper()}] {doc['content']}"
        for doc in relevant_docs
    ])
    
    # 调用 Claude 生成答案
    qa_system = f"""你是 {company_name} 的专业客服代表。

回答规则:
1. 只基于提供的知识库内容回答
2. 如果知识库中没有相关信息,明确说明无法回答并建议联系人工
3. 不要编造信息或做出知识库外的承诺
4. 语气要专业、温暖、有帮助
5. 如果涉及具体政策,引用具体条款

当前用户情绪:{intent_result.get('emotion', 'neutral')}
(如果用户情绪消极,在回答中先表达理解和歉意)"""
    
    qa_prompt = f"""用户问题:{user_query}

相关知识库内容:
{context_text}

请根据知识库内容回答用户问题。如果知识库内容不足以回答,请直接说明。"""
    
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=500,
        system=qa_system,
        messages=[{"role": "user", "content": qa_prompt}]
    )
    
    answer = response.content[0].text
    
    # 评估答案置信度
    has_answer = "无法" not in answer and "无相关" not in answer and "请联系人工" not in answer
    
    return {
        "answer": answer,
        "confidence": 0.85 if has_answer else 0.2,
        "source": "knowledge_base",
        "referenced_docs": [doc["type"] for doc in relevant_docs[:3]],
        "should_escalate": not has_answer
    }

78.4 对话流程管理

78.4.1 多轮对话状态机

from enum import Enum

class ConversationState(Enum):
    GREETING = "greeting"
    INTENT_COLLECTION = "intent_collection"
    INFO_GATHERING = "info_gathering"
    RESOLVING = "resolving"
    ESCALATING = "escalating"
    RESOLVED = "resolved"
    CLOSED = "closed"

class CustomerServiceConversation:
    """AI 客服会话管理器"""
    
    def __init__(self, session_id: str, company_config: dict):
        self.session_id = session_id
        self.config = company_config
        self.state = ConversationState.GREETING
        self.history = []
        self.intent_history = []
        self.failed_attempts = 0
        self.gathered_info = {}
        self.escalation_info = None
    
    def process_message(self, user_message: str) -> dict:
        """
        处理用户消息,返回响应
        """
        # Step 1: 意图识别
        intent = classify_intent(user_message, self.history[-5:] if self.history else None)
        self.intent_history.append(intent)
        
        # Step 2: 判断是否需要升级
        escalation_check = should_escalate(intent, self.history, self.failed_attempts)
        
        if escalation_check["should_escalate"]:
            return self._handle_escalation(user_message, escalation_check)
        
        # Step 3: 根据意图路由
        if intent.get("confidence", 0) < 0.6:
            return self._clarify_intent(user_message)
        
        # Step 4: 知识库查询和回答
        kb = self.config.get("knowledge_base")
        if kb:
            qa_result = answer_with_knowledge_base(
                user_message, kb, 
                self.config["company_name"],
                intent
            )
            
            if qa_result["confidence"] > 0.7:
                response = qa_result["answer"]
                self._record_turn(user_message, response, intent, "knowledge_base")
                return {
                    "response": response,
                    "state": "resolved",
                    "requires_followup": True
                }
        
        # Step 5: 通用 LLM 回答
        self.failed_attempts += 1
        return self._general_response(user_message, intent)
    
    def _handle_escalation(self, user_message: str, escalation_check: dict) -> dict:
        """处理升级到人工客服"""
        self.state = ConversationState.ESCALATING
        
        # 生成升级摘要
        summary = self._generate_handoff_summary()
        
        escalation_message = self._generate_escalation_message(
            escalation_check["reasons"],
            escalation_check["priority"]
        )
        
        self.escalation_info = {
            "trigger_reasons": escalation_check["reasons"],
            "priority": escalation_check["priority"],
            "conversation_summary": summary,
            "user_info": self.gathered_info,
            "intent_history": [i.get("primary_intent") for i in self.intent_history]
        }
        
        return {
            "response": escalation_message,
            "state": "escalating",
            "escalation_data": self.escalation_info
        }
    
    def _generate_handoff_summary(self) -> str:
        """生成交接给人工客服的会话摘要"""
        if not self.history:
            return "新会话,无历史记录"
        
        history_text = "\n".join([
            f"用户:{turn['user']}\nAI:{turn['assistant']}"
            for turn in self.history[-5:]
        ])
        
        summary_prompt = f"""请为以下客服对话生成简洁的接手摘要(不超过200字)。

对话记录:
{history_text}

摘要应包含:
1. 用户的主要问题
2. 已尝试的解决方法
3. 为什么需要升级人工
4. 用户情绪状态"""
        
        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=300,
            messages=[{"role": "user", "content": summary_prompt}]
        )
        
        return response.content[0].text
    
    def _generate_escalation_message(self, reasons: list, priority: str) -> str:
        """生成通知用户升级到人工的消息"""
        wait_time = "2-3 分钟" if priority == "high" else "5-10 分钟"
        
        return f"""我理解您的问题需要专业人工客服的协助。我已将您的对话记录和问题详情传递给人工客服团队。

预计等待时间:{wait_time}

在等待期间,您也可以:
- 查看我们的帮助中心(help.example.com)
- 通过邮件联系我们([email protected])

感谢您的耐心等待!"""
    
    def _record_turn(self, user_msg: str, assistant_msg: str, intent: dict, source: str):
        self.history.append({
            "user": user_msg,
            "assistant": assistant_msg,
            "intent": intent.get("primary_intent"),
            "source": source
        })

78.5 人工兜底的交接协议(Handoff Protocol)

78.5.1 交接数据包

class HandoffPackage:
    """
    人工客服接手时的完整数据包
    """
    
    def __init__(self, conversation: CustomerServiceConversation):
        self.conversation = conversation
    
    def generate(self) -> dict:
        return {
            "session_id": self.conversation.session_id,
            "timestamp": get_current_timestamp(),
            "priority": self._assess_priority(),
            "user_profile": {
                "identified_info": self.conversation.gathered_info,
                "emotion_trajectory": self._get_emotion_trajectory(),
                "interaction_count": len(self.conversation.history)
            },
            "issue_summary": {
                "primary_issue": self._get_primary_issue(),
                "intent_history": [i.get("primary_intent") for i in self.conversation.intent_history],
                "attempted_solutions": self._get_attempted_solutions(),
                "unresolved_aspects": self._get_unresolved_aspects()
            },
            "ai_assessment": {
                "escalation_reasons": self.conversation.escalation_info.get("trigger_reasons", []),
                "recommended_action": self._recommend_action(),
                "relevant_policies": self._identify_relevant_policies()
            },
            "full_conversation": self.conversation.history
        }
    
    def _assess_priority(self) -> str:
        if self.conversation.escalation_info:
            return self.conversation.escalation_info.get("priority", "normal")
        return "normal"
    
    def _get_emotion_trajectory(self) -> list:
        return [
            intent.get("emotion", "neutral") 
            for intent in self.conversation.intent_history
        ]
    
    def _get_primary_issue(self) -> str:
        if self.conversation.intent_history:
            return self.conversation.intent_history[0].get("raw_issue", "未识别")
        return "未知问题"
    
    def _get_attempted_solutions(self) -> list:
        return [
            turn.get("source", "unknown")
            for turn in self.conversation.history
            if turn.get("source") != "unknown"
        ]
    
    def _get_unresolved_aspects(self) -> str:
        if not self.conversation.history:
            return "尚未开始对话"
        # 最后一轮 AI 回复中未解决的内容
        return self.conversation.history[-1].get("assistant", "")[:200]
    
    def _recommend_action(self) -> str:
        intents = [i.get("primary_intent", "") for i in self.conversation.intent_history]
        if any("refund" in i for i in intents):
            return "验证订单信息,处理退款申请"
        if any("complaint" in i for i in intents):
            return "先表达关切,了解具体诉求,按投诉流程处理"
        return "继续了解用户问题并提供解决方案"
    
    def _identify_relevant_policies(self) -> list:
        intents = [i.get("primary_intent", "") for i in self.conversation.intent_history]
        policies = []
        if any("refund" in i for i in intents):
            policies.append("退款政策")
        if any("billing" in i for i in intents):
            policies.append("账单政策")
        return policies
    
    def get_current_timestamp(self):
        from datetime import datetime
        return datetime.now().isoformat()

78.6 监控与持续优化

78.6.1 关键指标

CUSTOMER_SERVICE_METRICS = {
    "deflection_rate": {
        "description": "AI 成功自主解决(无需人工)的比例",
        "target": ">= 0.70",
        "formula": "ai_resolved / total_conversations"
    },
    "escalation_accuracy": {
        "description": "升级决策的准确性(升级后人工确认确实需要升级的比例)",
        "target": ">= 0.85",
        "formula": "justified_escalations / total_escalations"
    },
    "first_contact_resolution": {
        "description": "单次对话解决率",
        "target": ">= 0.65",
        "formula": "single_session_resolved / total_conversations"
    },
    "intent_recognition_accuracy": {
        "description": "意图识别准确率",
        "target": ">= 0.90",
        "formula": "correct_intents / total_classified"
    },
    "user_satisfaction_score": {
        "description": "用户满意度(1-5)",
        "target": ">= 4.2",
        "formula": "user_rating_average"
    }
}

小结

AI 客服系统的核心价值在于:用精准的意图识别把问题导向正确的处理路径,用知识库问答高效解决标准化问题,用可信赖的升级机制在 AI 能力边界处将用户安全交接给人工。

设计这类系统的核心原则是保守优于冒进:宁可多升级几次人工,也不能让用户的合理诉求在自动化流程中被卡死。升级触发条件要宽松,升级的体验要流畅,交接数据包要完整——这三点是 AI 客服系统可信赖性的基础。

本章评分
4.7  / 5  (3 评分)

💬 留言讨论