对话式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轮时压缩历史。
保留意图切换点和关键实体信息。
丢弃中间澄清追问的对话文本。
压缩后继续对话不影响状态连续性。

五、总结与技术提炼

  1. 意图识别双通道架构平衡精度和延迟。
    规则引擎1ms响应覆盖高频确定性意图。
    LLM50-200ms响应覆盖模糊意图。
    加权合并置信度,规则权重0.6,LLM权重0.4。

  2. 实体提取规则优先、LLM兜底。
    订单号、日期、金额用正则稳定匹配。
    品类用词典匹配覆盖主流类别。
    自由文本实体用LLM提取兜底。

  3. 状态机驱动多轮对话流程。
    每个意图对应独立状态机。
    实体完整性触发状态转换。
    缺少实体时追问澄清,最多3轮。
    3轮未成功自动转人工。

  4. 意图切换检测保存历史状态。
    意图不一致时保存旧状态到intent_history。
    新意图重置实体收集流程。
    最多保留3个历史意图防止反复切换。

  5. 状态存储用Redis保证持久化。
    session_id作key,TTL 30分钟自动清理。
    超过10轮对话压缩历史保留关键信息。

  6. LLM调用成本和延迟可控。
    规则引擎过滤80%请求减少LLM调用。
    LLM结果缓存5分钟复用相似输入。
    总延迟控制在2秒以内。

© 版权声明

相关文章