拆解 OpenHands(13)--- Memory
0x00 概要大模型正在从生成工具演化为具有长期交互能力的智能体这对“记忆能力”提出了更高的要求因为大模型的 “记忆能力”决定了它能走多远从单轮问答到多轮协作从通用助手到垂直 Agent核心都是 “能否记住关键信息、锚定核心目标”——只有记忆突破AI才能“持续陪伴”这是增加用户黏性的必然。而LLM有限的上下文窗口决定了我们不可能将所有历史信息塞入提示。因此设计一个高效的 「记忆检索」机制至关重要。这不仅仅是技术选型如使用向量数据库更是策略设计。如何将对话历史、过往的行动轨迹、成功的经验与失败的教训进行压缩、提炼并结构化存储。0x01 大模型 Agent 记忆系统我们看看大模型 Agent 记忆系统的核心设计、挑战与实现路径。1.1 核心定位与需求背景在以自然语言为接口、大模型为核心的 Software 3.0 时代AI Agent 作为上下文驱动的生成式应用需突破传统上下文窗口的固有局限。传统依赖上下文窗口维持对话状态与任务记忆的方式存在长度受限、组织无序、知识静态、成本高昂四大痛点 —— 既无法承载超长历史信息也难以高效检索与动态更新知识更会因长文本处理消耗大量计算资源。从系统架构视角看Agentic System 可类比为新型操作系统LLM 扮演 CPU 角色上下文窗口则如同容量有限的 RAM而上下文工程就是核心的 “内存管理器”—— 其核心职责并非简单填充数据而是通过智能调度算法动态决定上下文数据的加载与换出确保系统高效运行与结果精准性。构建持久化、结构化、可检索的 Agent 记忆系统成为解决上述问题、支撑复杂任务执行的关键它记录 Agent 的交互历程与知识积累是连接短期交互与长期智能的核心纽带。1.2 主要功能记忆系统作为 Agent 的 “数据飞轮”是实现真正智能的关键其核心功能可类比人类认知机制涵盖多维度能力全类型数据存储与管理记录 Agent 运行过程中的交互信息、任务数据、工具结果等为决策提供全面支撑保障任务执行的连贯性分层记忆架构构建短期记忆当前任务即时上下文如用户最新指令、工具返回结果、工作记忆当前任务执行步骤、待处理子任务、长期记忆历史经验、用户偏好、领域知识库的三级架构适配不同场景需求高效检索与更新支持按语义相似度、时间戳、任务相关性等多维度快速召回信息结合增量更新机制动态优化记忆内容核心能力支撑实现多轮对话状态维持、历史经验积累与复用、复杂推理链路延续以及基于用户偏好的个性化服务。1.3 核心挑战Agent 系统输出不及预期的根源在基础模型能力达标的前提下多归因于上下文机制失效 —— 要么缺失关键信息要么因数据过量导致退化进而引发幻觉。而记忆系统的构建面临三大核心难点记忆膨胀与冗余长期积累的记忆数据易出现冗余、冲突内容直接降低检索效率与决策准确性检索与召回偏差语义匹配不精准或任务相关性判断失误可能导致关键信息遗漏引发任务理解断层记忆更新与丢失更新策略不合理会导致过期数据占据资源或关键信息因优先级设置不当被误删影响任务连贯性。1.4 关键实现要点在实现中我们需要考虑很多要点比如哪些内容需要保存成长期记忆什么时候保存由谁来判断未来如何召回召回粒度是多少如何修剪、更新、合并因此记忆不是静态的数据库它是一个活的系统。研究者将其生命周期拆解为三个核心过程形成、演化、检索。记忆形成Formation混合存储架构短期记忆基于 Redis 等内存缓存实现低延迟访问长期记忆结合 Milvus、Chroma 等向量数据库支持语义检索与关系型数据库存储结构化数据兼顾检索效率与数据规范性记忆筛选通过语义摘要、知识蒸馏等方法来从大量记忆中筛选出最重要的片段通过记忆巩固Consolidation存入长期记忆。记忆增强机制集成 RAG 技术将内部记忆与外部知识库结合弥补记忆局限提升决策准确性与知识覆盖面。记忆演化Evolution记忆库如果不维护就会变得混乱、冲突、过时。智能记忆治理引入记忆演化算法自动整合、集成、剔除冗余、过期、冲突数据通过数据压缩降低存储压力支持记忆优先级设置确保关键信息不丢失记忆检索Retrieval检索与更新优化优化记忆检索机制结合语义相似度、语法检索、图检索等与任务相关性排序提升召回准确性设计增量更新策略基于任务重要性与数据时效性动态调整存储优先级前后处理可以主动发起记忆检索的动作而不是等待指令也可以Agent需要对检索结果进行重排序Re-ranking和过滤确保喂给大模型的上下文是纯净的。0x02 大模型 Agent 记忆系统分类体系我们来看看大模型 Agent 记忆系统分类体系的分层逻辑与功能维度解析2.1 经典分层框架基于存储时效的核心分类Agent 记忆系统的分层设计根植于 1968 年 Atkinson-Shiffrin 记忆模型的核心逻辑结合 AI 应用场景优化后形成 “感知 - 短期 - 长期” 三级时效分层体系各层级功能与特性明确区分感知记忆环境感知记忆最瞬时的记忆形态仅存储当下环境中的即时数据如视觉、声音信息无长期复用价值仅在当前瞬间有效需通过转化机制进入更高层级记忆才能长期保留。短期记忆工作记忆聚焦当前任务与会话的即时信息存储对应 Agent 的 Session 级别数据管理通常基于 ES 等技术实现将会话内容、实体信息、任务执行中间状态统一为标准化 Segment 格式核心作用是保障上下文连续性与即时响应能力。其概念与 1974 年 Baddeley Hitch 模型中的 “工作记忆” 高度契合仅保留任务处理所需的短期有效信息。长期记忆Agent 实现 “持续进化” 的核心支撑可长期甚至永久存储海量数据与经验与短期记忆形成功能互补 —— 短期记忆保障即时处理效率长期记忆提供背景知识与历史经验沉淀二者协同实现智能决策。2.2 功能导向分类长期记忆的细分维度从实际应用功能出发长期记忆可进一步划分为四大核心类型覆盖不同场景的记忆需求检索记忆通过 RAG 技术对接外部知识库核心价值是补充模型原生知识同时减少内部知识冲突提升信息获取的精准性与时效性。通用记忆通过预训练或后续微调沉淀的基础通用知识构成 Agent 的核心认知底座支撑各类基础任务的理解与执行。规则记忆以强化学习RL、提示词Prompt等方式固化的行为规范用于约束 Agent 输出格式如 JSON、CoT 链式推理与行为边界确保响应的一致性与合规性。个性化记忆通过会话内容摘要等方式提取的用户画像信息包括用户偏好、行为习惯、身份特征等支撑长期交互中的个性化服务。2.3 LangGraph 分类体系LangGraph 框架从记忆与会话的绑定关系出发将记忆简化为 “短期 - 长期” 二元结构其中长期记忆进一步细分为三类具有明确功能边界的子类型短期记忆与特定会话或任务线程强绑定即常见的 “历史对话记录”是 LLM 推理 API 的核心基础参数核心作用是维持单一会话内的交互连贯性。长期记忆不依赖特定会话可跨场景复用包括语义记忆Semantic Memory聚焦事实与概念的存储如交互中积累的特定信息、概念间的关联关系是实现个性化服务的关键如记住用户的偏好事实情景记忆Episodic Memory记录过往事件与行动历程区别于孤立事实更侧重 “经历” 的完整留存帮助 Agent 回忆任务执行的具体过程与场景程序性记忆Procedural Memory存储执行任务的规则、方法与流程由模型权重、智能体代码、提示词策略等共同构成相当于 Agent 的 “内在方法论”指导其如何完成具体任务类似人类骑自行车的技能记忆。2.4 综述分类论文 Memory in the Age of Agents: A Survey的分类非常值得我们学习。研究者将记忆的形式归纳为三大类。这三种形式并不是互斥的而是像人类大脑的不同区域一样协同工作。2.4.1 Token级记忆Token-level Memory显性的认知符号这是目前最主流、最可解释的记忆形式。信息以离散的、可读的文本Token或数据块的形式存储在模型外部。根据组织方式的复杂程度它从简单的线性记录进化到了复杂的立体结构。一维扁平记忆Flat Memory机制记忆像是一条长长的流水账List或一堆无序的便签。为了应对无限增长的长度通常采用递归摘要Recursive Summarization即旧的对话被压缩成摘要新的对话继续追加。典型应用早期的对话机器人日志、简单的经验池Experience Pool。局限性随着记忆量的增加“大海捞针”变得极其困难且容易丢失上下文细节Lost in the Middle。二维平面记忆Planar Memory机制记忆不再是孤立的点而是通过拓扑结构建立了“关联”。最典型的形式是图Graph*和*树Tree。核心优势知识图谱Knowledge Graph例如Agent不仅记住了“苹果”还通过图结构记住了“苹果”是“水果”的一种且“长在树上”。像Mem0的图记忆版本就能在对话中实时构建这种实体关系。因果链条在解决复杂问题时Agent可以顺着图的边Edge进行多跳推理Multi-hop Reasoning发现那些如果不建立联系就无法察觉的隐性答案。三维层级记忆Hierarchical Memory机制这是最接近人类高级认知的形式。记忆被组织成了不同的抽象层级构成了立体的金字塔结构。运作方式底层存储着原始的、细节丰富的交互记录Raw Traces。中层对事件的摘要Event Summaries例如“周二下午开了一次关于预算的会议”。顶层高阶的洞察和规律High-level Insights例如“用户非常在意成本控制偏好保守方案”。价值这种结构如HiAgent或GraphRAG允许 Agent在宏观规划时调用顶层记忆在执行具体操作时调用底层细节极大地平衡了检索效率与信息密度。2.4.2 参数化记忆Parametric Memory刻在神经元里的本能这种记忆形式更隐蔽。信息不再是存储在硬盘上的文字而是直接变成了模型神经网络中的权重参数。内部参数记忆通过全量微调Fine-tuning直接修改模型权重。这就像是生物进化将知识变成了“本能”或“肌肉记忆”。但缺点也很明显更新太慢、成本太高且容易发生灾难性遗忘Catastrophic Forgetting学了新的忘了旧的。外部参数记忆这是现在的热门方向。我们不改动大模型本身而是给它挂载一些小的参数模块如LoRA或Adapter。这就像是给 Agent插上了不同的“技能卡”或“记忆卡”例如K-Adapter既保留了模型的通用能力又注入了特定领域的知识且支持即插即用。2.4.3 潜在记忆Latent Memory机器的原生语言这种记忆形式对于人类来说是不可读的但对于机器来说效率极高。它直接存储模型推理过程中的数学表示。生成式Generate利用辅助模型将长文档压缩成特殊的Gist Tokens或向量。Agent只要看到这串编码就能“脑补”出之前的上下文而不需要重新阅读几千字的原文。复用式Reuse直接存储推理时的KV Cache。这是对“思考过程”的直接快照调用时零延迟能完美复原当时的思维状态但对显存占用较大。0x03 比对上下文、知识库、记忆它们在系统里的角色完全不同。先简略看看几个常见概念的区别。上下文解决的是「这一次」上下文窗口是把最近的对话和信息塞给模型让它在当前任务里保持连贯。窗口再大也有边界而且天然是会话级的。它适合一次性写方案、短期问答、单次任务冲刺。知识库解决的是它「不知道」你们公司RAG 的核心价值是补齐模型权重之外的企业知识、业务数据、文档。它更偏静态知识和结构化事实。它适合企业客服知识问答、产品文档检索、合规和规则解释。AI 记忆解决的是它「不懂你」AI 记忆系统要保存并调用用户过去与模型的交互历史为新会话设置上下文并持续改进用户画像让 Agent 能稳定输出个性化结果。3.1 Agent记忆 vs. LLM记忆LLM记忆通常指模型内部的技术优化例如如何优化 Transformer的KV Cache键值缓存以减少重复计算或者通过架构调整如 Mamba、RWKV让模型能处理更长的上下文窗口。这更像是计算机的“显存优化”关注的是单次推理的效率和容量。Agent记忆这是指一个智能体为了在环境中长期存在维护的一个持久的、动态演化的认知状态。它不仅是存储数据更包含了“我是谁”、“我经历过什么”、“用户的偏好是什么”这些核心认知是跨越多次交互周期的。3.2 Agent记忆 vs. RAG总体上RAG有助于Agent更准确的回答问题而Memory则有助于Agent表现的更加智能。Memory 更像一本随时可写、可删、可更新的“笔记本”或“硬盘”而 RAG 更像一套结构稳定、更新不那么频繁的“参考书体系”。具体来看。RAG通常是静态的知识外挂有Modular RAG、Graph RA、Agentic RAG三类。比如您有一个巨大的文档库比如公司手册模型去里面搜索答案。它解决的是“知识库”的问题通常用于单次任务知识库本身很少随交互而改变。RAG像是代理的研究图书管理员从静态、共享的知识库中检索事实信息。Agent记忆是动态生长的。随着 Agent与您的每一次交互它的记忆库都在发生变化——它会记下新的经验修正错误的认知甚至遗忘不再重要的信息。它强调的是交互历史和经验积累。内存管理像是私人助理携带记录每个用户交互细节的私人笔记本。在更多时候你需要同时使用它们如果缺少RAGAI可能很了解用户但可能回答“不够专业”。如果缺少MemoryAI有很专业的知识但却不够个性化。一个优秀的AI代理需要两者兼备——RAG提供世界知识内存提供用户理解。3.3 Agent记忆 vs. 上下文工程上下文工程是一种资源管理手段。因为模型的窗口有限我们通过各种技巧如Prompt压缩、重要性筛选把最重要的信息塞进去。在未来一个人的本质就是其所有上下文的总和。提示词工程是为了获得最佳推理结果而编写和组织 LLM 指令的方法上下文工程则是指在 LLM 推理过程中动态规划和维护最优的输入 token 集合集合包括任何可能进入上下文的信息。Agent记忆是认知建模。它决定了哪些信息值得被保留下来成为长期记忆并在未来几天、几个月甚至几年后被调用。上下文工程是“怎么塞进去”而 Agent记忆是“该塞什么”的一部分。0x04 OpenHands Memory功能解析4.1 三层架构在智能代理的运行机制中Memory模块扮演着 “记忆中心” 的角色专门负责处理和管理代理所需的上下文信息。上下文管理的挑战在于如何在有限的上下文窗口内提供最相关的信息。由于大语言模型的上下文窗口存在容量限制单纯把所有历史信息堆砌进去显然不现实。为此OpenHands 设计了一套三层记忆模型Condenser 专注于历史压缩ConversationMemory 专注于消息格式化而 View 作为中间数据结构连接两者这三个层共同构成了 OpenHands 中对话历史管理和消息处理的核心机制分别负责压缩、表示和转换三个关键环节。既让代理能够获取到决策所需的完整上下文又通过Condenser的机制有效避免了上下文窗口溢出保障了代理在长时间任务中逻辑的连贯性。巧妙应对 “上下文有限” 这一难题。memory/ │ │──── condenser/ # 历史压缩器 │ │ │ │──── condenser.py # 压缩器基类 │ │──── ... # 各种压缩策略 │ │──── conversation_memory.py # 对话内存管理 │──── view.py # 事件视图4.1.1 ViewView 作为中间数据结构连接压缩和转换两个阶段。View 的主要工作是对原始的Event Stream进行首次过滤和整理。在众多事件中有些对语言模型的决策没有直接帮助比如NullAction、AgentStateChangedObservation这类 “噪声” 事件View会将它们排除在外。同时它还会处理 “记忆压缩” 相关事件最终形成一个相对简洁的事件序列。作为内存管理系统里处理事件历史的关键部分view.py 能确保代理在处理长对话历史时不会超出上下文的限制。Condenser.condensed_history () 方法返回压缩后的历史记录可能包含View对象View 包含筛选后、处理后的事件列表经过压缩或过滤后的一组事件供后续处理使用Condensation对象表示需要执行的压缩操作如请求摘要4.1.2 ConversationMemoryConversationMemory 的核心任务是将View提供的事件列表 —— 这些列表更贴合机器的处理方式 —— 转换成语言模型更容易理解的对话格式也就是List[Message]。每个Message对象都包含 “角色”比如 “用户”“助手”“工具”和 “内容” 两部分这正好符合大多数语言模型 API 对输入格式的要求。ConversationMemory 专注于消息格式转换确保消息格式正确提高 LLM 理解效率接收来自 View 的事件列表使用 process_events () 方法将事件转换为适合 LLM 的消息格式处理包括系统消息、用户消息、工具调用和观察结果等在内的完整对话流程输出可以直接传递给 LLM 的消息列表相关配置CondenserPipelineConfig定义压缩器管道配置各种具体的压缩器配置如ConversationWindowCondenserConfig、BrowserOutputCondenserConfig、LLMSummarizingCondenserConfig 等这种设计实现了关注点分离Condenser 专注于历史压缩ConversationMemory 专注于消息格式化而 View 作为中间数据结构连接两者。4.1.3 CondenserCondenser是解决长上下文问题的关键环节。当View中的事件数量超出预设的阈值时Condenser就会启动工作。它会借助语言模型对一部分较早的历史事件进行总结生成一个简短的CondensationObservation事件。之后用这个总结来替代那些被移除的大量原始事件从而实现对上下文的 “有损压缩”。Condenser 专注于历史压缩算法实现可以通过 Condenser 减少传递给 LLM 的上下文大小降低计算成本也防止超出 LLM 的上下文窗口限制负责压缩对话历史控制传递给 LLM 的事件数量内部使用 CondenserPipeline 来应用多种压缩策略如窗口限制、浏览器输出压缩、LLM 摘要等4.2 工作流程实际工作流程如下Agent.step()决策层 ↓ Condenser.condensed_history()历史压缩器 ↓ View.from_events() 事件视图 ↓ 返回View(events[...]) ↓ Agent处理View.events ↓ ConversationMemory.process_events()对话内存 ↓ LLM处理压缩后的事件历史4.3 图结构OpenHands 原生核心记忆实现不使用图结构默认采用线性时序文本与键值对存储图结构仅为可选扩展方案非框架标配。只使用一维的扁平记忆存在一定问题缺乏对“中间推理状态”的维护往往只存结果不存过程。记忆系统应该支持任务分解并具备双系统验证机制能够存储“推理链条”而非仅仅是静态知识。上下文不连贯。现实里的对话是按话题组织的话题内部高度相关话题之间又是突变的而在向量库里我们只看到一堆碎片化的 chunk。检索拿回来的内容往往是同一话题被撕成很多段顺序也乱了。碎片化和矛盾共存。比如用户连续几次提到“我喜欢 A”你一直在追加新条目最后搜出来全是类似的句子某个时间点用户改口说“其实我现在不喜欢 A 了”如果没有 update 机制只能继续 add新旧偏好会长期并存最终哪个被模型用到完全取决于召回运气。向量库难以处理复杂事实冲突往往导致检索时出现相互矛盾的信息。所以必须具备冲突检测和记忆再巩固机制支持知识的动态进化。召回不可控。简单的向量搜索 topK 很难保证那些真正重要、应该进入长期记忆层的内容一定能被抽出来。因为OpenHands 具备灵活的插件扩展能力若需针对复杂关联信息如实体关系、任务依赖链路进行记忆管理可通过自定义记忆插件引入图结构如知识图谱但这并非框架原生内置的核心记忆实现方式仅为特定场景下的可选优化方案。0x03 关键组件我们对 View、ConversationMemory 和 Condenser 再进行详细分析。3.1 总体关系3.1.1 依赖关系ConversationMemory 依赖于 Condenser 的输出特别是 View 中的事件列表来构建消息历史CodeActAgent 同时使用这两个组件先通过 Condenser 压缩历史再通过 ConversationMemory 转换为消息在 step () 方法中首先调用 Condenser.condensed_history () 处理原始事件历史生成精简的事件列表或压缩操作根据返回结果View 或 Condensation则其中包含经过处理的事件列表系统决定是继续处理还是执行压缩操作使用 ConversationMemory 的 process_events () 方法将压缩后的事件转换为结构化的消息列表供 LLM 使用最终将这些消息传递给 LLM 进行推理3.1.2 具体的筛选和移除操作筛选操作ConversationWindowCondenser维护一个固定大小的对话窗口只保留最近的事件BrowserOutputCondenser限制浏览器输出观察的数量LLMSummarizingCondenser使用 LLM 对历史进行摘要保留重要信息移除操作通过 CondensationAction 执行具体的移除操作在 condenser.py 中定义了如何移除不需要的事件通过摘要机制隐式移除详细信息工作流程如下AgentController.step() → Condenser.condensed_history() 【筛选/压缩】 → ConversationMemory.process_events() 【处理筛选后的事件】 → 传递给 LLM 的消息列表 【只包含筛选后的信息】3.2 View3.2.1 功能事件视图管理提供事件历史的线性有序视图作为LLM的输入。历史压缩处理处理CondensationAction 和 CondensationRequestActionfrom_events函数可以压缩事件。摘要集成将压缩摘要插入到适当位置接口标准化提供类似列表的访问接口__len__、__iter__、__getitem__支持索引和切片操作。3.2.2 代码class View(BaseModel): Linearly ordered view of events. Produced by a condenser to indicate the included events are ready to process as LLM input. events: list[Event] unhandled_condensation_request: bool False def __len__(self) - int: return len(self.events) def __iter__(self): return iter(self.events) overload def __getitem__(self, key: slice) - list[Event]: ... overload def __getitem__(self, key: int) - Event: ... def __getitem__(self, key: int | slice) - Event | list[Event]: if isinstance(key, slice): start, stop, step key.indices(len(self)) return [self[i] for i in range(start, stop, step)] elif isinstance(key, int): return self.events[key] else: raise ValueError(fInvalid key type: {type(key)}) staticmethod def from_events(events: list[Event]) - View: Create a view from a list of events, respecting the semantics of any condensation events. # 识别需要遗忘的事件 forgotten_event_ids: set[int] set() # 处理压缩动作和需求 for event in events: if isinstance(event, CondensationAction): # 标记被压缩的事件ID forgotten_event_ids.update(event.forgotten) # Make sure we also forget the condensation action itself # 标记压缩动作也要被遗忘 forgotten_event_ids.add(event.id) if isinstance(event, CondensationRequestAction): # 标记压缩请求也要被遗忘 forgotten_event_ids.add(event.id) # 保留未被遗忘的事件 kept_events [event for event in events if event.id not in forgotten_event_ids] # If we have a summary, insert it at the specified offset. summary: str | None None summary_offset: int | None None # The relevant summary is always in the last condensation event (i.e., the most recent one). # 从后往前查找最新压缩动作中的摘要 for event in reversed(events): if isinstance(event, CondensationAction): if event.summary is not None and event.summary_offset is not None: summary event.summary summary_offset event.summary_offset break # 在指定位置插入摘要信息 if summary is not None and summary_offset is not None: kept_events.insert( summary_offset, AgentCondensationObservation(contentsummary) ) # Check for an unhandled condensation request -- these are events closer to the # end of the list than any condensation action. # 检查未处理的压缩请求 unhandled_condensation_request False for event in reversed(events): if isinstance(event, CondensationAction): break # 遇到压缩动作就停止 if isinstance(event, CondensationRequestAction): unhandled_condensation_request True break return View( eventskept_events, unhandled_condensation_requestunhandled_condensation_request, )3.2.3 交互View 与其他组件的关系如下ConversationMemory 使用View提供的事件列表Condenser生成View比如 condensed_history 会返回View对象。Agent基于View做决策Event System处理各种事件类型CodeActAgent 使用View 的例子如下def step(self, state: State) - Action: # Condense the events from the state. If we get a view well pass those # to the conversation manager for processing, but if we get a condensation # event well just return that instead of an action. The controller will # immediately ask the agent to step again with the new view. condensed_history: list[Event] [] # 获取压缩后的历史视图 match self.condenser.condensed_history(state): case View(eventsevents): # 经过压缩后的事件列表 condensed_history events case Condensation(actioncondensation_action): # 如果返回的是压缩动作则立即执行 return condensation_action # 使用压缩后的事件构建消息历史 initial_user_message self._get_initial_user_message(state.history) messages self._get_messages(condensed_history, initial_user_message)3.3 ConversationMemoryConversationMemory使用View处理事件短期历史对事件流进行过滤并计算注入上下文的消息。它筛选出对代理不感兴趣的某些事件如代理状态变更观察或空操作/空观察。当上下文窗口或用户设置的令牌限制被超过时历史开始压缩将消息块压缩成摘要。每个摘要随后被注入到上下文中取代它所总结的相应块。此处只使用process_events 来介绍在 process_events 方法中系统会遍历所有事件动作和观察结果调用相应处理方法生成消息检查待处理的工具调用是否已完成如果工具调用已完成即有对应的响应添加原始工具调用消息添加对应的工具响应消息最后过滤掉不匹配的工具调用和响应流程如下。代码如下。该类用于将事件历史处理为智能体可理解的连贯对话能够处理工具调用动作和观察结果确保函数调用模式下工具调用的正确处理支持视觉信息处理可包含图像 URL当视觉功能激活时具有消息长度限制和格式过滤功能确保对话符合 LLM 输入要求特色是维护工具调用与响应的关联关系保证对话上下文的完整性class ConversationMemory: 将事件历史处理为智能体的连贯对话。 def __init__(self, config: AgentConfig, prompt_manager: PromptManager): self.agent_config config # 智能体配置 self.prompt_manager prompt_manager # 提示管理器 def process_events( self, condensed_history: list[Event], initial_user_action: MessageAction, max_message_chars: int | None None, vision_is_active: bool False, ) - list[Message]: 将状态历史处理为LLM的消息列表。 确保在函数调用模式下正确处理工具调用动作。 参数: condensed_history: 要转换的压缩事件历史 max_message_chars: 包含在LLM提示中的事件内容的最大字符数。 较长的观察结果将被截断。 vision_is_active: LLM中是否激活视觉功能。如果为True将包含图像URL。 initial_user_action: 初始用户消息动作如果有。用于确保对话正确开始。 events condensed_history # 使用压缩后的事件历史 # 确保事件列表以SystemMessageAction开头然后是MessageAction(sourceuser) self._ensure_system_message(events) self._ensure_initial_user_message(events, initial_user_action) # 记录视觉浏览状态 logger.debug(fVisual browsing: {self.agent_config.enable_som_visual_browsing}) # 初始化空消息列表 messages [] # 处理常规事件 pending_tool_call_action_messages: dict[str, Message] {} # 待处理的工具调用消息 tool_call_id_to_message: dict[str, Message] {} # 工具调用ID与消息的映射 # 遍历View提供的事件列表 for i, event in enumerate(events): # 从事件创建常规消息 if isinstance(event, Action): messages_to_add self._process_action( actionevent, pending_tool_call_action_messagespending_tool_call_action_messages, vision_is_activevision_is_active, ) elif isinstance(event, Observation): messages_to_add self._process_observation( obsevent, tool_call_id_to_messagetool_call_id_to_message, max_message_charsmax_message_chars, vision_is_activevision_is_active, enable_som_visual_browsingself.agent_config.enable_som_visual_browsing, current_indexi, eventsevents, ) else: raise ValueError(f未知事件类型: {type(event)}) # 检查待处理的工具调用消息看它们是否已完成 _response_ids_to_remove [] for ( response_id, pending_message, ) in pending_tool_call_action_messages.items(): assert pending_message.tool_calls is not None, ( 当启用函数调用且消息被视为待处理工具调用时工具调用不应为None。 f待处理消息: {pending_message} ) # 检查所有工具调用是否都有对应的响应 if all( tool_call.id in tool_call_id_to_message for tool_call in pending_message.tool_calls ): # 如果完成: # -- 1. 添加**发起**工具调用的消息 messages_to_add.append(pending_message) # -- 2. 添加工具调用的**结果** for tool_call in pending_message.tool_calls: messages_to_add.append(tool_call_id_to_message[tool_call.id]) tool_call_id_to_message.pop(tool_call.id) _response_ids_to_remove.append(response_id) # 清理已处理的待处理工具消息 for response_id in _response_ids_to_remove: pending_tool_call_action_messages.pop(response_id) messages messages_to_add # 应用最终过滤确保上下文中的消息没有不匹配的工具调用和工具响应 messages list(ConversationMemory._filter_unmatched_tool_calls(messages)) # 应用最终格式化 messages self._apply_user_message_formatting(messages) return messages3.3.1 处理工具调用_process_action 方法会处理工具调用工具调用类型为CmdRunAction执行 bash 命令IPythonRunCellAction运行 IPython 代码FileEditAction编辑文件FileReadAction: 读取文件BrowseInteractiveAction: 浏览网页AgentDelegateAction: 委派任务给其他代理MCPAction: 与 MCP 服务器交互处理流程为对于支持函数调用的工具创建包含 tool_calls 的消息将待处理的工具调用消息存储在 pending_tool_call_action_messages 字典中对于不支持函数调用的情况创建包含动作描述的普通消息3.3.2 处理工具响应Observation在 _process_observation 方法中ConversationMemory 处理各种工具调用的响应工具响应类型为CmdOutputObservation: 命令执行输出IPythonRunCellObservation: IPython 执行结果FileEditObservation: 文件编辑结果FileReadObservation: 文件读取结果BrowserOutputObservation: 浏览器操作结果AgentDelegateObservation: 委派任务的返回结果MCPObservation: MCP 工具调用结果处理流程为创建包含工具响应内容的消息将响应与相应的工具调用关联通过 tool_call_id存储在 tool_call_id_to_message 字典中3.4 CondenserCondensor的功能如下记忆压缩器负责总结事件块。它首先总结早期的事件。它从最早的代理行动和用户消息之间的观察开始。然后对用户消息之间的后续事件块进行相同的操作。如果没有更多的代理事件它将总结用户消息这次是逐条总结如果它们足够大且不是紧跟在代理完成动作事件之后我们假设这些任务可能很重要。摘要从LLM中以代理总结动作检索并保存在状态中。