Compare commits

...
20 Commits
Author SHA1 Message Date
Mervin Praison f36415d4ea Merge pull request #519 from MervinPraison/develop
Update version to 2.2.16 across project files
2025-05-27 06:10:32 +01:00
MervinPraison 13c3e31458 Update version to 2.2.16 across project files
- Incremented PraisonAI version from 2.2.15 to 2.2.16 in `pyproject.toml`, `uv.lock`, and all relevant Dockerfiles for consistency.
- Updated dependency version for `praisonaiagents` from 0.0.88 to 0.0.89.
- Ensured minimal changes to existing code while maintaining versioning accuracy across the project.
2025-05-27 06:10:01 +01:00
Mervin Praison 41d550c4a5 Merge pull request #518 from MervinPraison/claude/issue-484-20250527_043953
feat: Add context management for workflow loops
2025-05-27 05:54:45 +01:00
Mervin Praison ac570e5b93 Rename test_context_management.py to tests/unit/test_context_management.py 2025-05-27 05:54:18 +01:00
claude[bot]andMervinPraison a5465882ee feat: Add context management for workflow loops to prevent context length issues
- Add retain_full_context parameter to Task class (default False)
- By default, only include most recent previous task in context
- Maintain backwards compatibility with retain_full_context=True
- Add _build_task_context helper method to Process class  
- Update both async and sync workflow methods to use new context building
- Add test script to verify functionality

Fixes context accumulation issue in workflow loops where all previous
task results were being appended, causing exponential context growth.

Co-authored-by: MervinPraison <[email protected]>
2025-05-27 04:47:37 +00:00
Mervin Praison f86b03e5d8 Merge pull request #517 from MervinPraison/develop
Update version to 2.2.15 across project files
2025-05-27 05:47:27 +01:00
MervinPraison 397a466f55 Update version to 2.2.15 across project files
- Incremented PraisonAI version from 2.2.14 to 2.2.15 in `pyproject.toml`, `uv.lock`, and all relevant Dockerfiles for consistency.
- Updated dependency version for `praisonaiagents` from 0.0.87 to 0.0.88.
- Ensured minimal changes to existing code while maintaining versioning accuracy across the project.
2025-05-27 05:36:34 +01:00
Mervin Praison 36c3066ba4 Merge pull request #515 from MervinPraison/claude/issue-514-20250526_155559
fix: Add asyncio import to local scope in launch() method
2025-05-26 20:00:06 +01:00
claude[bot]andMervinPraison df06e794b3 fix: Add asyncio import to local scope in launch() method
This fixes the NameError: cannot access free variable 'asyncio' where it is not associated with a value in enclosing scope error that occurs at line 1563.

The issue was that the handle_agent_query function (containing line 1563) is defined inside a try/except block that imports FastAPI dependencies but didn't include asyncio. Added import asyncio to the try block and removed redundant import from line 1659.

Fixes #514

Co-authored-by: MervinPraison <[email protected]>
2025-05-26 15:59:29 +00:00
Mervin Praison 71fe678d66 Merge pull request #513 from MervinPraison/develop
Update version to 2.2.14 across project files
2025-05-25 06:47:58 +01:00
MervinPraison c8875eec81 Update version to 2.2.14 across project files
- Incremented PraisonAI version from 2.2.13 to 2.2.14 in `pyproject.toml`, `uv.lock`, and all relevant Dockerfiles for consistency.
- Ensured minimal changes to existing code while maintaining versioning accuracy across the project.
2025-05-25 06:47:32 +01:00
Mervin Praison f88204d2e8 Merge pull request #512 from MervinPraison/develop
Update version to 2.2.13 across project files
2025-05-24 23:18:37 +01:00
MervinPraison fc01a80956 Update version to 2.2.13 across project files
- Incremented PraisonAI version from 2.2.12 to 2.2.13 in `pyproject.toml`, `uv.lock`, and all relevant Dockerfiles for consistency.
- Ensured minimal changes to existing code while maintaining versioning accuracy across the project.
2025-05-24 23:18:09 +01:00
Mervin Praison cd91565b12 Merge pull request #511 from MervinPraison/develop
Update version to 2.2.12 across project files
2025-05-24 22:59:55 +01:00
MervinPraison d233d4597c Update version to 2.2.12 across project files
- Incremented PraisonAI version from 2.2.11 to 2.2.12 in `pyproject.toml`, `uv.lock`, and all relevant Dockerfiles for consistency.
- Ensured minimal changes to existing code while maintaining versioning accuracy across the project.
2025-05-24 22:59:36 +01:00
Mervin Praison a9fabce0ea Merge pull request #510 from MervinPraison/develop
Develop
2025-05-24 22:46:10 +01:00
MervinPraison 6bcd3ba9bd Update version to 2.2.11 across project files
- Incremented PraisonAI version from 2.2.10 to 2.2.11 in `pyproject.toml`, `uv.lock`, and all relevant Dockerfiles for consistency.
- Ensured minimal changes to existing code while maintaining versioning accuracy across the project.
2025-05-24 22:45:46 +01:00
MervinPraison 37028b0dd2 Enhance backup process for root configuration files in GitHub Actions workflow
- Updated echo statements to provide clearer context regarding the backup of `agents.yaml` and `tools.py`, preventing default file resolution interference during tests.
- Ensured minimal changes to existing code while improving clarity and user understanding of the backup process.
2025-05-24 22:38:10 +01:00
MervinPraison 6807d0e017 Add backup and restore steps for root configuration files in GitHub Actions workflow
- Introduced steps to back up and restore `agents.yaml` and `tools.py` during the workflow execution, preventing interference and ensuring a clean environment for tests.
- Added comprehensive execution debug step to provide detailed insights into the execution path and configuration status.
- Ensured minimal changes to existing code while enhancing the debugging and testing process for configuration management.
2025-05-24 22:35:23 +01:00
MervinPraison bca845d771 Add debug steps for logging level and researcher role detection in GitHub Actions workflow
- Introduced a new environment variable for log level and additional steps to search for the 'Researcher' role in YAML files, enhancing visibility during the workflow execution.
- Added a step to trace the AutoGen execution path, providing insights into framework decisions and available roles.
- Ensured minimal changes to existing code while improving the debugging and testing process for configuration and role management.
2025-05-24 22:07:21 +01:00
22 changed files with 578 additions and 99 deletions

No files matched your search

+258 -1
View File
@@ -5,7 +5,7 @@ on: [push, pull_request]
jobs:
quick-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -31,8 +31,24 @@ jobs:
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY || 'sk-test-key-for-github-actions-testing-only-not-real' }}" >> $GITHUB_ENV
echo "OPENAI_API_BASE=${{ secrets.OPENAI_API_BASE || 'https://api.openai.com/v1' }}" >> $GITHUB_ENV
echo "OPENAI_MODEL_NAME=${{ secrets.OPENAI_MODEL_NAME || 'gpt-4o-mini' }}" >> $GITHUB_ENV
echo "LOGLEVEL=DEBUG" >> $GITHUB_ENV
echo "PYTHONPATH=${{ github.workspace }}/src/praisonai-agents:$PYTHONPATH" >> $GITHUB_ENV
- name: Backup Root Config Files
run: |
echo "🔄 Backing up root configuration files to prevent default file resolution interference..."
echo "ℹ️ PraisonAI automatically uses 'agents.yaml' in working directory when no file specified"
echo "ℹ️ This can interfere with test files that specify their own YAML files"
if [ -f "agents.yaml" ]; then
mv agents.yaml agents.yaml.backup
echo "✅ Moved root agents.yaml to agents.yaml.backup"
echo " - This prevents default file fallback during tests"
fi
if [ -f "tools.py" ]; then
mv tools.py tools.py.backup
echo "✅ Moved tools.py to tools.py.backup"
fi
- name: Debug API Key Status
run: |
echo "🔍 Checking API key availability..."
@@ -45,10 +61,12 @@ jobs:
fi
echo "🌐 API Base: $OPENAI_API_BASE"
echo "🤖 Model: $OPENAI_MODEL_NAME"
echo "🐛 Log Level: $LOGLEVEL"
echo "📊 Environment Check:"
echo " - OPENAI_API_KEY length: ${#OPENAI_API_KEY}"
echo " - OPENAI_API_BASE: $OPENAI_API_BASE"
echo " - OPENAI_MODEL_NAME: $OPENAI_MODEL_NAME"
echo " - LOGLEVEL: $LOGLEVEL"
- name: Debug Python Environment Variables
run: |
@@ -68,6 +86,124 @@ jobs:
print(f' {key}: {value[:10] if len(value) > 10 else value}...')
"
- name: Find Researcher Role Source
run: |
echo "🔍 Hunting for the mysterious 'Researcher' role..."
python -c "
import os
import yaml
import glob
print('📋 Searching for Researcher role in all YAML files:')
yaml_files = glob.glob('tests/*.yaml')
for yaml_file in yaml_files:
try:
with open(yaml_file, 'r') as f:
config = yaml.safe_load(f)
# Check if any role contains 'researcher'
roles = config.get('roles', {})
for role_key, role_data in roles.items():
role_name = role_data.get('role', '')
if 'researcher' in role_key.lower() or 'researcher' in role_name.lower():
print(f' 🎯 FOUND in {yaml_file}:')
print(f' Framework: {config.get(\"framework\", \"NOT_SET\")}')
print(f' Role key: {role_key}')
print(f' Role name: {role_name}')
print(f' All roles: {list(roles.keys())}')
print()
except Exception as e:
print(f' ❌ Error reading {yaml_file}: {e}')
print('🔍 Checking for default configurations...')
# Check if there are any default configs or hardcoded roles
try:
import praisonai
print(f' PraisonAI package location: {praisonai.__file__}')
# Check if there are any example YAML files in the package
package_dir = os.path.dirname(praisonai.__file__)
for root, dirs, files in os.walk(package_dir):
for file in files:
if file.endswith(('.yaml', '.yml')):
file_path = os.path.join(root, file)
print(f' 📁 Found YAML in package: {file_path}')
except Exception as e:
print(f' ❌ Error checking package: {e}')
"
continue-on-error: true
- name: Trace AutoGen Execution Path
run: |
echo "🔍 Tracing AutoGen execution to find where it diverges..."
python -c "
import os
import sys
sys.path.insert(0, '.')
try:
from praisonai import PraisonAI
from praisonai.agents_generator import AgentsGenerator
# Test the exact execution path
print('🎯 Testing AutoGen execution path:')
praisonai = PraisonAI(agent_file='tests/autogen-agents.yaml')
print(f' 1. PraisonAI framework: \"{praisonai.framework}\"')
agents_gen = AgentsGenerator(
agent_file='tests/autogen-agents.yaml',
framework=praisonai.framework,
config_list=praisonai.config_list
)
print(f' 2. AgentsGenerator framework: \"{agents_gen.framework}\"')
# Load the YAML to check what it contains
import yaml
with open('tests/autogen-agents.yaml', 'r') as f:
config = yaml.safe_load(f)
framework = agents_gen.framework or config.get('framework')
print(f' 3. Final framework decision: \"{framework}\"')
print(f' 4. Available frameworks:')
# Check framework availability
try:
import autogen
print(f' ✅ AutoGen available')
except ImportError:
print(f' ❌ AutoGen NOT available')
try:
from praisonaiagents import Agent
print(f' ✅ PraisonAI agents available')
except ImportError:
print(f' ❌ PraisonAI agents NOT available')
try:
from crewai import Agent
print(f' ✅ CrewAI available')
except ImportError:
print(f' ❌ CrewAI NOT available')
print(f' 5. Roles in YAML: {list(config.get(\"roles\", {}).keys())}')
# Now test the actual framework execution
if framework == 'autogen':
print(f' 6. ✅ Should execute _run_autogen')
elif framework == 'praisonai':
print(f' 6. ❌ Would execute _run_praisonai (WRONG!)')
else:
print(f' 6. ❌ Would execute _run_crewai (DEFAULT FALLBACK)')
except Exception as e:
print(f'❌ Error tracing execution: {e}')
import traceback
traceback.print_exc()
"
continue-on-error: true
- name: Debug YAML Loading and Roles
run: |
echo "🔍 Tracing YAML file loading and role creation..."
@@ -216,6 +352,114 @@ jobs:
python -m praisonai tests/autogen-agents.yaml
continue-on-error: true
- name: Comprehensive Execution Debug
run: |
echo "🔍 Comprehensive debugging of PraisonAI execution path..."
python -c "
import os
import sys
import yaml
sys.path.insert(0, '.')
print('=' * 60)
print('🔍 COMPREHENSIVE EXECUTION DEBUG')
print('=' * 60)
# Check current working directory
print(f'📁 Current working directory: {os.getcwd()}')
# List files in current directory
print('📋 Files in current directory:')
for f in os.listdir('.'):
print(f' {f}')
print()
print('📋 Files in tests/ directory:')
for f in os.listdir('tests'):
if f.endswith('.yaml'):
print(f' tests/{f}')
# Check if root agents.yaml exists
print()
if os.path.exists('agents.yaml'):
print('❌ ROOT agents.yaml EXISTS (this is the problem!)')
with open('agents.yaml', 'r') as f:
root_config = yaml.safe_load(f)
print(f' Root framework: {root_config.get(\"framework\")}')
print(f' Root roles: {list(root_config.get(\"roles\", {}).keys())}')
else:
print('✅ ROOT agents.yaml does NOT exist (good!)')
# Test the actual execution path
print()
print('🎯 Testing EXACT execution path:')
try:
from praisonai import PraisonAI
from praisonai.agents_generator import AgentsGenerator
# Test with full path
test_file = 'tests/autogen-agents.yaml'
print(f' Using test file: {test_file}')
print(f' File exists: {os.path.exists(test_file)}')
# Load the test file directly
with open(test_file, 'r') as f:
test_config = yaml.safe_load(f)
print(f' Test file framework: {test_config.get(\"framework\")}')
print(f' Test file roles: {list(test_config.get(\"roles\", {}).keys())}')
# Create PraisonAI instance
praisonai = PraisonAI(agent_file=test_file)
print(f' PraisonAI.agent_file: {praisonai.agent_file}')
print(f' PraisonAI.framework: {praisonai.framework}')
# Create AgentsGenerator
agents_gen = AgentsGenerator(
agent_file=test_file,
framework=praisonai.framework,
config_list=praisonai.config_list
)
print(f' AgentsGenerator.agent_file: {agents_gen.agent_file}')
print(f' AgentsGenerator.framework: {agents_gen.framework}')
# Test what would happen in generate_crew_and_kickoff
print()
print('🔧 Testing generate_crew_and_kickoff logic:')
# Simulate the loading logic
if agents_gen.agent_yaml:
loaded_config = yaml.safe_load(agents_gen.agent_yaml)
print(' Would load from agent_yaml')
else:
if agents_gen.agent_file == '/app/api:app' or agents_gen.agent_file == 'api:app':
agents_gen.agent_file = 'agents.yaml'
print(f' Would change agent_file to: {agents_gen.agent_file}')
try:
with open(agents_gen.agent_file, 'r') as f:
loaded_config = yaml.safe_load(f)
print(f' Successfully loaded: {agents_gen.agent_file}')
except FileNotFoundError:
print(f' FileNotFoundError: {agents_gen.agent_file}')
loaded_config = None
if loaded_config:
final_framework = agents_gen.framework or loaded_config.get('framework')
print(f' Final framework decision: {final_framework}')
print(f' Loaded roles: {list(loaded_config.get(\"roles\", {}).keys())}')
if 'researcher' in loaded_config.get('roles', {}):
print(' ❌ FOUND Researcher role in loaded config!')
else:
print(' ✅ No Researcher role in loaded config')
except Exception as e:
print(f'❌ Error during execution debug: {e}')
import traceback
traceback.print_exc()
"
continue-on-error: true
- name: Run Fast Tests
run: |
# Run the fastest, most essential tests
@@ -225,3 +469,16 @@ jobs:
run: |
python -m pytest tests/test.py -v --tb=short --disable-warnings
continue-on-error: true
- name: Restore Root Config Files
run: |
echo "🔄 Restoring root configuration files..."
if [ -f "agents.yaml.backup" ]; then
mv agents.yaml.backup agents.yaml
echo "✅ Restored agents.yaml"
fi
if [ -f "tools.py.backup" ]; then
mv tools.py.backup tools.py
echo "✅ Restored tools.py"
fi
if: always() # Always run this step, even if previous steps failed
+1 -1
View File
@@ -1,6 +1,6 @@
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install flask praisonai==2.2.10 gunicorn markdown
RUN pip install flask praisonai==2.2.16 gunicorn markdown
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
+1 -1
View File
@@ -13,7 +13,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.10" \
"praisonai==2.2.16" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -15,7 +15,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.10" \
"praisonai==2.2.16" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -13,7 +13,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.10" \
"praisonai==2.2.16" \
"praisonai[ui]" \
"praisonai[crewai]"
+1 -1
View File
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
file.write(&#34;FROM python:3.11-slim\n&#34;)
file.write(&#34;WORKDIR /app\n&#34;)
file.write(&#34;COPY . .\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.2.10 gunicorn markdown\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.2.16 gunicorn markdown\n&#34;)
file.write(&#34;EXPOSE 8080\n&#34;)
file.write(&#39;CMD [&#34;gunicorn&#34;, &#34;-b&#34;, &#34;0.0.0.0:8080&#34;, &#34;api:app&#34;]\n&#39;)
+1 -1
View File
@@ -27,7 +27,7 @@ WORKDIR /app
COPY . .
RUN pip install flask praisonai==2.2.10 watchdog
RUN pip install flask praisonai==2.2.16 watchdog
EXPOSE 5555
+1 -1
View File
@@ -155,7 +155,7 @@ To facilitate local development with live reload, you can use Docker. Follow the
COPY . .
RUN pip install flask praisonai==2.2.10 watchdog
RUN pip install flask praisonai==2.2.16 watchdog
EXPOSE 5555
+1 -1
View File
@@ -208,7 +208,7 @@ To facilitate local development with live reload, you can use Docker. Follow the
COPY . .
RUN pip install flask praisonai==2.2.10 watchdog
RUN pip install flask praisonai==2.2.16 watchdog
EXPOSE 5555
+9 -3
View File
@@ -438,19 +438,25 @@ class AgentsGenerator:
if llm_model:
llm = PraisonAIModel(
model=llm_model.get("model") or os.environ.get("MODEL_NAME") or "openai/gpt-4o",
base_url=self.config_list[0].get('base_url') if self.config_list else None
).get_model()
else:
llm = PraisonAIModel().get_model()
llm = PraisonAIModel(
base_url=self.config_list[0].get('base_url') if self.config_list else None
).get_model()
# Configure function calling LLM
function_calling_llm_model = details.get('function_calling_llm')
if function_calling_llm_model:
function_calling_llm = PraisonAIModel(
model=function_calling_llm_model.get("model") or os.environ.get("MODEL_NAME") or "openai/gpt-4o",
base_url=self.config_list[0].get('base_url') if self.config_list else None
).get_model()
else:
function_calling_llm = PraisonAIModel().get_model()
function_calling_llm = PraisonAIModel(
base_url=self.config_list[0].get('base_url') if self.config_list else None
).get_model()
# Create CrewAI agent
agent = Agent(
role=role_filled,
+4 -1
View File
@@ -113,11 +113,14 @@ class PraisonAI:
Initialize the PraisonAI object with default parameters.
"""
self.agent_yaml = agent_yaml
# Create config_list with AutoGen compatibility
api_key = os.environ.get("OPENAI_API_KEY")
self.config_list = [
{
'model': os.environ.get("OPENAI_MODEL_NAME", "gpt-4o"),
'base_url': os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1"),
'api_key': os.environ.get("OPENAI_API_KEY")
'api_key': api_key,
'api_type': 'openai' # AutoGen expects this field
}
]
self.agent_file = agent_file
+1 -1
View File
@@ -56,7 +56,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.2.10 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.2.16 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+4 -4
View File
@@ -1,6 +1,6 @@
[project]
name = "PraisonAI"
version = "2.2.10"
version = "2.2.16"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
readme = "README.md"
license = ""
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.0.87",
"praisonaiagents>=0.0.89",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
@@ -89,7 +89,7 @@ autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.15", "crewai"]
[tool.poetry]
name = "PraisonAI"
version = "2.2.10"
version = "2.2.16"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
authors = ["Mervin Praison"]
license = ""
@@ -107,7 +107,7 @@ python = ">=3.10,<3.13"
rich = ">=13.7"
markdown = ">=3.5"
pyparsing = ">=3.0.0"
praisonaiagents = ">=0.0.87"
praisonaiagents = ">=0.0.89"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
@@ -1478,6 +1478,7 @@ Your Goal: {self.goal}
from pydantic import BaseModel
import threading
import time
import asyncio
# Define the request model here since we need pydantic
class AgentQuery(BaseModel):
@@ -1656,7 +1657,6 @@ Your Goal: {self.goal}
import threading
import time
import inspect
import asyncio # Ensure asyncio is imported
# logging is already imported at the module level
except ImportError as e:
@@ -30,6 +30,44 @@ class Process:
self.task_retry_counter: Dict[str, int] = {} # Initialize retry counter
self.workflow_finished = False # ADDED: Workflow finished flag
def _build_task_context(self, current_task: Task) -> str:
"""Build context for a task based on its retain_full_context setting"""
if not (current_task.previous_tasks or current_task.context):
return ""
context = "\nInput data from previous tasks:"
if current_task.retain_full_context:
# Original behavior: include all previous tasks
for prev_name in current_task.previous_tasks:
prev_task = next((t for t in self.tasks.values() if t.name == prev_name), None)
if prev_task and prev_task.result:
context += f"\n{prev_name}: {prev_task.result.raw}"
# Add data from context tasks
if current_task.context:
for ctx_task in current_task.context:
if ctx_task.result and ctx_task.name != current_task.name:
context += f"\n{ctx_task.name}: {ctx_task.result.raw}"
else:
# New behavior: only include the most recent previous task
if current_task.previous_tasks:
# Get the most recent previous task (last in the list)
prev_name = current_task.previous_tasks[-1]
prev_task = next((t for t in self.tasks.values() if t.name == prev_name), None)
if prev_task and prev_task.result:
context += f"\n{prev_name}: {prev_task.result.raw}"
# For context tasks, still include the most recent one
if current_task.context:
# Get the most recent context task with a result
for ctx_task in reversed(current_task.context):
if ctx_task.result and ctx_task.name != current_task.name:
context += f"\n{ctx_task.name}: {ctx_task.result.raw}"
break # Only include the most recent one
return context
def _find_next_not_started_task(self) -> Optional[Task]:
"""Fallback mechanism to find the next 'not started' task."""
fallback_attempts = 0
@@ -147,25 +185,8 @@ Description length: {len(current_task.description)}
""")
# Add context from previous tasks to description
if current_task.previous_tasks or current_task.context:
context = "\nInput data from previous tasks:"
# Add data from previous tasks in workflow
for prev_name in current_task.previous_tasks:
prev_task = next((t for t in self.tasks.values() if t.name == prev_name), None)
if prev_task and prev_task.result:
# Handle loop data
if current_task.task_type == "loop":
context += f"\n{prev_name}: {prev_task.result.raw}"
else:
context += f"\n{prev_name}: {prev_task.result.raw}"
# Add data from context tasks
if current_task.context:
for ctx_task in current_task.context:
if ctx_task.result and ctx_task.name != current_task.name:
context += f"\n{ctx_task.name}: {ctx_task.result.raw}"
context = self._build_task_context(current_task)
if context:
# Update task description with context
current_task.description = current_task.description + context
@@ -778,25 +799,8 @@ Description length: {len(current_task.description)}
""")
# Add context from previous tasks to description
if current_task.previous_tasks or current_task.context:
context = "\nInput data from previous tasks:"
# Add data from previous tasks in workflow
for prev_name in current_task.previous_tasks:
prev_task = next((t for t in self.tasks.values() if t.name == prev_name), None)
if prev_task and prev_task.result:
# Handle loop data
if current_task.task_type == "loop":
context += f"\n{prev_name}: {prev_task.result.raw}"
else:
context += f"\n{prev_name}: {prev_task.result.raw}"
# Add data from context tasks
if current_task.context:
for ctx_task in current_task.context:
if ctx_task.result and ctx_task.name != current_task.name:
context += f"\n{ctx_task.name}: {ctx_task.result.raw}"
context = self._build_task_context(current_task)
if context:
# Update task description with context
current_task.description = current_task.description + context
@@ -39,7 +39,8 @@ class Task:
memory=None,
quality_check=True,
input_file: Optional[str] = None,
rerun: bool = False # Renamed from can_rerun and logic inverted, default True for backward compatibility
rerun: bool = False, # Renamed from can_rerun and logic inverted, default True for backward compatibility
retain_full_context: bool = False # By default, only use previous task output, not all previous tasks
):
# Add check if memory config is provided
if memory is not None or (config and config.get('memory_config')):
@@ -78,6 +79,7 @@ class Task:
self.memory = memory
self.quality_check = quality_check
self.rerun = rerun # Assigning the rerun parameter
self.retain_full_context = retain_full_context
# Set logger level based on config verbose level
verbose = self.config.get("verbose", 0)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.87"
version = "0.0.89"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
authors = [
{ name="Mervin Praison" }
+1 -1
View File
@@ -1457,7 +1457,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.87"
version = "0.0.89"
source = { editable = "." }
dependencies = [
{ name = "mcp" },
+91 -33
View File
@@ -1,5 +1,6 @@
import unittest
import subprocess
import os
from praisonai.cli import PraisonAI
from .advanced_example import advanced
from .basic_example import main
@@ -17,10 +18,15 @@ class TestPraisonAIFramework(unittest.TestCase):
result = praisonai.run()
self.assertIn('Task Output', result)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
self.skipTest(f"Skipping due to API authentication: {e}")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
self.skipTest(f"Skipping due to no valid API key provided: {e}")
else:
# Real API key provided - test should fail, not skip
raise
def test_main_with_autogen_framework(self):
@@ -29,10 +35,15 @@ class TestPraisonAIFramework(unittest.TestCase):
result = praisonai.run()
self.assertTrue('Task Output' in result or '### Output ###' in result)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
self.skipTest(f"Skipping due to API authentication: {e}")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
self.skipTest(f"Skipping due to no valid API key provided: {e}")
else:
# Real API key provided - test should fail, not skip
raise
def test_main_with_custom_framework(self):
@@ -41,10 +52,15 @@ class TestPraisonAIFramework(unittest.TestCase):
result = praisonai.run()
self.assertIn('Task Output', result)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
self.skipTest(f"Skipping due to API authentication: {e}")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
self.skipTest(f"Skipping due to no valid API key provided: {e}")
else:
# Real API key provided - test should fail, not skip
raise
def test_main_with_internet_search_tool(self):
@@ -53,10 +69,15 @@ class TestPraisonAIFramework(unittest.TestCase):
result = praisonai.run()
self.assertIn('Task Output', result)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
self.skipTest(f"Skipping due to API authentication: {e}")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
self.skipTest(f"Skipping due to no valid API key provided: {e}")
else:
# Real API key provided - test should fail, not skip
raise
def test_main_with_built_in_tool(self):
@@ -65,34 +86,56 @@ class TestPraisonAIFramework(unittest.TestCase):
result = praisonai.run()
self.assertIn('Task Output', result)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
self.skipTest(f"Skipping due to API authentication: {e}")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
self.skipTest(f"Skipping due to no valid API key provided: {e}")
else:
# Real API key provided - test should fail, not skip
raise
class TestPraisonAICommandLine(unittest.TestCase):
def run_command(self, command):
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, text=True)
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True,
text=True,
env=os.environ.copy() # Fix: Inherit environment variables
)
return result.stdout
def test_praisonai_command(self):
command = "praisonai --framework autogen --auto create movie script about cat in mars"
result = self.run_command(command)
# Handle API authentication errors in command line output
if ('Invalid API Key' in result or 'AuthenticationError' in result or
'InstructorRetryException' in result or '401' in result):
self.skipTest(f"Skipping due to API authentication in command output")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
if ('Invalid API Key' in result or 'AuthenticationError' in result or
'InstructorRetryException' in result or '401' in result):
self.skipTest(f"Skipping due to no valid API key provided")
self.assertIn('TERMINATE', result)
def test_praisonai_init_command(self):
command = "praisonai --framework autogen --init create movie script about cat in mars"
result = self.run_command(command)
# Handle API authentication errors in command line output
if ('Invalid API Key' in result or 'AuthenticationError' in result or
'InstructorRetryException' in result or '401' in result):
self.skipTest(f"Skipping due to API authentication in command output")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
if ('Invalid API Key' in result or 'AuthenticationError' in result or
'InstructorRetryException' in result or '401' in result):
self.skipTest(f"Skipping due to no valid API key provided")
self.assertIn('created successfully', result)
class TestExamples(unittest.TestCase):
@@ -111,10 +154,15 @@ class TestExamples(unittest.TestCase):
f"Expected meaningful result, got: {result}"
)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
self.skipTest(f"Skipping due to API authentication: {e}")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
self.skipTest(f"Skipping due to no valid API key provided: {e}")
else:
# Real API key provided - test should fail, not skip
raise
def test_advanced_example(self):
@@ -132,10 +180,15 @@ class TestExamples(unittest.TestCase):
f"Expected meaningful result, got: {result}"
)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
self.skipTest(f"Skipping due to API authentication: {e}")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
self.skipTest(f"Skipping due to no valid API key provided: {e}")
else:
# Real API key provided - test should fail, not skip
raise
def test_auto_example(self):
@@ -153,10 +206,15 @@ class TestExamples(unittest.TestCase):
f"Expected meaningful result, got: {result}"
)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
self.skipTest(f"Skipping due to API authentication: {e}")
# Only skip if no API key provided or using test/fallback key
api_key = os.environ.get('OPENAI_API_KEY', '')
if (not api_key or
api_key.startswith('sk-test-') or
api_key == 'nokey' or
'fallback' in api_key):
self.skipTest(f"Skipping due to no valid API key provided: {e}")
else:
# Real API key provided - test should fail, not skip
raise
if __name__ == '__main__':
+32 -1
View File
@@ -1,14 +1,24 @@
# tests/test_agents_playbook.py
import unittest
import subprocess
import pytest
import os
from praisonai import PraisonAI
class TestPraisonAIFramework(unittest.TestCase):
@pytest.mark.real
def test_main_with_autogen_framework(self):
"""Test AutoGen framework integration with real API calls"""
# Check if we have a real API key
api_key = os.environ.get('OPENAI_API_KEY')
if not api_key or api_key == 'test-key' or api_key.startswith('sk-test-'):
self.skipTest("Skipping real test - no valid OPENAI_API_KEY provided")
praisonai = PraisonAI(agent_file='tests/autogen-agents.yaml')
try:
result = praisonai.run()
self.assertIn('### Task Output ###', result)
self.assertIn('### Output ###', result)
except Exception as e:
if ('Invalid API Key' in str(e) or 'AuthenticationError' in str(e) or
'InstructorRetryException' in str(e) or '401' in str(e)):
@@ -16,7 +26,14 @@ class TestPraisonAIFramework(unittest.TestCase):
else:
raise
@pytest.mark.real
def test_main_with_custom_framework(self):
"""Test CrewAI framework integration with real API calls"""
# Check if we have a real API key
api_key = os.environ.get('OPENAI_API_KEY')
if not api_key or api_key == 'test-key' or api_key.startswith('sk-test-'):
self.skipTest("Skipping real test - no valid OPENAI_API_KEY provided")
praisonai = PraisonAI(agent_file='tests/crewai-agents.yaml')
try:
result = praisonai.run()
@@ -28,7 +45,14 @@ class TestPraisonAIFramework(unittest.TestCase):
else:
raise
@pytest.mark.real
def test_main_with_internet_search_tool(self):
"""Test internet search tool integration with real API calls"""
# Check if we have a real API key
api_key = os.environ.get('OPENAI_API_KEY')
if not api_key or api_key == 'test-key' or api_key.startswith('sk-test-'):
self.skipTest("Skipping real test - no valid OPENAI_API_KEY provided")
praisonai = PraisonAI(agent_file='tests/search-tool-agents.yaml')
try:
result = praisonai.run()
@@ -40,7 +64,14 @@ class TestPraisonAIFramework(unittest.TestCase):
else:
raise
@pytest.mark.real
def test_main_with_built_in_tool(self):
"""Test built-in tool integration with real API calls"""
# Check if we have a real API key
api_key = os.environ.get('OPENAI_API_KEY')
if not api_key or api_key == 'test-key' or api_key.startswith('sk-test-'):
self.skipTest("Skipping real test - no valid OPENAI_API_KEY provided")
praisonai = PraisonAI(agent_file='tests/inbuilt-tool-agents.yaml')
try:
result = praisonai.run()
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""
Test script to verify context management functionality
"""
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))
from praisonaiagents.task.task import Task
from praisonaiagents.agent.agent import Agent
from praisonaiagents.process.process import Process
from praisonaiagents.main import TaskOutput
def test_context_management():
"""Test context management with and without retain_full_context"""
print("Starting context management test...")
try:
# Create a mock agent
agent = Agent(name="Test Agent", role="Tester", goal="Test context management")
print("✓ Created test agent")
# Create tasks with results
task1 = Task(
name="task1",
description="First task",
agent=agent,
status="completed"
)
task1.result = TaskOutput(
description="First task",
raw="Result from task 1",
agent="Test Agent"
)
print("✓ Created task1")
task2 = Task(
name="task2",
description="Second task",
agent=agent,
status="completed"
)
task2.result = TaskOutput(
description="Second task",
raw="Result from task 2",
agent="Test Agent"
)
# Set up the previous_tasks manually since it's not a constructor parameter
task2.previous_tasks = ["task1"]
print("✓ Created task2")
# Test case 1: Default behavior (retain_full_context=False)
task3_limited = Task(
name="task3_limited",
description="Third task with limited context",
agent=agent,
retain_full_context=False # Default behavior
)
# Set up the previous_tasks manually
task3_limited.previous_tasks = ["task1", "task2"]
print("✓ Created task3_limited")
# Test case 2: Full context retention (retain_full_context=True)
task3_full = Task(
name="task3_full",
description="Third task with full context",
agent=agent,
retain_full_context=True # Original behavior
)
# Set up the previous_tasks manually
task3_full.previous_tasks = ["task1", "task2"]
print("✓ Created task3_full")
# Create process and test context building
tasks_dict = {
task1.id: task1,
task2.id: task2,
task3_limited.id: task3_limited,
task3_full.id: task3_full
}
process = Process(tasks=tasks_dict, agents=[agent])
print("✓ Created process")
# Test limited context
limited_context = process._build_task_context(task3_limited)
print("Limited context (retain_full_context=False):")
print(f"'{limited_context}'")
print()
# Test full context
full_context = process._build_task_context(task3_full)
print("Full context (retain_full_context=True):")
print(f"'{full_context}'")
print()
# Verify results
assert "task2" in limited_context, "Limited context should include most recent task"
assert "task1" not in limited_context, "Limited context should NOT include earlier tasks"
assert "task1" in full_context, "Full context should include all previous tasks"
assert "task2" in full_context, "Full context should include all previous tasks"
print("✅ All tests passed!")
print("- Limited context only includes most recent previous task")
print("- Full context includes all previous tasks")
print("- Backwards compatibility maintained")
except Exception as e:
print(f"❌ Test failed with error: {e}")
import traceback
traceback.print_exc()
raise
if __name__ == "__main__":
test_context_management()
Generated
+5 -5
View File
@@ -3614,7 +3614,7 @@ wheels = [
[[package]]
name = "praisonai"
version = "2.2.10"
version = "2.2.16"
source = { editable = "." }
dependencies = [
{ name = "instructor" },
@@ -3756,7 +3756,7 @@ requires-dist = [
{ name = "plotly", marker = "extra == 'realtime'", specifier = ">=5.24.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.15" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.15" },
{ name = "praisonaiagents", specifier = ">=0.0.87" },
{ name = "praisonaiagents", specifier = ">=0.0.89" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = ">=0.2.19" },
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
@@ -3813,7 +3813,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.87"
version = "0.0.89"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mcp" },
@@ -3821,9 +3821,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/21/46/9761f0fc2d21361d418df9978ec71de74fd5a24e324c9ee7c8b0ad0f1965/praisonaiagents-0.0.87.tar.gz", hash = "sha256:d86c223e5b6f03c83a144270128e0b3f8b0929fb0e8389a85939b659048c04d1", size = 126602 }
sdist = { url = "https://files.pythonhosted.org/packages/3f/78/d374e38d8caa19fb4f6e64fc2642bddcdbaba9d9abd490bf638fbffe44ab/praisonaiagents-0.0.89.tar.gz", hash = "sha256:a3fed2a23ea0087f06316445f298fe3ca71cec6036a53cf02c2d4932ffea4572", size = 123918 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/6f/6e1253bacf0026a57ec4bb2467401f5e541082d5cc8580e22cdc483b6419/praisonaiagents-0.0.87-py3-none-any.whl", hash = "sha256:fa6779c4980cd1318de33489552b69e612f1ca7aae482a4788c6d89e5b974c08", size = 146337 },
{ url = "https://files.pythonhosted.org/packages/af/af/e6991e977b3b74826736ef8fbc3dfdaf6eaa3987d60a814ca5bcd2c4a8b1/praisonaiagents-0.0.89-py3-none-any.whl", hash = "sha256:32cf8e2781fe03eec9c148ad99cc342c9cd0686cce5ff5c39545b730ac986fbb", size = 143626 },
]
[[package]]