Skip to content

Commit b760b4a

Browse files
authored
Merge branch 'develop' into patch-2
2 parents 1c11673 + 9f855fc commit b760b4a

File tree

37 files changed

+2016
-282
lines changed

37 files changed

+2016
-282
lines changed

backend/apps/prompt_app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ async def generate_and_save_system_prompt_api(
2727
task_description=prompt_request.task_description,
2828
user_id=user_id,
2929
tenant_id=tenant_id,
30-
language=language
30+
language=language,
31+
tool_ids=prompt_request.tool_ids,
32+
sub_agent_ids=prompt_request.sub_agent_ids
3133
), media_type="text/event-stream")
3234
except Exception as e:
3335
logger.exception(f"Error occurred while generating system prompt: {e}")

backend/apps/vectordatabase_app.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Any, Dict, List, Optional
44

55
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Path, Query
6+
from fastapi.responses import JSONResponse
67

78
from consts.model import IndexingResponse
89
from nexent.vector_database.base import VectorDatabaseCore
@@ -195,3 +196,33 @@ def health_check(vdb_core: VectorDatabaseCore = Depends(get_vector_db_core)):
195196
return ElasticSearchService.health_check(vdb_core)
196197
except Exception as e:
197198
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=f"{str(e)}")
199+
200+
201+
@router.post("/{index_name}/chunks")
202+
def get_index_chunks(
203+
index_name: str = Path(...,
204+
description="Name of the index to get chunks from"),
205+
page: int = Query(
206+
None, description="Page number (1-based) for pagination"),
207+
page_size: int = Query(
208+
None, description="Number of records per page for pagination"),
209+
path_or_url: Optional[str] = Query(
210+
None, description="Filter chunks by document path_or_url"),
211+
vdb_core: VectorDatabaseCore = Depends(get_vector_db_core)
212+
):
213+
"""Get chunks from the specified index, with optional pagination support"""
214+
try:
215+
result = ElasticSearchService.get_index_chunks(
216+
index_name=index_name,
217+
page=page,
218+
page_size=page_size,
219+
path_or_url=path_or_url,
220+
vdb_core=vdb_core,
221+
)
222+
return JSONResponse(status_code=HTTPStatus.OK, content=result)
223+
except Exception as e:
224+
error_msg = str(e)
225+
logger.error(
226+
f"Error getting chunks for index '{index_name}': {error_msg}")
227+
raise HTTPException(
228+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=f"Error getting chunks: {error_msg}")

backend/consts/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ class VectorDatabaseType(str, Enum):
279279
os.getenv("LLM_SLOW_TOKEN_RATE_THRESHOLD", "10.0")) # tokens per second
280280

281281
# APP Version
282-
APP_VERSION = "v1.7.5.3"
282+
APP_VERSION = "v1.7.6"
283283

284284
DEFAULT_ZH_TITLE = "新对话"
285285
DEFAULT_EN_TITLE = "New Conversation"

backend/consts/model.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ class GeneratePromptRequest(BaseModel):
193193
task_description: str
194194
agent_id: int
195195
model_id: int
196+
tool_ids: Optional[List[int]] = None # Optional: tool IDs from frontend (takes precedence over database query)
197+
sub_agent_ids: Optional[List[int]] = None # Optional: sub-agent IDs from frontend (takes precedence over database query)
196198

197199

198200
class GenerateTitleRequest(BaseModel):

backend/services/prompt_service.py

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
import logging
33
import queue
44
import threading
5+
from typing import Optional, List
56

67
from jinja2 import StrictUndefined, Template
78
from smolagents import OpenAIServerModel
89

9-
from consts.const import LANGUAGE, MODEL_CONFIG_MAPPING, MESSAGE_ROLE, THINK_END_PATTERN, THINK_START_PATTERN
10+
from consts.const import LANGUAGE, MESSAGE_ROLE, THINK_END_PATTERN, THINK_START_PATTERN
1011
from consts.model import AgentInfoRequest
11-
from database.agent_db import update_agent, query_sub_agents_id_list, search_agent_info_by_agent_id
12+
from database.agent_db import update_agent, search_agent_info_by_agent_id
1213
from database.model_management_db import get_model_by_model_id
1314
from database.tool_db import query_tools_by_ids
14-
from services.agent_service import get_enable_tool_id_by_agent_id
15-
from utils.config_utils import tenant_config_manager, get_model_name_from_config
15+
from utils.config_utils import get_model_name_from_config
1616
from utils.prompt_template_utils import get_prompt_generate_prompt_template
1717

1818
# Configure logging
@@ -34,7 +34,7 @@ def _process_thinking_tokens(new_token: str, is_thinking: bool, token_join: list
3434
"""
3535
# Handle thinking mode
3636
if is_thinking:
37-
return not (THINK_END_PATTERN in new_token)
37+
return THINK_END_PATTERN not in new_token
3838

3939
# Handle start of thinking
4040
if THINK_START_PATTERN in new_token:
@@ -98,14 +98,16 @@ def call_llm_for_system_prompt(model_id: int, user_prompt: str, system_prompt: s
9898
raise e
9999

100100

101-
def gen_system_prompt_streamable(agent_id: int, model_id: int, task_description: str, user_id: str, tenant_id: str, language: str):
101+
def gen_system_prompt_streamable(agent_id: int, model_id: int, task_description: str, user_id: str, tenant_id: str, language: str, tool_ids: Optional[List[int]] = None, sub_agent_ids: Optional[List[int]] = None):
102102
for system_prompt in generate_and_save_system_prompt_impl(
103103
agent_id=agent_id,
104104
model_id=model_id,
105105
task_description=task_description,
106106
user_id=user_id,
107107
tenant_id=tenant_id,
108-
language=language
108+
language=language,
109+
tool_ids=tool_ids,
110+
sub_agent_ids=sub_agent_ids
109111
):
110112
# SSE format, each message ends with \n\n
111113
yield f"data: {json.dumps({'success': True, 'data': system_prompt}, ensure_ascii=False)}\n\n"
@@ -116,17 +118,35 @@ def generate_and_save_system_prompt_impl(agent_id: int,
116118
task_description: str,
117119
user_id: str,
118120
tenant_id: str,
119-
language: str):
120-
# Get description of tool and agent
121-
# In create mode (agent_id=0), return empty lists
122-
if agent_id == 0:
121+
language: str,
122+
tool_ids: Optional[List[int]] = None,
123+
sub_agent_ids: Optional[List[int]] = None):
124+
# Get description of tool and agent from frontend-provided IDs
125+
# Frontend always provides tool_ids and sub_agent_ids (could be empty arrays)
126+
127+
# Handle tool IDs
128+
if tool_ids and len(tool_ids) > 0:
129+
tool_info_list = query_tools_by_ids(tool_ids)
130+
logger.debug(f"Using frontend-provided tool IDs: {tool_ids}")
131+
else:
123132
tool_info_list = []
133+
logger.debug("No tools selected (empty tool_ids list)")
134+
135+
# Handle sub-agent IDs
136+
if sub_agent_ids and len(sub_agent_ids) > 0:
124137
sub_agent_info_list = []
138+
for sub_agent_id in sub_agent_ids:
139+
try:
140+
sub_agent_info = search_agent_info_by_agent_id(
141+
agent_id=sub_agent_id, tenant_id=tenant_id)
142+
sub_agent_info_list.append(sub_agent_info)
143+
except Exception as e:
144+
logger.warning(
145+
f"Failed to get sub-agent info for agent_id {sub_agent_id}: {str(e)}")
146+
logger.debug(f"Using frontend-provided sub-agent IDs: {sub_agent_ids}")
125147
else:
126-
tool_info_list = get_enabled_tool_description_for_generate_prompt(
127-
tenant_id=tenant_id, agent_id=agent_id)
128-
sub_agent_info_list = get_enabled_sub_agent_description_for_generate_prompt(
129-
tenant_id=tenant_id, agent_id=agent_id)
148+
sub_agent_info_list = []
149+
logger.debug("No sub-agents selected (empty sub_agent_ids list)")
130150

131151
# 1. Real-time streaming push
132152
final_results = {"duty": "", "constraint": "", "few_shots": "", "agent_var_name": "", "agent_display_name": "",
@@ -292,27 +312,3 @@ def join_info_for_generate_system_prompt(prompt_for_generate, sub_agent_info_lis
292312
"assistant_description": assistant_description
293313
})
294314
return content
295-
296-
297-
def get_enabled_tool_description_for_generate_prompt(agent_id: int, tenant_id: str):
298-
# Get tool information
299-
logger.info("Fetching tool instances")
300-
tool_id_list = get_enable_tool_id_by_agent_id(
301-
agent_id=agent_id, tenant_id=tenant_id)
302-
tool_info_list = query_tools_by_ids(tool_id_list)
303-
return tool_info_list
304-
305-
306-
def get_enabled_sub_agent_description_for_generate_prompt(agent_id: int, tenant_id: str):
307-
logger.info("Fetching sub-agents information")
308-
309-
sub_agent_id_list = query_sub_agents_id_list(
310-
main_agent_id=agent_id, tenant_id=tenant_id)
311-
312-
sub_agent_info_list = []
313-
for sub_agent_id in sub_agent_id_list:
314-
sub_agent_info = search_agent_info_by_agent_id(
315-
agent_id=sub_agent_id, tenant_id=tenant_id)
316-
317-
sub_agent_info_list.append(sub_agent_info)
318-
return sub_agent_info_list

backend/services/vectordatabase_service.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
from utils.config_utils import tenant_config_manager, get_model_name_from_config
3636
from utils.file_management_utils import get_all_files_status, get_file_size
3737

38+
ALLOWED_CHUNK_FIELDS = {"filename",
39+
"path_or_url", "content", "create_time", "id"}
40+
3841
# Configure logging
3942
logger = logging.getLogger("vectordatabase_service")
4043

@@ -572,7 +575,8 @@ async def list_files(
572575
'file_size': file_info.get('file_size', 0),
573576
'create_time': int(utc_create_timestamp * 1000),
574577
'status': "COMPLETED",
575-
'latest_task_id': ''
578+
'latest_task_id': '',
579+
'chunk_count': file_info.get('chunk_count', 0)
576580
}
577581
files.append(file_data)
578582

@@ -630,7 +634,7 @@ async def list_files(
630634
# Initialize chunks for all files
631635
for file_data in files:
632636
file_data['chunks'] = []
633-
file_data['chunk_count'] = 0
637+
file_data['chunk_count'] = file_data.get('chunk_count', 0)
634638

635639
if msearch_body:
636640
try:
@@ -667,7 +671,7 @@ async def list_files(
667671
else:
668672
for file_data in files:
669673
file_data['chunks'] = []
670-
file_data['chunk_count'] = 0
674+
file_data['chunk_count'] = file_data.get('chunk_count', 0)
671675

672676
return {"files": files}
673677

@@ -919,3 +923,62 @@ def get_summary(index_name: str = Path(..., description="Name of the index to ge
919923
except Exception as e:
920924
error_msg = f"Failed to get summary: {str(e)}"
921925
raise Exception(error_msg)
926+
927+
@staticmethod
928+
def get_index_chunks(
929+
index_name: str,
930+
page: Optional[int] = None,
931+
page_size: Optional[int] = None,
932+
path_or_url: Optional[str] = None,
933+
vdb_core: VectorDatabaseCore = Depends(get_vector_db_core),
934+
):
935+
"""
936+
Retrieve chunk records for the specified index with optional pagination.
937+
938+
Args:
939+
index_name: Name of the index to query
940+
page: Page number (1-based) when paginating
941+
page_size: Page size when paginating
942+
path_or_url: Optional document filter
943+
vdb_core: VectorDatabaseCore instance
944+
945+
Returns:
946+
Dictionary containing status, chunk list, total, and pagination metadata
947+
"""
948+
try:
949+
result = vdb_core.get_index_chunks(
950+
index_name,
951+
page=page,
952+
page_size=page_size,
953+
path_or_url=path_or_url,
954+
)
955+
raw_chunks = result.get("chunks", [])
956+
total = result.get("total", len(raw_chunks))
957+
result_page = result.get("page", page)
958+
result_page_size = result.get("page_size", page_size)
959+
960+
filtered_chunks: List[Any] = []
961+
for chunk in raw_chunks:
962+
if isinstance(chunk, dict):
963+
filtered_chunks.append(
964+
{
965+
field: chunk.get(field)
966+
for field in ALLOWED_CHUNK_FIELDS
967+
if field in chunk
968+
}
969+
)
970+
else:
971+
filtered_chunks.append(chunk)
972+
973+
return {
974+
"status": "success",
975+
"message": f"Successfully retrieved {len(filtered_chunks)} chunks from index {index_name}",
976+
"chunks": filtered_chunks,
977+
"total": total,
978+
"page": result_page,
979+
"page_size": result_page_size
980+
}
981+
except Exception as e:
982+
error_msg = f"Error retrieving chunks from index {index_name}: {str(e)}"
983+
logger.error(error_msg)
984+
raise Exception(error_msg)

doc/docs/zh/opensource-memorial-wall.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
每条消息应包含您的姓名/昵称和日期。
1616
请保持消息的礼貌和尊重,符合我们的行为准则。
1717
-->
18+
::: china-king-hs - 2025-11-20
19+
希望能正常使用nexent
20+
:::
1821

1922
::: info happyzhang - 2025-11-13
2023
也许我们正见证着未来的“后起之秀”😀
@@ -434,6 +437,10 @@ Nexent功能如此之强大,给我很多帮助,感谢开发者!厉害
434437
感谢 Nexent 让我踏上了开源之旅!给我一个机会制作智能体
435438
:::
436439

437-
::: info chengyudan - 2025-10-14
440+
::: info chengyudan - 2025-10-20
438441
感谢 Nexent 让我踏上了开源之旅!
439442
:::
443+
444+
::: info user - 2025-11-20
445+
学习ai - agent非常好的项目,后面会持续输出贡献!
446+
:::

docker/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ===== Necessary Configs (Neccessary till now, will be migrated to frontend page) =====
1+
# ===== Necessary Configs (Neccessary till now, will be migrated to frontend page) =====
22

33
# Voice Service Config
44
APPID=app_id

frontend/app/[locale]/agents/AgentConfiguration.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,25 @@ export default forwardRef<AgentConfigHandle, AgentConfigProps>(function AgentCon
161161
const currentAgentName = agentName;
162162
const currentAgentDisplayName = agentDisplayName;
163163

164+
// Extract tool IDs from selected tools (convert string IDs to numbers)
165+
// Always pass tool_ids array (empty array means no tools selected, undefined means use database)
166+
// In edit mode, we want to use current selection, so pass the array even if empty
167+
const toolIds = selectedTools.map((tool) => Number(tool.id));
168+
169+
// Get sub-agent IDs from enabledAgentIds
170+
// Always pass sub_agent_ids array (empty array means no sub-agents selected, undefined means use database)
171+
// In edit mode, we want to use current selection, so pass the array even if empty
172+
const subAgentIds = [...enabledAgentIds];
173+
164174
// Call backend API to generate agent prompt
175+
// Pass tool_ids and sub_agent_ids to use frontend selection instead of database query
165176
await generatePromptStream(
166177
{
167178
agent_id: agentIdToUse,
168179
task_description: businessLogic,
169180
model_id: selectedModel?.id?.toString() || "",
181+
tool_ids: toolIds,
182+
sub_agent_ids: subAgentIds,
170183
},
171184
(data) => {
172185
// Process streaming response data

0 commit comments

Comments
 (0)