AI Agent多轮对话管理:状态机到自由对话的平衡术

对话管理的核心问题 单轮交互很简单:用户提问→模型回答。但Agent需要处理多轮对话——记住之前说了什么、理解隐含意图、在适当的时候追问、在不同话题间切换。对话管理就是解决这些问题。 对话管理的三种范式 范式一:状态机(任务导向) 传统任务型对话系统采用状态机: States: [GREETING, COLLECTING_INFO, CONFIRMING, EXECUTING, CLOSING] GREETING → "您好,请问需要什么帮助?" COLLECTING_INFO → "请告诉我您的出发地和目的地" CONFIRMING → "您要从北京到上海,对吗?" EXECUTING → [执行预订] CLOSING → "预订成功,还有其他需要吗?" 每个state有明确的必填槽位(slot): slots: { origin: required, destination: required, date: required, passenger: optional } 优点: 流程清晰、可控性强、容易调试 缺点: 僵化、不自然、无法处理对话偏离 范式二:自由对话(开放域) LLM天然擅长自由对话——用户可以随时切换话题、提出任何问题。对话管理完全交给LLM的上下文理解能力。 优点: 自然、灵活、用户体验好 缺点: 不可控、可能偏离任务、难以保证任务完成 范式三:混合管理(推荐实践) 在实践中最有效的是混合方案——LLM负责对话自然性,系统负责状态追踪: 用户: "帮我订明天去上海的票" → 系统状态追踪: {intent: "订票", date: "明天", destination: "上海", origin: null} → origin缺失,需要追问 → LLM自然回复: "好的,去上海!请问您从哪里出发呢?" 用户: "从北京,高铁还是飞机?" → 系统更新: {origin: "北京"} → 需要决策: 交通方式 → LLM: "从北京到上海,高铁约4.5小时,飞机约2小时。您倾向哪种?" 对话状态追踪 状态表示 class DialogState: # 用户意图 intent: str # "book_ticket", "qa", "chitchat" # 已知信息(槽位) slots: dict # {"origin": "北京", "destination": "上海"} # 对话历史摘要 summary: str # "用户要订北京到上海的高铁票" # 缺失信息 missing_slots: list # ["date", "passenger_name"] # 上下文 topic: str # 当前话题 topic_history: list # 话题切换历史 状态更新 每次用户输入后更新状态: ...

2026-07-16 · 2 min · 340 words · 硅基 AGI 探索者
多轮对话管理

多轮对话管理实现

多轮对话的核心挑战 多轮对话不是简单的消息拼接——它需要管理对话状态、控制上下文窗口长度、处理话题切换、维护一致性。一个好的对话管理系统是LLM助手的"大脑"。 对话状态管理 from enum import Enum from dataclasses import dataclass, field class DialogState(Enum): GREETING = "greeting" INFORMATION_SEEKING = "information_seeking" PROBLEM_SOLVING = "problem_solving" CLARIFICATION = "clarification" CLOSING = "closing" @dataclass class ConversationContext: session_id: str user_id: str state: DialogState = DialogState.GREETING topic: str = "" entities: dict = field(default_factory=dict) # 提取的实体 history: list = field(default_factory=list) # 消息历史 user_preferences: dict = field(default_factory=dict) pending_clarification: str = "" class DialogManager: def __init__(self, llm): self.llm = llm self.sessions = {} # session_id -> ConversationContext def get_or_create_session(self, session_id, user_id): if session_id not in self.sessions: self.sessions[session_id] = ConversationContext( session_id=session_id, user_id=user_id ) return self.sessions[session_id] async def process_message(self, session_id, user_id, message): ctx = self.get_or_create_session(session_id, user_id) # 1. 状态检测 ctx.state = await self.detect_state(message, ctx) # 2. 实体提取 new_entities = await self.extract_entities(message) ctx.entities.update(new_entities) # 3. 构建上下文 context = self.build_context(ctx, message) # 4. 生成回复 response = await self.llm.generate(context) # 5. 更新历史 ctx.history.append({"role": "user", "content": message}) ctx.history.append({"role": "assistant", "content": response}) # 6. 上下文窗口管理 self.manage_window(ctx) return response 上下文窗口管理 class ContextWindowManager: def __init__(self, max_tokens=4096, reserve_tokens=1024): self.max_tokens = max_tokens self.reserve_tokens = reserve_tokens # 为生成预留 def manage(self, context): """管理上下文窗口大小""" available = self.max_tokens - self.reserve_tokens current_tokens = self.estimate_tokens(context.history) if current_tokens <= available: return # 在限制内,无需处理 # 策略1:摘要旧消息 while current_tokens > available and len(context.history) > 4: # 取最早的2条消息进行摘要 old_msgs = context.history[:2] summary = self.summarize(old_msgs) # 替换为摘要 context.history = [ {"role": "system", "content": f"[早期对话摘要]: {summary}"} ] + context.history[2:] current_tokens = self.estimate_tokens(context.history) # 策略2:如果仍然过长,截断 if current_tokens > available: # 保留system prompt和最近的消息 while current_tokens > available and len(context.history) > 2: context.history.pop(0 if context.history[0]["role"] != "system" else 1) current_tokens = self.estimate_tokens(context.history) def estimate_tokens(self, messages): return sum(len(m["content"]) // 4 for m in messages) def summarize(self, messages): """摘要旧消息""" text = "\n".join(f"{m['role']}: {m['content']}" for m in messages) # 使用LLM摘要 return f"[{len(messages)}条消息的摘要]" 话题管理 class TopicManager: def __init__(self, llm): self.llm = llm async def detect_topic_shift(self, current_message, history): """检测话题是否切换""" if len(history) < 2: return False, None recent_topic = await self.extract_topic(history[-2:]) current_topic = await self.extract_topic([{"content": current_message}]) similarity = await self.compute_similarity(recent_topic, current_topic) if similarity < 0.3: return True, current_topic # 话题切换 return False, recent_topic async def handle_topic_shift(self, ctx, new_topic): """处理话题切换""" # 保存当前话题的摘要 old_summary = await self.summarize_topic(ctx) ctx.topic_summaries = ctx.get("topic_summaries", []) ctx.topic_summaries.append({"topic": ctx.topic, "summary": old_summary}) # 切换到新话题 ctx.topic = new_topic ctx.state = DialogState.INFORMATION_SEEKING 意图跟踪 class IntentTracker: def __init__(self, llm): self.llm = llm self.intent_history = [] async def track(self, message, context): """跟踪用户意图""" prompt = f"""分析用户意图。 用户消息:{message} 对话历史摘要:{self.get_history_summary()} 当前状态:{context.state.value} 输出JSON: {{ "intent": "具体意图", "confidence": 0.0-1.0, "requires_clarification": true/false, "clarification_question": "如需澄清的问题" }}""" result = await self.llm.generate(prompt) intent = json.loads(result) self.intent_history.append(intent) return intent 会话持久化 import redis.asyncio as redis class SessionPersistence: def __init__(self, redis_url="redis://localhost:6379"): self.redis = redis.from_url(redis_url) async def save_session(self, session_id, context): """持久化会话""" await self.redis.setex( f"session:{session_id}", 86400, # 24小时过期 json.dumps({ "session_id": context.session_id, "user_id": context.user_id, "state": context.state.value, "topic": context.topic, "entities": context.entities, "history": context.history[-20:], # 只存最近20条 "user_preferences": context.user_preferences, }, ensure_ascii=False) ) async def load_session(self, session_id): """加载会话""" data = await self.redis.get(f"session:{session_id}") if data: d = json.loads(data) return ConversationContext( session_id=d["session_id"], user_id=d["user_id"], state=DialogState(d["state"]), topic=d["topic"], entities=d["entities"], history=d["history"], user_preferences=d["user_preferences"], ) return None 实践建议 会话超时:设置合理的会话过期时间(如30分钟无交互自动关闭) 上下文压缩:定期摘要旧对话,而非简单截断 意图确认:低置信度意图主动询问用户 多模态状态:不仅跟踪文本,还跟踪用户的情绪、满意度 A/B测试:对话策略的变更需要A/B测试验证效果 结语 多轮对话管理是LLM助手从"问答工具"升级为"对话伙伴"的关键。状态管理、上下文窗口控制、话题跟踪和会话持久化的协同工作,让Agent能够维持连贯、智能的多轮对话。 加入讨论 这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。 ...

2026-07-02 · 3 min · 491 words · 硅基 AGI 探索者
multi turn dialogue optimization

多轮对话Prompt优化策略

引言 单轮Prompt优化已有成熟方法论,但多轮对话场景引入了新的复杂性:上下文累积导致token膨胀、话题切换导致注意力分散、历史信息可能导致指令漂移。本文针对多轮对话的特有挑战,提出系统化的Prompt优化策略。 多轮对话的核心挑战 上下文膨胀 随着对话轮数增加,历史消息不断累积,导致:token消耗线性增长、超出上下文窗口限制、模型对早期信息注意力衰减、首token延迟增加。 话题漂移 用户在多轮对话中可能切换话题,导致:系统Prompt的指令被"冲淡"、早期设定的人物角色被遗忘、约束条件随轮数增加而弱化。 一致性问题 模型在不同轮次中对同一问题的回答可能不一致:前面给出的信息后面被修改、角色设定随对话深入而偏离、风格和语气发生变化。 上下文管理策略 策略一:消息摘要压缩 定期对历史对话进行摘要压缩,用摘要替代原始消息: def manage_context(messages, max_messages=20, summary_threshold=10): """管理对话上下文""" if len(messages) <= max_messages: return messages # 保留最近N条消息 recent = messages[-max_messages:] # 对较早的消息进行摘要 old_messages = messages[:-max_messages] if len(old_messages) > 3: summary = summarize_conversation(old_messages) return [ {"role": "system", "content": f"对话历史摘要:{summary}"}, *recent ] return messages 摘要频率和粒度的权衡:频繁摘要增加计算成本但信息保留更好;稀疏摘要成本低但可能丢失细节。建议每10-15轮进行一次摘要。 策略二:关键信息提取 不摘要整个对话,而是提取关键信息点: # 对话关键信息(系统维护) - 用户姓名:张三 - 用户需求:设计一个电商API - 已确定的技术栈:FastAPI + PostgreSQL - 已完成:数据模型设计 - 当前阶段:接口设计 - 待解决问题:认证方案选择 这种方式比全文摘要更紧凑,且关键信息不会在摘要中被遗漏。 策略三:分层上下文 将上下文分为不同层级,按优先级管理: # 永久上下文(不压缩) - 系统Prompt - 用户画像 - 会话目标 # 重要上下文(压缩后保留) - 关键决策记录 - 已确认的需求 - 重要的中间结果 # 临时上下文(可丢弃) - 非关键闲聊 - 已完成的子任务讨论 - 已过时的中间结果 指令持久化策略 定期重申关键指令 在多轮对话中,关键约束可能被淡化。策略:每隔N轮或在检测到话题切换时,重新注入关键指令。 ...

2026-06-27 · 2 min · 319 words · 硅基 AGI 探索者
鲁ICP备2026018361号