Compare commits

...
2 Commits
Author SHA1 Message Date
Mervin Praison f991f37cfe Release v2.3.23 2025-12-17 00:41:48 +00:00
Mervin Praison a43b3ac72c Release v2.3.22 2025-12-17 00:25:14 +00:00
16 changed files with 1656 additions and 115 deletions

No files matched your search

+33 -25
View File
@@ -562,35 +562,22 @@ workflow = Workflow(steps=[
max_iterations=5
)
])
```
### Task Callbacks & Variables (process="workflow")
```python
from praisonaiagents import Agent, Task, PraisonAIAgents
# Per-task agent config (no need to create agent separately)
task = Task(
description="Research {{topic}}",
agent_config={"name": "Researcher", "role": "Expert", "llm": "gpt-4o-mini"},
variables={"topic": "AI trends"}
# 5. CALLBACKS - Monitor workflow execution
workflow = Workflow(
steps=[step1, step2],
on_workflow_start=lambda w, i: print(f"Starting: {i}"),
on_step_complete=lambda name, r: print(f"{name}: {r.output[:50]}"),
on_workflow_complete=lambda w, r: print(f"Done: {r['status']}")
)
# Callbacks for workflow execution
def on_start(task, task_id):
print(f"Starting: {task.name}")
# 6. GUARDRAILS - Validate and retry
def validate(result):
return ("error" not in result.output, "Fix the error")
def on_complete(task, output):
print(f"Completed: {task.name}")
agents = PraisonAIAgents(
agents=[agent],
tasks=[task],
process="workflow",
on_task_start=on_start,
on_task_complete=on_complete,
variables={"global_var": "shared_value"} # Global variables for all tasks
)
workflow = Workflow(steps=[
WorkflowStep(name="gen", handler=generator, guardrail=validate, max_retries=3)
])
```
### 9. Hooks
@@ -736,6 +723,12 @@ praisonai workflow run deploy --workflow-var environment=staging --workflow-var
# Execute with planning mode (AI creates sub-steps for each workflow step)
praisonai workflow run "Research Blog" --planning --verbose
# Execute with reasoning mode (chain-of-thought)
praisonai workflow run "Analysis" --reasoning --verbose
# Execute with memory enabled
praisonai workflow run "Research" --memory
# Show workflow details
praisonai workflow show deploy
@@ -747,8 +740,23 @@ praisonai "What is AI?" --workflow "Research,Summarize" --save
# Inline workflow with step actions
praisonai "GPT-5" --workflow "Research:Search for info,Write:Write blog" --tools tavily
# Workflow CLI help
praisonai workflow help
```
**Workflow CLI Options:**
| Flag | Description |
|------|-------------|
| `--workflow-var key=value` | Set workflow variable (can be repeated) |
| `--llm <model>` | LLM model (e.g., openai/gpt-4o-mini) |
| `--tools <tools>` | Tools (comma-separated, e.g., tavily) |
| `--planning` | Enable planning mode |
| `--reasoning` | Enable reasoning mode |
| `--memory` | Enable memory |
| `--verbose` | Enable verbose output |
| `--save` | Save output to file |
### Hooks CLI:
```bash
# List configured hooks
+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.21" \
"praisonai>=2.3.23" \
"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.21" \
"praisonai>=2.3.23" \
"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.21" \
"praisonai>=2.3.23" \
"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.21" \
"praisonai>=2.3.23" \
"praisonai[ui]" \
"praisonai[crewai]"
@@ -826,7 +826,34 @@ Provide a JSON with the structure:
logging.info("Hierarchical task execution finished")
def workflow(self):
"""Synchronous version of workflow method"""
"""Synchronous version of workflow method.
DEPRECATED: This method is deprecated. Use the Workflow class instead:
```python
from praisonaiagents import Workflow, StepResult
from praisonaiagents.workflows import route, parallel, loop, repeat
workflow = Workflow(steps=[step1, step2])
result = workflow.start("input")
```
The Workflow class provides a simpler API with more features:
- route() for decision-based branching
- parallel() for concurrent execution
- loop() for iteration over lists/CSV
- repeat() for evaluator-optimizer patterns
- Callbacks (on_step_start, on_step_complete, etc.)
- Guardrails with validation feedback
- Status tracking
"""
import warnings
warnings.warn(
"process='workflow' is deprecated. Use the Workflow class instead: "
"from praisonaiagents import Workflow; workflow = Workflow(steps=[...]); workflow.start()",
DeprecationWarning,
stacklevel=2
)
current_iter = 0 # Track how many times we've looped
# Build workflow relationships first
for task in self.tasks.values():
@@ -23,7 +23,7 @@ import os
import re
import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Callable, Literal
from typing import Any, Dict, List, Optional, Callable, Literal, Tuple
from dataclasses import dataclass, field
logger = logging.getLogger(__name__)
@@ -190,7 +190,7 @@ class WorkflowStep:
should_run: Optional[Callable[['WorkflowContext'], bool]] = None # Function to check if step should run
handler: Optional[Callable[['WorkflowContext'], 'StepResult']] = None # Custom function instead of agent
on_error: Literal["stop", "continue", "retry"] = "stop"
max_retries: int = 1
max_retries: int = 3
# Context passing fields
context_from: Optional[List[str]] = None # Which previous steps to include context from
@@ -210,6 +210,26 @@ class WorkflowStep:
loop_over: Optional[str] = None # Variable name to iterate over (e.g., "items")
loop_var: str = "item" # Variable name for current item in loop
# Guardrail (like process="workflow")
guardrail: Optional[Callable[['StepResult'], Tuple[bool, Any]]] = None # Returns (is_valid, feedback)
# Output configuration (migrated from Task)
output_file: Optional[str] = None # Save output to file
output_json: Optional[Any] = None # Pydantic model for JSON output
output_pydantic: Optional[Any] = None # Pydantic model for structured output
# Image handling (migrated from Task)
images: Optional[List[str]] = None # Image paths/URLs for vision tasks
# Execution control (migrated from Task)
async_execution: bool = False # Run step asynchronously
quality_check: bool = True # Enable quality validation
rerun: bool = True # Allow step to be rerun
# Status tracking
status: str = "pending" # pending, running, completed, failed, skipped
retry_count: int = 0
# Backward compatibility aliases
@property
def evaluator(self):
@@ -238,7 +258,14 @@ class WorkflowStep:
"next_steps": self.next_steps,
"branch_condition": self.branch_condition,
"loop_over": self.loop_over,
"loop_var": self.loop_var
"loop_var": self.loop_var,
"output_file": self.output_file,
"output_json": self.output_json is not None,
"output_pydantic": self.output_pydantic is not None,
"images": self.images,
"async_execution": self.async_execution,
"quality_check": self.quality_check,
"rerun": self.rerun
}
@@ -257,6 +284,19 @@ class Workflow:
memory_config: Optional[Dict[str, Any]] = None # Memory configuration
planning: bool = False # Enable planning mode
planning_llm: Optional[str] = None # LLM for planning
reasoning: bool = False # Enable chain-of-thought reasoning
verbose: bool = False # Enable verbose output
# Callbacks (like process="workflow")
on_workflow_start: Optional[Callable[['Workflow', str], None]] = None # (workflow, input)
on_workflow_complete: Optional[Callable[['Workflow', Dict[str, Any]], None]] = None # (workflow, result)
on_step_start: Optional[Callable[[str, 'WorkflowContext'], None]] = None # (step_name, context)
on_step_complete: Optional[Callable[[str, 'StepResult'], None]] = None # (step_name, result)
on_step_error: Optional[Callable[[str, Exception], None]] = None # (step_name, error)
# Status tracking
status: str = "not_started" # not_started, running, completed, failed
step_statuses: Dict[str, str] = field(default_factory=dict) # {step_name: status}
def to_dict(self) -> Dict[str, Any]:
return {
@@ -307,12 +347,32 @@ class Workflow:
# Use default LLM if not specified
model = llm or self.default_llm or "gpt-4o-mini"
# Use workflow verbose setting if not overridden
verbose = verbose or self.verbose
# Add input to variables
all_variables = {**self.variables, "input": input}
results = []
previous_output = None
# Update workflow status
self.status = "running"
# Call on_workflow_start callback
if self.on_workflow_start:
try:
self.on_workflow_start(self, input)
except Exception as e:
logger.error(f"on_workflow_start callback failed: {e}")
# Planning mode - create execution plan before running
if self.planning:
plan = self._create_plan(input, model, verbose)
if plan and verbose:
print(f"📋 Execution Plan: {plan}")
all_variables["execution_plan"] = plan
# Process steps (may include Route, Parallel, Loop, Repeat)
i = 0
while i < len(self.steps):
@@ -374,68 +434,197 @@ class Workflow:
variables=all_variables.copy()
)
# Update step status
if hasattr(step, 'status'):
step.status = "running"
self.step_statuses[step.name] = "running"
# Call on_step_start callback
if self.on_step_start:
try:
self.on_step_start(step.name, context)
except Exception as e:
logger.error(f"on_step_start callback failed: {e}")
# Check should_run condition
if step.should_run:
try:
if not step.should_run(context):
if verbose:
print(f"⏭️ Skipped: {step.name}")
if hasattr(step, 'status'):
step.status = "skipped"
self.step_statuses[step.name] = "skipped"
i += 1
continue
except Exception as e:
logger.error(f"should_run failed for {step.name}: {e}")
# Execute step
# Execute step with retry and guardrail support
output = None
stop = False
step_error = None
max_retries = getattr(step, 'max_retries', 3)
retry_count = 0
validation_feedback = None
if step.handler:
# Custom handler function
while retry_count <= max_retries:
step_error = None
# Add validation feedback to context if retrying
if validation_feedback:
context.variables["validation_feedback"] = validation_feedback
try:
result = step.handler(context)
if isinstance(result, StepResult):
output = result.output
stop = result.stop_workflow
if result.variables:
all_variables.update(result.variables)
else:
output = str(result)
if step.handler:
# Custom handler function
result = step.handler(context)
if isinstance(result, StepResult):
output = result.output
stop = result.stop_workflow
if result.variables:
all_variables.update(result.variables)
else:
output = str(result)
elif step.agent:
# Direct agent with tools
# Substitute variables in action
action = step.action or input
for key, value in all_variables.items():
action = action.replace(f"{{{{{key}}}}}", str(value))
if previous_output:
action = action.replace("{{previous_output}}", str(previous_output))
action = action.replace("{{input}}", input)
# Add reasoning prompt if enabled
if self.reasoning:
action = f"Think step by step and reason through this task:\n\n{action}"
# Add validation feedback if retrying
if validation_feedback:
action = f"{action}\n\nPrevious attempt feedback: {validation_feedback}"
# Handle images if present
step_images = getattr(step, 'images', None)
if step_images:
output = step.agent.chat(action, images=step_images)
else:
output = step.agent.chat(action)
# Handle output_pydantic if present
output_pydantic = getattr(step, 'output_pydantic', None)
if output_pydantic and output:
try:
# Try to parse output as Pydantic model
if hasattr(output_pydantic, 'model_validate_json'):
parsed = output_pydantic.model_validate_json(output)
output = parsed.model_dump_json()
except Exception as e:
logger.debug(f"Pydantic parsing failed: {e}")
elif step.action:
# Action with agent_config - create temporary agent
from ..agent.agent import Agent
config = step.agent_config or self.default_agent_config or {}
# Get tools from step or config
step_tools = step.tools or config.get("tools", [])
temp_agent = Agent(
name=config.get("name", step.name),
role=config.get("role", "Assistant"),
goal=config.get("goal", "Complete the task"),
llm=config.get("llm", model),
tools=step_tools if step_tools else None,
verbose=verbose,
reasoning=self.reasoning
)
# Substitute variables in action
action = step.action
for key, value in all_variables.items():
action = action.replace(f"{{{{{key}}}}}", str(value))
if previous_output:
action = action.replace("{{previous_output}}", str(previous_output))
action = action.replace("{{input}}", input)
# Add reasoning prompt if enabled
if self.reasoning:
action = f"Think step by step and reason through this task:\n\n{action}"
if validation_feedback:
action = f"{action}\n\nPrevious attempt feedback: {validation_feedback}"
output = temp_agent.chat(action)
except Exception as e:
step_error = e
output = f"Error: {e}"
elif step.agent:
# Direct agent
if self.on_step_error:
try:
self.on_step_error(step.name, e)
except Exception:
pass
# Check guardrail if present
guardrail = getattr(step, 'guardrail', None)
if guardrail and output and not step_error:
try:
is_valid, feedback = guardrail(StepResult(output=output))
if not is_valid:
validation_feedback = str(feedback)
retry_count += 1
if verbose:
print(f"⚠️ {step.name} failed validation (attempt {retry_count}/{max_retries}): {feedback}")
continue # Retry
except Exception as e:
logger.error(f"Guardrail failed for {step.name}: {e}")
# Success - break out of retry loop
break
# Update step status
if step_error:
if hasattr(step, 'status'):
step.status = "failed"
self.step_statuses[step.name] = "failed"
else:
if hasattr(step, 'status'):
step.status = "completed"
self.step_statuses[step.name] = "completed"
# Create step result for callback
step_result = StepResult(output=output or "", stop_workflow=stop)
# Call on_step_complete callback
if self.on_step_complete:
try:
output = step.agent.chat(step.action or input)
self.on_step_complete(step.name, step_result)
except Exception as e:
output = f"Error: {e}"
elif step.action:
# Action with agent_config - create temporary agent
logger.error(f"on_step_complete callback failed: {e}")
# Handle output_file - save output to file
if hasattr(step, 'output_file') and step.output_file and output:
try:
from ..agent.agent import Agent
config = step.agent_config or self.default_agent_config or {}
temp_agent = Agent(
name=config.get("name", step.name),
role=config.get("role", "Assistant"),
goal=config.get("goal", "Complete the task"),
llm=config.get("llm", model),
verbose=verbose
)
# Substitute variables in action
action = step.action
import os
output_path = step.output_file
# Substitute variables in path
for key, value in all_variables.items():
action = action.replace(f"{{{{{key}}}}}", str(value))
if previous_output:
action = action.replace("{{previous_output}}", str(previous_output))
output = temp_agent.chat(action)
output_path = output_path.replace(f"{{{{{key}}}}}", str(value))
# Create directory if needed
os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else ".", exist_ok=True)
with open(output_path, "w") as f:
f.write(str(output))
if verbose:
print(f"📁 Saved output to: {output_path}")
except Exception as e:
output = f"Error: {e}"
logger.error(f"Failed to save output to file: {e}")
# Store result
results.append({
"step": step.name,
"output": output
"output": output,
"status": self.step_statuses.get(step.name, "completed"),
"retries": retry_count
})
previous_output = output
@@ -454,11 +643,59 @@ class Workflow:
i += 1
return {
# Update workflow status
self.status = "completed"
final_result = {
"output": previous_output,
"steps": results,
"variables": all_variables
"variables": all_variables,
"status": self.status
}
# Call on_workflow_complete callback
if self.on_workflow_complete:
try:
self.on_workflow_complete(self, final_result)
except Exception as e:
logger.error(f"on_workflow_complete callback failed: {e}")
return final_result
async def astart(
self,
input: str = "",
llm: Optional[str] = None,
verbose: bool = False
) -> Dict[str, Any]:
"""Async version of start() for async workflow execution.
Args:
input: The input text/prompt for the workflow
llm: LLM model to use (default: gpt-4o-mini)
verbose: Print step outputs
Returns:
Dict with 'output' (final result) and 'steps' (all step results)
"""
import asyncio
# Run the synchronous version in a thread pool
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(
None,
lambda: self.run(input, llm, verbose)
)
return result
async def arun(
self,
input: str = "",
llm: Optional[str] = None,
verbose: bool = False
) -> Dict[str, Any]:
"""Alias for astart() for backward compatibility."""
return await self.astart(input, llm, verbose)
def _normalize_steps(self) -> List['WorkflowStep']:
"""Convert mixed steps (Agent, function, WorkflowStep) to WorkflowStep list."""
@@ -489,21 +726,89 @@ class Workflow:
return normalized
def _create_plan(self, input: str, model: str, verbose: bool) -> Optional[str]:
"""Create an execution plan for the workflow using LLM.
Args:
input: The workflow input
model: LLM model to use
verbose: Print verbose output
Returns:
Execution plan as string, or None if planning fails
"""
try:
from ..agent.agent import Agent
# Describe the steps
step_descriptions = []
for i, step in enumerate(self.steps):
if isinstance(step, WorkflowStep):
step_descriptions.append(f"{i+1}. {step.name}: {step.description or step.action or 'Execute step'}")
elif hasattr(step, 'name'):
role = getattr(step, 'role', 'Agent')
step_descriptions.append(f"{i+1}. {step.name} ({role})")
elif callable(step):
step_descriptions.append(f"{i+1}. {getattr(step, '__name__', 'function')}")
elif isinstance(step, (Route, Parallel, Loop, Repeat)):
step_descriptions.append(f"{i+1}. {type(step).__name__} pattern")
else:
step_descriptions.append(f"{i+1}. Step {i+1}")
steps_text = "\n".join(step_descriptions)
planning_prompt = f"""You are a workflow planner. Given the following workflow steps and input, create a brief execution plan.
Workflow: {self.name}
Description: {self.description}
Steps:
{steps_text}
Input: {input}
Create a brief execution plan (2-3 sentences) describing how to best accomplish this task using the available steps. Focus on the key objectives and any important considerations."""
planner = Agent(
name="Planner",
role="Workflow Planner",
goal="Create efficient execution plans",
llm=self.planning_llm or model,
verbose=False
)
plan = planner.chat(planning_prompt)
return plan
except Exception as e:
logger.error(f"Planning failed: {e}")
return None
def _normalize_single_step(self, step: Any, index: int) -> 'WorkflowStep':
"""Normalize a single step to WorkflowStep."""
"""Normalize a single step to WorkflowStep.
Supports:
- WorkflowStep objects (passed through)
- Agent objects (wrapped with agent reference and tools)
- Callable functions (wrapped as handler)
- Strings (used as action)
"""
if isinstance(step, WorkflowStep):
return step
elif hasattr(step, 'chat'):
# It's an Agent - wrap with agent reference and preserve tools
agent_tools = getattr(step, 'tools', None)
return WorkflowStep(
name=getattr(step, 'name', f'agent_{index+1}'),
agent=step,
tools=agent_tools,
action="{{previous_output}}" if index > 0 else "{{input}}"
)
elif callable(step):
return WorkflowStep(
name=getattr(step, '__name__', f'step_{index+1}'),
handler=step
)
elif hasattr(step, 'chat'):
return WorkflowStep(
name=getattr(step, 'name', f'agent_{index+1}'),
agent=step,
action="{{input}}"
)
else:
return WorkflowStep(
name=f'step_{index+1}',
@@ -922,6 +1227,13 @@ class WorkflowManager:
loop_var_pattern = re.compile(r'loop_var:\s*(\w+)', re.IGNORECASE)
branch_pattern = re.compile(r'```branch\s*\n(.*?)\n```', re.DOTALL)
# New patterns for route, parallel, images, output_file
route_pattern = re.compile(r'```route\s*\n(.*?)\n```', re.DOTALL)
parallel_pattern = re.compile(r'```parallel\s*\n(.*?)\n```', re.DOTALL)
images_pattern = re.compile(r'```images\s*\n(.*?)\n```', re.DOTALL)
output_file_pattern = re.compile(r'output_file:\s*(.+)', re.IGNORECASE)
repeat_pattern = re.compile(r'```repeat\s*\n(.*?)\n```', re.DOTALL)
# Find all step headers
matches = list(step_pattern.finditer(body))
@@ -997,6 +1309,41 @@ class WorkflowManager:
if loop_var_match:
loop_var = loop_var_match.group(1)
# Extract route pattern (same as branch_condition)
route_match = route_pattern.search(step_content)
if route_match and not branch_condition:
branch_condition = self._parse_branch_condition(route_match.group(1).strip())
# Extract parallel pattern
parallel_steps = None
parallel_match = parallel_pattern.search(step_content)
if parallel_match:
parallel_str = parallel_match.group(1).strip()
# Parse as list of step names
parallel_steps = [s.strip().lstrip('- ').strip('"\'') for s in parallel_str.split('\n') if s.strip()]
# Set next_steps to parallel steps if not already set
if not next_steps:
next_steps = parallel_steps
# Extract images
images = None
images_match = images_pattern.search(step_content)
if images_match:
images_str = images_match.group(1).strip()
images = [s.strip() for s in images_str.split('\n') if s.strip()]
# Extract output_file
output_file = None
output_file_match = output_file_pattern.search(step_content)
if output_file_match:
output_file = output_file_match.group(1).strip()
# Extract repeat config
repeat_config = None
repeat_match = repeat_pattern.search(step_content)
if repeat_match:
repeat_config = self._parse_agent_config(repeat_match.group(1).strip())
# Get description (text before action block)
description = step_content
if action_match:
@@ -1010,8 +1357,14 @@ class WorkflowManager:
description = re.sub(r'next_steps:.*', '', description)
description = re.sub(r'loop_over:.*', '', description)
description = re.sub(r'loop_var:.*', '', description)
description = re.sub(r'output_file:.*', '', description)
description = description.strip()
# Set max_retries from repeat config
max_retries = 3
if repeat_config and 'max_iterations' in repeat_config:
max_retries = int(repeat_config['max_iterations'])
steps.append(WorkflowStep(
name=step_name,
description=description,
@@ -1025,7 +1378,10 @@ class WorkflowManager:
next_steps=next_steps,
branch_condition=branch_condition,
loop_over=loop_over,
loop_var=loop_var
loop_var=loop_var,
output_file=output_file,
images=images,
max_retries=max_retries
))
return steps
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.199"
version = "0.1.1"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
requires-python = ">=3.10"
authors = [
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.199"
version = "0.1.1"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+33 -25
View File
@@ -562,35 +562,22 @@ workflow = Workflow(steps=[
max_iterations=5
)
])
```
### Task Callbacks & Variables (process="workflow")
```python
from praisonaiagents import Agent, Task, PraisonAIAgents
# Per-task agent config (no need to create agent separately)
task = Task(
description="Research {{topic}}",
agent_config={"name": "Researcher", "role": "Expert", "llm": "gpt-4o-mini"},
variables={"topic": "AI trends"}
# 5. CALLBACKS - Monitor workflow execution
workflow = Workflow(
steps=[step1, step2],
on_workflow_start=lambda w, i: print(f"Starting: {i}"),
on_step_complete=lambda name, r: print(f"{name}: {r.output[:50]}"),
on_workflow_complete=lambda w, r: print(f"Done: {r['status']}")
)
# Callbacks for workflow execution
def on_start(task, task_id):
print(f"Starting: {task.name}")
# 6. GUARDRAILS - Validate and retry
def validate(result):
return ("error" not in result.output, "Fix the error")
def on_complete(task, output):
print(f"Completed: {task.name}")
agents = PraisonAIAgents(
agents=[agent],
tasks=[task],
process="workflow",
on_task_start=on_start,
on_task_complete=on_complete,
variables={"global_var": "shared_value"} # Global variables for all tasks
)
workflow = Workflow(steps=[
WorkflowStep(name="gen", handler=generator, guardrail=validate, max_retries=3)
])
```
### 9. Hooks
@@ -736,6 +723,12 @@ praisonai workflow run deploy --workflow-var environment=staging --workflow-var
# Execute with planning mode (AI creates sub-steps for each workflow step)
praisonai workflow run "Research Blog" --planning --verbose
# Execute with reasoning mode (chain-of-thought)
praisonai workflow run "Analysis" --reasoning --verbose
# Execute with memory enabled
praisonai workflow run "Research" --memory
# Show workflow details
praisonai workflow show deploy
@@ -747,8 +740,23 @@ praisonai "What is AI?" --workflow "Research,Summarize" --save
# Inline workflow with step actions
praisonai "GPT-5" --workflow "Research:Search for info,Write:Write blog" --tools tavily
# Workflow CLI help
praisonai workflow help
```
**Workflow CLI Options:**
| Flag | Description |
|------|-------------|
| `--workflow-var key=value` | Set workflow variable (can be repeated) |
| `--llm <model>` | LLM model (e.g., openai/gpt-4o-mini) |
| `--tools <tools>` | Tools (comma-separated, e.g., tavily) |
| `--planning` | Enable planning mode |
| `--reasoning` | Enable reasoning mode |
| `--memory` | Enable memory |
| `--verbose` | Enable verbose output |
| `--save` | Save output to file |
### Hooks CLI:
```bash
# List configured hooks
+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.21.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.21.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.23.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.23.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+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.21 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.3.23 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.21"
__version__ = "2.3.23"
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.0.199",
"praisonaiagents>=0.1.1",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
+4 -4
View File
@@ -4197,7 +4197,7 @@ requires-dist = [
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.22" },
{ name = "praisonai-tools", marker = "extra == 'autogen-v4'", specifier = ">=0.0.22" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.22" },
{ name = "praisonaiagents", specifier = ">=0.0.199" },
{ name = "praisonaiagents", specifier = ">=0.1.1" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = "==0.2.29" },
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
@@ -4252,7 +4252,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.199"
version = "0.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -4261,9 +4261,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8b/5f/fffbe2473e579edc0bc00022179a896e847128056b6b51905fc497b3cb7d/praisonaiagents-0.0.199.tar.gz", hash = "sha256:276a224350d1a975bdb69f5f07898a60ac9fce0b2d45edbab14e8b755a66853c", size = 538316, upload-time = "2025-12-17T00:01:47.411Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/41/14a8e49c644c26ee565511776c67cc978427f36aec68a1442ac666c35401/praisonaiagents-0.1.1.tar.gz", hash = "sha256:76f69188e4893b0e868f33ea02d26cccfa90b99d679c2db4185d8cf624fa740e", size = 550745, upload-time = "2025-12-17T00:37:35.974Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/25/489052a4ed1c8a65a08ae48fabe94b1a399cd82ed2e9c47264a1f82d066c/praisonaiagents-0.0.199-py3-none-any.whl", hash = "sha256:5bc887e097d2e444d63b755d356c537ed30e5f519c2e21adc819160ff4ed5697", size = 515315, upload-time = "2025-12-17T00:01:45.602Z" },
{ url = "https://files.pythonhosted.org/packages/f7/93/2cef070fae77bbef32a986ad7e1787f799442047f5dac831b9c137839353/praisonaiagents-0.1.1-py3-none-any.whl", hash = "sha256:c116551a35df0f8766496ba5102c54633f071735eae2a71b33eb4df5e2728e86", size = 518796, upload-time = "2025-12-17T00:37:33.824Z" },
]
[[package]]