feat: 新增外部系统工具加载能力,补充数据库初始化和异常处理
1. 在agent资源解析中新增从数据库加载外部工具的逻辑 2. 新增全局外部适配器注册和数据库外部schema初始化 3. 添加外部系统异常统一处理中间件 4. 在运行时工具解析中追加外部系统工具加载
This commit is contained in:
parent
824471454b
commit
825a5e9769
@ -320,12 +320,26 @@ async def resolve_agent_resource_options(
|
|||||||
|
|
||||||
if "tools" in fields_to_load:
|
if "tools" in fields_to_load:
|
||||||
from yuxi.agents.toolkits.service import get_tool_metadata
|
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"] = [
|
options["tools"] = [
|
||||||
_resource_option(tool["slug"], tool.get("name"), tool.get("description"))
|
_resource_option(tool["slug"], tool.get("name"), tool.get("description"))
|
||||||
for tool in get_tool_metadata(category="buildin")
|
for tool in get_tool_metadata(category="buildin")
|
||||||
if tool.get("slug")
|
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:
|
if "knowledges" in fields_to_load:
|
||||||
from yuxi.knowledge import knowledge_base
|
from yuxi.knowledge import knowledge_base
|
||||||
|
|
||||||
|
|||||||
@ -96,6 +96,13 @@ def get_tool_instances_by_category(category: str) -> list[Any]:
|
|||||||
|
|
||||||
async def resolve_configured_runtime_tools(context) -> list[Any]:
|
async def resolve_configured_runtime_tools(context) -> list[Any]:
|
||||||
from yuxi.agents.mcp.service import get_enabled_mcp_tools
|
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_tools = []
|
||||||
selected_tool_names: set[str] = set()
|
selected_tool_names: set[str] = set()
|
||||||
@ -130,4 +137,24 @@ async def resolve_configured_runtime_tools(context) -> list[Any]:
|
|||||||
selected_tools.append(tool)
|
selected_tools.append(tool)
|
||||||
selected_tool_names.add(tool.name)
|
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
|
return selected_tools
|
||||||
|
|||||||
@ -20,6 +20,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
|
||||||
|
from yuxi.external_systems.exceptions import ExternalSystemError
|
||||||
from server.routers import router
|
from server.routers import router
|
||||||
from server.utils.lifespan import lifespan
|
from server.utils.lifespan import lifespan
|
||||||
from server.utils.auth_middleware import is_public_path
|
from server.utils.auth_middleware import is_public_path
|
||||||
@ -41,6 +42,27 @@ app = FastAPI(lifespan=lifespan)
|
|||||||
# 所有业务接口统一挂载到 /api,具体分组在 server.routers 中集中注册。
|
# 所有业务接口统一挂载到 /api,具体分组在 server.routers 中集中注册。
|
||||||
app.include_router(router, prefix="/api")
|
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 设置
|
# CORS 设置
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
|
|||||||
@ -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.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.services.run_queue_service import close_queue_clients, get_redis_client
|
||||||
from yuxi.storage.postgres.manager import pg_manager
|
from yuxi.storage.postgres.manager import pg_manager
|
||||||
|
from yuxi.external import register_adapters
|
||||||
from yuxi.knowledge import knowledge_base
|
from yuxi.knowledge import knowledge_base
|
||||||
from yuxi.utils import logger
|
from yuxi.utils import logger
|
||||||
from yuxi.agents.backends.sandbox import init_sandbox_provider, shutdown_sandbox_provider
|
from yuxi.agents.backends.sandbox import init_sandbox_provider, shutdown_sandbox_provider
|
||||||
@ -17,6 +18,14 @@ from yuxi import get_version
|
|||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
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事件管理器"""
|
"""FastAPI lifespan事件管理器"""
|
||||||
# 初始化数据库连接
|
# 初始化数据库连接
|
||||||
try:
|
try:
|
||||||
@ -24,6 +33,7 @@ async def lifespan(app: FastAPI):
|
|||||||
await pg_manager.create_tables()
|
await pg_manager.create_tables()
|
||||||
await pg_manager.ensure_business_schema()
|
await pg_manager.ensure_business_schema()
|
||||||
await pg_manager.ensure_knowledge_schema()
|
await pg_manager.ensure_knowledge_schema()
|
||||||
|
await pg_manager.ensure_external_schema()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to initialize database during startup: {e}")
|
logger.error(f"Failed to initialize database during startup: {e}")
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user