Files

148 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Natural Memory Agent Lab
为 **Natural Memory v2(Qwen3.5-4B + 模型内记忆层)** 量身定制的**测试 Agent 框架**。
基座是 **[Pi Agent Harness](https://github.com/earendil-works/pi)**(MIT)——用它的
`pi-agent-core`(带状态管理与事件流的 agent runtime)与 `pi-ai`(统一多 provider LLM API),
而不是自己重写一个 agent 循环。
## 为什么用 Pi 而不是别的
| 理由 | 事实 |
|---|---|
| **provider 接线是现成的** | `pi-ai` 已有 `openai-completions` 适配器,provider 配置原生支持 `baseUrl`;本机 DSH 的 Grok/GPT provider 就是这么接的。**不需要改 Pi 的代码** |
| **插桩是一等公民** | `agent.subscribe(event => …)` 提供 `agent_start / turn_start / message_* / tool_execution_start|end / turn_end / agent_end`;`beforeToolCall`/`afterToolCall` 可拦截与改写 |
| **多会话天然支持** | stateful agent + 独立 SQLite session backend;本框架每个会话新建 Agent(空上下文),而模型记忆持续存在——这正是被测能力 |
| 许可 | MIT(© 2025 Mario Zechner),改/fork 无约束 |
## 架构:进程与语言双重隔离
```
┌──────────────────────────────┐ HTTP (OpenAI protocol) ┌────────────────────────────┐
│ model_server/server.py │ ◄───────────────────────────────────► │ agent/ (Node + Bun) │
│ 【LLM conda 环境】 │ /v1/chat/completions (tools) │ pi-agent-core + pi-ai │
│ torch 2.9/cu128 原样不动 │ /v1/memory/* /v1/diagnostics │ 自己的 node_modules │
│ 复用 natural_memory_service │ │ 工具 / 场景 / 记录 │
└──────────────────────────────┘ └────────────────────────────┘
```
模型侧留在原有的 `LLM` conda 环境(torch 2.9 + bitsandbytes 不能被 agent 依赖污染),
Agent 侧是独立的 Node 工程——两边只通过 HTTP 说话。
## 完整接管 NM2:观测面 + 控制面 + 路由即工具
项目的 `natural_memory_service.py` 只给了一个单条消息的 `/v1/chat` 和少量记录管理接口。
要「接管」NM2,agent 需要三样它给不了的东西,本 server 全部补齐:
### 1. 观测面(NM2 内部其实早就记录了,只是没人暴露)
| 暴露出来的状态 | 含义 |
|---|---|
| `v2_last_decisions` | **每轮路由决策的全部字段**:`need_memory`、页/记录 id、页/记录分数、`hop_count`、`hop_trace`、`stop_reason`、`confidence`、`score_margin`、`evidence_score` |
| `text_read_seconds` | **路由+重排耗时**(与解码耗时分离,便于成本归因) |
| `text_prefix_tokens` / `text_prefix_used` | 实际注入的前缀 token 数 |
| `auto_memory_probability` / `auto_memory_forget_probability` | **自动记忆策略头的写入/遗忘概率** |
| `context_compaction` | KV 自动压缩报告 |
| `v2_no_evidence` | 路由器是否判定无证据 |
每次调用后 server 会 snapshot 这些状态并保留**逐轮 trace 历史**(`GET /v1/nm2/trace`)。
### 2. 控制面(15 个可运行时改写的旋钮 + 治理操作 + 用户隔离)
| 类别 | 接口 |
|---|---|
| 旋钮读写 | `GET/POST /v1/nm2/config` —— `top_k_pages` `top_k_records` `max_hops` `hot_pages` `read_threshold` `write_threshold` `min_read_margin` `require_evidence` `auto_memory_threshold` `auto_forget_threshold` `gpu_cache_records` `gpu_cache_tokens` `context_chunk_tokens` `kv_budget_tokens` `kv_keep_recent_tokens`(**白名单 + 类型/范围校验**,拒绝非法键) |
| 记录治理 | `POST /v1/nm2/write`(可信显式写入,带 entity/attribute/value/importance/confidence)、`/correct`(生成新版本)、`/retract`、`/approve`(**quarantine → active**,此前模型没有公开包装)、`GET /v1/nm2/records`、`/audit`、`/export` |
| 会话/用户隔离 | `POST /v1/nm2/session` —— `save/load/list/delete/reset`,走 `MemoryOSV2.export_payload()` / `from_payload()`,**完整搬移记录、页、quarantine、检索阈值与 KV 预算**(阈值随会话走,不随进程走) |
| KV 预算 | `POST /v1/nm2/compact` —— 把长上下文压缩成 NM2 记录并返回热窗口报告 |
| 请求级覆盖 | chat 请求体里的 `nm2: { top_k_pages, top_k_records, max_hops, read_threshold, ... }`,**只在该次请求内生效**(进入前保存旧值、退出时恢复) |
### 3. 路由即工具:`POST /v1/nm2/probe`
只跑「粗索引 → 候选页 → 页内重排 → Top-K」这条有界路径,**不生成任何 token**,直接返回:
决策(need/hop/stop_reason/margin/evidence)、命中的记录及其分数、以及 `encode_seconds` / `route_seconds` 分解。
这让「这里记忆会给我什么」变成 agent 可调用、harness 可评分的**一等操作**。
### Agent 侧对应的工具
| 工具 | action | 用途 |
|---|---|---|
| `nm2_read` | `probe` / `search` / `trace` / `audit` / `stats` | 探测路由、检索记录、回看逐轮决策与延迟 |
| `nm2_write` | `write` / `correct` / `retract` / `approve` | 记忆治理 |
| `nm2_admin` | `config_get` / `config_set` / `compact` / `session` / `reset` | 调参、压缩、切换用户 |
只用三个工具而不是二十个:4B 模型对「少量工具 + 明确 action 枚举」的可靠性远高于长工具表。
### 仍然保留的(原有能力,未删)
- 用模型**自己的 chat template** 渲染完整消息列表,原生支持 Qwen 的 `<tool_call>` / `<tool_response>`,是**真正的 function calling**;
- 保持项目记忆语义:用户轮触发写入、生成只读、reset token 生效、写入自动持久化;
- `memory_mode = on | read_only | off`,即**无记忆对照组**的开关。
## Agent 侧做了什么
| 文件 | 作用 |
|---|---|
| `src/provider.ts` | 把本机模型注册成 `pi-ai` 的一个 provider(`createProvider` + `OpenAICompletions`) |
| `src/memory.ts` | 模型 server 的记忆/诊断客户端 + `MemoryRecorder`(逐轮记录,忠实于「每次用户消息 = 一条记录」而不是内部循环次数) |
| `src/tools.ts` | 工具集:`run_shell` / `read_file` / `write_file` / `http_get` / `notes` / `memory_admin` |
| `src/agent.ts` | `createTestAgent()`:Pi `Agent` + 事件插桩 + `ask()` 一次用户往返 |
| `src/run_scenario.ts` | 多会话场景编排、**双组(记忆开/关)**、断言评分、写出 JSONL 轨迹与 `summary.json` |
| `scenarios/*.json` | 场景套件 |
### 两个刻意的设计
1. **`notes` 工具(文件便签)是故意的。** 对照组若没有任何外部记忆,比较的就不是「模型内记忆 vs 无记忆」而是
「有记忆 vs 什么都没有」。两组都给同一份文件便签,才能真正隔离被测变量。
2. **`memory_admin` 把记忆治理变成可评测的 Agent 能力**(列出/审计/撤回),而不是看不见的旁路。
## 场景套件
| 场景 | 考的是什么 |
|---|---|
| `cross_session_recall` | 会话 1 写入 → 会话 2/3(**全新上下文、无历史文本**)询问 |
| `conflict_update` | 同属性旧值→新值,跨会话必须返回新值且旧值不得活跃 |
| `unknown_abstention` | 从未提及的属性必须明确说不知道,不许编造或硬套相似记忆 |
| `multi_hop_chain` | 项目→负责人→工号,两跳串联 |
| `forget_retraction` | 明确要求遗忘后,跨会话不得再使用该事实 |
## 运行
```powershell
# 1) 启动模型 server(在 LLM conda 环境,占用 GPU)
$env:PYTHONPATH = 'H:\Memory'
& C:\Users\Administrator\miniconda3\envs\LLM\python.exe H:\Memory\agent_lab\model_server\server.py `
--model-path qwen3_5_4b_natural_memory_v2 --port 8766
# 2) 安装 agent 侧依赖(独立于模型环境)
cd H:\Memory\agent_lab\agent
bun install
bun run typecheck
# 3) 跑单个场景(双组对照)
$env:NATURAL_MEMORY_API_KEY = 'local'
bun run scenario --scenario scenarios/cross_session_recall.json --arm both
# 4) 跑全套
bun run suite
```
产物:`runs/<timestamp>/<scenario>.<arm>.jsonl`(逐轮轨迹:用户消息、助手回答、工具调用与结果、记忆诊断)
与 `runs/<timestamp>/summary.json`(每场景每组的断言通过率、活跃记录数、每轮耗时)。
## 当前状态(重要,勿误读)
- 模型 server:**已成功启动过一次**(`{"event":"ready", ...}`,`memory_mode: on`),但因用户正在满载训练路由器、
需要让出 GPU,**在 tool call 往返验证之前就主动停掉了**。
- Agent 侧:代码按 `[email protected]` 与 `pi-agent-core` 的**实测 API 面**编写(`createProvider` / `Model` 字段 /
`AgentTool` / 事件名均已核对本机安装与官方 README),但**尚未 `bun install`、尚未 typecheck、尚未真正跑过**。
首次运行预计需要少量类型修正。
- 因此:**目前没有任何关于该模型 Agent 能力的实测结论。**
## 后续可加
- `pass^k` 式重复采样稳定性(参考 [τ-bench](https://github.com/sierra-research/tau-bench))
- 用户模拟器(多轮协商类场景)
- 记忆容量压力场景(逼近 `max_pages` / 淘汰行为)
- 多用户隔离场景(两个 user id 交叉询问,验证互不污染)
- 工具输出是否被误写入长期记忆(记忆污染)