33import logging
44from collections import deque
55
6- from fastapi import Header
7- from fastapi .responses import JSONResponse
6+ from fastapi import Header , Request , HTTPException
7+ from fastapi .responses import JSONResponse , StreamingResponse
8+ from consts .model import AgentRequest
89from agents .create_agent_info import create_tool_config_list
910from consts .model import AgentInfoRequest , ExportAndImportAgentInfo , ExportAndImportDataFormat , ToolInstanceInfoRequest , MCPInfo
1011from database .agent_db import create_agent , query_all_enabled_tool_instances , \
1112 search_blank_sub_agent_by_main_agent_id , \
1213 search_tools_for_sub_agent , search_agent_info_by_agent_id , update_agent , delete_agent_by_id , query_all_tools , \
1314 create_or_update_tool_by_tool_info , check_tool_is_available , query_all_agent_info_by_tenant_id , \
14- query_sub_agents_id_list , insert_related_agent , delete_all_related_agent
15+ query_sub_agents_id_list , insert_related_agent , delete_all_related_agent , search_agent_id_by_agent_name
1516from database .remote_mcp_db import get_mcp_server_by_name_and_tenant , check_mcp_name_exists
1617from services .remote_mcp_service import add_remote_mcp_server_list
1718from services .tool_configuration_service import update_tool_list
19+ from services .conversation_management_service import save_conversation_user , save_conversation_assistant
1820
19- from utils .auth_utils import get_current_user_id
21+ from utils .auth_utils import get_current_user_info
2022from utils .memory_utils import build_memory_config
23+ from utils .thread_utils import submit
2124from nexent .memory .memory_service import clear_memory
25+ from nexent .core .agents .run_agent import agent_run
26+ from services .memory_config_service import build_memory_context
27+ from agents .create_agent_info import create_agent_run_info
28+ from agents .agent_run_manager import agent_run_manager
29+ from agents .preprocess_manager import preprocess_manager
2230
2331
2432logger = logging .getLogger ("agent_service" )
@@ -69,7 +77,7 @@ def get_agent_info_impl(agent_id: int, tenant_id: str):
6977
7078
7179def get_creating_sub_agent_info_impl (authorization : str = Header (None )):
72- user_id , tenant_id = get_current_user_id (authorization )
80+ user_id , tenant_id , _ = get_current_user_info (authorization )
7381
7482 try :
7583 sub_agent_id = get_creating_sub_agent_id_service (tenant_id , user_id )
@@ -100,7 +108,7 @@ def get_creating_sub_agent_info_impl(authorization: str = Header(None)):
100108 "sub_agent_id_list" : query_sub_agents_id_list (main_agent_id = sub_agent_id , tenant_id = tenant_id )}
101109
102110def update_agent_info_impl (request : AgentInfoRequest , authorization : str = Header (None )):
103- user_id , tenant_id = get_current_user_id (authorization )
111+ user_id , tenant_id , _ = get_current_user_info (authorization )
104112
105113 try :
106114 update_agent (request .agent_id , request , tenant_id , user_id )
@@ -109,7 +117,7 @@ def update_agent_info_impl(request: AgentInfoRequest, authorization: str = Heade
109117 raise ValueError (f"Failed to update agent info: { str (e )} " )
110118
111119async def delete_agent_impl (agent_id : int , authorization : str = Header (None )):
112- user_id , tenant_id = get_current_user_id (authorization )
120+ user_id , tenant_id , _ = get_current_user_info (authorization )
113121
114122 try :
115123 delete_agent_by_id (agent_id , tenant_id , user_id )
@@ -183,7 +191,7 @@ async def export_agent_impl(agent_id: int, authorization: str = Header(None)) ->
183191 This function recursively finds all managed sub-agents and exports the detailed configuration of each agent (including tools, prompts, etc.) as a dictionary, and finally returns it as a formatted JSON string for frontend download and backup.
184192 """
185193
186- user_id , tenant_id = get_current_user_id (authorization )
194+ user_id , tenant_id , _ = get_current_user_info (authorization )
187195
188196 export_agent_dict = {}
189197 search_list = deque ([agent_id ])
@@ -251,7 +259,7 @@ async def import_agent_impl(agent_info: ExportAndImportDataFormat, authorization
251259 """
252260 Import agent using DFS
253261 """
254- user_id , tenant_id = get_current_user_id (authorization )
262+ user_id , tenant_id , _ = get_current_user_info (authorization )
255263 agent_id = agent_info .agent_id
256264
257265 # First, add MCP servers if any
@@ -459,4 +467,121 @@ def insert_related_agent_impl(parent_agent_id, child_agent_id, tenant_id):
459467 return JSONResponse (
460468 status_code = 400 ,
461469 content = {"message" :"Failed to insert relation" , "status" : "error" }
462- )
470+ )
471+
472+
473+ # Helper function for run_agent_stream, used to prepare context for an agent run
474+ async def prepare_agent_run (agent_request : AgentRequest , http_request : Request , authorization : str ):
475+ """
476+ Prepare for an agent run by creating context and run info, and registering the run.
477+ """
478+ user_id , tenant_id , language = get_current_user_info (authorization , http_request )
479+
480+ memory_context = build_memory_context (user_id , tenant_id , agent_request .agent_id )
481+ agent_run_info = await create_agent_run_info (agent_id = agent_request .agent_id ,
482+ minio_files = agent_request .minio_files ,
483+ query = agent_request .query ,
484+ history = agent_request .history ,
485+ authorization = authorization ,
486+ language = language )
487+ agent_run_manager .register_agent_run (agent_request .conversation_id , agent_run_info )
488+ return agent_run_info , memory_context
489+
490+
491+ # Helper function for run_agent_stream, used to save messages for either user or assistant
492+ def save_messages (agent_request , target :str , messages = None , authorization = None ):
493+ if target == "user" :
494+ if messages is not None :
495+ raise ValueError ("Messages should be None when saving for user." )
496+ submit (save_conversation_user , agent_request , authorization )
497+ elif target == "assistant" :
498+ if messages is None :
499+ raise ValueError ("Messages cannot be None when saving for assistant." )
500+ submit (save_conversation_assistant , agent_request , messages , authorization )
501+
502+
503+ # Helper function for run_agent_stream, used to generate stream response
504+ async def generate_stream (agent_run_info , memory_context , agent_request : AgentRequest , authorization : str ):
505+ messages = []
506+ try :
507+ async for chunk in agent_run (agent_run_info , memory_context ):
508+ messages .append (chunk )
509+ yield f"data: { chunk } \n \n "
510+ except Exception as e :
511+ logger .error (f"Agent run error: { str (e )} " )
512+ raise HTTPException (status_code = 500 , detail = f"Agent run error: { str (e )} " )
513+ finally :
514+ # Save assistant message only if not in debug mode
515+ if not agent_request .is_debug :
516+ save_messages (agent_request , target = "assistant" , messages = messages , authorization = authorization )
517+ # Unregister agent run instance for both debug and non-debug modes
518+ agent_run_manager .unregister_agent_run (agent_request .conversation_id )
519+
520+
521+ async def run_agent_stream (agent_request : AgentRequest , http_request : Request , authorization : str ):
522+ """
523+ Start an agent run and stream responses, using explicit user/tenant context.
524+ Mirrors the logic of agent_app.agent_run_api but reusable by services.
525+ """
526+ agent_run_info , memory_context = await prepare_agent_run (
527+ agent_request = agent_request ,
528+ http_request = http_request ,
529+ authorization = authorization
530+ )
531+
532+ # Save user message only if not in debug mode
533+ if not agent_request .is_debug :
534+ save_messages (
535+ agent_request ,
536+ target = "user" ,
537+ authorization = authorization
538+ )
539+
540+ return StreamingResponse (
541+ generate_stream (agent_run_info , memory_context , agent_request , authorization ),
542+ media_type = "text/event-stream" ,
543+ headers = {
544+ "Cache-Control" : "no-cache" ,
545+ "Connection" : "keep-alive"
546+ }
547+ )
548+
549+
550+ def stop_agent_tasks (conversation_id : int ):
551+ """
552+ Stop agent run and preprocess tasks for the specified conversation_id.
553+ Matches the behavior of agent_app.agent_stop_api.
554+ """
555+ # Stop agent run
556+ agent_stopped = agent_run_manager .stop_agent_run (conversation_id )
557+
558+ # Stop preprocess tasks
559+ preprocess_stopped = preprocess_manager .stop_preprocess_tasks (conversation_id )
560+
561+ if agent_stopped or preprocess_stopped :
562+ message_parts = []
563+ if agent_stopped :
564+ message_parts .append ("agent run" )
565+ if preprocess_stopped :
566+ message_parts .append ("preprocess tasks" )
567+
568+ message = f"successfully stopped { ' and ' .join (message_parts )} for conversation_id { conversation_id } "
569+ logging .info (message )
570+ return {"status" : "success" , "message" : message }
571+ else :
572+ message = f"no running agent or preprocess tasks found for conversation_id { conversation_id } "
573+ logging .error (message )
574+ return {"status" : "error" , "message" : message }
575+
576+
577+ def get_agent_id_by_name (agent_name : str , tenant_id : str ) -> int :
578+ """
579+ Resolve unique agent id by its unique name under the same tenant.
580+ """
581+ if not agent_name :
582+ raise HTTPException (status_code = 400 , detail = "agent_name required" )
583+ try :
584+ return search_agent_id_by_agent_name (agent_name , tenant_id )
585+ except Exception as _ :
586+ logger .error (f"Failed to find agent id with '{ agent_name } ' in tenant { tenant_id } " )
587+ raise HTTPException (status_code = 404 , detail = "agent not found" )
0 commit comments