mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-10 11:41:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
473fda653d | ||
|
|
30f1a440b6 | ||
|
|
f991f37cfe |
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
@@ -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]"
|
||||
|
||||
|
||||
@@ -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]" \
|
||||
|
||||
@@ -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]"
|
||||
|
||||
|
||||
@@ -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'])}")
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
||||
|
||||
@@ -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
|
||||
# =============================================================================
|
||||
|
||||
Generated
+1
-1
@@ -2422,7 +2422,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
+190
-17
@@ -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
|
||||
|
||||
@@ -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]"
|
||||
|
||||
@@ -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 @@
|
||||
__version__ = "2.3.22"
|
||||
__version__ = "2.3.25"
|
||||
@@ -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",
|
||||
|
||||
Generated
+4
-4
@@ -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]]
|
||||
|
||||
Reference in new issue
Block a user