主人要「现在就想看到在 736 条上它会建议合并哪些」,而不是等 23:00 的低峰首跑。
- 后端:Tidier.DryRun + TidyService.DryRun,**复用同一条扫描→召回→判定→构造候选路径**(抽出 tidyRoundPolicy,生产轮与试算轮只差「候选出口」与「召回降级策略」两处;禁止第二套实现)
- 🔴 零副作用:不写 memory_tidy_candidates / memory_tidy_runs,绝不改 memory_items 任何列;机械证明 = 跑前跑后 cyrene.db 与 -wal 的 SHA256 一致(测试断言「表行数不变」)
- 失败码诚实:500 tidy_unavailable / 409 tidy_disabled / 500 tidy_recall_unavailable(**embedding 不可用不降级成 0 条**)/ 503 tidy_timeout / 500 tidy_dry_run_failed;**空数组唯一含义 = 算过了没建议**
- 端点 POST /api/v1/admin/memories/tidy/dry-run(writeAuth);面板「记忆」页加试算卡(点了才跑、无轮询、三态分开、明写「仅试算,不会改动任何记忆」)
- 真机 e2e(741 条活库副本):76 ms / 15 分区 / 53 候选 / 41 skipped / 1 vetoed(low_confidence);样例 merge reason=fact_subset score=1.0000、link reason=similar_but_not_contained score=0.9248
- 变异:dry-run 顺手写库 ⇒ 两条测试红(行数 0→2);自造阈值 ⇒ 红;失败回 200 空数组 ⇒ 红
- 如实登记:Compared=688 被 400/分区护栏截断、候选上限 20/分区被打满 ⇒ 护栏值需在 741 条量级重定(非 bug)
175 lines
9.4 KiB
Go
175 lines
9.4 KiB
Go
package gateway
|
||
|
||
import (
|
||
"database/sql"
|
||
"io/fs"
|
||
"log/slog"
|
||
"net/http"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/bus"
|
||
pluginmgr "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/plugin"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||
)
|
||
|
||
// VectorState 是组合根报告的向量增强(pgvector)运行状态,供管理侧
|
||
// 「存储状态」端点回答「PG 到底有没有生效」(design/03 §3)。
|
||
//
|
||
// 它是**复用**而非探测:网关不读环境变量、不新开 PG 连接、不
|
||
// 自行 ping —— 真相只有组合根知道(DB_PG_URL 是否配置 + 活的向量
|
||
// 索引是哪个后端),因此由它注入,避免面板看到与运行时不一致的
|
||
// 第二个判断。
|
||
type VectorState struct {
|
||
// Configured 报告 DB_PG_URL 是否已配置(非空即算),即
|
||
// 增强开关在不在。
|
||
Configured bool
|
||
// Live 报告当前活的向量索引确实是 PostgreSQL+pgvector 后端。
|
||
// 配置了但连接/schema 准备失败时为 false —— 此时语义检索已降级,
|
||
// 面板必须显示「未就绪」而不是假装增强生效。
|
||
Live bool
|
||
}
|
||
|
||
// Deps 承载网关被注入的依赖。除 Auth 外每个字段都容忍
|
||
// nil,Auth 是必需的(认证支撑登录与每一个 RBAC
|
||
// 决策)。Registry/Loop 由具体的 *capability.Registry 与
|
||
// *loop.Loop 满足;Sessions/Personas/Memories 由具体的会话
|
||
// 管理器、人格注册表与记忆存储满足,只有当依赖留为
|
||
// nil 时 handler 才返回 []。
|
||
type Deps struct {
|
||
// Log 是基础 logger。为 nil 时使用丢弃型 logger。
|
||
Log *slog.Logger
|
||
// Auth 是 JWT/RBAC 服务。必需。
|
||
Auth Authenticator
|
||
// Bus 是 WS hub 所订阅的进程内事件总线。可选。
|
||
Bus bus.Bus
|
||
// Registry 枚举能力/插件/渠道。可选。
|
||
Registry CapabilityStore
|
||
// Loop 运行聊天回合。可选(未设置时 chat 返回错误)。
|
||
Loop Chatter
|
||
// Session 列出渠道会话。可选。
|
||
Session SessionStore
|
||
// Personas 列出人格。可选。
|
||
Personas PersonaStore
|
||
// Memories 列出记忆条目。可选。
|
||
Memories MemoryStore
|
||
// History 是持久化聊天历史存储(design/16)。可选;为 nil 时
|
||
// 会话/消息端点退化(列出回退到渠道会话,其余 501)。
|
||
History HistoryStore
|
||
// IoT 列出 IoT 设备。可选。
|
||
IoT IoTStore
|
||
// Scheduler 为 subagent_ask WS 路由审计子代理上下文。
|
||
// 可选(未设置时 subagent_ask 返回明确的错误)。
|
||
Scheduler SubagentAuditor
|
||
// Tasks 为 task_output_query WS 路由查询任务输出。
|
||
// 可选(未设置时 task_output_query 返回明确的错误)。
|
||
Tasks TaskOutputStore
|
||
// Automations 列出自动化与提醒。可选。
|
||
Automations AutomationStore
|
||
// Users 列出账号与 API key。可选(回退到已配置的
|
||
// 单个管理账号)。
|
||
Users UserStore
|
||
// Refresh 是非透明 refresh token 存储。可选:为 nil 时登录省略
|
||
// refreshToken,/auth/refresh + /auth/logout 返回 not-configured。
|
||
Refresh RefreshGrantStore
|
||
// Backthink 列出后台思考记录。可选。
|
||
Backthink BackthinkStore
|
||
// RAG 支撑管理侧知识库 RAG 模块。可选;未设置时列出/搜索
|
||
// 返回空,写端点报告明确的 501。
|
||
RAG RagStore
|
||
// LLM 是可热切换的 provider 栈(models.json,design/04 §3.1)。
|
||
// 可选:为 nil 时管理 /llm 端点回退到环境配置,使
|
||
// 既有的网关测试继续可用。
|
||
LLM *model.RuntimeLLM
|
||
// EnvPath 是进程启动时解析的 `.env` 文件路径(组合根传给 config.Load 的同一个)。
|
||
// 「重置为 env 默认」动作(切片 R1)按**既有 config 解析入口**重读 env 默认层,
|
||
// 网关自己绝不 fopen `.env`。空表示只看进程环境变量(与 config.Load("") 的
|
||
// 约定一致,生产走 systemd/env_file 时就是这种形态)。可选。
|
||
EnvPath string
|
||
// Channels 是运行时可编辑的渠道适配器配置(channels.json)。
|
||
// 可选:为 nil 时管理 /channels/config 端点返回明确的
|
||
// not-configured 错误,与 LLM 栈未设置时的行为一致。
|
||
Channels ChannelConfigStore
|
||
// Plugins 是运行时可安装的外部工具 provider 管理器
|
||
// (plugins.json + 活的 MCP server)。可选:为 nil 时安装返回
|
||
// 明确的 not-implemented 错误,卸载回退为普通的能力
|
||
// 移除,使既有的网关测试继续可用。
|
||
Plugins *pluginmgr.Manager
|
||
// OwnerRegistrar 把扫码登录绑定的主人 id 盖进主人
|
||
// 白名单(configs/users/owner.yaml)并热更新活的 profile。
|
||
// 可选:为 nil 时 weixin 确认流程仍会持久化 channels.json,但
|
||
// 跳过白名单登记(按设计即为尽力而为)。
|
||
OwnerRegistrar OwnerRegistrar
|
||
// AuditStore 持久化管理审计链(observability → audit)。为 nil 时
|
||
// 网关退化为内存存储,使端点仍能返回
|
||
// 数据;组合根注入基于 a.db 的 NewSQLiteAuditStore 以获得真实
|
||
// 持久化。可选。
|
||
AuditStore AuditStore
|
||
// DatabasePath 是组合根实际打开的 SQLite 数据库文件解析后
|
||
// 路径(由 resolveSQLitePath 推导的值,当数据目录默认值
|
||
// 生效时可能与 cfg.DB.SQLitePath 不同)。它支撑
|
||
// 管理备份/恢复端点。可选;为空时那些端点诚实作答,
|
||
// 返回明确的 not-configured 错误。
|
||
DatabasePath string
|
||
// DataDir 是解析后的数据目录(resolveDataDir 返回的值,当操作系统
|
||
// 默认值生效时可能与 cfg.DataDir 不同)。它支撑
|
||
// 图片上传存储(<DataDir>/uploads)及其静态路由。可选;
|
||
// 为空时上传 handler 回退到 cfg.DataDir。
|
||
DataDir string
|
||
// Database 是组合根已打开的活 SQLite 连接池。管理侧的
|
||
// 「存储状态」只读端点用它读页级统计与 documents 表 ——
|
||
// 复用既有池,端点自身不另开连接或连接池。可选;为 nil 时
|
||
// 该端点诚实作答(500 storage_unavailable),绝不假装
|
||
// 拿到了数据。
|
||
Database *sql.DB
|
||
// Vector 是组合根报告的向量增强(pgvector)运行时状态,
|
||
// 见 VectorState。网关只读它,绝不自行探测。可选;零值
|
||
// 表示「未配置」(kind:"none"),与未启用增强的部署一致。
|
||
Vector VectorState
|
||
// Streaming 标记注入的 Loop 具备流式能力(其 OnStream
|
||
// 已接线为发布 ws.stream_chunk)。设置后 hub 会跳过它自己的
|
||
// 单次最终 stream_chunk 发送,因为增量已通过 OnStream 流出。
|
||
Streaming bool
|
||
// Frontend 是为所有非 API、非 WS 路由提供的 SPA 文件系统。
|
||
// 为 nil 时网关不提供任何静态资产(只有 API 与 WS)。
|
||
// 使用内嵌的 dist(internal/webui)或外部 dist 目录。
|
||
Frontend fs.FS
|
||
// Webhooks 是外部挂载的入站 HTTP handler(端点路径 →
|
||
// handler),通常来自接收 webhook 回调的渠道适配器
|
||
// (例如 webhook)。每个都挂到 ServeMux 上且不经网关认证;
|
||
// handler 自身强制其签名/授权(webhook HMAC)。
|
||
// 可选。
|
||
Webhooks map[string]http.Handler
|
||
// Usage 是 LLM 用量汇总的只读出口(切片 U1)。可选;为 nil 时
|
||
// GET /api/v1/admin/llm/usage 诚实作答 500 usage_unavailable,
|
||
// 绝不回一份全 0 的假汇总。
|
||
Usage LlmUsageStore
|
||
// EventStore 是渠道事件留存的只读查询出口(design/26 §4.5;切片 E6)。
|
||
// 组合根注入的正是交给桥接层写入的**同一个** `*events.SQLStore` 句柄
|
||
// (同一个库、同一份口径)。可选;为 nil 时
|
||
// GET /api/v1/admin/channels/events 与 `.../counters` 诚实作答
|
||
// 500 channel_events_unavailable,绝不回一份全 0 的假汇总。
|
||
EventStore ChannelEventStore
|
||
// MaxParallelTurns 是单会话在途并行路数的上限(design/15 §2.5.7)。
|
||
// 零值/负数 ⇒ 编排器用默认 2;1 ⇒ 关闭真并行(恒 1 路,完全回落到
|
||
// queue + 回合内 steer 的现状);超过硬上限 3 ⇒ 钳到 3。
|
||
// 它刻意是编排器的构造期旋钮而不是一个全局:真并行只由显式声明
|
||
// (上行 `chat.parallel`)触发,因此调大它不会改变任何未声明并行的客户端行为。
|
||
MaxParallelTurns int
|
||
// Flow 是「最近一轮链路图」的只读出口(契约 v0.71 的
|
||
// GET /api/v1/admin/flow/graph)。组合根注入的正是交给 loop 的那个
|
||
// *loop.FlowPublisher —— 同一个对象,因此查询面读到的就是实时发布的那张图。
|
||
// 可选;为 nil 时端点诚实作答 500 flow_unavailable,绝不回
|
||
// graph:null(那会把「图源没接线」伪装成「这一轮确实没有图」)。
|
||
Flow FlowGraphSource
|
||
// FlowGraphs 是**已落盘轮次图**的只读出口(契约 v0.73;design/19 §4 的 P3
|
||
// 「多轮历史回放」)。组合根注入的是发布器**每轮收尾落一条**用的同一个
|
||
// `*flowgraph.SQLStore`。可选;为 nil 时端点回退到只有内存的行为
|
||
// (turns 为空、turnId 只能命中内存里那一轮),内存与落盘都没接线才 500。
|
||
FlowGraphs FlowGraphStore
|
||
// MemoryTidy 是记忆整理「试算」的注入出口(契约 v0.75;切片 MT1)。组合根
|
||
// 注入的就是低峰心跳跑的那个 `*memory.TidyService` —— 同一套规则、同一份配置,
|
||
// 因此试算给出的建议与当晚那一轮同口径。试算自身**零副作用**(不写任何表)。
|
||
// 可选;为 nil(真 nil,不是带类型的 nil 指针)时端点回 500 `tidy_unavailable`,
|
||
// 绝不回空候选假装「没有建议」。
|
||
MemoryTidy MemoryTidyDryRunner
|
||
}
|