feat(memory-source): 打通 commit→distill→memories.source→recall 全链(含 metadata.source 与 wiki:<路径> 标记)

- recall.go: RecallResult.Source = 记忆 source(此前只有 struct 字段没人填)
- distill/engine.go: DistillInput.Source(来源透传)
- auto_distill.go: AutoDistillTrigger 增加 source 参数并透传
- routes/core.go: /commit 请求体新增 source + metadata(此前缺失 → Go 静默丢弃 → 入库恒空)
- server.go: 蒸馏产物 Source = wiki:<路径>(有出处时),手动蒸馏端点也支持 source
- 清理 worker 误提交的 15MB 二进制(go/cmd/zhiyid/zhiyid-new)并加 .gitignore
This commit is contained in:
小唯
2026-09-26 21:43:54 +08:00
parent 8026142a08
commit 88f610aa1e
6 changed files with 35 additions and 7 deletions
Binary file not shown.
+2 -1
View File
@@ -13,7 +13,7 @@ import (
var DistillEngineRef *distill.Engine
// AutoDistillTrigger commit 后自动触发蒸馏流水线
func AutoDistillTrigger(episodeID string, content string, category string, namespace string, agentID string) {
func AutoDistillTrigger(episodeID string, content string, category string, namespace string, agentID string, source string) {
// 1. 硬规则过滤
if !distill.HardRulesPass(content) {
return
@@ -27,6 +27,7 @@ func AutoDistillTrigger(episodeID string, content string, category string, names
Category: distill.Category(category),
Namespace: namespace,
AgentID: agentID,
Source: source,
})
} else {
// 降级: 关键词提取
+15 -4
View File
@@ -79,6 +79,10 @@ func (a *API) Commit(w http.ResponseWriter, r *http.Request) {
// resolve_conflict=true: 确认这是修正/新事实, 检出冲突时仍触发蒸馏
// (2026-09-18 t_a54aae79 #14: 此前 note 里宣传了该参数但请求结构体没有它)
ResolveConflict bool `json:"resolve_conflict"`
// 2026-09-26:记忆出处(笔记路径等)。顶层 source 优先;wiki_curator/插件走 metadata.source。
// 此前结构体没有这两个字段 → Go 静默丢弃 → 入库 source 恒为空("发送≠落库")。
Source string `json:"source"`
Metadata map[string]string `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, 400, "invalid body")
@@ -95,6 +99,13 @@ func (a *API) Commit(w http.ResponseWriter, r *http.Request) {
req.Category = "general"
}
// 2026-09-26:解析来源(笔记出处)—— 顶层 source 优先,其次 metadata.source。
// 空值维持原行为:蒸馏产物来源保持 llm_distill。
src := req.Source
if src == "" && req.Metadata != nil {
src = req.Metadata["source"]
}
// 1. 写入 episode(原始日志,总是写入)
epID, err := a.LanceDB.InsertEpisode(req.AgentID, req.Namespace, req.Content, req.Category)
if err != nil {
@@ -120,7 +131,7 @@ func (a *API) Commit(w http.ResponseWriter, r *http.Request) {
a.mergeMemory(existing, req.Content, req.Category, req.AgentID)
// 合并后仍需触发蒸馏:让 t6 等冲突检测器有机会比对这条记忆,
// 且 exact_dup 可能是 namespace 相同但实际语义冲突(如"在"vs"不在")。
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID)
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID, src)
respond(w, 200, map[string]string{
"episode_id": epID, "memory_id": existing.ID,
"status": "merged", "reason": "exact_dup",
@@ -134,7 +145,7 @@ func (a *API) Commit(w http.ResponseWriter, r *http.Request) {
if eErr == nil && cosineSimilarity(vector, ev) >= 0.98 {
a.mergeMemory(existing, req.Content, req.Category, req.AgentID)
// 与 exact_dup 同理:合并后仍需触发蒸馏,让冲突检测器有机会比对。
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID)
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID, src)
respond(w, 200, map[string]string{
"episode_id": epID, "memory_id": existing.ID,
"status": "merged", "reason": "near_dup",
@@ -200,9 +211,9 @@ func (a *API) Commit(w http.ResponseWriter, r *http.Request) {
// 2026-09-18 (t_a54aae79 #14): 客户端显式确认修正/新事实 → 仍触发蒸馏
respData["conflicts"] = conflicts
respData["distill_note"] = "conflict detected but resolve_conflict=true: proceeding with auto-distill"
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID)
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID, src)
} else {
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID)
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID, src)
}
respond(w, 201, respData)
}
+14 -2
View File
@@ -207,7 +207,7 @@ func NewServer() http.Handler {
UpdatedAt: time.Now(),
DerivedFrom: input.EpisodeID,
Freshness: "fresh",
Source: "llm_distill", // 2026-09-07 方案C provenance: LLM蒸馏推断来源
Source: srcOrLLMDistill(input.Source), // 2026-09-26:有笔记出处→"wiki:<路径>";否则沿用 llm_distill(2026-09-07 方案C provenance)
}
if err := ldb.InsertMemory(mem); err != nil {
log.Printf("[distill] commit fact failed: %v", err)
@@ -953,6 +953,8 @@ func NewServer() http.Handler {
Category string `json:"category"`
Namespace string `json:"namespace"`
AgentID string `json:"agent_id"`
// 2026-09-26:手动蒸馏也支持带出处(便于补跑 wiki 批时保留笔记路径)
Source string `json:"source"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request body", http.StatusBadRequest)
@@ -969,7 +971,7 @@ func NewServer() http.Handler {
req.AgentID = "hermes-a06"
}
epID := fmt.Sprintf("ep_manual_%d", time.Now().UnixNano())
routes.AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID)
routes.AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID, req.Source)
respondJSON(w, 200, map[string]string{"status": "queued", "episode_id": epID})
})
// 蒸馏队列状态端点(G8-G9 配套)
@@ -1737,3 +1739,13 @@ func runStartupChecks(backend string) {
log.Printf("[startup] 数据目录正常: %s", canonicalDataDir)
}
}
// srcOrLLMDistill 2026-09-26:蒸馏产物来源判定。
// 有笔记出处(commit 时带 source/metadata.source,如 wiki_curator 传的 .md 路径)→ 标 "wiki:<路径>",
// 使 recall 回来的每条记忆都能追溯到自己来自哪篇笔记;无出处则沿用历史值 llm_distill。
func srcOrLLMDistill(src string) string {
if src != "" {
return "wiki:" + src
}
return "llm_distill"
}
+2
View File
@@ -41,6 +41,8 @@ type DistillInput struct {
Category Category
Namespace string
AgentID string
// 2026-09-26:来源(笔记路径等)—— 透传到蒸馏产物 MemoryRecord.Source,recall 时带回
Source string
}
// DistillResult 蒸馏输出
+2
View File
@@ -240,6 +240,8 @@ func (p *RecallPipeline) recallUncached(query, namespace string, topK int, diver
// 2026-09-22:带上来源,供全库召回时分辨记忆归属域
Namespace: c.Namespace,
AgentID: c.AgentID,
// 2026-09-26:把记忆出处直通给调用方 —— 蒸馏/审核记忆的 source 形如 "wiki:<笔记路径>"
Source: c.Source,
})
}
}