189 lines
11 KiB
Markdown
189 lines
11 KiB
Markdown
# 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. 测试方法(以及两次差点误判的地方)
|
||
|
||
1. **先量基准,再判模型**。所有数字(文件数、磁盘可用、文件首行)都在跑之前用 PowerShell 独立量出,
|
||
绝不用模型的输出去验证模型。
|
||
2. **跨会话必须换进程**。同一进程内的追问可能靠对话上下文答对,**不能证明是记忆在起作用**——
|
||
凡是声称"记忆有效"的结论,都用**全新进程、上下文为空**复现过。
|
||
3. **测延迟前必须查显卡**。我的设施和用户的评测共用一块 RTX 5070。
|
||
第十一轮长跑里,每轮生成从 12.7 秒"涨到"137.6 秒,而 prompt 词元只涨 1.7 倍——
|
||
比例不对,去查显卡才发现是**用户自己的 `locomo_nm21_e2e` 评测占满 100%**。
|
||
**那是争用,不是会话退化。** 差点当成性能衰减报出去。
|
||
4. **测量 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. 未完成与复现
|
||
|
||
### 未完成
|
||
|
||
1. **长时间稳定性**:24 轮只跑完 11 轮,因用户评测占满显卡而中止;**重跑前先查显卡**。
|
||
2. **更正修复的生产形态复跑**(上轮同样被显卡占用打断)。
|
||
3. **会话卫生**:`agent.state.messages` 替换实测未生效(下一轮仍复读旧对话),需换一条能真正清尾的路子。
|
||
4. **两代模型对照**:全程只用 NM2.0,NM2.1 未做同一套生产测试(代码共用,但权重不同)。
|
||
|
||
### 复现
|
||
|
||
```powershell
|
||
# 控制面(占卡;先确认显卡空闲!)
|
||
$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、会给没出处的数字。
|
||
这三条目前都是**用工具设计绕过去**的,不是修好了。
|
||
- **一个可推广的经验**:给小模型铺一条"正确且省事"的路(窄接口工具 + 可直接照抄的输出),
|
||
比反复叮嘱它别犯错有效得多——编造就是这么消失的。
|