memoryweave/go/internal/api/server.go

1060 lines
36 KiB
Go
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.

// HTTP 服务器 — 多 Agent 架构FileGraph + NetworkEventBus + 跨Agent缓存失效
package api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/xiaoxue/memoryweave/internal/api/middleware"
"github.com/xiaoxue/memoryweave/internal/api/routes"
"github.com/xiaoxue/memoryweave/internal/distill"
"github.com/xiaoxue/memoryweave/internal/governance"
"github.com/xiaoxue/memoryweave/internal/metrics"
"github.com/xiaoxue/memoryweave/internal/models"
"github.com/xiaoxue/memoryweave/internal/selfoptimize"
"github.com/xiaoxue/memoryweave/internal/storage"
)
// 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 == '-' {
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 {
case http.MethodGet:
routes.Skills.Get(w, r)
case http.MethodDelete:
routes.Skills.Delete(w, r)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func NewServer() http.Handler {
mux := http.NewServeMux()
// ─── 初始化依赖 ──────────────────────────────
emb := storage.NewEmbedder(os.Getenv("VLLM_ENDPOINT"))
rerank := storage.NewReranker(os.Getenv("RERANK_ENDPOINT"), emb)
// 存储后端选择LanceDB (Rust IPC) → SQLiteCGO→ 内存
var ldb storage.LanceDB
backend := os.Getenv("STORAGE_BACKEND")
switch backend {
case "lancedb":
sockPath := os.Getenv("LANCEDB_SOCKET")
if sockPath == "" {
sockPath = "/tmp/zhiyi-ipc.sock"
}
ldb = storage.NewRustLanceDBClient(sockPath, emb)
log.Printf("[zhiyid] 存储后端: LanceDB (Rust IPC) — %s", sockPath)
case "sqlite":
dbPath := os.Getenv("SQLITE_PATH")
sqliteDB, err := storage.NewSQLiteClient(dbPath)
if err != nil {
log.Printf("[zhiyid] SQLite 初始化失败 (%v),降级为内存存储", err)
ldb = storage.NewMemLanceClient(emb)
} else {
ldb = sqliteDB
log.Printf("[zhiyid] 存储后端: SQLite (CGO) — %s", dbPath)
}
default:
ldb = storage.NewMemLanceClient(emb)
log.Printf("[zhiyid] 存储后端: 内存(零依赖)")
}
// G7.2: 注册 LDB getter供 skill crystallize 使用)
routes.RegisterLDBGetter(func() storage.LanceDB { return ldb })
// 冲突检测器E4.1: Commit 时矛盾检测依赖此实例)
conflictDetector := governance.NewConflictDetector()
api := routes.NewAPI(ldb, emb, rerank, conflictDetector)
// 启动时初始化 Prometheus 指标
go func() {
time.Sleep(1 * time.Second) // 等待 Rust sidecar 就绪
if stats, err := ldb.Stats(); err == nil {
if total, ok := stats["total_memories"].(float64); ok {
metrics.TotalMemories.Set(total)
}
if total, ok := stats["total_episodes"].(float64); ok {
metrics.TotalEpisodes.Set(total)
}
log.Printf("[metrics] 初始化指标: memories=%.0f", stats["total_memories"])
}
}()
// 图谱SQLite (graph_nodes/graph_edges) — 设计要求,非 FileGraph JSON
graphPath := os.Getenv("GRAPH_PATH")
if graphPath == "" {
graphPath = "/var/lib/memoryweave/graph.db"
}
var graphStore governance.GraphStore
gs, err := governance.NewSQLiteGraphStore(graphPath)
if err != nil {
log.Printf("[zhiyid] WARN: SQLite 图谱初始化失败 (%v),降级为 InMemoryGraph", err)
graphStore = governance.NewInMemoryGraph()
} else {
graphStore = gs
log.Printf("[zhiyid] 图谱后端: SQLiteGraphStore — %s", graphPath)
}
graphUpdater := governance.NewAutoGraphUpdater(graphStore)
graphAPI := routes.NewGraphAPI(graphStore)
// G1: Recall 管线挂图谱扩展
api.Pipeline.SetGraphExpander(graphStore)
// G4: 自动蒸馏 → 注入图谱更新器
routes.SetGraphUpdater(graphUpdater)
// 冲突
conflictAPI := routes.NewConflictAPI(conflictDetector)
// ─── 缺口
gapDetector := selfoptimize.NewGapDetector(emb, ldb)
gapDetector.EnableGapRedisPersistence()
// 因果追踪持久化
routes.CascadeR.Tracker().EnableCausalRedisPersistence()
gapAPI := routes.NewGapAPI(gapDetector)
routes.InitGapRepair(gapDetector, ldb) // 共享同一个 GapDetector + LanceDB
// G1a: 挂缺隙记录器0 结果 → 自动记录 miss
api.Pipeline.SetMissRecorder(func(query, namespace string) {
gapDetector.RecordMiss(query + " [" + namespace + "]")
})
feedbackAPI := routes.NewFeedbackAPI(ldb)
// 遗忘器:支持按 Agent 类型设置衰减率
forgetter := governance.NewForgetter()
adminAPI := routes.NewAdminAPI(ldb, forgetter, graphStore)
agentRegistry := routes.NewAgentRegistry(nil)
evalAPI := routes.NewEvalAPI(api.Pipeline, ldb)
l3API := routes.WM
consolPipe := routes.NewConsolidationPipeline(ldb, graphStore, conflictDetector)
consolPipe.SetDataDir("/var/lib/memoryweave/lancedb", "/var/lib/memoryweave/graph.db")
// Obsidian
obsidian := routes.NewObsidianSyncer("", ldb)
// ─── 蒸馏引擎 ───────────────────────────────
llmEndpoint := os.Getenv("LLM_ENDPOINT")
llmModel := os.Getenv("LLM_MODEL")
if llmModel == "" { llmModel = "deepseek/deepseek-v4-pro" }
llmAPIKey := os.Getenv("LLM_API_KEY")
if llmAPIKey == "" { llmAPIKey = os.Getenv("API_KEY") }
distillEngine := distill.NewEngine(llmEndpoint, llmModel, llmAPIKey)
routes.DistillEngineRef = distillEngine
// 蒸馏完成 → 自动图谱更新 + 冲突检测 + 被动验证 + 回写内存
distill.OnDistillComplete = func(input distill.DistillInput, result distill.DistillResult) {
entityNames := make([]string, len(result.Entities))
for i, e := range result.Entities { entityNames[i] = e.Name }
graphUpdater.UpdateFromDistill(&governance.DistillInput{
Content: input.Content, Facts: result.Facts,
Entities: entityNames, Namespace: input.Namespace,
EpisodeID: input.EpisodeID,
})
// 回写蒸馏产物到记忆库LanceDB
for _, fact := range result.Facts {
mem := models.MemoryRecord{
ID: fmt.Sprintf("mem_%d", time.Now().UnixNano()),
AgentID: input.AgentID,
Namespace: input.Namespace,
Content: fact,
Category: "distilled",
Tier: "normal",
QualityScore: result.Overall,
Version: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DerivedFrom: input.EpisodeID,
}
if err := ldb.InsertMemory(mem); err != nil {
log.Printf("[distill] commit fact failed: %v", err)
}
}
// 冲突检测:加载同 namespace 已有记忆进行比较
zeroVec := make([]float32, 1024)
existingMems, _ := ldb.Search("memories", zeroVec, 100, input.Namespace)
conflictExisting := make([]map[string]interface{}, 0)
for _, m := range existingMems {
conflictExisting = append(conflictExisting, map[string]interface{}{
"content": m.Content,
"entities": entityNames, // 用当前内容提取的实体做交叉比较
})
}
for _, c := range conflictDetector.Scan(input.Content, entityNames, conflictExisting) {
if c.Strategy == "latest_wins" { conflictDetector.AutoResolve(c) }
}
// 被动验证:使用已加载的记忆进行 P1/P2/P3 匹配
validMems := make([]selfoptimize.MemoryForValidation, len(existingMems))
for i, m := range existingMems {
validMems[i] = selfoptimize.MemoryForValidation{
ID: m.ID, Content: m.Content, QualityScore: m.QualityScore,
}
}
selfoptimize.Validator.Validate(input.Content, validMems)
// 被动验证结果写回 quality_score
records := selfoptimize.Validator.GetRecords()
for _, r := range records {
if r.Confidence > 0 {
_ = ldb.Update("memories", r.MemoryID, map[string]any{
"quality_score": r.Confidence,
})
}
}
}
// ─── CO_OCCURS 追踪器(持久化到 Redis───
storage.CoOccurTrackerInstance = storage.NewCoOccurTracker(storage.RedisCoOccurPersist())
storage.LoadCoOccurFromRedis()
// ─── Dashboard 持久化到 Redisrestart 不丢指标)───
selfoptimize.EnableRedisPersistence()
// ─── VProp 持久化到 Redisrestart 不丢)───
selfoptimize.VProp.EnableVPropRedisPersistence()
// ─── Skill 持久化到 RedisG7: restart 不丢 skills───
routes.BayesianSkills.EnableRedisPersistence()
// ─── V 值传播器(竞争性架构核心)─────────────
vPropagator := selfoptimize.VProp
// 注入 V 值查询函数到存储层(召回排序融合)
storage.VValueProvider = func(memoryID string) float64 {
return vPropagator.GetMemVValue(memoryID)
}
// 在蒸馏完成回调中记录 V 值
originalOnComplete := distill.OnDistillComplete
distill.OnDistillComplete = func(input distill.DistillInput, result distill.DistillResult) {
// 调用已有的回调
if originalOnComplete != nil {
originalOnComplete(input, result)
}
// 记录 V 值决策:蒸馏成功 = 有用
if len(result.Facts) > 0 {
vPropagator.RecordDecision(
"distill_"+input.EpisodeID,
[]string{input.EpisodeID},
"distill", "success", "",
)
}
// 更新仪表盘指标
selfoptimize.Dash.RecordUseful()
// 记录蒸馏质量损失avg_distill_loss = 1 - Overall
loss := 1.0 - result.Overall
if loss < 0 {
loss = 0
}
if loss > 1 {
loss = 1
}
selfoptimize.Dash.RecordDistillLoss(loss)
}
// ─── 跨 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)
})
}()
// ─── 路由注册 ──────────────────────────────
mux.HandleFunc("/health", routes.HandleHealth)
// ─── Prometheus /metrics ─────────────────────
mux.Handle("/metrics", metrics.Handler())
// 核心 API
mux.HandleFunc("/api/v1/commit", func(w http.ResponseWriter, r *http.Request) {
api.Commit(w, r)
// 跨 Agent 广播 + 自动蒸馏
go func() {
var req struct {
AgentID string `json:"agent_id"`
Namespace string `json:"namespace"`
Content string `json:"content"`
}
if r.Body != nil {
body, _ := io.ReadAll(r.Body)
json.Unmarshal(body, &req)
r.Body = io.NopCloser(bytes.NewReader(body))
}
if req.Namespace != "" {
// 通知其他 Agent缓存失效 + 新记忆事件
governance.PushCacheInvalidate(req.Namespace, "commit")
governance.PushMemoryCommitted(req.AgentID, req.Namespace, "")
}
}()
})
mux.HandleFunc("/api/v1/recall", api.Recall)
mux.HandleFunc("/api/v1/recall/debug", api.RecallDebug)
mux.HandleFunc("/api/v1/feedback", api.Feedback)
mux.HandleFunc("/api/v1/bootstrap", api.Bootstrap)
mux.HandleFunc("/api/v1/stats", api.Stats)
mux.HandleFunc("/api/v1/batch-commit", api.BatchCommit)
// ─── WebSocket 实时推送 ──────────────────────
mux.HandleFunc("/api/v1/ws/", routes.WSHandler)
mux.HandleFunc("/api/v1/ws", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":"ok","protocol":"websocket","endpoint":"/api/v1/ws/{agent_id}"}`))
})
// 知识图谱
mux.HandleFunc("/api/v1/graph/stats", graphAPI.Stats)
mux.HandleFunc("/api/v1/graph/query", graphAPI.Query)
mux.HandleFunc("/api/v1/graph/navigate", graphAPI.Navigate)
// 新增pagerank + evidence_count
mux.HandleFunc("/api/v1/graph/pagerank", func(w http.ResponseWriter, r *http.Request) {
rank := graphStore.PageRank(0.85, 20)
data, _ := json.Marshal(map[string]interface{}{
"pagerank": rank,
"count": len(rank),
})
w.Header().Set("Content-Type", "application/json")
w.Write(data)
})
mux.HandleFunc("/api/v1/graph/evidence/", func(w http.ResponseWriter, r *http.Request) {
entity := r.URL.Path[len("/api/v1/graph/evidence/"):]
count := graphStore.EvidenceCount(entity)
data, _ := json.Marshal(map[string]interface{}{
"entity": entity,
"evidence_count": count,
})
w.Header().Set("Content-Type", "application/json")
w.Write(data)
})
// 图谱可视化导出
mux.HandleFunc("/api/v1/graph/export", func(w http.ResponseWriter, r *http.Request) {
ns := r.URL.Query().Get("namespace")
limitStr := r.URL.Query().Get("limit")
limit := 0
if limitStr != "" {
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
limit = l
}
}
nodes, edges := graphStore.GetGraph(ns, limit)
respondJSON(w, 200, map[string]interface{}{
"nodes": nodes,
"edges": edges,
"count": map[string]int{"nodes": len(nodes), "edges": len(edges)},
})
})
// 图谱导航 + Obsidian 笔记关联
mux.HandleFunc("/api/v1/graph/notes", func(w http.ResponseWriter, r *http.Request) {
entity := r.URL.Query().Get("entity")
maxHops := 2
if h := r.URL.Query().Get("max_hops"); h != "" {
if parsed, err := strconv.Atoi(h); err == nil && parsed > 0 {
maxHops = parsed
}
}
maxNotes := 10
if n := r.URL.Query().Get("max_notes"); n != "" {
if parsed, err := strconv.Atoi(n); err == nil && parsed > 0 {
maxNotes = parsed
}
}
var noteResults []map[string]interface{}
var paths []map[string]interface{}
if entity != "" {
p, err := graphStore.Navigate(normalizeEntity(entity), maxHops, "")
if err == nil {
paths = p
entities := []string{normalizeEntity(entity)}
for _, path := range p {
if s, ok := path["source"].(string); ok {
entities = append(entities, s)
}
if t, ok := path["target"].(string); ok {
entities = append(entities, t)
}
}
if obsidian != nil {
noteResults = obsidian.SearchNotesByEntities(entities, maxNotes)
}
}
}
respondJSON(w, 200, map[string]interface{}{
"entity": entity,
"graph_paths": paths,
"notes": noteResults,
"paths_count": len(paths),
"notes_count": len(noteResults),
})
})
// 静态文件服务(知识图谱可视化 HTML
staticDir := os.Getenv("STATIC_DIR")
if staticDir == "" {
staticDir = "/home/muc/projects/memoryweave/go/static"
}
fileServer := http.FileServer(http.Dir(staticDir))
mux.Handle("/static/", http.StripPrefix("/static/", fileServer))
// 冲突
mux.HandleFunc("/api/v1/conflicts", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
conflictAPI.List(w, r)
} else {
http.NotFound(w, r)
}
})
mux.HandleFunc("/api/v1/conflicts/resolve", conflictAPI.Resolve)
// 反馈 — G5: 修正记忆时填充 version_history
mux.HandleFunc("/api/v1/feedback/useful", func(w http.ResponseWriter, r *http.Request) {
feedbackAPI.MarkUseful(w, r)
// 质量监控useful → 质量上升
var req struct { MemoryID string `json:"memory_id"` }
if r.Body != nil {
body, _ := io.ReadAll(r.Body)
json.Unmarshal(body, &req)
r.Body = io.NopCloser(bytes.NewReader(body))
if req.MemoryID != "" {
selfoptimize.Dash.RecordUseful()
// V 值useful = success
selfoptimize.VProp.RecordDecision(
"useful_"+req.MemoryID,
[]string{req.MemoryID},
"user_feedback", "success", "",
)
selfoptimize.QualityMonitor.Check(req.MemoryID, 0, 5) // 清除潜在告警
}
}
})
mux.HandleFunc("/api/v1/feedback/not-useful", func(w http.ResponseWriter, r *http.Request) {
feedbackAPI.MarkNotUseful(w, r)
// 质量监控not-useful → 检查是否需要降权
var req struct { MemoryID string `json:"memory_id"` }
if r.Body != nil {
body, _ := io.ReadAll(r.Body)
json.Unmarshal(body, &req)
r.Body = io.NopCloser(bytes.NewReader(body))
if req.MemoryID != "" {
selfoptimize.Dash.RecordNotUseful()
// V 值not-useful = failure
selfoptimize.VProp.RecordDecision(
"notuseful_"+req.MemoryID,
[]string{req.MemoryID},
"user_feedback", "failure", "",
)
score := selfoptimize.Dash.QualityScore()
if record := selfoptimize.QualityMonitor.Check(req.MemoryID, score, 5); record != nil {
_ = record // WebSocket 通知
}
}
}
})
mux.HandleFunc("/api/v1/feedback/deprecate", feedbackAPI.Deprecate)
mux.HandleFunc("/api/v1/feedback/correct", func(w http.ResponseWriter, r *http.Request) {
feedbackAPI.Correct(w, r)
var req struct {
MemoryID string `json:"memory_id"`
NewContent string `json:"new_content"`
Source string `json:"source"`
}
if r.Body != nil {
body, _ := io.ReadAll(r.Body)
json.Unmarshal(body, &req)
r.Body = io.NopCloser(bytes.NewReader(body))
if req.MemoryID != "" && req.NewContent != "" {
routes.FillVersionHistory(req.MemoryID, "", req.NewContent, req.Source)
// 跨 Agent 通知
governance.PushMemoryUpdated(req.MemoryID, 0, "corrected")
}
}
})
// 缺口
mux.HandleFunc("/api/v1/gaps", gapAPI.List)
mux.HandleFunc("/api/v1/gaps/detect", gapAPI.Detect)
mux.HandleFunc("/api/v1/gaps/close/", func(w http.ResponseWriter, r *http.Request) {
gapAPI.Close(w, r)
topic := r.URL.Path[len("/api/v1/gaps/close/"):]
routes.PushGapFilled(topic, 1)
governance.PushGapFilled(topic, 1)
})
mux.HandleFunc("/api/v1/gaps/repair", routes.GapRepair.FullRepairHandler)
// Agent 注册
mux.HandleFunc("/api/v1/agents/register", agentRegistry.Register)
mux.HandleFunc("/api/v1/agents", agentRegistry.List)
// ─── 多 Agent EventBus 管理 ──────────────────
mux.HandleFunc("/api/v1/eventbus/subscribe", func(w http.ResponseWriter, r *http.Request) {
var req struct {
EventType string `json:"event_type"`
Callback string `json:"callback_url"`
}
json.NewDecoder(r.Body).Decode(&req)
governance.GlobalEventBus.Subscribe(req.EventType, req.Callback)
respondJSON(w, 200, map[string]string{"status": "subscribed"})
})
mux.HandleFunc("/api/v1/eventbus/unsubscribe", func(w http.ResponseWriter, r *http.Request) {
var req struct {
EventType string `json:"event_type"`
Callback string `json:"callback_url"`
}
json.NewDecoder(r.Body).Decode(&req)
governance.GlobalEventBus.Unsubscribe(req.EventType, req.Callback)
respondJSON(w, 200, map[string]string{"status": "unsubscribed"})
})
mux.HandleFunc("/api/v1/eventbus/list", func(w http.ResponseWriter, r *http.Request) {
subs := governance.GlobalEventBus.ListSubscribers()
respondJSON(w, 200, subs)
})
// 跨 Agent 缓存失效接收端点(供其他 Agent 实例 HTTP 回调)
mux.HandleFunc("/api/v1/cache/invalidate", func(w http.ResponseWriter, r *http.Request) {
var evt struct {
Type string `json:"type"`
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)
respondJSON(w, 200, map[string]string{"status": "invalidated"})
})
// 管理
// ?task=full 触发完整整合DBSCAN + 衰减校准 + 质量回溯),默认 cluster_only
mux.HandleFunc("/api/v1/admin/consolidate", func(w http.ResponseWriter, r *http.Request) {
task := r.URL.Query().Get("task")
if task == "" {
task = "cluster_only"
}
// full 模式给 5 分钟cluster_only 保持 30 秒
timeout := 30 * time.Second
if task == "full" {
timeout = 5 * time.Minute
}
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
done := make(chan *routes.ConsolidationReport, 1)
go func() {
report, _ := consolPipe.RunWithMode(task)
done <- report
}()
select {
case <-ctx.Done():
respondJSON(w, 408, map[string]string{"error": "consolidation timeout", "task": task})
return
case report := <-done:
if report == nil {
respondJSON(w, 500, map[string]string{"error": "consolidation failed", "task": task})
return
}
respondJSON(w, 200, report)
go governance.PushDistillationComplete(report.Merged, report.ConflictsFound)
}
})
mux.HandleFunc("/api/v1/admin/forget", adminAPI.Forget)
mux.HandleFunc("/api/v1/admin/dedup", api.Dedup)
mux.HandleFunc("/api/v1/admin/backup", adminAPI.Backup)
mux.HandleFunc("/api/v1/admin/audit", adminAPI.Audit)
// 遗忘器类型管理
mux.HandleFunc("/api/v1/admin/forgetter/type", func(w http.ResponseWriter, r *http.Request) {
var req struct {
AgentType string `json:"agent_type"`
}
if r.Method == "POST" {
json.NewDecoder(r.Body).Decode(&req)
forgetter.SetAgentType(req.AgentType)
}
respondJSON(w, 200, map[string]interface{}{
"agent_type": forgetter.AgentType(),
"decay_rate": AgentTypeDecayOrDefault(forgetter.AgentType()),
})
})
mux.HandleFunc("/api/v1/admin/distill/force", func(w http.ResponseWriter, r *http.Request) {
if routes.DistillEngineRef == nil {
respondJSON(w, 400, map[string]string{"error": "distill engine not initialized"})
return
}
var req struct {
Content string `json:"content"`
Category string `json:"category"`
Namespace string `json:"namespace"`
AgentID string `json:"agent_id"`
}
json.NewDecoder(r.Body).Decode(&req)
if req.Content == "" {
respondJSON(w, 400, map[string]string{"error": "content required"})
return
}
if req.Namespace == "" { req.Namespace = "hermes-main" }
if req.AgentID == "" { req.AgentID = "hermes-a06" }
epID := fmt.Sprintf("ep_manual_%d", time.Now().UnixNano())
routes.AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID)
respondJSON(w, 200, map[string]string{"status": "queued", "episode_id": epID})
})
mux.HandleFunc("/api/v1/distilled/{id}", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "DELETE" {
adminAPI.DeleteDistilled(w, r)
} else {
http.NotFound(w, r)
}
})
mux.HandleFunc("/api/v1/memory/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
adminAPI.Versions(w, r)
} else {
http.NotFound(w, r)
}
})
// L3
mux.HandleFunc("/api/v1/l3/worldmodel", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
l3API.GetHandler(w, r)
} else if r.Method == "POST" {
l3API.UpdateHandler(w, r)
} else {
http.NotFound(w, r)
}
})
// 触发器
mux.HandleFunc("/api/v1/triggers", routes.Triggers.List)
mux.HandleFunc("/api/v1/triggers/fire", routes.Triggers.Fire)
// 触发器 pause/resume — 手动解析路径
mux.HandleFunc("/api/v1/admin/triggers/", func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
// 格式: /api/v1/admin/triggers/{id}/{action}
parts := strings.Split(strings.TrimPrefix(path, "/api/v1/admin/triggers/"), "/")
if len(parts) != 2 {
http.NotFound(w, r)
return
}
id, action := parts[0], parts[1]
// 构造一个新请求,让 Pause/Resume 能通过 PathValue 读取
r.SetPathValue("id", id)
switch action {
case "pause":
routes.Triggers.Pause(w, r)
case "resume":
routes.Triggers.Resume(w, r)
default:
http.NotFound(w, r)
}
})
// SkillsG7
mux.HandleFunc("/api/v1/skills", routes.Skills.List)
mux.HandleFunc("POST /api/v1/skills", routes.Skills.Register) // 注册新 skill
mux.HandleFunc("/api/v1/skills/stats", routes.Skills.Stats) // skill 统计
mux.HandleFunc("/api/v1/skills/bayes", func(w http.ResponseWriter, r *http.Request) {
list := routes.BayesianSkills.List()
respondJSON(w, 200, list)
})
mux.HandleFunc("/api/v1/skills/{name}", skillByNameHandler) // GET/DELETE /api/v1/skills/{name}
mux.HandleFunc("/api/v1/skills/{name}/trial", routes.Skills.Trial)
mux.HandleFunc("/api/v1/skills/{name}/execute", routes.ExecuteSkill) // G7.3 skill执行
// G7.2: 结晶路由
mux.HandleFunc("/api/v1/crystallize/candidates", routes.GetSkillCandidates) // 获取候选
mux.HandleFunc("/api/v1/crystallize/memory/{id}", routes.CrystallizeSkill) // 对记忆执行结晶
// 评估 + 自调参
mux.HandleFunc("/api/v1/eval/run", evalAPI.Run)
mux.HandleFunc("/api/v1/eval/history", evalAPI.History)
mux.HandleFunc("/api/v1/eval/generate", evalAPI.Generate)
mux.HandleFunc("/api/v1/tuning/status", routes.Tuner.StatusHandler)
mux.HandleFunc("/api/v1/tuning/run", routes.Tuner.RunHandler)
mux.HandleFunc("/api/v1/tuning/analytics", routes.Tuner.AnalyticsHandler)
// 仪表盘 + 验证 + V值 + 缓存 + 流水线
mux.HandleFunc("/api/v1/metrics/self", func(w http.ResponseWriter, r *http.Request) {
metrics := selfoptimize.Dash.Metrics()
respondJSON(w, 200, metrics)
})
mux.HandleFunc("/api/v1/validate/passive", func(w http.ResponseWriter, r *http.Request) {
records := selfoptimize.Validator.GetRecords()
respondJSON(w, 200, records)
})
mux.HandleFunc("/api/v1/vvalue/decisions", func(w http.ResponseWriter, r *http.Request) {
decisions := selfoptimize.VProp.ListRecentDecisions(20)
respondJSON(w, 200, decisions)
})
// G5.3: V 值查询 + 触发反向传播
mux.HandleFunc("/api/v1/vvalue/memory/", func(w http.ResponseWriter, r *http.Request) {
memID := strings.TrimPrefix(r.URL.Path, "/api/v1/vvalue/memory/")
if memID == "" {
respondJSON(w, 400, map[string]string{"error": "memory_id required"})
return
}
v := selfoptimize.VProp.GetMemVValue(memID)
respondJSON(w, 200, map[string]interface{}{"memory_id": memID, "v_value": v})
})
mux.HandleFunc("/api/v1/trace/vprop", func(w http.ResponseWriter, r *http.Request) {
var req struct {
MemoryIDs []string `json:"memory_ids"`
Action string `json:"action"`
Outcome string `json:"outcome"`
ParentID string `json:"parent_id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err == nil && len(req.MemoryIDs) > 0 {
id := fmt.Sprintf("trace_%d", time.Now().UnixNano())
selfoptimize.VProp.RecordDecision(id, req.MemoryIDs, req.Action, req.Outcome, req.ParentID)
// 更新相关记忆的 quality_score
for _, mid := range req.MemoryIDs {
v := selfoptimize.VProp.GetMemVValue(mid)
lamb := 0.1
if ldb, ok := ldb.(interface{ UpdateQualityScore(id string, delta float64) error }); ok {
_ = ldb.UpdateQualityScore(mid, lamb*v)
}
}
respondJSON(w, 200, map[string]string{"status": "propagated"})
return
}
respondJSON(w, 400, map[string]string{"error": "memory_ids and outcome required"})
})
mux.HandleFunc("/api/v1/admin/cache", func(w http.ResponseWriter, r *http.Request) {
stats := storage.SearchCacheInstance.Stats()
respondJSON(w, 200, stats)
})
mux.HandleFunc("/api/v1/admin/pipeline", func(w http.ResponseWriter, r *http.Request) {
stats := selfoptimize.Flow.Stats()
respondJSON(w, 200, stats)
})
// G6: ephemeral namespace 管理
mux.HandleFunc("/api/v1/admin/ephemeral/clean", func(w http.ResponseWriter, r *http.Request) {
cleaned := []string{}
respondJSON(w, 200, map[string]interface{}{
"status": "cleaned",
"cleaned_namespaces": cleaned,
})
})
// Obsidian
mux.HandleFunc("/api/v1/obsidian/push", obsidian.PushHandler)
mux.HandleFunc("/api/v1/obsidian/pull", obsidian.PullHandler)
mux.HandleFunc("/api/v1/obsidian/status", obsidian.StatusHandler)
// 内部分支:跨 Agent 缓存失效回调
mux.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)
})
// ─── Redis 组件 ─────────────────────────────
heartbeat := storage.NewHeartbeat("zhiyid-primary")
_ = heartbeat // 后台 goroutine 自动心跳
// ─── 后台引擎启动 ──────────────────────────
selfoptimize.RegisterCommitFlow(selfoptimize.Flow)
selfoptimize.RegisterRecallFlow(selfoptimize.Flow)
selfoptimize.RegisterGapFlow(selfoptimize.Flow)
selfoptimize.RegisterCorrectFlow(selfoptimize.Flow)
selfoptimize.RegisterConsolidateFlow(selfoptimize.Flow)
// 注册真正的 consolidate 处理器:调用 ConsolidationPipeline
selfoptimize.Flow.Register("consolidate", func(task *selfoptimize.PipelineTask) error {
report, err := consolPipe.Run()
if err == nil && report != nil {
// 注入 consolidate 报告 → Dashboard
if report.Merged > 0 {
// 合并启发式: 按合并数比例缩放, 但限定单次贡献 ≤ 0.05, 防止污染 avg_distill_loss
loss := float64(report.Merged) * 0.002
if loss > 0.05 {
loss = 0.05
}
selfoptimize.Dash.RecordDistillLoss(loss)
}
if report.ConflictsFound > 0 {
selfoptimize.Dash.RecordConflictResolved(true)
}
for _, p := range report.Patterns {
if len(p) > 13 && p[:13] == "quality_score" {
// 解析 quality_score=0.50 → 注入 avg_distill_loss
var qs float64
if _, e := fmt.Sscanf(p, "quality_score=%f", &qs); e == nil {
selfoptimize.Dash.RecordDistillLoss(1.0 - qs)
}
}
}
}
return err
})
go selfoptimize.Flow.Start()
selfoptimize.Executor.Start(selfoptimize.Flow)
// G6: ephemeral 定时清理(每 5 分钟扫描一次,清理断开超过 5 分钟的 agent 的 ephemeral 记忆)
go func() {
for {
time.Sleep(5 * time.Minute)
now := time.Now().UnixMilli()
agents := agentRegistry.ListAgents()
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 {
log.Printf("[ephemeral] cleaned %d memories from %s (inactive for %ds)",
len(memories), ns, (now-agent.LastSeen)/1000)
}
}
}
}
}()
// 质量下降监控:每 30 分钟扫描所有低质量记忆
go func() {
for {
time.Sleep(30 * time.Minute)
zeroVec := make([]float32, 1024)
memories, err := ldb.Search("memories", zeroVec, 2000, "")
if err != nil || len(memories) == 0 {
continue
}
alerts := 0
for _, m := range memories {
if m.QualityScore > 0 && m.QualityScore < 0.3 {
if record := selfoptimize.QualityMonitor.Check(m.ID, m.QualityScore, 2); record != nil {
alerts++
if record.Status == "deprecating" {
log.Printf("[quality] memory %s quality=%.2f deprecating", m.ID[:20], m.QualityScore)
}
}
}
}
if alerts > 0 {
log.Printf("[quality] scan: %d low-quality memories flagged", alerts)
}
}
}()
// 图持久化 — 确保启动后写入
if saver, ok := graphStore.(interface{ Save() error }); ok {
saver.Save()
}
// ─── 触发器自动执行循环(每 30 秒检查一次)────────
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
for _, t := range []struct{ id, action string }{
{"t_distill", "distill"},
{"t_merge", "merge"},
{"t_prune", "prune"},
{"t_decay", "decay"},
{"t_backtrack", "backtrack"},
{"t_gap", "gap_scan"},
{"t_consolidation", "consolidation"},
} {
if !routes.Triggers.CanFire(t.id) {
continue
}
routes.Triggers.RecordFire(t.id)
go func(triggerID, action string) {
var err error
switch action {
case "distill", "consolidation", "backtrack":
// 深度整合含蒸馏质量回溯Step 4
_, err = consolPipe.Run()
case "merge":
_, err = consolPipe.RunMerge()
case "prune":
graphStore.Prune(0.15)
case "decay":
// 扫描所有记忆,按衰减率计算 freshness
memories, e := ldb.GetCandidatesForForgetting()
if e == nil {
for _, m := range memories {
// 解析 last_recalled_at
lastAccessed := time.Now().Add(-30 * 24 * time.Hour) // 默认30天前
if t, ok := m["last_recalled_at"].(string); ok && t != "" {
if parsed, err := time.Parse(time.RFC3339, t); err == nil {
lastAccessed = parsed
}
}
recallCount := 0
if rc, ok := m["recall_count"].(int); ok {
recallCount = rc
}
tier := "normal"
if ts, ok := m["tier"].(string); ok {
tier = ts
}
// E4.3: 获取实体图谱度namespace 作为实体)
entity := ""
if ns, ok := m["namespace"].(string); ok {
entity = ns
}
degree := 0
if entity != "" {
degree = graphStore.GetEntityDegree(entity)
}
if forgetter.ShouldForget(lastAccessed, recallCount, tier, degree) {
if id, ok := m["id"].(string); ok {
forgetter.ScanAndForget(id)
// 实际执行软删除(持久化到 LanceDB
_ = ldb.SoftDelete(id, "auto_forget")
}
}
}
}
case "gap_scan":
// 检查已有缺口:过期 7 天的自动关闭
gaps := gapDetector.List()
for _, g := range gaps {
if !g.Closed && time.Since(g.CreatedAt) > 7*24*time.Hour {
gapDetector.Close(g.Topic)
}
}
// 自动修复开放的缺口
for _, g := range gaps {
if !g.Closed && routes.GapRepair != nil {
if routes.GapRepair.AutoRepair(g) {
selfoptimize.Dash.RecordGapClosed()
}
}
}
}
if err != nil {
routes.Triggers.RecordFail(triggerID)
}
// Skill 结晶:每次蒸馏成功后记录
if action == "distill" || action == "consolidation" {
routes.Skills.RecordTrial("auto_distill", true)
}
routes.WSBus.Broadcast("trigger.fired", map[string]string{
"trigger_id": triggerID,
"action": action,
})
}(t.id, t.action)
}
}
}()
// 自优化指标定时采集(每 30 分钟)
go func() {
for {
time.Sleep(30 * time.Minute)
m := selfoptimize.Dash.Metrics()
date := time.Now().Format("2006-01-02")
for k, v := range m {
storage.GlobalMetricsStore.Set(date, k, v)
}
}
}()
// 首次指标采集
m := selfoptimize.Dash.Metrics()
date := time.Now().Format("2006-01-02")
for k, v := range m {
storage.GlobalMetricsStore.Set(date, k, v)
}
// Prometheus 指标定时同步(每 15s 采集一次系统指标)
go func() {
for {
time.Sleep(15 * time.Second)
// 同步 Dashboard → Prometheus gauges
metrics.SyncFromDashboard(selfoptimize.Dash.Metrics())
// 采集 SQLite 数据库文件大小
if fi, err := os.Stat(graphPath); err == nil {
metrics.SQLiteDBSizeBytes.Set(float64(fi.Size()))
}
// 采集进程 RSS 内存
if data, err := os.ReadFile("/proc/self/status"); err == nil {
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "VmRSS:") {
// VmRSS: 12345 kB
f := strings.Fields(line)
if len(f) >= 2 {
if kb, err := strconv.ParseFloat(f[1], 64); err == nil {
metrics.MemoryRSSBytes.Set(kb * 1024)
}
}
break
}
}
}
}
}()
log.Println("[zhiyid] 多 Agent 架构 — Redis + FileGraph + EventBus + vLLM — 已启动")
// Catch-all: return JSON for unknown paths
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" || r.URL.Path == "" {
respondJSON(w, 200, map[string]interface{}{
"service": "zhiyid", "status": "ok", "version": "0.1.0",
})
return
}
respondJSON(w, 404, map[string]interface{}{
"error": "endpoint not found: " + r.URL.Path,
})
})
return middleware.Auth(mux)
}
// ─── 辅助 ──────────────────────────────────────────
func respondJSON(w http.ResponseWriter, code int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(data)
}
func AgentTypeDecayOrDefault(agentType string) float64 {
if rate, ok := governance.AgentTypeDecay[agentType]; ok {
return rate
}
return 0.015
}