Skip to content

Commit ce4ef02

Browse files
authored
♻️ refactor tool app/service/db/test case
2 parents cbb992c + 37e848d commit ce4ef02

File tree

7 files changed

+895
-510
lines changed

7 files changed

+895
-510
lines changed

backend/apps/tool_config_app.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import logging
2+
from http import HTTPStatus
23
from typing import Optional
34

45
from fastapi import APIRouter, Header, HTTPException
56
from fastapi.responses import JSONResponse
67

8+
from consts.exceptions import MCPConnectionError
79
from consts.model import ToolInstanceInfoRequest, ToolInstanceSearchRequest
8-
from database.tool_db import query_all_tools
910
from services.tool_configuration_service import (
1011
search_tool_info_impl,
1112
update_tool_info_impl,
12-
update_tool_list,
13+
update_tool_list, list_all_tools,
1314
)
1415
from utils.auth_utils import get_current_user_id
1516

@@ -25,21 +26,22 @@ async def list_tools_api(authorization: Optional[str] = Header(None)):
2526
try:
2627
_, tenant_id = get_current_user_id(authorization)
2728
# now only admin can modify the tool, user_id is not used
28-
return query_all_tools(tenant_id=tenant_id)
29+
return await list_all_tools(tenant_id=tenant_id)
2930
except Exception as e:
3031
logging.error(f"Failed to get tool info, error in: {str(e)}")
3132
raise HTTPException(
32-
status_code=500, detail=f"Failed to get tool info, error in: {str(e)}")
33+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=f"Failed to get tool info, error in: {str(e)}")
3334

3435

3536
@router.post("/search")
3637
async def search_tool_info_api(request: ToolInstanceSearchRequest, authorization: Optional[str] = Header(None)):
3738
try:
38-
return search_tool_info_impl(request.agent_id, request.tool_id, authorization)
39+
_, tenant_id = get_current_user_id(authorization)
40+
return search_tool_info_impl(request.agent_id, request.tool_id, tenant_id)
3941
except Exception as e:
40-
logging.error(f"Failed to update tool, error in: {str(e)}")
42+
logging.error(f"Failed to search tool, error in: {str(e)}")
4143
raise HTTPException(
42-
status_code=500, detail=f"Failed to update tool, error in: {str(e)}")
44+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Failed to search tool info")
4345

4446

4547
@router.post("/update")
@@ -48,11 +50,12 @@ async def update_tool_info_api(request: ToolInstanceInfoRequest, authorization:
4850
Update an existing tool, create or update tool instance
4951
"""
5052
try:
51-
return update_tool_info_impl(request, authorization)
53+
user_id, tenant_id = get_current_user_id(authorization)
54+
return update_tool_info_impl(request, tenant_id, user_id)
5255
except Exception as e:
5356
logging.error(f"Failed to update tool, error in: {str(e)}")
5457
raise HTTPException(
55-
status_code=500, detail=f"Failed to update tool, error in: {str(e)}")
58+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=f"Failed to update tool, error in: {str(e)}")
5659

5760

5861
@router.get("/scan_tool")
@@ -64,12 +67,14 @@ async def scan_and_update_tool(
6467
user_id, tenant_id = get_current_user_id(authorization)
6568
await update_tool_list(tenant_id=tenant_id, user_id=user_id)
6669
return JSONResponse(
67-
status_code=200,
70+
status_code=HTTPStatus.OK,
6871
content={"message": "Successfully update tool", "status": "success"}
6972
)
73+
except MCPConnectionError as e:
74+
logger.error(f"MCP connection failed: {e}")
75+
raise HTTPException(
76+
status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail="MCP connection failed")
7077
except Exception as e:
7178
logger.error(f"Failed to update tool: {e}")
72-
return JSONResponse(
73-
status_code=400,
74-
content={"message": "Failed to update tool", "status": "error"}
75-
)
79+
raise HTTPException(
80+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Failed to update tool")

backend/database/tool_db.py

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ def create_or_update_tool_by_tool_info(tool_info, tenant_id: str, user_id: str):
2929
:param user_id: Optional user ID for filtering
3030
:return: Created or updated ToolInstance object
3131
"""
32-
3332
tool_info_dict = tool_info.__dict__ | {
3433
"tenant_id": tenant_id, "user_id": user_id}
3534

@@ -42,18 +41,14 @@ def create_or_update_tool_by_tool_info(tool_info, tenant_id: str, user_id: str):
4241
ToolInstance.delete_flag != 'Y',
4342
ToolInstance.tool_id == tool_info_dict['tool_id']
4443
)
45-
4644
tool_instance = query.first()
47-
4845
if tool_instance:
4946
# Update the existing ToolInstance
5047
for key, value in tool_info_dict.items():
5148
if hasattr(tool_instance, key):
5249
setattr(tool_instance, key, value)
5350
else:
5451
create_tool(tool_info_dict)
55-
56-
session.flush()
5752
return tool_instance
5853

5954

@@ -126,41 +121,37 @@ def update_tool_table_from_scan_tool_list(tenant_id: str, user_id: str, tool_lis
126121
"""
127122
scan all tools and update the tool table in PG database, remove the duplicate tools
128123
"""
129-
try:
130-
with get_db_session() as session:
131-
# get all existing tools (including complete information)
132-
existing_tools = session.query(ToolInfo).filter(ToolInfo.delete_flag != 'Y',
133-
ToolInfo.author == tenant_id).all()
134-
existing_tool_dict = {
135-
f"{tool.name}&{tool.source}": tool for tool in existing_tools}
136-
# set all tools to unavailable
137-
for tool in existing_tools:
138-
tool.is_available = False
139-
140-
for tool in tool_list:
141-
filtered_tool_data = filter_property(tool.__dict__, ToolInfo)
142-
143-
# check if the tool name is valid
144-
is_available = True if re.match(
145-
r'^[a-zA-Z_][a-zA-Z0-9_]*$', tool.name) is not None else False
146-
147-
if f"{tool.name}&{tool.source}" in existing_tool_dict:
148-
# by tool name and source to update the existing tool
149-
existing_tool = existing_tool_dict[f"{tool.name}&{tool.source}"]
150-
for key, value in filtered_tool_data.items():
151-
setattr(existing_tool, key, value)
152-
existing_tool.updated_by = user_id
153-
existing_tool.is_available = is_available
154-
else:
155-
# create new tool
156-
filtered_tool_data.update(
157-
{"created_by": user_id, "updated_by": user_id, "author": tenant_id, "is_available": is_available})
158-
new_tool = ToolInfo(**filtered_tool_data)
159-
session.add(new_tool)
160-
session.flush()
161-
logger.info("Updated tool table in PG database")
162-
except Exception as e:
163-
logger.error(f"Updated tool table failed due to {e}")
124+
with get_db_session() as session:
125+
# get all existing tools (including complete information)
126+
existing_tools = session.query(ToolInfo).filter(ToolInfo.delete_flag != 'Y',
127+
ToolInfo.author == tenant_id).all()
128+
existing_tool_dict = {
129+
f"{tool.name}&{tool.source}": tool for tool in existing_tools}
130+
# set all tools to unavailable
131+
for tool in existing_tools:
132+
tool.is_available = False
133+
134+
for tool in tool_list:
135+
filtered_tool_data = filter_property(tool.__dict__, ToolInfo)
136+
137+
# check if the tool name is valid
138+
is_available = True if re.match(
139+
r'^[a-zA-Z_][a-zA-Z0-9_]*$', tool.name) is not None else False
140+
141+
if f"{tool.name}&{tool.source}" in existing_tool_dict:
142+
# by tool name and source to update the existing tool
143+
existing_tool = existing_tool_dict[f"{tool.name}&{tool.source}"]
144+
for key, value in filtered_tool_data.items():
145+
setattr(existing_tool, key, value)
146+
existing_tool.updated_by = user_id
147+
existing_tool.is_available = is_available
148+
else:
149+
# create new tool
150+
filtered_tool_data.update(
151+
{"created_by": user_id, "updated_by": user_id, "author": tenant_id, "is_available": is_available})
152+
new_tool = ToolInfo(**filtered_tool_data)
153+
session.add(new_tool)
154+
logger.info("Updated tool table in PG database")
164155

165156

166157
def add_tool_field(tool_info):
@@ -178,14 +169,14 @@ def add_tool_field(tool_info):
178169
tool_dict = as_dict(tool)
179170
tool_dict["params"] = tool_params
180171

181-
# 合并tool_info和tool_dict
172+
# combine tool_info and tool_dict
182173
tool_info.update(tool_dict)
183174
return tool_info
184175

185176

186177
def search_tools_for_sub_agent(agent_id, tenant_id):
187178
with get_db_session() as session:
188-
# Query if there is an existing ToolInstance
179+
# query if there is an existing ToolInstance
189180
query = session.query(ToolInstance).filter(
190181
ToolInstance.agent_id == agent_id,
191182
ToolInstance.tenant_id == tenant_id,
@@ -221,4 +212,3 @@ def delete_tools_by_agent_id(agent_id, tenant_id, user_id):
221212
).update({
222213
ToolInstance.delete_flag: 'Y', 'updated_by': user_id
223214
})
224-
session.commit()

backend/services/tool_configuration_service.py

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@
1111
from mcpadapt.smolagents_adapter import _sanitize_function_name
1212

1313
from database.tool_db import create_or_update_tool_by_tool_info, query_tool_instances_by_id, \
14-
update_tool_table_from_scan_tool_list
14+
update_tool_table_from_scan_tool_list, query_all_tools
1515
from consts.model import ToolInstanceInfoRequest, ToolInfo, ToolSourceEnum
1616
from consts.const import LOCAL_MCP_SERVER
1717
from database.remote_mcp_db import get_mcp_records_by_tenant
18-
from utils.auth_utils import get_current_user_id
19-
from fastapi import Header
2018

2119
from consts.exceptions import MCPConnectionError
2220

@@ -207,31 +205,23 @@ async def get_all_mcp_tools(tenant_id: str) -> List[ToolInfo]:
207205
return tools_info
208206

209207

210-
def search_tool_info_impl(agent_id: int, tool_id: int, authorization: str = Header(None)):
208+
def search_tool_info_impl(agent_id: int, tool_id: int, tenant_id: str):
211209
"""
212210
Search for tool configuration information by agent ID and tool ID
213211
214212
Args:
215213
agent_id: Agent ID
216214
tool_id: Tool ID
217-
authorization:
215+
tenant_id: Tenant ID
218216
219217
Returns:
220218
Dictionary containing tool parameters and enabled status
221219
222220
Raises:
223221
ValueError: If database query fails
224222
"""
225-
_, tenant_id = get_current_user_id(authorization)
226-
try:
227-
# now only admin can modify the tool, user_id is not used
228-
tool_instance = query_tool_instances_by_id(
229-
agent_id, tool_id, tenant_id)
230-
except Exception as e:
231-
logger.error(
232-
f"search_tool_info_impl error in query_tool_instances_by_id, detail: {e}")
233-
raise ValueError(
234-
f"search_tool_info_impl error in query_tool_instances_by_id, detail: {e}")
223+
tool_instance = query_tool_instances_by_id(
224+
agent_id, tool_id, tenant_id)
235225

236226
if tool_instance:
237227
return {
@@ -245,29 +235,21 @@ def search_tool_info_impl(agent_id: int, tool_id: int, authorization: str = Head
245235
}
246236

247237

248-
def update_tool_info_impl(request: ToolInstanceInfoRequest, authorization: str = Header(None)):
238+
def update_tool_info_impl(tool_info: ToolInstanceInfoRequest, tenant_id: str, user_id: str):
249239
"""
250240
Update tool configuration information
251241
252242
Args:
253-
request: ToolInstanceInfoRequest containing tool configuration data
243+
tool_info: ToolInstanceInfoRequest containing tool configuration data
254244
255245
Returns:
256246
Dictionary containing the updated tool instance
257247
258248
Raises:
259249
ValueError: If database update fails
260250
"""
261-
user_id, tenant_id = get_current_user_id(authorization)
262-
try:
263-
tool_instance = create_or_update_tool_by_tool_info(
264-
request, tenant_id, user_id)
265-
except Exception as e:
266-
logger.error(
267-
f"update_tool_info_impl error in create_or_update_tool, detail: {e}")
268-
raise ValueError(
269-
f"update_tool_info_impl error in create_or_update_tool, detail: {e}")
270-
251+
tool_instance = create_or_update_tool_by_tool_info(
252+
tool_info, tenant_id, user_id)
271253
return {
272254
"tool_instance": tool_instance
273255
}
@@ -313,7 +295,6 @@ async def get_tool_from_remote_mcp_server(mcp_server_name: str, remote_mcp_serve
313295
f"failed to get tool from remote MCP server, detail: {e}")
314296

315297

316-
317298
async def update_tool_list(tenant_id: str, user_id: str):
318299
"""
319300
Scan and gather all available tools from both local and MCP sources
@@ -326,21 +307,38 @@ async def update_tool_list(tenant_id: str, user_id: str):
326307
List of ToolInfo objects containing tool metadata
327308
"""
328309
local_tools = get_local_tools()
329-
330310
# Discover LangChain tools (decorated functions) and include them in the
331-
# unified tool list.
332311
langchain_tools = get_langchain_tools()
333312

334313
try:
335314
mcp_tools = await get_all_mcp_tools(tenant_id)
336315
except Exception as e:
337316
logger.error(f"failed to get all mcp tools, detail: {e}")
338-
raise Exception(f"failed to get all mcp tools, detail: {e}")
317+
raise MCPConnectionError(f"failed to get all mcp tools, detail: {e}")
339318

340-
try:
341-
update_tool_table_from_scan_tool_list(tenant_id=tenant_id,
342-
user_id=user_id,
343-
tool_list=local_tools+mcp_tools+langchain_tools)
344-
except Exception as e:
345-
logger.error(f"failed to update tool list to PG, detail: {e}")
346-
raise Exception(f"failed to update tool list to PG, detail: {e}")
319+
update_tool_table_from_scan_tool_list(tenant_id=tenant_id,
320+
user_id=user_id,
321+
tool_list=local_tools+mcp_tools+langchain_tools)
322+
323+
324+
async def list_all_tools(tenant_id: str):
325+
"""
326+
List all tools for a given tenant
327+
"""
328+
tools_info = query_all_tools(tenant_id)
329+
# only return the fields needed
330+
formatted_tools = []
331+
for tool in tools_info:
332+
formatted_tool = {
333+
"tool_id": tool.get("tool_id"),
334+
"name": tool.get("name"),
335+
"description": tool.get("description"),
336+
"source": tool.get("source"),
337+
"is_available": tool.get("is_available"),
338+
"create_time": tool.get("create_time"),
339+
"usage": tool.get("usage"),
340+
"params": tool.get("params", [])
341+
}
342+
formatted_tools.append(formatted_tool)
343+
344+
return formatted_tools

frontend/services/mcpService.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,8 @@ export const updateToolList = async () => {
266266
let errorMessage = data.message || t('mcpService.message.updateToolListFailed');
267267

268268
switch (response.status) {
269-
case 400:
270-
errorMessage = t('mcpService.message.updateToolListBadRequest');
271-
break;
272-
case 404:
273-
errorMessage = t('mcpService.message.resourceNotFound');
274-
break;
275269
case 500:
276-
errorMessage = t('mcpService.message.serverInternalError');
270+
errorMessage = t('mcpService.message.updateToolListBadRequest');
277271
break;
278272
case 503:
279273
errorMessage = t('mcpService.message.serviceUnavailable');

0 commit comments

Comments
 (0)