Skip to content

第十二章 场景题

第十一章我们深入讨论了 AI Agent 的安全问题——从 Prompt 注入防护到输出过滤,从权限隔离到审计日志。安全是底线,但光有安全还不够。真正的 Agent 系统要在"安全"之上做到"好用",要在真实业务场景中经受住考验。本章我们就来回答一个更实际的问题:当 Agent 走出实验室、走进生产环境,面对真实用户、真实数据、真实约束时,该如何设计?

我们把场景题分为六类来讲。前三节是三个经典落地场景——智能客服、代码生成、数据分析,它们覆盖了大多数企业级 Agent 的应用形态;第四节讲多 Agent 协作,这是复杂系统的常见架构选择;第五节讲稳定性与可靠性,这是从"能跑"到"能扛"的分水岭;第六节精选了几道高频面试场景题,帮助读者把前面学的知识串起来、用出去。

学完本章,你不仅能回答"怎么设计一个客服 Agent",还能回答"为什么这样设计""不这样设计会怎样""面试官追问时怎么拆解"。每一节都会给出完整的代码框架和设计思路,代码逐行注释,方便你直接复用到自己的项目中。

12.1 智能客服 Agent

生活类比:酒店礼宾部

想象你走进一家五星级酒店,礼宾部的工作人员看到你提着行李、满头大汗地跑进来。他不会一上来就念一大段酒店介绍,而是先观察你的状态——你是着急赶时间,还是悠闲地找人聊天?你是来入住的,还是来找东西的?然后他会根据你的状态给出不同的回应:着急的话直接帮你办入住、叫电梯;闲聊的话顺便推荐一下附近的景点。

智能客服 Agent 就是这家酒店礼宾部的"数字版"。它需要做三件事:听懂你在说什么(意图识别)、判断你的情绪状态(情绪识别)、给出合适的回应(回复生成)。如果遇到它处理不了的复杂情况,还要知道什么时候该说"请您稍等,我叫经理来"——这就是 Human Handoff(人工转接)。

一、智能客服 Agent 的架构概览

一个完整的智能客服系统通常包含以下核心组件,它们像流水线一样协作:

用户请求 -> 意图识别 -> 对话管理 -> 知识检索 -> 回复生成 -> 用户
                ↓            ↓
            情绪识别     Human Handoff

核心组件说明:

  • 对话管理(Dialog Manager):维护对话状态,跟踪多轮对话上下文。就像礼宾部员工心里记着"这位客人刚才问了 WiFi 密码"。
  • 意图识别(Intent Recognition):判断用户是想咨询、投诉、查询还是购买。就像员工听你开口就知道你是要入住还是要问路。
  • 情绪识别(Sentiment Analysis):实时分析用户情绪,决定响应策略。就像员工看到你皱眉头就知道你可能不满意。
  • 知识检索(Knowledge Retrieval):从知识库中检索相关信息。就像员工翻看酒店服务手册找答案。
  • Human Handoff:在必要时将对话转接给人工客服。就像员工遇到搞不定的事就把经理叫来。

二、多轮对话管理

多轮对话是客服 Agent 的核心挑战。与单轮问答不同,客服场景需要 Agent 记住上下文、理解指代、追踪用户意图的变化。比如用户先问"你们有什么套餐",接着问"第一个多少钱"——"第一个"指代的是上一轮提到的第一个套餐。如果 Agent 没记住上下文,就会答非所问。

下面的 DialogManager 类把多轮对话管理拆成了四件事:存历史、取窗口、解指代、转消息。每一行都加了注释,方便你照着改。

python
from typing import List, Dict, Optional          # 引入类型注解,方便静态检查和阅读
from dataclasses import dataclass, field          # dataclass 用于简洁地定义数据类
from enum import Enum                              # Enum 用于定义枚举类型

class Intent(Enum):                                # 用户意图枚举
    INQUIRY = "咨询"                               #   咨询产品或服务
    COMPLAINT = "投诉"                             #   投诉不满
    ORDER = "下单"                                 #   下单购买
    AFTER_SALE = "售后"                            #   售后处理
    GREETING = "问候"                              #   寒暄问候

class Emotion(Enum):                               # 用户情绪枚举
    NEUTRAL = "中性"                               #   中性,正常对话
    HAPPY = "满意"                                 #   满意,可适时推荐
    ANGRY = "愤怒"                                 #   愤怒,需要安抚
    ANXIOUS = "焦虑"                               #   焦虑,需要确定性
    URGENT = "紧急"                                #   紧急,需要快速响应

@dataclass
class DialogTurn:                                  # 单轮对话记录
    """单轮对话记录"""                              #   docstring 说明用途
    role: str                                      #   "user" 或 "assistant"
    content: str                                   #   消息文本内容
    intent: Optional[Intent] = None                #   该轮识别出的意图
    emotion: Optional[Emotion] = None              #   该轮识别出的情绪
    entities: Dict[str, str] = field(default_factory=dict)  # 提取的实体,如 {"product": "手机"}

class DialogManager:                               # 多轮对话管理器
    """多轮对话管理器"""                            #   维护历史、上下文、指代消解
    
    def __init__(self, max_history: int = 10):     # 最多保留10轮,防止上下文过长
        self.max_history = max_history             #   存储上限
        self.history: List[DialogTurn] = []        #   对话历史列表
        self.context: Dict[str, str] = {}          #   上下文实体缓存
        self.current_intent: Optional[Intent] = None  # 当前对话意图
        self.user_profile: Dict = {}               #   用户画像
    
    def add_turn(self, turn: DialogTurn):          # 添加一轮对话
        """添加对话轮次"""                          #   docstring
        self.history.append(turn)                  #   追加到历史
        if len(self.history) > self.max_history:   #   超过上限时裁剪
            self.history = self.history[-self.max_history:]  # 保留最近N轮
    
    def get_context_window(self, n: int = 5) -> List[DialogTurn]:  # 取最近n轮
        """获取最近n轮对话"""                       #   用于构建LLM上下文
        return self.history[-n:]                    #   切片取最后n条
    
    def resolve_reference(self, text: str) -> str: # 指代消解
        """解析指代消解"""                          #   把"它""那个"替换成具体实体
        if "它" in text and "last_subject" in self.context:  # 如果提到了"它"
            text = text.replace("它", self.context["last_subject"])  # 替换为上一次的主体
        if "那个" in text and "last_product" in self.context:  # 如果提到了"那个"
            text = text.replace("那个", self.context["last_product"])  # 替换为上一次的产品
        return text                                #   返回消解后的文本
    
    def update_context(self, entities: Dict[str, str]):  # 更新上下文
        """更新上下文实体"""                        #   把新提取的实体存起来
        self.context.update(entities)              #   合并到context
        if "product" in entities:                  #   如果有产品实体
            self.context["last_product"] = entities["product"]  # 记住最近的产品
        if "subject" in entities:                  #   如果有主体实体
            self.context["last_subject"] = entities["subject"]  # 记住最近的主体
    
    def to_messages(self) -> List[Dict[str, str]]: # 转为LLM消息格式
        """转换为LLM API格式的消息列表"""            #   供ChatGPT等API调用
        return [{"role": t.role, "content": t.content} for t in self.history]  # 映射

三、情绪识别与响应策略

情绪识别是提升客服体验的关键。在用户愤怒时,Agent 需要优先安抚;在用户焦虑时,需要给予确定性回复。就像去医院看病,同样是"我肚子疼"——如果病人疼得满地打滚,护士会立刻叫医生;如果病人语气平静,护士会让你先挂号排队。

下面的 SentimentAnalyzer 把情绪分成五档,每档对应不同的响应策略:

python
class SentimentAnalyzer:                           # 情绪分析器
    """情绪分析器"""                                #   通过LLM分析用户情绪
    
    SENTIMENT_PROMPT = """分析以下用户消息的情绪。返回JSON格式:  # Prompt模板
{
    "emotion": "neutral|happy|angry|anxious|urgent",  # 情绪标签
    "confidence": 0.0-1.0,                          # 置信度
    "key_indicators": ["关键词1", "关键词2"]        # 指示情绪的关键词
}

用户消息:{message}"""                              # 占位符,运行时替换
    
    RESPONSE_STRATEGIES = {                        # 情绪-策略映射表
        Emotion.ANGRY: {                           #   愤怒
            "tone": "empathic",                    #     语气:共情
            "priority": "apology_first",          #     优先级:先道歉
            "action": "优先道歉并安抚,然后解决问题"  #     具体动作
        },
        Emotion.ANXIOUS: {                         #   焦虑
            "tone": "reassuring",                  #     语气:安抚
            "priority": "clarity_first",          #     优先级:先给确定性
            "action": "给出明确、确定的回复,消除不确定性"
        },
        Emotion.URGENT: {                          #   紧急
            "tone": "efficient",                   #     语气:高效
            "priority": "speed_first",            #     优先级:先快后全
            "action": "快速给出解决方案,可能触发Human Handoff"
        },
        Emotion.HAPPY: {                           #   满意
            "tone": "warm",                        #     语气:温暖
            "priority": "upsell_opportunity",     #     优先级:趁势推荐
            "action": "保持友好,适时推荐相关产品"
        }
    }
    
    async def analyze(self, message: str, llm_client) -> Dict:  # 异步分析情绪
        """使用LLM分析情绪"""                       #   返回JSON结果
        prompt = self.SENTIMENT_PROMPT.format(message=message)  # 填充模板
        response = await llm_client.chat(prompt)   #   调用LLM
        return response                            #   返回解析结果
    
    def get_strategy(self, emotion: Emotion) -> Dict:  # 根据情绪取策略
        """获取对应的响应策略"""                    #   没匹配到就返回默认
        return self.RESPONSE_STRATEGIES.get(emotion, {  # 默认中性策略
            "tone": "neutral",
            "priority": "standard",
            "action": "标准客服流程"
        })

四、知识库驱动的 RAG 客服

知识库是客服 Agent 的核心能力来源。通过 RAG(检索增强生成)技术,Agent 可以从产品文档、FAQ、历史工单中检索相关信息。就像礼宾部员工桌上有一本厚厚的酒店手册,遇到不知道的问题就翻一翻。

下面的 KnowledgeBase 类封装了三件事:索引文档(把文档切块存入向量库)、检索知识(根据用户问题找到相关片段)、构建 Prompt(把检索到的知识和对话历史拼成一个完整提示词)。

python
from langchain.text_splitter import RecursiveCharacterTextSplitter  # 递归文本切分器
from langchain_community.embeddings import OpenAIEmbeddings        # OpenAI嵌入模型
from langchain_community.vectorstores import Chroma                 # Chroma向量数据库

class KnowledgeBase:                                # 客服知识库
    """客服知识库"""                                  #   管理文档索引、检索、Prompt构建
    
    def __init__(self, persist_directory: str = "./kb_store"):  # 持久化目录
        self.embeddings = OpenAIEmbeddings()          #   初始化嵌入模型
        self.vectorstore = Chroma(                    #   初始化Chroma向量库
            persist_directory=persist_directory,      #     指定持久化路径
            embedding_function=self.embeddings        #     指定嵌入函数
        )
        self.text_splitter = RecursiveCharacterTextSplitter(  # 文本切分器
            chunk_size=500,                           #     每块约500字符
            chunk_overlap=50                          #     块间重叠50字符,保留上下文
        )
    
    def index_documents(self, documents: List[str], metadatas: List[Dict] = None):
        """索引文档到向量库"""                        #   把原始文档切块后存入
        chunks = self.text_splitter.create_documents(  #   切分文档
            documents, metadatas=metadatas            #     传入原文和元数据
        )
        self.vectorstore.add_documents(chunks)        #   加入向量库
        self.vectorstore.persist()                    #   持久化到磁盘
    
    def search(self, query: str, k: int = 5) -> List[Dict]:  # 相似度检索
        """检索相关知识"""                            #   返回最相关的k个片段
        docs = self.vectorstore.similarity_search(query, k=k)  # 向量相似度搜索
        return [{"content": d.page_content, "metadata": d.metadata} for d in docs]  # 映射
    
    def build_prompt(self, query: str, context: List[Dict], history: List[Dict]) -> str:
        """构建增强Prompt"""                         #   把知识+历史+规则拼成提示词
        context_text = "\n\n".join([                  #   拼接检索到的知识片段
            f"[来源:{c['metadata'].get('source', '未知')}]\n{c['content']}"  # 标注来源
            for c in context                          #     遍历每条知识
        ])
        
        history_text = "\n".join([                    #   拼接最近6轮对话历史
            f"{'用户' if h['role']=='user' else '客服'}: {h['content']}"  # 标注角色
            for h in history[-6:]                     #     只取最后6条
        ])
        
        return f"""你是一个专业的客服Agent。请根据以下知识库内容回答用户问题。  # 系统角色设定

## 知识库内容                                       # 注入检索到的知识
{context_text}

## 对话历史                                         # 注入历史上下文
{history_text}

## 规则                                             # 约束LLM的行为
1. 仅有知识库包含的信息才能作为回答依据               #   不准编造
2. 如果知识库中没有相关信息,请明确告知用户,并建议转人工客服  # 不准瞎猜
3. 保持友好、专业的态度                              #   语气要求
4. 回答要简洁、准确                                  #   长度要求

用户问题:{query}                                   # 实际问题
"""

五、Human Handoff 人工转接

Human Handoff 是客服 Agent 的关键安全网。当 Agent 无法处理时,需要无缝转接给人工客服。就像自动驾驶汽车遇到极端天气,会提醒驾驶员接管方向盘——关键不是"永远不犯错",而是"知道什么时候该交出去"。

转接判断不能靠单一条件,要多管齐下:用户连续愤怒、Agent 连续答不上来、用户明确要人工、问题太复杂。下面的 HumanHandoffManager 把这些条件都编码成了阈值:

python
from enum import Enum                                # 枚举类型

class HandoffReason(Enum):                           # 转接原因枚举
    KNOWLEDGE_GAP = "知识库无法覆盖"                 #   知识不够
    USER_REQUEST = "用户要求人工"                    #   用户主动要
    EMOTION_ESCALATION = "用户情绪升级"              #   用户越来越生气
    COMPLEX_ISSUE = "复杂问题"                       #   问题超出能力
    SENSITIVE_TOPIC = "敏感话题"                     #   涉及敏感内容
    SAFETY_CONCERN = "安全问题"                      #   涉及安全风险

class HumanHandoffManager:                           # 人工转接管理器
    """人工转接管理器"""                              #   判断何时转、生成摘要
    
    HANDOFF_THRESHOLDS = {                           # 转接阈值配置
        "angry_count": 3,                            #   连续3轮愤怒就转
        "unanswered_count": 2,                       #   连续2轮答不上就转
        "escalation_keywords": [                     #   升级关键词列表
            "投诉", "人工", "转人工", "找你们领导",   #     用户表达不满
            "法律", "律师", "起诉", "消费者协会"      #     涉及法律风险
        ]
    }
    
    def __init__(self):                              # 初始化计数器
        self.angry_rounds = 0                         #   连续愤怒轮数
        self.unanswered_rounds = 0                    #   连续未答上轮数
    
    def should_handoff(self,                          # 判断是否需要转人工
                       intent: Intent,               #   当前意图
                       emotion: Emotion,             #   当前情绪
                       kb_confidence: float,         #   知识库置信度
                       user_message: str) -> tuple[bool, Optional[HandoffReason]]:
        """判断是否需要转人工"""                      #   返回(是否转, 原因)
        
        # 条件1:情绪升级检测
        if emotion == Emotion.ANGRY:                 #   用户愤怒
            self.angry_rounds += 1                    #     累计愤怒轮数
            if self.angry_rounds >= self.HANDOFF_THRESHOLDS["angry_count"]:  # 达到阈值
                return True, HandoffReason.EMOTION_ESCALATION  # 转人工
        else:
            self.angry_rounds = 0                    #   不愤怒就重置
        
        # 条件2:知识库覆盖度不足
        if kb_confidence < 0.6:                      #   置信度低于0.6
            self.unanswered_rounds += 1               #     累计未答上轮数
            if self.unanswered_rounds >= self.HANDOFF_THRESHOLDS["unanswered_count"]:  # 达到阈值
                return True, HandoffReason.KNOWLEDGE_GAP  # 转人工
        else:
            self.unanswered_rounds = 0                #   有答案就重置
        
        # 条件3:用户明确要求转人工
        if any(kw in user_message for kw in self.HANDOFF_THRESHOLDS["escalation_keywords"]):  # 命中关键词
            return True, HandoffReason.USER_REQUEST   #   立即转
        
        # 条件4:复杂问题
        if intent == Intent.COMPLAINT:               #   投诉类意图
            return True, HandoffReason.COMPLEX_ISSUE  #   直接转
        
        return False, None                            #   不需要转
    
    def create_handoff_summary(self,                  # 生成转接摘要
                               dialog_manager: DialogManager,  # 对话管理器
                               reason: HandoffReason) -> str:  # 转接原因
        """生成转接摘要,方便人工客服快速了解情况"""    #   人工客服一看就懂
        history = dialog_manager.get_context_window(10)  # 取最近10轮
        
        summary = f"""## 转接摘要                     # 摘要标题
- 转接原因:{reason.value}                           #   转接原因
- 对话轮次:{len(history)}                            #   总轮数
- 用户意图:{dialog_manager.current_intent.value if dialog_manager.current_intent else '未知'}  # 意图

## 对话记录                                         #   原始记录
"""
        for turn in history:                          #   遍历每轮
            summary += f"\n{'👤 用户' if turn.role == 'user' else '🤖 Agent'}: {turn.content}"  # 格式化
        
        return summary                                #   返回摘要

六、完整客服 Agent 流水线

把前面的组件串起来,就是一个完整的客服 Agent。消息进来后,依次走过指代消解→情绪分析→知识检索→转接判断→策略选择→回复生成→历史更新七个步骤:

python
class CustomerServiceAgent:                          # 智能客服Agent
    """智能客服Agent"""                               #   完整流水线
    
    def __init__(self, llm_client, kb: KnowledgeBase):  # 依赖注入
        self.llm = llm_client                        #   LLM客户端
        self.kb = kb                                 #   知识库
        self.dialog = DialogManager()                #   对话管理器
        self.sentiment = SentimentAnalyzer()          #   情绪分析器
        self.handoff = HumanHandoffManager()          #   转接管理器
    
    async def handle_message(self, user_message: str) -> Dict:  # 处理用户消息
        """处理用户消息"""                            #   主入口
        # 步骤1:指代消解——把"它""那个"替换成具体实体
        resolved = self.dialog.resolve_reference(user_message)  # 消解后文本
        
        # 步骤2:情绪分析——判断用户当前情绪
        sentiment_result = await self.sentiment.analyze(resolved, self.llm)  # 调LLM分析
        emotion = Emotion(sentiment_result["emotion"])  # 解析情绪枚举
        
        # 步骤3:知识检索——从知识库找相关片段
        kb_results = self.kb.search(resolved, k=5)   #   取5条最相关
        kb_confidence = self._calculate_confidence(kb_results, resolved)  # 算置信度
        
        # 步骤4:转接判断——要不要叫人工
        should_handoff, reason = self.handoff.should_handoff(  # 多条件判断
            intent=Intent.INQUIRY,                   #   简化版,固定为咨询
            emotion=emotion,                         #   传入情绪
            kb_confidence=kb_confidence,             #   传入置信度
            user_message=resolved                    #   传入消息
        )
        
        if should_handoff:                           #   需要转人工
            return await self._execute_handoff(reason)  # 执行转接
        
        # 步骤5:获取响应策略——根据情绪决定语气
        strategy = self.sentiment.get_strategy(emotion)  # 取策略
        
        # 步骤6:构建Prompt并生成回复
        prompt = self.kb.build_prompt(               #   构建增强提示词
            resolved, kb_results, self.dialog.to_messages()  # 传入知识+历史
        )
        response = await self.llm.chat(prompt)       #   调LLM生成回复
        
        # 步骤7:更新对话历史——记录这一轮
        self.dialog.add_turn(DialogTurn(role="user", content=user_message, emotion=emotion))  # 记用户轮
        self.dialog.add_turn(DialogTurn(role="assistant", content=response))  # 记Agent轮
        
        return {                                      #   返回结果
            "response": response,                    #     回复文本
            "emotion": emotion.value,                #     情绪标签
            "strategy": strategy["tone"],            #     策略语气
            "handoff": False                         #     未转人工
        }
    
    async def _execute_handoff(self, reason: HandoffReason) -> Dict:  # 执行转接
        """执行人工转接"""                            #   生成摘要并通知用户
        summary = self.handoff.create_handoff_summary(self.dialog, reason)  # 生成摘要
        return {                                      #   返回转接信息
            "response": f"正在为您转接人工客服,请稍候...(转接原因:{reason.value})",  # 提示用户
            "handoff": True,                         #     标记转接
            "reason": reason.value,                  #     转接原因
            "summary": summary                       #     摘要给人工客服
        }
    
    def _calculate_confidence(self, kb_results: List[Dict], query: str) -> float:  # 算置信度
        """计算知识库检索置信度"""                    #   简化版
        if not kb_results:                           #   没有结果
            return 0.0                               #     置信度为0
        return min(1.0, len(kb_results) * 0.2)       #   结果越多越自信

七、常见误区

在构建智能客服 Agent 时,有几个常见误区需要避免:

  1. 误区一:认为 LLM 能包打天下。很多人觉得接个大模型就够了,不需要知识库、不需要转接逻辑。实际上,纯 LLM 容易"幻觉"——一本正经地编造退货政策、虚构订单号。知识库和转接逻辑是防止幻觉的安全网。

  2. 误区二:情绪识别只看关键词。用户说"好的"可能是满意,也可能是反讽。只靠关键词匹配会误判。建议用 LLM 做语义级情绪分析,并结合上下文。

  3. 误区三:转接阈值设得太高。有些团队怕"人工成本高",把转接阈值调到"连续5轮愤怒才转"。结果是用户已经被惹毛了才转,体验极差。宁可早转一步,也不要让用户在 Agent 手里多受一轮气。

  4. 误区四:忽略对话历史的压缩。超过10轮后上下文会很长,既费 Token 又影响生成质量。应该在合适时机做摘要压缩,把前几轮的关键信息浓缩成一段摘要。

八、本节小结

要点说明
多轮对话管理维护对话历史、上下文实体、指代消解是客服 Agent 的基础能力
情绪识别实时分析用户情绪,动态调整响应策略,提升用户体验
RAG 知识库通过向量检索增强回复准确性,是客服 Agent 的核心能力来源
Human Handoff设置合理的转接阈值,在 Agent 无法处理时无缝切换人工,是安全网
流水线设计意图识别→情绪分析→知识检索→策略选择→回复生成,形成完整闭环

智能客服是 Agent 最经典的落地场景,也是面试中最高频的系统设计题。掌握了这套架构,你不仅能搭建客服系统,还能把同样的思路迁移到 IT 工单、金融咨询、医疗导诊等任何"问答+流程"型场景。