ysoseri.us

实习日志 / 中文 / 控制面:这一轮对话,用的是哪一套模型、规则、知识和数据?

控制面:这一轮对话,用的是哪一套模型、规则、知识和数据?

中文 | English

9 月初,项目里能被"配置"的东西越来越多:用哪个模型、走哪个协议适配器、知识库是哪个版本、Agent 的规则和提示词是哪一版、数据跑在本地还是 MongoDB、第三方服务接了哪几家。每一样单独看都能改,可它们一起决定了 Agent 在一轮对话里的行为。我开始担心一种情况:运行时同时读到了新的规则、旧的模型和另一套知识索引,出了问题,谁也说不清那一轮到底用的是什么。

9 月 6 日,我写了一份控制面的完整建设方案。它要回答的就是标题里那个问题。

它管什么,不管什么

控制面负责管理系统配置、知识、模型、Agent 规则、外部集成、评估和运行状态。它必须保证六件事:

  • 所有可以发布的内容都有版本;
  • 修改先进入草稿,经过验证再发布;
  • 运行时只读取已发布的版本;
  • 发布、回滚和失败的操作都有完整的审计;
  • 管理员不能通过控制面直接修改生产环境里的案件状态;
  • 不同配置之间不会出现版本组合失配。

最后两条划了边界。案件、工作项、人工交接、客户消息和理赔事实,由理赔运营那一侧管理。控制面管的是"系统怎样运行",不碰"某一单发生了什么",它的接口里不提供任何直接写案件状态、工作项、交接或报案人消息的路径。

一组对象

方案里定义了十个核心对象:

对象作用
Configuration Item一个配置域里的逻辑对象,比如某个模型配置或某套 Agent 规则
Configuration Revision配置的不可变版本,带状态、作者、影响等级和验证结果
Knowledge Source / Version知识来源,以及它的解析、索引和发布版本
Validation Run针对某个版本或某个发布组合的验证记录
Approval Record高影响变更的独立复核人、复核范围、决定和时间
Release Set一次运行时发布所引用的完整配置版本组合
Runtime Snapshot运行时实际加载的已发布配置快照
Audit Event配置、权限、发布、回滚和失败操作的追加式记录
Secret Reference外部密钥的受保护引用,不保存明文
Operation Record导入、索引、评估、连接检查这类异步任务的状态

其中最关键的是 Release Set。单个配置可以各自编辑,但涉及多个域的变更不能分别激活。最终的发布要用一个 Release Set 固定一组相互兼容的版本,比如:

Model revision: 7
Data profile revision: 3
Knowledge revision: 18
Agent rules revision: 12
Integration revision: 5
Feature revision: 9

运行时只加载完整的 Release Set,并记下其中每个域的版本。这样就分成了两层:单域的版本(比如 Agent 规则第 12 版),和把它们组合在一起的一次完整发布。新规则配旧模型、再配另一套知识索引这种情况,从结构上就不会发生了。

配置域一共分了十一个:系统、模型、数据配置、知识、Agent 规则、外部集成、访问权限、评估、功能开关、运行状态和审计。数据配置一栏里列着 fixture、本地、Cloudflare、MongoDB、AWS 几种运行时,它们之间的兼容性也要由控制面检查。

生命周期和审批

每个版本的生命周期是:

draft -> validation -> published -> superseded

再加几条补充路径:验证不通过退回草稿;草稿可以撤回;回滚是一次新的发布操作;任何被接受或被拒绝的操作都产生一条审计。

规则也很直接:已发布的版本不能直接编辑,要改就新建一份草稿;验证失败不能改变当前已发布的版本;没有完整验证证据的版本不能发布;回滚创建一条新的发布记录,不覆盖历史;失败的操作也要写进审计。

审批这一块我有意做得很轻。普通影响的变更,验证通过就可以发布;高影响的变更,发布前必须存在一条由非作者产生的独立复核记录,但不建审批队列、不建审批工作流,发布接口只检查这条记录在不在。一个五人团队的原型项目,需要的是"高影响变更一定有第二个人看过"这个事实被记下来,不需要一整套流程。

第三方服务:能力和任务要分开

第三方服务在系统里有两种完全不同的存在方式,方案里把它们拆到了两边。

控制面这边是 Integration Registry,管的是"这个服务能做什么、怎么连":服务身份、类型和环境;支持的能力和能力版本;适配器类型、端点引用和区域;允许发送哪些数据字段、隐私级别;需要什么同意、需要 Northwind 哪种授权;密钥引用;超时、重试、幂等和结果未知时的策略;连接检查、健康状态和最近的失败原因;能不能进入 Release Set;以及它自己的版本、发布状态和撤回历史。

理赔运营那边是外部任务和外部请求,记的是"这一单具体请求了什么":请求内容、同意、发送状态、服务商的引用号、结果验证和安全重试。控制面不改写这些案件级的记录。

账号管理不另造一套认证

控制面也提供客户和员工账号的管理入口:查询、创建、停用、恢复、修改允许修改的资料,管理员工的角色和作用域,重置或撤销会话,查看账号变更的审计。但它不复制认证系统:继续用仓库现有的 token 库、不透明的 bearer token、服务端存的 token 哈希、角色和作用域校验;账号操作都通过现有的后端 repository 和数据库边界完成;密码、token 这类凭据只由现有的身份服务处理,控制面的接口从不返回它们的明文,也不接受浏览器直接提交的身份字段作为授权依据。

所有写操作照例要过认证、作用域、If-Match、Idempotency-Key,返回稳定的错误码,不允许部分提交,审计只追加。

运行时怎么接

运行时接入控制面只做几件事:取当前已发布的 Release Set;完整加载;校验模型、知识、Agent 规则和数据配置之间是否兼容;在每一轮对话、每一次模型调用和检索里记下版本来源。同时有三条禁止:禁止把草稿、未验证或已被取代的版本当作隐式的退路;服务商失败时,只能切换到满足同样边界的备用配置;禁止运行时自己发布、修改或降级配置。

做到这些,最终每一张理赔单、每一轮 Agent 对话、每一条审计记录,都能回答同一个问题:

这次行为使用了哪一套模型、规则、知识和数据配置?

管理端只显示真的东西

管理端前端只调用管理接口,不能直连数据库、对象存储、模型端点、向量索引或密钥管理。它沿用报案端和员工端共用的 token 和状态语义,信息密度可以更高;页面布局由真实的管理任务和后端投影决定,不能先复制一个旧原型再往里填数据;组件只展示接口返回的状态和动作,不在浏览器里推导发布、权限、连接或第三方的状态。发布和回滚按钮,只在后端返回了对应的允许动作时才出现。

方案里有一条我写得特别重:还没接通的服务商,必须显示 unconfigured、unsupported 或者真实的失败状态,不能显示伪造的"已连接""已发布"或"运行正常";导入、索引、评估、连接检查这类异步操作必须有真实的 Operation Record,不能用一个静态的进度条代替。Release Set、Runtime Snapshot、Integration Registry 这些对象,只有在后端模型、存储边界、接口和验证证据都齐了以后,才能标为已交付。

所以这份方案开头先声明了自己的身份:它是目标规格,不是现状说明,也不代表文中列出的模块已经全部实现。实现和验收时要按"设计要求、接口、持久化、前端展示、真实验证"逐项核对,没实现的列进缺口清单。

最终的链路

管理员创建草稿
  -> 系统验证配置和依赖
  -> 生成验证证据
  -> 高影响变更记录一次非作者复核
  -> 创建完整的 Release Set
  -> 原子发布
  -> 运行时加载并记录版本
  -> 运行结果可追溯
  -> 发现问题时创建一次回滚发布
  -> 所有过程进入审计

这份方案里新增的三个关键设计,是 Release Set、跨域发布的一致性和 Runtime Snapshot。没有它们,控制面只是一组管理页面;有了它们,系统才真正可发布、可回滚、可追溯。

后来

"禁止把旧版本当作隐式退路"这一条,后来在 10 月初的一次冗余代码审计里被翻了出来。审计发现,系统在没有已发布 Release Set 的时候,会悄悄从环境变量拼出一份旧式的模型配置;运行时缺了某个配置,会默默退回到旧版的提示词;管理端里创建发布配置的表单,"运行环境"一栏打开时就预选了 fixture,也就是一个只在内存里、重启就丢的测试环境。这些都和这份方案写下的原则相反,它们是在各种"先跑起来再说"的时刻一点点长出来的。

那次清理以后,缺少必需配置的运行时直接拒绝启动,并说清楚缺的是哪一项;管理端的下拉框默认取服务器当前真正在跑的配置。原则写在方案里并不能保证它被守住,要靠一次次核对,把偏离的地方找出来。