Compare commits

...
2 Commits
Author SHA1 Message Date
Mervin Praison d60c31a86d Release v2.3.29 2025-12-18 02:47:04 +00:00
Mervin Praison fcb7b2a0d0 fix(tests): Mark test_phase2_refactor tests as @pytest.mark.real
These tests make real API calls and should be excluded from CI.
2025-12-17 13:31:01 +00:00
11 changed files with 532 additions and 179 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
flask \
"praisonai>=2.3.28" \
"praisonai>=2.3.29" \
"praisonai[api]" \
gunicorn \
markdown
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.3.28" \
"praisonai>=2.3.29" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.3.28" \
"praisonai>=2.3.29" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.3.28" \
"praisonai>=2.3.29" \
"praisonai[ui]" \
"praisonai[crewai]"
+41 -36
View File
@@ -1,44 +1,49 @@
# Movie Script Workflow - Cat in Mars
# Uses the modern agents + steps format
name: Create Movie Script About Cat in Mars
description: A sequential workflow to research, design narrative, and write a movie script
framework: praisonai
process: sequential
topic: create movie script about cat in mars
memory: false
roles:
workflow:
verbose: true
agents:
researcher:
backstory: Skilled in research, with a focus on gathering accurate and relevant
information.
name: Researcher
role: Research Analyst
goal: Gather information about Mars and cats
role: Researcher
min_reflect: 1
max_reflect: 2
tasks:
research_task:
description: Research about Mars, its environment, and the feasibility of
a cat being on Mars. Also, research about cat behavior and characteristics.
expected_output: Document with research findings on Mars and cats.
backstory: Skilled in research, with a focus on gathering accurate and relevant information.
instructions: |
Research about Mars, its environment, and the feasibility of a cat being on Mars.
Also, research about cat behavior and characteristics.
tools:
- search_tool
- search_tool
narrative_designer:
backstory: Skilled in narrative development, with a focus on creating engaging
stories.
name: Narrative Designer
role: Story Concept Developer
goal: Create a story concept for a movie about a cat in Mars
role: Narrative Designer
tasks:
story_concept_development:
description: Use the research findings to craft a unique story concept for
a movie about a cat in Mars.
expected_output: Document with narrative arcs, character bios, and settings.
tools:
- ''
backstory: Skilled in narrative development, with a focus on creating engaging stories.
instructions: |
Use the research findings to craft a unique story concept for a movie about a cat in Mars.
Create narrative arcs, character bios, and settings.
scriptwriter:
backstory: Expert in dialogue and script structure, translating concepts into
scripts.
name: Scriptwriter
role: Script Writer
goal: Write a movie script about a cat in Mars
role: Scriptwriter
tasks:
scriptwriting_task:
description: Turn the story concept into a production-ready movie script,
including dialogue and scene details.
expected_output: Final movie script with dialogue and scene details.
tools:
- ''
dependencies: []
backstory: Expert in dialogue and script structure, translating concepts into scripts.
instructions: |
Turn the story concept into a production-ready movie script,
including dialogue and scene details.
steps:
- agent: researcher
action: "Research about Mars environment and cat behavior for the movie concept"
- agent: narrative_designer
action: "Create story concept based on research: {{previous_output}}"
- agent: scriptwriter
action: "Write the final movie script based on the story concept: {{previous_output}}"
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.28.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.28.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.29.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.29.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
@@ -1,25 +1,31 @@
"""
Flow Display Handler for CLI.
Provides visual workflow tracking.
Shows a visual flow diagram of the workflow WITHOUT executing it.
Usage: praisonai agents.yaml --flow-display
"""
from typing import Any, Dict, Tuple, List
from typing import Any, Dict, Tuple
from .base import FlagHandler
import os
import yaml
class FlowDisplayHandler(FlagHandler):
"""
Handler for --flow-display flag.
Enables visual workflow tracking for multi-agent executions.
Shows a visual flow diagram/chart of agents and tasks WITHOUT executing.
This allows users to preview the workflow structure before running it.
Example:
praisonai agents.yaml --flow-display
praisonai "Complex task" --flow-display
"""
def __init__(self, verbose: bool = False):
super().__init__(verbose)
self._console = None
@property
def feature_name(self) -> str:
return "flow_display"
@@ -30,160 +36,479 @@ class FlowDisplayHandler(FlagHandler):
@property
def flag_help(self) -> str:
return "Enable visual workflow tracking"
return "Show workflow flow diagram without executing"
def _get_console(self):
"""Get or create Rich Console."""
if self._console is None:
try:
from rich.console import Console
self._console = Console()
except ImportError:
pass
return self._console
def check_dependencies(self) -> Tuple[bool, str]:
"""Check if FlowDisplay is available."""
"""Check if Rich is available."""
try:
import importlib.util
if importlib.util.find_spec("praisonaiagents") is not None:
return True, ""
return False, "praisonaiagents not installed"
rich_available = importlib.util.find_spec("rich") is not None
if not rich_available:
return False, "rich not installed. Install with: pip install rich"
return True, ""
except ImportError:
return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
def _get_flow_display(self):
"""Get FlowDisplay class lazily."""
try:
from praisonaiagents import FlowDisplay
return FlowDisplay
except ImportError:
return None
def create_flow_display(self, title: str = "Workflow") -> Any:
"""
Create a FlowDisplay instance.
Args:
title: Title for the workflow display
Returns:
FlowDisplay instance or None
"""
FlowDisplay = self._get_flow_display()
if not FlowDisplay:
self.print_status(
"FlowDisplay requires praisonaiagents. Install with: pip install praisonaiagents",
"error"
)
return None
try:
return FlowDisplay(title=title)
except Exception as e:
self.log(f"Failed to create FlowDisplay: {e}", "error")
return None
return False, "rich not installed. Install with: pip install rich"
def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
"""
Apply flow display configuration.
Args:
config: Agent configuration dictionary
flag_value: Boolean indicating flow display
Returns:
Modified configuration
"""
"""Apply flow display configuration."""
if flag_value:
config['flow_display'] = True
return config
def display_workflow_start(self, workflow_name: str, agents: List[str] = None):
def display_flow_diagram(self, yaml_file: str, show_footer: bool = True) -> bool:
"""
Display workflow start.
Display a visual flow diagram of the workflow from YAML file.
Args:
workflow_name: Name of the workflow
agents: List of agent names
"""
self.print_status(f"\n🚀 Starting Workflow: {workflow_name}", "info")
self.print_status("=" * 50, "info")
if agents:
self.print_status("Agents:", "info")
for i, agent in enumerate(agents, 1):
self.print_status(f" {i}. {agent}", "info")
self.print_status("=" * 50, "info")
def display_step(self, step_num: int, step_name: str, status: str = "running"):
"""
Display a workflow step.
Args:
step_num: Step number
step_name: Name of the step
status: Step status (running, completed, failed)
"""
icons = {
'running': '⏳',
'completed': '✅',
'failed': '❌',
'pending': '⬜'
}
icon = icons.get(status, '•')
self.print_status(f" {icon} Step {step_num}: {step_name}", "info")
def display_workflow_end(self, success: bool = True, duration: float = None):
"""
Display workflow end.
Args:
success: Whether workflow completed successfully
duration: Workflow duration in seconds
"""
self.print_status("=" * 50, "info")
if success:
msg = "✅ Workflow completed successfully"
else:
msg = "❌ Workflow failed"
if duration:
msg += f" ({duration:.2f}s)"
self.print_status(msg, "success" if success else "error")
def track_execution(self, func):
"""
Decorator to track function execution in flow display.
Args:
func: Function to track
yaml_file: Path to the agents.yaml file
show_footer: Whether to show the "run without --flow-display" footer
Returns:
Wrapped function
True if displayed successfully, False otherwise
"""
import functools
console = self._get_console()
if not console:
print("Rich library not available for flow display")
return False
@functools.wraps(func)
def wrapper(*args, **kwargs):
func_name = func.__name__
# Load YAML config
try:
if not os.path.exists(yaml_file):
console.print(f"[red]Error: File not found: {yaml_file}[/red]")
return False
self.display_step(1, func_name, "running")
try:
result = func(*args, **kwargs)
self.display_step(1, func_name, "completed")
return result
except Exception:
self.display_step(1, func_name, "failed")
raise
with open(yaml_file, 'r') as f:
config = yaml.safe_load(f)
except Exception as e:
console.print(f"[red]Error loading YAML: {e}[/red]")
return False
return wrapper
# Detect YAML format: 'roles' (old) or 'agents'+'steps' (new workflow)
has_roles = 'roles' in config and config['roles']
has_agents = 'agents' in config and config['agents']
has_steps = 'steps' in config and config['steps']
if not has_roles and not has_agents:
console.print("[red]Error: Invalid YAML format. No 'roles' or 'agents' found.[/red]")
return False
try:
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from rich import box
# Determine format type
is_workflow_format = has_agents and has_steps
# Extract workflow info based on format
if is_workflow_format:
name = config.get('name', 'Workflow')
description = config.get('description', '')
framework = config.get('framework', 'praisonai')
workflow_config = config.get('workflow', {})
memory = workflow_config.get('memory_config', {}).get('persist', False)
num_agents = len(config['agents'])
num_steps = len(config['steps'])
else:
name = config.get('topic', 'Workflow')
description = ''
framework = config.get('framework', 'praisonai')
process = config.get('process', 'sequential')
memory = config.get('memory', False)
num_agents = len(config['roles'])
num_steps = sum(len(details.get('tasks', {})) for details in config['roles'].values())
# Build the flow diagram
console.print()
# === HEADER ===
header = Text()
header.append("📊 WORKFLOW FLOW DIAGRAM\n", style="bold cyan")
header.append("─" * 40, style="dim")
header_panel = Panel(
header,
border_style="cyan",
box=box.DOUBLE_EDGE,
padding=(0, 2)
)
console.print(header_panel)
# === WORKFLOW INFO ===
info_table = Table(show_header=False, box=None, padding=(0, 2))
info_table.add_column("Key", style="bold yellow")
info_table.add_column("Value", style="white")
info_table.add_row("📋 Name:", name)
if description:
desc_short = description[:60] + "..." if len(description) > 60 else description
info_table.add_row("📝 Description:", desc_short)
info_table.add_row("⚙️ Framework:", framework)
if is_workflow_format:
info_table.add_row("🔄 Format:", "WORKFLOW (steps-based)")
planning = workflow_config.get('planning', False)
info_table.add_row("🧠 Planning:", "✅ Enabled" if planning else "❌ Disabled")
else:
info_table.add_row("🔄 Process:", process.upper())
info_table.add_row("💾 Memory:", "✅ Enabled" if memory else "❌ Disabled")
info_table.add_row("👥 Agents:", str(num_agents))
info_table.add_row("📝 Steps:", str(num_steps))
console.print(Panel(info_table, title="[bold white]Workflow Configuration[/bold white]",
border_style="blue", box=box.ROUNDED))
console.print()
# === FLOW DIAGRAM ===
if is_workflow_format:
self._display_steps_flow(console, config)
else:
process = config.get('process', 'sequential')
if process == 'sequential':
self._display_sequential_flow(console, config)
elif process == 'hierarchical':
self._display_hierarchical_flow(console, config)
else:
self._display_sequential_flow(console, config)
# === DETAILED AGENT INFO ===
console.print()
if is_workflow_format:
self._display_workflow_agent_details(console, config)
else:
self._display_agent_details(console, config)
# === FOOTER ===
if show_footer:
console.print()
footer = Text()
footer.append("💡 ", style="bold")
footer.append("To execute this workflow, run without --flow-display flag", style="dim italic")
console.print(Panel(footer, border_style="dim", box=box.ROUNDED))
console.print()
return True
except ImportError as e:
console.print(f"[red]Error: Rich components not available: {e}[/red]")
return False
def execute(self, workflow_name: str = None, **kwargs) -> Any:
def _display_sequential_flow(self, console, config: Dict):
"""Display sequential flow diagram."""
from rich.panel import Panel
from rich.text import Text
from rich import box
roles = list(config['roles'].items())
# Box width (content area)
BOX_WIDTH = 36
def pad_line(content: str, width: int = BOX_WIDTH) -> str:
"""Pad content to fixed width."""
padding = width - len(content)
if padding > 0:
return content + " " * padding
return content[:width]
flow_text = Text()
flow_text.append(" SEQUENTIAL EXECUTION FLOW\n\n", style="bold magenta")
for i, (role_key, details) in enumerate(roles):
role_name = details.get('role', role_key)
tasks = list(details.get('tasks', {}).keys())
# Agent box
if i == 0:
flow_text.append(" ┌── START ──┐\n", style="green")
flow_text.append(" │\n", style="dim")
flow_text.append(" ▼\n", style="dim")
# Agent node
flow_text.append(" ╔════════════════════════════════════╗\n", style="cyan")
line = f" Agent {i+1}: {role_name}"
flow_text.append(f" ║{pad_line(line)}║\n", style="cyan")
# Tasks
for task in tasks:
line = f" -> {task}"
flow_text.append(f" ║{pad_line(line)}║\n", style="dim white")
flow_text.append(" ╚════════════════════════════════════╝\n", style="cyan")
# Arrow to next
if i < len(roles) - 1:
flow_text.append(" │\n", style="dim")
flow_text.append(" output\n", style="dim italic")
flow_text.append(" ▼\n", style="dim")
else:
flow_text.append(" │\n", style="dim")
flow_text.append(" ▼\n", style="dim")
flow_text.append(" └── END ────┘\n", style="red")
console.print(Panel(flow_text, border_style="magenta", box=box.DOUBLE_EDGE,
title="[bold white]Execution Flow[/bold white]"))
def _display_hierarchical_flow(self, console, config: Dict):
"""Display hierarchical flow diagram."""
from rich.panel import Panel
from rich.text import Text
from rich import box
manager_llm = config.get('manager_llm', 'default')
flow_text = Text()
flow_text.append("👑 HIERARCHICAL EXECUTION FLOW\n\n", style="bold magenta")
# Manager at top
flow_text.append(" ┌──────────────────────┐\n", style="yellow")
flow_text.append(" │ 👑 MANAGER AGENT │\n", style="bold yellow")
flow_text.append(f" │ LLM: {manager_llm:<14}│\n", style="dim yellow")
flow_text.append(" └──────────┬───────────┘\n", style="yellow")
flow_text.append(" │\n", style="dim")
flow_text.append(" ┌──────────┴───────────┐\n", style="dim")
flow_text.append(" │ delegates to │\n", style="dim italic")
flow_text.append(" └──────────┬───────────┘\n", style="dim")
roles = list(config['roles'].items())
num_agents = len(roles)
# Draw branches
if num_agents > 0:
branch_line = " "
for i in range(num_agents):
if i == 0:
branch_line += "┌" + "─" * 8
elif i == num_agents - 1:
branch_line += "┬" + "─" * 8 + "┐"
else:
branch_line += "┬" + "─" * 8
flow_text.append(branch_line + "\n", style="dim")
# Agent boxes
for i, (role_key, details) in enumerate(roles):
flow_text.append("\n ╔═══════════════╗", style="cyan")
flow_text.append("\n")
for i, (role_key, details) in enumerate(roles):
role_name = details.get('role', role_key)[:12]
flow_text.append(f" ║ 🤖 {role_name:<10} ║", style="cyan")
flow_text.append("\n")
for i, (role_key, details) in enumerate(roles):
flow_text.append(" ╚═══════════════╝", style="cyan")
flow_text.append("\n")
console.print(Panel(flow_text, border_style="magenta", box=box.DOUBLE_EDGE,
title="[bold white]Execution Flow[/bold white]"))
def _display_agent_details(self, console, config: Dict):
"""Display detailed agent information."""
from rich.table import Table
from rich import box
table = Table(
title="[bold cyan]📋 Agent & Task Details[/bold cyan]",
box=box.ROUNDED,
show_header=True,
header_style="bold magenta",
border_style="blue",
padding=(0, 1),
expand=True
)
table.add_column("#", style="dim", width=3, justify="center")
table.add_column("Agent", style="bold yellow", min_width=15)
table.add_column("Role", style="cyan", min_width=15)
table.add_column("Goal", style="white", min_width=25, overflow="fold")
table.add_column("Tasks", style="green", min_width=20)
table.add_column("Tools", style="magenta", min_width=15)
for i, (role_key, details) in enumerate(config['roles'].items(), 1):
role_name = details.get('role', role_key)
goal = details.get('goal', '-')
if len(goal) > 50:
goal = goal[:47] + "..."
tasks = list(details.get('tasks', {}).keys())
tasks_str = "\n".join(f"• {t}" for t in tasks) if tasks else "-"
tools = details.get('tools', [])
tools_str = ", ".join(t for t in tools if t) if tools else "-"
table.add_row(
str(i),
role_name,
role_key,
goal,
tasks_str,
tools_str
)
console.print(table)
def _display_steps_flow(self, console, config: Dict):
"""Display steps-based workflow flow diagram."""
from rich.panel import Panel
from rich.text import Text
from rich import box
steps = config.get('steps', [])
agents = config.get('agents', {})
# Box width (content area)
BOX_WIDTH = 36
def pad_line(content: str, width: int = BOX_WIDTH) -> str:
"""Pad content to fixed width, accounting for emoji width."""
# Emojis typically take 2 character widths
emoji_count = sum(1 for c in content if ord(c) > 0x1F300)
actual_len = len(content) + emoji_count # Add extra for emoji width
padding = width - actual_len
if padding > 0:
return content + " " * padding
return content[:width]
flow_text = Text()
flow_text.append(" STEPS-BASED EXECUTION FLOW\n\n", style="bold magenta")
flow_text.append(" ┌── START ──┐\n", style="green")
flow_text.append(" │\n", style="dim")
flow_text.append(" ▼\n", style="dim")
for i, step in enumerate(steps):
# Determine step type
agent_key = step.get('agent', '')
step_name = step.get('name', '')
# Check for special step types
has_route = 'route' in step
has_parallel = 'parallel' in step
has_loop = 'loop' in step
has_repeat = 'repeat' in step
if has_route:
# Route step
flow_text.append(" ╔════════════════════════════════════╗\n", style="yellow")
line = f" ROUTE: {step_name or 'routing'}"
flow_text.append(f" ║{pad_line(line)}║\n", style="yellow")
routes = step['route']
for route_name, route_agents in routes.items():
agents_str = ", ".join(route_agents) if isinstance(route_agents, list) else str(route_agents)
line = f" -> {route_name}: {agents_str}"
flow_text.append(f" ║{pad_line(line)}║\n", style="yellow")
flow_text.append(" ╚════════════════════════════════════╝\n", style="yellow")
elif has_parallel:
# Parallel step
flow_text.append(" ╔════════════════════════════════════╗\n", style="green")
line = f" PARALLEL: {step_name or 'parallel'}"
flow_text.append(f" ║{pad_line(line)}║\n", style="green")
for p_step in step['parallel']:
p_agent = p_step.get('agent', '?')
line = f" |-- {p_agent}"
flow_text.append(f" ║{pad_line(line)}║\n", style="green")
flow_text.append(" ╚════════════════════════════════════╝\n", style="green")
else:
# Regular agent step
agent_info = agents.get(agent_key, {})
agent_name = agent_info.get('name', agent_key)
flow_text.append(" ╔════════════════════════════════════╗\n", style="cyan")
line = f" Step {i+1}: {agent_name}"
flow_text.append(f" ║{pad_line(line)}║\n", style="cyan")
# Show modifiers
if has_loop:
loop_over = step['loop'].get('over', '?')
line = f" [loop over: {loop_over}]"
flow_text.append(f" ║{pad_line(line)}║\n", style="dim magenta")
if has_repeat:
until = step['repeat'].get('until', '?')
max_iter = step['repeat'].get('max_iterations', '?')
line = f" [repeat until: {until} max:{max_iter}]"
flow_text.append(f" ║{pad_line(line)}║\n", style="dim magenta")
flow_text.append(" ╚════════════════════════════════════╝\n", style="cyan")
# Arrow to next
if i < len(steps) - 1:
flow_text.append(" │\n", style="dim")
flow_text.append(" ▼\n", style="dim")
flow_text.append(" │\n", style="dim")
flow_text.append(" ▼\n", style="dim")
flow_text.append(" └── END ────┘\n", style="red")
console.print(Panel(flow_text, border_style="magenta", box=box.DOUBLE_EDGE,
title="[bold white]Execution Flow[/bold white]"))
def _display_workflow_agent_details(self, console, config: Dict):
"""Display detailed agent information for workflow format."""
from rich.table import Table
from rich import box
table = Table(
title="[bold cyan]📋 Agent Details[/bold cyan]",
box=box.ROUNDED,
show_header=True,
header_style="bold magenta",
border_style="blue",
padding=(0, 1),
expand=True
)
table.add_column("#", style="dim", width=3, justify="center")
table.add_column("Agent Key", style="bold yellow", min_width=15)
table.add_column("Name", style="cyan", min_width=15)
table.add_column("Role", style="white", min_width=20)
table.add_column("Goal", style="green", min_width=25, overflow="fold")
for i, (agent_key, details) in enumerate(config['agents'].items(), 1):
name = details.get('name', agent_key)
role = details.get('role', '-')
goal = details.get('goal', '-')
if len(goal) > 40:
goal = goal[:37] + "..."
table.add_row(
str(i),
agent_key,
name,
role,
goal
)
console.print(table)
def execute(self, yaml_file: str = None, **kwargs) -> bool:
"""
Execute flow display setup.
Execute flow display - shows diagram without running workflow.
Args:
workflow_name: Name of the workflow
yaml_file: Path to the YAML file
Returns:
FlowDisplay instance or None
True if displayed successfully
"""
if workflow_name:
self.display_workflow_start(workflow_name)
return self.create_flow_display(workflow_name or "Workflow")
if yaml_file:
return self.display_flow_diagram(yaml_file)
return False
+15
View File
@@ -495,6 +495,21 @@ class PraisonAI:
print(result)
return result
else:
# Flow Display - Show flow diagram WITHOUT executing (--flow-display flag)
if getattr(args, 'flow_display', False):
from .features.flow_display import FlowDisplayHandler
flow_handler = FlowDisplayHandler(verbose=getattr(args, 'verbose', False))
flow_handler.display_flow_diagram(self.agent_file)
return # Exit without executing
# Always show flow diagram before execution (default behavior)
try:
from .features.flow_display import FlowDisplayHandler
flow_handler = FlowDisplayHandler(verbose=getattr(args, 'verbose', False))
flow_handler.display_flow_diagram(self.agent_file, show_footer=False)
except Exception:
pass # Continue even if flow display fails
agents_generator = AgentsGenerator(
self.agent_file,
self.framework,
+1 -1
View File
@@ -57,7 +57,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.3.28 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.3.29 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.3.28"
__version__ = "2.3.29"
+9 -1
View File
@@ -1,15 +1,21 @@
#!/usr/bin/env python3
"""Test script to verify Phase 2 refactoring maintains backward compatibility"""
"""Test script to verify Phase 2 refactoring maintains backward compatibility
NOTE: These tests make real API calls and require valid API keys.
They are marked with @pytest.mark.real to be excluded from CI.
"""
import asyncio
import sys
import os
import pytest
# Add the correct path for imports
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src/praisonai-agents'))
from praisonaiagents.llm import LLM
@pytest.mark.real
def test_sync_methods():
"""Test synchronous methods still work correctly"""
print("Testing synchronous methods...")
@@ -52,6 +58,7 @@ def test_sync_methods():
print("✅ Synchronous methods working correctly\n")
@pytest.mark.real
async def test_async_methods():
"""Test asynchronous methods still work correctly"""
print("Testing asynchronous methods...")
@@ -94,6 +101,7 @@ async def test_async_methods():
print("✅ Asynchronous methods working correctly\n")
@pytest.mark.real
def test_edge_cases():
"""Test edge cases and special scenarios"""
print("Testing edge cases...")