feat(system):优化后端
1.新增后端测试 2.增加了后端的加密 3.增加了i18n(国际化)
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
# 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.
|
||||
|
||||
"""MCP 辅助模块:根据全局状态机中的配置创建 pydantic-ai MCPServer 实例。"""
|
||||
|
||||
from typing import Dict, List, Any, Sequence
|
||||
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
logger = get_logger("mcp_helper")
|
||||
|
||||
# 延迟导入 pydantic_ai.mcp,避免在 MCP 包未安装时崩溃
|
||||
try:
|
||||
from pydantic_ai.mcp import (
|
||||
MCPServerStdio,
|
||||
MCPServerSSE,
|
||||
MCPServerHTTP,
|
||||
)
|
||||
_MCP_AVAILABLE = True
|
||||
except ImportError:
|
||||
_MCP_AVAILABLE = False
|
||||
logger.warning("MCP package not installed. MCP servers will not be available.")
|
||||
|
||||
|
||||
def build_mcp_toolsets(configs: Dict[str, Dict[str, Any]]) -> List[Any]:
|
||||
"""根据配置字典创建 MCPServer 实例列表。
|
||||
|
||||
Args:
|
||||
configs: {server_id: {"name": ..., "transport": ..., ...}}
|
||||
|
||||
Returns:
|
||||
MCPServer 实例列表(可直接传给 Agent 的 toolsets 参数)
|
||||
"""
|
||||
if not _MCP_AVAILABLE:
|
||||
return []
|
||||
|
||||
toolsets = []
|
||||
for server_id, cfg in configs.items():
|
||||
try:
|
||||
transport = cfg.get("transport", "stdio")
|
||||
tool_prefix = cfg.get("tool_prefix")
|
||||
name = cfg.get("name", server_id)
|
||||
|
||||
if transport == "stdio":
|
||||
server = MCPServerStdio(
|
||||
command=cfg.get("command", ""),
|
||||
args=cfg.get("args", []),
|
||||
env=cfg.get("env"),
|
||||
tool_prefix=tool_prefix,
|
||||
id=server_id,
|
||||
)
|
||||
elif transport == "sse":
|
||||
server = MCPServerSSE(
|
||||
url=cfg.get("url", ""),
|
||||
tool_prefix=tool_prefix,
|
||||
id=server_id,
|
||||
)
|
||||
elif transport == "http":
|
||||
server = MCPServerHTTP(
|
||||
url=cfg.get("url", ""),
|
||||
tool_prefix=tool_prefix,
|
||||
id=server_id,
|
||||
)
|
||||
else:
|
||||
logger.warning(f"Unsupported MCP transport: {transport} for server {name}")
|
||||
continue
|
||||
|
||||
toolsets.append(server)
|
||||
logger.info(f"MCP server '{name}' ({transport}) registered as toolset")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to build MCP server '{server_id}': {e}")
|
||||
|
||||
return toolsets
|
||||
|
||||
|
||||
async def get_mcp_toolsets_from_gsm() -> List[Any]:
|
||||
"""从 GlobalStateMachine 拉取 MCP 配置并构建 toolsets。"""
|
||||
if not _MCP_AVAILABLE:
|
||||
return []
|
||||
|
||||
try:
|
||||
from kilostar.core.global_state_machine.gsm_snapshot import fetch_snapshot
|
||||
|
||||
# 走快照:MCP 配置变更频率极低,本地缓存命中率近 100%
|
||||
snapshot = await fetch_snapshot()
|
||||
return build_mcp_toolsets(snapshot.mcp_servers)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load MCP configs from GSM: {e}")
|
||||
return []
|
||||
|
||||
|
||||
async def get_all_toolsets_for_scope(scope: str) -> List[Any]:
|
||||
"""汇总某个 scope 下的全部 toolset:system + personal + mcp。
|
||||
|
||||
返回顺序保持稳定:先本地 toolset(system → personal),再 MCP toolset。
|
||||
任意一类拉取失败仅记录日志,不影响其他类。
|
||||
"""
|
||||
toolsets: List[Any] = []
|
||||
try:
|
||||
from kilostar.core.global_state_machine.gsm_snapshot import (
|
||||
build_toolsets_for_scope,
|
||||
fetch_snapshot,
|
||||
)
|
||||
|
||||
# 一次快照拉取覆盖 system + custom toolsets,本地按 scope 重建 FunctionToolset
|
||||
snapshot = await fetch_snapshot()
|
||||
local = build_toolsets_for_scope(snapshot, scope)
|
||||
if local:
|
||||
toolsets.extend(local)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load local toolsets from GSM ({scope}): {e}")
|
||||
|
||||
toolsets.extend(await get_mcp_toolsets_from_gsm())
|
||||
return toolsets
|
||||
|
||||
|
||||
async def list_mcp_tools_for_configs(
|
||||
configs: Dict[str, Dict[str, Any]],
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""对每个 MCP 服务器逐个尝试连接,列出它们暴露的工具名。
|
||||
|
||||
实现层面会进入 ``async with server:`` 上下文,调用一次 ``get_tools()``,
|
||||
再把工具名(带 tool_prefix)抽出来。任何一个 server 失败都不影响其他 server,
|
||||
出错时该项 ``tools=[]`` 并附带 ``error`` 字段。
|
||||
"""
|
||||
result: List[Dict[str, Any]] = []
|
||||
if not _MCP_AVAILABLE:
|
||||
return result
|
||||
|
||||
servers = build_mcp_toolsets(configs)
|
||||
for server in servers:
|
||||
server_id = getattr(server, "id", None)
|
||||
cfg = configs.get(server_id, {}) if server_id else {}
|
||||
name = cfg.get("name", server_id or "unknown")
|
||||
transport = cfg.get("transport", "stdio")
|
||||
item: Dict[str, Any] = {
|
||||
"server_id": server_id,
|
||||
"name": name,
|
||||
"transport": transport,
|
||||
"tool_prefix": cfg.get("tool_prefix"),
|
||||
"tools": [],
|
||||
}
|
||||
try:
|
||||
async with server:
|
||||
tools = await server.get_tools()
|
||||
item["tools"] = [
|
||||
getattr(t, "name", None) or getattr(t, "tool_name", str(t))
|
||||
for t in tools
|
||||
]
|
||||
except Exception as e:
|
||||
item["error"] = str(e)
|
||||
logger.warning(f"MCP server '{name}' list_tools failed: {e}")
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
async def list_mcp_tools_from_gsm() -> List[Dict[str, Any]]:
|
||||
"""从 GlobalStateMachine 拉取配置后调用 :func:`list_mcp_tools_for_configs`。"""
|
||||
if not _MCP_AVAILABLE:
|
||||
return []
|
||||
|
||||
try:
|
||||
from kilostar.core.global_state_machine.gsm_snapshot import fetch_snapshot
|
||||
|
||||
snapshot = await fetch_snapshot()
|
||||
return await list_mcp_tools_for_configs(snapshot.mcp_servers)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to list MCP tools from GSM: {e}")
|
||||
return []
|
||||
Reference in New Issue
Block a user