- Introduce a new `RuntimeTool` model to encapsulate tool data for runtime sessions, including attributes like `id`, `name`, `function_name`, `type`, and `description`. - Update the `AssistantConfig` model to include a list of reusable tools, allowing for better management of tools within assistant configurations. - Modify the `config_resolver` service to fetch and resolve tools associated with assistants, ensuring they are available during runtime. - Refactor tool-related CRUD operations in the `tools` route to support the new runtime execution model, enhancing the overall tool management system. - Update documentation and comments to reflect changes in tool execution and configuration handling, improving clarity for future development.
119 lines
3.6 KiB
Python
119 lines
3.6 KiB
Python
"""Reusable tool CRUD. Runtime execution is implemented per supported tool type."""
|
|
|
|
import uuid
|
|
|
|
from db.models import AssistantToolBinding, Tool
|
|
from db.session import get_session
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from schemas import ToolOut, ToolUpsert
|
|
from services.auth import require_admin
|
|
from services.masking import mask_secrets, merge_secrets
|
|
from sqlalchemy import select
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
router = APIRouter(
|
|
prefix="/api/tools",
|
|
tags=["tools"],
|
|
dependencies=[Depends(require_admin)],
|
|
)
|
|
|
|
|
|
def _to_out(tool: Tool) -> ToolOut:
|
|
return ToolOut(
|
|
id=tool.id,
|
|
name=tool.name,
|
|
function_name=tool.function_name,
|
|
type=tool.type,
|
|
description=tool.description,
|
|
definition=tool.definition,
|
|
secrets=mask_secrets(tool.secrets or {}),
|
|
status=tool.status,
|
|
updated_at=tool.updated_at.isoformat() if tool.updated_at else None,
|
|
)
|
|
|
|
|
|
def _payload(body: ToolUpsert, stored_secrets: dict | None = None) -> dict:
|
|
definition = body.definition.model_dump()
|
|
secrets = (
|
|
merge_secrets(body.secrets, stored_secrets or {})
|
|
if definition["type"] == "http"
|
|
else {}
|
|
)
|
|
return {
|
|
"name": body.name.strip(),
|
|
"function_name": body.function_name,
|
|
"type": definition["type"],
|
|
"description": body.description.strip(),
|
|
"definition": definition,
|
|
"secrets": secrets,
|
|
"status": body.status,
|
|
}
|
|
|
|
|
|
async def _commit(session: AsyncSession, tool: Tool) -> ToolOut:
|
|
try:
|
|
await session.commit()
|
|
except IntegrityError as exc:
|
|
await session.rollback()
|
|
raise HTTPException(409, "工具函数名已存在") from exc
|
|
await session.refresh(tool)
|
|
return _to_out(tool)
|
|
|
|
|
|
@router.get("", response_model=list[ToolOut])
|
|
async def list_tools(session: AsyncSession = Depends(get_session)):
|
|
rows = (
|
|
await session.execute(select(Tool).order_by(Tool.updated_at.desc()))
|
|
).scalars().all()
|
|
return [_to_out(tool) for tool in rows]
|
|
|
|
|
|
@router.post("", response_model=ToolOut)
|
|
async def create_tool(body: ToolUpsert, session: AsyncSession = Depends(get_session)):
|
|
tool = Tool(id=f"tool_{uuid.uuid4().hex[:12]}", **_payload(body))
|
|
session.add(tool)
|
|
return await _commit(session, tool)
|
|
|
|
|
|
@router.get("/{tool_id}", response_model=ToolOut)
|
|
async def get_tool(tool_id: str, session: AsyncSession = Depends(get_session)):
|
|
tool = await session.get(Tool, tool_id)
|
|
if not tool:
|
|
raise HTTPException(404, "工具不存在")
|
|
return _to_out(tool)
|
|
|
|
|
|
@router.put("/{tool_id}", response_model=ToolOut)
|
|
async def update_tool(
|
|
tool_id: str,
|
|
body: ToolUpsert,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
tool = await session.get(Tool, tool_id)
|
|
if not tool:
|
|
raise HTTPException(404, "工具不存在")
|
|
for key, value in _payload(body, tool.secrets or {}).items():
|
|
setattr(tool, key, value)
|
|
return await _commit(session, tool)
|
|
|
|
|
|
@router.delete("/{tool_id}")
|
|
async def delete_tool(tool_id: str, session: AsyncSession = Depends(get_session)):
|
|
tool = await session.get(Tool, tool_id)
|
|
if not tool:
|
|
raise HTTPException(404, "工具不存在")
|
|
in_use = (
|
|
await session.execute(
|
|
select(AssistantToolBinding.assistant_id)
|
|
.where(AssistantToolBinding.tool_id == tool_id)
|
|
.limit(1)
|
|
)
|
|
).scalar_one_or_none()
|
|
if in_use:
|
|
raise HTTPException(409, "工具正被助手引用,请先解绑")
|
|
await session.delete(tool)
|
|
await session.commit()
|
|
return {"ok": True}
|