Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/consts/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class ToolSourceEnum(Enum):
class ToolInfo(BaseModel):
name: str
description: str
params: List
description_zh: Optional[str] = None
source: str
inputs: str
output_type: str
Expand Down
38 changes: 34 additions & 4 deletions backend/database/tool_db.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import re
import json
from typing import List

from database.agent_db import logger
from database.client import get_db_session, filter_property, as_dict
from database.db_models import ToolInstance, ToolInfo
Expand Down Expand Up @@ -225,21 +225,51 @@ def update_tool_table_from_scan_tool_list(tenant_id: str, user_id: str, tool_lis


def add_tool_field(tool_info):
from services.tool_configuration_service import get_local_tools_description_zh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import 建议放到文件最前方,集中管理。


with get_db_session() as session:
# Query if there is an existing ToolInstance
query = session.query(ToolInfo).filter(
ToolInfo.tool_id == tool_info["tool_id"])
tool = query.first()

# add tool params
tool_params = tool.params
for ele in tool_params:
param_name = ele["name"]
ele["default"] = tool_info["params"].get(param_name)

tool_dict = as_dict(tool)
tool_dict["params"] = tool_params


# Merge description_zh from SDK for local tools
tool_name = tool_dict.get("name")
if tool_dict.get("source") == "local":
local_tool_descriptions = get_local_tools_description_zh()
if tool_name in local_tool_descriptions:
sdk_info = local_tool_descriptions[tool_name]
tool_dict["description_zh"] = sdk_info.get("description_zh")

# Merge params description_zh from SDK
for param in tool_params:
if not param.get("description_zh"):
for sdk_param in sdk_info.get("params", []):
if sdk_param.get("name") == param.get("name"):
param["description_zh"] = sdk_param.get("description_zh")
break

# Merge inputs description_zh from SDK
inputs_str = tool_dict.get("inputs", "{}")
try:
inputs = json.loads(inputs_str) if isinstance(inputs_str, str) else inputs_str
if isinstance(inputs, dict):
for key, value in inputs.items():
if isinstance(value, dict) and not value.get("description_zh"):
sdk_inputs = sdk_info.get("inputs", {})
if key in sdk_inputs:
value["description_zh"] = sdk_inputs[key].get("description_zh")
tool_dict["inputs"] = json.dumps(inputs, ensure_ascii=False)
except (json.JSONDecodeError, TypeError):
pass

# combine tool_info and tool_dict
tool_info.update(tool_dict)
return tool_info
Expand Down
128 changes: 122 additions & 6 deletions backend/services/tool_configuration_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,15 @@ def get_local_tools() -> List[ToolInfo]:
if param_name == "self" or param.default.exclude:
continue

# Get description in both languages
param_description = param.default.description if hasattr(param.default, 'description') else ""
param_description_zh = param.default.description_zh if hasattr(param.default, 'description_zh') else None

param_info = {
"type": python_type_to_json_schema(param.annotation),
"name": param_name,
"description": param.default.description
"description": param_description,
"description_zh": param_description_zh
}
if param.default.default is PydanticUndefined:
param_info["optional"] = False
Expand All @@ -95,14 +100,29 @@ def get_local_tools() -> List[ToolInfo]:

init_params_list.append(param_info)

# get tool fixed attributes
# Get tool fixed attributes with bilingual support
tool_description_zh = getattr(tool_class, 'description_zh', None)
tool_inputs = getattr(tool_class, 'inputs', {})

# Process inputs to add bilingual descriptions
processed_inputs = {}
if isinstance(tool_inputs, dict):
for key, value in tool_inputs.items():
if isinstance(value, dict):
processed_inputs[key] = {
**value,
"description_zh": value.get("description_zh")
}
else:
processed_inputs[key] = value

tool_info = ToolInfo(
name=getattr(tool_class, 'name'),
description=getattr(tool_class, 'description'),
description_zh=tool_description_zh,
params=init_params_list,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image 你这里把params去掉了,但是这里还是赋值了,需要看一下params参数是否能够去掉,如果可以,这里就不要赋值了。

source=ToolSourceEnum.LOCAL.value,
inputs=json.dumps(getattr(tool_class, 'inputs'),
ensure_ascii=False),
inputs=json.dumps(processed_inputs, ensure_ascii=False),
output_type=getattr(tool_class, 'output_type'),
category=getattr(tool_class, 'category'),
class_name=tool_class.__name__,
Expand All @@ -113,6 +133,61 @@ def get_local_tools() -> List[ToolInfo]:
return tools_info


def get_local_tools_description_zh() -> Dict[str, Dict]:
"""
Get description_zh for all local tools from SDK (not persisted to DB).

Returns:
Dict mapping tool name to {"description_zh": ..., "params": [...], "inputs": {...}}
"""
tools_classes = get_local_tools_classes()
result = {}
for tool_class in tools_classes:
tool_name = getattr(tool_class, 'name')

# Get tool-level description_zh
description_zh = getattr(tool_class, 'description_zh', None)

# Get class-level init_param_descriptions for fallback
init_param_descriptions = getattr(tool_class, 'init_param_descriptions', {})

# Get param-level description_zh
init_params_list = []
sig = inspect.signature(tool_class.__init__)
for param_name, param in sig.parameters.items():
if param_name == "self" or param.default.exclude:
continue

# First try to get from param.default.description_zh (FieldInfo)
param_description_zh = param.default.description_zh if hasattr(param.default, 'description_zh') else None

# Fallback to init_param_descriptions if not found
if param_description_zh is None and param_name in init_param_descriptions:
param_description_zh = init_param_descriptions[param_name].get('description_zh')

init_params_list.append({
"name": param_name,
"description_zh": param_description_zh
})

# Get inputs description_zh
tool_inputs = getattr(tool_class, 'inputs', {})
inputs_description_zh = {}
if isinstance(tool_inputs, dict):
for key, value in tool_inputs.items():
if isinstance(value, dict) and value.get("description_zh"):
inputs_description_zh[key] = {
"description_zh": value.get("description_zh")
}

result[tool_name] = {
"description_zh": description_zh,
"params": init_params_list,
"inputs": inputs_description_zh
}
return result


def get_local_tools_classes() -> List[type]:
"""
Get all tool classes from the nexent.core.tools package
Expand Down Expand Up @@ -371,20 +446,61 @@ async def list_all_tools(tenant_id: str):
List all tools for a given tenant
"""
tools_info = query_all_tools(tenant_id)

# Get description_zh from SDK for local tools (not persisted to DB)
local_tool_descriptions = get_local_tools_description_zh()

# only return the fields needed
formatted_tools = []
for tool in tools_info:
tool_name = tool.get("name")

# Merge description_zh from SDK for local tools
if tool.get("source") == "local" and tool_name in local_tool_descriptions:
sdk_info = local_tool_descriptions[tool_name]
description_zh = sdk_info.get("description_zh")

# Merge params description_zh from SDK (independent of tool-level description_zh)
params = tool.get("params", [])
if params:
for param in params:
if not param.get("description_zh"):
# Find matching param in SDK
for sdk_param in sdk_info.get("params", []):
if sdk_param.get("name") == param.get("name"):
param["description_zh"] = sdk_param.get("description_zh")
break

# Merge inputs description_zh from SDK
inputs_str = tool.get("inputs", "{}")
try:
inputs = json.loads(inputs_str) if isinstance(inputs_str, str) else inputs_str
if isinstance(inputs, dict):
for key, value in inputs.items():
if isinstance(value, dict) and not value.get("description_zh"):
# Find matching input in SDK
sdk_inputs = sdk_info.get("inputs", {})
if key in sdk_inputs:
value["description_zh"] = sdk_inputs[key].get("description_zh")
inputs_str = json.dumps(inputs, ensure_ascii=False)
except (json.JSONDecodeError, TypeError):
pass
else:
description_zh = tool.get("description_zh")
inputs_str = tool.get("inputs", "{}")

formatted_tool = {
"tool_id": tool.get("tool_id"),
"name": tool.get("name"),
"name": tool_name,
"origin_name": tool.get("origin_name"),
"description": tool.get("description"),
"description_zh": description_zh,
"source": tool.get("source"),
"is_available": tool.get("is_available"),
"create_time": tool.get("create_time"),
"usage": tool.get("usage"),
"params": tool.get("params", []),
"inputs": tool.get("inputs", {}),
"inputs": inputs_str,
"category": tool.get("category")
}
formatted_tools.append(formatted_tool)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
} from "@/hooks/useKnowledgeBaseSelector";
import { API_ENDPOINTS } from "@/services/api";
import log from "@/lib/logger";
import { isZhLocale, getLocalizedDescription } from "@/lib/utils";

export interface ToolConfigModalProps {
isOpen: boolean;
Expand Down Expand Up @@ -795,7 +796,7 @@ export default function ToolConfigModal({
const placeholder = t(
"toolConfig.input.knowledgeBaseSelector.placeholder",
{
name: param.description || param.name,
name: getLocalizedDescription(param.description, param.description_zh) || param.name,
}
);

Expand Down Expand Up @@ -893,7 +894,7 @@ export default function ToolConfigModal({
return (
<Select
placeholder={t("toolConfig.input.string.placeholder", {
name: param.description,
name: getLocalizedDescription(param.description, param.description_zh),
})}
options={options.map((option) => ({
value: option,
Expand All @@ -908,7 +909,7 @@ export default function ToolConfigModal({
return (
<InputNumber
placeholder={t("toolConfig.input.string.placeholder", {
name: param.description,
name: getLocalizedDescription(param.description, param.description_zh),
})}
/>
);
Expand All @@ -927,7 +928,7 @@ export default function ToolConfigModal({
return (
<Input.Password
placeholder={t("toolConfig.input.string.placeholder", {
name: param.description,
name: getLocalizedDescription(param.description, param.description_zh),
})}
/>
);
Expand All @@ -937,7 +938,7 @@ export default function ToolConfigModal({
return (
<Input.TextArea
placeholder={t(`toolConfig.input.${param.type}.placeholder`, {
name: param.description,
name: getLocalizedDescription(param.description, param.description_zh),
})}
autoSize={{ minRows: 1, maxRows: 8 }}
style={{ resize: "vertical" }}
Expand Down Expand Up @@ -1021,7 +1022,9 @@ export default function ToolConfigModal({
}
>
<div className="mb-4">
<p className="text-sm text-gray-500 mb-4">{tool?.description}</p>
<p className="text-sm text-gray-500 mb-4">
{getLocalizedDescription(tool?.description, tool?.description_zh)}
</p>
<div className="text-sm font-medium mb-2">
{t("toolConfig.title.paramConfig")}
</div>
Expand Down Expand Up @@ -1189,7 +1192,7 @@ export default function ToolConfigModal({
}
rules={rules}
tooltip={{
title: param.description,
title: getLocalizedDescription(param.description, param.description_zh),
placement: "topLeft",
styles: { root: { maxWidth: 400 } },
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@/services/agentConfigService";
import log from "@/lib/logger";
import { DEFAULT_TYPE } from "@/const/constants";
import { getLocalizedDescription } from "@/lib/utils";

const { Text, Title } = Typography;

Expand Down Expand Up @@ -204,9 +205,11 @@ export default function ToolTestPanel({
);

// Call validateTool with parameters
const toolName = tool.origin_name || tool.name || "";
const toolSource = tool.source || "";
const result = await validateTool(
tool.origin_name || tool.name,
tool.source, // Tool source
toolName,
toolSource, // Tool source
tool.usage || "", // Tool usage
toolParams, // tool input parameters
configs // tool configuration parameters
Expand Down Expand Up @@ -385,6 +388,12 @@ export default function ToolTestPanel({
paramInfo.description
? paramInfo.description
: paramName;
const description_zh =
paramInfo &&
typeof paramInfo === "object" &&
paramInfo.description_zh
? paramInfo.description_zh
: undefined;

const fieldName = `param_${paramName}`;
const rules: any[] = [];
Expand Down Expand Up @@ -455,13 +464,13 @@ export default function ToolTestPanel({
name={fieldName}
rules={rules}
tooltip={{
title: description,
title: getLocalizedDescription(description, description_zh),
placement: "topLeft",
styles: { root: { maxWidth: 400 } },
}}
>
<Input
placeholder={description}
placeholder={getLocalizedDescription(description, description_zh)}
/>
</Form.Item>
);
Expand Down
Loading
Loading