feat(rel): 文档层接入场景(document 节点挂场景)

场景贯穿流水线的 doc 层补完:
- 新增 TagSceneDocument,linkBlocksToDocument 里建文档节点时一并挂场景;
- RecallByScene 返回该场景下的文档 id(可枚举「这个场面有哪些文档」);
- 文档场景由**来源派生**(chan:<source>),不存冗余 Doc.Scene 字段——
  存一份会随来源改名而说谎,是同一事实的第二份真相。

go build/vet 干净,go test -count=1 ./internal/memory/ ./internal/agent/core/ 全绿。
This commit is contained in:
JianFeeeee
2026-09-15 09:16:22 +08:00
parent 9d45cd0277
commit b7e47a1b1f
2 changed files with 58 additions and 1 deletions

View File

@ -92,6 +92,13 @@ func (a *Agent) linkBlocksToDocument(docID string, blocks []memory.MemoryBlock,
log.Printf("[media] 写入 L3 文档节点失败 (%s): %v", docID, err)
return 0
}
// 文档层与场景模型兼容:文档节点也进场景,好让「这个场面有哪些文档」
// 可枚举、可统计(场景贯穿流水线的 doc 层落地)。
if scene != "" {
if err := a.memory.TagSceneDocument(scene, docID); err != nil {
log.Printf("[media] 文档挂场景失败 (%s): %v", docID, err)
}
}
// 文档层把场景传给块归档进图库的块属于该文档的来源场面QQ 归档的图
// 就该挂在 chan:qq 上),否则 L3 里这批块在场景召回中不可见。
for i := range blocks {

View File

@ -134,6 +134,8 @@ type SceneRecall struct {
// 为什么场景要能取回块:块是流水线里最细的子项目,而「那场对话里发过来的
// 那张图」只记住名字是没用的——场面重现时要把块本身带回来。
Blocks []MemoryBlock `json:"blocks,omitempty"`
// Documents 是该场景下的 L3 文档节点 id文档的场景由来源派生见 TagSceneDocument
Documents []string `json:"documents,omitempty"`
}
// tagSceneTx 在事务内把「关系 + 实体」挂到场景上(幂等 upsert
@ -434,7 +436,29 @@ func (g *GraphDB) RecallByScene(scenes []string, limit int) (*SceneRecall, error
}
out.Blocks = append(out.Blocks, b)
}
return out, brows.Err()
if err := brows.Err(); err != nil {
return nil, err
}
docQuery := `SELECT sr.ref_text, MAX(sr.weight) AS w
FROM scene_refs sr JOIN scenes s ON sr.scene_id = s.id
WHERE ` + where + ` AND sr.kind = 'document'
GROUP BY sr.ref_text ORDER BY w DESC LIMIT ?`
docArgs := append(append([]interface{}{}, args...), limit)
drows, err := g.db.Query(docQuery, docArgs...)
if err != nil {
return nil, err
}
defer drows.Close()
for drows.Next() {
var id string
var w float64
if err := drows.Scan(&id, &w); err != nil {
return nil, err
}
out.Documents = append(out.Documents, id)
}
return out, drows.Err()
}
// SceneStats 返回各场景的规模,按引用数降序。
@ -521,3 +545,29 @@ func (g *GraphDB) ScenesOfRelation(relationID int64) ([]string, error) {
}
return keys, rows.Err()
}
// TagSceneDocument 把 L3 文档节点挂到场景上document 层与场景模型兼容)。
//
// 文档的「场景」不由存储字段决定,而由**来源**派生chan:<source>)——存一份
// 冗余的 Doc.Scene 会随来源改名而说谎,是同一事实的第二份真相。
// 这里只登记「这份文档属于哪些场面」,供 scene 侧枚举与统计。
func (g *GraphDB) TagSceneDocument(sceneKey, docID string) error {
if docID == "" {
return nil
}
key := NormalizeSceneKey(sceneKey)
if key == "" {
return nil
}
g.mu.Lock()
defer g.mu.Unlock()
tx, err := g.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if err := tagSceneRefTx(tx, key, "document", 0, docID, 1.0); err != nil {
return err
}
return tx.Commit()
}