AI Agent的多轮对话:提升用户体验的关键技巧
在前面的文章中,我们讨论了 AI Agent 的各个核心系统。今天,我想聊聊如何实现一个好用的多轮对话系统。说实话,这个话题我琢磨了很久,因为它直接影响到用户体验。
从一个槽点说起
还记得我最开始做对话系统时的一个典型场景:
用户:帮我查一下订单状态
助手:好的,请提供订单号。
用户:A12345
助手:抱歉,我需要您提供订单号才能查询。
用户:...我刚才不是说了吗?
这种对话体验显然很糟糕。问题出在哪里?主要是 AI Agent 没有正确理解和维护对话上下文。后来我总结了几个关键点:
- 要理解用户意图
- 要记住上下文信息
- 要有状态管理机制
- 要能处理各种异常情况
对话系统的设计
经过多次迭代,我设计了一个相对完善的对话系统:
from typing import List, Dict, Any, Optional
from enum import Enum
from datetime import datetime
from pydantic import BaseModel
import asyncioclass DialogueState(Enum):IDLE = "idle"WAITING_INPUT = "waiting_input"PROCESSING = "processing"ERROR = "error"COMPLETED = "completed"class DialogueContext(BaseModel):session_id: struser_id: strcurrent_state: DialogueStatecurrent_intent: Optional[str]slots: Dict[str, Any]history: List[Dict[str, Any]]created_at: datetimeupdated_at: datetimeclass DialogueSystem:def __init__(self,llm,tool_registry,memory_system):self.llm = llmself.tool_registry = tool_registryself.memory_system = memory_systemself.sessions: Dict[str, DialogueContext] = {}async def process_message(self,session_id: str,user_id: str,message: str) -> str:# 1. 获取或创建会话上下文context = self._get_or_create_context(session_id,user_id)try:# 2. 更新状态context.current_state = DialogueState.PROCESSING# 3. 理解用户意图intent = await self._understand_intent(message,context)# 4. 更新上下文context.current_intent = intent.namecontext.slots.update(intent.slots)# 5. 执行对应的处理流程response = await self._handle_intent(intent,context)# 6. 记录对话历史self._update_history(context,message,response)return responseexcept Exception as e:context.current_state = DialogueState.ERRORreturn f"抱歉,处理您的请求时出现错误:{str(e)}"finally:# 保存上下文self._save_context(context)async def _understand_intent(self,message: str,context: DialogueContext) -> Intent:# 结合上下文理解用户意图response = await self.llm.understand_intent(message=message,history=context.history[-5:], # 最近5轮对话current_intent=context.current_intent,slots=context.slots)return Intent(name=response.intent,confidence=response.confidence,slots=response.slots)async def _handle_intent(self,intent: Intent,context: DialogueContext) -> str:# 检查是否有未填充的必要槽位missing_slots = self._get_missing_slots(intent)if missing_slots:# 返回槽位询问context.current_state = DialogueState.WAITING_INPUTreturn self._generate_slot_question(missing_slots[0])# 所有槽位都已填充,执行操作result = await self._execute_intent(intent,context)context.current_state = DialogueState.COMPLETEDreturn resultdef _get_or_create_context(self,session_id: str,user_id: str) -> DialogueContext:if session_id in self.sessions:return self.sessions[session_id]# 创建新会话context = DialogueContext(session_id=session_id,user_id=user_id,current_state=DialogueState.IDLE,current_intent=None,slots={},history=[],created_at=datetime.now(),updated_at=datetime.now())self.sessions[session_id] = contextreturn context
使用示例:
# 初始化对话系统
dialogue = DialogueSystem(llm=ChatGPT(),tool_registry=tool_registry,memory_system=memory_system
)# 处理用户消息
async def chat():responses = []# 第一轮:查询订单response = await dialogue.process_message(session_id="123",user_id="user_1",message="帮我查一下订单状态")responses.append(response)# 输出:好的,请提供订单号。# 第二轮:提供订单号response = await dialogue.process_message(session_id="123",user_id="user_1",message="A12345")responses.append(response)# 输出:您的订单 A12345 正在配送中,预计明天送达。# 第三轮:追问细节response = await dialogue.process_message(session_id="123",user_id="user_1",message="具体什么时候到?")responses.append(response)# 输出:根据物流信息,预计明天上午10:00-12:00送达。return responses# 运行对话
responses = await chat()
for r in responses:print(r)
关键实现细节
1. 意图理解
class IntentRecognizer:def __init__(self, llm):self.llm = llmasync def recognize(self,message: str,context: Dict[str, Any]) -> Intent:# 1. 准备提示词prompt = self._prepare_prompt(message,context)# 2. 调用 LLMresponse = await self.llm.generate(prompt)# 3. 解析结果intent = self._parse_response(response)# 4. 验证意图self._validate_intent(intent)return intentdef _prepare_prompt(self,message: str,context: Dict[str, Any]) -> str:return f"""请分析以下对话内容,识别用户意图:历史对话:{self._format_history(context.get('history', []))}当前状态:- 意图:{context.get('current_intent')}- 已知信息:{json.dumps(context.get('slots', {}), indent=2)}用户消息:{message}请返回:1. 意图名称2. 置信度3. 识别出的槽位信息"""
2. 状态管理
class StateManager:def __init__(self):self.state_handlers = {DialogueState.IDLE: self._handle_idle,DialogueState.WAITING_INPUT: self._handle_waiting,DialogueState.PROCESSING: self._handle_processing,DialogueState.ERROR: self._handle_error,DialogueState.COMPLETED: self._handle_completed}async def handle_state(self,context: DialogueContext,message: str) -> str:# 获取当前状态的处理器handler = self.state_handlers.get(context.current_state)if not handler:raise ValueError(f"未知状态:{context.current_state}")# 执行状态处理return await handler(context, message)async def _handle_waiting(self,context: DialogueContext,message: str) -> str:# 检查是否填充了等待的槽位slot_name = context.waiting_for_slotif self._is_valid_slot_value(slot_name,message):# 更新槽位context.slots[slot_name] = message# 继续处理return await self._continue_processing(context)else:# 重新询问return f"抱歉,这似乎不是有效的{slot_name},请重新输入。"
3. 上下文管理
class ContextManager:def __init__(self, memory_system):self.memory = memory_systemself.max_history = 10async def update_context(self,context: DialogueContext,message: str,response: str):# 1. 更新对话历史context.history.append({"role": "user","content": message,"timestamp": datetime.now()})context.history.append({"role": "assistant","content": response,"timestamp": datetime.now()})# 2. 限制历史长度if len(context.history) > self.max_history * 2:# 保存旧对话到长期记忆old_messages = context.history[:-self.max_history * 2]await self._save_to_memory(context.session_id,old_messages)# 保留最近的对话context.history = context.history[-self.max_history * 2:]# 3. 更新时间戳context.updated_at = datetime.now()async def _save_to_memory(self,session_id: str,messages: List[Dict]):# 将对话保存到长期记忆await self.memory.remember(content=self._format_messages(messages),metadata={"type": "dialogue","session_id": session_id,"timestamp": datetime.now()})
优化技巧
在实践中,我总结了一些提升用户体验的技巧:
1. 主动确认
class ConfirmationManager:def __init__(self, threshold: float = 0.8):self.threshold = thresholddef need_confirm(self,intent: Intent,context: DialogueContext) -> bool:# 检查是否需要确认if intent.confidence < self.threshold:return Trueif self._is_critical_operation(intent):return Truereturn Falsedef generate_confirmation(self,intent: Intent,context: DialogueContext) -> str:return f"""请确认您是否要{intent.description}?- 操作:{intent.name}- 参数:{json.dumps(intent.slots, indent=2)}回复"是"或"否"。"""
2. 错误恢复
class ErrorRecovery:async def recover(self,error: Exception,context: DialogueContext) -> str:# 分析错误analysis = await self._analyze_error(error)if analysis.can_retry:# 自动重试return await self._retry_operation(context)elif analysis.need_clarification:# 请求用户澄清return self._generate_clarification_question(analysis)else:# 友好的错误提示return self._generate_error_message(analysis)
3. 上下文压缩
class ContextCompressor:def compress_history(self,history: List[Dict],max_tokens: int) -> List[Dict]:# 1. 计算当前token数current_tokens = self._count_tokens(history)if current_tokens <= max_tokens:return history# 2. 提取关键信息key_messages = self._extract_key_messages(history)# 3. 压缩对话compressed = self._compress_messages(key_messages,max_tokens)return compresseddef _extract_key_messages(self,history: List[Dict]) -> List[Dict]:# 提取重要的对话轮次key_turns = []for i, msg in enumerate(history):if self._is_key_message(msg, history, i):key_turns.append(msg)return key_turns
实践心得
在实现和优化对话系统的过程中,我总结了几点经验:
以用户体验为中心
- 理解用户真实意图
- 保持对话的连贯性
- 给出清晰的反馈
要有容错机制
- 优雅处理异常
- 支持意图澄清
- 允许用户更正
注意性能优化
- 合理管理上下文
- 及时清理无用信息
- 异步处理耗时操作
写在最后
一个好的对话系统应该像一个专业的客服,既要理解用户需求,又要高效地解决问题。它不仅要"能听懂",还要"会说话"。
在下一篇文章中,我会讲解如何实现 AI Agent 的安全机制。如果你对对话系统的设计有什么想法,欢迎在评论区交流。
相关文章:
AI Agent的多轮对话:提升用户体验的关键技巧
在前面的文章中,我们讨论了 AI Agent 的各个核心系统。今天,我想聊聊如何实现一个好用的多轮对话系统。说实话,这个话题我琢磨了很久,因为它直接影响到用户体验。 从一个槽点说起 还记得我最开始做对话系统时的一个典型场景&…...
分布式光纤应变监测是一种高精度、分布式的监测技术
一、土木工程领域 桥梁结构健康监测 主跨应变监测:在大跨度桥梁的主跨部分,如悬索桥的主缆、斜拉桥的斜拉索和主梁,分布式光纤应变传感器可以沿着这些关键结构部件进行铺设。通过实时监测应变情况,能够精确捕捉到车辆荷载、风荷…...
机器学习10-解读CNN代码Pytorch版
机器学习10-解读CNN代码Pytorch版 我个人是Java程序员,关于Python代码的使用过程中的相关代码事项,在此进行记录 文章目录 机器学习10-解读CNN代码Pytorch版1-核心逻辑脉络2-参考网址3-解读CNN代码Pytorch版本1-MNIST数据集读取2-CNN网络的定义1-无注释版…...
面向程序员的Lean 4教程(2) - 数组和列表
面向程序员的Lean 4教程(2) - 数组和列表 上一节我们介绍了Lean4的基本语法,因为大部分程序员都有一定的编程基础,所以并没有介绍过细。这一节我们介绍Lean4中的线性表结构:数组和列表,顺带复习一下上一节的流程控制等内容。 数…...
【C++高并发服务器WebServer】-7:共享内存
本文目录 一、共享内存1.1 shmget函数1.2 shmat1.3 shmdt1.4 shmctl1.5 ftok1.6 共享内存和内存映射的关联1.7 小demo 二、共享内存操作命令 一、共享内存 共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会称…...
【力扣Hot 100】链表1
1. 相交链表 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交**:** !https://assets.leetcode-cn.com/aliyun-lc-upload/uplo…...
稀土抗菌剂:提升产品质量,保障公共健康
随着全球对抗菌技术需求的不断增长,传统的抗菌剂逐渐暴露出其局限性,包括耐药性、环境污染及副作用等问题。在此背景下,稀土抗菌剂作为一种新兴的抗菌材料,凭借其卓越的抗菌性能、环保特性以及应用多样性,正在成为各行…...
机器学习11-学习路径推荐
机器学习11-学习路径推荐 本文希望摒除AI学习商业宣传要素,推荐一条极简的AI学习路线!推荐内容均为在线免费内容,如果有条件可以咨询专业的培训机构! 文章目录 机器学习11-学习路径推荐[toc] 1-AI培训路线第一阶段 Python-人工智能…...
postgresql根据主键ID字段分批删除表数据
生产环境针对大表的处理相对比较麻烦。 方案1、直接truncate,可能会遇到系统卡主的情况,因为truncate的过程中会对表进行加锁,会导致数据不能正常的写入 方案2、创建一个同结构的表结构,rename旧表,不停业务rename表担…...
《边界感知的分而治之方法:基于扩散模型的无监督阴影去除解决方案》学习笔记
paper:Boundary-Aware Divide and Conquer: A Diffusion-Based Solution for Unsupervised Shadow Removal 目录 摘要 1、介绍 2、相关工作 2.1 阴影去除 2.2 去噪扩散概率模型(Denoising Diffusion Probabilistic Models, DDPM) 3、方…...
java后端之事务管理
Transactional注解:作用于业务层的方法、类、接口上,将当前方法交给spring进行事务管理,执行前开启事务,成功执行则提交事务,执行异常回滚事务 spring事务管理日志: 默认情况下,只有出现Runti…...
深入浅出 SQLSugar:快速掌握高效 .NET ORM 框架
SQLSugar 是一个高效、易用的 .NET ORM 框架,支持多种数据库(如 SQL Server、MySQL、PostgreSQL 等)。它提供了丰富的功能,包括 CRUD 操作、事务管理、动态表名、多表联查等,开发者可以通过简单的链式操作实现复杂的数…...
数据结构——概念与时间空间复杂度
目录 前言 一相关概念 1什么是数据结构 2什么是算法 二算法效率 1如何衡量算法效率的好坏 2算法的复杂度 三时间复杂度 1时间复杂度表示 2计算时间复杂度 2.1题一 2.2题二 2.3题三 2.4题四 2.5题五 2.6题六 2.7题七 2.8题八 四空间复杂度 1题一 2题二 3…...
浅谈在AI时代GIS的发展方向和建议
在AI时代,GIS(地理信息系统)的发展正经历着深刻的变革,随着人工智能技术的进步,GIS不再仅仅是传统的地图和空间数据处理工具,而是向更加智能化、自动化、精准化的方向发展。作为一名GIS开发工程师ÿ…...
牛客周赛 Round 78 A-C
A.时间表查询! 链接:https://ac.nowcoder.com/acm/contest/100671/A 来源:牛客网 题目描述 今天是2025年1月25日,今年的六场牛客寒假算法基础集训营中,前两场比赛已经依次于 20250121、20250123 举行;而…...
Java I/O 流介绍
Java学习资料 Java学习资料 Java学习资料 一、引言 在 Java 编程中,I/O(Input/Output)流是处理输入和输出操作的核心机制。它允许程序与外部设备(如文件、网络连接、键盘、显示器等)进行数据交互。通过使用 I/O 流&…...
linux 内核学习方向以及职位
### 学习路径 1. 基础阶段: - C语言高级特性 - 指针和内存管理 - 数据结构实现 - 位操作 - 多线程编程 - Linux系统编程 - 文件I/O操作 - 进程管理 - 信号处理 - IPC机制 - Socket编程 - 必备知识 - 操作系统原理 - 计算机体系结构 - …...
HTML-新浪新闻-实现标题-样式1
用css进行样式控制 css引入方式: --行内样式:写在标签的style属性中(不推荐) --内嵌样式:写在style标签中(可以写在页面任何位置,但通常约定写在head标签中) --外联样式…...
【电磁兼容】CE 传导骚扰
一。是什么? 传导骚扰是指电气或电子设备产生的不需要的电磁能量,这些能量通过导线或其他金属路径传播到其他设备或者系统中。这种类型的干扰通常发生在同一电源线路连接的不同装置之间,或者是共享相同布线系统的组件间。 传导骚扰可以分为两…...
能说说MyBatis的工作原理吗?
大家好,我是锋哥。今天分享关于【Redis为什么这么快?】面试题。希望对大家有帮助; 能说说MyBatis的工作原理吗? MyBatis 是一款流行的持久层框架,它通过简化数据库操作,帮助开发者更高效地与数据库进行交互。MyBatis…...
詳細講一下RN(React Native)中的列表組件FlatList和SectionList
1. FlatList 基礎使用 import React from react; import { View, Text, FlatList, StyleSheet } from react-native;export const SimpleListDemo: React.FC () > {// 1. 準備數據const data [{ id: 1, title: 項目 1 },{ id: 2, title: 項目 2 },{ id: 3, title: 項目 3…...
LabVIEW进行可靠性测试时有哪些常见的问题
在进行LabVIEW开发和测试时,尤其是用于可靠性测试,可能会遇到一些常见的问题。以下是一些常见问题及其解决方法: 1. 数据采集卡与硬件兼容性问题 问题描述:某些数据采集卡(DAQ)与硬件设备的兼容性问题可能…...
MFC程序设计(四)窗口创建机制
钩子函数 钩子属于win32技术,具有优先勾取消息的权利:当一个消息产生时,钩子勾取消息进行处理,然后消息才送回程序 接下来以一个勾取窗口创建消息的钩子为例进行讲解 钩子类型有键盘钩子,鼠标钩子,WH_CBT…...
【JavaEE进阶】Spring留言板实现
目录 🎍预期结果 🍀前端代码 🎄约定前后端交互接口 🚩需求分析 🚩接口定义 🌳实现服务器端代码 🚩lombok介绍 🚩代码实现 🌴运行测试 🎄前端代码实…...
Unity开发一个单人FPS游戏的教程总结
这个系列的前几篇文章介绍了如何从头开始用Unity开发一个FPS游戏,感兴趣的朋友可以回顾一下。这个系列的文章如下: Unity开发一个FPS游戏_unity 模仿开发fps 游戏-CSDN博客 Unity开发一个FPS游戏之二_unity 模仿开发fps 游戏-CSDN博客 Unity开发一个F…...
论文速读|Is Cosine-Similarity of Embeddings Really About Similarity?WWW24
论文地址: https://arxiv.org/abs/2403.05440 https://dl.acm.org/doi/abs/10.1145/3589335.3651526 bib引用: inproceedings{Steck_2024, series{WWW ’24},title{Is Cosine-Similarity of Embeddings Really About Similarity?},url{http://dx.doi.o…...
71.在 Vue 3 中使用 OpenLayers 实现按住 Shift 拖拽、旋转和缩放效果
前言 在前端开发中,地图功能是一个常见的需求。OpenLayers 是一个强大的开源地图库,支持多种地图源和交互操作。本文将介绍如何在 Vue 3 中集成 OpenLayers,并实现按住 Shift 键拖拽、旋转和缩放地图的效果。 实现效果 按住 Shift 键&#…...
PyQt6医疗多模态大语言模型(MLLM)实用系统框架构建初探(上.文章部分)
一、引言 1.1 研究背景与意义 在数字化时代,医疗行业正经历着深刻的变革,智能化技术的应用为其带来了前所未有的发展机遇。随着医疗数据的指数级增长,传统的医疗诊断和治疗方式逐渐难以满足现代医疗的需求。据统计,全球医疗数据量预计每年以 48% 的速度增长,到 2025 年将…...
250125-package
1. 定义 包就是文件夹,作用是在大型项目中,避免不同人的编写的java文件出现同名进而导致报错;想象一个场景,在一个根目录中,每一个人都有自己的一个java文件夹,他可以将自己编写的文件放在该文件夹里&…...
左右互博02-unidbg主动调用外层so函数
unidbg 代码 ` package com.koohairev.demo;import com.github.unidbg.AndroidEmulator; import com.github.unidbg.LibraryResolver; import com.github.unidbg.Module; import com.github.unidbg.Symbol; import com.github.unidbg.arm.backend.DynarmicFactory; import com.…...
