Files
sub2api-cn-relay-manager/docs/PLUGIN_CLOSED_LOOP_IMPLEMENTATION_PLAN_2026-05-28.md
2026-05-28 15:37:13 +08:00

16 KiB
Raw Permalink Blame History

插件闭环实施规划2026-05-28

日期2026-05-28

目标

把插件后续演进从“概念设计”推进到“可执行实施”,明确:

  • 当前数据库是什么
  • 后续继续沿用什么存储结构
  • 每个功能域怎样形成闭环
  • 智能路由的日志必须落在哪里
  • 哪些运行态适合放 Redis哪些必须写插件数据库

这份文档是对以下文档的进一步细化:

一、当前数据库结论

当前插件用的是什么数据库

当前插件主数据库是 SQLite

直接证据:

当前 SQLite 里已经在存什么

当前 SQLite 已经承载:

  • host 注册信息
  • pack/provider 元数据
  • provider drafts
  • import batches / import items
  • managed resources
  • probe results
  • access closures
  • reconcile runs

相关表来自:

当前数据库特性边界

SQLite 目前是合理选择,原因是:

  • 项目当前是单实例控制面为主
  • 状态库规模仍然偏中小
  • 现有 repo / migration / integration test 全围绕 SQLite 建设

但要明确一个实现事实:

这意味着:

  • 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_groups
  • logical_group_routes
  • logical_group_models
  • route_shadow_groups
  • route_decision_logs
  • route_failover_events
  • route_sticky_audit
  • provider account inventory 扩展表

Redis 负责

  • 当前 route sticky
  • route 失败计数
  • route cooldown
  • 可选的 user-model route cache

四、功能闭环规划

下面按功能域写成闭环,不再只列功能点。

4.1 增加模型闭环

目标闭环

运营在管理页完成一次“新增模型”后,应形成:

  1. provider manifest 被创建或更新
  2. 模型被纳入某个 logical_group
  3. 至少一条 route 被配置好
  4. route 对应的 shadow group 被定义好
  5. 后续可直接导入供应商帐号

当前已有能力

  • 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-groups
  • POST /api/logical-groups/{group_id}/models
  • GET /api/logical-groups
  • GET /api/logical-groups/{group_id}

验收标准

  • 新增模型后,不只是 pack 文件变了
  • 插件库里也能看到:
    • 该模型属于哪个 logical group
    • 哪些 route 支持它

4.2 维护逻辑分组闭环

目标闭环

运营维护一个逻辑分组时,应能完成:

  1. 新建逻辑分组
  2. 绑定公开模型集合
  3. 绑定多条 route
  4. 每条 route 绑定 shadow group
  5. 管理页可查看该逻辑分组当前真实承载结构

新表: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}/routes
  • PUT /api/logical-groups/{group_id}/routes/{route_id}
  • GET /api/logical-groups/{group_id}/routes
  • POST /api/logical-groups/{group_id}/routes/{route_id}/models

验收标准

  • 打开某个 logical group 时,可以完整看到:
    • 公开模型
    • route 列表
    • 每条 route 对应的 shadow group
    • route 当前状态

4.3 智能路由闭环

这是新增需求后的核心实现。

目标闭环

一次用户请求进入插件后,应形成完整闭环:

  1. 识别 logical group
  2. 识别 public model
  3. 计算 sticky key
  4. 选择 route
  5. 记录 route decision log
  6. 转发到对应 shadow group
  7. 收到结果后更新 sticky / fail count / cooldown
  8. 记录最终路由结果日志

运行态存储

Redis keysticky

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 keyroute failure

routefail:{route_id}

Redis keyroute 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不能每个请求把大量日志同步直写。

建议:

  1. 路由热路径先产出 RouteDecisionEvent
  2. 写入内存 channel/buffer
  3. 后台 writer 每 100ms 或每 100 条批量写 SQLite
  4. 如果 writer 满了:
    • 保底同步写一条精简版失败记录
    • 或至少 stderr 告警

路由服务接口建议

建议新增:

  • RouteResolver
  • StickyStore
  • RouteDecisionLogger
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 供应商帐号导入与停启用闭环

目标闭环

管理员对供应商帐号做一次操作后,应形成:

  1. 预检
  2. 导入或复用
  3. 绑定到 provider / route / shadow group
  4. 记录帐号库存状态
  5. 后续可以启用/停用/下线

当前问题

当前更像“导入任务系统”,还不是“帐号资产系统”。

建议新增表: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-accounts
  • POST /api/provider-accounts/{id}/disable
  • POST /api/provider-accounts/{id}/enable
  • POST /api/provider-accounts/{id}/retire

验收标准

  • 能看到帐号库存
  • 能看到帐号属于哪个 route / shadow group
  • 能手动停启用
  • 状态变化有日志可追

4.5 普通用户前端闭环

目标闭环

普通用户看到的是逻辑分组,而不是宿主真实 group。

一次用户进入 portal 后,应形成:

  1. 看到逻辑分组列表
  2. 看到该分组可用模型
  3. 申请/使用的 key 对应逻辑分组
  4. 后端实际路由到某个 shadow group
  5. 用户无需感知宿主真实 group 名称

当前现实

当前 /portal/ 已存在,但仍偏宿主分组视角。

待做接口

建议新增面向 portal 的聚合 API

  • GET /api/portal/logical-groups
  • GET /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。
后续真正的闭环实施顺序应当是:

  1. 先补 logical_group / route / shadow_group 数据模型
  2. 再做前置智能路由最小闭环
  3. 再做供应商帐号资产管理
  4. 最后把普通用户前端切到逻辑分组视角