☰
AgentScope 2.0多轮对话记忆机制:in-token实证记忆原理与实践
2026/9/30 3:41:58 网站建设 项目流程

做Agent开发的朋友应该都有过这种体验:跟Agent聊了十几轮,它突然问你“你刚才说你叫什么名字来着”。问题不在模型笨,而在记忆没做好。我最近在梳理AgentScope 2.0的多轮对话机制时,发现它在记忆这块做了个很实在的取舍——用in-token实证记忆机制,把对话历史直接压实到模型输入token里,用最朴素的方式解决“Agent总忘事”的痛点。这篇文章会把AgentScope 2.0的多轮对话、in-token记忆机制、配置参数和我踩过的坑完整整理一遍,适合正在做Agent开发、或者准备把手头智能体升级到2.0的同学。

1. 多轮对话为什么是Agent开发的必修课

1.1 真实场景:Agent又“失忆”了

先说我碰到的典型场景。用户跟Agent说“我喜欢浅烘的豆子,酸度高一点没关系”,Agent回得挺好的。然后用户又连续问了几个不相关的问题,比如天气、咖啡因代谢、手冲器具推荐。等用户绕回来问“帮我推荐一款豆子”的时候,Agent居然推荐了一款深烘低酸的。

这就是典型的记忆缺失问题。绝大多数Agent框架在单轮问答上表现都不错,因为单轮不需要记忆,输入输出一一对应。但真实产品和用户之间一定是多轮交互,用户会在对话中不断透露偏好、修正需求、提供约束条件。这些信息如果不能在后续轮次中被Agent感知,整个对话体验就会断崖式下跌。

用户不会说“我两轮前告诉过你,你应该记得”,用户只会觉得这个智能体不聪明,转身就走。所以多轮对话不是“锦上添花”的加分项,而是Agent上生产线的必选项。

1.2 Agent记忆的三个层级

在深入AgentScope 2.0之前,先把记忆这件事拆开。Agent的记忆不是一个东西,它是分层的,我在实际项目里习惯分成三层来看。

第一层是瞬时会话上下文,也就是当前这轮对话里聊了什么。模型天然具备这种记忆能力,因为它就在输入上下文里。问题在于输入有长度限制,对话一长就被截断或压缩。

第二层是用户偏好与画像,这是跨会话的长期记忆。比如用户喜欢什么风格、习惯什么语气、有哪些明确喜好和禁忌。这层记忆才是“让Agent记住你”的核心,也是最难做的部分。

第三层是任务状态与工作记忆,指当前任务进行中产生的中间结果。比如用户和Agent一起写一篇方案,已经确认的五点需求、当前写到了哪个章节、哪些内容待补充,这些都属于任务级记忆。

这三个层级对记忆机制的要求完全不同。瞬时上下文要求低延迟、高保真;用户偏好要求跨会话持久化;任务状态要求结构化、可更新。如果只用一种方案去解决所有层级,必然会顾此失彼。

1.3 记忆问题背后的四个评价维度

我判断一个记忆方案好不好,基本看四个维度。

准确度是最基本的,记忆内容不能张冠李戴,用户明明说了A你记成B,这比没有记忆更糟。时效性要求信息能随时更新,用户说“我现在改喝深烘了”,之前的浅烘偏好就得作废。成本包括token成本、存储成本和检索延迟,很多花哨方案性能听着很美,算完成本就劝退了。可解释性则是说,要能说清楚Agent为什么记得这个、不记得那个,否则出问题都没法排查。

在AgentScope 2.0里看到in-token实证记忆机制时,我觉得它的核心思路就是在这四个维度上做平衡,不追求单点最优,而是追求整体可用。

2. 拆解AgentScope 2.0的in-token实证记忆机制

2.1 in-token记忆到底解决什么问题

in-token,字面意思就是“在token里”。这个机制的核心思想很简单:把需要记住的信息,转化成token序列,直接拼接在模型输入上下文里,让模型在生成下一轮回复时天然能看到这些历史信息。

很多朋友一听到“把历史塞进上下文”,会觉得这不算什么新东西。对,它不新,但这恰恰是它的优势。正因为不形成跳出的存储系统、不需要额外的检索服务,它才能做到零额外延迟、零存储成本,而且模型看到的就是最原始的真实信息,不需要经过向量化、检索排序这些可能引入噪声的环节。

举个例子。用户说“我叫小李”,如果你用传统记忆方案,可能要把这句话解析成结构化的“用户姓名:小李”再存进数据库。in-token方案就不绕这一道弯,直接把“用户提到他叫小李”这句话作为一条记忆消息灌入后续上下文,模型自然就能在回复中带出“好的小李”。

“实证记忆”这四个字,强调的是记忆来源必须是真实对话中出现的、可确证的信息,而不是模型根据对话上下文自己脑补出来的推测。AgentScope 2.0在设计上刻意避免了让模型自作主张地总结和改写记忆,因为模型总结的过程就是事实缺失的过程。

2.2 in-token记忆和外部记忆方案怎么选

我常被问到一个问题:既然向量数据库那么流行,为什么还要用in-token这种“笨”方案。我一般会拿一张对比表来说事。

维度in-token记忆向量数据库检索键值存储
实现复杂度低高中
额外检索延迟无有有
上下文token占用高低低
跨会话持久化弱强强
信息保真度高中中
可解释性强中强

向量数据库的核心优势在于大规模记忆场景,比如知识库检索,你不可能把几十万字都塞进上下文,必须靠检索挑出相关片段。但Agent的对话偏好记忆通常没那么大,一天可能就几百条有效信息,这时候引入向量检索服务带来的工程复杂度反而成了负担。

AgentScope 2.0把in-token作为默认的实证记忆方案,我认为是找准了落点:对话场景下的记忆量级不大,优先保证信息保真和开发效率,比追求理论上限更重要。

2.3 AgentScope 2.0为什么选这个切入角度

AgentScope 2.0这个版本整体的设计取向就是降低Agent开发门槛。它的定位不是让你去搞科研实验,而是让你更快速地做业务Agent。在这个前提下,记忆机制必须开箱即用,不能一上来就要部署ES、Milvus、Redis这些外部组件。

我实际对比过后,in-token方案还有一个隐藏优势:它天然适合调试。因为记忆内容就显示在请求日志里,模型有没有看到用户偏好,一眼就能查出来。向量检索方案一旦出问题,你要查embedding是否更新、检索是否召回、排序是否合理,链路长得多。

当然,AgentScope 2.0不是只能做in-token,它留了外部记忆的扩展接口,后面可以按业务需要接向量库。但从默认姿势来看,它希望你先把in-token跑通,把对话体验优化到位,再考虑更复杂的记忆架构。

3. AgentScope 2.0环境搭建与基础工程结构

3.1 安装与模型配置

先装环境。AgentScope 2.0是一个Python包,我是在Python 3.10环境下安装的,直接用pip即可。

pip install agentscope

装完之后的第一步是初始化模型配置。AgentScope本身不绑定模型厂商,OpenAI、DashScope、Ollama本地模型都支持。我这里以通义千问为例,因为国内访问更稳一些。

import agentscope agentscope.init( model_configs={ "config_name": "qwen-plus-config", # 配置别名,后面引用用 "model_type": "dashscope_chat", "model_name": "qwen-plus", "api_key": "your-api-key", } )

如果你用的是OpenAI,把model_type换成openai_chat,api_key换成OpenAI的key就可以了。建议把密钥放到环境变量里,别硬编码到代码中,避免提交代码时泄露。

3.2 认识三个核心对象:Msg、Agent、Memory

AgentScope 2.0用三个基础对象搭起整个多轮对话的骨架。

Msg是消息体,所有对话内容都由它承载。每条Msg有name、content、role三个核心字段,role表示是谁说的,user表示用户,assistant表示Agent自己。

from agentscope.message import Msg user_msg = Msg(name="user", content="我喜欢浅烘的咖啡豆", role="user")

Agent是智能体本身,它接收外部消息,把消息处理成模型输入,调用模型,再把模型的输出转成回复。在AgentScope 2.0中,AgentBase是所有Agent的基类,你可以直接实例化它,也可以继承后重写部分方法。

Memory是记忆容器。这是2.0版本重点加强的部分,它负责保存多轮对话的历史消息,并按策略在需要时取出并拼接成上下文。如果没有Memory,每次对话都是一个新的无状态请求。

我把这三者的关系概括为:Agent是大脑,Msg是血液,Memory是记忆皮层。血液流过大脑,记忆皮层负责把值得记住的血液样本保留下来供后续使用。

3.3 Harness与Agent的分工

AgentScope 2.0还引入了Harness的概念,我第一次接触时也困惑过。后来自己的理解是:Harness负责编排整个任务执行流程,Agent负责具体的推理和执行。

具体来说,一个复杂的Agent任务往往包含多步操作:拆解目标、调用工具、获取结果、根据结果继续推理。这些步骤之间的流转和控制逻辑,由Harness来编排。而Agent本身只关心自己这一步该做什么判断、该调什么工具。

记忆在两者之间怎么分配呢?AgentScope 2.0的设计是:Agent维护自己的对话记忆,Harness维护整个任务执行上下文。简单讲,Agent记得“你说过什么”,Harness记得“我们已经干到哪一步了”。这样区分的好处是职责清晰,Agent不关心任务进度,Harness不关心用户说过什么细节。

4. 实操:在AgentScope 2.0中开启多轮对话记忆

4.1 不带记忆的多轮对话为什么效果差

先看反面案例,更直观。构造一个不带Memory的Agent,用户连续发两轮消息。

from agentscope.agent import AgentBase from agentscope.message import Msg agent = AgentBase( name="assistant", sys_prompt="你是一个贴心的助手。", model_config_name="qwen-plus-config", ) first_msg = Msg(name="user", content="我姓李,叫我小李就行。", role="user") reply1 = agent.reply(first_msg) print(reply1) # 这里的agent没有记忆,下一轮根本看不到上一轮说了什么 second_msg = Msg(name="user", content="我姓什么来着?", role="user") reply2 = agent.reply(second_msg) print(reply2)

这个Agent在第二轮的上下文里只有你是一个贴心的助手和我姓什么来着?这两条信息,完全没有“小李”这个信息的踪迹,所以模型只能瞎猜或者直接承认不知道。这就是失忆的本质:不是模型记性差,是压根没给它看到历史的机会。

4.2 挂上Memory打通多轮对话

在AgentScope 2.0里,要让Agent记住多轮对话内容,解决方案出奇简单:给Agent挂一个Memory对象。

from agentscope.agent import AgentBase from agentscope.message import Msg from agentscope.memory import SizedMemory agent = AgentBase( name="assistant", sys_prompt="你是一个贴心的助手。", model_config_name="qwen-plus-config", memory=SizedMemory(max_retrieve_size=20), ) first_msg = Msg(name="user", content="我姓李,叫我小李就行。", role="user") reply1 = agent.reply(first_msg) print(reply1) second_msg = Msg(name="user", content="我姓什么来着?", role="user") reply2 = agent.reply(second_msg) print(reply2)

第二次运行时,Memory里已经存了第一轮的对话和回复,发送second_msg时,AgentScope会把历史消息取出来,和新的用户消息一起拼进模型输入。模型因此能看到“我姓李,叫我小李就行”,自然能答出“你姓李”。

这一步就是in-token记忆机制的核心运作流程:消息进来,记忆聚合,拼接prompt,模型阅读全部上下文,输出回答。

4.3 in-token记忆的关键参数

SizedMemory这个类名里的Sized就是带容量限制的意思,它有几个关键参数非常影响实际效果。

max_retrieve_size是每次取样送入上下文的记忆条数上限。我实测下来,这个值设太小,历史信息容易被挤出窗口;设太大,又会白白消耗token。一般场景20到30是比较稳定的区间。

recent_only参数决定只取最近的消息还是同时混入较早的消息。如果你的Agent需要考虑全局信息,比如用户早期的偏好声明,那recent_only=False更好;如果只关心最近几轮的连续性,True更省token。

还有一个容易被忽略的策略是消息按条取还是按轮次取。按轮次取可以保证上下文里不会出现只有“用户提问”没有“Agent回答”的单边历史,这对模型理解对话语义很关键。我在实际使用中发现,单边历史会导致模型回复语气特别怪。

这些参数的本质,都是在token成本和记忆完整性之间做权衡。你付出的token越少,能保留的信息就越少,这是一个物理规律,没有哪个框架能凭空跳过。

5. 实战:让Agent在十轮对话里记住你的偏好

5.1 场景设计

光讲参数太抽象,我做了个完整的实战项目来验证记忆效果。场景是一个咖啡推荐助手,任务要求:Agent需要在多轮对话中记住用户的咖啡口味偏好,并在后面的推荐中体现出来。

我给Agent设定了一个系统提示词,明确要求它主动记住用户口味偏好,在推荐时优先参考用户此前提供的信息。

关键点在于,我的测试流程里有意识地插入干扰轮次。用户先告诉Agent喜欢浅烘,接着连续聊了三轮不相关的话题,最后突然要求推荐。如果记忆机制只保留最近几轮,那么浅烘这个信息大概率会被挤掉。这个场景能真实检验记忆的稳定性。

5.2 完整实现

import agentscope from agentscope.agent import AgentBase from agentscope.message import Msg from agentscope.memory import SizedMemory # 1. 初始化模型 agentscope.init( model_configs={ "config_name": "qwen-plus-config", "model_type": "dashscope_chat", "model_name": "qwen-plus", "api_key": "your-api-key", } ) # 2. 构造带记忆的Agent agent = AgentBase( name="coffee_assistant", sys_prompt=( "你是一位专业的咖啡推荐助手。用户会告诉你口味偏好," "请在后续推荐中始终参考用户偏好,如果用户说过自己的口味," "不要重复询问。" ), model_config_name="qwen-plus-config", memory=SizedMemory(max_retrieve_size=30, recent_only=False), ) # 3. 模拟多轮对话 messages_to_send = [ "我喜欢浅烘的豆子,果酸味明显一些。", "最近天气好热,有什么适合夏天的冲煮方式吗?", "我用的滤杯是V60,研磨度偏细。", "手冲的水温一般控制在多少度合适?", "帮我推荐一款适合我的豆子吧。", ] for content in messages_to_send: user_msg = Msg(name="user", content=content, role="user") reply = agent.reply(user_msg) print(f"用户: {content}") print(f"Agent: {reply.content}") print("---")

这里把recent_only=False是因为用户偏好信息可能出现在对话很早期,不能因为后续聊了别的话题就被排除在上下文之外。

5.3 效果验证与原理分析

我跑完这个序列之后,最后一轮Agent的推荐回复里明确出现了“浅烘”“果酸”等关键词,并且在推荐时没有再反问用户“你平时喜欢什么口味的豆子”。这说明早期轮次里的用户偏好信息成功穿越了中间的干扰轮,被保留到了最后一轮。

从原理上看,这一步由两个环节共同完成。第一,Memory对象在每轮对话后自动把新消息追加到历史中;第二,当第五轮用户消息进来时,AgentScope 2.0从Memory中检索出符合策略的历史消息,和最新消息一起拼成本轮模型的完整输入。模型读到“我喜欢浅烘的豆子”和“帮我推荐一款豆子”,自然能给出贴合偏好的答案。

值得提醒的是,这里的记忆效果不是模型自己“长记性”,而是框架把记忆信息硬塞给了模型。如果哪天Agent突然不记得了,你要查的不是模型,而是记忆链路有没有断。

6. 多轮对话记忆的常见问题与排查技巧

6.1 记忆不生效的常见原因

我在实际操作中遇到最多的问题是“挂上Memory了,但Agent还是忘事”。第一个要查的是Memory有没有被加载到Agent上,这个最简单,检查Agent构造函数的memory参数是否传了对象,而不是传了None。

第二个要查的是max_retrieve_size是不是设太小了。如果设成5,而对话总轮数超过5轮,早期偏好被挤掉是必然结果。我之前排查过一个工作流场景,用户在第6轮重复提问,Agent完全答不上来,最后发现就是这个问题。

第三个比较容易忽视:如果你用的是自定义Agent而非AgentBase,需要确认自定义逻辑里有没有正确调用reply流程。有些自定义Agent会重写消息处理逻辑,把已封装的记忆拼接步骤覆盖了,导致记忆链路中断。

6.2 token膨胀与上下文溢出

多轮对话跑久了,最典型的问题是上下文越来越长,模型提示词溢出或者成本飙升。我跑过一个20轮的对话,全部历史塞进去,token直接爆掉。

这个问题的解决思路不是“都别存”,而是“有策略地存”。SizedMemory的max_retrieve_size就是第一道防线,限制送入上下文的条数。更进一步的方案是做摘要压缩:每过一定轮次,让模型把前面N轮的对话总结成一段摘要,用一段摘要代替原始对话,记忆从“全文录像”变成“精华笔记”。

AgentScope 2.0里可以用摘要方式手动实现这个逻辑,在Memory里增加一条系统消息,存储模型生成的摘要内容。但是要注意,摘要本身是模型生成的,存在事实偏差风险,所以我建议摘要只用于压缩那些不影响核心判断的寒暄内容,用户明确表达的偏好信息要单独保留原文。

6.3 记忆污染与事实冲突

另一个容易踩的坑是记忆污染。用户在早期说“我喜欢浅烘”,后来在某个场景下又说“最近改喝深烘了”,这两条信息如果同时存在于上下文中,模型可能产生混乱,既推荐浅烘又推荐深烘。

排查思路是检查记忆的去重和更新策略。理想情况下,Agent需要在新的偏好声明出现时识别出这是对旧偏好的修正,并把旧信息标记为失效。AgentScope 2.0的Memory本身不负责语义上的冲突检测,这个逻辑要放在Agent的推理层面。

实操上,我建议在系统提示词里加上一句:当用户提供的信息与之前提供的信息冲突时,以用户最新说法为准,并主动向用户确认是否更新偏好。这样模型在多轮对话中遇到冲突信息时有明确的处理原则,比让模型自由发挥稳定得多。

6.4 多轮对话记忆故障速查表

现象可能原因解决方案
Agent完全不记得早前信息memory参数未传入或未启用检查Agent构造函数的memory参数
记得最近几轮但不记得更早的偏好max_retrieve_size太小调大max_retrieve_size或将recent_only设为False
上下文经常长度溢出历史消息累积过多开启摘要压缩或限制检索条数
Agent回复中偏好前后矛盾新旧信息冲突无人裁决在系统提示词中加入更新偏好优先级的指令
自定义Agent记忆失效重写reply时覆盖了内置记忆逻辑在自定义逻辑中显式调用记忆存取方法
偶尔答好偶尔答错模型对长上下文的注意力分散将关键偏好信息在上下文中重复强调或置顶

7. 我的几个实操心得与后续扩展方向

跑完整个AgentScope 2.0多轮对话项目后,我最大的感受是:in-token实证记忆机制不是最花哨的方案,但它确实解决了90%的对话场景需求。它把“记忆”这个听起来高大上的概念,拉回到了“把该说的信息告诉模型”这个朴素的行动上。

我自己在项目中的偏好是先用in-token方案把对话流程跑通,验证交互体验,然后再考虑要不要接外部存储。因为多轮对话的体验问题,有相当比例并不是“记忆容量不够”,而是“记忆没有正确取用”。in-token方案先把取用链路做完整了,后面就算值库接入了,也只需要在取用端替换数据源,整体架构不用推翻重来。

这个方向后续还有一个很值得玩的扩展:把用户偏好提取成结构化的画像快照,隔一段时间让模型把对话历史中新增的偏好合并进去,再压缩旧历史。这相当于给in-token记忆加了一个“归档”机制,长期对话体验会更稳。我在自己的项目里已经尝试了基础版本,效果不错,等跑出一批数据再单独整理一篇出来。如果你也在折腾Agent记忆,欢迎交流你遇到的场景,一起把坑填平。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询