diff --git a/go/internal/api/routes/graph.go b/go/internal/api/routes/graph.go index 731fc30..780e0c3 100644 --- a/go/internal/api/routes/graph.go +++ b/go/internal/api/routes/graph.go @@ -60,7 +60,7 @@ func (ga *GraphAPI) Query(w http.ResponseWriter, r *http.Request) { return } // 默认跨 namespace 搜索(空 = 匹配所有,§2.5.6) - results := ga.Graph.Query(normalizeEntity(req.Entity), req.Relation, req.Namespace) + results := ga.Graph.Query(NormalizeEntity(req.Entity), req.Relation, req.Namespace) respond(w, 200, map[string]interface{}{"results": results, "count": len(results)}) } @@ -89,8 +89,8 @@ func (ga *GraphAPI) Navigate(w http.ResponseWriter, r *http.Request) { // 模式 2: 双向 BFS(source + target) if req.Source != "" && req.Target != "" { - source := normalizeEntity(req.Source) - target := normalizeEntity(req.Target) + source := NormalizeEntity(req.Source) + target := NormalizeEntity(req.Target) paths, err := ga.Graph.NavigateBiDir(source, target, req.MaxHops, req.Namespace, req.RelationFilter) if err != nil { respondError(w, 500, "navigate failed: "+err.Error()) @@ -109,7 +109,7 @@ func (ga *GraphAPI) Navigate(w http.ResponseWriter, r *http.Request) { respondError(w, 400, "entity (or source+target) required") return } - entity := normalizeEntity(req.Entity) + entity := NormalizeEntity(req.Entity) paths, err := ga.Graph.Navigate(entity, req.MaxHops, req.Namespace, req.RelationFilter) if err != nil { respondError(w, 500, "navigate failed: "+err.Error()) @@ -162,8 +162,8 @@ func stripPrefix(id string) string { return strings.TrimPrefix(id, "n_") } -// normalizeEntity 规整实体名:去特殊字符 + n_前缀,保留中文和 Unicode 字符 -func normalizeEntity(entity string) string { +// NormalizeEntity 规整实体名:去特殊字符 + n_前缀,保留中文和 Unicode 字符 +func NormalizeEntity(entity string) string { if strings.HasPrefix(entity, "n_") { entity = entity[2:] } diff --git a/go/internal/api/routes/skill_execute.go b/go/internal/api/routes/skill_execute.go index 6a3cc16..6f61140 100644 --- a/go/internal/api/routes/skill_execute.go +++ b/go/internal/api/routes/skill_execute.go @@ -149,10 +149,26 @@ func applyForgettingLinkage(skill *BetaSkill, success bool) { // applyDegreePenalty 降低记忆 degree(retired skill → degree -2) func applyDegreePenalty(memID string, penalty int) error { - // degree 保护通过 BayesianSkills.GetSkillForMemory() 在 Forgetter.ShouldForget 中生效 - _ = memID - _ = penalty - return nil + ldb := getLDB() + if ldb == nil { + return fmt.Errorf("ldb not available") + } + candidates, err := ldb.GetSkillCandidates(1, 1000) + if err != nil { + return err + } + for _, mem := range candidates { + if mem.ID == memID { + newImportance := mem.Importance - float64(penalty)*0.05 + if newImportance < 0.01 { + newImportance = 0.01 + } + return ldb.Update("memories", memID, map[string]any{ + "importance": newImportance, + }) + } + } + return fmt.Errorf("memory not found: %s", memID) } // applyDecayAcceleration 加速记忆衰减(通过更新 importance) diff --git a/go/internal/api/server.go b/go/internal/api/server.go index ca735ce..62b788f 100644 --- a/go/internal/api/server.go +++ b/go/internal/api/server.go @@ -29,26 +29,6 @@ import ( // cachedGraphStoreRef 全局图谱缓存引用(用于 graph write 操作时主动失效) var cachedGraphStoreRef *storage.GraphCacheRef -// normalizeEntity 规整实体名:去特殊字符 + n_前缀 -func normalizeEntity(entity string) string { - if strings.HasPrefix(entity, "n_") { - entity = entity[2:] - } - // 保留中文、字母、数字、下划线、连字符,过滤其他特殊字符 - clean := strings.Map(func(r rune) rune { - if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' || - (r >= 0x4e00 && r <= 0x9fa5) { // 中文 Unicode 范围 - return r - } - return '_' - }, strings.ToLower(strings.TrimSpace(entity))) - // 合并连续下划线 - for strings.Contains(clean, "__") { - clean = strings.ReplaceAll(clean, "__", "_") - } - return strings.Trim(clean, "_") -} - // skillByNameHandler GET/DELETE /api/v1/skills/{name} 的 method 分支分发器 func skillByNameHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { @@ -260,19 +240,6 @@ func NewServer() http.Handler { // ─── 跨 Agent 缓存失效回调 ─────────────────── // 收到其他 Agent 的广播 → 失效本地缓存 governance.GlobalEventBus.Subscribe("cache.invalidate", "self") - go func() { - // 本地处理 cache.invalidate 事件(通过 HTTP self-call) - http.HandleFunc("/_internal/cache/invalidate", func(w http.ResponseWriter, r *http.Request) { - var evt struct { - Payload map[string]string `json:"payload"` - } - json.NewDecoder(r.Body).Decode(&evt) - ns := evt.Payload["namespace"] - storage.SearchCacheInstance.Invalidate(ns) - log.Printf("[cache] 跨 Agent 失效: %s", ns) - w.WriteHeader(200) - }) - }() // ─── 路由注册 ────────────────────────────── @@ -442,10 +409,10 @@ func NewServer() http.Handler { var noteResults []map[string]interface{} var paths []map[string]interface{} if entity != "" { - p, err := graphStore.Navigate(normalizeEntity(entity), maxHops, "", nil) + p, err := graphStore.Navigate(routes.NormalizeEntity(entity), maxHops, "", nil) if err == nil { paths = p - entities := []string{normalizeEntity(entity)} + entities := []string{routes.NormalizeEntity(entity)} for _, path := range p { if s, ok := path["source"].(string); ok { entities = append(entities, s) @@ -499,7 +466,7 @@ func NewServer() http.Handler { var entityA, entityB string // 模式1: "A和B是什么关系" / "A和B有什么关系" / "A和B的关系" hasAnd := strings.Contains(cleanQuery, "和") || strings.Contains(cleanQuery, "与") - if hasAnd && (strings.Contains(cleanQuery, "关系") || strings.Contains(cleanQuery, "关系")) { + if hasAnd && (strings.Contains(cleanQuery, "关系") || strings.Contains(cleanQuery, "关联")) { delim := "和" if strings.Contains(cleanQuery, "与") { delim = "与" @@ -524,13 +491,13 @@ func NewServer() http.Handler { var result map[string]interface{} if entityA != "" && entityB != "" { // 双向查询:A → B 的关系 - paths, _ := graphStore.Navigate(normalizeEntity(entityA), 2, "", nil) - relB, _ := graphStore.Navigate(normalizeEntity(entityB), 2, "", nil) + paths, _ := graphStore.Navigate(routes.NormalizeEntity(entityA), 2, "", nil) + relB, _ := graphStore.Navigate(routes.NormalizeEntity(entityB), 2, "", nil) // 找 A → B 的直接边 var directPath map[string]interface{} for _, p := range paths { - toNorm := normalizeEntity(p["to"].(string)) - targetNorm := normalizeEntity(entityB) + toNorm := routes.NormalizeEntity(p["to"].(string)) + targetNorm := routes.NormalizeEntity(entityB) if toNorm == targetNorm { directPath = p break @@ -548,7 +515,7 @@ func NewServer() http.Handler { } else { // 单实体查询:找 entity 的关系网 entity := cleanQuery - paths, _ := graphStore.Navigate(normalizeEntity(entity), 2, "", nil) + paths, _ := graphStore.Navigate(routes.NormalizeEntity(entity), 2, "", nil) // 按 relation 分组 relCounts := make(map[string]int) for _, p := range paths { @@ -702,7 +669,10 @@ func NewServer() http.Handler { EventType string `json:"event_type"` Callback string `json:"callback_url"` } - json.NewDecoder(r.Body).Decode(&req) + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } governance.GlobalEventBus.Subscribe(req.EventType, req.Callback) respondJSON(w, 200, map[string]string{"status": "subscribed"}) }) @@ -711,7 +681,10 @@ func NewServer() http.Handler { EventType string `json:"event_type"` Callback string `json:"callback_url"` } - json.NewDecoder(r.Body).Decode(&req) + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } governance.GlobalEventBus.Unsubscribe(req.EventType, req.Callback) respondJSON(w, 200, map[string]string{"status": "unsubscribed"}) }) @@ -726,7 +699,10 @@ func NewServer() http.Handler { Type string `json:"type"` Payload map[string]string `json:"payload"` } - json.NewDecoder(r.Body).Decode(&evt) + if err := json.NewDecoder(r.Body).Decode(&evt); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } ns := evt.Payload["namespace"] storage.SearchCacheInstance.Invalidate(ns) log.Printf("[cache] 跨 Agent 失效接收: %s", ns) @@ -777,7 +753,10 @@ func NewServer() http.Handler { AgentType string `json:"agent_type"` } if r.Method == "POST" { - json.NewDecoder(r.Body).Decode(&req) + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } forgetter.SetAgentType(req.AgentType) } respondJSON(w, 200, map[string]interface{}{ @@ -796,7 +775,10 @@ func NewServer() http.Handler { Namespace string `json:"namespace"` AgentID string `json:"agent_id"` } - json.NewDecoder(r.Body).Decode(&req) + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } if req.Content == "" { respondJSON(w, 400, map[string]string{"error": "content required"}) return @@ -964,7 +946,24 @@ func NewServer() http.Handler { // G6: ephemeral namespace 管理 mux.HandleFunc("/api/v1/admin/ephemeral/clean", func(w http.ResponseWriter, r *http.Request) { - cleaned := []string{} + now := time.Now().UnixMilli() + agents := agentRegistry.ListAgents() + var cleaned []string + for _, agent := range agents { + if now-agent.LastSeen > 5*60*1000 { + ns := agent.AgentID + "-ephemeral" + zeroVec := make([]float32, 1024) + memories, _ := ldb.Search("memories", zeroVec, 1000, ns) + for _, mem := range memories { + ldb.SoftDelete(mem.ID, "ephemeral_expired") + } + if len(memories) > 0 { + cleaned = append(cleaned, ns) + log.Printf("[ephemeral] cleaned %d memories from %s (inactive for %ds)", + len(memories), ns, (now-agent.LastSeen)/1000) + } + } + } respondJSON(w, 200, map[string]interface{}{ "status": "cleaned", "cleaned_namespaces": cleaned, @@ -981,7 +980,10 @@ func NewServer() http.Handler { var evt struct { Payload map[string]string `json:"payload"` } - json.NewDecoder(r.Body).Decode(&evt) + if err := json.NewDecoder(r.Body).Decode(&evt); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } ns := evt.Payload["namespace"] storage.SearchCacheInstance.Invalidate(ns) log.Printf("[cache] 内部跨 Agent 失效: %s", ns) diff --git a/go/zhiyid-new b/go/zhiyid-new index eaea022..09a091c 100755 Binary files a/go/zhiyid-new and b/go/zhiyid-new differ