11 KiB
NM2 模型 + Agent 生产级测试报告
测试对象:
H:\Memory\V2_dpskw\qwen3_5_4b_natural_memory_v2(NM2.0) 测试方式:真任务,不是合成场景;每条结论都配一台机器上可复现的客观基准 测试期:2026-09-15(Agent 侧来自本次会话的多轮实测) 被测设施:agent_lab/model_server/server.py(控制面)+agent_lab/agent(Pi Agent,11 个工具)
0. 一句话结论
记忆这层已经站住了;挡在"每天都要用"前面的是模型侧的行为,不是记忆检索。
七个测试族里 六个可判定通过(各带基准),长时间稳定性因显卡被占用未测完。 过程中修掉 1 个模型包真 bug(属性抽取把盘符冒号当分隔符)、1 个沿用已久的结构性缺陷(更正无法版本化 Agent 写入的记录), 以及若干 harness 缺陷;并记录了我自己三次方法错误,供复核。
1. 测试方法(以及两次差点误判的地方)
- 先量基准,再判模型。所有数字(文件数、磁盘可用、文件首行)都在跑之前用 PowerShell 独立量出, 绝不用模型的输出去验证模型。
- 跨会话必须换进程。同一进程内的追问可能靠对话上下文答对,不能证明是记忆在起作用—— 凡是声称"记忆有效"的结论,都用全新进程、上下文为空复现过。
- 测延迟前必须查显卡。我的设施和用户的评测共用一块 RTX 5070。
第十一轮长跑里,每轮生成从 12.7 秒"涨到"137.6 秒,而 prompt 词元只涨 1.7 倍——
比例不对,去查显卡才发现是用户自己的
locomo_nm21_e2e评测占满 100%。 那是争用,不是会话退化。 差点当成性能衰减报出去。 - 测量 setup 必须复现真实时序。写入路径上"本轮的事实"在生成前还没进库, 我一度把两条记录都写进库再测,量到的是另一个问题。
2. 逐族结论
| # | 测试族 | 判定 | 客观基准 | 实测结果 |
|---|---|---|---|---|
| 1 | 真实文件理解 | ✅ 通过 | WRITE_PATH_FIX.md(4023 字符) |
读完正确概括根因(confirmed_update 依赖学习打分 0.95、缺 (entity,attribute) 结构校验 → 误删) |
| 2 | 真实命令与数据统计 | ✅ 通过(修 harness 后) | artifacts 顶层 46 个;V2_dpskw 递归 .py 133 个;C 盘可用 34.47 GB |
list_files→46 ✓、list_files{recursive}→133 ✓、disk_space→34.52 GB ✓ |
| 3 | 跨会话记忆支撑真活 | ✅ 通过 | 新进程、上下文为空 | 召回命中(用记忆=要 · 命中 2 条 · 205 词元)→ 调工具 → 答对目录 |
| 4 | 更正必须生效 | ✅ 通过(经三轮修复) | 旧值应退位 | 改了:… → 旧记录 superseded、新进程答新路径;回归场景无污染 |
| 5 | 不知道时拒答 | ✅ 通过 | 无 | 股票 →「我没有你购买的股票信息」;模型参数量 →「我不知道。没有证据表明…」 |
| 6 | 多步工具链 | ✅ 通过(修 harness 后) | 9 个 client-*.txt;首个 client-api-check.txt 首行 PASS /api/send 返回成功 |
两步全对,17.7 秒 |
| 7 | 长时间日用稳定性 | ⚠️ 未测完 | —— | 24 轮只跑完 11 轮;已完成轮次全部答对,但耗时数据因显卡争用作废 |
3. 修掉的缺陷
3.1 模型包:属性抽取把盘符冒号当分隔符(真 bug)
infer_memory_metadata 的操作符列表含裸冒号:(?:…|是|为|叫|:|:|=)。
句子「我的评测报告放在 E:\deepseek\artifacts」用的是「放在」而非「是」,正则于是抓到盘符那个冒号:
属性='评测报告放在 E' 取值='\deepseek\artifacts'
后果:同一件事被归档在永远无法与 Agent 写的 评测报告路径 匹配的键下,旧值永远无法被取代。
修法:冒号前是单个 ASCII 字母时不作分隔符((?<![A-Za-z])[::])。CJK 后的冒号仍是正常分隔符。
修复后路径句返回 episode(宁可不给属性,也不给错属性)。
3.2 模型包:更正无法版本化「经接口写入」的记录(结构缺陷)
「措辞变了也要版本化」的补救路径按 slot_index 匹配继承对象;
而 Agent 经接口写入的记录 slot_index = -1(独立分页项,不占遗留槽位)。
凡是 Agent 自己写进记忆的事实,自动层永远无法把它版本化。
该段的注释本身写着它的来历(realistic 语料上 25/25 更新失败)——同一个 bug 类被修过一次,但修法绑死在槽位上。
修法:新增一条继承路径,但必须同时满足两道门:
| 门 | 理由 |
|---|---|
not candidate_conflict_key |
解析器给出结构时一律不猜(原有代码就有这道门) |
| 有身份记录自己的绝对分数 ≥ 5.0 | 实测两簇:真更新 8.591/8.6325/8.64,无关 ≤0.682/0.7421 |
为什么不用 score_margin(我第一版的做法):margin = top − second,
而自动层每一轮都会插一条近似重复的记录,把 margin 从 8.591 砸到 0.1523——
那道门正好卡在它本该放行的那一类上。
3.3 我自己的三次方法错误(如实记录)
| # | 错误 | 后果 | 纠正 |
|---|---|---|---|
| 1 | 继承块漏了 not candidate_conflict_key 这道门 |
用读取器的"最近邻猜测"覆盖了解析器给出的正确结构,把 默认语言=日语 污染成 蓝鲸-47 |
已回退并补门;用仪表复现确认机理 |
| 2 | 标定正类用了「我的A改成 v2」 | 改成 本身会被结构解析,那批样本永远走不到继承块——population 标错了 |
按正确 population 重测,两簇间隔 0.74 vs 8.63 |
| 3 | 测延迟时未查显卡 | 把争用当成"会话退化"(12.7s→137.6s) | 已作废该数据并记录纪律 |
3.4 harness 缺陷(Agent 侧)
| 缺陷 | 实测 | 修法 |
|---|---|---|
| 模型手写 PowerShell 必翻车 | -eq 'C:' 比字符 → 成功但零输出 → 同一命令重发 10+ 次 → 会话被污染 |
新增窄接口工具 disk_space / list_files / peek_files,模型只填路径 |
| 工具输出只给裸文件名,逼模型自己拼路径 | 它拿到 9 个正确文件名后编了个 client-0001.txt |
list_files 每项返回完整路径 |
| 静默零输出无法与"还没查到"区分 | 模型陷入重试 | 零输出时明确解释"多半是过滤条件没匹配、不要再发同一条" |
| 编造:问数量却跳过工具直接给数字 | 磁盘答 100 GB(真值 34.62);文件数答 0(真值 46) | 编造检测 + 强制重问(问题问数量、零工具调用、回答含数字 → 追加一轮要求先调工具) |
| 一次坏回合污染整段会话 | 失败命令的污染带进下一轮(99 秒) | 熔断改同步 abort();会话卫生仍待查实(替换 agent.state.messages 实测未生效) |
| 关掉记忆并不等于对照组 | memory_mode=off 只关自动路径,显式工具调用照样能读写记忆 |
所有写入端点在该模式下 409 拒绝 |
4. 一个反直觉的结论
修好"编造"的不是检测器,而是窄接口工具。
加了 disk_space/list_files 之后,同一个跨会话场景(目录只存在于记忆里)它自己调了工具并答对 46
(此前答 0,一次工具没调)。合理解释:以前要么手写命令(易错)要么干脆不查;有了省事的正路,它就走正路。
检测/强制重问至今没在实战中触发过,属兜底。
推论:面对小模型,先把正确路径铺平,比教它不要犯错更有效。
5. 日用可用性判定
| 能力 | 判定 |
|---|---|
| 单步 + 窄接口工具 | ✅ 可用(5–6 秒,答对) |
| 多步链路(工具给完整路径) | ✅ 可用(17.7 秒,两步都对) |
| 跨会话记忆召回 | ✅ 可用 |
| 拿不到就说拿不到 | ✅ 可用 |
| 自然措辞的更正(「改了…」) | ✅ 可用(经三轮修复后) |
| 长时间(数十轮)稳定 | ⚠️ 未验证(需独占显卡) |
| 口语化、过短的更新(「我换到 X 了」) | ❌ 路由弃权,自动层不版本化(路由自身的门) |
| 自己拼路径 / 手写 PowerShell | ❌ 不可用(已用完整路径与窄接口工具规避) |
6. 按用户方针做的收敛
用户方针:「我希望更多配置由模型决定,外部不要传入那么多参数(必要的保留)」。
据此删掉了 Agent 面上一批覆盖模型已学判断的参数:nm2_write 的 importance/confidence
(检查点本就有学到的 importance 头)、config_set 的 read_threshold/min_read_margin/auto_*_threshold/
max_hops/coverage_*/top_k_* 等。保留机器硬约束(KV 预算、缓存、分块)。
HTTP 的 /v1/nm2/config 仍保留全部旋钮——那是研究脚本在用的,收敛的是 Agent 能看到、能传的参数。
7. 未完成与复现
未完成
- 长时间稳定性:24 轮只跑完 11 轮,因用户评测占满显卡而中止;重跑前先查显卡。
- 更正修复的生产形态复跑(上轮同样被显卡占用打断)。
- 会话卫生:
agent.state.messages替换实测未生效(下一轮仍复读旧对话),需换一条能真正清尾的路子。 - 两代模型对照:全程只用 NM2.0,NM2.1 未做同一套生产测试(代码共用,但权重不同)。
复现
# 控制面(占卡;先确认显卡空闲!)
$env:PYTHONPATH='H:\Memory'
& C:\Users\Administrator\miniconda3\envs\LLM\python.exe H:\Memory\agent_lab\model_server\server.py `
--model-path H:\Memory\V2_dpskw\qwen3_5_4b_natural_memory_v2 --port 8766 --no-auto-persist
# 控制面自检
& C:\Users\Administrator\miniconda3\envs\LLM\python.exe H:\Memory\agent_lab\model_server\selftest.py
# Agent 终端(真任务)
cd H:\Memory\agent_lab\agent
$env:NATURAL_MEMORY_API_KEY='local'
$env:NATURAL_MEMORY_BASE_URL='http://127.0.0.1:8766/v1'
bun run tui
# 脚本化多轮:bun run tui --session x --say '…' --say '…'
# CPU 测试(不占卡):92 项
cd H:\Memory
& C:\Users\Administrator\miniconda3\envs\LLM\python.exe -m unittest discover -s V2_dpskw\tests -t . -p 'test_*.py'
# margin / 分数判别力测量
& C:\Users\Administrator\miniconda3\envs\LLM\python.exe H:\Memory\agent_lab\measure_inherit_margin.py --rounds 3
逐轮原始记录
H:\Memory\agent_lab\RESULTS.md §6.10(第一轮到第十一轮),含每次的基准、命令、原始输出与当时的判断。
8. 结论
- 记忆层可以用了:跨会话召回、更正生效、拒答、遗忘,都在真任务上验证过,且带了防误伤的回归检查。
- 模型本身仍是瓶颈:它不会自己拼路径、写不对 PowerShell、会给没出处的数字。 这三条目前都是用工具设计绕过去的,不是修好了。
- 一个可推广的经验:给小模型铺一条"正确且省事"的路(窄接口工具 + 可直接照抄的输出), 比反复叮嘱它别犯错有效得多——编造就是这么消失的。