Skip to content

Commit b416ec4

Browse files
authored
✨ Agent runtime isolation Step1: Service decomposition based on Docker #606
2 parents 9ee40cd + 29847eb commit b416ec4

28 files changed

+831
-146
lines changed

backend/apps/agent_app.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
# Import monitoring utilities
2323
from utils.monitoring import monitoring_manager
2424

25-
router = APIRouter(prefix="/agent")
25+
agent_runtime_router = APIRouter(prefix="/agent")
26+
agent_config_router = APIRouter(prefix="/agent")
2627
logger = logging.getLogger("agent_app")
2728

2829

2930
# Define API route
30-
@router.post("/run")
31+
@agent_runtime_router.post("/run")
3132
@monitoring_manager.monitor_endpoint("agent.run", exclude_params=["authorization"])
3233
async def agent_run_api(agent_request: AgentRequest, http_request: Request, authorization: str = Header(None)):
3334
"""
@@ -45,7 +46,7 @@ async def agent_run_api(agent_request: AgentRequest, http_request: Request, auth
4546
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent run error.")
4647

4748

48-
@router.get("/stop/{conversation_id}")
49+
@agent_runtime_router.get("/stop/{conversation_id}")
4950
async def agent_stop_api(conversation_id: int, authorization: Optional[str] = Header(None)):
5051
"""
5152
stop agent run and preprocess tasks for specified conversation_id
@@ -58,7 +59,7 @@ async def agent_stop_api(conversation_id: int, authorization: Optional[str] = He
5859
detail=f"no running agent or preprocess tasks found for conversation_id {conversation_id}")
5960

6061

61-
@router.post("/search_info")
62+
@agent_config_router.post("/search_info")
6263
async def search_agent_info_api(agent_id: int = Body(...), authorization: Optional[str] = Header(None)):
6364
"""
6465
Search agent info by agent_id
@@ -72,7 +73,7 @@ async def search_agent_info_api(agent_id: int = Body(...), authorization: Option
7273
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent search info error.")
7374

7475

75-
@router.get("/get_creating_sub_agent_id")
76+
@agent_config_router.get("/get_creating_sub_agent_id")
7677
async def get_creating_sub_agent_info_api(authorization: Optional[str] = Header(None)):
7778
"""
7879
Create a new sub agent, return agent_ID
@@ -85,7 +86,7 @@ async def get_creating_sub_agent_info_api(authorization: Optional[str] = Header(
8586
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent create error.")
8687

8788

88-
@router.post("/update")
89+
@agent_config_router.post("/update")
8990
async def update_agent_info_api(request: AgentInfoRequest, authorization: Optional[str] = Header(None)):
9091
"""
9192
Update an existing agent
@@ -99,7 +100,7 @@ async def update_agent_info_api(request: AgentInfoRequest, authorization: Option
99100
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent update error.")
100101

101102

102-
@router.delete("")
103+
@agent_config_router.delete("")
103104
async def delete_agent_api(request: AgentIDRequest, authorization: Optional[str] = Header(None)):
104105
"""
105106
Delete an agent
@@ -113,7 +114,7 @@ async def delete_agent_api(request: AgentIDRequest, authorization: Optional[str]
113114
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent delete error.")
114115

115116

116-
@router.post("/export")
117+
@agent_config_router.post("/export")
117118
async def export_agent_api(request: AgentIDRequest, authorization: Optional[str] = Header(None)):
118119
"""
119120
export an agent
@@ -127,7 +128,7 @@ async def export_agent_api(request: AgentIDRequest, authorization: Optional[str]
127128
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent export error.")
128129

129130

130-
@router.post("/import")
131+
@agent_config_router.post("/import")
131132
async def import_agent_api(request: AgentImportRequest, authorization: Optional[str] = Header(None)):
132133
"""
133134
import an agent
@@ -141,7 +142,7 @@ async def import_agent_api(request: AgentImportRequest, authorization: Optional[
141142
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent import error.")
142143

143144

144-
@router.get("/list")
145+
@agent_config_router.get("/list")
145146
async def list_all_agent_info_api(authorization: Optional[str] = Header(None), request: Request = None):
146147
"""
147148
list all agent info
@@ -155,7 +156,7 @@ async def list_all_agent_info_api(authorization: Optional[str] = Header(None), r
155156
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent list error.")
156157

157158

158-
@router.get("/call_relationship/{agent_id}")
159+
@agent_config_router.get("/call_relationship/{agent_id}")
159160
async def get_agent_call_relationship_api(agent_id: int, authorization: Optional[str] = Header(None)):
160161
"""
161162
Get agent call relationship tree including tools and sub-agents
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44
from fastapi.middleware.cors import CORSMiddleware
55
from fastapi.responses import JSONResponse
66

7-
from apps.agent_app import router as agent_router
7+
from apps.agent_app import agent_config_router as agent_router
88
from apps.config_sync_app import router as config_sync_router
9-
from apps.conversation_management_app import router as conversation_management_router
109
from apps.elasticsearch_app import router as elasticsearch_router
11-
from apps.file_management_app import router as file_manager_router
10+
from apps.file_management_app import file_management_config_router as file_manager_router
1211
from apps.image_app import router as proxy_router
1312
from apps.knowledge_summary_app import router as summary_router
14-
from apps.memory_config_app import router as memory_router
1513
from apps.me_model_managment_app import router as me_model_manager_router
1614
from apps.mock_user_management_app import router as mock_user_management_router
1715
from apps.model_managment_app import router as model_manager_router
@@ -20,7 +18,7 @@
2018
from apps.tenant_config_app import router as tenant_config_router
2119
from apps.tool_config_app import router as tool_config_router
2220
from apps.user_management_app import router as user_management_router
23-
from apps.voice_app import router as voice_router
21+
from apps.voice_app import voice_config_router as voice_router
2422
from consts.const import IS_SPEED_MODE
2523

2624
# Import monitoring utilities
@@ -41,10 +39,8 @@
4139

4240
app.include_router(me_model_manager_router)
4341
app.include_router(model_manager_router)
44-
app.include_router(memory_router)
4542
app.include_router(config_sync_router)
4643
app.include_router(agent_router)
47-
app.include_router(conversation_management_router)
4844
app.include_router(elasticsearch_router)
4945
app.include_router(voice_router)
5046
app.include_router(file_manager_router)

backend/apps/file_management_app.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,20 @@
1616
logger = logging.getLogger("file_management_app")
1717

1818
# Create API router
19-
router = APIRouter(prefix="/file")
19+
file_management_runtime_router = APIRouter(prefix="/file")
20+
file_management_config_router = APIRouter(prefix="/file")
2021

2122

2223
# Handle preflight requests
23-
@router.options("/{full_path:path}")
24+
@file_management_config_router.options("/{full_path:path}")
2425
async def options_route(full_path: str):
2526
return JSONResponse(
2627
status_code=HTTPStatus.OK,
2728
content={"detail": "OK"},
2829
)
2930

3031

31-
@router.post("/upload")
32+
@file_management_config_router.post("/upload")
3233
async def upload_files(
3334
file: List[UploadFile] = File(..., alias="file"),
3435
destination: str = Form(...,
@@ -59,7 +60,7 @@ async def upload_files(
5960
detail="No valid files uploaded")
6061

6162

62-
@router.post("/process")
63+
@file_management_config_router.post("/process")
6364
async def process_files(
6465
files: List[dict] = Body(
6566
..., description="List of file details to process, including path_or_url and filename"),
@@ -100,7 +101,7 @@ async def process_files(
100101
)
101102

102103

103-
@router.post("/storage")
104+
@file_management_runtime_router.post("/storage")
104105
async def storage_upload_files(
105106
files: List[UploadFile] = File(..., description="List of files to upload"),
106107
folder: str = Form(
@@ -125,7 +126,7 @@ async def storage_upload_files(
125126
}
126127

127128

128-
@router.get("/storage")
129+
@file_management_config_router.get("/storage")
129130
async def get_storage_files(
130131
prefix: str = Query("", description="File prefix filter"),
131132
limit: int = Query(100, description="Maximum number of files to return"),
@@ -160,7 +161,7 @@ async def get_storage_files(
160161
)
161162

162163

163-
@router.get("/storage/{path}/{object_name}")
164+
@file_management_config_router.get("/storage/{path}/{object_name}")
164165
async def get_storage_file(
165166
object_name: str = PathParam(..., description="File object name"),
166167
download: str = Query("ignore", description="How to get the file"),
@@ -200,7 +201,7 @@ async def get_storage_file(
200201
)
201202

202203

203-
@router.delete("/storage/{object_name:path}")
204+
@file_management_config_router.delete("/storage/{object_name:path}")
204205
async def remove_storage_file(
205206
object_name: str = PathParam(..., description="File object name to delete")
206207
):
@@ -224,7 +225,7 @@ async def remove_storage_file(
224225
)
225226

226227

227-
@router.post("/storage/batch-urls")
228+
@file_management_config_router.post("/storage/batch-urls")
228229
async def get_storage_file_batch_urls(
229230
request_data: dict = Body(...,
230231
description="JSON containing list of file object names"),
@@ -272,7 +273,7 @@ async def get_storage_file_batch_urls(
272273
}
273274

274275

275-
@router.post("/preprocess")
276+
@file_management_runtime_router.post("/preprocess")
276277
async def agent_preprocess_api(
277278
request: Request, query: str = Form(...),
278279
files: List[UploadFile] = File(...),

backend/apps/runtime_app.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import logging
2+
3+
from fastapi import FastAPI, HTTPException
4+
from fastapi.middleware.cors import CORSMiddleware
5+
from fastapi.responses import JSONResponse
6+
7+
from apps.agent_app import agent_runtime_router as agent_router
8+
from apps.voice_app import voice_runtime_router as voice_router
9+
from apps.conversation_management_app import router as conversation_management_router
10+
from apps.memory_config_app import router as memory_config_router
11+
from apps.file_management_app import file_management_runtime_router as file_management_router
12+
13+
# Import monitoring utilities
14+
from utils.monitoring import monitoring_manager
15+
16+
# Create logger instance
17+
logger = logging.getLogger("runtime_app")
18+
app = FastAPI(root_path="/api")
19+
20+
# Add CORS middleware
21+
app.add_middleware(
22+
CORSMiddleware,
23+
allow_origins=["*"],
24+
allow_credentials=True,
25+
allow_methods=["*"],
26+
allow_headers=["*"],
27+
)
28+
29+
app.include_router(agent_router)
30+
app.include_router(conversation_management_router)
31+
app.include_router(memory_config_router)
32+
app.include_router(file_management_router)
33+
app.include_router(voice_router)
34+
35+
# Initialize monitoring for the application
36+
monitoring_manager.setup_fastapi_app(app)
37+
38+
39+
# Global exception handler for HTTP exceptions
40+
@app.exception_handler(HTTPException)
41+
async def http_exception_handler(request, exc):
42+
logger.error(f"HTTPException: {exc.detail}")
43+
return JSONResponse(
44+
status_code=exc.status_code,
45+
content={"message": exc.detail},
46+
)
47+
48+
49+
# Global exception handler for all uncaught exceptions
50+
@app.exception_handler(Exception)
51+
async def generic_exception_handler(request, exc):
52+
logger.error(f"Generic Exception: {exc}")
53+
return JSONResponse(
54+
status_code=500,
55+
content={"message": "Internal server error, please try again later."},
56+
)
57+
58+

backend/apps/voice_app.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
logger = logging.getLogger("voice_app")
1818

19-
router = APIRouter(prefix="/voice")
19+
voice_runtime_router = APIRouter(prefix="/voice")
20+
voice_config_router = APIRouter(prefix="/voice")
2021

2122

22-
@router.websocket("/stt/ws")
23+
@voice_runtime_router.websocket("/stt/ws")
2324
async def stt_websocket(websocket: WebSocket):
2425
"""WebSocket endpoint for real-time audio streaming and STT"""
2526
logger.info("STT WebSocket connection attempt...")
@@ -39,7 +40,7 @@ async def stt_websocket(websocket: WebSocket):
3940
logger.info("STT WebSocket connection closed")
4041

4142

42-
@router.websocket("/tts/ws")
43+
@voice_runtime_router.websocket("/tts/ws")
4344
async def tts_websocket(websocket: WebSocket):
4445
"""WebSocket endpoint for streaming TTS"""
4546
logger.info("TTS WebSocket connection attempt...")
@@ -73,7 +74,7 @@ async def tts_websocket(websocket: WebSocket):
7374
await websocket.close()
7475

7576

76-
@router.post("/connectivity")
77+
@voice_config_router.post("/connectivity")
7778
async def check_voice_connectivity(request: VoiceConnectivityRequest):
7879
"""
7980
Check voice service connectivity
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from dotenv import load_dotenv
1111
load_dotenv()
1212

13-
from apps.base_app import app
13+
from apps.config_app import app
1414
from utils.logging_utils import configure_logging, configure_elasticsearch_logging
1515
from services.tool_configuration_service import initialize_tools_on_startup
1616

backend/runtime_service.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import uvicorn
2+
import logging
3+
import warnings
4+
import asyncio
5+
6+
from consts.const import APP_VERSION
7+
8+
warnings.filterwarnings("ignore", category=UserWarning)
9+
10+
from dotenv import load_dotenv
11+
load_dotenv()
12+
13+
from apps.runtime_app import app
14+
from utils.logging_utils import configure_logging, configure_elasticsearch_logging
15+
from services.tool_configuration_service import initialize_tools_on_startup
16+
17+
configure_logging(logging.INFO)
18+
configure_elasticsearch_logging()
19+
logger = logging.getLogger("runtime_service")
20+
21+
22+
async def startup_initialization():
23+
"""
24+
Perform initialization tasks during server startup
25+
"""
26+
logger.info("Starting server initialization...")
27+
logger.info(f"APP version is: {APP_VERSION}")
28+
try:
29+
# Initialize tools on startup - service layer handles detailed logging
30+
await initialize_tools_on_startup()
31+
logger.info("Server initialization completed successfully!")
32+
except Exception as e:
33+
logger.error(f"Server initialization failed: {str(e)}")
34+
# Don't raise the exception to allow server to start even if initialization fails
35+
logger.warning("Server will continue to start despite initialization issues")
36+
37+
38+
if __name__ == "__main__":
39+
asyncio.run(startup_initialization())
40+
uvicorn.run(app, host="0.0.0.0", port=5014, log_level="info")
41+
42+

doc/docs/en/backend/overview.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,9 @@ uv sync && uv pip install -e ../sdk
179179
### Service Startup
180180
```bash
181181
python backend/data_process_service.py # Data processing service
182-
python backend/main_service.py # Main service
183-
python backend/nexent_mcp_service.py # MCP service
182+
python backend/config_service.py # Config service
183+
python backend/runtime_service.py # Runtime service
184+
python backend/mcp_service.py # MCP service
184185
```
185186

186187
## Performance and Scalability

doc/docs/en/backend/tools/mcp.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,15 @@ DELETE /remote-proxies?service_name={service_name}
144144
**Start Main Service**
145145
```bash
146146
cd backend
147-
python main_service.py
147+
python config_service.py
148+
python runtime_service.py
148149
```
149150
Service will start at `http://localhost:5010`.
150151

151152
**Start MCP Service**
152153
```bash
153154
cd backend
154-
python nexent_mcp_service.py
155+
python mcp_service.py
155156
```
156157
Service will start at `http://localhost:5011`.
157158

0 commit comments

Comments
 (0)