# Agent 数据模型:工具 / 工具包 / 研判方案 的表关系 > 适用范围:`ai-server` 的 `module/agent` 域 + `sql/agent_tool_pack.sql`(含内置种子)。 > 结论先行:**工具 ↔ 工具包 ↔ 研判方案 是两级多对多**,都用 JSON 数组做松引用, > 完整性由「保存时校验 + 删除时清理 + 装配时跳过」三道防线保证(见文末规则表)。 ## 关系图 ```mermaid erDiagram agent_tool ||--o{ agent_tool_pack_item : "tool_names[] 按名引用" agent_tool_pack ||--o{ agent_tool_pack_item : "包内工具集合" agent_tool_pack ||--o{ agent_pack_bind : "被方案勾选" agent ||--o{ agent_pack_bind : "方案勾选的包" agent_tool { bigint id PK "雪花 ID" varchar tool_name UK "工具名(@Tool name)" varchar group_code "call/trans/track/graph/person/file/base" varchar status "ACTIVE | DELETED" } agent_tool_pack { bigint id PK "雪花 ID(内置包固定 1~6)" bigint user_id "创建者;内置包 = 1" varchar name "包名(user_id 内唯一)" text tool_names "JSON 数组:tool_name 列表" smallint is_builtin "1=内置(全局可见、不可改删)" } agent { bigint row_id PK "identity 自增" bigint user_id "创建者;内置方案 = 1" smallint is_builtin "1=内置(不可改删)" text tools_allow_json "JSON 数组:工具组编码白名单" text tool_packs_json "JSON 数组:工具包 id 白名单" text skills_allow_json "JSON 数组:技能名白名单" } ``` > 图中 `agent_tool_pack_item` / `agent_pack_bind` 是**逻辑关系**(多对多的语义实体), > 物理上分别落在 `agent_tool_pack.tool_names` 与 `agent.tool_packs_json` 两个 JSON 数组列里, > 不建物理中间表:数据量级为个位数包 × 十位数工具,JSON 松引用 + 服务层校验更贴合本域。 ## 多对多怎么成立 | 关系 | 基数 | 存储 | 说明 | |---|---|---|---| | 工具 ↔ 工具包 | **一个工具可出现在多个包**;一个包含多个工具 | `agent_tool_pack.tool_names[]`(工具名数组) | 同一个 `tool_name` 可以出现在任意多个包的数组里,互不影响 | | 工具包 ↔ 研判方案 | **一个包可被多个方案勾选**;一个方案可勾多个包 | `agent.tool_packs_json[]`(包 id 数组) | 同一个包 id 可以出现在任意多个方案的数组里;改包内容对所有引用它的方案**即时生效**(下次装配) | | 工具 ↔ 研判方案 | 传递多对多(经包)+ 直接的**工具组**白名单 | `agent.tools_allow_json[]`(组编码) | 方案最终可见的业务工具 = 所选工具组的全部工具 ∪ 所选工具包的工具;全不勾 = 不限制 | ## ID 约定 - `agent.row_id`:PostgreSQL identity 自增(存量默认方案 = 1「数刃」); - `agent_tool.id` / `agent_tool_pack.id`:应用侧雪花 ID(MyBatis-Plus `ASSIGN_ID`); **内置工具包固定占用 1~6**(雪花 ID 远大于此,不会碰撞),供脚本直接引用; - 前端传输一律**字符串 ID**(避免 JS 精度丢失);JSON 数组里也是字符串(`["1","2"]`)。 ## 完整性规则(三道防线) | 时机 | 校验/清理 | 实现位置 | |---|---|---| | **保存工具包** | 工具名逐个对照 `AgentToolRegistry.registeredToolNames()`(业务工具;base 基础工具不进包),非法 400 | `ToolPackService.validateToolNames` | | **保存研判方案** | 引用的包必须存在且「内置或属于当前用户」,否则 400/404 | `ToolPackService.requireBindable`(由 `AgentService.validateExpertConfig` 调用) | | **删除工具包** | 内置拒绝;同时从所有方案的 `tool_packs_json` 摘掉该 id,并**驱逐受影响的 agent 实例池** | `AgentToolPackController.deletePack` → `AgentService.removePackBinding` | | **装配对话(buildAgent)** | 解析包 id:坏 id / 已删除的包跳过(双保险);组白名单 ∪ 包内工具 = 工具级白名单,白名单外业务工具 `removeTool` 真实注销 | `ToolPackService.resolveToolNames` + `AgentToolRegistry.registerBusinessToolsForWhitelist` | | **改方案配置** | 保存后驱逐该方案的池化实例(否则旧工具面继续生效) | `AgentService.updateExpert` → `evictAgentPool` | ## 删除语义速查 | 删除对象 | 对工具包 | 对研判方案 | 对对话实例 | |---|---|---|---| | 工具(代码下线) | 包里残留该名字 → 装配时自然无此工具(惰性失效),不报错 | — | — | | 工具包 | — | `tool_packs_json` 被摘掉该 id(不留悬空引用) | 受影响方案实例池被驱逐 | | 研判方案 | 不影响(包独立存在) | — | 该方案池清空;已建会话再对话报「智能体不存在」 | ## 内置 vs 自建 | | 内置(`is_builtin=1`,`user_id=1`) | 自建(`is_builtin=0`) | |---|---|---| | 工具包 | 全局可见、不可编辑/删除(服务端双重拒绝);id 固定 1~6 | 仅创建者可见/改删 | | 研判方案 | 全局可见、不可编辑/删除(如默认方案「数刃」) | 仅创建者可见/改删 | | 绑定 | 任何方案可勾选内置包 | 只能勾选自己的包(保存时校验) | 默认方案(`agent.row_id=1`)绑定全部 6 个内置工具包(`sql/agent_tool_pack.sql` 维护; 内置方案 UI 不可编辑,其绑定关系只能由该脚本写入)。