工具
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
执行日志API
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
from app.core.database import get_db
|
||||
from app.models.execution_log import ExecutionLog
|
||||
@@ -14,6 +14,10 @@ from app.models.agent import Agent
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from app.core.exceptions import NotFoundError
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/execution-logs", tags=["execution-logs"])
|
||||
|
||||
@@ -266,3 +270,153 @@ async def get_execution_performance(
|
||||
for log in timeline_logs
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/executions/{execution_id}/nodes/{node_id}/data")
|
||||
async def get_node_execution_data(
|
||||
execution_id: str,
|
||||
node_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
获取特定节点的执行数据(输入和输出)
|
||||
|
||||
从执行日志中提取节点的输入和输出数据
|
||||
"""
|
||||
# 验证执行记录是否存在
|
||||
execution = db.query(Execution).filter(Execution.id == execution_id).first()
|
||||
|
||||
if not execution:
|
||||
raise NotFoundError("执行记录", execution_id)
|
||||
|
||||
# 验证权限:检查workflow或agent的所有权
|
||||
has_permission = False
|
||||
if execution.workflow_id:
|
||||
workflow = db.query(Workflow).filter(Workflow.id == execution.workflow_id).first()
|
||||
if workflow and workflow.user_id == current_user.id:
|
||||
has_permission = True
|
||||
elif execution.agent_id:
|
||||
agent = db.query(Agent).filter(Agent.id == execution.agent_id).first()
|
||||
if agent and (agent.user_id == current_user.id or agent.status in ["published", "running"]):
|
||||
has_permission = True
|
||||
|
||||
if not has_permission:
|
||||
raise NotFoundError("执行记录", execution_id)
|
||||
|
||||
# 查找节点的开始和完成日志
|
||||
start_log = db.query(ExecutionLog).filter(
|
||||
ExecutionLog.execution_id == execution_id,
|
||||
ExecutionLog.node_id == node_id,
|
||||
ExecutionLog.message.like('%开始执行%')
|
||||
).order_by(ExecutionLog.timestamp.asc()).first()
|
||||
|
||||
complete_log = db.query(ExecutionLog).filter(
|
||||
ExecutionLog.execution_id == execution_id,
|
||||
ExecutionLog.node_id == node_id,
|
||||
ExecutionLog.message.like('%执行完成%')
|
||||
).order_by(ExecutionLog.timestamp.desc()).first()
|
||||
|
||||
input_data = None
|
||||
output_data = None
|
||||
|
||||
# 从开始日志中提取输入数据
|
||||
if start_log and start_log.data:
|
||||
input_data = start_log.data.get('input', start_log.data)
|
||||
|
||||
# 从完成日志中提取输出数据
|
||||
if complete_log and complete_log.data:
|
||||
output_data = complete_log.data.get('output', complete_log.data)
|
||||
|
||||
return {
|
||||
"execution_id": execution_id,
|
||||
"node_id": node_id,
|
||||
"input": input_data,
|
||||
"output": output_data,
|
||||
"start_time": start_log.timestamp.isoformat() if start_log else None,
|
||||
"complete_time": complete_log.timestamp.isoformat() if complete_log else None,
|
||||
"duration": complete_log.duration if complete_log else None
|
||||
}
|
||||
|
||||
|
||||
@router.get("/cache/{key:path}")
|
||||
async def get_cache_value(
|
||||
key: str,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
获取缓存值(记忆数据)
|
||||
|
||||
从Redis或内存缓存中获取指定key的值
|
||||
"""
|
||||
try:
|
||||
from app.core.redis_client import get_redis_client
|
||||
|
||||
redis_client = get_redis_client()
|
||||
value = None
|
||||
|
||||
if redis_client:
|
||||
# 从Redis获取
|
||||
try:
|
||||
cached_data = redis_client.get(key)
|
||||
if cached_data:
|
||||
value = json.loads(cached_data)
|
||||
except json.JSONDecodeError:
|
||||
# 如果不是JSON,直接返回字符串
|
||||
value = cached_data
|
||||
except Exception as e:
|
||||
logger.warning(f"从Redis获取缓存失败: {str(e)}")
|
||||
|
||||
if value is None:
|
||||
# 如果Redis中没有,返回空
|
||||
raise HTTPException(status_code=404, detail=f"缓存键 '{key}' 不存在")
|
||||
|
||||
return {
|
||||
"key": key,
|
||||
"value": value,
|
||||
"exists": True
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"获取缓存值失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"获取缓存值失败: {str(e)}")
|
||||
|
||||
|
||||
@router.delete("/cache/{key:path}")
|
||||
async def delete_cache_value(
|
||||
key: str,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
删除缓存值(记忆数据)
|
||||
|
||||
从Redis或内存缓存中删除指定key的值
|
||||
"""
|
||||
try:
|
||||
from app.core.redis_client import get_redis_client
|
||||
|
||||
redis_client = get_redis_client()
|
||||
deleted = False
|
||||
|
||||
if redis_client:
|
||||
# 从Redis删除
|
||||
try:
|
||||
result = redis_client.delete(key)
|
||||
deleted = result > 0
|
||||
except Exception as e:
|
||||
logger.warning(f"从Redis删除缓存失败: {str(e)}")
|
||||
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail=f"缓存键 '{key}' 不存在")
|
||||
|
||||
return {
|
||||
"key": key,
|
||||
"deleted": True,
|
||||
"message": "缓存已删除"
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"删除缓存值失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"删除缓存值失败: {str(e)}")
|
||||
|
||||
176
backend/app/api/tools.py
Normal file
176
backend/app/api/tools.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
工具管理API
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from app.core.database import get_db
|
||||
from app.models.tool import Tool
|
||||
from app.services.tool_registry import tool_registry
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from pydantic import BaseModel
|
||||
|
||||
router = APIRouter(prefix="/api/v1/tools", tags=["tools"])
|
||||
|
||||
|
||||
class ToolCreate(BaseModel):
|
||||
"""创建工具请求"""
|
||||
name: str
|
||||
description: str
|
||||
category: Optional[str] = None
|
||||
function_schema: dict
|
||||
implementation_type: str
|
||||
implementation_config: Optional[dict] = None
|
||||
is_public: bool = False
|
||||
|
||||
|
||||
class ToolResponse(BaseModel):
|
||||
"""工具响应"""
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
category: Optional[str]
|
||||
function_schema: dict
|
||||
implementation_type: str
|
||||
implementation_config: Optional[dict]
|
||||
is_public: bool
|
||||
use_count: int
|
||||
user_id: Optional[str]
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@router.get("", response_model=List[ToolResponse])
|
||||
async def list_tools(
|
||||
category: Optional[str] = Query(None, description="工具分类"),
|
||||
search: Optional[str] = Query(None, description="搜索关键词"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取工具列表"""
|
||||
query = db.query(Tool).filter(Tool.is_public == True)
|
||||
|
||||
if category:
|
||||
query = query.filter(Tool.category == category)
|
||||
|
||||
if search:
|
||||
query = query.filter(
|
||||
Tool.name.contains(search) |
|
||||
Tool.description.contains(search)
|
||||
)
|
||||
|
||||
tools = query.order_by(Tool.use_count.desc(), Tool.created_at.desc()).all()
|
||||
return tools
|
||||
|
||||
|
||||
@router.get("/builtin")
|
||||
async def list_builtin_tools():
|
||||
"""获取内置工具列表"""
|
||||
schemas = tool_registry.get_all_tool_schemas()
|
||||
return schemas
|
||||
|
||||
|
||||
@router.get("/{tool_id}", response_model=ToolResponse)
|
||||
async def get_tool(
|
||||
tool_id: str,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取工具详情"""
|
||||
tool = db.query(Tool).filter(Tool.id == tool_id).first()
|
||||
if not tool:
|
||||
raise HTTPException(status_code=404, detail="工具不存在")
|
||||
return tool
|
||||
|
||||
|
||||
@router.post("", response_model=ToolResponse, status_code=201)
|
||||
async def create_tool(
|
||||
tool_data: ToolCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""创建工具"""
|
||||
# 检查工具名称是否已存在
|
||||
existing = db.query(Tool).filter(Tool.name == tool_data.name).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail=f"工具名称 '{tool_data.name}' 已存在")
|
||||
|
||||
tool = Tool(
|
||||
name=tool_data.name,
|
||||
description=tool_data.description,
|
||||
category=tool_data.category,
|
||||
function_schema=tool_data.function_schema,
|
||||
implementation_type=tool_data.implementation_type,
|
||||
implementation_config=tool_data.implementation_config,
|
||||
is_public=tool_data.is_public,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
db.add(tool)
|
||||
db.commit()
|
||||
db.refresh(tool)
|
||||
|
||||
return tool
|
||||
|
||||
|
||||
@router.put("/{tool_id}", response_model=ToolResponse)
|
||||
async def update_tool(
|
||||
tool_id: str,
|
||||
tool_data: ToolCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""更新工具"""
|
||||
tool = db.query(Tool).filter(Tool.id == tool_id).first()
|
||||
if not tool:
|
||||
raise HTTPException(status_code=404, detail="工具不存在")
|
||||
|
||||
# 检查权限(只有创建者可以更新)
|
||||
if tool.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="无权更新此工具")
|
||||
|
||||
# 检查名称冲突
|
||||
if tool_data.name != tool.name:
|
||||
existing = db.query(Tool).filter(Tool.name == tool_data.name).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail=f"工具名称 '{tool_data.name}' 已存在")
|
||||
|
||||
tool.name = tool_data.name
|
||||
tool.description = tool_data.description
|
||||
tool.category = tool_data.category
|
||||
tool.function_schema = tool_data.function_schema
|
||||
tool.implementation_type = tool_data.implementation_type
|
||||
tool.implementation_config = tool_data.implementation_config
|
||||
tool.is_public = tool_data.is_public
|
||||
|
||||
db.commit()
|
||||
db.refresh(tool)
|
||||
|
||||
return tool
|
||||
|
||||
|
||||
@router.delete("/{tool_id}", status_code=200)
|
||||
async def delete_tool(
|
||||
tool_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""删除工具"""
|
||||
tool = db.query(Tool).filter(Tool.id == tool_id).first()
|
||||
if not tool:
|
||||
raise HTTPException(status_code=404, detail="工具不存在")
|
||||
|
||||
# 检查权限(只有创建者可以删除)
|
||||
if tool.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="无权删除此工具")
|
||||
|
||||
# 内置工具不允许删除
|
||||
if tool.implementation_type == "builtin":
|
||||
raise HTTPException(status_code=400, detail="内置工具不允许删除")
|
||||
|
||||
db.delete(tool)
|
||||
db.commit()
|
||||
|
||||
return {"message": "工具已删除"}
|
||||
Reference in New Issue
Block a user