feat: 新增外部系统工具加载能力,补充数据库初始化和异常处理

1. 在agent资源解析中新增从数据库加载外部工具的逻辑
2. 新增全局外部适配器注册和数据库外部schema初始化
3. 添加外部系统异常统一处理中间件
4. 在运行时工具解析中追加外部系统工具加载
This commit is contained in:
Kris 2026-06-20 22:17:20 +08:00
parent 824471454b
commit 825a5e9769
4 changed files with 73 additions and 0 deletions

View File

@ -320,12 +320,26 @@ async def resolve_agent_resource_options(
if "tools" in fields_to_load:
from yuxi.agents.toolkits.service import get_tool_metadata
from yuxi.external_systems.infrastructure.container import (
create_use_cases_from_db,
)
from yuxi.external_systems.use_cases.dto.tool import (
ListToolOptionsInput,
)
options["tools"] = [
_resource_option(tool["slug"], tool.get("name"), tool.get("description"))
for tool in get_tool_metadata(category="buildin")
if tool.get("slug")
]
try:
use_cases = create_use_cases_from_db(db)
output = await use_cases.tool_service.list_tool_options(
ListToolOptionsInput(),
)
options["tools"].extend(output.items)
except Exception as e:
logger.warning(f"Failed to load external tool options: {e}")
if "knowledges" in fields_to_load:
from yuxi.knowledge import knowledge_base

View File

@ -96,6 +96,13 @@ def get_tool_instances_by_category(category: str) -> list[Any]:
async def resolve_configured_runtime_tools(context) -> list[Any]:
from yuxi.agents.mcp.service import get_enabled_mcp_tools
from yuxi.external_systems.infrastructure.container import (
create_use_cases_from_db,
)
from yuxi.external_systems.use_cases.dto.tool import (
BuildRuntimeToolsInput,
)
from yuxi.storage.postgres.manager import pg_manager
selected_tools = []
selected_tool_names: set[str] = set()
@ -130,4 +137,24 @@ async def resolve_configured_runtime_tools(context) -> list[Any]:
selected_tools.append(tool)
selected_tool_names.add(tool.name)
# 追加加载外部系统工具(按 slug 匹配 context.tools 中的选择)
external_tool_names = [
name
for name in getattr(context, "tools", None) or []
if isinstance(name, str) and name not in selected_tool_names
]
if external_tool_names:
async with pg_manager.get_async_session_context() as db:
use_cases = create_use_cases_from_db(db)
output = await use_cases.tool_service.build_runtime_tools(
BuildRuntimeToolsInput(slugs=external_tool_names),
)
selected_tools.extend(output.items)
selected_tool_names.update(tool.name for tool in output.items)
# 构建失败的工具 slug 必须显式记录,避免用户配置的工具被静默丢失
if output.failed_slugs:
logger.warning(
f"Failed to build external runtime tools, skipped: {output.failed_slugs}"
)
return selected_tools

View File

@ -20,6 +20,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from yuxi.external_systems.exceptions import ExternalSystemError
from server.routers import router
from server.utils.lifespan import lifespan
from server.utils.auth_middleware import is_public_path
@ -41,6 +42,27 @@ app = FastAPI(lifespan=lifespan)
# 所有业务接口统一挂载到 /api具体分组在 server.routers 中集中注册。
app.include_router(router, prefix="/api")
@app.exception_handler(ExternalSystemError)
async def external_system_error_handler(_request: Request, exc: ExternalSystemError) -> JSONResponse:
"""外部系统限界上下文异常统一映射为 HTTP 响应。
- ``status_code`` 来自异常类
- ``details`` 透传到响应体供前端结构化处理
- 429/503 ``details`` ``retry_after`` 自动设置 ``Retry-After`` 响应头
"""
headers: dict[str, str] = {}
if exc.status_code in (429, 503):
retry_after = exc.details.get("retry_after")
if retry_after is not None:
headers["Retry-After"] = str(retry_after)
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.message, "details": exc.details},
headers=headers,
)
# CORS 设置
app.add_middleware(
CORSMiddleware,

View File

@ -9,6 +9,7 @@ from yuxi.agents.mcp.service import ensure_builtin_mcp_servers_in_db
from yuxi.models.providers.service import ensure_builtin_model_providers_in_db
from yuxi.services.run_queue_service import close_queue_clients, get_redis_client
from yuxi.storage.postgres.manager import pg_manager
from yuxi.external import register_adapters
from yuxi.knowledge import knowledge_base
from yuxi.utils import logger
from yuxi.agents.backends.sandbox import init_sandbox_provider, shutdown_sandbox_provider
@ -17,6 +18,14 @@ from yuxi import get_version
@asynccontextmanager
async def lifespan(app: FastAPI):
# 注册外部系统适配器(避免模块导入副作用)
try:
register_adapters()
except Exception as e:
logger.error(f"Failed to register external system adapters during startup: {e}")
"""FastAPI lifespan事件管理器"""
# 初始化数据库连接
try:
@ -24,6 +33,7 @@ async def lifespan(app: FastAPI):
await pg_manager.create_tables()
await pg_manager.ensure_business_schema()
await pg_manager.ensure_knowledge_schema()
await pg_manager.ensure_external_schema()
except Exception as e:
logger.error(f"Failed to initialize database during startup: {e}")