feat: 重构工具配置为通用模型,增加使用统计
- ToolConfig 模型:支持多种工具类型(搜索、计算器、代码执行等) - ToolUsageLog 模型:记录工具调用日志 - 工具使用统计:调用次数、成功率、错误记录 - 后台管理界面:工具列表+统计展示 - API 重构:/api/v2/tools(替代 search-tools)
This commit is contained in:
203
main_v2.py
203
main_v2.py
@@ -18,11 +18,11 @@ import os
|
||||
from models_v2 import (
|
||||
init_db, get_db, SessionLocal,
|
||||
User, Conversation, Message, SystemConfig,
|
||||
LLMProvider, Agent, Channel, ChannelAgentMapping, MatrixRoomMapping, SearchToolConfig,
|
||||
LLMProvider, Agent, Channel, ChannelAgentMapping, MatrixRoomMapping, ToolConfig, ToolUsageLog,
|
||||
init_default_data
|
||||
)
|
||||
from services.llm_service import llm_service
|
||||
from services.agent_service import AgentService, LLMProviderService, ChannelService, SearchToolService
|
||||
from services.agent_service import AgentService, LLMProviderService, ChannelService, ToolService
|
||||
from services.conversation_service import ConversationService
|
||||
|
||||
# 配置日志
|
||||
@@ -419,123 +419,157 @@ async def unbind_agent(mapping_id: int, db: Session = Depends(get_db)):
|
||||
return {"success": success}
|
||||
|
||||
|
||||
# ==================== 搜索工具 API ====================
|
||||
# ==================== 工具配置 API ====================
|
||||
|
||||
@app.get("/api/v2/search-tools")
|
||||
async def get_search_tools(db: Session = Depends(get_db)):
|
||||
"""获取所有搜索工具配置"""
|
||||
service = SearchToolService(db)
|
||||
configs = service.get_all_configs()
|
||||
@app.get("/api/v2/tools")
|
||||
async def get_tools(tool_type: str = None, db: Session = Depends(get_db)):
|
||||
"""获取所有工具配置"""
|
||||
service = ToolService(db)
|
||||
if tool_type:
|
||||
tools = service.get_tools_by_type(tool_type)
|
||||
else:
|
||||
tools = service.get_all_tools()
|
||||
|
||||
return {
|
||||
"configs": [
|
||||
"tools": [
|
||||
{
|
||||
"id": c.id,
|
||||
"name": c.name,
|
||||
"provider": c.provider,
|
||||
"api_key": c.api_key,
|
||||
"api_base": c.api_base,
|
||||
"max_results": c.max_results,
|
||||
"include_raw_content": c.include_raw_content,
|
||||
"search_depth": c.search_depth,
|
||||
"is_active": c.is_active,
|
||||
"is_default": c.is_default
|
||||
"id": t.id,
|
||||
"name": t.name,
|
||||
"tool_type": t.tool_type,
|
||||
"provider": t.provider,
|
||||
"config": t.config,
|
||||
"is_active": t.is_active,
|
||||
"is_default": t.is_default,
|
||||
"total_calls": t.total_calls,
|
||||
"success_calls": t.success_calls,
|
||||
"failed_calls": t.failed_calls
|
||||
}
|
||||
for c in configs
|
||||
for t in tools
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/v2/search-tools")
|
||||
async def create_search_tool(data: dict, db: Session = Depends(get_db)):
|
||||
"""创建搜索工具配置"""
|
||||
service = SearchToolService(db)
|
||||
|
||||
# 如果设为默认,更新其他
|
||||
if data.get('is_default'):
|
||||
service.set_default_config(0) # 先清除所有默认
|
||||
|
||||
config = service.create_config(data)
|
||||
return {"success": True, "config": {"id": config.id, "name": config.name}}
|
||||
@app.post("/api/v2/tools")
|
||||
async def create_tool(data: dict, db: Session = Depends(get_db)):
|
||||
"""创建工具配置"""
|
||||
service = ToolService(db)
|
||||
tool = service.create_tool(data)
|
||||
return {"success": True, "tool": {"id": tool.id, "name": tool.name, "tool_type": tool.tool_type}}
|
||||
|
||||
|
||||
@app.put("/api/v2/search-tools/{config_id}")
|
||||
async def update_search_tool(config_id: int, data: dict, db: Session = Depends(get_db)):
|
||||
"""更新搜索工具配置"""
|
||||
service = SearchToolService(db)
|
||||
@app.put("/api/v2/tools/{tool_id}")
|
||||
async def update_tool(tool_id: int, data: dict, db: Session = Depends(get_db)):
|
||||
"""更新工具配置"""
|
||||
service = ToolService(db)
|
||||
|
||||
if data.get('is_default'):
|
||||
service.set_default_config(config_id)
|
||||
service.set_default_tool(tool_id)
|
||||
|
||||
config = service.update_config(config_id, data)
|
||||
tool = service.update_tool(tool_id, data)
|
||||
|
||||
if not config:
|
||||
return {"success": False, "message": "配置不存在"}
|
||||
if not tool:
|
||||
return {"success": False, "message": "工具不存在"}
|
||||
|
||||
return {"success": True, "config": {"id": config.id, "name": config.name}}
|
||||
return {"success": True, "tool": {"id": tool.id, "name": tool.name}}
|
||||
|
||||
|
||||
@app.delete("/api/v2/search-tools/{config_id}")
|
||||
async def delete_search_tool(config_id: int, db: Session = Depends(get_db)):
|
||||
"""删除搜索工具配置"""
|
||||
service = SearchToolService(db)
|
||||
success = service.delete_config(config_id)
|
||||
@app.delete("/api/v2/tools/{tool_id}")
|
||||
async def delete_tool(tool_id: int, db: Session = Depends(get_db)):
|
||||
"""删除工具配置"""
|
||||
service = ToolService(db)
|
||||
success = service.delete_tool(tool_id)
|
||||
|
||||
return {"success": success}
|
||||
|
||||
|
||||
@app.post("/api/v2/search-tools/{config_id}/default")
|
||||
async def set_search_tool_default(config_id: int, db: Session = Depends(get_db)):
|
||||
"""设置默认搜索工具"""
|
||||
service = SearchToolService(db)
|
||||
success = service.set_default_config(config_id)
|
||||
@app.post("/api/v2/tools/{tool_id}/default")
|
||||
async def set_tool_default(tool_id: int, db: Session = Depends(get_db)):
|
||||
"""设置默认工具"""
|
||||
service = ToolService(db)
|
||||
success = service.set_default_tool(tool_id)
|
||||
|
||||
return {"success": success}
|
||||
|
||||
|
||||
@app.post("/api/v2/search")
|
||||
@app.get("/api/v2/tools/stats")
|
||||
async def get_tool_stats(days: int = 7, db: Session = Depends(get_db)):
|
||||
"""获取工具使用统计"""
|
||||
service = ToolService(db)
|
||||
stats = service.get_usage_stats(days=days)
|
||||
return stats
|
||||
|
||||
|
||||
@app.post("/api/v2/tools/search")
|
||||
async def perform_search(data: dict, db: Session = Depends(get_db)):
|
||||
"""执行搜索(供前端或Agent调用)"""
|
||||
import httpx
|
||||
import time
|
||||
|
||||
query = data.get('query')
|
||||
if not query:
|
||||
return {"success": False, "message": "缺少搜索关键词"}
|
||||
|
||||
# 获取搜索工具配置
|
||||
service = SearchToolService(db)
|
||||
config_id = data.get('config_id')
|
||||
service = ToolService(db)
|
||||
tool_id = data.get('tool_id')
|
||||
|
||||
if config_id:
|
||||
config = service.get_config(config_id)
|
||||
if tool_id:
|
||||
tool = service.get_tool(tool_id)
|
||||
else:
|
||||
config = service.get_default_config()
|
||||
tool = service.get_default_tool('search')
|
||||
|
||||
if not config or not config.api_key:
|
||||
if not tool or not tool.config.get('api_key'):
|
||||
return {"success": False, "message": "未配置搜索工具"}
|
||||
|
||||
# Tavily Search API
|
||||
if config.provider == 'tavily':
|
||||
if tool.provider == 'tavily' or tool.tool_type == 'search':
|
||||
start_time = time.time()
|
||||
try:
|
||||
tavily_url = "https://api.tavily.com/search"
|
||||
config = tool.config
|
||||
payload = {
|
||||
"api_key": config.api_key,
|
||||
"api_key": config.get('api_key'),
|
||||
"query": query,
|
||||
"max_results": config.max_results,
|
||||
"include_raw_content": config.include_raw_content,
|
||||
"search_depth": config.search_depth
|
||||
"max_results": config.get('max_results', 5),
|
||||
"include_raw_content": config.get('include_raw_content', False),
|
||||
"search_depth": config.get('search_depth', 'basic')
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
response = await client.post(tavily_url, json=payload)
|
||||
result = response.json()
|
||||
|
||||
duration_ms = int((time.time() - start_time) * 1000)
|
||||
|
||||
# 更新统计和日志
|
||||
service.increment_stats(tool.id, True)
|
||||
service.log_usage({
|
||||
'tool_id': tool.id,
|
||||
'tool_type': 'search',
|
||||
'query': query,
|
||||
'success': True,
|
||||
'result_summary': f'{len(result.get("results", []))} results',
|
||||
'conversation_id': data.get('conversation_id'),
|
||||
'agent_id': data.get('agent_id'),
|
||||
'duration_ms': duration_ms
|
||||
})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"results": result.get("results", []),
|
||||
"query": query
|
||||
}
|
||||
except Exception as e:
|
||||
duration_ms = int((time.time() - start_time) * 1000)
|
||||
service.increment_stats(tool.id, False)
|
||||
service.log_usage({
|
||||
'tool_id': tool.id,
|
||||
'tool_type': 'search',
|
||||
'query': query,
|
||||
'success': False,
|
||||
'error_message': str(e),
|
||||
'conversation_id': data.get('conversation_id'),
|
||||
'duration_ms': duration_ms
|
||||
})
|
||||
return {"success": False, "message": str(e)}
|
||||
|
||||
return {"success": False, "message": "不支持的搜索提供商"}
|
||||
@@ -755,34 +789,63 @@ async def websocket_endpoint(websocket: WebSocket, user_id: str):
|
||||
|
||||
if should_search:
|
||||
# 执行搜索
|
||||
search_service = SearchToolService(db)
|
||||
search_config = search_service.get_default_config()
|
||||
tool_service = ToolService(db)
|
||||
search_tool = tool_service.get_default_tool('search')
|
||||
|
||||
if search_config and search_config.api_key:
|
||||
if search_tool and search_tool.config.get('api_key'):
|
||||
import httpx
|
||||
import time
|
||||
start_time = time.time()
|
||||
try:
|
||||
logger.info(f"执行搜索: query={message}")
|
||||
tavily_url = "https://api.tavily.com/search"
|
||||
config = search_tool.config
|
||||
payload = {
|
||||
"api_key": search_config.api_key,
|
||||
"api_key": config.get('api_key'),
|
||||
"query": message,
|
||||
"max_results": search_config.max_results,
|
||||
"search_depth": search_config.search_depth
|
||||
"max_results": config.get('max_results', 5),
|
||||
"search_depth": config.get('search_depth', 'basic')
|
||||
}
|
||||
|
||||
# 同步调用(简化处理)
|
||||
# 同步调用
|
||||
with httpx.Client(timeout=30) as client:
|
||||
resp = client.post(tavily_url, json=payload)
|
||||
search_result = resp.json()
|
||||
|
||||
duration_ms = int((time.time() - start_time) * 1000)
|
||||
|
||||
if search_result.get("results"):
|
||||
# 构建搜索上下文
|
||||
search_context = "\n\n【搜索结果】\n"
|
||||
for i, r in enumerate(search_result["results"][:5], 1):
|
||||
search_context += f"{i}. {r.get('title', 'N/A')}\n {r.get('content', r.get('snippet', 'N/A'))[:200]}\n 来源: {r.get('url', 'N/A')}\n"
|
||||
logger.info(f"搜索完成: {len(search_result['results'])} 条结果")
|
||||
|
||||
# 更新统计和日志
|
||||
tool_service.increment_stats(search_tool.id, True)
|
||||
tool_service.log_usage({
|
||||
'tool_id': search_tool.id,
|
||||
'tool_type': 'search',
|
||||
'query': message,
|
||||
'success': True,
|
||||
'result_summary': f'{len(search_result["results"])} results',
|
||||
'conversation_id': conversation_id,
|
||||
'agent_id': current_agent_id,
|
||||
'duration_ms': duration_ms
|
||||
})
|
||||
except Exception as e:
|
||||
duration_ms = int((time.time() - start_time) * 1000)
|
||||
logger.error(f"搜索失败: {e}")
|
||||
tool_service.increment_stats(search_tool.id, False)
|
||||
tool_service.log_usage({
|
||||
'tool_id': search_tool.id,
|
||||
'tool_type': 'search',
|
||||
'query': message,
|
||||
'success': False,
|
||||
'error_message': str(e),
|
||||
'conversation_id': conversation_id,
|
||||
'duration_ms': duration_ms
|
||||
})
|
||||
|
||||
# 获取或创建会话
|
||||
if conversation_id:
|
||||
|
||||
Reference in New Issue
Block a user