Files
TencentDB-Agent-Memory/scripts/seed-v2/README.md
T
2026-05-29 17:51:28 +08:00

230 lines
10 KiB
Markdown
Raw 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.
# seed-v2
> 通过 v2 API 把历史对话灌入 memory-tencentdb gateway。
## 是什么
`seed-v2` 是一个**纯 HTTP 客户端**,把一批历史对话喂给已经在跑的 gateway:
```
fixture.json
│ HTTP POST /v2/conversation/add (一轮一次)
gateway (standalone or service)
│ 自动调度 L0 → L1 → L2 → L3
本地 vectors.db / records / scene_blocks / persona.md
```
客户端**不 import 任何 plugin 内部模块**,只用 fetch + `/v2/conversation/add` + `/v2/pipeline/status`
跟老版 `src/cli/commands/seed.ts`v1import 核心 runtime 直跑)相比:
- ✅ 同一份代码同时适用于 standalone 和 service 模式
- ✅ 不会和已经在跑的 gateway 抢同一份 SQLite / state
- ✅ 通过 status 接口节流,串行写入 → 等抽取完 → 再写下一批
- ✅ 调度状态由 gateway 侧 `StatefulPipelineManager` 统一管,跟生产路径完全一致
## 调用方式
### 方式 1:通过 npm bin(推荐)
```bash
# 1. 把 gateway 起起来(standalone 用法)
cd extensions/memory-tencentdb/__tests__/standalone && ./start.sh
# 2. 灌数据
cd ../..
npm run seed-v2 -- --input ./scripts/seed-v2/fixtures/minimal.json
# 3. 完事停 gateway
./__tests__/standalone/stop.sh
```
### 方式 2:直接命令
```bash
node ./bin/seed-v2.mjs --input fixture.json --endpoint http://127.0.0.1:18420
```
### 方式 3:一键 wrapper(含起停 + 落盘断言)
```bash
./scripts/seed-v2/seed-v2.sh # 默认 fixture
./scripts/seed-v2/seed-v2.sh path/to/my-fixture.json # 自定 fixture
./scripts/seed-v2/seed-v2.sh --keep # 跑完不停 gateway
./scripts/seed-v2/seed-v2.sh --no-start # gateway 已在跑
```
## CLI 参数
| 参数 | 短 | env | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `--input <file>` | `-i` | — | **必填** | fixture JSON 路径 |
| `--endpoint <url>` | `-e` | `SEED_ENDPOINT` | `http://127.0.0.1:18420` | gateway 地址 |
| `--api-key <key>` | — | `SEED_API_KEY` | `standalone-e2e` | Bearer key |
| `--service-id <id>` | `-s` | `SEED_SERVICE_ID` | `default` | `x-tdai-service-id` |
| `--every-n <n>` | `-n` | `SEED_EVERY_N` | 5 | 每 N 轮等一次 L1 idle |
| `--poll-ms <ms>` | — | `SEED_POLL_MS` | 500 | status 轮询间隔 |
| `--stable-rounds <n>` | — | `SEED_STABLE_ROUNDS` | 2 | 连续 N 次 idle 才认为 stable |
| `--max-wait-ms <ms>` | — | `SEED_MAX_WAIT_MS` | 600000 | 每批 waitL1 only)超时(默认 10 分钟)|
| `--final-max-wait-ms <ms>` | — | `SEED_FINAL_MAX_WAIT_MS` | 600000 | 最终 waitL1+L2+L3 全 idle)超时(默认 10 分钟)|
| `--no-final-wait` | — | — | false | 写完最后一批不等 cascade |
| `--dry-run` | — | — | false | 只打印计划不实际请求 |
| `--session-key <key>` | — | `SEED_FALLBACK_SESSION_KEY` | — | fallback 给缺 sessionKey 的 session |
| `--strict-round-role` | — | `SEED_STRICT_ROUND_ROLE=1` | false | 每 round 必须含 user+assistant |
| `--no-auto-fill-timestamps` | — | `SEED_AUTO_FILL_TIMESTAMPS=0` | false | 缺 ts 时不自动 fill |
| `--quiet` | `-q` | `SEED_VERBOSE=0` | false | 静默 |
| `--help` | `-h` | — | — | 帮助 |
## Fixture 校验(与 v1 seed 完全对齐)
启动时会做 6 层校验(任一失败 → exit 2,不会浪费时间灌一半才挂):
| 层 | 检查 |
| --- | --- |
| 1. file | 存在 + 非空 + 合法 JSON |
| 2. top-level | Format A `{ sessions: [...] }` 或 Format B `[...]` |
| 3. session | sessionKey 必须非空字符串 + conversations 必须 2D array |
| 4. round | 必须非空 array`--strict-round-role` 时还要求 user + assistant 同时存在 |
| 5. message | role/content 非空字符串 + timestamp 类型校验(int 或 ISO string|
| 6. timestamp consistency | **全有 / 全无 / mixed** 三态:mixed 拒绝;全无时按 `--no-auto-fill-timestamps` 决定行为 |
错误输出示例(mixed timestamps):
```
$ seed-v2 --input mixed.json
[seed-v2] Seed input validation failed (1 error(s)):
[timestamp_consistency] Timestamp consistency check failed: some messages have timestamps while others do not. ...
exit code: 2
```
## Timestamp 三态
| 状态 | 行为 |
| --- | --- |
| **全有** | 保留原值,`hasTimestamps=true` |
| **全无 + auto-fill 开**(默认)| `fillTimestamps()` 给全局递增 epoch ms`Date.now()` 起,每条 +100ms)—— 跨 session 也单调,避免 L0 capture cursor 误过滤 |
| **全无 + `--no-auto-fill-timestamps`** | timestamp=0;写入时 client 用 `Date.now()` 兜底 |
| **mixed** | exit 2,明确报错 |
## Fixture 格式
兼容老 seed Format A`{ sessions: [...] }`)和 Format B(顶层数组):
```jsonc
{
"sessions": [
{
"sessionKey": "user-001",
"sessionId": "user-001", // 可选,缺省 = sessionKey
"conversations": [ // rounds
[ // round 0:一组消息
{ "role": "user", "content": "..." },
{ "role": "assistant", "content": "..." }
],
[ /* round 1 ... */ ]
]
}
]
}
```
参考最小示例:[`fixtures/minimal.json`](./fixtures/minimal.json)2 session × 6 round = 24 messages)。
## 阻塞节奏(重要)
完全对齐老 `src/core/seed/seed-runtime.ts:executeSeed`
```
for session, round:
POST /v2/conversation/add { session_id, messages } # 立刻返回
if (round + 1) % every_n == 0:
waitForL1Idle (stable 2 polls) # 只等 L1L2/L3 后台异步跑
end-of-session: waitForL1Idle # 末尾再等一次
final: waitForL1Idle # 写完所有 round 后再等 L1 drain
```
`/v2/pipeline/status` 按 L 分桶返回 `{ l1: {idle,…}, l2: {…}, l3: {…} }`。seed-v2 **只看 `data.l1.idle`**
- `l1.idle = (l1.queued === 0 && l1.running === 0)` — 跟老 v1 `seed-runtime.waitForL1Idle` 完全对齐
- L2/L3 抽取慢(cascade 一次 LLM 工具链可能 3-5 分钟)→ **不会**阻塞 seed 的下一批 dispatch
- L2 LLM timeout / lock-conflict drop 等异常情况 → **不会**让 `l1.idle` 误判 false(之前用单标量 `busy` 时会被这两个 bug 污染)
详见 [`docs/plans/server/14-pipeline-status-api.md`](../../docs/plans/server/14-pipeline-status-api.md)。
## Pipeline 调参(服务端 yaml
灌入速度由**两侧**共同决定:seed 客户端的 wait 节奏 + gateway 流水线的触发节奏。客户端参数已在上面 [CLI 参数](#cli-参数) 表里,下面是**服务端**对应的 `tdai-gateway.standalone.yaml` 配置项(标星号的最常调):
```yaml
memory:
capture:
enabled: true # L0 capture 开关,必须 true 否则没数据进流水线
extraction:
enabled: true
enableDedup: true # L1 抽取后去重(vector recall topK=5);关掉提速但可能产生重复 record
maxMemoriesPerSession: 20 # ★ 单 session L1 抽取的 atomic memory 数上限
persona:
triggerEveryN: 50 # ★ 每抽取 N 条 L1 record 触发 1 次 L3 persona-gen
maxScenes: 15 # L2 scene 数上限(达到后停止 CREATE 新 scene
pipeline:
everyNConversations: 5 # ★★★ 每 N 轮对话触发 1 次 L1 抽取(必须跟 seed 客户端 --every-n 对齐)
enableWarmup: true # 冷启动前几次更快触发 L1(让 persona.md 早点形成骨架)
l1IdleTimeoutSeconds: 600 # ★ session 空闲 N 秒后强制触发 L1(兜底,防 conversation < everyN 时 L1 永不抽)
l2DelayAfterL1Seconds: 90 # ★★ L1 完成后延迟 N 秒触发 L2(避免每条 L1 都触发一次 L2,太碎)
l2MinIntervalSeconds: 900 # ★★ 同 session 两次 L2 之间最小间隔(节流,防 L2 LLM 调用过频)
l2MaxIntervalSeconds: 3600 # ★ 同 session L2 最大间隔(兜底,无论间隔多久必跑一次)
```
### 怎么调:常见场景
| 场景 | 怎么调 |
| --- | --- |
| **seed 灌入想最快**(接受 L1 粒度粗)| `everyNConversations: 20`(每 20 轮才抽一次 L1+ seed `--every-n 20` 对齐 |
| **seed 灌入想 L1 立即抽**(每轮都抽,最细粒度但贵)| `everyNConversations: 1` + seed `--every-n 1` |
| **fixture 是短对话**< everyN 轮)触发不到 L1 | 缩 `l1IdleTimeoutSeconds: 60`1分钟无新消息就触发兜底)|
| **L2 cascade 太频繁**cost 高)| 拉大 `l2MinIntervalSeconds: 1800`30 分钟才一次)|
| **L2 cascade 太稀疏** | 缩 `l2DelayAfterL1Seconds: 30` + `l2MinIntervalSeconds: 300` |
| **测试/调试想看快速反馈** | `triggerEveryN: 10` + `everyNConversations: 2` + `l1IdleTimeoutSeconds: 30` |
### seed 客户端 vs 服务端的对应
| seed 参数 | 服务端 yaml 对应 | 是否需对齐 |
| --- | --- | --- |
| `--every-n` (`SEED_EVERY_N`) | `pipeline.everyNConversations` | ⚠️ **建议一致**(客户端 wait 间隔 = 服务端触发间隔,灌入流畅)|
| `--max-wait-ms` | `pipeline.l1IdleTimeoutSeconds` | wait 时长应 ≥ idle timeout(否则会 max-wait-reach 而不是 stable-idle|
| `--final-max-wait-ms` | 综合 L2 慢 task 时长 | ≥ p95(L2 LLM duration) × maxScenes/(同时抽的 scene 数)(默认 10 分钟够大多数场景)|
| `--stable-rounds` | — | 客户端独有,控制连续多少次 idle 才认定真稳定 |
### 调参对 seed 总耗时的影响(粗估)
| 配置 | persona-010 sessions × 366 msgs)耗时 |
| --- | --- |
| 默认(`everyNConversations=5``l2DelayAfterL1=90s``l2MinInterval=900s`| ~90 min(实测 persona-0|
| 降本:`everyNConversations=20``l2MinInterval=3600s` | ~30-50 minL1 抽得粗,L2 抽得少)|
| 极速:`--no-final-wait` + 仅 L0 模式(`extraction.enabled=false`| ~1-2 min(只灌 L0,不抽取)|
> ⚠️ **改 yaml 后必须重启 gateway**`scripts/seed-v2/seed-v2.sh` 自动起停;如果用 `start.sh` 手动起的需 `stop.sh && start.sh`)。
## 退出码
| code | 含义 |
| --- | --- |
| 0 | 全部成功 |
| 1 | seed 失败(写入或 wait 异常)|
| 2 | 前置条件不满足(fixture 缺失 / gateway 不通 / 配置错误)|
## 构建 + 发布
```bash
npm run build:seed-v2 # tsc 编译到 scripts/seed-v2/dist/
npm run build:scripts # 构建所有 scripts(含 seed-v2
```
`bin/seed-v2.mjs` 是薄启动器:dist 存在就跑 dist,否则 fallback 到 `tsx` 跑源码。开发期不需要先 build。