【Agentic Memory】MemTrace 如何做到自动溯源和自动优化
目录
- [【Agentic Memory】MemTrace 如何做到自动溯源和自动优化](#【Agentic Memory】MemTrace 如何做到自动溯源和自动优化)
- [0x00 概要](#0x00 概要)
- [0x01 MemTrace 在做什么------三层递进解读](#0x01 MemTrace 在做什么——三层递进解读)
- [1.1 第一层:给孩子的解释](#1.1 第一层:给孩子的解释)
- [1.2 第二层:给半专业人士的解释](#1.2 第二层:给半专业人士的解释)
- [1.3 第三层:给专业人士的解释](#1.3 第三层:给专业人士的解释)
- [0x02 组件详解 ------ 一条主线贯穿](#0x02 组件详解 —— 一条主线贯穿)
- [0x03 smartcomment:执行图录制层](#0x03 smartcomment:执行图录制层)
- [3.1 设计原理:为什么需要一个新的追踪工具?](#3.1 设计原理:为什么需要一个新的追踪工具?)
- [3.2 层次化数据模型](#3.2 层次化数据模型)
- [3.3 核心 API](#3.3 核心 API)
- [3.4 在 Membase 中的实际使用](#3.4 在 Membase 中的实际使用)
- [0x04 graphtrace:图归因 Agent 的核心引擎](#0x04 graphtrace:图归因 Agent 的核心引擎)
- [4.1 归因原理:三个保证 + 一个归纳](#4.1 归因原理:三个保证 + 一个归纳)
- [4.2 编译器领域的类比:程序切片 + Delta Debugging](#4.2 编译器领域的类比:程序切片 + Delta Debugging)
- [4.3 代码实现](#4.3 代码实现)
- [4.4 探索策略对比](#4.4 探索策略对比)
- [0x05 obstrace:搜索式消融变体](#0x05 obstrace:搜索式消融变体)
- [5.1 设计原理:为什么去掉图结构?](#5.1 设计原理:为什么去掉图结构?)
- [5.2 为什么这是一个有效消融?](#5.2 为什么这是一个有效消融?)
- [5.3 编译器领域的类比](#5.3 编译器领域的类比)
- [5.4 核心组件](#5.4 核心组件)
- [0x06 自动优化:从归因信号到系统改进](#0x06 自动优化:从归因信号到系统改进)
- [6.1 原理:文本梯度](#6.1 原理:文本梯度)
- [6.2 三阶段代码实现](#6.2 三阶段代码实现)
- [6.3 梯度历史机制](#6.3 梯度历史机制)
- [6.4 跨领域类比](#6.4 跨领域类比)
- [6.5 当前的边界:为什么只优化了 prompt?](#6.5 当前的边界:为什么只优化了 prompt?)
- [0x07 toolkits:连接归因与优化的桥梁](#0x07 toolkits:连接归因与优化的桥梁)
- [7.1 体系概览](#7.1 体系概览)
- [7.2 归因指令的完整推理范式](#7.2 归因指令的完整推理范式)
- [7.3 混合检索机制](#7.3 混合检索机制)
- [0x08 扩展讨论:归因与优化的解耦架构](#0x08 扩展讨论:归因与优化的解耦架构)
- [8.1 为什么"可以优化"?](#8.1 为什么"可以优化"?)
- [8.2 哪些错误是当前版本"未处理"的?](#8.2 哪些错误是当前版本"未处理"的?)
- [8.3 可以扩展到哪些类型的变量?](#8.3 可以扩展到哪些类型的变量?)
- [8.4 架构上如何扩展?------归因与优化本应是解耦的](#8.4 架构上如何扩展?——归因与优化本应是解耦的)
- [8.3 自动化的边界在哪里?](#8.3 自动化的边界在哪里?)
- [0x09 一条主线,三个解耦](#0x09 一条主线,三个解耦)
- 附录
- [附录A 入口脚本:各组件如何协作](#附录A 入口脚本:各组件如何协作)
- [A.1
run_error_attribution.py](#A.1 run_error_attribution.py) - [A.2
run_optimization.py](#A.2 run_optimization.py) - [A.3
generate_report.py](#A.3 generate_report.py) - [A.4
eval_retrieval_performance.py](#A.4 eval_retrieval_performance.py)
- [A.1
- [附录B annotation_interface:可视化标注](#附录B annotation_interface:可视化标注)
- [附录C 项目文件全景](#附录C 项目文件全景)
- [附录A 入口脚本:各组件如何协作](#附录A 入口脚本:各组件如何协作)
0x00 概要
记忆对于让大语言模型支持长时程推理至关重要,然而现有记忆系统仍然不可靠且难以调试。追踪记忆的动态演化,是理解信息如何随时间被综合、传播或污染的关键 。MemTrace 作者研究了大语言模型记忆系统中错误追踪与归因这一新问题,然后提出了一种新颖框架,将记忆流水线转化为可执行的记忆演化图,从而实现对操作级信息流的细粒度追踪。随后该作者也构建了 MemTraceBench------一个从 Long-Context、RAG、Mem0 和 EverMemOS 等代表性记忆系统中收集的基准,用以系统性地研究记忆失效模式。该作者还引入了一种自动归因方法,通过迭代追踪操作子图,精确定位任意失败案例的根因。分析表明,记忆失效是系统性的,其根源在于信息丢失、检索错位等操作级问题。关键的是,该作者还利用这些细粒度归因信号来指导下游提示词优化,建立起一个能自动修正故障的闭环系统,并将下游任务性能提升最高达 7.62%。
论文:MemTrace: Tracing and Attributing Errors in Large Language Model Memory Systems https://arxiv.org/abs/2605.28732
代码:https://github.com/zjunlp/MemTrace
0x01 MemTrace 在做什么------三层递进解读
下面用三个层次解释同一个核心问题:当 LLM 记忆系统答错问题时,MemTrace 如何自动找出根因并修复它?
1.1 第一层:给孩子的解释
想象一个机器人管家,专记全家人的喜好。你告诉它"妈妈爱喝热可可",它记下了。后来你问它"妈妈喝什么",它答"可乐"。妈妈从来不喝可乐------哪里出错了?
自动溯源 就是让 MemTrace 当侦探。它不像普通人只看最后一步("答错了"),而是从头翻小本子 :找到"热可可"这条记录,追踪它在小本子里的经历------有没有被涂改?有没有被撕掉?有没有被别的字盖住?如果发现中途有人把"热可可"改成了"可乐",那这就是罪犯。它告诉我们:问题出在记忆更新环节 ,类型是更新错误。
自动优化更进一步。既然找到了罪犯------原来是那条"更新指令"写得不好,让机器人在更新时把正确信息弄丢了。那我们改改这条指令,比如加上一句"发现新旧矛盾时,保留旧信息并标记提醒"。下次就不犯了。
整个过程:侦探破案 → 找到罪犯 → 修改法律条文 → 不再犯。
1.2 第二层:给半专业人士的解释
LLM 记忆系统的流水线有四步:
消息 → [事实提取] → [存储/更新/删除] → [检索] → [LLM 回答]
答案错了,原因藏在前面任一步。手动排查像大海捞针。MemTrace 的解法是用执行图串联整个过程。
第一步,smartcomment 录制执行图 :在记忆系统的关键位置插入追踪代码,记录每一步产出了什么变量 (事实列表、记忆单元、检索结果、提示词)、执行了什么操作 (LLM 调用、检索、过滤)、以及变量和操作之间的依赖关系。最终得到一张有向无环二分图------不是扁平的日志,而是体现信息传递路径的网络。
第二步,MemTrace 在图上自动归因 :把执行图和失败问题交给一个配备了图工具的大模型 Agent。Agent 从最早的消息开始,沿图逐操作检查:这个操作在其自身职责范围内 正确吗?如果正确,追踪它的产出继续向下游;如果不正确且修正它能挽救最终答案------这就是"最早决定性故障"。输出为 {error_type, op_id, reason}。
第三步,利用归因信号做自动优化:每次失败案例的归因结果,相当于给了系统一个"改进方向"(论文称之为"文本梯度")。归因找到的故障操作,必然涉及某个可配置的变量(比如 prompt 模板)。把失败案例和归因结果作为"改进信号"馈给优化器,自动改写这些变量的值。多轮闭环后,系统性能提升。
1. 执行记忆系统 → 2. 运行 MemTrace 归因 → 3. 分析哪些提示词导致了错误 → 4. 用 LLM 改进这些提示词 → 5. 回到 1
论文在 Mem0 记忆系统 + LoCoMo 基准上验证,三轮优化后任务性能提升 7.62%。
1.3 第三层:给专业人士的解释
问题形式化
设非参数记忆系统 ℳ 处理轨迹 τ、回答问题 q 得到预测 â(golden answer 为 a)。其执行为三步:记忆更新 𝒰_ℳ、记忆读取 ℛ_ℳ、答案生成 𝒬。
通过 smartcomment 插桩录制得到一个有向无环二分图 𝒢 = (𝒱, 𝒪, ℰ):
- 变量节点 𝒱:消息、记忆单元、中间摘要、LLM 提示词等执行中的具体产物
- 操作节点 𝒪:LLM 推理、工具调用、检索、过滤等计算步骤
- 边 ℰ:变量与操作间的信息流,每个操作 o 有输入 In(o) ⊂ 𝒱 和输出 Out(o) ⊂ 𝒱
- 结果指示器 Z(𝒢) ∈ {0,1},由 LLM Judge 比较 â 与 a 得到
决定性错误集(Decisive Error Set) 𝒪* 被定义为满足以下条件的最小因果割集:
- 故障性:∀ o ∈ 𝒪*,o 在执行中是错误的
- 上游正确性:所有严格上游祖先 Anc_𝒢(𝒪*) 功能正确
- 因果充分性:将 𝒪* 的输出替换为正确版本且下游假设完美,则 Z(𝒢^(𝒪*,*)) = 0(即能挽救失败)
- 最小性:去掉 𝒪* 中任何操作都破坏因果充分性
论文专注于单元素场景 |𝒪*| = 1。
自动溯源(MemTrace)
MemTrace 将归因建模为Agentic Graph Exploration。Agent(ReAct 框架,backbone 为 GPT-4.1 mini 或 GPT-5.4)配备三个模块:
模块 1:起始点选择
- 构造检索查询:
q + a拼接 - 对历史消息集同时执行稠密检索和稀疏检索(BM25),各取 top-N
- RRF 融合,取 ⌊N/2⌋ 条,和 q 一起构成初始前沿队列 ℒ₀
- 预留前沿容量供 Agent 动态添加
模块 2:图探索
- 前沿队列按
created_at排序(越早的变量优先级越高) - 每轮取最早变量 v_t,获取 𝒪(v_t) =
- 每个操作子图通过
RuntimeGraph.to_xml()渲染给 Agent - Agent 判断:局部正确 → 加入下游变量到 ℒ_t;故障 → 检查决定性 → 输出
- 更新 ℒ_t = (ℒ_{t-1} \ {v_t}) ∪ 𝒜_t
模块 3:上下文管理
- 预览模式(省略变量值)、
view_node_value()支持分页+正则搜索 - 超出阈值 T=272k token 时自动摘要压缩
消融变体 MemTrace-OBS:将操作子图压缩为扁平文本块(移除边和变量 ID),按时间戳串联,提供全局正则搜索工具。适用于图结构弱(如退化为长链)的场景。
自动优化
将信用分配(credit assignment)与提示词改写解耦:
python
smartcomment 录制执行图
→ MemTrace 归因(定位故障操作)
→ 优化问题简化为局部问题(仅涉及该操作的部分提示词)
→ 三阶段文本梯度优化:
(1) 生成反馈(每个失败 case → 结构化改进建议)
(2) 聚合反馈(合并冗余、去重)
(3) 更新目标(根据聚合反馈改进提示词,无关部分不变)
三个可优化目标:fact-extraction-system-prompt、memory-update-decision-prompt、question-answering-prompt。梯度历史使用 deque(maxlen=N) 存储 (previous_value, aggregated_feedback) 对作为优化动量。
0x02 组件详解 ------ 一条主线贯穿
以下所有组件围绕一条核心脉络展开,请始终记住这个逻辑链:
smartcomment → 录制 → 执行图DAG
↓
graphtrace → 沿图归因(定位故障操作 o*)
obstrace → 消融对照(去掉图结构的影响)
↓
toolkits → 消费归因结果(优化、报告、评测)
↓
扩展架构 → 从 prompt 优化扩展到任意变量类型
每个组件都有其设计原理 (为什么这么做)、具体实现 (代码怎么做的)、跨领域类比(编译器等其他领域中的类似思路)。
0x03 smartcomment:执行图录制层
3.1 设计原理:为什么需要一个新的追踪工具?
回忆一下我们想要什么样的执行图:它不仅要知道"做了哪些操作",还要知道每个操作的输入变量来自哪里、输出变量去了哪里、同一变量在多次操作中的版本演化。这需要追踪工具满足三个要求:
- 可插桩任意 Python 代码------记忆系统有异构的 schema,不可能要求它们统一改写为某个框架
- 变量是一等公民------要能追踪变量的依赖关系,而不只是事件的时序
- 变量是可版本化的------同一逻辑变量经历多次更新后,要能恢复其演化轨迹
下图对比了现有工具:
| 类型 | 代表 | 优点 | 缺点 |
|---|---|---|---|
| 插桩式 | MLflow, Langfuse | 非侵入 | 事件中心,无变量依赖 |
| 抽象原生式 | PyTorch, DSPy | 自动计算图 | 必须按框架重写代码 |
smartcomment 填补了空白:插桩式 + 图追踪。它通过显式插桩追踪任意 Python 变量,但以图结构而非事件流的方式记录变量依赖。这一设计选择背后的信念是:在 AI 辅助开发时代,编写少量插桩代码的成本远低于将系统重写到一个新框架中。
3.2 层次化数据模型
python
ExecNetwork(顶层执行图)
├── graph_id, user_id, project_id(元数据)
└── GraphDriver(存储后端抽象)
├── Session(会话:memory_construction / retrieval / response)
│ ├── Operation(操作:LLMInference、EmbeddingSearch、Filter...)
│ │ └── Edges(边:source_variable → operation → target_variable)
│ └── Variable(变量:消息、事实列表、记忆单元、提示词...版本化)
└── ...更多 Session...
关键数据结构:
ExecNetwork (smartcomment-main/smartcomment/runtime/network.py:80)------顶层容器:
filter_by_operation(op_id)→RuntimeGraph:按操作 ID 提取子图(节点+边+操作三元组),这是归因 Agent 最频繁调用的方法export_graph()/import_graph():JSON 持久化,使执行图可在各阶段间传递_reachable_closure(full_node_id, direction):BFS 可达闭包,支持方向参数和最大深度
RuntimeGraph (smartcomment-main/smartcomment/runtime/graph.py:13)------只读子图视图:
.nodes/.edges/.ops:构成子图的三元组.to_xml(include_variable_value=True)→ XML 字符串(供 LLM 消费的格式).to_markdown()→ Markdown 字符串
版本化变量 (runtime/variable.py:17):
full_node_id=name@version(如memory_unit@3表示第 3 版)comment_mutation创建新版本而非覆盖,保留完整演化历史
3.3 核心 API
smartcomment 的公有 API 分为两层:
上下文管理器(控制追踪范围):
| API | 作用 |
|---|---|
comment_graph(user_id, project_id) |
创建/进入一个执行图 |
comment_session(session_id, category, metadata) |
进入一个会话(如 memory_construction) |
comment_op_scope(op_name, category, comment) |
进入一个操作作用域 |
注解 API(录制变量和依赖):
| API | 作用 |
|---|---|
comment_variable(value, id_strategy, comment, category) |
将任意值注册为追踪变量 |
comment_link(source, target, comment) |
创建变量间的依赖边,自动关联当前操作 |
comment_mutation(variable, new_version, ...) |
标记变量原地更新 |
comment_fn(...) |
装饰器:将函数调用注册为操作 |
全局控制:
| API | 作用 |
|---|---|
enable_tracing() / disable_tracing() |
全局开关 |
current_graph() / current_session() / current_op() |
获取当前上下文 |
上下文通过 ContextVar 实现线程安全(runtime/context.py:24-26),这一设计使得同一进程内可并行录制多个独立执行图。
Identity 机制 :记忆系统中同一变量可能在多处被序列化/反序列化。smartcomment 通过 IdentityRegistry + 用户自定义 identity 函数来判定"两个运行时对象是否应视为同一追踪变量"。这是保证版本化变量正确性的关键。
3.4 在 Membase 中的实际使用
membase/runners/construction.py 展示了完整的录制流程:
python
if cfg.tracing:
enable_tracing()
with comment_graph(user_id=user_id, project_id=layer_type, ...) as graph:
with MonkeyPatcher(specs): # 注入记忆系统的 smartcomment 插桩
for session in trajectory:
with comment_session(session_id=session.id, category="memory_construction"):
for message in session:
layer.add_message(message) # 内部调用 comment_variable + comment_link
with comment_session(category="memory_construction"):
layer.flush()
if graph is not None:
graph_data = graph.export_graph()
json.dump(graph_data, open(traced_data_path, "w"))
三个 Runner 分别录制不同阶段:ConstructionRunner → 记忆构造阶段、SearchRunner → 检索阶段、EvaluationRunner → 回答与评测阶段。在优化流程中,这三个阶段的图被加载为完整 ExecNetwork。
0x04 graphtrace:图归因 Agent 的核心引擎
这是 MemTrace 的核心------一个专门为 DAG 执行图设计的 ReAct Agent,通过在图上的边跟随来定位最早决定性故障。
4.1 归因原理:三个保证 + 一个归纳
为什么图结构能定位错误?
核心问题
核心问题:给定一个失败执行图 𝒢 = (𝒱, 𝒪, ℰ) 和失败查询 (â ≠ a),目标是找出"最早决定性故障" o* ∈ 𝒪。
这个问题本质上是动态程序切片(Dynamic Program Slicing) 在记忆系统上的应用。我们来拆解其原理。
原理
原理一:信息流必然是沿图传递的
执行图 𝒢 是一个二分有向无环图。每个操作 o ∈ 𝒪 产出 Out(o),而这些产出又会成为下游操作的输入 In(o')。关键前提是:
如果所有上游操作 Anc_𝒢(o) 都正确,且操作 o 自身正确,那么 Out(o) 必然是正确的。
反之,如果最终输出 Z(𝒢) = 1(失败),则必然存在至少一个操作 o*,使得 o* 自身错误,且其所有祖先都正确。这就是"最早决定性故障"的因果充分性条件------因为 DAG 保证了偏序关系,最早错误的操作一定存在于从输入到输出的因果路径上。
这等价于编译器中的数据流分析。考虑一个经典问题:程序输出了错误值,如何定位?
| 领域 | 结构 | 分析方法 | 目标 |
|---|---|---|---|
| 编译器 | 程序依赖图(PDG) | 定义-使用链追溯 | 找出导致错误值的语句 |
| MemTrace | 操作-变量二分图 | 沿边回溯操作子图 | 找出最早决定性故障操作 |
程序依赖图(Program Dependence Graph, PDG) 是编译器中间表示的一种形式,其中:
- 节点 = 语句/表达式
- 数据依赖边 = 变量定义→使用
- 控制依赖边 = 分支条件→受控语句
MemTrace 的执行图与之高度相似:变量节点 = 定义值,操作节点 = 计算/转换,边 = 数据依赖。区别在于 PDG 中控制依赖在 MemTrace 中被显式建模为操作的一部分(因为记忆系统本质上是顺序流水线,控制流是确定的)。
原理二:二分图边跟随等价于定义-使用链追踪
_render_operation_subgraph(graphtrace/_utils/_helpers.py:9)的核心函数做了这样一件事:
python
给定焦点变量 v_t,获取涉及它的操作集:
O(v_t) = { o ∈ O | v_t ∈ In(o) ∪ Out(o) }
对于每个操作 o ∈ O(v_t),渲染子图 G_o(包含 o 的输入/输出/边/变量值)
Agent 判断 o 是否正确:
├── 正确 → 将 Out(o) 中的下游变量加入前沿
└── 错误 → 检查决定性 → 输出 {error_type, op_id, reason}
这完全等同于编译器调试中的向后切片(backward slice):从某个关注点出发,沿数据依赖边反向追踪所有可能影响该点的语句。
数学基础
归因可行的数学基础------DAG 的三个保证:
- 偏序保证:DAG 拓扑序 = 执行顺序。如果 o_i 在 o_j 之前发生,图中不存在从 o_j 到 o_i 的路径。
- 因果封闭性:操作 o 的输出 Out(o) 完全由 o 自身及其严格祖先 Anc_𝒢(o) 决定。
- 可干预性 :Z(𝒢) = 1(失败)→ 必然存在至少一个操作 o*,使得修正 o* 的输出且保持 Anc_𝒢(o*) 不变即可产生 Z(𝒢') = 0。
有了这三个保证,归因就简化为:从最早的变量开始,沿拓扑序逐个检查操作,直到找到"自身错误且所有上游正确"的那个操作。 这等价于 DAG 上的"第一个断点"搜索------信息流路径上距离入口最近的断裂点。
阶段级归纳
五种错误类型------阶段级归纳:
ErrorAttributionPrediction(toolkits/memtrace_utils.py:266)定义了五类错误,它们构成一个互斥完备划分:
python
class ErrorAttributionPrediction(BaseModel):
error_type: Literal[
"ExtractionError", # ① 信息从未进入记忆
"UpdateError", # ② 信息进入后被降级/覆盖
"DeletionError", # ③ 信息被删除
"RetrievalError", # ④ 信息在存储中但检索未命中
"ResponseError", # ⑤ 检索完备但 LLM 答错
]
op_id: str
reason: str
为什么恰好是这五类?因为任何记忆系统都包含五个串行阶段:
python
输入 → [提取] → 记忆 → [更新/删除] → 记忆 → [检索] → 上下文 → [回答] → 输出
信息从输入到输出的路径必然依次经过这五个阶段,不会跳过任何一个。因此任何信息丢失/错误必然发生在且仅发生在其中一个阶段------这就是"互斥完备"的来源。
4.2 编译器领域的类比:程序切片 + Delta Debugging
类比一:程序切片(Program Slicing)
给定程序点 p 和变量 v,程序切片是"所有可能影响 v 在 p 处值的语句"(Weiser, 1984)。计算切片需要对程序依赖图(PDG)做可达性分析。
MemTrace 的图探索本质上是对执行图的动态向后切片:
| 传统程序切片 | MemTrace | |
|---|---|---|
| 结构 | 程序依赖图(PDG):节点=语句,边=数据依赖+控制依赖 | 执行图:节点=变量/操作,边=信息流 |
| 分析方式 | 静态分析所有可能路径 | 动态分析一次具体执行 |
| 目标 | 找出影响某值的所有语句 | 找出导致错误的最早故障操作 |
| 方法 | 可达闭包(从关注点沿边反向遍历) | 边跟随(从最早变量沿边正向检查) |
类比二:Delta Debugging
Zeller(1999)用 Delta Debugging 定位导致程序失败的最小输入差异------核心是寻找"最小因果集"。这与 MemTrace 的 𝒪* 最小性约束完全一致:
- Delta Debugging:二分搜索输入空间,找最小失败诱导子集
- MemTrace:拓扑序扫描操作集,找最小决定性故障集
两者共享同一个数学模式:在因果偏序集上搜索最小充分失败原因。
类比三:关键路径分析
编译器指令调度中,关键路径是从入口到出口的最长延迟路径------优化关键路径上的指令能最大程度加速。
MemTrace 的"最早决定性故障"是信息流关键路径上的第一个断点:从输入证据到失败回答有一条信息传递链,链上任一环断裂(信息丢失)则下游必然失败。找到距离入口最近的断点,就是最早决定性故障。
类比四:CEGAR(Counterexample-Guided Abstraction Refinement)
CEGAR 的循环是:模型检查发现反例 → 检查反例是否为假 → 若是,沿反例回溯到抽象边界 → 细化该处抽象 → 重新检查。
MemTrace 的循环是:Agent 发现故障操作 o* → 检查 o* 涉及的可优化变量(prompt)→ "细化"该变量的值 → 重新执行验证。CEGAR 在抽象层面"细化",MemTrace 在 prompt 层面"优化"。
4.3 代码实现
归因由两个核心类协同完成:GraphTraceNotebook 管理图探索状态,GraphTraceAgent 驱动 LLM 推理。
GraphTraceNotebook (graphtrace/graph_trace_notebook.py:125)------管理探索状态:
python
class GraphTraceState: # graphtrace/models.py:167
to_explore_nodes: SortedDict[(created_at, full_node_id) → GraphTraceNodeRecord]
exploration_history: OrderedDict[full_node_id → GraphTraceNodeRecord]
关键方法对应论文的四个模块:
| 方法 | 论文模块 |
|---|---|
initialize_execution_graph(initial_full_node_ids) |
§4.1 起始点选择 |
explore_graph_trace_node(offset, limit) |
§4.2 图探索 |
view_node_value(full_node_id, pattern, offset, limit) |
§4.3 上下文管理 |
update_to_explore_nodes(action, full_node_id) |
§4.2 前沿管理 |
finish_node_exploration(outcome) |
§4.2 节点结论 |
finish_graph_exploration(outcome) |
§4.2 全局终结 |
GraphTraceAgent (graphtrace/graph_trace_agent.py:24)------驱动 LLM:
继承 agentscope.agent.ReActAgent,关键定制:
_reasoning()(:164):每个推理步骤前注入<system-hint>(来自DefaultGraphTraceToHint),内容取决于当前探索状态------未初始化则提示调initialize_execution_graph,有前沿则提示explore_graph_trace_node,正在探索则提示继续/扩展/完成_compress_memory_if_needed()(:189):超过max_context_limit(默认 600k)时用结构化输出压缩历史
操作子图渲染 (graphtrace/_utils/_helpers.py:9):
python
def _render_operation_subgraph(graph, op, focus_full_node_id, ...):
# 1. 确定焦点的角色:input / output / intermediate / disconnected
# 2. 列出直接后继变量
# 3. 列出子图中叶变量
# 4. 渲染规范子图(RuntimeGraph.to_xml())
渲染结果形如:
python
# Operation `o_123`
- Name: `LLMInference`
- Category: `fact_extraction`
- Focus Variable: `message@2`
- Focus Role: The focus variable is an input to this operation.
- Direct Successor Variables: `extracted_facts@3`
- Leaf Variables: `extracted_facts@3`
--- Subgraph ---
<graph>
<nodes>...</nodes>
<edges>...</edges>
<operations>...</operations>
</graph>
LLM 基于此做出判断:这个提取操作是否"在其职责范围内正确"?
4.4 探索策略对比
MemTraceConfig.exploration_strategy 支持两种策略:
| 策略 | 代码枚举 | 前沿初始化 | 适用场景 |
|---|---|---|---|
| 基于图探索 | "graph_search" |
通过混合检索获取起始证据节点 | 默认,图结构清晰 |
| 搜索式探索 | "operation_block_search" |
起始节点作为搜索索引而非边跟随起点 | 图结构弱/退化成长链 |
0x05 obstrace:搜索式消融变体
论文 §4.4 的 MemTrace-OBS:当执行图结构弱或退化为长链时,图的边跟随策略效率低。OBS 将每个操作子图压缩为扁平文本块,按时间戳串联,提供全局正则搜索工具。
5.1 设计原理:为什么去掉图结构?
当执行图结构弱(如长上下文记忆反复追加内容,图退化为一条链)时,边跟随效率低------Agent 每一步只能查看一个操作,而链上的操作几乎等价,逐个检查是浪费。
OBS 的核心假设:
如果操作名、变量值和注释已经能揭示大致的信息流和功能角色,Agent 可以通过全局搜索而不是边跟随来定位故障。
具体做法(obstrace/trace_notebook.py:82):
python
def flatten_execution_graph(network: ExecNetwork) -> str:
# 将每个操作子图渲染为文本块
# 移除:边和唯一变量标识符
# 保留:操作名、注释、类别、输入/输出/中间变量值
# 按时间戳排序,用 "### Operation ..." 分隔符串联
这创建了一个无 def-use 链的扁平中间表示------类似编译器中的线性 IR(栈式字节码或三元式序列),数据依赖隐含在操作顺序中,而非显式的图边。
OBS 的有效性 取决于一个条件:当操作本身的语义足够丰富时(操作名如 LLMInference:fact_extraction、注释如 "Extract key facts from user message"、变量值如 ["User likes hot cocoa"]),Agent 可以通过语义理解来推断信息流,而不需要依赖图结构。
5.2 为什么这是一个有效消融?
两者使用完全相同的 Agent 框架(ReAct)、同一个 LLM backbone、同一组归因指令,唯一差异是信息组织方式。因此性能差异直接量化了图结构对归因的贡献:
| 维度 | graphtrace(MemTrace) | obstrace(MemTrace-OBS) |
|---|---|---|
| 信息结构 | 有向无环二分图 | 扁平的线性文本 |
| 数据依赖 | 显式边(source→target) | 隐含在操作顺序中 |
| 探索方式 | 边跟随(逐操作正向检查) | 全局正则搜索(直接跳到匹配处) |
| 变量标识 | name@version |
无唯一标识,纯文本内容 |
论文表 1 的关键发现:
- GPT-4.1 mini:MemTrace ETA 36.46% vs OBS 20.00%(+16.46pp)→ 小模型依赖图结构缩小搜索空间
- GPT-5.4:MemTrace ETA 54.38% vs OBS 53.75%(接近)→ 大模型语义弥补了结构缺失
- 但 OIA 差距仍存在(46.25% vs 38.13%)→ 精确定位操作仍需图结构
OBS 的操作搜索工具 (trace_index.py:107 的 grep)支撑了这种消融设计:
python
def grep(self, pattern: str, fields: Iterable[str] | None = None,
regex: bool = True, max_results: int = 10) -> list[GrepMatch]:
"""跨所有操作块的文本搜索,返回匹配的操作 ID 和上下文"""
如果 Agent 怀疑"hot cocoa"关键词在提取阶段丢失,它可以直接搜索,而不是从输入一步步追踪到提取操作。这提供了更灵活的探索路径,代价是失去了结构约束带来的准确性保证。
5.3 编译器领域的类比
SSA vs 线性 IR:
| 编译器 | 记忆系统 |
|---|---|
| SSA(图结构,def-use 链显式) | MemTrace(执行图,边显式) |
| 线性 IR(三元式,依赖隐含) | OBS(扁平日志,依赖隐含) |
| 循环不变式外提沿 def-use 链分析 | 边跟随追踪信息流 |
| 窥孔优化在指令序列上模式匹配 | 正则搜索在日志上定位操作 |
断言式调试 vs 顺序调试:
- 顺序单步调试(图探索):逐语句执行,观察状态变化
- 断言式调试(OBS 搜索):设置条件断点(正则),命中时检查上下文
OBS 的 grep 工具(obstrace/trace_index.py:107)就是 Agent 的"条件断点"------Agent 基于对错误类型的假设("可能是提取阶段把热可可弄丢了"),直接跳转到对应阶段检查。
控制流图中的可达性分析
编译器在不可达代码消除(dead code elimination)中,从入口节点沿 CFG 的边做可达性分析,标记不可达的基本块。
MemTrace 的边跟随是"逆可达性分析"------从失败输出反向上溯,标记哪些变量/操作参与了失败路径。OBS 的搜索则像基于断言的检查------设置断点条件(正则表达式),命中时检查该位置的上下文。
5.4 核心组件
python
# TraceIndex(obstrace/trace_index.py:35)
# 解析扁平化日志文本,支持:
index = TraceIndex(ops_text)
index.get_op("o_42") # 按 ID 取单个操作块
index.grep("hot cocoa", # 跨块正则搜索
fields=["input", "output"],
max_results=10)
# CCTraceNotebook(obstrace/trace_notebook.py:191)
# 包装 TraceIndex,注册四个 Agent 工具:
notebook = CCTraceNotebook(graph)
notebook.inspect_trace(range) # 查看一段连续的操作日志
notebook.grep_log(regex) # 全局搜索
notebook.view_log_window(offset) # 日志分页
notebook.view_operation(op_id) # 查看单个操作详情
# ObsTraceAttributionAgent(obstrace/agent.py:47)
# 返回 AttributionPrediction = {error_type, op_id, reason, warning, trace_stats}
0x06 自动优化:从归因信号到系统改进
归因的目的是改进。这是 MemTrace 最独特的价值------不仅告诉你哪里错了,还能自动帮你修。
目前的实现只优化 LLM 提示词。
6.1 原理:文本梯度
文本梯度的本质是用 LLM 模拟反向传播,具体做两件事:(1) 分析失败原因并生成改进建议;(2) 根据建议改写目标文本。
| 神经网络训练 | MemTrace 优化 |
|---|---|
| 前向传播:模型计算输出 | 前向:执行记忆系统 → 得到 QA 结果 |
| 损失函数:比较输出与标签 | LLM Judge:判定回答是否正确 |
| 反向传播:计算 ∂L/∂w | MemTrace 归因:定位故障操作 o* |
| 梯度:∂L/∂w(数值、连续) | 文本梯度:"故障分析 → 改进建议"(语义、离散) |
| 优化器:SGD 更新 w | 优化器:LLM 改写 prompt |
| 动量:v ← αv - η∇L / 历史梯度加权平均 | 梯度历史:deque(旧值, 聚合反馈) |
关键差异:数值梯度在连续空间中有明确定义的"方向"和"步长";文本梯度在自然语言空间中,LLM 充当了"语义微分器"------输入失败信号和操作上下文,语义上推断 prompt 应该向哪个方向改进。
这本质上是一种符号优化------类似遗传规划中语义导向的变异操作,而不是数值优化。
6.2 三阶段代码实现
阶段一:生成反馈 (optimization_utils.py:833)
对每个归因失败案例,构造如下 prompt:
python
FEEDBACK_INSTRUCTION_TEMPLATE:
你的任务是为改进目标变量值生成反馈。
输入:
- 目标变量信息(值、注释、类别等)
- 失败案例(归因结果 + 操作子图 XML)
- 历史梯度(前几轮的优化历史)
优化边界:
- 操作流程不可优化
- 只有目标变量的值可优化
- 每一条反馈必须是对目标变量值的具体改进建议
输出 _FeedbackSuggestion({rationale, feedback}):
python
class _FeedbackSuggestion(BaseModel):
rationale: str # 思考过程
feedback: str # 具体改进建议
其中 _format_failed_case(:488)将归因结果渲染为 LLM 输入:
python
def _format_failed_case(error, graph):
op_graph = graph.filter_by_operation(error.op_id)
return (
f"Error type: {error.error_type}\n"
f"op_id: {error.op_id}\n"
f"Reason: {error.reason}\n\n"
"Attributed operation graph:\n"
f"{op_graph.to_xml(include_metadata=False)}"
)
阶段二:聚合反馈 (:845)
多条失败案例归因到同一目标变量时,产生多条反馈。聚合策略是分层分批摘要:
python
if len(feedbacks) == 1:
return feedbacks[0], 0.0 # 单条直接返回
summary = await summarize_batch(feedbacks[:batch_size])
for batch in remaining_batches:
summary = await summarize_batch([summary, *batch]) # 滚动聚合
这类似 MapReduce 或梯度累积------多条"梯度"不被平均,而是被 LLM 语义融合。输出 _AggregatedFeedbackSuggestion。
阶段三:更新目标 (:910)
python
UPDATE_INSTRUCTION_TEMPLATE:
根据聚合反馈改进目标变量值。
与反馈无关的部分应尽量保留。
输出:_TargetVariableUpdate = {rationale, improved_value}
更新约束(模板 :123-125)保证局部性------只改动与梯度相关的部分,对应神经网络中的参数选择性更新。
6.3 梯度历史机制
python
# optimization_utils.py:575
self._gradient_histories = defaultdict(
lambda: deque(maxlen=num_gradient_histories)
)
每次更新后存储 (previous_target_value, aggregated_feedback)。在后续轮次注入到反馈 prompt 的 {history_input_section} 中:
python
def _format_history(history):
for i, (prev, fb) in enumerate(history, 1):
yield f"Previous value (iter -{i}):\n{prev}"
yield f"Feedback from that iteration:\n{fb}"
这对应优化算法中的动量项------在深度学习中,SGD with Momentum 维护速度向量 v ← αv - η∇L;在文本优化中,历史反馈帮助 LLM 避免反复犯同样的优化错误。
6.4 跨领域类比
类比一:AutoTVM / 编译器自动调优
AutoTVM 是 TVM 深度编译器中的自动调优框架,核心流程:
程序 → 搜索空间定义 → 采样配置 → 在硬件上运行 → 收集性能数据 → 更新模型 → 采样更好配置
这与 MemTrace 优化高度相似:
| AutoTVM | MemTrace 优化 |
|---|---|
| 搜索空间定义 | PROMPT_TARGETS 注册 |
| 采样配置并运行硬件评测 | 执行记忆系统 + LLM Judge |
| 收集性能数据 | 收集归因结果 |
| 成本模型预测更好配置 | LLM 生成改进反馈 |
| 更新模型参数 | 聚合反馈 + 更新 prompt |
类比二:Profile-Guided Optimization (PGO)
编译器 PGO 三步走:插桩编译 → 代表性输入运行 → 收集 profile 数据 → 反馈指导优化。
MemTrace 优化:启用 smartcomment 追踪 → 在 LoCoMo 上运行 → 收集失败执行图 → 归因指导 prompt 更新。
类比三:RL 信用分配
RL 的核心问题:当智能体在 t=10 收到奖励时,t=1~t=9 中哪些动作有贡献?
- 蒙特卡洛:整条轨迹累积奖励,每个动作等分信用
- TD(λ):资格迹按时间折扣分配信用
- MemTrace:执行图精确追踪,故障操作获得全部信用(credit=1),其他操作 credit=0
_format_history 等价于 RL 中的资格迹------旧轮次的反馈以衰减后的形式持续影响当前决策。
类比四:与 TextGrad 的对比
TextGrad 是直接对文本做"反向传播"的框架------用 LLM 计算文本节点对损失函数的"梯度"(自然语言描述),然后沿计算图反向传播更新。
TextGrad 要求整个系统是"纯文本可微"的------所有中间结果必须是文本,所有操作必须是 LLM 调用。MemTrace 的优势在于不需要整个系统可微:smartcomment 录制异构执行图(包含检索、过滤、字符串操作等非 LLM 操作),归因定位故障后只在局部做文本梯度更新------只要归因能找到故障操作,将该操作的输出变量(prompt)视为可优化文本即可。
6.5 当前的边界:为什么只优化了 prompt?
run_optimization.py:53 的 PROMPT_TARGETS 硬编码了三个变量:
python
PROMPT_TARGETS = {
"custom_fact_extraction_prompt": "fact-extraction-system-prompt",
"custom_update_memory_prompt": "memory-update-decision-prompt",
"question_answering_prompt": "question-answering-prompt",
}
group_attributions(:320)检查每个归因结果的操作子图是否涉及这三个变量------若不涉及,则归入 unoptimizable 列表,静默丢弃。
这是合理但并不充分的"低垂果实"策略:
| 理由 | 说明 |
|---|---|
| 优化工具现成 | LLM 改写文本是最自然的操作 |
| prompt 确实敏感 | 调好它们就带来 7.62% 的提升 |
| 边界是自限而非系统限制 | 模板的"Only the value is optimizable"是给 LLM 的约束,不是架构约束 |
0x07 toolkits:连接归因与优化的桥梁
以上三部分解决了"如何录制执行图"和"如何在图上归因"的问题。接下来,归因结果如何被消费?这由 toolkits/ 提供。
7.1 体系概览
python
_base.py
├── _ApiPool(轮询凭证池,多 API key 负载均衡)
└── AgentBaseConfig / AgentBaseRunner(基础配置)
bench_utils.py
├── FailedQueryCase(失败查询的数据模型)
├── collect_failed_queries(exec_network) → list[FailedQueryCase]
└── load_memtracebench() → (graphs, failed_cases)
memtrace_utils.py ← 归因核心
├── ATTRIBUTION_INSTRUCTIONS(Agent 推理指令)
├── ErrorAttributionPrediction(归因输出 schema)
├── DocumentRetriever(BM25 + 稠密 + RRF)
└── MemTraceRunner(异步归因运行器)
optimization_utils.py ← 优化核心
├── OptimizerRunner(三阶段文本梯度)
└── FEEDBACK / AGGREGATION / UPDATE 模板
error_analysis_utils.py ← 报告核心
└── ErrorAnalysisReportRunner(迭代式报告)
7.2 归因指令的完整推理范式
ATTRIBUTION_INSTRUCTIONS(memtrace_utils.py:98)定义了 Agent 必须遵循的推理步骤:
第一步:理解操作子图结构
- 子图中入度 0 的变量 = 操作的输入
- 出度 0 的变量 = 操作的最终输出
- 通过操作名、类别、注释、变量值判断操作的功能角色
第二步:对照"理想行为"
- 提取阶段:关键信息应进入记忆
- 更新/删除阶段:关键信息应被保留
- 检索阶段:包含关键信息的记忆应被命中
- 回答阶段:LLM 应基于检索结果正确回答
第三步:应用"最早决定性故障"准则
- 局部正确性:操作仅当其自身职责范围内的输出错误时才标记为故障
- 决定性:修正该操作 + 假设下游完美 → 能挽救失败
- 最早:按执行顺序第一个满足条件的操作
- 优先性:RAG 先查检索 → Mem0 先确认提取 → EverMemOS 先验分段质量 → Long-Context 先查上下文裁剪
第四步:映射到五类错误
7.3 混合检索机制
DocumentRetriever(memtrace_utils.py:480)用于当标注的源证据不可用时,从执行图的 category="message" 节点中检索起始证据:
python
# 构建检索查询 = question + "\n" + golden_answer
# 三种检索模式:
sparse_retriever = BM25Retriever() # rank_bm25
dense_retriever = EmbeddingRetriever() # OpenAI-compatible API + QdrantStore
hybrid_retriever = RRFFuser( # Reciprocal Rank Fusion
retrievers=[sparse_retriever, dense_retriever],
k=N//2
)
0x08 扩展讨论:归因与优化的解耦架构
前面我们看到当前的 prompt-only 优化只是"低垂果实"。现在我们来讨论归因与优化在架构层面是天然解耦的------归因定位任意故障操作,优化的本质是"修改该操作涉及的某个可配置变量的值"。这两种能力之间没有硬性耦合。
8.1 为什么"可以优化"?
归因输出是一个具体操作 ID(如 o_42)和错误类型。通过 graph.filter_by_operation("o_42") 得到操作子图,子图中包含了该操作涉及的所有变量------包括可写的配置变量。因此优化就是:
- 可定位 :
filter_by_operation(o*)→ 子图 → 包含可优化变量 - 可修改:文本→LLM 改写,数值→参数更新,枚举→A/B 切换
- 可验证:修改后重跑记忆系统 → 重做归因 → 比较前后错误率
8.2 哪些错误是当前版本"未处理"的?
根据 group_attributions 的逻辑,以下归因结果会被丢进 unoptimizable:
| 归因类型 | 实际原因 | 可能的优化手段 |
|---|---|---|
RetrievalError 但检索非 prompt 驱动 |
Top-k 太小、embedding 模型差、reranker 阈值不对 | 超参搜索(贝叶斯、网格) |
UpdateError 源于 buffer 管理逻辑 |
Buffer size 过大/过小,合并策略不对 | 代码策略调整或参数优化 |
DeletionError 源于删除策略 |
TTL 太短、删除条件太宽松 | 配置参数调整 |
ExtractionError 源于 chunk 策略 |
Chunk size 不合理、重叠策略不对 | 超参优化 |
ResponseError 但检索上下文完备 |
LLM backbone 能力不足 | 模型替换或 prompt 优化(后者已覆盖) |
8.3 可以扩展到哪些类型的变量?
记忆系统出错的原因多种多样,提示词只是冰山一角。MemTrace 的归因已经能定位到任意环节的问题,优化的扩展只是工程实现问题,而非设计限制。
| 变量类型 | 示例 | 优化策略 | 前提 |
|---|---|---|---|
| 文本型 | prompt 模板 | LLM 文本梯度(已实现) | LLM 调用作为优化算子 |
| 数值整型 | top_k@1, chunk_size@1 |
贝叶斯优化 / 离散搜索 | 定义搜索空间 + 评估函数 |
| 连续值 | temperature@1, similarity_threshold@1 |
贝叶斯优化 / 网格搜索 | 同上 |
| 枚举型 | retrieval_strategy@1 = |
Bandit / A/B | 候选集 + 切换机制 |
| 布尔型 | enable_reranker@1 |
消融比较 | 开关机制 |
| 可组合型 | 多个变量共同导致一个错误 | 多目标优化 | 联合搜索空间 |
8.4 架构上如何扩展?------归因与优化本应是解耦的
MemTrace 的核心贡献在归因 (credit assignment),优化是下游消费端。架构上,优化器只需替换为不同策略即可。
当前的 OptimizerRunner 接受 errors: dict[str, list[ErrorAttributionPrediction]],key 是"目标变量在图中对应的 full_node_id"。这个设计是通用的------任何被 smartcomment 录制的变量,只要在执行图中能定位到,都可以作为优化目标。
扩展的思路:
python
归因输出:op_id="o_42", error_type="RetrievalError"
↓ graph.filter_by_operation("o_42") → 操作子图
↓ 子图中包含哪些变量?
│
├─ fact-extraction-system-prompt (文本) → 文本梯度优化(已有)
│
├─ top_k@3 (整数数值) →
│ 优化策略:贝叶斯优化 / 网格搜索 / △top-k ± 5 闭式试探
│ 前提:需要定义搜索空间 + 评估指标
│
├─ similarity_threshold@1 (浮点数) →
│ 优化策略:二分搜索 / 贝叶斯优化
│
├─ reranker_model@1 (枚举:cross-encoder vs cohere vs none) →
│ 优化策略:A/B 测试 / 消融
│
└─ buffer_max_tokens@2 (整数) →
优化策略:梯度试探 + 效果回测
要实现这点,只需:
- 在
PROMPT_TARGETS之外定义PARAM_TARGETS或其他优化目标注册表 - 为不同数据类型实现不同的优化算子(文本→LLM 改写,数值→搜索,枚举→A/B)
- 在
group_attributions中检查所有注册表,而不是只查PROMPT_TARGETS
核心思路 :为每种变量类型注册一个优化算子,group_attributions 检查归因结果中涉及的变量类型,分派给对应算子:
python
OPTIMIZABLE_VARIABLES = {
"prompts": {
"fact-extraction-system-prompt@3": TextGradientOptimizer(),
},
"params": {
"top_k@1": BayesianOptimizer(bounds=(1, 50), discrete=True),
},
"configs": {
"retrieval_strategy@1": BanditOptimizer(
choices=["sparse", "dense", "hybrid"]
),
},
}
这样,每增加一种优化策略,unoptimizable 列表就缩窄一部分。
8.3 自动化的边界在哪里?
当前已经是自动闭环------不需要人工干预即可完成"采样 → 执行 → 归因 → 优化 → 下一轮"。
python
1. 采样 trajectory ──────────→ 2. 执行记忆系统 ──→ 3. 收集失败案例
↓
6. 更新配置并进入下一轮 ←─── 5. 三阶段优化 ←── 4. MemTrace 归因
但真实的"自动程度"受限于:
- 归因准确率:OIA 最高 46.25%------约一半的归因定位错了操作。但只要信号的有用信号占比足够高,就能推动改进(论文 7.62% 提升证实了这点)
- 优化空间自推导 :当前
PROMPT_TARGETS是硬编码的。完全自动需要系统扫描执行图发现所有可写变量、自动推断类型、匹配合适优化器------类似 AutoTVM 的搜索空间自动推导 - 验证闭环开销:每轮需完整执行构造+检索+评测流水线,计算成本较高。可引入 early stopping(本轮无新失败 → 提前终止)
0x09 一条主线,三个解耦
最后用一个总结收束全文。MemTrace 整个项目围绕一条主线 ,实现了三个层次的解耦:
一条主线 :录制 → 归因 → 优化,形成一个完整的闭环。
三个解耦:
| 层次 | 解耦了什么 | 为什么重要 |
|---|---|---|
| 追踪与分析 | smartcomment 录制执行图,graphtrace/obstrace 在图上分析 | 更换记忆系统只需重新插桩,归因逻辑无需修改 |
| 归因与优化 | 归因只回答"哪里错了",优化决定"怎么修" | 归因可以独立改进(更高的 OIA),优化可以扩展(更多的变量类型) |
| 优化策略与变量类型 | 文本梯度、贝叶斯、Bandit 等都是"优化算子",按变量类型注册即可 | 每增加一种优化策略,就有一部分 unoptimizable 变为可优化 |
这种解耦架构使得 MemTrace 虽然当前只优化了三个 prompt 变量,但框架层面已经为扩展到任意变量类型做好了准备------归因已经能定位到任意操作,剩下的只是在优化端注册对应的优化算子。
附录
附录A 入口脚本:各组件如何协作
四个入口脚本展示了上述组件如何被组合调用:
A.1 run_error_attribution.py
ascii
load_memtracebench(split)
→ [(ExecNetwork, FailedQueryCase), ...]
↓
MemTraceRunner.arun() # 对每个 case 异步归因
├─ DocumentRetriever.retrieve() # 或使用标注源证据
├─ GraphTraceNotebook.initialize_execution_graph()
├─ GraphTraceAgent._reasoning() # ReAct 循环 (≤200 轮)
│ └─ notebook.explore_graph_trace_node()
│ └─ notebook.update_to_explore_nodes()
│ └─ notebook.finish_node_exploration()
│ └─ notebook.finish_graph_exploration()
└─ → ErrorAttributionPrediction{error_type, op_id, reason}
↓
evaluate() → {ETA, OIA, ...}
output: JSON(per-case + aggregate metrics)
缓存策略 (memtrace_utils.py:796):cache_dir / sha256(graph_id + case_id + config).json。避免重复归因。
A.2 run_optimization.py
ascii
for iteration in 1..sample_size:
├─ load_dataset().sample(1) # 采一个 LoCoMo trajectory
├─ ConstructionRunner.run() # + smartcomment 追踪
├─ SearchRunner.run() # + smartcomment 追踪
├─ EvaluationRunner.run() # + smartcomment 追踪
├─ collect_failed_queries() # 从执行图中提取失败 QA
├─ MemTraceRunner.arun() # 归因每个失败 case
├─ group_attributions() # 归因 → prompt target 分组
└─ OptimizerRunner.arun() # 三阶段优化
├─ generate_feedback() # 每 case → _FeedbackSuggestion
├─ aggregate_feedbacks() # 分组内聚合
└─ update_target() # _TargetVariableUpdate
↓
save(): iteration_N/{memory_system_config, feedbacks, summary, ...}
A.3 generate_report.py
ascii
load_cases(path) # JSON: [{query, pred, golden, attribution, graph_path}]
↓
ErrorAnalysisReportRunner.arun()
├─ 每个 case: graph.filter_by_operation(op_id).to_xml()
├─ 分批送入 LLM
└─ 迭代式报告生成
↓
output: error_analysis_report.json
A.4 eval_retrieval_performance.py
评估伪证据检索的 recall@k:
query = question + "\n" + golden_answer
corpus = graph.nodes where category == "message"
retrieval = sparse | dense | hybrid
metric = recall@k (ground truth source evidence in top-k?)
附录B annotation_interface:可视化标注
Streamlit 前端,供人工检查和标注执行图上的错误。核心视图:
- Timeline:Plotly 蛇形布局的消息流时间线
- Variable Relation:点击变量节点 → 1-hop BFS 关系子图
- Error Attribution:自动归因流水线 + 手动标注表单
标注支持七种错误类型(annotation_interface/components/error_attribution_view.py:38):AnnotationError、LLMAsAJudgeError、ExtractionError、UpdateError、DeletionError、RetrievalError、ResponseError。结果追加到 --output-path 指定的 JSON 数组。
附录C 项目文件全景
python
MemTrace-main/
│
├── smartcomment-main/ ← vendored smartcomment 0.1.2(追踪层)
│ └── smartcomment/
│ ├── api/public.py --- comment_variable/link/mutation/op/fn
│ ├── runtime/ --- ExecNetwork / RuntimeGraph / 上下文 / 变量 / 操作
│ ├── schema/ --- Pydantic 数据模型
│ ├── drivers/ --- GraphDriver 抽象 + InMemoryDriver
│ └── identity/ --- IdentityRegistry
│
├── graphtrace/ ← MemTrace 图归因 Agent
│ ├── graph_trace_agent.py --- GraphTraceAgent(ReActAgent 子类)
│ ├── graph_trace_notebook.py --- GraphTraceNotebook + DefaultGraphTraceToHint
│ ├── models.py --- GraphTraceNodeRecord + GraphTraceState
│ └── _utils/_helpers.py --- _render_operation_subgraph + _paginate_text
│
├── obstrace/ ← MemTrace-OBS 消融变体
│ ├── agent.py --- ObsTraceAttributionAgent
│ ├── trace_notebook.py --- CCTraceNotebook(扁平日志工具)
│ ├── trace_index.py --- TraceIndex(日志解析 + grep 搜索)
│ └── models.py --- OperationBlock + GrepMatch
│
├── toolkits/ ← 桥梁层
│ ├── memtrace_utils.py --- ATTRIBUTION_INSTRUCTIONS、DocumentRetriever、MemTraceRunner
│ ├── optimization_utils.py --- 三阶段优化器、PROMPT_TARGETS、梯度历史
│ ├── error_analysis_utils.py --- 迭代式报告生成器
│ ├── bench_utils.py --- MemTraceBench 加载、collect_failed_queries
│ └── _base.py --- _ApiPool、AgentBaseConfig/BaseRunner
│
├── membase/ ← 记忆系统框架(被追踪的目标)
│ ├── layers/ --- 8 种记忆系统适配器
│ ├── runners/ --- 构造/检索/评测流水线(含 smartcomment 插桩)
│ ├── baselines/ --- vendored 记忆系统源码
│ ├── datasets/ --- 数据集加载器
│ └── evaluation/ --- 评测指标(TokenF1, BLEU, LLMJudge...)
│
├── annotation_interface/ ← Streamlit 可视化标注前端
│
├── run_error_attribution.py --- 归因入口
├── run_optimization.py --- 优化入口
├── generate_report.py --- 报告生成入口
├── eval_retrieval_performance.py --- 检索评估入口
│
└── my-docs/ --- 本文档