Compare commits

...
3 Commits
Author SHA1 Message Date
Mervin Praison 473fda653d Release v2.3.25 2025-12-17 08:17:15 +00:00
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
22 changed files with 1485 additions and 86 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.25" \
"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.25" \
"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.25" \
"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.25" \
"praisonai[ui]" \
"praisonai[crewai]"
+26 -24
View File
@@ -1,36 +1,38 @@
"""
Simple Workflow Example
Simple Agentic Workflow Example
The easiest way to create and run workflows in PraisonAI.
Just pass functions or agents as steps - no complex configuration needed!
Demonstrates using Agent instances directly as workflow steps.
Each agent processes the input and passes output to the next agent.
"""
from praisonaiagents import Workflow, WorkflowContext, StepResult
from praisonaiagents import Agent, Workflow
# Define simple handler functions
def validate(ctx: WorkflowContext) -> StepResult:
"""Validate the input data."""
if not ctx.input:
return StepResult(output="No input provided", stop_workflow=True)
return StepResult(output=f"Validated: {ctx.input}")
# Create agents with specific roles
researcher = Agent(
name="Researcher",
role="Research Analyst",
goal="Research and provide information about topics",
instructions="You are a research analyst. Provide concise, factual information."
)
def process(ctx: WorkflowContext) -> StepResult:
"""Process the validated data."""
return StepResult(output=f"Processed: {ctx.previous_result}")
writer = Agent(
name="Writer",
role="Content Writer",
goal="Write engaging content based on research",
instructions="You are a content writer. Write clear, engaging content based on the research provided."
)
def format_output(ctx: WorkflowContext) -> StepResult:
"""Format the final output."""
return StepResult(output=f"✅ Final: {ctx.previous_result}")
# Create workflow - just list your functions!
# Create workflow with agents as steps
workflow = Workflow(
name="Simple Pipeline",
steps=[validate, process, format_output]
name="Simple Agentic Pipeline",
steps=[researcher, writer]
)
if __name__ == "__main__":
# Run the workflow - PraisonAI style uses .start()
result = workflow.start("Hello, World!", verbose=True)
# Run workflow - agents process sequentially
result = workflow.start(
"What are the key benefits of AI agents?",
verbose=True
)
print(f"\nFinal output: {result['output']}")
print(f"Steps completed: {len(result['steps'])}")
print(f"\nFinal output:\n{result['output']}")
@@ -0,0 +1,36 @@
"""
Simple Workflow Example
The easiest way to create and run workflows in PraisonAI.
Just pass functions or agents as steps - no complex configuration needed!
"""
from praisonaiagents import Workflow, WorkflowContext, StepResult
# Define simple handler functions
def validate(ctx: WorkflowContext) -> StepResult:
"""Validate the input data."""
if not ctx.input:
return StepResult(output="No input provided", stop_workflow=True)
return StepResult(output=f"Validated: {ctx.input}")
def process(ctx: WorkflowContext) -> StepResult:
"""Process the validated data."""
return StepResult(output=f"Processed: {ctx.previous_result}")
def format_output(ctx: WorkflowContext) -> StepResult:
"""Format the final output."""
return StepResult(output=f"✅ Final: {ctx.previous_result}")
# Create workflow - just list your functions!
workflow = Workflow(
name="Simple Pipeline",
steps=[validate, process, format_output]
)
if __name__ == "__main__":
# Run the workflow - PraisonAI style uses .start()
result = workflow.start("Hello, World!", verbose=True)
print(f"\nFinal output: {result['output']}")
print(f"Steps completed: {len(result['steps'])}")
@@ -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}',
@@ -656,7 +852,14 @@ class Workflow:
output = f"Error: {e}"
elif normalized.agent:
try:
output = normalized.agent.chat(normalized.action or input)
action = normalized.action or input
# Substitute variables
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)
output = normalized.agent.chat(action)
except Exception as e:
output = f"Error: {e}"
elif normalized.action:
@@ -1031,6 +1234,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 +1316,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 +1364,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 +1385,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.3"
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.3"
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.25.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.25.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.25 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.25"
+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.3",
"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.3" },
{ 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.3"
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/6d/6d/4c350703c10a53c8efee4a3ba989916f9e802dcc142b1a7fef8b874a7a00/praisonaiagents-0.1.3.tar.gz", hash = "sha256:064abf379e5ed4535db7c32fbe4344cb7274ef23f91842de1b336b12143c418d", size = 551139, upload-time = "2025-12-17T08:14:38.557Z" }
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/15/ba/fd74ac9f0fef8e05c9665faf541898cc8d015e857fd8524501409453d592/praisonaiagents-0.1.3-py3-none-any.whl", hash = "sha256:37e7320cbd304bc901cd32abb7184ffecc9ddf5526c25204c47097f391954334", size = 518808, upload-time = "2025-12-17T08:14:36.864Z" },
]
[[package]]