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
17 changes: 15 additions & 2 deletions backend/agents/create_agent_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
from database.agent_version_db import query_current_version_no
from database.tool_db import search_tools_for_sub_agent
from database.model_management_db import get_model_records, get_model_by_model_id
from database.knowledge_db import get_knowledge_name_map_by_index_names
from database.client import minio_client
from utils.model_name_utils import add_repo_to_name
from utils.prompt_template_utils import get_agent_prompt_template
from utils.config_utils import tenant_config_manager, get_model_name_from_config
from consts.const import LOCAL_MCP_SERVER, MODEL_CONFIG_MAPPING, LANGUAGE, DATA_PROCESS_SERVICE
import re

logger = logging.getLogger("create_agent_info")
logger.setLevel(logging.DEBUG)
Expand Down Expand Up @@ -262,11 +262,14 @@ async def create_agent_config(
if "KnowledgeBaseSearchTool" == tool.class_name:
index_names = tool.params.get("index_names")
if index_names:
# Batch query to get display names (knowledge_name) for all index_names
knowledge_name_map = get_knowledge_name_map_by_index_names(index_names)
for index_name in index_names:
try:
display_name = knowledge_name_map.get(index_name, index_name)
message = ElasticSearchService().get_summary(index_name=index_name)
Comment on lines +265 to 270
summary = message.get("summary", "")
knowledge_base_summary += f"**{index_name}**: {summary}\n\n"
knowledge_base_summary += f"**{display_name}**: {summary}\n\n"
except Exception as e:
logger.warning(
f"Failed to get summary for knowledge base {index_name}: {e}")
Expand Down Expand Up @@ -359,10 +362,20 @@ async def create_tool_config_list(agent_id, tenant_id, user_id, version_no: int
tenant_id=tenant_id, model_name=rerank_model_name
)

# Build display_name to index_name mapping for LLM parameter conversion
index_names = param_dict.get("index_names", [])
display_name_to_index_map = {}
if index_names:
knowledge_name_map = get_knowledge_name_map_by_index_names(index_names)
# Reverse the mapping: display_name (knowledge_name) -> index_name
for idx_name, kb_name in knowledge_name_map.items():
display_name_to_index_map[kb_name] = idx_name

tool_config.metadata = {
"vdb_core": get_vector_db_core(),
"embedding_model": get_embedding_model(tenant_id=tenant_id),
"rerank_model": rerank_model,
"display_name_to_index_map": display_name_to_index_map,
}
elif tool_config.class_name in ["DifySearchTool", "DataMateSearchTool"]:
rerank = param_dict.get("rerank", False)
Expand Down
39 changes: 39 additions & 0 deletions backend/database/knowledge_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,42 @@
)
except SQLAlchemyError as e:
raise e


def get_knowledge_name_map_by_index_names(index_names: List[str]) -> Dict[str, str]:
"""
Get a mapping from index_name to knowledge_name (display name) for the given index_names.
Used to build user-friendly knowledge base summaries in prompts.

Args:
index_names: List of internal index names

Returns:
Dict[str, str]: Mapping of index_name -> knowledge_name.
If a knowledge base is not found in the database,
the index_name itself is used as the fallback value.
"""
if not index_names:
return {}

try:
with get_db_session() as session:
result = session.query(
KnowledgeRecord.index_name,
KnowledgeRecord.knowledge_name
).filter(
KnowledgeRecord.index_name.in_(index_names),
KnowledgeRecord.delete_flag != 'Y'
).all()

knowledge_name_map = {}
for row in result:
knowledge_name_map[row.index_name] = row.knowledge_name

for index_name in index_names:
if index_name not in knowledge_name_map:
knowledge_name_map[index_name] = index_name

return knowledge_name_map
except SQLAlchemyError as e:
raise e

Check warning on line 415 in backend/database/knowledge_db.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add logic to this except clause or eliminate it and rethrow the exception automatically.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ2P-0coAG2S4uDQJvwj&open=AZ2P-0coAG2S4uDQJvwj&pullRequest=2781
10 changes: 10 additions & 0 deletions backend/services/tool_configuration_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
search_last_tool_instance_by_tool_id,
update_tool_table_from_scan_tool_list,
)
from database.knowledge_db import get_knowledge_name_map_by_index_names
from mcpadapt.smolagents_adapter import _sanitize_function_name
from services.file_management_service import get_llm_model
from services.vectordatabase_service import get_embedding_model, get_rerank_model, get_vector_db_core
Expand Down Expand Up @@ -714,11 +715,20 @@ def _validate_local_tool(
if rerank and rerank_model_name:
rerank_model = get_rerank_model(tenant_id=tenant_id, model_name=rerank_model_name)

# Build display_name to index_name mapping for LLM parameter conversion
index_names = instantiation_params.get("index_names", [])
display_name_to_index_map = {}
if index_names:
knowledge_name_map = get_knowledge_name_map_by_index_names(index_names)
for idx_name, kb_name in knowledge_name_map.items():
display_name_to_index_map[kb_name] = idx_name

params = {
**instantiation_params,
'vdb_core': vdb_core,
'embedding_model': embedding_model,
'rerank_model': rerank_model,
'display_name_to_index_map': display_name_to_index_map,
}
tool_instance = tool_class(**params)
elif tool_name in ["dify_search", "datamate_search"]:
Expand Down
4 changes: 3 additions & 1 deletion sdk/nexent/core/agents/nexent_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def create_local_tool(self, tool_config: ToolConfig):
# These parameters have exclude=True and cannot be passed to __init__
# due to smolagents.tools.Tool wrapper restrictions
filtered_params = {k: v for k, v in params.items()
if k not in ["vdb_core", "embedding_model", "observer", "rerank_model"]}
if k not in ["vdb_core", "embedding_model", "observer", "rerank_model", "display_name_to_index_map"]}
# Create instance with only non-excluded parameters
tools_obj = tool_class(**filtered_params)
# Set excluded parameters directly as attributes after instantiation
Expand All @@ -85,6 +85,8 @@ def create_local_tool(self, tool_config: ToolConfig):
"embedding_model", None) if tool_config.metadata else None
tools_obj.rerank_model = tool_config.metadata.get(
"rerank_model", None) if tool_config.metadata else None
tools_obj.display_name_to_index_map = tool_config.metadata.get(
"display_name_to_index_map", {}) if tool_config.metadata else {}
elif class_name in ["DifySearchTool", "DataMateSearchTool"]:
# These parameters have exclude=True and cannot be passed to __init__
filtered_params = {k: v for k, v in params.items()
Expand Down
39 changes: 39 additions & 0 deletions sdk/nexent/core/tools/knowledge_base_search_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,18 @@ def __init__(
description="The rerank model to use", default=None, exclude=True),
vdb_core: VectorDatabaseCore = Field(
description="Vector database client", default=None, exclude=True),
display_name_to_index_map: dict = Field(
description="Mapping from display_name (knowledge_name) to index_name",
default_factory=dict, exclude=True),
):
"""Initialize the KBSearchTool.

Args:
top_k (int, optional): Number of results to return. Defaults to 3.
observer (MessageObserver, optional): Message observer instance. Defaults to None.
display_name_to_index_map (dict, optional): Mapping from display_name to index_name.
When LLM passes display_name as index_names parameter, it will be converted
to the actual index_name for ES queries.

Raises:
ValueError: If language is not supported
Expand All @@ -106,16 +112,49 @@ def __init__(
self.rerank = rerank
self.rerank_model_name = rerank_model_name
self.rerank_model = rerank_model
self.display_name_to_index_map = display_name_to_index_map

self.record_ops = 1 # To record serial number
self.running_prompt_zh = "知识库检索中..."
self.running_prompt_en = "Searching the knowledge base..."


def _convert_to_index_names(self, names: List[str]) -> List[str]:
"""Convert display names (knowledge_name) to index names if necessary.

When LLM passes display_name as the index_names parameter,
this method converts it to the actual index_name for ES queries.

Args:
names: List of names that could be either display_name or index_name

Returns:
List of actual index_names for ES queries
"""
# Handle FieldInfo case (smolagents doesn't expand Field defaults)
display_map = self.display_name_to_index_map
if isinstance(display_map, FieldInfo):
display_map = display_map.default
if not display_map:
return names

converted_names = []
for name in names:
# If the name is in the map as a display_name, convert it to index_name
if name in display_map:
converted_names.append(display_map[name])
else:
# Otherwise, assume it's already an index_name
converted_names.append(name)
return converted_names

def forward(self, query: str, index_names: Optional[List[str]] = None) -> str:
# Parse index_names from string (always required)
search_index_names = index_names if index_names is not None else self.index_names

# Convert display names to index names if necessary
search_index_names = self._convert_to_index_names(search_index_names)

# Use the instance search_mode
search_mode = self.search_mode

Expand Down
Loading
Loading