《智能体工程驱动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 的相似度阈值去重,之后整合是按关键词合并碎片化记忆到目标字段,归档原始内容。

