chore: 迁移服务器前同步本地修改

This commit is contained in:
2026-08-05 21:26:13 +08:00
parent aa47a19e98
commit 1a914a2517
73 changed files with 908 additions and 1961 deletions
@@ -13,10 +13,6 @@
# limitations under the License.
from typing import Any, Dict, List, Optional, Tuple
from kilostar.utils.ray_compat import actor_class, _STANDALONE
if not _STANDALONE:
import ray
from kilostar.core.global_state_machine.individual_manager import (
GlobalIndividualManager,
@@ -28,7 +24,6 @@ from kilostar.core.global_state_machine.gsm_snapshot import GSMSnapshot
from kilostar.core.postgres_database import PostgresDatabase
@actor_class
class GlobalStateMachine:
"""全局状态机 Actor,统一持有 Provider/Tool/Skill/Individual/MCP/CustomToolset 注册表。
@@ -62,13 +57,13 @@ class GlobalStateMachine:
self.postgres_database
)
# MCP servers
rows = await self.postgres_database.list_mcp_servers_db.remote()
rows = await self.postgres_database.list_mcp_servers_db()
self._mcp_servers = {row["server_id"]: row for row in rows}
# Tool configs
cfg_rows = await self.postgres_database.list_tool_configs_db.remote()
cfg_rows = await self.postgres_database.list_tool_configs_db()
self._tool_configs = {row["tool_name"]: row["config"] for row in cfg_rows}
# Custom toolsets(含系统预置)
ts_rows = await self.postgres_database.list_custom_toolsets.remote()
ts_rows = await self.postgres_database.list_custom_toolsets()
self._custom_toolsets = {row["toolset_id"]: row for row in ts_rows}
# 补种系统预置工具集(首次启动或 DB 被 create_all 重建后)
await self._seed_system_toolsets()
@@ -96,12 +91,12 @@ class GlobalStateMachine:
continue
if tid in wanted_ids:
continue
await self.postgres_database.delete_custom_toolset.remote(tid)
await self.postgres_database.delete_custom_toolset(tid)
self._custom_toolsets.pop(tid, None)
for name, pkg in packages.items():
tid = f"system::{name}"
saved = await self.postgres_database.upsert_custom_toolset.remote(
saved = await self.postgres_database.upsert_custom_toolset(
toolset_id=tid,
name=pkg.get("display_name") or name,
tools=list(pkg.get("tools", [])),
@@ -155,18 +150,10 @@ class GlobalStateMachine:
def _publish_snapshot(self) -> None:
"""版本号 +1 并发布当前状态快照。"""
self._config_version += 1
snapshot = self._build_snapshot()
if _STANDALONE:
self._current_ref = snapshot
else:
self._current_ref = ray.put(snapshot)
self._current_ref = self._build_snapshot()
async def current_config_ref(self) -> Tuple[int, Any]:
"""返回 ``(version, ObjectRef 或 snapshot)``。
分布式模式返回 ObjectRef,调用方用 ``ray.get`` 自取;
单机模式直接返回 snapshot 对象。
"""
"""返回 ``(version, snapshot)``。进程内直接持有 snapshot 对象。"""
if self._current_ref is None:
self._publish_snapshot()
return self._config_version, self._current_ref
@@ -199,11 +186,11 @@ class GlobalStateMachine:
self._publish_snapshot()
return result
def get_provider_list(self):
async def get_provider_list(self):
"""返回内存中已登记的全部 Provider。"""
return self._global_provider_manager.get_provider_list()
def get_provider(self, provider_title):
async def get_provider(self, provider_title):
"""按 provider_title 取出单个 Provider 实例。"""
return self._global_provider_manager.get_provider(provider_title)
@@ -217,17 +204,17 @@ class GlobalStateMachine:
# ─── Tool / Toolset ────────────────────────────────────────
def get_tool_mapper(self):
async def get_tool_mapper(self):
"""返回 agent_name -> {tool_name: callable} 的全量映射。"""
return self._global_tool_manager.tool_mapper
def get_tool_list(self, agent_name: str):
async def get_tool_list(self, agent_name: str):
"""返回某个 agent 可用的工具集(其专属工具与 default 工具的并集)。"""
tools = self._global_tool_manager.tool_mapper.get(agent_name, {})
default_tools = self._global_tool_manager.tool_mapper.get("default", {})
return {**default_tools, **tools}
def get_tool_categories(self):
async def get_tool_categories(self):
"""返回工具按分类聚合的完整信息。"""
return {
"system": self._global_tool_manager.get_system_tools(),
@@ -241,22 +228,22 @@ class GlobalStateMachine:
"all": self._global_tool_manager.get_all_tools(),
}
def get_toolsets_for_scope(self, scope: str) -> List[Any]:
async def get_toolsets_for_scope(self, scope: str) -> List[Any]:
"""返回某个 scope 下的"系统 + 自定义工具组"toolset 列表(不含 MCP)。"""
return self._global_tool_manager.get_toolsets_for_scope(scope)
def get_retrieval_toolsets_for_scope(self, scope: str) -> List[Any]:
async def get_retrieval_toolsets_for_scope(self, scope: str) -> List[Any]:
"""仅返回 retrieval 工具集(system_node 专用,不包含 generation 工具)。"""
return self._global_tool_manager.get_retrieval_toolsets_for_scope(scope)
def list_toolset_packages(self) -> List[Dict[str, Any]]:
async def list_toolset_packages(self) -> List[Dict[str, Any]]:
"""列出所有磁盘工具包(前端"工具插件"页面卡片即由此渲染)。"""
return [
{k: v for k, v in pkg.items() if k != "readme_path"}
for pkg in self._global_tool_manager.toolset_packages.values()
]
def get_toolset_package_readme(self, name: str) -> Optional[str]:
async def get_toolset_package_readme(self, name: str) -> Optional[str]:
"""读取指定工具包的 README.md 内容;不存在返回 None。"""
pkg = self._global_tool_manager.toolset_packages.get(name)
if not pkg or not pkg.get("readme_path"):
@@ -271,26 +258,26 @@ class GlobalStateMachine:
async def add_mcp_server(self, server_id: str, config: Dict[str, Any]) -> bool:
"""注册一个 MCP 服务器配置(写库 → 写内存)。"""
saved = await self.postgres_database.upsert_mcp_server.remote(server_id, config)
saved = await self.postgres_database.upsert_mcp_server(server_id, config)
self._mcp_servers[server_id] = saved
self._publish_snapshot()
return True
def get_mcp_server(self, server_id: str) -> Optional[Dict[str, Any]]:
async def get_mcp_server(self, server_id: str) -> Optional[Dict[str, Any]]:
return self._mcp_servers.get(server_id)
def list_mcp_servers(self) -> List[Dict[str, Any]]:
async def list_mcp_servers(self) -> List[Dict[str, Any]]:
return [
{"server_id": sid, **cfg} for sid, cfg in self._mcp_servers.items()
]
async def delete_mcp_server(self, server_id: str) -> bool:
ok = await self.postgres_database.delete_mcp_server_db.remote(server_id)
ok = await self.postgres_database.delete_mcp_server_db(server_id)
self._mcp_servers.pop(server_id, None)
self._publish_snapshot()
return bool(ok)
def get_mcp_server_configs(self) -> Dict[str, Dict[str, Any]]:
async def get_mcp_server_configs(self) -> Dict[str, Dict[str, Any]]:
"""返回原始 MCP 服务器配置字典(供节点创建 toolsets 时使用)。"""
return dict(self._mcp_servers)
@@ -298,23 +285,23 @@ class GlobalStateMachine:
async def set_tool_config(self, tool_name: str, config: Dict[str, Any]) -> bool:
"""整体覆盖某工具的运行期配置(敏感字段在 DAO 内自动加密)。"""
saved = await self.postgres_database.upsert_tool_config.remote(
saved = await self.postgres_database.upsert_tool_config(
tool_name, config
)
self._tool_configs[tool_name] = saved["config"]
self._publish_snapshot()
return True
def get_tool_config(self, tool_name: str) -> Dict[str, Any]:
async def get_tool_config(self, tool_name: str) -> Dict[str, Any]:
"""按工具名取出配置;不存在则返回空字典。"""
return dict(self._tool_configs.get(tool_name, {}))
def list_tool_configs(self) -> Dict[str, Dict[str, Any]]:
async def list_tool_configs(self) -> Dict[str, Dict[str, Any]]:
"""返回全部已配置工具的配置(包含敏感字段,调用方需自行脱敏)。"""
return dict(self._tool_configs)
async def delete_tool_config(self, tool_name: str) -> bool:
ok = await self.postgres_database.delete_tool_config_db.remote(tool_name)
ok = await self.postgres_database.delete_tool_config_db(tool_name)
self._tool_configs.pop(tool_name, None)
self._publish_snapshot()
return bool(ok)
@@ -345,7 +332,7 @@ class GlobalStateMachine:
raise ValueError(
f"用户工具集只允许包含第三方工具,以下不合法:{invalid}"
)
saved = await self.postgres_database.upsert_custom_toolset.remote(
saved = await self.postgres_database.upsert_custom_toolset(
toolset_id=toolset_id,
name=name,
tools=list(tools),
@@ -359,14 +346,14 @@ class GlobalStateMachine:
self._publish_snapshot()
return saved
def list_custom_toolsets(self) -> List[Dict[str, Any]]:
async def list_custom_toolsets(self) -> List[Dict[str, Any]]:
return list(self._custom_toolsets.values())
def get_custom_toolset(self, toolset_id: str) -> Optional[Dict[str, Any]]:
async def get_custom_toolset(self, toolset_id: str) -> Optional[Dict[str, Any]]:
return self._custom_toolsets.get(toolset_id)
async def delete_custom_toolset(self, toolset_id: str) -> bool:
ok = await self.postgres_database.delete_custom_toolset.remote(toolset_id)
ok = await self.postgres_database.delete_custom_toolset(toolset_id)
self._custom_toolsets.pop(toolset_id, None)
self._global_tool_manager.rebuild_custom_toolsets(self._custom_toolsets)
self._publish_snapshot()
@@ -374,33 +361,33 @@ class GlobalStateMachine:
# ─── Skill ────────────────────────────────────────────────
def add_skill(self, skill_name: str):
async def add_skill(self, skill_name: str):
result = self._global_skill_manager.add_skill(skill_name)
self._publish_snapshot()
return result
def get_skill_list(self):
async def get_skill_list(self):
return self._global_skill_manager.get_skill_list()
def remove_skill(self, skill_name: str):
async def remove_skill(self, skill_name: str):
result = self._global_skill_manager.remove_skill(skill_name)
self._publish_snapshot()
return result
# ─── Individual ───────────────────────────────────────────
def add_individual(self, agent_id: str, config):
async def add_individual(self, agent_id: str, config):
result = self._global_individual_manager.add_individual(agent_id, config)
self._publish_snapshot()
return result
def get_individual(self, agent_id: str):
async def get_individual(self, agent_id: str):
return self._global_individual_manager.get_individual(agent_id)
def remove_individual(self, agent_id: str):
async def remove_individual(self, agent_id: str):
result = self._global_individual_manager.remove_individual(agent_id)
self._publish_snapshot()
return result
def list_individuals(self):
async def list_individuals(self):
return self._global_individual_manager.list_individuals()
@@ -14,14 +14,14 @@
"""GSM 快照对象与客户端拉取工具。
设计动机:把 GSM actor 内存里的"读路径配置"打包成可放进 Ray Object Store
的不可变快照。读端不再走 actor RPC,而是 ``fetch_snapshot()`` 一次拿到全量
当前配置,亚毫秒级共享内存读,绕开单 actor 的吞吐瓶颈。
设计动机:把 GSM 内存里的"读路径配置"打包成一个不可变快照对象。读端不逐字段
走 GSM 方法,而是 ``fetch_snapshot()`` 一次拿到全量当前配置,进程内直接引用,
配合版本号做进程内缓存失效。
GSM 仍然是 source of truth + 写入串行化器,但读路径解耦:
- 写入 → GSM 内存更新 → ``ray.put(snapshot)`` 拿到新 ObjectRef → 版本号 +1
- 读取 → ``current_config_ref()`` 拿 (version, ref) → ``ray.get(ref)`` 直读
- 写入 → GSM 内存更新 → 重建 snapshot 对象 → 版本号 +1
- 读取 → ``current_version()`` 校验缓存新鲜度 → ``current_config_ref()`` 拿快照
旧的 ``get_provider / get_individual / ...`` 接口保留不动,是低频路径的兜底;
新代码(特别是 skill task 这种高并发热路径)应优先走 ``fetch_snapshot``。
@@ -29,15 +29,9 @@ GSM 仍然是 source of truth + 写入串行化器,但读路径解耦:
from __future__ import annotations
import asyncio
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional, Tuple
from kilostar.utils.ray_compat import _STANDALONE
if not _STANDALONE:
import ray
from kilostar.core.global_state_machine.model_provider.base_provider import Provider
from kilostar.utils.logger import get_logger
@@ -73,7 +67,6 @@ class GSMSnapshot:
_local_cache: Dict[str, Any] = {"version": -1, "snapshot": None}
_cache_lock = asyncio.Lock()
async def fetch_snapshot(
@@ -83,54 +76,34 @@ async def fetch_snapshot(
) -> GSMSnapshot:
"""拉取当前 GSM 快照。
优先走"版本号检查 + ObjectRef 直读"路径:
单进程 asyncio 模型下,写入路径原子发布快照对象,读端不需要加锁:
1. 调 ``gsm.current_version.remote()`` 看本地缓存是否还新(一次轻量 RPC)
2. 若本地缓存版本号一致,直接返回缓存(亚毫秒,零网络)
3. 否则调 ``gsm.current_config_ref.remote()`` 拿 ref,``ray.get`` 解出
4. 更新本地缓存
Args:
use_cache: 默认开启进程内 LRU 缓存(实际是单槽位,持有当前版本);
测试或诊断场景可关掉强制重拉。
gsm_actor: 可选传入 GSM actor handle;省略时通过 ``ray_actor_hook`` 获取。
Note:
本函数在 task / actor 进程内多次调用是廉价的;建议每次需要 config 时
现取,不要把 snapshot 长期持有跨任务边界(避免 ObjectRef 阻碍回收)。
1. 先看本地缓存版本号是否与 ``current_version()`` 一致;
2. 一致则直接返回缓存对象(少一次全量数据传递);
3. 不一致则调 ``current_config_ref()`` 拿最新快照并更新缓存。
"""
if gsm_actor is None:
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_gsm
gsm_actor = ray_actor_hook("global_state_machine").global_state_machine
gsm_actor = get_gsm()
if use_cache:
async with _cache_lock:
try:
latest_version = await gsm_actor.current_version.remote()
except Exception:
latest_version = None
try:
latest_version = await gsm_actor.current_version()
except Exception:
latest_version = None
if (
latest_version is not None
and _local_cache.get("version") == latest_version
and _local_cache.get("snapshot") is not None
):
return _local_cache["snapshot"]
if (
latest_version is not None
and _local_cache["version"] == latest_version
and _local_cache["snapshot"] is not None
):
return _local_cache["snapshot"]
version, ref_or_snapshot = await gsm_actor.current_config_ref.remote()
if _STANDALONE:
snapshot = ref_or_snapshot
else:
snapshot = ray.get(ref_or_snapshot)
_local_cache["version"] = version
_local_cache["snapshot"] = snapshot
return snapshot
version, ref_or_snapshot = await gsm_actor.current_config_ref.remote()
if _STANDALONE:
return ref_or_snapshot
return ray.get(ref_or_snapshot)
version, snapshot = await gsm_actor.current_config_ref()
_local_cache["version"] = version
_local_cache["snapshot"] = snapshot
return snapshot
def reset_local_cache() -> None:
@@ -32,7 +32,7 @@ class GlobalIndividualManager:
"""
try:
try:
individuals = await postgres.get_all_worker_individual.remote()
individuals = await postgres.get_all_worker_individual()
for ind in individuals:
agent_id = getattr(ind, "agent_id", None)
if agent_id:
@@ -46,7 +46,7 @@ class ProviderManager:
async def init_provider_register(self, postgres) -> None:
"""从 Postgres 读取已存的 Provider 列表,按 provider_title 装入内存注册表。"""
providers = await postgres.get_provider.remote()
providers = await postgres.get_provider()
for provider in providers:
self.provider_register[provider.provider_title] = provider
@@ -90,7 +90,7 @@ class ProviderManager:
provider: Provider = await provider_class.create_provider(provider_args)
provider.provider_owner = provider_owner
self.provider_register[provider_title] = provider
await postgres_database.add_provider_db.remote(
await postgres_database.add_provider_db(
provider_id=str(ulid.ULID()),
provider_title=provider.provider_title,
provider_url=provider.provider_url,
@@ -125,7 +125,7 @@ class ProviderManager:
async def delete_provider(self, provider_title: str, postgres_database) -> None:
"""从内存注册表 + Postgres 中一并删除指定 Provider;不存在时静默返回。"""
if provider_title in self.provider_register:
await postgres_database.delete_provider_by_title.remote(
await postgres_database.delete_provider_by_title(
provider_title=provider_title
)
del self.provider_register[provider_title]