mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-09 17:31:52 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c267cdda87 | ||
|
|
b88bb04090 | ||
|
|
709a35fbdc | ||
|
|
8b792424b6 | ||
|
|
ef2a8cd658 | ||
|
|
d41d2dcba2 | ||
|
|
3bc1786b49 | ||
|
|
2eec3e2359 | ||
|
|
6168401a69 | ||
|
|
473fda653d | ||
|
|
30f1a440b6 | ||
|
|
f991f37cfe | ||
|
|
a43b3ac72c | ||
|
|
06f82249e1 |
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,19 +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/workflows) |
|
||||
| ⚡ Parallel Execution | [Example](examples/python/workflows/workflow_parallel.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| 📄 CSV Loop Processing | [Example](examples/python/workflows/workflow_loop_csv.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| 🔄 Evaluator-Optimizer | [Example](examples/python/workflows/workflow_repeat.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
|
||||
## Supported Providers
|
||||
|
||||
@@ -409,184 +406,202 @@ memories = auto.process_interaction(
|
||||
# Extracts: name="John", preference="Python for backend"
|
||||
```
|
||||
|
||||
### 8. Workflows
|
||||
### 8. Agentic Workflows
|
||||
|
||||
Create reusable multi-step workflows with context passing and per-step agents:
|
||||
Create powerful multi-agent workflows with the `Workflow` class:
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.memory import WorkflowManager, Workflow, WorkflowStep
|
||||
from praisonaiagents import Agent, Workflow
|
||||
|
||||
# Simple execution with default agent
|
||||
agent = Agent(name="Assistant", llm="gpt-4o-mini")
|
||||
manager = WorkflowManager()
|
||||
|
||||
result = manager.execute(
|
||||
"deploy",
|
||||
default_agent=agent,
|
||||
variables={"environment": "production"}
|
||||
# Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Research Analyst",
|
||||
goal="Research topics thoroughly",
|
||||
instructions="Provide concise, factual information."
|
||||
)
|
||||
|
||||
# Advanced: Per-step agent configuration
|
||||
workflow = Workflow(
|
||||
name="research_pipeline",
|
||||
default_llm="gpt-4o-mini",
|
||||
steps=[
|
||||
WorkflowStep(
|
||||
name="research",
|
||||
action="Research {{topic}}",
|
||||
agent_config={"role": "Researcher", "goal": "Find information"},
|
||||
tools=["tavily_search"]
|
||||
),
|
||||
WorkflowStep(
|
||||
name="write",
|
||||
action="Write report based on {{previous_output}}",
|
||||
agent_config={"role": "Writer", "goal": "Write content"},
|
||||
context_from=["research"] # Only include research output
|
||||
)
|
||||
]
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
role="Content Writer",
|
||||
goal="Write engaging content",
|
||||
instructions="Write clear, engaging content based on research."
|
||||
)
|
||||
|
||||
# Async execution
|
||||
import asyncio
|
||||
result = asyncio.run(manager.aexecute("deploy", default_llm="gpt-4o-mini"))
|
||||
# Create workflow with agents as steps
|
||||
workflow = Workflow(steps=[researcher, writer])
|
||||
|
||||
# Run workflow - agents process sequentially
|
||||
result = workflow.start("What are the benefits of AI agents?")
|
||||
print(result["output"])
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Context Passing**: Use `{{previous_output}}` and `{{step_name_output}}` variables
|
||||
- **Per-Step Agents**: Configure different agents with roles, goals, tools for each step
|
||||
- **Async Execution**: Use `aexecute()` for async workflows
|
||||
- **Planning Mode**: Enable at workflow level with `planning=True`
|
||||
- **Branching**: Use `next_steps` and `branch_condition` for conditional routing
|
||||
- **Loops**: Use `loop_over` and `loop_var` to iterate over data
|
||||
|
||||
### Choosing the Right Workflow System
|
||||
|
||||
| Use Case | Recommended |
|
||||
|----------|-------------|
|
||||
| Simple function pipelines | `Workflow` class ⭐ |
|
||||
| Agent-only pipelines | `Workflow` class |
|
||||
| CSV batch processing | `process="workflow"` |
|
||||
| Complex task routing | `process="workflow"` |
|
||||
| Markdown templates | `WorkflowManager` |
|
||||
| Early stop / conditional | `Workflow` class |
|
||||
|
||||
### Simple Workflow (Recommended)
|
||||
|
||||
The easiest way to create workflows - just pass functions as steps:
|
||||
|
||||
```python
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
|
||||
# Define simple handler functions
|
||||
def validate(ctx: WorkflowContext) -> StepResult:
|
||||
if not ctx.input:
|
||||
return StepResult(output="No input", stop_workflow=True)
|
||||
return StepResult(output=f"Valid: {ctx.input}")
|
||||
|
||||
def process(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output=f"Processed: {ctx.previous_result}")
|
||||
|
||||
# Create and run workflow
|
||||
workflow = Workflow(steps=[validate, process])
|
||||
result = workflow.start("Hello World", verbose=True)
|
||||
print(result["output"]) # "Processed: Valid: Hello World"
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Just pass functions** - No complex configuration needed
|
||||
- **Early stop** - Return `stop_workflow=True` to stop the workflow
|
||||
- **Context passing** - Access `ctx.input`, `ctx.previous_result`, `ctx.variables`
|
||||
- **Verbose mode** - See step-by-step progress
|
||||
|
||||
### Workflow Branching & Loops
|
||||
|
||||
```python
|
||||
from praisonaiagents import WorkflowStep
|
||||
|
||||
# Branching step
|
||||
decision_step = WorkflowStep(
|
||||
name="decide",
|
||||
action="Evaluate if task is complete",
|
||||
next_steps=["success_step", "retry_step"],
|
||||
branch_condition={"success": ["success_step"], "failure": ["retry_step"]}
|
||||
)
|
||||
|
||||
# Loop step
|
||||
loop_step = WorkflowStep(
|
||||
name="process_items",
|
||||
action="Process {{item}}",
|
||||
loop_over="items", # Variable containing list
|
||||
loop_var="item" # Current item variable name
|
||||
)
|
||||
```
|
||||
- **Agent-first** - Pass `Agent` objects directly as workflow steps
|
||||
- **Pattern helpers** - Use `route()`, `parallel()`, `loop()`, `repeat()`
|
||||
- **Planning mode** - Enable with `planning=True`
|
||||
- **Callbacks** - Monitor with `on_step_complete`, `on_workflow_complete`
|
||||
- **Async execution** - Use `workflow.astart()` for async
|
||||
|
||||
### Workflow Patterns (route, parallel, loop, repeat)
|
||||
|
||||
```python
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.memory.workflows import route, parallel, loop, repeat
|
||||
from praisonaiagents import Agent, Workflow
|
||||
from praisonaiagents.workflows import route, parallel, loop, repeat
|
||||
|
||||
# 1. ROUTING - Classifier agent routes to specialized agents
|
||||
classifier = Agent(name="Classifier", instructions="Respond with 'technical' or 'creative'")
|
||||
tech_agent = Agent(name="TechExpert", role="Technical Expert")
|
||||
creative_agent = Agent(name="Creative", role="Creative Writer")
|
||||
|
||||
# 1. ROUTING - Decision-based branching
|
||||
workflow = Workflow(steps=[
|
||||
classify_request, # Returns "approve" or "reject"
|
||||
classifier,
|
||||
route({
|
||||
"approve": [approve_handler],
|
||||
"reject": [reject_handler],
|
||||
"default": [fallback_handler]
|
||||
"technical": [tech_agent],
|
||||
"creative": [creative_agent]
|
||||
})
|
||||
])
|
||||
|
||||
# 2. PARALLEL - Concurrent execution
|
||||
# 2. PARALLEL - Multiple agents work concurrently
|
||||
market_agent = Agent(name="Market", role="Market Researcher")
|
||||
competitor_agent = Agent(name="Competitor", role="Competitor Analyst")
|
||||
aggregator = Agent(name="Aggregator", role="Synthesizer")
|
||||
|
||||
workflow = Workflow(steps=[
|
||||
parallel([research_market, research_competitors, research_customers]),
|
||||
summarize_results # Gets all parallel outputs
|
||||
parallel([market_agent, competitor_agent]),
|
||||
aggregator
|
||||
])
|
||||
|
||||
# 3. LOOP - Iterate over list or CSV
|
||||
# 3. LOOP - Agent processes each item
|
||||
processor = Agent(name="Processor", role="Item Processor")
|
||||
summarizer = Agent(name="Summarizer", role="Summarizer")
|
||||
|
||||
workflow = Workflow(
|
||||
steps=[loop(process_item, over="items")],
|
||||
variables={"items": ["a", "b", "c"]}
|
||||
steps=[loop(processor, over="items"), summarizer],
|
||||
variables={"items": ["AI", "ML", "NLP"]}
|
||||
)
|
||||
# Or from CSV file:
|
||||
workflow = Workflow(steps=[loop(process_row, from_csv="data.csv")])
|
||||
|
||||
# 4. REPEAT - Evaluator-Optimizer pattern
|
||||
generator = Agent(name="Generator", role="Content Generator")
|
||||
evaluator = Agent(name="Evaluator", instructions="Say 'APPROVED' if good")
|
||||
|
||||
workflow = Workflow(steps=[
|
||||
repeat(
|
||||
generator,
|
||||
until=lambda ctx: "done" in ctx.previous_result,
|
||||
max_iterations=5
|
||||
)
|
||||
generator,
|
||||
repeat(evaluator, until=lambda ctx: "approved" in ctx.previous_result.lower(), max_iterations=3)
|
||||
])
|
||||
```
|
||||
|
||||
### Task Callbacks & Variables (process="workflow")
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Per-task agent config (no need to create agent separately)
|
||||
task = Task(
|
||||
description="Research {{topic}}",
|
||||
agent_config={"name": "Researcher", "role": "Expert", "llm": "gpt-4o-mini"},
|
||||
variables={"topic": "AI trends"}
|
||||
# 5. CALLBACKS
|
||||
workflow = Workflow(
|
||||
steps=[researcher, writer],
|
||||
on_step_complete=lambda name, r: print(f"✅ {name} done")
|
||||
)
|
||||
|
||||
# Callbacks for workflow execution
|
||||
def on_start(task, task_id):
|
||||
print(f"Starting: {task.name}")
|
||||
# 6. WITH PLANNING & REASONING
|
||||
workflow = Workflow(
|
||||
steps=[researcher, writer],
|
||||
planning=True,
|
||||
reasoning=True
|
||||
)
|
||||
|
||||
def on_complete(task, output):
|
||||
print(f"Completed: {task.name}")
|
||||
# 7. ASYNC EXECUTION
|
||||
result = asyncio.run(workflow.astart("input"))
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
process="workflow",
|
||||
on_task_start=on_start,
|
||||
on_task_complete=on_complete,
|
||||
variables={"global_var": "shared_value"} # Global variables for all tasks
|
||||
# 8. STATUS TRACKING
|
||||
workflow.status # "not_started" | "running" | "completed"
|
||||
workflow.step_statuses # {"step1": "completed", "step2": "skipped"}
|
||||
```
|
||||
|
||||
### 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]
|
||||
|
||||
workflow:
|
||||
planning: true
|
||||
planning_llm: gpt-4o
|
||||
memory_config:
|
||||
provider: chroma
|
||||
persist: true
|
||||
```
|
||||
|
||||
### Loading YAML Workflows
|
||||
|
||||
```python
|
||||
from praisonaiagents.workflows import YAMLWorkflowParser, WorkflowManager
|
||||
|
||||
# Option 1: Parse YAML string
|
||||
parser = YAMLWorkflowParser()
|
||||
workflow = parser.parse_string(yaml_content)
|
||||
result = workflow.start("Research AI trends")
|
||||
|
||||
# Option 2: Load from file with WorkflowManager
|
||||
manager = WorkflowManager()
|
||||
workflow = manager.load_yaml("research_workflow.yaml")
|
||||
result = workflow.start("Research AI trends")
|
||||
|
||||
# Option 3: Execute YAML directly
|
||||
result = manager.execute_yaml(
|
||||
"research_workflow.yaml",
|
||||
input_data="Research AI trends",
|
||||
variables={"topic": "Machine Learning"}
|
||||
)
|
||||
```
|
||||
|
||||
@@ -606,6 +621,75 @@ hooks.register("pre_write_code", lambda ctx: print(f"Writing {ctx['file']}"))
|
||||
result = hooks.execute("pre_write_code", {"file": "main.py"})
|
||||
```
|
||||
|
||||
### 10. Extended agents.yaml with Workflow Patterns
|
||||
|
||||
You can now use advanced workflow patterns directly in agents.yaml by setting `process: workflow`:
|
||||
|
||||
```yaml
|
||||
# agents.yaml with workflow patterns
|
||||
framework: praisonai
|
||||
process: workflow # Enables workflow mode
|
||||
topic: "Research AI trends"
|
||||
|
||||
workflow:
|
||||
planning: true
|
||||
reasoning: true
|
||||
verbose: true
|
||||
|
||||
variables:
|
||||
topic: AI trends
|
||||
|
||||
roles:
|
||||
classifier:
|
||||
role: Request Classifier
|
||||
backstory: "Classify requests into categories"
|
||||
goal: Classify requests
|
||||
|
||||
researcher:
|
||||
role: Research Analyst
|
||||
backstory: "Expert researcher"
|
||||
goal: Research topics
|
||||
tools:
|
||||
- tavily_search
|
||||
|
||||
steps:
|
||||
# Sequential step
|
||||
- agent: classifier
|
||||
action: "Classify: {{topic}}"
|
||||
|
||||
# Route pattern - decision-based branching
|
||||
- name: routing
|
||||
route:
|
||||
technical: [tech_expert]
|
||||
default: [researcher]
|
||||
|
||||
# Parallel pattern - concurrent execution
|
||||
- name: parallel_research
|
||||
parallel:
|
||||
- agent: researcher
|
||||
action: "Research market trends"
|
||||
- agent: researcher
|
||||
action: "Research competitors"
|
||||
|
||||
# Loop pattern - iterate over items
|
||||
- agent: researcher
|
||||
action: "Analyze {{item}}"
|
||||
loop:
|
||||
over: topics
|
||||
|
||||
# Repeat pattern - evaluator-optimizer
|
||||
- agent: aggregator
|
||||
action: "Synthesize findings"
|
||||
repeat:
|
||||
until: "comprehensive"
|
||||
max_iterations: 3
|
||||
```
|
||||
|
||||
Run with the same simple command:
|
||||
```bash
|
||||
praisonai agents.yaml
|
||||
```
|
||||
|
||||
## Using No Code
|
||||
|
||||
### Auto Mode:
|
||||
@@ -733,6 +817,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
|
||||
|
||||
@@ -744,8 +834,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.20" \
|
||||
"praisonai>=2.3.26" \
|
||||
"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.20" \
|
||||
"praisonai>=2.3.26" \
|
||||
"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.20" \
|
||||
"praisonai>=2.3.26" \
|
||||
"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.20" \
|
||||
"praisonai>=2.3.26" \
|
||||
"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'])}")
|
||||
@@ -1,68 +1,83 @@
|
||||
"""
|
||||
Workflow Conditional Execution Example
|
||||
Agentic Conditional Workflow Example
|
||||
|
||||
Demonstrates using should_run to conditionally execute steps
|
||||
Demonstrates using should_run to conditionally execute agent steps
|
||||
based on input or previous results.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowStep, WorkflowContext, StepResult
|
||||
from praisonaiagents import Agent, Workflow, WorkflowStep
|
||||
|
||||
# Condition functions - return True to run the step, False to skip
|
||||
def is_sensitive_content(ctx: WorkflowContext) -> bool:
|
||||
"""Only run compliance check for sensitive topics."""
|
||||
sensitive_keywords = ["legal", "medical", "financial", "security"]
|
||||
return any(keyword in ctx.input.lower() for keyword in sensitive_keywords)
|
||||
# Condition functions
|
||||
def is_technical(ctx) -> bool:
|
||||
"""Only run technical review for technical content."""
|
||||
keywords = ["code", "programming", "algorithm", "database", "api"]
|
||||
return any(keyword in ctx.input.lower() for keyword in keywords)
|
||||
|
||||
def needs_translation(ctx: WorkflowContext) -> bool:
|
||||
"""Only translate if content is not in English."""
|
||||
return ctx.variables.get("language", "en") != "en"
|
||||
def needs_creative(ctx) -> bool:
|
||||
"""Only run creative enhancement for creative content."""
|
||||
keywords = ["story", "poem", "creative", "write", "narrative"]
|
||||
return any(keyword in ctx.input.lower() for keyword in keywords)
|
||||
|
||||
# Handler functions
|
||||
def process_content(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output=f"Processed: {ctx.input}")
|
||||
# Create agents
|
||||
analyzer = Agent(
|
||||
name="Analyzer",
|
||||
role="Content Analyzer",
|
||||
goal="Analyze and process content",
|
||||
instructions="Analyze the given content and provide insights."
|
||||
)
|
||||
|
||||
def compliance_check(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output=f"✅ Compliance verified for: {ctx.previous_result}")
|
||||
tech_reviewer = Agent(
|
||||
name="TechReviewer",
|
||||
role="Technical Reviewer",
|
||||
goal="Review technical content",
|
||||
instructions="Review the technical aspects and provide technical feedback."
|
||||
)
|
||||
|
||||
def translate(ctx: WorkflowContext) -> StepResult:
|
||||
lang = ctx.variables.get("language", "unknown")
|
||||
return StepResult(output=f"🌐 Translated to {lang}: {ctx.previous_result}")
|
||||
creative_enhancer = Agent(
|
||||
name="CreativeEnhancer",
|
||||
role="Creative Enhancer",
|
||||
goal="Enhance creative content",
|
||||
instructions="Enhance the creative aspects and make it more engaging."
|
||||
)
|
||||
|
||||
def finalize(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output=f"📄 Final: {ctx.previous_result}")
|
||||
finalizer = Agent(
|
||||
name="Finalizer",
|
||||
role="Content Finalizer",
|
||||
goal="Finalize the content",
|
||||
instructions="Provide the final polished version of the content."
|
||||
)
|
||||
|
||||
# Create workflow with conditional steps
|
||||
workflow = Workflow(
|
||||
name="Conditional Pipeline",
|
||||
name="Conditional Agentic Pipeline",
|
||||
steps=[
|
||||
WorkflowStep(name="process", handler=process_content),
|
||||
analyzer, # Always runs
|
||||
WorkflowStep(
|
||||
name="compliance",
|
||||
handler=compliance_check,
|
||||
should_run=is_sensitive_content # Only runs for sensitive content
|
||||
name="tech_review",
|
||||
agent=tech_reviewer,
|
||||
should_run=is_technical # Only runs for technical content
|
||||
),
|
||||
WorkflowStep(
|
||||
name="translate",
|
||||
handler=translate,
|
||||
should_run=needs_translation # Only runs if language != "en"
|
||||
name="creative_enhance",
|
||||
agent=creative_enhancer,
|
||||
should_run=needs_creative # Only runs for creative content
|
||||
),
|
||||
WorkflowStep(name="finalize", handler=finalize)
|
||||
finalizer # Always runs
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test 1: Normal content (skips compliance)
|
||||
print("=== Test 1: Normal content ===")
|
||||
result = workflow.start("Hello world", verbose=True)
|
||||
print(f"Output: {result['output']}\n")
|
||||
# Test 1: Technical content
|
||||
print("=== Test 1: Technical Content ===")
|
||||
result = workflow.start("Review this Python code for best practices", verbose=True)
|
||||
print(f"Output: {result['output'][:200]}...\n")
|
||||
|
||||
# Test 2: Sensitive content (runs compliance)
|
||||
print("=== Test 2: Legal content ===")
|
||||
result = workflow.start("Review this legal document", verbose=True)
|
||||
print(f"Output: {result['output']}\n")
|
||||
# Test 2: Creative content
|
||||
print("=== Test 2: Creative Content ===")
|
||||
result = workflow.start("Write a short story about a robot", verbose=True)
|
||||
print(f"Output: {result['output'][:200]}...\n")
|
||||
|
||||
# Test 3: Non-English content (runs translation)
|
||||
print("=== Test 3: Non-English content ===")
|
||||
workflow.variables = {"language": "es"}
|
||||
result = workflow.start("Hola mundo", verbose=True)
|
||||
print(f"Output: {result['output']}")
|
||||
# Test 3: General content (neither technical nor creative)
|
||||
print("=== Test 3: General Content ===")
|
||||
result = workflow.start("What is the weather like today?", verbose=True)
|
||||
print(f"Output: {result['output'][:200]}...")
|
||||
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Workflow Conditional Execution Example
|
||||
|
||||
Demonstrates using should_run to conditionally execute steps
|
||||
based on input or previous results.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowStep, WorkflowContext, StepResult
|
||||
|
||||
# Condition functions - return True to run the step, False to skip
|
||||
def is_sensitive_content(ctx: WorkflowContext) -> bool:
|
||||
"""Only run compliance check for sensitive topics."""
|
||||
sensitive_keywords = ["legal", "medical", "financial", "security"]
|
||||
return any(keyword in ctx.input.lower() for keyword in sensitive_keywords)
|
||||
|
||||
def needs_translation(ctx: WorkflowContext) -> bool:
|
||||
"""Only translate if content is not in English."""
|
||||
return ctx.variables.get("language", "en") != "en"
|
||||
|
||||
# Handler functions
|
||||
def process_content(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output=f"Processed: {ctx.input}")
|
||||
|
||||
def compliance_check(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output=f"✅ Compliance verified for: {ctx.previous_result}")
|
||||
|
||||
def translate(ctx: WorkflowContext) -> StepResult:
|
||||
lang = ctx.variables.get("language", "unknown")
|
||||
return StepResult(output=f"🌐 Translated to {lang}: {ctx.previous_result}")
|
||||
|
||||
def finalize(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output=f"📄 Final: {ctx.previous_result}")
|
||||
|
||||
# Create workflow with conditional steps
|
||||
workflow = Workflow(
|
||||
name="Conditional Pipeline",
|
||||
steps=[
|
||||
WorkflowStep(name="process", handler=process_content),
|
||||
WorkflowStep(
|
||||
name="compliance",
|
||||
handler=compliance_check,
|
||||
should_run=is_sensitive_content # Only runs for sensitive content
|
||||
),
|
||||
WorkflowStep(
|
||||
name="translate",
|
||||
handler=translate,
|
||||
should_run=needs_translation # Only runs if language != "en"
|
||||
),
|
||||
WorkflowStep(name="finalize", handler=finalize)
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test 1: Normal content (skips compliance)
|
||||
print("=== Test 1: Normal content ===")
|
||||
result = workflow.start("Hello world", verbose=True)
|
||||
print(f"Output: {result['output']}\n")
|
||||
|
||||
# Test 2: Sensitive content (runs compliance)
|
||||
print("=== Test 2: Legal content ===")
|
||||
result = workflow.start("Review this legal document", verbose=True)
|
||||
print(f"Output: {result['output']}\n")
|
||||
|
||||
# Test 3: Non-English content (runs translation)
|
||||
print("=== Test 3: Non-English content ===")
|
||||
workflow.variables = {"language": "es"}
|
||||
result = workflow.start("Hola mundo", verbose=True)
|
||||
print(f"Output: {result['output']}")
|
||||
@@ -1,33 +1,36 @@
|
||||
"""
|
||||
Workflow Loop with CSV Example
|
||||
Agentic Loop with CSV Workflow Example
|
||||
|
||||
Demonstrates iterating over a CSV file, processing each row.
|
||||
Demonstrates iterating over a CSV file with an agent processing each row.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.memory.workflows import loop
|
||||
from praisonaiagents import Agent, Workflow
|
||||
from praisonaiagents.workflows import loop
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
# Create sample CSV file
|
||||
csv_content = """name,email,task
|
||||
Alice,[email protected],Write documentation
|
||||
Bob,[email protected],Review pull requests
|
||||
Charlie,[email protected],Fix critical bug
|
||||
Diana,[email protected],Deploy to production"""
|
||||
# Create sample CSV file content
|
||||
csv_content = """topic,description
|
||||
AI Ethics,Ethical considerations in artificial intelligence
|
||||
Machine Learning,Algorithms that learn from data
|
||||
Neural Networks,Computing systems inspired by biological neural networks
|
||||
Natural Language Processing,AI understanding human language"""
|
||||
|
||||
# Processor for each CSV row
|
||||
def process_task(ctx: WorkflowContext) -> StepResult:
|
||||
"""Process a single task from CSV."""
|
||||
item = ctx.variables.get("item", {})
|
||||
name = item.get("name", "Unknown")
|
||||
task = item.get("task", "No task")
|
||||
index = ctx.variables.get("loop_index", 0)
|
||||
|
||||
return StepResult(
|
||||
output=f"[{index+1}] {name}: {task} ✅",
|
||||
variables={"last_processed": name}
|
||||
)
|
||||
# Create processor agent
|
||||
processor = Agent(
|
||||
name="TopicProcessor",
|
||||
role="Topic Analyst",
|
||||
goal="Analyze and explain topics",
|
||||
instructions="Provide a brief, insightful analysis of the given topic."
|
||||
)
|
||||
|
||||
# Create summarizer agent
|
||||
summarizer = Agent(
|
||||
name="Summarizer",
|
||||
role="Results Summarizer",
|
||||
goal="Summarize all processed results",
|
||||
instructions="Create a comprehensive summary of all the analyzed topics."
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create temp CSV file
|
||||
@@ -36,20 +39,21 @@ if __name__ == "__main__":
|
||||
csv_path = f.name
|
||||
|
||||
try:
|
||||
print("=== Testing Loop with CSV ===\n")
|
||||
print(f"CSV file: {csv_path}\n")
|
||||
print("=== Testing Agentic Loop with CSV ===\n")
|
||||
print(f"Processing CSV: {csv_path}\n")
|
||||
|
||||
# Create workflow
|
||||
# Create workflow with loop over CSV
|
||||
workflow = Workflow(
|
||||
name="CSV Task Processor",
|
||||
steps=[loop(process_task, from_csv=csv_path)]
|
||||
name="CSV Topic Processor",
|
||||
steps=[
|
||||
loop(processor, from_csv=csv_path),
|
||||
summarizer
|
||||
]
|
||||
)
|
||||
|
||||
result = workflow.start("Process all tasks", verbose=True)
|
||||
result = workflow.start("Analyze these AI topics", verbose=True)
|
||||
|
||||
print(f"\nAll outputs:\n{result['output']}")
|
||||
print(f"\nProcessed {len(result['variables']['loop_outputs'])} items")
|
||||
print(f"Last processed: {result['variables'].get('last_processed')}")
|
||||
print(f"\nFinal Summary:\n{result['output']}")
|
||||
|
||||
finally:
|
||||
os.unlink(csv_path)
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Workflow Loop with CSV Example
|
||||
|
||||
Demonstrates iterating over a CSV file, processing each row.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.workflows import loop
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
# Create sample CSV file
|
||||
csv_content = """name,email,task
|
||||
Alice,[email protected],Write documentation
|
||||
Bob,[email protected],Review pull requests
|
||||
Charlie,[email protected],Fix critical bug
|
||||
Diana,[email protected],Deploy to production"""
|
||||
|
||||
# Processor for each CSV row
|
||||
def process_task(ctx: WorkflowContext) -> StepResult:
|
||||
"""Process a single task from CSV."""
|
||||
item = ctx.variables.get("item", {})
|
||||
name = item.get("name", "Unknown")
|
||||
task = item.get("task", "No task")
|
||||
index = ctx.variables.get("loop_index", 0)
|
||||
|
||||
return StepResult(
|
||||
output=f"[{index+1}] {name}: {task} ✅",
|
||||
variables={"last_processed": name}
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create temp CSV file
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f:
|
||||
f.write(csv_content)
|
||||
csv_path = f.name
|
||||
|
||||
try:
|
||||
print("=== Testing Loop with CSV ===\n")
|
||||
print(f"CSV file: {csv_path}\n")
|
||||
|
||||
# Create workflow
|
||||
workflow = Workflow(
|
||||
name="CSV Task Processor",
|
||||
steps=[loop(process_task, from_csv=csv_path)]
|
||||
)
|
||||
|
||||
result = workflow.start("Process all tasks", verbose=True)
|
||||
|
||||
print(f"\nAll outputs:\n{result['output']}")
|
||||
print(f"\nProcessed {len(result['variables']['loop_outputs'])} items")
|
||||
print(f"Last processed: {result['variables'].get('last_processed')}")
|
||||
|
||||
finally:
|
||||
os.unlink(csv_path)
|
||||
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Workflow Loop with List Example
|
||||
|
||||
Demonstrates iterating over a list of items, processing each one.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.workflows import loop
|
||||
|
||||
# Sample data
|
||||
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
|
||||
|
||||
# Processor for each item
|
||||
def process_fruit(ctx: WorkflowContext) -> StepResult:
|
||||
"""Process a single fruit from the list."""
|
||||
fruit = ctx.variables.get("item", "unknown")
|
||||
index = ctx.variables.get("loop_index", 0)
|
||||
|
||||
# Simulate processing
|
||||
result = f"🍎 [{index + 1}] {fruit.upper()} - processed successfully"
|
||||
|
||||
return StepResult(
|
||||
output=result,
|
||||
variables={"last_fruit": fruit}
|
||||
)
|
||||
|
||||
# Aggregator to summarize results
|
||||
def summarize(ctx: WorkflowContext) -> StepResult:
|
||||
"""Summarize all processed fruits."""
|
||||
outputs = ctx.variables.get("loop_outputs", [])
|
||||
summary = f"📊 Processed {len(outputs)} fruits:\n"
|
||||
summary += "\n".join(f" {o}" for o in outputs)
|
||||
return StepResult(output=summary)
|
||||
|
||||
# Create workflow
|
||||
workflow = Workflow(
|
||||
name="Fruit Processor",
|
||||
steps=[
|
||||
loop(process_fruit, over="fruits"),
|
||||
summarize
|
||||
],
|
||||
variables={"fruits": fruits}
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Testing Loop with List ===\n")
|
||||
|
||||
result = workflow.start("Process all fruits", verbose=True)
|
||||
|
||||
print(f"\nFinal Output:\n{result['output']}")
|
||||
print(f"\nLoop outputs count: {len(result['variables']['loop_outputs'])}")
|
||||
print(f"Last processed: {result['variables'].get('last_fruit')}")
|
||||
@@ -1,53 +1,56 @@
|
||||
"""
|
||||
Workflow Parallel Execution Example
|
||||
Agentic Parallel Workflow Example
|
||||
|
||||
Demonstrates running multiple steps concurrently and
|
||||
combining their results.
|
||||
Demonstrates running multiple agents concurrently and
|
||||
combining their results with an aggregator agent.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.memory.workflows import parallel
|
||||
import time
|
||||
from praisonaiagents import Agent, Workflow
|
||||
from praisonaiagents.workflows import parallel
|
||||
|
||||
# Parallel workers - each does independent work
|
||||
def research_market(ctx: WorkflowContext) -> StepResult:
|
||||
"""Research market trends."""
|
||||
time.sleep(0.1) # Simulate work
|
||||
return StepResult(output="📊 Market: Growth expected 15% YoY")
|
||||
# Create parallel research agents
|
||||
market_researcher = Agent(
|
||||
name="MarketResearcher",
|
||||
role="Market Research Analyst",
|
||||
goal="Research market trends and opportunities",
|
||||
instructions="Analyze market trends. Provide concise market insights."
|
||||
)
|
||||
|
||||
def research_competitors(ctx: WorkflowContext) -> StepResult:
|
||||
"""Research competitor analysis."""
|
||||
time.sleep(0.1) # Simulate work
|
||||
return StepResult(output="🏢 Competitors: 3 major players identified")
|
||||
competitor_researcher = Agent(
|
||||
name="CompetitorResearcher",
|
||||
role="Competitive Intelligence Analyst",
|
||||
goal="Research competitor strategies",
|
||||
instructions="Analyze competitors. Provide key competitive insights."
|
||||
)
|
||||
|
||||
def research_customers(ctx: WorkflowContext) -> StepResult:
|
||||
"""Research customer feedback."""
|
||||
time.sleep(0.1) # Simulate work
|
||||
return StepResult(output="👥 Customers: 85% satisfaction rate")
|
||||
customer_researcher = Agent(
|
||||
name="CustomerResearcher",
|
||||
role="Customer Research Analyst",
|
||||
goal="Research customer needs and behaviors",
|
||||
instructions="Analyze customer segments. Provide customer insights."
|
||||
)
|
||||
|
||||
# Aggregator - combines parallel results
|
||||
def summarize_research(ctx: WorkflowContext) -> StepResult:
|
||||
"""Summarize all research findings."""
|
||||
outputs = ctx.variables.get("parallel_outputs", [])
|
||||
summary = "📋 RESEARCH SUMMARY:\n" + "\n".join(f" • {o}" for o in outputs)
|
||||
return StepResult(output=summary)
|
||||
# Create aggregator agent
|
||||
aggregator = Agent(
|
||||
name="Aggregator",
|
||||
role="Research Synthesizer",
|
||||
goal="Synthesize research findings",
|
||||
instructions="Combine all research findings into a comprehensive summary."
|
||||
)
|
||||
|
||||
# Create workflow with parallel execution
|
||||
workflow = Workflow(
|
||||
name="Parallel Research",
|
||||
name="Parallel Research Pipeline",
|
||||
steps=[
|
||||
parallel([research_market, research_competitors, research_customers]),
|
||||
summarize_research
|
||||
parallel([market_researcher, competitor_researcher, customer_researcher]),
|
||||
aggregator
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Testing Parallel Workflow ===\n")
|
||||
print("=== Testing Agentic Parallel Workflow ===\n")
|
||||
|
||||
start_time = time.time()
|
||||
result = workflow.start("Analyze the business landscape", verbose=True)
|
||||
elapsed = time.time() - start_time
|
||||
# Run workflow - all researchers work in parallel, then aggregator summarizes
|
||||
result = workflow.start("Research the AI industry", verbose=True)
|
||||
|
||||
print(f"\nFinal Output:\n{result['output']}")
|
||||
print(f"\nCompleted in {elapsed:.2f}s (parallel execution)")
|
||||
print(f"Individual outputs: {len(result['variables']['parallel_outputs'])}")
|
||||
print(f"\nFinal Summary:\n{result['output']}")
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Workflow Parallel Execution Example
|
||||
|
||||
Demonstrates running multiple steps concurrently and
|
||||
combining their results.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.workflows import parallel
|
||||
import time
|
||||
|
||||
# Parallel workers - each does independent work
|
||||
def research_market(ctx: WorkflowContext) -> StepResult:
|
||||
"""Research market trends."""
|
||||
time.sleep(0.1) # Simulate work
|
||||
return StepResult(output="📊 Market: Growth expected 15% YoY")
|
||||
|
||||
def research_competitors(ctx: WorkflowContext) -> StepResult:
|
||||
"""Research competitor analysis."""
|
||||
time.sleep(0.1) # Simulate work
|
||||
return StepResult(output="🏢 Competitors: 3 major players identified")
|
||||
|
||||
def research_customers(ctx: WorkflowContext) -> StepResult:
|
||||
"""Research customer feedback."""
|
||||
time.sleep(0.1) # Simulate work
|
||||
return StepResult(output="👥 Customers: 85% satisfaction rate")
|
||||
|
||||
# Aggregator - combines parallel results
|
||||
def summarize_research(ctx: WorkflowContext) -> StepResult:
|
||||
"""Summarize all research findings."""
|
||||
outputs = ctx.variables.get("parallel_outputs", [])
|
||||
summary = "📋 RESEARCH SUMMARY:\n" + "\n".join(f" • {o}" for o in outputs)
|
||||
return StepResult(output=summary)
|
||||
|
||||
# Create workflow with parallel execution
|
||||
workflow = Workflow(
|
||||
name="Parallel Research",
|
||||
steps=[
|
||||
parallel([research_market, research_competitors, research_customers]),
|
||||
summarize_research
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Testing Parallel Workflow ===\n")
|
||||
|
||||
start_time = time.time()
|
||||
result = workflow.start("Analyze the business landscape", verbose=True)
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
print(f"\nFinal Output:\n{result['output']}")
|
||||
print(f"\nCompleted in {elapsed:.2f}s (parallel execution)")
|
||||
print(f"Individual outputs: {len(result['variables']['parallel_outputs'])}")
|
||||
@@ -1,69 +1,53 @@
|
||||
"""
|
||||
Workflow Repeat (Evaluator-Optimizer) Example
|
||||
Agentic Evaluator-Optimizer (Repeat) Workflow Example
|
||||
|
||||
Demonstrates repeating a step until a condition is met.
|
||||
This is useful for iterative improvement patterns.
|
||||
Demonstrates the evaluator-optimizer pattern where an agent
|
||||
generates content and another evaluates it, repeating until approved.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.memory.workflows import repeat
|
||||
from praisonaiagents import Agent, Workflow
|
||||
from praisonaiagents.workflows import repeat
|
||||
|
||||
# Simulated content generator that improves each iteration
|
||||
class ContentGenerator:
|
||||
def __init__(self):
|
||||
self.iteration = 0
|
||||
self.content = []
|
||||
|
||||
def generate(self, ctx: WorkflowContext) -> StepResult:
|
||||
"""Generate content, adding more each iteration."""
|
||||
self.iteration += 1
|
||||
|
||||
# Add new content each iteration
|
||||
new_items = [
|
||||
f"Point {self.iteration}.1: Key insight about the topic",
|
||||
f"Point {self.iteration}.2: Supporting evidence"
|
||||
]
|
||||
self.content.extend(new_items)
|
||||
|
||||
output = f"Iteration {self.iteration}: Generated {len(self.content)} points total"
|
||||
|
||||
return StepResult(
|
||||
output=output,
|
||||
variables={
|
||||
"total_points": len(self.content),
|
||||
"content": self.content.copy()
|
||||
}
|
||||
)
|
||||
# Create generator agent
|
||||
generator = Agent(
|
||||
name="Generator",
|
||||
role="Content Generator",
|
||||
goal="Generate high-quality content",
|
||||
instructions="Generate content based on the topic. Improve based on feedback if provided."
|
||||
)
|
||||
|
||||
# Condition: stop when we have enough content
|
||||
def has_enough_content(ctx: WorkflowContext) -> bool:
|
||||
"""Check if we have generated enough content."""
|
||||
total = ctx.variables.get("total_points", 0)
|
||||
return total >= 6 # Stop when we have 6+ points
|
||||
# Create evaluator agent
|
||||
evaluator = Agent(
|
||||
name="Evaluator",
|
||||
role="Content Evaluator",
|
||||
goal="Evaluate and provide feedback on content",
|
||||
instructions="Evaluate the content quality. If good, respond with 'APPROVED'. Otherwise provide specific feedback for improvement."
|
||||
)
|
||||
|
||||
# Create generator instance
|
||||
generator = ContentGenerator()
|
||||
# Evaluation function to check if content is approved
|
||||
def is_approved(ctx) -> bool:
|
||||
return "approved" in ctx.previous_result.lower()
|
||||
|
||||
# Create workflow with repeat pattern
|
||||
# Create workflow with evaluator-optimizer pattern
|
||||
workflow = Workflow(
|
||||
name="Content Generator",
|
||||
name="Evaluator-Optimizer Pipeline",
|
||||
steps=[
|
||||
generator, # First, generate content
|
||||
repeat(
|
||||
generator.generate,
|
||||
until=has_enough_content,
|
||||
max_iterations=10
|
||||
evaluator, # Evaluate and provide feedback
|
||||
until=is_approved,
|
||||
max_iterations=3
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Testing Repeat (Evaluator-Optimizer) Pattern ===\n")
|
||||
print("=== Testing Agentic Evaluator-Optimizer Workflow ===\n")
|
||||
|
||||
result = workflow.start("Generate comprehensive content", verbose=True)
|
||||
# Run optimization workflow
|
||||
result = workflow.start(
|
||||
"Write a compelling product description for an AI assistant",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
print(f"\nFinal output: {result['output']}")
|
||||
print(f"Total iterations: {result['variables']['repeat_iterations']}")
|
||||
print(f"Total points generated: {result['variables']['total_points']}")
|
||||
print("\nGenerated content:")
|
||||
for point in result['variables']['content']:
|
||||
print(f" • {point}")
|
||||
print(f"\nFinal Result:\n{result['output']}")
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Workflow Repeat (Evaluator-Optimizer) Example
|
||||
|
||||
Demonstrates repeating a step until a condition is met.
|
||||
This is useful for iterative improvement patterns.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.workflows import repeat
|
||||
|
||||
# Simulated content generator that improves each iteration
|
||||
class ContentGenerator:
|
||||
def __init__(self):
|
||||
self.iteration = 0
|
||||
self.content = []
|
||||
|
||||
def generate(self, ctx: WorkflowContext) -> StepResult:
|
||||
"""Generate content, adding more each iteration."""
|
||||
self.iteration += 1
|
||||
|
||||
# Add new content each iteration
|
||||
new_items = [
|
||||
f"Point {self.iteration}.1: Key insight about the topic",
|
||||
f"Point {self.iteration}.2: Supporting evidence"
|
||||
]
|
||||
self.content.extend(new_items)
|
||||
|
||||
output = f"Iteration {self.iteration}: Generated {len(self.content)} points total"
|
||||
|
||||
return StepResult(
|
||||
output=output,
|
||||
variables={
|
||||
"total_points": len(self.content),
|
||||
"content": self.content.copy()
|
||||
}
|
||||
)
|
||||
|
||||
# Condition: stop when we have enough content
|
||||
def has_enough_content(ctx: WorkflowContext) -> bool:
|
||||
"""Check if we have generated enough content."""
|
||||
total = ctx.variables.get("total_points", 0)
|
||||
return total >= 6 # Stop when we have 6+ points
|
||||
|
||||
# Create generator instance
|
||||
generator = ContentGenerator()
|
||||
|
||||
# Create workflow with repeat pattern
|
||||
workflow = Workflow(
|
||||
name="Content Generator",
|
||||
steps=[
|
||||
repeat(
|
||||
generator.generate,
|
||||
until=has_enough_content,
|
||||
max_iterations=10
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Testing Repeat (Evaluator-Optimizer) Pattern ===\n")
|
||||
|
||||
result = workflow.start("Generate comprehensive content", verbose=True)
|
||||
|
||||
print(f"\nFinal output: {result['output']}")
|
||||
print(f"Total iterations: {result['variables']['repeat_iterations']}")
|
||||
print(f"Total points generated: {result['variables']['total_points']}")
|
||||
print("\nGenerated content:")
|
||||
for point in result['variables']['content']:
|
||||
print(f" • {point}")
|
||||
@@ -1,61 +1,65 @@
|
||||
"""
|
||||
Workflow Routing Example
|
||||
Agentic Routing Workflow Example
|
||||
|
||||
Demonstrates decision-based routing where the workflow
|
||||
takes different paths based on the output of a decision step.
|
||||
Demonstrates decision-based routing where an agent classifier
|
||||
routes to specialized handler agents based on the request type.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.memory.workflows import route
|
||||
from praisonaiagents import Agent, Workflow
|
||||
from praisonaiagents.workflows import route
|
||||
|
||||
# Decision maker - determines which route to take
|
||||
def classify_request(ctx: WorkflowContext) -> StepResult:
|
||||
"""Classify the input and decide the route."""
|
||||
input_lower = ctx.input.lower()
|
||||
|
||||
if "urgent" in input_lower or "emergency" in input_lower:
|
||||
return StepResult(output="priority: high")
|
||||
elif "question" in input_lower or "help" in input_lower:
|
||||
return StepResult(output="priority: support")
|
||||
else:
|
||||
return StepResult(output="priority: normal")
|
||||
# Create classifier agent
|
||||
classifier = Agent(
|
||||
name="Classifier",
|
||||
role="Request Classifier",
|
||||
goal="Classify incoming requests",
|
||||
instructions="Classify the request. Respond with ONLY 'technical', 'creative', or 'general'."
|
||||
)
|
||||
|
||||
# Route handlers
|
||||
def handle_high_priority(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output="🚨 HIGH PRIORITY: Escalating to senior team immediately!")
|
||||
# Create specialized handler agents
|
||||
tech_agent = Agent(
|
||||
name="TechExpert",
|
||||
role="Technical Expert",
|
||||
goal="Handle technical questions",
|
||||
instructions="You are a technical expert. Provide detailed technical answers."
|
||||
)
|
||||
|
||||
def handle_support(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output="💬 SUPPORT: Routing to help desk for assistance.")
|
||||
creative_agent = Agent(
|
||||
name="CreativeWriter",
|
||||
role="Creative Writer",
|
||||
goal="Handle creative requests",
|
||||
instructions="You are a creative writer. Write engaging, creative content."
|
||||
)
|
||||
|
||||
def handle_normal(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output="📋 NORMAL: Added to standard processing queue.")
|
||||
general_agent = Agent(
|
||||
name="GeneralAssistant",
|
||||
role="General Assistant",
|
||||
goal="Handle general requests",
|
||||
instructions="You are a helpful assistant. Provide clear, helpful responses."
|
||||
)
|
||||
|
||||
# Create workflow with routing
|
||||
workflow = Workflow(
|
||||
name="Request Router",
|
||||
name="Agentic Router",
|
||||
steps=[
|
||||
classify_request,
|
||||
classifier,
|
||||
route({
|
||||
"high": [handle_high_priority],
|
||||
"support": [handle_support],
|
||||
"normal": [handle_normal],
|
||||
"default": [handle_normal]
|
||||
"technical": [tech_agent],
|
||||
"creative": [creative_agent],
|
||||
"default": [general_agent]
|
||||
})
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test different inputs
|
||||
print("=== Testing Routing Workflow ===\n")
|
||||
print("=== Testing Agentic Routing Workflow ===\n")
|
||||
|
||||
# Test 1: Urgent request
|
||||
result = workflow.start("This is an URGENT matter!", verbose=True)
|
||||
print(f"Result: {result['output']}\n")
|
||||
# Test 1: Technical question
|
||||
print("--- Technical Request ---")
|
||||
result = workflow.start("How does machine learning work?", verbose=True)
|
||||
print(f"Result: {result['output'][:200]}...\n")
|
||||
|
||||
# Test 2: Support request
|
||||
result = workflow.start("I have a question about my account", verbose=True)
|
||||
print(f"Result: {result['output']}\n")
|
||||
|
||||
# Test 3: Normal request
|
||||
result = workflow.start("Please process my order", verbose=True)
|
||||
print(f"Result: {result['output']}")
|
||||
# Test 2: Creative request
|
||||
print("--- Creative Request ---")
|
||||
result = workflow.start("Write a poem about the ocean", verbose=True)
|
||||
print(f"Result: {result['output'][:200]}...")
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
Workflow Routing Example
|
||||
|
||||
Demonstrates decision-based routing where the workflow
|
||||
takes different paths based on the output of a decision step.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.workflows import route
|
||||
|
||||
# Decision maker - determines which route to take
|
||||
def classify_request(ctx: WorkflowContext) -> StepResult:
|
||||
"""Classify the input and decide the route."""
|
||||
input_lower = ctx.input.lower()
|
||||
|
||||
if "urgent" in input_lower or "emergency" in input_lower:
|
||||
return StepResult(output="priority: high")
|
||||
elif "question" in input_lower or "help" in input_lower:
|
||||
return StepResult(output="priority: support")
|
||||
else:
|
||||
return StepResult(output="priority: normal")
|
||||
|
||||
# Route handlers
|
||||
def handle_high_priority(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output="🚨 HIGH PRIORITY: Escalating to senior team immediately!")
|
||||
|
||||
def handle_support(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output="💬 SUPPORT: Routing to help desk for assistance.")
|
||||
|
||||
def handle_normal(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output="📋 NORMAL: Added to standard processing queue.")
|
||||
|
||||
# Create workflow with routing
|
||||
workflow = Workflow(
|
||||
name="Request Router",
|
||||
steps=[
|
||||
classify_request,
|
||||
route({
|
||||
"high": [handle_high_priority],
|
||||
"support": [handle_support],
|
||||
"normal": [handle_normal],
|
||||
"default": [handle_normal]
|
||||
})
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test different inputs
|
||||
print("=== Testing Routing Workflow ===\n")
|
||||
|
||||
# Test 1: Urgent request
|
||||
result = workflow.start("This is an URGENT matter!", verbose=True)
|
||||
print(f"Result: {result['output']}\n")
|
||||
|
||||
# Test 2: Support request
|
||||
result = workflow.start("I have a question about my account", verbose=True)
|
||||
print(f"Result: {result['output']}\n")
|
||||
|
||||
# Test 3: Normal request
|
||||
result = workflow.start("Please process my order", verbose=True)
|
||||
print(f"Result: {result['output']}")
|
||||
@@ -0,0 +1,117 @@
|
||||
# Extended agents.yaml with Workflow Patterns
|
||||
# This file demonstrates the extended agents.yaml format that supports
|
||||
# advanced workflow patterns while maintaining backward compatibility.
|
||||
#
|
||||
# Usage: praisonai agents_workflow.yaml
|
||||
#
|
||||
# Key differences from standard agents.yaml:
|
||||
# - process: workflow (triggers workflow mode)
|
||||
# - workflow: section for planning, reasoning, memory
|
||||
# - steps: section for workflow patterns (route, parallel, loop, repeat)
|
||||
|
||||
framework: praisonai
|
||||
process: workflow
|
||||
topic: "Research AI trends and create a comprehensive report"
|
||||
|
||||
# Workflow configuration (new)
|
||||
workflow:
|
||||
planning: true
|
||||
reasoning: true
|
||||
verbose: true
|
||||
memory_config:
|
||||
provider: chroma
|
||||
persist: true
|
||||
|
||||
# Variables for template substitution
|
||||
variables:
|
||||
topic: AI trends
|
||||
topics:
|
||||
- Machine Learning
|
||||
- Neural Networks
|
||||
- Natural Language Processing
|
||||
|
||||
# Agent definitions using familiar 'roles' format
|
||||
roles:
|
||||
classifier:
|
||||
role: Request Classifier
|
||||
backstory: |
|
||||
You are an expert at classifying requests into categories.
|
||||
Respond with ONLY one word: 'technical', 'creative', or 'research'.
|
||||
goal: Classify incoming requests accurately
|
||||
|
||||
researcher:
|
||||
role: Research Analyst
|
||||
backstory: |
|
||||
You are a skilled research analyst with expertise in technology trends.
|
||||
You provide concise, factual research findings with citations.
|
||||
goal: Research topics thoroughly and provide insights
|
||||
tools:
|
||||
- tavily_search
|
||||
llm:
|
||||
model: gpt-4o-mini
|
||||
|
||||
tech_expert:
|
||||
role: Technical Expert
|
||||
backstory: |
|
||||
You are a technical expert with deep knowledge in AI and ML.
|
||||
You provide detailed technical explanations.
|
||||
goal: Handle technical questions with expertise
|
||||
|
||||
creative_writer:
|
||||
role: Creative Writer
|
||||
backstory: |
|
||||
You are a creative writer who can make complex topics engaging.
|
||||
You write in a clear, accessible style.
|
||||
goal: Create engaging content from research
|
||||
|
||||
market_analyst:
|
||||
role: Market Analyst
|
||||
backstory: |
|
||||
You analyze market trends and competitive landscapes.
|
||||
You provide actionable business insights.
|
||||
goal: Analyze market trends and opportunities
|
||||
|
||||
aggregator:
|
||||
role: Synthesizer
|
||||
backstory: |
|
||||
You combine findings from multiple sources into coherent reports.
|
||||
You identify patterns and key insights.
|
||||
goal: Synthesize all findings into comprehensive reports
|
||||
|
||||
# Workflow steps with patterns (new)
|
||||
steps:
|
||||
# Step 1: Classify the request
|
||||
- agent: classifier
|
||||
action: "Classify this request: {{topic}}"
|
||||
|
||||
# Step 2: Route to appropriate handler based on classification
|
||||
- name: routing
|
||||
route:
|
||||
technical: [tech_expert]
|
||||
creative: [creative_writer]
|
||||
default: [researcher]
|
||||
|
||||
# Step 3: Parallel research on multiple aspects
|
||||
- name: parallel_research
|
||||
parallel:
|
||||
- agent: researcher
|
||||
action: "Research technology trends for {{topic}}"
|
||||
- agent: market_analyst
|
||||
action: "Analyze market opportunities for {{topic}}"
|
||||
|
||||
# Step 4: Loop over specific topics
|
||||
- agent: researcher
|
||||
action: "Deep dive into {{item}}"
|
||||
loop:
|
||||
over: topics
|
||||
|
||||
# Step 5: Aggregate all findings
|
||||
- agent: aggregator
|
||||
action: "Synthesize all research findings into a comprehensive report"
|
||||
repeat:
|
||||
until: "comprehensive"
|
||||
max_iterations: 2
|
||||
|
||||
# Optional callbacks
|
||||
callbacks:
|
||||
on_step_complete: log_step
|
||||
@@ -0,0 +1,116 @@
|
||||
# Complete Workflow Example
|
||||
# Demonstrates all workflow patterns in a single comprehensive workflow
|
||||
|
||||
name: Complete Research Workflow
|
||||
description: A comprehensive workflow demonstrating all patterns - route, parallel, loop, repeat
|
||||
framework: praisonai
|
||||
|
||||
workflow:
|
||||
planning: true
|
||||
reasoning: true
|
||||
verbose: true
|
||||
memory_config:
|
||||
provider: chroma
|
||||
persist: true
|
||||
|
||||
variables:
|
||||
topic: AI trends
|
||||
topics:
|
||||
- Machine Learning
|
||||
- Neural Networks
|
||||
- Natural Language Processing
|
||||
|
||||
agents:
|
||||
classifier:
|
||||
name: Classifier
|
||||
role: Request Classifier
|
||||
goal: Classify incoming requests
|
||||
instructions: |
|
||||
Classify the request. Respond with ONLY one word:
|
||||
- 'technical' for technical questions
|
||||
- 'creative' for creative requests
|
||||
- 'research' for research tasks
|
||||
|
||||
researcher:
|
||||
name: Researcher
|
||||
role: Research Analyst
|
||||
goal: Research topics thoroughly
|
||||
instructions: "You are a research analyst. Provide concise, factual research findings."
|
||||
|
||||
tech_expert:
|
||||
name: TechExpert
|
||||
role: Technical Expert
|
||||
goal: Handle technical questions
|
||||
instructions: "You are a technical expert. Provide detailed technical answers."
|
||||
|
||||
creative_writer:
|
||||
name: CreativeWriter
|
||||
role: Creative Writer
|
||||
goal: Handle creative requests
|
||||
instructions: "You are a creative writer. Write engaging, creative content."
|
||||
|
||||
market_analyst:
|
||||
name: MarketAnalyst
|
||||
role: Market Analyst
|
||||
goal: Analyze market trends
|
||||
instructions: "You are a market analyst. Provide market insights."
|
||||
|
||||
competitor_analyst:
|
||||
name: CompetitorAnalyst
|
||||
role: Competitor Analyst
|
||||
goal: Analyze competitors
|
||||
instructions: "You are a competitor analyst. Provide competitive insights."
|
||||
|
||||
aggregator:
|
||||
name: Aggregator
|
||||
role: Synthesizer
|
||||
goal: Combine and synthesize findings
|
||||
instructions: "You are a synthesizer. Combine all findings into a comprehensive report."
|
||||
|
||||
evaluator:
|
||||
name: Evaluator
|
||||
role: Quality Evaluator
|
||||
goal: Evaluate content quality
|
||||
instructions: |
|
||||
Evaluate the content. If it's comprehensive and well-structured, respond with "APPROVED".
|
||||
Otherwise, provide specific feedback for improvement.
|
||||
|
||||
steps:
|
||||
# Step 1: Classify the request
|
||||
- agent: classifier
|
||||
action: "Classify: {{input}}"
|
||||
|
||||
# Step 2: Route to appropriate handler
|
||||
- name: routing
|
||||
route:
|
||||
technical: [tech_expert]
|
||||
creative: [creative_writer]
|
||||
default: [researcher]
|
||||
|
||||
# Step 3: Parallel research
|
||||
- name: parallel_research
|
||||
parallel:
|
||||
- agent: market_analyst
|
||||
action: "Analyze market for {{topic}}"
|
||||
- agent: competitor_analyst
|
||||
action: "Analyze competitors for {{topic}}"
|
||||
|
||||
# Step 4: Loop over topics
|
||||
- agent: researcher
|
||||
action: "Research {{item}}"
|
||||
loop:
|
||||
over: topics
|
||||
|
||||
# Step 5: Aggregate findings
|
||||
- agent: aggregator
|
||||
action: "Synthesize all findings into a comprehensive report"
|
||||
|
||||
# Step 6: Evaluator-optimizer
|
||||
- agent: evaluator
|
||||
action: "Evaluate the report quality"
|
||||
repeat:
|
||||
until: "approved"
|
||||
max_iterations: 2
|
||||
|
||||
callbacks:
|
||||
on_step_complete: log_step
|
||||
@@ -0,0 +1,43 @@
|
||||
# Evaluator-Optimizer Workflow
|
||||
# Demonstrates the repeat() pattern for iterative improvement
|
||||
|
||||
name: Evaluator Optimizer Workflow
|
||||
description: A generator creates content, an evaluator reviews it repeatedly until quality standards are met
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
agents:
|
||||
generator:
|
||||
name: Generator
|
||||
role: Content Generator
|
||||
goal: Generate high-quality content
|
||||
instructions: |
|
||||
You are a content generator. Generate content based on the request.
|
||||
If feedback is provided, improve your content based on that feedback.
|
||||
Always strive for clarity, accuracy, and engagement.
|
||||
|
||||
evaluator:
|
||||
name: Evaluator
|
||||
role: Content Evaluator
|
||||
goal: Evaluate content quality and provide feedback
|
||||
instructions: |
|
||||
You are a content evaluator. Review the content provided.
|
||||
|
||||
If the content is high quality (clear, accurate, engaging), respond with:
|
||||
"APPROVED: [brief reason]"
|
||||
|
||||
If the content needs improvement, respond with:
|
||||
"NEEDS IMPROVEMENT: [specific feedback for improvement]"
|
||||
|
||||
Be constructive and specific in your feedback.
|
||||
|
||||
steps:
|
||||
- agent: generator
|
||||
action: "Generate content for: {{input}}"
|
||||
|
||||
- agent: evaluator
|
||||
action: "Evaluate this content and provide feedback: {{previous_output}}"
|
||||
repeat:
|
||||
until: "approved"
|
||||
max_iterations: 3
|
||||
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Example usage of YAML Workflow Parser.
|
||||
|
||||
This script demonstrates how to load and execute YAML workflow files.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure OPENAI_API_KEY is set
|
||||
if not os.getenv("OPENAI_API_KEY"):
|
||||
print("Please set OPENAI_API_KEY environment variable")
|
||||
print("export OPENAI_API_KEY='your-api-key'")
|
||||
exit(1)
|
||||
|
||||
from praisonaiagents.workflows import YAMLWorkflowParser, WorkflowManager
|
||||
|
||||
|
||||
def example_1_parse_and_run():
|
||||
"""Example 1: Parse YAML and run workflow directly."""
|
||||
print("=" * 60)
|
||||
print("Example 1: Parse YAML and run workflow")
|
||||
print("=" * 60)
|
||||
|
||||
yaml_content = """
|
||||
name: Quick Research
|
||||
description: A quick research workflow
|
||||
|
||||
agents:
|
||||
researcher:
|
||||
name: Researcher
|
||||
role: Research Analyst
|
||||
goal: Research topics
|
||||
instructions: "Provide brief, factual information."
|
||||
|
||||
steps:
|
||||
- agent: researcher
|
||||
action: "Research: {{input}}"
|
||||
"""
|
||||
|
||||
parser = YAMLWorkflowParser()
|
||||
workflow = parser.parse_string(yaml_content)
|
||||
|
||||
print(f"Workflow Name: {workflow.name}")
|
||||
print(f"Steps: {len(workflow.steps)}")
|
||||
|
||||
# Run the workflow
|
||||
result = workflow.start("What is Python?")
|
||||
print(f"Result: {result['output'][:200]}...")
|
||||
print()
|
||||
|
||||
|
||||
def example_2_load_yaml_file():
|
||||
"""Example 2: Load workflow from YAML file using WorkflowManager."""
|
||||
print("=" * 60)
|
||||
print("Example 2: Load YAML file with WorkflowManager")
|
||||
print("=" * 60)
|
||||
|
||||
# Get the directory of this script
|
||||
script_dir = Path(__file__).parent
|
||||
yaml_file = script_dir / "simple_workflow.yaml"
|
||||
|
||||
if not yaml_file.exists():
|
||||
print(f"YAML file not found: {yaml_file}")
|
||||
return
|
||||
|
||||
manager = WorkflowManager()
|
||||
workflow = manager.load_yaml(yaml_file)
|
||||
|
||||
print(f"Loaded workflow: {workflow.name}")
|
||||
print(f"Steps: {len(workflow.steps)}")
|
||||
|
||||
# Run the workflow
|
||||
result = workflow.start("Benefits of renewable energy")
|
||||
print(f"Result: {result['output'][:200]}...")
|
||||
print()
|
||||
|
||||
|
||||
def example_3_execute_yaml_directly():
|
||||
"""Example 3: Execute YAML file directly with WorkflowManager."""
|
||||
print("=" * 60)
|
||||
print("Example 3: Execute YAML directly")
|
||||
print("=" * 60)
|
||||
|
||||
script_dir = Path(__file__).parent
|
||||
yaml_file = script_dir / "simple_workflow.yaml"
|
||||
|
||||
if not yaml_file.exists():
|
||||
print(f"YAML file not found: {yaml_file}")
|
||||
return
|
||||
|
||||
manager = WorkflowManager()
|
||||
result = manager.execute_yaml(
|
||||
yaml_file,
|
||||
input_data="The future of AI",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
print(f"Status: {result['status']}")
|
||||
print(f"Result: {result['output'][:200]}...")
|
||||
print()
|
||||
|
||||
|
||||
def example_4_with_variables():
|
||||
"""Example 4: YAML workflow with variables."""
|
||||
print("=" * 60)
|
||||
print("Example 4: Workflow with variables")
|
||||
print("=" * 60)
|
||||
|
||||
yaml_content = """
|
||||
name: Variable Workflow
|
||||
variables:
|
||||
topic: Machine Learning
|
||||
style: concise
|
||||
|
||||
agents:
|
||||
writer:
|
||||
name: Writer
|
||||
role: Content Writer
|
||||
goal: Write content
|
||||
instructions: "Write {{style}} content about the topic."
|
||||
|
||||
steps:
|
||||
- agent: writer
|
||||
action: "Write about {{topic}}"
|
||||
"""
|
||||
|
||||
parser = YAMLWorkflowParser()
|
||||
workflow = parser.parse_string(yaml_content)
|
||||
|
||||
print(f"Variables: {workflow.variables}")
|
||||
|
||||
# Run with default variables
|
||||
result = workflow.start("")
|
||||
print(f"Result: {result['output'][:200]}...")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("YAML Workflow Examples")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Run examples
|
||||
example_1_parse_and_run()
|
||||
example_2_load_yaml_file()
|
||||
example_3_execute_yaml_directly()
|
||||
example_4_with_variables()
|
||||
|
||||
print("=" * 60)
|
||||
print("All examples completed!")
|
||||
print("=" * 60)
|
||||
@@ -0,0 +1,42 @@
|
||||
# Loop Workflow
|
||||
# Demonstrates the loop() pattern for iterating over items
|
||||
|
||||
name: Repetitive Processing Workflow
|
||||
description: An agent processes multiple items in a loop, then a summarizer combines results
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
variables:
|
||||
topics:
|
||||
- Artificial Intelligence
|
||||
- Machine Learning
|
||||
- Neural Networks
|
||||
- Natural Language Processing
|
||||
|
||||
agents:
|
||||
processor:
|
||||
name: Processor
|
||||
role: Topic Processor
|
||||
goal: Process and analyze each topic thoroughly
|
||||
instructions: |
|
||||
You are a topic processor. For each topic given, provide:
|
||||
1. A brief definition
|
||||
2. Key concepts
|
||||
3. Real-world applications
|
||||
Keep your response concise but informative.
|
||||
|
||||
summarizer:
|
||||
name: Summarizer
|
||||
role: Results Summarizer
|
||||
goal: Summarize all processed results into a final report
|
||||
instructions: "You are a summarizer. Combine all the processed topics into a cohesive final report."
|
||||
|
||||
steps:
|
||||
- agent: processor
|
||||
action: "Analyze this topic: {{item}}"
|
||||
loop:
|
||||
over: topics
|
||||
|
||||
- agent: summarizer
|
||||
action: "Create a comprehensive summary of all the analyzed topics"
|
||||
@@ -0,0 +1,46 @@
|
||||
# Parallel Workflow
|
||||
# Demonstrates the parallel() pattern for concurrent execution
|
||||
|
||||
name: Parallel Research Workflow
|
||||
description: Multiple agents research different aspects concurrently, then an aggregator combines results
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
agents:
|
||||
market_researcher:
|
||||
name: MarketResearcher
|
||||
role: Market Research Analyst
|
||||
goal: Research market trends and opportunities
|
||||
instructions: "You are a market research analyst. Analyze market trends and provide concise insights."
|
||||
|
||||
competitor_researcher:
|
||||
name: CompetitorResearcher
|
||||
role: Competitive Intelligence Analyst
|
||||
goal: Research competitor strategies and positioning
|
||||
instructions: "You are a competitive intelligence analyst. Analyze competitors and provide key insights."
|
||||
|
||||
customer_researcher:
|
||||
name: CustomerResearcher
|
||||
role: Customer Research Analyst
|
||||
goal: Research customer needs and behaviors
|
||||
instructions: "You are a customer research analyst. Analyze customer segments and provide insights."
|
||||
|
||||
aggregator:
|
||||
name: Aggregator
|
||||
role: Research Synthesizer
|
||||
goal: Synthesize all research findings into a comprehensive summary
|
||||
instructions: "You are a research synthesizer. Combine all research findings into a clear, comprehensive summary."
|
||||
|
||||
steps:
|
||||
- name: parallel_research
|
||||
parallel:
|
||||
- agent: market_researcher
|
||||
action: "Research market trends for: {{input}}"
|
||||
- agent: competitor_researcher
|
||||
action: "Research competitors for: {{input}}"
|
||||
- agent: customer_researcher
|
||||
action: "Research customer needs for: {{input}}"
|
||||
|
||||
- agent: aggregator
|
||||
action: "Synthesize all the research findings into a comprehensive report"
|
||||
@@ -0,0 +1,49 @@
|
||||
# Routing Workflow
|
||||
# Demonstrates the route() pattern for decision-based branching
|
||||
|
||||
name: Agentic Routing Workflow
|
||||
description: A classifier agent routes requests to specialized handler agents
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
agents:
|
||||
classifier:
|
||||
name: Classifier
|
||||
role: Request Classifier
|
||||
goal: Classify incoming requests by type
|
||||
instructions: |
|
||||
You are a request classifier. Analyze the request and respond with ONLY one word:
|
||||
- 'technical' for technical/coding questions
|
||||
- 'creative' for creative writing requests
|
||||
- 'general' for everything else
|
||||
|
||||
Respond with ONLY the classification word, nothing else.
|
||||
|
||||
tech_expert:
|
||||
name: TechExpert
|
||||
role: Technical Expert
|
||||
goal: Answer technical questions with expertise
|
||||
instructions: "You are a technical expert. Provide detailed, accurate technical answers."
|
||||
|
||||
creative_writer:
|
||||
name: CreativeWriter
|
||||
role: Creative Writer
|
||||
goal: Create engaging creative content
|
||||
instructions: "You are a creative writer. Write engaging, imaginative content."
|
||||
|
||||
general_assistant:
|
||||
name: GeneralAssistant
|
||||
role: General Assistant
|
||||
goal: Help with general requests
|
||||
instructions: "You are a helpful assistant. Provide clear, helpful responses."
|
||||
|
||||
steps:
|
||||
- agent: classifier
|
||||
action: "Classify this request: {{input}}"
|
||||
|
||||
- name: routing
|
||||
route:
|
||||
technical: [tech_expert]
|
||||
creative: [creative_writer]
|
||||
default: [general_assistant]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Simple Sequential Workflow
|
||||
# Demonstrates basic agent-to-agent workflow
|
||||
|
||||
name: Simple Research Workflow
|
||||
description: A simple workflow where a researcher gathers info and a writer summarizes it
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
agents:
|
||||
researcher:
|
||||
name: Researcher
|
||||
role: Research Analyst
|
||||
goal: Research and gather information about topics
|
||||
instructions: "You are a research analyst. Provide concise, factual information about the given topic."
|
||||
|
||||
writer:
|
||||
name: Writer
|
||||
role: Content Writer
|
||||
goal: Write clear and engaging summaries
|
||||
instructions: "You are a content writer. Take the research provided and write a clear, engaging summary."
|
||||
|
||||
steps:
|
||||
- agent: researcher
|
||||
action: "Research the following topic: {{input}}"
|
||||
|
||||
- agent: writer
|
||||
action: "Write a summary based on this research: {{previous_output}}"
|
||||
@@ -54,10 +54,12 @@ except ImportError:
|
||||
MCP = None
|
||||
from .session import Session
|
||||
from .memory.memory import Memory
|
||||
from .memory.workflows import (
|
||||
# Workflows - import from dedicated workflows module
|
||||
from .workflows import (
|
||||
Workflow, WorkflowStep, WorkflowContext, StepResult,
|
||||
Route, Parallel, Loop, Repeat,
|
||||
route, parallel, loop, repeat
|
||||
route, parallel, loop, repeat,
|
||||
Pipeline # Alias for Workflow
|
||||
)
|
||||
from .guardrails import GuardrailResult, LLMGuardrail
|
||||
|
||||
|
||||
@@ -48,30 +48,58 @@ def __getattr__(name):
|
||||
if name == "create_auto_memory":
|
||||
from .auto_memory import create_auto_memory
|
||||
return create_auto_memory
|
||||
# Workflows - now in dedicated module, re-export for backward compatibility
|
||||
if name == "WorkflowManager":
|
||||
from .workflows import WorkflowManager
|
||||
from ..workflows import WorkflowManager
|
||||
return WorkflowManager
|
||||
if name == "Workflow":
|
||||
from .workflows import Workflow
|
||||
from ..workflows import Workflow
|
||||
return Workflow
|
||||
if name == "WorkflowStep":
|
||||
from .workflows import WorkflowStep
|
||||
from ..workflows import WorkflowStep
|
||||
return WorkflowStep
|
||||
if name == "WorkflowContext":
|
||||
from .workflows import WorkflowContext
|
||||
from ..workflows import WorkflowContext
|
||||
return WorkflowContext
|
||||
if name == "StepResult":
|
||||
from .workflows import StepResult
|
||||
from ..workflows import StepResult
|
||||
return StepResult
|
||||
if name == "Pipeline":
|
||||
from ..workflows import Pipeline
|
||||
return Pipeline
|
||||
if name == "Route":
|
||||
from ..workflows import Route
|
||||
return Route
|
||||
if name == "Parallel":
|
||||
from ..workflows import Parallel
|
||||
return Parallel
|
||||
if name == "Loop":
|
||||
from ..workflows import Loop
|
||||
return Loop
|
||||
if name == "Repeat":
|
||||
from ..workflows import Repeat
|
||||
return Repeat
|
||||
if name == "route":
|
||||
from ..workflows import route
|
||||
return route
|
||||
if name == "parallel":
|
||||
from ..workflows import parallel
|
||||
return parallel
|
||||
if name == "loop":
|
||||
from ..workflows import loop
|
||||
return loop
|
||||
if name == "repeat":
|
||||
from ..workflows import repeat
|
||||
return repeat
|
||||
# Backward compatibility aliases
|
||||
if name == "StepInput":
|
||||
from .workflows import WorkflowContext
|
||||
from ..workflows import WorkflowContext
|
||||
return WorkflowContext
|
||||
if name == "StepOutput":
|
||||
from .workflows import StepResult
|
||||
from ..workflows import StepResult
|
||||
return StepResult
|
||||
if name == "create_workflow_manager":
|
||||
from .workflows import create_workflow_manager
|
||||
from ..workflows.workflows import create_workflow_manager
|
||||
return create_workflow_manager
|
||||
if name == "HooksManager":
|
||||
from .hooks import HooksManager
|
||||
@@ -98,13 +126,23 @@ __all__ = [
|
||||
"AutoMemory",
|
||||
"AutoMemoryExtractor",
|
||||
"create_auto_memory",
|
||||
# Workflows
|
||||
# Workflows (re-exported from praisonaiagents.workflows for backward compatibility)
|
||||
"WorkflowManager",
|
||||
"Workflow",
|
||||
"Pipeline",
|
||||
"WorkflowStep",
|
||||
"WorkflowContext",
|
||||
"StepResult",
|
||||
"create_workflow_manager",
|
||||
# Workflow patterns
|
||||
"Route",
|
||||
"Parallel",
|
||||
"Loop",
|
||||
"Repeat",
|
||||
"route",
|
||||
"parallel",
|
||||
"loop",
|
||||
"repeat",
|
||||
# Hooks
|
||||
"HooksManager",
|
||||
"HookResult",
|
||||
|
||||
@@ -779,6 +779,10 @@ class Workflow:
|
||||
return self.run(input, **kwargs)
|
||||
|
||||
|
||||
# Alias: Pipeline = Workflow (they are the same concept)
|
||||
Pipeline = Workflow
|
||||
|
||||
|
||||
class WorkflowManager:
|
||||
"""
|
||||
Manages workflow discovery, loading, and execution.
|
||||
|
||||
@@ -826,7 +826,34 @@ Provide a JSON with the structure:
|
||||
logging.info("Hierarchical task execution finished")
|
||||
|
||||
def workflow(self):
|
||||
"""Synchronous version of workflow method"""
|
||||
"""Synchronous version of workflow method.
|
||||
|
||||
DEPRECATED: This method is deprecated. Use the Workflow class instead:
|
||||
|
||||
```python
|
||||
from praisonaiagents import Workflow, StepResult
|
||||
from praisonaiagents.workflows import route, parallel, loop, repeat
|
||||
|
||||
workflow = Workflow(steps=[step1, step2])
|
||||
result = workflow.start("input")
|
||||
```
|
||||
|
||||
The Workflow class provides a simpler API with more features:
|
||||
- route() for decision-based branching
|
||||
- parallel() for concurrent execution
|
||||
- loop() for iteration over lists/CSV
|
||||
- repeat() for evaluator-optimizer patterns
|
||||
- Callbacks (on_step_start, on_step_complete, etc.)
|
||||
- Guardrails with validation feedback
|
||||
- Status tracking
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"process='workflow' is deprecated. Use the Workflow class instead: "
|
||||
"from praisonaiagents import Workflow; workflow = Workflow(steps=[...]); workflow.start()",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
current_iter = 0 # Track how many times we've looped
|
||||
# Build workflow relationships first
|
||||
for task in self.tasks.values():
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Workflows module for PraisonAI Agents.
|
||||
|
||||
Provides workflow/pipeline patterns for orchestrating agents and functions.
|
||||
"""
|
||||
|
||||
from .workflows import (
|
||||
# Core classes
|
||||
Workflow,
|
||||
Pipeline, # Alias for Workflow
|
||||
WorkflowStep,
|
||||
WorkflowContext,
|
||||
StepResult,
|
||||
WorkflowManager,
|
||||
|
||||
# Pattern helpers
|
||||
Route,
|
||||
Parallel,
|
||||
Loop,
|
||||
Repeat,
|
||||
|
||||
# Convenience functions
|
||||
route,
|
||||
parallel,
|
||||
loop,
|
||||
repeat,
|
||||
|
||||
# Backward compatibility aliases
|
||||
StepInput,
|
||||
StepOutput,
|
||||
)
|
||||
|
||||
from .yaml_parser import YAMLWorkflowParser
|
||||
|
||||
__all__ = [
|
||||
# Core
|
||||
"Workflow",
|
||||
"Pipeline",
|
||||
"WorkflowStep",
|
||||
"WorkflowContext",
|
||||
"StepResult",
|
||||
"WorkflowManager",
|
||||
|
||||
# YAML Parser
|
||||
"YAMLWorkflowParser",
|
||||
|
||||
# Patterns
|
||||
"Route",
|
||||
"Parallel",
|
||||
"Loop",
|
||||
"Repeat",
|
||||
"route",
|
||||
"parallel",
|
||||
"loop",
|
||||
"repeat",
|
||||
|
||||
# Aliases
|
||||
"StepInput",
|
||||
"StepOutput",
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,641 @@
|
||||
"""
|
||||
YAML Workflow Parser for PraisonAI Agents.
|
||||
|
||||
Parses YAML workflow files into Workflow objects with support for:
|
||||
- Agent definitions with planning, reasoning, tools
|
||||
- Workflow patterns: route(), parallel(), loop(), repeat()
|
||||
- Variables and template substitution
|
||||
- Callbacks and guardrails
|
||||
- Memory configuration
|
||||
"""
|
||||
|
||||
import yaml
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
from pathlib import Path
|
||||
|
||||
from ..agent.agent import Agent
|
||||
from .workflows import Workflow, WorkflowStep, route, parallel, loop, repeat
|
||||
|
||||
|
||||
class YAMLWorkflowParser:
|
||||
"""
|
||||
Parser for YAML workflow files.
|
||||
|
||||
Converts YAML workflow definitions into Workflow objects that can be executed.
|
||||
|
||||
Example YAML format:
|
||||
```yaml
|
||||
name: My Workflow
|
||||
description: A multi-agent workflow
|
||||
|
||||
workflow:
|
||||
planning: true
|
||||
reasoning: true
|
||||
verbose: true
|
||||
memory_config:
|
||||
provider: chroma
|
||||
persist: true
|
||||
|
||||
variables:
|
||||
topic: AI trends
|
||||
|
||||
agents:
|
||||
researcher:
|
||||
name: Researcher
|
||||
role: Research Analyst
|
||||
goal: Research topics
|
||||
instructions: "Provide research findings"
|
||||
tools:
|
||||
- tavily_search
|
||||
|
||||
steps:
|
||||
- agent: researcher
|
||||
action: "Research {{topic}}"
|
||||
- name: routing
|
||||
route:
|
||||
technical: [tech_agent]
|
||||
default: [general_agent]
|
||||
|
||||
callbacks:
|
||||
on_step_complete: log_step
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, tool_registry: Optional[Dict[str, Callable]] = None):
|
||||
"""
|
||||
Initialize the YAML workflow parser.
|
||||
|
||||
Args:
|
||||
tool_registry: Optional dictionary mapping tool names to callable functions
|
||||
"""
|
||||
self.tool_registry = tool_registry or {}
|
||||
self._agents: Dict[str, Agent] = {}
|
||||
self._callbacks: Dict[str, Callable] = {}
|
||||
|
||||
def parse_file(self, file_path: Union[str, Path]) -> Workflow:
|
||||
"""
|
||||
Parse a YAML workflow file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the YAML file
|
||||
|
||||
Returns:
|
||||
Workflow object ready for execution
|
||||
"""
|
||||
file_path = Path(file_path)
|
||||
if not file_path.exists():
|
||||
raise FileNotFoundError(f"Workflow file not found: {file_path}")
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
yaml_content = f.read()
|
||||
|
||||
return self.parse_string(yaml_content)
|
||||
|
||||
def parse_string(self, yaml_content: str) -> Workflow:
|
||||
"""
|
||||
Parse a YAML workflow string.
|
||||
|
||||
Args:
|
||||
yaml_content: YAML content as string
|
||||
|
||||
Returns:
|
||||
Workflow object ready for execution
|
||||
"""
|
||||
data = yaml.safe_load(yaml_content)
|
||||
return self._parse_workflow_data(data)
|
||||
|
||||
def _parse_workflow_data(self, data: Dict[str, Any]) -> Workflow:
|
||||
"""
|
||||
Parse workflow data dictionary into a Workflow object.
|
||||
|
||||
Args:
|
||||
data: Parsed YAML data dictionary
|
||||
|
||||
Returns:
|
||||
Workflow object
|
||||
"""
|
||||
# Extract workflow metadata
|
||||
name = data.get('name', 'Unnamed Workflow')
|
||||
description = data.get('description', '')
|
||||
|
||||
# Extract framework and process (for feature parity with agents.yaml)
|
||||
framework = data.get('framework', 'praisonai')
|
||||
process = data.get('process', 'sequential')
|
||||
manager_llm = data.get('manager_llm')
|
||||
|
||||
# Parse workflow configuration
|
||||
workflow_config = data.get('workflow', {})
|
||||
planning = workflow_config.get('planning', False)
|
||||
planning_llm = workflow_config.get('planning_llm')
|
||||
reasoning = workflow_config.get('reasoning', False)
|
||||
verbose = workflow_config.get('verbose', False)
|
||||
memory_config = workflow_config.get('memory_config')
|
||||
|
||||
# Parse variables
|
||||
variables = data.get('variables', {})
|
||||
|
||||
# Parse agents - support both 'agents' and 'roles' keys for backward compatibility
|
||||
agents_data = data.get('agents', {})
|
||||
if not agents_data and 'roles' in data:
|
||||
# Convert roles format to agents format
|
||||
agents_data = self._convert_roles_to_agents(data['roles'])
|
||||
self._agents = self._parse_agents(agents_data)
|
||||
|
||||
# Parse callbacks
|
||||
callbacks_data = data.get('callbacks', {})
|
||||
self._parse_callbacks(callbacks_data)
|
||||
|
||||
# Parse steps
|
||||
steps_data = data.get('steps', [])
|
||||
steps = self._parse_steps(steps_data)
|
||||
|
||||
# Create workflow
|
||||
workflow = Workflow(
|
||||
name=name,
|
||||
steps=steps,
|
||||
variables=variables,
|
||||
planning=planning,
|
||||
planning_llm=planning_llm,
|
||||
reasoning=reasoning,
|
||||
verbose=verbose,
|
||||
memory_config=memory_config,
|
||||
on_workflow_start=self._callbacks.get('on_workflow_start'),
|
||||
on_step_complete=self._callbacks.get('on_step_complete'),
|
||||
on_workflow_complete=self._callbacks.get('on_workflow_complete'),
|
||||
)
|
||||
|
||||
# Store additional attributes for feature parity with agents.yaml
|
||||
workflow.description = description
|
||||
workflow.framework = framework
|
||||
workflow.process = process
|
||||
workflow.manager_llm = manager_llm
|
||||
|
||||
return workflow
|
||||
|
||||
def _convert_roles_to_agents(self, roles: Dict[str, Dict]) -> Dict[str, Dict]:
|
||||
"""
|
||||
Convert agents.yaml 'roles' format to workflow 'agents' format.
|
||||
|
||||
This enables backward compatibility with the existing agents.yaml format.
|
||||
|
||||
Mapping:
|
||||
- backstory -> instructions
|
||||
- role -> role
|
||||
- goal -> goal
|
||||
- tools -> tools
|
||||
- llm -> llm
|
||||
- tasks -> (used to auto-generate steps if no steps defined)
|
||||
|
||||
Args:
|
||||
roles: The roles section from agents.yaml
|
||||
|
||||
Returns:
|
||||
Converted agents dictionary in workflow format
|
||||
"""
|
||||
agents = {}
|
||||
for role_id, role_config in roles.items():
|
||||
agent = {
|
||||
'name': role_config.get('role', role_id),
|
||||
'role': role_config.get('role', role_id),
|
||||
'goal': role_config.get('goal', ''),
|
||||
'instructions': role_config.get('backstory', ''),
|
||||
}
|
||||
|
||||
# Copy optional fields
|
||||
if 'llm' in role_config:
|
||||
llm_config = role_config['llm']
|
||||
if isinstance(llm_config, dict):
|
||||
agent['llm'] = llm_config.get('model', 'gpt-4o-mini')
|
||||
else:
|
||||
agent['llm'] = llm_config
|
||||
|
||||
if 'tools' in role_config:
|
||||
agent['tools'] = [t for t in role_config['tools'] if t]
|
||||
|
||||
if 'verbose' in role_config:
|
||||
agent['verbose'] = role_config['verbose']
|
||||
|
||||
if 'max_iter' in role_config:
|
||||
agent['max_iter'] = role_config['max_iter']
|
||||
|
||||
if 'planning' in role_config:
|
||||
agent['planning'] = role_config['planning']
|
||||
|
||||
if 'reasoning' in role_config:
|
||||
agent['reasoning'] = role_config['reasoning']
|
||||
|
||||
agents[role_id] = agent
|
||||
|
||||
return agents
|
||||
|
||||
def _parse_agents(self, agents_data: Dict[str, Dict]) -> Dict[str, Agent]:
|
||||
"""
|
||||
Parse agent definitions from YAML.
|
||||
|
||||
Args:
|
||||
agents_data: Dictionary of agent definitions
|
||||
|
||||
Returns:
|
||||
Dictionary mapping agent names to Agent objects
|
||||
"""
|
||||
agents = {}
|
||||
|
||||
for agent_id, agent_config in agents_data.items():
|
||||
agent = self._create_agent(agent_id, agent_config)
|
||||
agents[agent_id] = agent
|
||||
|
||||
return agents
|
||||
|
||||
def _create_agent(self, agent_id: str, config: Dict[str, Any]) -> Agent:
|
||||
"""
|
||||
Create an Agent from configuration.
|
||||
|
||||
Args:
|
||||
agent_id: Identifier for the agent
|
||||
config: Agent configuration dictionary
|
||||
|
||||
Returns:
|
||||
Agent object
|
||||
"""
|
||||
# Extract agent parameters
|
||||
name = config.get('name', agent_id)
|
||||
role = config.get('role', 'Assistant')
|
||||
goal = config.get('goal', '')
|
||||
instructions = config.get('instructions', '')
|
||||
backstory = config.get('backstory', '')
|
||||
|
||||
# LLM configuration
|
||||
llm = config.get('llm')
|
||||
|
||||
# Tools
|
||||
tools_config = config.get('tools', [])
|
||||
tools = self._resolve_tools(tools_config)
|
||||
|
||||
# Advanced parameters (feature parity with agents.yaml)
|
||||
planning = config.get('planning', False)
|
||||
reasoning = config.get('reasoning', False)
|
||||
verbose = config.get('verbose', False)
|
||||
allow_delegation = config.get('allow_delegation', False)
|
||||
max_iter = config.get('max_iter')
|
||||
cache = config.get('cache', True)
|
||||
|
||||
# Additional agents.yaml fields
|
||||
function_calling_llm = config.get('function_calling_llm')
|
||||
max_rpm = config.get('max_rpm')
|
||||
max_execution_time = config.get('max_execution_time')
|
||||
reflect_llm = config.get('reflect_llm')
|
||||
min_reflect = config.get('min_reflect')
|
||||
max_reflect = config.get('max_reflect')
|
||||
system_template = config.get('system_template')
|
||||
prompt_template = config.get('prompt_template')
|
||||
response_template = config.get('response_template')
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name=name,
|
||||
role=role,
|
||||
goal=goal,
|
||||
instructions=instructions,
|
||||
backstory=backstory,
|
||||
llm=llm,
|
||||
tools=tools if tools else None,
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
# Store additional attributes for later use
|
||||
agent._yaml_planning = planning
|
||||
agent._yaml_reasoning = reasoning
|
||||
agent._yaml_allow_delegation = allow_delegation
|
||||
agent._yaml_max_iter = max_iter
|
||||
agent._yaml_cache = cache
|
||||
|
||||
# Store additional agents.yaml fields for feature parity
|
||||
agent._yaml_function_calling_llm = function_calling_llm
|
||||
agent._yaml_max_rpm = max_rpm
|
||||
agent._yaml_max_execution_time = max_execution_time
|
||||
agent._yaml_reflect_llm = reflect_llm
|
||||
agent._yaml_min_reflect = min_reflect
|
||||
agent._yaml_max_reflect = max_reflect
|
||||
agent._yaml_system_template = system_template
|
||||
agent._yaml_prompt_template = prompt_template
|
||||
agent._yaml_response_template = response_template
|
||||
|
||||
return agent
|
||||
|
||||
def _resolve_tools(self, tools_config: List[str]) -> List[Callable]:
|
||||
"""
|
||||
Resolve tool names to callable functions.
|
||||
|
||||
Args:
|
||||
tools_config: List of tool names
|
||||
|
||||
Returns:
|
||||
List of callable tool functions
|
||||
"""
|
||||
tools = []
|
||||
for tool_name in tools_config:
|
||||
if tool_name in self.tool_registry:
|
||||
tools.append(self.tool_registry[tool_name])
|
||||
# If tool not in registry, skip it (will be resolved later or is a string reference)
|
||||
return tools
|
||||
|
||||
def _parse_callbacks(self, callbacks_data: Dict[str, str]) -> None:
|
||||
"""
|
||||
Parse callback definitions.
|
||||
|
||||
Args:
|
||||
callbacks_data: Dictionary of callback name to function name
|
||||
"""
|
||||
# For now, store callback names - they can be resolved later
|
||||
# In a full implementation, these would be resolved to actual functions
|
||||
for callback_name, func_name in callbacks_data.items():
|
||||
# Store as None for now - actual resolution happens at runtime
|
||||
self._callbacks[callback_name] = None
|
||||
|
||||
def _parse_steps(self, steps_data: List[Dict]) -> List:
|
||||
"""
|
||||
Parse workflow steps from YAML.
|
||||
|
||||
Args:
|
||||
steps_data: List of step definitions
|
||||
|
||||
Returns:
|
||||
List of workflow steps (Agent, WorkflowStep, or pattern objects)
|
||||
"""
|
||||
steps = []
|
||||
|
||||
for step_data in steps_data:
|
||||
step = self._parse_single_step(step_data)
|
||||
if step is not None:
|
||||
steps.append(step)
|
||||
|
||||
return steps
|
||||
|
||||
def _parse_single_step(self, step_data: Dict) -> Any:
|
||||
"""
|
||||
Parse a single step definition.
|
||||
|
||||
Args:
|
||||
step_data: Step definition dictionary
|
||||
|
||||
Returns:
|
||||
Step object (Agent, WorkflowStep, or pattern)
|
||||
"""
|
||||
# Check for pattern types
|
||||
if 'route' in step_data:
|
||||
return self._parse_route_step(step_data)
|
||||
elif 'parallel' in step_data:
|
||||
return self._parse_parallel_step(step_data)
|
||||
elif 'loop' in step_data:
|
||||
return self._parse_loop_step(step_data)
|
||||
elif 'repeat' in step_data:
|
||||
return self._parse_repeat_step(step_data)
|
||||
elif 'agent' in step_data:
|
||||
return self._parse_agent_step(step_data)
|
||||
else:
|
||||
# Generic step
|
||||
return self._parse_generic_step(step_data)
|
||||
|
||||
def _parse_agent_step(self, step_data: Dict) -> Agent:
|
||||
"""
|
||||
Parse an agent step.
|
||||
|
||||
Args:
|
||||
step_data: Step definition with 'agent' key
|
||||
|
||||
Returns:
|
||||
Agent object
|
||||
"""
|
||||
agent_id = step_data['agent']
|
||||
|
||||
if agent_id in self._agents:
|
||||
agent = self._agents[agent_id]
|
||||
|
||||
# Store step name if provided
|
||||
step_name = step_data.get('name')
|
||||
if step_name:
|
||||
agent._yaml_step_name = step_name
|
||||
|
||||
# If there's an action, we need to handle it
|
||||
action = step_data.get('action')
|
||||
if action:
|
||||
# Store action as task for the agent
|
||||
agent._yaml_action = action
|
||||
|
||||
# Handle guardrail
|
||||
guardrail = step_data.get('guardrail')
|
||||
if guardrail:
|
||||
agent._yaml_guardrail = guardrail
|
||||
|
||||
# Handle max_retries
|
||||
max_retries = step_data.get('max_retries')
|
||||
if max_retries:
|
||||
agent._yaml_max_retries = max_retries
|
||||
|
||||
# Handle expected_output (feature parity with agents.yaml tasks)
|
||||
expected_output = step_data.get('expected_output')
|
||||
if expected_output:
|
||||
agent._yaml_expected_output = expected_output
|
||||
|
||||
# Handle context/dependencies (feature parity with agents.yaml tasks)
|
||||
context = step_data.get('context')
|
||||
if context:
|
||||
agent._yaml_context = context
|
||||
|
||||
# Handle output_file
|
||||
output_file = step_data.get('output_file')
|
||||
if output_file:
|
||||
agent._yaml_output_file = output_file
|
||||
|
||||
# Handle output_json (structured output)
|
||||
output_json = step_data.get('output_json')
|
||||
if output_json:
|
||||
agent._yaml_output_json = output_json
|
||||
|
||||
# Handle output_pydantic
|
||||
output_pydantic = step_data.get('output_pydantic')
|
||||
if output_pydantic:
|
||||
agent._yaml_output_pydantic = output_pydantic
|
||||
|
||||
# Handle create_directory
|
||||
create_directory = step_data.get('create_directory')
|
||||
if create_directory is not None:
|
||||
agent._yaml_create_directory = create_directory
|
||||
|
||||
# Handle callback
|
||||
callback = step_data.get('callback')
|
||||
if callback:
|
||||
agent._yaml_callback = callback
|
||||
|
||||
# Handle async_execution
|
||||
async_execution = step_data.get('async_execution')
|
||||
if async_execution is not None:
|
||||
agent._yaml_async_execution = async_execution
|
||||
|
||||
return agent
|
||||
else:
|
||||
raise ValueError(f"Agent '{agent_id}' not defined in agents section")
|
||||
|
||||
def _parse_route_step(self, step_data: Dict) -> Dict:
|
||||
"""
|
||||
Parse a route pattern step.
|
||||
|
||||
Args:
|
||||
step_data: Step definition with 'route' key
|
||||
|
||||
Returns:
|
||||
Route pattern object
|
||||
"""
|
||||
route_config = step_data['route']
|
||||
|
||||
# Build routes dictionary with resolved agents
|
||||
routes = {}
|
||||
for route_key, agent_ids in route_config.items():
|
||||
if isinstance(agent_ids, list):
|
||||
routes[route_key] = [self._agents[aid] for aid in agent_ids if aid in self._agents]
|
||||
else:
|
||||
if agent_ids in self._agents:
|
||||
routes[route_key] = [self._agents[agent_ids]]
|
||||
|
||||
return route(routes)
|
||||
|
||||
def _parse_parallel_step(self, step_data: Dict) -> Dict:
|
||||
"""
|
||||
Parse a parallel pattern step.
|
||||
|
||||
Args:
|
||||
step_data: Step definition with 'parallel' key
|
||||
|
||||
Returns:
|
||||
Parallel pattern object
|
||||
"""
|
||||
parallel_config = step_data['parallel']
|
||||
|
||||
# Build list of parallel steps
|
||||
parallel_steps = []
|
||||
for item in parallel_config:
|
||||
if 'agent' in item:
|
||||
agent_id = item['agent']
|
||||
if agent_id in self._agents:
|
||||
agent = self._agents[agent_id]
|
||||
# Store action if present
|
||||
if 'action' in item:
|
||||
agent._yaml_action = item['action']
|
||||
parallel_steps.append(agent)
|
||||
|
||||
return parallel(parallel_steps)
|
||||
|
||||
def _parse_loop_step(self, step_data: Dict) -> Dict:
|
||||
"""
|
||||
Parse a loop pattern step.
|
||||
|
||||
Args:
|
||||
step_data: Step definition with 'loop' key
|
||||
|
||||
Returns:
|
||||
Loop pattern object
|
||||
"""
|
||||
loop_config = step_data['loop']
|
||||
agent_id = step_data.get('agent')
|
||||
|
||||
if agent_id and agent_id in self._agents:
|
||||
agent = self._agents[agent_id]
|
||||
|
||||
# Store action if present
|
||||
if 'action' in step_data:
|
||||
agent._yaml_action = step_data['action']
|
||||
|
||||
# Get loop parameters
|
||||
over = loop_config.get('over')
|
||||
from_csv = loop_config.get('from_csv')
|
||||
|
||||
return loop(agent, over=over, from_csv=from_csv)
|
||||
|
||||
raise ValueError("Loop step requires an agent")
|
||||
|
||||
def _parse_repeat_step(self, step_data: Dict) -> Dict:
|
||||
"""
|
||||
Parse a repeat pattern step.
|
||||
|
||||
Args:
|
||||
step_data: Step definition with 'repeat' key
|
||||
|
||||
Returns:
|
||||
Repeat pattern object
|
||||
"""
|
||||
repeat_config = step_data['repeat']
|
||||
agent_id = step_data.get('agent')
|
||||
|
||||
if agent_id and agent_id in self._agents:
|
||||
agent = self._agents[agent_id]
|
||||
|
||||
# Store action if present
|
||||
if 'action' in step_data:
|
||||
agent._yaml_action = step_data['action']
|
||||
|
||||
# Get repeat parameters
|
||||
until = repeat_config.get('until')
|
||||
max_iterations = repeat_config.get('max_iterations', 5)
|
||||
|
||||
# Create condition function from 'until' string
|
||||
if isinstance(until, str):
|
||||
condition = self._create_condition_from_string(until)
|
||||
else:
|
||||
condition = until
|
||||
|
||||
return repeat(agent, until=condition, max_iterations=max_iterations)
|
||||
|
||||
raise ValueError("Repeat step requires an agent")
|
||||
|
||||
def _create_condition_from_string(self, condition_str: str) -> Callable:
|
||||
"""
|
||||
Create a condition function from a string.
|
||||
|
||||
Args:
|
||||
condition_str: Condition string (e.g., "approved", "comprehensive")
|
||||
|
||||
Returns:
|
||||
Callable condition function
|
||||
"""
|
||||
def condition(ctx) -> bool:
|
||||
result = str(ctx.previous_result).lower()
|
||||
return condition_str.lower() in result
|
||||
return condition
|
||||
|
||||
def _parse_generic_step(self, step_data: Dict) -> WorkflowStep:
|
||||
"""
|
||||
Parse a generic workflow step.
|
||||
|
||||
Args:
|
||||
step_data: Step definition dictionary
|
||||
|
||||
Returns:
|
||||
WorkflowStep object
|
||||
"""
|
||||
name = step_data.get('name', 'step')
|
||||
action = step_data.get('action', '')
|
||||
|
||||
return WorkflowStep(
|
||||
name=name,
|
||||
action=action,
|
||||
)
|
||||
|
||||
def register_tool(self, name: str, tool: Callable) -> None:
|
||||
"""
|
||||
Register a tool for use in workflows.
|
||||
|
||||
Args:
|
||||
name: Tool name as referenced in YAML
|
||||
tool: Callable tool function
|
||||
"""
|
||||
self.tool_registry[name] = tool
|
||||
|
||||
def register_callback(self, name: str, callback: Callable) -> None:
|
||||
"""
|
||||
Register a callback function.
|
||||
|
||||
Args:
|
||||
name: Callback name as referenced in YAML
|
||||
callback: Callable callback function
|
||||
"""
|
||||
self._callbacks[name] = callback
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.198"
|
||||
version = "0.1.4"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Generated
+1
-1
@@ -2422,7 +2422,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.198"
|
||||
version = "0.1.4"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
+262
-157
@@ -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,19 +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/workflows) |
|
||||
| ⚡ Parallel Execution | [Example](examples/python/workflows/workflow_parallel.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| 📄 CSV Loop Processing | [Example](examples/python/workflows/workflow_loop_csv.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
| 🔄 Evaluator-Optimizer | [Example](examples/python/workflows/workflow_repeat.py) | [📖](https://docs.praison.ai/features/workflows) |
|
||||
|
||||
## Supported Providers
|
||||
|
||||
@@ -409,184 +406,202 @@ memories = auto.process_interaction(
|
||||
# Extracts: name="John", preference="Python for backend"
|
||||
```
|
||||
|
||||
### 8. Workflows
|
||||
### 8. Agentic Workflows
|
||||
|
||||
Create reusable multi-step workflows with context passing and per-step agents:
|
||||
Create powerful multi-agent workflows with the `Workflow` class:
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.memory import WorkflowManager, Workflow, WorkflowStep
|
||||
from praisonaiagents import Agent, Workflow
|
||||
|
||||
# Simple execution with default agent
|
||||
agent = Agent(name="Assistant", llm="gpt-4o-mini")
|
||||
manager = WorkflowManager()
|
||||
|
||||
result = manager.execute(
|
||||
"deploy",
|
||||
default_agent=agent,
|
||||
variables={"environment": "production"}
|
||||
# Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Research Analyst",
|
||||
goal="Research topics thoroughly",
|
||||
instructions="Provide concise, factual information."
|
||||
)
|
||||
|
||||
# Advanced: Per-step agent configuration
|
||||
workflow = Workflow(
|
||||
name="research_pipeline",
|
||||
default_llm="gpt-4o-mini",
|
||||
steps=[
|
||||
WorkflowStep(
|
||||
name="research",
|
||||
action="Research {{topic}}",
|
||||
agent_config={"role": "Researcher", "goal": "Find information"},
|
||||
tools=["tavily_search"]
|
||||
),
|
||||
WorkflowStep(
|
||||
name="write",
|
||||
action="Write report based on {{previous_output}}",
|
||||
agent_config={"role": "Writer", "goal": "Write content"},
|
||||
context_from=["research"] # Only include research output
|
||||
)
|
||||
]
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
role="Content Writer",
|
||||
goal="Write engaging content",
|
||||
instructions="Write clear, engaging content based on research."
|
||||
)
|
||||
|
||||
# Async execution
|
||||
import asyncio
|
||||
result = asyncio.run(manager.aexecute("deploy", default_llm="gpt-4o-mini"))
|
||||
# Create workflow with agents as steps
|
||||
workflow = Workflow(steps=[researcher, writer])
|
||||
|
||||
# Run workflow - agents process sequentially
|
||||
result = workflow.start("What are the benefits of AI agents?")
|
||||
print(result["output"])
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Context Passing**: Use `{{previous_output}}` and `{{step_name_output}}` variables
|
||||
- **Per-Step Agents**: Configure different agents with roles, goals, tools for each step
|
||||
- **Async Execution**: Use `aexecute()` for async workflows
|
||||
- **Planning Mode**: Enable at workflow level with `planning=True`
|
||||
- **Branching**: Use `next_steps` and `branch_condition` for conditional routing
|
||||
- **Loops**: Use `loop_over` and `loop_var` to iterate over data
|
||||
|
||||
### Choosing the Right Workflow System
|
||||
|
||||
| Use Case | Recommended |
|
||||
|----------|-------------|
|
||||
| Simple function pipelines | `Workflow` class ⭐ |
|
||||
| Agent-only pipelines | `Workflow` class |
|
||||
| CSV batch processing | `process="workflow"` |
|
||||
| Complex task routing | `process="workflow"` |
|
||||
| Markdown templates | `WorkflowManager` |
|
||||
| Early stop / conditional | `Workflow` class |
|
||||
|
||||
### Simple Workflow (Recommended)
|
||||
|
||||
The easiest way to create workflows - just pass functions as steps:
|
||||
|
||||
```python
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
|
||||
# Define simple handler functions
|
||||
def validate(ctx: WorkflowContext) -> StepResult:
|
||||
if not ctx.input:
|
||||
return StepResult(output="No input", stop_workflow=True)
|
||||
return StepResult(output=f"Valid: {ctx.input}")
|
||||
|
||||
def process(ctx: WorkflowContext) -> StepResult:
|
||||
return StepResult(output=f"Processed: {ctx.previous_result}")
|
||||
|
||||
# Create and run workflow
|
||||
workflow = Workflow(steps=[validate, process])
|
||||
result = workflow.start("Hello World", verbose=True)
|
||||
print(result["output"]) # "Processed: Valid: Hello World"
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Just pass functions** - No complex configuration needed
|
||||
- **Early stop** - Return `stop_workflow=True` to stop the workflow
|
||||
- **Context passing** - Access `ctx.input`, `ctx.previous_result`, `ctx.variables`
|
||||
- **Verbose mode** - See step-by-step progress
|
||||
|
||||
### Workflow Branching & Loops
|
||||
|
||||
```python
|
||||
from praisonaiagents import WorkflowStep
|
||||
|
||||
# Branching step
|
||||
decision_step = WorkflowStep(
|
||||
name="decide",
|
||||
action="Evaluate if task is complete",
|
||||
next_steps=["success_step", "retry_step"],
|
||||
branch_condition={"success": ["success_step"], "failure": ["retry_step"]}
|
||||
)
|
||||
|
||||
# Loop step
|
||||
loop_step = WorkflowStep(
|
||||
name="process_items",
|
||||
action="Process {{item}}",
|
||||
loop_over="items", # Variable containing list
|
||||
loop_var="item" # Current item variable name
|
||||
)
|
||||
```
|
||||
- **Agent-first** - Pass `Agent` objects directly as workflow steps
|
||||
- **Pattern helpers** - Use `route()`, `parallel()`, `loop()`, `repeat()`
|
||||
- **Planning mode** - Enable with `planning=True`
|
||||
- **Callbacks** - Monitor with `on_step_complete`, `on_workflow_complete`
|
||||
- **Async execution** - Use `workflow.astart()` for async
|
||||
|
||||
### Workflow Patterns (route, parallel, loop, repeat)
|
||||
|
||||
```python
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.memory.workflows import route, parallel, loop, repeat
|
||||
from praisonaiagents import Agent, Workflow
|
||||
from praisonaiagents.workflows import route, parallel, loop, repeat
|
||||
|
||||
# 1. ROUTING - Classifier agent routes to specialized agents
|
||||
classifier = Agent(name="Classifier", instructions="Respond with 'technical' or 'creative'")
|
||||
tech_agent = Agent(name="TechExpert", role="Technical Expert")
|
||||
creative_agent = Agent(name="Creative", role="Creative Writer")
|
||||
|
||||
# 1. ROUTING - Decision-based branching
|
||||
workflow = Workflow(steps=[
|
||||
classify_request, # Returns "approve" or "reject"
|
||||
classifier,
|
||||
route({
|
||||
"approve": [approve_handler],
|
||||
"reject": [reject_handler],
|
||||
"default": [fallback_handler]
|
||||
"technical": [tech_agent],
|
||||
"creative": [creative_agent]
|
||||
})
|
||||
])
|
||||
|
||||
# 2. PARALLEL - Concurrent execution
|
||||
# 2. PARALLEL - Multiple agents work concurrently
|
||||
market_agent = Agent(name="Market", role="Market Researcher")
|
||||
competitor_agent = Agent(name="Competitor", role="Competitor Analyst")
|
||||
aggregator = Agent(name="Aggregator", role="Synthesizer")
|
||||
|
||||
workflow = Workflow(steps=[
|
||||
parallel([research_market, research_competitors, research_customers]),
|
||||
summarize_results # Gets all parallel outputs
|
||||
parallel([market_agent, competitor_agent]),
|
||||
aggregator
|
||||
])
|
||||
|
||||
# 3. LOOP - Iterate over list or CSV
|
||||
# 3. LOOP - Agent processes each item
|
||||
processor = Agent(name="Processor", role="Item Processor")
|
||||
summarizer = Agent(name="Summarizer", role="Summarizer")
|
||||
|
||||
workflow = Workflow(
|
||||
steps=[loop(process_item, over="items")],
|
||||
variables={"items": ["a", "b", "c"]}
|
||||
steps=[loop(processor, over="items"), summarizer],
|
||||
variables={"items": ["AI", "ML", "NLP"]}
|
||||
)
|
||||
# Or from CSV file:
|
||||
workflow = Workflow(steps=[loop(process_row, from_csv="data.csv")])
|
||||
|
||||
# 4. REPEAT - Evaluator-Optimizer pattern
|
||||
generator = Agent(name="Generator", role="Content Generator")
|
||||
evaluator = Agent(name="Evaluator", instructions="Say 'APPROVED' if good")
|
||||
|
||||
workflow = Workflow(steps=[
|
||||
repeat(
|
||||
generator,
|
||||
until=lambda ctx: "done" in ctx.previous_result,
|
||||
max_iterations=5
|
||||
)
|
||||
generator,
|
||||
repeat(evaluator, until=lambda ctx: "approved" in ctx.previous_result.lower(), max_iterations=3)
|
||||
])
|
||||
```
|
||||
|
||||
### Task Callbacks & Variables (process="workflow")
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Per-task agent config (no need to create agent separately)
|
||||
task = Task(
|
||||
description="Research {{topic}}",
|
||||
agent_config={"name": "Researcher", "role": "Expert", "llm": "gpt-4o-mini"},
|
||||
variables={"topic": "AI trends"}
|
||||
# 5. CALLBACKS
|
||||
workflow = Workflow(
|
||||
steps=[researcher, writer],
|
||||
on_step_complete=lambda name, r: print(f"✅ {name} done")
|
||||
)
|
||||
|
||||
# Callbacks for workflow execution
|
||||
def on_start(task, task_id):
|
||||
print(f"Starting: {task.name}")
|
||||
# 6. WITH PLANNING & REASONING
|
||||
workflow = Workflow(
|
||||
steps=[researcher, writer],
|
||||
planning=True,
|
||||
reasoning=True
|
||||
)
|
||||
|
||||
def on_complete(task, output):
|
||||
print(f"Completed: {task.name}")
|
||||
# 7. ASYNC EXECUTION
|
||||
result = asyncio.run(workflow.astart("input"))
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
process="workflow",
|
||||
on_task_start=on_start,
|
||||
on_task_complete=on_complete,
|
||||
variables={"global_var": "shared_value"} # Global variables for all tasks
|
||||
# 8. STATUS TRACKING
|
||||
workflow.status # "not_started" | "running" | "completed"
|
||||
workflow.step_statuses # {"step1": "completed", "step2": "skipped"}
|
||||
```
|
||||
|
||||
### 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]
|
||||
|
||||
workflow:
|
||||
planning: true
|
||||
planning_llm: gpt-4o
|
||||
memory_config:
|
||||
provider: chroma
|
||||
persist: true
|
||||
```
|
||||
|
||||
### Loading YAML Workflows
|
||||
|
||||
```python
|
||||
from praisonaiagents.workflows import YAMLWorkflowParser, WorkflowManager
|
||||
|
||||
# Option 1: Parse YAML string
|
||||
parser = YAMLWorkflowParser()
|
||||
workflow = parser.parse_string(yaml_content)
|
||||
result = workflow.start("Research AI trends")
|
||||
|
||||
# Option 2: Load from file with WorkflowManager
|
||||
manager = WorkflowManager()
|
||||
workflow = manager.load_yaml("research_workflow.yaml")
|
||||
result = workflow.start("Research AI trends")
|
||||
|
||||
# Option 3: Execute YAML directly
|
||||
result = manager.execute_yaml(
|
||||
"research_workflow.yaml",
|
||||
input_data="Research AI trends",
|
||||
variables={"topic": "Machine Learning"}
|
||||
)
|
||||
```
|
||||
|
||||
@@ -606,6 +621,75 @@ hooks.register("pre_write_code", lambda ctx: print(f"Writing {ctx['file']}"))
|
||||
result = hooks.execute("pre_write_code", {"file": "main.py"})
|
||||
```
|
||||
|
||||
### 10. Extended agents.yaml with Workflow Patterns
|
||||
|
||||
You can now use advanced workflow patterns directly in agents.yaml by setting `process: workflow`:
|
||||
|
||||
```yaml
|
||||
# agents.yaml with workflow patterns
|
||||
framework: praisonai
|
||||
process: workflow # Enables workflow mode
|
||||
topic: "Research AI trends"
|
||||
|
||||
workflow:
|
||||
planning: true
|
||||
reasoning: true
|
||||
verbose: true
|
||||
|
||||
variables:
|
||||
topic: AI trends
|
||||
|
||||
roles:
|
||||
classifier:
|
||||
role: Request Classifier
|
||||
backstory: "Classify requests into categories"
|
||||
goal: Classify requests
|
||||
|
||||
researcher:
|
||||
role: Research Analyst
|
||||
backstory: "Expert researcher"
|
||||
goal: Research topics
|
||||
tools:
|
||||
- tavily_search
|
||||
|
||||
steps:
|
||||
# Sequential step
|
||||
- agent: classifier
|
||||
action: "Classify: {{topic}}"
|
||||
|
||||
# Route pattern - decision-based branching
|
||||
- name: routing
|
||||
route:
|
||||
technical: [tech_expert]
|
||||
default: [researcher]
|
||||
|
||||
# Parallel pattern - concurrent execution
|
||||
- name: parallel_research
|
||||
parallel:
|
||||
- agent: researcher
|
||||
action: "Research market trends"
|
||||
- agent: researcher
|
||||
action: "Research competitors"
|
||||
|
||||
# Loop pattern - iterate over items
|
||||
- agent: researcher
|
||||
action: "Analyze {{item}}"
|
||||
loop:
|
||||
over: topics
|
||||
|
||||
# Repeat pattern - evaluator-optimizer
|
||||
- agent: aggregator
|
||||
action: "Synthesize findings"
|
||||
repeat:
|
||||
until: "comprehensive"
|
||||
max_iterations: 3
|
||||
```
|
||||
|
||||
Run with the same simple command:
|
||||
```bash
|
||||
praisonai agents.yaml
|
||||
```
|
||||
|
||||
## Using No Code
|
||||
|
||||
### Auto Mode:
|
||||
@@ -733,6 +817,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
|
||||
|
||||
@@ -744,8 +834,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.20.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.20.tar.gz | shasum -a 256`.split.first
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.26.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.26.tar.gz | shasum -a 256`.split.first
|
||||
license "MIT"
|
||||
|
||||
depends_on "[email protected]"
|
||||
|
||||
@@ -318,7 +318,16 @@ class AgentsGenerator:
|
||||
print(f"File not found: {self.agent_file}")
|
||||
return
|
||||
|
||||
topic = config['topic']
|
||||
# Check if this is a workflow-mode YAML (process: workflow or has steps section)
|
||||
process_type = config.get('process', 'sequential')
|
||||
has_steps = 'steps' in config
|
||||
has_workflow_config = 'workflow' in config
|
||||
|
||||
if process_type == 'workflow' or (has_steps and has_workflow_config):
|
||||
# Route to YAMLWorkflowParser for advanced workflow patterns
|
||||
return self._run_yaml_workflow(config)
|
||||
|
||||
topic = config.get('topic', '')
|
||||
tools_dict = {}
|
||||
|
||||
# Only try to use praisonai_tools if it's available and needed
|
||||
@@ -406,6 +415,53 @@ class AgentsGenerator:
|
||||
agentops.init(os.environ.get("AGENTOPS_API_KEY"), default_tags=["crewai"])
|
||||
return self._run_crewai(config, topic, tools_dict)
|
||||
|
||||
def _run_yaml_workflow(self, config):
|
||||
"""
|
||||
Run a YAML workflow using the YAMLWorkflowParser.
|
||||
|
||||
This method handles agents.yaml files that have:
|
||||
- process: workflow
|
||||
- steps section with workflow patterns (route, parallel, loop, repeat)
|
||||
|
||||
Args:
|
||||
config (dict): The parsed YAML configuration
|
||||
|
||||
Returns:
|
||||
str: Result of the workflow execution
|
||||
"""
|
||||
if not PRAISONAI_AVAILABLE:
|
||||
raise ImportError("PraisonAI is not installed. Please install it with 'pip install praisonaiagents'")
|
||||
|
||||
try:
|
||||
from praisonaiagents.workflows import YAMLWorkflowParser
|
||||
except ImportError:
|
||||
raise ImportError("YAMLWorkflowParser not available. Please update praisonaiagents.")
|
||||
|
||||
# Ensure name is present (YAMLWorkflowParser handles roles->agents conversion)
|
||||
if 'name' not in config:
|
||||
config['name'] = config.get('topic', 'Workflow')
|
||||
|
||||
# Convert config back to YAML string for parser
|
||||
# Note: YAMLWorkflowParser handles 'roles' to 'agents' conversion internally
|
||||
import yaml as yaml_module
|
||||
yaml_content = yaml_module.dump(config, default_flow_style=False)
|
||||
|
||||
# Parse and execute
|
||||
parser = YAMLWorkflowParser()
|
||||
workflow = parser.parse_string(yaml_content)
|
||||
|
||||
# Get input from topic or variables
|
||||
input_data = config.get('topic', '')
|
||||
|
||||
# Execute workflow
|
||||
self.logger.info(f"Running workflow: {workflow.name}")
|
||||
result = workflow.start(input_data)
|
||||
|
||||
if result.get("status") == "completed":
|
||||
return result.get("output", "Workflow completed successfully")
|
||||
else:
|
||||
return f"Workflow failed: {result.get('error', 'Unknown error')}"
|
||||
|
||||
def _run_autogen(self, config, topic, tools_dict):
|
||||
"""
|
||||
Run agents using the AutoGen framework.
|
||||
|
||||
@@ -335,4 +335,225 @@ The final role you create should satisfy the provided task: """ + self.topic + "
|
||||
|
||||
|
||||
# generator = AutoGenerator(framework="crewai", topic="Create a movie script about Cat in Mars")
|
||||
# print(generator.generate())
|
||||
# print(generator.generate())
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Workflow Auto-Generation (Feature Parity)
|
||||
# =============================================================================
|
||||
|
||||
class WorkflowStepDetails(BaseModel):
|
||||
"""Details for a workflow step."""
|
||||
agent: str
|
||||
action: str
|
||||
expected_output: Optional[str] = None
|
||||
|
||||
class WorkflowRouteDetails(BaseModel):
|
||||
"""Details for a route step."""
|
||||
name: str
|
||||
route: Dict[str, List[str]]
|
||||
|
||||
class WorkflowParallelDetails(BaseModel):
|
||||
"""Details for a parallel step."""
|
||||
name: str
|
||||
parallel: List[WorkflowStepDetails]
|
||||
|
||||
class WorkflowAgentDetails(BaseModel):
|
||||
"""Details for a workflow agent."""
|
||||
name: str
|
||||
role: str
|
||||
goal: str
|
||||
instructions: str
|
||||
tools: Optional[List[str]] = None
|
||||
|
||||
class WorkflowStructure(BaseModel):
|
||||
"""Structure for auto-generated workflow."""
|
||||
name: str
|
||||
description: str
|
||||
agents: Dict[str, WorkflowAgentDetails]
|
||||
steps: List[Dict] # Can be agent steps, route, parallel, etc.
|
||||
|
||||
|
||||
class WorkflowAutoGenerator:
|
||||
"""
|
||||
Auto-generates workflow.yaml files from a topic description.
|
||||
|
||||
Uses lazy loading to avoid performance impact when not used.
|
||||
|
||||
Usage:
|
||||
generator = WorkflowAutoGenerator(topic="Research AI trends and write a report")
|
||||
path = generator.generate()
|
||||
"""
|
||||
|
||||
def __init__(self, topic: str = "Research and write about AI",
|
||||
workflow_file: str = "workflow.yaml",
|
||||
config_list: Optional[List[Dict]] = None):
|
||||
"""
|
||||
Initialize the WorkflowAutoGenerator.
|
||||
|
||||
Args:
|
||||
topic: The task/topic for the workflow
|
||||
workflow_file: Output file name
|
||||
config_list: Optional LLM configuration
|
||||
"""
|
||||
# Support multiple environment variable patterns
|
||||
model_name = os.environ.get("MODEL_NAME") or os.environ.get("OPENAI_MODEL_NAME", "gpt-4o-mini")
|
||||
base_url = (
|
||||
os.environ.get("OPENAI_BASE_URL") or
|
||||
os.environ.get("OPENAI_API_BASE") or
|
||||
os.environ.get("OLLAMA_API_BASE", "https://api.openai.com/v1")
|
||||
)
|
||||
|
||||
self.config_list = config_list or [
|
||||
{
|
||||
'model': model_name,
|
||||
'base_url': base_url,
|
||||
'api_key': os.environ.get("OPENAI_API_KEY")
|
||||
}
|
||||
]
|
||||
self.topic = topic
|
||||
self.workflow_file = workflow_file
|
||||
self._client = None # Lazy loading
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
"""Lazy load the OpenAI client to avoid performance impact."""
|
||||
if self._client is None:
|
||||
self._client = instructor.patch(
|
||||
OpenAI(
|
||||
base_url=self.config_list[0]['base_url'],
|
||||
api_key=self.config_list[0]['api_key'],
|
||||
),
|
||||
mode=instructor.Mode.JSON,
|
||||
)
|
||||
return self._client
|
||||
|
||||
def generate(self, pattern: str = "sequential") -> str:
|
||||
"""
|
||||
Generate a workflow YAML file.
|
||||
|
||||
Args:
|
||||
pattern: Workflow pattern - "sequential", "routing", "parallel", "loop"
|
||||
|
||||
Returns:
|
||||
Path to the generated workflow file
|
||||
"""
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.config_list[0]['model'],
|
||||
response_model=WorkflowStructure,
|
||||
max_retries=5,
|
||||
messages=[
|
||||
{"role": "system", "content": "You are a helpful assistant that designs workflow structures."},
|
||||
{"role": "user", "content": self._get_prompt(pattern)}
|
||||
]
|
||||
)
|
||||
|
||||
json_data = json.loads(response.model_dump_json())
|
||||
return self._save_workflow(json_data, pattern)
|
||||
|
||||
def _get_prompt(self, pattern: str) -> str:
|
||||
"""Generate the prompt based on the workflow pattern."""
|
||||
base_prompt = f"""Generate a workflow structure for: "{self.topic}"
|
||||
|
||||
The workflow should use the "{pattern}" pattern.
|
||||
Create 2-4 agents with clear roles and instructions.
|
||||
Each step should have a clear action.
|
||||
|
||||
"""
|
||||
|
||||
if pattern == "routing":
|
||||
base_prompt += """
|
||||
Include a classifier agent that routes to different specialized agents.
|
||||
The route step should have at least 2 routes plus a default.
|
||||
|
||||
Example structure:
|
||||
{
|
||||
"name": "Routing Workflow",
|
||||
"description": "Routes requests to specialized agents",
|
||||
"agents": {
|
||||
"classifier": {"name": "Classifier", "role": "Request Classifier", "goal": "Classify requests", "instructions": "Respond with ONLY: technical, creative, or general"},
|
||||
"tech_agent": {"name": "TechExpert", "role": "Technical Expert", "goal": "Handle technical questions", "instructions": "Provide technical answers"}
|
||||
},
|
||||
"steps": [
|
||||
{"agent": "classifier", "action": "Classify: {{input}}"},
|
||||
{"name": "routing", "route": {"technical": ["tech_agent"], "default": ["tech_agent"]}}
|
||||
]
|
||||
}
|
||||
"""
|
||||
elif pattern == "parallel":
|
||||
base_prompt += """
|
||||
Include multiple agents that work in parallel, then an aggregator.
|
||||
|
||||
Example structure:
|
||||
{
|
||||
"name": "Parallel Workflow",
|
||||
"description": "Multiple agents work concurrently",
|
||||
"agents": {
|
||||
"researcher1": {"name": "Researcher1", "role": "Market Analyst", "goal": "Research market", "instructions": "Provide market insights"},
|
||||
"researcher2": {"name": "Researcher2", "role": "Competitor Analyst", "goal": "Research competitors", "instructions": "Provide competitor insights"},
|
||||
"aggregator": {"name": "Aggregator", "role": "Synthesizer", "goal": "Combine findings", "instructions": "Synthesize all research"}
|
||||
},
|
||||
"steps": [
|
||||
{"name": "parallel_research", "parallel": [
|
||||
{"agent": "researcher1", "action": "Research market for {{input}}"},
|
||||
{"agent": "researcher2", "action": "Research competitors for {{input}}"}
|
||||
]},
|
||||
{"agent": "aggregator", "action": "Combine all findings"}
|
||||
]
|
||||
}
|
||||
"""
|
||||
else: # sequential
|
||||
base_prompt += """
|
||||
Create a sequential workflow where agents work one after another.
|
||||
|
||||
Example structure:
|
||||
{
|
||||
"name": "Sequential Workflow",
|
||||
"description": "Agents work in sequence",
|
||||
"agents": {
|
||||
"researcher": {"name": "Researcher", "role": "Research Analyst", "goal": "Research topics", "instructions": "Provide research findings"},
|
||||
"writer": {"name": "Writer", "role": "Content Writer", "goal": "Write content", "instructions": "Write clear content"}
|
||||
},
|
||||
"steps": [
|
||||
{"agent": "researcher", "action": "Research: {{input}}"},
|
||||
{"agent": "writer", "action": "Write based on: {{previous_output}}"}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
base_prompt += f"\nGenerate a workflow for: {self.topic}"
|
||||
return base_prompt
|
||||
|
||||
def _save_workflow(self, data: Dict, pattern: str) -> str:
|
||||
"""Save the workflow to a YAML file."""
|
||||
# Build the workflow YAML structure
|
||||
workflow_yaml = {
|
||||
'name': data.get('name', 'Auto-Generated Workflow'),
|
||||
'description': data.get('description', ''),
|
||||
'framework': 'praisonai',
|
||||
'workflow': {
|
||||
'verbose': True,
|
||||
'planning': False,
|
||||
'reasoning': False
|
||||
},
|
||||
'agents': {},
|
||||
'steps': data.get('steps', [])
|
||||
}
|
||||
|
||||
# Convert agents
|
||||
for agent_id, agent_data in data.get('agents', {}).items():
|
||||
workflow_yaml['agents'][agent_id] = {
|
||||
'name': agent_data.get('name', agent_id),
|
||||
'role': agent_data.get('role', 'Assistant'),
|
||||
'goal': agent_data.get('goal', ''),
|
||||
'instructions': agent_data.get('instructions', '')
|
||||
}
|
||||
if agent_data.get('tools'):
|
||||
workflow_yaml['agents'][agent_id]['tools'] = agent_data['tools']
|
||||
|
||||
# Write to file
|
||||
full_path = os.path.abspath(self.workflow_file)
|
||||
with open(full_path, 'w') as f:
|
||||
yaml.dump(workflow_yaml, f, default_flow_style=False, sort_keys=False)
|
||||
|
||||
return full_path
|
||||
@@ -19,6 +19,7 @@ Features:
|
||||
- todo: Todo list management
|
||||
- router: Smart model selection
|
||||
- flow_display: Visual workflow tracking
|
||||
- workflow: YAML workflow management
|
||||
"""
|
||||
|
||||
# Type hints available for IDE support
|
||||
@@ -39,6 +40,7 @@ __all__ = [
|
||||
'TodoHandler',
|
||||
'RouterHandler',
|
||||
'FlowDisplayHandler',
|
||||
'WorkflowHandler',
|
||||
]
|
||||
|
||||
def __getattr__(name):
|
||||
@@ -85,4 +87,7 @@ def __getattr__(name):
|
||||
elif name == 'FlowDisplayHandler':
|
||||
from .flow_display import FlowDisplayHandler
|
||||
return FlowDisplayHandler
|
||||
elif name == 'WorkflowHandler':
|
||||
from .workflow import WorkflowHandler
|
||||
return WorkflowHandler
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
@@ -0,0 +1,575 @@
|
||||
"""
|
||||
Workflow Handler for CLI.
|
||||
|
||||
Provides YAML workflow management commands.
|
||||
Usage: praisonai workflow run research.yaml
|
||||
praisonai workflow validate research.yaml
|
||||
praisonai workflow list
|
||||
praisonai workflow create --template routing
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
from .base import CommandHandler
|
||||
|
||||
|
||||
class WorkflowHandler(CommandHandler):
|
||||
"""
|
||||
Handler for workflow command.
|
||||
|
||||
Manages YAML workflow files - run, validate, list, and create from templates.
|
||||
|
||||
Example:
|
||||
praisonai workflow run research.yaml --var topic="AI"
|
||||
praisonai workflow validate research.yaml
|
||||
praisonai workflow list
|
||||
praisonai workflow create --template routing --output my_workflow.yaml
|
||||
"""
|
||||
|
||||
# Workflow templates
|
||||
TEMPLATES = {
|
||||
"simple": """# Simple Sequential Workflow
|
||||
name: Simple Workflow
|
||||
description: A simple sequential workflow
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
agents:
|
||||
researcher:
|
||||
name: Researcher
|
||||
role: Research Analyst
|
||||
goal: Research topics
|
||||
instructions: "Provide concise, factual information."
|
||||
|
||||
writer:
|
||||
name: Writer
|
||||
role: Content Writer
|
||||
goal: Write content
|
||||
instructions: "Write clear, engaging content."
|
||||
|
||||
steps:
|
||||
- agent: researcher
|
||||
action: "Research: {{input}}"
|
||||
|
||||
- agent: writer
|
||||
action: "Write summary based on: {{previous_output}}"
|
||||
""",
|
||||
"routing": """# Routing Workflow
|
||||
name: Routing Workflow
|
||||
description: Classifier routes to specialized agents
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
agents:
|
||||
classifier:
|
||||
name: Classifier
|
||||
role: Request Classifier
|
||||
goal: Classify requests
|
||||
instructions: "Respond with ONLY 'technical', 'creative', or 'general'."
|
||||
|
||||
tech_agent:
|
||||
name: TechExpert
|
||||
role: Technical Expert
|
||||
goal: Handle technical questions
|
||||
instructions: "Provide technical answers."
|
||||
|
||||
creative_agent:
|
||||
name: Creative
|
||||
role: Creative Writer
|
||||
goal: Handle creative requests
|
||||
instructions: "Write creative content."
|
||||
|
||||
general_agent:
|
||||
name: General
|
||||
role: General Assistant
|
||||
goal: Handle general requests
|
||||
instructions: "Provide helpful responses."
|
||||
|
||||
steps:
|
||||
- agent: classifier
|
||||
action: "Classify: {{input}}"
|
||||
|
||||
- name: routing
|
||||
route:
|
||||
technical: [tech_agent]
|
||||
creative: [creative_agent]
|
||||
default: [general_agent]
|
||||
""",
|
||||
"parallel": """# Parallel Workflow
|
||||
name: Parallel Research Workflow
|
||||
description: Multiple agents work concurrently
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
agents:
|
||||
researcher1:
|
||||
name: MarketResearcher
|
||||
role: Market Analyst
|
||||
goal: Research market trends
|
||||
instructions: "Provide market insights."
|
||||
|
||||
researcher2:
|
||||
name: CompetitorResearcher
|
||||
role: Competitor Analyst
|
||||
goal: Research competitors
|
||||
instructions: "Provide competitor insights."
|
||||
|
||||
aggregator:
|
||||
name: Aggregator
|
||||
role: Synthesizer
|
||||
goal: Combine findings
|
||||
instructions: "Synthesize all research."
|
||||
|
||||
steps:
|
||||
- name: parallel_research
|
||||
parallel:
|
||||
- agent: researcher1
|
||||
action: "Research market for: {{input}}"
|
||||
- agent: researcher2
|
||||
action: "Research competitors for: {{input}}"
|
||||
|
||||
- agent: aggregator
|
||||
action: "Combine all findings"
|
||||
""",
|
||||
"loop": """# Loop Workflow
|
||||
name: Loop Processing Workflow
|
||||
description: Process multiple items in a loop
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
variables:
|
||||
items:
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
|
||||
agents:
|
||||
processor:
|
||||
name: Processor
|
||||
role: Item Processor
|
||||
goal: Process each item
|
||||
instructions: "Process the given item thoroughly."
|
||||
|
||||
summarizer:
|
||||
name: Summarizer
|
||||
role: Summarizer
|
||||
goal: Summarize results
|
||||
instructions: "Summarize all processed items."
|
||||
|
||||
steps:
|
||||
- agent: processor
|
||||
action: "Process: {{item}}"
|
||||
loop:
|
||||
over: items
|
||||
|
||||
- agent: summarizer
|
||||
action: "Summarize all processed items"
|
||||
""",
|
||||
"evaluator-optimizer": """# Evaluator-Optimizer Workflow
|
||||
name: Evaluator Optimizer Workflow
|
||||
description: Generate and improve until approved
|
||||
|
||||
workflow:
|
||||
verbose: true
|
||||
|
||||
agents:
|
||||
generator:
|
||||
name: Generator
|
||||
role: Content Generator
|
||||
goal: Generate content
|
||||
instructions: "Generate content. Improve based on feedback if provided."
|
||||
|
||||
evaluator:
|
||||
name: Evaluator
|
||||
role: Evaluator
|
||||
goal: Evaluate content
|
||||
instructions: "If good, respond 'APPROVED'. Otherwise provide feedback."
|
||||
|
||||
steps:
|
||||
- agent: generator
|
||||
action: "Generate content for: {{input}}"
|
||||
|
||||
- agent: evaluator
|
||||
action: "Evaluate: {{previous_output}}"
|
||||
repeat:
|
||||
until: "approved"
|
||||
max_iterations: 3
|
||||
"""
|
||||
}
|
||||
|
||||
def __init__(self, verbose: bool = False):
|
||||
super().__init__(verbose)
|
||||
self._manager = None
|
||||
self._parser = None
|
||||
|
||||
@property
|
||||
def feature_name(self) -> str:
|
||||
return "workflow"
|
||||
|
||||
def get_actions(self) -> List[str]:
|
||||
return ["run", "validate", "list", "create", "auto", "help"]
|
||||
|
||||
def get_help_text(self) -> str:
|
||||
return """
|
||||
Workflow Commands:
|
||||
praisonai workflow run <file.yaml> - Run a YAML workflow
|
||||
praisonai workflow run <file.yaml> --var key=value - Run with variables
|
||||
praisonai workflow validate <file.yaml> - Validate a YAML workflow
|
||||
praisonai workflow list - List workflows in .praison/workflows/
|
||||
praisonai workflow create --template <name> - Create from template
|
||||
praisonai workflow auto "topic" --pattern <pattern> - Auto-generate workflow
|
||||
|
||||
Templates: simple, routing, parallel, loop, evaluator-optimizer
|
||||
Patterns: sequential, routing, parallel
|
||||
|
||||
Example:
|
||||
praisonai workflow run research.yaml --var topic="AI trends"
|
||||
praisonai workflow create --template routing --output my_workflow.yaml
|
||||
praisonai workflow auto "Research AI trends" --pattern parallel
|
||||
"""
|
||||
|
||||
def _get_manager(self):
|
||||
"""Get WorkflowManager lazily."""
|
||||
if self._manager is None:
|
||||
try:
|
||||
from praisonaiagents.workflows import WorkflowManager
|
||||
self._manager = WorkflowManager()
|
||||
except ImportError:
|
||||
self.print_status(
|
||||
"Workflows require praisonaiagents. Install with: pip install praisonaiagents",
|
||||
"error"
|
||||
)
|
||||
return None
|
||||
return self._manager
|
||||
|
||||
def _get_parser(self):
|
||||
"""Get YAMLWorkflowParser lazily."""
|
||||
if self._parser is None:
|
||||
try:
|
||||
from praisonaiagents.workflows import YAMLWorkflowParser
|
||||
self._parser = YAMLWorkflowParser()
|
||||
except ImportError:
|
||||
self.print_status(
|
||||
"Workflows require praisonaiagents. Install with: pip install praisonaiagents",
|
||||
"error"
|
||||
)
|
||||
return None
|
||||
return self._parser
|
||||
|
||||
def _parse_variables(self, args: List[str]) -> Dict[str, Any]:
|
||||
"""Parse --var key=value arguments."""
|
||||
variables = {}
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i] == "--var" and i + 1 < len(args):
|
||||
var_str = args[i + 1]
|
||||
if "=" in var_str:
|
||||
key, value = var_str.split("=", 1)
|
||||
variables[key.strip()] = value.strip()
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
return variables
|
||||
|
||||
def _get_file_from_args(self, args: List[str]) -> Optional[str]:
|
||||
"""Get the file path from arguments."""
|
||||
for arg in args:
|
||||
if arg.endswith(('.yaml', '.yml')) and not arg.startswith('--'):
|
||||
return arg
|
||||
return None
|
||||
|
||||
def action_run(self, args: List[str], **kwargs) -> Any:
|
||||
"""
|
||||
Run a YAML workflow file.
|
||||
|
||||
Args:
|
||||
args: [file.yaml, --var, key=value, ...]
|
||||
|
||||
Returns:
|
||||
Workflow execution result
|
||||
"""
|
||||
file_path = self._get_file_from_args(args)
|
||||
if not file_path:
|
||||
self.print_status("Usage: praisonai workflow run <file.yaml>", "error")
|
||||
return None
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
self.print_status(f"File not found: {file_path}", "error")
|
||||
return None
|
||||
|
||||
manager = self._get_manager()
|
||||
if not manager:
|
||||
return None
|
||||
|
||||
# Parse variables
|
||||
variables = self._parse_variables(args)
|
||||
|
||||
# Get input if provided
|
||||
input_data = kwargs.get('input', '')
|
||||
|
||||
# Check for --verbose flag
|
||||
verbose = '--verbose' in args or '-v' in args
|
||||
|
||||
self.print_status(f"Running workflow: {file_path}", "info")
|
||||
if variables:
|
||||
self.print_status(f"Variables: {variables}", "info")
|
||||
|
||||
try:
|
||||
result = manager.execute_yaml(
|
||||
file_path,
|
||||
input_data=input_data,
|
||||
variables=variables,
|
||||
verbose=verbose
|
||||
)
|
||||
|
||||
self.print_status("Workflow completed!", "success")
|
||||
|
||||
# Print output
|
||||
if result.get('output'):
|
||||
from rich import print as rprint
|
||||
rprint("\n[bold]Output:[/bold]")
|
||||
rprint(result['output'])
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.print_status(f"Workflow failed: {e}", "error")
|
||||
return None
|
||||
|
||||
def action_validate(self, args: List[str], **kwargs) -> bool:
|
||||
"""
|
||||
Validate a YAML workflow file.
|
||||
|
||||
Args:
|
||||
args: [file.yaml]
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
file_path = self._get_file_from_args(args)
|
||||
if not file_path:
|
||||
self.print_status("Usage: praisonai workflow validate <file.yaml>", "error")
|
||||
return False
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
self.print_status(f"File not found: {file_path}", "error")
|
||||
return False
|
||||
|
||||
parser = self._get_parser()
|
||||
if not parser:
|
||||
return False
|
||||
|
||||
self.print_status(f"Validating: {file_path}", "info")
|
||||
|
||||
try:
|
||||
workflow = parser.parse_file(file_path)
|
||||
|
||||
# Print validation results
|
||||
from rich import print as rprint
|
||||
from rich.table import Table
|
||||
|
||||
table = Table(title="Workflow Validation")
|
||||
table.add_column("Property", style="cyan")
|
||||
table.add_column("Value", style="green")
|
||||
|
||||
table.add_row("Name", workflow.name)
|
||||
table.add_row("Description", getattr(workflow, 'description', 'N/A'))
|
||||
table.add_row("Steps", str(len(workflow.steps)))
|
||||
table.add_row("Variables", str(len(workflow.variables)))
|
||||
table.add_row("Planning", str(workflow.planning))
|
||||
table.add_row("Reasoning", str(workflow.reasoning))
|
||||
|
||||
rprint(table)
|
||||
self.print_status("✓ Workflow is valid!", "success")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.print_status(f"✗ Validation failed: {e}", "error")
|
||||
return False
|
||||
|
||||
def action_list(self, args: List[str], **kwargs) -> List[str]:
|
||||
"""
|
||||
List available workflows in .praison/workflows/ directory.
|
||||
|
||||
Returns:
|
||||
List of workflow file names
|
||||
"""
|
||||
workflows_dir = Path(".praison/workflows")
|
||||
|
||||
if not workflows_dir.exists():
|
||||
self.print_status("No .praison/workflows/ directory found", "warning")
|
||||
self.print_status("Create one with: mkdir -p .praison/workflows", "info")
|
||||
return []
|
||||
|
||||
# Find all YAML files
|
||||
yaml_files = list(workflows_dir.glob("*.yaml")) + list(workflows_dir.glob("*.yml"))
|
||||
|
||||
if not yaml_files:
|
||||
self.print_status("No workflow files found in .praison/workflows/", "warning")
|
||||
return []
|
||||
|
||||
from rich import print as rprint
|
||||
from rich.table import Table
|
||||
|
||||
table = Table(title="Available Workflows")
|
||||
table.add_column("File", style="cyan")
|
||||
table.add_column("Name", style="green")
|
||||
table.add_column("Steps", style="yellow")
|
||||
|
||||
parser = self._get_parser()
|
||||
workflow_names = []
|
||||
|
||||
for yaml_file in yaml_files:
|
||||
try:
|
||||
if parser:
|
||||
workflow = parser.parse_file(yaml_file)
|
||||
table.add_row(
|
||||
yaml_file.name,
|
||||
workflow.name,
|
||||
str(len(workflow.steps))
|
||||
)
|
||||
else:
|
||||
table.add_row(yaml_file.name, "N/A", "N/A")
|
||||
workflow_names.append(yaml_file.name)
|
||||
except Exception as e:
|
||||
table.add_row(yaml_file.name, f"Error: {e}", "N/A")
|
||||
|
||||
rprint(table)
|
||||
return workflow_names
|
||||
|
||||
def action_create(self, args: List[str], **kwargs) -> Optional[str]:
|
||||
"""
|
||||
Create a workflow from a template.
|
||||
|
||||
Args:
|
||||
args: [--template, <name>, --output, <file.yaml>]
|
||||
|
||||
Returns:
|
||||
Path to created file
|
||||
"""
|
||||
# Parse arguments
|
||||
template_name = None
|
||||
output_file = None
|
||||
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i] == "--template" and i + 1 < len(args):
|
||||
template_name = args[i + 1]
|
||||
i += 2
|
||||
elif args[i] == "--output" and i + 1 < len(args):
|
||||
output_file = args[i + 1]
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if not template_name:
|
||||
self.print_status("Usage: praisonai workflow create --template <name>", "error")
|
||||
self.print_status(f"Available templates: {', '.join(self.TEMPLATES.keys())}", "info")
|
||||
return None
|
||||
|
||||
if template_name not in self.TEMPLATES:
|
||||
self.print_status(f"Unknown template: {template_name}", "error")
|
||||
self.print_status(f"Available templates: {', '.join(self.TEMPLATES.keys())}", "info")
|
||||
return None
|
||||
|
||||
# Default output file
|
||||
if not output_file:
|
||||
output_file = f"{template_name}_workflow.yaml"
|
||||
|
||||
# Check if file exists
|
||||
if os.path.exists(output_file):
|
||||
self.print_status(f"File already exists: {output_file}", "error")
|
||||
return None
|
||||
|
||||
# Write template
|
||||
template_content = self.TEMPLATES[template_name]
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(template_content)
|
||||
|
||||
self.print_status(f"✓ Created workflow: {output_file}", "success")
|
||||
self.print_status(f"Run with: praisonai workflow run {output_file}", "info")
|
||||
|
||||
return output_file
|
||||
|
||||
def action_auto(self, args: List[str], **kwargs) -> Optional[str]:
|
||||
"""
|
||||
Auto-generate a workflow from a topic description.
|
||||
|
||||
Args:
|
||||
args: ["topic description", --pattern, <pattern>, --output, <file.yaml>]
|
||||
|
||||
Returns:
|
||||
Path to created file
|
||||
"""
|
||||
# Parse arguments
|
||||
topic = None
|
||||
pattern = "sequential"
|
||||
output_file = None
|
||||
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i] == "--pattern" and i + 1 < len(args):
|
||||
pattern = args[i + 1]
|
||||
i += 2
|
||||
elif args[i] == "--output" and i + 1 < len(args):
|
||||
output_file = args[i + 1]
|
||||
i += 2
|
||||
elif not args[i].startswith("--") and topic is None:
|
||||
topic = args[i]
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if not topic:
|
||||
self.print_status('Usage: praisonai workflow auto "topic" --pattern <pattern>', "error")
|
||||
self.print_status("Patterns: sequential, routing, parallel", "info")
|
||||
return None
|
||||
|
||||
# Validate pattern
|
||||
valid_patterns = ["sequential", "routing", "parallel", "loop"]
|
||||
if pattern not in valid_patterns:
|
||||
self.print_status(f"Unknown pattern: {pattern}", "error")
|
||||
self.print_status(f"Valid patterns: {', '.join(valid_patterns)}", "info")
|
||||
return None
|
||||
|
||||
# Default output file
|
||||
if not output_file:
|
||||
# Create safe filename from topic
|
||||
safe_name = "".join(c if c.isalnum() else "_" for c in topic[:30]).lower()
|
||||
output_file = f"{safe_name}_workflow.yaml"
|
||||
|
||||
# Check if file exists
|
||||
if os.path.exists(output_file):
|
||||
self.print_status(f"File already exists: {output_file}", "error")
|
||||
return None
|
||||
|
||||
self.print_status(f"Generating {pattern} workflow for: {topic}", "info")
|
||||
|
||||
try:
|
||||
# Lazy import to avoid performance impact
|
||||
from praisonai.auto import WorkflowAutoGenerator
|
||||
|
||||
generator = WorkflowAutoGenerator(
|
||||
topic=topic,
|
||||
workflow_file=output_file
|
||||
)
|
||||
|
||||
result_path = generator.generate(pattern=pattern)
|
||||
|
||||
self.print_status(f"✓ Created workflow: {result_path}", "success")
|
||||
self.print_status(f"Run with: praisonai workflow run {output_file}", "info")
|
||||
|
||||
return result_path
|
||||
|
||||
except ImportError as e:
|
||||
self.print_status(f"Auto-generation requires instructor: pip install instructor", "error")
|
||||
return None
|
||||
except Exception as e:
|
||||
self.print_status(f"Generation failed: {e}", "error")
|
||||
return None
|
||||
@@ -1485,6 +1485,12 @@ class PraisonAI:
|
||||
return
|
||||
workflow_name = action_args[0]
|
||||
|
||||
# Check if it's a YAML file
|
||||
if workflow_name.endswith(('.yaml', '.yml')) and os.path.exists(workflow_name):
|
||||
# Use new YAML workflow parser
|
||||
self._run_yaml_workflow(workflow_name, action_args, variables, args)
|
||||
return
|
||||
|
||||
# Use global flags (--llm, --tools, --planning, --memory, --save, --verbose)
|
||||
workflow_llm = getattr(args, 'llm', None) if args else None
|
||||
workflow_tools_str = getattr(args, 'tools', None) if args else None
|
||||
@@ -1607,22 +1613,50 @@ class PraisonAI:
|
||||
print(f"[green]✅ Workflow created: {workflow_name}[/green]")
|
||||
print(f"[cyan]Edit the workflow in .praison/workflows/{workflow_name}.md[/cyan]")
|
||||
|
||||
elif action == 'validate':
|
||||
# Validate a YAML workflow file
|
||||
if not action_args:
|
||||
print("[red]ERROR: YAML file required. Usage: praisonai workflow validate <file.yaml>[/red]")
|
||||
return
|
||||
yaml_file = action_args[0]
|
||||
if not yaml_file.endswith(('.yaml', '.yml')):
|
||||
print("[red]ERROR: File must be a YAML file (.yaml or .yml)[/red]")
|
||||
return
|
||||
self._validate_yaml_workflow(yaml_file)
|
||||
|
||||
elif action == 'template':
|
||||
# Create from template
|
||||
template_name = action_args[0] if action_args else None
|
||||
output_file = None
|
||||
for i, arg in enumerate(action_args):
|
||||
if arg == '--output' and i + 1 < len(action_args):
|
||||
output_file = action_args[i + 1]
|
||||
self._create_workflow_from_template(template_name, output_file)
|
||||
|
||||
elif action == 'help' or action == '--help':
|
||||
print("[bold]Workflow Commands:[/bold]")
|
||||
print(" praisonai workflow list - List available workflows")
|
||||
print(" praisonai workflow run <name> - Execute a workflow")
|
||||
print(" praisonai workflow run <file.yaml> - Execute a YAML workflow")
|
||||
print(" praisonai workflow show <name> - Show workflow details")
|
||||
print(" praisonai workflow create <name> - Create a new workflow")
|
||||
print(" praisonai workflow validate <file.yaml> - Validate a YAML workflow")
|
||||
print(" praisonai workflow template <name> - Create from template")
|
||||
print("\n[bold]Templates:[/bold]")
|
||||
print(" simple, routing, parallel, loop, evaluator-optimizer")
|
||||
print("\n[bold]Options (uses global flags):[/bold]")
|
||||
print(" --workflow-var key=value - Set workflow variable (can be repeated)")
|
||||
print(" --var key=value - Set variable for YAML workflows")
|
||||
print(" --llm <model> - LLM model (e.g., openai/gpt-4o-mini)")
|
||||
print(" --tools <tools> - Tools (comma-separated, e.g., tavily)")
|
||||
print(" --planning - Enable planning mode")
|
||||
print(" --memory - Enable memory")
|
||||
print(" --verbose - Enable verbose output")
|
||||
print(" --save - Save output to file")
|
||||
print("\n[bold]Example:[/bold]")
|
||||
print("\n[bold]Examples:[/bold]")
|
||||
print(" praisonai workflow run 'Research Blog' --tools tavily --save")
|
||||
print(" praisonai workflow run research.yaml --var topic='AI trends'")
|
||||
print(" praisonai workflow template routing --output my_workflow.yaml")
|
||||
else:
|
||||
print(f"[red]Unknown workflow action: {action}[/red]")
|
||||
print("Use 'praisonai workflow help' for available commands")
|
||||
@@ -1633,6 +1667,178 @@ class PraisonAI:
|
||||
except Exception as e:
|
||||
print(f"[red]ERROR: Workflow command failed: {e}[/red]")
|
||||
|
||||
def _run_yaml_workflow(self, yaml_file: str, action_args: list, variables: dict = None, args=None):
|
||||
"""
|
||||
Run a YAML workflow file using the new YAMLWorkflowParser.
|
||||
|
||||
Args:
|
||||
yaml_file: Path to the YAML workflow file
|
||||
action_args: Additional arguments
|
||||
variables: Workflow variables
|
||||
args: Parsed command line arguments
|
||||
"""
|
||||
try:
|
||||
from praisonaiagents.workflows import WorkflowManager
|
||||
from rich import print
|
||||
from rich.table import Table
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
manager = WorkflowManager()
|
||||
|
||||
# Parse --var arguments from action_args
|
||||
parsed_vars = variables or {}
|
||||
i = 0
|
||||
while i < len(action_args):
|
||||
if action_args[i] == "--var" and i + 1 < len(action_args):
|
||||
var_str = action_args[i + 1]
|
||||
if "=" in var_str:
|
||||
key, value = var_str.split("=", 1)
|
||||
parsed_vars[key.strip()] = value.strip()
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# Get verbose flag
|
||||
verbose = '--verbose' in action_args or '-v' in action_args or (getattr(args, 'verbose', False) if args else False)
|
||||
|
||||
print(f"[bold cyan]Running YAML workflow: {yaml_file}[/bold cyan]")
|
||||
if parsed_vars:
|
||||
print(f"[cyan]Variables: {parsed_vars}[/cyan]")
|
||||
|
||||
# Load and execute the YAML workflow
|
||||
workflow = manager.load_yaml(yaml_file)
|
||||
|
||||
# Show workflow info
|
||||
table = Table(title=f"Workflow: {workflow.name}")
|
||||
table.add_column("Property", style="cyan")
|
||||
table.add_column("Value", style="green")
|
||||
table.add_row("Steps", str(len(workflow.steps)))
|
||||
table.add_row("Planning", str(workflow.planning))
|
||||
table.add_row("Reasoning", str(workflow.reasoning))
|
||||
console.print(table)
|
||||
|
||||
# Merge variables
|
||||
if parsed_vars:
|
||||
workflow.variables.update(parsed_vars)
|
||||
|
||||
# Set verbose
|
||||
if verbose:
|
||||
workflow.verbose = True
|
||||
|
||||
# Execute
|
||||
print("\n[bold]Executing workflow...[/bold]\n")
|
||||
result = workflow.start("")
|
||||
|
||||
if result.get("status") == "completed":
|
||||
print("\n[green]✅ Workflow completed successfully![/green]")
|
||||
|
||||
# Show output
|
||||
if result.get("output"):
|
||||
print("\n[bold]Output:[/bold]")
|
||||
output = result["output"]
|
||||
if len(output) > 2000:
|
||||
print(output[:2000] + "...")
|
||||
else:
|
||||
print(output)
|
||||
else:
|
||||
print(f"\n[red]❌ Workflow failed: {result.get('error', 'Unknown error')}[/red]")
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"[red]ERROR: YAML file not found: {yaml_file}[/red]")
|
||||
except Exception as e:
|
||||
print(f"[red]ERROR: YAML workflow failed: {e}[/red]")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _validate_yaml_workflow(self, yaml_file: str):
|
||||
"""
|
||||
Validate a YAML workflow file.
|
||||
|
||||
Args:
|
||||
yaml_file: Path to the YAML workflow file
|
||||
"""
|
||||
try:
|
||||
from praisonaiagents.workflows import YAMLWorkflowParser
|
||||
from rich import print
|
||||
from rich.table import Table
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
if not os.path.exists(yaml_file):
|
||||
print(f"[red]ERROR: File not found: {yaml_file}[/red]")
|
||||
return
|
||||
|
||||
print(f"[cyan]Validating: {yaml_file}[/cyan]")
|
||||
|
||||
parser = YAMLWorkflowParser()
|
||||
workflow = parser.parse_file(yaml_file)
|
||||
|
||||
# Show validation results
|
||||
table = Table(title="Workflow Validation")
|
||||
table.add_column("Property", style="cyan")
|
||||
table.add_column("Value", style="green")
|
||||
|
||||
table.add_row("Name", workflow.name)
|
||||
table.add_row("Description", getattr(workflow, 'description', 'N/A'))
|
||||
table.add_row("Steps", str(len(workflow.steps)))
|
||||
table.add_row("Variables", str(len(workflow.variables)))
|
||||
table.add_row("Planning", str(workflow.planning))
|
||||
table.add_row("Reasoning", str(workflow.reasoning))
|
||||
|
||||
console.print(table)
|
||||
print("[green]✓ Workflow is valid![/green]")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[red]✗ Validation failed: {e}[/red]")
|
||||
|
||||
def _create_workflow_from_template(self, template_name: str = None, output_file: str = None):
|
||||
"""
|
||||
Create a workflow from a template.
|
||||
|
||||
Uses templates from WorkflowHandler to avoid duplication.
|
||||
|
||||
Args:
|
||||
template_name: Name of the template
|
||||
output_file: Output file path
|
||||
"""
|
||||
from rich import print
|
||||
|
||||
# Use templates from WorkflowHandler to avoid duplication
|
||||
try:
|
||||
from .features.workflow import WorkflowHandler
|
||||
templates = WorkflowHandler.TEMPLATES
|
||||
except ImportError:
|
||||
print("[red]ERROR: WorkflowHandler not available.[/red]")
|
||||
return
|
||||
|
||||
if not template_name:
|
||||
print("[red]ERROR: Template name required.[/red]")
|
||||
print(f"[cyan]Available templates: {', '.join(templates.keys())}[/cyan]")
|
||||
return
|
||||
|
||||
if template_name not in templates:
|
||||
print(f"[red]ERROR: Unknown template: {template_name}[/red]")
|
||||
print(f"[cyan]Available templates: {', '.join(templates.keys())}[/cyan]")
|
||||
return
|
||||
|
||||
# Default output file
|
||||
if not output_file:
|
||||
output_file = f"{template_name}_workflow.yaml"
|
||||
|
||||
# Check if file exists
|
||||
if os.path.exists(output_file):
|
||||
print(f"[red]ERROR: File already exists: {output_file}[/red]")
|
||||
return
|
||||
|
||||
# Write template
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(templates[template_name])
|
||||
|
||||
print(f"[green]✓ Created workflow: {output_file}[/green]")
|
||||
print(f"[cyan]Run with: praisonai workflow run {output_file}[/cyan]")
|
||||
|
||||
def handle_hooks_command(self, action: str):
|
||||
"""
|
||||
Handle hooks subcommand actions.
|
||||
|
||||
@@ -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.20 gunicorn markdown\n")
|
||||
file.write("RUN pip install flask praisonai==2.3.26 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.20"
|
||||
__version__ = "2.3.26"
|
||||
@@ -12,7 +12,7 @@ dependencies = [
|
||||
"rich>=13.7",
|
||||
"markdown>=3.5",
|
||||
"pyparsing>=3.0.0",
|
||||
"praisonaiagents>=0.0.198",
|
||||
"praisonaiagents>=0.1.4",
|
||||
"python-dotenv>=0.19.0",
|
||||
"instructor>=1.3.3",
|
||||
"PyYAML>=6.0",
|
||||
|
||||
@@ -224,7 +224,7 @@ def run_tests(pattern=None, verbose=False, coverage=False):
|
||||
# Run only fast, non-integration tests
|
||||
cmd.extend(["tests/unit/", "-m", "not slow"])
|
||||
elif pattern == "all":
|
||||
cmd.append("tests/")
|
||||
cmd.extend(["tests/", "-m", "not real"]) # Exclude real tests that require API keys
|
||||
elif pattern == "frameworks":
|
||||
# Run both AutoGen and CrewAI integration tests (mock)
|
||||
cmd.extend(["tests/integration/autogen/", "tests/integration/crewai/"])
|
||||
|
||||
@@ -270,9 +270,10 @@ class TestSessionHandler:
|
||||
from praisonai.cli.features.session import SessionHandler
|
||||
handler = SessionHandler()
|
||||
actions = handler.get_actions()
|
||||
assert "start" in actions
|
||||
assert "list" in actions
|
||||
assert "show" in actions
|
||||
assert "resume" in actions
|
||||
assert "delete" in actions
|
||||
|
||||
|
||||
class TestToolsHandler:
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
Unit tests for Workflow CLI commands.
|
||||
|
||||
Tests the WorkflowHandler CLI feature for YAML workflow management.
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
||||
class TestWorkflowHandler:
|
||||
"""Tests for WorkflowHandler CLI feature."""
|
||||
|
||||
def test_workflow_handler_import(self):
|
||||
"""Test that WorkflowHandler can be imported."""
|
||||
from praisonai.cli.features import WorkflowHandler
|
||||
assert WorkflowHandler is not None
|
||||
|
||||
def test_workflow_handler_initialization(self):
|
||||
"""Test WorkflowHandler initialization."""
|
||||
from praisonai.cli.features.workflow import WorkflowHandler
|
||||
handler = WorkflowHandler(verbose=True)
|
||||
assert handler.feature_name == "workflow"
|
||||
|
||||
def test_workflow_get_actions(self):
|
||||
"""Test getting available actions."""
|
||||
from praisonai.cli.features.workflow import WorkflowHandler
|
||||
handler = WorkflowHandler()
|
||||
actions = handler.get_actions()
|
||||
assert "run" in actions
|
||||
assert "validate" in actions
|
||||
assert "list" in actions
|
||||
assert "create" in actions
|
||||
|
||||
def test_workflow_templates_exist(self):
|
||||
"""Test that workflow templates are defined."""
|
||||
from praisonai.cli.features.workflow import WorkflowHandler
|
||||
handler = WorkflowHandler()
|
||||
assert "simple" in handler.TEMPLATES
|
||||
assert "routing" in handler.TEMPLATES
|
||||
assert "parallel" in handler.TEMPLATES
|
||||
assert "loop" in handler.TEMPLATES
|
||||
assert "evaluator-optimizer" in handler.TEMPLATES
|
||||
|
||||
def test_workflow_create_template(self):
|
||||
"""Test creating a workflow from template."""
|
||||
from praisonai.cli.features.workflow import WorkflowHandler
|
||||
handler = WorkflowHandler()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
output_file = os.path.join(tmpdir, "test_workflow.yaml")
|
||||
result = handler.action_create(
|
||||
["--template", "simple", "--output", output_file]
|
||||
)
|
||||
|
||||
assert result == output_file
|
||||
assert os.path.exists(output_file)
|
||||
|
||||
# Check content
|
||||
with open(output_file, 'r') as f:
|
||||
content = f.read()
|
||||
assert "name: Simple Workflow" in content
|
||||
assert "agents:" in content
|
||||
assert "steps:" in content
|
||||
|
||||
def test_workflow_validate(self):
|
||||
"""Test validating a YAML workflow."""
|
||||
from praisonai.cli.features.workflow import WorkflowHandler
|
||||
handler = WorkflowHandler()
|
||||
|
||||
yaml_content = """
|
||||
name: Test Workflow
|
||||
agents:
|
||||
agent1:
|
||||
name: Agent1
|
||||
role: Worker
|
||||
goal: Work
|
||||
instructions: "Do work"
|
||||
|
||||
steps:
|
||||
- agent: agent1
|
||||
action: "Work"
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
|
||||
f.write(yaml_content)
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
result = handler.action_validate([temp_path])
|
||||
assert result == True
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
|
||||
def test_workflow_validate_invalid_file(self):
|
||||
"""Test validating a non-existent file."""
|
||||
from praisonai.cli.features.workflow import WorkflowHandler
|
||||
handler = WorkflowHandler()
|
||||
|
||||
result = handler.action_validate(["nonexistent.yaml"])
|
||||
assert result == False
|
||||
|
||||
def test_workflow_help_text(self):
|
||||
"""Test help text contains expected commands."""
|
||||
from praisonai.cli.features.workflow import WorkflowHandler
|
||||
handler = WorkflowHandler()
|
||||
help_text = handler.get_help_text()
|
||||
|
||||
assert "run" in help_text
|
||||
assert "validate" in help_text
|
||||
assert "list" in help_text
|
||||
assert "create" in help_text
|
||||
assert "template" in help_text
|
||||
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.0.198" },
|
||||
{ name = "praisonaiagents", specifier = ">=0.1.4" },
|
||||
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = "==0.2.29" },
|
||||
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
|
||||
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
|
||||
@@ -4252,7 +4252,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.198"
|
||||
version = "0.1.4"
|
||||
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/68/bb/a3daf58206327a4cc92abed7295b41c03f9c4a8f3411d9435c633eb45b7f/praisonaiagents-0.0.198.tar.gz", hash = "sha256:d02180d476dd3ea260ade633764f6f8831dddc550b17ffb21eed79809166d315", size = 523691, upload-time = "2025-12-16T23:46:10.247Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ab/9d/d286e0ba5421cb8a37ea86f4714a7fbcb39fe1f38e9a202cf0cf6381ae8c/praisonaiagents-0.1.4.tar.gz", hash = "sha256:d00a326ac65701bcc66c44aba7d1616ef1317a2491034a242ad4290f0269e190", size = 560571, upload-time = "2025-12-17T11:43:41.829Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/cf/704504b3f5f3c9fc5674a0514ebea294fd4ffe7d040aae86d8af91dc0446/praisonaiagents-0.0.198-py3-none-any.whl", hash = "sha256:9011703eca1474fbef0a7208e0f875f1b62ff19bfaf7806fa25ccf7afb5c65a0", size = 499909, upload-time = "2025-12-16T23:46:08.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/90/b6429a58f75aeec4da0034d7594863b3fd6e4bf916dbcbeb53a8597f0f4a/praisonaiagents-0.1.4-py3-none-any.whl", hash = "sha256:c7ad9b64e3d3a24b9560d695bc7447f286928e52f0979e84d02484662cb8a49d", size = 524190, upload-time = "2025-12-17T11:43:40.119Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user