praisonaiagents 0.0.80__py3-none-any.whl → 0.0.82__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,9 +23,9 @@ import uuid
23
23
  from dataclasses import dataclass
24
24
 
25
25
  # Global variables for API server
26
- _server_started = False
27
- _registered_agents = {}
28
- _shared_app = None
26
+ _server_started = {} # Dict of port -> started boolean
27
+ _registered_agents = {} # Dict of port -> Dict of path -> agent_id
28
+ _shared_apps = {} # Dict of port -> FastAPI app
29
29
 
30
30
  # Don't import FastAPI dependencies here - use lazy loading instead
31
31
 
@@ -1095,6 +1095,10 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
1095
1095
  start_time = time.time()
1096
1096
  reasoning_steps = reasoning_steps or self.reasoning_steps
1097
1097
  try:
1098
+ # Default to self.tools if tools argument is None
1099
+ if tools is None:
1100
+ tools = self.tools
1101
+
1098
1102
  # Search for existing knowledge if any knowledge is provided
1099
1103
  if self.knowledge:
1100
1104
  search_results = self.knowledge.search(prompt, agent_id=self.agent_id)
@@ -1408,181 +1412,329 @@ Your Goal: {self.goal}
1408
1412
  logging.error(f"Error in execute_tool_async: {str(e)}", exc_info=True)
1409
1413
  return {"error": f"Error in execute_tool_async: {str(e)}"}
1410
1414
 
1411
- def launch(self, path: str = '/', port: int = 8000, host: str = '0.0.0.0', debug: bool = False):
1415
+ def launch(self, path: str = '/', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = "http"):
1412
1416
  """
1413
- Launch the agent as an HTTP API endpoint.
1417
+ Launch the agent as an HTTP API endpoint or an MCP server.
1414
1418
 
1415
1419
  Args:
1416
- path: API endpoint path (default: '/')
1420
+ path: API endpoint path (default: '/') for HTTP, or base path for MCP.
1417
1421
  port: Server port (default: 8000)
1418
1422
  host: Server host (default: '0.0.0.0')
1419
1423
  debug: Enable debug mode for uvicorn (default: False)
1424
+ protocol: "http" to launch as FastAPI, "mcp" to launch as MCP server.
1420
1425
 
1421
1426
  Returns:
1422
1427
  None
1423
1428
  """
1424
- global _server_started, _registered_agents, _shared_app
1425
-
1426
- # Try to import FastAPI dependencies - lazy loading
1427
- try:
1428
- import uvicorn
1429
- from fastapi import FastAPI, HTTPException, Request
1430
- from fastapi.responses import JSONResponse
1431
- from pydantic import BaseModel
1432
- import threading
1433
- import time
1429
+ if protocol == "http":
1430
+ global _server_started, _registered_agents, _shared_apps
1434
1431
 
1435
- # Define the request model here since we need pydantic
1436
- class AgentQuery(BaseModel):
1437
- query: str
1432
+ # Try to import FastAPI dependencies - lazy loading
1433
+ try:
1434
+ import uvicorn
1435
+ from fastapi import FastAPI, HTTPException, Request
1436
+ from fastapi.responses import JSONResponse
1437
+ from pydantic import BaseModel
1438
+ import threading
1439
+ import time
1438
1440
 
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"\nTo add API capabilities, install the required dependencies:")
1445
- print(f"pip install {missing_module}")
1446
- print("\nOr 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
- )
1441
+ # Define the request model here since we need pydantic
1442
+ class AgentQuery(BaseModel):
1443
+ query: str
1444
+
1445
+ except ImportError as e:
1446
+ # Check which specific module is missing
1447
+ missing_module = str(e).split("No module named '")[-1].rstrip("'")
1448
+ display_error(f"Missing dependency: {missing_module}. Required for launch() method with HTTP mode.")
1449
+ logging.error(f"Missing dependency: {missing_module}. Required for launch() method with HTTP mode.")
1450
+ print(f"\nTo add API capabilities, install the required dependencies:")
1451
+ print(f"pip install {missing_module}")
1452
+ print("\nOr install all API dependencies with:")
1453
+ print("pip install 'praisonaiagents[api]'")
1454
+ return None
1455
+
1456
+ # Initialize port-specific collections if needed
1457
+ if port not in _registered_agents:
1458
+ _registered_agents[port] = {}
1459
+
1460
+ # Initialize shared FastAPI app if not already created for this port
1461
+ if _shared_apps.get(port) is None:
1462
+ _shared_apps[port] = FastAPI(
1463
+ title=f"PraisonAI Agents API (Port {port})",
1464
+ description="API for interacting with PraisonAI Agents"
1465
+ )
1466
+
1467
+ # Add a root endpoint with a welcome message
1468
+ @_shared_apps[port].get("/")
1469
+ async def root():
1470
+ return {
1471
+ "message": f"Welcome to PraisonAI Agents API on port {port}. See /docs for usage.",
1472
+ "endpoints": list(_registered_agents[port].keys())
1473
+ }
1474
+
1475
+ # Add healthcheck endpoint
1476
+ @_shared_apps[port].get("/health")
1477
+ async def healthcheck():
1478
+ return {
1479
+ "status": "ok",
1480
+ "endpoints": list(_registered_agents[port].keys())
1481
+ }
1456
1482
 
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."}
1483
+ # Normalize path to ensure it starts with /
1484
+ if not path.startswith('/'):
1485
+ path = f'/{path}'
1486
+
1487
+ # Check if path is already registered for this port
1488
+ if path in _registered_agents[port]:
1489
+ logging.warning(f"Path '{path}' is already registered on port {port}. Please use a different path.")
1490
+ print(f"⚠️ Warning: Path '{path}' is already registered on port {port}.")
1491
+ # Use a modified path to avoid conflicts
1492
+ original_path = path
1493
+ path = f"{path}_{self.agent_id[:6]}"
1494
+ logging.warning(f"Using '{path}' instead of '{original_path}'")
1495
+ print(f"🔄 Using '{path}' instead")
1461
1496
 
1462
- # Add healthcheck endpoint
1463
- @_shared_app.get("/health")
1464
- async def healthcheck():
1465
- return {"status": "ok", "agents": list(_registered_agents.keys())}
1466
-
1467
- # Normalize path to ensure it starts with /
1468
- if not path.startswith('/'):
1469
- path = f'/{path}'
1497
+ # Register the agent to this path
1498
+ _registered_agents[port][path] = self.agent_id
1470
1499
 
1471
- # Check if path is already registered by another agent
1472
- if path in _registered_agents and _registered_agents[path] != self.agent_id:
1473
- existing_agent = _registered_agents[path]
1474
- logging.warning(f"Path '{path}' is already registered by another agent. Please use a different path.")
1475
- print(f"⚠️ Warning: Path '{path}' is already registered by another agent.")
1476
- # Use a modified path to avoid conflicts
1477
- original_path = path
1478
- path = f"{path}_{self.agent_id[:6]}"
1479
- logging.warning(f"Using '{path}' instead of '{original_path}'")
1480
- print(f"🔄 Using '{path}' instead")
1481
-
1482
- # Register the agent to this path
1483
- _registered_agents[path] = self.agent_id
1484
-
1485
- # Define the endpoint handler
1486
- @_shared_app.post(path)
1487
- async def handle_agent_query(request: Request, query_data: Optional[AgentQuery] = None):
1488
- # Handle both direct JSON with query field and form data
1489
- if query_data is None:
1500
+ # Define the endpoint handler
1501
+ @_shared_apps[port].post(path)
1502
+ async def handle_agent_query(request: Request, query_data: Optional[AgentQuery] = None):
1503
+ # Handle both direct JSON with query field and form data
1504
+ if query_data is None:
1505
+ try:
1506
+ request_data = await request.json()
1507
+ if "query" not in request_data:
1508
+ raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1509
+ query = request_data["query"]
1510
+ except:
1511
+ # Fallback to form data or query params
1512
+ form_data = await request.form()
1513
+ if "query" in form_data:
1514
+ query = form_data["query"]
1515
+ else:
1516
+ raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1517
+ else:
1518
+ query = query_data.query
1519
+
1490
1520
  try:
1491
- request_data = await request.json()
1492
- if "query" not in request_data:
1493
- raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1494
- query = request_data["query"]
1495
- except:
1496
- # Fallback to form data or query params
1497
- form_data = await request.form()
1498
- if "query" in form_data:
1499
- query = form_data["query"]
1521
+ # Use async version if available, otherwise use sync version
1522
+ if asyncio.iscoroutinefunction(self.chat):
1523
+ response = await self.achat(query)
1500
1524
  else:
1501
- raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1502
- else:
1503
- query = query_data.query
1525
+ # Run sync function in a thread to avoid blocking
1526
+ loop = asyncio.get_event_loop()
1527
+ response = await loop.run_in_executor(None, lambda p=query: self.chat(p))
1528
+
1529
+ return {"response": response}
1530
+ except Exception as e:
1531
+ logging.error(f"Error processing query: {str(e)}", exc_info=True)
1532
+ return JSONResponse(
1533
+ status_code=500,
1534
+ content={"error": f"Error processing query: {str(e)}"}
1535
+ )
1536
+
1537
+ print(f"🚀 Agent '{self.name}' available at http://{host}:{port}")
1538
+
1539
+ # Start the server if it's not already running for this port
1540
+ if not _server_started.get(port, False):
1541
+ # Mark the server as started first to prevent duplicate starts
1542
+ _server_started[port] = True
1504
1543
 
1505
- try:
1506
- # Use async version if available, otherwise use sync version
1507
- if asyncio.iscoroutinefunction(self.chat):
1508
- response = await self.achat(query)
1509
- else:
1510
- # Run sync function in a thread to avoid blocking
1511
- loop = asyncio.get_event_loop()
1512
- response = await loop.run_in_executor(None, lambda: self.chat(query))
1544
+ # Start the server in a separate thread
1545
+ def run_server():
1546
+ try:
1547
+ print(f"✅ FastAPI server started at http://{host}:{port}")
1548
+ print(f"📚 API documentation available at http://{host}:{port}/docs")
1549
+ print(f"🔌 Available endpoints: {', '.join(list(_registered_agents[port].keys()))}")
1550
+ uvicorn.run(_shared_apps[port], host=host, port=port, log_level="debug" if debug else "info")
1551
+ except Exception as e:
1552
+ logging.error(f"Error starting server: {str(e)}", exc_info=True)
1553
+ print(f"❌ Error starting server: {str(e)}")
1513
1554
 
1514
- return {"response": response}
1515
- except Exception as e:
1516
- logging.error(f"Error processing query: {str(e)}", exc_info=True)
1517
- return JSONResponse(
1518
- status_code=500,
1519
- content={"error": f"Error processing query: {str(e)}"}
1520
- )
1521
-
1522
- print(f"🚀 Agent '{self.name}' available at http://{host}:{port}{path}")
1523
-
1524
- # Start the server if it's not already running
1525
- if not _server_started:
1526
- _server_started = True
1555
+ # Run server in a background thread
1556
+ server_thread = threading.Thread(target=run_server, daemon=True)
1557
+ server_thread.start()
1558
+
1559
+ # Wait for a moment to allow the server to start and register endpoints
1560
+ time.sleep(0.5)
1561
+ else:
1562
+ # If server is already running, wait a moment to make sure the endpoint is registered
1563
+ time.sleep(0.1)
1564
+ print(f"🔌 Available endpoints on port {port}: {', '.join(list(_registered_agents[port].keys()))}")
1565
+
1566
+ # Get the stack frame to check if this is the last launch() call in the script
1567
+ import inspect
1568
+ stack = inspect.stack()
1527
1569
 
1528
- # Start the server in a separate thread
1529
- def run_server():
1570
+ # If this is called from a Python script (not interactive), try to detect if it's the last launch call
1571
+ if len(stack) > 1 and stack[1].filename.endswith('.py'):
1572
+ caller_frame = stack[1]
1573
+ caller_line = caller_frame.lineno
1574
+
1530
1575
  try:
1531
- print(f"✅ FastAPI server started at http://{host}:{port}")
1532
- print(f"📚 API documentation available at http://{host}:{port}/docs")
1533
- print(f"🔌 Available endpoints: {', '.join(list(_registered_agents.keys()))}")
1534
- uvicorn.run(_shared_app, host=host, port=port, log_level="debug" if debug else "info")
1576
+ # Read the file to check if there are more launch calls after this one
1577
+ with open(caller_frame.filename, 'r') as f:
1578
+ lines = f.readlines()
1579
+
1580
+ # Check if there are more launch() calls after the current line
1581
+ has_more_launches = False
1582
+ for line_content in lines[caller_line:]: # renamed line to line_content
1583
+ if '.launch(' in line_content and not line_content.strip().startswith('#'):
1584
+ has_more_launches = True
1585
+ break
1586
+
1587
+ # If this is the last launch call, block the main thread
1588
+ if not has_more_launches:
1589
+ try:
1590
+ print("\nAll agents registered for HTTP mode. Press Ctrl+C to stop the servers.")
1591
+ while True:
1592
+ time.sleep(1)
1593
+ except KeyboardInterrupt:
1594
+ print("\nServers stopped")
1535
1595
  except Exception as e:
1536
- logging.error(f"Error starting server: {str(e)}", exc_info=True)
1537
- print(f"Error starting server: {str(e)}")
1538
-
1539
- # Run server in a background thread
1540
- server_thread = threading.Thread(target=run_server, daemon=True)
1541
- server_thread.start()
1542
-
1543
- # Wait for a moment to allow the server to start and register endpoints
1544
- time.sleep(0.5)
1545
- else:
1546
- # If server is already running, wait a moment to make sure the endpoint is registered
1547
- time.sleep(0.1)
1548
- print(f"🔌 Available endpoints: {', '.join(list(_registered_agents.keys()))}")
1549
-
1550
- # Get the stack frame to check if this is the last launch() call in the script
1551
- import inspect
1552
- stack = inspect.stack()
1553
-
1554
- # If this is called from a Python script (not interactive), try to detect if it's the last launch call
1555
- if len(stack) > 1 and stack[1].filename.endswith('.py'):
1556
- caller_frame = stack[1]
1557
- caller_line = caller_frame.lineno
1596
+ # If something goes wrong with detection, block anyway to be safe
1597
+ logging.error(f"Error in launch detection: {e}")
1598
+ try:
1599
+ print("\nKeeping HTTP servers alive. Press Ctrl+C to stop.")
1600
+ while True:
1601
+ time.sleep(1)
1602
+ except KeyboardInterrupt:
1603
+ print("\nServers stopped")
1604
+ return None
1558
1605
 
1606
+ elif protocol == "mcp":
1559
1607
  try:
1560
- # Read the file to check if there are more launch calls after this one
1561
- with open(caller_frame.filename, 'r') as f:
1562
- lines = f.readlines()
1608
+ import uvicorn
1609
+ from mcp.server.fastmcp import FastMCP
1610
+ from mcp.server.sse import SseServerTransport
1611
+ from starlette.applications import Starlette
1612
+ from starlette.requests import Request
1613
+ from starlette.routing import Mount, Route
1614
+ from mcp.server import Server as MCPServer # Alias to avoid conflict
1615
+ import threading
1616
+ import time
1617
+ import inspect
1618
+ import asyncio # Ensure asyncio is imported
1619
+ # logging is already imported at the module level
1563
1620
 
1564
- # Check if there are more launch() calls after the current line
1565
- has_more_launches = False
1566
- for line in lines[caller_line:]:
1567
- if '.launch(' in line and not line.strip().startswith('#'):
1568
- has_more_launches = True
1569
- break
1570
-
1571
- # If this is the last launch call, block the main thread
1572
- if not has_more_launches:
1621
+ except ImportError as e:
1622
+ missing_module = str(e).split("No module named '")[-1].rstrip("'")
1623
+ display_error(f"Missing dependency: {missing_module}. Required for launch() method with MCP mode.")
1624
+ logging.error(f"Missing dependency: {missing_module}. Required for launch() method with MCP mode.")
1625
+ print(f"\nTo add MCP capabilities, install the required dependencies:")
1626
+ print(f"pip install {missing_module} mcp praison-mcp starlette uvicorn") # Added mcp, praison-mcp, starlette, uvicorn
1627
+ print("\nOr install all MCP dependencies with relevant packages.")
1628
+ return None
1629
+
1630
+ mcp_server_instance_name = f"{self.name}_mcp_server" if self.name else "agent_mcp_server"
1631
+ mcp = FastMCP(mcp_server_instance_name)
1632
+
1633
+ # Determine the MCP tool name based on self.name
1634
+ actual_mcp_tool_name = f"execute_{self.name.lower().replace(' ', '_').replace('-', '_')}_task" if self.name \
1635
+ else "execute_task"
1636
+
1637
+ @mcp.tool(name=actual_mcp_tool_name)
1638
+ async def execute_agent_task(prompt: str) -> str:
1639
+ """Executes the agent's primary task with the given prompt."""
1640
+ logging.info(f"MCP tool '{actual_mcp_tool_name}' called with prompt: {prompt}")
1641
+ try:
1642
+ # Ensure self.achat is used as it's the async version and pass its tools
1643
+ if hasattr(self, 'achat') and asyncio.iscoroutinefunction(self.achat):
1644
+ response = await self.achat(prompt, tools=self.tools)
1645
+ elif hasattr(self, 'chat'): # Fallback for synchronous chat
1646
+ loop = asyncio.get_event_loop()
1647
+ response = await loop.run_in_executor(None, lambda p=prompt: self.chat(p, tools=self.tools))
1648
+ else:
1649
+ logging.error(f"Agent {self.name} has no suitable chat or achat method for MCP tool.")
1650
+ return f"Error: Agent {self.name} misconfigured for MCP."
1651
+ return response if response is not None else "Agent returned no response."
1652
+ except Exception as e:
1653
+ logging.error(f"Error in MCP tool '{actual_mcp_tool_name}': {e}", exc_info=True)
1654
+ return f"Error executing task: {str(e)}"
1655
+
1656
+ # Normalize base_path for MCP routes
1657
+ base_path = path.rstrip('/')
1658
+ sse_path = f"{base_path}/sse"
1659
+ messages_path_prefix = f"{base_path}/messages" # Prefix for message posting
1660
+
1661
+ # Ensure messages_path ends with a slash for Mount
1662
+ if not messages_path_prefix.endswith('/'):
1663
+ messages_path_prefix += '/'
1664
+
1665
+
1666
+ sse_transport = SseServerTransport(messages_path_prefix) # Pass the full prefix
1667
+
1668
+ async def handle_sse_connection(request: Request) -> None:
1669
+ logging.debug(f"SSE connection request received from {request.client} for path {request.url.path}")
1670
+ async with sse_transport.connect_sse(
1671
+ request.scope,
1672
+ request.receive,
1673
+ request._send, # noqa: SLF001
1674
+ ) as (read_stream, write_stream):
1675
+ await mcp._mcp_server.run( # Use the underlying server from FastMCP
1676
+ read_stream,
1677
+ write_stream,
1678
+ mcp._mcp_server.create_initialization_options(),
1679
+ )
1680
+
1681
+ starlette_app = Starlette(
1682
+ debug=debug,
1683
+ routes=[
1684
+ Route(sse_path, endpoint=handle_sse_connection),
1685
+ Mount(messages_path_prefix, app=sse_transport.handle_post_message),
1686
+ ],
1687
+ )
1688
+
1689
+ print(f"🚀 Agent '{self.name}' MCP server starting on http://{host}:{port}")
1690
+ print(f"📡 MCP SSE endpoint available at {sse_path}")
1691
+ print(f"📢 MCP messages post to {messages_path_prefix}")
1692
+ # Instead of trying to extract tool names, hardcode the known tool name
1693
+ tool_names = [actual_mcp_tool_name] # Use the determined dynamic tool name
1694
+ print(f"🛠️ Available MCP tools: {', '.join(tool_names)}")
1695
+
1696
+ # Uvicorn server running logic (similar to HTTP mode but standalone for MCP)
1697
+ def run_mcp_server():
1698
+ try:
1699
+ uvicorn.run(starlette_app, host=host, port=port, log_level="debug" if debug else "info")
1700
+ except Exception as e:
1701
+ logging.error(f"Error starting MCP server: {str(e)}", exc_info=True)
1702
+ print(f"❌ Error starting MCP server: {str(e)}")
1703
+
1704
+ server_thread = threading.Thread(target=run_mcp_server, daemon=True)
1705
+ server_thread.start()
1706
+ time.sleep(0.5) # Allow server to start
1707
+
1708
+ # Blocking logic for MCP mode
1709
+ import inspect # Already imported but good for clarity
1710
+ stack = inspect.stack()
1711
+ if len(stack) > 1 and stack[1].filename.endswith('.py'):
1712
+ caller_frame = stack[1]
1713
+ caller_line = caller_frame.lineno
1714
+ try:
1715
+ with open(caller_frame.filename, 'r') as f:
1716
+ lines = f.readlines()
1717
+ has_more_launches = False
1718
+ for line_content in lines[caller_line:]: # renamed line to line_content
1719
+ if '.launch(' in line_content and not line_content.strip().startswith('#'):
1720
+ has_more_launches = True
1721
+ break
1722
+ if not has_more_launches:
1723
+ try:
1724
+ print("\nAgent MCP server running. Press Ctrl+C to stop.")
1725
+ while True:
1726
+ time.sleep(1)
1727
+ except KeyboardInterrupt:
1728
+ print("\nMCP Server stopped")
1729
+ except Exception as e:
1730
+ logging.error(f"Error in MCP launch detection: {e}")
1573
1731
  try:
1574
- print("\nAll agents registered. Press Ctrl+C to stop the server.")
1732
+ print("\nKeeping MCP server alive. Press Ctrl+C to stop.")
1575
1733
  while True:
1576
1734
  time.sleep(1)
1577
1735
  except KeyboardInterrupt:
1578
- print("\nServer stopped")
1579
- except Exception as e:
1580
- # If something goes wrong with detection, block anyway to be safe
1581
- logging.error(f"Error in launch detection: {e}")
1582
- try:
1583
- while True:
1584
- time.sleep(1)
1585
- except KeyboardInterrupt:
1586
- print("\nServer stopped")
1587
-
1588
- return None
1736
+ print("\nMCP Server stopped")
1737
+ return None
1738
+ else:
1739
+ display_error(f"Invalid protocol: {protocol}. Choose 'http' or 'mcp'.")
1740
+ return None
@@ -50,7 +50,7 @@ def process_video(video_path: str, seconds_per_frame=2):
50
50
  return base64_frames
51
51
 
52
52
  class PraisonAIAgents:
53
- def __init__(self, agents, tasks=None, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None, memory=False, memory_config=None, embedder=None, user_id=None, max_iter=10, stream=True):
53
+ def __init__(self, agents, tasks=None, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None, memory=False, memory_config=None, embedder=None, user_id=None, max_iter=10, stream=True, name: Optional[str] = None):
54
54
  # Add check at the start if memory is requested
55
55
  if memory:
56
56
  try:
@@ -83,6 +83,7 @@ class PraisonAIAgents:
83
83
  self.max_retries = max_retries
84
84
  self.process = process
85
85
  self.stream = stream
86
+ self.name = name # Store the name for the Agents collection
86
87
 
87
88
  # Check for manager_llm in environment variable if not provided
88
89
  self.manager_llm = manager_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
@@ -885,228 +886,381 @@ Context:
885
886
  """Clear all state values"""
886
887
  self._state.clear()
887
888
 
888
- def launch(self, path: str = '/agents', port: int = 8000, host: str = '0.0.0.0', debug: bool = False):
889
+ def launch(self, path: str = '/agents', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = "http"):
889
890
  """
890
- Launch all agents as a single API endpoint. The endpoint accepts a query and processes it through
891
- all agents in sequence, with the output of each agent feeding into the next.
891
+ Launch all agents as a single API endpoint (HTTP) or an MCP server.
892
+ In HTTP mode, the endpoint accepts a query and processes it through all agents in sequence.
893
+ In MCP mode, an MCP server is started, exposing a tool to run the agent workflow.
892
894
 
893
895
  Args:
894
- path: API endpoint path (default: '/agents')
896
+ path: API endpoint path (default: '/agents') for HTTP, or base path for MCP.
895
897
  port: Server port (default: 8000)
896
898
  host: Server host (default: '0.0.0.0')
897
899
  debug: Enable debug mode for uvicorn (default: False)
900
+ protocol: "http" to launch as FastAPI, "mcp" to launch as MCP server.
898
901
 
899
902
  Returns:
900
903
  None
901
904
  """
902
- global _agents_server_started, _agents_registered_endpoints, _agents_shared_apps
903
-
904
- if not self.agents:
905
- logging.warning("No agents to launch. Add agents to the Agents instance first.")
906
- return
905
+ if protocol == "http":
906
+ global _agents_server_started, _agents_registered_endpoints, _agents_shared_apps
907
907
 
908
- # Try to import FastAPI dependencies - lazy loading
909
- try:
910
- import uvicorn
911
- from fastapi import FastAPI, HTTPException, Request
912
- from fastapi.responses import JSONResponse
913
- from pydantic import BaseModel
914
- import threading
915
- import time
916
-
917
- # Define the request model here since we need pydantic
918
- class AgentQuery(BaseModel):
919
- query: str
908
+ if not self.agents:
909
+ logging.warning("No agents to launch for HTTP mode. Add agents to the Agents instance first.")
910
+ return
920
911
 
921
- except ImportError as e:
922
- # Check which specific module is missing
923
- missing_module = str(e).split("No module named '")[-1].rstrip("'")
924
- display_error(f"Missing dependency: {missing_module}. Required for launch() method.")
925
- logging.error(f"Missing dependency: {missing_module}. Required for launch() method.")
926
- print(f"\nTo add API capabilities, install the required dependencies:")
927
- print(f"pip install {missing_module}")
928
- print("\nOr install all API dependencies with:")
929
- print("pip install 'praisonaiagents[api]'")
930
- return None
931
-
932
- # Initialize port-specific collections if needed
933
- if port not in _agents_registered_endpoints:
934
- _agents_registered_endpoints[port] = {}
912
+ # Try to import FastAPI dependencies - lazy loading
913
+ try:
914
+ import uvicorn
915
+ from fastapi import FastAPI, HTTPException, Request
916
+ from fastapi.responses import JSONResponse
917
+ from pydantic import BaseModel
918
+ import threading
919
+ import time
920
+ import asyncio # Ensure asyncio is imported for HTTP mode too
921
+
922
+ # Define the request model here since we need pydantic
923
+ class AgentQuery(BaseModel):
924
+ query: str
925
+
926
+ except ImportError as e:
927
+ # Check which specific module is missing
928
+ missing_module = str(e).split("No module named '")[-1].rstrip("'")
929
+ display_error(f"Missing dependency: {missing_module}. Required for launch() method with HTTP mode.")
930
+ logging.error(f"Missing dependency: {missing_module}. Required for launch() method with HTTP mode.")
931
+ print(f"\nTo add API capabilities, install the required dependencies:")
932
+ print(f"pip install {missing_module}")
933
+ print("\nOr install all API dependencies with:")
934
+ print("pip install 'praisonaiagents[api]'")
935
+ return None
935
936
 
936
- # Initialize shared FastAPI app if not already created for this port
937
- if _agents_shared_apps.get(port) is None:
938
- _agents_shared_apps[port] = FastAPI(
939
- title=f"PraisonAI Agents API (Port {port})",
940
- description="API for interacting with multiple PraisonAI Agents"
941
- )
937
+ # Initialize port-specific collections if needed
938
+ if port not in _agents_registered_endpoints:
939
+ _agents_registered_endpoints[port] = {}
940
+
941
+ # Initialize shared FastAPI app if not already created for this port
942
+ if _agents_shared_apps.get(port) is None:
943
+ _agents_shared_apps[port] = FastAPI(
944
+ title=f"PraisonAI Agents API (Port {port})",
945
+ description="API for interacting with multiple PraisonAI Agents"
946
+ )
947
+
948
+ # Add a root endpoint with a welcome message
949
+ @_agents_shared_apps[port].get("/")
950
+ async def root():
951
+ return {
952
+ "message": f"Welcome to PraisonAI Agents API on port {port}. See /docs for usage.",
953
+ "endpoints": list(_agents_registered_endpoints[port].keys())
954
+ }
955
+
956
+ # Add healthcheck endpoint
957
+ @_agents_shared_apps[port].get("/health")
958
+ async def healthcheck():
959
+ return {
960
+ "status": "ok",
961
+ "endpoints": list(_agents_registered_endpoints[port].keys())
962
+ }
942
963
 
943
- # Add a root endpoint with a welcome message
944
- @_agents_shared_apps[port].get("/")
945
- async def root():
946
- return {
947
- "message": f"Welcome to PraisonAI Agents API on port {port}. See /docs for usage.",
948
- "endpoints": list(_agents_registered_endpoints[port].keys())
949
- }
964
+ # Normalize path to ensure it starts with /
965
+ if not path.startswith('/'):
966
+ path = f'/{path}'
967
+
968
+ # Check if path is already registered for this port
969
+ if path in _agents_registered_endpoints[port]:
970
+ logging.warning(f"Path '{path}' is already registered on port {port}. Please use a different path.")
971
+ print(f"⚠️ Warning: Path '{path}' is already registered on port {port}.")
972
+ # Use a modified path to avoid conflicts
973
+ original_path = path
974
+ instance_id = str(uuid.uuid4())[:6]
975
+ path = f"{path}_{instance_id}"
976
+ logging.warning(f"Using '{path}' instead of '{original_path}'")
977
+ print(f"🔄 Using '{path}' instead")
950
978
 
951
- # Add healthcheck endpoint
952
- @_agents_shared_apps[port].get("/health")
953
- async def healthcheck():
954
- return {
955
- "status": "ok",
956
- "endpoints": list(_agents_registered_endpoints[port].keys())
957
- }
958
-
959
- # Normalize path to ensure it starts with /
960
- if not path.startswith('/'):
961
- path = f'/{path}'
979
+ # Generate a unique ID for this agent group's endpoint
980
+ endpoint_id = str(uuid.uuid4())
981
+ _agents_registered_endpoints[port][path] = endpoint_id
962
982
 
963
- # Check if path is already registered for this port
964
- if path in _agents_registered_endpoints[port]:
965
- logging.warning(f"Path '{path}' is already registered on port {port}. Please use a different path.")
966
- print(f"⚠️ Warning: Path '{path}' is already registered on port {port}.")
967
- # Use a modified path to avoid conflicts
968
- original_path = path
969
- instance_id = str(uuid.uuid4())[:6]
970
- path = f"{path}_{instance_id}"
971
- logging.warning(f"Using '{path}' instead of '{original_path}'")
972
- print(f"🔄 Using '{path}' instead")
973
-
974
- # Generate a unique ID for this agent group's endpoint
975
- endpoint_id = str(uuid.uuid4())
976
- _agents_registered_endpoints[port][path] = endpoint_id
977
-
978
- # Define the endpoint handler
979
- @_agents_shared_apps[port].post(path)
980
- async def handle_query(request: Request, query_data: Optional[AgentQuery] = None):
981
- # Handle both direct JSON with query field and form data
982
- if query_data is None:
983
+ # Define the endpoint handler
984
+ @_agents_shared_apps[port].post(path)
985
+ async def handle_query(request: Request, query_data: Optional[AgentQuery] = None):
986
+ # Handle both direct JSON with query field and form data
987
+ if query_data is None:
988
+ try:
989
+ request_data = await request.json()
990
+ if "query" not in request_data:
991
+ raise HTTPException(status_code=400, detail="Missing 'query' field in request")
992
+ query = request_data["query"]
993
+ except:
994
+ # Fallback to form data or query params
995
+ form_data = await request.form()
996
+ if "query" in form_data:
997
+ query = form_data["query"]
998
+ else:
999
+ raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1000
+ else:
1001
+ query = query_data.query
1002
+
983
1003
  try:
984
- request_data = await request.json()
985
- if "query" not in request_data:
986
- raise HTTPException(status_code=400, detail="Missing 'query' field in request")
987
- query = request_data["query"]
988
- except:
989
- # Fallback to form data or query params
990
- form_data = await request.form()
991
- if "query" in form_data:
992
- query = form_data["query"]
993
- else:
994
- raise HTTPException(status_code=400, detail="Missing 'query' field in request")
1004
+ # Process the query sequentially through all agents
1005
+ current_input = query
1006
+ results = []
1007
+
1008
+ for agent_instance in self.agents: # Corrected variable name to agent_instance
1009
+ try:
1010
+ # Use async version if available, otherwise use sync version
1011
+ if asyncio.iscoroutinefunction(agent_instance.chat):
1012
+ response = await agent_instance.achat(current_input)
1013
+ else:
1014
+ # Run sync function in a thread to avoid blocking
1015
+ loop = asyncio.get_event_loop()
1016
+ # Correctly pass current_input to the lambda for closure
1017
+ response = await loop.run_in_executor(None, lambda ci=current_input: agent_instance.chat(ci))
1018
+
1019
+ # Store this agent's result
1020
+ results.append({
1021
+ "agent": agent_instance.name,
1022
+ "response": response
1023
+ })
1024
+
1025
+ # Use this response as input to the next agent
1026
+ current_input = response
1027
+ except Exception as e:
1028
+ logging.error(f"Error with agent {agent_instance.name}: {str(e)}", exc_info=True)
1029
+ results.append({
1030
+ "agent": agent_instance.name,
1031
+ "error": str(e)
1032
+ })
1033
+ # Decide error handling: continue with original input, last good input, or stop?
1034
+ # For now, let's continue with the last successful 'current_input' or original 'query' if first agent fails
1035
+ # This part might need refinement based on desired behavior.
1036
+ # If an agent fails, its 'response' might be None or an error string.
1037
+ # current_input will carry that forward. Or, we could choose to halt or use last good input.
1038
+
1039
+ # Return all results and the final output
1040
+ return {
1041
+ "query": query,
1042
+ "results": results,
1043
+ "final_response": current_input
1044
+ }
1045
+ except Exception as e:
1046
+ logging.error(f"Error processing query: {str(e)}", exc_info=True)
1047
+ return JSONResponse(
1048
+ status_code=500,
1049
+ content={"error": f"Error processing query: {str(e)}"}
1050
+ )
1051
+
1052
+ print(f"🚀 Multi-Agent HTTP API available at http://{host}:{port}{path}")
1053
+ agent_names = ", ".join([agent.name for agent in self.agents])
1054
+ print(f"📊 Available agents for this endpoint ({len(self.agents)}): {agent_names}")
1055
+
1056
+ # Start the server if it's not already running for this port
1057
+ if not _agents_server_started.get(port, False):
1058
+ # Mark the server as started first to prevent duplicate starts
1059
+ _agents_server_started[port] = True
1060
+
1061
+ # Start the server in a separate thread
1062
+ def run_server():
1063
+ try:
1064
+ print(f"✅ FastAPI server started at http://{host}:{port}")
1065
+ print(f"📚 API documentation available at http://{host}:{port}/docs")
1066
+ print(f"🔌 Registered HTTP endpoints on port {port}: {', '.join(list(_agents_registered_endpoints[port].keys()))}")
1067
+ uvicorn.run(_agents_shared_apps[port], host=host, port=port, log_level="debug" if debug else "info")
1068
+ except Exception as e:
1069
+ logging.error(f"Error starting server: {str(e)}", exc_info=True)
1070
+ print(f"❌ Error starting server: {str(e)}")
1071
+
1072
+ # Run server in a background thread
1073
+ server_thread = threading.Thread(target=run_server, daemon=True)
1074
+ server_thread.start()
1075
+
1076
+ # Wait for a moment to allow the server to start and register endpoints
1077
+ time.sleep(0.5)
995
1078
  else:
996
- query = query_data.query
1079
+ # If server is already running, wait a moment to make sure the endpoint is registered
1080
+ time.sleep(0.1)
1081
+ print(f"🔌 Registered HTTP endpoints on port {port}: {', '.join(list(_agents_registered_endpoints[port].keys()))}")
1082
+
1083
+ # Get the stack frame to check if this is the last launch() call in the script
1084
+ import inspect
1085
+ stack = inspect.stack()
997
1086
 
1087
+ # If this is called from a Python script (not interactive), try to detect if it's the last launch call
1088
+ if len(stack) > 1 and stack[1].filename.endswith('.py'):
1089
+ caller_frame = stack[1]
1090
+ caller_line = caller_frame.lineno
1091
+
1092
+ try:
1093
+ # Read the file to check if there are more launch calls after this one
1094
+ with open(caller_frame.filename, 'r') as f:
1095
+ lines = f.readlines()
1096
+
1097
+ # Check if there are more launch() calls after the current line
1098
+ has_more_launches = False
1099
+ for line_content in lines[caller_line:]: # Renamed variable
1100
+ if '.launch(' in line_content and not line_content.strip().startswith('#'):
1101
+ has_more_launches = True
1102
+ break
1103
+
1104
+ # If this is the last launch call, block the main thread
1105
+ if not has_more_launches:
1106
+ try:
1107
+ print("\nAll agent groups registered for HTTP mode. Press Ctrl+C to stop the servers.")
1108
+ while True:
1109
+ time.sleep(1)
1110
+ except KeyboardInterrupt:
1111
+ print("\nServers stopped")
1112
+ except Exception as e:
1113
+ # If something goes wrong with detection, block anyway to be safe
1114
+ logging.error(f"Error in HTTP launch detection: {e}")
1115
+ try:
1116
+ print("\nKeeping HTTP servers alive. Press Ctrl+C to stop.")
1117
+ while True:
1118
+ time.sleep(1)
1119
+ except KeyboardInterrupt:
1120
+ print("\nServers stopped")
1121
+ return None
1122
+
1123
+ elif protocol == "mcp":
1124
+ if not self.agents:
1125
+ logging.warning("No agents to launch for MCP mode. Add agents to the Agents instance first.")
1126
+ return
1127
+
998
1128
  try:
999
- # Process the query sequentially through all agents
1000
- current_input = query
1001
- results = []
1129
+ import uvicorn
1130
+ from mcp.server.fastmcp import FastMCP
1131
+ from mcp.server.sse import SseServerTransport
1132
+ from starlette.applications import Starlette
1133
+ from starlette.requests import Request
1134
+ from starlette.routing import Mount, Route
1135
+ # from mcp.server import Server as MCPServer # Not directly needed if using FastMCP's server
1136
+ import threading
1137
+ import time
1138
+ import inspect
1139
+ import asyncio
1140
+ # logging is already imported at the module level
1002
1141
 
1003
- for agent in self.agents:
1142
+ except ImportError as e:
1143
+ missing_module = str(e).split("No module named '")[-1].rstrip("'")
1144
+ display_error(f"Missing dependency: {missing_module}. Required for launch() method with MCP mode.")
1145
+ logging.error(f"Missing dependency: {missing_module}. Required for launch() method with MCP mode.")
1146
+ print(f"\nTo add MCP capabilities, install the required dependencies:")
1147
+ print(f"pip install {missing_module} mcp praison-mcp starlette uvicorn")
1148
+ print("\nOr install all MCP dependencies with relevant packages.")
1149
+ return None
1150
+
1151
+ mcp_instance = FastMCP("praisonai_workflow_mcp_server")
1152
+
1153
+ # Determine the MCP tool name for the workflow based on self.name
1154
+ actual_mcp_tool_name = (f"execute_{self.name.lower().replace(' ', '_').replace('-', '_')}_workflow" if self.name
1155
+ else "execute_workflow")
1156
+
1157
+ @mcp_instance.tool(name=actual_mcp_tool_name)
1158
+ async def execute_workflow_tool(query: str) -> str: # Renamed for clarity
1159
+ """Executes the defined agent workflow with the given query."""
1160
+ logging.info(f"MCP tool '{actual_mcp_tool_name}' called with query: {query}")
1161
+ current_input = query
1162
+ final_response = "No agents in workflow or workflow did not produce a final response."
1163
+
1164
+ for agent_instance in self.agents:
1004
1165
  try:
1005
- # Use async version if available, otherwise use sync version
1006
- if asyncio.iscoroutinefunction(agent.chat):
1007
- response = await agent.achat(current_input)
1008
- else:
1009
- # Run sync function in a thread to avoid blocking
1166
+ logging.debug(f"Processing with agent: {agent_instance.name}")
1167
+ if hasattr(agent_instance, 'achat') and asyncio.iscoroutinefunction(agent_instance.achat):
1168
+ response = await agent_instance.achat(current_input, tools=agent_instance.tools)
1169
+ elif hasattr(agent_instance, 'chat'): # Fallback to sync chat if achat not suitable
1010
1170
  loop = asyncio.get_event_loop()
1011
- response = await loop.run_in_executor(None, lambda: agent.chat(current_input))
1012
-
1013
- # Store this agent's result
1014
- results.append({
1015
- "agent": agent.name,
1016
- "response": response
1017
- })
1171
+ response = await loop.run_in_executor(None, lambda ci=current_input: agent_instance.chat(ci, tools=agent_instance.tools))
1172
+ else:
1173
+ logging.warning(f"Agent {agent_instance.name} has no suitable chat or achat method.")
1174
+ response = f"Error: Agent {agent_instance.name} has no callable chat method."
1018
1175
 
1019
- # Use this response as input to the next agent
1020
- current_input = response
1176
+ current_input = response if response is not None else "Agent returned no response."
1177
+ final_response = current_input # Keep track of the last valid response
1178
+ logging.debug(f"Agent {agent_instance.name} responded. Current intermediate output: {current_input}")
1179
+
1021
1180
  except Exception as e:
1022
- logging.error(f"Error with agent {agent.name}: {str(e)}", exc_info=True)
1023
- results.append({
1024
- "agent": agent.name,
1025
- "error": str(e)
1026
- })
1027
- # Continue with original input if there's an error
1181
+ logging.error(f"Error during agent {agent_instance.name} execution in MCP workflow: {str(e)}", exc_info=True)
1182
+ current_input = f"Error from agent {agent_instance.name}: {str(e)}"
1183
+ final_response = current_input # Update final response to show error
1184
+ # Optionally break or continue based on desired error handling for the workflow
1185
+ # For now, we continue, and the error is passed to the next agent or returned.
1028
1186
 
1029
- # Return all results and the final output
1030
- return {
1031
- "query": query,
1032
- "results": results,
1033
- "final_response": current_input
1034
- }
1035
- except Exception as e:
1036
- logging.error(f"Error processing query: {str(e)}", exc_info=True)
1037
- return JSONResponse(
1038
- status_code=500,
1039
- content={"error": f"Error processing query: {str(e)}"}
1040
- )
1041
-
1042
- print(f"🚀 Multi-Agent API available at http://{host}:{port}{path}")
1043
- agent_names = ", ".join([agent.name for agent in self.agents])
1044
- print(f"📊 Available agents ({len(self.agents)}): {agent_names}")
1045
-
1046
- # Start the server if it's not already running for this port
1047
- if not _agents_server_started.get(port, False):
1048
- # Mark the server as started first to prevent duplicate starts
1049
- _agents_server_started[port] = True
1187
+ logging.info(f"MCP tool '{actual_mcp_tool_name}' completed. Final response: {final_response}")
1188
+ return final_response
1189
+
1190
+ base_mcp_path = path.rstrip('/')
1191
+ sse_mcp_path = f"{base_mcp_path}/sse"
1192
+ messages_mcp_path_prefix = f"{base_mcp_path}/messages"
1193
+ if not messages_mcp_path_prefix.endswith('/'):
1194
+ messages_mcp_path_prefix += '/'
1195
+
1196
+ sse_transport_mcp = SseServerTransport(messages_mcp_path_prefix)
1197
+
1198
+ async def handle_mcp_sse_connection(request: Request) -> None:
1199
+ logging.debug(f"MCP SSE connection request from {request.client} for path {request.url.path}")
1200
+ async with sse_transport_mcp.connect_sse(
1201
+ request.scope, request.receive, request._send,
1202
+ ) as (read_stream, write_stream):
1203
+ await mcp_instance._mcp_server.run(
1204
+ read_stream, write_stream, mcp_instance._mcp_server.create_initialization_options(),
1205
+ )
1050
1206
 
1051
- # Start the server in a separate thread
1052
- def run_server():
1207
+ starlette_mcp_app = Starlette(
1208
+ debug=debug,
1209
+ routes=[
1210
+ Route(sse_mcp_path, endpoint=handle_mcp_sse_connection),
1211
+ Mount(messages_mcp_path_prefix, app=sse_transport_mcp.handle_post_message),
1212
+ ],
1213
+ )
1214
+
1215
+ print(f"🚀 PraisonAIAgents MCP Workflow server starting on http://{host}:{port}")
1216
+ print(f"📡 MCP SSE endpoint available at {sse_mcp_path}")
1217
+ print(f"📢 MCP messages post to {messages_mcp_path_prefix}")
1218
+ # Instead of trying to extract tool names, hardcode the known tool name
1219
+ mcp_tool_names = [actual_mcp_tool_name] # Use the determined dynamic tool name
1220
+ print(f"🛠️ Available MCP tools: {', '.join(mcp_tool_names)}")
1221
+ agent_names_in_workflow = ", ".join([a.name for a in self.agents])
1222
+ print(f"🔄 Agents in MCP workflow: {agent_names_in_workflow}")
1223
+
1224
+ def run_praison_mcp_server():
1053
1225
  try:
1054
- print(f" FastAPI server started at http://{host}:{port}")
1055
- print(f"📚 API documentation available at http://{host}:{port}/docs")
1056
- print(f"🔌 Available endpoints: {', '.join(list(_agents_registered_endpoints[port].keys()))}")
1057
- uvicorn.run(_agents_shared_apps[port], host=host, port=port, log_level="debug" if debug else "info")
1226
+ uvicorn.run(starlette_mcp_app, host=host, port=port, log_level="debug" if debug else "info")
1058
1227
  except Exception as e:
1059
- logging.error(f"Error starting server: {str(e)}", exc_info=True)
1060
- print(f"❌ Error starting server: {str(e)}")
1061
-
1062
- # Run server in a background thread
1063
- server_thread = threading.Thread(target=run_server, daemon=True)
1064
- server_thread.start()
1065
-
1066
- # Wait for a moment to allow the server to start and register endpoints
1067
- time.sleep(0.5)
1068
- else:
1069
- # If server is already running, wait a moment to make sure the endpoint is registered
1070
- time.sleep(0.1)
1071
- print(f"🔌 Available endpoints on port {port}: {', '.join(list(_agents_registered_endpoints[port].keys()))}")
1072
-
1073
- # Get the stack frame to check if this is the last launch() call in the script
1074
- import inspect
1075
- stack = inspect.stack()
1076
-
1077
- # If this is called from a Python script (not interactive), try to detect if it's the last launch call
1078
- if len(stack) > 1 and stack[1].filename.endswith('.py'):
1079
- caller_frame = stack[1]
1080
- caller_line = caller_frame.lineno
1081
-
1082
- try:
1083
- # Read the file to check if there are more launch calls after this one
1084
- with open(caller_frame.filename, 'r') as f:
1085
- lines = f.readlines()
1086
-
1087
- # Check if there are more launch() calls after the current line
1088
- has_more_launches = False
1089
- for line in lines[caller_line:]:
1090
- if '.launch(' in line and not line.strip().startswith('#'):
1091
- has_more_launches = True
1092
- break
1093
-
1094
- # If this is the last launch call, block the main thread
1095
- if not has_more_launches:
1228
+ logging.error(f"Error starting PraisonAIAgents MCP server: {str(e)}", exc_info=True)
1229
+ print(f"❌ Error starting PraisonAIAgents MCP server: {str(e)}")
1230
+
1231
+ mcp_server_thread = threading.Thread(target=run_praison_mcp_server, daemon=True)
1232
+ mcp_server_thread.start()
1233
+ time.sleep(0.5)
1234
+
1235
+ import inspect
1236
+ stack = inspect.stack()
1237
+ if len(stack) > 1 and stack[1].filename.endswith('.py'):
1238
+ caller_frame = stack[1]
1239
+ caller_line = caller_frame.lineno
1240
+ try:
1241
+ with open(caller_frame.filename, 'r') as f:
1242
+ lines = f.readlines()
1243
+ has_more_launches = False
1244
+ for line_content in lines[caller_line:]:
1245
+ if '.launch(' in line_content and not line_content.strip().startswith('#'):
1246
+ has_more_launches = True
1247
+ break
1248
+ if not has_more_launches:
1249
+ try:
1250
+ print("\nPraisonAIAgents MCP server running. Press Ctrl+C to stop.")
1251
+ while True:
1252
+ time.sleep(1)
1253
+ except KeyboardInterrupt:
1254
+ print("\nPraisonAIAgents MCP Server stopped")
1255
+ except Exception as e:
1256
+ logging.error(f"Error in PraisonAIAgents MCP launch detection: {e}")
1096
1257
  try:
1097
- print("\nAll agents registered. Press Ctrl+C to stop the servers.")
1258
+ print("\nKeeping PraisonAIAgents MCP server alive. Press Ctrl+C to stop.")
1098
1259
  while True:
1099
1260
  time.sleep(1)
1100
1261
  except KeyboardInterrupt:
1101
- print("\nServers stopped")
1102
- except Exception as e:
1103
- # If something goes wrong with detection, block anyway to be safe
1104
- logging.error(f"Error in launch detection: {e}")
1105
- try:
1106
- print("\nKeeping servers alive. Press Ctrl+C to stop.")
1107
- while True:
1108
- time.sleep(1)
1109
- except KeyboardInterrupt:
1110
- print("\nServers stopped")
1111
-
1112
- return None
1262
+ print("\nPraisonAIAgents MCP Server stopped")
1263
+ return None
1264
+ else:
1265
+ display_error(f"Invalid protocol: {protocol}. Choose 'http' or 'mcp'.")
1266
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.80
3
+ Version: 0.0.82
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -1,10 +1,10 @@
1
1
  praisonaiagents/__init__.py,sha256=Z2_rSA6mYozz0r3ioUgKzl3QV8uWRDS_QaqPg2oGjqg,1324
2
2
  praisonaiagents/main.py,sha256=l29nGEbV2ReBi4szURbnH0Fk0w2F_QZTmECysyZjYcA,15066
3
3
  praisonaiagents/agent/__init__.py,sha256=j0T19TVNbfZcClvpbZDDinQxZ0oORgsMrMqx16jZ-bA,128
4
- praisonaiagents/agent/agent.py,sha256=OOP_05mOMoHNkeElQWqH9jKOFUivlIjZ_vZJWLhIkSc,75089
4
+ praisonaiagents/agent/agent.py,sha256=9vexAh3Jch5Fqst2aT-jf6xU1wAr4t0NePJZMmtVK_g,84183
5
5
  praisonaiagents/agent/image_agent.py,sha256=-5MXG594HVwSpFMcidt16YBp7udtik-Cp7eXlzLE1fY,8696
6
6
  praisonaiagents/agents/__init__.py,sha256=_1d6Pqyk9EoBSo7E68sKyd1jDRlN1vxvVIRpoMc0Jcw,168
7
- praisonaiagents/agents/agents.py,sha256=ABMQW07fH8HgY0adiwollDB5DQJZcf7uGhu4njiyIVM,49026
7
+ praisonaiagents/agents/agents.py,sha256=HQXAuvV_cORa-NUNVknve4d0B1OIqS6W5jYkw_bYEyA,59519
8
8
  praisonaiagents/agents/autoagents.py,sha256=olYDn--rlJp-SckxILqmREkkgNlzCgEEcAUzfMj-54E,13518
9
9
  praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9bge0Ujuto,246
10
10
  praisonaiagents/knowledge/chunking.py,sha256=G6wyHa7_8V0_7VpnrrUXbEmUmptlT16ISJYaxmkSgmU,7678
@@ -40,7 +40,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
40
40
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
41
41
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
42
42
  praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
43
- praisonaiagents-0.0.80.dist-info/METADATA,sha256=MAc45qEZryYHTSeN-iQ0Ia90WSHgKEDmu9ulXvfY8cw,1149
44
- praisonaiagents-0.0.80.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
45
- praisonaiagents-0.0.80.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
46
- praisonaiagents-0.0.80.dist-info/RECORD,,
43
+ praisonaiagents-0.0.82.dist-info/METADATA,sha256=uo5CC4tD3LReN8VJ-jOmPvM7-6i6ZLqmJffuvwEbvVY,1149
44
+ praisonaiagents-0.0.82.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
45
+ praisonaiagents-0.0.82.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
46
+ praisonaiagents-0.0.82.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5