Compare commits

...
2 Commits
Author SHA1 Message Date
Mervin Praison 30f1a440b6 Release v2.3.24 2025-12-17 08:04:24 +00:00
Mervin Praison f991f37cfe Release v2.3.23 2025-12-17 00:41:48 +00:00
15 changed files with 1415 additions and 61 deletions

No files matched your search

+190 -17
View File
@@ -40,7 +40,17 @@ PraisonAI is a production-ready Multi-AI Agents framework with self-reflection,
| 🔄 Self Reflection AI Agents | [Example](examples/python/concepts/self-reflection-details.py) | [📖](https://docs.praison.ai/features/selfreflection) |
| 🧠 Reasoning AI Agents | [Example](examples/python/concepts/reasoning-extraction.py) | [📖](https://docs.praison.ai/features/reasoning) |
| 👁️ Multi Modal AI Agents | [Example](examples/python/general/multimodal.py) | [📖](https://docs.praison.ai/features/multimodal) |
| 🎭 AI Agent Workflow (Context Passing, Per-Step Agents) | [Example](examples/python/stateful/workflow-state-example.py) | [📖](https://docs.praison.ai/features/workflows) |
| **🔄 Workflows** | | |
| ↳ Simple Workflow | [Example](examples/python/workflows/simple_workflow.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Workflow with Agents | [Example](examples/python/workflows/workflow_with_agents.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Agentic Routing (`route()`) | [Example](examples/python/workflows/workflow_routing.py) | [📖](https://docs.praison.ai/features/routing) |
| ↳ Parallel Execution (`parallel()`) | [Example](examples/python/workflows/workflow_parallel.py) | [📖](https://docs.praison.ai/features/parallelisation) |
| ↳ Loop over List/CSV (`loop()`) | [Example](examples/python/workflows/workflow_loop_csv.py) | [📖](https://docs.praison.ai/features/repetitive) |
| ↳ Evaluator-Optimizer (`repeat()`) | [Example](examples/python/workflows/workflow_repeat.py) | [📖](https://docs.praison.ai/features/evaluator-optimiser) |
| ↳ Conditional Steps | [Example](examples/python/workflows/workflow_conditional.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Workflow Branching | [Example](examples/python/workflows/workflow_branching.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Workflow Early Stop | [Example](examples/python/workflows/workflow_early_stop.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Workflow Checkpoints | [Example](examples/python/workflows/workflow_checkpoints.py) | [📖](https://docs.praison.ai/features/workflows) |
| 📚 Add Custom Knowledge | [Example](examples/python/concepts/knowledge-agents.py) | [📖](https://docs.praison.ai/features/knowledge) |
| 🧠 Memory (Short & Long Term) | [Example](examples/python/general/memory_example.py) | [📖](https://docs.praison.ai/concepts/memory) |
| 📄 Chat with PDF Agents | [Example](examples/python/concepts/chat-with-pdf.py) | [📖](https://docs.praison.ai/features/chat-with-pdf) |
@@ -103,20 +113,6 @@ PraisonAI is a production-ready Multi-AI Agents framework with self-reflection,
| @ @Mentions in Prompts | [Example](#mentions-in-prompts) | [📖](https://docs.praison.ai/cli/mentions) |
| 💾 Auto-Save Sessions | [Example](#session-management-python) | [📖](https://docs.praison.ai/cli/session) |
| 📜 History in Context | [Example](#session-management-python) | [📖](https://docs.praison.ai/cli/session) |
| ⏸️ Workflow Checkpoints | [Example](examples/python/workflows/workflow_checkpoints.py) | [📖](https://docs.praison.ai/cli/session) |
| 🔀 Workflow Branching | [Example](examples/python/workflows/workflow_branching.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🔁 Workflow Loops | [Example](examples/python/workflows/workflow_loops.py) | [📖](https://docs.praison.ai/features/workflows) |
| 📞 Task Callbacks | [Example](examples/python/workflows/task_callbacks.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🛑 Workflow Early Stop | [Example](examples/python/workflows/workflow_early_stop.py) | [📖](https://docs.praison.ai/features/workflows) |
| ⚡ Simple Workflow | [Example](examples/python/workflows/simple_workflow.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🤖 Workflow with Agents | [Example](examples/python/workflows/workflow_with_agents.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🔄 Conditional Steps | [Example](examples/python/workflows/workflow_conditional.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🎭 Mixed Steps | [Example](examples/python/workflows/workflow_mixed_steps.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🔀 Agentic Routing | [Example](examples/python/workflows/workflow_routing.py) | [📖](https://docs.praison.ai/features/workflow-routing) |
| ⚡ Parallel Execution | [Example](examples/python/workflows/workflow_parallel.py) | [📖](https://docs.praison.ai/features/workflow-parallel) |
| 🔁 Loop over List | [Example](examples/python/workflows/workflow_loop_list.py) | [📖](https://docs.praison.ai/features/workflow-loop) |
| 📄 Loop over CSV | [Example](examples/python/workflows/workflow_loop_csv.py) | [📖](https://docs.praison.ai/features/workflow-loop) |
| 🔄 Evaluator-Optimizer | [Example](examples/python/workflows/workflow_repeat.py) | [📖](https://docs.praison.ai/features/workflow-repeat) |
## Supported Providers
@@ -467,8 +463,8 @@ result = asyncio.run(manager.aexecute("deploy", default_llm="gpt-4o-mini"))
|----------|-------------|
| Simple function pipelines | `Workflow` class ⭐ |
| Agent-only pipelines | `Workflow` class |
| CSV batch processing | `process="workflow"` |
| Complex task routing | `process="workflow"` |
| CSV batch processing | `Workflow` + `loop()` |
| Complex task routing | `Workflow` + `route()` |
| Markdown templates | `WorkflowManager` |
| Early stop / conditional | `Workflow` class |
@@ -580,6 +576,162 @@ workflow = Workflow(steps=[
])
```
### Workflow with Agents
Use Agent objects directly as workflow steps:
```python
from praisonaiagents import Agent, Workflow
from praisonaiagents.workflows import route, parallel
# 1. SEQUENTIAL AGENTS
researcher = Agent(name="Researcher", role="Research expert", tools=[tavily_search])
writer = Agent(name="Writer", role="Content writer")
editor = Agent(name="Editor", role="Editor")
workflow = Workflow(steps=[researcher, writer, editor])
result = workflow.start("Research and write about AI")
# 2. PARALLEL AGENTS
workflow = Workflow(steps=[
parallel([researcher1, researcher2, researcher3]),
aggregator_agent
])
# 3. ROUTE TO AGENTS
workflow = Workflow(steps=[
classifier_function,
route({
"technical": [tech_agent],
"creative": [creative_agent],
"default": [general_agent]
})
])
# 4. WITH PLANNING & REASONING
workflow = Workflow(
steps=[researcher, writer, editor],
planning=True, # Create execution plan
planning_llm="gpt-4o", # LLM for planning
reasoning=True, # Chain-of-thought reasoning
verbose=True
)
# 5. TOOLS PER STEP
workflow = Workflow(steps=[
WorkflowStep(
name="research",
action="Research {{topic}}",
tools=[tavily_search, web_scraper],
agent_config={"name": "Researcher", "role": "Expert"}
)
])
# 6. OUTPUT TO FILE / IMAGES / PYDANTIC
from pydantic import BaseModel
class Report(BaseModel):
title: str
content: str
workflow = Workflow(steps=[
WorkflowStep(name="analyze", action="Analyze image", images=["image.jpg"]),
WorkflowStep(name="report", action="Generate report", output_pydantic=Report),
WorkflowStep(name="save", action="Save results", output_file="output/report.txt")
])
# 7. ASYNC EXECUTION
import asyncio
async def main():
result = await workflow.astart("input")
print(result)
asyncio.run(main())
# 8. STATUS TRACKING
workflow.status # "not_started" | "running" | "completed"
workflow.step_statuses # {"step1": "completed", "step2": "skipped"}
# 9. MEMORY CONFIG
workflow = Workflow(
steps=[researcher, writer],
memory_config={"provider": "chroma", "persist": True, "collection": "my_workflow"}
)
result1 = workflow.start("Research AI")
result2 = workflow.start("Continue the research") # Remembers first run
```
### YAML Workflow Template
```yaml
# .praison/workflows/research.yaml
name: Research Workflow
description: Research and write content with all patterns
agents:
researcher:
role: Research Expert
goal: Find accurate information
tools: [tavily_search, web_scraper]
writer:
role: Content Writer
goal: Write engaging content
editor:
role: Editor
goal: Polish content
steps:
# Sequential
- agent: researcher
action: Research {{topic}}
output_variable: research_data
# Routing
- name: classifier
action: Classify content type
route:
technical: [tech_handler]
creative: [creative_handler]
default: [general_handler]
# Parallel
- name: parallel_research
parallel:
- agent: researcher
action: Research market
- agent: researcher
action: Research competitors
# Loop
- agent: writer
action: Write about {{item}}
loop_over: topics
loop_var: item
# Repeat (evaluator-optimizer)
- agent: editor
action: Review and improve
repeat:
until: "quality > 8"
max_iterations: 3
# Output to file
- agent: writer
action: Write final report
output_file: output/{{topic}}_report.md
variables:
topic: AI trends
topics: [ML, NLP, Vision]
planning: true
planning_llm: gpt-4o
memory_config:
provider: chroma
persist: true
```
### 9. Hooks
Configure in `.praison/hooks.json`:
@@ -723,6 +875,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
@@ -734,8 +892,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.22" \
"praisonai>=2.3.24" \
"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.22" \
"praisonai>=2.3.24" \
"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.22" \
"praisonai>=2.3.24" \
"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.22" \
"praisonai>=2.3.24" \
"praisonai[ui]" \
"praisonai[crewai]"
@@ -213,6 +213,19 @@ class WorkflowStep:
# 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
@@ -245,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
}
@@ -264,6 +284,8 @@ 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)
@@ -325,6 +347,9 @@ 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}
@@ -341,6 +366,13 @@ class Workflow:
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):
@@ -456,19 +488,57 @@ class Workflow:
output = str(result)
elif step.agent:
# Direct agent
output = step.agent.chat(step.action or input)
# 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),
verbose=verbose
tools=step_tools if step_tools else None,
verbose=verbose,
reasoning=self.reasoning
)
# Substitute variables in action
action = step.action
@@ -476,6 +546,12 @@ class Workflow:
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)
@@ -526,6 +602,23 @@ class Workflow:
except Exception as e:
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:
import os
output_path = step.output_file
# Substitute variables in path
for key, value in all_variables.items():
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:
logger.error(f"Failed to save output to file: {e}")
# Store result
results.append({
"step": step.name,
@@ -569,6 +662,41 @@ class Workflow:
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."""
normalized = []
@@ -598,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}',
@@ -1031,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))
@@ -1106,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:
@@ -1119,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,
@@ -1134,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.1.0"
version = "0.1.2"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
requires-python = ">=3.10"
authors = [
@@ -1017,6 +1017,767 @@ class TestDocumentationExamples:
assert attempts[0] == 2 # First attempt + 1 retry
# =============================================================================
# Test: Agent Integration in Workflows
# =============================================================================
class TestAgentWorkflows:
"""Test Agent objects as workflow steps."""
def test_agent_normalization(self):
"""Test that Agent objects are properly normalized to WorkflowStep."""
# Create a mock agent
class MockAgent:
def __init__(self, name, tools=None):
self.name = name
self.tools = tools or []
def chat(self, message):
return f"Response from {self.name}: {message}"
agent = MockAgent("TestAgent", tools=["tool1", "tool2"])
workflow = Workflow(steps=[agent])
# Normalize and check
normalized = workflow._normalize_single_step(agent, 0)
assert normalized.name == "TestAgent"
assert normalized.agent == agent
assert normalized.tools == ["tool1", "tool2"]
def test_workflow_with_mock_agent(self):
"""Test workflow execution with mock agent."""
class MockAgent:
def __init__(self, name):
self.name = name
def chat(self, message):
return f"Processed: {message}"
agent = MockAgent("Processor")
workflow = Workflow(steps=[agent])
result = workflow.start("Hello World")
assert "Processed:" in result["output"]
assert "Hello World" in result["output"]
def test_sequential_mock_agents(self):
"""Test sequential execution of multiple mock agents."""
class MockAgent:
def __init__(self, name, prefix):
self.name = name
self.prefix = prefix
def chat(self, message):
return f"{self.prefix}: {message}"
agent1 = MockAgent("Agent1", "First")
agent2 = MockAgent("Agent2", "Second")
agent3 = MockAgent("Agent3", "Third")
workflow = Workflow(steps=[agent1, agent2, agent3])
result = workflow.start("Input")
# Final output should be from agent3
assert "Third:" in result["output"]
assert len(result["steps"]) == 3
def test_parallel_mock_agents(self):
"""Test parallel execution of mock agents."""
class MockAgent:
def __init__(self, name, output):
self.name = name
self.output = output
def chat(self, message):
return self.output
agent1 = MockAgent("Agent1", "Result A")
agent2 = MockAgent("Agent2", "Result B")
agent3 = MockAgent("Agent3", "Result C")
def aggregator(ctx: WorkflowContext) -> StepResult:
outputs = ctx.variables.get("parallel_outputs", [])
return StepResult(output=f"Combined: {len(outputs)} results")
workflow = Workflow(steps=[
parallel([agent1, agent2, agent3]),
aggregator
])
result = workflow.start("Test")
assert "Combined: 3 results" in result["output"]
def test_route_to_mock_agents(self):
"""Test routing to different mock agents."""
class MockAgent:
def __init__(self, name, output):
self.name = name
self.output = output
def chat(self, message):
return self.output
tech_agent = MockAgent("TechAgent", "Technical response")
creative_agent = MockAgent("CreativeAgent", "Creative response")
def classifier(ctx: WorkflowContext) -> StepResult:
if "tech" in ctx.input.lower():
return StepResult(output="category: technical")
return StepResult(output="category: creative")
workflow = Workflow(steps=[
classifier,
route({
"technical": [tech_agent],
"creative": [creative_agent]
})
])
# Test technical route
result = workflow.start("tech question")
assert "Technical response" in result["output"]
# Test creative route
result = workflow.start("creative request")
assert "Creative response" in result["output"]
def test_loop_with_mock_agent(self):
"""Test loop pattern with mock agent."""
class MockAgent:
def __init__(self, name):
self.name = name
def chat(self, message):
return f"Processed: {message}"
processor = MockAgent("Processor")
workflow = Workflow(
steps=[loop(processor, over="items")],
variables={"items": ["A", "B", "C"]}
)
result = workflow.start("Process items")
outputs = result["variables"].get("loop_outputs", [])
assert len(outputs) == 3
# =============================================================================
# Test: Workflow Configuration
# =============================================================================
class TestWorkflowConfiguration:
"""Test workflow configuration options."""
def test_workflow_verbose_setting(self):
"""Test that workflow verbose setting is used."""
def step1(ctx): return StepResult(output="Done")
workflow = Workflow(steps=[step1], verbose=True)
assert workflow.verbose == True
def test_workflow_reasoning_setting(self):
"""Test that workflow reasoning setting is stored."""
def step1(ctx): return StepResult(output="Done")
workflow = Workflow(steps=[step1], reasoning=True)
assert workflow.reasoning == True
def test_workflow_planning_setting(self):
"""Test that workflow planning setting is stored."""
def step1(ctx): return StepResult(output="Done")
workflow = Workflow(steps=[step1], planning=True, planning_llm="gpt-4o")
assert workflow.planning == True
assert workflow.planning_llm == "gpt-4o"
def test_workflow_default_llm(self):
"""Test default LLM setting."""
def step1(ctx): return StepResult(output="Done")
workflow = Workflow(steps=[step1], default_llm="gpt-4o-mini")
assert workflow.default_llm == "gpt-4o-mini"
def test_workflow_default_agent_config(self):
"""Test default agent config."""
def step1(ctx): return StepResult(output="Done")
workflow = Workflow(
steps=[step1],
default_agent_config={
"name": "DefaultAgent",
"role": "Assistant",
"tools": ["tool1"]
}
)
assert workflow.default_agent_config["name"] == "DefaultAgent"
assert workflow.default_agent_config["tools"] == ["tool1"]
# =============================================================================
# Test: WorkflowStep with Tools
# =============================================================================
class TestWorkflowStepTools:
"""Test WorkflowStep with tools configuration."""
def test_step_with_tools(self):
"""Test WorkflowStep with tools parameter."""
def my_tool(): return "tool result"
step = WorkflowStep(
name="step_with_tools",
action="Do something",
tools=[my_tool]
)
assert step.tools == [my_tool]
def test_step_with_agent_config_tools(self):
"""Test WorkflowStep with tools in agent_config."""
def my_tool(): return "tool result"
step = WorkflowStep(
name="step_with_config",
action="Do something",
agent_config={
"name": "Agent",
"role": "Helper",
"tools": [my_tool]
}
)
assert step.agent_config["tools"] == [my_tool]
# =============================================================================
# Test: Migrated Features (output_file, images, async, etc.)
# =============================================================================
class TestMigratedFeatures:
"""Test features migrated from process='workflow'."""
def test_step_output_file(self):
"""Test WorkflowStep with output_file parameter."""
import tempfile
import os
with tempfile.TemporaryDirectory() as tmpdir:
output_path = os.path.join(tmpdir, "output.txt")
def generate_content(ctx: WorkflowContext) -> StepResult:
return StepResult(output="Generated content for file")
workflow = Workflow(steps=[
WorkflowStep(
name="generator",
handler=generate_content,
output_file=output_path
)
])
result = workflow.start("Generate content")
# Check file was created
assert os.path.exists(output_path)
with open(output_path) as f:
content = f.read()
assert "Generated content" in content
def test_step_images_parameter(self):
"""Test WorkflowStep with images parameter."""
step = WorkflowStep(
name="vision_step",
action="Analyze this image",
images=["image1.jpg", "image2.png"]
)
assert step.images == ["image1.jpg", "image2.png"]
def test_step_output_pydantic_parameter(self):
"""Test WorkflowStep with output_pydantic parameter."""
from pydantic import BaseModel
class OutputModel(BaseModel):
title: str
content: str
step = WorkflowStep(
name="structured_step",
action="Generate structured output",
output_pydantic=OutputModel
)
assert step.output_pydantic == OutputModel
def test_step_async_execution_parameter(self):
"""Test WorkflowStep with async_execution parameter."""
step = WorkflowStep(
name="async_step",
action="Run async",
async_execution=True
)
assert step.async_execution == True
def test_step_quality_check_parameter(self):
"""Test WorkflowStep with quality_check parameter."""
step = WorkflowStep(
name="quality_step",
action="Check quality",
quality_check=False
)
assert step.quality_check == False
def test_step_rerun_parameter(self):
"""Test WorkflowStep with rerun parameter."""
step = WorkflowStep(
name="rerun_step",
action="Can rerun",
rerun=False
)
assert step.rerun == False
def test_workflow_astart_method_exists(self):
"""Test that Workflow has astart method."""
def step1(ctx): return StepResult(output="Done")
workflow = Workflow(steps=[step1])
assert hasattr(workflow, 'astart')
assert callable(workflow.astart)
def test_workflow_arun_method_exists(self):
"""Test that Workflow has arun method."""
def step1(ctx): return StepResult(output="Done")
workflow = Workflow(steps=[step1])
assert hasattr(workflow, 'arun')
assert callable(workflow.arun)
def test_step_to_dict_includes_new_fields(self):
"""Test that to_dict includes all new fields."""
step = WorkflowStep(
name="test_step",
action="Test action",
output_file="output.txt",
images=["img.jpg"],
async_execution=True,
quality_check=False,
rerun=False
)
d = step.to_dict()
assert d["output_file"] == "output.txt"
assert d["images"] == ["img.jpg"]
assert d["async_execution"] == True
assert d["quality_check"] == False
assert d["rerun"] == False
# =============================================================================
# Test: Planning and Reasoning Modes
# =============================================================================
class TestPlanningAndReasoning:
"""Test planning and reasoning modes in workflows."""
def test_workflow_planning_parameter_exists(self):
"""Test that Workflow accepts planning parameter."""
workflow = Workflow(
steps=[lambda ctx: StepResult(output="test")],
planning=True,
planning_llm="gpt-4o"
)
assert workflow.planning == True
assert workflow.planning_llm == "gpt-4o"
def test_workflow_reasoning_parameter_exists(self):
"""Test that Workflow accepts reasoning parameter."""
workflow = Workflow(
steps=[lambda ctx: StepResult(output="test")],
reasoning=True
)
assert workflow.reasoning == True
def test_workflow_memory_config_parameter_exists(self):
"""Test that Workflow accepts memory_config parameter."""
workflow = Workflow(
steps=[lambda ctx: StepResult(output="test")],
memory_config={"provider": "rag", "persist": True}
)
assert workflow.memory_config == {"provider": "rag", "persist": True}
def test_workflow_verbose_parameter_exists(self):
"""Test that Workflow accepts verbose parameter."""
workflow = Workflow(
steps=[lambda ctx: StepResult(output="test")],
verbose=True
)
assert workflow.verbose == True
class TestToolsPerStep:
"""Test tools per step functionality."""
def test_step_with_tools_list(self):
"""Test WorkflowStep with tools list."""
def my_tool():
return "tool result"
step = WorkflowStep(
name="test",
action="Do something",
tools=[my_tool]
)
assert step.tools == [my_tool]
def test_step_with_agent_config_tools(self):
"""Test WorkflowStep with tools in agent_config."""
def my_tool():
return "tool result"
step = WorkflowStep(
name="test",
action="Do something",
agent_config={
"name": "TestAgent",
"role": "Tester",
"tools": [my_tool]
}
)
assert step.agent_config["tools"] == [my_tool]
def test_agent_with_tools_in_workflow(self):
"""Test Agent with tools is properly handled in workflow."""
class MockAgent:
def __init__(self, name, tools=None):
self.name = name
self.tools = tools or []
def chat(self, message):
return f"Response with {len(self.tools)} tools"
def mock_tool():
return "tool result"
agent = MockAgent("TestAgent", tools=[mock_tool])
workflow = Workflow(steps=[agent])
# Normalize and verify tools are preserved
normalized = workflow._normalize_single_step(agent, 0)
assert normalized.tools == [mock_tool]
# Execute and verify
result = workflow.start("test")
assert "1 tools" in result["output"]
# =============================================================================
# Test: WorkflowManager with Patterns in MD Files
# =============================================================================
class TestWorkflowManagerPatterns:
"""Test WorkflowManager parsing of pattern syntax in markdown files."""
def test_parse_route_pattern_in_md(self):
"""Test parsing route pattern from markdown."""
import tempfile
import os
from praisonaiagents.workflows import WorkflowManager
md_content = """---
name: Route Test
description: Test routing pattern
---
## Step 1: Classifier
Classify the input.
```action
Classify this input as technical or creative
```
```route
technical: [Tech Handler]
creative: [Creative Handler]
default: [General Handler]
```
## Step 2: Tech Handler
Handle technical requests.
```action
Process technical request
```
## Step 3: Creative Handler
Handle creative requests.
```action
Process creative request
```
## Step 4: General Handler
Handle general requests.
```action
Process general request
```
"""
with tempfile.TemporaryDirectory() as tmpdir:
workflows_dir = os.path.join(tmpdir, ".praison", "workflows")
os.makedirs(workflows_dir)
with open(os.path.join(workflows_dir, "route_test.md"), "w") as f:
f.write(md_content)
manager = WorkflowManager(workspace_path=tmpdir)
workflows = manager.list_workflows()
assert len(workflows) == 1
workflow = workflows[0]
assert workflow.name == "Route Test"
# Check that route pattern is parsed
step1 = workflow.steps[0]
assert step1.name == "Classifier"
assert step1.branch_condition is not None
assert "technical" in step1.branch_condition
def test_parse_parallel_pattern_in_md(self):
"""Test parsing parallel pattern from markdown."""
import tempfile
import os
from praisonaiagents.workflows import WorkflowManager
md_content = """---
name: Parallel Test
description: Test parallel pattern
---
## Step 1: Research
Research in parallel.
```parallel
- Market Research
- Competitor Analysis
- Customer Survey
```
```action
Research the topic
```
## Step 2: Market Research
Research the market.
```action
Analyze market trends
```
## Step 3: Competitor Analysis
Analyze competitors.
```action
Analyze competitors
```
## Step 4: Customer Survey
Survey customers.
```action
Survey customers
```
"""
with tempfile.TemporaryDirectory() as tmpdir:
workflows_dir = os.path.join(tmpdir, ".praison", "workflows")
os.makedirs(workflows_dir)
with open(os.path.join(workflows_dir, "parallel_test.md"), "w") as f:
f.write(md_content)
manager = WorkflowManager(workspace_path=tmpdir)
workflows = manager.list_workflows()
assert len(workflows) == 1
workflow = workflows[0]
assert workflow.name == "Parallel Test"
# Check that parallel steps are parsed
step1 = workflow.steps[0]
assert step1.name == "Research"
assert hasattr(step1, 'parallel_steps') or step1.next_steps is not None
def test_parse_loop_pattern_in_md(self):
"""Test parsing loop pattern from markdown."""
import tempfile
import os
from praisonaiagents.workflows import WorkflowManager
md_content = """---
name: Loop Test
description: Test loop pattern
variables:
items: ["item1", "item2", "item3"]
---
## Step 1: Process Items
Process each item in the list.
loop_over: items
loop_var: current_item
```action
Process {{current_item}}
```
"""
with tempfile.TemporaryDirectory() as tmpdir:
workflows_dir = os.path.join(tmpdir, ".praison", "workflows")
os.makedirs(workflows_dir)
with open(os.path.join(workflows_dir, "loop_test.md"), "w") as f:
f.write(md_content)
manager = WorkflowManager(workspace_path=tmpdir)
workflows = manager.list_workflows()
assert len(workflows) == 1
workflow = workflows[0]
step1 = workflow.steps[0]
assert step1.name == "Process Items"
assert step1.loop_over == "items"
assert step1.loop_var == "current_item"
def test_parse_repeat_pattern_in_md(self):
"""Test parsing repeat pattern from markdown."""
import tempfile
import os
from praisonaiagents.workflows import WorkflowManager
md_content = """---
name: Repeat Test
description: Test repeat pattern
---
## Step 1: Generate
Generate content repeatedly.
```repeat
max_iterations: 5
until: count >= 10
```
```action
Generate more content
```
"""
with tempfile.TemporaryDirectory() as tmpdir:
workflows_dir = os.path.join(tmpdir, ".praison", "workflows")
os.makedirs(workflows_dir)
with open(os.path.join(workflows_dir, "repeat_test.md"), "w") as f:
f.write(md_content)
manager = WorkflowManager(workspace_path=tmpdir)
workflows = manager.list_workflows()
assert len(workflows) == 1
workflow = workflows[0]
step1 = workflow.steps[0]
assert step1.name == "Generate"
# Check repeat config is parsed
assert hasattr(step1, 'max_retries') or hasattr(step1, 'repeat_config')
def test_parse_output_file_in_md(self):
"""Test parsing output_file from markdown."""
import tempfile
import os
from praisonaiagents.workflows import WorkflowManager
md_content = """---
name: Output Test
description: Test output file
---
## Step 1: Generate Report
Generate a report and save to file.
output_file: output/report.txt
```action
Generate the report
```
"""
with tempfile.TemporaryDirectory() as tmpdir:
workflows_dir = os.path.join(tmpdir, ".praison", "workflows")
os.makedirs(workflows_dir)
with open(os.path.join(workflows_dir, "output_test.md"), "w") as f:
f.write(md_content)
manager = WorkflowManager(workspace_path=tmpdir)
workflows = manager.list_workflows()
assert len(workflows) == 1
workflow = workflows[0]
step1 = workflow.steps[0]
assert step1.name == "Generate Report"
assert step1.output_file == "output/report.txt"
def test_parse_images_in_md(self):
"""Test parsing images from markdown."""
import tempfile
import os
from praisonaiagents.workflows import WorkflowManager
md_content = """---
name: Vision Test
description: Test image handling
---
## Step 1: Analyze Image
Analyze the provided image.
```images
image1.jpg
image2.png
```
```action
Analyze these images
```
"""
with tempfile.TemporaryDirectory() as tmpdir:
workflows_dir = os.path.join(tmpdir, ".praison", "workflows")
os.makedirs(workflows_dir)
with open(os.path.join(workflows_dir, "vision_test.md"), "w") as f:
f.write(md_content)
manager = WorkflowManager(workspace_path=tmpdir)
workflows = manager.list_workflows()
assert len(workflows) == 1
workflow = workflows[0]
step1 = workflow.steps[0]
assert step1.name == "Analyze Image"
assert step1.images is not None
assert "image1.jpg" in step1.images
# =============================================================================
# Run Tests
# =============================================================================
+1 -1
View File
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.1.0"
version = "0.1.2"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+190 -17
View File
@@ -40,7 +40,17 @@ PraisonAI is a production-ready Multi-AI Agents framework with self-reflection,
| 🔄 Self Reflection AI Agents | [Example](examples/python/concepts/self-reflection-details.py) | [📖](https://docs.praison.ai/features/selfreflection) |
| 🧠 Reasoning AI Agents | [Example](examples/python/concepts/reasoning-extraction.py) | [📖](https://docs.praison.ai/features/reasoning) |
| 👁️ Multi Modal AI Agents | [Example](examples/python/general/multimodal.py) | [📖](https://docs.praison.ai/features/multimodal) |
| 🎭 AI Agent Workflow (Context Passing, Per-Step Agents) | [Example](examples/python/stateful/workflow-state-example.py) | [📖](https://docs.praison.ai/features/workflows) |
| **🔄 Workflows** | | |
| ↳ Simple Workflow | [Example](examples/python/workflows/simple_workflow.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Workflow with Agents | [Example](examples/python/workflows/workflow_with_agents.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Agentic Routing (`route()`) | [Example](examples/python/workflows/workflow_routing.py) | [📖](https://docs.praison.ai/features/routing) |
| ↳ Parallel Execution (`parallel()`) | [Example](examples/python/workflows/workflow_parallel.py) | [📖](https://docs.praison.ai/features/parallelisation) |
| ↳ Loop over List/CSV (`loop()`) | [Example](examples/python/workflows/workflow_loop_csv.py) | [📖](https://docs.praison.ai/features/repetitive) |
| ↳ Evaluator-Optimizer (`repeat()`) | [Example](examples/python/workflows/workflow_repeat.py) | [📖](https://docs.praison.ai/features/evaluator-optimiser) |
| ↳ Conditional Steps | [Example](examples/python/workflows/workflow_conditional.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Workflow Branching | [Example](examples/python/workflows/workflow_branching.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Workflow Early Stop | [Example](examples/python/workflows/workflow_early_stop.py) | [📖](https://docs.praison.ai/features/workflows) |
| ↳ Workflow Checkpoints | [Example](examples/python/workflows/workflow_checkpoints.py) | [📖](https://docs.praison.ai/features/workflows) |
| 📚 Add Custom Knowledge | [Example](examples/python/concepts/knowledge-agents.py) | [📖](https://docs.praison.ai/features/knowledge) |
| 🧠 Memory (Short & Long Term) | [Example](examples/python/general/memory_example.py) | [📖](https://docs.praison.ai/concepts/memory) |
| 📄 Chat with PDF Agents | [Example](examples/python/concepts/chat-with-pdf.py) | [📖](https://docs.praison.ai/features/chat-with-pdf) |
@@ -103,20 +113,6 @@ PraisonAI is a production-ready Multi-AI Agents framework with self-reflection,
| @ @Mentions in Prompts | [Example](#mentions-in-prompts) | [📖](https://docs.praison.ai/cli/mentions) |
| 💾 Auto-Save Sessions | [Example](#session-management-python) | [📖](https://docs.praison.ai/cli/session) |
| 📜 History in Context | [Example](#session-management-python) | [📖](https://docs.praison.ai/cli/session) |
| ⏸️ Workflow Checkpoints | [Example](examples/python/workflows/workflow_checkpoints.py) | [📖](https://docs.praison.ai/cli/session) |
| 🔀 Workflow Branching | [Example](examples/python/workflows/workflow_branching.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🔁 Workflow Loops | [Example](examples/python/workflows/workflow_loops.py) | [📖](https://docs.praison.ai/features/workflows) |
| 📞 Task Callbacks | [Example](examples/python/workflows/task_callbacks.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🛑 Workflow Early Stop | [Example](examples/python/workflows/workflow_early_stop.py) | [📖](https://docs.praison.ai/features/workflows) |
| ⚡ Simple Workflow | [Example](examples/python/workflows/simple_workflow.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🤖 Workflow with Agents | [Example](examples/python/workflows/workflow_with_agents.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🔄 Conditional Steps | [Example](examples/python/workflows/workflow_conditional.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🎭 Mixed Steps | [Example](examples/python/workflows/workflow_mixed_steps.py) | [📖](https://docs.praison.ai/features/workflows) |
| 🔀 Agentic Routing | [Example](examples/python/workflows/workflow_routing.py) | [📖](https://docs.praison.ai/features/workflow-routing) |
| ⚡ Parallel Execution | [Example](examples/python/workflows/workflow_parallel.py) | [📖](https://docs.praison.ai/features/workflow-parallel) |
| 🔁 Loop over List | [Example](examples/python/workflows/workflow_loop_list.py) | [📖](https://docs.praison.ai/features/workflow-loop) |
| 📄 Loop over CSV | [Example](examples/python/workflows/workflow_loop_csv.py) | [📖](https://docs.praison.ai/features/workflow-loop) |
| 🔄 Evaluator-Optimizer | [Example](examples/python/workflows/workflow_repeat.py) | [📖](https://docs.praison.ai/features/workflow-repeat) |
## Supported Providers
@@ -467,8 +463,8 @@ result = asyncio.run(manager.aexecute("deploy", default_llm="gpt-4o-mini"))
|----------|-------------|
| Simple function pipelines | `Workflow` class ⭐ |
| Agent-only pipelines | `Workflow` class |
| CSV batch processing | `process="workflow"` |
| Complex task routing | `process="workflow"` |
| CSV batch processing | `Workflow` + `loop()` |
| Complex task routing | `Workflow` + `route()` |
| Markdown templates | `WorkflowManager` |
| Early stop / conditional | `Workflow` class |
@@ -580,6 +576,162 @@ workflow = Workflow(steps=[
])
```
### Workflow with Agents
Use Agent objects directly as workflow steps:
```python
from praisonaiagents import Agent, Workflow
from praisonaiagents.workflows import route, parallel
# 1. SEQUENTIAL AGENTS
researcher = Agent(name="Researcher", role="Research expert", tools=[tavily_search])
writer = Agent(name="Writer", role="Content writer")
editor = Agent(name="Editor", role="Editor")
workflow = Workflow(steps=[researcher, writer, editor])
result = workflow.start("Research and write about AI")
# 2. PARALLEL AGENTS
workflow = Workflow(steps=[
parallel([researcher1, researcher2, researcher3]),
aggregator_agent
])
# 3. ROUTE TO AGENTS
workflow = Workflow(steps=[
classifier_function,
route({
"technical": [tech_agent],
"creative": [creative_agent],
"default": [general_agent]
})
])
# 4. WITH PLANNING & REASONING
workflow = Workflow(
steps=[researcher, writer, editor],
planning=True, # Create execution plan
planning_llm="gpt-4o", # LLM for planning
reasoning=True, # Chain-of-thought reasoning
verbose=True
)
# 5. TOOLS PER STEP
workflow = Workflow(steps=[
WorkflowStep(
name="research",
action="Research {{topic}}",
tools=[tavily_search, web_scraper],
agent_config={"name": "Researcher", "role": "Expert"}
)
])
# 6. OUTPUT TO FILE / IMAGES / PYDANTIC
from pydantic import BaseModel
class Report(BaseModel):
title: str
content: str
workflow = Workflow(steps=[
WorkflowStep(name="analyze", action="Analyze image", images=["image.jpg"]),
WorkflowStep(name="report", action="Generate report", output_pydantic=Report),
WorkflowStep(name="save", action="Save results", output_file="output/report.txt")
])
# 7. ASYNC EXECUTION
import asyncio
async def main():
result = await workflow.astart("input")
print(result)
asyncio.run(main())
# 8. STATUS TRACKING
workflow.status # "not_started" | "running" | "completed"
workflow.step_statuses # {"step1": "completed", "step2": "skipped"}
# 9. MEMORY CONFIG
workflow = Workflow(
steps=[researcher, writer],
memory_config={"provider": "chroma", "persist": True, "collection": "my_workflow"}
)
result1 = workflow.start("Research AI")
result2 = workflow.start("Continue the research") # Remembers first run
```
### YAML Workflow Template
```yaml
# .praison/workflows/research.yaml
name: Research Workflow
description: Research and write content with all patterns
agents:
researcher:
role: Research Expert
goal: Find accurate information
tools: [tavily_search, web_scraper]
writer:
role: Content Writer
goal: Write engaging content
editor:
role: Editor
goal: Polish content
steps:
# Sequential
- agent: researcher
action: Research {{topic}}
output_variable: research_data
# Routing
- name: classifier
action: Classify content type
route:
technical: [tech_handler]
creative: [creative_handler]
default: [general_handler]
# Parallel
- name: parallel_research
parallel:
- agent: researcher
action: Research market
- agent: researcher
action: Research competitors
# Loop
- agent: writer
action: Write about {{item}}
loop_over: topics
loop_var: item
# Repeat (evaluator-optimizer)
- agent: editor
action: Review and improve
repeat:
until: "quality > 8"
max_iterations: 3
# Output to file
- agent: writer
action: Write final report
output_file: output/{{topic}}_report.md
variables:
topic: AI trends
topics: [ML, NLP, Vision]
planning: true
planning_llm: gpt-4o
memory_config:
provider: chroma
persist: true
```
### 9. Hooks
Configure in `.praison/hooks.json`:
@@ -723,6 +875,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
@@ -734,8 +892,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.22.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.22.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.24.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.24.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.22 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.3.24 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.22"
__version__ = "2.3.24"
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.1.0",
"praisonaiagents>=0.1.2",
"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.1.0" },
{ name = "praisonaiagents", specifier = ">=0.1.2" },
{ 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.1.0"
version = "0.1.2"
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/1a/69/062b8e0867ec673483d83a7529b15d17c15196460060cd7e1542f96c8036/praisonaiagents-0.1.0.tar.gz", hash = "sha256:9c87d8ef965997ab6df2f76badda7911889c6409333b8d2afa20a4f60c80fef9", size = 545876, upload-time = "2025-12-17T00:24:37.466Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e3/cd/e00b4dfc4127e2f51acc8c414a392ec45b5e80c45386f082efc31fe7af1b/praisonaiagents-0.1.2.tar.gz", hash = "sha256:e8f94748af81897cd73cee1e74d031296c2103c2c7b8911ee0618c5cfb2a8492", size = 551139, upload-time = "2025-12-17T07:43:27.008Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/11/9f9a8802208e50a44d2e105c6b9743fa08f02378829fb5fad1b3ee9c8b8d/praisonaiagents-0.1.0-py3-none-any.whl", hash = "sha256:bb39f79e92b8a3abe215fcb8f49be896dee539a5e8998f55b14fb8bbbeb86905", size = 516692, upload-time = "2025-12-17T00:24:35.719Z" },
{ url = "https://files.pythonhosted.org/packages/8c/1f/8a4e5097bee7e8031c2428cf8c1e47b06c85c5f4ce552231de3224d0c301/praisonaiagents-0.1.2-py3-none-any.whl", hash = "sha256:c5a1241da8feb32957b0820ffaa37b03e4ff45462d322fde4ff84f224f10eb91", size = 518797, upload-time = "2025-12-17T07:43:25.172Z" },
]
[[package]]