7.2 上下文记忆的优化与重整合(智能体工程)

《智能体工程驱动AI Agent开发》1~6章试读-持续更新-CSDN博客

在前面章节中,我们已实现用户核心特质的自动化提取、持久化存储与智能体基础自进化能力,完整搭建起"记忆提取-动态更新-持久化存储-上下文注入"的基础读写闭环。但这套基础方案在长周期、多轮次的对话运行中,逐渐暴露出一系列核心短板:缺乏记忆冲突校验机制导致新旧信息异常覆盖、无标准化去重逻辑造成内容冗余堆积、全量记忆强制注入引发上下文Token无效膨胀、历史任务执行经验无法被有效复用等,直接制约了智能体长周期运行的稳定性与自进化的精准度。

针对上述痛点,本节将对上下文记忆体系开展全维度优化升级,围绕冲突校验去重、情景记忆拓展、按需语义检索、时间衰减机制、定期整合归档五大核心方向,系统性地完善记忆全生命周期的读写与管理逻辑,打造一套更高效、更精简、深度适配智能体长周期自进化需求的上下文记忆体系。

整体而言,当前项目已完成记忆体系从0到1的闭环搭建,实现了"extract→update→save→

load→to_prompt"的全流程贯通;而后续的优化升级将遵循阶梯式的落地路径:首要核心是筑牢记忆质量根基,解决"写得好"的核心问题,落地记忆重要性过滤、去重合并与冲突校验机制;其次是提升记忆应用效率,攻克"读得准"的关键难题,实现基于语义匹配的按需检索与精准注入;在此基础上,逐步完成情景记忆体系搭建、时间衰减与定期整合的长效管理机制落地,推动记忆体系完成从"基础可用"到"高效好用"再到"支撑自主进化"的全面跨越。

7.2.1 上下文工程的优化:记忆去重、过滤与冲突

记忆写入是上下文记忆体系的第一道关口,也是决定记忆质量的核心环节,属于我们优先落地的优化项。原有方案中,update函数采用无差别全量写入模式,既未对提取内容做价值筛选,也未建立重复内容合并与冲突信息校验机制。这样的话,不仅会让"你好""hi"这类无价值闲聊内容占用记忆存储空间,还会出现固定特质重复覆盖、新旧需求冲突无管控的问题,从源头造成记忆库冗余、信息失准。

1. 记忆重要性过滤机制

原有实现中,update函数会将子智能体提取到的所有字段无差别写入MD记忆文件,未设置任何信息筛选规则,即使用户"你好""hi"这类无实质业务信息的闲聊内容,也会被提取并写入记忆库,极易造成记忆库无效膨胀,导致用户核心特质被冗余信息淹没。

为此,我们参考行业成熟的记忆管理方案,在记忆写入前新增重要性打分环节:由记忆提取子智能体在完成用户特质提取的同时,为每一个提取项输出0~1区间的重要性评分,结合文旅导览业务场景设定0.6的入库阈值,仅保留超过阈值的高价值信息入库,从源头过滤无效内容,保证记忆库的信息密度与核心信息的辨识度。

2. 记忆去重与冲突校验机制

原有直接覆盖的更新逻辑存在两大核心缺陷:一是对于连续多轮对话重复提取的固定特质(如多轮对话均提取到"用户名叫xiaohua"),会出现无意义的重复覆盖操作;二是当用户新旧信息出现矛盾时(例如,先对北京豆汁有疑问,后续更新为愿意更多地尝试北京豆汁),没有配套的冲突检测与标准化处理机制,极易造成记忆信息失准。

对此,我们在save持久化操作前,新增合并去重与冲突校验双逻辑:去重逻辑会对同一分类下的内容做语义相似度比对,已存在的固定特质不再重复写入,仅更新其最后活跃时间;冲突校验逻辑遵循"最新表述优先、历史版本可回溯"的原则,确认用户最新决策后完成内容更新,同时将旧内容归档至历史版本库,既保证记忆的时效性,也实现更新过程的可控可追溯。

下面是修改后的通用记忆更新和管理器代码:

class MemoryExtractor:

"""记忆提取器 --- 根据 MD 标题结构,从最近 N 日对话中批量提取。"""

_SKIP_SECTIONS = frozenset({"说明"})

def init(self, model_name: str = "glm", md_path: str = "",

conversation_log_path: str = ""):

self.model_name = model_name

self.md_path = md_path

self.sections: Liststr = self._parse_sections(md_path)

self.conversation_log_path = conversation_log_path

if "qwen" in model_name:

from llm_moudle import qwen_moudle

self.client = qwen_moudle.QwenClient(model_name=model_name)

else:

from llm_moudle import glm_moudle

self.client = glm_moudle.GLMClient(model_name=model_name)

def _parse_sections(self, md_path: str) -> Liststr:

raw = _load_md(md_path)

if not raw:

return \[\]

return [

s for s in re.findall(r"^#{2,3}\s+(.+)$", raw, re.MULTILINE)

if s.strip() not in self._SKIP_SECTIONS

]

def _get_recent_conversations(self, days: int = 5) -> str:

if not self.conversation_log_path:

return ""

abs_path = os.path.abspath(self.conversation_log_path)

if not os.path.exists(abs_path):

abs_path = os.path.join(_PROJECT_ROOT, self.conversation_log_path)

if not os.path.exists(abs_path):

return ""

try:

with open(abs_path, "r", encoding="utf-8") as f:

data = json.load(f)

except (json.JSONDecodeError, ValueError):

return ""

today = datetime.now()

date_keys = (today - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(days)

parts = \[\]

for date_key in date_keys:

for conv in data.get(date_key, \[\]):

parts.append("" + date_key + " 用户: " + conv.get("query", "") +

"\n助手: " + conv.get("response", ""))

return "\n\n".join(parts)

async def extract(self, days: int = 5) -> Dictstr, Any:

text = self._get_recent_conversations(days)

if not text.strip() or not self.sections:

return {}

prompt = (

"从以下近" + str(days) + "日对话记录中提取信息。只提取这些字段:" + ", ".join(self.sections) + "\n"

"规则:只提取明确提到的,不推测。没有新发现就输出空 JSON: {}\n"

"输出纯 JSON:{\"字段名\": \"值或列表\", ...}"

)

try:

response = await self.client.chat(messages=[

{"role": "system", "content": prompt},

{"role": "user", "content": text},

])

raw = "".join(b.get("text", "") for b in response.get("content", \[\])

if b.get("type") == "text")

return _extract_json(raw)

except Exception as e:

logger.warning(f"MemoryExtractor 提取失败: {e}")

return {}

在这里,MemoryExtractor是记忆提取子智能体,它会先读取指定的Markdown文件,提取其中的 ## 和 ### 标题作为待提取字段,同时跳过"说明"这类非数据段落。初始化时还会根据模型名称自动创建对应的Qwen或GLM客户端。它能从对话日志文件中读取最近N天的对话记录,拼接成文本后,通过大模型按指定字段提取信息,最终返回纯JSON格式的结果。

UseMemory是通用记忆管理器,支持任意Markdown文件。初始化时,会加载并解析Markdown内容,过滤掉注释、分隔线和空值标记,将有效内容存入字典。它能把有内容的段落整理成规范的prompt供大模型使用,update方法会调用提取器获取新信息,通过计算新旧内容的相似度去重,超过 0.8 阈值则跳过,否则更新内容并保存回Markdown文件,实现记忆的动态管理。

7.2.2 上下文工程的优化:情景记忆(重点)

在上一节中,我们通过重要性过滤、去重合并与冲突校验,完成了用户事实类记忆写入环节的全流程优化,解决了记忆库冗余、信息失准的核心问题,实现了对"用户是什么、要什么"的精准持久化。而情景记忆作为上下文记忆体系的核心组成部分,解决的是智能体"做过什么、做成了什么、学到了什么"的经验沉淀问题,是让智能体从"被动执行工具调用的执行器",升级为"基于历史经验自主优化的决策体"的关键,也是行业通用四层记忆架构,当前方案缺失核心能力模块。

在我们当前的实现中,ConversationLogger仅完成了对话表层内容的存储,只记录了每轮对话的用户query与助手response,完全缺失了智能体执行任务的全流程行为数据与结果反馈。这种只记"对话内容"、不记"执行过程"的存储模式,让智能体的每一次任务执行都处于"从零开始"的状态---即便此前已经完成过数十次同类的行程规划、景点搜索、酒店推荐任务,也无法复用历史成功的执行策略,更无法规避此前出现过的工具调用失败、参数格式错误、结果不符合用户需求等问题。

具体来看,当前方案的核心缺失集中在四大维度:

  • 一是任务属性维度,未记录任务的核心类型(如搜索查询、行程规划、酒店推荐、景点攻略等),无法实现同类任务的经验匹配。
  • 二是执行结果维度,未记录任务的最终执行状态(成功/失败/部分完成),无法区分有效经验与失败教训。
  • 三是执行细节维度,未记录任务执行过程中的工具调用次数、接口响应耗时、工具入参配置等核心执行数据,无法还原历史任务的完整执行链路。
  • 四是质量反馈维度,未记录任务完成后的用户反馈评分,无法对历史经验的有效性做分级筛选。

这些全流程的情景记忆数据,是智能体实现自主学习与长周期自进化的核心燃料。当情景记忆库完成持续积累后,智能体在接收到新的用户需求时,无须再依赖固定的系统prompt做零样本推理,而是可以先从情景记忆库中检索同类任务的历史成功案例,将历史任务拆解逻辑、工具调用流程、参数配置方式作为few-shot学习示例注入上下文,直接复用已验证的有效策略;同时,历史执行失败的案例也会作为负面参考,帮助智能体提前规避同类错误,大幅提升任务执行的效率与准确率。

在我们的文旅导览场景中,这一能力的价值尤为突出:当用户发起"北京 4 天 3 晚小众行程规划"需求时,智能体可直接调取历史同类高评分任务的执行经验,复用"先查目的地天气→再筛选人少景美的小众景点→匹配对应预算的周边酒店"的成熟任务拆解逻辑,以及阿里搜索工具的入参配置方式,无须重新摸索任务执行路径,同时规避历史出现过的"景点信息重复""酒店预算不匹配"等问题,实现从"会做任务"到"擅长做任务"的能力升级。

针对当前方案的核心缺失,我们从存储结构、采集机制、复用逻辑三个核心环节,完成情景记忆体系的全量优化落地,实现与现有代码体系的无缝兼容。

1. 情景记忆存储结构重构

我们在原有conversation_log.json的日期分组存储格式基础上,新增情景记忆核心字段,重构标准化的存储结构,既完全兼容历史对话数据,也实现了任务全流程信息的完整留存。优化后的单条对话记录结构如下,与原有格式完全对齐:

{

"2026-04-05": [

{

"time": "11:22:37",

"model": "glm",

"query": "帮我做一份北京4天3晚的小众行程规划",

"response": "已为您完成北京4天3晚小众行程规划...",

"tool_calls": "search",

"task_type": "行程规划",

"execute_status": "success",

"tool_call_count": 3,

"execute_cost_time": 12.36,

"quality_score": 5

}

]

}

新增字段的核心定义如下:

  • task_type:任务类型,枚举值包括搜索查询、行程规划、酒店推荐、景点攻略、天气查询等,用于同类任务的经验匹配。
  • execute_status:执行结果状态,枚举值包括success(成功)、failed(失败)、partial(部分完成),用于区分有效经验与失败教训。
  • tool_call_count:本轮任务的工具调用总次数,用于衡量任务执行的复杂度。
  • execute_cost_time:本轮任务的总执行耗时,单位为秒,用于后续执行效率的优化参考。
  • quality_score:任务质量评分,取值 1~5 分,来自用户反馈或执行结果自动校验,用于经验的优先级排序。

2. 全自动化情景记忆采集机制

我们将情景记忆的采集环节嵌入Agent Loop核心循环的全流程,实现数据的无感化自动采集,无须人工干预,全程不改变原有Agent的执行逻辑。

采集分为三个核心节点:

  • 任务启动节点:当用户发起需求后,系统先通过语义解析识别任务类型,完成task_type字段的预填充。
  • 执行过程节点:在工具调用环节,实时统计工具调用次数、记录每个接口的响应耗时,任务执行完成后,汇总生成tool_call_count与execute_cost_time字段。
  • 任务结束节点:根据工具调用结果、响应内容完整性判断execute_status,并通过用户后续的反馈交互更新quality_score,最终将完整的情景记忆写入conversation_log.json,以完成数据持久化。

3. 情景记忆的检索与复用机制

与事实记忆的按需检索逻辑一致,我们针对情景记忆设计了"语义匹配-分级筛选- 精准注入"的复用机制,既实现了经验的有效复用,也避免全量注入导致的上下文膨胀。

当新任务发起时,系统先基于task_type做一级匹配,筛选出同类任务的历史记录;再通过execute_status和quality_score做二级筛选,优先保留执行成功、评分≥4 分的高价值经验,过滤执行失败的负面案例;最终将筛选后的历史执行策略,作为few-shot示例注入系统prompt,仅保留任务拆解逻辑、工具调用流程等核心经验信息,不冗余注入历史对话内容。对于历史执行失败的案例,系统会单独提取失败原因,作为"避坑提示"同步注入prompt,帮助智能体规避同类错误。

下面是修正后的情景记忆管理的核心代码:

class ConversationLogger:

"""对话记录器 --- 追加式写入,按日期归档。"""

def init(self, log_path: str = "memorys/conversation_log.json"):

self.log_path = os.path.abspath(log_path)

os.makedirs(os.path.dirname(self.log_path), exist_ok=True)

self._init_log_file()

def _init_log_file(self):

"""初始化日志文件,不存在则创建空 JSON。"""

if not os.path.exists(self.log_path):

with open(self.log_path, "w", encoding="utf-8") as f:

json.dump({}, f, ensure_ascii=False, indent=2)

def log(self, query: str, response: str,

tool_calls: ListDict\[str, Any] = None,

model: str = "", extra: Dictstr, Any = None) -> None:

"""追加一条对话记录。"""

date_key = datetime.now().strftime("%Y-%m-%d")

record = {

"time": datetime.now().strftime("%H:%M:%S"),

"model": model,

"query": query,

"response": response:200,

"tool_calls": tool_calls or \[\],

**(extra or {}),

}

data = self._load()

if date_key not in data:

datadate_key = \[\]

datadate_key.append(record)

self._save(data)

def _load(self) -> Dict:

if os.path.exists(self.log_path):

try:

with open(self.log_path, "r", encoding="utf-8") as f:

return json.load(f)

except (json.JSONDecodeError, ValueError):

return {}

return {}

def _save(self, data: Dict) -> None:

with open(self.log_path, "w", encoding="utf-8") as f:

json.dump(data, f, ensure_ascii=False, indent=2)

def search_similar_tasks(self, task_type: str, top_k: int = 3,

min_score: int = 4) -> ListDict\[str, Any]:

"""检索同类高评分成功任务,用于 few-shot 学习(情景记忆)。"""

data = self._load()

results = \[\]

today = datetime.now()

date_keys = (today - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(30)

for dk in date_keys:

for conv in data.get(dk, \[\]):

if (conv.get("task_type") == task_type

and conv.get("execute_status") == "success"

and conv.get("quality_score", 0) >= min_score):

results.append(conv)

results.sort(key=lambda x: x.get("quality_score", 0), reverse=True)

return results:top_k

def get_failed_cases(self, task_type: str, days: int = 15) -> ListDict\[str, Any]:

"""检索同类任务的历史失败案例,用于错误规避(情景记忆)。"""

data = self._load()

results = \[\]

today = datetime.now()

date_keys = (today - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(days)

for dk in date_keys:

for conv in data.get(dk, \[\]):

if conv.get("task_type") == task_type and conv.get("execute_status") == "failed":

results.append(conv)

return results

ConversationLogger类实现了一套完整的对话记录与情景记忆检索机制。初始化时会自动创建日志目录与文件,确保存储路径可用。其核心log方法采用追加式写入,会自动记录当前时间、使用模型、用户输入、助手回复(截取前 29 字符)及工具调用列表,还支持通过extra参数传入任务类型、执行状态、质量评分等情景记忆字段,所有记录按日期分组归档,便于后续按时间维度检索。

类中还提供了两个关键检索方法,search_similar_tasks会遍历近 30 天的对话,筛选出同类任务中执行成功且质量评分≥4 分的记录,按评分倒序排列后返回top_k条结果,帮助智能体复用历史成功经验做few-shot学习;get_failed_cases则检索近 15 天同类任务的失败案例,用于提前规避同类错误。此外,_load和_save方法分别负责日志文件的读取与写入,确保数据持久化的可靠性。

7.2.3 上下文工程的优化:记忆的衰减与重整合公式设计

在前两节的优化中,我们通过记忆写入管控完成了用户事实特质的精准沉淀,通过情景记忆体系搭建,实现了智能体执行经验的有效复用,解决了记忆体系"从无到有、从有到优"的核心问题。

但在智能体的长周期持续运行中,这套体系仍存在两大长效管理短板:一是对所有记忆无差别平等赋权,缺乏基于价值与时效的动态权重管理,早期的低价值记忆会持续占用上下文资源,甚至干扰核心决策;二是缺乏碎片化记忆的整合机制,多轮对话中重复提取的同类内容会持续堆积,造成记忆库冗余、用户核心特质被分散稀释。本节我们将围绕记忆衰减与定期整合两大核心机制,完成记忆全生命周期的长效管理优化,让记忆体系在长周期运行中始终保持精简、精准、高效。

当前我们的记忆体系中,无论是用户当日刚确认的姓名变更、出行约束等核心决策,还是数月前的单次闲聊、临时查询等低价值内容,均被赋予完全相同的权重,在检索匹配、prompt注入时被无差别对待。这种模式在短周期对话中影响有限,但随着智能体运行周期拉长,会出现两大核心问题:一是无效记忆持续膨胀,挤占上下文Token空间,提升推理成本;二是低价值历史记忆会干扰大模型的决策优先级,比如用户已明确将昵称从"xiaohua"改为"小xing",若旧记忆与新记忆权重平等,会出现模型仍使用旧昵称的一致性问题。

对此,我们参考行业成熟的记忆权重管理方案,结合文旅导览的业务场景,落地了三维度加权的动态衰减机制,核心评分公式为:

记忆有效得分 = 相关性 * 0.4 + 重要性 * 0.3 + 新鲜度 * 0.3

  • 相关性:指该记忆与用户当前核心需求的匹配程度,取值0~1,由语义匹配计算得出,比如用户发起北京行程规划需求时,出行约束、文旅偏好类记忆的相关性得分接近1,无关历史闲聊内容得分接近0。
  • 重要性:指该记忆对用户决策的影响程度,取值0~1,来自记忆提取环节的重要性打分,比如用户的敏感约束、已确认决策、核心画像类记忆,重要性得分普遍在0.8以上,单次临时查询类记忆得分普遍低于0.5。
  • 新鲜度:指记忆的时效性,取值0~1,基于记忆最后一次被激活的时间计算,距离当前时间越近得分越高,超过30天未被激活的记忆,新鲜度得分降至0.2以下。

结合我们USER.md中的实际记忆内容,该评分机制可实现精准的权重区分:例如我们"改名为小xing"的已确认决策,重要性得分0.9,当日刚被激活时新鲜度得分1.0,用户发起对话时相关性得分0.9,最终有效得分0.93,属于最高优先级记忆;而用户"曾用名xiaohua"的记忆,重要性得分0.6,若超过15天未被激活,新鲜度得分0.4,无相关需求时相关性得分0.1,最终有效得分仅0.34,权重大幅降低。

def memory_score(relevance: float, importance: float, freshness: float) -> float:

"""记忆有效得分 = 相关性*0.4 + 重要性*0.3 + 新鲜度*0.3。"""

return relevance * 0.4 + importance * 0.3 + freshness * 0.3

读者可以自行测试。

7.2.4 上下文工程的优化:记忆的重整合实现

在落地规则上,我们设置每日会话结束后的定时衰减计算任务,为每条记忆计算动态有效得分,并设置两级管控阈值:一是归档阈值 0.4,得分低于 0.4 的记忆,从活跃记忆库移出归档至冷存储,不再自动注入prompt,仅在用户主动提及相关内容时被检索激活;二是清理阈值 0.2,连续 30 天得分低于0.2的记忆,自动执行清理,彻底释放存储空间。同时,若某条记忆被用户再次提及或激活,会立即刷新其新鲜度得分,重新回归活跃记忆库,确保记忆的动态适配性。

1. 记忆定期整合机制

当前我们的记忆体系缺乏标准化的整合机制,多轮对话中重复提取、语义相近的碎片化记忆,会被分别存储在不同字段中,不仅造成记忆库的冗余堆积,还会让用户的核心需求与特质被分散稀释,无法形成统一的用户认知。比如,在我们的USER.md中,"了解北京小吃""曾询问北京小吃"两条语义高度重合的记忆,被分别存储在"已确认需求"和"历史摘要"两个字段中;"北京旅游:独自一人,行程一天"与"北京旅游时间限制:一天"两条核心约束,也被拆分在"偏好记录"和"敏感约束"中,这种碎片化的存储模式,既不利于大模型快速捕捉用户核心特质,也会加剧记忆库的膨胀。

对此我们参考人类睡眠中的记忆巩固生理机制,设计了定期记忆整合任务,在每日会话结束、智能体空闲时段执行,模拟人类"睡眠整理记忆"的过程,对当日新增记忆与历史活跃记忆做语义归一化处理,将重复、碎片化、语义相近的记忆,合并为一条凝练、完整、不丢失核心信息的结构化摘要,同时更新记忆的重要性得分与最后活跃时间。

结合以上分析,我们制定了明确的整合规则与落地示例,全程遵循"不篡改、不丢失、不臆测"三原则,仅做语义凝练与去重,不修改用户核心原意。其中核心代码如下所示:

import os

import sys

import re

import json

import logging

from datetime import datetime, timedelta

from typing import Any, Dict, List

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(file))))

from initspace.contextbuild import _load_md, _extract_json

logger = logging.getLogger(name)

_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(file)))

class MemoryExtractor:

"""记忆提取器 --- 根据 MD 标题结构,从最近 N 日对话中批量提取。"""

_SKIP_SECTIONS = frozenset({"说明"})

def init(self, model_name: str = "glm", md_path: str = "",

conversation_log_path: str = ""):

self.model_name = model_name

self.md_path = md_path

self.sections: Liststr = self._parse_sections(md_path)

self.conversation_log_path = conversation_log_path

if "qwen" in model_name:

from llm_moudle import qwen_moudle

self.client = qwen_moudle.QwenClient(model_name=model_name)

else:

from llm_moudle import glm_moudle

self.client = glm_moudle.GLMClient(model_name=model_name)

def _parse_sections(self, md_path: str) -> Liststr:

raw = _load_md(md_path)

if not raw:

return \[\]

return [

s for s in re.findall(r"^#{2,3}\s+(.+)$", raw, re.MULTILINE)

if s.strip() not in self._SKIP_SECTIONS

]

def _get_recent_conversations(self, days: int = 5) -> str:

if not self.conversation_log_path:

return ""

abs_path = os.path.abspath(self.conversation_log_path)

if not os.path.exists(abs_path):

abs_path = os.path.join(_PROJECT_ROOT, self.conversation_log_path)

if not os.path.exists(abs_path):

return ""

try:

with open(abs_path, "r", encoding="utf-8") as f:

data = json.load(f)

except (json.JSONDecodeError, ValueError):

return ""

today = datetime.now()

date_keys = (today - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(days)

parts = \[\]

for date_key in date_keys:

for conv in data.get(date_key, \[\]):

parts.append("" + date_key + " 用户: " + conv.get("query", "") +

"\n助手: " + conv.get("response", ""))

return "\n\n".join(parts)

async def extract(self, days: int = 5) -> Dictstr, Any:

text = self._get_recent_conversations(days)

if not text.strip() or not self.sections:

return {}

prompt = (

"从以下近" + str(days) + "日对话记录中提取信息。只提取这些字段:" + ", ".join(self.sections) + "\n"

"规则:只提取明确提到的,不推测。没有新发现就输出空 JSON: {}\n"

"输出纯 JSON:{\"字段名\": \"值或列表\", ...}"

)

try:

response = await self.client.chat(messages=[

{"role": "system", "content": prompt},

{"role": "user", "content": text},

])

raw = "".join(b.get("text", "") for b in response.get("content", \[\])

if b.get("type") == "text")

return _extract_json(raw)

except Exception as e:

logger.warning(f"MemoryExtractor 提取失败: {e}")

return {}

class UseMemory:

"""通用记忆管理器 --- 根据传入 MD 文件提取标题,从对话记录中抽取对应内容。"""

def init(self, md_path: str, conversation_log_path: str = "",

model_name: str = "glm"):

self.md_path = md_path

self.conversation_log_path = conversation_log_path

self.model_name = model_name

self.data: Dictstr, Any = {}

self.sections: Liststr = \[\]

self.extractor = MemoryExtractor(

model_name=model_name, md_path=md_path,

conversation_log_path=conversation_log_path,

)

self.load()

self._init_history()

非 data 段落标题,衰减/整合时跳过

_SKIP_SECTIONS = frozenset({"说明"})

记忆评分权重(7.2.3 公式)

RELEVANCE_WEIGHT = 0.4

IMPORTANCE_WEIGHT = 0.3

RECENCY_WEIGHT = 0.3

衰减阈值

ARCHIVE_THRESHOLD = 0.4

CLEAN_THRESHOLD = 0.2

def load(self) -> None:

raw = _load_md(self.md_path)

if not raw:

return

sections = re.split(r"(?=^## )", raw, flags=re.MULTILINE)

for sec in sections:

lines = sec.strip().splitlines()

if not lines:

continue

title_match = re.match(r"^## (.+)", lines0)

if not title_match:

continue

title = title_match.group(1).strip()

content_lines = [

l.strip() for l in lines1:

if l.strip() and not l.strip().startswith("<!--")

and l.strip() != "---" and "(暂无)" not in l and "(待识别)" not in l

]

self.datatitle = "\n".join(content_lines)

self.sections = list(self.data.keys())

def to_prompt(self) -> str:

parts = f"【{k}】\\n{v}" for k, v in self.data.items() if v

if not parts:

return ""

md_name = os.path.basename(self.md_path)

return "【" + md_name + " 记忆】\n以下是提取到的信息:\n\n" + "\n\n".join(parts)

相似度阈值:>= 视为重复跳过,< 视为冲突覆盖

SIMILARITY_THRESHOLD = 0.8

async def update(self, days: int = 5) -> Dictstr, Any:

"""从最近 N 日对话提取并更新;去重(相似度>=阈值跳过),有变更则写回 MD。"""

extracted = await self.extractor.extract(days)

if not extracted:

return {}

changed: Dictstr, Any = {}

for section, new_value in extracted.items():

old_value = self.data.get(section)

if not old_value:

self.datasection = new_value

changedsection = new_value

else:

score = self._similarity_score(old_value, new_value)

if score >= self.SIMILARITY_THRESHOLD:

logger.info("UseMemory 重复跳过: %s (相似度=%.2f)", section, score)

else:

self.datasection = new_value

changedsection = new_value

if changed:

self.save()

return changed

def _normalize(self, value: Any) -> str:

if isinstance(value, list):

return "\n".join(str(x).strip() for x in sorted(value))

return str(value).strip()

def _similarity_score(self, old: Any, new: Any) -> float:

from difflib import SequenceMatcher

return SequenceMatcher(None, self._normalize(old), self._normalize(new)).ratio()

def _is_same_content(self, old: Any, new: Any) -> bool:

return self._similarity_score(old, new) >= self.SIMILARITY_THRESHOLD

def save(self) -> None:

"""将 self.data 写回 MD 文件,保留标题结构,空值写(暂无)。"""

abs_path = os.path.abspath(self.md_path)

if not os.path.exists(os.path.dirname(abs_path) or "."):

abs_path = os.path.join(_PROJECT_ROOT, self.md_path)

lines = \[\]

for title in self.sections:

lines.append("## " + title)

lines.append("")

value = self.data.get(title)

if value:

items = value if isinstance(value, list) else value

for item in items:

lines.append("- " + str(item))

else:

lines.append("- (暂无)")

lines.append("")

with open(abs_path, "w", encoding="utf-8") as f:

f.write("\n".join(lines))

------------------------------------------------------------------

7.2.4 记忆评分 / 衰减 / 重整合

------------------------------------------------------------------

def _calculate_memory_score(self, section: str, current_query: str = "") -> float:

"""记忆有效得分 = 相关性×0.4 + 重要性×0.3 + 新鲜度×0.3。"""

history = self._load_history()

importance = history.get("importance", {}).get(section, 0.5)

last_active_str = history.get("last_active", {}).get(section)

if not last_active_str:

recency = 0.5

else:

try:

last_active = datetime.fromisoformat(last_active_str)

recency = max(0, 1 - ((datetime.now() - last_active).days / 30))

except ValueError:

recency = 0.5

if not current_query:

relevance = 0.5

else:

from difflib import SequenceMatcher

relevance = SequenceMatcher(None, current_query, str(self.data.get(section, ""))).ratio()

return round(

relevance * self.RELEVANCE_WEIGHT

  • importance * self.IMPORTANCE_WEIGHT

  • recency * self.RECENCY_WEIGHT, 2,

)

def run_memory_decay(self, current_query: str = ""):

"""记忆衰减:低分归档,极低分清理。"""

history = self._load_history()

archive = history.get("archive", {})

active = s for s in list(self.data.keys()) if s not in self._SKIP_SECTIONS

to_archive = \[\]

for section in active:

score = self._calculate_memory_score(section, current_query)

if score < self.CLEAN_THRESHOLD:

del self.datasection

logger.info("UseMemory 清理: %s (得分=%.2f)", section, score)

continue

if score < self.ARCHIVE_THRESHOLD:

to_archive.append(section)

for section in to_archive:

archive.setdefault(section, \[\]).append({

"content": self.datasection,

"archived_at": datetime.now().isoformat(),

})

del self.datasection

logger.info("UseMemory 归档: %s", section)

history"archive" = archive

self._save_history(history)

self.save()

def run_memory_consolidation(self):

"""合并碎片化/重复记忆(按关键词规则归并到目标段落)。"""

history = self._load_history()

data = self.data

rules = [

{"target": "历史摘要", "keywords": "上次", "曾经", "之前", "历史"},

{"target": "偏好记录", "keywords": "喜欢", "偏好", "口味", "风格"},

]

for rule in rules:

target = rule"target"

if target not in data:

continue

merged_parts = \[\]

for section in list(data.keys()):

if section == target or section in self._SKIP_SECTIONS:

continue

content = str(data.get(section, ""))

if any(kw in content for kw in rule"keywords"):

merged_parts.append(f"{section} {content}")

history.setdefault("archive", {}).setdefault(section, \[\]).append({

"content": content, "archived_at": datetime.now().isoformat(),

"reason": "consolidation",

})

del datasection

if merged_parts:

existing = str(data.get(target, ""))

datatarget = (existing + "\n" + "\n".join(merged_parts)) if existing else "\n".join(merged_parts)

history.setdefault("importance", {})target = 0.9

self._save_history(history)

self.save()

@property

def history_path(self) -> str:

md_name = os.path.splitext(os.path.basename(self.md_path))0

return os.path.join(_PROJECT_ROOT, "initspace", "memorys", md_name + "_history.json")

def _init_history(self):

if not os.path.exists(self.history_path):

self._save_history({"archive": {}, "last_active": {}, "importance": {}})

def _load_history(self) -> Dictstr, Any:

if os.path.exists(self.history_path):

try:

with open(self.history_path, "r", encoding="utf-8") as f:

content = f.read().strip()

if content:

return json.loads(content)

except (json.JSONDecodeError, ValueError):

pass

return {"archive": {}, "last_active": {}, "importance": {}}

def _save_history(self, history_data: Dictstr, Any):

os.makedirs(os.path.dirname(self.history_path) or ".", exist_ok=True)

with open(self.history_path, "w", encoding="utf-8") as f:

json.dump(history_data, f, ensure_ascii=False, indent=2)

这是一套完整的智能体上下文记忆全生命周期管理代码,覆盖从提取到整合的全流程。

核心包含两个类:MemoryExtractor是子Agent,初始化时会根据模型名自动创建GLM或Qwen客户端,先读取USER.md这类MD文件的 ##/### 标题(跳过"说明"等非数据段落),再从conversation_log.json里拉取最近N天的对话拼接成文本,最后用大模型按标题只提取明确提到的信息,输出纯JSON,失败或无新发现就返回空字典。

UseMemory是核心管理器,初始化会加载MD文件和配套的历史版本库,绑定提取器。基础功能是load解析MD存字典、save写回带"(暂无)"的MD、to_prompt只输出有内容的段落。核心功能是update方法提取后用 0.8 的相似度阈值去重,之后整合是按关键词合并碎片化记忆到目标字段,归档原始内容。

相关推荐
watersink1 小时前
机器学习极大似然估计与EM算法
人工智能·算法·机器学习
叠层归一研究院1 小时前
如何用程序搭建一个 AGI 种子系统(一):从向量种子到无限生长引擎
人工智能·python·算法·机器学习·agi
名不经传的养虾人1 小时前
从0到1:企业级AI项目迭代日记 Vol.87|记忆链路切换了,系统接管有了质量门
大数据·人工智能·ai编程·企业ai·多agent协作
GlobalInfo1 小时前
2026年显微外科手术机器人系统市场报告:市场规模、产业研究、十五五规划与发展趋势预测
大数据·人工智能·机器人
神奇小汤圆1 小时前
DeepSeek Harness 的基石:从源码理解 Cordis
人工智能
Leslie1651 小时前
模型调价之后,如何把 Token 账单变成可回滚的预算策略
人工智能
企鹅的企1 小时前
2027北京AI健康科技与智慧医疗展官方链接产业资源
人工智能·科技
独隅1 小时前
KMP 全栈进化:Koog 框架打造纯 Kotlin AI Agent 实战效果
开发语言·人工智能·kotlin
zhy295631 小时前
【DNN】Llama 3.2 1B模型LORA微调与QAIRT部署
人工智能·dnn·llama