feat(toolset): 工具系统重构为 toolset 统一管理,新增系统预置工具集
将工具管理从"agent 挂单个 tool"改为"agent 挂 toolset"模式: - 三个系统预置工具集(system_basic/system_chat/system_workflow)入 DB - 新增 send_file 工具(系统对话工具集)、修复 approval actor 调用 bug - 后端 agent 加载全部走 toolset 链路,移除 load_tools_from_list - 前端工具集中心卡片展示 + agent 配置改为 toolset 多选 - resource API 增加 category 过滤与系统 toolset 保护 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
"""toolset system refactor: add is_system/category to custom_toolset, seed system toolsets, migrate node tools to toolset_ids
|
||||
|
||||
Revision ID: 0008
|
||||
Revises: 0007
|
||||
Create Date: 2026-06-05
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from ulid import ULID
|
||||
|
||||
revision = "0008"
|
||||
down_revision = "0007"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
# 系统预置 toolset 的固定 ID(前端可能引用)
|
||||
SYSTEM_TOOLSETS = [
|
||||
{
|
||||
"toolset_id": "system_basic",
|
||||
"name": "系统基础工具集",
|
||||
"description": "文件读写、搜索、Python/Shell 执行等基础能力",
|
||||
"category": "system_basic",
|
||||
"tools": [
|
||||
"file_reader",
|
||||
"write_file",
|
||||
"edit_file",
|
||||
"search_file",
|
||||
"python_executor",
|
||||
"shell_executor",
|
||||
],
|
||||
},
|
||||
{
|
||||
"toolset_id": "system_chat",
|
||||
"name": "系统对话工具集",
|
||||
"description": "对话场景专用工具,例如向用户发送文件附件",
|
||||
"category": "system_chat",
|
||||
"tools": ["send_file"],
|
||||
},
|
||||
{
|
||||
"toolset_id": "system_workflow",
|
||||
"name": "系统工作流工具集",
|
||||
"description": "工作流场景专用工具,例如人工审批",
|
||||
"category": "system_workflow",
|
||||
"tools": ["approval"],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 1. 加 is_system / category 列
|
||||
op.add_column(
|
||||
"custom_toolset",
|
||||
sa.Column(
|
||||
"is_system",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
server_default=sa.text("false"),
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"custom_toolset",
|
||||
sa.Column(
|
||||
"category",
|
||||
sa.String(32),
|
||||
nullable=False,
|
||||
server_default=sa.text("'user'"),
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_custom_toolset_is_system", "custom_toolset", ["is_system"]
|
||||
)
|
||||
op.create_index("ix_custom_toolset_category", "custom_toolset", ["category"])
|
||||
|
||||
# 2. 种子三个系统工具集
|
||||
conn = op.get_bind()
|
||||
for ts in SYSTEM_TOOLSETS:
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"""
|
||||
INSERT INTO custom_toolset
|
||||
(toolset_id, name, description, owner_id, tools, is_system, category)
|
||||
VALUES (:tid, :name, :desc, NULL, CAST(:tools AS JSONB), true, :cat)
|
||||
ON CONFLICT (toolset_id) DO NOTHING
|
||||
"""
|
||||
),
|
||||
{
|
||||
"tid": ts["toolset_id"],
|
||||
"name": ts["name"],
|
||||
"desc": ts["description"],
|
||||
"tools": _json_dump(ts["tools"]),
|
||||
"cat": ts["category"],
|
||||
},
|
||||
)
|
||||
|
||||
# 3. 迁移 system_node_config.tools:旧值是 tool_name 列表,按工具名归属推断 toolset_id
|
||||
_migrate_tools_to_toolsets(conn, "system_node_config", "node_name")
|
||||
# 4. 同样迁移 specialist_individual / ordinary_individual
|
||||
_migrate_tools_to_toolsets(conn, "specialist_individual", "agent_id")
|
||||
_migrate_tools_to_toolsets(conn, "ordinary_individual", "agent_id")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_custom_toolset_category", table_name="custom_toolset")
|
||||
op.drop_index("ix_custom_toolset_is_system", table_name="custom_toolset")
|
||||
op.drop_column("custom_toolset", "category")
|
||||
op.drop_column("custom_toolset", "is_system")
|
||||
# 注意:tools 字段语义变更不可逆——保留 toolset_ids,不还原
|
||||
|
||||
|
||||
def _json_dump(value) -> str:
|
||||
import json
|
||||
|
||||
return json.dumps(value, ensure_ascii=False)
|
||||
|
||||
|
||||
_TOOL_TO_TOOLSET = {
|
||||
"file_reader": "system_basic",
|
||||
"write_file": "system_basic",
|
||||
"edit_file": "system_basic",
|
||||
"search_file": "system_basic",
|
||||
"python_executor": "system_basic",
|
||||
"shell_executor": "system_basic",
|
||||
"send_file": "system_chat",
|
||||
"approval": "system_workflow",
|
||||
}
|
||||
|
||||
|
||||
def _migrate_tools_to_toolsets(conn, table: str, pk_col: str) -> None:
|
||||
"""把表里 ``tools`` 字段(旧:tool_name 列表)转换为 toolset_id 列表。
|
||||
|
||||
第三方/未知工具名直接丢弃(这些应该由用户自定义的 toolset 承载,迁移期不识别)。
|
||||
"""
|
||||
rows = conn.execute(
|
||||
sa.text(f"SELECT {pk_col}, tools FROM {table} WHERE tools IS NOT NULL")
|
||||
).fetchall()
|
||||
for pk, old_tools in rows:
|
||||
if not old_tools:
|
||||
continue
|
||||
toolset_ids = sorted({
|
||||
_TOOL_TO_TOOLSET[t] for t in old_tools if t in _TOOL_TO_TOOLSET
|
||||
})
|
||||
conn.execute(
|
||||
sa.text(
|
||||
f"UPDATE {table} SET tools = CAST(:val AS JSONB) WHERE {pk_col} = :pk"
|
||||
),
|
||||
{"val": _json_dump(list(toolset_ids)), "pk": pk},
|
||||
)
|
||||
Reference in New Issue
Block a user