Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/praisonai-agents/praisonaiagents/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,16 @@ def _is_file_path(value: str) -> bool:
_registered_agents = {} # Dict of port -> Dict of path -> agent_id
_shared_apps = {} # Dict of port -> FastAPI app

def _get_registered_agents_for_port(port: int) -> Dict[str, str]:
"""Safely get registered agents for a port (thread-safe)."""
with _server_lock:
return _registered_agents.get(port, {}).copy()

def _get_shared_app_for_port(port: int):
"""Safely get shared app for a port (thread-safe)."""
with _server_lock:
return _shared_apps.get(port)

# Don't import FastAPI dependencies here - use lazy loading instead

if TYPE_CHECKING:
Expand Down Expand Up @@ -8789,8 +8799,10 @@ def run_server():
try:
print(f"✅ FastAPI server started at http://{host}:{port}")
print(f"📚 API documentation available at http://{host}:{port}/docs")
print(f"🔌 Available endpoints: {', '.join(list(_registered_agents[port].keys()))}")
uvicorn.run(_shared_apps[port], host=host, port=port, log_level="debug" if debug else "info")
endpoints = _get_registered_agents_for_port(port)
print(f"🔌 Available endpoints: {', '.join(list(endpoints.keys()))}")
app = _get_shared_app_for_port(port)
uvicorn.run(app, host=host, port=port, log_level="debug" if debug else "info")
except Exception as e:
logging.error(f"Error starting server: {str(e)}", exc_info=True)
print(f"❌ Error starting server: {str(e)}")
Expand All @@ -8804,7 +8816,8 @@ def run_server():
else:
# If server is already running, wait a moment to make sure the endpoint is registered
time.sleep(0.1)
print(f"🔌 Available endpoints on port {port}: {', '.join(list(_registered_agents[port].keys()))}")
endpoints = _get_registered_agents_for_port(port)
print(f"🔌 Available endpoints on port {port}: {', '.join(list(endpoints.keys()))}")

# Get the stack frame to check if this is the last launch() call in the script
import inspect
Expand Down
35 changes: 23 additions & 12 deletions src/praisonai-agents/praisonaiagents/agent/context_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,52 @@
from typing import Optional, Any, Dict, Union, List, TYPE_CHECKING

# Lazy imports for performance - these are only loaded when needed
# Thread-safe lazy loading with double-checked locking pattern
import threading
_lazy_lock = threading.Lock()
_subprocess = None
_glob = None
_ast = None
_asyncio = None

def _get_subprocess():
"""Lazy import subprocess to avoid import-time overhead."""
"""Lazy import subprocess to avoid import-time overhead (thread-safe)."""
global _subprocess
if _subprocess is None:
import subprocess
_subprocess = subprocess
with _lazy_lock:
if _subprocess is None:
import subprocess
_subprocess = subprocess
return _subprocess

def _get_glob():
"""Lazy import glob to avoid import-time overhead."""
"""Lazy import glob to avoid import-time overhead (thread-safe)."""
global _glob
if _glob is None:
import glob
_glob = glob
with _lazy_lock:
if _glob is None:
import glob
_glob = glob
return _glob

def _get_ast():
"""Lazy import ast to avoid import-time overhead."""
"""Lazy import ast to avoid import-time overhead (thread-safe)."""
global _ast
if _ast is None:
import ast
_ast = ast
with _lazy_lock:
if _ast is None:
import ast
_ast = ast
return _ast

def _get_asyncio():
"""Lazy import asyncio to avoid import-time overhead."""
"""Lazy import asyncio to avoid import-time overhead (thread-safe)."""
global _asyncio
if _asyncio is None:
import asyncio
_asyncio = asyncio
with _lazy_lock:
if _asyncio is None:
import asyncio
_asyncio = asyncio
return _asyncio

async def _async_subprocess_run(cmd: list, timeout: int = 60) -> tuple:
Expand Down
69 changes: 69 additions & 0 deletions test_agentic_functionality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
Real agentic test as required by AGENTS.md section 9.4.

This test verifies that the Agent actually runs end-to-end and calls the LLM,
not just smoke tests. This ensures our thread safety fixes don't break actual
agent functionality.
"""

import sys
import os

# Add the path to find praisonaiagents
sys.path.insert(0, '/home/runner/work/PraisonAI/PraisonAI/src/praisonai-agents')

def test_real_agentic_functionality():
"""Test that Agent actually runs and calls LLM end-to-end."""
print("🧪 Running real agentic test (Agent must call LLM)...")

try:
from praisonaiagents import Agent

# Create agent and run a real task
agent = Agent(name="test", instructions="You are a helpful assistant")
print(f"📋 Agent created: {agent.name}")

# This MUST call the LLM and produce actual output
print("🚀 Starting agent with real prompt...")
result = agent.start("Say hello in one sentence")

# Print full output for verification
print("📄 Agent output:")
print(f"Result: {result}")

# Verify we got actual output
if not result or not isinstance(result, str) or len(result.strip()) == 0:
print("❌ Agent did not produce valid output")
return False

print("✅ Agent successfully called LLM and produced output!")
return True

except Exception as e:
print(f"❌ Agent test failed with exception: {e}")
import traceback
traceback.print_exc()
return False


def main():
"""Run the real agentic test."""
print("🤖 Testing real agentic functionality after thread safety fixes...\n")

success = test_real_agentic_functionality()

print("\n" + "=" * 60)
if success:
print("🎉 Real agentic test PASSED!")
print("✅ Thread safety fixes do not break agent functionality")
else:
print("❌ Real agentic test FAILED!")
print("⚠️ Agent functionality may be broken")

return success


if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)
Loading