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.
- praisonaiagents/agent/agent.py +304 -152
- praisonaiagents/agents/agents.py +347 -193
- {praisonaiagents-0.0.80.dist-info → praisonaiagents-0.0.82.dist-info}/METADATA +1 -1
- {praisonaiagents-0.0.80.dist-info → praisonaiagents-0.0.82.dist-info}/RECORD +6 -6
- {praisonaiagents-0.0.80.dist-info → praisonaiagents-0.0.82.dist-info}/WHEEL +1 -1
- {praisonaiagents-0.0.80.dist-info → praisonaiagents-0.0.82.dist-info}/top_level.txt +0 -0
praisonaiagents/agent/agent.py
CHANGED
@@ -23,9 +23,9 @@ import uuid
|
|
23
23
|
from dataclasses import dataclass
|
24
24
|
|
25
25
|
# Global variables for API server
|
26
|
-
_server_started =
|
27
|
-
_registered_agents = {}
|
28
|
-
|
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
|
-
|
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
|
-
#
|
1436
|
-
|
1437
|
-
|
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
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
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
|
-
#
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
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
|
-
#
|
1463
|
-
|
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
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
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
|
-
|
1492
|
-
if
|
1493
|
-
|
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
|
-
|
1502
|
-
|
1503
|
-
|
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
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
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
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
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
|
-
#
|
1529
|
-
|
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
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
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
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
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
|
-
|
1561
|
-
|
1562
|
-
|
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
|
-
|
1565
|
-
|
1566
|
-
for
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
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("\
|
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("\
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
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
|
praisonaiagents/agents/agents.py
CHANGED
@@ -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
|
891
|
-
|
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
|
-
|
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
|
-
|
909
|
-
|
910
|
-
|
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
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
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
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
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
|
-
#
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
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
|
-
#
|
952
|
-
|
953
|
-
|
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
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
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
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
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
|
-
|
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
|
-
|
1000
|
-
|
1001
|
-
|
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
|
-
|
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
|
-
|
1006
|
-
if asyncio.iscoroutinefunction(
|
1007
|
-
response = await
|
1008
|
-
|
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:
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
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
|
-
|
1020
|
-
|
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
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
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
|
-
|
1030
|
-
return
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
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
|
-
|
1052
|
-
|
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
|
-
|
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
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
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("\
|
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("\
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
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,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=
|
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=
|
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.
|
44
|
-
praisonaiagents-0.0.
|
45
|
-
praisonaiagents-0.0.
|
46
|
-
praisonaiagents-0.0.
|
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,,
|
File without changes
|