Skip to content
Merged
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
36 changes: 36 additions & 0 deletions simple_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""
Simple test to verify the termination fix
"""
import sys
import os

# Add the src directory to the path so we can import praisonaiagents
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))

print("Testing agent termination fix...")

try:
from praisonaiagents import Agent

# Create agent with minimal setup
agent = Agent(instructions="You are a helpful AI assistant")

# Run the same test as in the issue
print("Running agent.start() ...")
response = agent.start("Write a movie script about a robot on Mars")

print("Agent completed successfully!")
print(f"Response type: {type(response)}")
print(f"Response length: {len(str(response)) if response else 'None'}")

# If we get here, the fix worked
print("SUCCESS: Program should terminate properly!")

except Exception as e:
print(f"ERROR: Exception occurred: {e}")
import traceback
traceback.print_exc()
sys.exit(1)

print("Test completed - program should exit now.")
37 changes: 31 additions & 6 deletions src/praisonai-agents/praisonaiagents/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1929,19 +1929,44 @@ async def _achat_completion(self, response, tools, reasoning_steps=False):

async def astart(self, prompt: str, **kwargs):
"""Async version of start method"""
return await self.achat(prompt, **kwargs)
try:
result = await self.achat(prompt, **kwargs)
return result
finally:
# Ensure proper cleanup of telemetry system to prevent hanging
self._cleanup_telemetry()

def run(self):
"""Alias for start() method"""
return self.start()

def _cleanup_telemetry(self):
"""Clean up telemetry system to ensure proper program termination."""
try:
# Import here to avoid circular imports
from ..telemetry import get_telemetry

# Get the global telemetry instance and shut it down
telemetry = get_telemetry()
if telemetry and hasattr(telemetry, 'shutdown'):
telemetry.shutdown()
except Exception as e:
# Log error but don't fail the execution
logging.debug(f"Error cleaning up telemetry: {e}")
Comment on lines +1943 to +1955
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The cleanup method correctly resolves the agent termination issue for single-run scenarios. However, it introduces a potential issue in applications that execute multiple agents sequentially within the same process.

get_telemetry() returns a global singleton instance of the telemetry collector. This method calls shutdown() on that singleton instance. Once shut down, the telemetry instance cannot be reused, and subsequent calls to tracking methods will likely be ignored. Therefore, if another agent is started in the same process, its activities will not be recorded by telemetry.

A complete solution would likely involve modifying get_telemetry() to re-initialize the telemetry system if the existing instance has been shut down. Since that's outside the changed files in this PR, this is a known limitation of the current fix. For many use cases where a script runs a single agent and exits, this fix is perfectly fine.


def start(self, prompt: str, **kwargs):
"""Start the agent with a prompt. This is a convenience method that wraps chat()."""
# Check if streaming is enabled and user wants streaming chunks
if self.stream and kwargs.get('stream', True):
return self._start_stream(prompt, **kwargs)
else:
return self.chat(prompt, **kwargs)
try:
# Check if streaming is enabled and user wants streaming chunks
if self.stream and kwargs.get('stream', True):
result = self._start_stream(prompt, **kwargs)
return result
else:
result = self.chat(prompt, **kwargs)
return result
finally:
# Ensure proper cleanup of telemetry system to prevent hanging
self._cleanup_telemetry()

def _start_stream(self, prompt: str, **kwargs):
"""Generator method that yields streaming chunks from the agent."""
Expand Down
49 changes: 49 additions & 0 deletions test_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""
Test script to verify the termination fix works
"""
import sys
import os
import signal
import time
from threading import Timer

# Add the src directory to the path so we can import praisonaiagents
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))

# Set up timeout mechanism
def timeout_handler(signum, frame):
print("ERROR: Test timed out - program is still hanging!")
sys.exit(1)

# Set up signal handler for timeout
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(30) # 30 second timeout
Comment on lines +19 to +21
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of signal.SIGALRM is specific to Unix-like systems and will raise an AttributeError on Windows. To make this test more portable, wrap this call in a platform check. A corresponding check will be needed in the finally block as well.

Suggested change
# Set up signal handler for timeout
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(30) # 30 second timeout
if sys.platform != "win32":
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(30) # 30 second timeout


try:
# Import here to avoid issues with path setup
from praisonaiagents import Agent

print("Testing agent termination fix...")

# Create agent with minimal setup
agent = Agent(instructions="You are a helpful AI assistant")

# Run the same test as in the issue
print("Running agent.start() ...")
response = agent.start("Write a short hello world message")

print(f"Agent completed successfully!")
print(f"Response (truncated): {str(response)[:100]}...")

# If we get here, the fix worked
print("SUCCESS: Program terminated properly without hanging!")

except Exception as e:
print(f"ERROR: Exception occurred: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
finally:
# Cancel the alarm
signal.alarm(0)
Comment on lines +48 to +49
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To complement the platform check for setting the alarm, this call to cancel the alarm should also be conditional to avoid errors on Windows.

Suggested change
# Cancel the alarm
signal.alarm(0)
if sys.platform != "win32":
signal.alarm(0)

30 changes: 30 additions & 0 deletions test_termination_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Test script to reproduce the termination issue
"""
import sys
import os

# Add the src directory to the path so we can import praisonaiagents
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))

from praisonaiagents import Agent

def test_termination():
"""Test that the agent terminates properly after execution"""
print("Testing agent termination...")

agent = Agent(instructions="You are a helpful AI assistant")
response = agent.start("Write a short hello world message")

print(f"Agent response: {response}")
print("Agent execution completed. Testing if program terminates...")

# If this completes without hanging, the fix works
return True

if __name__ == "__main__":
result = test_termination()
if result:
print("SUCCESS: Program terminated properly")
else:
print("FAILURE: Program did not terminate")
Loading