对话式AI的架构设计:从意图识别到多轮对话的状态管理
对话式AI的架构设计:从意图识别到多轮对话的状态管理
一、场景痛点与技术挑战
对话式AI是当下最热门的应用形态之一。
但从原型到生产级系统差距巨大。
核心痛点有五个。
一是意图识别精度不稳定。
用户表述同一意图的方式千变万化。
"我要退款"、"退货"、"这个不要了"。
意图模糊和交叉导致分类错误率超过20%。
二是多轮对话状态丢失。
第5轮对话时系统忘了第2轮的关键信息。
用户需要反复重复自己的诉求。
体验直线下降。
三是上下文窗口限制。
LLM的token限制导致长对话截断。
关键信息在前几轮,后几轮丢失引用。
四是延迟与成本矛盾。
每次调用LLM成本0.01-0.05美元。
多轮对话累计成本快速攀升。
用户等待3秒以上体验严重劣化。
五是意图切换和纠错困难。
用户中途改主意:"算了,还是换货吧"。
系统如何识别意图切换并回滚状态。
这是多数对话系统的盲区。
二、核心原理与架构设计
对话式AI系统分六个核心模块。
意图识别模块
负责分类用户输入的意图。
双通道架构:规则引擎+LLM。
规则引擎处理高频确定性意图。
关键词匹配+正则模式识别。
LLM处理低频模糊意图。
BERT分类模型或GPT prompt分类。
两者结果加权合并输出置信度。
意图分类体系采用层次结构。
一级意图:退款、咨询、投诉、购买。
二级意图:退款原因、咨询品类、投诉类型。
层次分类减少每层类别数,提高精度。
实体提取模块
从用户输入中提取关键参数。
退款意图需要订单号和退款原因。
咨询意图需要品类和预算范围。
实体类型:订单号、金额、日期、品类、地址。
提取方式两种。
规则提取:正则匹配订单号、日期格式。
LLM提取:GPT prompt提取语义实体。
规则优先,LLM兜底。
对话状态管理模块
这是系统的核心决策中枢。
维护整个对话的上下文状态。
状态包含:当前意图、已收集实体、对话轮数、
意图切换历史、待澄清字段。
状态机模型驱动对话流程。
每个一级意图对应一个状态机。
状态机定义收集实体的顺序和澄清策略。
状态转换由实体完整性触发。
所有实体收集完成则进入响应生成状态。
缺少实体则进入追问澄清状态。
澄清策略模块
当意图或实体不明确时触发追问。
意图澄清:"您是想退款还是换货?"
实体澄清:"请提供您的订单号。"
追问最多3轮,超过则转人工。
意图切换检测模块
识别用户中途改变意图。
"算了还是换货吧" → 从退款切换到换货。
检测方式:比较前后意图一致性。
不一致时保存旧状态,创建新状态。
旧状态暂存,用户可能再次切换回来。
响应生成模块
根据状态生成最终回复。
模板响应:确定性场景用预置模板。
LLM响应:开放性场景用GPT生成。
模板优先,LLM补充。
三、生产级代码实现
意图识别与实体提取
"""对话式AI核心模块实现"""
import re
import hashlib
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class Intent(Enum):
REFUND = "退款"
EXCHANGE = "换货"
CONSULT = "咨询"
COMPLAINT = "投诉"
PURCHASE = "购买"
UNKNOWN = "未知"
class IntentLevel(Enum):
HIGH = "高置信度" # >= 0.85
MEDIUM = "中等置信度" # >= 0.6
LOW = "低置信度" # < 0.6
@dataclass
class IntentResult:
intent: Intent
confidence: float
level: IntentLevel
raw_text: str
sub_intent: Optional[str] = None
@dataclass
class Entity:
name: str
value: str
source: str # "rule" or "llm"
confidence: float = 1.0
@dataclass
class DialogState:
session_id: str
current_intent: Optional[Intent] = None
entities: dict[str, Entity] = field(default_factory=dict)
turn_count: int = 0
intent_history: list[Intent] = field(default_factory=list)
pending_entities: list[str] = field(default_factory=list)
clarification_turns: int = 0
previous_intent: Optional[Intent] = None
class RuleBasedIntentClassifier:
"""规则引擎意图分类器"""
RULES = {
Intent.REFUND: [
r"退款|退钱|退回|退款|退费|不要了|不想要",
r"退货|退了|寄回去|拿回来",
],
Intent.EXCHANGE: [
r"换货|换一个|换新的|换型号|更换",
r"换颜色|换尺码|换版本",
],
Intent.CONSULT: [
r"咨询|问一下|了解一下|请问|怎么",
r"想知道|帮我查|查询|看看",
],
Intent.COMPLAINT: [
r"投诉|举报|差评|太差|太烂|坑人",
r"骗人|虚假|欺骗|不满|不满意",
],
Intent.PURCHASE: [
r"买|下单|购买|订购|要一个",
r|来一个|给我|发货|下单",
],
}
_compiled: dict[Intent, list[re.Pattern]] = {}
def __init__(self):
for intent, patterns in self.RULES.items():
self._compiled[intent] = [
re.compile(p, re.IGNORECASE) for p in patterns
]
def classify(self, text: str) -> Optional[IntentResult]:
"""规则匹配意图分类"""
best_intent = None
best_count = 0
for intent, patterns in self._compiled.items():
match_count = sum(1 for p in patterns if p.search(text))
if match_count > best_count:
best_count = match_count
best_intent = intent
if best_intent and best_count >= 1:
confidence = 0.5 + 0.15 * min(best_count, 3)
level = IntentLevel.HIGH if confidence >= 0.85 else IntentLevel.MEDIUM
return IntentResult(
intent=best_intent, confidence=min(confidence, 0.95),
level=level, raw_text=text,
)
return None
class LLMIntentClassifier:
"""LLM意图分类器(模拟GPT调用)"""
# 生产环境实际调用GPT API
# 这里用简化逻辑模拟
INTENT_MAP = {
"这个不要了": Intent.REFUND,
"算了还是换货吧": Intent.EXCHANGE,
"这东西质量太差": Intent.COMPLAINT,
"帮我查一下价格": Intent.CONSULT,
}
def classify(self, text: str) -> IntentResult:
"""LLM语义理解意图分类"""
# 模拟LLM推理结果
matched_intent = Intent.UNKNOWN
confidence = 0.3
for key, intent in self.INTENT_MAP.items():
if key in text:
matched_intent = intent
confidence = 0.75
if matched_intent == Intent.UNKNOWN:
# 无匹配时返回低置信度结果
confidence = 0.25
level = IntentLevel.HIGH if confidence >= 0.85 else \
IntentLevel.MEDIUM if confidence >= 0.6 else IntentLevel.LOW
return IntentResult(
intent=matched_intent, confidence=confidence,
level=level, raw_text=text,
)
class HybridIntentClassifier:
"""混合意图分类器:规则优先+LLM兜底"""
def __init__(self):
self.rule_classifier = RuleBasedIntentClassifier()
self.llm_classifier = LLMIntentClassifier()
def classify(self, text: str) -> IntentResult:
"""加权合并规则和LLM结果"""
rule_result = self.rule_classifier.classify(text)
llm_result = self.llm_classifier.classify(text)
# 规则置信度权重0.6,LLM权重0.4
if rule_result and rule_result.level != IntentLevel.LOW:
if llm_result.intent == rule_result.intent:
# 两者一致,置信度加权提升
confidence = 0.6 * rule_result.confidence + 0.4 * llm_result.confidence
return IntentResult(
intent=rule_result.intent,
confidence=min(confidence, 0.98),
level=IntentLevel.HIGH if confidence >= 0.85 else IntentLevel.MEDIUM,
raw_text=text,
)
else:
# 不一致,倾向规则结果
return rule_result
else:
# 规则无结果,完全依赖LLM
return llm_result
class EntityExtractor:
"""实体提取器"""
ORDER_PATTERN = re.compile(r"订单号[::]?\s*(\d{10,20})")
DATE_PATTERN = re.compile(r"(\d{4}[-/]\d{1,2}[-/]\d{1,2})")
AMOUNT_PATTERN = re.compile(r"(\d+(?:\.\d{1,2})?)\s*元")
# 品类词典
CATEGORY_MAP = {
"手机": "phone", "电脑": "computer",
"衣服": "clothing", "鞋子": "shoes",
"家电": "appliance", "食品": "food",
}
def extract(self, text: str) -> list[Entity]:
"""从文本提取实体"""
entities = []
# 规则提取
order_match = self.ORDER_PATTERN.search(text)
if order_match:
entities.append(Entity("order_id", order_match.group(1), "rule"))
date_match = self.DATE_PATTERN.search(text)
if date_match:
entities.append(Entity("date", date_match.group(1), "rule"))
amount_match = self.AMOUNT_PATTERN.search(text)
if amount_match:
entities.append(Entity("amount", amount_match.group(1), "rule"))
for keyword, category in self.CATEGORY_MAP.items():
if keyword in text:
entities.append(Entity("category", keyword, "rule"))
break
return entities
对话状态机管理
"""对话状态机管理器"""
# 每个意图的必填实体
INTENT_REQUIRED_ENTITIES = {
Intent.REFUND: ["order_id", "refund_reason"],
Intent.EXCHANGE: ["order_id", "exchange_reason"],
Intent.CONSULT: ["category"],
Intent.COMPLAINT: ["order_id", "complaint_detail"],
Intent.PURCHASE: ["category", "quantity"],
}
# 澄清追问模板
CLARIFICATION_TEMPLATES = {
"order_id": "请提供您的订单号(10-20位数字)。",
"refund_reason": "请说明退款原因(质量问题/不喜欢/其他)。",
"exchange_reason": "请说明换货原因和期望换的商品。",
"category": "您想咨询哪个品类?(手机/电脑/衣服/鞋子/家电)",
"complaint_detail": "请详细描述您遇到的问题。",
"quantity": "您需要购买多少件?",
}
INTENT_CLARIFICATION = {
(Intent.REFUND, Intent.EXCHANGE): "您是想退款还是换货?请明确您的需求。",
(Intent.REFUND, Intent.COMPLAINT): "您是想退款还是投诉?如果是投诉,请说明具体问题。",
}
class DialogStateMachine:
"""对话状态机"""
def __init__(self):
self.states: dict[str, DialogState] = {}
def get_state(self, session_id: str) -> DialogState:
"""获取或创建对话状态"""
if session_id not in self.states:
self.states[session_id] = DialogState(
session_id=session_id,
current_intent=None,
entities={},
turn_count=0,
intent_history=[],
pending_entities=[],
clarification_turns=0,
previous_intent=None,
)
return self.states[session_id]
def update_intent(self, session_id: str,
intent_result: IntentResult) -> DialogState:
"""更新意图状态"""
state = self.get_state(session_id)
# 检测意图切换
if state.current_intent and state.current_intent != intent_result.intent:
state.previous_intent = state.current_intent
state.intent_history.append(state.current_intent)
# 切换意图时重置实体收集
state.entities = {}
state.current_intent = intent_result.intent
state.turn_count += 1
# 计算待收集实体
if intent_result.intent != Intent.UNKNOWN:
required = INTENT_REQUIRED_ENTITIES.get(intent_result.intent, [])
collected = set(state.entities.keys())
state.pending_entities = [e for e in required if e not in collected]
return state
def update_entities(self, session_id: str,
entities: list[Entity]) -> DialogState:
"""更新已收集实体"""
state = self.get_state(session_id)
for entity in entities:
state.entities[entity.name] = entity
# 更新待收集列表
if state.current_intent:
required = INTENT_REQUIRED_ENTITIES.get(state.current_intent, [])
collected = set(state.entities.keys())
state.pending_entities = [e for e in required if e not in collected]
return state
def decide_action(self, session_id: str) -> dict:
"""决策下一步动作"""
state = self.get_state(session_id)
# 意图未知 → 意图澄清
if state.current_intent == Intent.UNKNOWN:
state.clarification_turns += 1
if state.clarification_turns >= 3:
return {"action": "transfer_human", "reason": "意图识别3轮未成功"}
return {"action": "clarify_intent", "message": "请问您需要什么帮助?"}
# 实体不完整 → 实体澄清
if state.pending_entities:
missing = state.pending_entities[0]
message = CLARIFICATION_TEMPLATES.get(missing, f"请提供{missing}信息。")
return {"action": "clarify_entity", "message": message, "missing": missing}
# 实体完整 → 响应生成
return {"action": "generate_response", "intent": state.current_intent.value,
"entities": {k: v.value for k, v in state.entities.items()}}
class ResponseGenerator:
"""响应生成器"""
# 模板响应
RESPONSE_TEMPLATES = {
Intent.REFUND: "您的订单{order_id}退款申请已受理,预计3-5个工作日到账。",
Intent.EXCHANGE: "您的订单{order_id}换货申请已受理,新商品将在2-3天内发出。",
Intent.CONSULT: "关于{category}的咨询:我们提供多种款式,详情请访问商品页面。",
Intent.COMPLAINT: "您的投诉已记录,客服将在24小时内联系您处理。",
Intent.PURCHASE: "已为您准备{category}订单,数量{quantity},请确认后支付。",
}
def generate(self, intent: Intent, entities: dict[str, str]) -> str:
"""生成响应"""
template = self.RESPONSE_TEMPLATES.get(intent)
if template:
# 用实体填充模板占位符
try:
return template.format(**entities)
except KeyError:
# 实体缺失时保留占位符原样
return template
return "感谢您的咨询,我们会尽快处理。"
四、性能优化与工程实践
意图识别性能优化。
规则引擎是第一道防线。
高频确定性意图80%由规则命中。
规则匹配延迟<1ms。
LLM仅在规则未命中时调用。
LLM调用延迟50-200ms。
加权合并时规则权重0.6,LLM权重0.4。
一致性结果置信度加权提升。
实体提取优化。
规则提取延迟<1ms。
正则匹配订单号、日期、金额格式稳定。
品类词典匹配覆盖主流类别。
LLM提取仅用于规则无法覆盖的实体。
如自由文本的退款原因描述。
对话状态管理优化。
状态存储用Redis。
session_id作为key,DialogState序列化存储。
TTL设30分钟,超时自动清理。
避免状态无限膨胀占用内存。
意图切换检测策略。
比较当前意图与前一轮意图。
不一致时保存旧状态到intent_history。
新意图开始新的实体收集流程。
最多保留3个历史意图。
超过3个表示用户反复切换,转人工。
LLM调用成本控制。
每次LLM调用约0.01-0.05美元。
规则引擎过滤掉80%的请求。
仅20%需要LLM参与。
日均10万对话约2000次LLM调用。
日均成本约20-100美元。
延迟优化策略。
规则通道1ms响应。
LLM通道异步调用,结果缓存5分钟。
相似输入的LLM结果复用缓存。
用户等待总延迟控制在2秒以内。
多轮对话截断处理。
对话超过10轮时压缩历史。
保留意图切换点和关键实体信息。
丢弃中间澄清追问的对话文本。
压缩后继续对话不影响状态连续性。
五、总结与技术提炼
-
意图识别双通道架构平衡精度和延迟。
规则引擎1ms响应覆盖高频确定性意图。
LLM50-200ms响应覆盖模糊意图。
加权合并置信度,规则权重0.6,LLM权重0.4。 -
实体提取规则优先、LLM兜底。
订单号、日期、金额用正则稳定匹配。
品类用词典匹配覆盖主流类别。
自由文本实体用LLM提取兜底。 -
状态机驱动多轮对话流程。
每个意图对应独立状态机。
实体完整性触发状态转换。
缺少实体时追问澄清,最多3轮。
3轮未成功自动转人工。 -
意图切换检测保存历史状态。
意图不一致时保存旧状态到intent_history。
新意图重置实体收集流程。
最多保留3个历史意图防止反复切换。 -
状态存储用Redis保证持久化。
session_id作key,TTL 30分钟自动清理。
超过10轮对话压缩历史保留关键信息。 -
LLM调用成本和延迟可控。
规则引擎过滤80%请求减少LLM调用。
LLM结果缓存5分钟复用相似输入。
总延迟控制在2秒以内。