摘要

本文通过一个完整的智能客服机器人案例,演示如何使用 OpenClaw 构建企业级客服系统。文章涵盖意图识别、槽位填充、多轮对话、知识库问答等核心功能,帮助开发者掌握 OpenClaw 在客服场景的应用。通过详细的系统设计和代码实现,让读者了解智能客服机器人的完整构建过程。🤖


1. 引言 - 智能客服概述

1.1 客服系统痛点

传统客服系统面临诸多挑战,人工客服难以满足现代服务需求:

痛点 传统客服 OpenClaw智能客服
响应慢 排队等待 即时响应
成本高 人力成本 自动化降本
质量不稳定 依赖个人能力 标准化服务
无法7×24 轮班制 全天候服务
数据难沉淀 分散记录 知识积累

1.2 系统架构设计

分析层

知识服务层

对话管理层

接入层

Web聊天

APP内嵌

微信公众号

电话语音

意图识别

槽位填充

对话状态追踪

回复生成

FAQ知识库

产品知识库

工单系统

人工客服

会话分析

热点问题

满意度分析

1.3 核心功能规划

功能模块 核心能力 技术实现
意图识别 理解用户意图 NLU分类
槽位填充 提取关键信息 实体识别
多轮对话 复杂场景处理 状态机 + DST
知识问答 FAQ自动回答 语义匹配 + RAG
人机协作 无缝转人工 智能路由

2. 意图识别模块

2.1 意图定义与管理

from dataclasses import dataclass, field
from typing import Dict, List, Optional
from enum import Enum
import re

class IntentCategory(Enum):
    """意图类别"""
    CONSULT = "consult"       # 咨询类
    COMPLAINT = "complaint"   # 投诉类
    SERVICE = "service"       # 服务类
    CHITCHAT = "chitchat"     # 闲聊类

@dataclass
class Intent:
    """意图定义"""
    id: str
    name: str
    category: IntentCategory
    description: str
    examples: List[str] = field(default_factory=list)
    slots: List[str] = field(default_factory=list)  # 需要的槽位
    responses: List[str] = field(default_factory=list)
    priority: int = 0

class IntentManager:
    """意图管理器"""
    
    def __init__(self):
        self.intents: Dict[str, Intent] = {}
        self.patterns: Dict[str, List[str]] = {}
    
    def register_intent(self, intent: Intent):
        """注册意图"""
        self.intents[intent.id] = intent
        
        # 生成模式
        self.patterns[intent.id] = self._generate_patterns(intent.examples)
    
    def _generate_patterns(self, examples: List[str]) -> List[str]:
        """生成匹配模式"""
        patterns = []
        
        for example in examples:
            # 简化:直接使用示例作为模式
            # 实际应使用模板生成
            pattern = example.lower()
            patterns.append(pattern)
        
        return patterns
    
    def recognize(self, text: str) -> List[Dict]:
        """识别意图"""
        text_lower = text.lower()
        
        results = []
        
        for intent_id, intent in self.intents.items():
            score = self._calculate_score(text_lower, intent)
            
            if score > 0:
                results.append({
                    "intent_id": intent_id,
                    "intent_name": intent.name,
                    "category": intent.category.value,
                    "confidence": score,
                    "required_slots": intent.slots
                })
        
        # 按置信度排序
        results.sort(key=lambda x: x["confidence"], reverse=True)
        
        return results
    
    def _calculate_score(self, text: str, intent: Intent) -> float:
        """计算意图匹配分数"""
        patterns = self.patterns.get(intent.id, [])
        
        max_score = 0
        
        for pattern in patterns:
            # 简单匹配
            if pattern in text:
                score = len(pattern) / len(text)
                max_score = max(max_score, score)
            else:
                # 关键词匹配
                keywords = pattern.split()
                matched = sum(1 for kw in keywords if kw in text)
                score = matched / len(keywords) * 0.5
                max_score = max(max_score, score)
        
        return max_score
    
    def get_intent(self, intent_id: str) -> Optional[Intent]:
        """获取意图"""
        return self.intents.get(intent_id)

# 使用示例
im = IntentManager()

# 注册意图
im.register_intent(Intent(
    id="query_order",
    name="查询订单",
    category=IntentCategory.CONSULT,
    description="用户查询订单状态",
    examples=[
        "我的订单到哪了",
        "查询订单状态",
        "订单发货了吗",
        "我想看一下我的订单"
    ],
    slots=["order_id"],
    responses=[
        "请提供您的订单号,我帮您查询",
        "好的,请告诉我订单号"
    ]
))

im.register_intent(Intent(
    id="return_goods",
    name="退货退款",
    category=IntentCategory.SERVICE,
    description="用户申请退货退款",
    examples=[
        "我要退货",
        "申请退款",
        "商品不满意想退货",
        "怎么退款"
    ],
    slots=["order_id", "reason"],
    responses=[
        "请提供订单号和退货原因",
        "好的,请告诉我订单号"
    ]
))

# 识别意图
result = im.recognize("我想查一下我的订单到哪了")
print(f"识别结果: {result}")

2.2 意图分类器

from typing import Dict, List, Tuple
import numpy as np

class IntentClassifier:
    """意图分类器"""
    
    def __init__(self):
        self.intent_manager: IntentManager = None
        self.vocabulary: Dict[str, int] = {}
        self.model = None
    
    def train(self, training_data: List[Tuple[str, str]]):
        """训练分类器"""
        # 构建词汇表
        vocab = set()
        for text, intent_id in training_data:
            words = text.lower().split()
            vocab.update(words)
        
        self.vocabulary = {word: i for i, word in enumerate(vocab)}
        
        # 构建特征矩阵
        X = []
        y = []
        
        intent_to_idx = {}
        idx = 0
        
        for text, intent_id in training_data:
            features = self._text_to_features(text)
            X.append(features)
            
            if intent_id not in intent_to_idx:
                intent_to_idx[intent_id] = idx
                idx += 1
            
            y.append(intent_to_idx[intent_id])
        
        self.idx_to_intent = {v: k for k, v in intent_to_idx.items()}
        
        # 训练模型(简化:使用简单的最近邻)
        self.X_train = np.array(X)
        self.y_train = np.array(y)
    
    def _text_to_features(self, text: str) -> np.ndarray:
        """文本转特征向量"""
        features = np.zeros(len(self.vocabulary))
        
        words = text.lower().split()
        for word in words:
            if word in self.vocabulary:
                features[self.vocabulary[word]] = 1
        
        return features
    
    def predict(self, text: str) -> List[Dict]:
        """预测意图"""
        if self.X_train is None:
            return []
        
        features = self._text_to_features(text)
        
        # 计算与训练样本的相似度
        similarities = []
        
        for i, train_features in enumerate(self.X_train):
            sim = np.dot(features, train_features) / (
                np.linalg.norm(features) * np.linalg.norm(train_features) + 1e-8
            )
            similarities.append((i, sim))
        
        # 找最相似的
        similarities.sort(key=lambda x: x[1], reverse=True)
        
        # 聚合结果
        intent_scores = {}
        for idx, sim in similarities[:5]:
            intent_id = self.idx_to_intent[self.y_train[idx]]
            intent_scores[intent_id] = intent_scores.get(intent_id, 0) + sim
        
        # 归一化
        total = sum(intent_scores.values())
        results = []
        
        for intent_id, score in intent_scores.items():
            results.append({
                "intent_id": intent_id,
                "confidence": score / total if total > 0 else 0
            })
        
        results.sort(key=lambda x: x["confidence"], reverse=True)
        
        return results

# 使用示例
classifier = IntentClassifier()

# 训练数据
training_data = [
    ("我的订单到哪了", "query_order"),
    ("查询订单状态", "query_order"),
    ("订单发货了吗", "query_order"),
    ("我要退货", "return_goods"),
    ("申请退款", "return_goods"),
    ("怎么退款", "return_goods"),
    ("你好", "greeting"),
    ("在吗", "greeting"),
]

classifier.train(training_data)

# 预测
result = classifier.predict("我想查订单")
print(f"预测结果: {result}")

3. 槽位填充模块

3.1 槽位定义

from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any
from enum import Enum
import re

class SlotType(Enum):
    """槽位类型"""
    TEXT = "text"
    NUMBER = "number"
    DATE = "date"
    PHONE = "phone"
    ORDER_ID = "order_id"
    ENUM = "enum"

@dataclass
class SlotDefinition:
    """槽位定义"""
    name: str
    type: SlotType
    description: str
    required: bool = True
    prompt: str = ""
    validation_pattern: str = ""
    enum_values: List[str] = field(default_factory=list)

@dataclass
class SlotValue:
    """槽位值"""
    name: str
    value: Any
    confidence: float = 1.0
    source: str = "extracted"  # extracted, user_provided, default

class SlotFiller:
    """槽位填充器"""
    
    def __init__(self):
        self.slot_definitions: Dict[str, SlotDefinition] = {}
        self.extractors: Dict[SlotType, callable] = {
            SlotType.TEXT: self._extract_text,
            SlotType.NUMBER: self._extract_number,
            SlotType.DATE: self._extract_date,
            SlotType.PHONE: self._extract_phone,
            SlotType.ORDER_ID: self._extract_order_id,
            SlotType.ENUM: self._extract_enum
        }
    
    def register_slot(self, slot: SlotDefinition):
        """注册槽位"""
        self.slot_definitions[slot.name] = slot
    
    def fill_slots(self, text: str, required_slots: List[str]) -> Dict[str, SlotValue]:
        """填充槽位"""
        result = {}
        
        for slot_name in required_slots:
            slot_def = self.slot_definitions.get(slot_name)
            
            if not slot_def:
                continue
            
            extractor = self.extractors.get(slot_def.type)
            
            if extractor:
                value = extractor(text, slot_def)
                
                if value is not None:
                    result[slot_name] = SlotValue(
                        name=slot_name,
                        value=value,
                        confidence=0.8
                    )
        
        return result
    
    def _extract_text(self, text: str, slot_def: SlotDefinition) -> Optional[str]:
        """提取文本"""
        # 简化:返回整个文本
        return text if text else None
    
    def _extract_number(self, text: str, slot_def: SlotDefinition) -> Optional[float]:
        """提取数字"""
        match = re.search(r'\d+\.?\d*', text)
        if match:
            return float(match.group())
        return None
    
    def _extract_date(self, text: str, slot_def: SlotDefinition) -> Optional[str]:
        """提取日期"""
        patterns = [
            r'\d{4}[-/年]\d{1,2}[-/月]\d{1,2}日?',
            r'\d{1,2}月\d{1,2}日',
            r'今天|明天|后天|昨天'
        ]
        
        for pattern in patterns:
            match = re.search(pattern, text)
            if match:
                return match.group()
        
        return None
    
    def _extract_phone(self, text: str, slot_def: SlotDefinition) -> Optional[str]:
        """提取电话"""
        match = re.search(r'1[3-9]\d{9}', text)
        if match:
            return match.group()
        return None
    
    def _extract_order_id(self, text: str, slot_def: SlotDefinition) -> Optional[str]:
        """提取订单号"""
        # 假设订单号格式:字母+数字,长度10-20
        match = re.search(r'[A-Z]{2}\d{10,}', text.upper())
        if match:
            return match.group()
        
        # 尝试提取纯数字订单号
        match = re.search(r'\d{10,}', text)
        if match:
            return match.group()
        
        return None
    
    def _extract_enum(self, text: str, slot_def: SlotDefinition) -> Optional[str]:
        """提取枚举值"""
        for value in slot_def.enum_values:
            if value in text:
                return value
        return None
    
    def validate_slot(self, slot_name: str, value: Any) -> bool:
        """验证槽位值"""
        slot_def = self.slot_definitions.get(slot_name)
        
        if not slot_def:
            return False
        
        if slot_def.validation_pattern:
            return bool(re.match(slot_def.validation_pattern, str(value)))
        
        return True
    
    def get_missing_slots(self, filled_slots: Dict[str, SlotValue], 
                         required_slots: List[str]) -> List[str]:
        """获取缺失的槽位"""
        missing = []
        
        for slot_name in required_slots:
            slot_def = self.slot_definitions.get(slot_name)
            
            if slot_def and slot_def.required:
                if slot_name not in filled_slots:
                    missing.append(slot_name)
        
        return missing
    
    def get_prompt(self, slot_name: str) -> str:
        """获取槽位提示"""
        slot_def = self.slot_definitions.get(slot_name)
        
        if slot_def:
            return slot_def.prompt
        
        return f"请提供{slot_name}"

# 使用示例
sf = SlotFiller()

# 注册槽位
sf.register_slot(SlotDefinition(
    name="order_id",
    type=SlotType.ORDER_ID,
    description="订单号",
    required=True,
    prompt="请提供您的订单号",
    validation_pattern=r'[A-Z]{2}\d{10,}'
))

sf.register_slot(SlotDefinition(
    name="phone",
    type=SlotType.PHONE,
    description="手机号",
    required=True,
    prompt="请提供您的手机号"
))

sf.register_slot(SlotDefinition(
    name="reason",
    type=SlotType.ENUM,
    description="退货原因",
    required=True,
    prompt="请选择退货原因:质量问题、不喜欢、发错货",
    enum_values=["质量问题", "不喜欢", "发错货", "其他"]
))

# 填充槽位
text = "我的订单号是AB1234567890,手机是13800138000"
slots = sf.fill_slots(text, ["order_id", "phone", "reason"])

print(f"填充结果: {[(k, v.value) for k, v in slots.items()]}")

# 检查缺失槽位
missing = sf.get_missing_slots(slots, ["order_id", "phone", "reason"])
print(f"缺失槽位: {missing}")

if missing:
    print(f"提示: {sf.get_prompt(missing[0])}")

4. 多轮对话模块

4.1 对话状态追踪

from dataclasses import dataclass, field
from typing import Dict, List, Optional
from enum import Enum
import time

class DialogState(Enum):
    """对话状态"""
    IDLE = "idle"
    INTENT_RECOGNIZED = "intent_recognized"
    SLOT_FILLING = "slot_filling"
    CONFIRMING = "confirming"
    EXECUTING = "executing"
    COMPLETED = "completed"
    FAILED = "failed"

@dataclass
class DialogContext:
    """对话上下文"""
    session_id: str
    user_id: str
    state: DialogState = DialogState.IDLE
    current_intent: Optional[str] = None
    slots: Dict[str, SlotValue] = field(default_factory=dict)
    history: List[Dict] = field(default_factory=list)
    turn_count: int = 0
    created_at: float = field(default_factory=time.time)
    updated_at: float = field(default_factory=time.time)

class DialogStateTracker:
    """对话状态追踪器"""
    
    def __init__(self, intent_manager: IntentManager, slot_filler: SlotFiller):
        self.intent_manager = intent_manager
        self.slot_filler = slot_filler
        self.contexts: Dict[str, DialogContext] = {}
    
    def create_context(self, session_id: str, user_id: str) -> DialogContext:
        """创建对话上下文"""
        context = DialogContext(
            session_id=session_id,
            user_id=user_id
        )
        
        self.contexts[session_id] = context
        return context
    
    def get_context(self, session_id: str) -> Optional[DialogContext]:
        """获取对话上下文"""
        return self.contexts.get(session_id)
    
    def process_input(self, session_id: str, text: str) -> Dict:
        """处理用户输入"""
        context = self.get_context(session_id)
        
        if not context:
            return {"error": "会话不存在"}
        
        # 更新上下文
        context.turn_count += 1
        context.updated_at = time.time()
        
        # 记录历史
        context.history.append({
            "role": "user",
            "content": text,
            "timestamp": time.time()
        })
        
        # 根据当前状态处理
        if context.state == DialogState.IDLE:
            return self._handle_idle(context, text)
        
        elif context.state == DialogState.SLOT_FILLING:
            return self._handle_slot_filling(context, text)
        
        elif context.state == DialogState.CONFIRMING:
            return self._handle_confirming(context, text)
        
        else:
            return self._handle_idle(context, text)
    
    def _handle_idle(self, context: DialogContext, text: str) -> Dict:
        """处理空闲状态"""
        # 识别意图
        intents = self.intent_manager.recognize(text)
        
        if not intents:
            return {
                "response": "抱歉,我没有理解您的意思,请重新描述",
                "state": "idle"
            }
        
        # 选择最可能的意图
        top_intent = intents[0]
        
        if top_intent["confidence"] < 0.3:
            return {
                "response": "您是想" + "还是".join([i["intent_name"] for i in intents[:3]]) + "?",
                "state": "clarifying",
                "candidates": intents[:3]
            }
        
        # 设置当前意图
        context.current_intent = top_intent["intent_id"]
        context.state = DialogState.INTENT_RECOGNIZED
        
        # 获取意图定义
        intent = self.intent_manager.get_intent(top_intent["intent_id"])
        
        if not intent:
            return {
                "response": "抱歉,系统出现错误",
                "state": "error"
            }
        
        # 尝试填充槽位
        filled_slots = self.slot_filler.fill_slots(text, intent.slots)
        context.slots.update(filled_slots)
        
        # 检查缺失槽位
        missing = self.slot_filler.get_missing_slots(context.slots, intent.slots)
        
        if missing:
            context.state = DialogState.SLOT_FILLING
            prompt = self.slot_filler.get_prompt(missing[0])
            
            return {
                "response": prompt,
                "state": "slot_filling",
                "missing_slots": missing
            }
        else:
            context.state = DialogState.CONFIRMING
            
            return {
                "response": self._generate_confirmation(context),
                "state": "confirming"
            }
    
    def _handle_slot_filling(self, context: DialogContext, text: str) -> Dict:
        """处理槽位填充状态"""
        intent = self.intent_manager.get_intent(context.current_intent)
        
        if not intent:
            return {"error": "意图不存在"}
        
        # 填充槽位
        missing = self.slot_filler.get_missing_slots(context.slots, intent.slots)
        
        if missing:
            filled = self.slot_filler.fill_slots(text, missing)
            context.slots.update(filled)
        
        # 再次检查缺失
        missing = self.slot_filler.get_missing_slots(context.slots, intent.slots)
        
        if missing:
            prompt = self.slot_filler.get_prompt(missing[0])
            
            return {
                "response": prompt,
                "state": "slot_filling",
                "missing_slots": missing
            }
        else:
            context.state = DialogState.CONFIRMING
            
            return {
                "response": self._generate_confirmation(context),
                "state": "confirming"
            }
    
    def _handle_confirming(self, context: DialogContext, text: str) -> Dict:
        """处理确认状态"""
        # 检查用户确认
        confirm_keywords = ["是的", "确认", "对", "没问题", "好的"]
        cancel_keywords = ["不是", "取消", "不对", "重新"]
        
        if any(kw in text for kw in confirm_keywords):
            context.state = DialogState.EXECUTING
            
            return {
                "response": "好的,正在为您处理...",
                "state": "executing",
                "action": "execute"
            }
        
        elif any(kw in text for kw in cancel_keywords):
            context.state = DialogState.IDLE
            context.current_intent = None
            context.slots.clear()
            
            return {
                "response": "好的,已取消。请问还有什么可以帮您?",
                "state": "idle"
            }
        
        else:
            return {
                "response": "请确认是否正确?回复'是'确认,回复'否'重新填写",
                "state": "confirming"
            }
    
    def _generate_confirmation(self, context: DialogContext) -> str:
        """生成确认信息"""
        intent = self.intent_manager.get_intent(context.current_intent)
        
        parts = [f"您要{intent.name},"]
        
        for slot_name, slot_value in context.slots.items():
            slot_def = self.slot_filler.slot_definitions.get(slot_name)
            if slot_def:
                parts.append(f"{slot_def.description}{slot_value.value},")
        
        parts.append("确认吗?")
        
        return "".join(parts)

# 使用示例
dst = DialogStateTracker(im, sf)

# 创建会话
context = dst.create_context("session_001", "user_001")

# 处理对话
response1 = dst.process_input("session_001", "我要退货")
print(f"机器人: {response1['response']}")

response2 = dst.process_input("session_001", "AB1234567890")
print(f"机器人: {response2['response']}")

response3 = dst.process_input("session_001", "质量问题")
print(f"机器人: {response3['response']}")

response4 = dst.process_input("session_001", "是的")
print(f"机器人: {response4['response']}")

5. 知识问答模块

5.1 FAQ知识库

from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
import re

@dataclass
class FAQItem:
    """FAQ条目"""
    id: str
    question: str
    answer: str
    keywords: List[str] = field(default_factory=list)
    category: str = ""
    tags: List[str] = field(default_factory=list)

class FAQKnowledgeBase:
    """FAQ知识库"""
    
    def __init__(self):
        self.faqs: Dict[str, FAQItem] = {}
        self.keyword_index: Dict[str, List[str]] = {}
    
    def add_faq(self, faq: FAQItem):
        """添加FAQ"""
        self.faqs[faq.id] = faq
        
        # 更新关键词索引
        for keyword in faq.keywords:
            if keyword not in self.keyword_index:
                self.keyword_index[keyword] = []
            self.keyword_index[keyword].append(faq.id)
    
    def search(self, query: str, top_k: int = 5) -> List[Tuple[FAQItem, float]]:
        """搜索FAQ"""
        query_lower = query.lower()
        
        scores = {}
        
        # 关键词匹配
        for keyword, faq_ids in self.keyword_index.items():
            if keyword in query_lower:
                for faq_id in faq_ids:
                    scores[faq_id] = scores.get(faq_id, 0) + 1
        
        # 问题相似度
        for faq_id, faq in self.faqs.items():
            # 简单的词重叠度
            question_words = set(faq.question.lower().split())
            query_words = set(query_lower.split())
            
            overlap = len(question_words & query_words)
            
            if overlap > 0:
                scores[faq_id] = scores.get(faq_id, 0) + overlap * 0.5
        
        # 排序
        sorted_faqs = sorted(scores.items(), key=lambda x: x[1], reverse=True)
        
        results = []
        for faq_id, score in sorted_faqs[:top_k]:
            faq = self.faqs.get(faq_id)
            if faq:
                results.append((faq, score))
        
        return results
    
    def get_answer(self, query: str) -> Optional[Dict]:
        """获取答案"""
        results = self.search(query, top_k=1)
        
        if results:
            faq, score = results[0]
            
            # 归一化分数
            confidence = min(score / 5, 1.0)
            
            return {
                "question": faq.question,
                "answer": faq.answer,
                "confidence": confidence,
                "source": "faq"
            }
        
        return None

# 使用示例
faq_kb = FAQKnowledgeBase()

# 添加FAQ
faq_kb.add_faq(FAQItem(
    id="faq_001",
    question="如何修改收货地址?",
    answer="您可以在订单详情页面点击"修改地址"按钮进行修改。如果订单已发货,请联系客服处理。",
    keywords=["收货地址", "修改地址", "地址"],
    category="订单"
))

faq_kb.add_faq(FAQItem(
    id="faq_002",
    question="退货运费谁承担?",
    answer="质量问题退货,运费由商家承担;非质量问题退货,运费由买家承担。",
    keywords=["退货", "运费", "承担"],
    category="售后"
))

faq_kb.add_faq(FAQItem(
    id="faq_003",
    question="如何联系客服?",
    answer="您可以通过以下方式联系客服:1. 在线客服(点击右下角图标);2. 客服热线:400-xxx-xxxx;3. 邮箱:service@example.com",
    keywords=["联系客服", "客服", "热线"],
    category="服务"
))

# 搜索
result = faq_kb.get_answer("怎么修改收货地址")
print(f"问题: {result['question']}")
print(f"答案: {result['answer']}")
print(f"置信度: {result['confidence']:.2f}")

5.2 智能问答引擎

from typing import Dict, List, Optional

class IntelligentQA:
    """智能问答引擎"""
    
    def __init__(self, faq_kb: FAQKnowledgeBase):
        self.faq_kb = faq_kb
        self.confidence_threshold = 0.5
    
    def answer(self, question: str) -> Dict:
        """回答问题"""
        # 尝试FAQ匹配
        faq_result = self.faq_kb.get_answer(question)
        
        if faq_result and faq_result["confidence"] >= self.confidence_threshold:
            return {
                "success": True,
                "answer": faq_result["answer"],
                "source": "faq",
                "confidence": faq_result["confidence"],
                "need_human": False
            }
        
        # 尝试知识库问答(RAG)
        # rag_result = self.rag_query(question)
        
        # 如果置信度不够,转人工
        return {
            "success": False,
            "answer": "抱歉,这个问题我暂时无法回答,正在为您转接人工客服...",
            "source": "fallback",
            "confidence": 0,
            "need_human": True
        }
    
    def batch_answer(self, questions: List[str]) -> List[Dict]:
        """批量回答"""
        return [self.answer(q) for q in questions]
    
    def get_suggestions(self, question: str) -> List[str]:
        """获取相关问题建议"""
        results = self.faq_kb.search(question, top_k=5)
        
        return [faq.question for faq, _ in results]

# 使用示例
qa = IntelligentQA(faq_kb)

# 提问
result = qa.answer("退货谁出运费")
print(f"答案: {result['answer']}")
print(f"来源: {result['source']}")

# 获取建议
suggestions = qa.get_suggestions("退货")
print(f"相关问题: {suggestions}")

6. 人机协作模块

6.1 智能路由

from typing import Dict, List, Optional
from dataclasses import dataclass
from enum import Enum

class TransferReason(Enum):
    """转人工原因"""
    LOW_CONFIDENCE = "low_confidence"
    COMPLEX_INTENT = "complex_intent"
    USER_REQUEST = "user_request"
    EMOTION_NEGATIVE = "emotion_negative"
    SYSTEM_ERROR = "system_error"

@dataclass
class HumanAgent:
    """人工客服"""
    id: str
    name: str
    skills: List[str]
    status: str  # online, busy, offline
    current_load: int = 0
    max_load: int = 5

class IntelligentRouter:
    """智能路由"""
    
    def __init__(self):
        self.agents: Dict[str, HumanAgent] = {}
        self.transfer_rules: List[Dict] = []
    
    def register_agent(self, agent: HumanAgent):
        """注册客服"""
        self.agents[agent.id] = agent
    
    def add_transfer_rule(self, rule: Dict):
        """添加转人工规则"""
        self.transfer_rules.append(rule)
    
    def should_transfer(self, context: DialogContext, qa_result: Dict) -> Tuple[bool, TransferReason]:
        """判断是否需要转人工"""
        # 规则1:置信度过低
        if qa_result.get("confidence", 1) < 0.3:
            return True, TransferReason.LOW_CONFIDENCE
        
        # 规则2:用户明确要求
        if context.history:
            last_msg = context.history[-1].get("content", "")
            transfer_keywords = ["转人工", "人工客服", "真人"]
            if any(kw in last_msg for kw in transfer_keywords):
                return True, TransferReason.USER_REQUEST
        
        # 规则3:复杂意图
        intent = context.current_intent
        complex_intents = ["complaint", "refund_high_value"]
        if intent in complex_intents:
            return True, TransferReason.COMPLEX_INTENT
        
        # 规则4:情绪负面
        # emotion = self.detect_emotion(context)
        # if emotion == "negative":
        #     return True, TransferReason.EMOTION_NEGATIVE
        
        return False, None
    
    def route_to_agent(self, context: DialogContext, reason: TransferReason) -> Optional[HumanAgent]:
        """路由到人工客服"""
        # 找到最合适的客服
        available_agents = [
            agent for agent in self.agents.values()
            if agent.status == "online" and agent.current_load < agent.max_load
        ]
        
        if not available_agents:
            return None
        
        # 按负载排序
        available_agents.sort(key=lambda x: x.current_load)
        
        # 选择负载最低的
        selected = available_agents[0]
        
        # 更新负载
        selected.current_load += 1
        
        return selected
    
    def transfer(self, session_id: str, reason: TransferReason) -> Dict:
        """执行转人工"""
        context = self.contexts.get(session_id)
        
        if not context:
            return {"success": False, "message": "会话不存在"}
        
        agent = self.route_to_agent(context, reason)
        
        if agent:
            return {
                "success": True,
                "agent_id": agent.id,
                "agent_name": agent.name,
                "message": f"正在为您转接{agent.name},请稍候..."
            }
        else:
            return {
                "success": False,
                "message": "当前客服繁忙,请稍后再试或留言"
            }

# 使用示例
router = IntelligentRouter()

# 注册客服
router.register_agent(HumanAgent(
    id="agent_001",
    name="客服小王",
    skills=["订单", "售后"],
    status="online",
    current_load=2
))

router.register_agent(HumanAgent(
    id="agent_002",
    name="客服小李",
    skills=["投诉", "技术"],
    status="online",
    current_load=1
))

# 判断是否转人工
context = DialogContext(session_id="session_001", user_id="user_001")
qa_result = {"confidence": 0.2}

should, reason = router.should_transfer(context, qa_result)
if should:
    print(f"需要转人工,原因: {reason.value}")

7. 最佳实践

7.1 系统设计原则

原则 说明 实践
用户优先 体验至上 快速响应 + 准确理解
人机协作 无缝衔接 智能路由
持续学习 不断优化 日志分析 + 模型更新
安全合规 数据保护 敏感信息处理

7.2 常见问题

问题 原因 解决方案
意图识别不准 训练数据不足 增加样本 + 规则补充
槽位提取失败 表述多样 多模式匹配
对话卡死 状态异常 超时重置

8. 总结

8.1 核心要点

本文通过完整的智能客服机器人案例,展示了 OpenClaw 在客服场景的应用:

模块 核心功能 技术要点
意图识别 理解用户 NLU分类
槽位填充 信息提取 实体识别
多轮对话 场景处理 DST
知识问答 FAQ回答 语义匹配
人机协作 无缝转接 智能路由

8.2 下一步学习

  • 第81篇:OpenClaw 行业应用:金融风控系统

参考资料


Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐