假如说要设计一个多轮对话Agent,你会怎么设计?

张开发
2026/4/7 23:45:09 15 分钟阅读

分享文章

假如说要设计一个多轮对话Agent,你会怎么设计?
1. 题目分析几乎每个人都用过多轮对话——打开 ChatGPT 聊几句就是。但是要设计一个多轮对话可不容易。多轮对话 Agent 的设计之所以难不是因为某一个技术点特别深奥而是因为它要求你同时想清楚好几件事情怎么协同运作上下文怎么管、状态怎么追踪、记忆怎么存、上下文窗口装不下了怎么办、对话中途要调工具怎么处理……这些子问题单拎出来都不算太难但一旦放进多轮对话这个场景里它们之间的耦合关系会让整体复杂度指数级上升。面试官抛出这道设计题就是想知道你能不能抓住多轮对话最核心的那几个设计决策点讲清楚每个点上有哪些取舍、你会怎么选择、为什么。接下来我就沿着一个请求从用户嘴里说出来到最终返回答案的数据流走一遍看看这条路上到底有哪些关卡需要你做设计决策。1.1 对话历史的管理LLM 本身是无状态的。每次调用你得把整段对话历史塞进 prompt它才能记住之前说了什么。这意味着对话历史的管理方式直接决定了整个多轮对话 Agent 的上限。最朴素的方案是全量拼接——把从第一轮到当前轮的所有 messages 原封不动地拼成一个列表送进去。这在对话刚开始的几轮没什么问题但随着对话轮数增加token 数量会线性增长很快就会撞上上下文窗口的天花板。而且更关键的是不是所有历史消息都同等重要。第三轮用户随口问的一句今天天气怎么样到第二十轮讨论技术架构时已经完全不相关了但它还占着宝贵的上下文空间。所以你需要一套对话历史的管理策略核心要回答两个问题哪些历史该保留哪些该丢弃保留的历史以什么形式存在常见的策略有三种。第一种是滑动窗口只保留最近 N 轮对话更早的直接截断。简单粗暴但有效。问题是如果用户在第 5 轮提到的一个关键信息比如我说的是生产环境的数据库到第 15 轮还被引用窗口一滑就丢了。第二种是摘要压缩当历史超过一定长度时用 LLM 对早期对话做一次摘要把几千 token 的详细对话压缩成几百 token 的核心要点然后用摘要 最近 N 轮原文的组合送进去。这种方式在信息保留和 token 节省之间取得了不错的平衡但摘要本身可能丢失细节而且每次做摘要也有额外的 LLM 调用成本。第三种是基于重要性的选择性保留给每条消息打一个重要性分数比如包含用户明确指令的消息分数高闲聊的分数低然后优先保留高分消息。这种方式更精细但重要性评分本身又是一个不小的工程问题。实际工程中最常用的是摘要 滑动窗口的混合方案远期历史做摘要近期历史保留原文再加上一个 system prompt 持续携带任务背景信息。这样既控制了 token 用量又保证了近期上下文的完整性和远期关键信息的不丢失。1.2 记忆系统对话历史管理解决的是单次会话内的上下文问题但一个真正好用的多轮对话 Agent 还需要有跨会话的记忆能力。用户昨天告诉 Agent 我是后端开发主要用 Go 语言今天再来问技术问题时Agent 应该记得这个偏好而不是每次都从头确认。记忆系统的设计通常分为三层。工作记忆Working Memory也就是当前对话的上下文就是上一节讨论的对话历史。它是短时的、高精度的但容量有限。短期记忆Short-term Memory用来存储当前会话或近几次会话中提取出的结构化信息。比如在一次帮用户排查 bug 的对话中Agent 从对话里提取出问题现象接口超时、环境生产环境 k8s、已排除原因数据库连接正常这些结构化 slot存进一个轻量的 key-value 存储里。这些信息不依赖原始对话文本所以不占上下文窗口但又随时可以按需检索注入到 prompt 中。长期记忆Long-term Memory用来存储跨会话的持久化信息。实现上通常是把关键信息 embedding 之后存进向量数据库下次对话开始时根据当前话题做语义检索把相关的历史记忆注入到 system prompt 里。这一层解决的是用户偏好记忆、历史对话的关键结论这类需要长期保持的信息。三层记忆的工程挑战各不相同。工作记忆的挑战在上一节已经聊过了——窗口管理和信息压缩。短期记忆的挑战在于信息提取的准确性——你需要让 LLM 从非结构化的对话中准确抽取出结构化信息这本身就是一个有噪声的过程。长期记忆的挑战在于检索的相关性和时效性——三个月前存的一条用户在做 Python 项目的记忆现在用户已经转去做 Rust 了这条过期记忆如果被检索出来注入 prompt反而会误导 Agent。所以长期记忆还需要一套衰减和更新机制。1.3 对话状态追踪多轮对话不是一轮一轮独立的问答拼接它有连贯的上下文语境和正在推进的任务状态。Agent 必须随时知道当前聊到了什么阶段、还差哪些信息没收集到、用户的核心意图有没有发生变化。这就是对话状态追踪Dialogue State Tracking, DST要解决的问题。传统的任务型对话系统里DST 的做法比较明确预先定义好一组 slot比如订机票场景的出发地、目的地、日期、舱位每轮对话后更新 slot 的填充状态所有 slot 填满了就触发对应的 action。但 LLM 时代的多轮对话 Agent 面对的场景要复杂得多——用户的意图可能是开放式的可能中途切换话题可能一句话里包含多个意图也可能隐含的意图需要推理才能发现。在 LLM-based 的 Agent 中对话状态追踪通常不再用传统的 slot-filling 范式而是让 LLM 自己来理解和维护对话状态。具体的实现方式有两种路线。第一种是隐式状态追踪不显式维护任何状态对象完全依赖 LLM 从对话历史中读出当前状态。每轮对话时把完整历史送进去LLM 自己判断现在该做什么。这种方式实现最简单但问题是当对话变长后LLM 对远处上下文的注意力会衰减lost in the middle 问题可能遗漏早期的关键信息。第二种是显式状态追踪在每轮对话后让 LLM 输出一个结构化的状态对象JSON 格式记录当前的任务进度、已收集的信息、待确认的事项等。这个状态对象在下一轮对话时作为 system prompt 的一部分注入相当于给 LLM 一个备忘录。这种方式的好处是状态清晰可控、不容易遗漏信息代价是每轮都要多做一次状态更新的 LLM 调用。实践中我推荐的做法是显式状态 轻量更新维护一个结构化的 dialogue state但不是每轮都重新生成整个状态而是让 LLM 只输出相对于上一轮状态的增量变更delta update这样既保持了状态的准确性又控制了额外的 token 开销。1.4 上下文窗口的工程策略前面几个设计点最终都会汇聚到一个绕不开的硬约束上LLM 的上下文窗口是有限的。即使是 128K 甚至更大窗口的模型在高频长对话场景下也会被撑满。而且窗口大不代表效果好——研究表明当输入内容超过一定长度后LLM 对中间位置信息的理解质量会显著下降。面对这个约束工程上的策略可以分为三个层次来思考。第一层减少输入量。除了前面提到的对话历史压缩和摘要之外还有一些细节值得关注。比如工具调用的返回结果往往很长一个 API 返回了一大坨 JSON但真正有用的可能只是其中几个字段——可以在工具返回结果后做一次裁剪只保留和当前任务相关的字段。再比如 system prompt 里的工具定义如果 Agent 有 20 个工具每个工具的描述占 200 token光工具定义就占了 4000 token。可以根据当前对话的主题做动态工具筛选只把可能用到的工具定义送进去。第二层分层存储按需加载。不是所有信息都需要同时待在上下文窗口里。可以把信息分成必须常驻system prompt、当前任务状态、近期需要最近几轮对话、按需检索长期记忆、历史对话摘要三个层级。常驻信息始终在 prompt 里近期信息用滑动窗口管理按需信息存在外部存储里只在 LLM 需要时通过检索注入。第三层多 Agent 分工。当一个对话涉及多个子任务时比如用户先问了技术问题又让帮忙写一封邮件可以用一个主 Agent 做对话管理和意图路由具体的子任务分发给专门的子 Agent 处理。每个子 Agent 只接收和自己任务相关的上下文这样每个 Agent 的上下文压力都大大降低。这也是为什么很多生产级 Agent 框架如 AutoGen、CrewAI都采用了多 Agent 协作的架构。1.5 工具调用与对话流的编排一个实用的多轮对话 Agent 不太可能只靠纯文本生成来完成所有任务它需要调用工具——查数据库、调 API、执行代码、搜索网页。问题在于工具调用要自然地嵌入到多轮对话的流程中而不是打断对话的节奏。这里有几个设计决策需要做。首先是调用时机的判断Agent 需要判断当前这轮对话是直接回答就行还是需要先调工具获取信息再回答。这个判断通常交给 LLM 自己来做Function Calling 或 ReAct 范式但你需要在 prompt 里给出清晰的指引告诉它什么情况该调工具、什么情况不该。比如用户问你觉得这个方案怎么样——这是一个观点性问题不需要调工具但用户问这个接口昨天的 P99 延迟是多少——这需要查监控系统。其次是多轮工具调用的状态连续性。用户可能在第一轮说查一下订单 12345 的状态Agent 调了订单查询工具返回结果第二轮用户接着说那把它取消掉——这里 Agent 需要理解它指的是订单 12345并且需要把上一轮的查询结果作为上下文来构造取消请求的参数。工具调用的上下文不能丢。最后是调用失败的优雅处理。工具可能超时、返回错误、或者返回了空结果。Agent 不能把一个 500 错误码原样丢给用户它需要把失败转化为对话语言——抱歉订单系统暂时无法访问您可以稍后再试或者告诉我订单号我帮您记下来等系统恢复后处理。这种从系统错误到自然语言的转换需要在 prompt 和 error handling 层面都做好设计。1.6 对话的元控制机制前面五个策略讨论的都是 Agent怎么正常工作但生产环境里你还必须考虑出了问题怎么办。多轮对话 Agent 需要一套元控制机制来保证对话的健壮性。澄清与确认机制。当 Agent 对用户意图不确定时它应该主动发起追问而不是猜一个可能错误的答案就往下走。这听起来简单但实际需要解决一个微妙的平衡问太多会让用户烦你到底能不能帮我干活问太少会执行错误的动作。一个好的策略是设定一个置信度阈值——当 LLM 对意图理解的置信度高于阈值时直接执行低于阈值时才追问而阈值的高低取决于操作的风险级别查个信息可以大胆猜删个数据必须确认。话题切换与意图漂移处理。多轮对话中用户会频繁切换话题。Agent 需要能够识别话题切换正确地暂存或结束当前任务的状态切换到新话题。更复杂的场景是用户在两个话题之间来回切换刚才那个数据库的问题你说到哪了Agent 需要能恢复之前的对话上下文。安全防护与输出控制。多轮对话天然比单轮更容易被 prompt injection 攻击——攻击者可以在前几轮对话中逐步铺垫最后在某一轮触发恶意行为。所以每一轮的输入都需要过安全检测不能因为前几轮是正常的就放松警惕。此外对话中涉及的敏感操作删除数据、发送消息、支付等需要有二次确认机制和操作审计日志。1.7 思路小结回头来看设计一个多轮对话 Agent 的核心挑战可以归结为一句话在一个无状态的 LLM 之上构建一整套有状态的对话管理机制。对话历史管理解决记得住最近说了什么记忆系统解决记得住以前说了什么状态追踪解决知道现在聊到哪了上下文工程解决有限空间里装最重要的信息工具编排解决对话中需要干活时怎么干元控制解决出问题时怎么兜住。这六个设计点不是独立的模块它们紧密耦合、相互影响——比如记忆系统的设计直接影响上下文工程的策略状态追踪的精度决定了工具调用的准确性。一个好的设计方案需要把它们当做一个整体来统筹考虑。2. 参考回答设计一个多轮对话 Agent我会围绕六个核心设计点来展开。首先是对话历史管理LLM 本身无状态每轮都需要把历史送进去我通常用摘要滑动窗口的混合策略——远期对话做压缩摘要近期保留原文这样在 token 用量和信息保留之间取得平衡。第二是记忆系统我会设计三层工作记忆就是当前上下文短期记忆用 key-value 存结构化的 slot 信息比如当前任务的关键参数长期记忆用向量数据库做跨会话的语义检索新对话开始时把相关历史记忆注入 system prompt。第三是对话状态追踪对于任务型对话我推荐显式维护一个 JSON 格式的状态对象每轮只做增量更新这样 Agent 始终清楚当前任务进度和待收集的信息。第四是上下文窗口的工程优化从三个层面入手减量裁剪工具返回、动态筛选工具定义、分层存储常驻信息、近期信息、按需检索三级、多 Agent 分工主 Agent 路由子 Agent 各自处理子任务以降低单个 Agent 的上下文压力。第五是工具调用的编排关键在于让工具调用和对话流自然融合工程上要处理好跨轮的参数传递、条件式任务记录、以及调用失败的优雅降级。最后是元控制机制包括不确定时主动澄清而不是猜测执行、话题切换时正确暂存和恢复上下文、以及每轮都做安全检测防止渐进式的 prompt injection。实际项目中这六个点不是独立的模块它们之间紧密耦合需要作为一个整体来设计和调优。学习资源推荐如果你想更深入地学习大模型以下是一些非常有价值的学习资源这些资源将帮助你从不同角度学习大模型提升你的实践能力。一、全套AGI大模型学习路线AI大模型时代的学习之旅从基础到前沿掌握人工智能的核心技能​因篇幅有限仅展示部分资料需要点击文章最下方名片即可前往获取二、640套AI大模型报告合集这套包含640份报告的合集涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师还是对AI大模型感兴趣的爱好者这套报告合集都将为您提供宝贵的信息和启示​因篇幅有限仅展示部分资料需要点击文章最下方名片即可前往获取三、AI大模型经典PDF籍随着人工智能技术的飞速发展AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型如GPT-3、BERT、XLNet等以其强大的语言理解和生成能力正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。因篇幅有限仅展示部分资料需要点击文章最下方名片即可前往获取四、AI大模型商业化落地方案作为普通人入局大模型时代需要持续学习和实践不断提高自己的技能和认知水平同时也需要有责任感和伦理意识为人工智能的健康发展贡献力量。

更多文章