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:
2026-06-05 18:03:49 +00:00
parent d39c80743d
commit 0e57c5cf16
23 changed files with 584 additions and 169 deletions
+8 -8
View File
@@ -35,7 +35,7 @@ class AgentRegister(BaseModel):
provider_title: str
model_id: str
individual_name: str
tools: Optional[List[str]] = None
toolsets: Optional[List[str]] = None
persona_id: Optional[str] = None
display_name: Optional[str] = None
@@ -45,7 +45,7 @@ class AgentLocalRegister(BaseModel):
path: str
individual_name: str
tools: Optional[List[str]] = None
toolsets: Optional[List[str]] = None
@agent_router.get("")
@@ -78,13 +78,15 @@ async def load_agent(
agent_register.individual_name,
agent_register.provider_title,
agent_register.model_id,
agent_register.tools,
agent_register.toolsets,
agent_register.persona_id,
agent_register.display_name,
)
scope = agent_register.individual_name
toolsets = await get_all_toolsets_for_scope(scope)
toolsets = await get_all_toolsets_for_scope(
scope, toolset_ids=agent_register.toolsets
)
# Resolve persona system_prompt from DB
persona_prompt = None
@@ -100,7 +102,6 @@ async def load_agent(
global_state_machine,
agent_register.provider_title,
agent_register.model_id,
agent_register.tools,
toolsets,
accept_lang,
persona_prompt,
@@ -111,7 +112,6 @@ async def load_agent(
global_state_machine,
agent_register.provider_title,
agent_register.model_id,
agent_register.tools,
toolsets,
accept_lang,
persona_prompt,
@@ -140,7 +140,7 @@ class WorkerIndividualCreate(BaseModel):
output_template: dict
bound_skill: Dict[str, List[str]]
workspace: List[str]
tools: Optional[List[str]] = None
toolsets: Optional[List[str]] = None
node_affinity: str = "cpu"
@field_validator("node_affinity")
@@ -163,7 +163,7 @@ class WorkerIndividualUpdate(BaseModel):
output_template: Optional[dict] = None
bound_skill: Optional[Dict[str, List[str]]] = None
workspace: Optional[List[str]] = None
tools: Optional[List[str]] = None
toolsets: Optional[List[str]] = None
node_affinity: Optional[str] = None
@field_validator("node_affinity")
+13 -3
View File
@@ -290,16 +290,22 @@ async def create_custom_toolset(
@resource_router.get("/custom-toolset")
async def list_custom_toolsets(
category: Optional[str] = None,
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""列出工具组:USER 只能看到自己的;ADMIN 及以上可看全部。"""
"""列出工具组:支持按 category 过滤。USER 只能看到自己的+系统的ADMIN 看全部。"""
from kilostar.utils.check_user.role_check import get_authority
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
toolsets = await global_state_machine.list_custom_toolsets.remote()
authority = await get_authority(token_data.user_id)
if authority < UserAuthority.ADMINISTRATOR:
toolsets = [t for t in toolsets if t.get("owner_id") == token_data.user_id]
toolsets = [
t for t in toolsets
if t.get("is_system") or t.get("owner_id") == token_data.user_id
]
if category:
toolsets = [t for t in toolsets if t.get("category") == category]
return {"toolsets": toolsets}
@@ -326,6 +332,8 @@ async def update_custom_toolset(
existing = await global_state_machine.get_custom_toolset.remote(toolset_id)
if not existing:
raise HTTPException(status_code=404, detail="Custom toolset not found")
if existing.get("is_system"):
raise HTTPException(status_code=403, detail="系统预置工具集不可修改")
await _assert_toolset_owner_or_admin(existing, token_data)
name = body.name if body.name is not None else existing["name"]
tools = body.tools if body.tools is not None else existing["tools"]
@@ -348,11 +356,13 @@ async def delete_custom_toolset(
toolset_id: str,
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""删除工具组:USER 只能删自己的;ADMIN 及以上可删任意。"""
"""删除工具组:系统预置不可删;USER 只能删自己的;ADMIN 及以上可删任意用户的"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
existing = await global_state_machine.get_custom_toolset.remote(toolset_id)
if not existing:
raise HTTPException(status_code=404, detail="Custom toolset not found")
if existing.get("is_system"):
raise HTTPException(status_code=403, detail="系统预置工具集不可删除")
await _assert_toolset_owner_or_admin(existing, token_data)
ok = await global_state_machine.delete_custom_toolset.remote(toolset_id)
if not ok:
@@ -112,6 +112,7 @@ class GlobalStateMachine:
for scope, name_to_cls in tm.tool_mapper.items()
},
system_tools_by_scope=system_tools_by_scope,
all_funcs=dict(tm._all_funcs),
)
def _publish_snapshot(self) -> None:
@@ -270,22 +271,31 @@ class GlobalStateMachine:
tools: List[str],
description: Optional[str] = None,
owner_id: Optional[str] = None,
is_system: bool = False,
category: str = "user",
) -> Dict[str, Any]:
"""新增/更新一个自定义工具组:仅允许引用非 system/非 mcp 的工具。"""
# 校验:只能放第三方(非 system / 非 mcp)工具
invalid = [
t for t in tools if not self._global_tool_manager.is_third_party_tool(t)
]
if invalid:
raise ValueError(
f"自定义工具组只允许包含第三方工具,以下不合法:{invalid}"
)
"""新增/更新一个工具集。
- 系统 toolset``is_system=True``)允许包含 system 工具,由启动期种子写入;
API 调用方一般不应直接传 ``is_system=True``
- 用户 toolset``is_system=False``)只允许引用非 system / 非 mcp 的工具
"""
if not is_system:
invalid = [
t for t in tools if not self._global_tool_manager.is_third_party_tool(t)
]
if invalid:
raise ValueError(
f"用户工具集只允许包含第三方工具,以下不合法:{invalid}"
)
saved = await self.postgres_database.upsert_custom_toolset.remote(
toolset_id=toolset_id,
name=name,
tools=list(tools),
description=description,
owner_id=owner_id,
is_system=is_system,
category=category,
)
self._custom_toolsets[toolset_id] = saved
self._global_tool_manager.rebuild_custom_toolsets(self._custom_toolsets)
@@ -68,6 +68,8 @@ class GSMSnapshot:
# 客户端按名字 + ``tool_funcs`` 在自己进程里重建 FunctionToolset
# 避开把不可序列化/版本耦合的 toolset 实例塞进快照的坑。
system_tools_by_scope: Dict[str, List[str]] = field(default_factory=dict)
# 全部插件函数(system + 第三方),用于 toolset 装配时统一查表。
all_funcs: Dict[str, Callable[..., Any]] = field(default_factory=dict)
_local_cache: Dict[str, Any] = {"version": -1, "snapshot": None}
@@ -141,18 +143,22 @@ def reset_local_cache() -> None:
def build_toolsets_for_scope(
snapshot: GSMSnapshot, scope: str
snapshot: GSMSnapshot,
scope: str,
toolset_ids: Optional[List[str]] = None,
) -> List[Any]:
"""在调用方进程里按 ``snapshot`` 现场组装 FunctionToolset 列表。
复刻 ``GlobalToolManager.get_toolsets_for_scope`` 的合并逻辑:
新模型下"系统工具集"也存在 ``custom_toolsets`` 里(``is_system=True``),
所以本函数只按 ``toolset_ids`` 在 ``custom_toolsets`` 中按需挑选并装配;
所有工具函数(system + 第三方)都从 ``snapshot.all_funcs`` 统一查表。
- 系统 toolset:按 ``default`` + ``scope`` 两个 bucket 拼装
- 自定义 toolset``custom_toolsets`` 里所有有效项
返回的 toolset 是 *进程局部* 的——pydantic-ai FunctionToolset 实例不能跨进程
共享,但函数对象本身已经躺在 snapshot 里被 cloudpickle 还原过,
重新 ``FunctionToolset(tools=[...])`` 几乎零代价
Args:
snapshot: 当前 GSM 快照。
scope: 调用方所属 scope(保留参数:未来可按 scope 过滤系统 toolset
的可见性,目前仅用于命名/日志)。
toolset_ids: agent 配置的 toolset 列表;为 None 表示返回全部 toolset
(兼容老调用,但建议传入显式列表)
"""
try:
from pydantic_ai.toolsets import FunctionToolset
@@ -161,32 +167,24 @@ def build_toolsets_for_scope(
return []
result: List[Any] = []
for bucket in ("default", scope):
names = snapshot.system_tools_by_scope.get(bucket) or []
funcs = [snapshot.tool_funcs[n] for n in names if n in snapshot.tool_funcs]
if not funcs:
target_ids = (
list(toolset_ids)
if toolset_ids is not None
else list(snapshot.custom_toolsets.keys())
)
for toolset_id in target_ids:
defn = snapshot.custom_toolsets.get(toolset_id)
if not defn:
continue
try:
result.append(
FunctionToolset(tools=funcs, id=f"system::{bucket}")
)
except Exception as e: # pragma: no cover - 防御
_logger.error(f"build system toolset {bucket} failed: {e}")
for toolset_id, defn in snapshot.custom_toolsets.items():
names = defn.get("tools") or []
funcs = [
snapshot.third_party_funcs[n]
for n in names
if n in snapshot.third_party_funcs
]
funcs = [snapshot.all_funcs[n] for n in names if n in snapshot.all_funcs]
if not funcs:
continue
try:
result.append(
FunctionToolset(tools=funcs, id=f"custom::{toolset_id}")
FunctionToolset(tools=funcs, id=f"toolset::{toolset_id}")
)
except Exception as e: # pragma: no cover - 防御
_logger.error(f"build custom toolset {toolset_id} failed: {e}")
_logger.error(f"build toolset {toolset_id} failed: {e}")
return result
@@ -29,6 +29,7 @@ class GlobalToolManager:
_system_toolsets: Dict[str, Any]
_custom_toolsets: Dict[str, Any]
_third_party_funcs: Dict[str, Callable]
_all_funcs: Dict[str, Callable]
tool_mapper: Dict[str, Dict[str, Type[BaseToolData]]]
def __init__(self) -> None:
@@ -39,6 +40,7 @@ class GlobalToolManager:
self._retrieval_toolsets = {}
self._custom_toolsets = {}
self._third_party_funcs = {}
self._all_funcs = {}
self.tool_mapper = defaultdict(dict)
tool_plugin_dir = (
@@ -91,6 +93,8 @@ class GlobalToolManager:
if category == "mcp":
continue
self._all_funcs[plugin_name] = tool_func
scopes = [s for s in action_scopes if s] or ["default"]
if is_system:
@@ -138,7 +142,11 @@ class GlobalToolManager:
logger.error(f"Failed to build retrieval toolset {scope}: {e}")
def rebuild_custom_toolsets(self, custom_defs: Dict[str, Dict[str, Any]]) -> None:
"""根据 DB 中的自定义工具组定义重建 custom FunctionToolset。"""
"""根据 DB 中的 toolset 定义重建 FunctionToolset。
系统 toolsetis_system=True)允许包含 system 工具,用户 toolset 只取得到 callable
的工具(理论上业务层已校验只包含第三方工具)。
"""
FunctionToolset = self._import_function_toolset()
if FunctionToolset is None:
self._custom_toolsets = {}
@@ -146,22 +154,21 @@ class GlobalToolManager:
new_map: Dict[str, Any] = {}
for toolset_id, defn in custom_defs.items():
tools_names = defn.get("tools") or []
funcs = [
self._third_party_funcs[n]
for n in tools_names
if n in self._third_party_funcs
]
funcs = [self._all_funcs[n] for n in tools_names if n in self._all_funcs]
if not funcs:
continue
try:
new_map[toolset_id] = FunctionToolset(
tools=funcs,
id=f"custom::{toolset_id}",
id=f"toolset::{toolset_id}",
)
except Exception as e:
logger.error(f"Failed to build custom toolset {toolset_id}: {e}")
logger.error(f"Failed to build toolset {toolset_id}: {e}")
self._custom_toolsets = new_map
def get_toolset_by_id(self, toolset_id: str) -> Any | None:
return self._custom_toolsets.get(toolset_id)
@staticmethod
def _import_function_toolset():
try:
@@ -45,14 +45,12 @@ class ConsciousnessNode:
global_state_machine: GlobalStateMachine,
provider_title: str,
model_id: str,
tools_list: list[str] = None,
toolsets=None,
locale: str | None = None,
custom_system_prompt: str | None = None,
) -> None:
system_prompt: str = agent_prompt("consciousness_node", locale=locale, custom_system_prompt=custom_system_prompt)
output_type = Union[ForregulatoryNode, ForWorkflow, ForWorkflowEngine]
from kilostar.utils.get_tool import load_tools_from_list
from kilostar.core.global_state_machine.gsm_snapshot import fetch_snapshot
snapshot = await fetch_snapshot(gsm_actor=global_state_machine)
@@ -62,7 +60,6 @@ class ConsciousnessNode:
raise ValueError(t("provider_not_registered", locale=locale, provider_title=provider_title))
agent_factory = AgentFactory()
callables = load_tools_from_list(tools_list)
self.agent = agent_factory.create_agent(
provider=provider,
model_id=model_id,
@@ -70,7 +67,6 @@ class ConsciousnessNode:
system_prompt=system_prompt,
deps_type=ConsciousnessNodeDeps,
agent_name="consciousness_node",
tools=callables,
toolsets=toolsets,
)
@@ -47,7 +47,6 @@ class RegulatoryNode:
global_state_machine: GlobalStateMachine,
provider_title: str,
model_id: str,
tools_list: list[str] = None,
toolsets=None,
locale: str | None = None,
custom_system_prompt: str | None = None,
@@ -60,7 +59,7 @@ class RegulatoryNode:
global_state_machine: 全局状态机
provider_title: 供应商名
model_id: 模型id
tools_list: 工具列表
toolsets: 已装配好的 FunctionToolset 列表
locale: 语言代码(zh/en),控制system prompt语言
custom_system_prompt: 管理员自定义追加提示词(可选)
Returns:
@@ -68,7 +67,6 @@ class RegulatoryNode:
"""
system_prompt: str = agent_prompt("regulatory_node", locale=locale, custom_system_prompt=custom_system_prompt)
output_type = Union[MessageResponse]
from kilostar.utils.get_tool import load_tools_from_list
from kilostar.core.global_state_machine.gsm_snapshot import fetch_snapshot
# 走 Object Store 快照而不是 actor RPC:高频读路径不再受单 actor 串行限制
@@ -79,7 +77,6 @@ class RegulatoryNode:
raise ValueError(t("provider_not_registered", locale=locale, provider_title=provider_title))
agent_factory = AgentFactory()
callables = load_tools_from_list(tools_list)
self.agent = agent_factory.create_agent(
provider=provider,
model_id=model_id,
@@ -87,7 +84,6 @@ class RegulatoryNode:
system_prompt=system_prompt,
deps_type=RegulatoryNodeDeps,
agent_name="regulatory_node",
tools=callables,
toolsets=toolsets,
)
@@ -1,7 +1,7 @@
from datetime import datetime
from typing import List, Optional
from sqlalchemy import String, Text, DateTime, func
from sqlalchemy import String, Text, DateTime, Boolean, func, text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
@@ -9,10 +9,12 @@ from .base import BaseDataModel
class CustomToolsetModel(BaseDataModel):
"""用户自定义工具:把若干个非 system / 非 mcp 的工具插件打包成一个 toolset。
"""工具:把若干个工具插件打包成一个 toolset。
``tools`` 字段保存工具名列表(即 ``plugin/tool_plugin/`` 下的目录名);
GSM 启动时按列表把对应工具函数装进同一个 ``FunctionToolset``
系统预置工具集(is_system=True)由启动期种子写入,前端不允许修改/删除。
用户自定义工具集(is_system=False)只能包含第三方工具
``tools`` 字段保存工具名列表(即 ``plugin/tool_plugin/`` 下的目录名)。
"""
__tablename__ = "custom_toolset"
@@ -22,7 +24,23 @@ class CustomToolsetModel(BaseDataModel):
description: Mapped[Optional[str]] = mapped_column(Text)
owner_id: Mapped[Optional[str]] = mapped_column(String(64), index=True)
tools: Mapped[List[str]] = mapped_column(
JSONB, default=list, comment="工具名列表,仅允许非 system/非 mcp 的工具"
JSONB, default=list, comment="工具名列表"
)
is_system: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
server_default=text("false"),
index=True,
comment="是否系统预置工具集(不可修改/删除)",
)
category: Mapped[str] = mapped_column(
String(32),
nullable=False,
default="user",
server_default=text("'user'"),
index=True,
comment="分类:system_basic/system_chat/system_workflow/user",
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
@@ -33,7 +33,7 @@ class SystemNodeConfigModel(BaseDataModel):
provider_title: Mapped[str] = mapped_column(String(50), nullable=False)
model_id: Mapped[str] = mapped_column(String(100), nullable=False)
tools: Mapped[Optional[List[str]]] = mapped_column(
JSONB, default=list, comment="节点可调用的工具标识列表"
JSONB, default=list, comment="节点挂载的工具集 ID 列表(custom_toolset.toolset_id"
)
persona_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("persona_template.template_id", ondelete="SET NULL"),
@@ -7,7 +7,7 @@ from kilostar.core.postgres_database.model.custom_toolset import CustomToolsetMo
class CustomToolsetDatabase:
"""用户自定义工具 DAO。``tools`` 字段是工具名列表,业务层负责保证只放非 system/非 mcp 的工具"""
"""工具 DAO。包含系统预置 toolsetis_system=True)和用户自定义 toolset"""
def __init__(self, async_session_maker):
self.async_session_maker = async_session_maker
@@ -20,6 +20,8 @@ class CustomToolsetDatabase:
"description": row.description,
"owner_id": row.owner_id,
"tools": list(row.tools or []),
"is_system": bool(row.is_system),
"category": row.category,
}
@database_exception
@@ -30,6 +32,8 @@ class CustomToolsetDatabase:
tools: List[str],
description: Optional[str] = None,
owner_id: Optional[str] = None,
is_system: bool = False,
category: str = "user",
) -> Dict[str, Any]:
async with self.async_session_maker() as session:
stmt = select(CustomToolsetModel).where(
@@ -41,6 +45,8 @@ class CustomToolsetDatabase:
row.description = description
row.owner_id = owner_id
row.tools = list(tools)
row.is_system = is_system
row.category = category
else:
row = CustomToolsetModel(
toolset_id=toolset_id,
@@ -48,6 +54,8 @@ class CustomToolsetDatabase:
description=description,
owner_id=owner_id,
tools=list(tools),
is_system=is_system,
category=category,
)
session.add(row)
await session.commit()
@@ -44,7 +44,7 @@ async def approval(message: str, trace_id: str) -> str:
Returns:
用户的审批结果
"""
actor_list = ray_actor_hook("global_state_machine")
await actor_list.global_state_machine.put_pending.remote(trace_id, message)
reply = await actor_list.global_state_machine.get_received.remote(trace_id)
actor_list = ray_actor_hook("global_workflow_manager")
await actor_list.global_workflow_manager.put_pending.remote(trace_id, message)
reply = await actor_list.global_workflow_manager.get_received.remote(trace_id)
return reply
@@ -0,0 +1,17 @@
# Copyright 2026 zhaoxi826
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .send_file import SendFileToolData, send_file
__all__ = ["SendFileToolData", "send_file"]
@@ -0,0 +1,60 @@
# Copyright 2026 zhaoxi826
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from kilostar.plugin.tool_plugin.base_tool import BaseToolData
from kilostar.utils.ray_hook import ray_actor_hook
from typing import List, Literal, Dict
class SendFileToolData(BaseToolData):
"""``send_file`` 工具元数据:把 agent 生成的文件作为附件推送到对话窗口。"""
is_system: bool = True
action_scope: List[
Literal[
"control_node",
"consciousness_node",
"regulatory_node",
"growth_node",
"",
]
] = []
config_args: Dict[str, str] = {}
category: str = "system"
async def send_file(filename: str, content: str, trace_id: str) -> str:
"""把 agent 生成的文件作为附件发送给当前对话窗口。
通过 global_workflow_manager 的 pending 队列推送一条带特殊前缀的 JSON 消息,
前端识别后渲染为可下载的文件卡片。
Args:
filename: 文件名(含扩展名),如 "report.md" / "main.py"
content: 文件内容(UTF-8 文本)
trace_id: 当前会话/工作流的 trace_id
Returns:
发送结果说明
"""
payload = json.dumps(
{"type": "file", "filename": filename, "content": content},
ensure_ascii=False,
)
actor_list = ray_actor_hook("global_workflow_manager")
await actor_list.global_workflow_manager.put_pending.remote(
trace_id, f"__FILE__{payload}"
)
return f"已发送文件: {filename}"
+10 -5
View File
@@ -14,7 +14,7 @@
"""MCP 辅助模块:根据全局状态机中的配置创建 pydantic-ai MCPServer 实例。"""
from typing import Dict, List, Any, Sequence
from typing import Dict, List, Any, Optional, Sequence
from kilostar.utils.logger import get_logger
@@ -100,10 +100,16 @@ async def get_mcp_toolsets_from_gsm() -> List[Any]:
return []
async def get_all_toolsets_for_scope(scope: str) -> List[Any]:
async def get_all_toolsets_for_scope(
scope: str, toolset_ids: Optional[List[str]] = None
) -> List[Any]:
"""汇总某个 scope 下的全部 toolsetsystem + personal + mcp。
返回顺序保持稳定:先本地 toolsetsystem → personal),再 MCP toolset。
Args:
scope: 调用方所属 scope。
toolset_ids: agent 配置的 toolset 列表;为 None 表示返回全部。
返回顺序保持稳定:先本地 toolset(按 toolset_ids),再 MCP toolset。
任意一类拉取失败仅记录日志,不影响其他类。
"""
toolsets: List[Any] = []
@@ -113,9 +119,8 @@ async def get_all_toolsets_for_scope(scope: str) -> List[Any]:
fetch_snapshot,
)
# 一次快照拉取覆盖 system + custom toolsets,本地按 scope 重建 FunctionToolset
snapshot = await fetch_snapshot()
local = build_toolsets_for_scope(snapshot, scope)
local = build_toolsets_for_scope(snapshot, scope, toolset_ids=toolset_ids)
if local:
toolsets.extend(local)
except Exception as e:
@@ -56,15 +56,14 @@ class BaseIndividual:
"""根据 agent_config 拉起一个 pydantic-ai Agent 实例。
从 GlobalStateMachine 取出 Provider,按 agent_config 中的 provider_title
和 model_id 选择模型,加载工具列表,并把 system_prompt 注册为动态提示词。
若调用方未显式提供 ``toolsets``,会自动从全局状态机拉取 MCP toolsets 注入
和 model_id 选择模型,加载工具,并把 system_prompt 注册为动态提示词。
若调用方未显式提供 ``toolsets``,会自动从全局状态机拉取配置的工具集
Args:
agent_name: Agent 的人类可读名称,用于日志与展示。
system_prompt: 该 Agent 的基础系统提示词,会和 task_event 拼接成动态提示词。
toolsets: 显式传入的外部工具集;为 ``None`` 时会自动拉取 MCP toolsets
toolsets: 显式传入的外部工具集;为 ``None`` 时会自动按配置拉取。
"""
from kilostar.utils.get_tool import load_tools_from_list
from kilostar.utils.mcp_helper import get_all_toolsets_for_scope
from kilostar.core.global_state_machine.gsm_snapshot import fetch_snapshot
@@ -75,7 +74,7 @@ class BaseIndividual:
"provider_title", "openai"
) # default fallback
model_id = self.agent_config.get("model_id", "gpt-4o") # default fallback
tools_list = self.agent_config.get("tools", None)
toolset_ids = self.agent_config.get("tools", None)
# 直读快照,避开 actor RPC 单线程串行
snapshot = await fetch_snapshot(gsm_actor=global_state_machine)
@@ -84,10 +83,10 @@ class BaseIndividual:
raise ValueError(f"Provider {provider_title!r} 未注册")
agent_factory = AgentFactory()
callables = load_tools_from_list(tools_list)
if toolsets is None:
toolsets = await get_all_toolsets_for_scope(agent_name)
toolsets = await get_all_toolsets_for_scope(
agent_name, toolset_ids=toolset_ids
)
self.agent = agent_factory.create_agent(
provider=provider,
@@ -96,7 +95,6 @@ class BaseIndividual:
system_prompt=system_prompt,
deps_type=WorkerIndividualDeps,
agent_name=agent_name,
tools=callables,
toolsets=toolsets,
)