16
16
import asyncio
17
17
from contextlib import asynccontextmanager
18
18
import importlib
19
- import inspect
20
- import json
21
19
import logging
22
20
import os
23
21
from pathlib import Path
24
- import signal
25
22
import sys
26
23
import time
27
24
import traceback
55
52
from typing_extensions import override
56
53
57
54
from ..agents import RunConfig
58
- from ..agents .base_agent import BaseAgent
59
55
from ..agents .live_request_queue import LiveRequest
60
56
from ..agents .live_request_queue import LiveRequestQueue
61
57
from ..agents .llm_agent import Agent
62
- from ..agents .llm_agent import LlmAgent
63
58
from ..agents .run_config import StreamingMode
64
59
from ..artifacts .in_memory_artifact_service import InMemoryArtifactService
65
60
from ..evaluation .eval_case import EvalCase
75
70
from ..sessions .vertex_ai_session_service import VertexAiSessionService
76
71
from ..tools .base_toolset import BaseToolset
77
72
from .cli_eval import EVAL_SESSION_ID_PREFIX
78
- from .cli_eval import EvalCaseResult
79
73
from .cli_eval import EvalMetric
80
74
from .cli_eval import EvalMetricResult
81
75
from .cli_eval import EvalMetricResultPerInvocation
82
76
from .cli_eval import EvalSetResult
83
77
from .cli_eval import EvalStatus
78
+ from .utils import cleanup
84
79
from .utils import common
85
80
from .utils import create_empty_state
86
81
from .utils import envs
@@ -230,27 +225,8 @@ def get_fast_api_app(
230
225
231
226
trace .set_tracer_provider (provider )
232
227
233
- toolsets_to_close : set [BaseToolset ] = set ()
234
-
235
228
@asynccontextmanager
236
229
async def internal_lifespan (app : FastAPI ):
237
- # Set up signal handlers for graceful shutdown
238
- original_sigterm = signal .getsignal (signal .SIGTERM )
239
- original_sigint = signal .getsignal (signal .SIGINT )
240
-
241
- def cleanup_handler (sig , frame ):
242
- # Log the signal
243
- logger .info ("Received signal %s, performing pre-shutdown cleanup" , sig )
244
- # Do synchronous cleanup if needed
245
- # Then call original handler if it exists
246
- if sig == signal .SIGTERM and callable (original_sigterm ):
247
- original_sigterm (sig , frame )
248
- elif sig == signal .SIGINT and callable (original_sigint ):
249
- original_sigint (sig , frame )
250
-
251
- # Install cleanup handlers
252
- signal .signal (signal .SIGTERM , cleanup_handler )
253
- signal .signal (signal .SIGINT , cleanup_handler )
254
230
255
231
try :
256
232
if lifespan :
@@ -259,46 +235,8 @@ def cleanup_handler(sig, frame):
259
235
else :
260
236
yield
261
237
finally :
262
- # During shutdown, properly clean up all toolsets
263
- logger .info (
264
- "Server shutdown initiated, cleaning up %s toolsets" ,
265
- len (toolsets_to_close ),
266
- )
267
-
268
- # Create tasks for all toolset closures to run concurrently
269
- cleanup_tasks = []
270
- for toolset in toolsets_to_close :
271
- task = asyncio .create_task (close_toolset_safely (toolset ))
272
- cleanup_tasks .append (task )
273
-
274
- if cleanup_tasks :
275
- # Wait for all cleanup tasks with timeout
276
- done , pending = await asyncio .wait (
277
- cleanup_tasks ,
278
- timeout = 10.0 , # 10 second timeout for cleanup
279
- return_when = asyncio .ALL_COMPLETED ,
280
- )
281
-
282
- # If any tasks are still pending, log it
283
- if pending :
284
- logger .warning (
285
- f"{ len (pending )} toolset cleanup tasks didn't complete in time"
286
- )
287
- for task in pending :
288
- task .cancel ()
289
-
290
- # Restore original signal handlers
291
- signal .signal (signal .SIGTERM , original_sigterm )
292
- signal .signal (signal .SIGINT , original_sigint )
293
-
294
- async def close_toolset_safely (toolset ):
295
- """Safely close a toolset with error handling."""
296
- try :
297
- logger .info (f"Closing toolset: { type (toolset ).__name__ } " )
298
- await toolset .close ()
299
- logger .info (f"Successfully closed toolset: { type (toolset ).__name__ } " )
300
- except Exception as e :
301
- logger .error (f"Error closing toolset { type (toolset ).__name__ } : { e } " )
238
+ # Create tasks for all runner closures to run concurrently
239
+ await cleanup .close_runners (list (runner_dict .values ()))
302
240
303
241
# Run the FastAPI server.
304
242
app = FastAPI (lifespan = internal_lifespan )
@@ -903,16 +841,6 @@ async def process_messages():
903
841
for task in pending :
904
842
task .cancel ()
905
843
906
- def _get_all_toolsets (agent : BaseAgent ) -> set [BaseToolset ]:
907
- toolsets = set ()
908
- if isinstance (agent , LlmAgent ):
909
- for tool_union in agent .tools :
910
- if isinstance (tool_union , BaseToolset ):
911
- toolsets .add (tool_union )
912
- for sub_agent in agent .sub_agents :
913
- toolsets .update (_get_all_toolsets (sub_agent ))
914
- return toolsets
915
-
916
844
async def _get_root_agent_async (app_name : str ) -> Agent :
917
845
"""Returns the root agent for the given app."""
918
846
if app_name in root_agent_dict :
@@ -924,7 +852,6 @@ async def _get_root_agent_async(app_name: str) -> Agent:
924
852
raise ValueError (f'Unable to find "root_agent" from { app_name } .' )
925
853
926
854
root_agent_dict [app_name ] = root_agent
927
- toolsets_to_close .update (_get_all_toolsets (root_agent ))
928
855
return root_agent
929
856
930
857
async def _get_runner_async (app_name : str ) -> Runner :
0 commit comments