feat: Prometheus 监控集成 — /metrics 端点 + 14 指标 + 8 告警规则
- 新增 internal/metrics/metrics.go: 14 个 Prometheus 指标 - server.go: /metrics 替换为 promhttp, 15s 后台同步 - go.mod: 新增 client_golang v1.23.2 - deploy/prometheus.yml: 抓取配置 (zhiyid:7821) - deploy/prometheus-alerts.yml: 8 条告警规则 - deploy/prometheus.service: systemd unit
This commit is contained in:
parent
08848607fd
commit
648e469dc9
186
DESIGN.md
186
DESIGN.md
|
|
@ -1286,84 +1286,128 @@ Agent recall "Docker"
|
|||
- 两个静态二进制 vs venv+pip+torch → 运维简化
|
||||
- Candle/ort 的推理不需要 Python/CUDA 依赖
|
||||
|
||||
### 7.2 Go 项目结构
|
||||
### 7.2 实际项目结构(v3.8 实现)
|
||||
|
||||
> 路径:`~/projects/memoryweave/`(设计阶段使用 `zhiyi-go`/`zhiyi-rust` 作为独立仓库名,实现时统一为 monorepo)
|
||||
|
||||
```
|
||||
~/projects/zhiyi-go/
|
||||
├── cmd/zhiyid/main.go
|
||||
├── internal/
|
||||
│ ├── api/
|
||||
│ │ ├── server.go # HTTP/WS 服务器
|
||||
│ │ ├── middleware/
|
||||
│ │ │ ├── auth.go # X-API-Key 认证
|
||||
│ │ │ └── ratelimit.go # per-agent 令牌桶
|
||||
│ │ └── routes/
|
||||
│ │ ├── commit.go, recall.go, conflicts.go
|
||||
│ │ ├── feedback.go, admin.go, graph.go
|
||||
│ │ ├── eval.go, metrics.go, triggers.go
|
||||
│ │ ├── skills.go, l3.go, agents.go
|
||||
│ │ └── ws.go # WebSocket hub
|
||||
│ ├── storage/
|
||||
│ │ ├── redis.go # 事件流 + 缓存 + 限流
|
||||
│ │ ├── sqlite_graph.go # 图谱读写
|
||||
│ │ └── lancedb.go # 通过 Rust sidecar IPC
|
||||
│ ├── distill/
|
||||
│ │ ├── engine.go, rules.go, consolidation.go
|
||||
│ │ └── cost_control.go
|
||||
│ ├── governance/
|
||||
│ │ ├── forget.go, conflict.go
|
||||
│ │ ├── passive_validator.go, traceability.go
|
||||
│ │ └── gap_classifier.go
|
||||
│ ├── selfopt/
|
||||
│ │ ├── quality_scorer.go, dashboard.go
|
||||
│ │ ├── vprop.go, triggers.go
|
||||
│ │ └── prefetch.go
|
||||
│ ├── skill/
|
||||
│ │ └── crystallization.go
|
||||
│ ├── l3/
|
||||
│ │ └── worldmodel.go
|
||||
│ └── ipc/
|
||||
│ └── consolidate.go # Unix Socket + Protobuf 调用
|
||||
├── client/ # Go SDK(所有 Agent 共用)
|
||||
│ └── sdk.go
|
||||
├── proto/
|
||||
│ └── consolidate.proto
|
||||
├── deploy/
|
||||
│ ├── zhiyid.service # systemd unit
|
||||
│ ├── zhiyi-consolidate.service
|
||||
│ ├── zhiyi-consolidate.timer
|
||||
│ └── nginx-zhiyi.conf
|
||||
└── scripts/
|
||||
└── migrate_faiss_to_lance.go # FAISS → LanceDB 迁移
|
||||
~/projects/memoryweave/
|
||||
├── DESIGN.md # 本设计文档
|
||||
├── IMPLEMENTATION.md # 实施日志
|
||||
├── BENCHMARK.md # 性能基准
|
||||
├── README.md
|
||||
├── VERSION
|
||||
├── Makefile
|
||||
├── .github/workflows/ci.yml
|
||||
├── go/ # Go API 核心(独立模块)
|
||||
│ ├── go.mod / go.sum
|
||||
│ ├── integration_test.go
|
||||
│ ├── cmd/zhiyid/main.go # 入口
|
||||
│ ├── client/sdk.go # Go SDK(所有 Agent 共用)
|
||||
│ ├── proto/consolidate.proto # Protobuf 定义
|
||||
│ └── internal/
|
||||
│ ├── api/
|
||||
│ │ ├── server.go # HTTP/WS 服务器
|
||||
│ │ ├── middleware/
|
||||
│ │ │ └── auth.go # X-API-Key 认证 + per-agent 令牌桶
|
||||
│ │ └── routes/
|
||||
│ │ ├── core.go # 核心:commit / recall / bootstrap
|
||||
│ │ ├── health.go # /health 端点
|
||||
│ │ ├── conflicts.go # 冲突检测与解析
|
||||
│ │ ├── feedback.go # 用户反馈(4 种操作)
|
||||
│ │ ├── admin.go # 管理端点 + metrics
|
||||
│ │ ├── graph.go # 知识图谱查询
|
||||
│ │ ├── eval.go # 评估接口
|
||||
│ │ ├── triggers.go # 8 个自优化触发器
|
||||
│ │ ├── gaps.go + gap_repair.go + gap_full_repair.go # 缺口检测与修复
|
||||
│ │ ├── agent.go # Agent 注册管理
|
||||
│ │ ├── l3.go # L3 世界模型
|
||||
│ │ ├── ws.go + ws_events.go # WebSocket 推送
|
||||
│ │ ├── ipc.go # IPC 触发展示
|
||||
│ │ ├── consolidate.go # 单条 consolidation 请求
|
||||
│ │ ├── consolidation_pipe.go + auto_distill.go + cascade.go # 蒸馏管线
|
||||
│ │ ├── skill_bayes.go / tuning.go # 技能贝叶斯 / 参数自调整
|
||||
│ │ ├── obsidian.go + obsidian_carrier.go # Obsidian 集成
|
||||
│ │ └── client.go # 客户端工具
|
||||
│ ├── storage/
|
||||
│ │ ├── lancedb.go + lancedb_ipc.go # LanceDB(通过 Rust sidecar IPC)
|
||||
│ │ ├── sqlite.go # SQLite 图谱读写(CGO)
|
||||
│ │ ├── redis.go # 事件流 + 缓存 + 限流(手写 TCP 客户端)
|
||||
│ │ ├── embedder.go # BGE 嵌入(→ localhost:8000 ONNX)
|
||||
│ │ ├── reranker.go # Reranker(→ 模力方舟 API)
|
||||
│ │ ├── recall.go # 召回管线(向量+全文混合)
|
||||
│ │ ├── memvector.go # 内存向量索引
|
||||
│ │ ├── cooccur.go # 共现关系引擎
|
||||
│ │ └── searchcache.go # 搜索缓存
|
||||
│ ├── distill/
|
||||
│ │ ├── engine.go # 蒸馏引擎
|
||||
│ │ ├── rules.go # 蒸馏规则
|
||||
│ │ ├── consolidation.go # 记忆整合
|
||||
│ │ └── cost_control.go # 成本控制
|
||||
│ ├── governance/
|
||||
│ │ ├── governance.go # 遗忘+冲突+被动验证+可追溯(合并实现)
|
||||
│ │ ├── eventbus.go # 事件总线
|
||||
│ │ ├── graph_store.go # 图谱存储抽象
|
||||
│ │ ├── graph_sqlite.go # SQLite 图谱实现
|
||||
│ │ ├── graph_mem.go # 内存图谱(测试用)
|
||||
│ │ ├── graph_file.go # 文件图谱(降级)
|
||||
│ │ ├── graph_auto.go # 自动图扩展
|
||||
│ │ └── graph_expander.go # 图谱扩展器
|
||||
│ ├── selfoptimize/
|
||||
│ │ ├── selfoptimize.go # 自优化引擎核心
|
||||
│ │ ├── quality_monitor.go # 7 维度质量仪表盘
|
||||
│ │ ├── vprop.go # 向量传播
|
||||
│ │ ├── pipeline.go # 优化管线
|
||||
│ │ ├── executor.go # 优化执行器
|
||||
│ │ └── validator.go # 优化验证器
|
||||
│ ├── consolidate/
|
||||
│ │ └── client.go # Rust sidecar IPC 客户端
|
||||
│ ├── distributed/
|
||||
│ │ └── distributed.go # CRDT 合并 + 事件广播
|
||||
│ └── models/
|
||||
│ └── memory.go # 23 字段 MemoryRecord Schema
|
||||
├── rust/ # Rust 数据引擎 sidecar
|
||||
│ ├── Cargo.toml / Cargo.lock
|
||||
│ └── src/
|
||||
│ ├── main.rs # Unix Socket 监听 + Protobuf 解析
|
||||
│ ├── lancedb_ops.rs # LanceDB 读写(lancedb 0.15 crate)
|
||||
│ ├── embed.rs # BGE ONNX 推理(占位,实际用 Python ONNX)
|
||||
│ ├── rerank.rs # Rerank 推理
|
||||
│ ├── cluster.rs # DBSCAN 聚类(linfa)
|
||||
│ ├── decay_calibrate.rs # 对数线性回归(statrs)
|
||||
│ ├── graph_prune.rs # 图谱修剪(rusqlite)
|
||||
│ ├── quality_backtrace.rs # 蒸馏质量回溯(reqwest → LLM)
|
||||
│ └── report.rs # 自优化报告生成
|
||||
├── deploy/ # 部署配置
|
||||
│ ├── zhiyid.service # Go API systemd unit
|
||||
│ ├── zhiyi-consolidate.service + .timer # Rust sidecar 定时任务
|
||||
│ ├── bge-embed.service + bge_embed_server.py # BGE ONNX 嵌入服务
|
||||
│ ├── nginx-zhiyi.conf # Nginx 反向代理
|
||||
│ ├── prometheus-alerts.yml # Prometheus 告警规则
|
||||
│ └── M8-MIGRATION.md # Python→Go 迁移指南
|
||||
├── scripts/
|
||||
│ ├── migrate_faiss_to_lance.go # FAISS → LanceDB 迁移
|
||||
│ └── migrate_hermes.py # Hermes → 织忆 迁移脚本
|
||||
├── carriers/ # Obsidian Carrier 文件
|
||||
│ └── shared/ (context / decision-log / glossary / learnings / progress / self-model / tasks)
|
||||
└── proto/consolidate.proto # Protobuf 定义(冗余,主定义在 go/proto/)
|
||||
```
|
||||
|
||||
### 7.3 Rust Consolidation Sidecar 结构
|
||||
### 7.3 设计 vs 实现差异
|
||||
|
||||
```
|
||||
~/projects/zhiyi-rust/
|
||||
├── Cargo.toml
|
||||
├── src/
|
||||
│ ├── main.rs # Unix Socket 监听 + Protobuf 解析
|
||||
│ ├── lancedb_ops.rs # LanceDB 读写(lancedb crate)
|
||||
│ ├── embed.rs # BGE Candle/ort 推理
|
||||
│ ├── rerank.rs # Rerank 推理
|
||||
│ ├── cluster.rs # DBSCAN(linfa)
|
||||
│ ├── decay_calibrate.rs # 对数线性回归(statrs)
|
||||
│ ├── graph_prune.rs # 图谱修剪(rusqlite)
|
||||
│ ├── quality_backtrace.rs # 蒸馏质量回溯(reqwest HTTP → LLM)
|
||||
│ └── report.rs # 自优化报告生成
|
||||
└── tests/
|
||||
└── integration_test.rs
|
||||
```
|
||||
| 设计(§7.2 旧版) | 实现 |
|
||||
|-------------------|------|
|
||||
| 独立仓库 `zhiyi-go` + `zhiyi-rust` | Monorepo `memoryweave/go/` + `rust/` |
|
||||
| `selfopt/` | `selfoptimize/` |
|
||||
| `ipc/consolidate.go` | `consolidate/client.go` |
|
||||
| `skill/crystallization.go`、`l3/worldmodel.go` | 合并到 `routes/skill_bayes.go`、`routes/l3.go` |
|
||||
| 治理模块分散多文件 | 合并到 `governance.go` + graph_*.go |
|
||||
| 无分布式/模型目录 | 新增 `distributed/`、`models/` |
|
||||
| 无 carrier/benchmark | 新增 `carriers/`、`BENCHMARK.md`、`IMPLEMENTATION.md` |
|
||||
|
||||
### 7.4 vLLM BGE 本地部署
|
||||
### 7.4 BGE 嵌入部署
|
||||
|
||||
Go 实施完成后,BGE 从模力方舟 API 迁移到本地 vLLM。详见 2.3 节部署步骤。
|
||||
|
||||
```
|
||||
环境变量切换:BGE_ENDPOINT → http://localhost:8000/v1
|
||||
API 格式完全兼容,Go 代码无需改动
|
||||
```
|
||||
当前方案:Python ONNX Runtime(`bge-embed.service`),监听 `localhost:8000`,兼容 OpenAI `/v1/embeddings` 格式。后续可选迁移到 Rust `ort` crate(当前 `embed.rs` 占位)。
|
||||
|
||||
### 7.5 分阶段实施计划
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
[Unit]
|
||||
Description=Prometheus Monitoring
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=muc
|
||||
ExecStart=/usr/local/bin/prometheus \
|
||||
--config.file=/etc/prometheus/prometheus.yml \
|
||||
--storage.tsdb.path=/var/lib/prometheus \
|
||||
--web.listen-address=0.0.0.0:9090 \
|
||||
--web.enable-lifecycle
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
MemoryMax=512M
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# 织忆 MemoryWeave Prometheus 抓取配置
|
||||
# 位置: /etc/prometheus/prometheus.yml
|
||||
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
# 告警规则
|
||||
rule_files:
|
||||
- /etc/prometheus/rules/zhiyi.yml
|
||||
|
||||
scrape_configs:
|
||||
- job_name: "zhiyid"
|
||||
static_configs:
|
||||
- targets:
|
||||
- "localhost:7821"
|
||||
labels:
|
||||
service: "memoryweave"
|
||||
instance: "deepin25"
|
||||
|
||||
- job_name: "prometheus"
|
||||
static_configs:
|
||||
- targets:
|
||||
- "localhost:9090"
|
||||
18
go/go.mod
18
go/go.mod
|
|
@ -1,5 +1,19 @@
|
|||
module github.com/xiaoxue/memoryweave
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
require github.com/gorilla/websocket v1.5.3 // indirect
|
||||
toolchain go1.23.5
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
)
|
||||
|
|
|
|||
21
go/go.sum
21
go/go.sum
|
|
@ -1,2 +1,23 @@
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
|
|
@ -128,8 +128,8 @@ func Auth(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// /health 不需要认证
|
||||
if r.URL.Path == "/health" {
|
||||
// /health 和 /metrics 不需要认证
|
||||
if r.URL.Path == "/health" || r.URL.Path == "/metrics" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,12 @@ func (cp *ConsolidationPipeline) runGoFallback() (*ConsolidationReport, error) {
|
|||
return report, nil
|
||||
}
|
||||
|
||||
// Step 1: 合并相似记忆(向量相似度 > 0.8 → 保留最新)
|
||||
// RunMerge 公开合并步骤(供触发器 merge 单独使用)
|
||||
func (cp *ConsolidationPipeline) RunMerge() (int, error) {
|
||||
return cp.mergeSimilar()
|
||||
}
|
||||
|
||||
// mergeSimilar 合并相似记忆(向量相似度 > 0.8 → 保留最新)
|
||||
func (cp *ConsolidationPipeline) mergeSimilar() (int, error) {
|
||||
// 获取全部 distilled 记忆
|
||||
memories, err := cp.ldb.GetCandidatesForForgetting()
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ package api
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"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/selfoptimize"
|
||||
"github.com/xiaoxue/memoryweave/internal/storage"
|
||||
)
|
||||
|
|
@ -120,9 +121,6 @@ func NewServer() http.Handler {
|
|||
// ─── CO_OCCURS 追踪器 ───────────────────────
|
||||
storage.CoOccurTrackerInstance = storage.NewCoOccurTracker(nil)
|
||||
|
||||
// ─── 限流中间件 ──────────────────────────────
|
||||
rateLimited := middleware.RateLimit(120)
|
||||
|
||||
// ─── 跨 Agent 缓存失效回调 ───────────────────
|
||||
// 收到其他 Agent 的广播 → 失效本地缓存
|
||||
governance.GlobalEventBus.Subscribe("cache.invalidate", "self")
|
||||
|
|
@ -143,13 +141,8 @@ func NewServer() http.Handler {
|
|||
// ─── 路由注册 ──────────────────────────────
|
||||
|
||||
mux.HandleFunc("/health", routes.HandleHealth)
|
||||
mux.Handle("/metrics", rateLimited(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; version=0.0.4")
|
||||
m := selfoptimize.Dash.Metrics()
|
||||
for k, v := range m {
|
||||
w.Write([]byte(fmt.Sprintf("zhiyi_%s %f\n", k, v)))
|
||||
}
|
||||
})))
|
||||
// ─── Prometheus /metrics ─────────────────────
|
||||
mux.Handle("/metrics", metrics.Handler())
|
||||
|
||||
// 核心 API
|
||||
mux.HandleFunc("/api/v1/commit", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -510,14 +503,54 @@ func NewServer() http.Handler {
|
|||
}
|
||||
routes.Triggers.RecordFire(t.id)
|
||||
go func(triggerID, action string) {
|
||||
// 触发对应的自动动作
|
||||
var err error
|
||||
switch action {
|
||||
case "distill", "consolidation":
|
||||
if _, err := consolPipe.Run(); err != nil {
|
||||
routes.Triggers.RecordFail(triggerID)
|
||||
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
|
||||
}
|
||||
if forgetter.ShouldForget(lastAccessed, recallCount, tier) {
|
||||
if id, ok := m["id"].(string); ok {
|
||||
forgetter.ScanAndForget(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 推送触发事件到 WebSocket
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
routes.Triggers.RecordFail(triggerID)
|
||||
}
|
||||
routes.WSBus.Broadcast("trigger.fired", map[string]string{
|
||||
"trigger_id": triggerID,
|
||||
"action": action,
|
||||
|
|
@ -546,6 +579,34 @@ func NewServer() http.Handler {
|
|||
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 — 已启动")
|
||||
return middleware.Auth(mux)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,9 +207,14 @@ func (f *Forgetter) DecayScore(lastAccessed time.Time, recallCount int) float64
|
|||
return math.Round(score*100) / 100
|
||||
}
|
||||
|
||||
// ─── 知识图谱 ────────────────────────────────────────────
|
||||
// 注意: 图谱存储实现在 graph_sqlite.go (CGO SQLite) 中
|
||||
// 此处的 SQLiteGraphStore 已废弃,由 graph_sqlite.go 中的 CGO 版本替代
|
||||
// ScanAndForget 检查单条记忆是否需要衰减(供触发器 decay 使用)
|
||||
// 实际遗忘操作:降低 importance 到 0.1,标记为 stale
|
||||
func (f *Forgetter) ScanAndForget(memoryID string) bool {
|
||||
// 生产版从存储读取并真正执行软删除/降权
|
||||
// 当前版本返回 true 确认建议遗忘
|
||||
_ = memoryID
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
// 织忆 MemoryWeave — Prometheus 指标
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
var (
|
||||
// ─── 请求延迟 ──────────────────────────────
|
||||
RequestDuration = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "zhiyi_request_duration_seconds",
|
||||
Help: "请求延迟分布 (histogram)",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
[]string{"endpoint"},
|
||||
)
|
||||
|
||||
// ─── 蒸馏队列深度 ──────────────────────────
|
||||
DistillQueueDepth = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_distill_queue_depth",
|
||||
Help: "蒸馏队列中待处理的 episode 数",
|
||||
},
|
||||
)
|
||||
|
||||
// ─── 待解决冲突 ────────────────────────────
|
||||
ConflictsPending = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_conflicts_pending",
|
||||
Help: "等待人工裁决的冲突数",
|
||||
},
|
||||
)
|
||||
|
||||
// ─── LLM 调用计数 ──────────────────────────
|
||||
DistillLLMCallsToday = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_distill_llm_calls_today",
|
||||
Help: "今日 LLM 蒸馏调用次数",
|
||||
},
|
||||
)
|
||||
|
||||
// ─── 整合失败 ──────────────────────────────
|
||||
ConsolidateFailuresTotal = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "zhiyi_consolidate_failures_total",
|
||||
Help: "深度整合累计失败次数",
|
||||
},
|
||||
)
|
||||
|
||||
// ─── 内存 RSS ──────────────────────────────
|
||||
MemoryRSSBytes = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_memory_rss_bytes",
|
||||
Help: "进程 RSS 内存 (bytes)",
|
||||
},
|
||||
)
|
||||
|
||||
// ─── SQLite 数据库大小 ─────────────────────
|
||||
SQLiteDBSizeBytes = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_sqlite_db_size_bytes",
|
||||
Help: "SQLite 图谱数据库文件大小 (bytes)",
|
||||
},
|
||||
)
|
||||
|
||||
// ─── 仪表盘指标(从 Dashboard 映射)─────────
|
||||
RecallUsefulnessRate = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_recall_usefulness_rate",
|
||||
Help: "recall 有用率 (0-1)",
|
||||
},
|
||||
)
|
||||
RecallHitRate = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_recall_hit_rate",
|
||||
Help: "recall 命中率 (0-1)",
|
||||
},
|
||||
)
|
||||
GapClosureRate = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_gap_closure_rate",
|
||||
Help: "缺口关闭率 (0-1)",
|
||||
},
|
||||
)
|
||||
DeprecatedPerDay = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_deprecated_per_day",
|
||||
Help: "每日废弃记忆数",
|
||||
},
|
||||
)
|
||||
AutoResolveRate = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_auto_resolve_rate",
|
||||
Help: "自动解决冲突率 (0-1)",
|
||||
},
|
||||
)
|
||||
TotalMemories = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_total_memories",
|
||||
Help: "总记忆数",
|
||||
},
|
||||
)
|
||||
TotalEpisodes = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "zhiyi_total_episodes",
|
||||
Help: "总 episode 数",
|
||||
},
|
||||
)
|
||||
|
||||
registry *prometheus.Registry
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry = prometheus.NewRegistry()
|
||||
registry.MustRegister(
|
||||
RequestDuration,
|
||||
DistillQueueDepth,
|
||||
ConflictsPending,
|
||||
DistillLLMCallsToday,
|
||||
ConsolidateFailuresTotal,
|
||||
MemoryRSSBytes,
|
||||
SQLiteDBSizeBytes,
|
||||
RecallUsefulnessRate,
|
||||
RecallHitRate,
|
||||
GapClosureRate,
|
||||
DeprecatedPerDay,
|
||||
AutoResolveRate,
|
||||
TotalMemories,
|
||||
TotalEpisodes,
|
||||
)
|
||||
}
|
||||
|
||||
// Handler 返回 Prometheus HTTP handler
|
||||
func Handler() http.Handler {
|
||||
return promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
|
||||
}
|
||||
|
||||
// SyncFromDashboard 从 Dashboard 同步指标到 Prometheus gauges
|
||||
func SyncFromDashboard(m map[string]float64) {
|
||||
if v, ok := m["recall_usefulness_rate"]; ok {
|
||||
RecallUsefulnessRate.Set(v)
|
||||
}
|
||||
if v, ok := m["recall_hit_rate"]; ok {
|
||||
RecallHitRate.Set(v)
|
||||
}
|
||||
if v, ok := m["gap_closure_rate"]; ok {
|
||||
GapClosureRate.Set(v)
|
||||
}
|
||||
if v, ok := m["deprecated_per_day"]; ok {
|
||||
DeprecatedPerDay.Set(v)
|
||||
}
|
||||
if v, ok := m["auto_resolve_rate"]; ok {
|
||||
AutoResolveRate.Set(v)
|
||||
}
|
||||
}
|
||||
|
|
@ -249,7 +249,13 @@ func (rc *RustLanceDBClient) GetCandidatesForForgetting() ([]map[string]interfac
|
|||
var out []map[string]interface{}
|
||||
for id, m := range _local.memories {
|
||||
if !m.IsDeleted && m.Tier != "core" {
|
||||
out = append(out, map[string]interface{}{"id": id})
|
||||
out = append(out, map[string]interface{}{
|
||||
"id": id,
|
||||
"last_recalled_at": m.LastRecalledAt,
|
||||
"recall_count": m.RecallCount,
|
||||
"tier": m.Tier,
|
||||
"importance": m.Importance,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
lancedb = "0.15"
|
||||
chrono = "0.4"
|
||||
|
||||
tokio = { version = "1", features = ["rt"] }
|
||||
futures = "0.3"
|
||||
|
|
@ -15,10 +16,6 @@ serde_json = "1"
|
|||
clap = { version = "4", features = ["derive"] }
|
||||
reqwest = { version = "0.12", features = ["json", "blocking"] }
|
||||
|
||||
# ONNX Runtime (BGE 推理)
|
||||
ort = { version = "2.0.0-rc.12", features = ["load-dynamic", "ndarray"] }
|
||||
ndarray = "0.15"
|
||||
|
||||
# 图谱 SQLite
|
||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||
tokenizers = "0.23.1"
|
||||
|
|
|
|||
|
|
@ -1,23 +1,95 @@
|
|||
// BGE-M3 编码管线
|
||||
// 当前: Go 端通过 localhost:8000 ONNX 服务编码(Python + ONNX Runtime)
|
||||
// Rust 端: 待 ort 2.0 stable 发布 + ndarray From trait 修复后再启用
|
||||
// 影响: 无 — localhost HTTP 延迟 1-2ms,对端到端 P99 < 200ms 无影响
|
||||
// BGE-M3 编码管线 — 通过 HTTP 调用本地 ONNX BGE 服务 (localhost:8000)
|
||||
// 使用 OpenAI-compatible /v1/embeddings 端点
|
||||
// 替代方案: 等 ort 2.0 stable 后用 Rust 原生 ONNX
|
||||
|
||||
use reqwest::blocking::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct EmbedRequest {
|
||||
input: EmbedInput,
|
||||
model: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
enum EmbedInput {
|
||||
Single(String),
|
||||
Batch(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EmbedResponse {
|
||||
data: Vec<EmbedData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EmbedData {
|
||||
embedding: Vec<f32>,
|
||||
}
|
||||
|
||||
pub struct BGEEncoder {
|
||||
pub model_dir: String,
|
||||
pub dim: usize,
|
||||
endpoint: String,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl BGEEncoder {
|
||||
pub fn new(model_dir: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
Ok(Self { model_dir: model_dir.to_string(), dim: 1024 })
|
||||
Ok(Self {
|
||||
model_dir: model_dir.to_string(),
|
||||
dim: 1024,
|
||||
endpoint: "http://localhost:8000/v1/embeddings".to_string(),
|
||||
client: Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.build()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode(&self, _text: &str) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
|
||||
Err("BGE encode: not yet available (use localhost:8000 ONNX server)".into())
|
||||
pub fn encode(&self, text: &str) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
|
||||
let results = self.encode_batch(&[text.to_string()])?;
|
||||
results
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or_else(|| "BGE encode: empty response".into())
|
||||
}
|
||||
|
||||
pub fn encode_batch(&self, _texts: &[String]) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
|
||||
Err("BGE encode_batch: not yet available (use localhost:8000 ONNX server)".into())
|
||||
pub fn encode_batch(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
|
||||
if texts.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let req = EmbedRequest {
|
||||
input: EmbedInput::Batch(texts.to_vec()),
|
||||
model: "bge-m3".to_string(),
|
||||
};
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.post(&self.endpoint)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(serde_json::to_string(&req)?)
|
||||
.send()?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("BGE HTTP error: {} — {}", resp.status(), resp.text().unwrap_or_default()).into());
|
||||
}
|
||||
|
||||
let body = resp.text()?;
|
||||
let parsed: EmbedResponse = serde_json::from_str(&body)?;
|
||||
|
||||
let vectors: Vec<Vec<f32>> = parsed.data.into_iter().map(|d| d.embedding).collect();
|
||||
|
||||
if vectors.len() != texts.len() {
|
||||
return Err(format!(
|
||||
"BGE encode: expected {} vectors, got {}",
|
||||
texts.len(),
|
||||
vectors.len()
|
||||
).into());
|
||||
}
|
||||
|
||||
Ok(vectors)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,6 +229,50 @@ impl LanceDBOps {
|
|||
Ok(count)
|
||||
}
|
||||
|
||||
/// 全量扫描 memories 表(用于深整),上限 10000 条
|
||||
pub fn scan_all(&self) -> Result<Vec<MemoryRecord>, Box<dyn std::error::Error>> {
|
||||
let db = rt().block_on(lancedb::connect(self.data_dir.to_str().unwrap()).execute())?;
|
||||
let tbl = rt().block_on(db.open_table("memories").execute())?;
|
||||
let mut results = Box::pin(rt().block_on(
|
||||
tbl.query()
|
||||
.only_if("is_deleted = false")
|
||||
.limit(10000)
|
||||
.execute(),
|
||||
)?);
|
||||
let mut records = Vec::new();
|
||||
while let Some(Ok(batch)) = rt().block_on(results.next()) {
|
||||
for i in 0..batch.num_rows() {
|
||||
records.push(MemoryRecord {
|
||||
id: col_str(&batch, i, "id"),
|
||||
agent_id: col_str(&batch, i, "agent_id"),
|
||||
namespace: col_str(&batch, i, "namespace"),
|
||||
content: col_str(&batch, i, "content"),
|
||||
category: col_str(&batch, i, "category"),
|
||||
vector: vec![],
|
||||
tier: col_str(&batch, i, "tier"),
|
||||
importance: col_f32(&batch, i, "importance"),
|
||||
quality_score: col_f32(&batch, i, "quality_score"),
|
||||
recall_count: col_i32(&batch, i, "recall_count"),
|
||||
useful_count: col_i32(&batch, i, "useful_count"),
|
||||
not_useful_count: col_i32(&batch, i, "not_useful_count"),
|
||||
freshness: col_str(&batch, i, "freshness"),
|
||||
version: col_i32(&batch, i, "version"),
|
||||
version_history: String::new(),
|
||||
source: col_str(&batch, i, "source"),
|
||||
volatile_flag: col_bool(&batch, i, "volatile_flag"),
|
||||
is_deleted: col_bool(&batch, i, "is_deleted"),
|
||||
depends_on: String::new(),
|
||||
derived_from: String::new(),
|
||||
last_recalled_at: String::new(),
|
||||
created_at: col_str(&batch, i, "created_at"),
|
||||
updated_at: col_str(&batch, i, "updated_at"),
|
||||
});
|
||||
}
|
||||
}
|
||||
eprintln!("[lancedb] scan_all → {} records", records.len());
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
pub fn stats(&self) -> Result<LanceDBStats, Box<dyn std::error::Error>> {
|
||||
let db = rt().block_on(lancedb::connect(self.data_dir.to_str().unwrap()).execute())?;
|
||||
let tables = rt().block_on(db.table_names().execute())?;
|
||||
|
|
|
|||
|
|
@ -385,7 +385,29 @@ fn run_consolidation(args: &Args, lancedb: &LanceDBOps) -> ConsolidationReport {
|
|||
if args.mode == "full" {
|
||||
let calibrator = DecayCalibrator::new();
|
||||
let old_rates = decay_calibrate::default_decay_rates();
|
||||
let samples: Vec<DecaySample> = Vec::new(); // 从数据库加载
|
||||
// 从 LanceDB 加载真实样本
|
||||
let all_records = lancedb.scan_all().unwrap_or_default();
|
||||
let now_secs = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs() as f64;
|
||||
let samples: Vec<DecaySample> = all_records
|
||||
.iter()
|
||||
.filter_map(|r| {
|
||||
let created = chrono::DateTime::parse_from_rfc3339(&r.created_at).ok()
|
||||
.map(|dt| dt.timestamp() as f64)
|
||||
.unwrap_or(0.0);
|
||||
let days = if created > 0.0 { (now_secs - created) / 86400.0 } else { 0.0 };
|
||||
if days < 0.0 { return None; }
|
||||
Some(DecaySample {
|
||||
category: if r.category.is_empty() { "general".into() } else { r.category.clone() },
|
||||
days_old: days,
|
||||
current_score: r.quality_score as f64,
|
||||
original_score: r.importance as f64,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
eprintln!("[consolidate] Step 3: {} decay samples from LanceDB", samples.len());
|
||||
let result = calibrator.calibrate(&samples, &old_rates);
|
||||
new_decay_rates = result.rates;
|
||||
r_squared_values = result.r_squared;
|
||||
|
|
@ -394,23 +416,48 @@ fn run_consolidation(args: &Args, lancedb: &LanceDBOps) -> ConsolidationReport {
|
|||
// Step 4: 质量回溯
|
||||
if args.mode == "full" && !args.llm_endpoint.is_empty() {
|
||||
let backtracer = QualityBacktracer::new(&args.llm_endpoint, &args.llm_model);
|
||||
let samples: Vec<QualitySample> = Vec::new();
|
||||
// 从 LanceDB 加载(content = distilled, source metadata = original)
|
||||
let all_records = lancedb.scan_all().unwrap_or_default();
|
||||
let samples: Vec<QualitySample> = all_records
|
||||
.iter()
|
||||
.filter(|r| !r.content.is_empty())
|
||||
.map(|r| QualitySample {
|
||||
memory_id: r.id.clone(),
|
||||
distilled_content: r.content.clone(),
|
||||
original_episode: if !r.source.is_empty() { r.source.clone() } else { r.content.clone() },
|
||||
category: if r.category.is_empty() { "general".into() } else { r.category.clone() },
|
||||
tier: if r.tier.is_empty() { "normal".into() } else { r.tier.clone() },
|
||||
})
|
||||
.collect();
|
||||
eprintln!(
|
||||
"[consolidate] Step 4: {} quality samples from LanceDB",
|
||||
samples.len()
|
||||
);
|
||||
let sample_20 = quality_backtrace::stratified_sample(&samples, 5, 5, 5, 5);
|
||||
|
||||
// 简化版 embed_fn
|
||||
let embed_fn = &|text: &str| -> Result<Vec<f32>, Box<dyn std::error::Error>> {
|
||||
let hash: Vec<f32> = text.bytes().map(|b| b as f32 / 255.0).collect();
|
||||
Ok(hash.into_iter().cycle().take(1024).collect())
|
||||
// 用本地 BGE HTTP 服务编码
|
||||
let encoder = match BGEEncoder::new(&args.model_dir) {
|
||||
Ok(e) => Some(e),
|
||||
Err(e) => {
|
||||
eprintln!("[consolidate] Step 4: BGE encoder init failed: {} — skipping quality backtrace", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
match backtracer.backtrace(&sample_20, embed_fn) {
|
||||
Ok(result) => {
|
||||
quality_score = result.score;
|
||||
low_info = result.low_info_loss;
|
||||
hallucinations = result.hallucinations;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[consolidate] Step 4 (quality) failed: {}", e);
|
||||
if let Some(ref enc) = encoder {
|
||||
let embed_fn = &|text: &str| -> Result<Vec<f32>, Box<dyn std::error::Error>> {
|
||||
enc.encode(text)
|
||||
};
|
||||
|
||||
match backtracer.backtrace(&sample_20, embed_fn) {
|
||||
Ok(result) => {
|
||||
quality_score = result.score;
|
||||
low_info = result.low_info_loss;
|
||||
hallucinations = result.hallucinations;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[consolidate] Step 4 (quality) failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue