☰
PaddleNLP Pipelines Agent 记忆模块详解:ConversationMemory 与 NoMemory 的实现与应用
2026/9/27 0:59:36 网站建设 项目流程
  • 人工智能
  • 大模型
  • 预训练
  • 微调
  • LoRA
  • RLHF
  • 强化学习
  • 分布式训练

【免费下载链接】PaddleNLP

Easy-to-use and powerful LLM and SLM library with awesome model zoo.

项目地址:https://gitcode.com/gh_mirrors/pa/PaddleNLP
点击查看免费下载

导读

PaddleNLP(本仓库中位于 slm/pipelines 目录)为 LLM Agent 提供了一个轻量级的记忆(Memory)机制,用于在多轮推理与工具调用之间保存和回放对话上下文。本文围绕该模块的 API 文档主题,深入剖析抽象基类Memory、对话记忆实现ConversationMemory与空实现NoMemory的源码结构、行为细节,以及它们如何被Agent主循环调用,帮助读者掌握在 Agent 应用中接入、配置乃至自定义记忆组件的完整方法。

一、模块定位:Agent 记忆抽象接口

记忆模块位于仓库的 slm/pipelines/pipelines/agents/memory 目录下,由四个文件构成:

  • base.py:定义抽象基类Memory;
  • conversation_memory.py:定义ConversationMemory,负责存储对话历史;
  • no_memory.py:定义NoMemory,一个不保存任何数据的空实现;
  • __init__.py:统一导出上述类,供外部以from pipelines.agents.memory import Memory, ConversationMemory, NoMemory方式导入。

在 Agent 中,记忆的核心作用是跨推理步骤保存"输入—输出—观察(observation)"的上下文,使得多跳(Multi-hop)问答等需要分步推理的场景能够持续引用此前各轮的中间结果。这一点在 Agent 基类的类注释 中有明确体现:Agent 会迭代地"生成思考 → 选择工具 → 生成输入 → 观察工具输出",而记忆正是连接这些迭代的信息载体。

抽象基类 Memory 的三个契约方法

从 base.py 源码可以看到,Memory是一个继承abc.ABC的抽象类,定义了三个必须由子类实现的抽象方法:

方法签名职责
loadload(keys: Optional[List[str]] = None, **kwargs) -> Any从记忆中加载本次模型运行所需的上下文
savesave(data: Dict[str, Any]) -> None将本次模型运行的上下文写入记忆
clearclear() -> None清空记忆内容

load的可选参数keys用于指定需要加载的数据项;save接收一个字典。任何自定义记忆类只需实现这三个方法,即可被 Agent 无缝使用——这正是该模块的扩展点所在。

二、ConversationMemory:基于 OrderedDict 的对话历史存储

ConversationMemory 是默认的对话记忆实现,其核心数据结构是一个 Python 列表,每个元素为一个保留插入顺序的collections.OrderedDict,形如{"Human": ..., "AI": ...}。

构造参数:input_key 与 output_key

def __init__(self, input_key: str = "input", output_key: str = "output"):

构造时接收两个字符串参数:

  • input_key:用于从save(data)传入的字典中取出用户输入的键,默认值为"input";
  • output_key:用于取出模型输出的键,默认值为"output"。

这两个键名需要与 Agent 调用save时传入的数据结构保持一致(详见下文 Agent 集成部分)。

save:把一次对话快照写入列表

def save(self, data: Dict[str, Any]) -> None: chat_snippet = collections.OrderedDict() chat_snippet["Human"] = data[self.input_key] chat_snippet["AI"] = data[self.output_key] self.list.append(chat_snippet)

save从data字典中按下述方式提取内容:data[self.input_key]作为"Human"(用户消息),data[self.output_key]作为"AI"(模型回复),组装成一个OrderedDict追加到self.list尾部。因此记忆按时间顺序累积,每次调用save就新增一轮对话。

load:格式化为 Human/AI 转写文本

def load(self, keys: Optional[List[str]] = None, **kwargs) -> str: chat_transcript = "" window_size = kwargs.get("window_size", None) if window_size is not None: chat_list = self.list[-window_size:] else: chat_list = self.list for chat_snippet in chat_list: chat_transcript += f"Human: {chat_snippet['Human']}\n" chat_transcript += f"AI: {chat_snippet['AI']}\n" return chat_transcript

load将存储的对话历史渲染为一段可直接拼入 Prompt 的文本,其关键特性如下:

  • 输出格式:每轮对话按Human: ...与AI: ...两行输出,并以换行符分隔;
  • window_size 窗口控制:通过kwargs传入可选参数window_size(整数),指定只加载最近 N 轮对话。实现使用self.list[-window_size:]截取列表尾部,从而实现"滑动窗口"效果,有效控制送入 LLM 的上下文长度;
  • 返回空串语义:记忆为空时返回"",与NoMemory.load()的返回值一致,方便上层统一处理。

clear:清空全部历史

def clear(self) -> None: self.list = []

直接重置内部列表,达到清空记忆的目的。

三、NoMemory:无记忆模式的空实现

NoMemory 是Memory的一个"空操作"实现,用于关闭记忆功能,避免每次迭代都产生额外状态:

  • load()直接返回空字符串"";
  • save(data)为空操作(pass),不存储任何数据;
  • clear()为空操作。

从源码注释看,其设计意图非常明确——"不存储任何数据的记忆类"。值得注意的是,Agent 的构造函数中self.memory = memory or NoMemory()表明:当用户未显式传入记忆实例时,Agent 默认启用 NoMemory,即默认行为是不保留上下文。

四、与 Agent 主循环的集成方式

记忆并非孤立组件,其实际消费方是 slm/pipelines/pipelines/agents/base.py 中的Agent类。理解集成点有助于读者掌握"何时写入、写入什么"。

构造注入与默认值

在Agent.__init__中(约第 243~272 行),memory是一个可选参数,类型标注为Optional[Memory]:

memory: Optional[Memory] = None, ... self.memory = memory or NoMemory()

即传入ConversationMemory()实例即可开启记忆;不传则自动退化为NoMemory。

迭代中的自动保存

在Agent._step方法中(约第 401~415 行),每次完成一轮"规划 + 工具调用"后都会触发记忆写入:

observation = self.tm.run_tool(next_step.prompt_node_response, params) if not next_step.is_last() else None memory_data = self.prepare_data_for_memory(input=query, output=prompt_node_response, observation=observation) self.memory.save(data=memory_data)

这里保存的数据包含三个字段:input(当前查询)、output(LLM 的规划回复)与observation(工具观察结果),与ConversationMemory默认的input_key/output_key一一对应。

prepare_data_for_memory 的归一化逻辑

Agent.prepare_data_for_memory(约第 440~446 行)负责把原始参数转换为可存入记忆的字典,其实现值得注意:

return { k: v if isinstance(v, str) else next(iter(v)) for k, v in kwargs.items() if isinstance(v, (str, Iterable)) }

它会过滤掉既非字符串也非可迭代对象的字段;对可迭代但非字符串的值取第一个元素(next(iter(v)))。同时,该方法是可覆写的扩展点——文档注释明确指出可重写它来定制保存到记忆的数据格式,例如追加额外的元信息或调整字段命名。

自定义记忆的两种接入方式

结合上述源码结构,读者可按需选择:

  1. 复用现成实现:构造ConversationMemory(input_key=..., output_key=...)后传入 Agent;
  2. 继承Memory:实现load/save/clear三个方法(例如将对话写入外部存储、数据库或缓存),再注入 Agent;如需改变写入内容,还可同时覆写prepare_data_for_memory。

五、行为验证:测试用例逐条对照

仓库中 slm/pipelines/tests/agents/test_memory.py 用三个单元测试完整锁定了上述行为,可作为理解实现语义的权威参照:

  • test_no_memory:验证NoMemory().load() == "",且save/clear可安全调用而不报错;
  • test_conversation_memory:验证依次save两轮对话({"input": "Hello", "output": "Hi there"}与{"input": "How are you?", "output": "I'm doing well, thanks."})后,load()输出逐行拼接的Human: .../AI: ...转写;load(window_size=1)只返回最近一轮;clear()后load()重新变为空串;
  • test_conversation_memory_window_size:单独验证窗口截断逻辑,包括清空后再以window_size=1加载同样返回空串的边界情况。

这些断言与前述源码实现完全吻合,读者亦可在本地运行python -m unittest tests/agents/test_memory.py(在 slm/pipelines 目录下)复现验证。

六、小结与扩展指引

Memory模块以极简的三方法抽象(load/save/clear)提供了足够的表达力:ConversationMemory内置有序列表存储、Human/AI 格式化输出与window_size窗口控制,覆盖了 Agent 多轮对话的基本需求;NoMemory则提供了零成本的无状态默认路径。二者在Agent的_step循环中自动联动,并通过prepare_data_for_memory保留了数据定制入口。

进一步深入可查阅:

  • 抽象基类定义:理解方法契约与可扩展性;
  • 对话记忆实现:窗口控制与格式化细节;
  • 空记忆实现:默认无状态语义;
  • Agent 集成源码:构造注入与迭代保存调用链;
  • 单元测试:全部行为的可执行验证。
  • 人工智能
  • 大模型
  • 预训练
  • 微调
  • LoRA
  • RLHF
  • 强化学习
  • 分布式训练

【免费下载链接】PaddleNLP

Easy-to-use and powerful LLM and SLM library with awesome model zoo.

项目地址:https://gitcode.com/gh_mirrors/pa/PaddleNLP
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询