mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-12 01:41:26 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a14ca673da | ||
|
|
02d9d1e04f | ||
|
|
d3ee549ccc | ||
|
|
63fbd04af3 | ||
|
|
51f95ad904 | ||
|
|
44b800df0e | ||
|
|
6e3a99b8b6 | ||
|
|
b7d53b8f11 | ||
|
|
643fac5762 | ||
|
|
92b71cbc8f | ||
|
|
d7555ba1b2 | ||
|
|
22dd13ec66 | ||
|
|
ba40e818e4 | ||
|
|
12f829321c | ||
|
|
149ed5bd94 | ||
|
|
15f2937dd5 | ||
|
|
d28199a18d | ||
|
|
0d3d6bb852 | ||
|
|
5f93f83c9f | ||
|
|
c9786fbd1e | ||
|
|
95e8e38998 | ||
|
|
73f8961e61 | ||
|
|
4caf10a982 | ||
|
|
fb42a1010a | ||
|
|
fe60dd40af | ||
|
|
7f37d754a7 | ||
|
|
26f60fb056 | ||
|
|
0f0b640184 | ||
|
|
d857dfc6b7 | ||
|
|
0135a9e09f | ||
|
|
847b5e6372 | ||
|
|
05a7265c4e | ||
|
|
de1734c29a | ||
|
|
f0e91b11ae | ||
|
|
6e1a9143e2 | ||
|
|
10d76d3a85 | ||
|
|
a38a9c06c6 | ||
|
|
9c90416ab5 | ||
|
|
efa5247344 | ||
|
|
720213d82d | ||
|
|
f6af16ef5e | ||
|
|
8439b98ec2 | ||
|
|
92271fc118 | ||
|
|
1b43dc6a4b | ||
|
|
929cefa55e | ||
|
|
225843e39b | ||
|
|
77913183a9 | ||
|
|
e5b5d747f4 | ||
|
|
8cb779c51c | ||
|
|
020ca46c90 | ||
|
|
557e243eed | ||
|
|
0628204fce | ||
|
|
2a11bf5221 | ||
|
|
ecc2182d24 | ||
|
|
c6b91b2d57 | ||
|
|
a5ed17945f | ||
|
|
ff3ca3052d | ||
|
|
6f1f4a5363 | ||
|
|
dd9dfa5970 | ||
|
|
5f4fc8fa2f | ||
|
|
dd9dd5d5d9 | ||
|
|
d2c629b82c | ||
|
|
609ac5f7a6 | ||
|
|
75c72e909e | ||
|
|
9492ff8de1 | ||
|
|
a0a3aa1a53 | ||
|
|
6a20001551 | ||
|
|
577295c147 | ||
|
|
06f64a551c | ||
|
|
928b824180 | ||
|
|
1631973256 |
No files matched your search
@@ -0,0 +1,145 @@
|
||||
name: Performance Benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
iterations:
|
||||
description: 'Number of benchmark iterations'
|
||||
required: false
|
||||
default: '100'
|
||||
fail_on_regression:
|
||||
description: 'Fail if performance regresses'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e src/praisonai-agents[all]
|
||||
pip install agno crewai langgraph
|
||||
|
||||
- name: Run Import Time Benchmark
|
||||
id: import_time
|
||||
run: |
|
||||
cd src/praisonai-agents
|
||||
python benchmarks/import_time.py 2>&1 | tee import_time_output.txt
|
||||
|
||||
# Extract import time
|
||||
IMPORT_TIME=$(grep -oP 'Median:\s+\K[\d.]+' import_time_output.txt || echo "0")
|
||||
echo "import_time=$IMPORT_TIME" >> $GITHUB_OUTPUT
|
||||
|
||||
# Check threshold (200ms)
|
||||
if (( $(echo "$IMPORT_TIME > 200" | bc -l) )); then
|
||||
echo "::warning::Import time ${IMPORT_TIME}ms exceeds 200ms threshold"
|
||||
if [ "${{ inputs.fail_on_regression }}" == "true" ]; then
|
||||
echo "import_passed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
echo "import_passed=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run Memory Usage Benchmark
|
||||
id: memory
|
||||
run: |
|
||||
cd src/praisonai-agents
|
||||
python benchmarks/memory_usage.py 2>&1 | tee memory_output.txt
|
||||
|
||||
# Extract peak memory
|
||||
PEAK_MEMORY=$(grep -oP 'Peak:\s+\K[\d.]+' memory_output.txt || echo "0")
|
||||
echo "peak_memory=$PEAK_MEMORY" >> $GITHUB_OUTPUT
|
||||
|
||||
# Check threshold (30MB)
|
||||
if (( $(echo "$PEAK_MEMORY > 30" | bc -l) )); then
|
||||
echo "::warning::Memory usage ${PEAK_MEMORY}MB exceeds 30MB threshold"
|
||||
if [ "${{ inputs.fail_on_regression }}" == "true" ]; then
|
||||
echo "memory_passed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
echo "memory_passed=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run Simple Benchmark
|
||||
id: simple
|
||||
run: |
|
||||
cd src/praisonai-agents
|
||||
python benchmarks/simple_benchmark.py 2>&1 | tee simple_output.txt
|
||||
|
||||
# Extract PraisonAI time
|
||||
PRAISON_TIME=$(grep -oP 'PraisonAI\s+\K[\d.]+' simple_output.txt | head -1 || echo "0")
|
||||
echo "praison_time=$PRAISON_TIME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run Tools Benchmark
|
||||
id: tools
|
||||
run: |
|
||||
cd src/praisonai-agents
|
||||
python benchmarks/tools_benchmark.py 2>&1 | tee tools_output.txt
|
||||
|
||||
# Extract PraisonAI time with tools
|
||||
PRAISON_TOOLS_TIME=$(grep -oP 'PraisonAI\s+\K[\d.]+' tools_output.txt | head -1 || echo "0")
|
||||
echo "praison_tools_time=$PRAISON_TOOLS_TIME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run Parameter Impact Benchmark
|
||||
id: param_impact
|
||||
run: |
|
||||
cd src/praisonai-agents
|
||||
python benchmarks/param_impact_benchmark.py --no-save --iterations ${{ inputs.iterations }} 2>&1 | tee param_impact_output.txt
|
||||
|
||||
- name: Check Lazy Imports
|
||||
id: lazy_imports
|
||||
run: |
|
||||
cd src/praisonai-agents
|
||||
python -c "
|
||||
import sys
|
||||
from praisonaiagents import Agent
|
||||
agent = Agent(name='Test', output='silent')
|
||||
|
||||
# Check litellm is NOT loaded
|
||||
if 'litellm' in sys.modules:
|
||||
print('FAIL: litellm loaded during silent agent creation')
|
||||
exit(1)
|
||||
else:
|
||||
print('PASS: litellm not loaded (lazy import working)')
|
||||
"
|
||||
|
||||
- name: Generate Benchmark Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Performance Benchmark Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Metric | Value | Threshold | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|--------|-------|-----------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Import Time | ${{ steps.import_time.outputs.import_time }}ms | <200ms | ${{ steps.import_time.outputs.import_passed == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Peak Memory | ${{ steps.memory.outputs.peak_memory }}MB | <30MB | ${{ steps.memory.outputs.memory_passed == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Agent Instantiation | ${{ steps.simple.outputs.praison_time }}μs | - | ℹ️ |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Agent with Tools | ${{ steps.tools.outputs.praison_tools_time }}μs | - | ℹ️ |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Lazy Import Check" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ litellm not loaded during silent agent creation" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Fail on Regression
|
||||
if: inputs.fail_on_regression == 'true'
|
||||
run: |
|
||||
if [ "${{ steps.import_time.outputs.import_passed }}" == "false" ]; then
|
||||
echo "::error::Import time regression detected"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ steps.memory.outputs.memory_passed }}" == "false" ]; then
|
||||
echo "::error::Memory usage regression detected"
|
||||
exit 1
|
||||
fi
|
||||
echo "All performance checks passed!"
|
||||
@@ -137,7 +137,6 @@ PraisonAI Agents is the **fastest AI agent framework** for agent instantiation.
|
||||
| PraisonAI (LiteLLM) | 7.56 | 2.00x |
|
||||
| PydanticAI | 226.94 | 60.16x |
|
||||
| LangGraph | 4,558.71 | 1,209x |
|
||||
| CrewAI | 15,607.92 | 4,138x |
|
||||
|
||||
<details>
|
||||
<summary>Run benchmarks yourself</summary>
|
||||
|
||||
@@ -3,11 +3,27 @@
|
||||
This file is auto-generated. Do not edit manually.
|
||||
Regenerate with: `praisonai docs api-md --write`
|
||||
|
||||
# Shared Types
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import ContextPolicy, GuardrailResult, HandoffConfig, HandoffCycleError, HandoffDepthError, HandoffError, HandoffInputData, HandoffResult, HandoffTimeoutError, ReflectionOutput, StepResult, TaskOutput, ToolResult, ToolValidationError, WorkflowContext
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class GuardrailResult">GuardrailResult.<a href="./src/praisonai-agents/praisonaiagents/guardrails/guardrail_result.py">from_tuple</a>(result: Tuple[bool, Any]) -> 'GuardrailResult'</code>
|
||||
* <code title="class HandoffConfig">HandoffConfig.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">from_dict</a>(data: Dict[str, Any]) -> 'HandoffConfig'</code>
|
||||
* <code title="class HandoffConfig">HandoffConfig.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class TaskOutput">TaskOutput.<a href="./src/praisonai-agents/praisonaiagents/main.py">json</a>() -> Optional[str]</code>
|
||||
* <code title="class TaskOutput">TaskOutput.<a href="./src/praisonai-agents/praisonaiagents/main.py">to_dict</a>() -> dict</code>
|
||||
* <code title="class ToolResult">ToolResult.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Agents
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Agent, Agents
|
||||
from praisonaiagents import Agent, AutoAgents, AutoRagAgent, ContextAgent, DeepResearchAgent, ImageAgent, PlanningAgent, PromptExpanderAgent, QueryRewriterAgent, create_context_agent
|
||||
```
|
||||
|
||||
Methods:
|
||||
@@ -22,7 +38,7 @@ Methods:
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">auto_memory</a>(value)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">background</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">background</a>(value)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">chat</a>(prompt, temperature = 1.0, tools = None, output_json = None, output_pydantic = None, reasoning_steps = False, stream = None, task_name = None, task_description = None, task_id = None, config = None, force_retrieval = False, skip_retrieval = False, attachments = None)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">chat</a>(prompt, temperature = 1.0, tools = None, output_json = None, output_pydantic = None, reasoning_steps = False, stream = None, task_name = None, task_description = None, task_id = None, config = None, force_retrieval = False, skip_retrieval = False, attachments = None, tool_choice = None)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">chat_with_context</a>(message: str, context: 'ContextPack', **kwargs) -> str</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">checkpoints</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">checkpoints</a>(value)</code>
|
||||
@@ -33,7 +49,6 @@ Methods:
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">context_manager</a>(value)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">delete_history</a>(index: int) -> bool</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">delete_history_matching</a>(pattern: str) -> int</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">display_generating</a>(content: str, start_time: float)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">display_name</a>() -> str</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">ephemeral</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">execute</a>(task, context = None)</code>
|
||||
@@ -64,87 +79,666 @@ Methods:
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">retrieve</a>(query: str, **kwargs) -> 'ContextPack'</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">rules_manager</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">run</a>(prompt: str, **kwargs)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">run_autonomous</a>(prompt: str, max_iterations: Optional[int] = None, timeout_seconds: Optional[float] = None)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">run_autonomous</a>(prompt: str, max_iterations: Optional[int] = None, timeout_seconds: Optional[float] = None, completion_promise: Optional[str] = None, clear_context: bool = False)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">run_autonomous_async</a>(prompt: str, max_iterations: Optional[int] = None, timeout_seconds: Optional[float] = None, completion_promise: Optional[str] = None, clear_context: bool = False)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">session_id</a>() -> Optional[str]</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">skill_manager</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">start</a>(prompt: str = None, **kwargs)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">store_memory</a>(content: str, memory_type: str = 'short_term', **kwargs)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">stream_emitter</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">stream_emitter</a>(value)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">switch_model</a>(new_model: str) -> None</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">thinking_budget</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">thinking_budget</a>(value)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">add_task</a>(task)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">aexecute_task</a>(task_id)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">append_to_state</a>(key: str, value: Any, max_length: Optional[int] = None) -> List[Any]</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_all_tasks</a>()</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_task</a>(task_id)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">astart</a>(content = None, return_dict = False, **kwargs)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clean_json_output</a>(output: str) -> str</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clear_state</a>() -> None</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">context_manager</a>()</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">current_plan</a>()</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">default_completion_checker</a>(task, agent_output)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">delete_state</a>(key: str) -> bool</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">display_token_usage</a>()</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">execute_task</a>(task_id)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_agent_details</a>(agent_name)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_state</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_tasks_status</a>()</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_detailed_token_report</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_plan_markdown</a>() -> str</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_state</a>(key: str, default: Any = None) -> Any</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_details</a>(task_id)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_result</a>(task_id)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_status</a>(task_id)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_todo_markdown</a>() -> str</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_token_usage_summary</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">has_state</a>(key: str) -> bool</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">increment_state</a>(key: str, amount: float = 1, default: float = 0) -> float</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">launch</a>(path: str = '/agents', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = 'http')</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">restore_session_state</a>(session_id: str) -> bool</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run</a>(content = None, return_dict = False, **kwargs)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run_all_tasks</a>()</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run_task</a>(task_id)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_output_to_file</a>(task, task_output)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_session_state</a>(session_id: str, include_memory: bool = True) -> None</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">set_state</a>(key: str, value: Any) -> None</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, output = None, **kwargs)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">todo_list</a>()</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_plan_step_status</a>(step_id: str, status: str) -> bool</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_state</a>(updates: Dict) -> None</code>
|
||||
* <code title="class AutoAgents">AutoAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/autoagents.py">astart</a>()</code>
|
||||
* <code title="class AutoAgents">AutoAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/autoagents.py">start</a>()</code>
|
||||
* <code title="class AutoRagAgent">AutoRagAgent.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">achat</a>(message: str, **kwargs) -> str</code>
|
||||
* <code title="class AutoRagAgent">AutoRagAgent.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">chat</a>(message: str, **kwargs) -> str</code>
|
||||
* <code title="class AutoRagAgent">AutoRagAgent.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">name</a>() -> str</code>
|
||||
* <code title="class AutoRagAgent">AutoRagAgent.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">rag</a>() -> Optional['RAG']</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">aanalyze_codebase</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">acreate_implementation_blueprint</a>(feature_request: str, context_analysis: Optional[Dict[str, Any]] = None) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">agenerate_prp</a>(feature_request: str, context_analysis: Optional[Dict[str, Any]] = None) -> str</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">analyze_codebase</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">analyze_codebase_with_gitingest</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">analyze_integration_points</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">analyze_test_patterns</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">build_implementation_blueprint</a>(feature_request: str, context_analysis: Dict[str, Any] = None) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">compile_context_documentation</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">create_implementation_blueprint</a>(feature_request: str, context_analysis: Optional[Dict[str, Any]] = None) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">create_quality_gates</a>(requirements: List[str]) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">create_validation_framework</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">execute_prp</a>(prp_file_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">extract_implementation_patterns</a>(project_path: str, ast_analysis: Dict[str, Any] = None) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">generate_comprehensive_prp</a>(feature_request: str, context_analysis: Dict[str, Any] = None) -> str</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">generate_feature_prp</a>(feature_request: str) -> str</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">generate_prp</a>(feature_request: str, context_analysis: Optional[Dict[str, Any]] = None) -> str</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">get_agent_interaction_summary</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">log_debug</a>(message: str, **kwargs)</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">perform_ast_analysis</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">save_comprehensive_session_report</a>()</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">save_markdown_output</a>(content: str, filename: str, section_title: str = 'Output')</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">setup_logging</a>()</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">setup_output_directories</a>()</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">start</a>(input_text: str) -> str</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">aresearch</a>(query: str, instructions: Optional[str] = None, model: Optional[str] = None, summary_mode: Optional[Literal['auto', 'detailed', 'concise']] = None, web_search: Optional[bool] = None, code_interpreter: Optional[bool] = None, mcp_servers: Optional[List[Dict[str, Any]]] = None, file_ids: Optional[List[str]] = None, file_search: Optional[bool] = None, file_search_stores: Optional[List[str]] = None) -> DeepResearchResponse</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">async_openai_client</a>()</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">clarify</a>(query: str, model: Optional[str] = None) -> str</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">follow_up</a>(query: str, previous_interaction_id: str, model: Optional[str] = None) -> DeepResearchResponse</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">gemini_client</a>()</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">openai_client</a>()</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">research</a>(query: str, instructions: Optional[str] = None, model: Optional[str] = None, summary_mode: Optional[Literal['auto', 'detailed', 'concise']] = None, web_search: Optional[bool] = None, code_interpreter: Optional[bool] = None, mcp_servers: Optional[List[Dict[str, Any]]] = None, file_ids: Optional[List[str]] = None, file_search: Optional[bool] = None, file_search_stores: Optional[List[str]] = None, stream: bool = True) -> DeepResearchResponse</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">rewrite_query</a>(query: str, model: Optional[str] = None) -> str</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">achat</a>(prompt: str, temperature: float = 0.2, tools: Optional[List[Any]] = None, output_json: Optional[str] = None, output_pydantic: Optional[Any] = None, reasoning_steps: bool = False, **kwargs) -> Union[str, Dict[str, Any]]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">aedit</a>(image: str, prompt: str, mask: Optional[str] = None, n: int = 1, size: Optional[str] = None, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">agenerate</a>(prompt: str, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">agenerate_image</a>(prompt: str, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">avariation</a>(image: str, n: int = 1, size: Optional[str] = None, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">chat</a>(prompt: str, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">edit</a>(image: str, prompt: str, mask: Optional[str] = None, n: int = 1, size: Optional[str] = None, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">generate</a>(prompt: str, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">generate_image</a>(prompt: str, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">litellm</a>()</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">variation</a>(image: str, n: int = 1, size: Optional[str] = None, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class PlanningAgent">PlanningAgent.<a href="./src/praisonai-agents/praisonaiagents/planning/planner.py">analyze_context</a>(context: str) -> str</code>
|
||||
* <code title="class PlanningAgent">PlanningAgent.<a href="./src/praisonai-agents/praisonaiagents/planning/planner.py">analyze_context_sync</a>(context: str) -> str</code>
|
||||
* <code title="class PlanningAgent">PlanningAgent.<a href="./src/praisonai-agents/praisonaiagents/planning/planner.py">create_plan</a>(request: str, agents: List['Agent'], tasks: Optional[List['Task']] = None, context: Optional[str] = None) -> Plan</code>
|
||||
* <code title="class PlanningAgent">PlanningAgent.<a href="./src/praisonai-agents/praisonaiagents/planning/planner.py">create_plan_sync</a>(request: str, agents: List['Agent'], tasks: Optional[List['Task']] = None, context: Optional[str] = None) -> Plan</code>
|
||||
* <code title="class PlanningAgent">PlanningAgent.<a href="./src/praisonai-agents/praisonaiagents/planning/planner.py">is_tool_allowed</a>(tool_name: str) -> bool</code>
|
||||
* <code title="class PlanningAgent">PlanningAgent.<a href="./src/praisonai-agents/praisonaiagents/planning/planner.py">refine_plan</a>(plan: Plan, feedback: str) -> Plan</code>
|
||||
* <code title="class PlanningAgent">PlanningAgent.<a href="./src/praisonai-agents/praisonaiagents/planning/planner.py">refine_plan_sync</a>(plan: Plan, feedback: str) -> Plan</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">agent</a>()</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand</a>(prompt: str, strategy: ExpandStrategy = ..., context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand_basic</a>(prompt: str, context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand_creative</a>(prompt: str, context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand_detailed</a>(prompt: str, context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand_structured</a>(prompt: str, context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">add_abbreviation</a>(abbrev: str, expansion: str) -> None</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">add_abbreviations</a>(abbreviations: Dict[str, str]) -> None</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">agent</a>()</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite</a>(query: str, strategy: RewriteStrategy = ..., chat_history: Optional[List[Dict[str, str]]] = None, context: Optional[str] = None, num_queries: int = None) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_basic</a>(query: str) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_contextual</a>(query: str, chat_history: List[Dict[str, str]]) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_hyde</a>(query: str) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_multi_query</a>(query: str, num_queries: int = None) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_step_back</a>(query: str) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_sub_queries</a>(query: str) -> RewriteResult</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">create_context_agent</a>(llm: Optional[Union[str, Any]] = None, **kwargs) -> ContextAgent</code>
|
||||
|
||||
# Tools
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Tools, tool
|
||||
from praisonaiagents import BaseTool, FunctionTool, ToolRegistry, Tools, get_registry, get_tool, register_tool, tool, validate_tool
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">__call__</a>(**kwargs) -> Any</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">get_schema</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">run</a>(**kwargs) -> Any</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">safe_run</a>(**kwargs) -> ToolResult</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate</a>() -> bool</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate_class</a>() -> bool</code>
|
||||
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">__call__</a>(*args, **kwargs) -> Any</code>
|
||||
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">injected_params</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">run</a>(**kwargs) -> Any</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">clear</a>() -> None</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">discover_plugins</a>() -> int</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get</a>(name: str) -> Optional[Union[BaseTool, Callable]]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_all</a>() -> Dict[str, Union[BaseTool, Callable]]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">list_base_tools</a>() -> List[BaseTool]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">list_tools</a>() -> List[str]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">register</a>(tool: Union[BaseTool, Callable], name: Optional[str] = None, overwrite: bool = False) -> None</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">unregister</a>(name: str) -> bool</code>
|
||||
* <code title="class Tools">Tools.<a href="./src/praisonai-agents/praisonaiagents/tools/tools.py">internet_search</a>(*args, **kwargs)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_registry</a>() -> ToolRegistry</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_tool</a>(name: str) -> Optional[Union[BaseTool, Callable]]</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">register_tool</a>(tool: Union[BaseTool, Callable], name: Optional[str] = None) -> None</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">tool</a>(func: Optional[Callable] = None) -> Union[FunctionTool, Callable[[Callable], FunctionTool]]</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate_tool</a>(tool: Any) -> bool</code>
|
||||
|
||||
# Workflows
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Loop, Parallel, Pipeline, Repeat, Route, Workflow, loop, parallel, repeat, route
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">arun</a>(input: str = '', llm: Optional[str] = None, verbose: bool = False) -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">astart</a>(input: str = '', llm: Optional[str] = None, verbose: bool = False) -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">from_template</a>(uri: str, config: Optional[Dict[str, Any]] = None, offline: bool = False, **kwargs) -> 'Workflow'</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">memory_config</a>() -> Optional[Dict[str, Any]]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_step_complete</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_step_error</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_step_start</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_workflow_complete</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_workflow_start</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">planning_llm</a>() -> Optional[str]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">reasoning</a>() -> bool</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">run</a>(input: str = '', llm: Optional[str] = None, verbose: bool = False, stream: bool = None) -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">start</a>(input: str = '', **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">stream</a>() -> bool</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">verbose</a>() -> bool</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">verbose</a>(value: bool)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">loop</a>(step: Any = None, steps: Optional[List[Any]] = None, over: Optional[str] = None, from_csv: Optional[str] = None, from_file: Optional[str] = None, var_name: str = 'item', parallel: bool = False, max_workers: Optional[int] = None, output_variable: Optional[str] = None) -> Loop</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">parallel</a>(steps: List) -> Parallel</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">repeat</a>(step: Any, until: Optional[Callable[[WorkflowContext], bool]] = None, max_iterations: int = 10) -> Repeat</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">route</a>(routes: Dict[str, List], default: Optional[List] = None) -> Route</code>
|
||||
|
||||
# DB
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import db
|
||||
```
|
||||
|
||||
# Memory
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Memory, MemoryBackend, MemoryConfig
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">build_context_for_task</a>(task_descr: str, user_id: Optional[str] = None, additional: str = '', max_items: int = 3, include_in_output: Optional[bool] = None) -> str</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">calculate_quality_metrics</a>(output: str, expected_output: str, llm: Optional[str] = None, custom_prompt: Optional[str] = None) -> Dict[str, float]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">compute_quality_score</a>(completeness: float, relevance: float, clarity: float, accuracy: float, weights: Dict[str, float] = None) -> float</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">delete_long_term</a>(memory_id: str) -> bool</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">delete_memories</a>(memory_ids: List[str]) -> int</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">delete_memories_matching</a>(query: str, memory_type: Optional[str] = None, limit: int = 10) -> int</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">delete_memory</a>(memory_id: str, memory_type: Optional[str] = None) -> bool</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">delete_short_term</a>(memory_id: str) -> bool</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">finalize_task_output</a>(content: str, agent_name: str, quality_score: float, threshold: float = 0.7, metrics: Dict[str, Any] = None, task_id: str = None)</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">get_all_memories</a>() -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">get_learn_context</a>() -> str</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">learn</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_all</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_entity_only</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_long_term</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_short_term</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_user_memory</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search</a>(query: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, limit: int = 5, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_entity</a>(query: str, limit: int = 5) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_long_term</a>(query: str, limit: int = 5, relevance_cutoff: float = 0.0, min_quality: float = 0.0, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_short_term</a>(query: str, limit: int = 5, min_quality: float = 0.0, relevance_cutoff: float = 0.0, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_user_memory</a>(user_id: str, query: str, limit: int = 5, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_with_quality</a>(query: str, min_quality: float = 0.0, memory_type: Literal['short', 'long'] = 'long', limit: int = 5) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_entity</a>(name: str, type_: str, desc: str, relations: str)</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_long_term</a>(text: str, metadata: Dict[str, Any] = None, completeness: float = None, relevance: float = None, clarity: float = None, accuracy: float = None, weights: Dict[str, float] = None, evaluator_quality: float = None)</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_quality</a>(text: str, quality_score: float, task_id: Optional[str] = None, iteration: Optional[int] = None, metrics: Optional[Dict[str, float]] = None, memory_type: Literal['short', 'long'] = 'long') -> None</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_short_term</a>(text: str, metadata: Dict[str, Any] = None, completeness: float = None, relevance: float = None, clarity: float = None, accuracy: float = None, weights: Dict[str, float] = None, evaluator_quality: float = None)</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_user_memory</a>(user_id: str, text: str, extra: Dict[str, Any] = None)</code>
|
||||
* <code title="class MemoryConfig">MemoryConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Knowledge
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Chunking, ChunkingStrategy, Knowledge, KnowledgeConfig
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">SUPPORTED_CHUNKERS</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">__call__</a>(text: Union[str, List[str]], **kwargs) -> Union[List[Any], List[List[Any]]]</code>
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">chunk</a>(text: Union[str, List[str]], **kwargs) -> Union[List[Any], List[List[Any]]]</code>
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">chunker</a>()</code>
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">embedding_model</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">add</a>(file_path, user_id = None, agent_id = None, run_id = None, metadata = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">chunker</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">config</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">delete</a>(memory_id)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">delete_all</a>(user_id = None, agent_id = None, run_id = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">get</a>(memory_id)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">get_all</a>(user_id = None, agent_id = None, run_id = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">get_corpus_stats</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">history</a>(memory_id)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">index</a>(path: str, incremental: bool = True, force: bool = False, include_glob: list = None, exclude_glob: list = None, user_id: str = None, agent_id: str = None, run_id: str = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">markdown</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">memory</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">normalize_content</a>(content)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">reset</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">search</a>(query, user_id = None, agent_id = None, run_id = None, rerank = None, **kwargs)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">store</a>(content, user_id = None, agent_id = None, run_id = None, metadata = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">update</a>(memory_id, data)</code>
|
||||
* <code title="class KnowledgeConfig">KnowledgeConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# RAG
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import CitationsMode, ContextPack, RAG, RAGCitation, RAGConfig, RAGResult, RetrievalConfig, RetrievalPolicy
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class ContextPack">ContextPack.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">format_for_prompt</a>(include_sources: bool = True) -> str</code>
|
||||
* <code title="class ContextPack">ContextPack.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'ContextPack'</code>
|
||||
* <code title="class ContextPack">ContextPack.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">has_citations</a>() -> bool</code>
|
||||
* <code title="class ContextPack">ContextPack.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">aquery</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> RAGResult</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">aretrieve</a>(query: str, **kwargs) -> ContextPack</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">astream</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> AsyncIterator[str]</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">get_citations</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> List[Citation]</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">llm</a>()</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">query</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> RAGResult</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">retrieve</a>(query: str, **kwargs) -> ContextPack</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">stream</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> Iterator[str]</code>
|
||||
* <code title="class RAGCitation">RAGCitation.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'Citation'</code>
|
||||
* <code title="class RAGCitation">RAGCitation.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class RAGConfig">RAGConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'RAGConfig'</code>
|
||||
* <code title="class RAGConfig">RAGConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class RAGResult">RAGResult.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">format_answer_with_citations</a>() -> str</code>
|
||||
* <code title="class RAGResult">RAGResult.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'RAGResult'</code>
|
||||
* <code title="class RAGResult">RAGResult.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">has_citations</a>() -> bool</code>
|
||||
* <code title="class RAGResult">RAGResult.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class RetrievalConfig">RetrievalConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/retrieval_config.py">from_dict</a>(data: Dict[str, Any]) -> 'RetrievalConfig'</code>
|
||||
* <code title="class RetrievalConfig">RetrievalConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/retrieval_config.py">get_strategy</a>(corpus_stats = None)</code>
|
||||
* <code title="class RetrievalConfig">RetrievalConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/retrieval_config.py">get_token_budget</a>(model_name: Optional[str] = None)</code>
|
||||
* <code title="class RetrievalConfig">RetrievalConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/retrieval_config.py">should_retrieve</a>(query: str, force: bool = False, skip: bool = False) -> bool</code>
|
||||
* <code title="class RetrievalConfig">RetrievalConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/retrieval_config.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class RetrievalConfig">RetrievalConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/retrieval_config.py">to_knowledge_config</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class RetrievalConfig">RetrievalConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/retrieval_config.py">to_rag_config</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Handoff
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Handoff, RECOMMENDED_PROMPT_PREFIX, handoff, handoff_filters, prompt_with_handoff_instructions
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">default_tool_description</a>() -> str</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">default_tool_name</a>() -> str</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">execute_async</a>(source_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None) -> HandoffResult</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">execute_programmatic</a>(source_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None) -> HandoffResult</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">to_tool_function</a>(source_agent: 'Agent') -> Callable</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">tool_description</a>() -> str</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">tool_name</a>() -> str</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">handoff</a>(agent: 'Agent', tool_name_override: Optional[str] = None, tool_description_override: Optional[str] = None, on_handoff: Optional[Callable] = None, input_type: Optional[type] = None, input_filter: Optional[Callable[[HandoffInputData], HandoffInputData]] = None, config: Optional[HandoffConfig] = None, context_policy: Optional[str] = None, timeout_seconds: Optional[float] = None, max_concurrent: Optional[int] = None, detect_cycles: Optional[bool] = None, max_depth: Optional[int] = None) -> Handoff</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">keep_last_n_messages</a>(n: int) -> Callable[[HandoffInputData], HandoffInputData]</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">remove_all_tools</a>(data: HandoffInputData) -> HandoffInputData</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">remove_system_messages</a>(data: HandoffInputData) -> HandoffInputData</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">prompt_with_handoff_instructions</a>(base_prompt: str, agent: 'Agent') -> str</code>
|
||||
|
||||
# Guardrails
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import GuardrailAction, GuardrailConfig, LLMGuardrail
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class GuardrailConfig">GuardrailConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class LLMGuardrail">LLMGuardrail.<a href="./src/praisonai-agents/praisonaiagents/guardrails/llm_guardrail.py">__call__</a>(task_output: TaskOutput) -> Tuple[bool, Union[str, TaskOutput]]</code>
|
||||
|
||||
# Planning
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import ApprovalCallback, Plan, PlanStep, PlanStorage, PlanningConfig, READ_ONLY_TOOLS, RESTRICTED_TOOLS, TodoItem, TodoList
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class ApprovalCallback">ApprovalCallback.<a href="./src/praisonai-agents/praisonaiagents/planning/approval.py">__call__</a>(plan: 'Plan') -> bool</code>
|
||||
* <code title="class ApprovalCallback">ApprovalCallback.<a href="./src/praisonai-agents/praisonaiagents/planning/approval.py">always_approve</a>(plan: 'Plan') -> bool</code>
|
||||
* <code title="class ApprovalCallback">ApprovalCallback.<a href="./src/praisonai-agents/praisonaiagents/planning/approval.py">always_reject</a>(plan: 'Plan') -> bool</code>
|
||||
* <code title="class ApprovalCallback">ApprovalCallback.<a href="./src/praisonai-agents/praisonaiagents/planning/approval.py">approve_if_no_dangerous_tools</a>(plan: 'Plan') -> bool</code>
|
||||
* <code title="class ApprovalCallback">ApprovalCallback.<a href="./src/praisonai-agents/praisonaiagents/planning/approval.py">approve_if_small</a>(plan: 'Plan', max_steps: int = 5) -> bool</code>
|
||||
* <code title="class ApprovalCallback">ApprovalCallback.<a href="./src/praisonai-agents/praisonaiagents/planning/approval.py">async_call</a>(plan: 'Plan') -> bool</code>
|
||||
* <code title="class ApprovalCallback">ApprovalCallback.<a href="./src/praisonai-agents/praisonaiagents/planning/approval.py">console_approval</a>(plan: 'Plan') -> bool</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">add_step</a>(step: PlanStep) -> None</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">approve</a>() -> None</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">cancel</a>() -> None</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">complete</a>() -> None</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">completed_step_ids</a>() -> List[str]</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">from_dict</a>(data: Dict[str, Any]) -> 'Plan'</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">from_markdown</a>(markdown: str) -> 'Plan'</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">get_next_steps</a>() -> List[PlanStep]</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">get_step</a>(step_id: str) -> Optional[PlanStep]</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">is_complete</a>() -> bool</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">progress</a>() -> float</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">remove_step</a>(step_id: str) -> bool</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">start_execution</a>() -> None</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">to_markdown</a>() -> str</code>
|
||||
* <code title="class Plan">Plan.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">update_step_status</a>(step_id: str, status: str) -> bool</code>
|
||||
* <code title="class PlanStep">PlanStep.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">from_dict</a>(data: Dict[str, Any]) -> 'PlanStep'</code>
|
||||
* <code title="class PlanStep">PlanStep.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">is_ready</a>(completed_steps: List[str]) -> bool</code>
|
||||
* <code title="class PlanStep">PlanStep.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">mark_complete</a>() -> None</code>
|
||||
* <code title="class PlanStep">PlanStep.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">mark_in_progress</a>() -> None</code>
|
||||
* <code title="class PlanStep">PlanStep.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">mark_skipped</a>() -> None</code>
|
||||
* <code title="class PlanStep">PlanStep.<a href="./src/praisonai-agents/praisonaiagents/planning/plan.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">cleanup_old_plans</a>(keep_count: int = 10) -> int</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">delete_plan</a>(plan_id: str) -> bool</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">delete_todo</a>(name: str) -> bool</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">get_latest_plan</a>() -> Optional[Plan]</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">list_plans</a>() -> List[Dict[str, Any]]</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">list_todos</a>() -> List[str]</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">load_plan</a>(plan_id: str) -> Optional[Plan]</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">load_plan_from_file</a>(path: str) -> Optional[Plan]</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">load_todo</a>(name: str = 'current') -> Optional[TodoList]</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">save_plan</a>(plan: Plan, filename: Optional[str] = None) -> str</code>
|
||||
* <code title="class PlanStorage">PlanStorage.<a href="./src/praisonai-agents/praisonaiagents/planning/storage.py">save_todo</a>(todo: TodoList, name: str = 'current') -> str</code>
|
||||
* <code title="class PlanningConfig">PlanningConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class TodoItem">TodoItem.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">complete</a>() -> None</code>
|
||||
* <code title="class TodoItem">TodoItem.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">from_dict</a>(data: Dict[str, Any]) -> 'TodoItem'</code>
|
||||
* <code title="class TodoItem">TodoItem.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">is_ready</a>(completed_ids: List[str]) -> bool</code>
|
||||
* <code title="class TodoItem">TodoItem.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">reset</a>() -> None</code>
|
||||
* <code title="class TodoItem">TodoItem.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">start</a>() -> None</code>
|
||||
* <code title="class TodoItem">TodoItem.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">add</a>(item: Union[TodoItem, str]) -> TodoItem</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">complete</a>(item_id: str) -> bool</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">completed</a>() -> List[TodoItem]</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">completed_ids</a>() -> List[str]</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">from_dict</a>(data: Dict[str, Any]) -> 'TodoList'</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">from_json</a>(json_str: str) -> 'TodoList'</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">from_markdown</a>(markdown: str) -> 'TodoList'</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">from_plan</a>(plan: 'Plan') -> 'TodoList'</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">get</a>(item_id: str) -> Optional[TodoItem]</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">get_ready_items</a>() -> List[TodoItem]</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">in_progress</a>() -> List[TodoItem]</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">is_complete</a>() -> bool</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">pending</a>() -> List[TodoItem]</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">progress</a>() -> float</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">remove</a>(item_id: str) -> bool</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">start</a>(item_id: str) -> bool</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">sync_with_plan</a>(plan: 'Plan') -> None</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">to_json</a>() -> str</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">to_markdown</a>() -> str</code>
|
||||
* <code title="class TodoList">TodoList.<a href="./src/praisonai-agents/praisonaiagents/planning/todo.py">update_from_plan</a>(plan: 'Plan') -> None</code>
|
||||
|
||||
# Skills
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import SkillLoader, SkillManager, SkillMetadata, SkillProperties, SkillsConfig
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">activate</a>(skill: LoadedSkill) -> bool</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load</a>(skill_path: str, activate: bool = False) -> Optional[LoadedSkill]</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_all_resources</a>(skill: LoadedSkill) -> None</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_assets</a>(skill: LoadedSkill) -> dict</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_metadata</a>(skill_path: str) -> Optional[LoadedSkill]</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_references</a>(skill: LoadedSkill) -> dict</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_scripts</a>(skill: LoadedSkill) -> dict</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">activate</a>(skill: LoadedSkill) -> bool</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">activate_by_name</a>(name: str) -> bool</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">add_skill</a>(skill_path: str) -> Optional[LoadedSkill]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">clear</a>() -> None</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">discover</a>(skill_dirs: Optional[List[str]] = None, include_defaults: bool = True) -> int</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_available_skills</a>() -> List[SkillMetadata]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_instructions</a>(name: str) -> Optional[str]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_skill</a>(name: str) -> Optional[LoadedSkill]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">load_resources</a>(name: str) -> bool</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">skill_names</a>() -> List[str]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">skills</a>() -> List[LoadedSkill]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">to_prompt</a>() -> str</code>
|
||||
* <code title="class SkillMetadata">SkillMetadata.<a href="./src/praisonai-agents/praisonaiagents/skills/models.py">from_properties</a>(props: SkillProperties) -> 'SkillMetadata'</code>
|
||||
* <code title="class SkillProperties">SkillProperties.<a href="./src/praisonai-agents/praisonaiagents/skills/models.py">to_dict</a>() -> dict</code>
|
||||
* <code title="class SkillsConfig">SkillsConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Session
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Session
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">Agent</a>(name: str, role: str = 'Assistant', instructions: Optional[str] = None, tools: Optional[List[Any]] = None, memory: bool = True, knowledge: Optional[List[str]] = None, **kwargs) -> 'Agent'</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">add_knowledge</a>(source: str) -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">add_memory</a>(text: str, memory_type: str = 'long', **metadata) -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">chat</a>(message: str, **kwargs) -> str</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">clear_memory</a>(memory_type: str = 'all') -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">create_agent</a>(*args, **kwargs) -> 'Agent'</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">get_context</a>(query: str, max_items: int = 3) -> str</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">get_state</a>(key: str, default: Any = None) -> Any</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">increment_state</a>(key: str, increment: int = 1, default: int = 0) -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">knowledge</a>() -> 'Knowledge'</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">memory</a>() -> 'Memory'</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">restore_state</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">save_state</a>(state_data: Dict[str, Any]) -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">search_knowledge</a>(query: str, limit: int = 5) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">search_memory</a>(query: str, memory_type: str = 'long', limit: int = 5) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">send_message</a>(message: str, **kwargs) -> str</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">set_state</a>(key: str, value: Any) -> None</code>
|
||||
|
||||
# MCP
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import MCP
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class MCP">MCP.<a href="./src/praisonai-agents/praisonaiagents/mcp/mcp.py">get_tools</a>() -> List[Callable]</code>
|
||||
* <code title="class MCP">MCP.<a href="./src/praisonai-agents/praisonaiagents/mcp/mcp.py">shutdown</a>()</code>
|
||||
* <code title="class MCP">MCP.<a href="./src/praisonai-agents/praisonaiagents/mcp/mcp.py">to_openai_tool</a>()</code>
|
||||
|
||||
# Telemetry
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import MinimalTelemetry, TelemetryCollector, cleanup_telemetry_resources, disable_performance_mode, disable_telemetry, enable_performance_mode, enable_telemetry, get_telemetry
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class MinimalTelemetry">MinimalTelemetry.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">flush</a>()</code>
|
||||
* <code title="class MinimalTelemetry">MinimalTelemetry.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">get_metrics</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class MinimalTelemetry">MinimalTelemetry.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">shutdown</a>()</code>
|
||||
* <code title="class MinimalTelemetry">MinimalTelemetry.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">track_agent_execution</a>(agent_name: str = None, success: bool = True, async_mode: bool = False)</code>
|
||||
* <code title="class MinimalTelemetry">MinimalTelemetry.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">track_error</a>(error_type: str = None)</code>
|
||||
* <code title="class MinimalTelemetry">MinimalTelemetry.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">track_feature_usage</a>(feature_name: str)</code>
|
||||
* <code title="class MinimalTelemetry">MinimalTelemetry.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">track_task_completion</a>(task_name: str = None, success: bool = True)</code>
|
||||
* <code title="class MinimalTelemetry">MinimalTelemetry.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">track_tool_usage</a>(tool_name: str, success: bool = True, execution_time: float = None)</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">get_metrics</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">record_cost</a>(cost: float, model: str = None)</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">record_tokens</a>(prompt_tokens: int, completion_tokens: int, model: str = None)</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">start</a>()</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">stop</a>()</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">trace_agent_execution</a>(agent_name: str, **attributes)</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">trace_llm_call</a>(model: str = None, **attributes)</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">trace_task_execution</a>(task_name: str, agent_name: str = None, **attributes)</code>
|
||||
* <code title="class TelemetryCollector">TelemetryCollector.<a href="./src/praisonai-agents/praisonaiagents/telemetry/telemetry.py">trace_tool_call</a>(tool_name: str, **attributes)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/telemetry/__init__.py">cleanup_telemetry_resources</a>()</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/telemetry/__init__.py">disable_performance_mode</a>()</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/telemetry/__init__.py">disable_telemetry</a>()</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/telemetry/__init__.py">enable_performance_mode</a>()</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/telemetry/__init__.py">enable_telemetry</a>()</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/telemetry/__init__.py">get_telemetry</a>() -> 'MinimalTelemetry'</code>
|
||||
|
||||
# Observability
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import FlowDisplay, obs, track_workflow
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class FlowDisplay">FlowDisplay.<a href="./src/praisonai-agents/praisonaiagents/flow_display.py">display</a>()</code>
|
||||
* <code title="class FlowDisplay">FlowDisplay.<a href="./src/praisonai-agents/praisonaiagents/flow_display.py">start</a>()</code>
|
||||
* <code title="class FlowDisplay">FlowDisplay.<a href="./src/praisonai-agents/praisonaiagents/flow_display.py">stop</a>()</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/flow_display.py">track_workflow</a>()</code>
|
||||
|
||||
# Context
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import ContextManager, ManagerConfig
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">capture_llm_boundary</a>(messages: List[Dict[str, Any]], tools: List[Dict[str, Any]]) -> SnapshotHookData</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">emergency_truncate</a>(messages: List[Dict[str, Any]], target_tokens: int) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">estimate_tokens</a>(text: str, validate: bool = False) -> Tuple[int, Optional[EstimationMetrics]]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_history</a>() -> List[Dict[str, Any]]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_last_snapshot_hook</a>() -> Optional[SnapshotHookData]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_resolved_config</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_stats</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_tool_budget</a>(tool_name: str) -> int</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">process</a>(messages: List[Dict[str, Any]], system_prompt: str = '', tools: Optional[List[Dict[str, Any]]] = None, trigger: Literal['turn', 'tool_call', 'manual', 'overflow'] = 'turn') -> Dict[str, Any]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">register_snapshot_callback</a>(callback: Callable[[SnapshotHookData], None]) -> None</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">reset</a>() -> None</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">set_tool_budget</a>(tool_name: str, max_tokens: int, protected: bool = False) -> None</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">truncate_tool_output</a>(tool_name: str, output: str) -> str</code>
|
||||
* <code title="class ManagerConfig">ManagerConfig.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">from_env</a>() -> 'ManagerConfig'</code>
|
||||
* <code title="class ManagerConfig">ManagerConfig.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">merge</a>(**overrides) -> 'ManagerConfig'</code>
|
||||
* <code title="class ManagerConfig">ManagerConfig.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Config
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import AutonomyConfig, AutonomyLevel, CachingConfig, ExecutionConfig, ExecutionPreset, GuardrailConfig, HooksConfig, KnowledgeConfig, MemoryConfig, MultiAgentExecutionConfig, MultiAgentHooksConfig, MultiAgentMemoryConfig, MultiAgentOutputConfig, MultiAgentPlanningConfig, OutputConfig, OutputPreset, PlanningConfig, ReflectionConfig, SkillsConfig, TemplateConfig, WebConfig, WebSearchProvider
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class AutonomyConfig">AutonomyConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class CachingConfig">CachingConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ExecutionConfig">ExecutionConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class GuardrailConfig">GuardrailConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class HooksConfig">HooksConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class KnowledgeConfig">KnowledgeConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class MemoryConfig">MemoryConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class MultiAgentExecutionConfig">MultiAgentExecutionConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class MultiAgentHooksConfig">MultiAgentHooksConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class MultiAgentMemoryConfig">MultiAgentMemoryConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class MultiAgentOutputConfig">MultiAgentOutputConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class MultiAgentPlanningConfig">MultiAgentPlanningConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class OutputConfig">OutputConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class PlanningConfig">PlanningConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ReflectionConfig">ReflectionConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class SkillsConfig">SkillsConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class TemplateConfig">TemplateConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class WebConfig">WebConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Display
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import async_display_callbacks, clean_triple_backticks, display_error, display_generating, display_instruction, display_interaction, display_self_reflection, display_tool_call, error_logs, register_display_callback, sync_display_callbacks
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">clean_triple_backticks</a>(text: str) -> str</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_error</a>(message: str, console = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_generating</a>(content: str = '', start_time: Optional[float] = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_instruction</a>(message: str, console = None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_interaction</a>(message, response, markdown = True, generation_time = None, console = None, agent_name = None, agent_role = None, agent_tools = None, task_name = None, task_description = None, task_id = None, metrics = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_self_reflection</a>(message: str, console = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_tool_call</a>(message: str, console = None, tool_name: str = None, tool_input: dict = None, tool_output: str = None, elapsed_time: float = None, success: bool = True)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">register_display_callback</a>(display_type: str, callback_fn, is_async: bool = False)</code>
|
||||
|
||||
# Utilities
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import ArrayMode, is_policy_string, parse_policy_string, resolve, resolve_autonomy, resolve_caching, resolve_context, resolve_execution, resolve_guardrail_policies, resolve_guardrails, resolve_hooks, resolve_knowledge, resolve_memory, resolve_output, resolve_planning, resolve_reflection, resolve_routing, resolve_skills, resolve_web
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/parse_utils.py">is_policy_string</a>(value: str) -> bool</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/parse_utils.py">parse_policy_string</a>(value: str) -> tuple</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve</a>(value: Any, param_name: str, config_class: Optional[Type] = None, presets: Optional[Dict[str, Any]] = None, default: Any = None, instance_check: Optional[Callable[[Any], bool]] = None, url_schemes: Optional[Dict[str, str]] = None, array_mode: Optional[str] = None, string_mode: Optional[str] = None) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_autonomy</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_caching</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_context</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_execution</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_guardrail_policies</a>(policies: list, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_guardrails</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_hooks</a>(value: Any, config_class: Optional[Type] = None) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_knowledge</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_memory</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_output</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_planning</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_reflection</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_routing</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_skills</a>(value: Any, config_class: Type) -> Any</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/config/param_resolver.py">resolve_web</a>(value: Any, config_class: Type) -> Any</code>
|
||||
|
||||
# Other
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import EmbeddingResult, Task, aembedding, aembeddings, embedding, embeddings, get_dimensions
|
||||
from praisonaiagents import AgentManager, EmbeddingResult, Task, aembedding, aembeddings, embedding, embeddings, get_dimensions
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">add_task</a>(task)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">aexecute_task</a>(task_id)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">append_to_state</a>(key: str, value: Any, max_length: Optional[int] = None) -> List[Any]</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_all_tasks</a>()</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_task</a>(task_id)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">astart</a>(content = None, return_dict = False, **kwargs)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clean_json_output</a>(output: str) -> str</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clear_state</a>() -> None</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">context_manager</a>()</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">current_plan</a>()</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">default_completion_checker</a>(task, agent_output)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">delete_state</a>(key: str) -> bool</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">display_token_usage</a>()</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">execute_task</a>(task_id)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_agent_details</a>(agent_name)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_state</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_tasks_status</a>()</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_detailed_token_report</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_plan_markdown</a>() -> str</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_state</a>(key: str, default: Any = None) -> Any</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_details</a>(task_id)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_result</a>(task_id)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_status</a>(task_id)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_todo_markdown</a>() -> str</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_token_usage_summary</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">has_state</a>(key: str) -> bool</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">increment_state</a>(key: str, amount: float = 1, default: float = 0) -> float</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">launch</a>(path: str = '/agents', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = 'http')</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">restore_session_state</a>(session_id: str) -> bool</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run</a>(content = None, return_dict = False, **kwargs)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run_all_tasks</a>()</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run_task</a>(task_id)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_output_to_file</a>(task, task_output)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_session_state</a>(session_id: str, include_memory: bool = True) -> None</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">set_state</a>(key: str, value: Any) -> None</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, output = None, **kwargs)</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">todo_list</a>()</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_plan_step_status</a>(step_id: str, status: str) -> bool</code>
|
||||
* <code title="class AgentManager">AgentManager.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_state</a>(updates: Dict) -> None</code>
|
||||
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">execute_callback</a>(task_output: TaskOutput) -> None</code>
|
||||
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">execute_callback_sync</a>(task_output: TaskOutput) -> None</code>
|
||||
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">initialize_memory</a>()</code>
|
||||
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">store_in_memory</a>(content: str, agent_name: str = None, task_id: str = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/embedding/embed.py">aembedding</a>(input: Union[str, List[str]], model: str = 'text-embedding-3-small', dimensions: Optional[int] = None, encoding_format: str = 'float', timeout: float = 600.0, api_key: Optional[str] = None, api_base: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, **kwargs) -> EmbeddingResult</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/embedding/embed.py">aembeddings</a>(input: Union[str, List[str]], model: str = 'text-embedding-3-small', dimensions: Optional[int] = None, encoding_format: str = 'float', timeout: float = 600.0, api_key: Optional[str] = None, api_base: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, **kwargs) -> EmbeddingResult</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/embedding/embed.py">embedding</a>(input: Union[str, List[str]], model: str = 'text-embedding-3-small', dimensions: Optional[int] = None, encoding_format: str = 'float', timeout: float = 600.0, api_key: Optional[str] = None, api_base: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, **kwargs) -> EmbeddingResult</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/embedding/embed.py">embeddings</a>(input: Union[str, List[str]], model: str = 'text-embedding-3-small', dimensions: Optional[int] = None, encoding_format: str = 'float', timeout: float = 600.0, api_key: Optional[str] = None, api_base: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, **kwargs) -> EmbeddingResult</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/embedding/dimensions.py">get_dimensions</a>(model_name: str) -> int</code>
|
||||
|
||||
# Wrapper (praisonai)
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonai import CloudProvider, Deploy, DeployConfig, DeployType, PraisonAI, __version__
|
||||
from praisonai import AgentApp, CloudProvider, Deploy, DeployConfig, DeployType, PraisonAI, __version__
|
||||
```
|
||||
|
||||
# CLI
|
||||
@@ -156,6 +750,7 @@ Methods:
|
||||
* <code title="cli">praisonai agents create <a href="./src/praisonai/praisonai/cli/commands/agents.py">--help</a></code>
|
||||
* <code title="cli">praisonai agents info <a href="./src/praisonai/praisonai/cli/commands/agents.py">--help</a></code>
|
||||
* <code title="cli">praisonai agents list <a href="./src/praisonai/praisonai/cli/commands/agents.py">--help</a></code>
|
||||
* <code title="cli">praisonai app app <a href="./src/praisonai/praisonai/cli/commands/app.py">--help</a></code>
|
||||
* <code title="cli">praisonai audit agent-centric <a href="./src/praisonai/praisonai/cli/commands/audit.py">--help</a></code>
|
||||
* <code title="cli">praisonai batch batch-run <a href="./src/praisonai/praisonai/cli/commands/batch.py">--help</a></code>
|
||||
* <code title="cli">praisonai batch list <a href="./src/praisonai/praisonai/cli/commands/batch.py">--help</a></code>
|
||||
@@ -169,6 +764,19 @@ Methods:
|
||||
* <code title="cli">praisonai benchmark profile <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
|
||||
* <code title="cli">praisonai benchmark sdk <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
|
||||
* <code title="cli">praisonai benchmark workflow <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
|
||||
* <code title="cli">praisonai bot bot-callback <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
|
||||
* <code title="cli">praisonai bot discord <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
|
||||
* <code title="cli">praisonai bot slack <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
|
||||
* <code title="cli">praisonai bot telegram <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser browser-callback <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser click <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser navigate <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser open <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser profiles <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser screenshot <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser snapshot <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser status <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai browser type <a href="./src/praisonai/praisonai/cli/commands/browser.py">--help</a></code>
|
||||
* <code title="cli">praisonai call call-main <a href="./src/praisonai/praisonai/cli/commands/call.py">--help</a></code>
|
||||
* <code title="cli">praisonai chat <a href="./src/praisonai/praisonai/cli/main.py">--help</a></code>
|
||||
* <code title="cli">praisonai chat chat-main <a href="./src/praisonai/praisonai/cli/commands/chat.py">--help</a></code>
|
||||
@@ -229,6 +837,8 @@ Methods:
|
||||
* <code title="cli">praisonai environment doctor <a href="./src/praisonai/praisonai/cli/commands/environment.py">--help</a></code>
|
||||
* <code title="cli">praisonai environment view <a href="./src/praisonai/praisonai/cli/commands/environment.py">--help</a></code>
|
||||
* <code title="cli">praisonai eval accuracy <a href="./src/praisonai/praisonai/cli/commands/eval.py">--help</a></code>
|
||||
* <code title="cli">praisonai eval judge <a href="./src/praisonai/praisonai/cli/commands/eval.py">--help</a></code>
|
||||
* <code title="cli">praisonai eval list-judges <a href="./src/praisonai/praisonai/cli/commands/eval.py">--help</a></code>
|
||||
* <code title="cli">praisonai eval performance <a href="./src/praisonai/praisonai/cli/commands/eval.py">--help</a></code>
|
||||
* <code title="cli">praisonai examples info <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
|
||||
* <code title="cli">praisonai examples list <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
|
||||
@@ -243,6 +853,8 @@ Methods:
|
||||
* <code title="cli">praisonai knowledge index <a href="./src/praisonai/praisonai/cli/commands/knowledge.py">--help</a></code>
|
||||
* <code title="cli">praisonai knowledge list <a href="./src/praisonai/praisonai/cli/commands/knowledge.py">--help</a></code>
|
||||
* <code title="cli">praisonai knowledge search <a href="./src/praisonai/praisonai/cli/commands/knowledge.py">--help</a></code>
|
||||
* <code title="cli">praisonai loop help <a href="./src/praisonai/praisonai/cli/commands/loop.py">--help</a></code>
|
||||
* <code title="cli">praisonai loop loop-main <a href="./src/praisonai/praisonai/cli/commands/loop.py">--help</a></code>
|
||||
* <code title="cli">praisonai lsp logs <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
|
||||
* <code title="cli">praisonai lsp lsp-callback <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
|
||||
* <code title="cli">praisonai lsp start <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
|
||||
@@ -268,6 +880,12 @@ Methods:
|
||||
* <code title="cli">praisonai package install <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
|
||||
* <code title="cli">praisonai package list <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
|
||||
* <code title="cli">praisonai package uninstall <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
|
||||
* <code title="cli">praisonai plugins disable <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
|
||||
* <code title="cli">praisonai plugins doctor <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
|
||||
* <code title="cli">praisonai plugins enable <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
|
||||
* <code title="cli">praisonai plugins info <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
|
||||
* <code title="cli">praisonai plugins list <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
|
||||
* <code title="cli">praisonai plugins plugins-callback <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
|
||||
* <code title="cli">praisonai profile imports <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
|
||||
* <code title="cli">praisonai profile optimize <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
|
||||
* <code title="cli">praisonai profile profile-callback <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
|
||||
@@ -281,11 +899,24 @@ Methods:
|
||||
* <code title="cli">praisonai rag query <a href="./src/praisonai/praisonai/cli/commands/rag.py">--help</a></code>
|
||||
* <code title="cli">praisonai rag serve <a href="./src/praisonai/praisonai/cli/commands/rag.py">--help</a></code>
|
||||
* <code title="cli">praisonai realtime realtime-main <a href="./src/praisonai/praisonai/cli/commands/realtime.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe apply <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe create <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe info <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe install <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe judge <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe list <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe optimize <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe run <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai recipe serve <a href="./src/praisonai/praisonai/cli/commands/recipe.py">--help</a></code>
|
||||
* <code title="cli">praisonai registry list <a href="./src/praisonai/praisonai/cli/commands/registry.py">--help</a></code>
|
||||
* <code title="cli">praisonai registry serve <a href="./src/praisonai/praisonai/cli/commands/registry.py">--help</a></code>
|
||||
* <code title="cli">praisonai replay cleanup <a href="./src/praisonai/praisonai/cli/commands/replay.py">--help</a></code>
|
||||
* <code title="cli">praisonai replay context <a href="./src/praisonai/praisonai/cli/commands/replay.py">--help</a></code>
|
||||
* <code title="cli">praisonai replay dashboard <a href="./src/praisonai/praisonai/cli/commands/replay.py">--help</a></code>
|
||||
* <code title="cli">praisonai replay delete <a href="./src/praisonai/praisonai/cli/commands/replay.py">--help</a></code>
|
||||
* <code title="cli">praisonai replay flow <a href="./src/praisonai/praisonai/cli/commands/replay.py">--help</a></code>
|
||||
* <code title="cli">praisonai replay list <a href="./src/praisonai/praisonai/cli/commands/replay.py">--help</a></code>
|
||||
* <code title="cli">praisonai replay show <a href="./src/praisonai/praisonai/cli/commands/replay.py">--help</a></code>
|
||||
* <code title="cli">praisonai research research-main <a href="./src/praisonai/praisonai/cli/commands/research.py">--help</a></code>
|
||||
* <code title="cli">praisonai retrieval index <a href="./src/praisonai/praisonai/cli/commands/retrieval.py">--help</a></code>
|
||||
* <code title="cli">praisonai retrieval query <a href="./src/praisonai/praisonai/cli/commands/retrieval.py">--help</a></code>
|
||||
@@ -295,6 +926,11 @@ Methods:
|
||||
* <code title="cli">praisonai rules list <a href="./src/praisonai/praisonai/cli/commands/rules.py">--help</a></code>
|
||||
* <code title="cli">praisonai run <a href="./src/praisonai/praisonai/cli/main.py">--help</a></code>
|
||||
* <code title="cli">praisonai run run-main <a href="./src/praisonai/praisonai/cli/commands/run.py">--help</a></code>
|
||||
* <code title="cli">praisonai sandbox explain <a href="./src/praisonai/praisonai/cli/commands/sandbox.py">--help</a></code>
|
||||
* <code title="cli">praisonai sandbox list <a href="./src/praisonai/praisonai/cli/commands/sandbox.py">--help</a></code>
|
||||
* <code title="cli">praisonai sandbox recreate <a href="./src/praisonai/praisonai/cli/commands/sandbox.py">--help</a></code>
|
||||
* <code title="cli">praisonai sandbox sandbox-callback <a href="./src/praisonai/praisonai/cli/commands/sandbox.py">--help</a></code>
|
||||
* <code title="cli">praisonai sandbox status <a href="./src/praisonai/praisonai/cli/commands/sandbox.py">--help</a></code>
|
||||
* <code title="cli">praisonai schedule delete <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
|
||||
* <code title="cli">praisonai schedule describe <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
|
||||
* <code title="cli">praisonai schedule list <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
|
||||
@@ -304,17 +940,33 @@ Methods:
|
||||
* <code title="cli">praisonai schedule start <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
|
||||
* <code title="cli">praisonai schedule stats <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
|
||||
* <code title="cli">praisonai schedule stop <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve a2a <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve a2u <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve acp <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve agents <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve docs <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve gateway <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve lsp <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve mcp <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve rag <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve recipe <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve registry <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve scheduler <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve serve-callback <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve start <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve status <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve stop <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve ui <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve unified <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai session delete <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai session export <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai session import <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai session list <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai session resume <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai session show <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills check <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills create <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills eligible <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills info <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills install <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills list <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
@@ -335,11 +987,16 @@ Methods:
|
||||
* <code title="cli">praisonai tools info <a href="./src/praisonai/praisonai/cli/commands/tools.py">--help</a></code>
|
||||
* <code title="cli">praisonai tools list <a href="./src/praisonai/praisonai/cli/commands/tools.py">--help</a></code>
|
||||
* <code title="cli">praisonai tools test <a href="./src/praisonai/praisonai/cli/commands/tools.py">--help</a></code>
|
||||
* <code title="cli">praisonai tools validate <a href="./src/praisonai/praisonai/cli/commands/tools.py">--help</a></code>
|
||||
* <code title="cli">praisonai traces disable <a href="./src/praisonai/praisonai/cli/commands/traces.py">--help</a></code>
|
||||
* <code title="cli">praisonai traces enable <a href="./src/praisonai/praisonai/cli/commands/traces.py">--help</a></code>
|
||||
* <code title="cli">praisonai traces list <a href="./src/praisonai/praisonai/cli/commands/traces.py">--help</a></code>
|
||||
* <code title="cli">praisonai traces status <a href="./src/praisonai/praisonai/cli/commands/traces.py">--help</a></code>
|
||||
* <code title="cli">praisonai train train-main <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
|
||||
* <code title="cli">praisonai train agents <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
|
||||
* <code title="cli">praisonai train list <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
|
||||
* <code title="cli">praisonai train llm <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
|
||||
* <code title="cli">praisonai train show <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
|
||||
* <code title="cli">praisonai train train-callback <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
|
||||
* <code title="cli">praisonai ui chat <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
|
||||
* <code title="cli">praisonai ui code <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
|
||||
* <code title="cli">praisonai ui gradio <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
|
||||
@@ -405,7 +1062,8 @@ export type { DbAdapter, DbConfig, DbMessage, DbRun, DbTrace } from "./db";
|
||||
export { MemoryPostgresAdapter, NeonPostgresAdapter, PostgresSessionStorage, createMemoryPostgres, createNeonPostgres, createPostgresSessionStorage } from "./db/postgres";
|
||||
export { MemoryRedisAdapter, UpstashRedisAdapter, createMemoryRedis, createUpstashRedis } from "./db/redis";
|
||||
export { SQLiteAdapter, createSQLiteAdapter } from "./db/sqlite";
|
||||
export { EvalResults, EvalSuite, Evaluator, accuracyEval, containsKeywordsCriterion, createDefaultEvaluator, createEvalResults, createEvaluator, lengthCriterion, noHarmfulContentCriterion, performanceEval, relevanceCriterion, reliabilityEval } from "./eval";
|
||||
export { // LLM-as-Judge
|
||||
Judge, AccuracyJudge, CriteriaJudge, EvalResults, EvalSuite, Evaluator, RecipeJudge, accuracyEval, addJudge, addOptimizationRule, containsKeywordsCriterion, createDefaultEvaluator, createEvalResults, createEvaluator, getJudge, getOptimizationRule, lengthCriterion, listJudges, listOptimizationRules, noHarmfulContentCriterion, parseJudgeResponse, performanceEval, relevanceCriterion, reliabilityEval, removeJudge, removeOptimizationRule } from "./eval";
|
||||
export { AgentEventBus, AgentEvents, EventEmitterPubSub, PubSub, createEventBus, createPubSub } from "./events";
|
||||
export { LLMGuardrail, createLLMGuardrail } from "./guardrails/llm-guardrail";
|
||||
export { DisplayTypes, HooksManager, WorkflowHooksExecutor, clearAllCallbacks, clearApprovalCallback, createHooksManager, createLoggingOperationHooks, createLoggingWorkflowHooks, createTimingWorkflowHooks, createValidationOperationHooks, createWorkflowHooks, executeCallback, executeSyncCallback, getRegisteredDisplayTypes, hasApprovalCallback, registerApprovalCallback, registerDisplayCallback, requestApproval, unregisterDisplayCallback } from "./hooks";
|
||||
|
||||
@@ -13,6 +13,12 @@ GOOGLE_API_KEY=your_google_api_key_here
|
||||
GROQ_API_KEY=your_groq_api_key_here
|
||||
COHERE_API_KEY=your_cohere_api_key_here
|
||||
|
||||
# Bot Tokens (for messaging bot services)
|
||||
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
|
||||
SLACK_APP_TOKEN=xapp-your-slack-app-token
|
||||
DISCORD_BOT_TOKEN=your-discord-bot-token
|
||||
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
|
||||
|
||||
# Optional: Service Configuration
|
||||
FLASK_ENV=development
|
||||
CHAINLIT_HOST=0.0.0.0
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=3.10.2" \
|
||||
"praisonai>=3.12.2" \
|
||||
"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>=3.10.2" \
|
||||
"praisonai>=3.12.2" \
|
||||
"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>=3.10.2" \
|
||||
"praisonai>=3.12.2" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Bot Environment Variables
|
||||
# Copy to .env and fill in your values
|
||||
|
||||
# Required: LLM API Key
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
|
||||
# Slack Bot (Socket Mode)
|
||||
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
|
||||
SLACK_APP_TOKEN=xapp-your-slack-app-token
|
||||
|
||||
# Discord Bot
|
||||
DISCORD_BOT_TOKEN=your-discord-bot-token
|
||||
|
||||
# Telegram Bot
|
||||
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
|
||||
@@ -0,0 +1,42 @@
|
||||
# Dockerfile for PraisonAI Slack Bot
|
||||
# Secrets are passed at runtime via environment variables
|
||||
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
python3-dev \
|
||||
curl \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create praison config directory
|
||||
RUN mkdir -p /root/.praison
|
||||
|
||||
# Install praisonai with bot dependencies
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai \
|
||||
slack-bolt \
|
||||
slack-sdk \
|
||||
"discord.py>=2.0.0" \
|
||||
"python-telegram-bot>=20.0"
|
||||
|
||||
# Set environment variables for directory management
|
||||
ENV PRAISON_CONFIG_DIR=/root/.praison
|
||||
ENV DOCKER_CONTAINER=true
|
||||
|
||||
# Bot tokens should be passed at runtime (not baked into image)
|
||||
# SLACK_BOT_TOKEN=xoxb-...
|
||||
# SLACK_APP_TOKEN=xapp-...
|
||||
|
||||
# Optional: Copy custom agent configuration
|
||||
# COPY agent.yaml /app/agent.yaml
|
||||
|
||||
# Expose no ports - Socket Mode doesn't need them
|
||||
# For webhook mode, expose port 3000
|
||||
|
||||
# Default command - tokens from environment
|
||||
CMD ["praisonai", "bot", "slack"]
|
||||
@@ -0,0 +1,66 @@
|
||||
# PraisonAI Bot Docker Deployment
|
||||
|
||||
Deploy Slack, Discord, or Telegram bots using Docker.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Copy environment template
|
||||
cp .env.template .env
|
||||
|
||||
# 2. Edit .env with your tokens
|
||||
nano .env
|
||||
|
||||
# 3. Run specific bot
|
||||
docker compose up slack-bot -d
|
||||
|
||||
# Or run all bots
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `OPENAI_API_KEY` | Yes | LLM API key |
|
||||
| `SLACK_BOT_TOKEN` | For Slack | Bot token (xoxb-...) |
|
||||
| `SLACK_APP_TOKEN` | For Slack | App token (xapp-...) |
|
||||
| `DISCORD_BOT_TOKEN` | For Discord | Bot token |
|
||||
| `TELEGRAM_BOT_TOKEN` | For Telegram | Bot token |
|
||||
|
||||
## Slack Setup
|
||||
|
||||
1. Create app at https://api.slack.com/apps
|
||||
2. Enable **Socket Mode** → Generate App-Level Token with `connections:write`
|
||||
3. Add **OAuth Scopes**: `chat:write`, `app_mentions:read`, `im:history`
|
||||
4. Enable **Event Subscriptions** → Subscribe to `app_mention`, `message.im`
|
||||
5. **Install to Workspace**
|
||||
6. Copy Bot Token and App Token to `.env`
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Start Slack bot
|
||||
docker compose up slack-bot -d
|
||||
|
||||
# View logs
|
||||
docker compose logs -f slack-bot
|
||||
|
||||
# Stop bot
|
||||
docker compose down slack-bot
|
||||
|
||||
# Rebuild after updates
|
||||
docker compose build --no-cache slack-bot
|
||||
```
|
||||
|
||||
## Custom Agent Configuration
|
||||
|
||||
Mount your agent config:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
slack-bot:
|
||||
volumes:
|
||||
- ./agent.yaml:/app/agent.yaml
|
||||
command: ["praisonai", "bot", "slack", "--agent", "/app/agent.yaml"]
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
version: '3.8'
|
||||
|
||||
# PraisonAI Bot Services
|
||||
# Run specific bots: docker compose up slack-bot
|
||||
# Run all bots: docker compose up
|
||||
|
||||
services:
|
||||
# Slack Bot
|
||||
slack-bot:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/bots/Dockerfile
|
||||
volumes:
|
||||
- praison_data:/root/.praison
|
||||
environment:
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
SLACK_BOT_TOKEN: ${SLACK_BOT_TOKEN}
|
||||
SLACK_APP_TOKEN: ${SLACK_APP_TOKEN}
|
||||
PRAISON_CONFIG_DIR: /root/.praison
|
||||
command: [ "praisonai", "bot", "slack" ]
|
||||
restart: unless-stopped
|
||||
|
||||
# Discord Bot
|
||||
discord-bot:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/bots/Dockerfile
|
||||
volumes:
|
||||
- praison_data:/root/.praison
|
||||
environment:
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN}
|
||||
PRAISON_CONFIG_DIR: /root/.praison
|
||||
command: [ "praisonai", "bot", "discord" ]
|
||||
restart: unless-stopped
|
||||
|
||||
# Telegram Bot
|
||||
telegram-bot:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/bots/Dockerfile
|
||||
volumes:
|
||||
- praison_data:/root/.praison
|
||||
environment:
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
|
||||
PRAISON_CONFIG_DIR: /root/.praison
|
||||
command: [ "praisonai", "bot", "telegram" ]
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
praison_data:
|
||||
driver: local
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
CHAINLIT_DB_DIR: /root/.praison
|
||||
command: praisonai ui --host 0.0.0.0
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8082"]
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8082" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
@@ -49,7 +49,7 @@ services:
|
||||
CHAINLIT_DB_DIR: /root/.praison
|
||||
command: praisonai chat --host 0.0.0.0 --port 8083
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8083"]
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8083" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
@@ -69,7 +69,7 @@ services:
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
PRAISON_CONFIG_DIR: /root/.praison
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8080/health" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
@@ -89,6 +89,7 @@ services:
|
||||
PRAISON_CONFIG_DIR: /root/.praison
|
||||
networks:
|
||||
- praison_network
|
||||
# For bot services (Slack, Discord, Telegram), see docker/bots/
|
||||
|
||||
volumes:
|
||||
praison_data:
|
||||
@@ -96,4 +97,4 @@ volumes:
|
||||
|
||||
networks:
|
||||
praison_network:
|
||||
driver: bridge
|
||||
driver: bridge
|
||||
File renamed without changes.
@@ -1,21 +1,24 @@
|
||||
"""
|
||||
Basic Memory Example - Agent-Centric API
|
||||
|
||||
Demonstrates the simplest memory usage with consolidated params.
|
||||
"""
|
||||
|
||||
"""Basic memory example - minimal usage."""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Basic: Enable memory with preset
|
||||
# Simple enable (uses default file-based memory)
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant with memory.",
|
||||
memory="sqlite", # Preset: sqlite, redis, postgres, file
|
||||
memory=True,
|
||||
)
|
||||
|
||||
# Run a simple task
|
||||
response = agent.start("Remember that my favorite color is blue.")
|
||||
print(response)
|
||||
# With preset
|
||||
agent_redis = Agent(
|
||||
instructions="You are a helpful assistant.",
|
||||
memory="redis", # Uses redis preset
|
||||
)
|
||||
|
||||
# Follow-up to test memory
|
||||
response = agent.start("What is my favorite color?")
|
||||
print(response)
|
||||
# With URL
|
||||
agent_postgres = Agent(
|
||||
instructions="You are a helpful assistant.",
|
||||
memory="postgresql://localhost/mydb", # URL auto-detected
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
response = agent.chat("Remember that my favorite color is blue.")
|
||||
print(response)
|
||||
File renamed without changes.
@@ -1,26 +1,22 @@
|
||||
"""
|
||||
Basic Workflow Example - Agent-Centric API
|
||||
|
||||
Demonstrates minimal workflow with consolidated params.
|
||||
"""
|
||||
|
||||
"""Basic workflow example with consolidated params."""
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.workflows import Workflow, WorkflowStep
|
||||
|
||||
# Create a simple workflow
|
||||
# Create agents
|
||||
writer = Agent(instructions="You are a content writer.")
|
||||
editor = Agent(instructions="You are an editor.")
|
||||
|
||||
# Create workflow with consolidated params
|
||||
workflow = Workflow(
|
||||
name="simple_workflow",
|
||||
output="verbose", # Workflow-level output preset
|
||||
name="Content Pipeline",
|
||||
steps=[
|
||||
WorkflowStep(name="write", agent=writer, action="Write about {{topic}}"),
|
||||
WorkflowStep(name="edit", agent=editor, action="Edit the content", context=["write"]),
|
||||
],
|
||||
output="verbose",
|
||||
planning=True,
|
||||
)
|
||||
|
||||
# Add a step
|
||||
step = WorkflowStep(
|
||||
name="writer",
|
||||
action="Write a haiku about programming",
|
||||
agent=Agent(instructions="You are a creative poet."),
|
||||
output="result.txt", # Save output to file
|
||||
)
|
||||
|
||||
# Note: This is a structural example - actual execution requires workflow.run()
|
||||
print("Workflow created with output preset:", workflow.output)
|
||||
print("Step output file:", step.output_file)
|
||||
if __name__ == "__main__":
|
||||
result = workflow.run(variables={"topic": "AI agents"})
|
||||
print(result)
|
||||
@@ -2,32 +2,34 @@
|
||||
"""
|
||||
Basic Hooks Example for PraisonAI Agents.
|
||||
|
||||
This example demonstrates how to use the hooks system to:
|
||||
This example demonstrates the simplified hook API using add_hook():
|
||||
1. Log tool calls before execution
|
||||
2. Block dangerous operations
|
||||
3. Add context to agent responses
|
||||
4. Monitor session lifecycle
|
||||
|
||||
For the simplest example, see simple_hooks.py
|
||||
|
||||
Usage:
|
||||
python basic_hooks.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.hooks import (
|
||||
HookRegistry, HookRunner, HookEvent, HookResult,
|
||||
add_hook, remove_hook, has_hook, get_default_registry,
|
||||
HookRunner, HookEvent, HookResult,
|
||||
BeforeToolInput, AfterToolInput
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Create a hook registry
|
||||
registry = HookRegistry()
|
||||
# Clear any existing hooks from previous runs
|
||||
get_default_registry().clear()
|
||||
|
||||
# ==========================================================================
|
||||
# Hook 1: Log all tool calls
|
||||
# Hook 1: Log all tool calls (using string event name)
|
||||
# ==========================================================================
|
||||
@registry.on(HookEvent.BEFORE_TOOL)
|
||||
@add_hook('before_tool')
|
||||
def log_tool_calls(event_data: BeforeToolInput) -> HookResult:
|
||||
"""Log every tool call before execution."""
|
||||
print(f"\n📝 [LOG] Tool: {event_data.tool_name}")
|
||||
@@ -35,9 +37,9 @@ def main():
|
||||
return HookResult.allow()
|
||||
|
||||
# ==========================================================================
|
||||
# Hook 2: Block dangerous file operations (using function logic)
|
||||
# Hook 2: Block dangerous file operations
|
||||
# ==========================================================================
|
||||
@registry.on(HookEvent.BEFORE_TOOL)
|
||||
@add_hook('before_tool')
|
||||
def block_delete_operations(event_data: BeforeToolInput) -> HookResult:
|
||||
"""Block any delete or remove operations."""
|
||||
dangerous_keywords = ['delete', 'remove', 'rm', 'unlink']
|
||||
@@ -53,7 +55,7 @@ def main():
|
||||
# ==========================================================================
|
||||
# Hook 3: Require confirmation for write operations
|
||||
# ==========================================================================
|
||||
@registry.on(HookEvent.BEFORE_TOOL)
|
||||
@add_hook('before_tool')
|
||||
def confirm_write_operations(event_data: BeforeToolInput) -> HookResult:
|
||||
"""Request confirmation for write operations."""
|
||||
write_keywords = ['write', 'save', 'create', 'update']
|
||||
@@ -62,14 +64,13 @@ def main():
|
||||
if any(kw in tool_lower for kw in write_keywords):
|
||||
print(f"\n⚠️ [CONFIRM] Write operation: {event_data.tool_name}")
|
||||
# In a real scenario, you might prompt the user here
|
||||
# For this example, we'll auto-approve
|
||||
return HookResult.allow("Auto-approved for demo")
|
||||
return HookResult.allow()
|
||||
|
||||
# ==========================================================================
|
||||
# Hook 4: Add timing information after tool execution
|
||||
# ==========================================================================
|
||||
@registry.on(HookEvent.AFTER_TOOL)
|
||||
@add_hook('after_tool')
|
||||
def log_tool_completion(event_data: AfterToolInput) -> HookResult:
|
||||
"""Log tool completion with timing."""
|
||||
print(f"\n✅ [DONE] Tool: {event_data.tool_name}")
|
||||
@@ -82,10 +83,14 @@ def main():
|
||||
# Test the hooks
|
||||
# ==========================================================================
|
||||
print("=" * 60)
|
||||
print("Testing Hooks System")
|
||||
print("Testing Hooks System (using add_hook API)")
|
||||
print("=" * 60)
|
||||
|
||||
runner = HookRunner(registry)
|
||||
# Check hooks are registered
|
||||
print(f"\nHooks registered for 'before_tool': {has_hook('before_tool')}")
|
||||
print(f"Hooks registered for 'after_tool': {has_hook('after_tool')}")
|
||||
|
||||
runner = HookRunner(get_default_registry())
|
||||
|
||||
# Test 1: Normal tool call (should be logged and allowed)
|
||||
print("\n--- Test 1: Normal read operation ---")
|
||||
@@ -132,7 +137,7 @@ def main():
|
||||
print("\n" + "=" * 60)
|
||||
print("Registered Hooks:")
|
||||
print("=" * 60)
|
||||
for event, hooks in registry.list_hooks().items():
|
||||
for event, hooks in get_default_registry().list_hooks().items():
|
||||
print(f"\n{event}:")
|
||||
for hook in hooks:
|
||||
print(f" - {hook['name']} (matcher: {hook['matcher'] or '*'})")
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Hooks Example for PraisonAI Agents.
|
||||
|
||||
This example shows the SIMPLEST way to use hooks with the add_hook API.
|
||||
Just a few lines of code to intercept tool calls!
|
||||
|
||||
No need to import HookResult - just return None/True to allow,
|
||||
False to deny, or a string to deny with a reason.
|
||||
|
||||
Usage:
|
||||
python simple_hooks.py
|
||||
"""
|
||||
|
||||
from praisonaiagents.hooks import add_hook, has_hook
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Register hooks with simple string event names
|
||||
# =============================================================================
|
||||
|
||||
@add_hook('before_tool')
|
||||
def log_tools(event_data):
|
||||
"""Log every tool call. Return nothing (None) to allow."""
|
||||
print(f"🔧 Tool: {event_data.tool_name}")
|
||||
# No return needed - defaults to allow
|
||||
|
||||
|
||||
@add_hook('before_tool')
|
||||
def block_dangerous(event_data):
|
||||
"""Block delete operations. Return False or string to deny."""
|
||||
if 'delete' in event_data.tool_name.lower():
|
||||
print(f"🚫 Blocked: {event_data.tool_name}")
|
||||
return "Delete operations are not allowed" # String = deny with reason
|
||||
# No return = allow
|
||||
|
||||
|
||||
@add_hook('after_tool')
|
||||
def log_completion(event_data):
|
||||
"""Log tool completion."""
|
||||
print(f"✅ Done: {event_data.tool_name} ({event_data.execution_time_ms:.0f}ms)")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Test the hooks
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 50)
|
||||
print("Simple Hooks Example")
|
||||
print("=" * 50)
|
||||
|
||||
# Check hooks are registered
|
||||
print(f"\nHooks registered for 'before_tool': {has_hook('before_tool')}")
|
||||
print(f"Hooks registered for 'after_tool': {has_hook('after_tool')}")
|
||||
|
||||
# You can now create an Agent and the hooks will be applied
|
||||
print("\n💡 Hooks are now globally registered!")
|
||||
print(" Any Agent will automatically use these hooks.")
|
||||
print("\n Example:")
|
||||
print(" from praisonaiagents import Agent")
|
||||
print(" agent = Agent(instructions='You are helpful')")
|
||||
print(" agent.start('Help me with files')")
|
||||
print("\n📝 Hook returns:")
|
||||
print(" - None or True → Allow the operation")
|
||||
print(" - False → Deny the operation")
|
||||
print(" - 'reason' → Deny with a custom message")
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Messaging Bot Example - Deploy Agents to Telegram, Discord, Slack
|
||||
|
||||
This example demonstrates how to configure bots for different
|
||||
messaging platforms.
|
||||
"""
|
||||
|
||||
import os
|
||||
from praisonaiagents import Agent, BotConfig
|
||||
|
||||
# Telegram Bot Configuration
|
||||
telegram_config = BotConfig(
|
||||
token=os.environ.get("TELEGRAM_BOT_TOKEN", ""),
|
||||
command_prefix="/",
|
||||
mention_required=True,
|
||||
typing_indicator=True,
|
||||
max_message_length=4096,
|
||||
metadata={"platform": "telegram"}
|
||||
)
|
||||
|
||||
# Discord Bot Configuration
|
||||
discord_config = BotConfig(
|
||||
token=os.environ.get("DISCORD_BOT_TOKEN", ""),
|
||||
command_prefix="!",
|
||||
mention_required=True,
|
||||
typing_indicator=True,
|
||||
metadata={
|
||||
"platform": "discord",
|
||||
"guild_ids": [] # Add your guild IDs here
|
||||
}
|
||||
)
|
||||
|
||||
# Slack Bot Configuration
|
||||
slack_config = BotConfig(
|
||||
token=os.environ.get("SLACK_BOT_TOKEN", ""),
|
||||
command_prefix="/",
|
||||
mention_required=True,
|
||||
typing_indicator=True,
|
||||
metadata={
|
||||
"platform": "slack",
|
||||
"app_token": os.environ.get("SLACK_APP_TOKEN", "")
|
||||
}
|
||||
)
|
||||
|
||||
# Create the agent that will handle messages
|
||||
assistant = Agent(
|
||||
name="assistant",
|
||||
instructions="""You are a helpful assistant that responds to messages
|
||||
from users on messaging platforms. Be friendly and concise.""",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Bot Configurations:")
|
||||
print()
|
||||
|
||||
print("Telegram Config:")
|
||||
print(f" Token configured: {bool(telegram_config.token)}")
|
||||
print(f" Command prefix: {telegram_config.command_prefix}")
|
||||
print(f" Mention required: {telegram_config.mention_required}")
|
||||
print()
|
||||
|
||||
print("Discord Config:")
|
||||
print(f" Token configured: {bool(discord_config.token)}")
|
||||
print(f" Command prefix: {discord_config.command_prefix}")
|
||||
print()
|
||||
|
||||
print("Slack Config:")
|
||||
print(f" Token configured: {bool(slack_config.token)}")
|
||||
print(f" Command prefix: {slack_config.command_prefix}")
|
||||
print()
|
||||
|
||||
print("To start a bot, use the CLI:")
|
||||
print(" praisonai bot telegram --token $TELEGRAM_BOT_TOKEN")
|
||||
print(" praisonai bot discord --token $DISCORD_BOT_TOKEN")
|
||||
print(" praisonai bot slack --token $SLACK_BOT_TOKEN")
|
||||
@@ -0,0 +1,391 @@
|
||||
"""
|
||||
Conditional Branching Example - PraisonAI Agents
|
||||
|
||||
This example demonstrates the `if:` pattern for conditional workflow execution:
|
||||
1. Basic if/then/else branching
|
||||
2. Numeric condition evaluation (>, <, >=, <=, ==, !=)
|
||||
3. String condition evaluation (==, !=)
|
||||
4. Contains condition (in, contains)
|
||||
5. Combining if: with loop: for per-item decisions
|
||||
|
||||
The if: pattern enables dynamic workflow paths based on
|
||||
runtime conditions and variable values.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.workflows import if_, loop
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 1: Basic If/Then/Else - Score-Based Routing
|
||||
# =============================================================================
|
||||
|
||||
def basic_if_example():
|
||||
"""
|
||||
Route to different handlers based on a score threshold.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 1: Basic If/Then/Else - Score Routing")
|
||||
print("="*60)
|
||||
|
||||
def approve(ctx: WorkflowContext) -> StepResult:
|
||||
score = ctx.variables.get("score", 0)
|
||||
print(f" ✅ APPROVED: Score {score} meets threshold")
|
||||
return StepResult(output="Application approved")
|
||||
|
||||
def reject(ctx: WorkflowContext) -> StepResult:
|
||||
score = ctx.variables.get("score", 0)
|
||||
print(f" ❌ REJECTED: Score {score} below threshold")
|
||||
return StepResult(output="Application rejected")
|
||||
|
||||
# Test with passing score
|
||||
workflow_pass = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{score}} >= 70",
|
||||
then_steps=[approve],
|
||||
else_steps=[reject]
|
||||
)
|
||||
],
|
||||
variables={"score": 85}
|
||||
)
|
||||
|
||||
print("\nTest 1: Score = 85")
|
||||
workflow_pass.start("Evaluate application")
|
||||
|
||||
# Test with failing score
|
||||
workflow_fail = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{score}} >= 70",
|
||||
then_steps=[approve],
|
||||
else_steps=[reject]
|
||||
)
|
||||
],
|
||||
variables={"score": 55}
|
||||
)
|
||||
|
||||
print("\nTest 2: Score = 55")
|
||||
workflow_fail.start("Evaluate application")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 2: String Condition - Status-Based Processing
|
||||
# =============================================================================
|
||||
|
||||
def string_condition_example():
|
||||
"""
|
||||
Branch based on string equality.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 2: String Condition - Status Processing")
|
||||
print("="*60)
|
||||
|
||||
def process_active(ctx: WorkflowContext) -> StepResult:
|
||||
print(" 🟢 Processing ACTIVE user")
|
||||
return StepResult(output="Active user processed")
|
||||
|
||||
def process_inactive(ctx: WorkflowContext) -> StepResult:
|
||||
print(" 🔴 Processing INACTIVE user")
|
||||
return StepResult(output="Inactive user archived")
|
||||
|
||||
# Test with active status
|
||||
workflow = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{status}} == active",
|
||||
then_steps=[process_active],
|
||||
else_steps=[process_inactive]
|
||||
)
|
||||
],
|
||||
variables={"status": "active"}
|
||||
)
|
||||
|
||||
print("\nTest: status = 'active'")
|
||||
workflow.start("Process user")
|
||||
|
||||
# Test with inactive status
|
||||
workflow2 = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{status}} == active",
|
||||
then_steps=[process_active],
|
||||
else_steps=[process_inactive]
|
||||
)
|
||||
],
|
||||
variables={"status": "inactive"}
|
||||
)
|
||||
|
||||
print("\nTest: status = 'inactive'")
|
||||
workflow2.start("Process user")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 3: Contains Condition - Content Filtering
|
||||
# =============================================================================
|
||||
|
||||
def contains_condition_example():
|
||||
"""
|
||||
Branch based on whether a string contains a substring.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 3: Contains Condition - Content Filtering")
|
||||
print("="*60)
|
||||
|
||||
def flag_content(ctx: WorkflowContext) -> StepResult:
|
||||
print(" 🚨 FLAGGED: Message contains sensitive content")
|
||||
return StepResult(output="Content flagged for review")
|
||||
|
||||
def approve_content(ctx: WorkflowContext) -> StepResult:
|
||||
print(" ✅ APPROVED: Message is clean")
|
||||
return StepResult(output="Content approved")
|
||||
|
||||
# Test with flagged content
|
||||
workflow1 = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="error in {{message}}",
|
||||
then_steps=[flag_content],
|
||||
else_steps=[approve_content]
|
||||
)
|
||||
],
|
||||
variables={"message": "System error: connection failed"}
|
||||
)
|
||||
|
||||
print("\nTest 1: Message contains 'error'")
|
||||
workflow1.start("Filter content")
|
||||
|
||||
# Test with clean content
|
||||
workflow2 = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="error in {{message}}",
|
||||
then_steps=[flag_content],
|
||||
else_steps=[approve_content]
|
||||
)
|
||||
],
|
||||
variables={"message": "Operation completed successfully"}
|
||||
)
|
||||
|
||||
print("\nTest 2: Message is clean")
|
||||
workflow2.start("Filter content")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 4: If Inside Loop - Per-Item Decisions
|
||||
# =============================================================================
|
||||
|
||||
def if_inside_loop_example():
|
||||
"""
|
||||
Make decisions for each item in a collection.
|
||||
Combines iteration with conditional branching.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 4: If Inside Loop - Per-Item Decisions")
|
||||
print("="*60)
|
||||
|
||||
results = {"premium": [], "standard": []}
|
||||
|
||||
def premium_service(ctx: WorkflowContext) -> StepResult:
|
||||
customer = ctx.variables.get("customer", {})
|
||||
name = customer.get("name", "Unknown")
|
||||
results["premium"].append(name)
|
||||
print(f" ⭐ {name}: Premium service activated")
|
||||
return StepResult(output=f"Premium service for {name}")
|
||||
|
||||
def standard_service(ctx: WorkflowContext) -> StepResult:
|
||||
customer = ctx.variables.get("customer", {})
|
||||
name = customer.get("name", "Unknown")
|
||||
results["standard"].append(name)
|
||||
print(f" 📦 {name}: Standard service activated")
|
||||
return StepResult(output=f"Standard service for {name}")
|
||||
|
||||
workflow = Workflow(
|
||||
steps=[
|
||||
loop(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{customer.tier}} == premium",
|
||||
then_steps=[premium_service],
|
||||
else_steps=[standard_service]
|
||||
)
|
||||
],
|
||||
over="customers",
|
||||
var_name="customer"
|
||||
)
|
||||
],
|
||||
variables={
|
||||
"customers": [
|
||||
{"name": "Alice", "tier": "premium"},
|
||||
{"name": "Bob", "tier": "standard"},
|
||||
{"name": "Charlie", "tier": "premium"},
|
||||
{"name": "Diana", "tier": "standard"}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
print("\nProcessing customers:")
|
||||
workflow.start("Assign service tiers")
|
||||
|
||||
print(f"\nSummary:")
|
||||
print(f" Premium customers: {results['premium']}")
|
||||
print(f" Standard customers: {results['standard']}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 5: Nested Conditions - Multi-Level Decision Tree
|
||||
# =============================================================================
|
||||
|
||||
def nested_conditions_example():
|
||||
"""
|
||||
Demonstrate nested if conditions for complex decision trees.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 5: Nested Conditions - Decision Tree")
|
||||
print("="*60)
|
||||
|
||||
def vip_treatment(ctx: WorkflowContext) -> StepResult:
|
||||
print(" 👑 VIP Treatment: Dedicated support + Priority queue")
|
||||
return StepResult(output="VIP treatment applied") # noqa: ARG001
|
||||
|
||||
def regular_premium(ctx: WorkflowContext) -> StepResult:
|
||||
print(" ⭐ Premium: Priority support")
|
||||
return StepResult(output="Premium support applied")
|
||||
|
||||
def basic_support(ctx: WorkflowContext) -> StepResult:
|
||||
print(" 📞 Basic: Standard support queue")
|
||||
return StepResult(output="Basic support applied")
|
||||
|
||||
# Outer condition: Is premium?
|
||||
# Inner condition (if premium): Is VIP?
|
||||
workflow = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{tier}} == premium",
|
||||
then_steps=[
|
||||
if_(
|
||||
condition="{{spend}} > 10000",
|
||||
then_steps=[vip_treatment],
|
||||
else_steps=[regular_premium]
|
||||
)
|
||||
],
|
||||
else_steps=[basic_support]
|
||||
)
|
||||
],
|
||||
variables={"tier": "premium", "spend": 15000}
|
||||
)
|
||||
|
||||
print("\nTest 1: Premium tier, high spend")
|
||||
workflow.start("Determine support level")
|
||||
|
||||
workflow2 = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{tier}} == premium",
|
||||
then_steps=[
|
||||
if_(
|
||||
condition="{{spend}} > 10000",
|
||||
then_steps=[vip_treatment],
|
||||
else_steps=[regular_premium]
|
||||
)
|
||||
],
|
||||
else_steps=[basic_support]
|
||||
)
|
||||
],
|
||||
variables={"tier": "premium", "spend": 5000}
|
||||
)
|
||||
|
||||
print("\nTest 2: Premium tier, low spend")
|
||||
workflow2.start("Determine support level")
|
||||
|
||||
workflow3 = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{tier}} == premium",
|
||||
then_steps=[
|
||||
if_(
|
||||
condition="{{spend}} > 10000",
|
||||
then_steps=[vip_treatment],
|
||||
else_steps=[regular_premium]
|
||||
)
|
||||
],
|
||||
else_steps=[basic_support]
|
||||
)
|
||||
],
|
||||
variables={"tier": "basic", "spend": 20000}
|
||||
)
|
||||
|
||||
print("\nTest 3: Basic tier (regardless of spend)")
|
||||
workflow3.start("Determine support level")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 6: No Else Branch - Optional Processing
|
||||
# =============================================================================
|
||||
|
||||
def no_else_example():
|
||||
"""
|
||||
If condition without else branch - only execute if true.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 6: No Else Branch - Optional Processing")
|
||||
print("="*60)
|
||||
|
||||
def send_notification(ctx: WorkflowContext) -> StepResult:
|
||||
print(" 📧 Notification sent!")
|
||||
return StepResult(output="Notification sent")
|
||||
|
||||
def continue_processing(ctx: WorkflowContext) -> StepResult:
|
||||
print(" ➡️ Continuing with main workflow...")
|
||||
return StepResult(output="Continued")
|
||||
|
||||
# Only send notification if opted in
|
||||
workflow = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{notify}} == true",
|
||||
then_steps=[send_notification]
|
||||
# No else_steps - just skip if false
|
||||
),
|
||||
continue_processing
|
||||
],
|
||||
variables={"notify": "true"}
|
||||
)
|
||||
|
||||
print("\nTest 1: notify = true")
|
||||
workflow.start("Process with notification")
|
||||
|
||||
workflow2 = Workflow(
|
||||
steps=[
|
||||
if_(
|
||||
condition="{{notify}} == true",
|
||||
then_steps=[send_notification]
|
||||
),
|
||||
continue_processing
|
||||
],
|
||||
variables={"notify": "false"}
|
||||
)
|
||||
|
||||
print("\nTest 2: notify = false")
|
||||
workflow2.start("Process without notification")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Main - Run All Examples
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n" + "="*60)
|
||||
print("PraisonAI Conditional Branching Examples")
|
||||
print("="*60)
|
||||
|
||||
basic_if_example()
|
||||
string_condition_example()
|
||||
contains_condition_example()
|
||||
if_inside_loop_example()
|
||||
nested_conditions_example()
|
||||
no_else_example()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("All conditional branching examples completed!")
|
||||
print("="*60)
|
||||
@@ -0,0 +1,214 @@
|
||||
"""Example: Merge Strategies for Multi-Agent Workflows
|
||||
|
||||
This example demonstrates how to use and create custom merge strategies
|
||||
for combining outputs from parallel agent executions.
|
||||
"""
|
||||
from praisonaiagents.agents import (
|
||||
MergeStrategyProtocol,
|
||||
FirstWinsMerge,
|
||||
ConcatMerge,
|
||||
DictMerge,
|
||||
)
|
||||
|
||||
|
||||
def example_first_wins_merge():
|
||||
"""Demonstrate FirstWinsMerge strategy."""
|
||||
print("=" * 50)
|
||||
print("FirstWinsMerge Examples")
|
||||
print("=" * 50)
|
||||
|
||||
merge = FirstWinsMerge()
|
||||
|
||||
# 1. Basic usage - returns first non-None
|
||||
print("\n1. Basic Usage:")
|
||||
outputs = [None, "Agent 2 response", "Agent 3 response"]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 2. All None returns None
|
||||
print("\n2. All None:")
|
||||
outputs = [None, None, None]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 3. First value wins
|
||||
print("\n3. First Value Wins:")
|
||||
outputs = ["Fast agent", "Slow agent", "Slowest agent"]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 4. Works with any type
|
||||
print("\n4. Various Types:")
|
||||
print(f" Dict: {merge.merge([None, {'key': 'value'}])}")
|
||||
print(f" List: {merge.merge([None, [1, 2, 3]])}")
|
||||
print(f" Number: {merge.merge([None, 42])}")
|
||||
|
||||
|
||||
def example_concat_merge():
|
||||
"""Demonstrate ConcatMerge strategy."""
|
||||
print("\n" + "=" * 50)
|
||||
print("ConcatMerge Examples")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. Default separator (double newline)
|
||||
print("\n1. Default Separator:")
|
||||
merge = ConcatMerge()
|
||||
outputs = ["Part 1: Introduction", "Part 2: Analysis", "Part 3: Conclusion"]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Result:\n{result}")
|
||||
|
||||
# 2. Custom separator
|
||||
print("\n2. Custom Separator:")
|
||||
merge = ConcatMerge(separator=" | ")
|
||||
outputs = ["Alice", "Bob", "Charlie"]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 3. Skips None values
|
||||
print("\n3. Skips None:")
|
||||
merge = ConcatMerge(separator=", ")
|
||||
outputs = ["A", None, "C", None, "E"]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 4. Converts non-strings
|
||||
print("\n4. Converts Non-Strings:")
|
||||
merge = ConcatMerge(separator=" + ")
|
||||
outputs = [1, 2, 3]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
|
||||
def example_dict_merge():
|
||||
"""Demonstrate DictMerge strategy."""
|
||||
print("\n" + "=" * 50)
|
||||
print("DictMerge Examples")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. Basic shallow merge
|
||||
print("\n1. Shallow Merge (default):")
|
||||
merge = DictMerge()
|
||||
outputs = [
|
||||
{"name": "Alice", "score": 10},
|
||||
{"name": "Bob", "level": 5},
|
||||
{"score": 20, "rank": 1}
|
||||
]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 2. Later values override
|
||||
print("\n2. Override Behavior:")
|
||||
merge = DictMerge()
|
||||
outputs = [
|
||||
{"status": "pending"},
|
||||
{"status": "processing"},
|
||||
{"status": "complete"}
|
||||
]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 3. Deep merge for nested dicts
|
||||
print("\n3. Deep Merge:")
|
||||
merge = DictMerge(deep=True)
|
||||
outputs = [
|
||||
{"config": {"a": 1, "b": 2}},
|
||||
{"config": {"b": 3, "c": 4}},
|
||||
{"config": {"d": 5}}
|
||||
]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 4. Skips non-dict values
|
||||
print("\n4. Skips Non-Dict Values:")
|
||||
merge = DictMerge()
|
||||
outputs = [{"a": 1}, "not a dict", None, {"b": 2}]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
|
||||
def example_custom_merge():
|
||||
"""Demonstrate creating custom merge strategies."""
|
||||
print("\n" + "=" * 50)
|
||||
print("Custom Merge Strategy Examples")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. Longest output merge
|
||||
class LongestOutputMerge:
|
||||
"""Returns the longest output."""
|
||||
def merge(self, outputs, context=None):
|
||||
valid = [o for o in outputs if o is not None]
|
||||
if not valid:
|
||||
return None
|
||||
return max(valid, key=lambda x: len(str(x)))
|
||||
|
||||
print("\n1. LongestOutputMerge:")
|
||||
merge = LongestOutputMerge()
|
||||
outputs = ["short", "medium length", "this is the longest response"]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 2. Majority vote merge
|
||||
class MajorityVoteMerge:
|
||||
"""Returns the most common output."""
|
||||
def merge(self, outputs, context=None):
|
||||
from collections import Counter
|
||||
valid = [o for o in outputs if o is not None]
|
||||
if not valid:
|
||||
return None
|
||||
counts = Counter(valid)
|
||||
return counts.most_common(1)[0][0]
|
||||
|
||||
print("\n2. MajorityVoteMerge:")
|
||||
merge = MajorityVoteMerge()
|
||||
outputs = ["yes", "yes", "no", "yes", "no"]
|
||||
result = merge.merge(outputs)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Result: {result} (3 votes)")
|
||||
|
||||
# 3. Weighted merge
|
||||
class WeightedMerge:
|
||||
"""Merges with agent weights from context."""
|
||||
def merge(self, outputs, context=None):
|
||||
weights = context.get("weights", [1] * len(outputs)) if context else [1] * len(outputs)
|
||||
best_idx = 0
|
||||
best_weight = 0
|
||||
for i, (output, weight) in enumerate(zip(outputs, weights)):
|
||||
if output is not None and weight > best_weight:
|
||||
best_idx = i
|
||||
best_weight = weight
|
||||
return outputs[best_idx] if outputs else None
|
||||
|
||||
print("\n3. WeightedMerge:")
|
||||
merge = WeightedMerge()
|
||||
outputs = ["junior answer", "senior answer", "expert answer"]
|
||||
context = {"weights": [1, 5, 10]}
|
||||
result = merge.merge(outputs, context=context)
|
||||
print(f" Inputs: {outputs}")
|
||||
print(f" Weights: {context['weights']}")
|
||||
print(f" Result: {result}")
|
||||
|
||||
# 4. Protocol compliance
|
||||
print("\n4. Protocol Compliance:")
|
||||
print(f" LongestOutputMerge: {isinstance(LongestOutputMerge(), MergeStrategyProtocol)}")
|
||||
print(f" MajorityVoteMerge: {isinstance(MajorityVoteMerge(), MergeStrategyProtocol)}")
|
||||
print(f" WeightedMerge: {isinstance(WeightedMerge(), MergeStrategyProtocol)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
example_first_wins_merge()
|
||||
example_concat_merge()
|
||||
example_dict_merge()
|
||||
example_custom_merge()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("All examples completed!")
|
||||
print("=" * 50)
|
||||
@@ -0,0 +1,174 @@
|
||||
"""Example: Retry Policies and Fallback Chains
|
||||
|
||||
This example demonstrates how to configure retry behavior with
|
||||
exponential backoff, jitter, and fallback chains.
|
||||
"""
|
||||
from praisonaiagents.tools import (
|
||||
RetryPolicy,
|
||||
FallbackChain,
|
||||
ToolExecutionConfig,
|
||||
)
|
||||
|
||||
|
||||
def example_retry_policy():
|
||||
"""Demonstrate RetryPolicy configuration and usage."""
|
||||
print("=" * 50)
|
||||
print("RetryPolicy Examples")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. Default policy
|
||||
print("\n1. Default RetryPolicy:")
|
||||
policy = RetryPolicy()
|
||||
print(f" max_attempts: {policy.max_attempts}")
|
||||
print(f" backoff_factor: {policy.backoff_factor}")
|
||||
print(f" initial_delay_ms: {policy.initial_delay_ms}")
|
||||
print(f" max_delay_ms: {policy.max_delay_ms}")
|
||||
print(f" retry_on: {policy.retry_on}")
|
||||
|
||||
# 2. Exponential backoff calculation
|
||||
print("\n2. Exponential Backoff (no jitter):")
|
||||
policy = RetryPolicy(
|
||||
initial_delay_ms=1000,
|
||||
backoff_factor=2.0,
|
||||
max_delay_ms=30000,
|
||||
jitter=False
|
||||
)
|
||||
for attempt in range(5):
|
||||
delay = policy.get_delay_ms(attempt)
|
||||
print(f" Attempt {attempt}: delay = {delay}ms")
|
||||
|
||||
# 3. With jitter for production
|
||||
print("\n3. With Jitter (production-ready):")
|
||||
policy = RetryPolicy(
|
||||
initial_delay_ms=1000,
|
||||
backoff_factor=2.0,
|
||||
jitter=True,
|
||||
jitter_factor=0.25 # ±25%
|
||||
)
|
||||
print(" Delays for attempt 0 (10 samples):")
|
||||
delays = [policy.get_delay_ms(0) for _ in range(10)]
|
||||
print(f" {delays}")
|
||||
print(f" Range: {min(delays)} - {max(delays)} (expected: 750-1250)")
|
||||
|
||||
# 4. should_retry logic
|
||||
print("\n4. should_retry Logic:")
|
||||
policy = RetryPolicy(max_attempts=3, retry_on={"timeout", "rate_limit"})
|
||||
|
||||
test_cases = [
|
||||
("timeout", 0),
|
||||
("timeout", 2),
|
||||
("timeout", 3), # At max_attempts
|
||||
("unknown_error", 0), # Not in retry_on
|
||||
]
|
||||
for error_type, attempt in test_cases:
|
||||
result = policy.should_retry(error_type, attempt)
|
||||
print(f" should_retry('{error_type}', {attempt}) = {result}")
|
||||
|
||||
# 5. Custom error types
|
||||
print("\n5. Custom Error Types:")
|
||||
policy = RetryPolicy(retry_on={"api_error", "network_error", "throttled"})
|
||||
print(f" Retries on: {policy.retry_on}")
|
||||
print(f" should_retry('api_error', 0) = {policy.should_retry('api_error', 0)}")
|
||||
print(f" should_retry('timeout', 0) = {policy.should_retry('timeout', 0)}")
|
||||
|
||||
|
||||
def example_fallback_chain():
|
||||
"""Demonstrate FallbackChain configuration."""
|
||||
print("\n" + "=" * 50)
|
||||
print("FallbackChain Examples")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. Basic chain
|
||||
print("\n1. Basic FallbackChain:")
|
||||
chain = FallbackChain(
|
||||
tools=["web_search", "cached_search", "default_response"]
|
||||
)
|
||||
print(f" Tools: {chain.tools}")
|
||||
print(f" Length: {len(chain)}")
|
||||
print(f" stop_on_success: {chain.stop_on_success}")
|
||||
|
||||
# 2. Iteration
|
||||
print("\n2. Iterating over chain:")
|
||||
for i, tool in enumerate(chain):
|
||||
print(f" {i+1}. {tool}")
|
||||
|
||||
# 3. Try all (don't stop on success)
|
||||
print("\n3. Try All Mode:")
|
||||
chain = FallbackChain(
|
||||
tools=["validator_1", "validator_2", "validator_3"],
|
||||
stop_on_success=False
|
||||
)
|
||||
print(f" stop_on_success: {chain.stop_on_success}")
|
||||
print(" (All tools will be tried regardless of success)")
|
||||
|
||||
|
||||
def example_tool_execution_config():
|
||||
"""Demonstrate ToolExecutionConfig combining retry and fallback."""
|
||||
print("\n" + "=" * 50)
|
||||
print("ToolExecutionConfig Examples")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. Default config
|
||||
print("\n1. Default Config:")
|
||||
config = ToolExecutionConfig()
|
||||
print(f" retry_policy: {config.retry_policy}")
|
||||
print(f" fallback_chain: {config.fallback_chain}")
|
||||
print(f" timeout_ms: {config.timeout_ms}")
|
||||
|
||||
# 2. Using default() factory
|
||||
print("\n2. Default Factory:")
|
||||
config = ToolExecutionConfig.default()
|
||||
print(f" retry_policy.max_attempts: {config.retry_policy.max_attempts}")
|
||||
|
||||
# 3. Full configuration
|
||||
print("\n3. Full Configuration:")
|
||||
config = ToolExecutionConfig(
|
||||
retry_policy=RetryPolicy(
|
||||
max_attempts=5,
|
||||
backoff_factor=1.5,
|
||||
initial_delay_ms=500,
|
||||
jitter=True
|
||||
),
|
||||
fallback_chain=FallbackChain(
|
||||
tools=["primary_api", "backup_api", "cache"]
|
||||
),
|
||||
timeout_ms=10000
|
||||
)
|
||||
print(f" retry_policy.max_attempts: {config.retry_policy.max_attempts}")
|
||||
print(f" retry_policy.jitter: {config.retry_policy.jitter}")
|
||||
print(f" fallback_chain tools: {config.fallback_chain.tools}")
|
||||
print(f" timeout_ms: {config.timeout_ms}")
|
||||
|
||||
|
||||
def example_validation():
|
||||
"""Demonstrate validation of RetryPolicy parameters."""
|
||||
print("\n" + "=" * 50)
|
||||
print("Validation Examples")
|
||||
print("=" * 50)
|
||||
|
||||
invalid_configs = [
|
||||
("max_attempts=0", {"max_attempts": 0}),
|
||||
("backoff_factor=0.5", {"backoff_factor": 0.5}),
|
||||
("initial_delay_ms=-1", {"initial_delay_ms": -1}),
|
||||
("max_delay < initial", {"initial_delay_ms": 1000, "max_delay_ms": 500}),
|
||||
("jitter_factor=1.5", {"jitter_factor": 1.5}),
|
||||
]
|
||||
|
||||
for name, kwargs in invalid_configs:
|
||||
try:
|
||||
RetryPolicy(**kwargs)
|
||||
print(f"\n {name}: No error (unexpected!)")
|
||||
except ValueError as e:
|
||||
print(f"\n {name}:")
|
||||
print(f" → ValueError: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
example_retry_policy()
|
||||
example_fallback_chain()
|
||||
example_tool_execution_config()
|
||||
example_validation()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("All examples completed!")
|
||||
print("=" * 50)
|
||||
@@ -0,0 +1,163 @@
|
||||
"""Example: Tool Validation with Custom Validators
|
||||
|
||||
This example demonstrates how to use ValidationResult and implement
|
||||
custom validators using the ToolValidatorProtocol.
|
||||
"""
|
||||
from praisonaiagents.tools import (
|
||||
ValidationResult,
|
||||
ToolValidatorProtocol,
|
||||
PassthroughValidator,
|
||||
)
|
||||
|
||||
|
||||
def example_validation_result():
|
||||
"""Demonstrate ValidationResult usage."""
|
||||
print("=" * 50)
|
||||
print("ValidationResult Examples")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. Success factory
|
||||
success = ValidationResult.success()
|
||||
print(f"\n1. Success: valid={success.valid}, bool={bool(success)}")
|
||||
|
||||
# 2. Failure factory
|
||||
failure = ValidationResult.failure(
|
||||
errors=["Query cannot be empty", "Missing required field"],
|
||||
remediation="Provide a non-empty query string"
|
||||
)
|
||||
print(f"\n2. Failure: valid={failure.valid}")
|
||||
print(f" Errors: {failure.errors}")
|
||||
print(f" Remediation: {failure.remediation}")
|
||||
|
||||
# 3. Fluent API - building incrementally
|
||||
result = ValidationResult(valid=True)
|
||||
result.add_warning("Query is very long, may be slow")
|
||||
result.add_warning("Consider using pagination")
|
||||
print(f"\n3. With warnings: valid={result.valid}, warnings={result.warnings}")
|
||||
|
||||
# 4. Adding errors changes validity
|
||||
result.add_error("Rate limit exceeded")
|
||||
print(f"\n4. After error: valid={result.valid}, errors={result.errors}")
|
||||
|
||||
|
||||
class InputLengthValidator:
|
||||
"""Custom validator that checks string input lengths."""
|
||||
|
||||
def __init__(self, max_length: int = 100):
|
||||
self.max_length = max_length
|
||||
|
||||
def validate_args(self, tool_name, args, context=None):
|
||||
"""Validate that string arguments don't exceed max length."""
|
||||
for key, value in args.items():
|
||||
if isinstance(value, str) and len(value) > self.max_length:
|
||||
return ValidationResult.failure(
|
||||
errors=[f"Argument '{key}' ({len(value)} chars) exceeds limit ({self.max_length})"],
|
||||
remediation=f"Truncate '{key}' to {self.max_length} characters or less"
|
||||
)
|
||||
return ValidationResult.success()
|
||||
|
||||
def validate_result(self, tool_name, result, context=None):
|
||||
"""Validate tool results."""
|
||||
return ValidationResult.success()
|
||||
|
||||
|
||||
class TypeValidator:
|
||||
"""Custom validator that checks argument types."""
|
||||
|
||||
def __init__(self, type_specs: dict):
|
||||
"""
|
||||
Args:
|
||||
type_specs: Dict mapping arg names to expected types
|
||||
e.g., {"query": str, "limit": int}
|
||||
"""
|
||||
self.type_specs = type_specs
|
||||
|
||||
def validate_args(self, tool_name, args, context=None):
|
||||
result = ValidationResult(valid=True)
|
||||
|
||||
for arg_name, expected_type in self.type_specs.items():
|
||||
if arg_name in args:
|
||||
actual_value = args[arg_name]
|
||||
if not isinstance(actual_value, expected_type):
|
||||
result.add_error(
|
||||
f"Argument '{arg_name}' must be {expected_type.__name__}, "
|
||||
f"got {type(actual_value).__name__}"
|
||||
)
|
||||
|
||||
if not result.valid:
|
||||
result.remediation = "Check argument types and try again"
|
||||
|
||||
return result
|
||||
|
||||
def validate_result(self, tool_name, result, context=None):
|
||||
return ValidationResult.success()
|
||||
|
||||
|
||||
def example_custom_validators():
|
||||
"""Demonstrate custom validator implementations."""
|
||||
print("\n" + "=" * 50)
|
||||
print("Custom Validator Examples")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. InputLengthValidator
|
||||
print("\n1. InputLengthValidator:")
|
||||
validator = InputLengthValidator(max_length=50)
|
||||
|
||||
# Valid input
|
||||
result = validator.validate_args("search", {"query": "short query"})
|
||||
print(f" Short query: valid={result.valid}")
|
||||
|
||||
# Invalid input
|
||||
long_query = "x" * 100
|
||||
result = validator.validate_args("search", {"query": long_query})
|
||||
print(f" Long query: valid={result.valid}")
|
||||
print(f" Error: {result.errors[0]}")
|
||||
print(f" Fix: {result.remediation}")
|
||||
|
||||
# 2. TypeValidator
|
||||
print("\n2. TypeValidator:")
|
||||
validator = TypeValidator({"query": str, "limit": int, "offset": int})
|
||||
|
||||
# Valid types
|
||||
result = validator.validate_args("search", {"query": "test", "limit": 10})
|
||||
print(f" Correct types: valid={result.valid}")
|
||||
|
||||
# Invalid types
|
||||
result = validator.validate_args("search", {"query": 123, "limit": "ten"})
|
||||
print(f" Wrong types: valid={result.valid}")
|
||||
for error in result.errors:
|
||||
print(f" - {error}")
|
||||
|
||||
# 3. Protocol compliance check
|
||||
print("\n3. Protocol Compliance:")
|
||||
print(f" InputLengthValidator: {isinstance(InputLengthValidator(), ToolValidatorProtocol)}")
|
||||
print(f" TypeValidator: {isinstance(TypeValidator({}), ToolValidatorProtocol)}")
|
||||
print(f" PassthroughValidator: {isinstance(PassthroughValidator(), ToolValidatorProtocol)}")
|
||||
|
||||
|
||||
def example_passthrough_validator():
|
||||
"""Demonstrate the default PassthroughValidator."""
|
||||
print("\n" + "=" * 50)
|
||||
print("PassthroughValidator Example")
|
||||
print("=" * 50)
|
||||
|
||||
validator = PassthroughValidator()
|
||||
|
||||
# Always returns valid
|
||||
result = validator.validate_args("any_tool", {"any": "args"})
|
||||
print(f"\nvalidate_args: valid={result.valid}")
|
||||
|
||||
result = validator.validate_result("any_tool", {"any": "result"})
|
||||
print(f"validate_result: valid={result.valid}")
|
||||
|
||||
print("\nPassthroughValidator is useful as a default when no validation is needed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
example_validation_result()
|
||||
example_custom_validators()
|
||||
example_passthrough_validator()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("All examples completed!")
|
||||
print("=" * 50)
|
||||
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Model Failover Example - Automatic Provider Switching
|
||||
|
||||
This example demonstrates how to configure model failover
|
||||
for reliability and cost optimization.
|
||||
"""
|
||||
|
||||
import os
|
||||
from praisonaiagents import (
|
||||
AuthProfile,
|
||||
FailoverConfig,
|
||||
FailoverManager,
|
||||
)
|
||||
|
||||
# Create auth profiles for different providers
|
||||
openai_profile = AuthProfile(
|
||||
name="openai-primary",
|
||||
provider="openai",
|
||||
api_key=os.environ.get("OPENAI_API_KEY", "sk-..."),
|
||||
priority=1, # Highest priority
|
||||
rate_limit=100,
|
||||
)
|
||||
|
||||
anthropic_profile = AuthProfile(
|
||||
name="anthropic-backup",
|
||||
provider="anthropic",
|
||||
api_key=os.environ.get("ANTHROPIC_API_KEY", "sk-ant-..."),
|
||||
priority=2, # Second priority
|
||||
rate_limit=50,
|
||||
)
|
||||
|
||||
groq_profile = AuthProfile(
|
||||
name="groq-fallback",
|
||||
provider="groq",
|
||||
api_key=os.environ.get("GROQ_API_KEY", "gsk-..."),
|
||||
priority=3, # Third priority (fast but limited)
|
||||
rate_limit=30,
|
||||
)
|
||||
|
||||
# Configure failover behavior
|
||||
failover_config = FailoverConfig(
|
||||
max_retries=3,
|
||||
retry_delay=1.0,
|
||||
exponential_backoff=True,
|
||||
max_retry_delay=60.0,
|
||||
failover_on_rate_limit=True,
|
||||
failover_on_timeout=True,
|
||||
failover_on_error=True,
|
||||
)
|
||||
|
||||
# Create failover manager
|
||||
manager = FailoverManager(failover_config)
|
||||
manager.add_profile(openai_profile)
|
||||
manager.add_profile(anthropic_profile)
|
||||
manager.add_profile(groq_profile)
|
||||
|
||||
def demonstrate_failover():
|
||||
"""Demonstrate failover functionality."""
|
||||
|
||||
print("Failover Manager Status:")
|
||||
print()
|
||||
|
||||
# Get current status
|
||||
status = manager.status()
|
||||
for name, info in status.items():
|
||||
print(f" {name}:")
|
||||
print(f" Status: {info.get('status', 'unknown')}")
|
||||
print(f" Priority: {info.get('priority', 'N/A')}")
|
||||
print(f" Failures: {info.get('failure_count', 0)}")
|
||||
print()
|
||||
|
||||
# Get next available profile
|
||||
next_profile = manager.get_next_profile()
|
||||
if next_profile:
|
||||
print(f"Next profile to use: {next_profile.name}")
|
||||
print(f" Provider: {next_profile.provider}")
|
||||
print(f" Priority: {next_profile.priority}")
|
||||
print()
|
||||
|
||||
# Simulate marking a failure
|
||||
print("Simulating failure on primary provider...")
|
||||
manager.mark_failure("openai-primary", "Rate limit exceeded")
|
||||
|
||||
# Get next profile after failure
|
||||
next_profile = manager.get_next_profile()
|
||||
if next_profile:
|
||||
print(f"After failure, next profile: {next_profile.name}")
|
||||
print()
|
||||
|
||||
# Get retry delay
|
||||
delay = manager.get_retry_delay("openai-primary")
|
||||
print(f"Retry delay for openai-primary: {delay}s")
|
||||
print()
|
||||
|
||||
# Reset all providers
|
||||
print("Resetting all providers...")
|
||||
manager.reset_all()
|
||||
|
||||
next_profile = manager.get_next_profile()
|
||||
if next_profile:
|
||||
print(f"After reset, next profile: {next_profile.name}")
|
||||
|
||||
def cost_optimization_example():
|
||||
"""Example of cost-optimized failover."""
|
||||
|
||||
print("\nCost Optimization Example:")
|
||||
print("-" * 40)
|
||||
|
||||
# Create cost-optimized manager
|
||||
cost_manager = FailoverManager()
|
||||
|
||||
# Cheaper model first
|
||||
cost_manager.add_profile(AuthProfile(
|
||||
name="gpt-4o-mini",
|
||||
provider="openai",
|
||||
api_key=os.environ.get("OPENAI_API_KEY", ""),
|
||||
priority=1,
|
||||
metadata={"model": "gpt-4o-mini", "cost_per_1k": 0.00015}
|
||||
))
|
||||
|
||||
# Premium model as fallback
|
||||
cost_manager.add_profile(AuthProfile(
|
||||
name="gpt-4o",
|
||||
provider="openai",
|
||||
api_key=os.environ.get("OPENAI_API_KEY", ""),
|
||||
priority=2,
|
||||
metadata={"model": "gpt-4o", "cost_per_1k": 0.005}
|
||||
))
|
||||
|
||||
# Most expensive as last resort
|
||||
cost_manager.add_profile(AuthProfile(
|
||||
name="claude-opus",
|
||||
provider="anthropic",
|
||||
api_key=os.environ.get("ANTHROPIC_API_KEY", ""),
|
||||
priority=3,
|
||||
metadata={"model": "claude-3-opus", "cost_per_1k": 0.015}
|
||||
))
|
||||
|
||||
print("Cost-optimized failover chain:")
|
||||
for profile in [
|
||||
cost_manager.get_profile("gpt-4o-mini"),
|
||||
cost_manager.get_profile("gpt-4o"),
|
||||
cost_manager.get_profile("claude-opus"),
|
||||
]:
|
||||
if profile:
|
||||
cost = profile.metadata.get("cost_per_1k", "N/A")
|
||||
print(f" {profile.priority}. {profile.name} (${cost}/1K tokens)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Model Failover Configuration")
|
||||
print("=" * 40)
|
||||
print()
|
||||
|
||||
print("Failover Config:")
|
||||
print(f" Max retries: {failover_config.max_retries}")
|
||||
print(f" Initial delay: {failover_config.retry_delay}s")
|
||||
print(f" Exponential backoff: {failover_config.exponential_backoff}")
|
||||
print(f" Max delay: {failover_config.max_retry_delay}s")
|
||||
print()
|
||||
|
||||
print("Auth Profiles:")
|
||||
for profile in [openai_profile, anthropic_profile, groq_profile]:
|
||||
print(f" {profile.name}:")
|
||||
print(f" Provider: {profile.provider}")
|
||||
print(f" Priority: {profile.priority}")
|
||||
print(f" Rate limit: {profile.rate_limit} req/min")
|
||||
print()
|
||||
|
||||
demonstrate_failover()
|
||||
cost_optimization_example()
|
||||
File renamed without changes.
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Gateway Example - Multi-Agent Coordination
|
||||
|
||||
This example demonstrates how to use the Gateway for coordinating
|
||||
multiple agents and managing real-time communication.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, GatewayConfig, SessionConfig
|
||||
|
||||
# Configure the gateway
|
||||
gateway_config = GatewayConfig(
|
||||
host="127.0.0.1",
|
||||
port=8765,
|
||||
max_connections=100,
|
||||
heartbeat_interval=30,
|
||||
session_config=SessionConfig(
|
||||
timeout=3600, # 1 hour session timeout
|
||||
max_messages=500,
|
||||
)
|
||||
)
|
||||
|
||||
# Create specialized agents
|
||||
researcher = Agent(
|
||||
name="researcher",
|
||||
instructions="You research topics thoroughly and provide detailed information.",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="writer",
|
||||
instructions="You write clear, engaging content based on research.",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Example: Simple agent interaction
|
||||
if __name__ == "__main__":
|
||||
print("Gateway Configuration:")
|
||||
print(f" Host: {gateway_config.host}")
|
||||
print(f" Port: {gateway_config.port}")
|
||||
print(f" WebSocket URL: {gateway_config.ws_url}")
|
||||
print(f" Max Connections: {gateway_config.max_connections}")
|
||||
print()
|
||||
|
||||
# Test agent
|
||||
response = researcher.start("What are the key benefits of multi-agent systems?")
|
||||
print("Researcher Response:")
|
||||
print(response)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Example: Memory with Tracing
|
||||
|
||||
This example demonstrates how to use memory with context tracing enabled.
|
||||
When tracing is enabled, memory operations (store/search) emit trace events
|
||||
that can be used for debugging and analysis with the LLM Judge.
|
||||
|
||||
Usage:
|
||||
python memory_with_tracing_example.py
|
||||
|
||||
To judge the trace:
|
||||
praisonai recipe judge <trace_id> --memory
|
||||
"""
|
||||
|
||||
from praisonaiagents.memory import Memory
|
||||
from praisonaiagents.trace.context_events import (
|
||||
ContextTraceEmitter,
|
||||
ContextListSink,
|
||||
set_context_emitter,
|
||||
reset_context_emitter,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Set up trace emitter to capture events
|
||||
sink = ContextListSink()
|
||||
emitter = ContextTraceEmitter(sink=sink, session_id="memory-demo", enabled=True)
|
||||
token = set_context_emitter(emitter)
|
||||
|
||||
try:
|
||||
# Create memory instance
|
||||
memory = Memory(config={
|
||||
"provider": "rag",
|
||||
"use_embedding": False,
|
||||
"agent_name": "demo_agent", # Used in trace events
|
||||
})
|
||||
|
||||
# Store some memories
|
||||
print("Storing memories...")
|
||||
memory.store_short_term("The user's name is Alice")
|
||||
memory.store_short_term("Alice likes Python programming")
|
||||
memory.store_long_term("Important: Alice prefers dark mode in all applications")
|
||||
|
||||
# Search memories
|
||||
print("\nSearching memories...")
|
||||
results = memory.search_short_term("Alice", limit=5)
|
||||
print(f"Found {len(results)} short-term memories")
|
||||
|
||||
results = memory.search_long_term("preferences", limit=5)
|
||||
print(f"Found {len(results)} long-term memories")
|
||||
|
||||
# Print captured trace events
|
||||
events = sink.get_events()
|
||||
print(f"\n--- Captured {len(events)} trace events ---")
|
||||
for event in events:
|
||||
print(f" {event.event_type.value}: {event.data}")
|
||||
|
||||
# Filter memory-specific events
|
||||
memory_events = [e for e in events if 'memory' in e.event_type.value]
|
||||
print(f"\n--- Memory-specific events: {len(memory_events)} ---")
|
||||
for event in memory_events:
|
||||
print(f" {event.event_type.value}:")
|
||||
print(f" agent: {event.agent_name}")
|
||||
print(f" data: {event.data}")
|
||||
|
||||
finally:
|
||||
reset_context_emitter(token)
|
||||
print("\nTrace emitter reset.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
Nested Workflows Example - PraisonAI Agents
|
||||
|
||||
This example demonstrates how to use nested workflow patterns:
|
||||
1. Loop inside Loop (nested iteration)
|
||||
2. Parallel inside Loop (concurrent processing per item)
|
||||
3. Route inside Loop (conditional routing per item)
|
||||
4. Depth limit protection (prevents infinite recursion)
|
||||
|
||||
These patterns enable complex data processing pipelines
|
||||
while maintaining clean, readable workflow definitions.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow, WorkflowContext, StepResult
|
||||
from praisonaiagents.workflows import loop, parallel, route
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 1: Nested Loops - Process Matrix Data
|
||||
# =============================================================================
|
||||
|
||||
def nested_loop_example():
|
||||
"""
|
||||
Process a 2D matrix using nested loops.
|
||||
Outer loop iterates rows, inner loop iterates columns.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 1: Nested Loops - Matrix Processing")
|
||||
print("="*60)
|
||||
|
||||
results = []
|
||||
|
||||
def process_cell(ctx: WorkflowContext) -> StepResult:
|
||||
row = ctx.variables.get("row", {})
|
||||
col = ctx.variables.get("col", "")
|
||||
row_name = row.get("name", "?")
|
||||
results.append(f"{row_name}-{col}")
|
||||
return StepResult(output=f"Processed cell {row_name}-{col}")
|
||||
|
||||
# Inner loop processes columns for each row
|
||||
inner_loop = loop(
|
||||
process_cell,
|
||||
over="columns",
|
||||
var_name="col"
|
||||
)
|
||||
|
||||
# Outer loop iterates through rows
|
||||
outer_loop = loop(
|
||||
steps=[inner_loop],
|
||||
over="rows",
|
||||
var_name="row"
|
||||
)
|
||||
|
||||
workflow = Workflow(
|
||||
steps=[outer_loop],
|
||||
variables={
|
||||
"rows": [
|
||||
{"name": "Row1"},
|
||||
{"name": "Row2"}
|
||||
],
|
||||
"columns": ["A", "B", "C"]
|
||||
}
|
||||
)
|
||||
|
||||
workflow.start("Process matrix")
|
||||
print(f"Processed cells: {results}")
|
||||
# Output: ['Row1-A', 'Row1-B', 'Row1-C', 'Row2-A', 'Row2-B', 'Row2-C']
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 2: Parallel Inside Loop - Concurrent Task Processing
|
||||
# =============================================================================
|
||||
|
||||
def parallel_inside_loop_example():
|
||||
"""
|
||||
For each project, run multiple analysis tasks in parallel.
|
||||
Combines iteration with concurrent execution.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 2: Parallel Inside Loop - Concurrent Analysis")
|
||||
print("="*60)
|
||||
|
||||
results = []
|
||||
|
||||
def security_scan(ctx: WorkflowContext) -> StepResult:
|
||||
project = ctx.variables.get("project", "unknown")
|
||||
results.append(f"security:{project}")
|
||||
return StepResult(output=f"Security scan complete for {project}")
|
||||
|
||||
def performance_test(ctx: WorkflowContext) -> StepResult:
|
||||
project = ctx.variables.get("project", "unknown")
|
||||
results.append(f"performance:{project}")
|
||||
return StepResult(output=f"Performance test complete for {project}")
|
||||
|
||||
def code_review(ctx: WorkflowContext) -> StepResult:
|
||||
project = ctx.variables.get("project", "unknown")
|
||||
results.append(f"review:{project}")
|
||||
return StepResult(output=f"Code review complete for {project}")
|
||||
|
||||
# Parallel tasks run concurrently for each project
|
||||
parallel_analysis = parallel([security_scan, performance_test, code_review])
|
||||
|
||||
# Loop through projects, running parallel analysis for each
|
||||
workflow = Workflow(
|
||||
steps=[
|
||||
loop(
|
||||
steps=[parallel_analysis],
|
||||
over="projects",
|
||||
var_name="project"
|
||||
)
|
||||
],
|
||||
variables={
|
||||
"projects": ["frontend", "backend", "api"]
|
||||
}
|
||||
)
|
||||
|
||||
workflow.start("Analyze all projects")
|
||||
print(f"Analysis results: {sorted(results)}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 3: Route Inside Loop - Conditional Processing Per Item
|
||||
# =============================================================================
|
||||
|
||||
def route_inside_loop_example():
|
||||
"""
|
||||
Process items differently based on their type.
|
||||
Each item in the loop is routed to the appropriate handler.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 3: Route Inside Loop - Type-Based Processing")
|
||||
print("="*60)
|
||||
|
||||
results = []
|
||||
|
||||
def process_image(ctx: WorkflowContext) -> StepResult:
|
||||
item = ctx.variables.get("item", {})
|
||||
results.append(f"image:{item.get('name')}")
|
||||
return StepResult(output=f"Processed image: {item.get('name')}")
|
||||
|
||||
def process_video(ctx: WorkflowContext) -> StepResult:
|
||||
item = ctx.variables.get("item", {})
|
||||
results.append(f"video:{item.get('name')}")
|
||||
return StepResult(output=f"Processed video: {item.get('name')}")
|
||||
|
||||
def process_document(ctx: WorkflowContext) -> StepResult:
|
||||
item = ctx.variables.get("item", {})
|
||||
results.append(f"document:{item.get('name')}")
|
||||
return StepResult(output=f"Processed document: {item.get('name')}")
|
||||
|
||||
# Route based on item type
|
||||
type_router = route({
|
||||
"image": [process_image],
|
||||
"video": [process_video],
|
||||
"document": [process_document]
|
||||
})
|
||||
|
||||
# First, determine the type, then route
|
||||
def get_type(ctx: WorkflowContext) -> StepResult:
|
||||
item = ctx.variables.get("item", {})
|
||||
item_type = item.get("type", "unknown")
|
||||
return StepResult(output=item_type)
|
||||
|
||||
workflow = Workflow(
|
||||
steps=[
|
||||
loop(
|
||||
steps=[get_type, type_router],
|
||||
over="files",
|
||||
var_name="item"
|
||||
)
|
||||
],
|
||||
variables={
|
||||
"files": [
|
||||
{"name": "photo.jpg", "type": "image"},
|
||||
{"name": "movie.mp4", "type": "video"},
|
||||
{"name": "report.pdf", "type": "document"},
|
||||
{"name": "logo.png", "type": "image"}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
workflow.start("Process all files")
|
||||
print(f"Processing results: {results}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 4: Three-Level Nesting - Complex Data Pipeline
|
||||
# =============================================================================
|
||||
|
||||
def three_level_nesting_example():
|
||||
"""
|
||||
Demonstrate three levels of nesting:
|
||||
Department -> Team -> Member
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 4: Three-Level Nesting - Organization Processing")
|
||||
print("="*60)
|
||||
|
||||
results = []
|
||||
|
||||
def process_member(ctx: WorkflowContext) -> StepResult:
|
||||
dept = ctx.variables.get("dept", {}).get("name", "?")
|
||||
team = ctx.variables.get("team", {}).get("name", "?")
|
||||
member = ctx.variables.get("member", "?")
|
||||
results.append(f"{dept}/{team}/{member}")
|
||||
return StepResult(output=f"Processed {member}")
|
||||
|
||||
# Level 3: Process each member
|
||||
member_loop = loop(process_member, over="members", var_name="member")
|
||||
|
||||
# Level 2: Process each team (contains member loop)
|
||||
team_loop = loop(steps=[member_loop], over="teams", var_name="team")
|
||||
|
||||
# Level 1: Process each department (contains team loop)
|
||||
dept_loop = loop(steps=[team_loop], over="departments", var_name="dept")
|
||||
|
||||
workflow = Workflow(
|
||||
steps=[dept_loop],
|
||||
variables={
|
||||
"departments": [
|
||||
{"name": "Engineering"},
|
||||
{"name": "Marketing"}
|
||||
],
|
||||
"teams": [
|
||||
{"name": "Frontend", "members": ["Alice", "Bob"]},
|
||||
{"name": "Backend", "members": ["Charlie"]}
|
||||
],
|
||||
"members": ["Member1", "Member2"] # Simplified for demo
|
||||
}
|
||||
)
|
||||
|
||||
workflow.start("Process organization")
|
||||
print(f"Processed paths: {results}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Main - Run All Examples
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n" + "="*60)
|
||||
print("PraisonAI Nested Workflows Examples")
|
||||
print("="*60)
|
||||
|
||||
nested_loop_example()
|
||||
parallel_inside_loop_example()
|
||||
route_inside_loop_example()
|
||||
three_level_nesting_example()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("All nested workflow examples completed!")
|
||||
print("="*60)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Sandbox Example - Secure Code Execution
|
||||
|
||||
This example demonstrates how to configure and use sandboxes
|
||||
for safe code execution.
|
||||
"""
|
||||
|
||||
from praisonaiagents import (
|
||||
SandboxConfig,
|
||||
ResourceLimits,
|
||||
SandboxResult,
|
||||
SandboxStatus
|
||||
)
|
||||
from praisonaiagents.sandbox import SecurityPolicy
|
||||
|
||||
# Minimal sandbox for untrusted code
|
||||
minimal_sandbox = SandboxConfig(
|
||||
sandbox_type="subprocess",
|
||||
resource_limits=ResourceLimits.minimal(),
|
||||
security_policy=SecurityPolicy.strict(),
|
||||
)
|
||||
|
||||
# Standard sandbox for general use
|
||||
standard_sandbox = SandboxConfig(
|
||||
sandbox_type="subprocess",
|
||||
resource_limits=ResourceLimits.standard(),
|
||||
security_policy=SecurityPolicy.standard(),
|
||||
)
|
||||
|
||||
# Docker sandbox for full isolation
|
||||
docker_sandbox = SandboxConfig.docker(image="python:3.11-slim")
|
||||
|
||||
# Custom sandbox configuration
|
||||
custom_sandbox = SandboxConfig(
|
||||
sandbox_type="subprocess",
|
||||
working_dir="/tmp/sandbox",
|
||||
resource_limits=ResourceLimits(
|
||||
memory_mb=256,
|
||||
cpu_percent=50,
|
||||
timeout_seconds=30,
|
||||
network_enabled=False,
|
||||
max_processes=5,
|
||||
),
|
||||
security_policy=SecurityPolicy(
|
||||
allow_network=False,
|
||||
allow_file_write=True,
|
||||
allow_subprocess=False,
|
||||
blocked_imports=["subprocess", "os.system", "eval"],
|
||||
),
|
||||
auto_cleanup=True,
|
||||
)
|
||||
|
||||
def demonstrate_result_handling():
|
||||
"""Show how to handle sandbox results."""
|
||||
|
||||
# Simulated successful result
|
||||
success_result = SandboxResult(
|
||||
status=SandboxStatus.COMPLETED,
|
||||
exit_code=0,
|
||||
stdout="Hello, World!\n",
|
||||
stderr="",
|
||||
duration_seconds=0.1,
|
||||
)
|
||||
|
||||
# Simulated timeout result
|
||||
timeout_result = SandboxResult(
|
||||
status=SandboxStatus.TIMEOUT,
|
||||
exit_code=None,
|
||||
stdout="Partial output...",
|
||||
stderr="",
|
||||
error="Execution timed out after 30 seconds",
|
||||
duration_seconds=30.0,
|
||||
)
|
||||
|
||||
# Simulated error result
|
||||
error_result = SandboxResult(
|
||||
status=SandboxStatus.FAILED,
|
||||
exit_code=1,
|
||||
stdout="",
|
||||
stderr="NameError: name 'undefined_var' is not defined",
|
||||
error="Execution failed",
|
||||
)
|
||||
|
||||
print("Result Handling Examples:")
|
||||
print()
|
||||
|
||||
for name, result in [
|
||||
("Success", success_result),
|
||||
("Timeout", timeout_result),
|
||||
("Error", error_result)
|
||||
]:
|
||||
print(f"{name} Result:")
|
||||
print(f" Status: {result.status.name}")
|
||||
print(f" Success: {result.success}")
|
||||
if result.stdout:
|
||||
print(f" Output: {result.stdout.strip()}")
|
||||
if result.stderr:
|
||||
print(f" Error: {result.stderr.strip()}")
|
||||
if result.error:
|
||||
print(f" Message: {result.error}")
|
||||
print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Sandbox Configurations:")
|
||||
print()
|
||||
|
||||
print("Minimal Sandbox (for untrusted code):")
|
||||
print(f" Type: {minimal_sandbox.sandbox_type}")
|
||||
print(f" Memory: {minimal_sandbox.resource_limits.memory_mb}MB")
|
||||
print(f" Timeout: {minimal_sandbox.resource_limits.timeout_seconds}s")
|
||||
print(f" Network: {minimal_sandbox.resource_limits.network_enabled}")
|
||||
print()
|
||||
|
||||
print("Standard Sandbox:")
|
||||
print(f" Type: {standard_sandbox.sandbox_type}")
|
||||
print(f" Memory: {standard_sandbox.resource_limits.memory_mb}MB")
|
||||
print(f" Timeout: {standard_sandbox.resource_limits.timeout_seconds}s")
|
||||
print()
|
||||
|
||||
print("Docker Sandbox:")
|
||||
print(f" Type: {docker_sandbox.sandbox_type}")
|
||||
print(f" Image: {docker_sandbox.image}")
|
||||
print()
|
||||
|
||||
print("Custom Sandbox:")
|
||||
print(f" Type: {custom_sandbox.sandbox_type}")
|
||||
print(f" Working Dir: {custom_sandbox.working_dir}")
|
||||
print(f" Memory: {custom_sandbox.resource_limits.memory_mb}MB")
|
||||
print(f" Max Processes: {custom_sandbox.resource_limits.max_processes}")
|
||||
print()
|
||||
|
||||
demonstrate_result_handling()
|
||||
|
||||
print("To run code in sandbox via CLI:")
|
||||
print(" praisonai sandbox run \"print('Hello, World!')\"")
|
||||
print(" praisonai sandbox run --file script.py")
|
||||
print(" praisonai sandbox shell")
|
||||
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Example 1: Save Agent Output Using write_file Tool
|
||||
|
||||
The agent decides when and what to save based on the task.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.tools import write_file
|
||||
|
||||
# Create agent with write_file tool
|
||||
agent = Agent(
|
||||
name="ContentWriter",
|
||||
role="Technical Writer",
|
||||
goal="Create and save technical documentation",
|
||||
tools=[write_file]
|
||||
)
|
||||
|
||||
# Agent will use write_file tool to save the content
|
||||
result = agent.start("Write a short poem about coding and save it to poem.txt")
|
||||
|
||||
print("✅ Task completed!")
|
||||
print(f"Result: {result}")
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Example 2: Save Agent Output Using Task.output_file
|
||||
|
||||
Task output is automatically saved to the specified file.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, Task, Agents
|
||||
|
||||
# Create agent
|
||||
writer = Agent(
|
||||
name="ContentWriter",
|
||||
role="Writer",
|
||||
goal="Create engaging content"
|
||||
)
|
||||
|
||||
# Task with output_file - auto-saves result
|
||||
task = Task(
|
||||
description="Write a short blog post about the benefits of AI assistants",
|
||||
expected_output="A well-structured blog post in markdown format",
|
||||
agent=writer,
|
||||
output_file="blog_post.md",
|
||||
create_directory=True
|
||||
)
|
||||
|
||||
# Run
|
||||
agents = Agents(agents=[writer], tasks=[task])
|
||||
result = agents.start()
|
||||
|
||||
print("✅ Task completed!")
|
||||
print("Output saved to: blog_post.md")
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
Example 3: Save Agent Output Using Workflow output_file
|
||||
|
||||
Workflow step output is automatically saved with variable substitution.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Workflow
|
||||
|
||||
# Define workflow with output_file
|
||||
workflow_config = {
|
||||
"metadata": {
|
||||
"name": "content-generator",
|
||||
"version": "1.0"
|
||||
},
|
||||
"variables": {
|
||||
"output_dir": "generated",
|
||||
"topic": "Python programming"
|
||||
},
|
||||
"agents": {
|
||||
"writer": {
|
||||
"role": "Content Writer",
|
||||
"goal": "Create engaging content",
|
||||
"llm": "gpt-4o-mini"
|
||||
}
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"agent": "writer",
|
||||
"action": "Write a short tutorial about Python basics",
|
||||
"expected_output": "A beginner-friendly tutorial",
|
||||
"output_file": "{{output_dir}}/tutorial.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Run workflow
|
||||
workflow = Workflow(config=workflow_config)
|
||||
result = workflow.run()
|
||||
|
||||
print("✅ Workflow completed!")
|
||||
print("Output saved to: generated/tutorial.md")
|
||||
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
Example 4: Save Agent Output Manually
|
||||
|
||||
Capture the response and save it with full control.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from pathlib import Path
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="StoryWriter",
|
||||
instructions="You are a creative writer who writes short stories."
|
||||
)
|
||||
|
||||
# Get response
|
||||
response = agent.start("Write a very short story about a robot learning to paint")
|
||||
|
||||
# Save manually with error handling
|
||||
output_path = Path("stories/robot_story.txt")
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
output_path.write_text(response)
|
||||
print(f"✅ Saved to {output_path}")
|
||||
except IOError as e:
|
||||
print(f"❌ Failed to save: {e}")
|
||||
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Example: Auto-save agent output to file
|
||||
|
||||
This example demonstrates how to automatically save agent responses to files.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.config import OutputConfig
|
||||
|
||||
# Method 1: Simplest - just pass a file path string
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
output="output/response.txt" # Just a string path!
|
||||
)
|
||||
|
||||
# When you call start(), the response is automatically saved
|
||||
# result = agent.start("Write a short greeting")
|
||||
# Prints: ✅ Output saved to output/response.txt
|
||||
|
||||
# Method 2: With template formatting using OutputConfig
|
||||
agent_with_template = Agent(
|
||||
instructions="You are a blog writer",
|
||||
output=OutputConfig(
|
||||
output_file="output/blog_post.md",
|
||||
template="""# {{title}}
|
||||
|
||||
{{content}}
|
||||
|
||||
---
|
||||
*Generated by AI*
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
# The agent will format its response according to the template
|
||||
# result = agent_with_template.start("Write about Python programming")
|
||||
|
||||
# Method 3: Combine with other output settings
|
||||
agent_verbose = Agent(
|
||||
instructions="You are a researcher",
|
||||
output=OutputConfig(
|
||||
verbose=True, # Show rich output in terminal
|
||||
output_file="output/research.md", # Also save to file
|
||||
template="## Research Summary\n\n{{content}}"
|
||||
)
|
||||
)
|
||||
|
||||
print("Examples ready! Uncomment agent.start() calls to run.")
|
||||
print("Output will be saved to the specified files automatically.")
|
||||
@@ -0,0 +1,35 @@
|
||||
# Save Agent Output Examples
|
||||
|
||||
Examples demonstrating different methods to save agent output to files.
|
||||
|
||||
## Examples
|
||||
|
||||
| File | Method | Description |
|
||||
|------|--------|-------------|
|
||||
| `01_write_file_tool.py` | write_file Tool | Agent decides when/what to save |
|
||||
| `02_task_output_file.py` | Task.output_file | Auto-save task result |
|
||||
| `03_workflow_output_file.py` | Workflow output_file | Save workflow step output |
|
||||
| `04_manual_save.py` | Manual | Full control over saving |
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
pip install praisonaiagents
|
||||
export OPENAI_API_KEY="your-key"
|
||||
```
|
||||
|
||||
## Run Examples
|
||||
|
||||
```bash
|
||||
python 01_write_file_tool.py
|
||||
python 02_task_output_file.py
|
||||
python 03_workflow_output_file.py
|
||||
python 04_manual_save.py
|
||||
```
|
||||
|
||||
## Which Method to Use?
|
||||
|
||||
- **write_file Tool**: When agent needs to decide what to save
|
||||
- **Task.output_file**: For task-based workflows with auto-save
|
||||
- **Workflow output_file**: For YAML workflows with variable substitution
|
||||
- **Manual**: When you need full control over the saving process
|
||||
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
Specialized Agents Example
|
||||
|
||||
Demonstrates using specialized agent types (AudioAgent, VideoAgent, ImageAgent, OCRAgent)
|
||||
with the Agents class for multi-agent workflows.
|
||||
|
||||
Usage:
|
||||
python specialized_agents_example.py
|
||||
"""
|
||||
|
||||
# Example 1: Simple TTS with AudioAgent
|
||||
def example_tts():
|
||||
"""Text-to-Speech example using AudioAgent."""
|
||||
from praisonaiagents import AudioAgent
|
||||
|
||||
agent = AudioAgent(llm="openai/tts-1")
|
||||
agent.speech("Hello, welcome to PraisonAI!", output="hello.mp3")
|
||||
print("Generated: hello.mp3")
|
||||
|
||||
# Example 2: Simple STT with AudioAgent
|
||||
def example_stt():
|
||||
"""Speech-to-Text example using AudioAgent."""
|
||||
from praisonaiagents import AudioAgent
|
||||
|
||||
agent = AudioAgent(llm="openai/whisper-1")
|
||||
text = agent.transcribe("hello.mp3")
|
||||
print(f"Transcribed: {text}")
|
||||
|
||||
# Example 3: Image generation with ImageAgent
|
||||
def example_image():
|
||||
"""Image generation example using ImageAgent."""
|
||||
from praisonaiagents import ImageAgent
|
||||
|
||||
agent = ImageAgent(llm="openai/dall-e-3")
|
||||
result = agent.generate("A beautiful sunset over mountains")
|
||||
print(f"Generated image: {result}")
|
||||
|
||||
# Example 4: Multi-agent pipeline with mixed agent types via YAML
|
||||
def example_multi_agent_pipeline():
|
||||
"""
|
||||
Multi-agent pipeline example using YAML workflow:
|
||||
1. AudioAgent (STT) - Transcribe audio
|
||||
2. Agent (Research) - Research the topic
|
||||
3. ImageAgent - Generate image
|
||||
|
||||
Note: Specialized agents (AudioAgent, VideoAgent, etc.) are best used
|
||||
through YAML workflows or directly via their methods (speech, transcribe, etc.)
|
||||
"""
|
||||
from praisonaiagents.workflows.yaml_parser import YAMLWorkflowParser
|
||||
|
||||
# Define multi-agent pipeline in YAML
|
||||
yaml_content = """
|
||||
name: Multi-Agent Pipeline
|
||||
process: sequential
|
||||
|
||||
agents:
|
||||
transcriber:
|
||||
agent: AudioAgent
|
||||
llm: openai/whisper-1
|
||||
role: Transcriber
|
||||
goal: Transcribe audio to text
|
||||
|
||||
researcher:
|
||||
role: Research Specialist
|
||||
goal: Research topics and provide summaries
|
||||
instructions: You research topics and provide concise summaries.
|
||||
|
||||
artist:
|
||||
agent: ImageAgent
|
||||
llm: openai/dall-e-3
|
||||
role: Visual Artist
|
||||
goal: Create images based on research
|
||||
|
||||
steps:
|
||||
- agent: transcriber
|
||||
action: transcribe
|
||||
input: "{{audio_file}}"
|
||||
|
||||
- agent: researcher
|
||||
action: "Research the following topic and provide a summary: {{previous_output}}"
|
||||
|
||||
- agent: artist
|
||||
action: generate
|
||||
prompt: "Create an image representing: {{previous_output}}"
|
||||
|
||||
variables:
|
||||
audio_file: input.mp3
|
||||
"""
|
||||
|
||||
parser = YAMLWorkflowParser()
|
||||
workflow = parser.parse_string(yaml_content)
|
||||
|
||||
print("Multi-agent pipeline created successfully!")
|
||||
print(f"Workflow: {workflow.name}")
|
||||
print(f"Agents: {list(parser._agents.keys())}")
|
||||
print(f"Agent types: {[a.__class__.__name__ for a in parser._agents.values()]}")
|
||||
print(f"Steps: {len(workflow.steps)}")
|
||||
|
||||
# Example 5: Using YAML workflow with specialized agents
|
||||
def example_yaml_workflow():
|
||||
"""Parse YAML workflow with specialized agent types."""
|
||||
from praisonaiagents.workflows.yaml_parser import YAMLWorkflowParser
|
||||
|
||||
yaml_content = """
|
||||
name: Media Pipeline
|
||||
process: sequential
|
||||
|
||||
agents:
|
||||
transcriber:
|
||||
agent: AudioAgent
|
||||
llm: openai/whisper-1
|
||||
role: Transcriber
|
||||
goal: Transcribe audio
|
||||
|
||||
researcher:
|
||||
role: Researcher
|
||||
goal: Research topics
|
||||
|
||||
artist:
|
||||
agent: ImageAgent
|
||||
llm: openai/dall-e-3
|
||||
role: Artist
|
||||
goal: Create images
|
||||
|
||||
steps:
|
||||
- agent: transcriber
|
||||
action: transcribe
|
||||
- agent: researcher
|
||||
action: "Research: {{previous_output}}"
|
||||
- agent: artist
|
||||
action: generate
|
||||
"""
|
||||
|
||||
parser = YAMLWorkflowParser()
|
||||
workflow = parser.parse_string(yaml_content)
|
||||
|
||||
print(f"Workflow: {workflow.name}")
|
||||
print(f"Agents: {list(parser._agents.keys())}")
|
||||
print(f"Agent types: {[a.__class__.__name__ for a in parser._agents.values()]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Specialized Agents Examples")
|
||||
print("=" * 60)
|
||||
|
||||
# Run the YAML workflow example (doesn't require API keys)
|
||||
print("\n--- Example: YAML Workflow with Specialized Agents ---")
|
||||
example_yaml_workflow()
|
||||
|
||||
print("\n--- Example: Multi-Agent Pipeline ---")
|
||||
example_multi_agent_pipeline()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Examples completed!")
|
||||
print("=" * 60)
|
||||
File renamed without changes.
@@ -0,0 +1,274 @@
|
||||
"""
|
||||
Storage Backends Example for PraisonAI Agents.
|
||||
|
||||
Demonstrates how to use different storage backends (File, SQLite, Redis)
|
||||
for agent data persistence across all supported components.
|
||||
|
||||
Supported Backends:
|
||||
- FileBackend: JSON file-based storage (default, zero dependencies)
|
||||
- SQLiteBackend: SQLite database storage (zero dependencies)
|
||||
- RedisBackend: Redis-based storage (requires redis package)
|
||||
|
||||
Supported Components:
|
||||
- BaseJSONStore: Generic JSON storage
|
||||
- TrainingStorage: Agent training data
|
||||
- SessionManager: CLI session state
|
||||
- RunHistory: Recipe run history
|
||||
- MCPToolIndex: MCP tool schemas
|
||||
|
||||
Usage:
|
||||
python storage_backends_example.py
|
||||
"""
|
||||
|
||||
from praisonaiagents.storage import (
|
||||
FileBackend,
|
||||
SQLiteBackend,
|
||||
BaseJSONStore,
|
||||
get_backend,
|
||||
)
|
||||
|
||||
|
||||
def example_file_backend():
|
||||
"""Example: Using FileBackend for JSON file storage."""
|
||||
print("\n=== FileBackend Example ===")
|
||||
|
||||
# Create file backend
|
||||
backend = FileBackend(storage_dir="/tmp/praison_example")
|
||||
|
||||
# Save data
|
||||
backend.save("agent_state", {
|
||||
"agent_name": "Research Assistant",
|
||||
"messages": ["Hello", "How can I help?"],
|
||||
"context": {"topic": "AI"}
|
||||
})
|
||||
|
||||
# Load data
|
||||
data = backend.load("agent_state")
|
||||
print(f"Loaded: {data}")
|
||||
|
||||
# List keys
|
||||
keys = backend.list_keys()
|
||||
print(f"Keys: {keys}")
|
||||
|
||||
# Check existence
|
||||
print(f"Exists: {backend.exists('agent_state')}")
|
||||
|
||||
# Clean up
|
||||
backend.delete("agent_state")
|
||||
print("Deleted agent_state")
|
||||
|
||||
|
||||
def example_sqlite_backend():
|
||||
"""Example: Using SQLiteBackend for database storage."""
|
||||
print("\n=== SQLiteBackend Example ===")
|
||||
|
||||
# Create SQLite backend (single file, better for concurrent access)
|
||||
backend = SQLiteBackend(db_path="/tmp/praison_example.db")
|
||||
|
||||
# Save multiple sessions
|
||||
for i in range(3):
|
||||
backend.save(f"session_{i}", {
|
||||
"session_id": f"sess-{i}",
|
||||
"messages": [f"Message {j}" for j in range(i + 1)],
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
})
|
||||
|
||||
# List all sessions
|
||||
keys = backend.list_keys(prefix="session_")
|
||||
print(f"Sessions: {keys}")
|
||||
|
||||
# Load specific session
|
||||
session = backend.load("session_1")
|
||||
print(f"Session 1: {session}")
|
||||
|
||||
# Clean up
|
||||
backend.clear()
|
||||
backend.close()
|
||||
print("Cleaned up SQLite backend")
|
||||
|
||||
|
||||
def example_backend_factory():
|
||||
"""Example: Using get_backend factory function."""
|
||||
print("\n=== Backend Factory Example ===")
|
||||
|
||||
# Easy switching between backends
|
||||
backend_type = "sqlite" # Change to "file" or "redis" as needed
|
||||
|
||||
if backend_type == "file":
|
||||
backend = get_backend("file", storage_dir="/tmp/praison_factory")
|
||||
elif backend_type == "sqlite":
|
||||
backend = get_backend("sqlite", db_path="/tmp/praison_factory.db")
|
||||
# elif backend_type == "redis":
|
||||
# backend = get_backend("redis", url="redis://localhost:6379")
|
||||
|
||||
# Use backend
|
||||
backend.save("config", {"model": "gpt-4", "temperature": 0.7})
|
||||
config = backend.load("config")
|
||||
print(f"Config: {config}")
|
||||
|
||||
# Clean up
|
||||
backend.delete("config")
|
||||
|
||||
|
||||
def example_with_base_json_store():
|
||||
"""Example: Using BaseJSONStore with custom backend."""
|
||||
print("\n=== BaseJSONStore with Backend Example ===")
|
||||
|
||||
# Create a custom store with SQLite backend
|
||||
backend = SQLiteBackend(db_path="/tmp/praison_store.db")
|
||||
|
||||
store = BaseJSONStore(
|
||||
storage_path="/tmp/my_store.json", # Used as key name
|
||||
backend=backend,
|
||||
)
|
||||
|
||||
# Save and load data
|
||||
store.save({"items": [1, 2, 3], "count": 3})
|
||||
data = store.load()
|
||||
print(f"Store data: {data}")
|
||||
|
||||
# Clean up
|
||||
store.delete()
|
||||
backend.close()
|
||||
|
||||
|
||||
def example_training_storage_with_backend():
|
||||
"""Example: Using TrainingStorage with SQLite backend."""
|
||||
print("\n=== TrainingStorage with Backend Example ===")
|
||||
|
||||
try:
|
||||
from praisonai.train.agents.storage import TrainingStorage
|
||||
|
||||
# Create SQLite backend for training data
|
||||
backend = SQLiteBackend(db_path="/tmp/training.db")
|
||||
|
||||
# Use with TrainingStorage
|
||||
storage = TrainingStorage(
|
||||
session_id="train-example-001",
|
||||
backend=backend,
|
||||
)
|
||||
|
||||
print(f"Training storage created with session: {storage.session_id}")
|
||||
print("Training data will be stored in SQLite instead of JSON files")
|
||||
|
||||
backend.close()
|
||||
except ImportError:
|
||||
print("praisonai package not installed, skipping training example")
|
||||
|
||||
|
||||
def example_session_manager_with_backend():
|
||||
"""Example: Using SessionManager with SQLite backend."""
|
||||
print("\n=== SessionManager with Backend Example ===")
|
||||
|
||||
try:
|
||||
from praisonai.cli.state.sessions import SessionManager
|
||||
|
||||
# Create SQLite backend for sessions
|
||||
backend = SQLiteBackend(db_path="/tmp/sessions.db")
|
||||
|
||||
# Use with SessionManager
|
||||
manager = SessionManager(backend=backend)
|
||||
sessions = manager.list(limit=10)
|
||||
|
||||
print(f"SessionManager created with SQLite backend, {len(sessions)} sessions")
|
||||
print("Sessions will be stored in database instead of files")
|
||||
|
||||
backend.close()
|
||||
except ImportError:
|
||||
print("praisonai package not installed, skipping session example")
|
||||
|
||||
|
||||
def example_run_history_with_backend():
|
||||
"""Example: Using RunHistory with SQLite backend."""
|
||||
print("\n=== RunHistory with Backend Example ===")
|
||||
|
||||
try:
|
||||
from praisonai.recipe.history import RunHistory
|
||||
|
||||
# Create SQLite backend for run history
|
||||
backend = SQLiteBackend(db_path="/tmp/runs.db")
|
||||
|
||||
# Use with RunHistory
|
||||
history = RunHistory(backend=backend)
|
||||
runs = history.list()
|
||||
|
||||
print(f"RunHistory created with SQLite backend, {len(runs)} runs")
|
||||
print("Recipe runs will be stored in database instead of files")
|
||||
|
||||
backend.close()
|
||||
except ImportError:
|
||||
print("praisonai package not installed, skipping run history example")
|
||||
|
||||
|
||||
def example_mcp_tool_index_with_backend():
|
||||
"""Example: Using MCPToolIndex with SQLite backend."""
|
||||
print("\n=== MCPToolIndex with Backend Example ===")
|
||||
|
||||
try:
|
||||
from praisonai.mcp_server.tool_index import MCPToolIndex
|
||||
|
||||
# Create SQLite backend for MCP tool index
|
||||
backend = SQLiteBackend(db_path="/tmp/mcp.db")
|
||||
|
||||
# Use with MCPToolIndex
|
||||
index = MCPToolIndex(backend=backend)
|
||||
servers = index.list_servers()
|
||||
|
||||
print(f"MCPToolIndex created with SQLite backend, {len(servers)} servers")
|
||||
print("MCP tool schemas will be stored in database instead of files")
|
||||
|
||||
backend.close()
|
||||
except ImportError:
|
||||
print("praisonai package not installed, skipping MCP example")
|
||||
|
||||
|
||||
def example_redis_backend():
|
||||
"""Example: Using RedisBackend for high-speed caching."""
|
||||
print("\n=== RedisBackend Example ===")
|
||||
|
||||
try:
|
||||
from praisonaiagents.storage import RedisBackend
|
||||
|
||||
# Create Redis backend with TTL
|
||||
backend = RedisBackend(
|
||||
url="redis://localhost:6379",
|
||||
prefix="example:",
|
||||
ttl=3600 # 1 hour TTL
|
||||
)
|
||||
|
||||
# CRUD operations
|
||||
backend.save("test_key", {"data": "value", "count": 42})
|
||||
data = backend.load("test_key")
|
||||
print(f"Loaded from Redis: {data}")
|
||||
|
||||
# List keys
|
||||
keys = backend.list_keys()
|
||||
print(f"Keys: {keys}")
|
||||
|
||||
# Clean up
|
||||
backend.delete("test_key")
|
||||
backend.close()
|
||||
print("RedisBackend example completed")
|
||||
|
||||
except ImportError:
|
||||
print("redis package not installed: pip install redis")
|
||||
except Exception as e:
|
||||
print(f"Redis not available: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("PraisonAI Storage Backends Examples")
|
||||
print("=" * 50)
|
||||
|
||||
example_file_backend()
|
||||
example_sqlite_backend()
|
||||
example_backend_factory()
|
||||
example_with_base_json_store()
|
||||
example_training_storage_with_backend()
|
||||
example_session_manager_with_backend()
|
||||
example_run_history_with_backend()
|
||||
example_mcp_tool_index_with_backend()
|
||||
example_redis_backend()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("All examples completed!")
|
||||
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
Custom Trace Sink Example
|
||||
|
||||
Demonstrates how to create custom trace sinks for PraisonAI agents.
|
||||
The trace system uses a protocol-driven design - implement 3 methods
|
||||
and your sink works with any agent.
|
||||
|
||||
Protocol: ContextTraceSinkProtocol (AGENTS.md naming: XProtocol for interfaces)
|
||||
- emit(event) - Receive a trace event
|
||||
- flush() - Flush any buffered events
|
||||
- close() - Release resources
|
||||
|
||||
Examples:
|
||||
1. HTTP Sink - Send events to a remote server
|
||||
2. SQLite Sink - Store events in a database
|
||||
3. Console Sink - Pretty-print events to terminal
|
||||
"""
|
||||
|
||||
from praisonaiagents import (
|
||||
Agent,
|
||||
ContextTraceEmitter,
|
||||
ContextTraceSinkProtocol, # Protocol for type hints (optional)
|
||||
trace_context,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 1: HTTP Sink - Send events to a remote server
|
||||
# =============================================================================
|
||||
|
||||
class HTTPSink:
|
||||
"""Send trace events to a remote HTTP endpoint."""
|
||||
|
||||
def __init__(self, url: str, batch_size: int = 10):
|
||||
self.url = url
|
||||
self.batch_size = batch_size
|
||||
self.buffer = []
|
||||
|
||||
def emit(self, event):
|
||||
"""Buffer events and send in batches."""
|
||||
self.buffer.append(event.to_dict())
|
||||
if len(self.buffer) >= self.batch_size:
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
"""Send buffered events to server."""
|
||||
if self.buffer:
|
||||
# In production, use: requests.post(self.url, json=self.buffer)
|
||||
print(f"[HTTP] Would send {len(self.buffer)} events to {self.url}")
|
||||
self.buffer.clear()
|
||||
|
||||
def close(self):
|
||||
"""Flush remaining events."""
|
||||
self.flush()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 2: SQLite Sink - Store events in a database
|
||||
# =============================================================================
|
||||
|
||||
class SQLiteSink:
|
||||
"""Store trace events in SQLite database."""
|
||||
|
||||
def __init__(self, db_path: str = "traces.db"):
|
||||
import sqlite3
|
||||
self.conn = sqlite3.connect(db_path)
|
||||
self.conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY,
|
||||
session_id TEXT,
|
||||
event_type TEXT,
|
||||
agent_name TEXT,
|
||||
timestamp REAL,
|
||||
data TEXT
|
||||
)
|
||||
""")
|
||||
self.conn.commit()
|
||||
|
||||
def emit(self, event):
|
||||
"""Insert event into database."""
|
||||
import json
|
||||
self.conn.execute(
|
||||
"INSERT INTO events (session_id, event_type, agent_name, timestamp, data) VALUES (?, ?, ?, ?, ?)",
|
||||
(event.session_id, event.event_type.value, event.agent_name, event.timestamp, json.dumps(event.data))
|
||||
)
|
||||
|
||||
def flush(self):
|
||||
"""Commit pending transactions."""
|
||||
self.conn.commit()
|
||||
|
||||
def close(self):
|
||||
"""Commit and close connection."""
|
||||
self.flush()
|
||||
self.conn.close()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Example 3: Console Sink - Pretty-print events
|
||||
# =============================================================================
|
||||
|
||||
class ConsoleSink:
|
||||
"""Print trace events to console with colors."""
|
||||
|
||||
COLORS = {
|
||||
"session_start": "\033[92m", # Green
|
||||
"session_end": "\033[92m",
|
||||
"agent_start": "\033[94m", # Blue
|
||||
"agent_end": "\033[94m",
|
||||
"tool_call_start": "\033[93m", # Yellow
|
||||
"tool_call_end": "\033[93m",
|
||||
"llm_request": "\033[95m", # Magenta
|
||||
"llm_response": "\033[95m",
|
||||
}
|
||||
RESET = "\033[0m"
|
||||
|
||||
def emit(self, event):
|
||||
"""Print event with color coding."""
|
||||
color = self.COLORS.get(event.event_type.value, "")
|
||||
print(f"{color}[{event.event_type.value}]{self.RESET} {event.agent_name or 'session'}")
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Usage Examples
|
||||
# =============================================================================
|
||||
|
||||
def example_http_sink():
|
||||
"""Example: Send events to HTTP endpoint."""
|
||||
print("\n=== HTTP Sink Example ===")
|
||||
|
||||
sink = HTTPSink(url="https://my-telemetry.example.com/events")
|
||||
emitter = ContextTraceEmitter(sink=sink, session_id="http-demo", enabled=True)
|
||||
|
||||
with trace_context(emitter):
|
||||
# Events automatically go to HTTPSink
|
||||
emitter.session_start({"demo": True})
|
||||
emitter.agent_start("demo_agent", {"role": "demo"})
|
||||
emitter.agent_end("demo_agent")
|
||||
emitter.session_end()
|
||||
|
||||
print("HTTP sink example complete")
|
||||
|
||||
|
||||
def example_console_sink():
|
||||
"""Example: Pretty-print events to console."""
|
||||
print("\n=== Console Sink Example ===")
|
||||
|
||||
sink = ConsoleSink()
|
||||
emitter = ContextTraceEmitter(sink=sink, session_id="console-demo", enabled=True)
|
||||
|
||||
with trace_context(emitter):
|
||||
emitter.session_start({})
|
||||
emitter.agent_start("researcher", {"role": "Research Assistant"})
|
||||
emitter.tool_call_start("researcher", "web_search", {"query": "AI trends"})
|
||||
emitter.tool_call_end("researcher", "web_search", "Found 10 results", 150.0)
|
||||
emitter.llm_request("researcher", "gpt-4o-mini", [{"role": "user", "content": "test"}])
|
||||
emitter.llm_response("researcher", 100, 500, "stop", "Response text")
|
||||
emitter.agent_end("researcher")
|
||||
emitter.session_end()
|
||||
|
||||
print("Console sink example complete")
|
||||
|
||||
|
||||
def example_with_real_agent():
|
||||
"""Example: Use custom sink with a real agent.
|
||||
|
||||
Note: Requires OPENAI_API_KEY environment variable.
|
||||
"""
|
||||
import os
|
||||
print("\n=== Real Agent with Custom Sink ===")
|
||||
|
||||
# Check for API key
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Skipping real agent test (no OPENAI_API_KEY)")
|
||||
return
|
||||
|
||||
# Create custom sink
|
||||
sink = ConsoleSink()
|
||||
emitter = ContextTraceEmitter(sink=sink, session_id="real-agent-demo", enabled=True)
|
||||
|
||||
# Use trace_context to capture all agent events
|
||||
with trace_context(emitter):
|
||||
agent = Agent(
|
||||
name="demo_agent",
|
||||
instructions="You are a helpful assistant. Be brief.",
|
||||
llm="gpt-4o-mini",
|
||||
)
|
||||
|
||||
# This chat will emit events to ConsoleSink
|
||||
result = agent.chat("Say hello in 5 words or less")
|
||||
print(f"\nAgent response: {result}")
|
||||
|
||||
print("Real agent example complete")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run examples
|
||||
example_http_sink()
|
||||
example_console_sink()
|
||||
example_with_real_agent()
|
||||
|
||||
print("\n✅ All examples complete!")
|
||||
print("\nKey points:")
|
||||
print("1. Implement emit(), flush(), close() - that's it!")
|
||||
print("2. Use trace_context() for automatic cleanup")
|
||||
print("3. Zero overhead when tracing is disabled")
|
||||
@@ -0,0 +1,62 @@
|
||||
# Nested Workflow Patterns Example
|
||||
# Demonstrates: nested loops, if conditions, and combined patterns
|
||||
|
||||
agents:
|
||||
data_processor:
|
||||
role: Data Processor
|
||||
goal: Process individual data items
|
||||
backstory: Expert at transforming and validating data
|
||||
|
||||
quality_checker:
|
||||
role: Quality Checker
|
||||
goal: Verify data quality meets standards
|
||||
backstory: Meticulous quality assurance specialist
|
||||
|
||||
approver:
|
||||
role: Approver
|
||||
goal: Approve high-quality items
|
||||
backstory: Senior reviewer with approval authority
|
||||
|
||||
rejector:
|
||||
role: Rejector
|
||||
goal: Handle rejected items
|
||||
backstory: Specialist in rejection workflows
|
||||
|
||||
workflow:
|
||||
name: Nested Data Processing Pipeline
|
||||
|
||||
variables:
|
||||
batches:
|
||||
- name: batch_1
|
||||
items: ["item_a", "item_b"]
|
||||
- name: batch_2
|
||||
items: ["item_c", "item_d"]
|
||||
quality_threshold: 80
|
||||
|
||||
steps:
|
||||
# Step 1: Process each batch with nested item loop
|
||||
- loop:
|
||||
over: batches
|
||||
var_name: batch
|
||||
steps:
|
||||
# Nested loop: process each item in the batch
|
||||
- loop:
|
||||
over: "{{batch.items}}"
|
||||
var_name: item
|
||||
steps:
|
||||
- agent: data_processor
|
||||
action: "Process item {{item}} from {{batch.name}}"
|
||||
|
||||
# Step 2: Quality check with conditional branching
|
||||
- agent: quality_checker
|
||||
action: "Check quality of all processed items and return a score"
|
||||
|
||||
# Step 3: Conditional approval based on quality score
|
||||
- if:
|
||||
condition: "{{quality_score}} >= {{quality_threshold}}"
|
||||
then:
|
||||
- agent: approver
|
||||
action: "Approve the batch - quality score meets threshold"
|
||||
else:
|
||||
- agent: rejector
|
||||
action: "Reject the batch - quality score below threshold"
|
||||
@@ -0,0 +1,280 @@
|
||||
# Performance Benchmark CI
|
||||
# Runs import time and instantiation benchmarks on push/PR
|
||||
# Fails if import time exceeds 200ms threshold
|
||||
|
||||
name: Performance Benchmark
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
paths:
|
||||
- 'praisonaiagents/**'
|
||||
- 'benchmarks/**'
|
||||
- '.github/workflows/benchmark.yml'
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
paths:
|
||||
- 'praisonaiagents/**'
|
||||
- 'benchmarks/**'
|
||||
- '.github/workflows/benchmark.yml'
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install package
|
||||
run: |
|
||||
pip install -e .
|
||||
pip install pytest
|
||||
|
||||
- name: Measure Import Time
|
||||
id: import_time
|
||||
run: |
|
||||
# Measure import time (full Agent import)
|
||||
IMPORT_TIME=$(python -c "
|
||||
import time
|
||||
import sys
|
||||
|
||||
# Clear any cached modules
|
||||
for key in list(sys.modules.keys()):
|
||||
if 'praisonai' in key:
|
||||
del sys.modules[key]
|
||||
|
||||
start = time.perf_counter()
|
||||
from praisonaiagents import Agent
|
||||
elapsed = (time.perf_counter() - start) * 1000
|
||||
print(f'{elapsed:.1f}')
|
||||
")
|
||||
|
||||
echo "import_time=$IMPORT_TIME" >> $GITHUB_OUTPUT
|
||||
echo "Import time: ${IMPORT_TIME}ms"
|
||||
|
||||
- name: Check Import Time Threshold
|
||||
run: |
|
||||
IMPORT_TIME=${{ steps.import_time.outputs.import_time }}
|
||||
THRESHOLD=200
|
||||
|
||||
echo "Import time: ${IMPORT_TIME}ms"
|
||||
echo "Threshold: ${THRESHOLD}ms"
|
||||
|
||||
# Compare using bc for floating point
|
||||
if (( $(echo "$IMPORT_TIME > $THRESHOLD" | bc -l) )); then
|
||||
echo "❌ FAIL: Import time ${IMPORT_TIME}ms exceeds threshold ${THRESHOLD}ms"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ PASS: Import time ${IMPORT_TIME}ms is within threshold ${THRESHOLD}ms"
|
||||
fi
|
||||
|
||||
- name: Measure Instantiation Time
|
||||
run: |
|
||||
python -c "
|
||||
import time
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Warmup
|
||||
for _ in range(5):
|
||||
Agent(name='Test', output='silent')
|
||||
|
||||
# Measure
|
||||
times = []
|
||||
for _ in range(50):
|
||||
start = time.perf_counter()
|
||||
Agent(name='Test', output='silent')
|
||||
times.append((time.perf_counter() - start) * 1e6)
|
||||
|
||||
avg = sum(times) / len(times)
|
||||
min_t = min(times)
|
||||
max_t = max(times)
|
||||
|
||||
print(f'Instantiation time:')
|
||||
print(f' Average: {avg:.2f}μs')
|
||||
print(f' Min: {min_t:.2f}μs')
|
||||
print(f' Max: {max_t:.2f}μs')
|
||||
"
|
||||
|
||||
- name: Verify Lazy Imports
|
||||
run: |
|
||||
python -c "
|
||||
import sys
|
||||
|
||||
# Clear any cached modules
|
||||
for key in list(sys.modules.keys()):
|
||||
if 'praisonai' in key or 'litellm' in key:
|
||||
del sys.modules[key]
|
||||
|
||||
# Import package
|
||||
import praisonaiagents
|
||||
|
||||
# Check heavy deps are NOT loaded
|
||||
heavy_deps = ['litellm', 'chromadb', 'mem0', 'rich']
|
||||
loaded = []
|
||||
for dep in heavy_deps:
|
||||
if dep in sys.modules:
|
||||
loaded.append(dep)
|
||||
|
||||
if loaded:
|
||||
print(f'⚠️ WARNING: Heavy deps loaded eagerly: {loaded}')
|
||||
# Don't fail, just warn - some deps may be needed
|
||||
else:
|
||||
print('✅ All heavy deps are lazy loaded')
|
||||
"
|
||||
|
||||
- name: Run Quick Benchmark (if available)
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if [ -f "benchmarks/quick_benchmark.py" ]; then
|
||||
python benchmarks/quick_benchmark.py --fast || true
|
||||
else
|
||||
echo "Quick benchmark not found, skipping"
|
||||
fi
|
||||
|
||||
# Real API benchmark - only runs when API keys are available
|
||||
# Triggered manually or on main branch with secrets
|
||||
real-api-benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
if: github.event_name == 'workflow_dispatch' || (github.ref == 'refs/heads/main' && github.event_name == 'push')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install package
|
||||
run: |
|
||||
pip install -e .
|
||||
pip install pytest
|
||||
|
||||
- name: Check API Key Availability
|
||||
id: check_keys
|
||||
run: |
|
||||
if [ -n "${{ secrets.OPENAI_API_KEY }}" ]; then
|
||||
echo "has_openai=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ OpenAI API key available"
|
||||
else
|
||||
echo "has_openai=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ OpenAI API key not available, skipping real API tests"
|
||||
fi
|
||||
|
||||
- name: Run Real API Benchmark
|
||||
if: steps.check_keys.outputs.has_openai == 'true'
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
python -c "
|
||||
import time
|
||||
import os
|
||||
|
||||
# Verify API key is set
|
||||
if not os.environ.get('OPENAI_API_KEY'):
|
||||
print('❌ OPENAI_API_KEY not set')
|
||||
exit(1)
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Create a simple agent
|
||||
agent = Agent(
|
||||
name='BenchmarkAgent',
|
||||
instructions='You are a helpful assistant. Be concise.',
|
||||
llm='gpt-4o-mini',
|
||||
output='silent'
|
||||
)
|
||||
|
||||
# Warmup run
|
||||
print('Running warmup...')
|
||||
agent.chat('Say hello in one word')
|
||||
|
||||
# Benchmark runs
|
||||
print('Running benchmark (3 iterations)...')
|
||||
times = []
|
||||
for i in range(3):
|
||||
start = time.perf_counter()
|
||||
response = agent.chat('What is 2+2? Answer with just the number.')
|
||||
elapsed = time.perf_counter() - start
|
||||
times.append(elapsed)
|
||||
print(f' Run {i+1}: {elapsed:.2f}s')
|
||||
|
||||
avg = sum(times) / len(times)
|
||||
print(f'')
|
||||
print(f'Real API Benchmark Results:')
|
||||
print(f' Average response time: {avg:.2f}s')
|
||||
print(f' Min: {min(times):.2f}s')
|
||||
print(f' Max: {max(times):.2f}s')
|
||||
|
||||
# Verify response quality
|
||||
if '4' in response:
|
||||
print('✅ Response quality check passed')
|
||||
else:
|
||||
print(f'⚠️ Unexpected response: {response}')
|
||||
"
|
||||
|
||||
- name: Run Multi-Agent Benchmark
|
||||
if: steps.check_keys.outputs.has_openai == 'true'
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
python -c "
|
||||
import time
|
||||
import os
|
||||
|
||||
from praisonaiagents import Agent, Agents, Task
|
||||
|
||||
# Create agents
|
||||
researcher = Agent(
|
||||
name='Researcher',
|
||||
instructions='You research topics. Be brief.',
|
||||
llm='gpt-4o-mini',
|
||||
output='silent'
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name='Writer',
|
||||
instructions='You write summaries. Be brief.',
|
||||
llm='gpt-4o-mini',
|
||||
output='silent'
|
||||
)
|
||||
|
||||
# Create tasks
|
||||
task1 = Task(
|
||||
description='List 2 facts about Python programming',
|
||||
agent=researcher,
|
||||
expected_output='2 brief facts'
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
description='Summarize the facts in one sentence',
|
||||
agent=writer,
|
||||
expected_output='One sentence summary'
|
||||
)
|
||||
|
||||
# Run workflow
|
||||
print('Running multi-agent workflow...')
|
||||
start = time.perf_counter()
|
||||
|
||||
agents = Agents(agents=[researcher, writer], tasks=[task1, task2])
|
||||
result = agents.start()
|
||||
|
||||
elapsed = time.perf_counter() - start
|
||||
|
||||
print(f'')
|
||||
print(f'Multi-Agent Benchmark Results:')
|
||||
print(f' Total workflow time: {elapsed:.2f}s')
|
||||
print(f' Agents: 2')
|
||||
print(f' Tasks: 2')
|
||||
print('✅ Multi-agent workflow completed successfully')
|
||||
"
|
||||
@@ -11,7 +11,8 @@ Shows how to monitor agent performance when using external tools.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.tools import duckduckgo, wikipedia_tools
|
||||
from praisonaiagents import duckduckgo
|
||||
from praisonai_tools import wikipedia_tools
|
||||
from praisonaiagents.telemetry import (
|
||||
monitor_function, track_api_call, get_api_stats,
|
||||
get_slowest_functions, performance_monitor
|
||||
|
||||
@@ -0,0 +1,635 @@
|
||||
# AGENTS.md - PraisonAI SDK Comprehensive Guide
|
||||
|
||||
> **For AI Agents and Developers**: This document provides the complete context needed to work with the PraisonAI ecosystem, including design principles, architecture, repository structure, and implementation guidelines.
|
||||
|
||||
---
|
||||
|
||||
## 1. What is PraisonAI?
|
||||
|
||||
PraisonAI is a **high-performance, agentic AI framework** designed for building production-ready AI agents and multi-agent workflows.
|
||||
|
||||
### Core Philosophy
|
||||
|
||||
```
|
||||
Simpler than competitors • More extensible • Faster • Agent-centric
|
||||
```
|
||||
|
||||
| Principle | Description |
|
||||
|-----------|-------------|
|
||||
| **Agent-Centric** | Every design decision centers on Agents, workflows, sessions, tools, and memory |
|
||||
| **Protocol-Driven Core** | Core SDK is lightweight with protocols/hooks/adapters only |
|
||||
| **Minimal API** | Fewer parameters, sensible defaults, explicit overrides |
|
||||
| **Performance-First** | Lazy loading, optional dependencies, no hot-path regressions |
|
||||
| **Production-Ready** | Safe by default, multi-agent safe, async-safe |
|
||||
Powerful, lightweight, highly reliable and robust
|
||||
Easy and developer frienldy for non developers
|
||||
Easy and user friendly
|
||||
---
|
||||
|
||||
## 2. Repository Structure
|
||||
|
||||
### 2.1 Canonical Paths
|
||||
|
||||
```
|
||||
/Users/praison/praisonai-package/
|
||||
├── src/
|
||||
│ ├── praisonai-agents/ # Core SDK (praisonaiagents)
|
||||
│ │ ├── praisonaiagents/ # Python package
|
||||
│ │ ├── tests/ # Unit & integration tests
|
||||
│ │ ├── examples/ # Package-level examples
|
||||
│ │ └── pyproject.toml # Package config
|
||||
│ │
|
||||
│ ├── praisonai/ # Wrapper (praisonai)
|
||||
│ │ ├── praisonai/ # CLI, integrations, heavy impls
|
||||
│ │ └── pyproject.toml
|
||||
│ │
|
||||
│ └── praisonai-ts/ # TypeScript SDK
|
||||
│ └── src/
|
||||
│
|
||||
├── examples/ # Main examples directory
|
||||
│ ├── python/ # Python examples (primary)
|
||||
│ ├── yaml/ # YAML configuration examples
|
||||
│ ├── js/ # JavaScript/TypeScript examples
|
||||
│ └── cookbooks/ # End-to-end recipes
|
||||
│
|
||||
/Users/praison/PraisonAI-tools/ # External tools/plugins
|
||||
/Users/praison/PraisonAIDocs/ # Documentation (Mintlify)
|
||||
└── docs/
|
||||
├── agents/
|
||||
├── api/
|
||||
├── capabilities/
|
||||
├── cli/
|
||||
├── concepts/
|
||||
├── features/
|
||||
├── guides/
|
||||
├── knowledge/
|
||||
├── memory/
|
||||
├── models/
|
||||
├── observability/
|
||||
├── rag/
|
||||
├── sdk/
|
||||
├── tools/
|
||||
├── tutorials/
|
||||
└── ... (49+ subdirectories)
|
||||
```
|
||||
|
||||
### 2.2 Package Hierarchy
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ praisonai (Wrapper) │
|
||||
│ CLI • Integrations • Heavy Implementations • Optional Deps │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ praisonaiagents (Core SDK) │
|
||||
│ Protocols • Hooks • Adapters • Base Classes • Decorators │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ praisonai-tools (External) │
|
||||
│ Optional Tools • Plugins • Community Extensions │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Core SDK Architecture (praisonaiagents)
|
||||
|
||||
### 3.1 Key Modules
|
||||
|
||||
| Module | Size | Purpose |
|
||||
|--------|------|---------|
|
||||
| `agent/` | 1.4M | Agent class, protocols, handoff, autonomy |
|
||||
| `memory/` | 984K | Memory protocols, adapters, file memory |
|
||||
| `context/` | 960K | Context management, artifacts, fast context |
|
||||
| `tools/` | 732K | Tool SDK, decorators, registry, protocols |
|
||||
| `llm/` | 620K | LLM client, model router |
|
||||
| `workflows/` | 456K | Workflow engine, patterns (Route, Parallel, Loop) |
|
||||
| `knowledge/` | 436K | Knowledge/RAG protocols, vector stores |
|
||||
| `hooks/` | 260K | Hook system, middleware, events |
|
||||
| `eval/` | 240K | Evaluation framework |
|
||||
| `rag/` | 264K | RAG protocols, retriever, reranker |
|
||||
| `plugins/` | 56K | Plugin manager, registry |
|
||||
| `policy/` | 64K | Policy engine |
|
||||
| `bus/` | 48K | Event bus |
|
||||
| `streaming/` | 56K | Streaming events, callbacks |
|
||||
| `trace/` | 152K | Trace protocols, context events |
|
||||
|
||||
### 3.2 Protocol-First Design
|
||||
|
||||
Core SDK uses `typing.Protocol` for all extension points:
|
||||
|
||||
```
|
||||
Pattern: Each major module has a protocols.py file
|
||||
|
||||
- Protocols define WHAT (interface contract)
|
||||
- Adapters implement HOW (concrete implementation)
|
||||
- Naming: XProtocol for interfaces, XAdapter for implementations
|
||||
- Users can implement any protocol for custom behavior
|
||||
```
|
||||
|
||||
**Discovery:** Find all protocols dynamically:
|
||||
```bash
|
||||
grep -r "class.*Protocol" praisonaiagents/ --include="*.py"
|
||||
```
|
||||
|
||||
**Key principle:** When adding new extensibility, create a Protocol first, then implement adapters.
|
||||
|
||||
### 3.3 Core Dependencies
|
||||
|
||||
```toml
|
||||
# Required (always loaded) - pyproject.toml
|
||||
dependencies = [
|
||||
"pydantic>=2.10.0", # Type validation
|
||||
"rich", # Terminal UI
|
||||
"openai>=2.0.0", # OpenAI client
|
||||
"posthog>=3.0.0", # Telemetry
|
||||
"aiohttp>=3.8.0" # Async HTTP
|
||||
]
|
||||
|
||||
# Optional (lazy loaded)
|
||||
[project.optional-dependencies]
|
||||
memory = ["chromadb>=1.0.0", "litellm>=1.81.0"]
|
||||
knowledge = ["mem0ai>=0.1.0", "chromadb>=1.0.0", "markitdown[all]>=0.1.0"]
|
||||
mcp = ["mcp>=1.20.0", "fastapi>=0.115.0", "uvicorn>=0.34.0"]
|
||||
llm = ["litellm>=1.81.0"]
|
||||
api = ["fastapi>=0.115.0", "uvicorn>=0.34.0"]
|
||||
auth = ["PyJWT>=2.8.0", "passlib[bcrypt]>=1.7.4"]
|
||||
mongodb = ["pymongo>=4.6.3", "motor>=3.4.0"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Core Engineering Principles
|
||||
|
||||
### 4.1 Protocol-Driven Core (MUST)
|
||||
|
||||
```
|
||||
Core SDK (praisonaiagents) Wrapper (praisonai)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━
|
||||
✅ Protocols ✅ Heavy implementations
|
||||
✅ Hooks ✅ CLI commands
|
||||
✅ Adapters ✅ External integrations
|
||||
✅ Base classes ✅ Optional deps
|
||||
✅ Decorators ✅ UI components
|
||||
✅ Dataclasses ✅ Database adapters
|
||||
❌ Heavy implementations ❌ Core logic
|
||||
```
|
||||
|
||||
### 4.2 No Performance Impact (MUST)
|
||||
|
||||
```python
|
||||
# ✅ CORRECT: Lazy import
|
||||
def use_chromadb():
|
||||
try:
|
||||
import chromadb
|
||||
except ImportError:
|
||||
raise ImportError("Install with: pip install praisonaiagents[memory]")
|
||||
|
||||
# ❌ WRONG: Module-level heavy import
|
||||
import chromadb # Adds 500ms+ to import time
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- No module-level imports of optional dependencies
|
||||
- No heavy initialization in `__init__.py`
|
||||
- Use `__getattr__` for lazy loading in package roots
|
||||
- Target: < 200ms package import time
|
||||
|
||||
### 4.3 DRY Approach (MUST)
|
||||
|
||||
- Reuse existing abstractions; don't duplicate
|
||||
- Refactor safely when duplication is found
|
||||
- Check existing protocols before creating new ones
|
||||
- Use existing EventBus instead of creating new messaging
|
||||
|
||||
### 4.4 Agent-Centric Design (MUST)
|
||||
|
||||
Every API decision must prioritize:
|
||||
1. **Agents** - Core execution unit
|
||||
2. **Multi-agent workflows** - Coordination patterns
|
||||
3. **Sessions** - State management
|
||||
4. **Tools** - Agent capabilities
|
||||
5. **Memory** - Persistence and context
|
||||
|
||||
### 4.5 Async-Safe & Multi-Agent Safe (MUST)
|
||||
|
||||
```
|
||||
Async-Safe:
|
||||
- All I/O operations must have async variants
|
||||
- Never block the event loop with sync I/O in async context
|
||||
- Use asyncio primitives for coordination (not threading)
|
||||
|
||||
Multi-Agent Safe:
|
||||
- No shared mutable global state between agents
|
||||
- Each agent owns its context, memory, and session
|
||||
- Cross-agent communication via explicit channels (EventBus, handoff)
|
||||
- Resource isolation by default, explicit sharing when needed
|
||||
```
|
||||
|
||||
### 4.6 Invariants (Never Break)
|
||||
|
||||
| Invariant | Meaning |
|
||||
|-----------|---------|
|
||||
| Protocol-driven core | Core SDK has NO heavy implementations |
|
||||
| Lazy imports | Optional deps never imported at module level |
|
||||
| Backward compatible | Public API changes require deprecation cycle |
|
||||
| Safe defaults | New features are opt-in, not opt-out |
|
||||
| Deterministic tests | Tests must not depend on timing or external state |
|
||||
|
||||
### 4.7 Concurrency Model
|
||||
|
||||
```
|
||||
Sync/Async Boundaries:
|
||||
- User-facing: Both sync and async entry points (run/start)
|
||||
- Internal: Prefer async, wrap for sync callers
|
||||
- Tools: Support both sync and async execution
|
||||
- Hooks: Fire in the current execution context (sync or async)
|
||||
|
||||
Event Loop Rules:
|
||||
- One event loop per thread
|
||||
- Never nest event loops
|
||||
- Use asyncio.run() only at top level
|
||||
```
|
||||
|
||||
### 4.8 Error Handling & Observability
|
||||
|
||||
```
|
||||
Error Model:
|
||||
- Fail fast with clear error messages
|
||||
- Include remediation hints in exceptions
|
||||
- Propagate context (agent name, tool name, session ID)
|
||||
- Hook points for error interception (on_error events)
|
||||
|
||||
Observability Hooks:
|
||||
- All key operations emit events via EventBus
|
||||
- Trace protocols for external observability integration
|
||||
- Structured logging with correlation IDs
|
||||
- Metrics collection via optional adapters
|
||||
```
|
||||
|
||||
### 4.9 Naming Conventions
|
||||
|
||||
```python
|
||||
# Registration/Mutation
|
||||
add_X() # User-facing registration (add_hook, add_policy)
|
||||
remove_X() # Unregistration
|
||||
register_X() # Internal/framework registration
|
||||
|
||||
# Retrieval
|
||||
get_X() # Single item by ID (get_tool, get_policy)
|
||||
search_X() # Multiple by query (search_memory)
|
||||
list_X() # All items (list_tools, list_plugins)
|
||||
has_X() # Existence check
|
||||
|
||||
# Persistence
|
||||
save() / load() # Disk persistence (NOT store)
|
||||
|
||||
# Configuration
|
||||
class XConfig: # Configuration dataclass (MemoryConfig, HooksConfig)
|
||||
|
||||
# Protocols
|
||||
class XProtocol(Protocol): # Abstract interface
|
||||
class XAdapter: # Implementation
|
||||
|
||||
# Execution
|
||||
run() # Synchronous, blocking
|
||||
start() # Async/long-running
|
||||
execute() # Internal/low-level
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API Philosophy
|
||||
|
||||
### 5.1 Simple API (User-Facing)
|
||||
|
||||
```python
|
||||
# Basic usage - 3 lines
|
||||
from praisonaiagents import Agent
|
||||
agent = Agent(name="assistant", instructions="Be helpful")
|
||||
response = agent.start("Hello!")
|
||||
|
||||
# With tools - still simple
|
||||
from praisonaiagents import Agent, tool
|
||||
|
||||
@tool
|
||||
def search(query: str) -> list:
|
||||
"""Search the web."""
|
||||
return [{"result": query}]
|
||||
|
||||
agent = Agent(name="researcher", tools=[search])
|
||||
```
|
||||
|
||||
### 5.2 Progressive Disclosure
|
||||
|
||||
```python
|
||||
# Level 1: Minimal (most users)
|
||||
agent = Agent(name="assistant")
|
||||
|
||||
# Level 2: With common options
|
||||
agent = Agent(
|
||||
name="assistant",
|
||||
llm="gpt-4o-mini",
|
||||
memory=True
|
||||
)
|
||||
|
||||
# Level 3: Full control (power users)
|
||||
from praisonaiagents import Agent, MemoryConfig, HooksConfig
|
||||
|
||||
agent = Agent(
|
||||
name="assistant",
|
||||
llm="gpt-4o-mini",
|
||||
memory=MemoryConfig(provider="chroma", use_long_term=True),
|
||||
hooks=HooksConfig(before_tool=[my_validator]),
|
||||
guardrail=my_guardrail
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Extension Points
|
||||
|
||||
### 6.1 Tools
|
||||
|
||||
```python
|
||||
# Decorator style (simplest)
|
||||
from praisonaiagents import tool
|
||||
|
||||
@tool
|
||||
def my_tool(query: str) -> str:
|
||||
"""Tool description."""
|
||||
return f"Result: {query}"
|
||||
|
||||
# Class style (more control)
|
||||
from praisonaiagents.tools import BaseTool
|
||||
|
||||
class MyTool(BaseTool):
|
||||
name = "my_tool"
|
||||
description = "Does something"
|
||||
|
||||
def run(self, query: str) -> str:
|
||||
return f"Result: {query}"
|
||||
```
|
||||
|
||||
**Base files:**
|
||||
- `praisonaiagents/tools/base.py` - BaseTool class
|
||||
- `praisonaiagents/tools/decorator.py` - @tool decorator
|
||||
- `praisonaiagents/tools/registry.py` - Tool registry
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Agents only recognize parameters.** When creating tools, always expose all options as function parameters—agents cannot discover env vars or config files.
|
||||
|
||||
### 6.2 Hooks & Middleware
|
||||
|
||||
```python
|
||||
from praisonaiagents.hooks import add_hook, before_tool, after_tool
|
||||
|
||||
@before_tool
|
||||
def validate_args(event):
|
||||
print(f"Calling: {event.tool_name}")
|
||||
|
||||
@after_tool
|
||||
def log_result(event):
|
||||
print(f"Result: {event.result}")
|
||||
|
||||
# Or functional style
|
||||
hook_id = add_hook("before_tool", my_handler)
|
||||
```
|
||||
|
||||
### 6.3 Memory Adapters
|
||||
|
||||
```python
|
||||
from praisonaiagents.memory.protocols import MemoryProtocol
|
||||
|
||||
class MyMemoryAdapter: # Implements MemoryProtocol
|
||||
def store_short_term(self, text, metadata=None):
|
||||
...
|
||||
def search_short_term(self, query, limit=5):
|
||||
...
|
||||
```
|
||||
|
||||
### 6.4 Database Adapters
|
||||
|
||||
```python
|
||||
# Base files in praisonaiagents/db/
|
||||
from praisonaiagents.db import DbAdapter, AsyncDbAdapter
|
||||
|
||||
class MyDbAdapter(DbAdapter):
|
||||
def connect(self): ...
|
||||
def query(self, sql, params): ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. CLI Representation
|
||||
|
||||
Every feature must have CLI parity:
|
||||
|
||||
```bash
|
||||
# Basic agent
|
||||
praisonai agent run --name "assistant" --prompt "Hello"
|
||||
|
||||
# With tools
|
||||
praisonai agent run --tools web_search --prompt "Search for Python"
|
||||
|
||||
# Multi-agent workflow
|
||||
praisonai workflow run --file workflow.yaml
|
||||
|
||||
# Server mode
|
||||
praisonai serve --port 8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Documentation Standards
|
||||
|
||||
### 8.1 Mintlify Components
|
||||
|
||||
Use these Mintlify components for docs:
|
||||
|
||||
- `<Accordion>` / `<AccordionGroup>` - Collapsible content
|
||||
- `<Card>` / `<CardGroup>` / `<Columns>` - Visual containers
|
||||
- `<CodeGroup>` - Tabbed code blocks
|
||||
- `<Steps>` / `<Step>` - Sequential instructions
|
||||
- `<Tabs>` / `<Tab>` - Tabbed content
|
||||
- `<Note>`, `<Warning>`, `<Info>`, `<Tip>` - Callouts
|
||||
- `<Frame>` - Image containers
|
||||
- `<Icon>` - Inline icons
|
||||
- `<Tooltip>` - Hover text
|
||||
|
||||
### 8.2 Mermaid Diagram Standards
|
||||
|
||||
```
|
||||
Two-color scheme:
|
||||
- Dark Red (#8B0000) - Agents, inputs, outputs
|
||||
- Teal/Cyan (#189AB4) - Tools
|
||||
- White text (#fff) for contrast
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Input]:::agent --> B[Agent]:::agent
|
||||
B --> C[Tool]:::tool
|
||||
C --> D[Output]:::agent
|
||||
|
||||
classDef agent fill:#8B0000,color:#fff
|
||||
classDef tool fill:#189AB4,color:#fff
|
||||
```
|
||||
|
||||
### 8.3 Documentation Principles
|
||||
|
||||
| Principle | Description |
|
||||
|-----------|-------------|
|
||||
| **Beginner-friendly** | Non-developers should understand core concepts |
|
||||
| **Copy-paste success** | Code examples must run with minimal setup |
|
||||
| **Less text, more interaction** | Prefer components over long paragraphs |
|
||||
| **Show, don't tell** | Use diagrams, tabs, and accordions to explain |
|
||||
| **Few lines, big impact** | Examples should feel like "only 3 lines to do this" |
|
||||
|
||||
### 8.4 Page Title Conventions
|
||||
|
||||
```
|
||||
SDK docs: "Memory Module", "Tools Module", "Hooks Module"
|
||||
API docs: "Agent API", "Tool API", "Memory API"
|
||||
Guides: "Single Agent Guide", "Multi-Agent Guide"
|
||||
Tutorials: "Build a Research Agent", "Create Custom Tools"
|
||||
```
|
||||
|
||||
### 8.5 Example Quality Standards
|
||||
|
||||
```
|
||||
Every code example MUST:
|
||||
- Run without modification (copy-paste success)
|
||||
- Include necessary imports
|
||||
- Use realistic but simple data
|
||||
- Show expected output in comments
|
||||
- Be the SHORTEST way to accomplish the task
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Testing Standards
|
||||
|
||||
### 9.1 TDD Mandatory
|
||||
|
||||
```python
|
||||
# 1. Write failing test first
|
||||
def test_new_feature():
|
||||
result = new_feature()
|
||||
assert result == expected
|
||||
|
||||
# 2. Implement feature
|
||||
# 3. Verify test passes
|
||||
```
|
||||
|
||||
### 9.2 Test Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── unit/ # Fast, isolated tests
|
||||
├── integration/ # Cross-module tests
|
||||
├── e2e/ # End-to-end tests
|
||||
└── fixtures/ # Test data
|
||||
```
|
||||
|
||||
### 9.3 Run Tests
|
||||
|
||||
```bash
|
||||
# All tests
|
||||
pytest tests/
|
||||
|
||||
# Specific category
|
||||
pytest tests/unit/
|
||||
|
||||
# With coverage
|
||||
pytest --cov=praisonaiagents tests/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Implementation Checklist
|
||||
|
||||
For every feature/change:
|
||||
|
||||
- [ ] **Protocol-first**: Add protocol to core if needed
|
||||
- [ ] **No new deps**: Use optional dependencies only
|
||||
- [ ] **Lazy imports**: Heavy deps imported inside functions
|
||||
- [ ] **Naming**: Follow conventions (add_*, get_*, XConfig)
|
||||
- [ ] **Tests**: TDD - write failing tests first
|
||||
- [ ] **CLI**: Add corresponding CLI command/option
|
||||
- [ ] **Docs**: Update Mintlify documentation
|
||||
- [ ] **Examples**: Add to examples/ directory
|
||||
- [ ] **Multi-agent safe**: No shared mutable state
|
||||
- [ ] **Async-safe**: Support async/await patterns
|
||||
- [ ] **Performance**: No import-time or hot-path regressions
|
||||
|
||||
---
|
||||
|
||||
## 11. Quick Reference
|
||||
|
||||
### 11.1 Core Imports
|
||||
|
||||
```python
|
||||
# Most common
|
||||
from praisonaiagents import Agent, Agents, Task, tool, Tools
|
||||
|
||||
# Configuration
|
||||
from praisonaiagents import (
|
||||
MemoryConfig, KnowledgeConfig, PlanningConfig,
|
||||
OutputConfig, ExecutionConfig, HooksConfig
|
||||
)
|
||||
|
||||
# Hooks
|
||||
from praisonaiagents.hooks import add_hook, before_tool, after_tool
|
||||
|
||||
# Event Bus
|
||||
from praisonaiagents.bus import EventBus, get_default_bus
|
||||
|
||||
# Memory
|
||||
from praisonaiagents import Memory, Session
|
||||
|
||||
# Workflows
|
||||
from praisonaiagents import Workflow, Route, Parallel, Loop
|
||||
```
|
||||
|
||||
### 11.2 File Locations
|
||||
|
||||
| What | Where |
|
||||
|------|-------|
|
||||
| Agent class | `praisonaiagents/agent/agent.py` |
|
||||
| Tool decorator | `praisonaiagents/tools/decorator.py` |
|
||||
| Base tool | `praisonaiagents/tools/base.py` |
|
||||
| Hook system | `praisonaiagents/hooks/` |
|
||||
| Memory protocols | `praisonaiagents/memory/protocols.py` |
|
||||
| Event bus | `praisonaiagents/bus/bus.py` |
|
||||
| Workflow engine | `praisonaiagents/workflows/workflows.py` |
|
||||
| Policy engine | `praisonaiagents/policy/engine.py` |
|
||||
|
||||
---
|
||||
|
||||
## 12. Design Goals
|
||||
|
||||
> Make PraisonAI the **best agent framework in the world**
|
||||
|
||||
1. **Simpler** than competitors (fewer concepts, cleaner API)
|
||||
2. **More extensible** (protocol-driven, plugin-ready)
|
||||
3. **Faster** (lazy loading, minimal overhead)
|
||||
4. **Safer** (guardrails, policies, HITL by default)
|
||||
5. **Production-ready** (observability, checkpoints, replay)
|
||||
|
||||
---
|
||||
|
||||
## 13. Paid Value Path
|
||||
|
||||
The core remains **free and open source**. Clear upgrade paths to paid value:
|
||||
|
||||
| Free (Core) | Paid (Cloud/Services) |
|
||||
|-------------|----------------------|
|
||||
| All protocols | Managed hosting |
|
||||
| All base classes | Enterprise support |
|
||||
| Local execution | Budget management |
|
||||
| File-based memory | Multi-tenant RBAC |
|
||||
| Basic observability | Advanced analytics |
|
||||
| Community support | SLA-backed support |
|
||||
|
||||
---
|
||||
|
||||
*This document is the source of truth for the PraisonAI SDK architecture and design principles.*
|
||||
@@ -1,6 +1,6 @@
|
||||
# PraisonAI Agents - Tools Benchmark Results
|
||||
|
||||
**Generated:** 2025-12-18 14:40:35
|
||||
**Generated:** 2026-01-23 11:50:18
|
||||
**Iterations:** 100
|
||||
**Test:** Agent instantiation WITH TOOLS
|
||||
|
||||
@@ -8,22 +8,22 @@
|
||||
|
||||
| Framework | Avg Time (μs) | Relative |
|
||||
|-----------|---------------|----------|
|
||||
| **PraisonAI** | **3.24** | **1.00x (fastest)** |
|
||||
| Agno | 5.12 | 1.58x |
|
||||
| PraisonAI (LiteLLM) | 8.59 | 2.65x |
|
||||
| OpenAI Agents SDK | 279.95 | 86.44x |
|
||||
| LangGraph | 2,310.82 | 713x |
|
||||
| CrewAI | 15,773.44 | 4,870x |
|
||||
| **PraisonAI** | **5.30** | **1.00x (fastest)** |
|
||||
| Agno | 5.62 | 1.06x |
|
||||
| PraisonAI (LiteLLM) | 11.08 | 2.09x |
|
||||
| OpenAI Agents SDK | 315.49 | 59.55x |
|
||||
| LangGraph | 2,511.95 | 474x |
|
||||
| CrewAI | 41,349.24 | 7,804x |
|
||||
|
||||
## Package Versions
|
||||
|
||||
| Package | Version |
|
||||
|---------|--------|
|
||||
| PraisonAI | 0.1.5 |
|
||||
| Agno | 2.3.14 |
|
||||
| PraisonAI | 0.13.11 |
|
||||
| Agno | 2.4.2 |
|
||||
| OpenAI Agents SDK | 0.6.3 |
|
||||
| LangGraph | 1.0.5 |
|
||||
| CrewAI | 1.6.1 |
|
||||
| LangGraph | 1.0.7 |
|
||||
| CrewAI | 1.8.1 |
|
||||
|
||||
## How to Reproduce
|
||||
|
||||
|
||||
@@ -9,8 +9,16 @@ Key Optimizations Applied:
|
||||
2. Lazy os.getcwd() - fast_context_path resolved only when needed
|
||||
3. Lazy console initialization - Rich Console created on demand
|
||||
|
||||
Advanced Profiling Features:
|
||||
- Line-by-line profiling of __init__ method
|
||||
- Call graph analysis
|
||||
- Memory allocation tracking
|
||||
- Function call counting
|
||||
- Attribute access profiling
|
||||
|
||||
Usage:
|
||||
python benchmarks/deep_profiling.py
|
||||
python benchmarks/deep_profiling.py --deep # More detailed profiling
|
||||
"""
|
||||
|
||||
import time
|
||||
@@ -136,7 +144,7 @@ def profile_praisonai_detailed():
|
||||
instructions="Be concise, reply with one sentence.",
|
||||
llm="gpt-4o-mini",
|
||||
tools=tools,
|
||||
verbose=False
|
||||
output="silent"
|
||||
)
|
||||
profiler.end_step()
|
||||
|
||||
@@ -156,7 +164,7 @@ def profile_praisonai_detailed():
|
||||
instructions="Be concise, reply with one sentence.",
|
||||
llm="gpt-4o-mini",
|
||||
tools=tools,
|
||||
verbose=False
|
||||
output="silent"
|
||||
)
|
||||
|
||||
pr.disable()
|
||||
@@ -249,14 +257,14 @@ def profile_praisonai_components():
|
||||
|
||||
# Test different configurations
|
||||
configs = [
|
||||
("Minimal (no tools, no llm)", {"name": "Test", "verbose": False}),
|
||||
("With name only", {"name": "Test Agent", "verbose": False}),
|
||||
("With instructions", {"name": "Test", "instructions": "Be concise", "verbose": False}),
|
||||
("With llm (gpt-4o-mini)", {"name": "Test", "llm": "gpt-4o-mini", "verbose": False}),
|
||||
("With llm (openai/gpt-4o-mini)", {"name": "Test", "llm": "openai/gpt-4o-mini", "verbose": False}),
|
||||
("With tools", {"name": "Test", "tools": tools, "verbose": False}),
|
||||
("Full config (OpenAI SDK)", {"name": "Test", "instructions": "Be concise", "llm": "gpt-4o-mini", "tools": tools, "verbose": False}),
|
||||
("Full config (LiteLLM)", {"name": "Test", "instructions": "Be concise", "llm": "openai/gpt-4o-mini", "tools": tools, "verbose": False}),
|
||||
("Minimal (no tools, no llm)", {"name": "Test", "output": "silent"}),
|
||||
("With name only", {"name": "Test Agent", "output": "silent"}),
|
||||
("With instructions", {"name": "Test", "instructions": "Be concise", "output": "silent"}),
|
||||
("With llm (gpt-4o-mini)", {"name": "Test", "llm": "gpt-4o-mini", "output": "silent"}),
|
||||
("With llm (openai/gpt-4o-mini)", {"name": "Test", "llm": "openai/gpt-4o-mini", "output": "silent"}),
|
||||
("With tools", {"name": "Test", "tools": tools, "output": "silent"}),
|
||||
("Full config (OpenAI SDK)", {"name": "Test", "instructions": "Be concise", "llm": "gpt-4o-mini", "tools": tools, "output": "silent"}),
|
||||
("Full config (LiteLLM)", {"name": "Test", "instructions": "Be concise", "llm": "openai/gpt-4o-mini", "tools": tools, "output": "silent"}),
|
||||
]
|
||||
|
||||
print(f"\n{'Configuration':<40} {'Avg Time (μs)':<15} {'Min (μs)':<15} {'Max (μs)':<15}")
|
||||
@@ -295,7 +303,7 @@ def compare_specific_operations():
|
||||
|
||||
# Operation 1: Bare minimum instantiation
|
||||
def praisonai_minimal():
|
||||
return PraisonAgent(name="Test", verbose=False)
|
||||
return PraisonAgent(name="Test", output="silent")
|
||||
|
||||
def agno_minimal():
|
||||
return AgnoAgent(model=OpenAIChat(id="gpt-4o-mini"))
|
||||
@@ -306,7 +314,7 @@ def compare_specific_operations():
|
||||
|
||||
# Operation 2: With tools
|
||||
def praisonai_with_tools():
|
||||
return PraisonAgent(name="Test", tools=tools, verbose=False)
|
||||
return PraisonAgent(name="Test", tools=tools, output="silent")
|
||||
|
||||
def agno_with_tools():
|
||||
return AgnoAgent(model=OpenAIChat(id="gpt-4o-mini"), tools=tools)
|
||||
@@ -322,7 +330,7 @@ def compare_specific_operations():
|
||||
instructions="Be concise",
|
||||
llm="gpt-4o-mini",
|
||||
tools=tools,
|
||||
verbose=False
|
||||
output="silent"
|
||||
)
|
||||
|
||||
def agno_full():
|
||||
@@ -344,12 +352,199 @@ def compare_specific_operations():
|
||||
print(f"{op_name:<30} {praison_time:<18.2f} {agno_time:<18.2f} {ratio:<10.1f}x")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Advanced: Attribute Access Profiling
|
||||
# ============================================================================
|
||||
|
||||
def profile_attribute_access():
|
||||
"""Profile attribute access patterns in Agent.__init__"""
|
||||
print("\n" + "="*70)
|
||||
print("ATTRIBUTE ACCESS PROFILING")
|
||||
print("="*70)
|
||||
|
||||
from praisonaiagents import Agent as PraisonAgent
|
||||
|
||||
# Count attribute accesses during init
|
||||
attr_counts = {}
|
||||
original_setattr = object.__setattr__
|
||||
|
||||
def counting_setattr(obj, name, value):
|
||||
if isinstance(obj, PraisonAgent):
|
||||
attr_counts[name] = attr_counts.get(name, 0) + 1
|
||||
return original_setattr(obj, name, value)
|
||||
|
||||
# Temporarily patch
|
||||
import builtins
|
||||
old_setattr = builtins.setattr if hasattr(builtins, 'setattr') else None
|
||||
|
||||
# Create agent and count
|
||||
agent = PraisonAgent(name="Test", output="silent")
|
||||
|
||||
# Sort by count
|
||||
sorted_attrs = sorted(attr_counts.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
print(f"\n{'Attribute':<40} {'Set Count':<15}")
|
||||
print("-" * 55)
|
||||
for attr, count in sorted_attrs[:30]:
|
||||
print(f"{attr:<40} {count:<15}")
|
||||
|
||||
print(f"\nTotal attributes set: {len(attr_counts)}")
|
||||
print(f"Total setattr calls: {sum(attr_counts.values())}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Advanced: Function Call Analysis
|
||||
# ============================================================================
|
||||
|
||||
def analyze_init_function_calls():
|
||||
"""Analyze which functions are called during Agent.__init__"""
|
||||
print("\n" + "="*70)
|
||||
print("FUNCTION CALL ANALYSIS: PraisonAI Agent.__init__")
|
||||
print("="*70)
|
||||
|
||||
import sys
|
||||
from praisonaiagents import Agent as PraisonAgent
|
||||
|
||||
call_counts = {}
|
||||
|
||||
def trace_calls(frame, event, arg):
|
||||
if event == 'call':
|
||||
code = frame.f_code
|
||||
func_name = code.co_name
|
||||
filename = code.co_filename
|
||||
|
||||
# Only track praisonaiagents calls
|
||||
if 'praisonaiagents' in filename:
|
||||
key = f"{func_name} ({filename.split('/')[-1]}:{code.co_firstlineno})"
|
||||
call_counts[key] = call_counts.get(key, 0) + 1
|
||||
return trace_calls
|
||||
|
||||
# Enable tracing
|
||||
sys.settrace(trace_calls)
|
||||
|
||||
try:
|
||||
agent = PraisonAgent(name="Test", output="silent")
|
||||
finally:
|
||||
sys.settrace(None)
|
||||
|
||||
# Sort by count
|
||||
sorted_calls = sorted(call_counts.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
print(f"\n{'Function':<60} {'Calls':<10}")
|
||||
print("-" * 70)
|
||||
for func, count in sorted_calls[:40]:
|
||||
print(f"{func:<60} {count:<10}")
|
||||
|
||||
print(f"\nTotal unique functions called: {len(call_counts)}")
|
||||
print(f"Total function calls: {sum(call_counts.values())}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Advanced: Import Time Analysis
|
||||
# ============================================================================
|
||||
|
||||
def analyze_import_overhead():
|
||||
"""Analyze import overhead for key modules"""
|
||||
print("\n" + "="*70)
|
||||
print("IMPORT OVERHEAD ANALYSIS")
|
||||
print("="*70)
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
modules_to_test = [
|
||||
'praisonaiagents',
|
||||
'praisonaiagents.agent.agent',
|
||||
'praisonaiagents.config.param_resolver',
|
||||
'praisonaiagents.config.presets',
|
||||
'praisonaiagents.config.feature_configs',
|
||||
]
|
||||
|
||||
print(f"\n{'Module':<50} {'Import Time (ms)':<20}")
|
||||
print("-" * 70)
|
||||
|
||||
for module_name in modules_to_test:
|
||||
# Clear from cache
|
||||
to_remove = [k for k in sys.modules.keys() if k.startswith('praisonaiagents')]
|
||||
for k in to_remove:
|
||||
del sys.modules[k]
|
||||
|
||||
start = time.perf_counter()
|
||||
try:
|
||||
importlib.import_module(module_name)
|
||||
elapsed = (time.perf_counter() - start) * 1000
|
||||
print(f"{module_name:<50} {elapsed:<20.2f}")
|
||||
except ImportError as e:
|
||||
print(f"{module_name:<50} FAILED: {e}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Advanced: Agno vs PraisonAI Architecture Comparison
|
||||
# ============================================================================
|
||||
|
||||
def compare_architecture():
|
||||
"""Compare architectural differences between Agno and PraisonAI"""
|
||||
print("\n" + "="*70)
|
||||
print("ARCHITECTURE COMPARISON: Agno vs PraisonAI")
|
||||
print("="*70)
|
||||
|
||||
from praisonaiagents import Agent as PraisonAgent
|
||||
from agno.agent import Agent as AgnoAgent
|
||||
from agno.models.openai import OpenAIChat
|
||||
|
||||
# Create instances
|
||||
praison_agent = PraisonAgent(name="Test", output="silent")
|
||||
agno_agent = AgnoAgent(model=OpenAIChat(id="gpt-4o-mini"))
|
||||
|
||||
print("\n### Class Structure ###")
|
||||
print(f"\nPraisonAI Agent:")
|
||||
print(f" - Uses @dataclass: No (regular class)")
|
||||
print(f" - __slots__: {'Yes' if hasattr(PraisonAgent, '__slots__') else 'No'}")
|
||||
print(f" - Instance dict size: {len(praison_agent.__dict__)} attributes")
|
||||
|
||||
print(f"\nAgno Agent:")
|
||||
print(f" - Uses @dataclass: Yes (init=False)")
|
||||
print(f" - __slots__: {'Yes' if hasattr(AgnoAgent, '__slots__') else 'No'}")
|
||||
print(f" - Instance dict size: {len(agno_agent.__dict__)} attributes")
|
||||
|
||||
print("\n### Key Differences ###")
|
||||
print("\n1. Agno uses @dataclass(init=False) - class attributes pre-declared")
|
||||
print("2. Agno __init__ is mostly simple attribute assignments")
|
||||
print("3. PraisonAI __init__ does extensive parameter resolution")
|
||||
print("4. PraisonAI imports config modules inside __init__")
|
||||
print("5. PraisonAI calls resolve() 10+ times for feature params")
|
||||
|
||||
# Count resolve calls in PraisonAI
|
||||
print("\n### Parameter Resolution Overhead ###")
|
||||
print("\nPraisonAI resolve() calls in __init__:")
|
||||
print(" - output: resolve()")
|
||||
print(" - execution: resolve()")
|
||||
print(" - templates: resolve()")
|
||||
print(" - caching: resolve()")
|
||||
print(" - hooks: resolve()")
|
||||
print(" - skills: resolve()")
|
||||
print(" - memory: resolve()")
|
||||
print(" - knowledge: resolve()")
|
||||
print(" - planning: resolve()")
|
||||
print(" - reflection: resolve()")
|
||||
print(" - guardrails: resolve()")
|
||||
print(" - web: resolve()")
|
||||
print(" Total: 12 resolve() calls per Agent creation")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Deep Profiling Benchmark')
|
||||
parser.add_argument('--deep', action='store_true', help='Run deep profiling (slower)')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
os.environ.setdefault("OPENAI_API_KEY", "sk-test")
|
||||
|
||||
print("="*70)
|
||||
@@ -363,6 +558,17 @@ if __name__ == "__main__":
|
||||
profile_praisonai_components()
|
||||
compare_specific_operations()
|
||||
|
||||
# Advanced profiling (optional)
|
||||
if args.deep:
|
||||
print("\n" + "="*70)
|
||||
print("ADVANCED PROFILING (--deep mode)")
|
||||
print("="*70)
|
||||
analyze_init_function_calls()
|
||||
analyze_import_overhead()
|
||||
|
||||
# Always run architecture comparison
|
||||
compare_architecture()
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("PROFILING COMPLETE")
|
||||
print("="*70)
|
||||
@@ -0,0 +1,451 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PraisonAI Agents - Detailed Profiling Benchmark
|
||||
|
||||
Provides function-level profiling for PraisonAI agent execution.
|
||||
Shows exactly which functions are called, from which files, and how long each takes.
|
||||
|
||||
Usage:
|
||||
export OPENAI_API_KEY=your_key
|
||||
python benchmarks/detailed_profiling_benchmark.py
|
||||
python benchmarks/detailed_profiling_benchmark.py --framework praisonai
|
||||
python benchmarks/detailed_profiling_benchmark.py --framework agno
|
||||
"""
|
||||
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import cProfile
|
||||
import pstats
|
||||
import io
|
||||
from functools import wraps
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
|
||||
# Defaults
|
||||
DEFAULT_MODEL = "gpt-4o-mini"
|
||||
DEFAULT_PROMPT = "What is 2+2? Reply with just the number."
|
||||
|
||||
|
||||
class DetailedProfiler:
|
||||
"""Detailed function-level profiler with file path tracking."""
|
||||
|
||||
def __init__(self):
|
||||
self.call_times = defaultdict(list)
|
||||
self.call_counts = defaultdict(int)
|
||||
self.profiler = None
|
||||
|
||||
def start(self):
|
||||
"""Start cProfile profiler."""
|
||||
self.profiler = cProfile.Profile()
|
||||
self.profiler.enable()
|
||||
|
||||
def stop(self):
|
||||
"""Stop profiler and return stats."""
|
||||
if self.profiler:
|
||||
self.profiler.disable()
|
||||
return self.profiler
|
||||
return None
|
||||
|
||||
def get_stats_string(self, sort_by='cumtime', limit=50):
|
||||
"""Get formatted stats string."""
|
||||
if not self.profiler:
|
||||
return "No profiler data"
|
||||
|
||||
s = io.StringIO()
|
||||
ps = pstats.Stats(self.profiler, stream=s)
|
||||
ps.sort_stats(sort_by)
|
||||
ps.print_stats(limit)
|
||||
return s.getvalue()
|
||||
|
||||
def get_detailed_breakdown(self, filter_pattern=None, limit=100):
|
||||
"""Get detailed breakdown with file paths."""
|
||||
if not self.profiler:
|
||||
return []
|
||||
|
||||
stats = pstats.Stats(self.profiler)
|
||||
|
||||
results = []
|
||||
for (filename, line, func), (cc, nc, tt, ct, callers) in stats.stats.items():
|
||||
# Filter by pattern if provided
|
||||
if filter_pattern and filter_pattern not in filename and filter_pattern not in func:
|
||||
continue
|
||||
|
||||
results.append({
|
||||
'file': filename,
|
||||
'line': line,
|
||||
'function': func,
|
||||
'calls': nc,
|
||||
'total_time': tt,
|
||||
'cumulative_time': ct,
|
||||
'time_per_call': tt / nc if nc > 0 else 0,
|
||||
})
|
||||
|
||||
# Sort by cumulative time
|
||||
results.sort(key=lambda x: x['cumulative_time'], reverse=True)
|
||||
return results[:limit]
|
||||
|
||||
|
||||
def profile_praisonai_detailed(model, prompt):
|
||||
"""Profile PraisonAI agent execution in detail."""
|
||||
print("\n" + "=" * 80)
|
||||
print("DETAILED PROFILING: PraisonAI")
|
||||
print("=" * 80)
|
||||
|
||||
profiler = DetailedProfiler()
|
||||
|
||||
# Profile import
|
||||
print("\n--- Import Phase ---")
|
||||
import_start = time.perf_counter()
|
||||
profiler.start()
|
||||
from praisonaiagents import Agent
|
||||
profiler.stop()
|
||||
import_time = time.perf_counter() - import_start
|
||||
print(f"Import time: {import_time*1000:.2f}ms")
|
||||
|
||||
# Show import profile
|
||||
print("\nTop 20 functions during import (by cumulative time):")
|
||||
print("-" * 80)
|
||||
breakdown = profiler.get_detailed_breakdown(filter_pattern='praisonai', limit=20)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in breakdown:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# Profile instantiation
|
||||
print("\n--- Instantiation Phase ---")
|
||||
profiler2 = DetailedProfiler()
|
||||
inst_start = time.perf_counter()
|
||||
profiler2.start()
|
||||
agent = Agent(
|
||||
name="Calculator",
|
||||
instructions="You are a helpful assistant. Be very brief.",
|
||||
llm=model
|
||||
)
|
||||
profiler2.stop()
|
||||
inst_time = time.perf_counter() - inst_start
|
||||
print(f"Instantiation time: {inst_time*1000:.2f}ms")
|
||||
|
||||
print("\nTop 30 functions during instantiation (by cumulative time):")
|
||||
print("-" * 80)
|
||||
breakdown = profiler2.get_detailed_breakdown(filter_pattern='praisonai', limit=30)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in breakdown:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# Profile execution
|
||||
print("\n--- Execution Phase (agent.start) ---")
|
||||
profiler3 = DetailedProfiler()
|
||||
exec_start = time.perf_counter()
|
||||
profiler3.start()
|
||||
response = agent.start(prompt, output="silent")
|
||||
profiler3.stop()
|
||||
exec_time = time.perf_counter() - exec_start
|
||||
print(f"Execution time: {exec_time*1000:.2f}ms ({exec_time:.2f}s)")
|
||||
print(f"Response: {str(response)[:100]}")
|
||||
|
||||
print("\nTop 50 functions during execution (by cumulative time):")
|
||||
print("-" * 80)
|
||||
breakdown = profiler3.get_detailed_breakdown(limit=50)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in breakdown:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# PraisonAI-specific functions
|
||||
print("\n--- PraisonAI-Specific Functions ---")
|
||||
print("-" * 80)
|
||||
praisonai_funcs = profiler3.get_detailed_breakdown(filter_pattern='praisonai', limit=50)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in praisonai_funcs:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 80)
|
||||
print("SUMMARY: PraisonAI")
|
||||
print("=" * 80)
|
||||
print(f"Import time: {import_time*1000:>10.2f}ms")
|
||||
print(f"Instantiation time: {inst_time*1000:>10.2f}ms")
|
||||
print(f"Execution time: {exec_time*1000:>10.2f}ms")
|
||||
print(f"Total time: {(import_time+inst_time+exec_time)*1000:>10.2f}ms")
|
||||
|
||||
return {
|
||||
'import_time': import_time,
|
||||
'instantiation_time': inst_time,
|
||||
'execution_time': exec_time,
|
||||
'total_time': import_time + inst_time + exec_time,
|
||||
}
|
||||
|
||||
|
||||
def profile_agno_detailed(model, prompt):
|
||||
"""Profile Agno agent execution in detail."""
|
||||
print("\n" + "=" * 80)
|
||||
print("DETAILED PROFILING: Agno")
|
||||
print("=" * 80)
|
||||
|
||||
profiler = DetailedProfiler()
|
||||
|
||||
# Profile import
|
||||
print("\n--- Import Phase ---")
|
||||
import_start = time.perf_counter()
|
||||
profiler.start()
|
||||
from agno.agent import Agent
|
||||
from agno.models.openai import OpenAIChat
|
||||
profiler.stop()
|
||||
import_time = time.perf_counter() - import_start
|
||||
print(f"Import time: {import_time*1000:.2f}ms")
|
||||
|
||||
print("\nTop 20 functions during import (by cumulative time):")
|
||||
print("-" * 80)
|
||||
breakdown = profiler.get_detailed_breakdown(filter_pattern='agno', limit=20)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in breakdown:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# Profile instantiation
|
||||
print("\n--- Instantiation Phase ---")
|
||||
profiler2 = DetailedProfiler()
|
||||
inst_start = time.perf_counter()
|
||||
profiler2.start()
|
||||
agent = Agent(
|
||||
model=OpenAIChat(id=model),
|
||||
instructions=["You are a helpful assistant. Be very brief."],
|
||||
)
|
||||
profiler2.stop()
|
||||
inst_time = time.perf_counter() - inst_start
|
||||
print(f"Instantiation time: {inst_time*1000:.2f}ms")
|
||||
|
||||
print("\nTop 30 functions during instantiation (by cumulative time):")
|
||||
print("-" * 80)
|
||||
breakdown = profiler2.get_detailed_breakdown(filter_pattern='agno', limit=30)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in breakdown:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# Profile execution
|
||||
print("\n--- Execution Phase (agent.run) ---")
|
||||
profiler3 = DetailedProfiler()
|
||||
exec_start = time.perf_counter()
|
||||
profiler3.start()
|
||||
response = agent.run(prompt)
|
||||
profiler3.stop()
|
||||
exec_time = time.perf_counter() - exec_start
|
||||
content = str(response.content)[:100] if hasattr(response, 'content') else str(response)[:100]
|
||||
print(f"Execution time: {exec_time*1000:.2f}ms ({exec_time:.2f}s)")
|
||||
print(f"Response: {content}")
|
||||
|
||||
print("\nTop 50 functions during execution (by cumulative time):")
|
||||
print("-" * 80)
|
||||
breakdown = profiler3.get_detailed_breakdown(limit=50)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in breakdown:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# Agno-specific functions
|
||||
print("\n--- Agno-Specific Functions ---")
|
||||
print("-" * 80)
|
||||
agno_funcs = profiler3.get_detailed_breakdown(filter_pattern='agno', limit=50)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in agno_funcs:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 80)
|
||||
print("SUMMARY: Agno")
|
||||
print("=" * 80)
|
||||
print(f"Import time: {import_time*1000:>10.2f}ms")
|
||||
print(f"Instantiation time: {inst_time*1000:>10.2f}ms")
|
||||
print(f"Execution time: {exec_time*1000:>10.2f}ms")
|
||||
print(f"Total time: {(import_time+inst_time+exec_time)*1000:>10.2f}ms")
|
||||
|
||||
return {
|
||||
'import_time': import_time,
|
||||
'instantiation_time': inst_time,
|
||||
'execution_time': exec_time,
|
||||
'total_time': import_time + inst_time + exec_time,
|
||||
}
|
||||
|
||||
|
||||
def profile_crewai_detailed(model, prompt):
|
||||
"""Profile CrewAI agent execution in detail."""
|
||||
print("\n" + "=" * 80)
|
||||
print("DETAILED PROFILING: CrewAI")
|
||||
print("=" * 80)
|
||||
|
||||
profiler = DetailedProfiler()
|
||||
|
||||
# Profile import
|
||||
print("\n--- Import Phase ---")
|
||||
import_start = time.perf_counter()
|
||||
profiler.start()
|
||||
from crewai import Agent, Task, Crew
|
||||
profiler.stop()
|
||||
import_time = time.perf_counter() - import_start
|
||||
print(f"Import time: {import_time*1000:.2f}ms")
|
||||
|
||||
# Profile instantiation
|
||||
print("\n--- Instantiation Phase ---")
|
||||
profiler2 = DetailedProfiler()
|
||||
inst_start = time.perf_counter()
|
||||
profiler2.start()
|
||||
agent = Agent(
|
||||
role="Calculator",
|
||||
goal="Answer math questions",
|
||||
backstory="You are a helpful assistant.",
|
||||
llm=model,
|
||||
verbose=False
|
||||
)
|
||||
task = Task(
|
||||
description=prompt,
|
||||
expected_output="A number",
|
||||
agent=agent
|
||||
)
|
||||
crew = Crew(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
verbose=False
|
||||
)
|
||||
profiler2.stop()
|
||||
inst_time = time.perf_counter() - inst_start
|
||||
print(f"Instantiation time: {inst_time*1000:.2f}ms")
|
||||
|
||||
# Profile execution
|
||||
print("\n--- Execution Phase (crew.kickoff) ---")
|
||||
profiler3 = DetailedProfiler()
|
||||
exec_start = time.perf_counter()
|
||||
profiler3.start()
|
||||
response = crew.kickoff()
|
||||
profiler3.stop()
|
||||
exec_time = time.perf_counter() - exec_start
|
||||
print(f"Execution time: {exec_time*1000:.2f}ms ({exec_time:.2f}s)")
|
||||
print(f"Response: {str(response)[:100]}")
|
||||
|
||||
print("\nTop 50 functions during execution (by cumulative time):")
|
||||
print("-" * 80)
|
||||
breakdown = profiler3.get_detailed_breakdown(limit=50)
|
||||
print(f"{'Function':<40} {'File':<50} {'Calls':>8} {'CumTime':>10}")
|
||||
print("-" * 80)
|
||||
for item in breakdown:
|
||||
func = item['function'][:38]
|
||||
file = item['file'].split('/')[-1][:48] if '/' in item['file'] else item['file'][:48]
|
||||
print(f"{func:<40} {file:<50} {item['calls']:>8} {item['cumulative_time']*1000:>9.2f}ms")
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 80)
|
||||
print("SUMMARY: CrewAI")
|
||||
print("=" * 80)
|
||||
print(f"Import time: {import_time*1000:>10.2f}ms")
|
||||
print(f"Instantiation time: {inst_time*1000:>10.2f}ms")
|
||||
print(f"Execution time: {exec_time*1000:>10.2f}ms")
|
||||
print(f"Total time: {(import_time+inst_time+exec_time)*1000:>10.2f}ms")
|
||||
|
||||
return {
|
||||
'import_time': import_time,
|
||||
'instantiation_time': inst_time,
|
||||
'execution_time': exec_time,
|
||||
'total_time': import_time + inst_time + exec_time,
|
||||
}
|
||||
|
||||
|
||||
def compare_results(results):
|
||||
"""Compare results across frameworks."""
|
||||
print("\n" + "=" * 80)
|
||||
print("COMPARISON SUMMARY")
|
||||
print("=" * 80)
|
||||
|
||||
print(f"\n{'Framework':<20} {'Import':<12} {'Instantiate':<12} {'Execute':<12} {'Total':<12}")
|
||||
print("-" * 68)
|
||||
|
||||
# Sort by execution time
|
||||
sorted_results = sorted(results.items(), key=lambda x: x[1]['execution_time'])
|
||||
fastest_exec = sorted_results[0][1]['execution_time']
|
||||
|
||||
for name, times in sorted_results:
|
||||
import_ms = times['import_time'] * 1000
|
||||
inst_ms = times['instantiation_time'] * 1000
|
||||
exec_ms = times['execution_time'] * 1000
|
||||
total_ms = times['total_time'] * 1000
|
||||
ratio = times['execution_time'] / fastest_exec
|
||||
|
||||
print(f"{name:<20} {import_ms:>10.1f}ms {inst_ms:>10.1f}ms {exec_ms:>10.1f}ms {total_ms:>10.1f}ms ({ratio:.2f}x)")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Agents - Detailed Profiling Benchmark')
|
||||
parser.add_argument('--framework', '-f', type=str, default='all',
|
||||
choices=['all', 'praisonai', 'agno', 'crewai'],
|
||||
help='Framework to profile (default: all)')
|
||||
parser.add_argument('--model', '-m', type=str, default=DEFAULT_MODEL,
|
||||
help=f'Model to use (default: {DEFAULT_MODEL})')
|
||||
parser.add_argument('--prompt', '-p', type=str, default=DEFAULT_PROMPT,
|
||||
help=f'Prompt to use')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check API key
|
||||
if not os.environ.get('OPENAI_API_KEY'):
|
||||
print("ERROR: Set OPENAI_API_KEY environment variable")
|
||||
exit(1)
|
||||
|
||||
print("=" * 80)
|
||||
print("PraisonAI Agents - Detailed Profiling Benchmark")
|
||||
print("=" * 80)
|
||||
print(f"\nModel: {args.model}")
|
||||
print(f"Prompt: \"{args.prompt}\"")
|
||||
print(f"Framework: {args.framework}")
|
||||
print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
results = {}
|
||||
|
||||
if args.framework in ['all', 'praisonai']:
|
||||
try:
|
||||
results['PraisonAI'] = profile_praisonai_detailed(args.model, args.prompt)
|
||||
except Exception as e:
|
||||
print(f"PraisonAI error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if args.framework in ['all', 'agno']:
|
||||
try:
|
||||
results['Agno'] = profile_agno_detailed(args.model, args.prompt)
|
||||
except Exception as e:
|
||||
print(f"Agno error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if args.framework in ['all', 'crewai']:
|
||||
try:
|
||||
results['CrewAI'] = profile_crewai_detailed(args.model, args.prompt)
|
||||
except Exception as e:
|
||||
print(f"CrewAI error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if len(results) > 1:
|
||||
compare_results(results)
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("Profiling complete!")
|
||||
print("=" * 80)
|
||||
@@ -219,7 +219,7 @@ def save_results(results: dict, model: str, iterations: int, prompt: str):
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Agents - Real Execution Benchmark')
|
||||
parser.add_argument('--no-save', action='store_true', help='Do not save results to file')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
parser.add_argument('--model', '-m', type=str, default=DEFAULT_MODEL, help=f'Model to use (default: {DEFAULT_MODEL})')
|
||||
parser.add_argument('--iterations', '-i', type=int, default=DEFAULT_ITERATIONS, help=f'Number of iterations (default: {DEFAULT_ITERATIONS})')
|
||||
parser.add_argument('--prompt', '-p', type=str, default=DEFAULT_PROMPT, help=f'Prompt to use (default: "{DEFAULT_PROMPT}")')
|
||||
@@ -287,8 +287,8 @@ if __name__ == '__main__':
|
||||
|
||||
print('\n' + '=' * 60)
|
||||
|
||||
# Save results (unless --no-save)
|
||||
if results and not args.no_save:
|
||||
# Save results (if --save)
|
||||
if results and args.save:
|
||||
save_results(results, model, iterations, prompt)
|
||||
elif args.no_save:
|
||||
print('\nResults not saved (--no-save flag used)')
|
||||
elif not args.save:
|
||||
print('\nResults not saved (use --save flag to save results to file)')
|
||||
@@ -16,6 +16,7 @@ CI Gate:
|
||||
import sys
|
||||
import time
|
||||
import statistics
|
||||
import argparse
|
||||
|
||||
|
||||
def clear_modules():
|
||||
@@ -145,4 +146,11 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Agents Import Time Benchmark')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.save:
|
||||
print("\nNote: Results are not saved to file by default. Use --save to save them.")
|
||||
|
||||
sys.exit(main())
|
||||
@@ -15,6 +15,7 @@ CI Gate:
|
||||
"""
|
||||
import sys
|
||||
import tracemalloc
|
||||
import argparse
|
||||
|
||||
|
||||
def clear_modules():
|
||||
@@ -86,4 +87,11 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Agents Memory Usage Benchmark')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.save:
|
||||
print("\nNote: Results are not saved to file by default. Use --save to save them.")
|
||||
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PraisonAI Agents - Parameter Impact Benchmark
|
||||
|
||||
Profiles each Agent parameter to identify which features have the most
|
||||
performance impact. Results are ordered by cost (time + memory).
|
||||
|
||||
This benchmark helps identify optimization opportunities by measuring
|
||||
the incremental cost of each feature.
|
||||
|
||||
Usage:
|
||||
python benchmarks/param_impact_benchmark.py
|
||||
python benchmarks/param_impact_benchmark.py --no-save
|
||||
python benchmarks/param_impact_benchmark.py --iterations 50
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import gc
|
||||
import time
|
||||
import tracemalloc
|
||||
import statistics
|
||||
from typing import Literal, List, Dict, Any, Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ParamResult:
|
||||
"""Result for a single parameter test."""
|
||||
param_name: str
|
||||
param_value: Any
|
||||
avg_time_us: float # microseconds
|
||||
min_time_us: float
|
||||
max_time_us: float
|
||||
std_dev_us: float
|
||||
avg_memory_kb: float # kilobytes
|
||||
delta_time_us: float = 0.0 # difference from baseline
|
||||
delta_memory_kb: float = 0.0
|
||||
|
||||
|
||||
def measure_instantiation(
|
||||
create_fn: Callable,
|
||||
iterations: int = 100,
|
||||
warmup: int = 10
|
||||
) -> Dict[str, float]:
|
||||
"""Measure instantiation time and memory for a function."""
|
||||
# Warmup
|
||||
for _ in range(warmup):
|
||||
create_fn()
|
||||
|
||||
# Measure time
|
||||
times = []
|
||||
for _ in range(iterations):
|
||||
gc.collect()
|
||||
start = time.perf_counter()
|
||||
create_fn()
|
||||
elapsed = (time.perf_counter() - start) * 1_000_000 # microseconds
|
||||
times.append(elapsed)
|
||||
|
||||
# Measure memory (separate pass for accuracy)
|
||||
memory_usages = []
|
||||
for _ in range(min(iterations, 20)): # Fewer iterations for memory
|
||||
gc.collect()
|
||||
tracemalloc.start()
|
||||
create_fn()
|
||||
_, peak = tracemalloc.get_traced_memory()
|
||||
tracemalloc.stop()
|
||||
memory_usages.append(peak / 1024) # KB
|
||||
|
||||
return {
|
||||
'avg_time': statistics.mean(times),
|
||||
'min_time': min(times),
|
||||
'max_time': max(times),
|
||||
'std_dev': statistics.stdev(times) if len(times) > 1 else 0,
|
||||
'avg_memory': statistics.mean(memory_usages) if memory_usages else 0,
|
||||
}
|
||||
|
||||
|
||||
def sample_tool(city: Literal['nyc', 'sf']):
|
||||
"""Sample tool for benchmark testing."""
|
||||
return 'cloudy' if city == 'nyc' else 'sunny'
|
||||
|
||||
|
||||
def run_benchmark(iterations: int = 100) -> List[ParamResult]:
|
||||
"""Run parameter impact benchmark."""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
results = []
|
||||
|
||||
print('=' * 70)
|
||||
print('PraisonAI Agents - Parameter Impact Benchmark')
|
||||
print('=' * 70)
|
||||
print(f'\nIterations: {iterations}')
|
||||
print('Measuring incremental cost of each Agent parameter\n')
|
||||
|
||||
# Baseline: Minimal agent
|
||||
print("Testing baseline (minimal agent)...")
|
||||
baseline_stats = measure_instantiation(
|
||||
lambda: Agent(name='Test', output='silent'),
|
||||
iterations
|
||||
)
|
||||
baseline_result = ParamResult(
|
||||
param_name='BASELINE',
|
||||
param_value='Agent(name="Test", output="silent")',
|
||||
avg_time_us=baseline_stats['avg_time'],
|
||||
min_time_us=baseline_stats['min_time'],
|
||||
max_time_us=baseline_stats['max_time'],
|
||||
std_dev_us=baseline_stats['std_dev'],
|
||||
avg_memory_kb=baseline_stats['avg_memory'],
|
||||
)
|
||||
results.append(baseline_result)
|
||||
print(f" Baseline: {baseline_stats['avg_time']:.2f} μs, {baseline_stats['avg_memory']:.2f} KB")
|
||||
|
||||
# Define parameters to test
|
||||
# Format: (param_name, param_value, description)
|
||||
params_to_test = [
|
||||
# Core identity
|
||||
('instructions', 'Be helpful and concise.', 'instructions="..."'),
|
||||
('role', 'Assistant', 'role="Assistant"'),
|
||||
('goal', 'Help users', 'goal="Help users"'),
|
||||
('backstory', 'I am an AI assistant.', 'backstory="..."'),
|
||||
|
||||
# LLM configuration
|
||||
('llm', 'gpt-4o-mini', 'llm="gpt-4o-mini"'),
|
||||
('llm', 'openai/gpt-4o-mini', 'llm="openai/gpt-4o-mini" (LiteLLM)'),
|
||||
|
||||
# Tools
|
||||
('tools', [sample_tool], 'tools=[sample_tool]'),
|
||||
|
||||
# Features (bool flags)
|
||||
('context', True, 'context=True'),
|
||||
('context', False, 'context=False'),
|
||||
('planning', True, 'planning=True'),
|
||||
('reflection', True, 'reflection=True'),
|
||||
('memory', True, 'memory=True'),
|
||||
|
||||
# Output modes
|
||||
('output', 'verbose', 'output="verbose"'),
|
||||
('output', 'actions', 'output="actions"'),
|
||||
('output', 'stream', 'output="stream"'),
|
||||
|
||||
# Execution config
|
||||
('execution', 'fast', 'execution="fast"'),
|
||||
('execution', 'thorough', 'execution="thorough"'),
|
||||
|
||||
# Caching
|
||||
('caching', True, 'caching=True'),
|
||||
('caching', False, 'caching=False'),
|
||||
]
|
||||
|
||||
# Test each parameter
|
||||
for param_name, param_value, description in params_to_test:
|
||||
print(f"Testing {description}...")
|
||||
|
||||
try:
|
||||
# Build kwargs
|
||||
kwargs = {'name': 'Test', 'output': 'silent'}
|
||||
kwargs[param_name] = param_value
|
||||
|
||||
# Special case: if testing output param, don't override with silent
|
||||
if param_name == 'output':
|
||||
kwargs = {'name': 'Test'}
|
||||
kwargs[param_name] = param_value
|
||||
|
||||
stats = measure_instantiation(
|
||||
lambda k=kwargs: Agent(**k),
|
||||
iterations
|
||||
)
|
||||
|
||||
result = ParamResult(
|
||||
param_name=param_name,
|
||||
param_value=description,
|
||||
avg_time_us=stats['avg_time'],
|
||||
min_time_us=stats['min_time'],
|
||||
max_time_us=stats['max_time'],
|
||||
std_dev_us=stats['std_dev'],
|
||||
avg_memory_kb=stats['avg_memory'],
|
||||
delta_time_us=stats['avg_time'] - baseline_stats['avg_time'],
|
||||
delta_memory_kb=stats['avg_memory'] - baseline_stats['avg_memory'],
|
||||
)
|
||||
results.append(result)
|
||||
|
||||
delta_sign = '+' if result.delta_time_us >= 0 else ''
|
||||
print(f" {stats['avg_time']:.2f} μs ({delta_sign}{result.delta_time_us:.2f} μs from baseline)")
|
||||
|
||||
except Exception as e:
|
||||
print(f" SKIPPED: {e}")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def print_results(results: List[ParamResult]):
|
||||
"""Print results sorted by performance impact."""
|
||||
print('\n' + '=' * 70)
|
||||
print('RESULTS - Sorted by Time Impact (highest cost first)')
|
||||
print('=' * 70)
|
||||
|
||||
# Sort by delta time (excluding baseline)
|
||||
sorted_results = sorted(
|
||||
[r for r in results if r.param_name != 'BASELINE'],
|
||||
key=lambda x: x.delta_time_us,
|
||||
reverse=True
|
||||
)
|
||||
|
||||
# Get baseline
|
||||
baseline = next((r for r in results if r.param_name == 'BASELINE'), None)
|
||||
|
||||
if baseline:
|
||||
print(f"\nBaseline: {baseline.avg_time_us:.2f} μs, {baseline.avg_memory_kb:.2f} KB")
|
||||
|
||||
print(f"\n{'Parameter':<35} {'Avg (μs)':<12} {'Delta (μs)':<12} {'Memory (KB)':<12} {'Impact':<10}")
|
||||
print('-' * 85)
|
||||
|
||||
for r in sorted_results:
|
||||
# Calculate impact level
|
||||
if r.delta_time_us > 1000:
|
||||
impact = '🔴 HIGH'
|
||||
elif r.delta_time_us > 100:
|
||||
impact = '🟡 MEDIUM'
|
||||
elif r.delta_time_us > 10:
|
||||
impact = '🟢 LOW'
|
||||
else:
|
||||
impact = '⚪ MINIMAL'
|
||||
|
||||
delta_str = f"+{r.delta_time_us:.2f}" if r.delta_time_us >= 0 else f"{r.delta_time_us:.2f}"
|
||||
print(f"{r.param_value:<35} {r.avg_time_us:<12.2f} {delta_str:<12} {r.avg_memory_kb:<12.2f} {impact:<10}")
|
||||
|
||||
# Summary
|
||||
print('\n' + '=' * 70)
|
||||
print('SUMMARY')
|
||||
print('=' * 70)
|
||||
|
||||
high_impact = [r for r in sorted_results if r.delta_time_us > 1000]
|
||||
medium_impact = [r for r in sorted_results if 100 < r.delta_time_us <= 1000]
|
||||
low_impact = [r for r in sorted_results if 10 < r.delta_time_us <= 100]
|
||||
|
||||
print(f"\nHigh impact (>1ms): {len(high_impact)} parameters")
|
||||
for r in high_impact:
|
||||
print(f" - {r.param_value}: +{r.delta_time_us:.2f} μs")
|
||||
|
||||
print(f"\nMedium impact (100μs-1ms): {len(medium_impact)} parameters")
|
||||
for r in medium_impact:
|
||||
print(f" - {r.param_value}: +{r.delta_time_us:.2f} μs")
|
||||
|
||||
print(f"\nLow impact (10-100μs): {len(low_impact)} parameters")
|
||||
|
||||
print('\n' + '=' * 70)
|
||||
|
||||
|
||||
def save_results(results: List[ParamResult], filename: str = 'PARAM_IMPACT_RESULTS.md'):
|
||||
"""Save results to markdown file."""
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
filepath = os.path.join(os.path.dirname(__file__), filename)
|
||||
|
||||
# Sort by delta time
|
||||
sorted_results = sorted(
|
||||
[r for r in results if r.param_name != 'BASELINE'],
|
||||
key=lambda x: x.delta_time_us,
|
||||
reverse=True
|
||||
)
|
||||
baseline = next((r for r in results if r.param_name == 'BASELINE'), None)
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write('# PraisonAI Agents - Parameter Impact Benchmark Results\n\n')
|
||||
f.write(f'**Generated:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n\n')
|
||||
|
||||
if baseline:
|
||||
f.write(f'**Baseline:** {baseline.avg_time_us:.2f} μs, {baseline.avg_memory_kb:.2f} KB\n\n')
|
||||
|
||||
f.write('## Results (Sorted by Impact)\n\n')
|
||||
f.write('| Parameter | Avg Time (μs) | Delta (μs) | Memory (KB) | Impact |\n')
|
||||
f.write('|-----------|---------------|------------|-------------|--------|\n')
|
||||
|
||||
for r in sorted_results:
|
||||
if r.delta_time_us > 1000:
|
||||
impact = '🔴 HIGH'
|
||||
elif r.delta_time_us > 100:
|
||||
impact = '🟡 MEDIUM'
|
||||
elif r.delta_time_us > 10:
|
||||
impact = '🟢 LOW'
|
||||
else:
|
||||
impact = '⚪ MINIMAL'
|
||||
|
||||
delta_str = f"+{r.delta_time_us:.2f}" if r.delta_time_us >= 0 else f"{r.delta_time_us:.2f}"
|
||||
f.write(f'| {r.param_value} | {r.avg_time_us:.2f} | {delta_str} | {r.avg_memory_kb:.2f} | {impact} |\n')
|
||||
|
||||
f.write('\n## Recommendations\n\n')
|
||||
|
||||
high_impact = [r for r in sorted_results if r.delta_time_us > 1000]
|
||||
if high_impact:
|
||||
f.write('### High Impact Parameters (>1ms)\n\n')
|
||||
f.write('These parameters add significant overhead and should be used carefully:\n\n')
|
||||
for r in high_impact:
|
||||
f.write(f'- **{r.param_value}**: +{r.delta_time_us:.2f} μs\n')
|
||||
f.write('\n')
|
||||
|
||||
f.write('### Best Practices\n\n')
|
||||
f.write('1. Use `output="silent"` for production workloads\n')
|
||||
f.write('2. Only enable `context=True` when needed for long conversations\n')
|
||||
f.write('3. Use `llm="gpt-4o-mini"` (OpenAI SDK) for fastest instantiation\n')
|
||||
f.write('4. Avoid `memory=True` unless persistent memory is required\n')
|
||||
|
||||
f.write('\n## How to Reproduce\n\n')
|
||||
f.write('```bash\n')
|
||||
f.write('cd praisonai-agents\n')
|
||||
f.write('python benchmarks/param_impact_benchmark.py\n')
|
||||
f.write('```\n')
|
||||
|
||||
print(f'\nResults saved to: {filepath}')
|
||||
return filepath
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Parameter Impact Benchmark')
|
||||
parser.add_argument('--iterations', type=int, default=100, help='Number of iterations per test')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
results = run_benchmark(iterations=args.iterations)
|
||||
print_results(results)
|
||||
|
||||
if args.save:
|
||||
save_results(results)
|
||||
else:
|
||||
print('\nResults not saved (use --save flag to save results to file)')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -14,6 +14,7 @@ import tracemalloc
|
||||
import statistics
|
||||
from typing import Literal, List, Callable
|
||||
from dataclasses import dataclass, field
|
||||
import argparse
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -186,7 +187,7 @@ def instantiate_praisonai_agent():
|
||||
instructions="Be concise, reply with one sentence.",
|
||||
llm="gpt-4o-mini",
|
||||
tools=tools,
|
||||
verbose=False
|
||||
output="silent"
|
||||
)
|
||||
|
||||
|
||||
@@ -197,7 +198,7 @@ def instantiate_praisonai_litellm_agent():
|
||||
instructions="Be concise, reply with one sentence.",
|
||||
llm="openai/gpt-4o-mini",
|
||||
tools=tools,
|
||||
verbose=False
|
||||
output="silent"
|
||||
)
|
||||
|
||||
|
||||
@@ -364,6 +365,10 @@ def save_benchmark_results(results: dict, baseline):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Agents - Comprehensive Performance Benchmark')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
print("="*70)
|
||||
print("PraisonAI Agents - Comprehensive Performance Benchmark")
|
||||
print("="*70)
|
||||
@@ -520,4 +525,7 @@ if __name__ == "__main__":
|
||||
print("\n" + "="*70)
|
||||
|
||||
# Save results to files
|
||||
save_benchmark_results(results, baseline)
|
||||
if args.save:
|
||||
save_benchmark_results(results, baseline)
|
||||
else:
|
||||
print('\nResults not saved (use --save flag to save results to file)')
|
||||
@@ -0,0 +1,427 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive Quick Benchmark - PraisonAI vs Agno
|
||||
|
||||
All-in-one benchmark comparing two frameworks across multiple metrics:
|
||||
- Import time (cold start)
|
||||
- Agent instantiation time
|
||||
- Memory footprint (per-agent)
|
||||
- Function call count (cProfile)
|
||||
- Deep profiling (hot spots)
|
||||
|
||||
Designed for rapid iteration with minimal iterations.
|
||||
|
||||
Usage:
|
||||
python benchmarks/quick_benchmark.py # All metrics
|
||||
python benchmarks/quick_benchmark.py --fast # Quick mode (fewer iterations)
|
||||
python benchmarks/quick_benchmark.py --profile # Include cProfile details
|
||||
python benchmarks/quick_benchmark.py --memory # Include memory details
|
||||
python benchmarks/quick_benchmark.py --import # Include import time
|
||||
python benchmarks/quick_benchmark.py --all # All detailed output
|
||||
"""
|
||||
|
||||
import time
|
||||
import argparse
|
||||
import sys
|
||||
import gc
|
||||
|
||||
# ============================================================================
|
||||
# BENCHMARK FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
def measure_import_time(module_name, clear_cache=True, full_agent_import=True):
|
||||
"""Measure import time for a module.
|
||||
|
||||
Args:
|
||||
module_name: Name of the module to import
|
||||
clear_cache: Whether to clear module cache before import
|
||||
full_agent_import: If True, measure full Agent class import (recommended).
|
||||
If False, measure package import only (misleading for Agno).
|
||||
|
||||
Note: Agno's main __init__.py only exports __version__, so measuring
|
||||
'import agno' gives misleading results (5ms). The actual Agent import
|
||||
'from agno.agent import Agent' takes ~500ms. This function measures
|
||||
the full Agent import by default for accurate comparison.
|
||||
"""
|
||||
if clear_cache:
|
||||
# Clear module cache
|
||||
mods_to_remove = [k for k in sys.modules.keys() if module_name.split('.')[0] in k]
|
||||
for m in mods_to_remove:
|
||||
del sys.modules[m]
|
||||
|
||||
gc.collect()
|
||||
start = time.perf_counter()
|
||||
|
||||
if module_name == 'praisonaiagents':
|
||||
if full_agent_import:
|
||||
from praisonaiagents import Agent # noqa: F401
|
||||
else:
|
||||
import praisonaiagents # noqa: F401
|
||||
elif module_name == 'agno':
|
||||
if full_agent_import:
|
||||
from agno.agent import Agent # noqa: F401
|
||||
else:
|
||||
import agno # noqa: F401
|
||||
|
||||
end = time.perf_counter()
|
||||
return (end - start) * 1000 # Return in milliseconds
|
||||
|
||||
def get_praisonai_factory():
|
||||
"""Get PraisonAI agent factory function."""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
def create():
|
||||
return Agent(name="Test", instructions="You are helpful.", output="silent")
|
||||
|
||||
return create
|
||||
|
||||
def get_agno_factory():
|
||||
"""Get Agno agent factory function."""
|
||||
try:
|
||||
from agno.agent import Agent as AgnoAgent
|
||||
|
||||
def create():
|
||||
return AgnoAgent(name="Test", instructions=["You are helpful."])
|
||||
|
||||
return create
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
def measure_instantiation(factory, iterations=50, warmup=5):
|
||||
"""Measure agent instantiation time."""
|
||||
# Warmup
|
||||
for _ in range(warmup):
|
||||
factory()
|
||||
|
||||
gc.collect()
|
||||
|
||||
# Measure
|
||||
times = []
|
||||
for _ in range(iterations):
|
||||
start = time.perf_counter()
|
||||
factory()
|
||||
times.append((time.perf_counter() - start) * 1e6) # microseconds
|
||||
|
||||
return {
|
||||
'avg': sum(times) / len(times),
|
||||
'min': min(times),
|
||||
'max': max(times),
|
||||
'median': sorted(times)[len(times) // 2],
|
||||
}
|
||||
|
||||
def measure_memory(factory, iterations=20):
|
||||
"""Measure memory footprint per agent."""
|
||||
import tracemalloc
|
||||
|
||||
gc.collect()
|
||||
tracemalloc.start()
|
||||
|
||||
agents = []
|
||||
for _ in range(iterations):
|
||||
agents.append(factory())
|
||||
|
||||
current, peak = tracemalloc.get_traced_memory()
|
||||
tracemalloc.stop()
|
||||
|
||||
per_agent = current / iterations / 1024 # KB per agent
|
||||
return {
|
||||
'per_agent_kb': per_agent,
|
||||
'total_kb': current / 1024,
|
||||
'peak_kb': peak / 1024,
|
||||
}
|
||||
|
||||
def measure_function_calls(factory, iterations=50):
|
||||
"""Count function calls using cProfile."""
|
||||
import cProfile
|
||||
import pstats
|
||||
from io import StringIO
|
||||
|
||||
profiler = cProfile.Profile()
|
||||
profiler.enable()
|
||||
|
||||
for _ in range(iterations):
|
||||
factory()
|
||||
|
||||
profiler.disable()
|
||||
|
||||
stream = StringIO()
|
||||
stats = pstats.Stats(profiler, stream=stream)
|
||||
|
||||
# Get total function calls
|
||||
total_calls = stats.total_calls
|
||||
calls_per_agent = total_calls / iterations
|
||||
|
||||
# Get top functions by time
|
||||
stats.sort_stats('tottime')
|
||||
top_functions = []
|
||||
for func, (cc, nc, tt, ct, callers) in list(stats.stats.items())[:10]:
|
||||
filename, line, name = func
|
||||
top_functions.append({
|
||||
'name': name,
|
||||
'calls': nc,
|
||||
'tottime': tt * 1e6 / iterations, # μs per agent
|
||||
})
|
||||
|
||||
return {
|
||||
'total_calls': total_calls,
|
||||
'calls_per_agent': calls_per_agent,
|
||||
'top_functions': top_functions,
|
||||
}
|
||||
|
||||
def run_deep_profile(factory, iterations=50):
|
||||
"""Run detailed cProfile analysis."""
|
||||
import cProfile
|
||||
import pstats
|
||||
from io import StringIO
|
||||
|
||||
profiler = cProfile.Profile()
|
||||
profiler.enable()
|
||||
|
||||
for _ in range(iterations):
|
||||
factory()
|
||||
|
||||
profiler.disable()
|
||||
|
||||
stream = StringIO()
|
||||
stats = pstats.Stats(profiler, stream=stream)
|
||||
stats.sort_stats('cumulative')
|
||||
stats.print_stats(25)
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
# ============================================================================
|
||||
# MAIN
|
||||
# ============================================================================
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Comprehensive Quick Benchmark - PraisonAI vs Agno",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python benchmarks/quick_benchmark.py # Standard benchmark
|
||||
python benchmarks/quick_benchmark.py --fast # Quick mode
|
||||
python benchmarks/quick_benchmark.py --all # All details
|
||||
"""
|
||||
)
|
||||
parser.add_argument("--iterations", "-n", type=int, default=50,
|
||||
help="Iterations for timing (default: 50)")
|
||||
parser.add_argument("--fast", "-f", action="store_true",
|
||||
help="Fast mode (20 iterations)")
|
||||
parser.add_argument("--profile", "-p", action="store_true",
|
||||
help="Show detailed cProfile output")
|
||||
parser.add_argument("--memory", "-m", action="store_true",
|
||||
help="Show memory details")
|
||||
parser.add_argument("--import", dest="show_import", action="store_true",
|
||||
help="Show import time details")
|
||||
parser.add_argument("--all", "-A", action="store_true",
|
||||
help="Show all details")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.fast:
|
||||
args.iterations = 20
|
||||
|
||||
if args.all:
|
||||
args.profile = True
|
||||
args.memory = True
|
||||
args.show_import = True
|
||||
|
||||
print("=" * 70)
|
||||
print("COMPREHENSIVE QUICK BENCHMARK - PraisonAI vs Agno")
|
||||
print("=" * 70)
|
||||
print(f"Iterations: {args.iterations}")
|
||||
print()
|
||||
|
||||
results = {'praisonai': {}, 'agno': {}}
|
||||
|
||||
# ========================================================================
|
||||
# 1. IMPORT TIME
|
||||
# ========================================================================
|
||||
print("📦 Measuring import time...")
|
||||
|
||||
results['praisonai']['import_ms'] = measure_import_time('praisonaiagents')
|
||||
results['agno']['import_ms'] = measure_import_time('agno')
|
||||
|
||||
# ========================================================================
|
||||
# 2. GET FACTORIES (after import measurement)
|
||||
# ========================================================================
|
||||
print("🏭 Loading factories...")
|
||||
|
||||
praisonai_factory = get_praisonai_factory()
|
||||
agno_factory = get_agno_factory()
|
||||
|
||||
if agno_factory is None:
|
||||
print("⚠️ Agno not installed, skipping comparison")
|
||||
return 1
|
||||
|
||||
# ========================================================================
|
||||
# 3. INSTANTIATION TIME
|
||||
# ========================================================================
|
||||
print("⏱️ Measuring instantiation time...")
|
||||
|
||||
results['praisonai']['time'] = measure_instantiation(praisonai_factory, args.iterations)
|
||||
results['agno']['time'] = measure_instantiation(agno_factory, args.iterations)
|
||||
|
||||
# ========================================================================
|
||||
# 4. MEMORY FOOTPRINT
|
||||
# ========================================================================
|
||||
print("💾 Measuring memory footprint...")
|
||||
|
||||
results['praisonai']['memory'] = measure_memory(praisonai_factory, min(args.iterations, 20))
|
||||
results['agno']['memory'] = measure_memory(agno_factory, min(args.iterations, 20))
|
||||
|
||||
# ========================================================================
|
||||
# 5. FUNCTION CALLS
|
||||
# ========================================================================
|
||||
print("📊 Counting function calls...")
|
||||
|
||||
results['praisonai']['calls'] = measure_function_calls(praisonai_factory, args.iterations)
|
||||
results['agno']['calls'] = measure_function_calls(agno_factory, args.iterations)
|
||||
|
||||
# ========================================================================
|
||||
# RESULTS SUMMARY
|
||||
# ========================================================================
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("RESULTS SUMMARY")
|
||||
print("=" * 70)
|
||||
|
||||
# Import Time
|
||||
print()
|
||||
print("📦 IMPORT TIME (cold start)")
|
||||
print("-" * 50)
|
||||
print(f"{'Framework':<15} {'Time (ms)':<15} {'Relative':<15}")
|
||||
print("-" * 50)
|
||||
p_import = results['praisonai']['import_ms']
|
||||
a_import = results['agno']['import_ms']
|
||||
print(f"{'PraisonAI':<15} {p_import:<15.2f} {'1.00x':<15}")
|
||||
print(f"{'Agno':<15} {a_import:<15.2f} {a_import/p_import:.2f}x")
|
||||
|
||||
# Instantiation Time
|
||||
print()
|
||||
print("⏱️ INSTANTIATION TIME (per agent)")
|
||||
print("-" * 50)
|
||||
print(f"{'Framework':<15} {'Avg (μs)':<12} {'Min (μs)':<12} {'Median (μs)':<12}")
|
||||
print("-" * 50)
|
||||
p_time = results['praisonai']['time']
|
||||
a_time = results['agno']['time']
|
||||
print(f"{'PraisonAI':<15} {p_time['avg']:<12.2f} {p_time['min']:<12.2f} {p_time['median']:<12.2f}")
|
||||
print(f"{'Agno':<15} {a_time['avg']:<12.2f} {a_time['min']:<12.2f} {a_time['median']:<12.2f}")
|
||||
|
||||
ratio = p_time['avg'] / a_time['avg']
|
||||
diff = p_time['avg'] - a_time['avg']
|
||||
print()
|
||||
if ratio > 1:
|
||||
print(f" → PraisonAI is {ratio:.2f}x slower ({diff:.2f}μs more)")
|
||||
else:
|
||||
print(f" → PraisonAI is {1/ratio:.2f}x faster ({-diff:.2f}μs less)")
|
||||
|
||||
# Memory
|
||||
print()
|
||||
print("💾 MEMORY FOOTPRINT (per agent)")
|
||||
print("-" * 50)
|
||||
print(f"{'Framework':<15} {'Per Agent (KB)':<15} {'Peak (KB)':<15}")
|
||||
print("-" * 50)
|
||||
p_mem = results['praisonai']['memory']
|
||||
a_mem = results['agno']['memory']
|
||||
print(f"{'PraisonAI':<15} {p_mem['per_agent_kb']:<15.2f} {p_mem['peak_kb']:<15.2f}")
|
||||
print(f"{'Agno':<15} {a_mem['per_agent_kb']:<15.2f} {a_mem['peak_kb']:<15.2f}")
|
||||
|
||||
# Function Calls
|
||||
print()
|
||||
print("📊 FUNCTION CALLS (per agent)")
|
||||
print("-" * 50)
|
||||
print(f"{'Framework':<15} {'Calls/Agent':<15} {'Total Calls':<15}")
|
||||
print("-" * 50)
|
||||
p_calls = results['praisonai']['calls']
|
||||
a_calls = results['agno']['calls']
|
||||
print(f"{'PraisonAI':<15} {p_calls['calls_per_agent']:<15.1f} {p_calls['total_calls']:<15}")
|
||||
print(f"{'Agno':<15} {a_calls['calls_per_agent']:<15.1f} {a_calls['total_calls']:<15}")
|
||||
|
||||
# ========================================================================
|
||||
# OVERALL SCORE
|
||||
# ========================================================================
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("OVERALL COMPARISON")
|
||||
print("=" * 70)
|
||||
|
||||
metrics = [
|
||||
("Import Time", p_import, a_import, "lower"),
|
||||
("Instantiation", p_time['avg'], a_time['avg'], "lower"),
|
||||
("Memory/Agent", p_mem['per_agent_kb'], a_mem['per_agent_kb'], "lower"),
|
||||
("Function Calls", p_calls['calls_per_agent'], a_calls['calls_per_agent'], "lower"),
|
||||
]
|
||||
|
||||
print(f"{'Metric':<20} {'PraisonAI':<15} {'Agno':<15} {'Winner':<15}")
|
||||
print("-" * 65)
|
||||
|
||||
praison_wins = 0
|
||||
agno_wins = 0
|
||||
|
||||
for name, p_val, a_val, better in metrics:
|
||||
if better == "lower":
|
||||
winner = "PraisonAI ✓" if p_val <= a_val else "Agno ✓"
|
||||
if p_val <= a_val:
|
||||
praison_wins += 1
|
||||
else:
|
||||
agno_wins += 1
|
||||
else:
|
||||
winner = "PraisonAI ✓" if p_val >= a_val else "Agno ✓"
|
||||
if p_val >= a_val:
|
||||
praison_wins += 1
|
||||
else:
|
||||
agno_wins += 1
|
||||
|
||||
if isinstance(p_val, float):
|
||||
print(f"{name:<20} {p_val:<15.2f} {a_val:<15.2f} {winner:<15}")
|
||||
else:
|
||||
print(f"{name:<20} {p_val:<15} {a_val:<15} {winner:<15}")
|
||||
|
||||
print("-" * 65)
|
||||
print(f"Score: PraisonAI {praison_wins} - Agno {agno_wins}")
|
||||
print("=" * 70)
|
||||
|
||||
# ========================================================================
|
||||
# DETAILED OUTPUT (if requested)
|
||||
# ========================================================================
|
||||
|
||||
if args.show_import:
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("IMPORT TIME DETAILS")
|
||||
print("=" * 70)
|
||||
print(f"PraisonAI: {results['praisonai']['import_ms']:.2f}ms")
|
||||
print(f"Agno: {results['agno']['import_ms']:.2f}ms")
|
||||
|
||||
if args.memory:
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("MEMORY DETAILS")
|
||||
print("=" * 70)
|
||||
print("PraisonAI:")
|
||||
print(f" Per Agent: {p_mem['per_agent_kb']:.2f} KB")
|
||||
print(f" Total: {p_mem['total_kb']:.2f} KB")
|
||||
print(f" Peak: {p_mem['peak_kb']:.2f} KB")
|
||||
print("Agno:")
|
||||
print(f" Per Agent: {a_mem['per_agent_kb']:.2f} KB")
|
||||
print(f" Total: {a_mem['total_kb']:.2f} KB")
|
||||
print(f" Peak: {a_mem['peak_kb']:.2f} KB")
|
||||
|
||||
if args.profile:
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("PRAISONAI DEEP PROFILE")
|
||||
print("=" * 70)
|
||||
print(run_deep_profile(praisonai_factory, args.iterations))
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("AGNO DEEP PROFILE")
|
||||
print("=" * 70)
|
||||
print(run_deep_profile(agno_factory, args.iterations))
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -15,6 +15,7 @@ import os
|
||||
from typing import Literal, List
|
||||
from dataclasses import dataclass, field
|
||||
from importlib.metadata import version as get_version
|
||||
import argparse
|
||||
|
||||
# Ensure API key is set
|
||||
if not os.environ.get('OPENAI_API_KEY'):
|
||||
@@ -392,6 +393,10 @@ def save_results(results: dict, versions: dict):
|
||||
# ============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Agents - Real-World Execution Benchmark')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
print("="*70)
|
||||
print("PraisonAI Agents - Real-World Execution Benchmark")
|
||||
print("="*70)
|
||||
@@ -470,4 +475,7 @@ if __name__ == "__main__":
|
||||
print("\n" + "="*70)
|
||||
|
||||
# Save results
|
||||
save_results(results, versions)
|
||||
if args.save:
|
||||
save_results(results, versions)
|
||||
else:
|
||||
print('\nResults not saved (use --save flag to save results to file)')
|
||||
@@ -61,7 +61,7 @@ def benchmark_praisonai_response(iterations: int = 5) -> ResponseResult:
|
||||
name="Test Agent",
|
||||
instructions="Be concise, reply with one sentence.",
|
||||
llm="gpt-4o-mini",
|
||||
verbose=False
|
||||
output="silent"
|
||||
)
|
||||
|
||||
start = time.perf_counter()
|
||||
@@ -114,7 +114,7 @@ def benchmark_praisonai_with_tool(iterations: int = 5) -> ResponseResult:
|
||||
instructions="You are a weather assistant. Use the get_weather tool to answer questions.",
|
||||
llm="gpt-4o-mini",
|
||||
tools=[get_weather],
|
||||
verbose=False
|
||||
output="silent"
|
||||
)
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
@@ -10,6 +10,7 @@ Usage:
|
||||
|
||||
import time
|
||||
from typing import Literal
|
||||
import argparse
|
||||
|
||||
|
||||
ITERATIONS = 100
|
||||
@@ -47,11 +48,11 @@ def run_benchmark():
|
||||
from praisonaiagents import Agent as PraisonAgent
|
||||
|
||||
results['PraisonAI'] = measure_instantiation(
|
||||
lambda: PraisonAgent(name='Test', llm='gpt-4o-mini', verbose=False)
|
||||
lambda: PraisonAgent(name='Test', llm='gpt-4o-mini', output="silent")
|
||||
)
|
||||
|
||||
results['PraisonAI (LiteLLM)'] = measure_instantiation(
|
||||
lambda: PraisonAgent(name='Test', llm='openai/gpt-4o-mini', verbose=False)
|
||||
lambda: PraisonAgent(name='Test', llm='openai/gpt-4o-mini', output="silent")
|
||||
)
|
||||
|
||||
# Other frameworks for comparison
|
||||
@@ -239,5 +240,13 @@ def update_readme(readme_path: str, table_rows: list):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Agents - Performance Benchmark')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
results = run_benchmark()
|
||||
save_results(results)
|
||||
|
||||
if args.save:
|
||||
save_results(results)
|
||||
else:
|
||||
print('\nResults not saved (use --save flag to save results to file)')
|
||||
@@ -11,6 +11,7 @@ Usage:
|
||||
import time
|
||||
from typing import Literal
|
||||
from importlib.metadata import version as get_version
|
||||
import argparse
|
||||
|
||||
|
||||
ITERATIONS = 100
|
||||
@@ -57,11 +58,11 @@ def run_benchmark():
|
||||
from praisonaiagents import Agent as PraisonAgent
|
||||
|
||||
results['PraisonAI'] = measure_instantiation(
|
||||
lambda: PraisonAgent(name='Test', llm='gpt-4o-mini', tools=tools, verbose=False)
|
||||
lambda: PraisonAgent(name='Test', llm='gpt-4o-mini', tools=tools, output="silent")
|
||||
)
|
||||
|
||||
results['PraisonAI (LiteLLM)'] = measure_instantiation(
|
||||
lambda: PraisonAgent(name='Test', llm='openai/gpt-4o-mini', tools=tools, verbose=False)
|
||||
lambda: PraisonAgent(name='Test', llm='openai/gpt-4o-mini', tools=tools, output="silent")
|
||||
)
|
||||
|
||||
# Other frameworks with tools
|
||||
@@ -209,5 +210,13 @@ def save_results(results: dict, filename: str = 'TOOLS_BENCHMARK_RESULTS.md'):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='PraisonAI Agents - Tools Benchmark')
|
||||
parser.add_argument('--save', action='store_true', help='Save results to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
results = run_benchmark()
|
||||
save_results(results)
|
||||
|
||||
if args.save:
|
||||
save_results(results)
|
||||
else:
|
||||
print('\nResults not saved (use --save flag to save results to file)')
|
||||
@@ -1,24 +0,0 @@
|
||||
"""Basic memory example - minimal usage."""
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Simple enable (uses default file-based memory)
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant with memory.",
|
||||
memory=True,
|
||||
)
|
||||
|
||||
# With preset
|
||||
agent_redis = Agent(
|
||||
instructions="You are a helpful assistant.",
|
||||
memory="redis", # Uses redis preset
|
||||
)
|
||||
|
||||
# With URL
|
||||
agent_postgres = Agent(
|
||||
instructions="You are a helpful assistant.",
|
||||
memory="postgresql://localhost/mydb", # URL auto-detected
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
response = agent.chat("Remember that my favorite color is blue.")
|
||||
print(response)
|
||||
@@ -1,22 +0,0 @@
|
||||
"""Basic workflow example with consolidated params."""
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.workflows import Workflow, WorkflowStep
|
||||
|
||||
# Create agents
|
||||
writer = Agent(instructions="You are a content writer.")
|
||||
editor = Agent(instructions="You are an editor.")
|
||||
|
||||
# Create workflow with consolidated params
|
||||
workflow = Workflow(
|
||||
name="Content Pipeline",
|
||||
steps=[
|
||||
WorkflowStep(name="write", agent=writer, action="Write about {{topic}}"),
|
||||
WorkflowStep(name="edit", agent=editor, action="Edit the content", context=["write"]),
|
||||
],
|
||||
output="verbose",
|
||||
planning=True,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = workflow.run(variables={"topic": "AI agents"})
|
||||
print(result)
|
||||
@@ -2,7 +2,7 @@ from praisonaiagents import Agent
|
||||
|
||||
# Detailed LLM configuration
|
||||
llm_config = {
|
||||
"model": "gemini/gemini-1.5-flash-latest", # Model name without provider prefix
|
||||
"model": "gemini/gemini-3-flash-preview", # Model name without provider prefix
|
||||
|
||||
# Core settings
|
||||
"temperature": 0.7, # Controls randomness (like temperature)
|
||||
@@ -25,7 +25,7 @@ llm_config = {
|
||||
|
||||
# Additional controls
|
||||
"seed": 42, # For reproducible responses
|
||||
"stop_phrases": ["##", "END"], # Custom stop sequences
|
||||
"stop_phrases": ["---END---", "END"], # Custom stop sequences
|
||||
}
|
||||
|
||||
agent = Agent(
|
||||
|
||||
@@ -5,6 +5,25 @@ This module uses lazy loading to minimize import time and memory usage.
|
||||
Heavy dependencies like litellm are only loaded when actually needed.
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# NAMING CONVENTION GUIDE (Simplified patterns for consistency)
|
||||
# =============================================================================
|
||||
# | Pattern | When to Use | Examples |
|
||||
# |---------------|-------------------------|----------------------------------|
|
||||
# | add_X | Register something | add_hook, add_tool, add_profile |
|
||||
# | get_X | Retrieve something | get_tool, get_profile |
|
||||
# | remove_X | Unregister something | remove_hook, remove_tool |
|
||||
# | has_X | Check existence | has_hook, has_tool |
|
||||
# | list_X | List all items | list_tools, list_profiles |
|
||||
# | enable_X | Turn on feature | enable_telemetry |
|
||||
# | disable_X | Turn off feature | disable_telemetry |
|
||||
# | XConfig | Configuration class | MemoryConfig, HooksConfig |
|
||||
# | @decorator | Decorator | @tool, @add_hook |
|
||||
# |---------------|-------------------------|----------------------------------|
|
||||
# | set_default_X | Internal/advanced only | (don't simplify - internal) |
|
||||
# | create_X | Factory function | (already well-named) |
|
||||
# =============================================================================
|
||||
|
||||
# =============================================================================
|
||||
# NAMESPACE PACKAGE GUARD
|
||||
# =============================================================================
|
||||
@@ -45,559 +64,441 @@ from .tools.tools import Tools
|
||||
from .tools.base import BaseTool, ToolResult, ToolValidationError, validate_tool
|
||||
from .tools.decorator import tool, FunctionTool
|
||||
from .tools.registry import get_registry, register_tool, get_tool, ToolRegistry
|
||||
from .db import db
|
||||
from .obs import obs
|
||||
# db and obs are lazy-loaded via __getattr__ for performance
|
||||
|
||||
# Sub-packages for organized imports (pa.config, pa.tools, etc.)
|
||||
# These enable: import praisonaiagents as pa; pa.config.MemoryConfig
|
||||
from . import config
|
||||
from . import tools
|
||||
from . import memory
|
||||
from . import workflows
|
||||
# Note: knowledge and mcp are lazy-loaded via __getattr__ due to heavy deps
|
||||
# Note: db, obs, knowledge and mcp are lazy-loaded via __getattr__ due to heavy deps
|
||||
|
||||
# Embedding API - explicit import shadows subpackage, enabling:
|
||||
# from praisonaiagents import embedding, embeddings, EmbeddingResult, get_dimensions
|
||||
# Note: litellm is still lazy-loaded INSIDE the functions (no performance impact)
|
||||
from .embedding.embed import embedding, aembedding
|
||||
from .embedding.result import EmbeddingResult
|
||||
from .embedding.dimensions import get_dimensions
|
||||
embeddings = embedding # Plural alias (OpenAI style: client.embeddings.create)
|
||||
aembeddings = aembedding # Plural alias for async
|
||||
# Embedding API - LAZY LOADED via __getattr__ for performance
|
||||
# Supports: embedding, embeddings, aembedding, aembeddings, EmbeddingResult, get_dimensions
|
||||
|
||||
# Workflows - lightweight module
|
||||
from .workflows import (
|
||||
Workflow, WorkflowStep, WorkflowContext, StepResult,
|
||||
Route, Parallel, Loop, Repeat,
|
||||
route, parallel, loop, repeat,
|
||||
Pipeline # Alias for Workflow
|
||||
)
|
||||
from .guardrails import GuardrailResult, LLMGuardrail
|
||||
# Workflows - LAZY LOADED (moved to __getattr__)
|
||||
# Workflow, WorkflowStep, WorkflowContext, StepResult, Route, Parallel, Loop, Repeat, etc.
|
||||
|
||||
# Handoff - lightweight (unified agent-to-agent transfer)
|
||||
from .agent.handoff import (
|
||||
Handoff, handoff, handoff_filters,
|
||||
RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions,
|
||||
HandoffConfig, HandoffResult, HandoffInputData,
|
||||
ContextPolicy, HandoffError, HandoffCycleError, HandoffDepthError, HandoffTimeoutError,
|
||||
)
|
||||
# Guardrails - LAZY LOADED (imports main.py which imports rich)
|
||||
# GuardrailResult and LLMGuardrail moved to __getattr__
|
||||
|
||||
# Flow display (optional)
|
||||
try:
|
||||
from .flow_display import FlowDisplay, track_workflow
|
||||
except ImportError:
|
||||
FlowDisplay = None
|
||||
track_workflow = None
|
||||
# Handoff - LAZY LOADED (moved to __getattr__)
|
||||
# Handoff, handoff, handoff_filters, etc.
|
||||
|
||||
# Main display utilities - these are used frequently so import directly
|
||||
# but they don't pull in litellm
|
||||
from .main import (
|
||||
TaskOutput,
|
||||
ReflectionOutput,
|
||||
display_interaction,
|
||||
display_self_reflection,
|
||||
display_instruction,
|
||||
display_tool_call,
|
||||
display_error,
|
||||
display_generating,
|
||||
clean_triple_backticks,
|
||||
error_logs,
|
||||
register_display_callback,
|
||||
sync_display_callbacks,
|
||||
async_display_callbacks,
|
||||
)
|
||||
# Flow display - LAZY LOADED (moved to __getattr__)
|
||||
# FlowDisplay and track_workflow are now lazy loaded
|
||||
|
||||
# Module-level caches for lazy-loaded classes
|
||||
# Using threading.local() ensures thread-safe access to cached values
|
||||
# Main display utilities - LAZY LOADED to avoid importing rich at startup
|
||||
# These are only needed when output=verbose, not for silent mode
|
||||
# Moved to __getattr__ for lazy loading
|
||||
|
||||
# ============================================================================
|
||||
# LAZY LOADING CONFIGURATION
|
||||
# ============================================================================
|
||||
# Using centralized _lazy.py utility for DRY, thread-safe lazy loading.
|
||||
# All heavy dependencies are loaded on-demand to minimize import time.
|
||||
# ============================================================================
|
||||
|
||||
from ._lazy import lazy_import, create_lazy_getattr_with_fallback
|
||||
|
||||
# Thread-safe cache for lazy-loaded values
|
||||
_lazy_cache = {}
|
||||
|
||||
# Backward compatibility: _get_lazy_cache function for tests
|
||||
import threading
|
||||
_lazy_cache_local = threading.local()
|
||||
|
||||
def _get_lazy_cache():
|
||||
"""Get thread-local lazy cache dict. Thread-safe for concurrent access."""
|
||||
"""Get thread-local lazy cache dict. Thread-safe for concurrent access.
|
||||
|
||||
Note: This is kept for backward compatibility with tests.
|
||||
The main lazy loading now uses the centralized _lazy.py utility.
|
||||
"""
|
||||
if not hasattr(_lazy_cache_local, 'cache'):
|
||||
_lazy_cache_local.cache = {}
|
||||
return _lazy_cache_local.cache
|
||||
|
||||
# Backward-compatible alias (module-level access still works, now thread-safe)
|
||||
_lazy_cache = {} # Kept for compatibility, but prefer _get_lazy_cache()
|
||||
# ============================================================================
|
||||
# LAZY IMPORT MAPPING
|
||||
# ============================================================================
|
||||
# Maps attribute names to (module_path, attr_name) tuples.
|
||||
# This is the single source of truth for all lazy imports.
|
||||
# ============================================================================
|
||||
|
||||
# Flags for optional modules
|
||||
_mcp_available = None
|
||||
_planning_available = None
|
||||
_telemetry_available = None
|
||||
_agui_available = None
|
||||
_a2a_available = None
|
||||
_LAZY_IMPORTS = {
|
||||
# Main display utilities (imports rich)
|
||||
'TaskOutput': ('praisonaiagents.main', 'TaskOutput'),
|
||||
'ReflectionOutput': ('praisonaiagents.main', 'ReflectionOutput'),
|
||||
'display_interaction': ('praisonaiagents.main', 'display_interaction'),
|
||||
'display_self_reflection': ('praisonaiagents.main', 'display_self_reflection'),
|
||||
'display_instruction': ('praisonaiagents.main', 'display_instruction'),
|
||||
'display_tool_call': ('praisonaiagents.main', 'display_tool_call'),
|
||||
'display_error': ('praisonaiagents.main', 'display_error'),
|
||||
'display_generating': ('praisonaiagents.main', 'display_generating'),
|
||||
'clean_triple_backticks': ('praisonaiagents.main', 'clean_triple_backticks'),
|
||||
'error_logs': ('praisonaiagents.main', 'error_logs'),
|
||||
'register_display_callback': ('praisonaiagents.main', 'register_display_callback'),
|
||||
'sync_display_callbacks': ('praisonaiagents.main', 'sync_display_callbacks'),
|
||||
'async_display_callbacks': ('praisonaiagents.main', 'async_display_callbacks'),
|
||||
|
||||
# Workflows
|
||||
'Workflow': ('praisonaiagents.workflows', 'Workflow'),
|
||||
'WorkflowStep': ('praisonaiagents.workflows', 'WorkflowStep'),
|
||||
'WorkflowContext': ('praisonaiagents.workflows', 'WorkflowContext'),
|
||||
'StepResult': ('praisonaiagents.workflows', 'StepResult'),
|
||||
'Route': ('praisonaiagents.workflows', 'Route'),
|
||||
'Parallel': ('praisonaiagents.workflows', 'Parallel'),
|
||||
'Loop': ('praisonaiagents.workflows', 'Loop'),
|
||||
'Repeat': ('praisonaiagents.workflows', 'Repeat'),
|
||||
'route': ('praisonaiagents.workflows', 'route'),
|
||||
'parallel': ('praisonaiagents.workflows', 'parallel'),
|
||||
'loop': ('praisonaiagents.workflows', 'loop'),
|
||||
'repeat': ('praisonaiagents.workflows', 'repeat'),
|
||||
'Pipeline': ('praisonaiagents.workflows', 'Pipeline'),
|
||||
|
||||
# Handoff
|
||||
'Handoff': ('praisonaiagents.agent.handoff', 'Handoff'),
|
||||
'handoff': ('praisonaiagents.agent.handoff', 'handoff'),
|
||||
'handoff_filters': ('praisonaiagents.agent.handoff', 'handoff_filters'),
|
||||
'RECOMMENDED_PROMPT_PREFIX': ('praisonaiagents.agent.handoff', 'RECOMMENDED_PROMPT_PREFIX'),
|
||||
'prompt_with_handoff_instructions': ('praisonaiagents.agent.handoff', 'prompt_with_handoff_instructions'),
|
||||
'HandoffConfig': ('praisonaiagents.agent.handoff', 'HandoffConfig'),
|
||||
'HandoffResult': ('praisonaiagents.agent.handoff', 'HandoffResult'),
|
||||
'HandoffInputData': ('praisonaiagents.agent.handoff', 'HandoffInputData'),
|
||||
'ContextPolicy': ('praisonaiagents.agent.handoff', 'ContextPolicy'),
|
||||
'HandoffError': ('praisonaiagents.agent.handoff', 'HandoffError'),
|
||||
'HandoffCycleError': ('praisonaiagents.agent.handoff', 'HandoffCycleError'),
|
||||
'HandoffDepthError': ('praisonaiagents.agent.handoff', 'HandoffDepthError'),
|
||||
'HandoffTimeoutError': ('praisonaiagents.agent.handoff', 'HandoffTimeoutError'),
|
||||
|
||||
# Embedding API
|
||||
'embedding': ('praisonaiagents.embedding.embed', 'embedding'),
|
||||
'embeddings': ('praisonaiagents.embedding.embed', 'embedding'),
|
||||
'aembedding': ('praisonaiagents.embedding.embed', 'aembedding'),
|
||||
'aembeddings': ('praisonaiagents.embedding.embed', 'aembedding'),
|
||||
'embed': ('praisonaiagents.embedding.embed', 'embed'),
|
||||
'aembed': ('praisonaiagents.embedding.embed', 'aembed'),
|
||||
'EmbeddingResult': ('praisonaiagents.embedding.result', 'EmbeddingResult'),
|
||||
'get_dimensions': ('praisonaiagents.embedding.dimensions', 'get_dimensions'),
|
||||
|
||||
# Guardrails
|
||||
'GuardrailResult': ('praisonaiagents.guardrails', 'GuardrailResult'),
|
||||
'LLMGuardrail': ('praisonaiagents.guardrails', 'LLMGuardrail'),
|
||||
|
||||
# Flow display
|
||||
'FlowDisplay': ('praisonaiagents.flow_display', 'FlowDisplay'),
|
||||
'track_workflow': ('praisonaiagents.flow_display', 'track_workflow'),
|
||||
|
||||
# Agent classes
|
||||
'Agent': ('praisonaiagents.agent.agent', 'Agent'),
|
||||
'ImageAgent': ('praisonaiagents.agent.image_agent', 'ImageAgent'),
|
||||
'VideoAgent': ('praisonaiagents.agent.video_agent', 'VideoAgent'),
|
||||
'VideoConfig': ('praisonaiagents.agent.video_agent', 'VideoConfig'),
|
||||
'AudioAgent': ('praisonaiagents.agent.audio_agent', 'AudioAgent'),
|
||||
'AudioConfig': ('praisonaiagents.agent.audio_agent', 'AudioConfig'),
|
||||
'OCRAgent': ('praisonaiagents.agent.ocr_agent', 'OCRAgent'),
|
||||
'OCRConfig': ('praisonaiagents.agent.ocr_agent', 'OCRConfig'),
|
||||
'ContextAgent': ('praisonaiagents.agent.context_agent', 'ContextAgent'),
|
||||
'create_context_agent': ('praisonaiagents.agent.context_agent', 'create_context_agent'),
|
||||
'DeepResearchAgent': ('praisonaiagents.agent.deep_research_agent', 'DeepResearchAgent'),
|
||||
'DeepResearchResponse': ('praisonaiagents.agent.deep_research_agent', 'DeepResearchResponse'),
|
||||
'ReasoningStep': ('praisonaiagents.agent.deep_research_agent', 'ReasoningStep'),
|
||||
'WebSearchCall': ('praisonaiagents.agent.deep_research_agent', 'WebSearchCall'),
|
||||
'CodeExecutionStep': ('praisonaiagents.agent.deep_research_agent', 'CodeExecutionStep'),
|
||||
'MCPCall': ('praisonaiagents.agent.deep_research_agent', 'MCPCall'),
|
||||
'FileSearchCall': ('praisonaiagents.agent.deep_research_agent', 'FileSearchCall'),
|
||||
'Provider': ('praisonaiagents.agent.deep_research_agent', 'Provider'),
|
||||
'QueryRewriterAgent': ('praisonaiagents.agent.query_rewriter_agent', 'QueryRewriterAgent'),
|
||||
'RewriteStrategy': ('praisonaiagents.agent.query_rewriter_agent', 'RewriteStrategy'),
|
||||
'RewriteResult': ('praisonaiagents.agent.query_rewriter_agent', 'RewriteResult'),
|
||||
'PromptExpanderAgent': ('praisonaiagents.agent.prompt_expander_agent', 'PromptExpanderAgent'),
|
||||
'ExpandStrategy': ('praisonaiagents.agent.prompt_expander_agent', 'ExpandStrategy'),
|
||||
'ExpandResult': ('praisonaiagents.agent.prompt_expander_agent', 'ExpandResult'),
|
||||
'VisionAgent': ('praisonaiagents.agent.vision_agent', 'VisionAgent'),
|
||||
'VisionConfig': ('praisonaiagents.agent.vision_agent', 'VisionConfig'),
|
||||
'EmbeddingAgent': ('praisonaiagents.agent.embedding_agent', 'EmbeddingAgent'),
|
||||
'EmbeddingConfig': ('praisonaiagents.agent.embedding_agent', 'EmbeddingConfig'),
|
||||
'RealtimeAgent': ('praisonaiagents.agent.realtime_agent', 'RealtimeAgent'),
|
||||
'RealtimeConfig': ('praisonaiagents.agent.realtime_agent', 'RealtimeConfig'),
|
||||
'CodeAgent': ('praisonaiagents.agent.code_agent', 'CodeAgent'),
|
||||
'CodeConfig': ('praisonaiagents.agent.code_agent', 'CodeConfig'),
|
||||
|
||||
# Agents / AgentManager
|
||||
'AgentManager': ('praisonaiagents.agents.agents', 'AgentManager'),
|
||||
# Note: 'Agents' is handled by _custom_handler for deprecation warning
|
||||
'Task': ('praisonaiagents.task.task', 'Task'),
|
||||
'AutoAgents': ('praisonaiagents.agents.autoagents', 'AutoAgents'),
|
||||
'AutoRagAgent': ('praisonaiagents.agents.auto_rag_agent', 'AutoRagAgent'),
|
||||
'AutoRagConfig': ('praisonaiagents.agents.auto_rag_agent', 'AutoRagConfig'),
|
||||
'RagRetrievalPolicy': ('praisonaiagents.agents.auto_rag_agent', 'RetrievalPolicy'),
|
||||
|
||||
# Session
|
||||
'Session': ('praisonaiagents.session', 'Session'),
|
||||
|
||||
# App (AgentApp protocol and config)
|
||||
'AgentAppProtocol': ('praisonaiagents.app.protocols', 'AgentAppProtocol'),
|
||||
'AgentAppConfig': ('praisonaiagents.app.config', 'AgentAppConfig'),
|
||||
|
||||
# MCP (optional)
|
||||
'MCP': ('praisonaiagents.mcp.mcp', 'MCP'),
|
||||
|
||||
# Knowledge
|
||||
'Knowledge': ('praisonaiagents.knowledge.knowledge', 'Knowledge'),
|
||||
'Chunking': ('praisonaiagents.knowledge.chunking', 'Chunking'),
|
||||
|
||||
# FastContext
|
||||
'FastContext': ('praisonaiagents.context.fast', 'FastContext'),
|
||||
'FastContextResult': ('praisonaiagents.context.fast', 'FastContextResult'),
|
||||
'FileMatch': ('praisonaiagents.context.fast', 'FileMatch'),
|
||||
'LineRange': ('praisonaiagents.context.fast', 'LineRange'),
|
||||
|
||||
# RAG
|
||||
'RetrievalConfig': ('praisonaiagents.rag.retrieval_config', 'RetrievalConfig'),
|
||||
'RetrievalPolicy': ('praisonaiagents.rag.retrieval_config', 'RetrievalPolicy'),
|
||||
'CitationsMode': ('praisonaiagents.rag.retrieval_config', 'CitationsMode'),
|
||||
'ContextPack': ('praisonaiagents.rag.models', 'ContextPack'),
|
||||
'RAGResult': ('praisonaiagents.rag', 'RAGResult'),
|
||||
'Citation': ('praisonaiagents.rag', 'Citation'),
|
||||
'RAG': ('praisonaiagents.rag', 'RAG'),
|
||||
'RAGConfig': ('praisonaiagents.rag', 'RAGConfig'),
|
||||
'RAGCitation': ('praisonaiagents.rag', 'Citation'),
|
||||
|
||||
# Skills
|
||||
'SkillManager': ('praisonaiagents.skills', 'SkillManager'),
|
||||
'SkillProperties': ('praisonaiagents.skills', 'SkillProperties'),
|
||||
'SkillMetadata': ('praisonaiagents.skills', 'SkillMetadata'),
|
||||
'SkillLoader': ('praisonaiagents.skills', 'SkillLoader'),
|
||||
|
||||
# Memory
|
||||
'Memory': ('praisonaiagents.memory.memory', 'Memory'),
|
||||
|
||||
# Planning
|
||||
'Plan': ('praisonaiagents.planning', 'Plan'),
|
||||
'PlanStep': ('praisonaiagents.planning', 'PlanStep'),
|
||||
'TodoList': ('praisonaiagents.planning', 'TodoList'),
|
||||
'TodoItem': ('praisonaiagents.planning', 'TodoItem'),
|
||||
'PlanStorage': ('praisonaiagents.planning', 'PlanStorage'),
|
||||
'PlanningAgent': ('praisonaiagents.planning', 'PlanningAgent'),
|
||||
'ApprovalCallback': ('praisonaiagents.planning', 'ApprovalCallback'),
|
||||
'READ_ONLY_TOOLS': ('praisonaiagents.planning', 'READ_ONLY_TOOLS'),
|
||||
'RESTRICTED_TOOLS': ('praisonaiagents.planning', 'RESTRICTED_TOOLS'),
|
||||
|
||||
# Trace (protocol-driven, for custom sinks) - AGENTS.md naming: XProtocol
|
||||
'ContextTraceSinkProtocol': ('praisonaiagents.trace', 'ContextTraceSinkProtocol'),
|
||||
'ContextTraceSink': ('praisonaiagents.trace', 'ContextTraceSink'), # Backward compat alias
|
||||
'TraceSinkProtocol': ('praisonaiagents.trace', 'TraceSinkProtocol'),
|
||||
'TraceSink': ('praisonaiagents.trace', 'TraceSink'), # Backward compat alias
|
||||
'ContextTraceEmitter': ('praisonaiagents.trace', 'ContextTraceEmitter'),
|
||||
'ContextEvent': ('praisonaiagents.trace', 'ContextEvent'),
|
||||
'ContextEventType': ('praisonaiagents.trace', 'ContextEventType'),
|
||||
'trace_context': ('praisonaiagents.trace', 'trace_context'),
|
||||
'ContextListSink': ('praisonaiagents.trace', 'ContextListSink'),
|
||||
'ContextNoOpSink': ('praisonaiagents.trace', 'ContextNoOpSink'),
|
||||
|
||||
# Telemetry
|
||||
'get_telemetry': ('praisonaiagents.telemetry', 'get_telemetry'),
|
||||
'enable_telemetry': ('praisonaiagents.telemetry', 'enable_telemetry'),
|
||||
'disable_telemetry': ('praisonaiagents.telemetry', 'disable_telemetry'),
|
||||
'enable_performance_mode': ('praisonaiagents.telemetry', 'enable_performance_mode'),
|
||||
'disable_performance_mode': ('praisonaiagents.telemetry', 'disable_performance_mode'),
|
||||
'cleanup_telemetry_resources': ('praisonaiagents.telemetry', 'cleanup_telemetry_resources'),
|
||||
'MinimalTelemetry': ('praisonaiagents.telemetry', 'MinimalTelemetry'),
|
||||
'TelemetryCollector': ('praisonaiagents.telemetry', 'TelemetryCollector'),
|
||||
|
||||
# UI (optional)
|
||||
'AGUI': ('praisonaiagents.ui.agui', 'AGUI'),
|
||||
'A2A': ('praisonaiagents.ui.a2a', 'A2A'),
|
||||
|
||||
# Feature configs
|
||||
'MemoryConfig': ('praisonaiagents.config.feature_configs', 'MemoryConfig'),
|
||||
'KnowledgeConfig': ('praisonaiagents.config.feature_configs', 'KnowledgeConfig'),
|
||||
'PlanningConfig': ('praisonaiagents.config.feature_configs', 'PlanningConfig'),
|
||||
'ReflectionConfig': ('praisonaiagents.config.feature_configs', 'ReflectionConfig'),
|
||||
'GuardrailConfig': ('praisonaiagents.config.feature_configs', 'GuardrailConfig'),
|
||||
'WebConfig': ('praisonaiagents.config.feature_configs', 'WebConfig'),
|
||||
'OutputConfig': ('praisonaiagents.config.feature_configs', 'OutputConfig'),
|
||||
'ExecutionConfig': ('praisonaiagents.config.feature_configs', 'ExecutionConfig'),
|
||||
'TemplateConfig': ('praisonaiagents.config.feature_configs', 'TemplateConfig'),
|
||||
'CachingConfig': ('praisonaiagents.config.feature_configs', 'CachingConfig'),
|
||||
'HooksConfig': ('praisonaiagents.config.feature_configs', 'HooksConfig'),
|
||||
'SkillsConfig': ('praisonaiagents.config.feature_configs', 'SkillsConfig'),
|
||||
'AutonomyConfig': ('praisonaiagents.config.feature_configs', 'AutonomyConfig'),
|
||||
'MemoryBackend': ('praisonaiagents.config.feature_configs', 'MemoryBackend'),
|
||||
'ChunkingStrategy': ('praisonaiagents.config.feature_configs', 'ChunkingStrategy'),
|
||||
'GuardrailAction': ('praisonaiagents.config.feature_configs', 'GuardrailAction'),
|
||||
'WebSearchProvider': ('praisonaiagents.config.feature_configs', 'WebSearchProvider'),
|
||||
'OutputPreset': ('praisonaiagents.config.feature_configs', 'OutputPreset'),
|
||||
'ExecutionPreset': ('praisonaiagents.config.feature_configs', 'ExecutionPreset'),
|
||||
'AutonomyLevel': ('praisonaiagents.config.feature_configs', 'AutonomyLevel'),
|
||||
'MultiAgentHooksConfig': ('praisonaiagents.config.feature_configs', 'MultiAgentHooksConfig'),
|
||||
'MultiAgentOutputConfig': ('praisonaiagents.config.feature_configs', 'MultiAgentOutputConfig'),
|
||||
'MultiAgentExecutionConfig': ('praisonaiagents.config.feature_configs', 'MultiAgentExecutionConfig'),
|
||||
'MultiAgentPlanningConfig': ('praisonaiagents.config.feature_configs', 'MultiAgentPlanningConfig'),
|
||||
'MultiAgentMemoryConfig': ('praisonaiagents.config.feature_configs', 'MultiAgentMemoryConfig'),
|
||||
|
||||
# Parameter resolver
|
||||
'resolve': ('praisonaiagents.config.param_resolver', 'resolve'),
|
||||
'ArrayMode': ('praisonaiagents.config.param_resolver', 'ArrayMode'),
|
||||
'resolve_memory': ('praisonaiagents.config.param_resolver', 'resolve_memory'),
|
||||
'resolve_knowledge': ('praisonaiagents.config.param_resolver', 'resolve_knowledge'),
|
||||
'resolve_output': ('praisonaiagents.config.param_resolver', 'resolve_output'),
|
||||
'resolve_execution': ('praisonaiagents.config.param_resolver', 'resolve_execution'),
|
||||
'resolve_web': ('praisonaiagents.config.param_resolver', 'resolve_web'),
|
||||
'resolve_planning': ('praisonaiagents.config.param_resolver', 'resolve_planning'),
|
||||
'resolve_reflection': ('praisonaiagents.config.param_resolver', 'resolve_reflection'),
|
||||
'resolve_context': ('praisonaiagents.config.param_resolver', 'resolve_context'),
|
||||
'resolve_autonomy': ('praisonaiagents.config.param_resolver', 'resolve_autonomy'),
|
||||
'resolve_caching': ('praisonaiagents.config.param_resolver', 'resolve_caching'),
|
||||
'resolve_hooks': ('praisonaiagents.config.param_resolver', 'resolve_hooks'),
|
||||
'resolve_skills': ('praisonaiagents.config.param_resolver', 'resolve_skills'),
|
||||
'resolve_routing': ('praisonaiagents.config.param_resolver', 'resolve_routing'),
|
||||
'resolve_guardrails': ('praisonaiagents.config.param_resolver', 'resolve_guardrails'),
|
||||
'resolve_guardrail_policies': ('praisonaiagents.config.param_resolver', 'resolve_guardrail_policies'),
|
||||
|
||||
# Presets
|
||||
'MEMORY_PRESETS': ('praisonaiagents.config.presets', 'MEMORY_PRESETS'),
|
||||
'MEMORY_URL_SCHEMES': ('praisonaiagents.config.presets', 'MEMORY_URL_SCHEMES'),
|
||||
'OUTPUT_PRESETS': ('praisonaiagents.config.presets', 'OUTPUT_PRESETS'),
|
||||
'EXECUTION_PRESETS': ('praisonaiagents.config.presets', 'EXECUTION_PRESETS'),
|
||||
'WEB_PRESETS': ('praisonaiagents.config.presets', 'WEB_PRESETS'),
|
||||
'PLANNING_PRESETS': ('praisonaiagents.config.presets', 'PLANNING_PRESETS'),
|
||||
'REFLECTION_PRESETS': ('praisonaiagents.config.presets', 'REFLECTION_PRESETS'),
|
||||
'CONTEXT_PRESETS': ('praisonaiagents.config.presets', 'CONTEXT_PRESETS'),
|
||||
'AUTONOMY_PRESETS': ('praisonaiagents.config.presets', 'AUTONOMY_PRESETS'),
|
||||
'CACHING_PRESETS': ('praisonaiagents.config.presets', 'CACHING_PRESETS'),
|
||||
'MULTI_AGENT_OUTPUT_PRESETS': ('praisonaiagents.config.presets', 'MULTI_AGENT_OUTPUT_PRESETS'),
|
||||
'MULTI_AGENT_EXECUTION_PRESETS': ('praisonaiagents.config.presets', 'MULTI_AGENT_EXECUTION_PRESETS'),
|
||||
'GUARDRAIL_PRESETS': ('praisonaiagents.config.presets', 'GUARDRAIL_PRESETS'),
|
||||
'KNOWLEDGE_PRESETS': ('praisonaiagents.config.presets', 'KNOWLEDGE_PRESETS'),
|
||||
|
||||
# Parse utilities
|
||||
'detect_url_scheme': ('praisonaiagents.config.parse_utils', 'detect_url_scheme'),
|
||||
'is_path_like': ('praisonaiagents.config.parse_utils', 'is_path_like'),
|
||||
'suggest_similar': ('praisonaiagents.config.parse_utils', 'suggest_similar'),
|
||||
'is_policy_string': ('praisonaiagents.config.parse_utils', 'is_policy_string'),
|
||||
'parse_policy_string': ('praisonaiagents.config.parse_utils', 'parse_policy_string'),
|
||||
|
||||
# Context management
|
||||
'ContextConfig': ('praisonaiagents.context.models', 'ContextConfig'),
|
||||
'OptimizerStrategy': ('praisonaiagents.context.models', 'OptimizerStrategy'),
|
||||
'ManagerConfig': ('praisonaiagents.context.manager', 'ManagerConfig'),
|
||||
'ContextManager': ('praisonaiagents.context.manager', 'ContextManager'),
|
||||
|
||||
# db and obs modules
|
||||
'db': ('praisonaiagents.db', 'db'),
|
||||
'obs': ('praisonaiagents.obs', 'obs'),
|
||||
|
||||
# Gateway protocols and config (implementations in praisonai wrapper)
|
||||
'GatewayProtocol': ('praisonaiagents.gateway.protocols', 'GatewayProtocol'),
|
||||
'GatewaySessionProtocol': ('praisonaiagents.gateway.protocols', 'GatewaySessionProtocol'),
|
||||
'GatewayClientProtocol': ('praisonaiagents.gateway.protocols', 'GatewayClientProtocol'),
|
||||
'GatewayEvent': ('praisonaiagents.gateway.protocols', 'GatewayEvent'),
|
||||
'GatewayMessage': ('praisonaiagents.gateway.protocols', 'GatewayMessage'),
|
||||
'EventType': ('praisonaiagents.gateway.protocols', 'EventType'),
|
||||
'GatewayConfig': ('praisonaiagents.gateway.config', 'GatewayConfig'),
|
||||
'SessionConfig': ('praisonaiagents.gateway.config', 'SessionConfig'),
|
||||
|
||||
# Bot protocols and config (implementations in praisonai wrapper)
|
||||
'BotProtocol': ('praisonaiagents.bots.protocols', 'BotProtocol'),
|
||||
'BotMessage': ('praisonaiagents.bots.protocols', 'BotMessage'),
|
||||
'BotUser': ('praisonaiagents.bots.protocols', 'BotUser'),
|
||||
'BotChannel': ('praisonaiagents.bots.protocols', 'BotChannel'),
|
||||
'MessageType': ('praisonaiagents.bots.protocols', 'MessageType'),
|
||||
'BotConfig': ('praisonaiagents.bots.config', 'BotConfig'),
|
||||
|
||||
# Sandbox protocols and config (implementations in praisonai wrapper)
|
||||
'SandboxProtocol': ('praisonaiagents.sandbox.protocols', 'SandboxProtocol'),
|
||||
'SandboxResult': ('praisonaiagents.sandbox.protocols', 'SandboxResult'),
|
||||
'SandboxStatus': ('praisonaiagents.sandbox.protocols', 'SandboxStatus'),
|
||||
'ResourceLimits': ('praisonaiagents.sandbox.protocols', 'ResourceLimits'),
|
||||
'SandboxConfig': ('praisonaiagents.sandbox.config', 'SandboxConfig'),
|
||||
'SecurityPolicy': ('praisonaiagents.sandbox.config', 'SecurityPolicy'),
|
||||
|
||||
# Model failover
|
||||
'AuthProfile': ('praisonaiagents.llm.failover', 'AuthProfile'),
|
||||
'ProviderStatus': ('praisonaiagents.llm.failover', 'ProviderStatus'),
|
||||
'FailoverConfig': ('praisonaiagents.llm.failover', 'FailoverConfig'),
|
||||
'FailoverManager': ('praisonaiagents.llm.failover', 'FailoverManager'),
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
"""
|
||||
Lazy load heavy modules to avoid impacting package load time.
|
||||
def _custom_handler(name, cache):
|
||||
"""Handle special cases that need custom logic."""
|
||||
import warnings
|
||||
|
||||
This function is called when an attribute is not found in the module.
|
||||
It enables deferred loading of heavy dependencies like litellm.
|
||||
"""
|
||||
global _mcp_available, _planning_available, _telemetry_available
|
||||
global _agui_available, _a2a_available
|
||||
|
||||
# Return cached values if available
|
||||
if name in _lazy_cache:
|
||||
return _lazy_cache[name]
|
||||
|
||||
# Agent and related classes (triggers litellm import chain)
|
||||
if name == "Agent":
|
||||
from .agent.agent import Agent
|
||||
_lazy_cache[name] = Agent
|
||||
return Agent
|
||||
elif name == "ImageAgent":
|
||||
from .agent.image_agent import ImageAgent
|
||||
_lazy_cache[name] = ImageAgent
|
||||
return ImageAgent
|
||||
elif name == "VideoAgent":
|
||||
from .agent.video_agent import VideoAgent
|
||||
_lazy_cache[name] = VideoAgent
|
||||
return VideoAgent
|
||||
elif name == "VideoConfig":
|
||||
from .agent.video_agent import VideoConfig
|
||||
_lazy_cache[name] = VideoConfig
|
||||
return VideoConfig
|
||||
elif name == "AudioAgent":
|
||||
from .agent.audio_agent import AudioAgent
|
||||
_lazy_cache[name] = AudioAgent
|
||||
return AudioAgent
|
||||
elif name == "AudioConfig":
|
||||
from .agent.audio_agent import AudioConfig
|
||||
_lazy_cache[name] = AudioConfig
|
||||
return AudioConfig
|
||||
elif name == "OCRAgent":
|
||||
from .agent.ocr_agent import OCRAgent
|
||||
_lazy_cache[name] = OCRAgent
|
||||
return OCRAgent
|
||||
elif name == "OCRConfig":
|
||||
from .agent.ocr_agent import OCRConfig
|
||||
_lazy_cache[name] = OCRConfig
|
||||
return OCRConfig
|
||||
elif name == "ContextAgent":
|
||||
from .agent.context_agent import ContextAgent
|
||||
_lazy_cache[name] = ContextAgent
|
||||
return ContextAgent
|
||||
elif name == "create_context_agent":
|
||||
from .agent.context_agent import create_context_agent
|
||||
_lazy_cache[name] = create_context_agent
|
||||
return create_context_agent
|
||||
elif name == "DeepResearchAgent":
|
||||
from .agent.deep_research_agent import DeepResearchAgent
|
||||
_lazy_cache[name] = DeepResearchAgent
|
||||
return DeepResearchAgent
|
||||
elif name == "DeepResearchResponse":
|
||||
from .agent.deep_research_agent import DeepResearchResponse
|
||||
_lazy_cache[name] = DeepResearchResponse
|
||||
return DeepResearchResponse
|
||||
elif name == "Citation":
|
||||
from .agent.deep_research_agent import Citation
|
||||
_lazy_cache[name] = Citation
|
||||
return Citation
|
||||
elif name == "ReasoningStep":
|
||||
from .agent.deep_research_agent import ReasoningStep
|
||||
_lazy_cache[name] = ReasoningStep
|
||||
return ReasoningStep
|
||||
elif name == "WebSearchCall":
|
||||
from .agent.deep_research_agent import WebSearchCall
|
||||
_lazy_cache[name] = WebSearchCall
|
||||
return WebSearchCall
|
||||
elif name == "CodeExecutionStep":
|
||||
from .agent.deep_research_agent import CodeExecutionStep
|
||||
_lazy_cache[name] = CodeExecutionStep
|
||||
return CodeExecutionStep
|
||||
elif name == "MCPCall":
|
||||
from .agent.deep_research_agent import MCPCall
|
||||
_lazy_cache[name] = MCPCall
|
||||
return MCPCall
|
||||
elif name == "FileSearchCall":
|
||||
from .agent.deep_research_agent import FileSearchCall
|
||||
_lazy_cache[name] = FileSearchCall
|
||||
return FileSearchCall
|
||||
elif name == "Provider":
|
||||
from .agent.deep_research_agent import Provider
|
||||
_lazy_cache[name] = Provider
|
||||
return Provider
|
||||
elif name == "QueryRewriterAgent":
|
||||
from .agent.query_rewriter_agent import QueryRewriterAgent
|
||||
_lazy_cache[name] = QueryRewriterAgent
|
||||
return QueryRewriterAgent
|
||||
elif name == "RewriteStrategy":
|
||||
from .agent.query_rewriter_agent import RewriteStrategy
|
||||
_lazy_cache[name] = RewriteStrategy
|
||||
return RewriteStrategy
|
||||
elif name == "RewriteResult":
|
||||
from .agent.query_rewriter_agent import RewriteResult
|
||||
_lazy_cache[name] = RewriteResult
|
||||
return RewriteResult
|
||||
elif name == "PromptExpanderAgent":
|
||||
from .agent.prompt_expander_agent import PromptExpanderAgent
|
||||
_lazy_cache[name] = PromptExpanderAgent
|
||||
return PromptExpanderAgent
|
||||
elif name == "ExpandStrategy":
|
||||
from .agent.prompt_expander_agent import ExpandStrategy
|
||||
_lazy_cache[name] = ExpandStrategy
|
||||
return ExpandStrategy
|
||||
elif name == "ExpandResult":
|
||||
from .agent.prompt_expander_agent import ExpandResult
|
||||
_lazy_cache[name] = ExpandResult
|
||||
return ExpandResult
|
||||
|
||||
# Agents (canonical) and PraisonAIAgents (backward-compatible alias)
|
||||
elif name == "Agents":
|
||||
from .agents.agents import Agents
|
||||
_lazy_cache[name] = Agents
|
||||
_lazy_cache["PraisonAIAgents"] = Agents # Also cache alias
|
||||
return Agents
|
||||
elif name == "PraisonAIAgents":
|
||||
import warnings
|
||||
# Agents deprecation warning (use AgentManager instead)
|
||||
if name == "Agents":
|
||||
warnings.warn(
|
||||
"PraisonAIAgents is deprecated, use Agents instead. "
|
||||
"Example: from praisonaiagents import Agents",
|
||||
"Agents is deprecated, use AgentManager instead. "
|
||||
"Example: from praisonaiagents import AgentManager",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
stacklevel=4
|
||||
)
|
||||
from .agents.agents import Agents
|
||||
_lazy_cache["Agents"] = Agents
|
||||
_lazy_cache[name] = Agents
|
||||
return Agents
|
||||
value = lazy_import('praisonaiagents.agents.agents', 'AgentManager', cache)
|
||||
cache['AgentManager'] = value
|
||||
cache['Agents'] = value
|
||||
return value
|
||||
|
||||
# Task
|
||||
elif name == "Task":
|
||||
from .task.task import Task
|
||||
_lazy_cache[name] = Task
|
||||
return Task
|
||||
# PraisonAIAgents deprecation warning (use AgentManager instead)
|
||||
if name == "PraisonAIAgents":
|
||||
warnings.warn(
|
||||
"PraisonAIAgents is deprecated, use AgentManager instead. "
|
||||
"Example: from praisonaiagents import AgentManager",
|
||||
DeprecationWarning,
|
||||
stacklevel=4
|
||||
)
|
||||
value = lazy_import('praisonaiagents.agents.agents', 'AgentManager', cache)
|
||||
cache['AgentManager'] = value
|
||||
cache['PraisonAIAgents'] = value
|
||||
return value
|
||||
|
||||
# AutoAgents
|
||||
elif name == "AutoAgents":
|
||||
from .agents.autoagents import AutoAgents
|
||||
_lazy_cache[name] = AutoAgents
|
||||
return AutoAgents
|
||||
# WorkflowStep deprecation warning (use Task instead) - Phase 4 Consolidation
|
||||
if name == "WorkflowStep":
|
||||
warnings.warn(
|
||||
"WorkflowStep is deprecated, use Task instead. "
|
||||
"Task now supports all WorkflowStep features including action, handler, loop_over, etc. "
|
||||
"Example: from praisonaiagents import Task",
|
||||
DeprecationWarning,
|
||||
stacklevel=4
|
||||
)
|
||||
value = lazy_import('praisonaiagents.task.task', 'Task', cache)
|
||||
cache['Task'] = value
|
||||
cache['WorkflowStep'] = value
|
||||
return value
|
||||
|
||||
# AutoRagAgent (auto RAG retrieval decision)
|
||||
elif name == "AutoRagAgent":
|
||||
from .agents.auto_rag_agent import AutoRagAgent
|
||||
_lazy_cache[name] = AutoRagAgent
|
||||
return AutoRagAgent
|
||||
elif name == "AutoRagConfig":
|
||||
from .agents.auto_rag_agent import AutoRagConfig
|
||||
_lazy_cache[name] = AutoRagConfig
|
||||
return AutoRagConfig
|
||||
elif name == "RagRetrievalPolicy":
|
||||
from .agents.auto_rag_agent import RetrievalPolicy
|
||||
_lazy_cache[name] = RetrievalPolicy
|
||||
return RetrievalPolicy
|
||||
# Module imports (return the module itself)
|
||||
if name == 'memory':
|
||||
import importlib
|
||||
return importlib.import_module('.memory', 'praisonaiagents')
|
||||
if name == 'workflows':
|
||||
import importlib
|
||||
return importlib.import_module('.workflows', 'praisonaiagents')
|
||||
|
||||
# Session (imports requests lazily via session/api.py)
|
||||
elif name == "Session":
|
||||
from .session import Session
|
||||
_lazy_cache[name] = Session
|
||||
return Session
|
||||
|
||||
# MCP support (optional)
|
||||
elif name == "MCP":
|
||||
if _mcp_available is None:
|
||||
try:
|
||||
from .mcp.mcp import MCP as _MCP
|
||||
_lazy_cache[name] = _MCP
|
||||
return _MCP
|
||||
except ImportError:
|
||||
return None
|
||||
return _lazy_cache.get(name)
|
||||
|
||||
# Knowledge module (imports chromadb, mem0)
|
||||
elif name == "Knowledge":
|
||||
from praisonaiagents.knowledge.knowledge import Knowledge
|
||||
_lazy_cache[name] = Knowledge
|
||||
return Knowledge
|
||||
elif name == "Chunking":
|
||||
from praisonaiagents.knowledge.chunking import Chunking
|
||||
_lazy_cache[name] = Chunking
|
||||
return Chunking
|
||||
|
||||
# FastContext support
|
||||
elif name == "FastContext":
|
||||
from praisonaiagents.context.fast import FastContext
|
||||
_lazy_cache[name] = FastContext
|
||||
return FastContext
|
||||
elif name == "FastContextResult":
|
||||
from praisonaiagents.context.fast import FastContextResult
|
||||
_lazy_cache[name] = FastContextResult
|
||||
return FastContextResult
|
||||
elif name == "FileMatch":
|
||||
from praisonaiagents.context.fast import FileMatch
|
||||
_lazy_cache[name] = FileMatch
|
||||
return FileMatch
|
||||
elif name == "LineRange":
|
||||
from praisonaiagents.context.fast import LineRange
|
||||
_lazy_cache[name] = LineRange
|
||||
return LineRange
|
||||
|
||||
# Retrieval configuration (Agent-first unified config)
|
||||
elif name == "RetrievalConfig":
|
||||
from praisonaiagents.rag.retrieval_config import RetrievalConfig
|
||||
_lazy_cache[name] = RetrievalConfig
|
||||
return RetrievalConfig
|
||||
elif name == "RetrievalPolicy":
|
||||
from praisonaiagents.rag.retrieval_config import RetrievalPolicy
|
||||
_lazy_cache[name] = RetrievalPolicy
|
||||
return RetrievalPolicy
|
||||
elif name == "CitationsMode":
|
||||
from praisonaiagents.rag.retrieval_config import CitationsMode
|
||||
_lazy_cache[name] = CitationsMode
|
||||
return CitationsMode
|
||||
|
||||
# RAG models (used by Agent.query() and Agent.retrieve())
|
||||
elif name == "ContextPack":
|
||||
from praisonaiagents.rag.models import ContextPack
|
||||
_lazy_cache[name] = ContextPack
|
||||
return ContextPack
|
||||
elif name == "RAGResult":
|
||||
from praisonaiagents.rag import RAGResult
|
||||
_lazy_cache[name] = RAGResult
|
||||
return RAGResult
|
||||
elif name == "Citation":
|
||||
from praisonaiagents.rag import Citation
|
||||
_lazy_cache[name] = Citation
|
||||
return Citation
|
||||
|
||||
# RAG internals (advanced use - prefer Agent.query() for normal usage)
|
||||
elif name == "RAG":
|
||||
from praisonaiagents.rag import RAG
|
||||
_lazy_cache[name] = RAG
|
||||
return RAG
|
||||
elif name == "RAGConfig":
|
||||
from praisonaiagents.rag import RAGConfig
|
||||
_lazy_cache[name] = RAGConfig
|
||||
return RAGConfig
|
||||
elif name == "RAGCitation":
|
||||
from praisonaiagents.rag import Citation
|
||||
_lazy_cache[name] = Citation
|
||||
return Citation
|
||||
|
||||
# Agent Skills support (lazy loaded for zero performance impact)
|
||||
elif name == "SkillManager":
|
||||
from praisonaiagents.skills import SkillManager
|
||||
_lazy_cache[name] = SkillManager
|
||||
return SkillManager
|
||||
elif name == "SkillProperties":
|
||||
from praisonaiagents.skills import SkillProperties
|
||||
_lazy_cache[name] = SkillProperties
|
||||
return SkillProperties
|
||||
elif name == "SkillMetadata":
|
||||
from praisonaiagents.skills import SkillMetadata
|
||||
_lazy_cache[name] = SkillMetadata
|
||||
return SkillMetadata
|
||||
elif name == "SkillLoader":
|
||||
from praisonaiagents.skills import SkillLoader
|
||||
_lazy_cache[name] = SkillLoader
|
||||
return SkillLoader
|
||||
|
||||
# Memory module (imports chromadb)
|
||||
elif name == "Memory":
|
||||
from praisonaiagents.memory.memory import Memory
|
||||
_lazy_cache[name] = Memory
|
||||
return Memory
|
||||
|
||||
# Embedding module (unified embedding API)
|
||||
# Note: Must import from .embed submodule to get functions, not the module
|
||||
elif name == "embedding":
|
||||
from praisonaiagents.embedding.embed import embedding
|
||||
_lazy_cache[name] = embedding
|
||||
return embedding
|
||||
elif name == "aembedding":
|
||||
from praisonaiagents.embedding.embed import aembedding
|
||||
_lazy_cache[name] = aembedding
|
||||
return aembedding
|
||||
elif name == "embed":
|
||||
from praisonaiagents.embedding.embed import embed
|
||||
_lazy_cache[name] = embed
|
||||
return embed
|
||||
elif name == "aembed":
|
||||
from praisonaiagents.embedding.embed import aembed
|
||||
_lazy_cache[name] = aembed
|
||||
return aembed
|
||||
elif name == "EmbeddingResult":
|
||||
from praisonaiagents.embedding.result import EmbeddingResult
|
||||
_lazy_cache[name] = EmbeddingResult
|
||||
return EmbeddingResult
|
||||
elif name == "get_dimensions":
|
||||
from praisonaiagents.embedding.dimensions import get_dimensions
|
||||
_lazy_cache[name] = get_dimensions
|
||||
return get_dimensions
|
||||
|
||||
# Planning mode support (lazy)
|
||||
elif name in ("Plan", "PlanStep", "TodoList", "TodoItem", "PlanStorage",
|
||||
"PlanningAgent", "ApprovalCallback", "READ_ONLY_TOOLS",
|
||||
"RESTRICTED_TOOLS"):
|
||||
try:
|
||||
from . import planning as _planning
|
||||
result = getattr(_planning, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except ImportError:
|
||||
if name in ("READ_ONLY_TOOLS", "RESTRICTED_TOOLS"):
|
||||
return []
|
||||
return None
|
||||
|
||||
# Telemetry support (lazy loaded)
|
||||
elif name in ("get_telemetry", "enable_telemetry", "disable_telemetry",
|
||||
"enable_performance_mode", "disable_performance_mode",
|
||||
"cleanup_telemetry_resources", "MinimalTelemetry", "TelemetryCollector"):
|
||||
try:
|
||||
from . import telemetry as _telemetry
|
||||
result = getattr(_telemetry, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except ImportError:
|
||||
# Provide stub functions
|
||||
if name == "get_telemetry":
|
||||
return lambda: None
|
||||
elif name == "enable_telemetry":
|
||||
import logging
|
||||
def _stub(*args, **kwargs):
|
||||
logging.warning(
|
||||
"Telemetry not available. Install with: pip install praisonaiagents[telemetry]"
|
||||
)
|
||||
return None
|
||||
return _stub
|
||||
elif name in ("disable_telemetry", "enable_performance_mode",
|
||||
"disable_performance_mode", "cleanup_telemetry_resources"):
|
||||
return lambda: None
|
||||
return None
|
||||
|
||||
# AG-UI support (optional, lazy loaded)
|
||||
elif name == "AGUI":
|
||||
try:
|
||||
from praisonaiagents.ui.agui import AGUI
|
||||
_lazy_cache[name] = AGUI
|
||||
return AGUI
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
# A2A support (optional, lazy loaded)
|
||||
elif name == "A2A":
|
||||
try:
|
||||
from praisonaiagents.ui.a2a import A2A
|
||||
_lazy_cache[name] = A2A
|
||||
return A2A
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
# Feature Config classes (agent-centric API)
|
||||
elif name in ("MemoryConfig", "KnowledgeConfig", "PlanningConfig",
|
||||
"ReflectionConfig", "GuardrailConfig", "WebConfig",
|
||||
"OutputConfig", "ExecutionConfig", "TemplateConfig",
|
||||
"CachingConfig", "HooksConfig", "SkillsConfig", "AutonomyConfig",
|
||||
"MemoryBackend", "ChunkingStrategy", "GuardrailAction",
|
||||
"WebSearchProvider", "OutputPreset", "ExecutionPreset", "AutonomyLevel",
|
||||
# Multi-Agent config classes
|
||||
"MultiAgentHooksConfig", "MultiAgentOutputConfig",
|
||||
"MultiAgentExecutionConfig", "MultiAgentPlanningConfig",
|
||||
"MultiAgentMemoryConfig"):
|
||||
from .config import feature_configs
|
||||
result = getattr(feature_configs, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
|
||||
# Unified parameter resolver (agent-centric API)
|
||||
elif name in ("resolve", "ArrayMode", "resolve_memory", "resolve_knowledge",
|
||||
"resolve_output", "resolve_execution", "resolve_web",
|
||||
"resolve_planning", "resolve_reflection", "resolve_context",
|
||||
"resolve_autonomy", "resolve_caching", "resolve_hooks",
|
||||
"resolve_skills", "resolve_routing", "resolve_guardrails",
|
||||
"resolve_guardrail_policies"):
|
||||
from .config import param_resolver
|
||||
result = getattr(param_resolver, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
|
||||
# Preset registries (agent-centric API)
|
||||
elif name in ("MEMORY_PRESETS", "MEMORY_URL_SCHEMES", "OUTPUT_PRESETS",
|
||||
"EXECUTION_PRESETS", "WEB_PRESETS", "PLANNING_PRESETS",
|
||||
"REFLECTION_PRESETS", "CONTEXT_PRESETS", "AUTONOMY_PRESETS",
|
||||
"CACHING_PRESETS", "MULTI_AGENT_OUTPUT_PRESETS",
|
||||
"MULTI_AGENT_EXECUTION_PRESETS", "GUARDRAIL_PRESETS",
|
||||
"KNOWLEDGE_PRESETS"):
|
||||
from .config import presets
|
||||
result = getattr(presets, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
|
||||
# Parse utilities (agent-centric API)
|
||||
elif name in ("detect_url_scheme", "is_path_like", "suggest_similar",
|
||||
"is_policy_string", "parse_policy_string"):
|
||||
from .config import parse_utils
|
||||
result = getattr(parse_utils, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
|
||||
# Context management config
|
||||
elif name == "ContextConfig":
|
||||
from .context.models import ContextConfig
|
||||
_lazy_cache[name] = ContextConfig
|
||||
return ContextConfig
|
||||
elif name == "OptimizerStrategy":
|
||||
from .context.models import OptimizerStrategy
|
||||
_lazy_cache[name] = OptimizerStrategy
|
||||
return OptimizerStrategy
|
||||
elif name == "ManagerConfig":
|
||||
from .context.manager import ManagerConfig
|
||||
_lazy_cache[name] = ManagerConfig
|
||||
return ManagerConfig
|
||||
elif name == "ContextManager":
|
||||
from .context.manager import ContextManager
|
||||
_lazy_cache[name] = ContextManager
|
||||
return ContextManager
|
||||
|
||||
# =========================================================================
|
||||
# Sub-package fallback: delegate to sub-packages for additional symbols
|
||||
# This enables: from praisonaiagents import duckduckgo, FileMemory, etc.
|
||||
# =========================================================================
|
||||
|
||||
# Try tools sub-package (110+ tool functions)
|
||||
try:
|
||||
from . import tools as _tools_module
|
||||
if hasattr(_tools_module, name):
|
||||
result = getattr(_tools_module, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
# Try memory sub-package (FileMemory, RulesManager, etc.)
|
||||
try:
|
||||
from . import memory as _memory_module
|
||||
if hasattr(_memory_module, name):
|
||||
result = getattr(_memory_module, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
# Try config sub-package (already handled above, but catch any stragglers)
|
||||
try:
|
||||
from . import config as _config_module
|
||||
if hasattr(_config_module, name):
|
||||
result = getattr(_config_module, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
# Try workflows sub-package (Pipeline, Route, etc. - already imported, catch aliases)
|
||||
try:
|
||||
from . import workflows as _workflows_module
|
||||
if hasattr(_workflows_module, name):
|
||||
result = getattr(_workflows_module, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
raise AttributeError(f"Not handled by custom_handler: {name}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SUBPACKAGE FUNCTION OVERRIDES
|
||||
# ============================================================================
|
||||
# Some subpackage names conflict with function names we want to export.
|
||||
# Override them here to return the function instead of the module.
|
||||
# ============================================================================
|
||||
|
||||
# Override 'embedding' to return the function, not the subpackage
|
||||
from .embedding.embed import embedding as _embedding_func
|
||||
embedding = _embedding_func
|
||||
|
||||
# Also provide embeddings alias
|
||||
embeddings = _embedding_func
|
||||
|
||||
|
||||
# Create the __getattr__ function using centralized utility
|
||||
__getattr__ = create_lazy_getattr_with_fallback(
|
||||
mapping=_LAZY_IMPORTS,
|
||||
module_name=__name__,
|
||||
cache=_lazy_cache,
|
||||
fallback_modules=['tools', 'memory', 'config', 'workflows'],
|
||||
custom_handler=_custom_handler
|
||||
)
|
||||
|
||||
|
||||
# Initialize telemetry only if explicitly enabled via config
|
||||
@@ -698,7 +599,8 @@ def warmup(include_litellm: bool = False, include_openai: bool = True) -> dict:
|
||||
__all__ = [
|
||||
# Core classes - the essentials
|
||||
'Agent',
|
||||
'Agents',
|
||||
'AgentManager', # Primary class for multi-agent coordination (v0.14.16+)
|
||||
'Agents', # Deprecated alias for AgentManager
|
||||
'Task',
|
||||
|
||||
# Tool essentials
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
"""
|
||||
Centralized lazy loading utility for zero-overhead imports.
|
||||
|
||||
This module provides DRY utilities for lazy loading across the praisonaiagents package.
|
||||
All heavy dependencies should use these utilities to ensure consistent behavior and
|
||||
thread-safe caching.
|
||||
|
||||
Usage:
|
||||
# Simple lazy import
|
||||
from praisonaiagents._lazy import lazy_import
|
||||
HookEvent = lazy_import('praisonaiagents.hooks.types', 'HookEvent')
|
||||
|
||||
# Create a __getattr__ function for a module
|
||||
from praisonaiagents._lazy import create_lazy_getattr
|
||||
|
||||
_LAZY_IMPORTS = {
|
||||
'HookEvent': ('praisonaiagents.hooks.types', 'HookEvent'),
|
||||
'HookResult': ('praisonaiagents.hooks.types', 'HookResult'),
|
||||
}
|
||||
|
||||
__getattr__ = create_lazy_getattr(_LAZY_IMPORTS, __name__)
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import threading
|
||||
from typing import Any, Callable, Dict, Optional, Tuple
|
||||
|
||||
# Global cache lock for thread-safe access
|
||||
_cache_lock = threading.Lock()
|
||||
|
||||
# Global module cache
|
||||
_module_cache: Dict[str, Any] = {}
|
||||
|
||||
|
||||
def lazy_import(
|
||||
module_path: str,
|
||||
attr_name: str,
|
||||
cache: Optional[Dict[str, Any]] = None
|
||||
) -> Any:
|
||||
"""
|
||||
Lazily import an attribute from a module with thread-safe caching.
|
||||
|
||||
Args:
|
||||
module_path: Full module path (e.g., 'praisonaiagents.hooks.types')
|
||||
attr_name: Attribute name to import (e.g., 'HookEvent')
|
||||
cache: Optional cache dict (uses global cache if None)
|
||||
|
||||
Returns:
|
||||
The imported attribute
|
||||
|
||||
Raises:
|
||||
ModuleNotFoundError: If the module doesn't exist
|
||||
AttributeError: If the attribute doesn't exist in the module
|
||||
|
||||
Example:
|
||||
HookEvent = lazy_import('praisonaiagents.hooks.types', 'HookEvent')
|
||||
"""
|
||||
cache = cache if cache is not None else _module_cache
|
||||
cache_key = f"{module_path}.{attr_name}"
|
||||
|
||||
# Fast path: check cache without lock
|
||||
if cache_key in cache:
|
||||
return cache[cache_key]
|
||||
|
||||
# Slow path: acquire lock and import
|
||||
with _cache_lock:
|
||||
# Double-check after acquiring lock
|
||||
if cache_key in cache:
|
||||
return cache[cache_key]
|
||||
|
||||
module = importlib.import_module(module_path)
|
||||
value = getattr(module, attr_name)
|
||||
cache[cache_key] = value
|
||||
return value
|
||||
|
||||
|
||||
class LazyModule:
|
||||
"""
|
||||
A lazy module wrapper that defers import until first attribute access.
|
||||
|
||||
Useful for creating module-level lazy imports that behave like the real module.
|
||||
|
||||
Example:
|
||||
_hooks_types = LazyModule('praisonaiagents.hooks.types')
|
||||
# Module not loaded yet
|
||||
|
||||
HookEvent = _hooks_types.HookEvent
|
||||
# Now module is loaded and cached
|
||||
"""
|
||||
|
||||
__slots__ = ('_module_path', '_module', '_lock')
|
||||
|
||||
def __init__(self, module_path: str):
|
||||
object.__setattr__(self, '_module_path', module_path)
|
||||
object.__setattr__(self, '_module', None)
|
||||
object.__setattr__(self, '_lock', threading.Lock())
|
||||
|
||||
def _load_module(self) -> Any:
|
||||
"""Load the module if not already loaded."""
|
||||
if self._module is None:
|
||||
with self._lock:
|
||||
if self._module is None:
|
||||
module = importlib.import_module(self._module_path)
|
||||
object.__setattr__(self, '_module', module)
|
||||
return self._module
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
module = self._load_module()
|
||||
return getattr(module, name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
loaded = "loaded" if self._module is not None else "not loaded"
|
||||
return f"<LazyModule '{self._module_path}' ({loaded})>"
|
||||
|
||||
|
||||
def create_lazy_getattr(
|
||||
mapping: Dict[str, Tuple[str, str]],
|
||||
module_name: str = 'unknown',
|
||||
cache: Optional[Dict[str, Any]] = None
|
||||
) -> Callable[[str], Any]:
|
||||
"""
|
||||
Create a __getattr__ function for lazy loading module attributes.
|
||||
|
||||
This is the DRY way to implement lazy loading in __init__.py files.
|
||||
|
||||
Args:
|
||||
mapping: Dict mapping attribute names to (module_path, attr_name) tuples
|
||||
module_name: Name of the module (for error messages)
|
||||
cache: Optional cache dict (creates new one if None)
|
||||
|
||||
Returns:
|
||||
A __getattr__ function suitable for use in a module
|
||||
|
||||
Example:
|
||||
# In __init__.py
|
||||
from praisonaiagents._lazy import create_lazy_getattr
|
||||
|
||||
_LAZY_IMPORTS = {
|
||||
'HookEvent': ('praisonaiagents.hooks.types', 'HookEvent'),
|
||||
'HookResult': ('praisonaiagents.hooks.types', 'HookResult'),
|
||||
}
|
||||
|
||||
__getattr__ = create_lazy_getattr(_LAZY_IMPORTS, __name__)
|
||||
"""
|
||||
_cache = cache if cache is not None else {}
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
# Check cache first
|
||||
if name in _cache:
|
||||
return _cache[name]
|
||||
|
||||
# Check if it's in our mapping
|
||||
if name in mapping:
|
||||
module_path, attr_name = mapping[name]
|
||||
value = lazy_import(module_path, attr_name, _cache)
|
||||
_cache[name] = value
|
||||
return value
|
||||
|
||||
raise AttributeError(f"module {module_name!r} has no attribute {name!r}")
|
||||
|
||||
return __getattr__
|
||||
|
||||
|
||||
def create_lazy_getattr_with_fallback(
|
||||
mapping: Dict[str, Tuple[str, str]],
|
||||
module_name: str = 'unknown',
|
||||
cache: Optional[Dict[str, Any]] = None,
|
||||
fallback_modules: Optional[list] = None,
|
||||
custom_handler: Optional[Callable[[str, Dict[str, Any]], Any]] = None
|
||||
) -> Callable[[str], Any]:
|
||||
"""
|
||||
Create a __getattr__ function with fallback to sub-packages.
|
||||
|
||||
This is useful for main __init__.py files that need to:
|
||||
1. Check a mapping of known lazy imports
|
||||
2. Fall back to sub-packages for additional symbols
|
||||
3. Handle custom logic for optional modules
|
||||
|
||||
Args:
|
||||
mapping: Dict mapping attribute names to (module_path, attr_name) tuples
|
||||
module_name: Name of the module (for error messages)
|
||||
cache: Optional cache dict (creates new one if None)
|
||||
fallback_modules: List of sub-package names to try if not in mapping
|
||||
custom_handler: Optional function(name, cache) for custom logic before fallback
|
||||
|
||||
Returns:
|
||||
A __getattr__ function suitable for use in a module
|
||||
"""
|
||||
_cache = cache if cache is not None else {}
|
||||
_fallback_modules = fallback_modules or []
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
# Check cache first
|
||||
if name in _cache:
|
||||
return _cache[name]
|
||||
|
||||
# Check if it's in our mapping
|
||||
if name in mapping:
|
||||
module_path, attr_name = mapping[name]
|
||||
try:
|
||||
value = lazy_import(module_path, attr_name, _cache)
|
||||
_cache[name] = value
|
||||
return value
|
||||
except (ImportError, AttributeError):
|
||||
# Optional module not available
|
||||
_cache[name] = None
|
||||
return None
|
||||
|
||||
# Try custom handler if provided
|
||||
if custom_handler is not None:
|
||||
try:
|
||||
result = custom_handler(name, _cache)
|
||||
if result is not None:
|
||||
_cache[name] = result
|
||||
return result
|
||||
except AttributeError:
|
||||
pass # Continue to fallback
|
||||
|
||||
# Try fallback modules
|
||||
for subpkg in _fallback_modules:
|
||||
try:
|
||||
submodule = importlib.import_module(f'.{subpkg}', module_name)
|
||||
if hasattr(submodule, name):
|
||||
result = getattr(submodule, name)
|
||||
_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
continue
|
||||
|
||||
raise AttributeError(f"module {module_name!r} has no attribute {name!r}")
|
||||
|
||||
return __getattr__
|
||||
|
||||
|
||||
def create_lazy_getattr_with_groups(
|
||||
groups: Dict[str, Dict[str, Tuple[str, str]]],
|
||||
module_name: str = 'unknown',
|
||||
cache: Optional[Dict[str, Any]] = None
|
||||
) -> Callable[[str], Any]:
|
||||
"""
|
||||
Create a __getattr__ function with grouped lazy imports.
|
||||
|
||||
When one attribute from a group is accessed, all attributes in that group
|
||||
are imported together. This is useful when multiple attributes come from
|
||||
the same module and are often used together.
|
||||
|
||||
Args:
|
||||
groups: Dict mapping group names to dicts of (attr_name -> (module_path, attr_name))
|
||||
module_name: Name of the module (for error messages)
|
||||
cache: Optional cache dict (creates new one if None)
|
||||
|
||||
Returns:
|
||||
A __getattr__ function suitable for use in a module
|
||||
|
||||
Example:
|
||||
_LAZY_GROUPS = {
|
||||
'types': {
|
||||
'HookEvent': ('praisonaiagents.hooks.types', 'HookEvent'),
|
||||
'HookResult': ('praisonaiagents.hooks.types', 'HookResult'),
|
||||
},
|
||||
'events': {
|
||||
'BeforeToolInput': ('praisonaiagents.hooks.events', 'BeforeToolInput'),
|
||||
'AfterToolInput': ('praisonaiagents.hooks.events', 'AfterToolInput'),
|
||||
},
|
||||
}
|
||||
|
||||
__getattr__ = create_lazy_getattr_with_groups(_LAZY_GROUPS, __name__)
|
||||
"""
|
||||
_cache = cache if cache is not None else {}
|
||||
|
||||
# Build reverse mapping: attr_name -> group_name
|
||||
_attr_to_group: Dict[str, str] = {}
|
||||
for group_name, attrs in groups.items():
|
||||
for attr_name in attrs:
|
||||
_attr_to_group[attr_name] = group_name
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
# Check cache first
|
||||
if name in _cache:
|
||||
return _cache[name]
|
||||
|
||||
# Check if it's in our groups
|
||||
if name in _attr_to_group:
|
||||
group_name = _attr_to_group[name]
|
||||
group = groups[group_name]
|
||||
|
||||
# Import all attributes in the group
|
||||
for attr_name, (module_path, real_attr_name) in group.items():
|
||||
if attr_name not in _cache:
|
||||
value = lazy_import(module_path, real_attr_name)
|
||||
_cache[attr_name] = value
|
||||
|
||||
return _cache[name]
|
||||
|
||||
raise AttributeError(f"module {module_name!r} has no attribute {name!r}")
|
||||
|
||||
return __getattr__
|
||||
@@ -122,16 +122,26 @@ _configure_litellm = configure_litellm
|
||||
# ROOT LOGGER CONFIGURATION
|
||||
# ========================================================================
|
||||
def configure_root_logger():
|
||||
"""Configure the root logger with RichHandler."""
|
||||
from rich.logging import RichHandler
|
||||
"""Configure the root logger with optional RichHandler.
|
||||
|
||||
Uses standard StreamHandler by default for performance.
|
||||
RichHandler is only used when LOGLEVEL=DEBUG for better debugging experience.
|
||||
"""
|
||||
loglevel = os.environ.get('LOGLEVEL', 'WARNING').upper()
|
||||
|
||||
# Only use RichHandler for DEBUG level to avoid importing rich at startup
|
||||
# This significantly improves import time for silent/normal operation
|
||||
if loglevel == 'DEBUG':
|
||||
from rich.logging import RichHandler
|
||||
handlers = [RichHandler(rich_tracebacks=True)]
|
||||
else:
|
||||
handlers = [logging.StreamHandler()]
|
||||
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, loglevel, logging.WARNING),
|
||||
format="%(asctime)s %(filename)s:%(lineno)d %(levelname)s %(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(rich_tracebacks=True)],
|
||||
handlers=handlers,
|
||||
force=True
|
||||
)
|
||||
|
||||
|
||||
@@ -1,46 +1,147 @@
|
||||
"""Agent module for AI agents"""
|
||||
from .agent import Agent
|
||||
from .image_agent import ImageAgent
|
||||
from .video_agent import VideoAgent, VideoConfig
|
||||
from .audio_agent import AudioAgent, AudioConfig
|
||||
from .ocr_agent import OCRAgent, OCRConfig
|
||||
from .context_agent import ContextAgent, create_context_agent
|
||||
from .handoff import (
|
||||
Handoff, handoff, handoff_filters,
|
||||
RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions,
|
||||
HandoffConfig, HandoffResult, HandoffInputData,
|
||||
ContextPolicy, HandoffError, HandoffCycleError, HandoffDepthError, HandoffTimeoutError,
|
||||
)
|
||||
from .router_agent import RouterAgent
|
||||
from .deep_research_agent import (
|
||||
DeepResearchAgent,
|
||||
DeepResearchResponse,
|
||||
Citation,
|
||||
ReasoningStep,
|
||||
WebSearchCall,
|
||||
CodeExecutionStep,
|
||||
MCPCall,
|
||||
FileSearchCall,
|
||||
Provider
|
||||
)
|
||||
from .query_rewriter_agent import (
|
||||
QueryRewriterAgent,
|
||||
RewriteStrategy,
|
||||
RewriteResult
|
||||
)
|
||||
from .prompt_expander_agent import (
|
||||
PromptExpanderAgent,
|
||||
ExpandStrategy,
|
||||
ExpandResult
|
||||
)
|
||||
from .protocols import (
|
||||
AgentProtocol,
|
||||
RunnableAgentProtocol,
|
||||
ToolAwareAgentProtocol,
|
||||
MemoryAwareAgentProtocol,
|
||||
FullAgentProtocol,
|
||||
ContextEngineerProtocol,
|
||||
)
|
||||
"""Agent module for AI agents - uses lazy loading for performance"""
|
||||
|
||||
# Lazy loading cache
|
||||
_lazy_cache = {}
|
||||
|
||||
def __getattr__(name):
|
||||
"""Lazy load agent classes to avoid importing rich at startup."""
|
||||
if name in _lazy_cache:
|
||||
return _lazy_cache[name]
|
||||
|
||||
# Core Agent - always needed
|
||||
if name == 'Agent':
|
||||
from .agent import Agent
|
||||
_lazy_cache[name] = Agent
|
||||
return Agent
|
||||
|
||||
# Specialized agents - lazy loaded (import rich)
|
||||
if name == 'ImageAgent':
|
||||
from .image_agent import ImageAgent
|
||||
_lazy_cache[name] = ImageAgent
|
||||
return ImageAgent
|
||||
elif name == 'VideoAgent':
|
||||
from .video_agent import VideoAgent
|
||||
_lazy_cache[name] = VideoAgent
|
||||
return VideoAgent
|
||||
elif name == 'VideoConfig':
|
||||
from .video_agent import VideoConfig
|
||||
_lazy_cache[name] = VideoConfig
|
||||
return VideoConfig
|
||||
elif name == 'AudioAgent':
|
||||
from .audio_agent import AudioAgent
|
||||
_lazy_cache[name] = AudioAgent
|
||||
return AudioAgent
|
||||
elif name == 'AudioConfig':
|
||||
from .audio_agent import AudioConfig
|
||||
_lazy_cache[name] = AudioConfig
|
||||
return AudioConfig
|
||||
elif name == 'OCRAgent':
|
||||
from .ocr_agent import OCRAgent
|
||||
_lazy_cache[name] = OCRAgent
|
||||
return OCRAgent
|
||||
elif name == 'OCRConfig':
|
||||
from .ocr_agent import OCRConfig
|
||||
_lazy_cache[name] = OCRConfig
|
||||
return OCRConfig
|
||||
elif name == 'ContextAgent':
|
||||
from .context_agent import ContextAgent
|
||||
_lazy_cache[name] = ContextAgent
|
||||
return ContextAgent
|
||||
elif name == 'create_context_agent':
|
||||
from .context_agent import create_context_agent
|
||||
_lazy_cache[name] = create_context_agent
|
||||
return create_context_agent
|
||||
elif name == 'RouterAgent':
|
||||
from .router_agent import RouterAgent
|
||||
_lazy_cache[name] = RouterAgent
|
||||
return RouterAgent
|
||||
elif name == 'VisionAgent':
|
||||
from .vision_agent import VisionAgent
|
||||
_lazy_cache[name] = VisionAgent
|
||||
return VisionAgent
|
||||
elif name == 'VisionConfig':
|
||||
from .vision_agent import VisionConfig
|
||||
_lazy_cache[name] = VisionConfig
|
||||
return VisionConfig
|
||||
elif name == 'EmbeddingAgent':
|
||||
from .embedding_agent import EmbeddingAgent
|
||||
_lazy_cache[name] = EmbeddingAgent
|
||||
return EmbeddingAgent
|
||||
elif name == 'EmbeddingConfig':
|
||||
from .embedding_agent import EmbeddingConfig
|
||||
_lazy_cache[name] = EmbeddingConfig
|
||||
return EmbeddingConfig
|
||||
elif name == 'RealtimeAgent':
|
||||
from .realtime_agent import RealtimeAgent
|
||||
_lazy_cache[name] = RealtimeAgent
|
||||
return RealtimeAgent
|
||||
elif name == 'RealtimeConfig':
|
||||
from .realtime_agent import RealtimeConfig
|
||||
_lazy_cache[name] = RealtimeConfig
|
||||
return RealtimeConfig
|
||||
elif name == 'CodeAgent':
|
||||
from .code_agent import CodeAgent
|
||||
_lazy_cache[name] = CodeAgent
|
||||
return CodeAgent
|
||||
elif name == 'CodeConfig':
|
||||
from .code_agent import CodeConfig
|
||||
_lazy_cache[name] = CodeConfig
|
||||
return CodeConfig
|
||||
|
||||
# Handoff - lightweight
|
||||
_handoff_names = {
|
||||
'Handoff', 'handoff', 'handoff_filters',
|
||||
'RECOMMENDED_PROMPT_PREFIX', 'prompt_with_handoff_instructions',
|
||||
'HandoffConfig', 'HandoffResult', 'HandoffInputData',
|
||||
'ContextPolicy', 'HandoffError', 'HandoffCycleError',
|
||||
'HandoffDepthError', 'HandoffTimeoutError'
|
||||
}
|
||||
if name in _handoff_names:
|
||||
from . import handoff as _handoff_module
|
||||
value = getattr(_handoff_module, name)
|
||||
_lazy_cache[name] = value
|
||||
return value
|
||||
|
||||
# Deep research agent
|
||||
_deep_research_names = {
|
||||
'DeepResearchAgent', 'DeepResearchResponse', 'Citation',
|
||||
'ReasoningStep', 'WebSearchCall', 'CodeExecutionStep',
|
||||
'MCPCall', 'FileSearchCall', 'Provider'
|
||||
}
|
||||
if name in _deep_research_names:
|
||||
from . import deep_research_agent as _dr_module
|
||||
value = getattr(_dr_module, name)
|
||||
_lazy_cache[name] = value
|
||||
return value
|
||||
|
||||
# Query rewriter agent
|
||||
_query_rewriter_names = {'QueryRewriterAgent', 'RewriteStrategy', 'RewriteResult'}
|
||||
if name in _query_rewriter_names:
|
||||
from . import query_rewriter_agent as _qr_module
|
||||
value = getattr(_qr_module, name)
|
||||
_lazy_cache[name] = value
|
||||
return value
|
||||
|
||||
# Prompt expander agent
|
||||
_prompt_expander_names = {'PromptExpanderAgent', 'ExpandStrategy', 'ExpandResult'}
|
||||
if name in _prompt_expander_names:
|
||||
from . import prompt_expander_agent as _pe_module
|
||||
value = getattr(_pe_module, name)
|
||||
_lazy_cache[name] = value
|
||||
return value
|
||||
|
||||
# Protocols - lightweight
|
||||
_protocol_names = {
|
||||
'AgentProtocol', 'RunnableAgentProtocol', 'ToolAwareAgentProtocol',
|
||||
'MemoryAwareAgentProtocol', 'FullAgentProtocol', 'ContextEngineerProtocol'
|
||||
}
|
||||
if name in _protocol_names:
|
||||
from . import protocols as _protocols_module
|
||||
value = getattr(_protocols_module, name)
|
||||
_lazy_cache[name] = value
|
||||
return value
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
__all__ = [
|
||||
'Agent',
|
||||
@@ -51,6 +152,14 @@ __all__ = [
|
||||
'AudioConfig',
|
||||
'OCRAgent',
|
||||
'OCRConfig',
|
||||
'VisionAgent',
|
||||
'VisionConfig',
|
||||
'EmbeddingAgent',
|
||||
'EmbeddingConfig',
|
||||
'RealtimeAgent',
|
||||
'RealtimeConfig',
|
||||
'CodeAgent',
|
||||
'CodeConfig',
|
||||
'ContextAgent',
|
||||
'create_context_agent',
|
||||
'Handoff',
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -53,12 +53,16 @@ class AutonomyConfig:
|
||||
doom_loop_threshold: Number of repeated actions to trigger doom loop
|
||||
auto_escalate: Whether to automatically escalate complexity
|
||||
observe: Whether to emit observability events
|
||||
completion_promise: Optional string that signals completion when wrapped in <promise>TEXT</promise>
|
||||
clear_context: Whether to clear chat history between iterations
|
||||
"""
|
||||
enabled: bool = True
|
||||
max_iterations: int = 20
|
||||
doom_loop_threshold: int = 3
|
||||
auto_escalate: bool = True
|
||||
observe: bool = False
|
||||
completion_promise: Optional[str] = None
|
||||
clear_context: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "AutonomyConfig":
|
||||
@@ -69,6 +73,8 @@ class AutonomyConfig:
|
||||
doom_loop_threshold=data.get("doom_loop_threshold", 3),
|
||||
auto_escalate=data.get("auto_escalate", True),
|
||||
observe=data.get("observe", False),
|
||||
completion_promise=data.get("completion_promise"),
|
||||
clear_context=data.get("clear_context", False),
|
||||
)
|
||||
|
||||
|
||||
@@ -209,21 +215,23 @@ class AutonomyResult:
|
||||
Attributes:
|
||||
success: Whether the task completed successfully
|
||||
output: Final output/response
|
||||
completion_reason: Why execution stopped (goal, timeout, max_iterations, doom_loop, error)
|
||||
completion_reason: Why execution stopped (goal, timeout, max_iterations, doom_loop, error, promise)
|
||||
iterations: Number of iterations executed
|
||||
stage: Final execution stage
|
||||
actions: List of actions taken
|
||||
duration_seconds: Total execution time
|
||||
error: Error message if failed
|
||||
started_at: ISO 8601 timestamp when execution started
|
||||
"""
|
||||
success: bool
|
||||
output: str = ""
|
||||
completion_reason: str = "goal" # goal, timeout, max_iterations, doom_loop, error
|
||||
completion_reason: str = "goal" # goal, timeout, max_iterations, doom_loop, error, promise
|
||||
iterations: int = 0
|
||||
stage: str = "direct"
|
||||
actions: List[Dict[str, Any]] = field(default_factory=list)
|
||||
duration_seconds: float = 0.0
|
||||
error: Optional[str] = None
|
||||
started_at: Optional[str] = None
|
||||
|
||||
|
||||
class DoomLoopTracker:
|
||||
@@ -395,6 +403,8 @@ class AutonomyMixin:
|
||||
prompt: str,
|
||||
max_iterations: Optional[int] = None,
|
||||
timeout_seconds: Optional[float] = None,
|
||||
completion_promise: Optional[str] = None,
|
||||
clear_context: bool = False,
|
||||
) -> AutonomyResult:
|
||||
"""Run an autonomous task execution loop.
|
||||
|
||||
@@ -403,12 +413,17 @@ class AutonomyMixin:
|
||||
- Progressive escalation based on task complexity
|
||||
- Doom loop detection and recovery
|
||||
- Iteration limits and timeouts
|
||||
- Completion detection
|
||||
- Completion detection (keyword-based or promise-based)
|
||||
- Optional context clearing between iterations
|
||||
|
||||
Args:
|
||||
prompt: The task to execute
|
||||
max_iterations: Override max iterations (default from config)
|
||||
timeout_seconds: Timeout in seconds (default: no timeout)
|
||||
completion_promise: Optional string that signals completion when
|
||||
wrapped in <promise>TEXT</promise> tags in the response
|
||||
clear_context: Whether to clear chat history between iterations
|
||||
(forces agent to rely on external state like files)
|
||||
|
||||
Returns:
|
||||
AutonomyResult with success status, output, and metadata
|
||||
@@ -418,7 +433,11 @@ class AutonomyMixin:
|
||||
|
||||
Example:
|
||||
agent = Agent(instructions="...", autonomy=True)
|
||||
result = agent.run_autonomous("Refactor the auth module")
|
||||
result = agent.run_autonomous(
|
||||
"Refactor the auth module",
|
||||
completion_promise="DONE",
|
||||
clear_context=True
|
||||
)
|
||||
if result.success:
|
||||
print(result.output)
|
||||
"""
|
||||
@@ -436,8 +455,17 @@ class AutonomyMixin:
|
||||
config_max_iter = self.autonomy_config.get("max_iterations", 20)
|
||||
effective_max_iter = max_iterations if max_iterations is not None else config_max_iter
|
||||
|
||||
# Get completion_promise from config if not provided as param
|
||||
effective_promise = completion_promise
|
||||
if effective_promise is None:
|
||||
effective_promise = self.autonomy_config.get("completion_promise")
|
||||
|
||||
# Get clear_context from config if not explicitly set
|
||||
effective_clear_context = clear_context
|
||||
if not clear_context:
|
||||
effective_clear_context = self.autonomy_config.get("clear_context", False)
|
||||
|
||||
# Analyze prompt and get recommended stage
|
||||
signals = self.analyze_prompt(prompt)
|
||||
stage = self.get_recommended_stage(prompt)
|
||||
|
||||
# Reset doom loop tracker for new task
|
||||
@@ -474,9 +502,9 @@ class AutonomyMixin:
|
||||
)
|
||||
|
||||
# Execute one turn using the agent's chat method
|
||||
# The chat method is expected to be provided by the Agent class
|
||||
# Always use the original prompt (prompt re-injection)
|
||||
try:
|
||||
response = self.chat(prompt if iterations == 1 else "Continue with the task")
|
||||
response = self.chat(prompt)
|
||||
except Exception as e:
|
||||
logger.error(f"Error during autonomous execution: {e}")
|
||||
return AutonomyResult(
|
||||
@@ -496,8 +524,24 @@ class AutonomyMixin:
|
||||
"response": str(response)[:500], # Truncate for storage
|
||||
})
|
||||
|
||||
# Check for completion signals in response
|
||||
response_lower = str(response).lower()
|
||||
response_str = str(response)
|
||||
|
||||
# Check for completion promise FIRST (structured signal)
|
||||
if effective_promise:
|
||||
promise_tag = f"<promise>{effective_promise}</promise>"
|
||||
if promise_tag in response_str:
|
||||
return AutonomyResult(
|
||||
success=True,
|
||||
output=response_str,
|
||||
completion_reason="promise",
|
||||
iterations=iterations,
|
||||
stage=stage,
|
||||
actions=actions_taken,
|
||||
duration_seconds=time.time() - start_time,
|
||||
)
|
||||
|
||||
# Check for keyword-based completion signals (fallback)
|
||||
response_lower = response_str.lower()
|
||||
completion_signals = [
|
||||
"task completed",
|
||||
"task complete",
|
||||
@@ -509,7 +553,7 @@ class AutonomyMixin:
|
||||
if any(signal in response_lower for signal in completion_signals):
|
||||
return AutonomyResult(
|
||||
success=True,
|
||||
output=str(response),
|
||||
output=response_str,
|
||||
completion_reason="goal",
|
||||
iterations=iterations,
|
||||
stage=stage,
|
||||
@@ -521,13 +565,17 @@ class AutonomyMixin:
|
||||
if stage == "direct":
|
||||
return AutonomyResult(
|
||||
success=True,
|
||||
output=str(response),
|
||||
output=response_str,
|
||||
completion_reason="goal",
|
||||
iterations=iterations,
|
||||
stage=stage,
|
||||
actions=actions_taken,
|
||||
duration_seconds=time.time() - start_time,
|
||||
)
|
||||
|
||||
# Clear context between iterations if enabled
|
||||
if effective_clear_context:
|
||||
self.clear_history()
|
||||
|
||||
# Max iterations reached
|
||||
return AutonomyResult(
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
"""CodeAgent - Code generation, execution, review, and refactoring with sandboxing."""
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
|
||||
@dataclass
|
||||
class CodeConfig:
|
||||
"""Configuration for CodeAgent.
|
||||
|
||||
Attributes:
|
||||
sandbox: Enable sandboxed execution (default: True for safety)
|
||||
timeout: Execution timeout in seconds
|
||||
allowed_languages: List of allowed programming languages
|
||||
max_output_length: Maximum output length in characters
|
||||
working_directory: Working directory for code execution
|
||||
environment: Environment variables for execution
|
||||
"""
|
||||
sandbox: bool = True
|
||||
timeout: int = 30
|
||||
allowed_languages: List[str] = field(default_factory=lambda: ["python"])
|
||||
max_output_length: int = 10000
|
||||
working_directory: Optional[str] = None
|
||||
environment: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert config to dictionary."""
|
||||
return {k: v for k, v in asdict(self).items() if v is not None}
|
||||
|
||||
|
||||
class CodeAgent:
|
||||
"""Agent for code generation, execution, review, and refactoring.
|
||||
|
||||
This agent provides capabilities for:
|
||||
- Generating code from natural language descriptions
|
||||
- Executing code in a sandboxed environment
|
||||
- Reviewing code for issues and improvements
|
||||
- Refactoring and fixing code
|
||||
- Explaining code functionality
|
||||
|
||||
Example:
|
||||
```python
|
||||
from praisonaiagents import CodeAgent
|
||||
|
||||
agent = CodeAgent(name="Coder")
|
||||
|
||||
# Generate code
|
||||
code = agent.generate("Write a function to calculate fibonacci")
|
||||
|
||||
# Execute code
|
||||
result = agent.execute("print('Hello, World!')")
|
||||
|
||||
# Review code
|
||||
review = agent.review(code)
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "CodeAgent",
|
||||
llm: Optional[str] = None,
|
||||
code: Optional[Union[bool, Dict, CodeConfig]] = None,
|
||||
instructions: Optional[str] = None,
|
||||
verbose: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
"""Initialize CodeAgent.
|
||||
|
||||
Args:
|
||||
name: Agent name
|
||||
llm: LLM model (default: gpt-4o-mini)
|
||||
code: Code configuration (bool, dict, or CodeConfig)
|
||||
instructions: System instructions
|
||||
verbose: Enable verbose output
|
||||
**kwargs: Additional arguments
|
||||
"""
|
||||
self.name = name
|
||||
self.llm = llm or "gpt-4o-mini"
|
||||
self.instructions = instructions
|
||||
self.verbose = verbose
|
||||
|
||||
# Resolve configuration (Precedence Ladder)
|
||||
if code is None or code is True:
|
||||
self._code_config = CodeConfig()
|
||||
elif isinstance(code, dict):
|
||||
self._code_config = CodeConfig(**code)
|
||||
elif isinstance(code, CodeConfig):
|
||||
self._code_config = code
|
||||
else:
|
||||
self._code_config = CodeConfig()
|
||||
|
||||
# Lazy-loaded dependencies
|
||||
self._litellm = None
|
||||
self._console = None
|
||||
|
||||
# Configure logging
|
||||
self._logger = logging.getLogger(f"CodeAgent.{name}")
|
||||
if not verbose:
|
||||
self._logger.setLevel(logging.WARNING)
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
"""Lazy load Rich console."""
|
||||
if self._console is None:
|
||||
try:
|
||||
from rich.console import Console
|
||||
self._console = Console()
|
||||
except ImportError:
|
||||
pass
|
||||
return self._console
|
||||
|
||||
@property
|
||||
def litellm(self):
|
||||
"""Lazy load litellm."""
|
||||
if self._litellm is None:
|
||||
try:
|
||||
import litellm
|
||||
self._litellm = litellm
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"litellm required for CodeAgent. "
|
||||
"Install with: pip install litellm"
|
||||
)
|
||||
return self._litellm
|
||||
|
||||
def _log(self, message: str, style: str = ""):
|
||||
"""Log message if verbose."""
|
||||
if self.verbose and self.console:
|
||||
self.console.print(f"[{style}]{message}[/{style}]" if style else message)
|
||||
|
||||
# =========================================================================
|
||||
# Code Generation Methods
|
||||
# =========================================================================
|
||||
|
||||
def generate(self, prompt: str, language: str = "python", **kwargs) -> str:
|
||||
"""Generate code from natural language description.
|
||||
|
||||
Args:
|
||||
prompt: Natural language description of desired code
|
||||
language: Target programming language
|
||||
**kwargs: Additional arguments for LLM
|
||||
|
||||
Returns:
|
||||
Generated code as string
|
||||
"""
|
||||
self._log(f"Generating {language} code...", "cyan")
|
||||
|
||||
system_prompt = f"""You are an expert {language} programmer.
|
||||
Generate clean, well-documented, production-ready code.
|
||||
Only output the code, no explanations unless asked.
|
||||
Follow best practices and coding standards."""
|
||||
|
||||
if self.instructions:
|
||||
system_prompt += f"\n\nAdditional instructions: {self.instructions}"
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
|
||||
response = self.litellm.completion(
|
||||
model=kwargs.get("model", self.llm),
|
||||
messages=messages,
|
||||
**{k: v for k, v in kwargs.items() if k != "model"}
|
||||
)
|
||||
|
||||
code = response.choices[0].message.content
|
||||
self._log("Code generated successfully", "green")
|
||||
return code
|
||||
|
||||
def generate_code(self, prompt: str, language: str = "python", **kwargs) -> str:
|
||||
"""Alias for generate() method."""
|
||||
return self.generate(prompt, language, **kwargs)
|
||||
|
||||
async def agenerate(self, prompt: str, language: str = "python", **kwargs) -> str:
|
||||
"""Generate code asynchronously.
|
||||
|
||||
Args:
|
||||
prompt: Natural language description
|
||||
language: Target programming language
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
Generated code as string
|
||||
"""
|
||||
self._log(f"Generating {language} code (async)...", "cyan")
|
||||
|
||||
system_prompt = f"""You are an expert {language} programmer.
|
||||
Generate clean, well-documented, production-ready code.
|
||||
Only output the code, no explanations unless asked.
|
||||
Follow best practices and coding standards."""
|
||||
|
||||
if self.instructions:
|
||||
system_prompt += f"\n\nAdditional instructions: {self.instructions}"
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
|
||||
response = await self.litellm.acompletion(
|
||||
model=kwargs.get("model", self.llm),
|
||||
messages=messages,
|
||||
**{k: v for k, v in kwargs.items() if k != "model"}
|
||||
)
|
||||
|
||||
code = response.choices[0].message.content
|
||||
self._log("Code generated successfully", "green")
|
||||
return code
|
||||
|
||||
# =========================================================================
|
||||
# Code Execution Methods
|
||||
# =========================================================================
|
||||
|
||||
def execute(self, code: str, language: str = "python", **kwargs) -> Dict[str, Any]:
|
||||
"""Execute code in sandboxed environment.
|
||||
|
||||
Args:
|
||||
code: Code to execute
|
||||
language: Programming language
|
||||
**kwargs: Additional execution options
|
||||
|
||||
Returns:
|
||||
Dictionary with stdout, stderr, return_code, and execution_time
|
||||
"""
|
||||
if language not in self._code_config.allowed_languages:
|
||||
return {
|
||||
"stdout": "",
|
||||
"stderr": f"Language '{language}' not allowed. Allowed: {self._code_config.allowed_languages}",
|
||||
"return_code": 1,
|
||||
"execution_time": 0
|
||||
}
|
||||
|
||||
self._log(f"Executing {language} code...", "cyan")
|
||||
|
||||
if language == "python":
|
||||
return self._execute_python(code, **kwargs)
|
||||
else:
|
||||
return {
|
||||
"stdout": "",
|
||||
"stderr": f"Execution for '{language}' not implemented",
|
||||
"return_code": 1,
|
||||
"execution_time": 0
|
||||
}
|
||||
|
||||
def execute_code(self, code: str, language: str = "python", **kwargs) -> Dict[str, Any]:
|
||||
"""Alias for execute() method."""
|
||||
return self.execute(code, language, **kwargs)
|
||||
|
||||
def _execute_python(self, code: str, **kwargs) -> Dict[str, Any]:
|
||||
"""Execute Python code."""
|
||||
import subprocess
|
||||
import time
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Write code to temp file
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
||||
f.write(code)
|
||||
temp_file = f.name
|
||||
|
||||
try:
|
||||
# Execute in subprocess (basic sandboxing)
|
||||
env = os.environ.copy()
|
||||
env.update(self._code_config.environment)
|
||||
|
||||
result = subprocess.run(
|
||||
["python", temp_file],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=self._code_config.timeout,
|
||||
cwd=self._code_config.working_directory,
|
||||
env=env
|
||||
)
|
||||
|
||||
execution_time = time.time() - start_time
|
||||
|
||||
stdout = result.stdout[:self._code_config.max_output_length]
|
||||
stderr = result.stderr[:self._code_config.max_output_length]
|
||||
|
||||
self._log(f"Execution completed in {execution_time:.2f}s", "green")
|
||||
|
||||
return {
|
||||
"stdout": stdout,
|
||||
"stderr": stderr,
|
||||
"return_code": result.returncode,
|
||||
"execution_time": execution_time
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
"stdout": "",
|
||||
"stderr": f"Execution timed out after {self._code_config.timeout}s",
|
||||
"return_code": -1,
|
||||
"execution_time": self._code_config.timeout
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"stdout": "",
|
||||
"stderr": str(e),
|
||||
"return_code": -1,
|
||||
"execution_time": time.time() - start_time
|
||||
}
|
||||
finally:
|
||||
# Clean up temp file
|
||||
try:
|
||||
os.unlink(temp_file)
|
||||
except:
|
||||
pass
|
||||
|
||||
async def aexecute(self, code: str, language: str = "python", **kwargs) -> Dict[str, Any]:
|
||||
"""Execute code asynchronously.
|
||||
|
||||
Args:
|
||||
code: Code to execute
|
||||
language: Programming language
|
||||
**kwargs: Additional options
|
||||
|
||||
Returns:
|
||||
Execution result dictionary
|
||||
"""
|
||||
# Run in thread pool to avoid blocking
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, lambda: self.execute(code, language, **kwargs))
|
||||
|
||||
# =========================================================================
|
||||
# Code Review Methods
|
||||
# =========================================================================
|
||||
|
||||
def review(self, code: str, language: str = "python", **kwargs) -> str:
|
||||
"""Review code for issues, bugs, and improvements.
|
||||
|
||||
Args:
|
||||
code: Code to review
|
||||
language: Programming language
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
Review feedback as string
|
||||
"""
|
||||
self._log("Reviewing code...", "cyan")
|
||||
|
||||
system_prompt = f"""You are an expert {language} code reviewer.
|
||||
Analyze the code for:
|
||||
1. Bugs and potential issues
|
||||
2. Security vulnerabilities
|
||||
3. Performance problems
|
||||
4. Code style and best practices
|
||||
5. Suggestions for improvement
|
||||
|
||||
Provide clear, actionable feedback."""
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": f"Review this {language} code:\n\n```{language}\n{code}\n```"}
|
||||
]
|
||||
|
||||
response = self.litellm.completion(
|
||||
model=kwargs.get("model", self.llm),
|
||||
messages=messages,
|
||||
**{k: v for k, v in kwargs.items() if k != "model"}
|
||||
)
|
||||
|
||||
review = response.choices[0].message.content
|
||||
self._log("Review completed", "green")
|
||||
return review
|
||||
|
||||
# =========================================================================
|
||||
# Code Explanation Methods
|
||||
# =========================================================================
|
||||
|
||||
def explain(self, code: str, language: str = "python", **kwargs) -> str:
|
||||
"""Explain what code does in plain language.
|
||||
|
||||
Args:
|
||||
code: Code to explain
|
||||
language: Programming language
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
Explanation as string
|
||||
"""
|
||||
self._log("Explaining code...", "cyan")
|
||||
|
||||
system_prompt = f"""You are an expert {language} programmer and teacher.
|
||||
Explain the code in clear, simple terms that anyone can understand.
|
||||
Include:
|
||||
1. What the code does overall
|
||||
2. Key components and their purpose
|
||||
3. How data flows through the code
|
||||
4. Any important patterns or techniques used"""
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": f"Explain this {language} code:\n\n```{language}\n{code}\n```"}
|
||||
]
|
||||
|
||||
response = self.litellm.completion(
|
||||
model=kwargs.get("model", self.llm),
|
||||
messages=messages,
|
||||
**{k: v for k, v in kwargs.items() if k != "model"}
|
||||
)
|
||||
|
||||
explanation = response.choices[0].message.content
|
||||
self._log("Explanation completed", "green")
|
||||
return explanation
|
||||
|
||||
# =========================================================================
|
||||
# Code Refactoring Methods
|
||||
# =========================================================================
|
||||
|
||||
def refactor(self, code: str, instructions: str = "", language: str = "python", **kwargs) -> str:
|
||||
"""Refactor code to improve quality.
|
||||
|
||||
Args:
|
||||
code: Code to refactor
|
||||
instructions: Specific refactoring instructions
|
||||
language: Programming language
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
Refactored code as string
|
||||
"""
|
||||
self._log("Refactoring code...", "cyan")
|
||||
|
||||
system_prompt = f"""You are an expert {language} programmer.
|
||||
Refactor the code to improve:
|
||||
1. Readability and maintainability
|
||||
2. Performance where possible
|
||||
3. Code organization
|
||||
4. Following best practices
|
||||
|
||||
Only output the refactored code."""
|
||||
|
||||
user_prompt = f"Refactor this {language} code:\n\n```{language}\n{code}\n```"
|
||||
if instructions:
|
||||
user_prompt += f"\n\nSpecific instructions: {instructions}"
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt}
|
||||
]
|
||||
|
||||
response = self.litellm.completion(
|
||||
model=kwargs.get("model", self.llm),
|
||||
messages=messages,
|
||||
**{k: v for k, v in kwargs.items() if k != "model"}
|
||||
)
|
||||
|
||||
refactored = response.choices[0].message.content
|
||||
self._log("Refactoring completed", "green")
|
||||
return refactored
|
||||
|
||||
# =========================================================================
|
||||
# Bug Fixing Methods
|
||||
# =========================================================================
|
||||
|
||||
def fix(self, code: str, error: str = "", language: str = "python", **kwargs) -> str:
|
||||
"""Fix bugs in code.
|
||||
|
||||
Args:
|
||||
code: Code with bugs
|
||||
error: Error message or description of the bug
|
||||
language: Programming language
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
Fixed code as string
|
||||
"""
|
||||
self._log("Fixing code...", "cyan")
|
||||
|
||||
system_prompt = f"""You are an expert {language} debugger.
|
||||
Fix the bugs in the code. Only output the fixed code.
|
||||
Ensure the fix:
|
||||
1. Addresses the root cause
|
||||
2. Doesn't introduce new bugs
|
||||
3. Maintains existing functionality"""
|
||||
|
||||
user_prompt = f"Fix this {language} code:\n\n```{language}\n{code}\n```"
|
||||
if error:
|
||||
user_prompt += f"\n\nError/Bug description: {error}"
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt}
|
||||
]
|
||||
|
||||
response = self.litellm.completion(
|
||||
model=kwargs.get("model", self.llm),
|
||||
messages=messages,
|
||||
**{k: v for k, v in kwargs.items() if k != "model"}
|
||||
)
|
||||
|
||||
fixed = response.choices[0].message.content
|
||||
self._log("Fix completed", "green")
|
||||
return fixed
|
||||
@@ -0,0 +1,403 @@
|
||||
"""
|
||||
EmbeddingAgent - A specialized agent for generating text embeddings.
|
||||
|
||||
This agent provides embedding capabilities for text using AI embedding models,
|
||||
with support for batch processing and similarity calculations.
|
||||
|
||||
Follows the Agent() class patterns with:
|
||||
- Lazy loading for heavy dependencies (litellm, rich)
|
||||
- Precedence Ladder for configuration resolution
|
||||
- Both sync and async methods
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Dict, Any, Union, List
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmbeddingConfig:
|
||||
"""
|
||||
Configuration for embedding settings.
|
||||
|
||||
Follows the Precedence Ladder pattern:
|
||||
- Instance > Config > Array > Dict > String > Bool > Default
|
||||
"""
|
||||
dimensions: Optional[int] = None
|
||||
encoding_format: str = "float" # float or base64
|
||||
timeout: int = 60
|
||||
|
||||
api_base: Optional[str] = None
|
||||
api_key: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for LiteLLM calls."""
|
||||
return {
|
||||
"dimensions": self.dimensions,
|
||||
"encoding_format": self.encoding_format,
|
||||
"timeout": self.timeout,
|
||||
"api_base": self.api_base,
|
||||
"api_key": self.api_key,
|
||||
}
|
||||
|
||||
|
||||
class EmbeddingAgent:
|
||||
"""
|
||||
A specialized agent for generating text embeddings.
|
||||
|
||||
Provides:
|
||||
- Single text embedding
|
||||
- Batch text embedding
|
||||
- Similarity calculation between texts
|
||||
|
||||
Supported Providers:
|
||||
- OpenAI: `text-embedding-3-small`, `text-embedding-3-large`, `text-embedding-ada-002`
|
||||
- Azure: `azure/text-embedding-3-small`
|
||||
- Cohere: `cohere/embed-english-v3.0`, `cohere/embed-multilingual-v3.0`
|
||||
- Voyage: `voyage/voyage-3`, `voyage/voyage-3-lite`
|
||||
- Mistral: `mistral/mistral-embed`
|
||||
|
||||
Example:
|
||||
```python
|
||||
from praisonaiagents import EmbeddingAgent
|
||||
|
||||
# Simple usage
|
||||
agent = EmbeddingAgent()
|
||||
embedding = agent.embed("Hello world")
|
||||
print(f"Embedding dimension: {len(embedding)}")
|
||||
|
||||
# Batch embedding
|
||||
embeddings = agent.embed_batch([
|
||||
"First text",
|
||||
"Second text",
|
||||
"Third text"
|
||||
])
|
||||
|
||||
# Calculate similarity
|
||||
score = agent.similarity("Hello", "Hi there")
|
||||
print(f"Similarity: {score:.2f}")
|
||||
```
|
||||
"""
|
||||
|
||||
DEFAULT_MODEL = "text-embedding-3-small"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
llm: Optional[str] = None,
|
||||
model: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
embedding: Optional[Union[bool, Dict, "EmbeddingConfig"]] = None,
|
||||
verbose: Union[bool, int] = True,
|
||||
):
|
||||
"""Initialize EmbeddingAgent.
|
||||
|
||||
Args:
|
||||
name: Agent name for identification
|
||||
instructions: Optional instructions (unused for embeddings)
|
||||
llm: Model name (e.g., "text-embedding-3-small")
|
||||
model: Alias for llm= parameter
|
||||
base_url: Custom API endpoint URL
|
||||
api_key: API key for the provider
|
||||
embedding: Embedding configuration. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- dict: {"dimensions": 1536}
|
||||
- EmbeddingConfig: Full configuration object
|
||||
verbose: Verbosity level for output
|
||||
"""
|
||||
if llm is None and model is not None:
|
||||
llm = model
|
||||
|
||||
self.name = name or "EmbeddingAgent"
|
||||
self.instructions = instructions
|
||||
self.llm = llm or os.getenv('PRAISONAI_EMBEDDING_MODEL', self.DEFAULT_MODEL)
|
||||
self.base_url = base_url
|
||||
self.api_key = api_key
|
||||
self._embedding_config = self._resolve_embedding_config(embedding)
|
||||
self.verbose = verbose
|
||||
|
||||
self._litellm = None
|
||||
self._console = None
|
||||
|
||||
self._configure_logging(verbose)
|
||||
|
||||
def _resolve_embedding_config(self, embedding: Optional[Union[bool, Dict, EmbeddingConfig]]) -> EmbeddingConfig:
|
||||
"""Resolve embedding parameter using Precedence Ladder."""
|
||||
if embedding is None or embedding is True or embedding is False:
|
||||
return EmbeddingConfig()
|
||||
elif isinstance(embedding, EmbeddingConfig):
|
||||
return embedding
|
||||
elif isinstance(embedding, dict):
|
||||
return EmbeddingConfig(**embedding)
|
||||
return EmbeddingConfig()
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
"""Lazily initialize Rich Console."""
|
||||
if self._console is None:
|
||||
from rich.console import Console
|
||||
self._console = Console()
|
||||
return self._console
|
||||
|
||||
@property
|
||||
def litellm(self):
|
||||
"""Lazy load litellm module when needed."""
|
||||
if self._litellm is None:
|
||||
try:
|
||||
import litellm
|
||||
litellm.telemetry = False
|
||||
litellm.success_callback = []
|
||||
self._litellm = litellm
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"litellm is required for embedding generation. "
|
||||
"Please install with: pip install litellm"
|
||||
)
|
||||
return self._litellm
|
||||
|
||||
def _configure_logging(self, verbose: Union[bool, int]) -> None:
|
||||
"""Configure logging levels."""
|
||||
if isinstance(verbose, int) and verbose >= 10:
|
||||
logging.getLogger("litellm").setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger("litellm").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
|
||||
def _get_model_params(self, model: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Build parameters for LiteLLM calls."""
|
||||
params = {"model": model or self.llm}
|
||||
if self.api_key:
|
||||
params["api_key"] = self.api_key
|
||||
if self.base_url:
|
||||
params["api_base"] = self.base_url
|
||||
if self._embedding_config.dimensions:
|
||||
params["dimensions"] = self._embedding_config.dimensions
|
||||
if self._embedding_config.encoding_format != "float":
|
||||
params["encoding_format"] = self._embedding_config.encoding_format
|
||||
return params
|
||||
|
||||
def embed(
|
||||
self,
|
||||
text: str,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> List[float]:
|
||||
"""
|
||||
Generate embedding for a single text.
|
||||
|
||||
Args:
|
||||
text: Text to embed
|
||||
model: Override model for this call
|
||||
**kwargs: Additional provider-specific parameters
|
||||
|
||||
Returns:
|
||||
List of floats representing the embedding vector
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = EmbeddingAgent()
|
||||
embedding = agent.embed("Hello world")
|
||||
print(f"Dimension: {len(embedding)}")
|
||||
```
|
||||
"""
|
||||
params = self._get_model_params(model)
|
||||
params["input"] = [text]
|
||||
params.update(kwargs)
|
||||
|
||||
if self.verbose:
|
||||
self.console.print(f"[cyan]Generating embedding with {params['model']}...[/cyan]")
|
||||
|
||||
response = self.litellm.embedding(**params)
|
||||
result = response.data[0]["embedding"]
|
||||
|
||||
if self.verbose:
|
||||
self.console.print(f"[green]✓ Embedding generated (dim={len(result)})[/green]")
|
||||
|
||||
return result
|
||||
|
||||
def embed_batch(
|
||||
self,
|
||||
texts: List[str],
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> List[List[float]]:
|
||||
"""
|
||||
Generate embeddings for multiple texts.
|
||||
|
||||
Args:
|
||||
texts: List of texts to embed
|
||||
model: Override model for this call
|
||||
**kwargs: Additional provider-specific parameters
|
||||
|
||||
Returns:
|
||||
List of embedding vectors
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = EmbeddingAgent()
|
||||
embeddings = agent.embed_batch(["Hello", "World"])
|
||||
print(f"Generated {len(embeddings)} embeddings")
|
||||
```
|
||||
"""
|
||||
if not texts:
|
||||
return []
|
||||
|
||||
params = self._get_model_params(model)
|
||||
params["input"] = texts
|
||||
params.update(kwargs)
|
||||
|
||||
if self.verbose:
|
||||
self.console.print(f"[cyan]Generating {len(texts)} embeddings with {params['model']}...[/cyan]")
|
||||
|
||||
response = self.litellm.embedding(**params)
|
||||
results = [item["embedding"] for item in response.data]
|
||||
|
||||
if self.verbose:
|
||||
self.console.print(f"[green]✓ {len(results)} embeddings generated[/green]")
|
||||
|
||||
return results
|
||||
|
||||
def similarity(
|
||||
self,
|
||||
text1: str,
|
||||
text2: str,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> float:
|
||||
"""
|
||||
Calculate cosine similarity between two texts.
|
||||
|
||||
Args:
|
||||
text1: First text
|
||||
text2: Second text
|
||||
model: Override model for this call
|
||||
**kwargs: Additional parameters
|
||||
|
||||
Returns:
|
||||
Cosine similarity score (0.0 to 1.0)
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = EmbeddingAgent()
|
||||
score = agent.similarity("Hello", "Hi there")
|
||||
print(f"Similarity: {score:.2f}")
|
||||
```
|
||||
"""
|
||||
embeddings = self.embed_batch([text1, text2], model=model, **kwargs)
|
||||
return self._cosine_similarity(embeddings[0], embeddings[1])
|
||||
|
||||
def _cosine_similarity(self, vec1: List[float], vec2: List[float]) -> float:
|
||||
"""Calculate cosine similarity between two vectors."""
|
||||
if len(vec1) != len(vec2):
|
||||
raise ValueError("Vectors must have the same dimension")
|
||||
|
||||
dot_product = sum(a * b for a, b in zip(vec1, vec2))
|
||||
norm1 = math.sqrt(sum(a * a for a in vec1))
|
||||
norm2 = math.sqrt(sum(b * b for b in vec2))
|
||||
|
||||
if norm1 == 0 or norm2 == 0:
|
||||
return 0.0
|
||||
|
||||
return dot_product / (norm1 * norm2)
|
||||
|
||||
def find_most_similar(
|
||||
self,
|
||||
query: str,
|
||||
candidates: List[str],
|
||||
top_k: int = 5,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Find the most similar texts to a query.
|
||||
|
||||
Args:
|
||||
query: Query text
|
||||
candidates: List of candidate texts to compare
|
||||
top_k: Number of top results to return
|
||||
model: Override model for this call
|
||||
**kwargs: Additional parameters
|
||||
|
||||
Returns:
|
||||
List of dicts with 'text', 'score', and 'index' keys
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = EmbeddingAgent()
|
||||
results = agent.find_most_similar(
|
||||
"What is AI?",
|
||||
["Artificial intelligence is...", "Machine learning...", "Deep learning..."],
|
||||
top_k=2
|
||||
)
|
||||
for r in results:
|
||||
print(f"{r['score']:.2f}: {r['text'][:50]}...")
|
||||
```
|
||||
"""
|
||||
if not candidates:
|
||||
return []
|
||||
|
||||
all_texts = [query] + candidates
|
||||
embeddings = self.embed_batch(all_texts, model=model, **kwargs)
|
||||
|
||||
query_embedding = embeddings[0]
|
||||
candidate_embeddings = embeddings[1:]
|
||||
|
||||
scores = []
|
||||
for i, emb in enumerate(candidate_embeddings):
|
||||
score = self._cosine_similarity(query_embedding, emb)
|
||||
scores.append({
|
||||
"text": candidates[i],
|
||||
"score": score,
|
||||
"index": i
|
||||
})
|
||||
|
||||
scores.sort(key=lambda x: x["score"], reverse=True)
|
||||
return scores[:top_k]
|
||||
|
||||
async def aembed(
|
||||
self,
|
||||
text: str,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> List[float]:
|
||||
"""Async version of embed()."""
|
||||
params = self._get_model_params(model)
|
||||
params["input"] = [text]
|
||||
params.update(kwargs)
|
||||
|
||||
response = await self.litellm.aembedding(**params)
|
||||
return response.data[0]["embedding"]
|
||||
|
||||
async def aembed_batch(
|
||||
self,
|
||||
texts: List[str],
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> List[List[float]]:
|
||||
"""Async version of embed_batch()."""
|
||||
if not texts:
|
||||
return []
|
||||
|
||||
params = self._get_model_params(model)
|
||||
params["input"] = texts
|
||||
params.update(kwargs)
|
||||
|
||||
response = await self.litellm.aembedding(**params)
|
||||
return [item["embedding"] for item in response.data]
|
||||
|
||||
async def asimilarity(
|
||||
self,
|
||||
text1: str,
|
||||
text2: str,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> float:
|
||||
"""Async version of similarity()."""
|
||||
embeddings = await self.aembed_batch([text1, text2], model=model, **kwargs)
|
||||
return self._cosine_similarity(embeddings[0], embeddings[1])
|
||||
@@ -0,0 +1,431 @@
|
||||
"""RealtimeAgent - Real-time voice conversations using OpenAI Realtime API."""
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
|
||||
@dataclass
|
||||
class RealtimeConfig:
|
||||
"""Configuration for RealtimeAgent.
|
||||
|
||||
Attributes:
|
||||
voice: Voice to use for audio output (alloy, echo, fable, onyx, nova, shimmer)
|
||||
modalities: List of modalities to use (text, audio)
|
||||
turn_detection: Turn detection mode (server_vad, none)
|
||||
input_audio_format: Input audio format (pcm16, g711_ulaw, g711_alaw)
|
||||
output_audio_format: Output audio format (pcm16, g711_ulaw, g711_alaw)
|
||||
temperature: Sampling temperature (0.0 to 2.0)
|
||||
max_response_output_tokens: Maximum tokens for response
|
||||
instructions: System instructions for the session
|
||||
"""
|
||||
voice: str = "alloy"
|
||||
modalities: List[str] = field(default_factory=lambda: ["text", "audio"])
|
||||
turn_detection: str = "server_vad"
|
||||
input_audio_format: str = "pcm16"
|
||||
output_audio_format: str = "pcm16"
|
||||
temperature: float = 0.8
|
||||
max_response_output_tokens: Optional[int] = None
|
||||
instructions: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert config to dictionary."""
|
||||
return {k: v for k, v in asdict(self).items() if v is not None}
|
||||
|
||||
|
||||
class RealtimeAgent:
|
||||
"""Agent for real-time voice conversations using OpenAI Realtime API.
|
||||
|
||||
This agent enables bidirectional audio streaming for voice conversations,
|
||||
supporting both text and audio modalities with configurable voice settings.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from praisonaiagents import RealtimeAgent
|
||||
|
||||
agent = RealtimeAgent(
|
||||
name="VoiceAssistant",
|
||||
realtime={"voice": "nova"}
|
||||
)
|
||||
|
||||
# Connect and start conversation
|
||||
await agent.aconnect()
|
||||
await agent.send_text("Hello!")
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "RealtimeAgent",
|
||||
llm: Optional[str] = None,
|
||||
realtime: Optional[Union[bool, Dict, RealtimeConfig]] = None,
|
||||
instructions: Optional[str] = None,
|
||||
verbose: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
"""Initialize RealtimeAgent.
|
||||
|
||||
Args:
|
||||
name: Agent name
|
||||
llm: LLM model (default: gpt-4o-realtime-preview)
|
||||
realtime: Realtime configuration (bool, dict, or RealtimeConfig)
|
||||
instructions: System instructions
|
||||
verbose: Enable verbose output
|
||||
**kwargs: Additional arguments
|
||||
"""
|
||||
self.name = name
|
||||
self.llm = llm or "gpt-4o-realtime-preview"
|
||||
self.instructions = instructions
|
||||
self.verbose = verbose
|
||||
|
||||
# Resolve configuration (Precedence Ladder)
|
||||
if realtime is None or realtime is True:
|
||||
self._realtime_config = RealtimeConfig()
|
||||
elif isinstance(realtime, dict):
|
||||
self._realtime_config = RealtimeConfig(**realtime)
|
||||
elif isinstance(realtime, RealtimeConfig):
|
||||
self._realtime_config = realtime
|
||||
else:
|
||||
self._realtime_config = RealtimeConfig()
|
||||
|
||||
# Override instructions if provided
|
||||
if instructions:
|
||||
self._realtime_config.instructions = instructions
|
||||
|
||||
# Lazy-loaded dependencies
|
||||
self._client = None
|
||||
self._console = None
|
||||
self._ws = None
|
||||
|
||||
# Callbacks
|
||||
self._on_message_callbacks: List[Callable] = []
|
||||
self._on_audio_callbacks: List[Callable] = []
|
||||
self._on_error_callbacks: List[Callable] = []
|
||||
|
||||
# Connection state
|
||||
self._connected = False
|
||||
|
||||
# Configure logging
|
||||
self._logger = logging.getLogger(f"RealtimeAgent.{name}")
|
||||
if not verbose:
|
||||
self._logger.setLevel(logging.WARNING)
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
"""Lazy load Rich console."""
|
||||
if self._console is None:
|
||||
try:
|
||||
from rich.console import Console
|
||||
self._console = Console()
|
||||
except ImportError:
|
||||
pass
|
||||
return self._console
|
||||
|
||||
def _log(self, message: str, style: str = ""):
|
||||
"""Log message if verbose."""
|
||||
if self.verbose and self.console:
|
||||
self.console.print(f"[{style}]{message}[/{style}]" if style else message)
|
||||
|
||||
# =========================================================================
|
||||
# Connection Methods
|
||||
# =========================================================================
|
||||
|
||||
def connect(self, **kwargs) -> bool:
|
||||
"""Connect to the Realtime API (sync wrapper).
|
||||
|
||||
Returns:
|
||||
True if connected successfully
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
# If already in async context, can't use run_until_complete
|
||||
self._log("Use aconnect() in async context", "yellow")
|
||||
return False
|
||||
return loop.run_until_complete(self.aconnect(**kwargs))
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
return loop.run_until_complete(self.aconnect(**kwargs))
|
||||
|
||||
async def aconnect(self, **kwargs) -> bool:
|
||||
"""Connect to the Realtime API asynchronously.
|
||||
|
||||
Returns:
|
||||
True if connected successfully
|
||||
"""
|
||||
if self._connected:
|
||||
self._log("Already connected", "yellow")
|
||||
return True
|
||||
|
||||
try:
|
||||
# Lazy import websockets
|
||||
try:
|
||||
import websockets
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"websockets required for RealtimeAgent. "
|
||||
"Install with: pip install websockets"
|
||||
)
|
||||
|
||||
# Get API key
|
||||
import os
|
||||
api_key = os.environ.get("OPENAI_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError("OPENAI_API_KEY environment variable required")
|
||||
|
||||
# Build WebSocket URL
|
||||
url = f"wss://api.openai.com/v1/realtime?model={self.llm}"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"OpenAI-Beta": "realtime=v1"
|
||||
}
|
||||
|
||||
self._ws = await websockets.connect(url, extra_headers=headers)
|
||||
self._connected = True
|
||||
|
||||
# Send session configuration
|
||||
config = self._realtime_config.to_dict()
|
||||
await self._send_event({
|
||||
"type": "session.update",
|
||||
"session": config
|
||||
})
|
||||
|
||||
self._log(f"Connected to Realtime API with model {self.llm}", "green")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self._log(f"Connection failed: {e}", "red")
|
||||
for callback in self._on_error_callbacks:
|
||||
callback(e)
|
||||
return False
|
||||
|
||||
def disconnect(self) -> bool:
|
||||
"""Disconnect from the Realtime API (sync wrapper).
|
||||
|
||||
Returns:
|
||||
True if disconnected successfully
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
self._log("Use adisconnect() in async context", "yellow")
|
||||
return False
|
||||
return loop.run_until_complete(self.adisconnect())
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
return loop.run_until_complete(self.adisconnect())
|
||||
|
||||
async def adisconnect(self) -> bool:
|
||||
"""Disconnect from the Realtime API asynchronously.
|
||||
|
||||
Returns:
|
||||
True if disconnected successfully
|
||||
"""
|
||||
if not self._connected or not self._ws:
|
||||
return True
|
||||
|
||||
try:
|
||||
await self._ws.close()
|
||||
self._connected = False
|
||||
self._ws = None
|
||||
self._log("Disconnected from Realtime API", "yellow")
|
||||
return True
|
||||
except Exception as e:
|
||||
self._log(f"Disconnect error: {e}", "red")
|
||||
return False
|
||||
|
||||
# =========================================================================
|
||||
# Send Methods
|
||||
# =========================================================================
|
||||
|
||||
async def _send_event(self, event: Dict[str, Any]) -> bool:
|
||||
"""Send an event to the Realtime API.
|
||||
|
||||
Args:
|
||||
event: Event dictionary to send
|
||||
|
||||
Returns:
|
||||
True if sent successfully
|
||||
"""
|
||||
if not self._connected or not self._ws:
|
||||
self._log("Not connected", "red")
|
||||
return False
|
||||
|
||||
try:
|
||||
import json
|
||||
await self._ws.send(json.dumps(event))
|
||||
return True
|
||||
except Exception as e:
|
||||
self._log(f"Send error: {e}", "red")
|
||||
return False
|
||||
|
||||
def send_text(self, text: str) -> bool:
|
||||
"""Send text message (sync wrapper).
|
||||
|
||||
Args:
|
||||
text: Text message to send
|
||||
|
||||
Returns:
|
||||
True if sent successfully
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
self._log("Use async send in async context", "yellow")
|
||||
return False
|
||||
return loop.run_until_complete(self.asend_text(text))
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
return loop.run_until_complete(self.asend_text(text))
|
||||
|
||||
async def asend_text(self, text: str) -> bool:
|
||||
"""Send text message asynchronously.
|
||||
|
||||
Args:
|
||||
text: Text message to send
|
||||
|
||||
Returns:
|
||||
True if sent successfully
|
||||
"""
|
||||
event = {
|
||||
"type": "conversation.item.create",
|
||||
"item": {
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [{"type": "input_text", "text": text}]
|
||||
}
|
||||
}
|
||||
if await self._send_event(event):
|
||||
# Request response
|
||||
return await self._send_event({"type": "response.create"})
|
||||
return False
|
||||
|
||||
def send_audio(self, audio_data: bytes) -> bool:
|
||||
"""Send audio data (sync wrapper).
|
||||
|
||||
Args:
|
||||
audio_data: Raw audio bytes (PCM16 format)
|
||||
|
||||
Returns:
|
||||
True if sent successfully
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
return False
|
||||
return loop.run_until_complete(self.asend_audio(audio_data))
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
return loop.run_until_complete(self.asend_audio(audio_data))
|
||||
|
||||
async def asend_audio(self, audio_data: bytes) -> bool:
|
||||
"""Send audio data asynchronously.
|
||||
|
||||
Args:
|
||||
audio_data: Raw audio bytes (PCM16 format)
|
||||
|
||||
Returns:
|
||||
True if sent successfully
|
||||
"""
|
||||
import base64
|
||||
encoded = base64.b64encode(audio_data).decode("utf-8")
|
||||
|
||||
event = {
|
||||
"type": "input_audio_buffer.append",
|
||||
"audio": encoded
|
||||
}
|
||||
return await self._send_event(event)
|
||||
|
||||
# =========================================================================
|
||||
# Callback Registration
|
||||
# =========================================================================
|
||||
|
||||
def on_message(self, callback: Callable[[str], None]) -> None:
|
||||
"""Register callback for text messages.
|
||||
|
||||
Args:
|
||||
callback: Function to call with message text
|
||||
"""
|
||||
self._on_message_callbacks.append(callback)
|
||||
|
||||
def on_audio(self, callback: Callable[[bytes], None]) -> None:
|
||||
"""Register callback for audio data.
|
||||
|
||||
Args:
|
||||
callback: Function to call with audio bytes
|
||||
"""
|
||||
self._on_audio_callbacks.append(callback)
|
||||
|
||||
def on_error(self, callback: Callable[[Exception], None]) -> None:
|
||||
"""Register callback for errors.
|
||||
|
||||
Args:
|
||||
callback: Function to call with exception
|
||||
"""
|
||||
self._on_error_callbacks.append(callback)
|
||||
|
||||
# =========================================================================
|
||||
# Receive Methods
|
||||
# =========================================================================
|
||||
|
||||
async def receive_loop(self) -> None:
|
||||
"""Main receive loop for processing incoming events."""
|
||||
if not self._connected or not self._ws:
|
||||
return
|
||||
|
||||
import json
|
||||
import base64
|
||||
|
||||
try:
|
||||
async for message in self._ws:
|
||||
try:
|
||||
event = json.loads(message)
|
||||
event_type = event.get("type", "")
|
||||
|
||||
# Handle text responses
|
||||
if event_type == "response.text.delta":
|
||||
text = event.get("delta", "")
|
||||
for callback in self._on_message_callbacks:
|
||||
callback(text)
|
||||
|
||||
# Handle audio responses
|
||||
elif event_type == "response.audio.delta":
|
||||
audio_b64 = event.get("delta", "")
|
||||
if audio_b64:
|
||||
audio_bytes = base64.b64decode(audio_b64)
|
||||
for callback in self._on_audio_callbacks:
|
||||
callback(audio_bytes)
|
||||
|
||||
# Handle errors
|
||||
elif event_type == "error":
|
||||
error = event.get("error", {})
|
||||
self._log(f"API Error: {error}", "red")
|
||||
for callback in self._on_error_callbacks:
|
||||
callback(Exception(str(error)))
|
||||
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
self._log(f"Receive loop error: {e}", "red")
|
||||
for callback in self._on_error_callbacks:
|
||||
callback(e)
|
||||
|
||||
# =========================================================================
|
||||
# Context Manager
|
||||
# =========================================================================
|
||||
|
||||
async def __aenter__(self):
|
||||
"""Async context manager entry."""
|
||||
await self.aconnect()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Async context manager exit."""
|
||||
await self.adisconnect()
|
||||
@@ -0,0 +1,448 @@
|
||||
"""
|
||||
VisionAgent - A specialized agent for image analysis and understanding.
|
||||
|
||||
This agent provides vision capabilities for analyzing, describing, and
|
||||
extracting information from images using AI vision models.
|
||||
|
||||
Follows the Agent() class patterns with:
|
||||
- Lazy loading for heavy dependencies (litellm, rich)
|
||||
- Precedence Ladder for configuration resolution
|
||||
- Both sync and async methods
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Dict, Any, Union, List
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VisionConfig:
|
||||
"""
|
||||
Configuration for vision processing settings.
|
||||
|
||||
Follows the Precedence Ladder pattern:
|
||||
- Instance > Config > Array > Dict > String > Bool > Default
|
||||
"""
|
||||
detail: str = "auto" # low, high, auto
|
||||
max_tokens: int = 4096
|
||||
timeout: int = 60
|
||||
|
||||
api_base: Optional[str] = None
|
||||
api_key: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for LiteLLM calls."""
|
||||
return {
|
||||
"detail": self.detail,
|
||||
"max_tokens": self.max_tokens,
|
||||
"timeout": self.timeout,
|
||||
"api_base": self.api_base,
|
||||
"api_key": self.api_key,
|
||||
}
|
||||
|
||||
|
||||
class VisionAgent:
|
||||
"""
|
||||
A specialized agent for image analysis and understanding.
|
||||
|
||||
Provides:
|
||||
- Image analysis and description
|
||||
- Multi-image comparison
|
||||
- Text extraction from images
|
||||
|
||||
Supported Providers:
|
||||
- OpenAI: `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`
|
||||
- Anthropic: `claude-3-5-sonnet-20241022`, `claude-3-opus-20240229`
|
||||
- Google: `gemini/gemini-1.5-pro`, `gemini/gemini-1.5-flash`
|
||||
|
||||
Example:
|
||||
```python
|
||||
from praisonaiagents import VisionAgent
|
||||
|
||||
# Simple usage
|
||||
agent = VisionAgent()
|
||||
description = agent.describe("https://example.com/image.jpg")
|
||||
print(description)
|
||||
|
||||
# Analyze with custom prompt
|
||||
result = agent.analyze(
|
||||
"https://example.com/chart.png",
|
||||
prompt="What data does this chart show?"
|
||||
)
|
||||
|
||||
# Compare images
|
||||
comparison = agent.compare([
|
||||
"image1.jpg",
|
||||
"image2.jpg"
|
||||
])
|
||||
|
||||
# Extract text
|
||||
text = agent.extract_text("document.png")
|
||||
```
|
||||
"""
|
||||
|
||||
DEFAULT_MODEL = "gpt-4o"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
llm: Optional[str] = None,
|
||||
model: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
vision: Optional[Union[bool, Dict, "VisionConfig"]] = None,
|
||||
verbose: Union[bool, int] = True,
|
||||
):
|
||||
"""Initialize VisionAgent.
|
||||
|
||||
Args:
|
||||
name: Agent name for identification
|
||||
instructions: Optional instructions for the agent
|
||||
llm: Model name (e.g., "gpt-4o", "claude-3-5-sonnet-20241022")
|
||||
model: Alias for llm= parameter
|
||||
base_url: Custom API endpoint URL
|
||||
api_key: API key for the provider
|
||||
vision: Vision configuration. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- dict: {"detail": "high", "max_tokens": 8192}
|
||||
- VisionConfig: Full configuration object
|
||||
verbose: Verbosity level for output
|
||||
"""
|
||||
if llm is None and model is not None:
|
||||
llm = model
|
||||
|
||||
self.name = name or "VisionAgent"
|
||||
self.instructions = instructions
|
||||
self.llm = llm or os.getenv('PRAISONAI_VISION_MODEL', self.DEFAULT_MODEL)
|
||||
self.base_url = base_url
|
||||
self.api_key = api_key
|
||||
self._vision_config = self._resolve_vision_config(vision)
|
||||
self.verbose = verbose
|
||||
|
||||
self._litellm = None
|
||||
self._console = None
|
||||
|
||||
self._configure_logging(verbose)
|
||||
|
||||
def _resolve_vision_config(self, vision: Optional[Union[bool, Dict, VisionConfig]]) -> VisionConfig:
|
||||
"""Resolve vision parameter using Precedence Ladder."""
|
||||
if vision is None or vision is True or vision is False:
|
||||
return VisionConfig()
|
||||
elif isinstance(vision, VisionConfig):
|
||||
return vision
|
||||
elif isinstance(vision, dict):
|
||||
return VisionConfig(**vision)
|
||||
return VisionConfig()
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
"""Lazily initialize Rich Console."""
|
||||
if self._console is None:
|
||||
from rich.console import Console
|
||||
self._console = Console()
|
||||
return self._console
|
||||
|
||||
@property
|
||||
def litellm(self):
|
||||
"""Lazy load litellm module when needed."""
|
||||
if self._litellm is None:
|
||||
try:
|
||||
import litellm
|
||||
litellm.telemetry = False
|
||||
litellm.success_callback = []
|
||||
self._litellm = litellm
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"litellm is required for vision processing. "
|
||||
"Please install with: pip install litellm"
|
||||
)
|
||||
return self._litellm
|
||||
|
||||
def _configure_logging(self, verbose: Union[bool, int]) -> None:
|
||||
"""Configure logging levels."""
|
||||
if isinstance(verbose, int) and verbose >= 10:
|
||||
logging.getLogger("litellm").setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger("litellm").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
|
||||
def _get_model_params(self, model: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Build parameters for LiteLLM calls."""
|
||||
params = {"model": model or self.llm}
|
||||
if self.api_key:
|
||||
params["api_key"] = self.api_key
|
||||
if self.base_url:
|
||||
params["api_base"] = self.base_url
|
||||
return params
|
||||
|
||||
def _build_image_content(self, image: str, detail: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Build image content for vision API."""
|
||||
detail = detail or self._vision_config.detail
|
||||
|
||||
if image.startswith(('http://', 'https://')):
|
||||
return {
|
||||
"type": "image_url",
|
||||
"image_url": {"url": image, "detail": detail}
|
||||
}
|
||||
else:
|
||||
import base64
|
||||
path = Path(image)
|
||||
if path.exists():
|
||||
with open(path, "rb") as f:
|
||||
data = base64.b64encode(f.read()).decode("utf-8")
|
||||
ext = path.suffix.lower().lstrip('.')
|
||||
mime = f"image/{ext}" if ext in ('png', 'jpg', 'jpeg', 'gif', 'webp') else "image/png"
|
||||
return {
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:{mime};base64,{data}", "detail": detail}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"type": "image_url",
|
||||
"image_url": {"url": image, "detail": detail}
|
||||
}
|
||||
|
||||
def analyze(
|
||||
self,
|
||||
image: str,
|
||||
prompt: Optional[str] = None,
|
||||
detail: Optional[str] = None,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""
|
||||
Analyze an image and return analysis.
|
||||
|
||||
Args:
|
||||
image: Image URL or local file path
|
||||
prompt: Custom prompt for analysis (default: general analysis)
|
||||
detail: Detail level (low, high, auto)
|
||||
model: Override model for this call
|
||||
**kwargs: Additional provider-specific parameters
|
||||
|
||||
Returns:
|
||||
Analysis text
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = VisionAgent()
|
||||
result = agent.analyze(
|
||||
"https://example.com/chart.png",
|
||||
prompt="What trends does this chart show?"
|
||||
)
|
||||
```
|
||||
"""
|
||||
prompt = prompt or "Analyze this image in detail. Describe what you see, including objects, colors, composition, and any text or data present."
|
||||
|
||||
params = self._get_model_params(model)
|
||||
params["messages"] = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt},
|
||||
self._build_image_content(image, detail)
|
||||
]
|
||||
}
|
||||
]
|
||||
params["max_tokens"] = kwargs.pop("max_tokens", self._vision_config.max_tokens)
|
||||
params.update(kwargs)
|
||||
|
||||
if self.verbose:
|
||||
self.console.print(f"[cyan]Analyzing image with {params['model']}...[/cyan]")
|
||||
|
||||
response = self.litellm.completion(**params)
|
||||
result = response.choices[0].message.content
|
||||
|
||||
if self.verbose:
|
||||
self.console.print("[green]✓ Analysis complete[/green]")
|
||||
|
||||
return result
|
||||
|
||||
def describe(
|
||||
self,
|
||||
image: str,
|
||||
detail: Optional[str] = None,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""
|
||||
Generate a detailed description of an image.
|
||||
|
||||
Args:
|
||||
image: Image URL or local file path
|
||||
detail: Detail level (low, high, auto)
|
||||
model: Override model for this call
|
||||
**kwargs: Additional parameters
|
||||
|
||||
Returns:
|
||||
Detailed description text
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = VisionAgent()
|
||||
description = agent.describe("photo.jpg")
|
||||
print(description)
|
||||
```
|
||||
"""
|
||||
prompt = """Provide a comprehensive description of this image. Include:
|
||||
1. Main subject and composition
|
||||
2. Colors, lighting, and mood
|
||||
3. Background and setting
|
||||
4. Any text, symbols, or notable details
|
||||
5. Overall impression and context"""
|
||||
|
||||
return self.analyze(image, prompt=prompt, detail=detail, model=model, **kwargs)
|
||||
|
||||
def compare(
|
||||
self,
|
||||
images: List[str],
|
||||
prompt: Optional[str] = None,
|
||||
detail: Optional[str] = None,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""
|
||||
Compare multiple images.
|
||||
|
||||
Args:
|
||||
images: List of image URLs or file paths
|
||||
prompt: Custom comparison prompt
|
||||
detail: Detail level (low, high, auto)
|
||||
model: Override model for this call
|
||||
**kwargs: Additional parameters
|
||||
|
||||
Returns:
|
||||
Comparison analysis text
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = VisionAgent()
|
||||
comparison = agent.compare([
|
||||
"before.jpg",
|
||||
"after.jpg"
|
||||
], prompt="What changed between these images?")
|
||||
```
|
||||
"""
|
||||
if len(images) < 2:
|
||||
raise ValueError("At least 2 images are required for comparison")
|
||||
|
||||
prompt = prompt or f"Compare these {len(images)} images. Describe their similarities and differences in detail."
|
||||
|
||||
content = [{"type": "text", "text": prompt}]
|
||||
for i, img in enumerate(images):
|
||||
content.append(self._build_image_content(img, detail))
|
||||
|
||||
params = self._get_model_params(model)
|
||||
params["messages"] = [{"role": "user", "content": content}]
|
||||
params["max_tokens"] = kwargs.pop("max_tokens", self._vision_config.max_tokens)
|
||||
params.update(kwargs)
|
||||
|
||||
if self.verbose:
|
||||
self.console.print(f"[cyan]Comparing {len(images)} images with {params['model']}...[/cyan]")
|
||||
|
||||
response = self.litellm.completion(**params)
|
||||
result = response.choices[0].message.content
|
||||
|
||||
if self.verbose:
|
||||
self.console.print("[green]✓ Comparison complete[/green]")
|
||||
|
||||
return result
|
||||
|
||||
def extract_text(
|
||||
self,
|
||||
image: str,
|
||||
detail: Optional[str] = None,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""
|
||||
Extract text from an image (OCR-like functionality).
|
||||
|
||||
Args:
|
||||
image: Image URL or local file path
|
||||
detail: Detail level (low, high, auto) - recommend "high" for text
|
||||
model: Override model for this call
|
||||
**kwargs: Additional parameters
|
||||
|
||||
Returns:
|
||||
Extracted text
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = VisionAgent()
|
||||
text = agent.extract_text("document.png", detail="high")
|
||||
print(text)
|
||||
```
|
||||
"""
|
||||
detail = detail or "high"
|
||||
prompt = """Extract ALL text visible in this image.
|
||||
Preserve the original formatting and structure as much as possible.
|
||||
Include headers, paragraphs, lists, tables, captions, and any other text.
|
||||
If text is unclear, indicate with [unclear].
|
||||
Return ONLY the extracted text, no additional commentary."""
|
||||
|
||||
return self.analyze(image, prompt=prompt, detail=detail, model=model, **kwargs)
|
||||
|
||||
async def aanalyze(
|
||||
self,
|
||||
image: str,
|
||||
prompt: Optional[str] = None,
|
||||
detail: Optional[str] = None,
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Async version of analyze()."""
|
||||
prompt = prompt or "Analyze this image in detail."
|
||||
|
||||
params = self._get_model_params(model)
|
||||
params["messages"] = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt},
|
||||
self._build_image_content(image, detail)
|
||||
]
|
||||
}
|
||||
]
|
||||
params["max_tokens"] = kwargs.pop("max_tokens", self._vision_config.max_tokens)
|
||||
params.update(kwargs)
|
||||
|
||||
response = await self.litellm.acompletion(**params)
|
||||
return response.choices[0].message.content
|
||||
|
||||
async def adescribe(self, image: str, **kwargs) -> str:
|
||||
"""Async version of describe()."""
|
||||
prompt = "Provide a comprehensive description of this image."
|
||||
return await self.aanalyze(image, prompt=prompt, **kwargs)
|
||||
|
||||
async def acompare(self, images: List[str], **kwargs) -> str:
|
||||
"""Async version of compare()."""
|
||||
if len(images) < 2:
|
||||
raise ValueError("At least 2 images are required for comparison")
|
||||
|
||||
prompt = kwargs.pop("prompt", None) or f"Compare these {len(images)} images."
|
||||
detail = kwargs.pop("detail", None)
|
||||
model = kwargs.pop("model", None)
|
||||
|
||||
content = [{"type": "text", "text": prompt}]
|
||||
for img in images:
|
||||
content.append(self._build_image_content(img, detail))
|
||||
|
||||
params = self._get_model_params(model)
|
||||
params["messages"] = [{"role": "user", "content": content}]
|
||||
params["max_tokens"] = kwargs.pop("max_tokens", self._vision_config.max_tokens)
|
||||
params.update(kwargs)
|
||||
|
||||
response = await self.litellm.acompletion(**params)
|
||||
return response.choices[0].message.content
|
||||
|
||||
async def aextract_text(self, image: str, **kwargs) -> str:
|
||||
"""Async version of extract_text()."""
|
||||
kwargs.setdefault("detail", "high")
|
||||
prompt = "Extract ALL text visible in this image. Preserve formatting."
|
||||
return await self.aanalyze(image, prompt=prompt, **kwargs)
|
||||
@@ -1,6 +1,18 @@
|
||||
"""Agents module for managing multiple AI agents"""
|
||||
from .agents import Agents, PraisonAIAgents
|
||||
"""Agents module for managing multiple AI agents.
|
||||
|
||||
AgentManager is the primary class for multi-agent coordination.
|
||||
Agents is a deprecated alias for backward compatibility.
|
||||
"""
|
||||
from .agents import AgentManager, Agents, PraisonAIAgents
|
||||
from .autoagents import AutoAgents
|
||||
from .auto_rag_agent import AutoRagAgent, AutoRagConfig, RetrievalPolicy
|
||||
from .protocols import MergeStrategyProtocol, FirstWinsMerge, ConcatMerge, DictMerge
|
||||
|
||||
__all__ = ['Agents', 'PraisonAIAgents', 'AutoAgents', 'AutoRagAgent', 'AutoRagConfig', 'RetrievalPolicy']
|
||||
__all__ = [
|
||||
'AgentManager', # Primary class (v0.14.16+)
|
||||
'Agents', # Deprecated alias for AgentManager
|
||||
'PraisonAIAgents', # Deprecated alias for AgentManager
|
||||
'AutoAgents',
|
||||
'AutoRagAgent', 'AutoRagConfig', 'RetrievalPolicy',
|
||||
'MergeStrategyProtocol', 'FirstWinsMerge', 'ConcatMerge', 'DictMerge',
|
||||
]
|
||||
@@ -169,7 +169,34 @@ def process_task_context(context_item, verbose=0, user_id=None):
|
||||
else:
|
||||
return str(context_item) # Fallback for unknown types
|
||||
|
||||
class Agents:
|
||||
class AgentManager:
|
||||
"""
|
||||
Multi-agent coordinator that manages and delegates work to multiple agents.
|
||||
|
||||
AgentManager orchestrates the execution of tasks across multiple Agent instances,
|
||||
supporting sequential, parallel, and hierarchical execution patterns.
|
||||
|
||||
Example:
|
||||
from praisonaiagents import Agent, AgentManager, Task
|
||||
|
||||
researcher = Agent(role="Researcher", instructions="Research topics")
|
||||
writer = Agent(role="Writer", instructions="Write content")
|
||||
|
||||
task1 = Task(description="Research AI trends", agent=researcher)
|
||||
task2 = Task(description="Write article", agent=writer)
|
||||
|
||||
manager = AgentManager(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task1, task2],
|
||||
process="sequential"
|
||||
)
|
||||
result = manager.start()
|
||||
|
||||
Note:
|
||||
The class was renamed from `Agents` to `AgentManager` in v0.14.16.
|
||||
`Agents` remains as a deprecated alias for backward compatibility.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
agents,
|
||||
@@ -194,7 +221,7 @@ class Agents:
|
||||
caching: Optional[Any] = None, # Union[bool, CachingConfig] - caching
|
||||
):
|
||||
"""
|
||||
Initialize PraisonAIAgents with consolidated feature parameters.
|
||||
Initialize AgentManager with consolidated feature parameters.
|
||||
|
||||
Args:
|
||||
agents: List of Agent instances
|
||||
@@ -798,8 +825,10 @@ Context:
|
||||
tasks_to_run = []
|
||||
|
||||
# Run sync task in an executor to avoid blocking the event loop
|
||||
# Use copy_context_to_callable to propagate contextvars (needed for trace emission)
|
||||
from ..trace.context_events import copy_context_to_callable
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, self.run_task, task_id)
|
||||
await loop.run_in_executor(None, copy_context_to_callable(lambda tid=task_id: self.run_task(tid)))
|
||||
|
||||
if tasks_to_run:
|
||||
await asyncio.gather(*tasks_to_run)
|
||||
@@ -822,8 +851,10 @@ Context:
|
||||
# Before running a sync task, execute all pending async tasks
|
||||
await flush_async_tasks()
|
||||
# Run sync task in an executor to avoid blocking the event loop
|
||||
# Use copy_context_to_callable to propagate contextvars (needed for trace emission)
|
||||
from ..trace.context_events import copy_context_to_callable
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, self.run_task, task_id)
|
||||
await loop.run_in_executor(None, copy_context_to_callable(lambda tid=task_id: self.run_task(tid)))
|
||||
|
||||
# Execute any remaining async tasks at the end
|
||||
await flush_async_tasks()
|
||||
@@ -835,8 +866,10 @@ Context:
|
||||
await self.arun_task(task_id)
|
||||
else:
|
||||
# Run sync task in an executor to avoid blocking the event loop
|
||||
# Use copy_context_to_callable to propagate contextvars (needed for trace emission)
|
||||
from ..trace.context_events import copy_context_to_callable
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, self.run_task, task_id)
|
||||
await loop.run_in_executor(None, copy_context_to_callable(lambda tid=task_id: self.run_task(tid)))
|
||||
|
||||
async def astart(self, content=None, return_dict=False, **kwargs):
|
||||
"""Async version of start method.
|
||||
@@ -1678,9 +1711,11 @@ Context:
|
||||
response = await agent_instance.achat(current_input, task_name=None, task_description=None, task_id=None)
|
||||
else:
|
||||
# Run sync function in a thread to avoid blocking
|
||||
# Use copy_context_to_callable to propagate contextvars (needed for trace emission)
|
||||
from ..trace.context_events import copy_context_to_callable
|
||||
loop = asyncio.get_running_loop()
|
||||
# Correctly pass current_input to the lambda for closure
|
||||
response = await loop.run_in_executor(None, lambda ci=current_input: agent_instance.chat(ci))
|
||||
response = await loop.run_in_executor(None, copy_context_to_callable(lambda ci=current_input: agent_instance.chat(ci)))
|
||||
|
||||
# Store this agent's result
|
||||
results.append({
|
||||
@@ -1752,8 +1787,10 @@ Context:
|
||||
if asyncio.iscoroutinefunction(agent.chat):
|
||||
response = await agent.achat(query)
|
||||
else:
|
||||
# Use copy_context_to_callable to propagate contextvars (needed for trace emission)
|
||||
from ..trace.context_events import copy_context_to_callable
|
||||
loop = asyncio.get_running_loop()
|
||||
response = await loop.run_in_executor(None, lambda q=query: agent.chat(q))
|
||||
response = await loop.run_in_executor(None, copy_context_to_callable(lambda q=query: agent.chat(q)))
|
||||
|
||||
return {
|
||||
"agent": agent.display_name,
|
||||
@@ -1888,8 +1925,10 @@ Context:
|
||||
if hasattr(agent_instance, 'achat') and asyncio.iscoroutinefunction(agent_instance.achat):
|
||||
response = await agent_instance.achat(current_input, tools=agent_instance.tools, task_name=None, task_description=None, task_id=None)
|
||||
elif hasattr(agent_instance, 'chat'): # Fallback to sync chat if achat not suitable
|
||||
# Use copy_context_to_callable to propagate contextvars (needed for trace emission)
|
||||
from ..trace.context_events import copy_context_to_callable
|
||||
loop = asyncio.get_running_loop()
|
||||
response = await loop.run_in_executor(None, lambda ci=current_input: agent_instance.chat(ci, tools=agent_instance.tools))
|
||||
response = await loop.run_in_executor(None, copy_context_to_callable(lambda ci=current_input: agent_instance.chat(ci, tools=agent_instance.tools)))
|
||||
else:
|
||||
logging.warning(f"Agent {agent_instance.display_name} has no suitable chat or achat method.")
|
||||
response = f"Error: Agent {agent_instance.display_name} has no callable chat method."
|
||||
@@ -2361,6 +2400,7 @@ Context:
|
||||
# Keep plan tasks for results but note original tasks are preserved in _plan_tasks
|
||||
|
||||
|
||||
# Backward compatibility alias - Agents points to Agents
|
||||
Agents = Agents
|
||||
PraisonAIAgents = Agents
|
||||
# Backward compatibility aliases
|
||||
# Agents is deprecated in favor of AgentManager (v0.14.16+)
|
||||
Agents = AgentManager
|
||||
PraisonAIAgents = AgentManager
|
||||
@@ -261,6 +261,23 @@ def register_profile(profile: AgentProfile) -> None:
|
||||
BUILTIN_PROFILES[profile.name] = profile
|
||||
|
||||
|
||||
# Simplified alias
|
||||
add_profile = register_profile
|
||||
|
||||
|
||||
def has_profile(name: str) -> bool:
|
||||
"""Check if a profile exists."""
|
||||
return name in BUILTIN_PROFILES
|
||||
|
||||
|
||||
def remove_profile(name: str) -> bool:
|
||||
"""Remove a profile by name. Returns True if found and removed."""
|
||||
if name in BUILTIN_PROFILES:
|
||||
del BUILTIN_PROFILES[name]
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_profiles_by_mode(mode: AgentMode) -> List[AgentProfile]:
|
||||
"""Get all profiles that can be used in a specific mode."""
|
||||
return [
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""Agent protocols for extensibility."""
|
||||
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class MergeStrategyProtocol(Protocol):
|
||||
"""Protocol for merging outputs from multiple agents.
|
||||
|
||||
Used in parallel execution workflows where multiple agents
|
||||
produce outputs that need to be combined.
|
||||
|
||||
Example:
|
||||
class CustomMerge:
|
||||
def merge(self, outputs, context=None):
|
||||
return max(outputs, key=len) # Return longest output
|
||||
|
||||
# Check protocol compliance
|
||||
assert isinstance(CustomMerge(), MergeStrategyProtocol)
|
||||
"""
|
||||
|
||||
def merge(
|
||||
self,
|
||||
outputs: List[Any],
|
||||
context: Optional[Dict[str, Any]] = None
|
||||
) -> Any:
|
||||
"""Merge multiple agent outputs into one.
|
||||
|
||||
Args:
|
||||
outputs: List of outputs from agents
|
||||
context: Optional context including agent names, etc.
|
||||
|
||||
Returns:
|
||||
Merged output
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class FirstWinsMerge:
|
||||
"""Returns the first non-None output."""
|
||||
|
||||
def merge(
|
||||
self,
|
||||
outputs: List[Any],
|
||||
context: Optional[Dict[str, Any]] = None
|
||||
) -> Any:
|
||||
for output in outputs:
|
||||
if output is not None:
|
||||
return output
|
||||
return None
|
||||
|
||||
|
||||
class ConcatMerge:
|
||||
"""Concatenates all string outputs with a separator."""
|
||||
|
||||
def __init__(self, separator: str = "\n\n"):
|
||||
self.separator = separator
|
||||
|
||||
def merge(
|
||||
self,
|
||||
outputs: List[Any],
|
||||
context: Optional[Dict[str, Any]] = None
|
||||
) -> str:
|
||||
string_outputs = [str(o) for o in outputs if o is not None]
|
||||
return self.separator.join(string_outputs)
|
||||
|
||||
|
||||
class DictMerge:
|
||||
"""Merges dictionary outputs by combining keys.
|
||||
|
||||
Useful when agents return structured data that needs to be combined.
|
||||
Later values override earlier ones for duplicate keys.
|
||||
|
||||
Example:
|
||||
merge = DictMerge()
|
||||
result = merge.merge([
|
||||
{"name": "Alice", "score": 10},
|
||||
{"name": "Bob", "level": 5}
|
||||
])
|
||||
# Result: {"name": "Bob", "score": 10, "level": 5}
|
||||
"""
|
||||
|
||||
def __init__(self, deep: bool = False):
|
||||
"""Initialize DictMerge.
|
||||
|
||||
Args:
|
||||
deep: If True, recursively merge nested dicts
|
||||
"""
|
||||
self.deep = deep
|
||||
|
||||
def merge(
|
||||
self,
|
||||
outputs: List[Any],
|
||||
context: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
result: Dict[str, Any] = {}
|
||||
for output in outputs:
|
||||
if output is None:
|
||||
continue
|
||||
if not isinstance(output, dict):
|
||||
continue
|
||||
if self.deep:
|
||||
result = self._deep_merge(result, output)
|
||||
else:
|
||||
result.update(output)
|
||||
return result
|
||||
|
||||
def _deep_merge(self, base: Dict, update: Dict) -> Dict:
|
||||
"""Recursively merge dictionaries."""
|
||||
result = base.copy()
|
||||
for key, value in update.items():
|
||||
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
||||
result[key] = self._deep_merge(result[key], value)
|
||||
else:
|
||||
result[key] = value
|
||||
return result
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
App module for production deployment of AI agents.
|
||||
|
||||
This module provides the protocol and configuration for AgentApp,
|
||||
a production platform for deploying agents as web services.
|
||||
|
||||
The protocol is defined here in the core SDK (lightweight).
|
||||
The implementation lives in the praisonai wrapper (heavy deps like FastAPI).
|
||||
|
||||
Example:
|
||||
# Protocol is importable from core SDK
|
||||
from praisonaiagents import AgentAppProtocol, AgentAppConfig
|
||||
|
||||
# Implementation is in wrapper
|
||||
from praisonai import AgentApp
|
||||
|
||||
app = AgentApp(
|
||||
name="My App",
|
||||
agents=[researcher, writer],
|
||||
)
|
||||
app.serve(port=8000)
|
||||
"""
|
||||
|
||||
from .protocols import AgentAppProtocol
|
||||
from .config import AgentAppConfig
|
||||
|
||||
__all__ = [
|
||||
'AgentAppProtocol',
|
||||
'AgentAppConfig',
|
||||
]
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
AgentApp configuration.
|
||||
|
||||
This module defines the configuration dataclass for AgentApp.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentAppConfig:
|
||||
"""
|
||||
Configuration for AgentApp.
|
||||
|
||||
This dataclass holds all configuration options for an AgentApp instance.
|
||||
It follows the PraisonAI principle of sensible defaults with explicit overrides.
|
||||
|
||||
Attributes:
|
||||
name: Name of the application (default: "PraisonAI App")
|
||||
host: Host address to bind to (default: "0.0.0.0")
|
||||
port: Port number to listen on (default: 8000)
|
||||
reload: Enable auto-reload for development (default: False)
|
||||
cors_origins: List of allowed CORS origins (default: ["*"])
|
||||
api_prefix: API route prefix (default: "/api")
|
||||
docs_url: URL for API documentation (default: "/docs")
|
||||
openapi_url: URL for OpenAPI schema (default: "/openapi.json")
|
||||
debug: Enable debug mode (default: False)
|
||||
log_level: Logging level (default: "info")
|
||||
workers: Number of worker processes (default: 1)
|
||||
timeout: Request timeout in seconds (default: 60)
|
||||
metadata: Additional metadata for the app
|
||||
|
||||
Example:
|
||||
config = AgentAppConfig(
|
||||
name="My AI App",
|
||||
port=9000,
|
||||
reload=True,
|
||||
debug=True
|
||||
)
|
||||
"""
|
||||
|
||||
name: str = "PraisonAI App"
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 8000
|
||||
reload: bool = False
|
||||
cors_origins: List[str] = field(default_factory=lambda: ["*"])
|
||||
api_prefix: str = "/api"
|
||||
docs_url: str = "/docs"
|
||||
openapi_url: str = "/openapi.json"
|
||||
debug: bool = False
|
||||
log_level: str = "info"
|
||||
workers: int = 1
|
||||
timeout: int = 60
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
AgentApp protocol definitions.
|
||||
|
||||
This module defines the protocol (interface) for AgentApp implementations.
|
||||
The protocol is lightweight and lives in the core SDK.
|
||||
Implementations live in the praisonai wrapper.
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Protocol, runtime_checkable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..agent.agent import Agent
|
||||
from ..agents.agents import AgentManager
|
||||
from ..workflows.workflows import Workflow
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AgentAppProtocol(Protocol):
|
||||
"""
|
||||
Protocol for AgentApp implementations.
|
||||
|
||||
AgentApp is a production platform for deploying agents as web services.
|
||||
It wraps agents, managers, and workflows into a unified API server.
|
||||
|
||||
Implementations should:
|
||||
- Provide FastAPI-based HTTP endpoints
|
||||
- Support WebSocket connections for real-time communication
|
||||
- Handle agent lifecycle management
|
||||
- Provide health checks and monitoring endpoints
|
||||
|
||||
Example implementation (in praisonai wrapper):
|
||||
class AgentApp:
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "PraisonAI App",
|
||||
agents: List[Agent] = None,
|
||||
managers: List[AgentManager] = None,
|
||||
workflows: List[Workflow] = None,
|
||||
):
|
||||
...
|
||||
|
||||
def serve(self, host: str = "0.0.0.0", port: int = 8000):
|
||||
'''Start the FastAPI server.'''
|
||||
...
|
||||
|
||||
def get_app(self):
|
||||
'''Get the FastAPI app instance for custom mounting.'''
|
||||
...
|
||||
"""
|
||||
|
||||
def serve(
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
reload: bool = False,
|
||||
**kwargs: Any
|
||||
) -> None:
|
||||
"""
|
||||
Start the AgentApp server.
|
||||
|
||||
Args:
|
||||
host: Host address to bind to (default: "0.0.0.0")
|
||||
port: Port number to listen on (default: 8000)
|
||||
reload: Enable auto-reload for development (default: False)
|
||||
**kwargs: Additional server configuration
|
||||
"""
|
||||
...
|
||||
|
||||
def get_app(self) -> Any:
|
||||
"""
|
||||
Get the underlying web application instance.
|
||||
|
||||
Returns:
|
||||
The FastAPI application instance for custom mounting or configuration.
|
||||
"""
|
||||
...
|
||||
@@ -11,6 +11,7 @@ import json
|
||||
import os
|
||||
from typing import Dict, Set, Optional, Callable, Any, Literal, List
|
||||
from functools import wraps
|
||||
import contextvars
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@@ -56,6 +57,9 @@ approval_callback: Optional[Callable] = None
|
||||
# Context variable to track if we're in an approved execution context
|
||||
_approved_context: ContextVar[Set[str]] = ContextVar('approved_context', default=set())
|
||||
|
||||
# Context variable for YAML-defined auto-approved tools (from agents.yaml approve field)
|
||||
_yaml_approved_tools: ContextVar[Set[str]] = ContextVar('yaml_approved_tools', default=set())
|
||||
|
||||
# Global permission allowlist
|
||||
_permission_allowlist: Optional["PermissionAllowlist"] = None
|
||||
|
||||
@@ -257,6 +261,41 @@ def is_already_approved(tool_name: str) -> bool:
|
||||
approved = _approved_context.get(set())
|
||||
return tool_name in approved
|
||||
|
||||
|
||||
def is_yaml_approved(tool_name: str) -> bool:
|
||||
"""Check if a tool is auto-approved via YAML approve field."""
|
||||
try:
|
||||
yaml_approved = _yaml_approved_tools.get()
|
||||
return tool_name in yaml_approved
|
||||
except LookupError:
|
||||
return False
|
||||
|
||||
|
||||
def is_env_auto_approve() -> bool:
|
||||
"""Check if PRAISONAI_AUTO_APPROVE environment variable is set."""
|
||||
return os.environ.get("PRAISONAI_AUTO_APPROVE", "").lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
def set_yaml_approved_tools(tools: List[str]) -> contextvars.Token:
|
||||
"""
|
||||
Set the list of YAML-approved tools for the current context.
|
||||
|
||||
This is called by the workflow runner when parsing agents.yaml with approve field.
|
||||
|
||||
Args:
|
||||
tools: List of tool names to auto-approve
|
||||
|
||||
Returns:
|
||||
Token that can be used to reset the context
|
||||
"""
|
||||
return _yaml_approved_tools.set(set(tools))
|
||||
|
||||
|
||||
def reset_yaml_approved_tools(token: contextvars.Token) -> None:
|
||||
"""Reset YAML-approved tools to previous state."""
|
||||
_yaml_approved_tools.reset(token)
|
||||
|
||||
|
||||
def clear_approval_context():
|
||||
"""Clear the approval context."""
|
||||
_approved_context.set(set())
|
||||
@@ -278,6 +317,16 @@ def require_approval(risk_level: RiskLevel = "high"):
|
||||
if is_already_approved(tool_name):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# Skip approval if tool is auto-approved via YAML approve field (primary)
|
||||
if is_yaml_approved(tool_name):
|
||||
mark_approved(tool_name)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# Skip approval if PRAISONAI_AUTO_APPROVE env var is set (secondary)
|
||||
if is_env_auto_approve():
|
||||
mark_approved(tool_name)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# Request approval before executing the function
|
||||
try:
|
||||
# Try to check if we're in an async context
|
||||
@@ -310,6 +359,16 @@ def require_approval(risk_level: RiskLevel = "high"):
|
||||
if is_already_approved(tool_name):
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
# Skip approval if tool is auto-approved via YAML approve field (primary)
|
||||
if is_yaml_approved(tool_name):
|
||||
mark_approved(tool_name)
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
# Skip approval if PRAISONAI_AUTO_APPROVE env var is set (secondary)
|
||||
if is_env_auto_approve():
|
||||
mark_approved(tool_name)
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
# Request approval before executing the function
|
||||
decision = await request_approval(tool_name, kwargs)
|
||||
if not decision.approved:
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Bot Protocols for PraisonAI Agents.
|
||||
|
||||
Provides protocols and base classes for building messaging bot implementations
|
||||
(Telegram, Discord, Slack, etc.) that connect agents to messaging platforms.
|
||||
|
||||
This module contains only protocols and lightweight utilities.
|
||||
Heavy implementations live in the praisonai wrapper package.
|
||||
"""
|
||||
|
||||
from .protocols import (
|
||||
BotProtocol,
|
||||
BotMessageProtocol,
|
||||
BotUserProtocol,
|
||||
BotChannelProtocol,
|
||||
BotMessage,
|
||||
BotUser,
|
||||
BotChannel,
|
||||
MessageType,
|
||||
)
|
||||
from .config import BotConfig
|
||||
|
||||
__all__ = [
|
||||
"BotProtocol",
|
||||
"BotMessageProtocol",
|
||||
"BotUserProtocol",
|
||||
"BotChannelProtocol",
|
||||
"BotMessage",
|
||||
"BotUser",
|
||||
"BotChannel",
|
||||
"BotConfig",
|
||||
"MessageType",
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Bot Configuration for PraisonAI Agents.
|
||||
|
||||
Provides configuration dataclasses for bot settings.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class BotConfig:
|
||||
"""Configuration for messaging bots.
|
||||
|
||||
Attributes:
|
||||
token: Bot authentication token
|
||||
webhook_url: URL for webhook mode (optional)
|
||||
webhook_path: Path for webhook endpoint
|
||||
polling_interval: Interval for polling mode (seconds)
|
||||
allowed_users: List of allowed user IDs (empty = all allowed)
|
||||
allowed_channels: List of allowed channel IDs (empty = all allowed)
|
||||
command_prefix: Prefix for commands (default: "/")
|
||||
mention_required: Whether bot mention is required in groups
|
||||
typing_indicator: Whether to show typing indicator
|
||||
max_message_length: Maximum message length before splitting
|
||||
retry_attempts: Number of retry attempts for failed operations
|
||||
timeout: Request timeout in seconds
|
||||
metadata: Additional platform-specific configuration
|
||||
"""
|
||||
|
||||
token: str = ""
|
||||
webhook_url: Optional[str] = None
|
||||
webhook_path: str = "/webhook"
|
||||
polling_interval: float = 1.0
|
||||
allowed_users: List[str] = field(default_factory=list)
|
||||
allowed_channels: List[str] = field(default_factory=list)
|
||||
command_prefix: str = "/"
|
||||
mention_required: bool = True
|
||||
typing_indicator: bool = True
|
||||
max_message_length: int = 4096
|
||||
retry_attempts: int = 3
|
||||
timeout: int = 30
|
||||
reply_in_thread: bool = False # Default to inline, set True for thread replies
|
||||
thread_threshold: int = 500 # Auto-thread responses longer than this (0 = disabled)
|
||||
|
||||
# Default tools enabled for all bots
|
||||
default_tools: List[str] = field(default_factory=lambda: ["execute_command", "search_web"])
|
||||
|
||||
# Auto-approve tool calls (useful for trusted environments)
|
||||
auto_approve_tools: bool = False # If True, skip confirmation for tool execution
|
||||
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary (hides sensitive data)."""
|
||||
return {
|
||||
"token": "***" if self.token else None,
|
||||
"webhook_url": self.webhook_url,
|
||||
"webhook_path": self.webhook_path,
|
||||
"polling_interval": self.polling_interval,
|
||||
"allowed_users": self.allowed_users,
|
||||
"allowed_channels": self.allowed_channels,
|
||||
"command_prefix": self.command_prefix,
|
||||
"mention_required": self.mention_required,
|
||||
"typing_indicator": self.typing_indicator,
|
||||
"max_message_length": self.max_message_length,
|
||||
"retry_attempts": self.retry_attempts,
|
||||
"timeout": self.timeout,
|
||||
"reply_in_thread": self.reply_in_thread,
|
||||
"thread_threshold": self.thread_threshold,
|
||||
"default_tools": self.default_tools,
|
||||
"auto_approve_tools": self.auto_approve_tools,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
|
||||
@property
|
||||
def is_webhook_mode(self) -> bool:
|
||||
"""Whether bot is configured for webhook mode."""
|
||||
return bool(self.webhook_url)
|
||||
|
||||
def is_user_allowed(self, user_id: str) -> bool:
|
||||
"""Check if a user is allowed to interact with the bot."""
|
||||
if not self.allowed_users:
|
||||
return True
|
||||
return user_id in self.allowed_users
|
||||
|
||||
def is_channel_allowed(self, channel_id: str) -> bool:
|
||||
"""Check if a channel is allowed for bot interaction."""
|
||||
if not self.allowed_channels:
|
||||
return True
|
||||
return channel_id in self.allowed_channels
|
||||
@@ -0,0 +1,453 @@
|
||||
"""
|
||||
Bot Protocols for PraisonAI Agents.
|
||||
|
||||
Defines the interfaces for messaging bot implementations.
|
||||
These protocols enable agents to communicate through messaging platforms
|
||||
like Telegram, Discord, Slack, etc.
|
||||
|
||||
All implementations should live in the praisonai wrapper package.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Protocol,
|
||||
Union,
|
||||
runtime_checkable,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..agent import Agent
|
||||
|
||||
|
||||
class MessageType(str, Enum):
|
||||
"""Types of bot messages."""
|
||||
|
||||
TEXT = "text"
|
||||
IMAGE = "image"
|
||||
AUDIO = "audio"
|
||||
VIDEO = "video"
|
||||
FILE = "file"
|
||||
LOCATION = "location"
|
||||
STICKER = "sticker"
|
||||
COMMAND = "command"
|
||||
CALLBACK = "callback"
|
||||
REACTION = "reaction"
|
||||
REPLY = "reply"
|
||||
EDIT = "edit"
|
||||
DELETE = "delete"
|
||||
|
||||
|
||||
@dataclass
|
||||
class BotUser:
|
||||
"""Represents a user in a messaging platform.
|
||||
|
||||
Attributes:
|
||||
user_id: Platform-specific user identifier
|
||||
username: User's username (if available)
|
||||
display_name: User's display name
|
||||
is_bot: Whether this user is a bot
|
||||
metadata: Additional platform-specific metadata
|
||||
"""
|
||||
|
||||
user_id: str
|
||||
username: Optional[str] = None
|
||||
display_name: Optional[str] = None
|
||||
is_bot: bool = False
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"user_id": self.user_id,
|
||||
"username": self.username,
|
||||
"display_name": self.display_name,
|
||||
"is_bot": self.is_bot,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "BotUser":
|
||||
"""Create from dictionary."""
|
||||
return cls(
|
||||
user_id=data.get("user_id", "unknown"),
|
||||
username=data.get("username"),
|
||||
display_name=data.get("display_name"),
|
||||
is_bot=data.get("is_bot", False),
|
||||
metadata=data.get("metadata", {}),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BotChannel:
|
||||
"""Represents a channel/chat in a messaging platform.
|
||||
|
||||
Attributes:
|
||||
channel_id: Platform-specific channel identifier
|
||||
name: Channel name (if available)
|
||||
channel_type: Type of channel (dm, group, channel, thread)
|
||||
metadata: Additional platform-specific metadata
|
||||
"""
|
||||
|
||||
channel_id: str
|
||||
name: Optional[str] = None
|
||||
channel_type: str = "dm" # dm, group, channel, thread
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"channel_id": self.channel_id,
|
||||
"name": self.name,
|
||||
"channel_type": self.channel_type,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "BotChannel":
|
||||
"""Create from dictionary."""
|
||||
return cls(
|
||||
channel_id=data.get("channel_id", "unknown"),
|
||||
name=data.get("name"),
|
||||
channel_type=data.get("channel_type", "dm"),
|
||||
metadata=data.get("metadata", {}),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BotMessage:
|
||||
"""Represents a message in a messaging platform.
|
||||
|
||||
Attributes:
|
||||
message_id: Platform-specific message identifier
|
||||
content: Message content (text or structured data)
|
||||
message_type: Type of message
|
||||
sender: User who sent the message
|
||||
channel: Channel where the message was sent
|
||||
timestamp: Message timestamp
|
||||
reply_to: ID of message being replied to
|
||||
thread_id: Thread identifier (for threaded conversations)
|
||||
attachments: List of attachment URLs or data
|
||||
metadata: Additional platform-specific metadata
|
||||
"""
|
||||
|
||||
message_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
content: Union[str, Dict[str, Any]] = ""
|
||||
message_type: MessageType = MessageType.TEXT
|
||||
sender: Optional[BotUser] = None
|
||||
channel: Optional[BotChannel] = None
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
reply_to: Optional[str] = None
|
||||
thread_id: Optional[str] = None
|
||||
attachments: List[Dict[str, Any]] = field(default_factory=list)
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"message_id": self.message_id,
|
||||
"content": self.content,
|
||||
"message_type": self.message_type.value if isinstance(self.message_type, MessageType) else self.message_type,
|
||||
"sender": self.sender.to_dict() if self.sender else None,
|
||||
"channel": self.channel.to_dict() if self.channel else None,
|
||||
"timestamp": self.timestamp,
|
||||
"reply_to": self.reply_to,
|
||||
"thread_id": self.thread_id,
|
||||
"attachments": self.attachments,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "BotMessage":
|
||||
"""Create from dictionary."""
|
||||
msg_type = data.get("message_type", "text")
|
||||
try:
|
||||
msg_type = MessageType(msg_type)
|
||||
except ValueError:
|
||||
msg_type = MessageType.TEXT
|
||||
|
||||
sender_data = data.get("sender")
|
||||
channel_data = data.get("channel")
|
||||
|
||||
return cls(
|
||||
message_id=data.get("message_id", str(uuid.uuid4())),
|
||||
content=data.get("content", ""),
|
||||
message_type=msg_type,
|
||||
sender=BotUser.from_dict(sender_data) if sender_data else None,
|
||||
channel=BotChannel.from_dict(channel_data) if channel_data else None,
|
||||
timestamp=data.get("timestamp", time.time()),
|
||||
reply_to=data.get("reply_to"),
|
||||
thread_id=data.get("thread_id"),
|
||||
attachments=data.get("attachments", []),
|
||||
metadata=data.get("metadata", {}),
|
||||
)
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
"""Get message text content."""
|
||||
if isinstance(self.content, str):
|
||||
return self.content
|
||||
return self.content.get("text", "")
|
||||
|
||||
@property
|
||||
def is_command(self) -> bool:
|
||||
"""Check if message is a command."""
|
||||
return self.message_type == MessageType.COMMAND or (
|
||||
isinstance(self.content, str) and self.content.startswith("/")
|
||||
)
|
||||
|
||||
@property
|
||||
def command(self) -> Optional[str]:
|
||||
"""Extract command name if this is a command message."""
|
||||
if not self.is_command:
|
||||
return None
|
||||
text = self.text
|
||||
if text.startswith("/"):
|
||||
return text.split()[0][1:] # Remove "/" and get first word
|
||||
return None
|
||||
|
||||
@property
|
||||
def command_args(self) -> List[str]:
|
||||
"""Extract command arguments if this is a command message."""
|
||||
if not self.is_command:
|
||||
return []
|
||||
text = self.text
|
||||
parts = text.split()
|
||||
return parts[1:] if len(parts) > 1 else []
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class BotMessageProtocol(Protocol):
|
||||
"""Protocol for bot message handling."""
|
||||
|
||||
@property
|
||||
def message_id(self) -> str:
|
||||
"""Unique message identifier."""
|
||||
...
|
||||
|
||||
@property
|
||||
def content(self) -> Union[str, Dict[str, Any]]:
|
||||
"""Message content."""
|
||||
...
|
||||
|
||||
@property
|
||||
def sender(self) -> Optional[BotUser]:
|
||||
"""Message sender."""
|
||||
...
|
||||
|
||||
@property
|
||||
def channel(self) -> Optional[BotChannel]:
|
||||
"""Message channel."""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class BotUserProtocol(Protocol):
|
||||
"""Protocol for bot user representation."""
|
||||
|
||||
@property
|
||||
def user_id(self) -> str:
|
||||
"""Unique user identifier."""
|
||||
...
|
||||
|
||||
@property
|
||||
def username(self) -> Optional[str]:
|
||||
"""User's username."""
|
||||
...
|
||||
|
||||
@property
|
||||
def is_bot(self) -> bool:
|
||||
"""Whether this user is a bot."""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class BotChannelProtocol(Protocol):
|
||||
"""Protocol for bot channel representation."""
|
||||
|
||||
@property
|
||||
def channel_id(self) -> str:
|
||||
"""Unique channel identifier."""
|
||||
...
|
||||
|
||||
@property
|
||||
def channel_type(self) -> str:
|
||||
"""Type of channel (dm, group, channel, thread)."""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class BotProtocol(Protocol):
|
||||
"""Protocol for messaging bot implementations.
|
||||
|
||||
Bots connect agents to messaging platforms, handling:
|
||||
- Message receiving and sending
|
||||
- Command handling
|
||||
- Webhook/polling management
|
||||
- User and channel management
|
||||
|
||||
Example usage (implementation in praisonai wrapper):
|
||||
from praisonai.bots import TelegramBot
|
||||
|
||||
bot = TelegramBot(token="...", agent=my_agent)
|
||||
await bot.start()
|
||||
"""
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""Whether the bot is currently running."""
|
||||
...
|
||||
|
||||
@property
|
||||
def platform(self) -> str:
|
||||
"""Platform name (telegram, discord, slack, etc.)."""
|
||||
...
|
||||
|
||||
@property
|
||||
def bot_user(self) -> Optional[BotUser]:
|
||||
"""The bot's user information."""
|
||||
...
|
||||
|
||||
# Lifecycle methods
|
||||
async def start(self) -> None:
|
||||
"""Start the bot (begin receiving messages)."""
|
||||
...
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Stop the bot."""
|
||||
...
|
||||
|
||||
# Agent management
|
||||
def set_agent(self, agent: "Agent") -> None:
|
||||
"""Set the agent that handles messages.
|
||||
|
||||
Args:
|
||||
agent: The agent to handle incoming messages
|
||||
"""
|
||||
...
|
||||
|
||||
def get_agent(self) -> Optional["Agent"]:
|
||||
"""Get the current agent."""
|
||||
...
|
||||
|
||||
# Message handling
|
||||
async def send_message(
|
||||
self,
|
||||
channel_id: str,
|
||||
content: Union[str, Dict[str, Any]],
|
||||
reply_to: Optional[str] = None,
|
||||
thread_id: Optional[str] = None,
|
||||
) -> BotMessage:
|
||||
"""Send a message to a channel.
|
||||
|
||||
Args:
|
||||
channel_id: Target channel ID
|
||||
content: Message content
|
||||
reply_to: Optional message ID to reply to
|
||||
thread_id: Optional thread ID for threaded replies
|
||||
|
||||
Returns:
|
||||
The sent message
|
||||
"""
|
||||
...
|
||||
|
||||
async def edit_message(
|
||||
self,
|
||||
channel_id: str,
|
||||
message_id: str,
|
||||
content: Union[str, Dict[str, Any]],
|
||||
) -> BotMessage:
|
||||
"""Edit an existing message.
|
||||
|
||||
Args:
|
||||
channel_id: Channel containing the message
|
||||
message_id: ID of message to edit
|
||||
content: New message content
|
||||
|
||||
Returns:
|
||||
The edited message
|
||||
"""
|
||||
...
|
||||
|
||||
async def delete_message(
|
||||
self,
|
||||
channel_id: str,
|
||||
message_id: str,
|
||||
) -> bool:
|
||||
"""Delete a message.
|
||||
|
||||
Args:
|
||||
channel_id: Channel containing the message
|
||||
message_id: ID of message to delete
|
||||
|
||||
Returns:
|
||||
True if deleted successfully
|
||||
"""
|
||||
...
|
||||
|
||||
# Event handlers
|
||||
def on_message(self, handler: Callable[[BotMessage], Any]) -> Callable:
|
||||
"""Register a message handler.
|
||||
|
||||
Args:
|
||||
handler: Function to call when a message is received
|
||||
|
||||
Returns:
|
||||
The handler function (for decorator use)
|
||||
"""
|
||||
...
|
||||
|
||||
def on_command(self, command: str) -> Callable:
|
||||
"""Decorator to register a command handler.
|
||||
|
||||
Args:
|
||||
command: Command name (without /)
|
||||
|
||||
Example:
|
||||
@bot.on_command("help")
|
||||
async def handle_help(message: BotMessage):
|
||||
await bot.send_message(message.channel.channel_id, "Help text...")
|
||||
"""
|
||||
...
|
||||
|
||||
# Typing indicator
|
||||
async def send_typing(self, channel_id: str) -> None:
|
||||
"""Send typing indicator to a channel.
|
||||
|
||||
Args:
|
||||
channel_id: Target channel ID
|
||||
"""
|
||||
...
|
||||
|
||||
# User/channel info
|
||||
async def get_user(self, user_id: str) -> Optional[BotUser]:
|
||||
"""Get user information.
|
||||
|
||||
Args:
|
||||
user_id: User ID to look up
|
||||
|
||||
Returns:
|
||||
User information or None if not found
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_channel(self, channel_id: str) -> Optional[BotChannel]:
|
||||
"""Get channel information.
|
||||
|
||||
Args:
|
||||
channel_id: Channel ID to look up
|
||||
|
||||
Returns:
|
||||
Channel information or None if not found
|
||||
"""
|
||||
...
|
||||
@@ -252,11 +252,14 @@ class ContextCompactor:
|
||||
role = msg.get("role", "")
|
||||
content = msg.get("content", "")
|
||||
|
||||
# If this is a tool result, truncate it
|
||||
# If this is a tool result, truncate it (use smart format)
|
||||
if role == "tool" or msg.get("tool_call_id"):
|
||||
if isinstance(content, str) and len(content) > 500:
|
||||
pruned_msg = msg.copy()
|
||||
pruned_msg["content"] = content[:200] + "\n...[output truncated]..."
|
||||
tail_size = min(100, len(content) // 5)
|
||||
head = content[:200]
|
||||
tail = content[-tail_size:] if tail_size > 0 else ""
|
||||
pruned_msg["content"] = f"{head}\n...[{len(content):,} chars, showing first/last portions]...\n{tail}"
|
||||
result.append(pruned_msg)
|
||||
else:
|
||||
result.append(msg)
|
||||
|
||||
@@ -29,6 +29,7 @@ __all__ = [
|
||||
"MemoryConfig",
|
||||
"KnowledgeConfig",
|
||||
"PlanningConfig",
|
||||
"MultiAgentPlanningConfig",
|
||||
"ReflectionConfig",
|
||||
"GuardrailConfig",
|
||||
"WebConfig",
|
||||
@@ -91,6 +92,7 @@ _MODULE_MAP = {
|
||||
"MemoryConfig": "feature_configs",
|
||||
"KnowledgeConfig": "feature_configs",
|
||||
"PlanningConfig": "feature_configs",
|
||||
"MultiAgentPlanningConfig": "feature_configs",
|
||||
"ReflectionConfig": "feature_configs",
|
||||
"GuardrailConfig": "feature_configs",
|
||||
"WebConfig": "feature_configs",
|
||||
|
||||
@@ -557,6 +557,15 @@ class OutputConfig:
|
||||
# Shows: [timestamp] Calling LLM..., Executing tool..., Response: ...
|
||||
status_trace: bool = False
|
||||
|
||||
# Output file - save agent response to file automatically
|
||||
# When set, saves the response to the specified file path
|
||||
output_file: Optional[str] = None
|
||||
|
||||
# Output template - format template for the response
|
||||
# Agent will be instructed to follow this template when generating response
|
||||
# Example: "# {{title}}\n\n{{content}}\n\n---\nGenerated by AI"
|
||||
template: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
@@ -570,6 +579,8 @@ class OutputConfig:
|
||||
"simple_output": self.simple_output,
|
||||
"show_parameters": self.show_parameters,
|
||||
"status_trace": self.status_trace,
|
||||
"output_file": self.output_file,
|
||||
"template": self.template,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -671,6 +671,119 @@ def resolve_guardrails(value: Any, config_class: Type) -> Any:
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Batch Resolution for Performance
|
||||
# =============================================================================
|
||||
|
||||
def resolve_batch(
|
||||
params: dict,
|
||||
presets_map: dict,
|
||||
config_classes: dict,
|
||||
defaults: dict = None,
|
||||
) -> dict:
|
||||
"""
|
||||
Resolve multiple parameters in a single batch call for performance.
|
||||
|
||||
This reduces function call overhead by processing all parameters together
|
||||
instead of making 11+ separate resolve() calls.
|
||||
|
||||
Args:
|
||||
params: Dict of {param_name: value} to resolve
|
||||
presets_map: Dict of {param_name: presets_dict}
|
||||
config_classes: Dict of {param_name: config_class}
|
||||
defaults: Dict of {param_name: default_value}
|
||||
|
||||
Returns:
|
||||
Dict of {param_name: resolved_value}
|
||||
|
||||
Example:
|
||||
results = resolve_batch(
|
||||
params={
|
||||
'output': 'silent',
|
||||
'execution': None,
|
||||
'memory': True,
|
||||
},
|
||||
presets_map={
|
||||
'output': OUTPUT_PRESETS,
|
||||
'execution': EXECUTION_PRESETS,
|
||||
'memory': MEMORY_PRESETS,
|
||||
},
|
||||
config_classes={
|
||||
'output': OutputConfig,
|
||||
'execution': ExecutionConfig,
|
||||
'memory': MemoryConfig,
|
||||
},
|
||||
defaults={
|
||||
'output': OutputConfig(),
|
||||
'execution': ExecutionConfig(),
|
||||
'memory': None,
|
||||
}
|
||||
)
|
||||
"""
|
||||
defaults = defaults or {}
|
||||
results = {}
|
||||
|
||||
for param_name, value in params.items():
|
||||
config_class = config_classes.get(param_name)
|
||||
presets = presets_map.get(param_name)
|
||||
default = defaults.get(param_name)
|
||||
|
||||
# Fast path: None -> default
|
||||
if value is None:
|
||||
results[param_name] = default
|
||||
continue
|
||||
|
||||
# Fast path: already a config instance
|
||||
if config_class and isinstance(value, config_class):
|
||||
results[param_name] = value
|
||||
continue
|
||||
|
||||
# Fast path: bool handling
|
||||
if isinstance(value, bool):
|
||||
if value:
|
||||
results[param_name] = config_class() if config_class else True
|
||||
else:
|
||||
results[param_name] = None
|
||||
continue
|
||||
|
||||
# Fast path: string preset lookup
|
||||
if isinstance(value, str) and presets:
|
||||
value_lower = value.lower()
|
||||
for preset_key in presets:
|
||||
if preset_key.lower() == value_lower:
|
||||
preset_value = presets[preset_key]
|
||||
if preset_value is None:
|
||||
results[param_name] = None
|
||||
elif config_class and isinstance(preset_value, config_class):
|
||||
results[param_name] = preset_value
|
||||
elif isinstance(preset_value, dict) and config_class:
|
||||
results[param_name] = config_class(**preset_value)
|
||||
else:
|
||||
results[param_name] = preset_value
|
||||
break
|
||||
else:
|
||||
# Not a preset - use full resolve for error handling
|
||||
results[param_name] = resolve(
|
||||
value=value,
|
||||
param_name=param_name,
|
||||
config_class=config_class,
|
||||
presets=presets,
|
||||
default=default,
|
||||
)
|
||||
continue
|
||||
|
||||
# Fallback to full resolve for complex cases
|
||||
results[param_name] = resolve(
|
||||
value=value,
|
||||
param_name=param_name,
|
||||
config_class=config_class,
|
||||
presets=presets,
|
||||
default=default,
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def resolve_guardrail_policies(
|
||||
policies: list,
|
||||
config_class: Type,
|
||||
|
||||
@@ -99,6 +99,7 @@ __all__ = [
|
||||
"PruneToolsOptimizer",
|
||||
"NonDestructiveOptimizer",
|
||||
"SummarizeOptimizer",
|
||||
"LLMSummarizeOptimizer",
|
||||
"SmartOptimizer",
|
||||
"get_optimizer",
|
||||
"get_effective_history",
|
||||
@@ -125,6 +126,7 @@ __all__ = [
|
||||
"EstimationMetrics",
|
||||
"PerToolBudget",
|
||||
"SnapshotHookData",
|
||||
"SessionDeduplicationCache",
|
||||
# Protocols (NEW)
|
||||
"ContextView",
|
||||
"ContextMutator",
|
||||
@@ -203,7 +205,7 @@ def __getattr__(name: str):
|
||||
# Optimization
|
||||
if name in ("BaseOptimizer", "TruncateOptimizer", "SlidingWindowOptimizer",
|
||||
"PruneToolsOptimizer", "NonDestructiveOptimizer", "SummarizeOptimizer",
|
||||
"SmartOptimizer", "get_optimizer"):
|
||||
"LLMSummarizeOptimizer", "SmartOptimizer", "get_optimizer"):
|
||||
from . import optimizer
|
||||
return getattr(optimizer, name)
|
||||
|
||||
@@ -218,7 +220,8 @@ def __getattr__(name: str):
|
||||
if name in ("ContextManager", "MultiAgentContextManager", "create_context_manager",
|
||||
"ManagerConfig", "ContextPolicy", "EstimationMode", "ContextShareMode",
|
||||
"ToolShareMode", "OptimizationEvent", "OptimizationEventType",
|
||||
"EstimationMetrics", "PerToolBudget", "SnapshotHookData"):
|
||||
"EstimationMetrics", "PerToolBudget", "SnapshotHookData",
|
||||
"SessionDeduplicationCache"):
|
||||
from . import manager
|
||||
return getattr(manager, name)
|
||||
|
||||
|
||||
@@ -182,7 +182,11 @@ class ContextComposer:
|
||||
|
||||
# Truncate to max tokens (rough: 4 chars per token)
|
||||
max_chars = self.max_tool_output_tokens * 4
|
||||
truncated = content[:max_chars] + "\n...[output truncated]..."
|
||||
# Use smart truncation format that judge recognizes as OK
|
||||
tail_chars = min(max_chars // 5, 1000) # Keep ~20% or 1000 chars from end
|
||||
head = content[:max_chars - tail_chars]
|
||||
tail = content[-tail_chars:] if tail_chars > 0 else ""
|
||||
truncated = f"{head}\n...[{len(content):,} chars, showing first/last portions]...\n{tail}"
|
||||
|
||||
result = msg.copy()
|
||||
result["content"] = truncated
|
||||
|
||||
@@ -46,7 +46,11 @@ class ContextMetrics:
|
||||
# Token tracking
|
||||
tokens_processed: int = 0
|
||||
tokens_saved: int = 0
|
||||
tokens_saved_by_summarization: int = 0 # Tokens saved by LLM summarization
|
||||
tokens_saved_by_truncation: int = 0 # Tokens saved by truncation
|
||||
compactions_triggered: int = 0
|
||||
tool_outputs_summarized: int = 0 # Count of tool outputs summarized
|
||||
tool_outputs_truncated: int = 0 # Count of tool outputs truncated
|
||||
|
||||
# Cache stats
|
||||
cache_hits: int = 0
|
||||
@@ -76,8 +80,14 @@ class ContextMetrics:
|
||||
"tokens": {
|
||||
"processed": self.tokens_processed,
|
||||
"saved": self.tokens_saved,
|
||||
"saved_by_summarization": self.tokens_saved_by_summarization,
|
||||
"saved_by_truncation": self.tokens_saved_by_truncation,
|
||||
"compactions": self.compactions_triggered,
|
||||
},
|
||||
"tool_outputs": {
|
||||
"summarized": self.tool_outputs_summarized,
|
||||
"truncated": self.tool_outputs_truncated,
|
||||
},
|
||||
"cache": {
|
||||
"hits": self.cache_hits,
|
||||
"misses": self.cache_misses,
|
||||
|
||||
@@ -26,13 +26,133 @@ from enum import Enum
|
||||
from .models import (
|
||||
ContextLedger, BudgetAllocation, OptimizerStrategy, OptimizationResult
|
||||
)
|
||||
from .tokens import estimate_tokens_heuristic, estimate_messages_tokens
|
||||
from .tokens import estimate_tokens_heuristic, estimate_messages_tokens, estimate_message_tokens
|
||||
from .budgeter import ContextBudgeter
|
||||
from .ledger import ContextLedgerManager, MultiAgentLedger
|
||||
from .optimizer import get_optimizer
|
||||
from .monitor import ContextMonitor, MultiAgentMonitor
|
||||
|
||||
|
||||
class SessionDeduplicationCache:
|
||||
"""
|
||||
Thread-safe session-level content deduplication cache.
|
||||
|
||||
Tracks content hashes across all agents in a workflow session
|
||||
to prevent duplicate content from being sent to LLM.
|
||||
"""
|
||||
|
||||
def __init__(self, max_size: int = 1000):
|
||||
"""
|
||||
Initialize session deduplication cache.
|
||||
|
||||
Args:
|
||||
max_size: Maximum number of hashes to cache (LRU eviction)
|
||||
"""
|
||||
self._cache: Dict[str, str] = {} # hash -> agent_name
|
||||
self._lock = threading.Lock()
|
||||
self._max_size = max_size
|
||||
self._stats = {"duplicates_prevented": 0, "tokens_saved": 0}
|
||||
|
||||
def check_and_add(self, content_hash: str, agent_name: str, tokens: int = 0) -> bool:
|
||||
"""
|
||||
Check if content hash exists and add if new.
|
||||
|
||||
Args:
|
||||
content_hash: Hash of the content
|
||||
agent_name: Name of the agent adding this content
|
||||
tokens: Estimated tokens in this content
|
||||
|
||||
Returns:
|
||||
True if duplicate (already exists), False if new
|
||||
"""
|
||||
with self._lock:
|
||||
if content_hash in self._cache:
|
||||
self._stats["duplicates_prevented"] += 1
|
||||
self._stats["tokens_saved"] += tokens
|
||||
return True # Duplicate
|
||||
|
||||
# LRU eviction if at capacity
|
||||
if len(self._cache) >= self._max_size:
|
||||
# Remove oldest entry
|
||||
oldest_key = next(iter(self._cache))
|
||||
del self._cache[oldest_key]
|
||||
|
||||
self._cache[content_hash] = agent_name
|
||||
return False # New content
|
||||
|
||||
def get_stats(self) -> Dict[str, int]:
|
||||
"""Get deduplication statistics."""
|
||||
with self._lock:
|
||||
return self._stats.copy()
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear the cache."""
|
||||
with self._lock:
|
||||
self._cache.clear()
|
||||
self._stats = {"duplicates_prevented": 0, "tokens_saved": 0}
|
||||
|
||||
|
||||
def deduplicate_topics(topics: list, key: str = "title", similarity_threshold: float = 0.8) -> list:
|
||||
"""
|
||||
Programmatic deduplication of topics/items before agent processing.
|
||||
|
||||
This helps prevent duplicate content from being passed to downstream agents,
|
||||
reducing token waste and improving quality.
|
||||
|
||||
Args:
|
||||
topics: List of topic dicts or strings
|
||||
key: Key to use for comparison if topics are dicts (default: "title")
|
||||
similarity_threshold: Similarity threshold for fuzzy matching (0.0-1.0)
|
||||
|
||||
Returns:
|
||||
Deduplicated list of topics
|
||||
"""
|
||||
if not topics:
|
||||
return topics
|
||||
|
||||
seen_hashes = set()
|
||||
seen_normalized = set()
|
||||
unique_topics = []
|
||||
|
||||
for topic in topics:
|
||||
# Get the content to compare
|
||||
if isinstance(topic, dict):
|
||||
content = str(topic.get(key, topic.get("content", str(topic))))
|
||||
else:
|
||||
content = str(topic)
|
||||
|
||||
# Normalize for comparison
|
||||
normalized = content.lower().strip()
|
||||
# Remove common words for better matching
|
||||
normalized = " ".join(w for w in normalized.split() if len(w) > 3)
|
||||
|
||||
# Check exact hash match
|
||||
content_hash = hashlib.md5(normalized.encode()).hexdigest()
|
||||
if content_hash in seen_hashes:
|
||||
continue
|
||||
|
||||
# Check fuzzy match using simple word overlap
|
||||
is_duplicate = False
|
||||
for seen in seen_normalized:
|
||||
# Calculate Jaccard similarity
|
||||
words1 = set(normalized.split())
|
||||
words2 = set(seen.split())
|
||||
if words1 and words2:
|
||||
intersection = len(words1 & words2)
|
||||
union = len(words1 | words2)
|
||||
similarity = intersection / union if union > 0 else 0
|
||||
if similarity >= similarity_threshold:
|
||||
is_duplicate = True
|
||||
break
|
||||
|
||||
if not is_duplicate:
|
||||
seen_hashes.add(content_hash)
|
||||
seen_normalized.add(normalized)
|
||||
unique_topics.append(topic)
|
||||
|
||||
return unique_topics
|
||||
|
||||
|
||||
class EstimationMode(str, Enum):
|
||||
"""Token estimation modes."""
|
||||
HEURISTIC = "heuristic"
|
||||
@@ -194,6 +314,13 @@ class ManagerConfig:
|
||||
tool_budgets: Dict[str, PerToolBudget] = field(default_factory=dict)
|
||||
protected_tools: List[str] = field(default_factory=list)
|
||||
|
||||
# LLM-powered summarization
|
||||
llm_summarize: bool = False # Enable LLM-powered summarization
|
||||
|
||||
# Smart tool output summarization
|
||||
smart_tool_summarize: bool = True # Summarize large tool outputs using LLM before truncating
|
||||
tool_summarize_limits: Dict[str, int] = field(default_factory=dict) # Per-tool min_chars_to_summarize
|
||||
|
||||
# Estimation
|
||||
estimation_mode: EstimationMode = EstimationMode.HEURISTIC
|
||||
log_estimation_mismatch: bool = False
|
||||
@@ -243,6 +370,9 @@ class ManagerConfig:
|
||||
"allow_absolute_paths": self.allow_absolute_paths,
|
||||
"prune_after_tokens": self.prune_after_tokens,
|
||||
"keep_recent_turns": self.keep_recent_turns,
|
||||
"llm_summarize": self.llm_summarize,
|
||||
"smart_tool_summarize": self.smart_tool_summarize,
|
||||
"tool_summarize_limits": self.tool_summarize_limits,
|
||||
"source": self.source,
|
||||
}
|
||||
return result
|
||||
@@ -364,6 +494,8 @@ class ContextManager:
|
||||
config: Optional[ManagerConfig] = None,
|
||||
session_id: str = "",
|
||||
agent_name: str = "",
|
||||
session_cache: Optional[SessionDeduplicationCache] = None,
|
||||
llm_summarize_fn: Optional[Callable] = None,
|
||||
):
|
||||
"""
|
||||
Initialize context manager.
|
||||
@@ -373,12 +505,20 @@ class ContextManager:
|
||||
config: Manager configuration
|
||||
session_id: Session identifier
|
||||
agent_name: Agent name for monitoring
|
||||
session_cache: Shared session cache for cross-agent deduplication
|
||||
llm_summarize_fn: Optional LLM function for intelligent summarization
|
||||
"""
|
||||
self.model = model
|
||||
self.config = config or ManagerConfig.from_env()
|
||||
self.session_id = session_id
|
||||
self.agent_name = agent_name
|
||||
|
||||
# LLM summarization function (auto-wired from agent when llm_summarize=True)
|
||||
self._llm_summarize_fn = llm_summarize_fn
|
||||
|
||||
# Session-level deduplication cache (shared across agents in workflow)
|
||||
self._session_cache = session_cache
|
||||
|
||||
# Core components
|
||||
self._budgeter = ContextBudgeter(
|
||||
model=model,
|
||||
@@ -475,7 +615,21 @@ class ContextManager:
|
||||
details={"utilization": utilization, "threshold": self.config.compact_threshold},
|
||||
)
|
||||
|
||||
# Auto-compact if needed
|
||||
# Step 1: Deduplicate messages before optimization
|
||||
deduped_messages = self._deduplicate_messages(messages)
|
||||
dedup_saved = estimate_messages_tokens(messages) - estimate_messages_tokens(deduped_messages)
|
||||
if dedup_saved > 0:
|
||||
self._add_history_event(
|
||||
OptimizationEventType.CAP_OUTPUTS,
|
||||
tokens_before=estimate_messages_tokens(messages),
|
||||
tokens_after=estimate_messages_tokens(deduped_messages),
|
||||
tokens_saved=dedup_saved,
|
||||
details={"reason": "deduplication", "messages_removed": len(messages) - len(deduped_messages)},
|
||||
)
|
||||
messages = deduped_messages
|
||||
result["tokens_before"] = estimate_messages_tokens(messages)
|
||||
|
||||
# Step 2: Auto-compact if needed
|
||||
if self.config.auto_compact and needs_optimization:
|
||||
optimized_messages, opt_result = self._optimize_with_benefit_check(
|
||||
messages,
|
||||
@@ -524,11 +678,14 @@ class ContextManager:
|
||||
if original_tokens <= target_tokens:
|
||||
return messages, None
|
||||
|
||||
# Get optimizer
|
||||
# Get optimizer with LLM summarization if configured
|
||||
optimizer = get_optimizer(
|
||||
self.config.strategy,
|
||||
preserve_recent=self.config.keep_recent_turns,
|
||||
protected_tools=self.config.protected_tools,
|
||||
llm_summarize_fn=self._llm_summarize_fn if self.config.llm_summarize else None,
|
||||
smart_tool_summarize=self.config.smart_tool_summarize,
|
||||
tool_summarize_limits=self.config.tool_summarize_limits,
|
||||
)
|
||||
|
||||
# Try optimization
|
||||
@@ -573,6 +730,70 @@ class ContextManager:
|
||||
|
||||
return optimized, result
|
||||
|
||||
def _deduplicate_messages(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Remove duplicate content from messages.
|
||||
|
||||
Detects and removes messages with identical content hashes,
|
||||
keeping only the first occurrence. Preserves message order.
|
||||
Uses session-level cache for cross-agent deduplication if available.
|
||||
|
||||
Args:
|
||||
messages: List of messages to deduplicate
|
||||
|
||||
Returns:
|
||||
Deduplicated list of messages
|
||||
"""
|
||||
if not messages:
|
||||
return messages
|
||||
|
||||
seen_hashes: set = set()
|
||||
result: List[Dict[str, Any]] = []
|
||||
|
||||
for msg in messages:
|
||||
role = msg.get("role", "")
|
||||
content = msg.get("content", "")
|
||||
|
||||
# Always keep system messages (usually unique)
|
||||
if role == "system":
|
||||
result.append(msg)
|
||||
continue
|
||||
|
||||
# Always keep assistant messages with tool_calls (they're actions)
|
||||
if role == "assistant" and msg.get("tool_calls"):
|
||||
result.append(msg)
|
||||
continue
|
||||
|
||||
# For tool results and user/assistant content, check for duplicates
|
||||
if isinstance(content, str) and len(content) > 100:
|
||||
# Hash the content (first 2000 chars to avoid hashing huge content)
|
||||
content_key = content[:2000]
|
||||
content_hash = hashlib.md5(content_key.encode()).hexdigest()[:16]
|
||||
|
||||
# Check local seen hashes first
|
||||
if content_hash in seen_hashes:
|
||||
import logging
|
||||
logging.debug(f"[Context] Dedup: skipping local duplicate (hash={content_hash[:8]}, agent={self.agent_name})")
|
||||
continue
|
||||
|
||||
# Check session-level cache for cross-agent deduplication
|
||||
if self._session_cache:
|
||||
tokens = estimate_message_tokens(msg)
|
||||
if self._session_cache.check_and_add(content_hash, self.agent_name, tokens):
|
||||
# Duplicate found in session cache - skip
|
||||
import logging
|
||||
logging.debug(f"[Context] Dedup: skipping session duplicate (hash={content_hash[:8]}, agent={self.agent_name}, tokens={tokens})")
|
||||
continue
|
||||
|
||||
seen_hashes.add(content_hash)
|
||||
|
||||
result.append(msg)
|
||||
|
||||
return result
|
||||
|
||||
def _take_snapshot(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
@@ -623,8 +844,9 @@ class ContextManager:
|
||||
message_hash = hashlib.sha256(messages_json.encode()).hexdigest()[:16]
|
||||
tools_hash = hashlib.sha256(tools_json.encode()).hexdigest()[:16]
|
||||
|
||||
from datetime import timezone
|
||||
hook_data = SnapshotHookData(
|
||||
timestamp=datetime.utcnow().isoformat() + "Z",
|
||||
timestamp=datetime.now(tz=timezone.utc).isoformat().replace('+00:00', 'Z'),
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
message_hash=message_hash,
|
||||
@@ -751,7 +973,11 @@ class ContextManager:
|
||||
ratio = max_tokens / current_tokens
|
||||
max_chars = int(len(output) * ratio * 0.9) # 10% safety margin
|
||||
|
||||
truncated = output[:max_chars] + "\n...[output truncated]..."
|
||||
# Use smart truncation format that judge recognizes as OK
|
||||
tail_size = min(max_chars // 5, 1000)
|
||||
head = output[:max_chars - tail_size]
|
||||
tail = output[-tail_size:] if tail_size > 0 else ""
|
||||
truncated = f"{head}\n...[{len(output):,} chars, showing first/last portions]...\n{tail}"
|
||||
|
||||
self._add_history_event(
|
||||
OptimizationEventType.CAP_OUTPUTS,
|
||||
@@ -773,8 +999,9 @@ class ContextManager:
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Add an event to optimization history."""
|
||||
from datetime import timezone
|
||||
event = OptimizationEvent(
|
||||
timestamp=datetime.utcnow().isoformat() + "Z",
|
||||
timestamp=datetime.now(tz=timezone.utc).isoformat().replace('+00:00', 'Z'),
|
||||
event_type=event_type,
|
||||
strategy=strategy,
|
||||
tokens_before=tokens_before,
|
||||
@@ -809,6 +1036,87 @@ class ContextManager:
|
||||
"monitor_stats": self._monitor.get_stats(),
|
||||
}
|
||||
|
||||
def emergency_truncate(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
target_tokens: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Emergency truncation when optimization isn't enough.
|
||||
|
||||
Aggressively removes messages to fit within target tokens.
|
||||
Preserves system messages and most recent turns.
|
||||
|
||||
Args:
|
||||
messages: Messages to truncate
|
||||
target_tokens: Target token count
|
||||
|
||||
Returns:
|
||||
Truncated messages list
|
||||
"""
|
||||
if not messages:
|
||||
return messages
|
||||
|
||||
# Estimate current tokens
|
||||
current_tokens = estimate_messages_tokens(messages)
|
||||
if current_tokens <= target_tokens:
|
||||
return messages
|
||||
|
||||
# Separate system and non-system messages
|
||||
system_msgs = [m for m in messages if m.get("role") == "system"]
|
||||
other_msgs = [m for m in messages if m.get("role") != "system"]
|
||||
|
||||
# Keep system messages (they're usually small)
|
||||
result = list(system_msgs)
|
||||
system_tokens = estimate_messages_tokens(system_msgs)
|
||||
remaining_budget = target_tokens - system_tokens
|
||||
|
||||
if remaining_budget <= 0:
|
||||
# Even system messages exceed budget - truncate system content
|
||||
for msg in result:
|
||||
if isinstance(msg.get("content"), str) and len(msg["content"]) > 500:
|
||||
content = msg["content"]
|
||||
tail_size = min(50, len(content) // 10)
|
||||
msg["content"] = f"{content[:450]}\n...[{len(content):,} chars, showing first/last portions]...\n{content[-tail_size:] if tail_size > 0 else ''}"
|
||||
return result
|
||||
|
||||
# Keep most recent messages that fit
|
||||
kept_msgs = []
|
||||
tokens_used = 0
|
||||
|
||||
for msg in reversed(other_msgs):
|
||||
msg_tokens = estimate_message_tokens(msg)
|
||||
if tokens_used + msg_tokens <= remaining_budget:
|
||||
kept_msgs.insert(0, msg)
|
||||
tokens_used += msg_tokens
|
||||
else:
|
||||
# Try to fit a truncated version
|
||||
if msg.get("role") == "user" and isinstance(msg.get("content"), str):
|
||||
# Keep user messages but truncate content
|
||||
available = remaining_budget - tokens_used
|
||||
if available > 50:
|
||||
truncated_msg = msg.copy()
|
||||
max_chars = available * 4 # ~4 chars per token
|
||||
content = msg["content"]
|
||||
tail_size = min(max_chars // 10, 100)
|
||||
truncated_msg["content"] = f"{content[:max_chars - tail_size]}\n...[{len(content):,} chars, showing first/last portions]...\n{content[-tail_size:] if tail_size > 0 else ''}"
|
||||
kept_msgs.insert(0, truncated_msg)
|
||||
break
|
||||
|
||||
result.extend(kept_msgs)
|
||||
|
||||
# Log the emergency truncation
|
||||
self._add_history_event(
|
||||
OptimizationEventType.CAP_OUTPUTS,
|
||||
tokens_before=current_tokens,
|
||||
tokens_after=estimate_messages_tokens(result),
|
||||
tokens_saved=current_tokens - estimate_messages_tokens(result),
|
||||
messages_affected=len(messages) - len(result),
|
||||
details={"reason": "emergency_truncation", "target_tokens": target_tokens},
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def get_resolved_config(self) -> Dict[str, Any]:
|
||||
"""Get the fully resolved configuration with source info."""
|
||||
return {
|
||||
@@ -847,6 +1155,7 @@ class MultiAgentContextManager:
|
||||
self,
|
||||
config: Optional[ManagerConfig] = None,
|
||||
default_policy: Optional[ContextPolicy] = None,
|
||||
session_cache: Optional[SessionDeduplicationCache] = None,
|
||||
):
|
||||
"""
|
||||
Initialize multi-agent context manager.
|
||||
@@ -854,10 +1163,14 @@ class MultiAgentContextManager:
|
||||
Args:
|
||||
config: Base configuration
|
||||
default_policy: Default context sharing policy
|
||||
session_cache: Shared session deduplication cache
|
||||
"""
|
||||
self.config = config or ManagerConfig.from_env()
|
||||
self.default_policy = default_policy or ContextPolicy()
|
||||
|
||||
# Session-level deduplication cache (shared across all agents)
|
||||
self._session_cache = session_cache or SessionDeduplicationCache()
|
||||
|
||||
self._agents: Dict[str, ContextManager] = {}
|
||||
self._multi_ledger = MultiAgentLedger()
|
||||
self._multi_monitor = MultiAgentMonitor(
|
||||
@@ -884,9 +1197,14 @@ class MultiAgentContextManager:
|
||||
model=model,
|
||||
config=self.config,
|
||||
agent_name=agent_id,
|
||||
session_cache=self._session_cache, # Share session cache
|
||||
)
|
||||
return self._agents[agent_id]
|
||||
|
||||
def get_session_cache(self) -> SessionDeduplicationCache:
|
||||
"""Get the session deduplication cache."""
|
||||
return self._session_cache
|
||||
|
||||
def set_agent_policy(self, agent_id: str, policy: ContextPolicy) -> None:
|
||||
"""Set context policy for an agent."""
|
||||
self._policies[agent_id] = policy
|
||||
@@ -978,6 +1296,7 @@ class MultiAgentContextManager:
|
||||
agent_id: policy.to_dict()
|
||||
for agent_id, policy in self._policies.items()
|
||||
},
|
||||
"session_dedup": self._session_cache.get_stats() if self._session_cache else {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -214,6 +214,9 @@ class OptimizationResult:
|
||||
messages_removed: int = 0
|
||||
messages_tagged: int = 0
|
||||
tool_outputs_pruned: int = 0
|
||||
tool_outputs_summarized: int = 0 # Count of tool outputs summarized via LLM
|
||||
tokens_saved_by_summarization: int = 0 # Tokens saved specifically by LLM summarization
|
||||
tokens_saved_by_truncation: int = 0 # Tokens saved specifically by truncation
|
||||
summary_added: bool = False
|
||||
|
||||
@property
|
||||
@@ -274,12 +277,21 @@ class ContextConfig:
|
||||
prune_after_tokens: int = 40000
|
||||
protected_tools: List[str] = field(default_factory=list)
|
||||
|
||||
# Per-tool output limits (tool_name -> max_chars)
|
||||
tool_limits: Dict[str, int] = field(default_factory=dict)
|
||||
|
||||
# Monitoring
|
||||
monitor: MonitorConfig = field(default_factory=MonitorConfig)
|
||||
|
||||
# Sliding window
|
||||
keep_recent_turns: int = 5
|
||||
|
||||
# LLM-powered summarization
|
||||
llm_summarize: bool = False # Enable LLM-powered summarization (uses agent's LLM)
|
||||
|
||||
# Smart tool output summarization (summarize before truncating)
|
||||
smart_tool_summarize: bool = True # Summarize large tool outputs using LLM before truncating
|
||||
|
||||
# Session tracking (Agno pattern)
|
||||
session_tracking: bool = False # Enable goal/plan/progress tracking
|
||||
track_summary: bool = True # Auto-extract conversation summary
|
||||
@@ -301,6 +313,31 @@ class ContextConfig:
|
||||
result = asdict(self)
|
||||
result['strategy'] = self.strategy.value
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def for_recipe(cls) -> "ContextConfig":
|
||||
"""
|
||||
Preset for recipe/workflow use cases with many tool calls.
|
||||
|
||||
Optimized for:
|
||||
- Preventing context overflow in multi-step workflows
|
||||
- Preserving important tool outputs
|
||||
- Triggering compaction earlier (70% vs 80%)
|
||||
- Limiting tool outputs to prevent explosion
|
||||
|
||||
Returns:
|
||||
ContextConfig with recipe-optimized settings
|
||||
"""
|
||||
return cls(
|
||||
auto_compact=True,
|
||||
compact_threshold=0.7, # Trigger at 70% (earlier than default 80%)
|
||||
strategy=OptimizerStrategy.SMART,
|
||||
tool_output_max=2000, # Limit each tool output to ~2000 tokens
|
||||
keep_recent_turns=3, # Keep last 3 turns intact
|
||||
prune_after_tokens=50000, # Start pruning after 50K tokens
|
||||
output_reserve=8000,
|
||||
history_ratio=0.6,
|
||||
)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
|
||||
@@ -151,12 +151,14 @@ class PruneToolsOptimizer(BaseOptimizer):
|
||||
def __init__(
|
||||
self,
|
||||
preserve_recent: int = 5,
|
||||
max_output_chars: int = 500,
|
||||
max_output_chars: int = 4000, # Increased from 500 to allow full page content
|
||||
protected_tools: Optional[List[str]] = None,
|
||||
tool_limits: Optional[Dict[str, int]] = None, # Per-tool limits
|
||||
):
|
||||
self.preserve_recent = preserve_recent
|
||||
self.max_output_chars = max_output_chars
|
||||
self.protected_tools = protected_tools or []
|
||||
self.tool_limits = tool_limits or {} # {tool_name: max_chars}
|
||||
|
||||
def optimize(
|
||||
self,
|
||||
@@ -192,11 +194,18 @@ class PruneToolsOptimizer(BaseOptimizer):
|
||||
result.append(msg)
|
||||
continue
|
||||
|
||||
# Truncate if too long
|
||||
if isinstance(content, str) and len(content) > self.max_output_chars:
|
||||
# Get per-tool limit or use default
|
||||
limit = self.tool_limits.get(tool_name, self.max_output_chars)
|
||||
|
||||
# Truncate if too long (use smart format)
|
||||
if isinstance(content, str) and len(content) > limit:
|
||||
pruned_msg = msg.copy()
|
||||
pruned_msg["content"] = content[:self.max_output_chars] + "\n...[output truncated]..."
|
||||
tail_size = min(limit // 5, 500)
|
||||
head = content[:limit - tail_size]
|
||||
tail = content[-tail_size:] if tail_size > 0 else ""
|
||||
pruned_msg["content"] = f"{head}\n...[{len(content):,} chars, showing first/last portions]...\n{tail}"
|
||||
pruned_msg["_pruned"] = True
|
||||
pruned_msg["_original_length"] = len(content)
|
||||
result.append(pruned_msg)
|
||||
pruned_count += 1
|
||||
else:
|
||||
@@ -287,15 +296,32 @@ class NonDestructiveOptimizer(BaseOptimizer):
|
||||
|
||||
class SummarizeOptimizer(BaseOptimizer):
|
||||
"""
|
||||
Summarize older messages using LLM.
|
||||
Summarize older messages using LLM or fallback to truncation.
|
||||
|
||||
Note: This is a placeholder that creates a structured summary.
|
||||
Actual LLM summarization should be done at the agent level.
|
||||
When an LLM summarize function is provided, uses it to create
|
||||
intelligent summaries. Otherwise falls back to truncation.
|
||||
"""
|
||||
|
||||
def __init__(self, preserve_recent: int = 5, max_summary_items: int = 10):
|
||||
def __init__(
|
||||
self,
|
||||
preserve_recent: int = 5,
|
||||
max_summary_items: int = 10,
|
||||
llm_summarize_fn: Optional[callable] = None,
|
||||
max_summary_tokens: int = 500,
|
||||
):
|
||||
"""
|
||||
Initialize summarize optimizer.
|
||||
|
||||
Args:
|
||||
preserve_recent: Number of recent messages to keep intact
|
||||
max_summary_items: Max items to include in fallback summary
|
||||
llm_summarize_fn: Optional function(messages) -> str for LLM summarization
|
||||
max_summary_tokens: Target token count for LLM summary
|
||||
"""
|
||||
self.preserve_recent = preserve_recent
|
||||
self.max_summary_items = max_summary_items
|
||||
self.llm_summarize_fn = llm_summarize_fn
|
||||
self.max_summary_tokens = max_summary_tokens
|
||||
|
||||
def optimize(
|
||||
self,
|
||||
@@ -324,24 +350,16 @@ class SummarizeOptimizer(BaseOptimizer):
|
||||
older = other_msgs[:-self.preserve_recent] if len(other_msgs) > self.preserve_recent else []
|
||||
|
||||
if older:
|
||||
# Create summary
|
||||
summary_parts = []
|
||||
for msg in older[:self.max_summary_items]:
|
||||
role = msg.get("role", "unknown")
|
||||
content = msg.get("content", "")
|
||||
if isinstance(content, str) and content:
|
||||
summary_parts.append(f"[{role}]: {content[:150]}...")
|
||||
# Try LLM summarization first
|
||||
summary = self._create_summary(older)
|
||||
|
||||
if summary_parts:
|
||||
summary = (
|
||||
"[Previous conversation summary]\n"
|
||||
+ "\n".join(summary_parts)
|
||||
)
|
||||
if summary:
|
||||
result.append({
|
||||
"role": "system",
|
||||
"content": summary,
|
||||
"_summary": True,
|
||||
"_original_count": len(older),
|
||||
"_llm_summarized": self.llm_summarize_fn is not None,
|
||||
})
|
||||
|
||||
result.extend(recent)
|
||||
@@ -356,6 +374,206 @@ class SummarizeOptimizer(BaseOptimizer):
|
||||
messages_removed=len(older),
|
||||
summary_added=bool(older),
|
||||
)
|
||||
|
||||
def _create_summary(self, messages: List[Dict[str, Any]]) -> str:
|
||||
"""Create summary using LLM or fallback to truncation."""
|
||||
if self.llm_summarize_fn:
|
||||
try:
|
||||
# Use LLM to create intelligent summary
|
||||
return self.llm_summarize_fn(messages, self.max_summary_tokens)
|
||||
except Exception:
|
||||
# Fallback to truncation on error
|
||||
pass
|
||||
|
||||
# Fallback: Create truncated summary
|
||||
summary_parts = []
|
||||
for msg in messages[:self.max_summary_items]:
|
||||
role = msg.get("role", "unknown")
|
||||
content = msg.get("content", "")
|
||||
if isinstance(content, str) and content:
|
||||
summary_parts.append(f"[{role}]: {content[:150]}...")
|
||||
|
||||
if summary_parts:
|
||||
return "[Previous conversation summary]\n" + "\n".join(summary_parts)
|
||||
return ""
|
||||
|
||||
|
||||
class SummarizeToolOutputsOptimizer(BaseOptimizer):
|
||||
"""
|
||||
Summarize large tool outputs using LLM before truncation.
|
||||
|
||||
This optimizer specifically targets tool role messages with large content,
|
||||
using an LLM to create intelligent summaries that preserve key information.
|
||||
Falls back to keeping original content if LLM is unavailable or fails.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm_summarize_fn: Optional[callable] = None,
|
||||
max_output_tokens: int = 1000,
|
||||
min_chars_to_summarize: int = 2000,
|
||||
preserve_recent: int = 2,
|
||||
tool_summarize_limits: Optional[Dict[str, int]] = None,
|
||||
):
|
||||
"""
|
||||
Initialize tool output summarizer.
|
||||
|
||||
Args:
|
||||
llm_summarize_fn: Function(content, max_tokens) -> summary string
|
||||
max_output_tokens: Target token count for summarized output
|
||||
min_chars_to_summarize: Default minimum chars before summarization triggers
|
||||
preserve_recent: Number of recent tool outputs to preserve intact
|
||||
tool_summarize_limits: Per-tool min_chars_to_summarize limits {tool_name: min_chars}
|
||||
"""
|
||||
self.llm_summarize_fn = llm_summarize_fn
|
||||
self.max_output_tokens = max_output_tokens
|
||||
self.min_chars_to_summarize = min_chars_to_summarize
|
||||
self.preserve_recent = preserve_recent
|
||||
self.tool_summarize_limits = tool_summarize_limits or {}
|
||||
|
||||
def optimize(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
target_tokens: int,
|
||||
ledger: Optional[ContextLedger] = None,
|
||||
) -> tuple:
|
||||
original_tokens = estimate_messages_tokens(messages)
|
||||
|
||||
# If already under budget or no LLM function, return as-is
|
||||
if original_tokens <= target_tokens or not self.llm_summarize_fn:
|
||||
return messages, OptimizationResult(
|
||||
original_tokens=original_tokens,
|
||||
optimized_tokens=original_tokens,
|
||||
tokens_saved=0,
|
||||
strategy_used=OptimizerStrategy.SMART,
|
||||
)
|
||||
|
||||
result = []
|
||||
summarized_count = 0
|
||||
|
||||
# Find tool messages and their indices
|
||||
tool_indices = [i for i, m in enumerate(messages) if m.get("role") == "tool"]
|
||||
|
||||
# Preserve recent tool outputs (only if there are more than preserve_recent tools)
|
||||
if tool_indices and self.preserve_recent > 0 and len(tool_indices) > self.preserve_recent:
|
||||
recent_tool_indices = set(tool_indices[-self.preserve_recent:])
|
||||
else:
|
||||
recent_tool_indices = set() # Summarize all if few tools or preserve_recent=0
|
||||
|
||||
for i, msg in enumerate(messages):
|
||||
role = msg.get("role", "")
|
||||
content = msg.get("content", "")
|
||||
|
||||
# Only process tool messages with large content
|
||||
if role == "tool" and i not in recent_tool_indices:
|
||||
# Get per-tool limit or use default
|
||||
tool_name = msg.get("name", "")
|
||||
min_chars = self.tool_summarize_limits.get(tool_name, self.min_chars_to_summarize)
|
||||
if isinstance(content, str) and len(content) >= min_chars:
|
||||
# Try to summarize
|
||||
try:
|
||||
summary = self.llm_summarize_fn(content, self.max_output_tokens)
|
||||
if summary and len(summary) < len(content):
|
||||
summarized_msg = msg.copy()
|
||||
summarized_msg["content"] = summary
|
||||
summarized_msg["_summarized"] = True
|
||||
summarized_msg["_original_length"] = len(content)
|
||||
result.append(summarized_msg)
|
||||
summarized_count += 1
|
||||
continue
|
||||
except Exception:
|
||||
# Fallback to original on error
|
||||
pass
|
||||
|
||||
result.append(msg)
|
||||
|
||||
optimized_tokens = estimate_messages_tokens(result)
|
||||
|
||||
tokens_saved = original_tokens - optimized_tokens
|
||||
|
||||
return result, OptimizationResult(
|
||||
original_tokens=original_tokens,
|
||||
optimized_tokens=optimized_tokens,
|
||||
tokens_saved=tokens_saved,
|
||||
strategy_used=OptimizerStrategy.SMART,
|
||||
tool_outputs_summarized=summarized_count,
|
||||
tokens_saved_by_summarization=tokens_saved, # All savings from summarization
|
||||
)
|
||||
|
||||
|
||||
class LLMSummarizeOptimizer(SummarizeOptimizer):
|
||||
"""
|
||||
LLM-powered summarization optimizer.
|
||||
|
||||
Uses the agent's LLM to create intelligent summaries of older messages,
|
||||
preserving key information while reducing token count.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
preserve_recent: int = 5,
|
||||
llm_client: Optional[Any] = None,
|
||||
model: str = "gpt-4o-mini",
|
||||
max_summary_tokens: int = 500,
|
||||
):
|
||||
"""
|
||||
Initialize LLM summarize optimizer.
|
||||
|
||||
Args:
|
||||
preserve_recent: Number of recent messages to keep intact
|
||||
llm_client: OpenAI-compatible client for summarization
|
||||
model: Model to use for summarization
|
||||
max_summary_tokens: Target token count for summary
|
||||
"""
|
||||
self.llm_client = llm_client
|
||||
self.model = model
|
||||
|
||||
# Create LLM summarize function if client provided
|
||||
llm_fn = self._llm_summarize if llm_client else None
|
||||
|
||||
super().__init__(
|
||||
preserve_recent=preserve_recent,
|
||||
llm_summarize_fn=llm_fn,
|
||||
max_summary_tokens=max_summary_tokens,
|
||||
)
|
||||
|
||||
def _llm_summarize(self, messages: List[Dict[str, Any]], max_tokens: int) -> str:
|
||||
"""Use LLM to create intelligent summary."""
|
||||
if not self.llm_client:
|
||||
return ""
|
||||
|
||||
# Build content to summarize
|
||||
content_parts = []
|
||||
for msg in messages:
|
||||
role = msg.get("role", "unknown")
|
||||
content = msg.get("content", "")
|
||||
if isinstance(content, str) and content:
|
||||
# Truncate very long content for summarization input
|
||||
truncated = content[:2000] if len(content) > 2000 else content
|
||||
content_parts.append(f"[{role}]: {truncated}")
|
||||
|
||||
if not content_parts:
|
||||
return ""
|
||||
|
||||
# Create summarization prompt
|
||||
summarize_prompt = f"""Summarize the following conversation history concisely, preserving key facts, decisions, and context. Keep the summary under {max_tokens} tokens.
|
||||
|
||||
Conversation:
|
||||
{chr(10).join(content_parts)}
|
||||
|
||||
Summary:"""
|
||||
|
||||
try:
|
||||
response = self.llm_client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[{"role": "user", "content": summarize_prompt}],
|
||||
max_tokens=max_tokens,
|
||||
temperature=0.3,
|
||||
)
|
||||
summary = response.choices[0].message.content
|
||||
return f"[AI Summary of {len(messages)} previous messages]\n{summary}"
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
class SmartOptimizer(BaseOptimizer):
|
||||
@@ -363,25 +581,43 @@ class SmartOptimizer(BaseOptimizer):
|
||||
Smart optimization combining multiple strategies.
|
||||
|
||||
Applies strategies in order:
|
||||
1. Prune tool outputs
|
||||
2. Sliding window
|
||||
3. Summarize if still over
|
||||
1. Summarize tool outputs (if LLM available and smart_tool_summarize=True)
|
||||
2. Prune tool outputs (fallback truncation)
|
||||
3. Sliding window
|
||||
4. Summarize conversation if still over
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
preserve_recent: int = 5,
|
||||
protected_tools: Optional[List[str]] = None,
|
||||
tool_limits: Optional[Dict[str, int]] = None,
|
||||
llm_summarize_fn: Optional[callable] = None,
|
||||
smart_tool_summarize: bool = True,
|
||||
tool_summarize_limits: Optional[Dict[str, int]] = None,
|
||||
):
|
||||
self.preserve_recent = preserve_recent
|
||||
self.protected_tools = protected_tools or []
|
||||
self.tool_limits = tool_limits or {}
|
||||
self.smart_tool_summarize = smart_tool_summarize
|
||||
self.tool_summarize_limits = tool_summarize_limits or {}
|
||||
|
||||
# Tool output summarization (LLM-powered, applied first when available)
|
||||
self._summarize_tools = SummarizeToolOutputsOptimizer(
|
||||
llm_summarize_fn=llm_summarize_fn if smart_tool_summarize else None,
|
||||
preserve_recent=preserve_recent,
|
||||
tool_summarize_limits=tool_summarize_limits,
|
||||
)
|
||||
self._prune = PruneToolsOptimizer(
|
||||
preserve_recent=preserve_recent,
|
||||
protected_tools=protected_tools,
|
||||
tool_limits=tool_limits,
|
||||
)
|
||||
self._window = SlidingWindowOptimizer()
|
||||
self._summarize = SummarizeOptimizer(preserve_recent=preserve_recent)
|
||||
self._summarize = SummarizeOptimizer(
|
||||
preserve_recent=preserve_recent,
|
||||
llm_summarize_fn=llm_summarize_fn,
|
||||
)
|
||||
|
||||
def optimize(
|
||||
self,
|
||||
@@ -399,8 +635,29 @@ class SmartOptimizer(BaseOptimizer):
|
||||
strategy_used=OptimizerStrategy.SMART,
|
||||
)
|
||||
|
||||
# Step 1: Prune tool outputs
|
||||
result, prune_result = self._prune.optimize(messages, target_tokens, ledger)
|
||||
# Step 1: Summarize tool outputs (LLM-powered, if available)
|
||||
tool_summarized_count = 0
|
||||
tokens_saved_by_summarization = 0
|
||||
if self._summarize_tools.llm_summarize_fn:
|
||||
result, tool_summary_result = self._summarize_tools.optimize(messages, target_tokens, ledger)
|
||||
tool_summarized_count = tool_summary_result.tool_outputs_summarized
|
||||
tokens_saved_by_summarization = tool_summary_result.tokens_saved_by_summarization
|
||||
|
||||
if estimate_messages_tokens(result) <= target_tokens:
|
||||
return result, OptimizationResult(
|
||||
original_tokens=original_tokens,
|
||||
optimized_tokens=tool_summary_result.optimized_tokens,
|
||||
tokens_saved=original_tokens - tool_summary_result.optimized_tokens,
|
||||
strategy_used=OptimizerStrategy.SMART,
|
||||
tool_outputs_summarized=tool_summarized_count,
|
||||
tokens_saved_by_summarization=tokens_saved_by_summarization,
|
||||
)
|
||||
else:
|
||||
result = messages
|
||||
|
||||
# Step 2: Prune tool outputs (fallback truncation)
|
||||
result, prune_result = self._prune.optimize(result, target_tokens, ledger)
|
||||
tokens_saved_by_truncation = prune_result.tokens_saved
|
||||
|
||||
if estimate_messages_tokens(result) <= target_tokens:
|
||||
return result, OptimizationResult(
|
||||
@@ -408,10 +665,13 @@ class SmartOptimizer(BaseOptimizer):
|
||||
optimized_tokens=prune_result.optimized_tokens,
|
||||
tokens_saved=original_tokens - prune_result.optimized_tokens,
|
||||
strategy_used=OptimizerStrategy.SMART,
|
||||
tool_outputs_summarized=tool_summarized_count,
|
||||
tool_outputs_pruned=prune_result.tool_outputs_pruned,
|
||||
tokens_saved_by_summarization=tokens_saved_by_summarization,
|
||||
tokens_saved_by_truncation=tokens_saved_by_truncation,
|
||||
)
|
||||
|
||||
# Step 2: Sliding window
|
||||
# Step 3: Sliding window
|
||||
result, window_result = self._window.optimize(result, target_tokens, ledger)
|
||||
|
||||
if estimate_messages_tokens(result) <= target_tokens:
|
||||
@@ -424,7 +684,7 @@ class SmartOptimizer(BaseOptimizer):
|
||||
messages_removed=window_result.messages_removed,
|
||||
)
|
||||
|
||||
# Step 3: Summarize
|
||||
# Step 4: Summarize conversation
|
||||
result, summary_result = self._summarize.optimize(result, target_tokens, ledger)
|
||||
|
||||
optimized_tokens = estimate_messages_tokens(result)
|
||||
@@ -434,7 +694,10 @@ class SmartOptimizer(BaseOptimizer):
|
||||
optimized_tokens=optimized_tokens,
|
||||
tokens_saved=original_tokens - optimized_tokens,
|
||||
strategy_used=OptimizerStrategy.SMART,
|
||||
tool_outputs_summarized=tool_summarized_count,
|
||||
tool_outputs_pruned=prune_result.tool_outputs_pruned,
|
||||
tokens_saved_by_summarization=tokens_saved_by_summarization,
|
||||
tokens_saved_by_truncation=tokens_saved_by_truncation,
|
||||
messages_removed=window_result.messages_removed + summary_result.messages_removed,
|
||||
summary_added=summary_result.summary_added,
|
||||
)
|
||||
@@ -450,6 +713,9 @@ OPTIMIZER_REGISTRY: Dict[OptimizerStrategy, type] = {
|
||||
OptimizerStrategy.SMART: SmartOptimizer,
|
||||
}
|
||||
|
||||
# LLM summarizer available separately (not in registry as it needs client)
|
||||
LLM_SUMMARIZE_OPTIMIZER = LLMSummarizeOptimizer
|
||||
|
||||
|
||||
def get_optimizer(
|
||||
strategy: OptimizerStrategy,
|
||||
|
||||
@@ -26,14 +26,56 @@ __all__ = [
|
||||
"PerformanceEvaluator",
|
||||
"ReliabilityEvaluator",
|
||||
"CriteriaEvaluator",
|
||||
"MediaEvaluator",
|
||||
"AccuracyResult",
|
||||
"PerformanceResult",
|
||||
"ReliabilityResult",
|
||||
"CriteriaResult",
|
||||
"MediaEvaluationResult",
|
||||
"EvaluationScore",
|
||||
"PerformanceMetrics",
|
||||
"ToolCallResult",
|
||||
"CriteriaScore",
|
||||
# DRY: Common grading base classes and protocols
|
||||
"BaseLLMGrader",
|
||||
"GradeResult",
|
||||
"GraderProtocol",
|
||||
"GradeResultProtocol",
|
||||
"ScoredResultProtocol",
|
||||
"AsyncGraderProtocol",
|
||||
"parse_score_reasoning",
|
||||
# Eval package types
|
||||
"EvalCase",
|
||||
"EvalResult",
|
||||
"EvalReport",
|
||||
"EvalPackage",
|
||||
"EvalRunnerProtocol",
|
||||
# Unified Judge API (follows add_X/get_X/list_X naming)
|
||||
"Judge",
|
||||
"JudgeConfig",
|
||||
"JudgeResult",
|
||||
"JudgeProtocol",
|
||||
"JudgeResultProtocol",
|
||||
"AccuracyJudge",
|
||||
"CriteriaJudge",
|
||||
"RecipeJudge",
|
||||
"add_judge",
|
||||
"get_judge",
|
||||
"list_judges",
|
||||
"remove_judge",
|
||||
# Dynamic judge configuration (domain-agnostic)
|
||||
"JudgeCriteriaConfig",
|
||||
"OptimizationRuleProtocol",
|
||||
"add_optimization_rule",
|
||||
"get_optimization_rule",
|
||||
"list_optimization_rules",
|
||||
"remove_optimization_rule",
|
||||
# Token utilities
|
||||
"estimate_tokens",
|
||||
"get_context_length",
|
||||
"count_tokens",
|
||||
"needs_chunking",
|
||||
"get_recommended_chunk_size",
|
||||
]
|
||||
|
||||
_LAZY_IMPORTS = {
|
||||
@@ -42,14 +84,56 @@ _LAZY_IMPORTS = {
|
||||
"PerformanceEvaluator": ("performance", "PerformanceEvaluator"),
|
||||
"ReliabilityEvaluator": ("reliability", "ReliabilityEvaluator"),
|
||||
"CriteriaEvaluator": ("criteria", "CriteriaEvaluator"),
|
||||
"MediaEvaluator": ("media", "MediaEvaluator"),
|
||||
"AccuracyResult": ("results", "AccuracyResult"),
|
||||
"PerformanceResult": ("results", "PerformanceResult"),
|
||||
"ReliabilityResult": ("results", "ReliabilityResult"),
|
||||
"CriteriaResult": ("results", "CriteriaResult"),
|
||||
"MediaEvaluationResult": ("media", "MediaEvaluationResult"),
|
||||
"EvaluationScore": ("results", "EvaluationScore"),
|
||||
"PerformanceMetrics": ("results", "PerformanceMetrics"),
|
||||
"ToolCallResult": ("results", "ToolCallResult"),
|
||||
"CriteriaScore": ("results", "CriteriaScore"),
|
||||
# DRY: Common grading base classes and protocols
|
||||
"BaseLLMGrader": ("grader", "BaseLLMGrader"),
|
||||
"GradeResult": ("grader", "GradeResult"),
|
||||
"GraderProtocol": ("protocols", "GraderProtocol"),
|
||||
"GradeResultProtocol": ("protocols", "GradeResultProtocol"),
|
||||
"ScoredResultProtocol": ("protocols", "ScoredResultProtocol"),
|
||||
"AsyncGraderProtocol": ("protocols", "AsyncGraderProtocol"),
|
||||
"parse_score_reasoning": ("grader", "parse_score_reasoning"),
|
||||
# Eval package types
|
||||
"EvalCase": ("package", "EvalCase"),
|
||||
"EvalResult": ("package", "EvalResult"),
|
||||
"EvalReport": ("package", "EvalReport"),
|
||||
"EvalPackage": ("package", "EvalPackage"),
|
||||
"EvalRunnerProtocol": ("package", "EvalRunnerProtocol"),
|
||||
# Unified Judge API (follows add_X/get_X/list_X naming)
|
||||
"Judge": ("judge", "Judge"),
|
||||
"JudgeConfig": ("judge", "JudgeConfig"),
|
||||
"JudgeResult": ("results", "JudgeResult"),
|
||||
"JudgeProtocol": ("protocols", "JudgeProtocol"),
|
||||
"JudgeResultProtocol": ("protocols", "JudgeResultProtocol"),
|
||||
"AccuracyJudge": ("judge", "AccuracyJudge"),
|
||||
"CriteriaJudge": ("judge", "CriteriaJudge"),
|
||||
"RecipeJudge": ("judge", "RecipeJudge"),
|
||||
"add_judge": ("judge", "add_judge"),
|
||||
"get_judge": ("judge", "get_judge"),
|
||||
"list_judges": ("judge", "list_judges"),
|
||||
"remove_judge": ("judge", "remove_judge"),
|
||||
# Dynamic judge configuration (domain-agnostic)
|
||||
"JudgeCriteriaConfig": ("judge", "JudgeCriteriaConfig"),
|
||||
"OptimizationRuleProtocol": ("protocols", "OptimizationRuleProtocol"),
|
||||
"add_optimization_rule": ("judge", "add_optimization_rule"),
|
||||
"get_optimization_rule": ("judge", "get_optimization_rule"),
|
||||
"list_optimization_rules": ("judge", "list_optimization_rules"),
|
||||
"remove_optimization_rule": ("judge", "remove_optimization_rule"),
|
||||
# Token utilities
|
||||
"estimate_tokens": ("tokens", "estimate_tokens"),
|
||||
"get_context_length": ("tokens", "get_context_length"),
|
||||
"count_tokens": ("tokens", "count_tokens"),
|
||||
"needs_chunking": ("tokens", "needs_chunking"),
|
||||
"get_recommended_chunk_size": ("tokens", "get_recommended_chunk_size"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@ Evaluates agent output accuracy by comparing against expected output using LLM-a
|
||||
|
||||
import os
|
||||
import logging
|
||||
from typing import Any, Callable, List, Optional, Union, TYPE_CHECKING
|
||||
from typing import Callable, Optional, Union, TYPE_CHECKING
|
||||
|
||||
from .base import BaseEvaluator
|
||||
from .results import AccuracyResult, EvaluationScore
|
||||
from .grader import parse_score_reasoning
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..agent.agent import Agent
|
||||
@@ -123,19 +124,8 @@ REASONING: [brief explanation]"""
|
||||
|
||||
response_text = response.choices[0].message.content or ""
|
||||
|
||||
score = 5.0
|
||||
reasoning = "Unable to parse response"
|
||||
|
||||
lines = response_text.strip().split('\n')
|
||||
for line in lines:
|
||||
if line.startswith('SCORE:'):
|
||||
try:
|
||||
score = float(line.replace('SCORE:', '').strip())
|
||||
score = max(1.0, min(10.0, score))
|
||||
except ValueError:
|
||||
pass
|
||||
elif line.startswith('REASONING:'):
|
||||
reasoning = line.replace('REASONING:', '').strip()
|
||||
# DRY: Use common parsing function from grader module
|
||||
score, reasoning = parse_score_reasoning(response_text)
|
||||
|
||||
return EvaluationScore(
|
||||
score=score,
|
||||
|
||||
@@ -6,10 +6,11 @@ Evaluates agent outputs against custom criteria using LLM-as-judge.
|
||||
|
||||
import os
|
||||
import logging
|
||||
from typing import Callable, List, Literal, Optional, Union, TYPE_CHECKING
|
||||
from typing import Callable, Literal, Optional, Union, TYPE_CHECKING
|
||||
|
||||
from .base import BaseEvaluator
|
||||
from .results import CriteriaResult, CriteriaScore
|
||||
from .grader import parse_score_reasoning
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..agent.agent import Agent
|
||||
@@ -146,28 +147,24 @@ REASONING: [brief explanation]"""
|
||||
|
||||
response_text = response.choices[0].message.content or ""
|
||||
|
||||
score = 5.0
|
||||
passed = False
|
||||
reasoning = "Unable to parse response"
|
||||
|
||||
lines = response_text.strip().split('\n')
|
||||
for line in lines:
|
||||
if self.scoring_type == "binary":
|
||||
# DRY: Use common parsing for numeric mode, custom for binary
|
||||
if self.scoring_type == "binary":
|
||||
score = 5.0
|
||||
passed = False
|
||||
reasoning = "Unable to parse response"
|
||||
|
||||
lines = response_text.strip().split('\n')
|
||||
for line in lines:
|
||||
if line.startswith('RESULT:'):
|
||||
result_str = line.replace('RESULT:', '').strip().upper()
|
||||
passed = result_str == 'PASS'
|
||||
score = 10.0 if passed else 0.0
|
||||
else:
|
||||
if line.startswith('SCORE:'):
|
||||
try:
|
||||
score = float(line.replace('SCORE:', '').strip())
|
||||
score = max(1.0, min(10.0, score))
|
||||
passed = score >= self.threshold
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if line.startswith('REASONING:'):
|
||||
reasoning = line.replace('REASONING:', '').strip()
|
||||
elif line.startswith('REASONING:'):
|
||||
reasoning = line.replace('REASONING:', '').strip()
|
||||
else:
|
||||
# DRY: Use common parsing function from grader module
|
||||
score, reasoning = parse_score_reasoning(response_text)
|
||||
passed = score >= self.threshold
|
||||
|
||||
return CriteriaScore(
|
||||
score=score,
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
"""
|
||||
Base LLM Grader for PraisonAI Agents.
|
||||
|
||||
Provides a common base class for LLM-as-judge grading to eliminate duplication
|
||||
across AccuracyEvaluator, CriteriaEvaluator, and TrainingGrader.
|
||||
|
||||
DRY: This module extracts the common prompt building and response parsing logic
|
||||
that was duplicated across multiple evaluators.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class GradeResult:
|
||||
"""
|
||||
Result from grading an agent output.
|
||||
|
||||
This is the common result type used by all graders.
|
||||
|
||||
Attributes:
|
||||
score: Quality score (1-10)
|
||||
reasoning: Explanation for the score
|
||||
suggestions: List of improvement suggestions (optional)
|
||||
input_text: The input that was given
|
||||
output: The output that was graded
|
||||
expected_output: Optional expected output
|
||||
timestamp: When grading occurred
|
||||
"""
|
||||
score: float
|
||||
reasoning: str
|
||||
suggestions: List[str] = field(default_factory=list)
|
||||
input_text: str = ""
|
||||
output: str = ""
|
||||
expected_output: Optional[str] = None
|
||||
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "GradeResult":
|
||||
"""Create from dictionary."""
|
||||
return cls(
|
||||
score=data.get("score", 5.0),
|
||||
reasoning=data.get("reasoning", ""),
|
||||
suggestions=data.get("suggestions", []),
|
||||
input_text=data.get("input_text", ""),
|
||||
output=data.get("output", ""),
|
||||
expected_output=data.get("expected_output"),
|
||||
timestamp=data.get("timestamp", datetime.utcnow().isoformat()),
|
||||
)
|
||||
|
||||
|
||||
class BaseLLMGrader:
|
||||
"""
|
||||
Base class for LLM-as-judge grading.
|
||||
|
||||
Provides common functionality for:
|
||||
- Building grading prompts
|
||||
- Parsing LLM responses (SCORE/REASONING/SUGGESTIONS format)
|
||||
- Lazy litellm import
|
||||
- Sync and async grading
|
||||
|
||||
Subclasses can override:
|
||||
- _build_prompt() for custom prompt templates
|
||||
- _parse_response() for custom response parsing
|
||||
|
||||
Usage:
|
||||
grader = BaseLLMGrader()
|
||||
result = grader.grade(
|
||||
input_text="What is Python?",
|
||||
output="Python is a programming language",
|
||||
expected_output="Python is a high-level programming language"
|
||||
)
|
||||
print(f"Score: {result.score}/10")
|
||||
"""
|
||||
|
||||
# Default prompt template - can be overridden by subclasses
|
||||
PROMPT_TEMPLATE = """You are an expert evaluator for AI agent outputs. Your task is to grade the quality of an agent's response.
|
||||
|
||||
INPUT (what the agent was asked):
|
||||
{input_text}
|
||||
|
||||
AGENT OUTPUT (what the agent responded):
|
||||
{output}
|
||||
{expected_section}
|
||||
GRADING CRITERIA:
|
||||
- Accuracy: Is the response factually correct?
|
||||
- Completeness: Does it fully address the input?
|
||||
- Clarity: Is it well-written and easy to understand?
|
||||
- Relevance: Does it stay on topic?
|
||||
|
||||
SCORING GUIDELINES:
|
||||
- 10: Perfect - Excellent in all criteria
|
||||
- 8-9: Very Good - Minor improvements possible
|
||||
- 6-7: Good - Some issues but mostly correct
|
||||
- 4-5: Fair - Significant issues
|
||||
- 2-3: Poor - Major problems
|
||||
- 1: Very Poor - Completely wrong or irrelevant
|
||||
|
||||
Respond in this EXACT format:
|
||||
SCORE: [number 1-10]
|
||||
REASONING: [brief explanation of the score]
|
||||
SUGGESTIONS:
|
||||
- [first suggestion for improvement]
|
||||
- [second suggestion if applicable]
|
||||
- [third suggestion if applicable]
|
||||
|
||||
If no suggestions are needed, write "SUGGESTIONS: None"
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: Optional[str] = None,
|
||||
temperature: float = 0.1,
|
||||
max_tokens: int = 500,
|
||||
):
|
||||
"""
|
||||
Initialize the grader.
|
||||
|
||||
Args:
|
||||
model: LLM model to use for grading (default: gpt-4o-mini)
|
||||
temperature: Temperature for LLM calls (default: 0.1 for consistency)
|
||||
max_tokens: Maximum tokens for LLM response
|
||||
"""
|
||||
self.model = model or os.getenv("OPENAI_MODEL_NAME", "gpt-4o-mini")
|
||||
self.temperature = temperature
|
||||
self.max_tokens = max_tokens
|
||||
|
||||
def _build_prompt(
|
||||
self,
|
||||
input_text: str,
|
||||
output: str,
|
||||
expected_output: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Build the grading prompt.
|
||||
|
||||
Override this method in subclasses for custom prompt templates.
|
||||
|
||||
Args:
|
||||
input_text: The input given to the agent
|
||||
output: The agent's output to grade
|
||||
expected_output: Optional expected output for comparison
|
||||
|
||||
Returns:
|
||||
The prompt string for the LLM
|
||||
"""
|
||||
expected_section = ""
|
||||
if expected_output:
|
||||
expected_section = f"""
|
||||
EXPECTED OUTPUT (ideal response):
|
||||
{expected_output}
|
||||
"""
|
||||
|
||||
return self.PROMPT_TEMPLATE.format(
|
||||
input_text=input_text,
|
||||
output=output,
|
||||
expected_section=expected_section,
|
||||
)
|
||||
|
||||
def _parse_response(
|
||||
self,
|
||||
response_text: str,
|
||||
input_text: str,
|
||||
output: str,
|
||||
expected_output: Optional[str],
|
||||
) -> GradeResult:
|
||||
"""
|
||||
Parse the LLM response into a GradeResult.
|
||||
|
||||
Override this method in subclasses for custom parsing logic.
|
||||
|
||||
Args:
|
||||
response_text: Raw LLM response
|
||||
input_text: Original input
|
||||
output: Original output
|
||||
expected_output: Original expected output
|
||||
|
||||
Returns:
|
||||
Parsed GradeResult
|
||||
"""
|
||||
score = 5.0 # Default
|
||||
reasoning = "Unable to parse response"
|
||||
suggestions: List[str] = []
|
||||
|
||||
lines = response_text.strip().split('\n')
|
||||
in_suggestions = False
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('SCORE:'):
|
||||
try:
|
||||
score_str = line.replace('SCORE:', '').strip()
|
||||
score = float(score_str)
|
||||
# Clamp to valid range
|
||||
score = max(1.0, min(10.0, score))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
elif line.startswith('REASONING:'):
|
||||
reasoning = line.replace('REASONING:', '').strip()
|
||||
|
||||
elif line.startswith('SUGGESTIONS:'):
|
||||
in_suggestions = True
|
||||
rest = line.replace('SUGGESTIONS:', '').strip()
|
||||
if rest.lower() != 'none' and rest:
|
||||
suggestions.append(rest)
|
||||
|
||||
elif in_suggestions and line.startswith('-'):
|
||||
suggestion = line.lstrip('- ').strip()
|
||||
if suggestion and suggestion.lower() != 'none':
|
||||
suggestions.append(suggestion)
|
||||
|
||||
return GradeResult(
|
||||
score=score,
|
||||
reasoning=reasoning,
|
||||
suggestions=suggestions,
|
||||
input_text=input_text,
|
||||
output=output,
|
||||
expected_output=expected_output,
|
||||
)
|
||||
|
||||
def _get_litellm(self):
|
||||
"""
|
||||
Lazy import litellm.
|
||||
|
||||
Returns:
|
||||
The litellm module
|
||||
|
||||
Raises:
|
||||
ImportError: If litellm is not installed
|
||||
"""
|
||||
try:
|
||||
import litellm
|
||||
return litellm
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"litellm is required for LLM grading. "
|
||||
"Install with: pip install litellm"
|
||||
)
|
||||
|
||||
def grade(
|
||||
self,
|
||||
input_text: str,
|
||||
output: str,
|
||||
expected_output: Optional[str] = None,
|
||||
) -> GradeResult:
|
||||
"""
|
||||
Grade an agent output.
|
||||
|
||||
Args:
|
||||
input_text: The input given to the agent
|
||||
output: The agent's output to grade
|
||||
expected_output: Optional expected output for comparison
|
||||
|
||||
Returns:
|
||||
GradeResult with score, reasoning, and suggestions
|
||||
"""
|
||||
litellm = self._get_litellm()
|
||||
|
||||
prompt = self._build_prompt(input_text, output, expected_output)
|
||||
|
||||
try:
|
||||
response = litellm.completion(
|
||||
model=self.model,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=self.temperature,
|
||||
max_tokens=self.max_tokens,
|
||||
)
|
||||
|
||||
response_text = response.choices[0].message.content or ""
|
||||
return self._parse_response(response_text, input_text, output, expected_output)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Grading failed: {e}")
|
||||
return GradeResult(
|
||||
score=5.0,
|
||||
reasoning=f"Grading error: {str(e)}",
|
||||
suggestions=[],
|
||||
input_text=input_text,
|
||||
output=output,
|
||||
expected_output=expected_output,
|
||||
)
|
||||
|
||||
async def grade_async(
|
||||
self,
|
||||
input_text: str,
|
||||
output: str,
|
||||
expected_output: Optional[str] = None,
|
||||
) -> GradeResult:
|
||||
"""
|
||||
Grade an agent output asynchronously.
|
||||
|
||||
Args:
|
||||
input_text: The input given to the agent
|
||||
output: The agent's output to grade
|
||||
expected_output: Optional expected output for comparison
|
||||
|
||||
Returns:
|
||||
GradeResult with score, reasoning, and suggestions
|
||||
"""
|
||||
litellm = self._get_litellm()
|
||||
|
||||
prompt = self._build_prompt(input_text, output, expected_output)
|
||||
|
||||
try:
|
||||
response = await litellm.acompletion(
|
||||
model=self.model,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=self.temperature,
|
||||
max_tokens=self.max_tokens,
|
||||
)
|
||||
|
||||
response_text = response.choices[0].message.content or ""
|
||||
return self._parse_response(response_text, input_text, output, expected_output)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Async grading failed: {e}")
|
||||
return GradeResult(
|
||||
score=5.0,
|
||||
reasoning=f"Grading error: {str(e)}",
|
||||
suggestions=[],
|
||||
input_text=input_text,
|
||||
output=output,
|
||||
expected_output=expected_output,
|
||||
)
|
||||
|
||||
|
||||
def parse_score_reasoning(response_text: str) -> tuple:
|
||||
"""
|
||||
Parse SCORE and REASONING from LLM response.
|
||||
|
||||
DRY: Static utility function for parsing LLM-as-judge responses.
|
||||
Used by AccuracyEvaluator, CriteriaEvaluator, and BaseLLMGrader.
|
||||
|
||||
Args:
|
||||
response_text: Raw LLM response text
|
||||
|
||||
Returns:
|
||||
Tuple of (score: float, reasoning: str)
|
||||
"""
|
||||
score = 5.0 # Default
|
||||
reasoning = "Unable to parse response"
|
||||
|
||||
lines = response_text.strip().split('\n')
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('SCORE:'):
|
||||
try:
|
||||
score_str = line.replace('SCORE:', '').strip()
|
||||
score = float(score_str)
|
||||
# Clamp to valid range
|
||||
score = max(1.0, min(10.0, score))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
elif line.startswith('REASONING:'):
|
||||
reasoning = line.replace('REASONING:', '').strip()
|
||||
|
||||
return score, reasoning
|
||||
|
||||
|
||||
__all__ = [
|
||||
'GradeResult',
|
||||
'BaseLLMGrader',
|
||||
'parse_score_reasoning',
|
||||
]
|
||||
Loaded 100 of 381 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user