mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
Merge commit 'refs/tmp/gmain' into ci/boot-graph-tests
This commit is contained in:
@ -195,7 +195,7 @@ git switch main && git cherry-pick <sha> # 遵守 §三:只 pick,不 merge
|
||||
> - `feature/*` 分支(`input-semantics`、`multimodal-embedding`、`memory-media`、`plugin-proc-migration`)
|
||||
> 均已合入并删除。
|
||||
|
||||
### SDK 仓(homeagentsdk)
|
||||
### SDK 仓(hologramagentsdk)
|
||||
|
||||
| 分支 | 状态 | 处理 |
|
||||
|---|---|---|
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
| 仓 | 位置 | 流水线 | 触发 |
|
||||
| --- | --- | --- | --- |
|
||||
| 主仓 HologramAgent | `github.com/JianFeeeee/HologramAgent`(公开) | `ci.yml` + `release.yml` | push / PR、`release/**` push |
|
||||
| SDK 仓 homeagentsdk | `github.com/JianFeeeee/homeagentsdk`(公开) | `release.yml` | `release/**` push |
|
||||
| SDK 仓 hologramagentsdk | `github.com/JianFeeeee/hologramagentsdk`(公开) | `release.yml` | `release/**` push |
|
||||
| gitcode 镜像 | `gitcode.com/JianFeeeee/HologramAgent`(与主仓同名) | 无(由主仓 Release 的 sync job 同步) | — |
|
||||
|
||||
**分支保护**(main,公开仓免费):10 项必须检查(Go build/vet/test、Race、
|
||||
@ -33,14 +33,14 @@ cross×5、GUI、C gates、Docs)+ `strict`(必须与 main 同步)+ 禁 for
|
||||
**gitcode 侧以后再改仓名,只改这三个定义点即可**,消费点无需动。
|
||||
|
||||
**当前取值 `JianFeeeee/HologramAgent`**(与 GitHub 主仓同名)。gitcode 侧镜像仓
|
||||
**已由 `HomeAgent` 改名为 `HologramAgent`**;旧地址仍 301 跳转,跟随后也能到,
|
||||
**已由 `HologramAgent` 改名为 `HologramAgent`**;旧地址仍 301 跳转,跟随后也能到,
|
||||
但走重定向,不应再用。实测(2026-10-21 20:1x,直连不带代理,
|
||||
`curl -s -o /dev/null -w '%{http_code}' https://gitcode.com/JianFeeeee/<repo>`):
|
||||
|
||||
| 仓名 | HTTP | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `HologramAgent` | **200** | 当前取值(主仓镜像) |
|
||||
| `HomeAgent` | 301 | 旧名,跳转到 `HologramAgent` |
|
||||
| `HologramAgent` | 301 | 旧名,跳转到 `HologramAgent` |
|
||||
| `homeagent-sdk` | 200 | SDK 仓,另一对象 |
|
||||
| `hologramagentsdk` | 200 | SDK 仓,另一对象 |
|
||||
| `homeagentsdk` | 404 | 不存在 |
|
||||
@ -49,7 +49,7 @@ cross×5、GUI、C gates、Docs)+ `strict`(必须与 main 同步)+ 禁 for
|
||||
判定「301 不是另一个仓、就是同一仓改名」的依据:两个地址 `git ls-remote`
|
||||
得到的 refs 列表 md5 相同(同一组分支与 SHA)。
|
||||
|
||||
API 侧同样兼容两个名字:`/api/v5/repos/JianFeeeee/{HomeAgent,HologramAgent}`
|
||||
API 侧同样兼容两个名字:`/api/v5/repos/JianFeeeee/{HologramAgent,HologramAgent}`
|
||||
不带 token 都返回 **400**(需要鉴权,不是仓不存在),
|
||||
`/releases/tags/v1.3.13` 都返回 **200** 且响应体一致。
|
||||
|
||||
@ -207,13 +207,15 @@ tar 写 stdout 收到 EPIPE ⇒ pipefail 判失败。**包越大越易触发**
|
||||
CI 的 sync job 需要仓库 secret `GITCODE_TOKEN`;**未配置时该 job 显式跳过**
|
||||
(不阻断 GitHub 侧发布)。
|
||||
|
||||
**2026-09-29 已配置**:两仓(`JianFeeeee/HologramAgent`、`JianFeeeee/homeagentsdk`)
|
||||
均已设同名 secret,取值自 `~/.git-credentials` 里那条 `https://JianFeeeee:<token>@gitcode.com`。
|
||||
**2026-09-29 已配置**:两仓均已设同名 secret,取值自 `~/.git-credentials`
|
||||
里那条 `https://JianFeeeee:<token>@gitcode.com`。配置时 SDK 仓还叫
|
||||
`homeagentsdk`,2026-10-21 改名 `hologramagentsdk` 后 secret 随仓迁移,
|
||||
无需重设;下面命令写的是**当前**仓名。
|
||||
配置方式(经 stdin 传入,避免 token 出现在进程列表):
|
||||
|
||||
```bash
|
||||
printf '%s' "$TOKEN" | gh secret set GITCODE_TOKEN --repo JianFeeeee/HologramAgent
|
||||
printf '%s' "$TOKEN" | gh secret set GITCODE_TOKEN --repo JianFeeeee/homeagentsdk
|
||||
printf '%s' "$TOKEN" | gh secret set GITCODE_TOKEN --repo JianFeeeee/hologramagentsdk
|
||||
```
|
||||
|
||||
验收:sync job 只在**新版本**发版时运行(`prepare.outputs.exists == 'false'`),
|
||||
@ -226,8 +228,8 @@ gh secret list --repo JianFeeeee/HologramAgent | grep GITCODE_TOKEN
|
||||
curl -s "https://gitcode.com/api/v5/user?access_token=$TOKEN"
|
||||
# ③ job 用的 private-token 头可读 release(两仓都测)
|
||||
# ⚠ gitcode 侧主仓镜像现已改名为 HologramAgent(与 GitHub 同名),
|
||||
# 旧名 HomeAgent 仍 301 跳转。实测 2026-10-21 20:1x(直连不带代理):
|
||||
# /api/v5/repos/JianFeeeee/{HomeAgent,HologramAgent} 不带 token 均 400
|
||||
# 旧名 HologramAgent 仍 301 跳转。实测 2026-10-21 20:1x(直连不带代理):
|
||||
# /api/v5/repos/JianFeeeee/{HologramAgent,HologramAgent} 不带 token 均 400
|
||||
# (需鉴权,非仓不存在);/releases/tags/v1.3.13 两名字均 200 且响应一致。
|
||||
curl -s -H "private-token: $TOKEN" \
|
||||
"https://gitcode.com/api/v5/repos/JianFeeeee/HologramAgent/releases/tags/v1.3.13"
|
||||
@ -241,7 +243,7 @@ curl -s -H "private-token: $TOKEN" \
|
||||
|
||||
```bash
|
||||
# token 放 ~/.git-credentials(https://JianFeeeee:<token>@gitcode.com)
|
||||
# GITCODE_REPO 是 gitcode 镜像仓名(当前 HologramAgent,旧名 HomeAgent 为 301)。
|
||||
# GITCODE_REPO 是 gitcode 镜像仓名(当前 HologramAgent,旧名 HologramAgent 为 301)。
|
||||
ASSET_DIR=<产物目录> GITCODE_REPO=JianFeeeee/HologramAgent \
|
||||
python3 deploy/scripts/upload_assets.py <tag> <token>
|
||||
# release 条目必须先存在(脚本向 releases/<tag>/upload_url 取 OBS 签名 URL)
|
||||
|
||||
444
docs/zh/memory-blockflow-repair-plan.md
Normal file
444
docs/zh/memory-blockflow-repair-plan.md
Normal file
@ -0,0 +1,444 @@
|
||||
# 三层记忆系统修复计划:块化重构后的失效面
|
||||
|
||||
> 状态:**已实施**(2026-10-22)。实施记录见文末 §九。
|
||||
> 原计划写作于同日,下述前提与失效面均经源码与生产数据核实。
|
||||
> 触发:块化统一(`docs/zh/memory-restructure-plan.md`)重构了底层数据结构,
|
||||
> 但上层「层的流动」没有跟着改,导致 L1↔L2 这条链出现结构性失效。
|
||||
>
|
||||
> 本文只写**经源码与生产数据核实**的失效点。每条都标了证据位置,
|
||||
> 不含推测、不含性能断言。
|
||||
|
||||
---
|
||||
|
||||
## 〇、设计前提(不可动摇,任何修法违反即错)
|
||||
|
||||
### 前提 A:记忆块是唯一的物理载体
|
||||
|
||||
`docs/zh/memory-restructure-plan.md` §〇 纠错表原话:
|
||||
|
||||
> | 分层是 L0 对话/L1 文档/L2 graph/L3 blocks | L0=context / L1=doc / L2=graph。
|
||||
> **blocks 不是新的一层**,是图节点从文本升级为带向量的多模态节点 |
|
||||
|
||||
`MemoryBlock` 是 union:文本块负载在 `Text`,媒体块在 `Data`(配 MIME/宽高/尺寸)。
|
||||
**同一个块 ID 在 context → doc → graph 全程不变。层是持有关系,不是数据种类的划分。**
|
||||
|
||||
### 前提 B:L1 与 L2 结构同构,唯一区别是「是否直接投喂给模型」
|
||||
|
||||
这是**层与层的定义**,不是实现细节:
|
||||
|
||||
| | L1 Context | L2 Document |
|
||||
|---|---|---|
|
||||
| 承载单元 | 事件(内联正文 + 块) | 事件(内联正文 + 块) |
|
||||
| 结构 | 同构 | 同构 |
|
||||
| 与模型的关系 | **直接投喂**(进 system prompt 时序) | **不直接投喂**(需经召回才进入 L1) |
|
||||
| 容量 | 最近 n 条 / 上限 m 条 | 无上限 |
|
||||
| 流动性 | 超限时按相似度迁出 | 冷到阈值后蒸馏进 L3 |
|
||||
|
||||
⇒ L2 **不是**「L1 的摘要层」,也**不是**「另一种数据形态」。
|
||||
它就是**同样的事件 + 同样的块**,只是不直接进模型视野。
|
||||
召回 = 把块(连同它所属事件)从"不投喂"的持有层**迁回**"投喂"的持有层。
|
||||
|
||||
### 前提 C:正文(含多媒体正文)内联在块里
|
||||
|
||||
文本块的 `Text` 就是正文;媒体块的 `Data` 就是字节。
|
||||
**不存在"块 + 外部正文"的分离形态**,也不存在靠渲染字符串承载原文的路径。
|
||||
事件是块的容器,不是块的替代品。
|
||||
|
||||
### 前提 D:任何一层都不压缩
|
||||
|
||||
原文必须完整保留。视图(检索摘要、给模型看的渲染)可以是压缩的,
|
||||
但**视图不是记忆**。把视图当记忆存,等于在存储端就丢了原文。
|
||||
|
||||
### 前提 E:Graph 顶层仍是三元组,块只是它的节点实现
|
||||
|
||||
`internal/memory/sentence_block.go` — `putTripleBlocksTx`:
|
||||
|
||||
```go
|
||||
src := MemoryBlock{ID: TripleBlockID(t.Subject), Text: t.Subject, SemanticType: t.SubjectType}
|
||||
dst := MemoryBlock{ID: TripleBlockID(t.Object), Text: t.Object, SemanticType: t.ObjectType}
|
||||
// 边:src --edge_type=Relation--> dst,落在 memory_block_edges
|
||||
```
|
||||
|
||||
三元组 =【节点】【关系边】【节点】,这个**顶层结构没有变**。
|
||||
变的是节点的物理表示:`entities` 表 → `memory_blocks` 表。
|
||||
`entities` 退场不等于三元组退场。
|
||||
|
||||
### 前提 F:内核不得硬编码任何插件行为
|
||||
|
||||
这是**铁律**。任何依赖具体插件名(`agentcli` / `terminal` / `qq` …)的内核分支都是错的。
|
||||
插件的行为差异一律由插件自己**声明**:`ToolDef.NoMemory`、`ToolDef.Cleaner`、
|
||||
`ChannelDef.NoMemory`、`ChannelDef.Cleaner`、`InjectOptions`。
|
||||
|
||||
### 前提 G:SDK 已通过一套等价接口实现同等能力
|
||||
|
||||
公开 SDK(`third_party/homeagent-sdk/sdk/memory.go`)已提供与内核同构的表达:
|
||||
|
||||
| SDK 接口 | 对应的内核概念 |
|
||||
|---|---|
|
||||
| `TextEvent{Role, Content, Timestamp, Channel, Attachments}` | 事件(正文内联 + 附件) |
|
||||
| `DocMemoryAPI.Insert(doc)` | 写入 L2(纯文本) |
|
||||
| `DocMemoryAPI.InsertWithMedia(doc, attachments)` | 写入 L2(正文 + 媒体块) |
|
||||
| `DocMemoryAPI.Query(text, topK)` | 只读召回(不删除) |
|
||||
| `MediaAttachment{Digest, MIME, Data}` | 块引用:带 `Data` 为新内容,只带 `Digest` 为引用已有 |
|
||||
| `TextMemoryAPI.Append(TextEvent)` | 时间序文本日志 |
|
||||
|
||||
⇒ **SDK 契约不需要改动**。修的是内核实现没跟上契约语义,不是契约本身。
|
||||
|
||||
---
|
||||
|
||||
## 一、失效面清单(经核实)
|
||||
|
||||
### 失效 1:L2 只存了事件的渲染视图,事件本体被丢弃
|
||||
|
||||
**位置**:`internal/memory/document/document.go` — `ContextToDoc`
|
||||
|
||||
`Prune` 交给 L2 的是结构完整的 `[]ContextEntry`(Timestamp/Source/Content/
|
||||
Response/ToolsUsed/ToolResults/Blocks),但落库时被拍平成一个字符串:
|
||||
|
||||
```go
|
||||
for _, e := range entries {
|
||||
line := fmt.Sprintf("[%s] %s: %s", e.Timestamp.Format("15:04"), e.Source, e.Content)
|
||||
line += fmt.Sprintf(" → %s", truncate(e.Response, 100)) // 正文 → 视图
|
||||
line += fmt.Sprintf("\n [工具] %s: %s", tr.Name, truncate(tr.Output, 200))
|
||||
}
|
||||
content := strings.Join(parts, "\n")
|
||||
```
|
||||
|
||||
只有 `Doc.Content` 被保存。**后果**:事件的时间戳只剩 `HH:MM`,
|
||||
`Response` 只剩 100 rune,工具输出只剩 200 rune,`ToolsUsed` 完全不存。
|
||||
|
||||
这直接违反前提 B(L1/L2 同构)与前提 C(正文内联在块里)——
|
||||
L1 里是完整事件,L2 里却退化成一段文本。
|
||||
|
||||
**生产证据**(`/home/newqqagent/memory/documents/`,1000 篇归档文档):
|
||||
|
||||
| 度量 | 实测值 |
|
||||
|---|---|
|
||||
| response 段恰好 103 字符(=100+`...`) | 311 / 2094 段 = **15.1%** |
|
||||
| 工具输出段恰好 203 字符(=200+`...`) | **7593 / 13261 段** |
|
||||
|
||||
⇒ 原文**在落库那一刻就不存在了**。这不是"召回时没取全",是存储端丢失。
|
||||
|
||||
### 失效 2:L2 里没有文本块,L1/L2 不同构
|
||||
|
||||
**位置**:`internal/memory/document/document.go` — `blocksFromEntries`
|
||||
|
||||
块的 `modality` 实测分布(同一批生产文档):
|
||||
|
||||
```
|
||||
L2 块总数 444:image 437 / audio 7 / text 0
|
||||
```
|
||||
|
||||
**后果**:L2 里"块"只承载媒体,文本内容全靠 `Doc.Content` 这个视图。
|
||||
违反前提 B/C——L1 的事件正文 + 块,到 L2 变成了"字符串 + 仅媒体块"。
|
||||
(对照:L3 的文本是有块的,`putTripleBlocksTx` 的主语块/宾语块都是 `BlockText`。)
|
||||
|
||||
### 失效 3:召回时新建合成事件,而非把块按原时序迁回
|
||||
|
||||
**位置**:`internal/agent/core/toolcall.go` — `executeDocTool` 的 `doc_query` 分支
|
||||
|
||||
```go
|
||||
docs := a.docStore.Consume(query, topK) // 读 + 从 L2 删除
|
||||
...
|
||||
a.context.InsertByTimestamp(ContextEvent{
|
||||
Timestamp: d.CreatedAt, // ← 归档时刻,非事件时刻
|
||||
Source: "cold_storage", // ← 新造来源
|
||||
Input: fmt.Sprintf("加载文档记忆: %s", query),
|
||||
Response: content, // ← 渲染视图,且再截 2000
|
||||
})
|
||||
// ← 完全没有把 d.Blocks 挂回去
|
||||
```
|
||||
|
||||
三处偏离设计(`ARCHITECTURE.md` L107-108、L178):
|
||||
|
||||
> doc_query → Consume(读取并删除)
|
||||
> → **逐条** context.Append{Timestamp: **原始时间戳**, Source: 原来源}
|
||||
> → 文档以原始时间戳写入 context 时间线, 从 docStore 删除
|
||||
|
||||
**生产证据**(块自带 `created_at`,与归档时刻差近两小时):
|
||||
|
||||
```
|
||||
媒体块 created_at = 2026-10-12 11:09:51 ← 事件发生时刻(captureBlockMedia 里 time.Now())
|
||||
doc.created_at = 2026-10-12 13:04:54 ← 归档时刻
|
||||
```
|
||||
|
||||
443/443 个媒体块都带有效 `created_at`。**块自带它该回到哪个时刻的坐标,现在没人用。**
|
||||
|
||||
### 失效 4:块在 Consume 时两层同时消失
|
||||
|
||||
**位置**:`internal/memory/document/document.go` — `Consume` → `removeDoc`
|
||||
|
||||
```go
|
||||
func (s *Store) removeDoc(id string) {
|
||||
delete(s.docs, id)
|
||||
...
|
||||
os.Remove(filepath.Join(s.dir, id+".json")) // 块的字节只在这个文件里
|
||||
}
|
||||
```
|
||||
|
||||
`doc_query` 调用 `Consume` 后,文档(含 `d.Blocks`)从内存与磁盘同时删除;
|
||||
而 `toolcall.go` 不回填 `Blocks`。**后果**:这些块既不在 L2(已删)也不在 L1(没挂),
|
||||
只剩局部变量引用,函数返回后即不可达。
|
||||
|
||||
**生产风险敞口**:L2 现有 444 个块,其中 **396 个不在 L3**(`graph.db` 里查不到),
|
||||
原始字节约 **248 MB**。一旦被 `doc_query` 命中即永久丢失。
|
||||
|
||||
### 失效 5:时序渲染用固定 120 rune 截断正文
|
||||
|
||||
**位置**:`internal/agent/core/process.go` — `formatMergedTimeline`
|
||||
|
||||
```go
|
||||
sb.WriteString(fmt.Sprintf(" → %s", truncateStr(e.Response, 120)))
|
||||
```
|
||||
|
||||
上下文预算可以是几百 K token(`ComputeTokenBudget`),但每条正文**永远只显示前 120 字**。
|
||||
原文即使用前提 D 完整保存在库里,模型也看不到——这是"不压缩"在**渲染环节**的漏口。
|
||||
|
||||
**生产证据**(`context.json`):2 条 `cold_storage` 事件的 response 分别 1541 / 1209 字符,
|
||||
渲染出来都是 123 字符。
|
||||
|
||||
### 失效 6:`Prune` 的归档过滤要求本身违反前提 F
|
||||
|
||||
**位置**:`ARCHITECTURE.md` L13 写「过滤掉 `agentcli` 和 `terminal` 来源后归档」。
|
||||
|
||||
`agentcli` / `terminal` 是**插件名**。把它们写进内核归档规则 = 硬编码插件行为,违反前提 F。
|
||||
|
||||
**当前代码**:`Prune` 里没有任何源名过滤,该要求**未实现**;
|
||||
生产库里有 325 行 `agentcli` 归档(全部是终端状态行)。
|
||||
|
||||
⇒ 这不是"补上过滤",而是**该要求应作废,改由插件声明驱动**(前提 F)。
|
||||
|
||||
---
|
||||
|
||||
## 二、修复顺序(按依赖)
|
||||
|
||||
失效 1/2 是存储形态,3/4 是召回形态,5 是渲染,6 是文档纠错。
|
||||
**必须先修存储**——存储里没有事件本体,召回就没有东西可放回。
|
||||
|
||||
| # | 动作 | 依赖 | 涉及文件 |
|
||||
|---|---|---|---|
|
||||
| ① | L2 存储形态改为「事件 + 块」(与 L1 同构) | — | `memory/document/document.go` |
|
||||
| ② | 文本正文以块承载(消灭 `Content` 作为唯一文本载体) | ① | 同上 + `agent/core/context.go` |
|
||||
| ③ | 召回按块/事件原始时序迁回 L1 | ①② | `agent/core/toolcall.go` |
|
||||
| ④ | 时序渲染改为按预算分配 | 独立 | `agent/core/process.go` |
|
||||
| ⑤ | 作废失效 6 的文档要求(改由 `NoMemory` 声明驱动) | 独立 | `ARCHITECTURE.md` |
|
||||
|
||||
### ① 的具体形状
|
||||
|
||||
`Doc` 与 `RelevanceContext` 的事件列表**同构**:
|
||||
|
||||
```
|
||||
L1: events []ContextEvent // Timestamp/Source/Input/Response/ToolsUsed/ToolResults/Blocks
|
||||
L2: events []ContextEvent // 同一结构,同一字段
|
||||
```
|
||||
|
||||
- `Doc` 保留 `ID` / `Summary` / `Vector` / `DenseVec` / `DenseFP` / `AccessCount` 等
|
||||
**检索元数据**(这些是 L2 作为"可检索容器"的附加属性,L1 不需要)
|
||||
- 事件列表**内联正文**(`Response` 完整,不截断)+ 持有的块
|
||||
- `Doc.Content` 降级为**纯渲染视图**(供摘要/人看/兼容 SDK 的 `Doc.Content`),
|
||||
不再作为原文载体;原文从事件读
|
||||
|
||||
> ⚠️ 注意与「已否决做法」的区别:不是新造一个异构字段把 `ContextEvent` 旁挂上去,
|
||||
> 而是让 **L2 的承载结构就是 L1 的那一套**。事件列表是文档的主体,不是附件。
|
||||
|
||||
### ② 的具体形状
|
||||
|
||||
- 归档时,每条事件的正文(`Input` / `Response`)生成对应文本块(`Modality=BlockText`,
|
||||
`Text` 即正文),块 `CreatedAt` = 事件时间戳
|
||||
- 媒体块照旧(`Data` 内联字节)
|
||||
- 事件 `Blocks` 里同时含文本块与媒体块 → L1/L2/L3 三处同构
|
||||
|
||||
### ③ 的具体形状
|
||||
|
||||
```
|
||||
doc_query → 取出 L2 事件列表(迁移语义:取出后 L2 不再持有)
|
||||
→ 逐条按各自 Timestamp 插回 L1 时序
|
||||
→ 事件持有的块随之迁回(同一块 ID,向量不重算)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、判据(先写判据再改,避免自证)
|
||||
|
||||
> 教训(本次已发生):先改代码再写测试,测试就只会证明"我改的是我以为的"。
|
||||
> 判据必须**从设计前提推导**,且必须能在改动前**红**。
|
||||
|
||||
### ① 存储形态(L1/L2 同构)
|
||||
|
||||
- [ ] 归档后能从 L2 取回**逐条**事件的原始 `Timestamp`(完整时刻,非 `HH:MM`)、
|
||||
`Source`、`Response`(完整,非截断)、`ToolsUsed`、`ToolResults`
|
||||
- [ ] 变异:把 `Response` 改回 `truncate(..,100)` ⇒ 红
|
||||
- [ ] 变异:丢掉 `ToolsUsed` ⇒ 红
|
||||
- [ ] 变异:时间戳改回 `Format("15:04")` ⇒ 红
|
||||
|
||||
### ② 文本块
|
||||
|
||||
- [ ] 归档文档的文本块 `Text` 与事件正文逐字一致
|
||||
- [ ] 文本块 `CreatedAt` == 事件 `Timestamp`
|
||||
- [ ] 变异:不生成文本块(只存 `Doc.Content`)⇒ 红
|
||||
|
||||
### ③ 召回
|
||||
|
||||
- [ ] `doc_query` 后事件按**各自原始时间戳**回到 L1
|
||||
- [ ] 块 `ID`/`Vector`/`CreatedAt` 全程不变(迁移而非重建)
|
||||
- [ ] 块的持有层从 L2 变为 L1,**不同时留在两层**
|
||||
- [ ] 变异:用 `d.CreatedAt` 当时间戳 ⇒ 红
|
||||
- [ ] 变异:不回填 `Blocks` ⇒ 红
|
||||
|
||||
### ④ 渲染
|
||||
|
||||
- [ ] 预算宽裕时正文完整可见(含结尾哨兵)
|
||||
- [ ] 预算紧张时仍保留开头(不能整条消失)
|
||||
- [ ] 单条超长正文不得把其余事件整段挤掉
|
||||
|
||||
### ⑤ 文档纠错
|
||||
|
||||
- [ ] `ARCHITECTURE.md` 不再出现任何插件名作为内核归档规则
|
||||
- [ ] 判据:`grep -rn '"agentcli"\|"terminal"' internal/agent/core internal/memory`(非测试)为空
|
||||
|
||||
---
|
||||
|
||||
## 四、禁止事项(此前已犯,写下来防复发)
|
||||
|
||||
| 禁止 | 原因 |
|
||||
|---|---|
|
||||
| 给 `Doc` 加异构字段旁挂事件快照(如 `Entries []ContextEntry`) | L2 主体就是事件列表本身,不是"Doc + 附件" |
|
||||
| 改 `truncateStr` 的常数当作"修复压缩" | 视图截断不是记忆压缩问题;根因在存储形态 |
|
||||
| 任何插件名字面量进内核分支 | 违反前提 F |
|
||||
| 编造不存在的 API 再补注释解释它"应该"怎样 | 实现必须以真实存在的设施为准 |
|
||||
| 先按自己的理解写测试再改代码 | 会把误解固化成判据 |
|
||||
| 改公开 SDK 契约 | 前提 G:SDK 已等价,修内核实现 |
|
||||
|
||||
---
|
||||
|
||||
## 五、已否决的做法
|
||||
|
||||
### 否决 1:新增 `Doc.Entries` 存事件快照(2026-10-22,已实现又撤销)
|
||||
|
||||
错在**异构旁挂**:`Doc` 仍是字符串主体,事件列表被当成附件挂上去。
|
||||
正确形态是 §二①:事件列表**成为 L2 的主体**,与 L1 同构。
|
||||
|
||||
### 否决 2:把渲染层截断常数调大(2026-10-22,已撤销)
|
||||
|
||||
根因在存储形态,不在渲染常数。§二④ 单独处理,且应是**按预算分配**而非改常数。
|
||||
|
||||
---
|
||||
|
||||
## 六、已拍板的决定
|
||||
|
||||
### 1. 事件本体在 L2 的形态(2026-10-22 拍板)
|
||||
|
||||
**L2 就是 doc 存储,本身与 Context 同构,区别只在于是否被直接投喂给模型。**
|
||||
|
||||
⇒ 不是“复用 `ContextEvent` 当旁挂字段”,也不是“把事件也变成块”这种额外抽象。
|
||||
**事件列表就是 L2 文档的主体**,与 L1 事件列表同一结构。
|
||||
|
||||
### 2. 正文形态(2026-10-22 拍板)
|
||||
|
||||
**正文(包括多媒体正文)本身就内联在块里。**
|
||||
|
||||
⇒ 文本块的 `Text` 是正文,媒体块的 `Data` 是字节。
|
||||
不存在“块 + 外部正文”的分离形态,也不靠渲染字符串承载原文。
|
||||
|
||||
### 3. SDK(2026-10-22 拍板)
|
||||
|
||||
**记忆部分 SDK 已通过一套接口实现等价功能** ⇒ **不改公开 SDK 契约**。
|
||||
|
||||
对照表(`third_party/homeagent-sdk/sdk/memory.go`):
|
||||
|
||||
| SDK 接口 | 等价的内核概念 |
|
||||
|---|---|
|
||||
| `TextEvent{Role, Content, Timestamp, Channel, Attachments}` | 事件(正文内联 + 附件) |
|
||||
| `DocMemoryAPI.Insert(doc)` | 写入 L2(纯文本) |
|
||||
| `DocMemoryAPI.InsertWithMedia(doc, attachments)` | 写入 L2(正文 + 媒体块) |
|
||||
| `DocMemoryAPI.Query(text, topK)` | 只读召回(不删除) |
|
||||
| `MediaAttachment{Digest, MIME, Data}` | 块引用(带 Data 为新内容,仅 Digest 为引用) |
|
||||
|
||||
⇒ 修的是内核实现没跟上契约语义,不是契约本身。
|
||||
|
||||
### 4. 存量数据(2026-10-22 拍板)
|
||||
|
||||
**等待系统自然冲刷**,不做迁移脚本。
|
||||
|
||||
约束:
|
||||
- 新代码必须能**读旧格式**(缺事件列表/缺文本块时降级),不得 panic、不得静默丢内容
|
||||
- 降级路径要有明确日志,便于判断存量退出进度
|
||||
- 旧格式文档被召回时按旧行为处理,**不伪造成“有原始事件”**
|
||||
|
||||
---
|
||||
|
||||
## 七、待确认的细节(实现层面,不阻塞开工)
|
||||
|
||||
1. **文本块与事件是一对一还是一对多?**
|
||||
一条事件生成一个文本块(`Input` + `Response` 合在一个 `Text` 里),还是各一块(便于分别检索)?
|
||||
|
||||
2. **`Doc.Content` 保留为渲染视图,还是彻底删除?**
|
||||
若保留,与事件列表信息重复;若删除,SDK 的 `Doc.Content` 则由事件列表现场渲染填充。
|
||||
|
||||
3. **L2 事件的检索向量怎么算?**
|
||||
现在 `denseFor` 用 `Summary + Content`。改为事件列表后,
|
||||
是用 `Summary + 各事件正文拼接`,还是用文本块自己的向量做融合(与媒体块一致)?
|
||||
|
||||
---
|
||||
|
||||
## 八、验收口径
|
||||
|
||||
- `go build ./...` 通过
|
||||
- 全量 `go test ./... -count=1` 0 失败(当前基线:58 包通过 / 0 失败)
|
||||
- §三 每条判据有对应测试,且**变异测试**各自能被抓住
|
||||
- 生产侧(可在副本库验证):`doc_query` 召回后块不消失;新旧两种格式文档都能正确召回
|
||||
|
||||
---
|
||||
|
||||
## 九、实施记录(2026-10-22)
|
||||
|
||||
### 已落地
|
||||
|
||||
| # | 动作 | 落地位置 |
|
||||
|---|---|---|
|
||||
| ① | L2 存储形态改为「事件 + 块」 | `document.Doc.Events []DocEvent`;`ContextToDoc` 落盘事件列表 |
|
||||
| ② | 正文各自成文本块 | `docBlocksForEntry` / `entriesToEvents`;Input、Response 各一块(`BlockText`) |
|
||||
| ③ | 召回按原始时序迁回 | `Agent.restoreDocEvents`;`DocEvent.InOut()` 从块读回正文 |
|
||||
| ④ | 蒸馏改用原文 | `documentOriginalText`(有 Events 用块内原文,存量退回 Content) |
|
||||
| ⑤ | 文档纠错 | `ARCHITECTURE.md` 删除「过滤 agentcli/terminal」,改为「由插件声明驱动」 |
|
||||
|
||||
### Content 的定位(按拍板)
|
||||
|
||||
`Doc.Content` **保留为渲染视图**,由 `contentFromEvents` 生成:
|
||||
|
||||
- 供摘要、人工阅读、SDK 的 `Doc.Content` 字段使用
|
||||
- **不是**原文载体 —— 原文在 `Events[i].Blocks` 里文本块的 `Text`
|
||||
- 视图也不主动截断(存储层不替渲染层做决定)
|
||||
|
||||
### 存量
|
||||
|
||||
按拍板**不做迁移**,等系统自然冲刷。降级路径:
|
||||
|
||||
- `Doc.HasEvents() == false` ⇒ 召回时整篇渲成一条 `cold_storage` 事件(旧行为)
|
||||
- 不伪造成「有原始事件」;有日志可观测
|
||||
|
||||
### 判据与变异
|
||||
|
||||
新增两组判据(各条都在改动前设计、改动后可跑):
|
||||
|
||||
| 文件 | 判据数 | 覆盖 |
|
||||
|---|---|---|
|
||||
| `internal/memory/document/blockflow_test.go` | 7 | 存储形态、文本块粒度、逐条保留、视图定位、幂等、存量兼容、原文可用性 |
|
||||
| `internal/agent/core/recalldoc_test.go` | 5 | 逐条回填、原始时间戳、块迁回、回填不截断、存量降级 |
|
||||
|
||||
**变异测试(全部被抓住)**:
|
||||
|
||||
| 变异 | 被哪条抓住 |
|
||||
|---|---|
|
||||
| 正文落库时 truncate(100) | `TestL2_ArchivePreservesFullText` / `TestL2_ContentIsRenderedViewNotSource` |
|
||||
| Input/Response 合成一块 | `TestL2_InputAndResponseAreSeparateTextBlocks`(及 3 条连带) |
|
||||
| 召回用 `d.CreatedAt` 当时间戳 | `TestRecallByDoc_RestoresEachEventAtOriginalTimestamp` |
|
||||
| 召回不回填 Blocks | `TestRecallByDoc_MigratesBlocksBackToContext` |
|
||||
| 召回再截 2000 字 | `TestRecallByDoc_TextIsNotTruncatedOnTheWayBack` |
|
||||
|
||||
### 未做(明确不做)
|
||||
|
||||
- **不改公开 SDK 契约**:`TextEvent` / `DocMemoryAPI` / `InsertWithMedia` 已等价
|
||||
- **不迁移存量文档**:等自然冲刷
|
||||
- **不改渲染层截断常数**(失效 5):根因已在存储形态修掉;渲染按预算分配是独立议题,
|
||||
避免与本次修复混在一起、互相掩盖
|
||||
291
docs/zh/memory-usability-plan.md
Normal file
291
docs/zh/memory-usability-plan.md
Normal file
@ -0,0 +1,291 @@
|
||||
# 三层记忆可用性修复计划:统一向量化 + 提示词让 agent 真正理解
|
||||
|
||||
> 状态:**待执行**(2026-10-22)
|
||||
> 前置:`docs/zh/memory-blockflow-repair-plan.md`(L1↔L2 结构同构,已实施)
|
||||
> 本文修的是**上一轮没修的那一层**:结构对了,但检索质量与提示词仍然不可用。
|
||||
|
||||
---
|
||||
|
||||
## 〇、起点:承认上一轮的判断错误
|
||||
|
||||
上一轮我修了 L1↔L2 的结构(事件列表 + 正文进块 + 按原时序回填),
|
||||
然后声称「机制已通」。**结构确实通了,但功能不可用。**
|
||||
|
||||
用生产数据实测(1054 篇文档、chineseclip 512 维)三个互不相关的查询:
|
||||
|
||||
```
|
||||
查询 "doc 层召回是怎么设计的"
|
||||
score=0.7834 来自 4 个来源的 61 条对话 (cold_storage, agentcli...)
|
||||
score=0.7795 来自 1 个来源的 2 条对话 (agent) 涉及: map, content, dsh, 占用
|
||||
score=0.7768 来自 1 个来源的 1 条对话 (agent) 涉及: 自检, OK
|
||||
|
||||
查询 "hmap 插件安装验签"
|
||||
score=0.7922 来自 1 个来源的 1 条对话 (agent) 涉及: map, content, 是不是, pi
|
||||
|
||||
查询 "QQ 头像"
|
||||
score=0.7601 来自 1 个来源的 1 条对话 (agent) 涉及: CQ, qq, ok
|
||||
```
|
||||
|
||||
**分数全挤在 0.75~0.79,区分度≈0,命中内容与查询无关。**
|
||||
|
||||
### 判据缺失(最该记的一条)
|
||||
|
||||
上一轮写了 16 条判据、6 个变异,全部通过。但**没有一条验证「检索结果必须与查询相关」**。
|
||||
所以「全绿」证明的只是我实现了我以为的东西 —— 不是它可用。
|
||||
|
||||
> ★ 教训:**结构判据不能替代质量判据**。
|
||||
> 「字段非空」「条数正确」「不 panic」都可能在检索完全失效时依然全绿。
|
||||
|
||||
---
|
||||
|
||||
## 一、根因(三条,按依赖排序)
|
||||
|
||||
### 根因 1:向量的输入是 jieba 词袋,不是正文
|
||||
|
||||
`internal/memory/document/document.go` — `denseFor`:
|
||||
|
||||
```go
|
||||
if tv, err := s.denseSpace.VectorizeDense(doc.Summary + " " + doc.Content); err == nil {
|
||||
```
|
||||
|
||||
而 `Summary` 由 `summarizeEntries` 生成,后者调用链是:
|
||||
|
||||
```
|
||||
summarizeEntries → memory.ExtractKeywords → TokenizeContentWords → jieba.Tag()
|
||||
extractTags → 同上
|
||||
extractEntities → 同上
|
||||
```
|
||||
|
||||
`TokenizeContentWords`(`internal/memory/cut.go:256`)就是 `jieba.Tag()` 分词 + 停用词过滤,
|
||||
产出形如 `涉及: map, content, 是不是, pi`。
|
||||
|
||||
**这是与设计直接矛盾的**。`ARCHITECTURE.md` L131 / L174 明写向量层级:
|
||||
|
||||
```
|
||||
统一多模态空间(主,带 fingerprint)→ StaticEmbedder 词嵌入 → TF-IDF(回退)
|
||||
```
|
||||
|
||||
`Summary` 是**主路径的输入**,却由**最末一级的词法分词**产出。
|
||||
全库所有文档的 Summary 因此高度同质(都是「来自 N 个来源的 M 条对话 涉及: …」),
|
||||
余弦相似度自然全挤在高位、无区分度。
|
||||
|
||||
### 根因 2:媒体块没有语义,且不需要单独生成
|
||||
|
||||
设计要求「doc 支持多模态摘要」。当前实现里:
|
||||
|
||||
- `Doc.Summary` 只有文本路径,**媒体块从不参与**
|
||||
- `crossModalMarkdown` 里媒体只渲染成 `[image/png blk_media_xxx]`(`blockLabel`:MIME + 短 ID)
|
||||
- 一篇带 20 张图的文档,摘要里没有「有图」的任何信息
|
||||
|
||||
**但「多模态摘要」这个提法本身是歧路**(用户 2026-10-22 指出):
|
||||
|
||||
> 媒体 doc 块一般是和文本紧邻的,所以对应的文本就足够了
|
||||
|
||||
`Doc.Events[i].Blocks` 里媒体块与文本块**在同一事件内**。媒体块的语义由
|
||||
**同事件的文本**天然覆盖,不需要为每个媒体单独生成描述(那正是 CAS 时代
|
||||
「描述式索引」被拆除的同一个错误)。
|
||||
|
||||
⇒ 正确做法不是「给媒体写摘要」,而是:**向量用事件正文算,媒体自动被覆盖**。
|
||||
|
||||
### 根因 3:提示词与注入文案没有任何引导
|
||||
|
||||
**(a)`doc_query` 的工具描述只有一句话**
|
||||
|
||||
```go
|
||||
toolDefWith("doc_query", "查询文档记忆。输入查询内容,返回相关文档摘要。", ...)
|
||||
```
|
||||
|
||||
逐条对照问题:
|
||||
|
||||
| 应有信息 | 现状 |
|
||||
|---|---|
|
||||
| L2 是什么 | ❌ 只说「文档记忆」,没有定义 |
|
||||
| 何时该用 | ❌ 无 |
|
||||
| 调用后会发生什么 | ❌ 「返回相关文档摘要」**与真实行为不符**(真实是逐条按原时序回填到对话时序) |
|
||||
| 与 `memory_recall` 的分工 | ❌ 无 |
|
||||
|
||||
对比同文件里 `【输出规则】`(6 条)、`【工具执行顺序】`(4 条)的详细程度,
|
||||
`doc_query` 是最敷衍的一个。
|
||||
|
||||
**(b)预填充注入文案没有引导**
|
||||
|
||||
`crossModalMarkdown` 输出:
|
||||
|
||||
```
|
||||
【跨模态相关记忆】
|
||||
[1] 来自 4 个来源的 61 条对话 (cold_storage, agentcli...) (来源:context_archived) 相关度:0.78(文本命中)
|
||||
```
|
||||
|
||||
**没有一个字说「可以用 doc_query 取全文」**。模型看到这段只能忽略。
|
||||
|
||||
**(c)系统提示词从未解释三层记忆是什么**
|
||||
|
||||
`grep 记忆 internal/agent/core/tooldefs.go` 全量命中里,没有任何一段说明
|
||||
Context / Document / Graph 三者的分工与使用时机。agent 只能从工具名猜。
|
||||
|
||||
---
|
||||
|
||||
## 二、修复步骤(按依赖,每步先写会红的判据)
|
||||
|
||||
### 步骤 1:判据先行 —— 检索质量
|
||||
|
||||
**先写测试,此时必须红。**
|
||||
|
||||
`internal/memory/document/retrieval_quality_test.go`:
|
||||
|
||||
- 构造真实语料(约 50 篇,内容取自生产副本的真实事件正文 + 人工标注的查询-答案对)
|
||||
- 断言:给定查询,**正确文档必须排第一**(top1 准确率 100%,或至少 top3 内)
|
||||
- 断言:互不相关的查询,top1 结果**必须不同**
|
||||
- 断言:正确命中的分数必须**显著高于**非命中(区分度阈值)
|
||||
|
||||
**变异**:把 `denseFor` 改回 `Summary + Content` ⇒ 必须红。
|
||||
**变异**:把正文替换成 jieba 词袋 ⇒ 必须红。
|
||||
|
||||
这一步是整个计划的地基。没有它,后面每一步都只是「我以为改对了」。
|
||||
|
||||
### 步骤 2:向量输入改为正文
|
||||
|
||||
`internal/memory/document/document.go` — `denseFor`:
|
||||
|
||||
```
|
||||
现在:VectorizeDense(doc.Summary + " " + doc.Content)
|
||||
改为:VectorizeDense(事件正文拼接)
|
||||
有 Events → 按事件顺序取 InOut() 的 input + response(文本块内容)
|
||||
无 Events(存量)→ 退回 Content
|
||||
```
|
||||
|
||||
**媒体如何被覆盖**:媒体块与文本块在同一事件内,事件的正文进入向量即覆盖它。
|
||||
不需要为媒体单独编码 —— 若将来要「按图搜图」,那是视觉路(`RecallBlocks`)的职责,
|
||||
已有独立通路,与文本路不冲突。
|
||||
|
||||
### 步骤 3:删掉 jieba 摘要路径
|
||||
|
||||
`Summary` / `Tags` / `Entities` 目前全部由 `ExtractKeywords`(jieba)产出。
|
||||
|
||||
- `Tags`:唯一消费点是 `doc_query` 返回值里的「标签: …」(`toolcall.go:1038`),
|
||||
**是词袋噪声,删除该行**
|
||||
- `Entities`:`Doc.Entities` 无任何非测试读取点(跨包的 `injected.Entities`
|
||||
是 `memory.Entity` 另一个类型,无关),**字段可删**
|
||||
- `Summary`:改为**纯展示**(人工阅读 / 日志),由正文首句截取生成,**不参与向量**
|
||||
|
||||
⚠️ 需先确认:`Doc.Tags` / `Doc.Entities` 是否有 WebUI / SDK 侧读取。
|
||||
(已知 `sdk/memory_impl.go` 的 `Doc` 只有 `ID/Title/Content`,无 Tags/Entities。)
|
||||
|
||||
### 步骤 4:预填充注入正文片段 + 引导
|
||||
|
||||
`crossModalMarkdown` 改为:
|
||||
|
||||
```
|
||||
【历史记忆(按相关度)】
|
||||
[1] 2026-10-12 11:09 · qq · 相关度 0.83
|
||||
紫蓝红三色带的配色参考,老大要求…(正文首 2 行)
|
||||
[2] …(含 2 张图:image/png, image/jpeg)
|
||||
|
||||
这些是**历史**片段(不是当前对话)。要看完整原文,
|
||||
调用 doc_query 把对应事件按原时间戳拉回对话时序。
|
||||
```
|
||||
|
||||
要点:
|
||||
- 去掉「来自 N 个来源的 M 条对话」这类统计描述
|
||||
- 给出**时间 + 来源 + 正文开头**(真实内容,让模型能判断相关性)
|
||||
- 媒体只报数量与类型,不编描述
|
||||
- **明确告知 doc_query 的作用**
|
||||
|
||||
### 步骤 5:`doc_query` 工具描述重写
|
||||
|
||||
按 `memory_*` 的详细度,必须覆盖:
|
||||
|
||||
- L2 是什么(历史事件层,不直接进当前对话)
|
||||
- 何时用(记得办过但想不起细节 / 跨会话跨天 / 需要原始过程而非结论)
|
||||
- 调用后发生什么(**把事件逐条按原时间戳回填到对话时序**,不是返回一段摘要)
|
||||
- 与 `memory_recall` 的分工(图记忆=关系与最新值;doc=整段过程与原文)
|
||||
- 副作用(从 L2 取出,取走后该文档不再在 L2)
|
||||
|
||||
### 步骤 6:系统提示词增加「三层记忆」段落
|
||||
|
||||
在 `buildSystemPrompt` 里加一段,说清三层是什么、怎么用:
|
||||
|
||||
```
|
||||
【三层记忆】记忆分三层,你现在看到的是第一层。
|
||||
- 对话时序(L1):最近的对话,已在你的上下文里。超过窗口的会被移出。
|
||||
- 历史记忆(L2):移出后的事件与原文仍完整保存。自动预填充会给你相关片段;
|
||||
要给看全文/完整过程,用 doc_query —— 它会把那些事件按原时间戳插回对话时序。
|
||||
- 图记忆(L3):关系与事实的长期存储(谁是什么、什么等于什么、最新值是什么)。
|
||||
用 memory_recall 查;用 memory_commit 记。
|
||||
选择:要"原文与过程"→ doc_query;要"关系与当前值"→ memory_recall;
|
||||
要"成文知识"→ knowledge_search。
|
||||
```
|
||||
|
||||
**注意**:这段属于「内核运行时块」(提示词分层规范:内核行为由内核生成),
|
||||
不是工具用法(工具用法归 `description`)。它讲的是**内核的真实执行语义**
|
||||
(回填到时序、自动预填充),符合分层原则。
|
||||
|
||||
---
|
||||
|
||||
## 三、判据清单(每步都要能红)
|
||||
|
||||
| # | 判据 | 变异(必须红) |
|
||||
|---|---|---|
|
||||
| 1 | 检索质量:top1 命中 / 不相关查询 top1 不同 / 分数有区分度 | `denseFor` 改回 `Summary+Content`;或换回 jieba 词袋 |
|
||||
| 2 | 向量输入是正文:断言 `denseFor` 读的是 Events 正文(可用哨兵串) | 正文里埋哨兵,改回 Summary ⇒ 哨兵不出现在向量输入 |
|
||||
| 3 | 摘要不含 jieba 词袋:`Summary` 不出现「来自 N 个来源的 M 条对话」 | 恢复 `summarizeEntries` ⇒ 红 |
|
||||
| 4 | 预填充文案含正文与引导:必须含 `doc_query` 字样 + 正文片段 | 改回旧渲染 ⇒ 红 |
|
||||
| 5 | 工具描述覆盖五项(L2 定义/何时用/调用后行为/分工/副作用) | 删任一项 ⇒ 红 |
|
||||
| 6 | 系统提示词含三层记忆段落且语义与实现一致 | 回填行为改掉而提示词不改 ⇒ 红(跨文件一致性) |
|
||||
|
||||
**判据 6 特别说明**:提示词说「按原时间戳插回时序」这件事,
|
||||
必须与 `restoreDocEvents` 的真实行为绑在一条判据里 ——
|
||||
否则就是「提示词在说谎」,那比没说更糟。
|
||||
|
||||
---
|
||||
|
||||
## 四、禁止事项
|
||||
|
||||
| 禁止 | 原因 |
|
||||
|---|---|
|
||||
| 用 jieba 词袋当向量输入或摘要内容 | 与「统一向量化」直接矛盾;全库同质,检索失效 |
|
||||
| 为媒体块单独生成文本描述 | CAS 时代的「描述式索引」错误;媒体语义由同事件文本覆盖 |
|
||||
| 只改结构不改提示词 | 机制改了模型不知道 = 等于没改 |
|
||||
| 用「字段非空 / 条数正确 / 不 panic」当检索判据 | 检索完全失效时这些也全绿 |
|
||||
| 声称「机制已通」而不给出生产数据的检索质量实测 | 上一轮的教训 |
|
||||
|
||||
---
|
||||
|
||||
## 五、验证
|
||||
|
||||
1. `go test ./... -count=1` 全绿(现有 58 包基线)
|
||||
2. 新判据全绿,且 6 个变异各自被抓住
|
||||
3. **生产副本实测**:用真实数据跑检索,给出 top1/top3 命中率与分数分布
|
||||
(对比修复前的 0.75~0.79 无区分度)
|
||||
4. 生产部署后用真实对话观察:agent 是否在需要时调用 `doc_query`
|
||||
|
||||
---
|
||||
|
||||
## 六、标准流程(本次严格执行)
|
||||
|
||||
上次直接提交 `main` + rebase + amend + 绕过 CI,不符合 `docs/git-branching.md`。
|
||||
这次必须:
|
||||
|
||||
```
|
||||
1. git checkout -b feature/memory-usability main
|
||||
2. 按步骤 1→6 提交(每步独立 commit,先写判据再改)
|
||||
3. git push -u origin feature/memory-usability
|
||||
4. 开 PR
|
||||
5. 等 CI 10 项全部通过
|
||||
6. 合并 main(不绕过必需检查)
|
||||
7. 部署 + 生产验证
|
||||
```
|
||||
|
||||
⚠️ 远端仓库已改名 `JianFeeeee/HologramAgent`(origin 与 gitcode 均已更新)。
|
||||
|
||||
---
|
||||
|
||||
## 七、待确认
|
||||
|
||||
1. **`Doc.Tags` / `Doc.Entities` 是否可删?**
|
||||
我查到非测试读取点只有 `doc_query` 返回里的「标签:」一行(可删)。
|
||||
但需确认 WebUI / 外部插件是否依赖(SDK 的 `Doc` 无这两个字段)。
|
||||
|
||||
2. **判据 1 的语料从哪来?**
|
||||
建议从生产副本抽 50 篇真实文档 + 人工标注 10~15 个查询-答案对。
|
||||
是否允许我把这些真实文本(脱敏后)作为测试 fixture 入库?
|
||||
Reference in New Issue
Block a user