第十二章 场景题
第十一章我们深入讨论了 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 类把多轮对话管理拆成了四件事:存历史、取窗口、解指代、转消息。每一行都加了注释,方便你照着改。
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 把情绪分成五档,每档对应不同的响应策略:
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(把检索到的知识和对话历史拼成一个完整提示词)。
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 把这些条件都编码成了阈值:
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。消息进来后,依次走过指代消解→情绪分析→知识检索→转接判断→策略选择→回复生成→历史更新七个步骤:
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 时,有几个常见误区需要避免:
误区一:认为 LLM 能包打天下。很多人觉得接个大模型就够了,不需要知识库、不需要转接逻辑。实际上,纯 LLM 容易"幻觉"——一本正经地编造退货政策、虚构订单号。知识库和转接逻辑是防止幻觉的安全网。
误区二:情绪识别只看关键词。用户说"好的"可能是满意,也可能是反讽。只靠关键词匹配会误判。建议用 LLM 做语义级情绪分析,并结合上下文。
误区三:转接阈值设得太高。有些团队怕"人工成本高",把转接阈值调到"连续5轮愤怒才转"。结果是用户已经被惹毛了才转,体验极差。宁可早转一步,也不要让用户在 Agent 手里多受一轮气。
误区四:忽略对话历史的压缩。超过10轮后上下文会很长,既费 Token 又影响生成质量。应该在合适时机做摘要压缩,把前几轮的关键信息浓缩成一段摘要。
八、本节小结
| 要点 | 说明 |
|---|---|
| 多轮对话管理 | 维护对话历史、上下文实体、指代消解是客服 Agent 的基础能力 |
| 情绪识别 | 实时分析用户情绪,动态调整响应策略,提升用户体验 |
| RAG 知识库 | 通过向量检索增强回复准确性,是客服 Agent 的核心能力来源 |
| Human Handoff | 设置合理的转接阈值,在 Agent 无法处理时无缝切换人工,是安全网 |
| 流水线设计 | 意图识别→情绪分析→知识检索→策略选择→回复生成,形成完整闭环 |
智能客服是 Agent 最经典的落地场景,也是面试中最高频的系统设计题。掌握了这套架构,你不仅能搭建客服系统,还能把同样的思路迁移到 IT 工单、金融咨询、医疗导诊等任何"问答+流程"型场景。