2222import uuid
2323from dataclasses import dataclass
2424
25+ # Don't import FastAPI dependencies here - use lazy loading instead
26+
2527if TYPE_CHECKING :
2628 from ..task .task import Task
2729
30+ # Shared variables for API server
31+ _shared_app = None
32+ _server_started = False
33+ _registered_agents = {}
34+
2835@dataclass
2936class ChatCompletionMessage :
3037 content : str
@@ -1399,4 +1406,157 @@ async def execute_tool_async(self, function_name: str, arguments: Dict[str, Any]
13991406
14001407 except Exception as e :
14011408 logging .error (f"Error in execute_tool_async: { str (e )} " , exc_info = True )
1402- return {"error" : f"Error in execute_tool_async: { str (e )} " }
1409+ return {"error" : f"Error in execute_tool_async: { str (e )} " }
1410+
1411+ def launch (self , path : str = '/' , port : int = 8000 , host : str = '0.0.0.0' , autostart : bool = True , debug : bool = False , blocking : bool = True ):
1412+ """
1413+ Launch the agent as an HTTP API endpoint.
1414+
1415+ Args:
1416+ path: API endpoint path (default: '/')
1417+ port: Server port (default: 8000)
1418+ host: Server host (default: '0.0.0.0')
1419+ autostart: Whether to start the server automatically (default: True)
1420+ debug: Enable debug mode for uvicorn (default: False)
1421+ blocking: If True, blocks the main thread to keep the server running (default: True)
1422+
1423+ Returns:
1424+ None
1425+ """
1426+ global _server_started , _registered_agents , _shared_app
1427+
1428+ # Try to import FastAPI dependencies - lazy loading
1429+ try :
1430+ import uvicorn
1431+ from fastapi import FastAPI , HTTPException , Request
1432+ from fastapi .responses import JSONResponse
1433+ from pydantic import BaseModel
1434+
1435+ # Define the request model here since we need pydantic
1436+ class AgentQuery (BaseModel ):
1437+ query : str
1438+
1439+ except ImportError as e :
1440+ # Check which specific module is missing
1441+ missing_module = str (e ).split ("No module named '" )[- 1 ].rstrip ("'" )
1442+ display_error (f"Missing dependency: { missing_module } . Required for launch() method." )
1443+ logging .error (f"Missing dependency: { missing_module } . Required for launch() method." )
1444+ print (f"\n To add API capabilities, install the required dependencies:" )
1445+ print (f"pip install { missing_module } " )
1446+ print ("\n Or install all API dependencies with:" )
1447+ print ("pip install 'praisonaiagents[api]'" )
1448+ return None
1449+
1450+ # Initialize shared FastAPI app if not already created
1451+ if _shared_app is None :
1452+ _shared_app = FastAPI (
1453+ title = "PraisonAI Agents API" ,
1454+ description = "API for interacting with PraisonAI Agents"
1455+ )
1456+
1457+ # Add a root endpoint with a welcome message
1458+ @_shared_app .get ("/" )
1459+ async def root ():
1460+ return {"message" : "Welcome to PraisonAI Agents API. See /docs for usage." }
1461+
1462+ # Normalize path to ensure it starts with /
1463+ if not path .startswith ('/' ):
1464+ path = f'/{ path } '
1465+
1466+ # Check if path is already registered by another agent
1467+ if path in _registered_agents and _registered_agents [path ] != self .agent_id :
1468+ existing_agent = _registered_agents [path ]
1469+ logging .warning (f"Path '{ path } ' is already registered by another agent. Please use a different path." )
1470+ print (f"⚠️ Warning: Path '{ path } ' is already registered by another agent." )
1471+ # Use a modified path to avoid conflicts
1472+ original_path = path
1473+ path = f"{ path } _{ self .agent_id [:6 ]} "
1474+ logging .warning (f"Using '{ path } ' instead of '{ original_path } '" )
1475+ print (f"🔄 Using '{ path } ' instead" )
1476+
1477+ # Register the agent to this path
1478+ _registered_agents [path ] = self .agent_id
1479+
1480+ # Define the endpoint handler
1481+ @_shared_app .post (path )
1482+ async def handle_agent_query (request : Request , query_data : Optional [AgentQuery ] = None ):
1483+ # Handle both direct JSON with query field and form data
1484+ if query_data is None :
1485+ try :
1486+ request_data = await request .json ()
1487+ if "query" not in request_data :
1488+ raise HTTPException (status_code = 400 , detail = "Missing 'query' field in request" )
1489+ query = request_data ["query" ]
1490+ except :
1491+ # Fallback to form data or query params
1492+ form_data = await request .form ()
1493+ if "query" in form_data :
1494+ query = form_data ["query" ]
1495+ else :
1496+ raise HTTPException (status_code = 400 , detail = "Missing 'query' field in request" )
1497+ else :
1498+ query = query_data .query
1499+
1500+ try :
1501+ # Use async version if available, otherwise use sync version
1502+ if asyncio .iscoroutinefunction (self .chat ):
1503+ response = await self .achat (query )
1504+ else :
1505+ # Run sync function in a thread to avoid blocking
1506+ loop = asyncio .get_event_loop ()
1507+ response = await loop .run_in_executor (None , lambda : self .chat (query ))
1508+
1509+ return {"response" : response }
1510+ except Exception as e :
1511+ logging .error (f"Error processing query: { str (e )} " , exc_info = True )
1512+ return JSONResponse (
1513+ status_code = 500 ,
1514+ content = {"error" : f"Error processing query: { str (e )} " }
1515+ )
1516+
1517+ print (f"🚀 Agent '{ self .name } ' available at http://{ host } :{ port } { path } " )
1518+
1519+ # Start the server if this is the first launch call and autostart is True
1520+ if autostart and not _server_started :
1521+ _server_started = True
1522+
1523+ # Add healthcheck endpoint
1524+ @_shared_app .get ("/health" )
1525+ async def healthcheck ():
1526+ return {"status" : "ok" , "agents" : list (_registered_agents .keys ())}
1527+
1528+ # Start the server in a separate thread to not block execution
1529+ import threading
1530+ def run_server ():
1531+ try :
1532+ uvicorn .run (_shared_app , host = host , port = port , log_level = "debug" if debug else "info" )
1533+ except Exception as e :
1534+ logging .error (f"Error starting server: { str (e )} " , exc_info = True )
1535+ print (f"❌ Error starting server: { str (e )} " )
1536+
1537+ server_thread = threading .Thread (target = run_server , daemon = True )
1538+ server_thread .start ()
1539+
1540+ # Give the server a moment to start up
1541+ import time
1542+ time .sleep (0.5 )
1543+
1544+ print (f"✅ FastAPI server started at http://{ host } :{ port } " )
1545+ print (f"📚 API documentation available at http://{ host } :{ port } /docs" )
1546+
1547+ # If blocking is True, keep the main thread alive
1548+ if blocking :
1549+ print ("\n Server is running in blocking mode. Press Ctrl+C to stop..." )
1550+ try :
1551+ while True :
1552+ time .sleep (1 )
1553+ except KeyboardInterrupt :
1554+ print ("\n Server stopped" )
1555+ else :
1556+ # Note for non-blocking mode
1557+ print ("\n Note: Server is running in a background thread. To keep it alive, either:" )
1558+ print ("1. Set blocking=True when calling launch()" )
1559+ print ("2. Keep your main application running" )
1560+ print ("3. Use a loop in your code to prevent the program from exiting" )
1561+
1562+ return None
0 commit comments