16 KiB
插件闭环实施规划(2026-05-28)
日期:2026-05-28
目标
把插件后续演进从“概念设计”推进到“可执行实施”,明确:
- 当前数据库是什么
- 后续继续沿用什么存储结构
- 每个功能域怎样形成闭环
- 智能路由的日志必须落在哪里
- 哪些运行态适合放 Redis,哪些必须写插件数据库
这份文档是对以下文档的进一步细化:
一、当前数据库结论
当前插件用的是什么数据库
当前插件主数据库是 SQLite。
直接证据:
- 配置项:
- internal/config/config.go
- 环境变量
SUB2API_CRM_SQLITE_DSN
- 默认配置:
- .env.example
SUB2API_CRM_SQLITE_DSN=file:/data/sub2api-cn-relay-manager.db?_foreign_keys=on&_busy_timeout=5000
- 打开数据库与迁移入口:
当前 SQLite 里已经在存什么
当前 SQLite 已经承载:
- host 注册信息
- pack/provider 元数据
- provider drafts
- import batches / import items
- managed resources
- probe results
- access closures
- reconcile runs
相关表来自:
- 0001_init.sql
- 0002_operational_runtime.sql
- 后续 migrations
0003到0009*
当前数据库特性边界
SQLite 目前是合理选择,原因是:
- 项目当前是单实例控制面为主
- 状态库规模仍然偏中小
- 现有 repo / migration / integration test 全围绕 SQLite 建设
但要明确一个实现事实:
- internal/store/sqlite/db.go
已经把连接池限制为单 writer 友好模式:
SetMaxOpenConns(1)SetMaxIdleConns(1)
这意味着:
- SQLite 很适合当前控制面状态持久化
- 但智能路由日志不能无限制高频同步写库,否则会放大写锁争用
所以后续方案应当是:
- SQLite 继续作为插件主状态库
- Redis 作为智能路由运行态缓存
- 智能路由日志按结构化事件写回 SQLite
二、后续存储策略
总体原则
1. SQLite:保存“真相状态”
放 SQLite 的必须是:
- 配置真相
- 可审计真相
- 需要查询/回放/后台管理的结构化记录
2. Redis:保存“短期运行态”
放 Redis 的应该是:
- sticky route
- route cooldown
- 短期失败计数
- 热路由选择缓存
3. 智能路由日志:必须最终落 SQLite
用户已经明确要求:
- 智能路由日志要存在插件中
所以要求不能只停留在 Redis,也不能只打 stdout。
正确做法是:
- 请求热路径上先写结构化内存事件/异步队列
- 由后台 writer 批量落 SQLite
- 关键失败场景允许同步兜底落一条简版事件
三、目标架构
建议后续插件内部形成三层存储:
SQLite
- 配置真相
- 业务真相
- 路由日志
Redis
- sticky route
- cooldown
- short-lived routing cache
In-memory
- 单请求上下文
- 异步日志缓冲队列
SQLite 负责
logical_groupslogical_group_routeslogical_group_modelsroute_shadow_groupsroute_decision_logsroute_failover_eventsroute_sticky_auditprovider account inventory扩展表
Redis 负责
- 当前 route sticky
- route 失败计数
- route cooldown
- 可选的 user-model route cache
四、功能闭环规划
下面按功能域写成闭环,不再只列功能点。
4.1 增加模型闭环
目标闭环
运营在管理页完成一次“新增模型”后,应形成:
- provider manifest 被创建或更新
- 模型被纳入某个
logical_group - 至少一条 route 被配置好
- route 对应的 shadow group 被定义好
- 后续可直接导入供应商帐号
当前已有能力
- provider drafts
- publish to pack repo
- 同模型冲突校验
待补技术细节
新增模型不应只产生 provider 文件,还应额外落地以下数据:
新表:logical_groups
CREATE TABLE logical_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
logical_group_id TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
status TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
route_policy TEXT NOT NULL DEFAULT 'priority',
sticky_mode TEXT NOT NULL DEFAULT 'conversation_preferred',
conversation_ttl_seconds INTEGER NOT NULL DEFAULT 7200,
user_model_ttl_seconds INTEGER NOT NULL DEFAULT 1800,
failover_threshold INTEGER NOT NULL DEFAULT 2,
cooldown_seconds INTEGER NOT NULL DEFAULT 600,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
新表:logical_group_models
CREATE TABLE logical_group_models (
id INTEGER PRIMARY KEY AUTOINCREMENT,
logical_group_id TEXT NOT NULL,
public_model TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (logical_group_id, public_model)
);
闭环接口
建议新增:
POST /api/logical-groupsPOST /api/logical-groups/{group_id}/modelsGET /api/logical-groupsGET /api/logical-groups/{group_id}
验收标准
- 新增模型后,不只是 pack 文件变了
- 插件库里也能看到:
- 该模型属于哪个 logical group
- 哪些 route 支持它
4.2 维护逻辑分组闭环
目标闭环
运营维护一个逻辑分组时,应能完成:
- 新建逻辑分组
- 绑定公开模型集合
- 绑定多条 route
- 每条 route 绑定 shadow group
- 管理页可查看该逻辑分组当前真实承载结构
新表:logical_group_routes
CREATE TABLE logical_group_routes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
route_id TEXT NOT NULL UNIQUE,
logical_group_id TEXT NOT NULL,
name TEXT NOT NULL,
status TEXT NOT NULL,
priority INTEGER NOT NULL,
weight INTEGER NOT NULL DEFAULT 100,
shadow_group_id TEXT NOT NULL,
shadow_host_id TEXT NOT NULL,
upstream_base_url_hint TEXT NOT NULL DEFAULT '',
cooldown_until TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
新表:logical_group_route_models
CREATE TABLE logical_group_route_models (
id INTEGER PRIMARY KEY AUTOINCREMENT,
route_id TEXT NOT NULL,
public_model TEXT NOT NULL,
shadow_model TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'active',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (route_id, public_model)
);
闭环接口
POST /api/logical-groups/{group_id}/routesPUT /api/logical-groups/{group_id}/routes/{route_id}GET /api/logical-groups/{group_id}/routesPOST /api/logical-groups/{group_id}/routes/{route_id}/models
验收标准
- 打开某个 logical group 时,可以完整看到:
- 公开模型
- route 列表
- 每条 route 对应的 shadow group
- route 当前状态
4.3 智能路由闭环
这是新增需求后的核心实现。
目标闭环
一次用户请求进入插件后,应形成完整闭环:
- 识别 logical group
- 识别 public model
- 计算 sticky key
- 选择 route
- 记录 route decision log
- 转发到对应 shadow group
- 收到结果后更新 sticky / fail count / cooldown
- 记录最终路由结果日志
运行态存储
Redis key:sticky
lg:{logical_group_id}:m:{public_model}:conv:{conversation_id}
lg:{logical_group_id}:m:{public_model}:sess:{session_id}
lg:{logical_group_id}:m:{public_model}:user:{user_id}
Redis key:route failure
routefail:{route_id}
Redis key:route cooldown
routecool:{route_id}
路由日志必须写入 SQLite
新表:route_decision_logs
CREATE TABLE route_decision_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
request_id TEXT NOT NULL,
logical_group_id TEXT NOT NULL,
public_model TEXT NOT NULL,
user_key TEXT NOT NULL DEFAULT '',
conversation_key TEXT NOT NULL DEFAULT '',
sticky_key TEXT NOT NULL DEFAULT '',
sticky_key_type TEXT NOT NULL DEFAULT '',
sticky_hit INTEGER NOT NULL DEFAULT 0,
selected_route_id TEXT NOT NULL,
selected_shadow_group_id TEXT NOT NULL,
fallback_used INTEGER NOT NULL DEFAULT 0,
error_class TEXT NOT NULL DEFAULT '',
upstream_status INTEGER NOT NULL DEFAULT 0,
latency_ms INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
新表:route_failover_events
CREATE TABLE route_failover_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
request_id TEXT NOT NULL,
logical_group_id TEXT NOT NULL,
public_model TEXT NOT NULL,
from_route_id TEXT NOT NULL,
to_route_id TEXT NOT NULL,
reason TEXT NOT NULL,
failure_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
新表:route_sticky_audit
CREATE TABLE route_sticky_audit (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sticky_key TEXT NOT NULL,
sticky_key_type TEXT NOT NULL,
logical_group_id TEXT NOT NULL,
public_model TEXT NOT NULL,
route_id TEXT NOT NULL,
action TEXT NOT NULL,
expires_at TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
为什么日志必须进 SQLite
因为后续必须支持:
- 查询“这次为什么走了 codex2api 而不是 asxs”
- 查询某个 logical group 最近 24h 的 route 命中情况
- 查询 fallback 是否频繁发生
- 查询 sticky 命中率
这些都必须靠结构化插件日志完成,宿主侧日志无法替代。
日志写入策略
SQLite 是单 writer,不能每个请求把大量日志同步直写。
建议:
- 路由热路径先产出
RouteDecisionEvent - 写入内存 channel/buffer
- 后台 writer 每 100ms 或每 100 条批量写 SQLite
- 如果 writer 满了:
- 保底同步写一条精简版失败记录
- 或至少 stderr 告警
路由服务接口建议
建议新增:
RouteResolverStickyStoreRouteDecisionLogger
type RouteResolver interface {
Resolve(ctx context.Context, req RouteRequest) (RouteDecision, error)
}
type StickyStore interface {
Get(ctx context.Context, key string) (StickyBinding, bool, error)
Set(ctx context.Context, key string, binding StickyBinding, ttl time.Duration) error
Delete(ctx context.Context, key string) error
}
type RouteDecisionLogger interface {
Append(ctx context.Context, event RouteDecisionEvent) error
}
验收标准
- 同一会话优先命中同一路 route
- retryable 失败时能 fallback
- fallback 有日志
- sticky 命中有日志
- 所有路由决策都能在插件库里查询到
4.4 供应商帐号导入与停启用闭环
目标闭环
管理员对供应商帐号做一次操作后,应形成:
- 预检
- 导入或复用
- 绑定到 provider / route / shadow group
- 记录帐号库存状态
- 后续可以启用/停用/下线
当前问题
当前更像“导入任务系统”,还不是“帐号资产系统”。
建议新增表:provider_accounts
CREATE TABLE provider_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
host_id TEXT NOT NULL,
provider_id TEXT NOT NULL,
route_id TEXT NOT NULL DEFAULT '',
shadow_group_id TEXT NOT NULL DEFAULT '',
host_account_id TEXT NOT NULL,
key_fingerprint TEXT NOT NULL,
account_name TEXT NOT NULL DEFAULT '',
account_status TEXT NOT NULL,
last_probe_status TEXT NOT NULL DEFAULT '',
last_probe_at TEXT NOT NULL DEFAULT '',
disabled_reason TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (host_id, host_account_id)
);
闭环接口
GET /api/provider-accountsPOST /api/provider-accounts/{id}/disablePOST /api/provider-accounts/{id}/enablePOST /api/provider-accounts/{id}/retire
验收标准
- 能看到帐号库存
- 能看到帐号属于哪个 route / shadow group
- 能手动停启用
- 状态变化有日志可追
4.5 普通用户前端闭环
目标闭环
普通用户看到的是逻辑分组,而不是宿主真实 group。
一次用户进入 portal 后,应形成:
- 看到逻辑分组列表
- 看到该分组可用模型
- 申请/使用的 key 对应逻辑分组
- 后端实际路由到某个 shadow group
- 用户无需感知宿主真实 group 名称
当前现实
当前 /portal/ 已存在,但仍偏宿主分组视角。
待做接口
建议新增面向 portal 的聚合 API:
GET /api/portal/logical-groupsGET /api/portal/logical-groups/{group_id}GET /api/portal/logical-groups/{group_id}/models
验收标准
- 用户前端不再直接暴露
gpt-shared__asxs这种 shadow group - 只看到
GPT Shared - 用户使用体验上是一个产品,不是多个宿主组装件
五、实施顺序
为了保证每步都闭环,建议按下面顺序推进。
Phase 1:数据模型闭环
目标:
- 先让插件库知道什么是
logical_group / route / shadow_group
范围:
- SQLite migrations
- sqlite repos
- 基础 CRUD API
输出:
- 逻辑分组配置可持久化
- route 配置可持久化
- 每个闭环功能完成后,必须提交、推送、部署到
remote43,并完成服务器验证后再更新执行板
Phase 2:智能路由最小闭环
目标:
- 让请求真正能按 logical group -> route -> shadow group 跑起来
范围:
- Redis sticky
- route resolver
- route forwarding
- SQLite route logs
输出:
asxs + codex2api单 logical group 跑通
Phase 3:帐号资产闭环
目标:
- 把“导入任务”升级成“帐号资产管理”
范围:
- provider_accounts 视图
- enable/disable/retire
- route / shadow group 归属展示
输出:
- 能运维帐号池
Phase 4:普通用户产品闭环
目标:
- 用户前端彻底切换到逻辑分组视角
范围:
- portal 聚合 API
- portal 前端改造
输出:
- 用户只看到逻辑分组
六、技术决策总结
当前数据库
- SQLite
后续主状态库
- 继续使用 SQLite
智能路由运行态
- Redis
智能路由日志
- 必须最终落 SQLite
为什么不是一开始就换 PostgreSQL
当前不建议立刻切 PostgreSQL,原因是:
- 现有 repo、migration、integration test 都围绕 SQLite
- 当前主要瓶颈不是查询能力,而是产品结构未闭环
- 先把闭环跑通比先换库更重要
但要保留一个现实判断:
- 如果后续 route 日志量显著升高
- 或多实例控制面出现
- 或需要复杂聚合分析
那时再评估:
- SQLite -> PostgreSQL
- Redis 保持不变
一句话结论
当前插件数据库是 SQLite;后续仍建议以 SQLite 作为主状态库,并以 Redis 承载智能路由运行态缓存。
同时,智能路由日志必须结构化写回插件 SQLite,不能只停留在 Redis 或 stdout。
后续真正的闭环实施顺序应当是:
- 先补
logical_group / route / shadow_group数据模型 - 再做前置智能路由最小闭环
- 再做供应商帐号资产管理
- 最后把普通用户前端切到逻辑分组视角