Skip to content

Commit 3553949

Browse files
committed
Merge branch 'develop' of https://github.com/ModelEngine-Group/nexent into develop_multi_agent
2 parents 13ca3a3 + c40d884 commit 3553949

File tree

144 files changed

+6086
-3264
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

144 files changed

+6086
-3264
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
.idea
2-
.gitignore
32
/.env
43
.vscode
54

backend/apps/agent_app.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ async def import_agent_api(request: AgentImportRequest, authorization: Optional[
134134
import an agent
135135
"""
136136
try:
137-
await import_agent_impl(request.agent_info, authorization)
137+
await import_agent_impl(
138+
request.agent_info,
139+
authorization,
140+
force_import=request.force_import
141+
)
138142
return {}
139143
except Exception as e:
140144
logger.error(f"Agent import error: {str(e)}")

backend/apps/vectordatabase_app.py

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Path, Query
66
from fastapi.responses import JSONResponse
77

8-
from consts.model import IndexingResponse
8+
from consts.model import ChunkCreateRequest, ChunkUpdateRequest, HybridSearchRequest, IndexingResponse
99
from nexent.vector_database.base import VectorDatabaseCore
1010
from services.vectordatabase_service import (
1111
ElasticSearchService,
@@ -226,3 +226,125 @@ def get_index_chunks(
226226
f"Error getting chunks for index '{index_name}': {error_msg}")
227227
raise HTTPException(
228228
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=f"Error getting chunks: {error_msg}")
229+
230+
231+
@router.post("/{index_name}/chunk")
232+
def create_chunk(
233+
index_name: str = Path(..., description="Name of the index"),
234+
payload: ChunkCreateRequest = Body(..., description="Chunk data"),
235+
vdb_core: VectorDatabaseCore = Depends(get_vector_db_core),
236+
authorization: Optional[str] = Header(None),
237+
):
238+
"""Create a manual chunk."""
239+
try:
240+
user_id, _ = get_current_user_id(authorization)
241+
result = ElasticSearchService.create_chunk(
242+
index_name=index_name,
243+
chunk_request=payload,
244+
vdb_core=vdb_core,
245+
user_id=user_id,
246+
)
247+
return JSONResponse(status_code=HTTPStatus.OK, content=result)
248+
except Exception as exc:
249+
logger.error(
250+
"Error creating chunk for index %s: %s", index_name, exc, exc_info=True
251+
)
252+
raise HTTPException(
253+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
254+
)
255+
256+
257+
@router.put("/{index_name}/chunk/{chunk_id}")
258+
def update_chunk(
259+
index_name: str = Path(..., description="Name of the index"),
260+
chunk_id: str = Path(..., description="Chunk identifier"),
261+
payload: ChunkUpdateRequest = Body(...,
262+
description="Chunk update payload"),
263+
vdb_core: VectorDatabaseCore = Depends(get_vector_db_core),
264+
authorization: Optional[str] = Header(None),
265+
):
266+
"""Update an existing chunk."""
267+
try:
268+
user_id, _ = get_current_user_id(authorization)
269+
result = ElasticSearchService.update_chunk(
270+
index_name=index_name,
271+
chunk_id=chunk_id,
272+
chunk_request=payload,
273+
vdb_core=vdb_core,
274+
user_id=user_id,
275+
)
276+
return JSONResponse(status_code=HTTPStatus.OK, content=result)
277+
except ValueError as exc:
278+
raise HTTPException(
279+
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc))
280+
except Exception as exc:
281+
logger.error(
282+
"Error updating chunk %s for index %s: %s",
283+
chunk_id,
284+
index_name,
285+
exc,
286+
exc_info=True,
287+
)
288+
raise HTTPException(
289+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
290+
)
291+
292+
293+
@router.delete("/{index_name}/chunk/{chunk_id}")
294+
def delete_chunk(
295+
index_name: str = Path(..., description="Name of the index"),
296+
chunk_id: str = Path(..., description="Chunk identifier"),
297+
vdb_core: VectorDatabaseCore = Depends(get_vector_db_core),
298+
authorization: Optional[str] = Header(None),
299+
):
300+
"""Delete a chunk."""
301+
try:
302+
get_current_user_id(authorization)
303+
result = ElasticSearchService.delete_chunk(
304+
index_name=index_name,
305+
chunk_id=chunk_id,
306+
vdb_core=vdb_core,
307+
)
308+
return JSONResponse(status_code=HTTPStatus.OK, content=result)
309+
except ValueError as exc:
310+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail=str(exc))
311+
except Exception as exc:
312+
logger.error(
313+
"Error deleting chunk %s for index %s: %s",
314+
chunk_id,
315+
index_name,
316+
exc,
317+
exc_info=True,
318+
)
319+
raise HTTPException(
320+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
321+
)
322+
323+
324+
@router.post("/search/hybrid")
325+
async def hybrid_search(
326+
payload: HybridSearchRequest,
327+
vdb_core: VectorDatabaseCore = Depends(get_vector_db_core),
328+
authorization: Optional[str] = Header(None),
329+
):
330+
"""Run a hybrid (accurate + semantic) search across indices."""
331+
try:
332+
_, tenant_id = get_current_user_id(authorization)
333+
result = ElasticSearchService.search_hybrid(
334+
index_names=payload.index_names,
335+
query=payload.query,
336+
tenant_id=tenant_id,
337+
top_k=payload.top_k,
338+
weight_accurate=payload.weight_accurate,
339+
vdb_core=vdb_core,
340+
)
341+
return JSONResponse(status_code=HTTPStatus.OK, content=result)
342+
except ValueError as exc:
343+
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST,
344+
detail=str(exc))
345+
except Exception as exc:
346+
logger.error(f"Hybrid search failed: {exc}", exc_info=True)
347+
raise HTTPException(
348+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
349+
detail=f"Error executing hybrid search: {str(exc)}",
350+
)

backend/consts/model.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,43 @@ class IndexingResponse(BaseModel):
175175
total_submitted: int
176176

177177

178+
class ChunkCreateRequest(BaseModel):
179+
"""Request payload for manual chunk creation."""
180+
181+
content: str = Field(..., min_length=1, description="Chunk content")
182+
title: Optional[str] = Field(None, description="Optional chunk title")
183+
filename: Optional[str] = Field(None, description="Associated file name")
184+
path_or_url: Optional[str] = Field(None, description="Source path or URL")
185+
chunk_id: Optional[str] = Field(
186+
None, description="Explicit chunk identifier")
187+
metadata: Dict[str, Any] = Field(
188+
default_factory=dict, description="Additional chunk metadata")
189+
190+
191+
class ChunkUpdateRequest(BaseModel):
192+
"""Request payload for chunk updates."""
193+
194+
content: Optional[str] = Field(None, description="Updated chunk content")
195+
title: Optional[str] = Field(None, description="Updated chunk title")
196+
filename: Optional[str] = Field(None, description="Updated file name")
197+
path_or_url: Optional[str] = Field(
198+
None, description="Updated source path or URL")
199+
metadata: Dict[str, Any] = Field(
200+
default_factory=dict, description="Additional metadata updates")
201+
202+
203+
class HybridSearchRequest(BaseModel):
204+
"""Request payload for hybrid knowledge-base searches."""
205+
query: str = Field(..., min_length=1,
206+
description="Search query text")
207+
index_names: List[str] = Field(..., min_items=1,
208+
description="List of index names to search")
209+
top_k: int = Field(10, ge=1, le=100,
210+
description="Number of results to return")
211+
weight_accurate: float = Field(0.5, ge=0.0, le=1.0,
212+
description="Weight applied to accurate search scores")
213+
214+
178215
# Request models
179216
class ProcessParams(BaseModel):
180217
chunking_strategy: Optional[str] = "basic"
@@ -304,6 +341,7 @@ class ExportAndImportDataFormat(BaseModel):
304341

305342
class AgentImportRequest(BaseModel):
306343
agent_info: ExportAndImportDataFormat
344+
force_import: bool = False
307345

308346

309347
class ConvertStateRequest(BaseModel):

backend/prompts/utils/prompt_generate.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,49 @@ USER_PROMPT: |-
249249
{% else %}
250250
你没有可用的助手
251251
{% endif %}
252+
253+
254+
AGENT_NAME_REGENERATE_SYSTEM_PROMPT: |-
255+
### 你是【Agent变量名调整专家】
256+
你的工作是根据任务描述以及已有变量名,生成一个语义一致但不重复的 Python 变量名。
257+
258+
#### 约束
259+
1. 变量名只能包含字母、数字和下划线,并且以字母或下划线开头,以“_assistant”结尾,长度不超过 30 个字符。
260+
2. 避免与已有变量名重复,同时保持与原始名称相近的语义。
261+
3. 仅输出变量名本身,不要附加额外解释或标点。
262+
263+
264+
AGENT_NAME_REGENERATE_USER_PROMPT: |-
265+
### 任务描述
266+
{{ task_description }}
267+
268+
### 原始变量名
269+
{{ original_value }}
270+
271+
### 已有变量名
272+
{{ existing_values }}
273+
274+
请在满足约束的前提下,生成一个新的变量名,使其语义接近原始变量名但不与已有变量名重复。只输出变量名本身。
275+
276+
277+
AGENT_DISPLAY_NAME_REGENERATE_SYSTEM_PROMPT: |-
278+
### 你是【Agent展示名称调整专家】
279+
你的任务是结合业务背景和已有展示名称,生成一个语义一致但不重复的 Agent 展示名称。
280+
281+
#### 约束
282+
1. 展示名称需自然、易读,能够概括 Agent 的核心能力,以“助手”结尾,长度不超过 30 个字符。
283+
2. 不得包含具体工具名称或多余符号。
284+
3. 仅输出展示名称本身。
285+
286+
287+
AGENT_DISPLAY_NAME_REGENERATE_USER_PROMPT: |-
288+
### 任务描述
289+
{{ task_description }}
290+
291+
### 原始展示名称
292+
{{ original_value }}
293+
294+
### 已有展示名称
295+
{{ existing_values }}
296+
297+
请输出一个新的展示名称,语义接近原始名称但不与已存在的展示名称重复,并满足上述约束。只输出展示名称本身。

backend/prompts/utils/prompt_generate_en.yaml

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,50 @@ USER_PROMPT: |-
251251
{{assistant_description}}
252252
{% else %}
253253
You have no available assistants
254-
{% endif %}
254+
{% endif %}
255+
256+
257+
AGENT_NAME_REGENERATE_SYSTEM_PROMPT: |-
258+
### You are an [Agent Variable Name Refinement Expert]
259+
Your job is to generate a Python-friendly variable name that keeps the same intent while avoiding duplicates.
260+
261+
#### Constraints
262+
1. The name may contain only letters, numbers, and underscores, must start with a letter or underscore, ending with "_assistant", and stay within 30 characters.
263+
2. Keep the new name semantically close to the original one while ensuring it does not duplicate existing names.
264+
3. Return the variable name only—no explanations or extra symbols.
265+
266+
267+
AGENT_NAME_REGENERATE_USER_PROMPT: |-
268+
### Task Description
269+
{{ task_description }}
270+
271+
### Original Variable Name
272+
{{ original_value }}
273+
274+
### Existing Variable Names
275+
{{ existing_values }}
276+
277+
Generate a new variable name that satisfies the constraints, keeps the original meaning, and does not duplicate any existing names. Output only the variable name.
278+
279+
280+
AGENT_DISPLAY_NAME_REGENERATE_SYSTEM_PROMPT: |-
281+
### You are an [Agent Display Name Refinement Expert]
282+
You summarize the agent's capability and generate a readable display name that remains unique within the workspace.
283+
284+
#### Constraints
285+
1. The name must be concise, easy to read, summarize the agent's responsibility, ending with "Assistant", and stay within 30 characters.
286+
2. Avoid mentioning specific tool names or adding extra punctuation.
287+
3. Return only the display name.
288+
289+
290+
AGENT_DISPLAY_NAME_REGENERATE_USER_PROMPT: |-
291+
### Task Description
292+
{{ task_description }}
293+
294+
### Original Display Name
295+
{{ original_value }}
296+
297+
### Existing Display Names
298+
{{ existing_values }}
299+
300+
Output a new display name that is semantically aligned with the original while remaining unique and honoring the constraints. Return only the display name.

0 commit comments

Comments
 (0)