Compare commits

...
2 Commits
Author SHA1 Message Date
Mervin Praison 9533abd8a4 Release v3.7.1 2026-01-10 14:29:43 +00:00
Mervin Praison 39c9e50875 Release v3.7.0 2026-01-10 13:09:09 +00:00
53 changed files with 4979 additions and 81 deletions

No files matched your search

+862
View File
@@ -0,0 +1,862 @@
# PraisonAI API Reference
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, AutoAgents, AutoRagAgent, ContextAgent, DeepResearchAgent, ImageAgent, PraisonAIAgents, PromptExpanderAgent, QueryRewriterAgent, create_context_agent
```
Methods:
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">achat</a>(prompt: str, temperature = 1.0, tools = None, output_json = None, output_pydantic = None, reasoning_steps = False, task_name = None, task_description = None, task_id = None)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">aexecute</a>(task, context = None)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">agent_id</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">analyze_prompt</a>(prompt: str) -> set</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">arun</a>(prompt: str, **kwargs)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">astart</a>(prompt: str, **kwargs)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">auto_memory</a>()</code>
* <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)</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>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">clean_json_output</a>(output: str) -> str</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">clear_history</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">console</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">context_manager</a>()</code>
* <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">display_generating</a>(content: str, start_time: float)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">execute</a>(task, context = None)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">execute_tool</a>(function_name, arguments)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">execute_tool_async</a>(function_name: str, arguments: Dict[str, Any]) -> Any</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">from_template</a>(uri: str, config: Optional[Dict[str, Any]] = None, offline: bool = False, **kwargs) -> 'Agent'</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">generate_task</a>() -> 'Task'</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">get_available_tools</a>() -> List[Any]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">get_memory_context</a>(query: Optional[str] = None) -> str</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">get_recommended_stage</a>(prompt: str) -> str</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">get_rules_context</a>(file_path: Optional[str] = None, include_manual: Optional[List[str]] = None) -> str</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">get_skills_prompt</a>() -> str</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">handoff_to</a>(target_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None, config: Optional['HandoffConfig'] = None) -> 'HandoffResult'</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">handoff_to_async</a>(target_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None, config: Optional['HandoffConfig'] = None) -> 'HandoffResult'</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">launch</a>(path: str = '/', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = 'http')</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">llm_model</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">output_style</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">output_style</a>(value)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">policy</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">policy</a>(value)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">query</a>(question: str, **kwargs) -> 'RAGResult'</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">rag</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">rag_query</a>(question: str, **kwargs) -> 'RAGResult'</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">retrieval_config</a>()</code>
* <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">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, **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">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, **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">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_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">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">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">chat</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 PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">add_task</a>(task)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">aexecute_task</a>(task_id)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<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 PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_all_tasks</a>()</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_task</a>(task_id)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">astart</a>(content = None, return_dict = False, **kwargs)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clean_json_output</a>(output: str) -> str</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clear_state</a>() -> None</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">context_manager</a>()</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">current_plan</a>()</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">default_completion_checker</a>(task, agent_output)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">delete_state</a>(key: str) -> bool</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">display_token_usage</a>()</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">execute_task</a>(task_id)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_agent_details</a>(agent_name)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_state</a>() -> Dict[str, Any]</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_tasks_status</a>()</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_detailed_token_report</a>() -> Dict[str, Any]</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_plan_markdown</a>() -> str</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_state</a>(key: str, default: Any = None) -> Any</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_details</a>(task_id)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_result</a>(task_id)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_status</a>(task_id)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_todo_markdown</a>() -> str</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_token_usage_summary</a>() -> Dict[str, Any]</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">has_state</a>(key: str) -> bool</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<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 PraisonAIAgents">PraisonAIAgents.<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 PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">restore_session_state</a>(session_id: str) -> bool</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run</a>(content = None, return_dict = False, **kwargs)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run_all_tasks</a>()</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run_task</a>(task_id)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_output_to_file</a>(task, task_output)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<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 PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">set_state</a>(key: str, value: Any) -> None</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, **kwargs)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">todo_list</a>()</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_plan_step_status</a>(step_id: str, status: str) -> bool</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_state</a>(updates: Dict) -> None</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 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="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, WorkflowStep, 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 WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">async_execution</a>() -> bool</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">branch_condition</a>() -> Optional[Dict[str, List[str]]]</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">context_from</a>() -> Optional[List[str]]</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">evaluator</a>()</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">executor</a>()</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">max_retries</a>() -> int</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">next_steps</a>() -> Optional[List[str]]</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_error</a>() -> str</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">output_file</a>() -> Optional[str]</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">output_json</a>() -> Optional[Any]</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">output_pydantic</a>() -> Optional[Any]</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">output_variable</a>() -> Optional[str]</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">quality_check</a>() -> bool</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">rerun</a>() -> bool</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">retain_full_context</a>() -> bool</code>
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">to_dict</a>() -> Dict[str, Any]</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">loop</a>(step: Any, over: Optional[str] = None, from_csv: Optional[str] = None, from_file: Optional[str] = None, var_name: str = 'item') -> 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
```
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">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">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>
# Knowledge
Types:
```python
from praisonaiagents import Chunking, Knowledge
```
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>
# RAG
Types:
```python
from praisonaiagents import RAG, RAGCitation, RAGConfig, RAGResult
```
Methods:
* <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>
# 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 LLMGuardrail
```
Methods:
* <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>
# Skills
Types:
```python
from praisonaiagents import SkillLoader, SkillManager, SkillMetadata, SkillProperties
```
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>
# 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>
# Telemetry
Types:
```python
from praisonaiagents import get_telemetry
```
Methods:
* <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">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>
# 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)</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)</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>
# Other
Types:
```python
from praisonaiagents import AutoRagConfig, Citation, CodeExecutionStep, DeepResearchResponse, ExpandResult, ExpandStrategy, FileSearchCall, MCPCall, Provider, RagRetrievalPolicy, ReasoningStep, RewriteResult, RewriteStrategy, Task, WebSearchCall
```
Methods:
* <code title="class AutoRagConfig">AutoRagConfig.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">from_dict</a>(data: Dict[str, Any]) -> 'AutoRagConfig'</code>
* <code title="class AutoRagConfig">AutoRagConfig.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">to_dict</a>() -> Dict[str, Any]</code>
* <code title="class Citation">Citation.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'Citation'</code>
* <code title="class Citation">Citation.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
* <code title="class DeepResearchResponse">DeepResearchResponse.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">get_all_sources</a>() -> List[Dict[str, str]]</code>
* <code title="class DeepResearchResponse">DeepResearchResponse.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">get_citation_text</a>(citation: Citation) -> str</code>
* <code title="class RewriteResult">RewriteResult.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">all_queries</a>() -> List[str]</code>
* <code title="class RewriteResult">RewriteResult.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">primary_query</a>() -> str</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>
# Wrapper (praisonai)
Types:
```python
from praisonai import CloudProvider, Deploy, DeployConfig, DeployType, PraisonAI, __version__
```
# CLI
Methods:
* <code title="cli">praisonai <a href="./src/praisonai/praisonai/cli/main.py">--help</a></code>
* <code title="cli">praisonai acp acp-main <a href="./src/praisonai/praisonai/cli/commands/acp.py">--help</a></code>
* <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 audit agent-centric <a href="./src/praisonai/praisonai/cli/commands/audit.py">--help</a></code>
* <code title="cli">praisonai benchmark agent <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
* <code title="cli">praisonai benchmark benchmark-callback <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
* <code title="cli">praisonai benchmark cli <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
* <code title="cli">praisonai benchmark compare <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
* <code title="cli">praisonai benchmark litellm <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
* <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 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>
* <code title="cli">praisonai code <a href="./src/praisonai/praisonai/cli/main.py">--help</a></code>
* <code title="cli">praisonai code code-main <a href="./src/praisonai/praisonai/cli/commands/code.py">--help</a></code>
* <code title="cli">praisonai commit commit-main <a href="./src/praisonai/praisonai/cli/commands/commit.py">--help</a></code>
* <code title="cli">praisonai completion bash <a href="./src/praisonai/praisonai/cli/commands/completion.py">--help</a></code>
* <code title="cli">praisonai completion completion-callback <a href="./src/praisonai/praisonai/cli/commands/completion.py">--help</a></code>
* <code title="cli">praisonai completion fish <a href="./src/praisonai/praisonai/cli/commands/completion.py">--help</a></code>
* <code title="cli">praisonai completion zsh <a href="./src/praisonai/praisonai/cli/commands/completion.py">--help</a></code>
* <code title="cli">praisonai config get <a href="./src/praisonai/praisonai/cli/commands/config.py">--help</a></code>
* <code title="cli">praisonai config list <a href="./src/praisonai/praisonai/cli/commands/config.py">--help</a></code>
* <code title="cli">praisonai config path <a href="./src/praisonai/praisonai/cli/commands/config.py">--help</a></code>
* <code title="cli">praisonai config reset <a href="./src/praisonai/praisonai/cli/commands/config.py">--help</a></code>
* <code title="cli">praisonai config set <a href="./src/praisonai/praisonai/cli/commands/config.py">--help</a></code>
* <code title="cli">praisonai context add <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context clear <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context compact <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context export <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context show <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context stats <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai debug acp <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai debug debug-callback <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai debug interactive <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai debug lsp <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai debug trace <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai deploy aws <a href="./src/praisonai/praisonai/cli/commands/deploy.py">--help</a></code>
* <code title="cli">praisonai deploy azure <a href="./src/praisonai/praisonai/cli/commands/deploy.py">--help</a></code>
* <code title="cli">praisonai deploy docker <a href="./src/praisonai/praisonai/cli/commands/deploy.py">--help</a></code>
* <code title="cli">praisonai deploy gcp <a href="./src/praisonai/praisonai/cli/commands/deploy.py">--help</a></code>
* <code title="cli">praisonai diag diag-callback <a href="./src/praisonai/praisonai/cli/commands/diag.py">--help</a></code>
* <code title="cli">praisonai diag export <a href="./src/praisonai/praisonai/cli/commands/diag.py">--help</a></code>
* <code title="cli">praisonai docs api-md <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
* <code title="cli">praisonai docs generate <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
* <code title="cli">praisonai docs list <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
* <code title="cli">praisonai docs report <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
* <code title="cli">praisonai docs run <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
* <code title="cli">praisonai docs run-all <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
* <code title="cli">praisonai docs serve <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
* <code title="cli">praisonai docs stats <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
* <code title="cli">praisonai doctor config <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai doctor db <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai doctor doctor-callback <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai doctor env <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai doctor mcp <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai doctor network <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai doctor performance <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai doctor selftest <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai doctor tools <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
* <code title="cli">praisonai endpoints list <a href="./src/praisonai/praisonai/cli/commands/endpoints.py">--help</a></code>
* <code title="cli">praisonai endpoints test <a href="./src/praisonai/praisonai/cli/commands/endpoints.py">--help</a></code>
* <code title="cli">praisonai environment check <a href="./src/praisonai/praisonai/cli/commands/environment.py">--help</a></code>
* <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 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>
* <code title="cli">praisonai examples report <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples run <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples run-all <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples stats <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai hooks add <a href="./src/praisonai/praisonai/cli/commands/hooks.py">--help</a></code>
* <code title="cli">praisonai hooks list <a href="./src/praisonai/praisonai/cli/commands/hooks.py">--help</a></code>
* <code title="cli">praisonai hooks remove <a href="./src/praisonai/praisonai/cli/commands/hooks.py">--help</a></code>
* <code title="cli">praisonai knowledge add <a href="./src/praisonai/praisonai/cli/commands/knowledge.py">--help</a></code>
* <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 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>
* <code title="cli">praisonai lsp status <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
* <code title="cli">praisonai lsp stop <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
* <code title="cli">praisonai mcp add <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp list <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp mcp-callback <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp remove <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp test <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai memory add <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory clear <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory search <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory show <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <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 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>
* <code title="cli">praisonai profile query <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
* <code title="cli">praisonai profile snapshot <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
* <code title="cli">praisonai profile startup <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
* <code title="cli">praisonai profile suite <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
* <code title="cli">praisonai rag chat <a href="./src/praisonai/praisonai/cli/commands/rag.py">--help</a></code>
* <code title="cli">praisonai rag eval <a href="./src/praisonai/praisonai/cli/commands/rag.py">--help</a></code>
* <code title="cli">praisonai rag index <a href="./src/praisonai/praisonai/cli/commands/rag.py">--help</a></code>
* <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 install <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 run <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 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>
* <code title="cli">praisonai retrieval search <a href="./src/praisonai/praisonai/cli/commands/retrieval.py">--help</a></code>
* <code title="cli">praisonai rules add <a href="./src/praisonai/praisonai/cli/commands/rules.py">--help</a></code>
* <code title="cli">praisonai rules clear <a href="./src/praisonai/praisonai/cli/commands/rules.py">--help</a></code>
* <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 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>
* <code title="cli">praisonai schedule logs <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
* <code title="cli">praisonai schedule restart <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
* <code title="cli">praisonai schedule schedule-callback <a href="./src/praisonai/praisonai/cli/commands/schedule.py">--help</a></code>
* <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 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 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 create <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>
* <code title="cli">praisonai skills validate <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai templates create <a href="./src/praisonai/praisonai/cli/commands/templates.py">--help</a></code>
* <code title="cli">praisonai templates list <a href="./src/praisonai/praisonai/cli/commands/templates.py">--help</a></code>
* <code title="cli">praisonai test info <a href="./src/praisonai/praisonai/cli/commands/test.py">--help</a></code>
* <code title="cli">praisonai test run <a href="./src/praisonai/praisonai/cli/commands/test.py">--help</a></code>
* <code title="cli">praisonai todo add <a href="./src/praisonai/praisonai/cli/commands/todo.py">--help</a></code>
* <code title="cli">praisonai todo done <a href="./src/praisonai/praisonai/cli/commands/todo.py">--help</a></code>
* <code title="cli">praisonai todo list <a href="./src/praisonai/praisonai/cli/commands/todo.py">--help</a></code>
* <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 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 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>
* <code title="cli">praisonai ui realtime <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
* <code title="cli">praisonai ui ui-main <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
* <code title="cli">praisonai version check <a href="./src/praisonai/praisonai/cli/commands/version.py">--help</a></code>
* <code title="cli">praisonai version show <a href="./src/praisonai/praisonai/cli/commands/version.py">--help</a></code>
* <code title="cli">praisonai version version-callback <a href="./src/praisonai/praisonai/cli/commands/version.py">--help</a></code>
* <code title="cli">praisonai workflow create <a href="./src/praisonai/praisonai/cli/commands/workflow.py">--help</a></code>
* <code title="cli">praisonai workflow list <a href="./src/praisonai/praisonai/cli/commands/workflow.py">--help</a></code>
* <code title="cli">praisonai workflow run <a href="./src/praisonai/praisonai/cli/commands/workflow.py">--help</a></code>
# TypeScript
Types/Exports:
```ts
export { Agent, Agents, PraisonAIAgents, Router } from "./agent";
export type { PraisonAIAgentsConfig, SimpleAgentConfig, SimpleRouteConfig, SimpleRouterConfig } from "./agent";
export { ContextAgent, createContextAgent } from "./agent/context";
export { Handoff, handoff, handoffFilters } from "./agent/handoff";
export { ImageAgent, createImageAgent } from "./agent/image";
export { PromptExpanderAgent, createPromptExpanderAgent } from "./agent/prompt-expander";
export { QueryRewriterAgent, createQueryRewriterAgent } from "./agent/query-rewriter";
export { DeepResearchAgent, createDeepResearchAgent } from "./agent/research";
export { RouterAgent, createRouter, routeConditions } from "./agent/router";
export { // Agent loop
createAgentLoop, // DevTools
enableDevTools, // MCP
createMCP, // Middleware (renamed to avoid conflicts)
createCachingMiddleware, // Models
createModel, // Multimodal
createImagePart, // Next.js
createRouteHandler, // OAuth for MCP
type OAuthClientProvider, // Server adapters
createHttpHandler, // Speech & Transcription
generateSpeech, // Telemetry (AI SDK v6 parity)
configureTelemetry, // Tool Approval (AI SDK v6 parity)
ApprovalManager, // Tools
defineTool, // UI Message (AI SDK v6 parity)
convertToModelMessages, AIAgentStep, AIEmbedManyResult, AIEmbedOptions, AIEmbedResult, AIFilePart, AIGenerateImageOptions, AIGenerateImageResult, AIGenerateObjectOptions, AIGenerateObjectResult, AIGenerateTextOptions, AIGenerateTextResult, AIImagePart, AIMiddleware, AIMiddlewareConfig, AIModelMessage, AISpan, AISpanKind, AISpanOptions, AISpanStatus, AIStreamObjectOptions, AIStreamObjectResult, AIStreamTextOptions, AIStreamTextResult, AITelemetryEvent, AITelemetrySettings, AITextPart, AIToolDefinition, AITracer, AgentLoop, DANGEROUS_PATTERNS, MODEL_ALIASES, SPEECH_MODELS, TRANSCRIPTION_MODELS, ToolApprovalDeniedError, ToolApprovalTimeoutError, aiEmbed, aiEmbedMany, aiGenerateImage, aiGenerateObject, aiGenerateText, aiStreamObject, aiStreamText, applyMiddleware, autoEnableDevTools, base64ToUint8Array, clearAICache, clearEvents, closeAllMCPClients, closeMCPClient, convertToUIMessages, createAILoggingMiddleware, createAISpan, createApprovalResponse, createDangerousPatternChecker, createDevToolsMiddleware, createExpressHandler, createFastifyHandler, createFilePart, createHonoHandler, createMultimodalMessage, createNestHandler, createPagesHandler, createPdfPart, createSystemMessage, createTelemetryMiddleware, createTelemetrySettings, createTextMessage, createTextPart, createToolSet, disableAITelemetry, disableDevTools, enableAITelemetry, functionToTool, getAICacheStats, getApprovalManager, getDevToolsState, getDevToolsUrl, getEvents, getMCPClient, getModel, getTelemetrySettings, getToolsNeedingApproval, getTracer, hasModelAlias, hasPendingApprovals, initOpenTelemetry, isDangerous, isDataUrl, isDevToolsEnabled, isTelemetryEnabled, isUrl, listModelAliases, mcpToolsToAITools, parseModel, pipeUIMessageStreamToResponse, recordEvent, resolveModelAlias, safeValidateUIMessages, setApprovalManager, stopAfterSteps, stopWhen, stopWhenNoToolCalls, toMessageContent, toUIMessageStreamResponse, transcribe, uint8ArrayToBase64, validateUIMessages, withApproval, withSpan, wrapModel } from "./ai";
export { AutoAgents, createAutoAgents } from "./auto";
export { BaseCache, FileCache, MemoryCache, createFileCache, createMemoryCache } from "./cache";
export { CLI_SPEC_VERSION, executeCommand, parseArgs } from "./cli";
export { // Autonomy Mode
AutonomyManager, // Background Jobs
JobQueue, // Checkpoints
CheckpointManager, // Cost Tracker
CostTracker, // External Agents
BaseExternalAgent, // Fast Context
FastContext, // Flow Display
FlowDisplay, // Git Integration
GitManager, // Interactive TUI
InteractiveTUI, // N8N Integration
N8NIntegration, // Repo Map
RepoMap, // Sandbox Executor
SandboxExecutor, // Scheduler
Scheduler, // Slash Commands
SlashCommandHandler, AiderAgent, ClaudeCodeAgent, CodexCliAgent, CommandValidator, CostTokenUsage, DEFAULT_BLOCKED_COMMANDS, DEFAULT_BLOCKED_PATHS, DEFAULT_IGNORE_PATTERNS, DiffViewer, FileCheckpointStorage, FileJobStorage, GeminiCliAgent, GenericExternalAgent, HistoryManager, MODEL_PRICING, MODE_POLICIES, MemoryCheckpointStorage, MemoryJobStorage, StatusDisplay, cliApprovalPrompt, createAutonomyManager, createCheckpointManager, createCostTracker, createDiffViewer, createExternalAgent, createFastContext, createFileCheckpointStorage, createFileJobStorage, createFlowDisplay, createGitManager, createHistoryManager, createInteractiveTUI, createJobQueue, createN8NIntegration, createRepoMap, createSandboxExecutor, createScheduler, createSlashCommandHandler, createStatusDisplay, cronExpressions, estimateTokens, executeSlashCommand, externalAgentAsTool, formatCost, getExternalAgentRegistry, getQuickContext, getRepoTree, isSlashCommand, parseSlashCommand, registerCommand, renderWorkflow, sandboxExec, triggerN8NWebhook } from "./cli/features";
export { createDbAdapter, db, getDefaultDbAdapter, setDefaultDbAdapter } from "./db";
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 { EvalSuite, accuracyEval, performanceEval, reliabilityEval } from "./eval";
export { AgentEventBus, AgentEvents, EventEmitterPubSub, PubSub, createEventBus, createPubSub } from "./events";
export { LLMGuardrail, createLLMGuardrail } from "./guardrails/llm-guardrail";
export { // Computer Use
createComputerUse, ComputerUseClient, createCLIApprovalPrompt, createComputerUseAgent } from "./integrations/computer-use";
export { BaseObservabilityProvider, ConsoleObservabilityProvider, LangfuseObservabilityProvider, MemoryObservabilityProvider, ObservabilityTraceContext, createConsoleObservability, createLangfuseObservability, createMemoryObservability } from "./integrations/observability";
export { // Natural Language Postgres
createNLPostgres, NLPostgresClient, NLPostgresConfig, createPostgresTool } from "./integrations/postgres";
export { // Slack
createSlackBot, SlackBot, parseSlackMessage, verifySlackSignature } from "./integrations/slack";
export { BaseVectorStore, ChromaVectorStore, MemoryVectorStore, PineconeVectorStore, QdrantVectorStore, VectorQueryResult, WeaviateVectorStore, createChromaStore, createMemoryVectorStore, createPineconeStore, createQdrantStore, createWeaviateStore } from "./integrations/vector";
export { BaseVoiceProvider, ElevenLabsVoiceProvider, OpenAIVoiceProvider, createElevenLabsVoice, createOpenAIVoice } from "./integrations/voice";
export { GraphRAG, GraphStore, createGraphRAG } from "./knowledge/graph-rag";
export { BaseReranker, CohereReranker, CrossEncoderReranker, LLMReranker, createCohereReranker, createCrossEncoderReranker, createLLMReranker } from "./knowledge/reranker";
export { // Provider classes
OpenAIProvider, // Provider factory and utilities
createProvider, // Provider registry (extensibility API)
ProviderRegistry, // Types
type LLMProvider, AnthropicProvider, BaseProvider, GoogleProvider, ProviderMessage, ProviderToolDefinition, createProviderRegistry, getAvailableProviders, getDefaultProvider, getDefaultRegistry, hasProvider, isProviderAvailable, listProviders, parseModelString, registerBuiltinProviders, registerProvider, unregisterProvider } from "./llm/providers";
export { ADAPTERS, AISDK_PROVIDERS, COMMUNITY_PROVIDERS, PROVIDER_ALIASES } from "./llm/providers/ai-sdk/types";
export { AutoMemory, AutoMemoryKnowledgeBase, AutoMemoryVectorStore, DEFAULT_POLICIES, createAutoMemory, createLLMSummarizer } from "./memory/auto-memory";
export { FileMemory, createFileMemory } from "./memory/file-memory";
export { Memory, createMemory } from "./memory/memory";
export type { MemoryConfig, MemoryEntry } from "./memory/memory";
export { // Adapters
NoopObservabilityAdapter, // Constants
OBSERVABILITY_TOOLS, // Global adapter management
setObservabilityAdapter, // Types
type SpanKind, ConsoleObservabilityAdapter, MemoryObservabilityAdapter, clearAdapterCache, createConsoleAdapter, createMemoryAdapter, createObservabilityAdapter, getObservabilityAdapter, getObservabilityToolInfo, hasObservabilityToolEnvVar, listObservabilityTools, noopAdapter, resetObservabilityAdapter, trace } from "./observability";
export { Plan, PlanStep, PlanStorage, PlanningAgent, TaskAgent, TodoItem, TodoList, createPlan, createPlanStorage, createPlanningAgent, createTaskAgent, createTodoList } from "./planning";
export { SkillManager, createSkillManager, parseSkillFile } from "./skills";
export { AgentTelemetry, TelemetryCollector, cleanupTelemetry, createAgentTelemetry, disableTelemetry, enableTelemetry, getTelemetry } from "./telemetry";
export { BaseTool, FunctionTool, ToolRegistry, ToolResult, ToolValidationError, createTool, getRegistry, getTool, registerTool, tool, validateTool } from "./tools";
export { airweaveSearch, bedrockBrowserClick, bedrockBrowserFill, bedrockBrowserNavigate, bedrockCodeInterpreter, codeExecution, codeMode, createCustomTool, exaSearch, firecrawlCrawl, firecrawlScrape, parallelSearch, perplexitySearch, registerCustomTool, registerLocalTool, registerNpmTool, superagentGuard, superagentRedact, superagentVerify, tavilyCrawl, tavilyExtract, tavilySearch, valyuBioSearch, valyuCompanyResearch, valyuEconomicsSearch, valyuFinanceSearch, valyuPaperSearch, valyuPatentSearch, valyuSecSearch, valyuWebSearch } from "./tools/builtins";
export { MissingDependencyError, MissingEnvVarError, ToolsRegistry, composeMiddleware, createLoggingMiddleware, createRateLimitMiddleware, createRedactionMiddleware, createRetryMiddleware, createTimeoutMiddleware, createToolsRegistry, createTracingMiddleware, createValidationMiddleware, getToolsRegistry, resetToolsRegistry } from "./tools/registry";
export type { InstallHints, PraisonTool, RedactionHooks, RegisteredTool, ToolCapabilities, ToolExecutionContext, ToolExecutionResult, ToolFactory, ToolHooks, ToolInstallStatus, ToolLimits, ToolLogger, ToolMetadata, ToolMiddleware, ToolParameterProperty, ToolParameterSchema } from "./tools/registry";
export { registerBuiltinTools, tools } from "./tools/tools";
export { Workflow, loop, parallel, repeat, route } from "./workflows";
export type { StepResult, WorkflowContext, WorkflowStep } from "./workflows";
export { createWorkflowFromYAML, loadWorkflowFromFile, parseYAMLWorkflow, validateWorkflowDefinition } from "./workflows/yaml-parser";
```
# Optional Plugins
External tools are available via `praisonai-tools` package:
```bash
pip install praisonai-tools
```
See [PraisonAI-tools](https://github.com/MervinPraison/PraisonAI-tools) for available tools.
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=3.6.2" \
"praisonai>=3.7.1" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=3.6.2" \
"praisonai>=3.7.1" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=3.6.2" \
"praisonai>=3.7.1" \
"praisonai[ui]" \
"praisonai[crewai]"
+96
View File
@@ -0,0 +1,96 @@
# Consolidated Params Examples
Examples demonstrating the **Agent-Centric API** with consolidated parameters.
## Precedence Ladder
```
Instance > Config > Array > Dict > String > Bool > Default
```
## Quick Reference
| Feature | Presets | Example |
|---------|---------|---------|
| `memory` | file, sqlite, redis, postgres, mem0, mongodb | `memory="sqlite"` |
| `output` | minimal, normal, verbose, debug, silent | `output="verbose"` |
| `execution` | fast, balanced, thorough, unlimited | `execution="balanced"` |
| `planning` | reasoning, read_only, auto | `planning="reasoning"` |
| `reflection` | minimal, standard, thorough | `reflection="standard"` |
| `guardrails` | strict, permissive, safety | `guardrails="strict"` |
| `web` | duckduckgo, tavily, google, bing, serper | `web="duckduckgo"` |
| `context` | sliding_window, summarize, truncate | `context="sliding_window"` |
| `autonomy` | suggest, auto_edit, full_auto | `autonomy="suggest"` |
| `caching` | enabled, disabled, prompt | `caching="prompt"` |
## Examples
### Basic Examples (Single Feature)
| File | Feature | Description |
|------|---------|-------------|
| `basic_agent.py` | Agent | Minimal agent with memory |
| `basic_memory.py` | memory | SQLite memory preset |
| `basic_output.py` | output | Verbose output preset |
| `basic_execution.py` | execution | Execution control |
| `basic_guardrails.py` | guardrails | Callable validator |
| `basic_reflection.py` | reflection | Self-reflection preset |
| `basic_web.py` | web | Web search preset |
| `basic_planning.py` | planning | Planning preset |
| `basic_autonomy.py` | autonomy | Autonomy preset |
| `basic_caching.py` | caching | Caching preset |
| `basic_knowledge.py` | knowledge | RAG with sources |
| `basic_context.py` | context | Context management |
| `basic_hooks.py` | hooks | Lifecycle callbacks |
### Multi-Agent Examples
| File | Description |
|------|-------------|
| `basic_agents.py` | Multi-agent with memory and planning |
### Workflow Examples
| File | Description |
|------|-------------|
| `basic_workflow.py` | Simple workflow with output preset |
| `basic_workflow_agentlike.py` | Workflow with agent-like params |
| `basic_step_override.py` | Step-level param overrides |
| `advanced_workflow_full_features.py` | All consolidated params |
### Advanced Examples
| File | Description |
|------|-------------|
| `advanced_output_execution.py` | Combined output + execution |
## Usage Forms
Each consolidated param supports multiple input forms:
```python
# Bool - enable with defaults
memory=True
# String preset
memory="sqlite"
# Dict config
memory={"backend": "sqlite", "user_id": "user123"}
# Config instance
memory=MemoryConfig(backend="sqlite", user_id="user123")
# Array with overrides (for some params)
guardrails=["strict", {"max_retries": 10}]
```
## Running Examples
```bash
# Set API key
export OPENAI_API_KEY=your_key_here
# Run any example
python basic_agent.py
```
@@ -0,0 +1,18 @@
"""
Basic Autonomy Example - Agent-Centric API
Demonstrates autonomy with consolidated params.
Presets: suggest, auto_edit, full_auto
"""
from praisonaiagents import Agent
# Basic: Enable autonomy with preset
agent = Agent(
instructions="You are an autonomous assistant.",
autonomy="suggest", # Presets: suggest, auto_edit, full_auto
)
if __name__ == "__main__":
response = agent.start("Help me organize my project files.")
print(response)
@@ -0,0 +1,23 @@
"""
Basic Caching Example - Agent-Centric API
Demonstrates caching with consolidated params.
Presets: enabled, disabled, prompt
"""
from praisonaiagents import Agent
# Basic: Enable caching with preset
agent = Agent(
instructions="You are a helpful assistant with caching enabled.",
caching="prompt", # Presets: enabled, disabled, prompt
)
if __name__ == "__main__":
# First call - will cache
response = agent.start("What is the capital of France?")
print(response)
# Second call - may use cache
response = agent.start("What is the capital of France?")
print(response)
@@ -0,0 +1,18 @@
"""
Basic Context Example - Agent-Centric API
Demonstrates context management with consolidated params.
Presets: sliding_window, summarize, truncate
"""
from praisonaiagents import Agent
# Basic: Enable context management with preset
agent = Agent(
instructions="You are a helpful assistant with context management.",
context="sliding_window", # Presets: sliding_window, summarize, truncate
)
if __name__ == "__main__":
response = agent.start("Tell me a long story about a dragon.")
print(response)
@@ -0,0 +1,28 @@
"""
Basic Hooks Example - Agent-Centric API
Demonstrates lifecycle hooks with consolidated params.
Valid keys: on_step, on_tool_call, middleware
"""
from praisonaiagents import Agent
# Define hook callbacks
def on_step_callback(step_info):
print(f"Step: {step_info}")
def on_tool_call_callback(tool_info):
print(f"Tool called: {tool_info}")
# Basic: Enable hooks with dict
agent = Agent(
instructions="You are a helpful assistant.",
hooks={
"on_step": on_step_callback,
"on_tool_call": on_tool_call_callback,
},
)
if __name__ == "__main__":
response = agent.start("What is 2 + 2?")
print(response)
@@ -0,0 +1,18 @@
"""
Basic Knowledge (RAG) Example - Agent-Centric API
Demonstrates knowledge with consolidated params.
Supports: bool, list of sources, dict config, KnowledgeConfig
"""
from praisonaiagents import Agent
# Basic: Enable knowledge with list of sources
agent = Agent(
instructions="You are a helpful assistant with document knowledge.",
knowledge=["docs/"], # List of file/folder paths
)
if __name__ == "__main__":
response = agent.start("What does the documentation say about installation?")
print(response)
@@ -0,0 +1,18 @@
"""
Basic Planning Example - Agent-Centric API
Demonstrates planning with consolidated params.
Presets: reasoning, read_only, auto
"""
from praisonaiagents import Agent
# Basic: Enable planning with preset
agent = Agent(
instructions="You are a strategic planner.",
planning="reasoning", # Presets: reasoning, read_only, auto
)
if __name__ == "__main__":
response = agent.start("Plan a 3-day trip to Tokyo.")
print(response)
@@ -0,0 +1,18 @@
"""
Basic Reflection Example - Agent-Centric API
Demonstrates reflection (self-reflect) with consolidated params.
Presets: minimal, standard, thorough
"""
from praisonaiagents import Agent
# Basic: Enable reflection with preset
agent = Agent(
instructions="You are a helpful assistant that reflects on your answers.",
reflection="standard", # Presets: minimal, standard, thorough
)
if __name__ == "__main__":
response = agent.start("Explain quantum computing in simple terms.")
print(response)
+18
View File
@@ -0,0 +1,18 @@
"""
Basic Web Search Example - Agent-Centric API
Demonstrates web search with consolidated params.
Presets: duckduckgo, tavily, google, bing, serper, search_only, fetch_only
"""
from praisonaiagents import Agent
# Basic: Enable web search with preset
agent = Agent(
instructions="You are a research assistant with web access.",
web="duckduckgo", # Presets: duckduckgo, tavily, google, bing, serper
)
if __name__ == "__main__":
response = agent.start("What are the latest AI developments in 2024?")
print(response)
@@ -254,10 +254,11 @@ class Agent:
instructions: Optional[str] = None,
# LLM configuration
llm: Optional[Union[str, Any]] = None,
function_calling_llm: Optional[Any] = None,
llm_config: Optional[Dict[str, Any]] = None,
base_url: Optional[str] = None,
api_key: Optional[str] = None,
model: Optional[Union[str, Any]] = None, # Alias for llm=
function_calling_llm: Optional[Any] = None, # DEPRECATED: use llm= with LLMConfig
llm_config: Optional[Dict[str, Any]] = None, # DEPRECATED: use llm= with LLMConfig
base_url: Optional[str] = None, # Kept separate (connection/auth)
api_key: Optional[str] = None, # Kept separate (connection/auth)
# Tools
tools: Optional[List[Any]] = None,
allow_delegation: bool = False,
@@ -783,13 +784,30 @@ class Agent:
self._llm_config = llm_config or {}
self._llm_configurable = self._llm_config.get('configurable', False)
# ============================================================
# LLM CONSOLIDATION: Handle model= alias and deprecation warnings
# Precedence: llm= > model= > default
# ============================================================
import warnings
# Handle model= alias for llm= (NO warnings - both are valid)
if llm is None and model is not None:
llm = model # model= is an alias for llm=
# Legacy params (llm_config, function_calling_llm) are accepted silently
# for backward compatibility - NO deprecation warnings per policy
# Store rate limiter (optional, zero overhead when None)
self._rate_limiter = rate_limiter
# Store OpenAI client parameters for lazy initialization
# Store OpenAI client parameters for lazy initialization (kept separate)
self._openai_api_key = api_key
self._openai_base_url = base_url
self.__openai_client = None
# Expose base_url and api_key as properties for tests
self.base_url = base_url
self.api_key = api_key
# If base_url is provided, always create a custom LLM instance
if base_url:
@@ -957,9 +975,14 @@ Your Goal: {self.goal}
self.tools.append(internet_search)
logging.info("Model does not support native web search. Added DuckDuckGo fallback tool.")
# Log warning if web_fetch is enabled but model doesn't support it
# Raise error if web_fetch is explicitly enabled but model doesn't support it
# Web fetch is only supported by Anthropic/Claude models
if web_fetch and not self._model_supports_web_fetch():
logging.warning(f"Model '{self.llm}' does not support native web fetch. Web fetch will be ignored.")
raise ValueError(
f"web_fetch is only supported on Anthropic/Claude models. "
f"Model '{self.llm}' does not support web fetch. "
f"Either use a Claude model (e.g., 'anthropic/claude-sonnet-4') or disable fetch with web=WebConfig(fetch=False)."
)
# Initialize guardrail settings
self.guardrail = guardrail
@@ -17,7 +17,7 @@ Supported Expansion Strategies:
Example:
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
agent = PromptExpanderAgent(verbose=True)
agent = PromptExpanderAgent()
# Basic expansion
result = agent.expand("write a movie script in 3 lines")
@@ -29,7 +29,7 @@ Example:
# With tools for context gathering
from praisonaiagents.tools import internet_search
agent = PromptExpanderAgent(tools=[internet_search], verbose=True)
agent = PromptExpanderAgent(tools=[internet_search])
result = agent.expand("latest AI developments")
"""
@@ -73,13 +73,12 @@ class PromptExpanderAgent:
Attributes:
name: Name of the agent
model: LLM model to use for expansion
verbose: Whether to print detailed logs
model: LLM model to use for expansion (alias for llm=)
temperature: Temperature for LLM generation (higher = more creative)
tools: Optional tools for context gathering
Example:
agent = PromptExpanderAgent(model="gpt-4o-mini", verbose=True)
agent = PromptExpanderAgent(model="gpt-4o-mini")
result = agent.expand("write a poem", strategy=ExpandStrategy.CREATIVE)
print(result.expanded_prompt)
"""
@@ -230,8 +229,7 @@ Expanded prompt:"""
backstory="You are an expert at understanding user intent and transforming brief requests into comprehensive, detailed prompts.",
tools=self.tools,
llm=self.model,
verbose=self.verbose,
markdown=False
output={"verbose": self.verbose, "markdown": False}
)
return self._agent
@@ -15,7 +15,7 @@ Supported Rewriting Strategies:
Example:
from praisonaiagents import QueryRewriterAgent, RewriteStrategy
agent = QueryRewriterAgent(verbose=True)
agent = QueryRewriterAgent()
# Basic rewriting
result = agent.rewrite("AI trends")
@@ -34,7 +34,7 @@ Example:
# With search tools for context-aware rewriting
from praisonaiagents.tools import internet_search
agent = QueryRewriterAgent(tools=[internet_search], verbose=True)
agent = QueryRewriterAgent(tools=[internet_search])
result = agent.rewrite("latest AI developments") # Searches first, then rewrites
"""
@@ -96,13 +96,12 @@ class QueryRewriterAgent:
Attributes:
name: Name of the agent
model: LLM model to use for rewriting
verbose: Whether to print detailed logs
model: LLM model to use for rewriting (alias for llm=)
max_queries: Maximum number of queries to generate for multi-query strategy
abbreviations: Dictionary of common abbreviations to expand
Example:
agent = QueryRewriterAgent(model="gpt-4o-mini", verbose=True)
agent = QueryRewriterAgent(model="gpt-4o-mini")
result = agent.rewrite("ML best practices", strategy=RewriteStrategy.MULTI_QUERY)
for query in result.rewritten_queries:
print(query)
@@ -288,8 +287,7 @@ Improved version:"""
backstory="You are an expert at understanding user intent and transforming queries for optimal information retrieval.",
tools=self.tools,
llm=self.model,
verbose=self.verbose,
markdown=False
output={"verbose": self.verbose, "markdown": False}
)
return self._agent
@@ -148,6 +148,7 @@ class Agents:
guardrails: Optional[Any] = None, # Union[bool, Callable, GuardrailConfig] - validation
web: Optional[Any] = None, # Union[bool, WebConfig] - web search/fetch
reflection: Optional[Any] = None, # Union[bool, ReflectionConfig] - self-reflection
caching: Optional[Any] = None, # Union[bool, CachingConfig] - caching
):
"""
Initialize PraisonAIAgents with consolidated feature parameters.
@@ -177,6 +178,7 @@ class Agents:
self._guardrails = guardrails
self._web = web
self._reflection = reflection
self._caching = caching
# ─────────────────────────────────────────────────────────────────────
# Extract values from consolidated params using UNIFIED CANONICAL resolver
# Precedence: Instance > Config > Dict > Array > String > Bool > Default
@@ -74,6 +74,17 @@ class AutoAgents(Agents):
api_key: Optional[str] = None,
max_agents: int = 3, # Maximum number of agents
autonomy: Optional[Any] = None, # Union[bool, dict, AutonomyConfig] - Enable autonomy for all agents
# ============================================================
# CONSOLIDATED PARAMS (new SDK contract - take precedence)
# ============================================================
output: Optional[Any] = None, # Union[str, OutputConfig] - overrides verbose/markdown
reflection: Optional[Any] = None, # Union[bool, ReflectionConfig] - overrides self_reflect/max_reflect/min_reflect/reflect_llm
caching: Optional[Any] = None, # Union[bool, CachingConfig] - overrides cache
knowledge: Optional[Any] = None, # Union[bool, List[str], KnowledgeConfig] - overrides knowledge_sources/embedder_config
execution: Optional[Any] = None, # Union[str, ExecutionConfig] - overrides max_iter
guardrails: Optional[Any] = None, # Union[bool, Callable, GuardrailConfig]
web: Optional[Any] = None, # Union[bool, WebConfig]
hooks: Optional[Any] = None, # HooksConfig
):
"""Initialize AutoAgents with configuration for automatic agent and task creation."""
if max_agents < 1:
@@ -112,6 +123,16 @@ class AutoAgents(Agents):
self.api_key = api_key
self.autonomy = autonomy # Store autonomy config for agents
# Store consolidated params (new SDK contract - take precedence over legacy)
self._output = output
self._reflection = reflection
self._caching = caching
self._knowledge = knowledge
self._execution = execution
self._guardrails = guardrails
self._web = web
self._hooks = hooks
# Display initial instruction
if self.verbose:
display_instruction(f"🎯 Main Task: {self.instructions}")
@@ -129,14 +150,21 @@ class AutoAgents(Agents):
self._display_agents_and_tasks(agents, tasks)
# Initialize parent class with generated agents and tasks
# Use consolidated output= instead of legacy verbose=
super().__init__(
agents=agents,
tasks=tasks,
verbose=verbose,
completion_checker=completion_checker,
max_retries=max_retries,
process=process,
manager_llm=manager_llm or self.llm
manager_llm=manager_llm or self.llm,
output=self._output, # Consolidated output param
execution=self._execution,
hooks=self._hooks,
memory=self.memory,
reflection=self._reflection,
caching=self._caching,
guardrails=self._guardrails,
web=self._web,
autonomy=self.autonomy,
)
def _display_agents_and_tasks(self, agents: List[Agent], tasks: List[Task]):
@@ -431,38 +459,33 @@ DO NOT use strings for tasks. Each task MUST be a complete object with all four
agent_tools = self._assign_tools_to_agent(agent_config)
# Create the agent with all parameters
# Consolidated params take precedence over legacy params
# Use consolidated knowledge if provided, else fall back to legacy knowledge_sources
effective_knowledge = self._knowledge if self._knowledge is not None else self.knowledge_sources
agent = Agent(
name=agent_config.name,
role=agent_config.role,
goal=agent_config.goal,
backstory=agent_config.backstory,
tools=agent_tools, # Use assigned tools
verbose=self.verbose >= 1,
allow_code_execution=self.allow_code_execution,
memory=self.memory,
markdown=self.markdown,
self_reflect=self.self_reflect,
max_reflect=self.max_reflect,
min_reflect=self.min_reflect,
llm=self.llm,
function_calling_llm=self.function_calling_llm,
llm=self.llm, # Consolidated LLM param
code_execution_mode=self.code_execution_mode,
embedder_config=self.embedder_config,
knowledge=self.knowledge_sources,
use_system_prompt=self.use_system_prompt,
cache=self.cache,
allow_delegation=self.allow_delegation,
step_callback=self.step_callback,
system_template=self.system_template,
prompt_template=self.prompt_template,
response_template=self.response_template,
max_rpm=self.max_rpm,
max_execution_time=self.max_execution_time,
max_iter=self.max_iter,
reflect_llm=self.reflect_llm,
base_url=self.base_url,
api_key=self.api_key,
autonomy=self.autonomy # Pass autonomy config to agents
# Consolidated params (new SDK contract)
output=self._output, # Overrides verbose/markdown
reflection=self._reflection, # Overrides self_reflect/max_reflect/min_reflect/reflect_llm
caching=self._caching, # Overrides cache
knowledge=effective_knowledge, # Overrides knowledge_sources/embedder_config
execution=self._execution, # Overrides max_iter
guardrails=self._guardrails,
web=self._web,
hooks=self._hooks,
autonomy=self.autonomy,
)
agents.append(agent)
@@ -376,7 +376,7 @@ class SubagentDelegator:
return result
async def _create_agent(self, profile: AgentProfile, task: DelegationTask) -> Any:
"""Create an agent from profile."""
"""Create an agent from profile using consolidated params."""
if self.agent_factory:
return self.agent_factory(profile.name)
@@ -387,14 +387,17 @@ class SubagentDelegator:
# Filter tools based on profile
tools = self._get_tools_for_profile(profile)
# Use consolidated params (protocol-compliant)
# output='silent' replaces verbose=False
# execution={max_iter: N} replaces max_iter=N
agent = Agent(
name=profile.name,
role=profile.name.title(),
goal=task.objective,
backstory=profile.system_prompt,
tools=tools,
verbose=False,
max_iter=min(task.max_steps, profile.max_steps),
output='silent',
execution={'max_iter': min(task.max_steps, profile.max_steps)},
)
return agent
@@ -76,6 +76,14 @@ OUTPUT_PRESETS: Dict[str, Dict[str, Any]] = {
"metrics": False,
"reasoning_steps": False,
},
# Streaming preset - enables streaming by default
"stream": {
"verbose": True,
"markdown": True,
"stream": True,
"metrics": False,
"reasoning_steps": False,
},
}
@@ -722,9 +722,8 @@ Workflow Finished: {self.workflow_finished} # ADDED: Workflow Finished Status
goal="Manage the entire flow of tasks and delegate them to the right agent",
backstory="Expert project manager to coordinate tasks among agents",
llm=self.manager_llm,
verbose=self.verbose,
markdown=True,
self_reflect=False
output={"verbose": self.verbose, "markdown": True},
reflection=False
)
class ManagerInstructions(BaseModel):
@@ -1384,9 +1383,8 @@ Workflow Finished: {self.workflow_finished} # ADDED: Workflow Finished Status
goal="Manage the entire flow of tasks and delegate them to the right agent",
backstory="Expert project manager to coordinate tasks among agents",
llm=self.manager_llm,
verbose=self.verbose,
markdown=True,
self_reflect=False
output={"verbose": self.verbose, "markdown": True},
reflection=False
)
class ManagerInstructions(BaseModel):
@@ -254,6 +254,10 @@ class WorkflowStep:
knowledge: Optional[Any] = None # Union[bool, List[str], KnowledgeConfig] - RAG
web: Optional[Any] = None # Union[bool, WebConfig] - web search/fetch
reflection: Optional[Any] = None # Union[bool, ReflectionConfig] - self-reflection
memory: Optional[Any] = None # Union[bool, MemoryConfig] - step-level memory
planning: Optional[Any] = None # Union[bool, PlanningConfig] - step-level planning
hooks: Optional[Any] = None # Union[List, HooksConfig] - step lifecycle hooks
caching: Optional[Any] = None # Union[bool, CachingConfig] - step-level caching
# Status tracking
status: str = "pending" # pending, running, completed, failed, skipped
@@ -278,6 +282,10 @@ class WorkflowStep:
_knowledge_config: Optional[Any] = field(default=None, repr=False)
_web_config: Optional[Any] = field(default=None, repr=False)
_reflection_config: Optional[Any] = field(default=None, repr=False)
_memory_config: Optional[Any] = field(default=None, repr=False)
_planning_config: Optional[Any] = field(default=None, repr=False)
_hooks_config: Optional[Any] = field(default=None, repr=False)
_caching_config: Optional[Any] = field(default=None, repr=False)
def __post_init__(self):
"""Resolve consolidated params to internal values."""
@@ -370,6 +378,62 @@ class WorkflowStep:
config_class=ReflectionConfig, presets=REFLECTION_PRESETS,
default=None,
)
# Resolve memory (NEW for feature parity)
try:
from ..config.feature_configs import MemoryConfig
from ..config.presets import MEMORY_PRESETS
except ImportError:
MemoryConfig = None
MEMORY_PRESETS = {}
if MemoryConfig and self.memory is not None:
self._memory_config = resolve(
value=self.memory, param_name="memory",
config_class=MemoryConfig, presets=MEMORY_PRESETS,
default=None,
)
# Resolve planning (NEW for feature parity)
try:
from ..config.feature_configs import PlanningConfig
from ..config.presets import PLANNING_PRESETS
except ImportError:
PlanningConfig = None
PLANNING_PRESETS = {}
if PlanningConfig and self.planning is not None:
self._planning_config = resolve(
value=self.planning, param_name="planning",
config_class=PlanningConfig, presets=PLANNING_PRESETS,
default=None,
)
# Resolve hooks (NEW for feature parity)
try:
from ..config.feature_configs import HooksConfig
except ImportError:
HooksConfig = None
if HooksConfig and self.hooks is not None:
# Hooks don't have presets, just config class
if isinstance(self.hooks, list):
self._hooks_config = HooksConfig(callbacks=self.hooks)
elif hasattr(self.hooks, 'callbacks'):
self._hooks_config = self.hooks
else:
self._hooks_config = self.hooks
# Resolve caching (NEW for feature parity)
try:
from ..config.feature_configs import CachingConfig
from ..config.presets import CACHING_PRESETS
except ImportError:
CachingConfig = None
CACHING_PRESETS = {}
if CachingConfig and self.caching is not None:
self._caching_config = resolve(
value=self.caching, param_name="caching",
config_class=CachingConfig, presets=CACHING_PRESETS,
default=None,
)
# Property accessors for backward compatibility
@property
@@ -507,6 +571,8 @@ class Workflow:
guardrails: Optional[Any] = None # Union[bool, Callable, GuardrailConfig] - validation
web: Optional[Any] = None # Union[bool, WebConfig] - web search/fetch
reflection: Optional[Any] = None # Union[bool, ReflectionConfig] - self-reflection
execution: Optional[Any] = None # Union[str, ExecutionConfig] - execution control
caching: Optional[Any] = None # Union[bool, CachingConfig] - caching
# Status tracking
status: str = "not_started" # not_started, running, completed, failed
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.10.10"
version = "0.11.1"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
readme = "README.md"
requires-python = ">=3.10"
+12 -8
View File
@@ -179,25 +179,27 @@ class TestAgentWebFetchParameter:
"""Test the web_fetch parameter in Agent class."""
def test_agent_init_with_web_fetch_true(self):
"""Test Agent initialization with web_fetch=True."""
"""Test Agent initialization with web=WebConfig(fetch=True)."""
from praisonaiagents.agent.agent import Agent
from praisonaiagents.config.feature_configs import WebConfig
agent = Agent(
name="Test Agent",
llm="anthropic/claude-3-5-sonnet-latest",
web_fetch=True
web=WebConfig(fetch=True)
)
assert agent.web_fetch is True
def test_agent_init_with_web_fetch_dict(self):
"""Test Agent initialization with web_fetch as dict."""
"""Test Agent initialization with web=WebConfig(fetch=True)."""
from praisonaiagents.agent.agent import Agent
web_fetch_config = {"max_uses": 10, "allowed_domains": ["example.com"]}
from praisonaiagents.config.feature_configs import WebConfig
# Now using consolidated web= param with WebConfig
agent = Agent(
name="Test Agent",
llm="anthropic/claude-3-5-sonnet-latest",
web_fetch=web_fetch_config
web=WebConfig(fetch=True)
)
assert agent.web_fetch == web_fetch_config
assert agent.web_fetch is True
def test_agent_init_without_web_fetch(self):
"""Test Agent initialization without web_fetch."""
@@ -208,10 +210,11 @@ class TestAgentWebFetchParameter:
def test_agent_passes_web_fetch_to_llm(self):
"""Test that Agent passes web_fetch to LLM instance."""
from praisonaiagents.agent.agent import Agent
from praisonaiagents.config.feature_configs import WebConfig
agent = Agent(
name="Test Agent",
llm="anthropic/claude-3-5-sonnet-latest",
web_fetch=True
web=WebConfig(fetch=True)
)
assert agent.llm_instance.web_fetch is True
@@ -270,12 +273,13 @@ class TestWebFetchIntegration:
def test_web_fetch_with_anthropic(self):
"""Test actual web fetch with Anthropic API."""
from praisonaiagents.agent.agent import Agent
from praisonaiagents.config.feature_configs import WebConfig
agent = Agent(
name="Web Fetch Agent",
instructions="You are a helpful assistant that can fetch web content.",
llm="anthropic/claude-3-5-sonnet-latest",
web_fetch=True
web=WebConfig(fetch=True)
)
# This would make an actual API call
@@ -0,0 +1,123 @@
"""
Tests for wrapper agent modernization - ensuring legacy args are mapped to consolidated params.
TDD: These tests verify that wrapper classes (PromptExpanderAgent, QueryRewriterAgent)
correctly use consolidated params (output=) instead of legacy args (verbose=, markdown=).
"""
class TestPromptExpanderAgentModernization:
"""Test PromptExpanderAgent uses consolidated output param."""
def test_agent_uses_output_config_not_legacy_args(self):
"""PromptExpanderAgent should pass output= to Agent, not verbose=/markdown=."""
from praisonaiagents.agent.prompt_expander_agent import PromptExpanderAgent
# Create wrapper with verbose=True
expander = PromptExpanderAgent(verbose=True)
# Access the lazy-initialized agent
agent = expander.agent
# Verify: Agent should have output config set, not legacy verbose/markdown as direct args
# The Agent stores resolved output in self.verbose and self.markdown after resolution
# But the key is that the wrapper passed output= dict, not verbose=/markdown= directly
assert hasattr(agent, 'verbose')
assert hasattr(agent, 'markdown')
# verbose should be True (from output config)
assert agent.verbose == True
# markdown should be False (from output config)
assert agent.markdown == False
def test_agent_verbose_false_propagates(self):
"""PromptExpanderAgent with verbose=False should propagate correctly."""
from praisonaiagents.agent.prompt_expander_agent import PromptExpanderAgent
expander = PromptExpanderAgent(verbose=False)
agent = expander.agent
assert agent.verbose == False
assert agent.markdown == False
class TestQueryRewriterAgentModernization:
"""Test QueryRewriterAgent uses consolidated output param."""
def test_agent_uses_output_config_not_legacy_args(self):
"""QueryRewriterAgent should pass output= to Agent, not verbose=/markdown=."""
from praisonaiagents.agent.query_rewriter_agent import QueryRewriterAgent
# Create wrapper with verbose=True
rewriter = QueryRewriterAgent(verbose=True)
# Access the lazy-initialized agent
agent = rewriter.agent
# Verify output config was applied
assert agent.verbose == True
assert agent.markdown == False
def test_agent_verbose_false_propagates(self):
"""QueryRewriterAgent with verbose=False should propagate correctly."""
from praisonaiagents.agent.query_rewriter_agent import QueryRewriterAgent
rewriter = QueryRewriterAgent(verbose=False)
agent = rewriter.agent
assert agent.verbose == False
assert agent.markdown == False
class TestProcessManagerAgentModernization:
"""Test Process class manager agents use consolidated params."""
def test_manager_agent_creation_uses_consolidated_params(self):
"""Process manager agent should use output= and reflection=, not legacy args."""
# This is a structural test - we verify the code pattern
import inspect
from praisonaiagents.process.process import Process
# Get source of hierarchical method (sync version)
source = inspect.getsource(Process.hierarchical)
# After fix: should contain output= and reflection=
assert 'output=' in source, "Process.hierarchical should use output= consolidated param"
assert 'reflection=' in source, "Process.hierarchical should use reflection= consolidated param"
# Should NOT contain legacy args passed to Agent
assert 'self_reflect=' not in source, "Process.hierarchical should not use legacy self_reflect="
class TestAutoAgentsConsolidatedParams:
"""Test AutoAgents already uses consolidated params correctly."""
def test_autoagents_has_consolidated_params(self):
"""AutoAgents should accept consolidated params."""
import inspect
from praisonaiagents.agents.autoagents import AutoAgents
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
# Should have consolidated params
assert 'output' in params
assert 'reflection' in params
assert 'caching' in params
assert 'knowledge' in params
assert 'execution' in params
assert 'guardrails' in params
assert 'web' in params
assert 'hooks' in params
def test_autoagents_consolidated_takes_precedence(self):
"""AutoAgents consolidated params should override legacy params."""
import inspect
from praisonaiagents.agents.autoagents import AutoAgents
# Get source of _create_agents_and_tasks
source = inspect.getsource(AutoAgents._create_agents_and_tasks)
# Should pass consolidated params to Agent
assert 'output=self._output' in source
assert 'reflection=self._reflection' in source
assert 'caching=self._caching' in source
@@ -0,0 +1,113 @@
"""
TDD Tests for AutoAgents SDK Parity.
Tests that AutoAgents correctly maps legacy params to consolidated params
and passes them through to Agent instances.
"""
import pytest
from unittest.mock import patch, MagicMock
class TestAutoAgentsLegacyParamMapping:
"""Test that AutoAgents maps legacy params to consolidated params."""
def test_autoagents_accepts_legacy_verbose_param(self):
"""AutoAgents should accept verbose param (legacy)."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
assert 'verbose' in params
def test_autoagents_accepts_legacy_markdown_param(self):
"""AutoAgents should accept markdown param (legacy)."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
assert 'markdown' in params
def test_autoagents_accepts_legacy_self_reflect_param(self):
"""AutoAgents should accept self_reflect param (legacy)."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
assert 'self_reflect' in params
def test_autoagents_accepts_legacy_cache_param(self):
"""AutoAgents should accept cache param (legacy)."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
assert 'cache' in params
def test_autoagents_accepts_consolidated_output_param(self):
"""AutoAgents should accept consolidated output param."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
# This test will FAIL initially - AutoAgents doesn't have output param yet
assert 'output' in params, "AutoAgents should accept consolidated 'output' param"
def test_autoagents_accepts_consolidated_reflection_param(self):
"""AutoAgents should accept consolidated reflection param."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
# This test will FAIL initially - AutoAgents doesn't have reflection param yet
assert 'reflection' in params, "AutoAgents should accept consolidated 'reflection' param"
def test_autoagents_accepts_consolidated_caching_param(self):
"""AutoAgents should accept consolidated caching param."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
# This test will FAIL initially - AutoAgents doesn't have caching param yet
assert 'caching' in params, "AutoAgents should accept consolidated 'caching' param"
def test_autoagents_accepts_consolidated_knowledge_param(self):
"""AutoAgents should accept consolidated knowledge param."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
sig = inspect.signature(AutoAgents.__init__)
params = list(sig.parameters.keys())
# This test will FAIL initially - AutoAgents doesn't have knowledge param yet
assert 'knowledge' in params, "AutoAgents should accept consolidated 'knowledge' param"
class TestAutoAgentsParamPrecedence:
"""Test that consolidated params take precedence over legacy params."""
def test_consolidated_output_overrides_legacy_verbose(self):
"""Consolidated output param should override legacy verbose."""
# This tests the mapping logic once implemented
pass # Placeholder - will be implemented after AutoAgents is updated
def test_consolidated_reflection_overrides_legacy_self_reflect(self):
"""Consolidated reflection param should override legacy self_reflect."""
pass # Placeholder
def test_consolidated_caching_overrides_legacy_cache(self):
"""Consolidated caching param should override legacy cache."""
pass # Placeholder
class TestAutoAgentsAgentCreation:
"""Test that AutoAgents creates Agent instances with correct params."""
@patch('praisonaiagents.agents.autoagents.AutoAgents._generate_config')
@patch('praisonaiagents.agents.autoagents.AutoAgents._create_agents_and_tasks')
def test_agent_receives_consolidated_params(self, mock_create, mock_config):
"""Agents created by AutoAgents should receive consolidated params."""
# Setup mocks to avoid LLM calls
mock_config.return_value = MagicMock()
mock_create.return_value = ([], [])
# This test verifies the param passing logic
# Will be more detailed once implementation is done
pass
@@ -0,0 +1,124 @@
"""
TDD tests for delegator.py protocol compliance.
Tests that SubagentDelegator creates agents using consolidated params
instead of legacy params like verbose=, max_iter=.
"""
import pytest
from unittest.mock import Mock, patch
class TestDelegatorProtocolCompliance:
"""Test that delegator uses consolidated params when creating agents."""
@pytest.mark.asyncio
async def test_create_agent_uses_output_not_verbose(self):
"""Delegator should use output='silent' instead of verbose=False."""
from praisonaiagents.agents.delegator import SubagentDelegator, DelegationTask
from praisonaiagents.agents.profiles import AgentProfile
# Create a delegator
delegator = SubagentDelegator()
# Create a profile and task
profile = AgentProfile(
name="test_agent",
system_prompt="Test prompt",
max_steps=10,
)
task = DelegationTask(
task_id="test-1",
agent_name="test_agent",
objective="Test task",
max_steps=5,
)
# Mock Agent to capture how it's called (Agent is imported inside _create_agent)
with patch('praisonaiagents.agent.agent.Agent') as MockAgent:
MockAgent.return_value = Mock()
await delegator._create_agent(profile, task)
# Verify Agent was called
MockAgent.assert_called_once()
call_kwargs = MockAgent.call_args[1]
# Should NOT have verbose= param
assert 'verbose' not in call_kwargs, "Should not use legacy verbose= param"
# Should have output= param with 'silent' preset
assert 'output' in call_kwargs, "Should use consolidated output= param"
assert call_kwargs['output'] == 'silent', "Should use output='silent' for quiet mode"
@pytest.mark.asyncio
async def test_create_agent_uses_execution_not_max_iter(self):
"""Delegator should use execution= instead of max_iter=."""
from praisonaiagents.agents.delegator import SubagentDelegator, DelegationTask
from praisonaiagents.agents.profiles import AgentProfile
delegator = SubagentDelegator()
profile = AgentProfile(
name="test_agent",
system_prompt="Test prompt",
max_steps=10,
)
task = DelegationTask(
task_id="test-2",
agent_name="test_agent",
objective="Test task",
max_steps=5,
)
with patch('praisonaiagents.agent.agent.Agent') as MockAgent:
MockAgent.return_value = Mock()
await delegator._create_agent(profile, task)
call_kwargs = MockAgent.call_args[1]
# Should NOT have max_iter= param
assert 'max_iter' not in call_kwargs, "Should not use legacy max_iter= param"
# Should have execution= param with max_iter in config
assert 'execution' in call_kwargs, "Should use consolidated execution= param"
exec_config = call_kwargs['execution']
assert isinstance(exec_config, dict), "execution should be a dict config"
assert 'max_iter' in exec_config, "execution config should contain max_iter"
assert exec_config['max_iter'] == 5, "max_iter should be min(task.max_steps, profile.max_steps)"
class TestDelegatorBackwardCompatibility:
"""Test that delegator still works correctly after modernization."""
@pytest.mark.asyncio
async def test_create_agent_returns_valid_agent(self):
"""Created agent should be usable."""
from praisonaiagents.agents.delegator import SubagentDelegator, DelegationTask
from praisonaiagents.agents.profiles import AgentProfile
delegator = SubagentDelegator()
profile = AgentProfile(
name="test_agent",
system_prompt="You are a helpful assistant.",
max_steps=10,
)
task = DelegationTask(
task_id="test-3",
agent_name="test_agent",
objective="Say hello",
max_steps=5,
)
with patch('praisonaiagents.agent.agent.Agent') as MockAgent:
mock_agent = Mock()
mock_agent.chat = Mock(return_value="Hello!")
MockAgent.return_value = mock_agent
agent = await delegator._create_agent(profile, task)
# Agent should be returned
assert agent is not None
assert agent == mock_agent
@@ -0,0 +1,236 @@
"""
Tests for Gap Closure - LLM Consolidation + Parity
TDD: These tests define the expected behavior for:
1. GAP A: No deprecation warnings for model/llm alias
2. GAP B: YAML parser supports consolidated params
3. GAP C: Wrapper agents use output= (not verbose/markdown)
4. GAP D: AutoAgents uses consolidated llm= (not function_calling_llm)
5. GAP E: model= and llm= are aliases with NO warnings
"""
import pytest
import warnings
class TestNoDeprecationWarnings:
"""GAP A + E: No deprecation warnings for model/llm or legacy params."""
def test_no_warning_for_model_alias(self):
"""model= should work without any deprecation warning."""
from praisonaiagents import Agent
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
agent = Agent(instructions="Test", model="gpt-4o-mini")
# Should NOT emit any deprecation warnings
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(deprecation_warnings) == 0, f"Unexpected warnings: {[str(x.message) for x in deprecation_warnings]}"
def test_no_warning_for_llm_param(self):
"""llm= should work without any deprecation warning."""
from praisonaiagents import Agent
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
agent = Agent(instructions="Test", llm="gpt-4o-mini")
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(deprecation_warnings) == 0, f"Unexpected warnings: {[str(x.message) for x in deprecation_warnings]}"
def test_no_warning_for_llm_config(self):
"""llm_config= should work without deprecation warning (backward compat)."""
from praisonaiagents import Agent
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
agent = Agent(instructions="Test", llm_config={"temperature": 0.7})
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(deprecation_warnings) == 0, f"Unexpected warnings: {[str(x.message) for x in deprecation_warnings]}"
def test_no_warning_for_function_calling_llm(self):
"""function_calling_llm= should work without deprecation warning (backward compat)."""
from praisonaiagents import Agent
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
agent = Agent(instructions="Test", function_calling_llm="gpt-4o-mini")
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(deprecation_warnings) == 0, f"Unexpected warnings: {[str(x.message) for x in deprecation_warnings]}"
def test_model_and_llm_are_equivalent(self):
"""model= and llm= should produce identical behavior."""
from praisonaiagents import Agent
agent1 = Agent(instructions="Test", llm="gpt-4o")
agent2 = Agent(instructions="Test", model="gpt-4o")
assert agent1.llm == agent2.llm == "gpt-4o"
class TestAutoAgentsConsolidation:
"""GAP D: AutoAgents uses consolidated llm= (not function_calling_llm)."""
def test_autoagents_does_not_pass_function_calling_llm_to_agent(self):
"""AutoAgents should not pass function_calling_llm= to Agent()."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
# Get the source code of the _create_agents_and_tasks method
source = inspect.getsource(AutoAgents._create_agents_and_tasks)
# Should NOT contain function_calling_llm= in Agent() call
# This is a static analysis test
assert "function_calling_llm=self.function_calling_llm" not in source, \
"AutoAgents._create_agents_and_tasks still passes function_calling_llm= to Agent()"
def test_autoagents_accepts_llm_param(self):
"""AutoAgents should accept llm= parameter."""
from praisonaiagents.agents.autoagents import AutoAgents
# Should not raise
auto = AutoAgents(instructions="Test task", llm="gpt-4o-mini")
assert auto.llm == "gpt-4o-mini"
def test_autoagents_no_deprecation_warnings(self):
"""AutoAgents should not emit deprecation warnings."""
from praisonaiagents.agents.autoagents import AutoAgents
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
auto = AutoAgents(
instructions="Test task",
llm="gpt-4o-mini",
function_calling_llm="gpt-4o-mini" # Legacy param
)
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(deprecation_warnings) == 0, f"Unexpected warnings: {[str(x.message) for x in deprecation_warnings]}"
class TestYAMLParserConsolidation:
"""GAP B: YAML parser supports consolidated params."""
def test_yaml_parser_maps_legacy_reflection_fields(self):
"""YAML parser should map legacy reflect_llm/min_reflect/max_reflect to reflection config."""
from praisonaiagents.workflows.yaml_parser import YAMLWorkflowParser
yaml_content = """
roles:
researcher:
role: Researcher
goal: Research topics
backstory: Expert researcher
reflect_llm: gpt-4o
min_reflect: 2
max_reflect: 5
tasks:
- description: Research AI
expected_output: Report
"""
import yaml
config = yaml.safe_load(yaml_content)
parser = YAMLWorkflowParser()
# The parser should handle legacy fields without error
# and map them appropriately
agent = parser._create_agent("researcher", config["roles"]["researcher"])
# Legacy fields should be stored for later use
assert hasattr(agent, '_yaml_reflect_llm')
assert hasattr(agent, '_yaml_min_reflect')
assert hasattr(agent, '_yaml_max_reflect')
def test_yaml_parser_supports_new_reflection_structure(self):
"""YAML parser should support new reflection: structure."""
from praisonaiagents.workflows.yaml_parser import YAMLWorkflowParser
yaml_content = """
roles:
researcher:
role: Researcher
goal: Research topics
backstory: Expert researcher
reflection:
enabled: true
max_iterations: 5
min_iterations: 2
tasks:
- description: Research AI
expected_output: Report
"""
import yaml
config = yaml.safe_load(yaml_content)
parser = YAMLWorkflowParser()
agent = parser._create_agent("researcher", config["roles"]["researcher"])
# Should handle new structure
assert agent is not None
class TestWrapperAgentsModernization:
"""GAP C: Wrapper agents use output= (not verbose/markdown)."""
def test_prompt_expander_agent_uses_output_in_agent_call(self):
"""PromptExpanderAgent should use output= when creating internal Agent."""
from praisonaiagents.agent.prompt_expander_agent import PromptExpanderAgent
import inspect
# Get the source of the agent property
source = inspect.getsource(PromptExpanderAgent.agent.fget)
# Should use output= not verbose= or markdown=
assert "output=" in source, "PromptExpanderAgent.agent should use output="
assert "verbose=" not in source or "output=" in source, \
"PromptExpanderAgent.agent should not pass verbose= directly to Agent()"
def test_query_rewriter_agent_uses_output_in_agent_call(self):
"""QueryRewriterAgent should use output= when creating internal Agent."""
from praisonaiagents.agent.query_rewriter_agent import QueryRewriterAgent
import inspect
# Get the source of the agent property
source = inspect.getsource(QueryRewriterAgent.agent.fget)
# Should use output= not verbose= or markdown=
assert "output=" in source, "QueryRewriterAgent.agent should use output="
assert "verbose=" not in source or "output=" in source, \
"QueryRewriterAgent.agent should not pass verbose= directly to Agent()"
def test_prompt_expander_accepts_model_param(self):
"""PromptExpanderAgent should accept model= parameter."""
from praisonaiagents.agent.prompt_expander_agent import PromptExpanderAgent
agent = PromptExpanderAgent(model="gpt-4o-mini")
assert agent.model == "gpt-4o-mini"
def test_query_rewriter_accepts_model_param(self):
"""QueryRewriterAgent should accept model= parameter."""
from praisonaiagents.agent.query_rewriter_agent import QueryRewriterAgent
agent = QueryRewriterAgent(model="gpt-4o-mini")
assert agent.model == "gpt-4o-mini"
class TestAutoAgentsNoLegacyVerboseMarkdown:
"""AutoAgents should not pass verbose/markdown to Agent when output= is set."""
def test_autoagents_uses_output_not_verbose_markdown(self):
"""AutoAgents should use output= and not pass verbose/markdown when output is set."""
from praisonaiagents.agents.autoagents import AutoAgents
import inspect
source = inspect.getsource(AutoAgents._create_agents_and_tasks)
# Should pass output= to Agent
assert "output=self._output" in source or "output=" in source, \
"AutoAgents should pass output= to Agent()"
# Should NOT pass verbose= or markdown= directly
assert "verbose=self.verbose" not in source, \
"AutoAgents should not pass verbose= to Agent()"
@@ -0,0 +1,215 @@
"""
Tests for LLM parameter consolidation.
TDD: These tests define the expected behavior for:
1. Agent accepts `llm=` consolidated usage (new)
2. Agent accepts `model=` alias (new)
3. Agent still accepts `llm_config=` and `function_calling_llm=` but maps them internally (deprecated)
4. base_url/api_key remain separate and unchanged
5. Deprecation warnings emitted only when legacy params are used
"""
import pytest
import warnings
class TestLLMConsolidation:
"""Tests for LLM parameter consolidation in Agent."""
def test_agent_accepts_llm_string(self):
"""Agent accepts llm= as a string model name."""
from praisonaiagents import Agent
agent = Agent(
instructions="Test agent",
llm="gpt-4o-mini"
)
assert agent.llm == "gpt-4o-mini"
def test_agent_accepts_model_alias(self):
"""Agent accepts model= as an alias for llm=."""
from praisonaiagents import Agent
agent = Agent(
instructions="Test agent",
model="gpt-4o-mini"
)
# model= should behave identically to llm=
assert agent.llm == "gpt-4o-mini"
def test_model_and_llm_are_equivalent(self):
"""model= and llm= produce identical agents."""
from praisonaiagents import Agent
agent1 = Agent(instructions="Test", llm="gpt-4o")
agent2 = Agent(instructions="Test", model="gpt-4o")
assert agent1.llm == agent2.llm
def test_llm_takes_precedence_over_model(self):
"""If both llm= and model= are provided, llm= takes precedence."""
from praisonaiagents import Agent
agent = Agent(
instructions="Test",
llm="gpt-4o",
model="gpt-3.5-turbo" # Should be ignored
)
assert agent.llm == "gpt-4o"
def test_llm_config_still_works_without_warning(self):
"""llm_config= still works without deprecation warning (backward compat)."""
from praisonaiagents import Agent
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
agent = Agent(
instructions="Test",
llm_config={"temperature": 0.7}
)
# Should NOT emit deprecation warning (per new policy)
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(deprecation_warnings) == 0, f"Unexpected warnings: {[str(x.message) for x in deprecation_warnings]}"
def test_function_calling_llm_still_works_without_warning(self):
"""function_calling_llm= still works without deprecation warning (backward compat)."""
from praisonaiagents import Agent
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
agent = Agent(
instructions="Test",
function_calling_llm="gpt-4o-mini"
)
# Should NOT emit deprecation warning (per new policy)
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(deprecation_warnings) == 0, f"Unexpected warnings: {[str(x.message) for x in deprecation_warnings]}"
def test_base_url_remains_separate(self):
"""base_url= remains a separate parameter, not consolidated."""
from praisonaiagents import Agent
agent = Agent(
instructions="Test",
llm="gpt-4o",
base_url="https://custom.api.com/v1"
)
assert agent.base_url == "https://custom.api.com/v1"
def test_api_key_remains_separate(self):
"""api_key= remains a separate parameter, not consolidated."""
from praisonaiagents import Agent
agent = Agent(
instructions="Test",
llm="gpt-4o",
api_key="sk-test-key"
)
assert agent.api_key == "sk-test-key"
def test_no_deprecation_warning_on_normal_path(self):
"""No deprecation warnings when using only new consolidated params."""
from praisonaiagents import Agent
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
agent = Agent(
instructions="Test",
llm="gpt-4o",
base_url="https://api.openai.com/v1",
api_key="sk-test"
)
# Should NOT emit deprecation warnings for llm/base_url/api_key
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
llm_related = [x for x in deprecation_warnings
if "llm_config" in str(x.message).lower()
or "function_calling_llm" in str(x.message).lower()]
assert len(llm_related) == 0
class TestWebFetchGating:
"""Tests for web_fetch gating (Claude-only)."""
def test_non_claude_model_with_web_fetch_raises_error(self):
"""Non-Claude model + web fetch requested → error."""
from praisonaiagents import Agent
from praisonaiagents.config.feature_configs import WebConfig
# This should raise an error or warning when fetch is explicitly requested
with pytest.raises((ValueError, RuntimeError)) as exc_info:
agent = Agent(
instructions="Test",
llm="gpt-4o", # OpenAI model - does NOT support fetch
web=WebConfig(fetch=True) # Explicitly request fetch
)
# Force initialization to trigger the check
_ = agent.web
assert "fetch" in str(exc_info.value).lower() or "claude" in str(exc_info.value).lower()
def test_claude_model_with_web_fetch_allowed(self):
"""Claude model + web fetch requested → allowed."""
from praisonaiagents import Agent
from praisonaiagents.config.feature_configs import WebConfig
# This should work without error
agent = Agent(
instructions="Test",
llm="anthropic/claude-sonnet-4", # Claude model - supports fetch
web=WebConfig(fetch=True)
)
# Should not raise
assert agent is not None
def test_web_enabled_without_fetch_works_on_any_model(self):
"""web enabled with search only (no fetch) works on any model."""
from praisonaiagents import Agent
from praisonaiagents.config.feature_configs import WebConfig
# Web search (not fetch) should work on any model that supports it
agent = Agent(
instructions="Test",
llm="gpt-4o-search-preview", # OpenAI search model
web=WebConfig(search=True, fetch=False) # Explicitly disable fetch
)
# Should not raise - web search is allowed
assert agent is not None
def test_web_fetch_false_on_non_claude_no_error(self):
"""web=True with fetch=False on non-Claude should not error."""
from praisonaiagents import Agent
from praisonaiagents.config.feature_configs import WebConfig
agent = Agent(
instructions="Test",
llm="gpt-4o",
web=WebConfig(search=True, fetch=False) # Explicitly disable fetch
)
assert agent is not None
class TestWrapperAgentConsolidation:
"""Tests for wrapper agents using consolidated params."""
def test_prompt_expander_accepts_model_param(self):
"""PromptExpanderAgent accepts model= parameter."""
from praisonaiagents.agent.prompt_expander_agent import PromptExpanderAgent
agent = PromptExpanderAgent(model="gpt-4o-mini")
# PromptExpanderAgent stores model in self.model attribute
assert agent.model == "gpt-4o-mini"
def test_prompt_expander_model_is_used(self):
"""PromptExpanderAgent uses the model parameter correctly."""
from praisonaiagents.agent.prompt_expander_agent import PromptExpanderAgent
# Should accept model= parameter without error
agent = PromptExpanderAgent(model="gpt-4o-mini")
assert agent is not None
assert agent.model == "gpt-4o-mini"
def test_query_rewriter_accepts_model_param(self):
"""QueryRewriterAgent accepts model= parameter."""
from praisonaiagents.agent.query_rewriter_agent import QueryRewriterAgent
agent = QueryRewriterAgent(model="gpt-4o-mini")
assert agent.model == "gpt-4o-mini"
@@ -0,0 +1,169 @@
"""
Tests for the 'stream' output preset and streaming behavior.
Verifies:
1. output="stream" is a valid preset string
2. Streaming path yields chunks without buffering
3. LLM call count == 1 for simple streaming case
4. Precedence: start(stream=...) overrides OutputConfig.stream
"""
import pytest
import warnings
class TestStreamPresetValidation:
"""Test that 'stream' is a valid output preset."""
def test_stream_preset_exists_in_output_presets(self):
"""'stream' should be a valid key in OUTPUT_PRESETS."""
from praisonaiagents.config.presets import OUTPUT_PRESETS
assert "stream" in OUTPUT_PRESETS, "Missing 'stream' preset in OUTPUT_PRESETS"
assert OUTPUT_PRESETS["stream"]["stream"] is True, "'stream' preset should have stream=True"
def test_stream_preset_resolves_correctly(self):
"""output='stream' should resolve to OutputConfig with stream=True."""
from praisonaiagents.config.param_resolver import resolve
from praisonaiagents.config.presets import OUTPUT_PRESETS
from praisonaiagents.config.feature_configs import OutputConfig
result = resolve(
value="stream",
param_name="output",
config_class=OutputConfig,
presets=OUTPUT_PRESETS,
)
assert isinstance(result, OutputConfig)
assert result.stream is True
assert result.verbose is True # stream preset enables verbose
def test_all_output_presets_are_valid(self):
"""All documented presets should be valid."""
from praisonaiagents.config.presets import OUTPUT_PRESETS
expected_presets = {"minimal", "normal", "verbose", "debug", "silent", "stream"}
actual_presets = set(OUTPUT_PRESETS.keys())
assert expected_presets.issubset(actual_presets), \
f"Missing presets: {expected_presets - actual_presets}"
def test_invalid_preset_raises_helpful_error(self):
"""Invalid preset should raise error listing valid options including 'stream'."""
from praisonaiagents.config.param_resolver import resolve
from praisonaiagents.config.presets import OUTPUT_PRESETS
from praisonaiagents.config.feature_configs import OutputConfig
with pytest.raises(ValueError) as exc_info:
resolve(
value="invalid_preset",
param_name="output",
config_class=OutputConfig,
presets=OUTPUT_PRESETS,
)
error_msg = str(exc_info.value).lower()
assert "invalid_preset" in error_msg
assert "stream" in error_msg, "Error should list 'stream' as valid preset"
class TestStreamPresetWithOverrides:
"""Test array form with stream preset and overrides."""
def test_stream_preset_with_override_to_disable(self):
"""['stream', {'stream': False}] should override stream to False."""
from praisonaiagents.config.param_resolver import resolve, ArrayMode
from praisonaiagents.config.presets import OUTPUT_PRESETS
from praisonaiagents.config.feature_configs import OutputConfig
result = resolve(
value=["stream", {"stream": False}],
param_name="output",
config_class=OutputConfig,
presets=OUTPUT_PRESETS,
array_mode=ArrayMode.PRESET_OVERRIDE,
)
assert isinstance(result, OutputConfig)
assert result.stream is False # Override takes effect
assert result.verbose is True # Base preset value preserved
def test_verbose_preset_with_stream_override(self):
"""['verbose', {'stream': True}] should enable streaming."""
from praisonaiagents.config.param_resolver import resolve, ArrayMode
from praisonaiagents.config.presets import OUTPUT_PRESETS
from praisonaiagents.config.feature_configs import OutputConfig
result = resolve(
value=["verbose", {"stream": True}],
param_name="output",
config_class=OutputConfig,
presets=OUTPUT_PRESETS,
array_mode=ArrayMode.PRESET_OVERRIDE,
)
assert isinstance(result, OutputConfig)
assert result.stream is True
assert result.metrics is True # verbose preset has metrics=True
class TestStreamPrecedence:
"""Test precedence: start(stream=...) should override OutputConfig.stream."""
def test_start_stream_kwarg_overrides_output_config(self):
"""start(stream=True) should enable streaming even if output='silent'."""
# This is a behavioral test - we verify the logic in start() method
# The actual streaming behavior is tested in integration tests
# Verify the start method checks kwargs['stream'] first
import inspect
from praisonaiagents.agent.agent import Agent
source = inspect.getsource(Agent.start)
# start() should check kwargs.get('stream', ...) OR getattr(self, 'stream', ...)
assert "kwargs.get('stream'" in source or "stream" in source, \
"start() should check stream kwarg"
class TestNoDeprecationWarnings:
"""Ensure no deprecation warnings for stream preset."""
def test_stream_preset_no_warnings(self):
"""Using output='stream' should not emit any warnings."""
from praisonaiagents.config.param_resolver import resolve
from praisonaiagents.config.presets import OUTPUT_PRESETS
from praisonaiagents.config.feature_configs import OutputConfig
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
resolve(
value="stream",
param_name="output",
config_class=OutputConfig,
presets=OUTPUT_PRESETS,
)
deprecation_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
assert len(deprecation_warnings) == 0, \
f"Unexpected warnings: {[str(x.message) for x in deprecation_warnings]}"
class TestOutputConfigStreamField:
"""Test OutputConfig has stream field."""
def test_output_config_has_stream_field(self):
"""OutputConfig should have a stream field."""
from praisonaiagents.config.feature_configs import OutputConfig
config = OutputConfig()
assert hasattr(config, 'stream'), "OutputConfig missing 'stream' field"
def test_output_config_stream_default_is_false(self):
"""OutputConfig.stream should default to False."""
from praisonaiagents.config.feature_configs import OutputConfig
config = OutputConfig()
assert config.stream is False, "OutputConfig.stream should default to False"
+1 -1
View File
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.10.10"
version = "0.11.1"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.6.2.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.6.2.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.7.1.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.7.1.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+17
View File
@@ -0,0 +1,17 @@
"""
Development utilities for PraisonAI.
This module contains development-only tools that are NOT imported during normal runtime.
"""
__all__ = ["generate_api_md", "ApiMdGenerator"]
def __getattr__(name: str):
"""Lazy load development utilities."""
if name in ("generate_api_md", "ApiMdGenerator"):
from .api_md import generate_api_md, ApiMdGenerator
if name == "generate_api_md":
return generate_api_md
return ApiMdGenerator
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+981
View File
@@ -0,0 +1,981 @@
"""
API Markdown Generator for PraisonAI.
Generates a comprehensive api.md file documenting all public API surfaces:
- Python Core SDK (praisonaiagents)
- Python Wrapper/Integrations (praisonai)
- CLI commands
- TypeScript package (praisonai-ts)
Uses AST-based static analysis to avoid importing heavy dependencies.
Example output format (OpenAI SDK style):
# Agents
Types:
```python
from praisonaiagents import Agent, AgentConfig
from praisonaiagents.types import RunResult
```
Methods:
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent.py">start</a>(prompt: str, **kwargs) -> RunResult</code>
Usage:
python -m praisonai._dev.api_md --write
python -m praisonai._dev.api_md --check
python -m praisonai._dev.api_md --stdout
"""
import ast
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple
@dataclass
class SymbolInfo:
"""Information about a discovered symbol."""
name: str
kind: str # 'class', 'function', 'type', 'constant'
module_path: str # e.g., 'praisonaiagents.agent.agent'
file_path: str # relative to repo root
line_number: int
signature: Optional[str] = None
return_type: Optional[str] = None
methods: List["MethodInfo"] = field(default_factory=list)
docstring: Optional[str] = None
@dataclass
class MethodInfo:
"""Information about a class method."""
name: str
signature: str
return_type: Optional[str]
line_number: int
@dataclass
class CLICommand:
"""Information about a CLI command."""
command_path: str # e.g., 'praisonai docs api-md'
help_text: Optional[str]
file_path: str
line_number: int
@dataclass
class TSExport:
"""Information about a TypeScript export."""
name: str
kind: str # 'class', 'function', 'type', 'const'
source_file: str
is_type: bool = False
class ApiMdGenerator:
"""
Generates api.md from source code using AST analysis.
Design principles:
- AST-first: Never import modules that may have heavy dependencies
- Deterministic: Stable ordering for reproducible output
- Comprehensive: Cover all public exports
"""
# Section groupings for organizing exports
SECTION_GROUPS = {
'Shared Types': ['TaskOutput', 'ReflectionOutput', 'ToolResult', 'ToolValidationError',
'GuardrailResult', 'StepResult', 'WorkflowContext', 'HandoffResult',
'HandoffInputData', 'HandoffConfig', 'ContextPolicy', 'HandoffError',
'HandoffCycleError', 'HandoffDepthError', 'HandoffTimeoutError'],
'Agents': ['Agent', 'Agents', 'PraisonAIAgents', 'ImageAgent', 'ContextAgent',
'DeepResearchAgent', 'QueryRewriterAgent', 'PromptExpanderAgent',
'AutoAgents', 'AutoRagAgent', 'PlanningAgent', 'create_context_agent'],
'Tools': ['Tools', 'BaseTool', 'tool', 'FunctionTool', 'ToolRegistry',
'get_registry', 'register_tool', 'get_tool', 'validate_tool'],
'Workflows': ['Workflow', 'WorkflowStep', 'Pipeline', 'Route', 'Parallel',
'Loop', 'Repeat', 'route', 'parallel', 'loop', 'repeat'],
'DB': ['db', 'DbAdapter', 'AsyncDbAdapter'],
'Memory': ['Memory', 'MemoryConfig', 'MemoryBackend'],
'Knowledge': ['Knowledge', 'Chunking', 'ChunkingStrategy', 'KnowledgeConfig'],
'RAG': ['RAG', 'RAGConfig', 'RAGResult', 'RAGCitation', 'RetrievalConfig',
'RetrievalPolicy', 'CitationsMode', 'ContextPack'],
'Handoff': ['Handoff', 'handoff', 'handoff_filters', 'RECOMMENDED_PROMPT_PREFIX',
'prompt_with_handoff_instructions'],
'Guardrails': ['LLMGuardrail', 'GuardrailConfig', 'GuardrailAction', 'GUARDRAIL_PRESETS'],
'Planning': ['Plan', 'PlanStep', 'TodoList', 'TodoItem', 'PlanStorage',
'ApprovalCallback', 'READ_ONLY_TOOLS', 'RESTRICTED_TOOLS', 'PlanningConfig'],
'Skills': ['SkillManager', 'SkillProperties', 'SkillMetadata', 'SkillLoader', 'SkillsConfig'],
'Session': ['Session'],
'MCP': ['MCP'],
'Telemetry': ['get_telemetry', 'enable_telemetry', 'disable_telemetry',
'enable_performance_mode', 'disable_performance_mode',
'cleanup_telemetry_resources', 'MinimalTelemetry', 'TelemetryCollector'],
'Observability': ['obs', 'FlowDisplay', 'track_workflow'],
'Context': ['FastContext', 'FastContextResult', 'FileMatch', 'LineRange',
'ContextManager', 'ManagerConfig'],
'UI': ['AGUI', 'A2A'],
'Config': ['MemoryConfig', 'KnowledgeConfig', 'PlanningConfig', 'ReflectionConfig',
'GuardrailConfig', 'WebConfig', 'OutputConfig', 'ExecutionConfig',
'TemplateConfig', 'CachingConfig', 'HooksConfig', 'SkillsConfig',
'AutonomyConfig', 'OutputPreset', 'ExecutionPreset', 'AutonomyLevel',
'WebSearchProvider', 'MultiAgentHooksConfig', 'MultiAgentOutputConfig',
'MultiAgentExecutionConfig', 'MultiAgentPlanningConfig', 'MultiAgentMemoryConfig'],
'Display': ['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'],
'Utilities': ['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', 'is_policy_string',
'parse_policy_string'],
}
def __init__(self, repo_root: Optional[Path] = None):
"""Initialize the generator with repository root path."""
if repo_root is None:
# Auto-detect repo root
repo_root = self._find_repo_root()
self.repo_root = Path(repo_root).resolve()
# Package paths
self.agents_pkg = self.repo_root / "src" / "praisonai-agents" / "praisonaiagents"
self.wrapper_pkg = self.repo_root / "src" / "praisonai" / "praisonai"
self.ts_pkg = self.repo_root / "src" / "praisonai-ts"
# Discovered symbols
self.agents_symbols: Dict[str, SymbolInfo] = {}
self.wrapper_symbols: Dict[str, SymbolInfo] = {}
self.cli_commands: List[CLICommand] = []
self.ts_exports: List[TSExport] = []
def _find_repo_root(self) -> Path:
"""Find repository root by looking for .git directory."""
current = Path.cwd()
while current != current.parent:
if (current / ".git").exists():
return current
current = current.parent
# Fallback to expected location
return Path("/Users/praison/praisonai-package")
def discover_all(self) -> None:
"""Discover all public API symbols."""
self._discover_python_exports(self.agents_pkg, "praisonaiagents", self.agents_symbols)
self._discover_python_exports(self.wrapper_pkg, "praisonai", self.wrapper_symbols)
self._discover_cli_commands()
self._discover_ts_exports()
def _discover_python_exports(
self,
pkg_path: Path,
pkg_name: str,
symbols: Dict[str, SymbolInfo]
) -> None:
"""Discover Python exports from __init__.py using AST."""
init_file = pkg_path / "__init__.py"
if not init_file.exists():
return
try:
with open(init_file, 'r', encoding='utf-8') as f:
source = f.read()
tree = ast.parse(source)
except (SyntaxError, UnicodeDecodeError):
return
# Extract __all__ if present
all_exports: Set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name) and target.id == '__all__':
if isinstance(node.value, ast.List):
for elt in node.value.elts:
if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
all_exports.add(elt.value)
# Track imports to find source files
import_sources: Dict[str, Tuple[str, str]] = {} # name -> (module, original_name)
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module:
for alias in node.names:
name = alias.asname if alias.asname else alias.name
import_sources[name] = (node.module, alias.name)
# Parse __getattr__ for lazy-loaded symbols
lazy_sources = self._parse_getattr_lazy_imports(tree)
import_sources.update(lazy_sources)
# For each export, find its definition
for export_name in sorted(all_exports):
if export_name in import_sources:
module_path, original_name = import_sources[export_name]
# Resolve to file
symbol_info = self._resolve_symbol(
pkg_path, pkg_name, module_path, original_name, export_name
)
if symbol_info:
symbols[export_name] = symbol_info
else:
# Defined in __init__.py itself
symbol_info = self._find_symbol_in_file(
init_file, export_name, pkg_name
)
if symbol_info:
symbols[export_name] = symbol_info
def _parse_getattr_lazy_imports(self, tree: ast.Module) -> Dict[str, Tuple[str, str]]:
"""
Parse __getattr__ function to extract lazy-loaded imports.
Looks for patterns like:
if name == "Agent":
from .agent.agent import Agent
return Agent
"""
lazy_sources: Dict[str, Tuple[str, str]] = {}
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef) and node.name == '__getattr__':
# Walk through ALL nodes in the function to find if statements
for stmt in ast.walk(node):
if isinstance(stmt, ast.If):
# Check if this is `if name == "SomeName":`
symbol_name = self._extract_name_comparison(stmt.test)
if symbol_name:
# Look for import statement in the body
for inner_stmt in stmt.body:
if isinstance(inner_stmt, ast.ImportFrom) and inner_stmt.module:
for alias in inner_stmt.names:
imported_name = alias.name
lazy_sources[symbol_name] = (inner_stmt.module, imported_name)
break
break
return lazy_sources
def _extract_name_comparison(self, test: ast.expr) -> Optional[str]:
"""
Extract the string value from comparisons like `name == "Agent"`.
Also handles `name in ("A", "B", "C")` patterns.
"""
if isinstance(test, ast.Compare):
# Handle: name == "Agent"
if (len(test.ops) == 1 and
isinstance(test.ops[0], ast.Eq) and
isinstance(test.left, ast.Name) and test.left.id == 'name'):
if len(test.comparators) == 1:
comp = test.comparators[0]
if isinstance(comp, ast.Constant) and isinstance(comp.value, str):
return comp.value
# Handle: name in ("A", "B", "C")
if (len(test.ops) == 1 and
isinstance(test.ops[0], ast.In) and
isinstance(test.left, ast.Name) and test.left.id == 'name'):
# Return first item for now (we'll get others via elif)
if len(test.comparators) == 1:
comp = test.comparators[0]
if isinstance(comp, ast.Tuple):
for elt in comp.elts:
if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
return elt.value
return None
def _resolve_symbol(
self,
pkg_path: Path,
pkg_name: str,
module_path: str,
original_name: str,
export_name: str
) -> Optional[SymbolInfo]:
"""Resolve a symbol to its definition file."""
file_path = None
# Handle relative imports (starting with .)
if module_path.startswith('.'):
# Convert relative to absolute
parts = module_path.lstrip('.').split('.')
rel_depth = len(module_path) - len(module_path.lstrip('.'))
if rel_depth == 1:
# Same package
file_path = pkg_path
else:
file_path = pkg_path.parent
for _ in range(rel_depth - 1):
file_path = file_path.parent
for part in parts:
if part:
file_path = file_path / part
# Handle absolute imports starting with package name
elif module_path.startswith(pkg_name + '.') or module_path == pkg_name:
rel_path = module_path[len(pkg_name):].lstrip('.')
parts = rel_path.split('.') if rel_path else []
file_path = pkg_path
for part in parts:
file_path = file_path / part
else:
# Treat as relative import within package (e.g., 'agent.agent' -> pkg_path/agent/agent.py)
# This handles lazy imports in __getattr__ that use implicit relative paths
parts = module_path.split('.')
file_path = pkg_path
for part in parts:
file_path = file_path / part
if file_path is None:
return None
# Try as directory with __init__.py or as .py file
if file_path.is_dir():
init_file = file_path / "__init__.py"
if init_file.exists():
return self._find_symbol_in_file(init_file, original_name, pkg_name, export_name)
py_file = file_path.with_suffix('.py')
if py_file.exists():
return self._find_symbol_in_file(py_file, original_name, pkg_name, export_name)
return None
def _find_symbol_in_file(
self,
file_path: Path,
symbol_name: str,
pkg_name: str,
export_name: Optional[str] = None
) -> Optional[SymbolInfo]:
"""Find a symbol definition in a Python file."""
if not file_path.exists():
return None
try:
with open(file_path, 'r', encoding='utf-8') as f:
source = f.read()
tree = ast.parse(source)
except (SyntaxError, UnicodeDecodeError):
return None
rel_path = self._get_relative_path(file_path)
module_path = self._file_to_module(file_path, pkg_name)
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef) and node.name == symbol_name:
methods = self._extract_methods(node)
return SymbolInfo(
name=export_name or symbol_name,
kind='class',
module_path=module_path,
file_path=rel_path,
line_number=node.lineno,
signature=self._extract_class_signature(node),
methods=methods,
docstring=ast.get_docstring(node)
)
elif isinstance(node, ast.FunctionDef) and node.name == symbol_name:
return SymbolInfo(
name=export_name or symbol_name,
kind='function',
module_path=module_path,
file_path=rel_path,
line_number=node.lineno,
signature=self._extract_function_signature(node),
return_type=self._extract_return_type(node),
docstring=ast.get_docstring(node)
)
elif isinstance(node, ast.AsyncFunctionDef) and node.name == symbol_name:
return SymbolInfo(
name=export_name or symbol_name,
kind='function',
module_path=module_path,
file_path=rel_path,
line_number=node.lineno,
signature=self._extract_function_signature(node),
return_type=self._extract_return_type(node),
docstring=ast.get_docstring(node)
)
elif isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name) and target.id == symbol_name:
return SymbolInfo(
name=export_name or symbol_name,
kind='constant',
module_path=module_path,
file_path=rel_path,
line_number=node.lineno
)
# Check for re-exports in __init__.py
if file_path.name == "__init__.py":
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module:
for alias in node.names:
name = alias.asname if alias.asname else alias.name
if name == symbol_name:
# Recursively resolve
return self._resolve_symbol(
file_path.parent, pkg_name,
'.' + node.module if not node.module.startswith('.') else node.module,
alias.name, export_name or symbol_name
)
return None
def _extract_methods(self, class_node: ast.ClassDef) -> List[MethodInfo]:
"""Extract public methods from a class."""
methods = []
for node in class_node.body:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
if not node.name.startswith('_') or node.name in ('__init__', '__call__'):
methods.append(MethodInfo(
name=node.name,
signature=self._extract_function_signature(node),
return_type=self._extract_return_type(node),
line_number=node.lineno
))
return sorted(methods, key=lambda m: m.name)
def _extract_function_signature(self, node: ast.FunctionDef) -> str:
"""Extract function signature as string."""
args = []
# Regular args
defaults_offset = len(node.args.args) - len(node.args.defaults)
for i, arg in enumerate(node.args.args):
if arg.arg == 'self' or arg.arg == 'cls':
continue
arg_str = arg.arg
if arg.annotation:
arg_str += f": {self._annotation_to_str(arg.annotation)}"
if i >= defaults_offset:
default = node.args.defaults[i - defaults_offset]
arg_str += f" = {self._default_to_str(default)}"
args.append(arg_str)
# *args
if node.args.vararg:
arg_str = f"*{node.args.vararg.arg}"
if node.args.vararg.annotation:
arg_str += f": {self._annotation_to_str(node.args.vararg.annotation)}"
args.append(arg_str)
# **kwargs
if node.args.kwarg:
arg_str = f"**{node.args.kwarg.arg}"
if node.args.kwarg.annotation:
arg_str += f": {self._annotation_to_str(node.args.kwarg.annotation)}"
args.append(arg_str)
return f"({', '.join(args)})"
def _extract_class_signature(self, node: ast.ClassDef) -> str:
"""Extract class __init__ signature."""
for item in node.body:
if isinstance(item, ast.FunctionDef) and item.name == '__init__':
return self._extract_function_signature(item)
return "()"
def _extract_return_type(self, node: ast.FunctionDef) -> Optional[str]:
"""Extract return type annotation."""
if node.returns:
return self._annotation_to_str(node.returns)
return None
def _annotation_to_str(self, node: ast.expr) -> str:
"""Convert AST annotation to string."""
if isinstance(node, ast.Name):
return node.id
elif isinstance(node, ast.Constant):
return repr(node.value)
elif isinstance(node, ast.Subscript):
value = self._annotation_to_str(node.value)
slice_val = self._annotation_to_str(node.slice)
return f"{value}[{slice_val}]"
elif isinstance(node, ast.Attribute):
return f"{self._annotation_to_str(node.value)}.{node.attr}"
elif isinstance(node, ast.Tuple):
elts = ', '.join(self._annotation_to_str(e) for e in node.elts)
return elts
elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
left = self._annotation_to_str(node.left)
right = self._annotation_to_str(node.right)
return f"{left} | {right}"
elif isinstance(node, ast.List):
elts = ', '.join(self._annotation_to_str(e) for e in node.elts)
return f"[{elts}]"
return "Any"
def _default_to_str(self, node: ast.expr) -> str:
"""Convert default value to string."""
if isinstance(node, ast.Constant):
return repr(node.value)
elif isinstance(node, ast.Name):
return node.id
elif isinstance(node, ast.List):
return "[]"
elif isinstance(node, ast.Dict):
return "{}"
elif isinstance(node, ast.Call):
return "..."
return "..."
def _get_relative_path(self, file_path: Path) -> str:
"""Get path relative to repo root."""
try:
return "./" + str(file_path.relative_to(self.repo_root))
except ValueError:
return str(file_path)
def _file_to_module(self, file_path: Path, pkg_name: str) -> str:
"""Convert file path to module path."""
try:
if pkg_name == "praisonaiagents":
rel = file_path.relative_to(self.agents_pkg.parent)
else:
rel = file_path.relative_to(self.wrapper_pkg.parent)
parts = list(rel.parts)
if parts[-1] == "__init__.py":
parts = parts[:-1]
elif parts[-1].endswith('.py'):
parts[-1] = parts[-1][:-3]
return '.'.join(parts)
except ValueError:
return pkg_name
def _discover_cli_commands(self) -> None:
"""Discover CLI commands from Typer apps."""
commands_dir = self.wrapper_pkg / "cli" / "commands"
if not commands_dir.exists():
return
for py_file in sorted(commands_dir.glob("*.py")):
if py_file.name.startswith('_'):
continue
self._parse_typer_commands(py_file)
# Also check main.py for argparse commands
main_py = self.wrapper_pkg / "cli" / "main.py"
if main_py.exists():
self._parse_main_commands(main_py)
def _parse_typer_commands(self, file_path: Path) -> None:
"""Parse Typer command definitions from a file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
source = f.read()
tree = ast.parse(source)
except (SyntaxError, UnicodeDecodeError):
return
rel_path = self._get_relative_path(file_path)
command_name = file_path.stem
# Find @app.command() decorators
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
for decorator in node.decorator_list:
if self._is_command_decorator(decorator):
cmd_name = self._extract_command_name(decorator, node.name)
help_text = ast.get_docstring(node)
self.cli_commands.append(CLICommand(
command_path=f"praisonai {command_name} {cmd_name}".strip(),
help_text=help_text[:100] if help_text else None,
file_path=rel_path,
line_number=node.lineno
))
def _is_command_decorator(self, decorator: ast.expr) -> bool:
"""Check if decorator is @app.command() or similar."""
if isinstance(decorator, ast.Call):
if isinstance(decorator.func, ast.Attribute):
return decorator.func.attr in ('command', 'callback')
elif isinstance(decorator, ast.Attribute):
return decorator.attr in ('command', 'callback')
return False
def _extract_command_name(self, decorator: ast.expr, func_name: str) -> str:
"""Extract command name from decorator or function name."""
if isinstance(decorator, ast.Call):
for arg in decorator.args:
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
return arg.value
for kw in decorator.keywords:
if kw.arg == 'name' and isinstance(kw.value, ast.Constant):
return kw.value.value
# Convert function name: some_command -> some-command
return func_name.replace('_', '-')
def _parse_main_commands(self, file_path: Path) -> None:
"""Parse main CLI commands from argparse in main.py."""
# Add base commands that are handled in main.py
rel_path = self._get_relative_path(file_path)
base_commands = [
("praisonai", "Main CLI entry point"),
("praisonai run", "Run agents from YAML file"),
("praisonai chat", "Terminal chat interface"),
("praisonai code", "Terminal code assistant"),
]
for cmd, help_text in base_commands:
self.cli_commands.append(CLICommand(
command_path=cmd,
help_text=help_text,
file_path=rel_path,
line_number=1
))
def _discover_ts_exports(self) -> None:
"""Discover TypeScript exports from index.ts."""
index_file = self.ts_pkg / "src" / "index.ts"
if not index_file.exists():
return
try:
with open(index_file, 'r', encoding='utf-8') as f:
content = f.read()
except (IOError, UnicodeDecodeError):
return
# Parse export statements using regex
# export { Name1, Name2 } from './path'
export_pattern = r"export\s*\{([^}]+)\}\s*from\s*['\"]([^'\"]+)['\"]"
type_export_pattern = r"export\s+type\s*\{([^}]+)\}\s*from\s*['\"]([^'\"]+)['\"]"
for match in re.finditer(export_pattern, content):
names = match.group(1)
source = match.group(2)
for name in names.split(','):
name = name.strip()
if ' as ' in name:
name = name.split(' as ')[1].strip()
if name and not name.startswith('type '):
self.ts_exports.append(TSExport(
name=name,
kind='export',
source_file=source,
is_type=False
))
for match in re.finditer(type_export_pattern, content):
names = match.group(1)
source = match.group(2)
for name in names.split(','):
name = name.strip()
if ' as ' in name:
name = name.split(' as ')[1].strip()
if name:
self.ts_exports.append(TSExport(
name=name,
kind='type',
source_file=source,
is_type=True
))
def generate(self) -> str:
"""Generate the api.md content."""
self.discover_all()
lines = [
"# PraisonAI API Reference",
"",
"This file is auto-generated. Do not edit manually.",
"Regenerate with: `praisonai docs api-md --write`",
"",
]
# Group symbols by section
sections = self._group_symbols_by_section()
# Generate each section
section_order = [
'Shared Types', 'Agents', 'Tools', 'Workflows', 'DB', 'Memory',
'Knowledge', 'RAG', 'Handoff', 'Guardrails', 'Planning', 'Skills',
'Session', 'MCP', 'Telemetry', 'Observability', 'Context', 'UI',
'Config', 'Display', 'Utilities'
]
for section_name in section_order:
if section_name in sections and sections[section_name]:
lines.extend(self._generate_section(section_name, sections[section_name]))
# Handle remaining symbols not in predefined sections
remaining = self._get_remaining_symbols(sections)
if remaining:
lines.extend(self._generate_section('Other', remaining))
# Wrapper package exports
if self.wrapper_symbols:
lines.extend(self._generate_wrapper_section())
# CLI section
if self.cli_commands:
lines.extend(self._generate_cli_section())
# TypeScript section
if self.ts_exports:
lines.extend(self._generate_ts_section())
# Optional Plugins section
lines.extend(self._generate_plugins_section())
return '\n'.join(lines)
def _group_symbols_by_section(self) -> Dict[str, List[SymbolInfo]]:
"""Group symbols by their section."""
sections: Dict[str, List[SymbolInfo]] = {}
for section_name, symbol_names in self.SECTION_GROUPS.items():
section_symbols = []
for name in symbol_names:
if name in self.agents_symbols:
section_symbols.append(self.agents_symbols[name])
if section_symbols:
sections[section_name] = sorted(section_symbols, key=lambda s: s.name)
return sections
def _get_remaining_symbols(self, sections: Dict[str, List[SymbolInfo]]) -> List[SymbolInfo]:
"""Get symbols not in any predefined section."""
used_names = set()
for symbol_list in sections.values():
for symbol in symbol_list:
used_names.add(symbol.name)
remaining = []
for name, symbol in self.agents_symbols.items():
if name not in used_names:
remaining.append(symbol)
return sorted(remaining, key=lambda s: s.name)
def _generate_section(self, section_name: str, symbols: List[SymbolInfo]) -> List[str]:
"""Generate a section of the API documentation."""
lines = [
f"# {section_name}",
"",
]
# Types block
imports = []
for symbol in symbols:
if symbol.module_path.startswith('praisonaiagents'):
imports.append(symbol.name)
if imports:
lines.append("Types:")
lines.append("```python")
# Group imports by module
lines.append(f"from praisonaiagents import {', '.join(sorted(imports))}")
lines.append("```")
lines.append("")
# Methods block
methods_lines = []
for symbol in symbols:
if symbol.kind == 'class' and symbol.methods:
for method in symbol.methods:
if method.name == '__init__':
continue
ret_type = f" -> {method.return_type}" if method.return_type else ""
methods_lines.append(
f'* <code title="class {symbol.name}">{symbol.name}.'
f'<a href="{symbol.file_path}">{method.name}</a>'
f'{method.signature}{ret_type}</code>'
)
elif symbol.kind == 'function':
ret_type = f" -> {symbol.return_type}" if symbol.return_type else ""
module_prefix = symbol.module_path.split('.')[0]
methods_lines.append(
f'* <code title="function">{module_prefix}.'
f'<a href="{symbol.file_path}">{symbol.name}</a>'
f'{symbol.signature or "()"}{ret_type}</code>'
)
if methods_lines:
lines.append("Methods:")
lines.append("")
lines.extend(methods_lines)
lines.append("")
return lines
def _generate_wrapper_section(self) -> List[str]:
"""Generate section for praisonai wrapper package."""
lines = [
"# Wrapper (praisonai)",
"",
"Types:",
"```python",
]
imports = sorted(self.wrapper_symbols.keys())
lines.append(f"from praisonai import {', '.join(imports)}")
lines.append("```")
lines.append("")
return lines
def _generate_cli_section(self) -> List[str]:
"""Generate CLI commands section."""
lines = [
"# CLI",
"",
"Methods:",
"",
]
# Sort and deduplicate commands
seen = set()
for cmd in sorted(self.cli_commands, key=lambda c: c.command_path):
if cmd.command_path in seen:
continue
seen.add(cmd.command_path)
lines.append(
f'* <code title="cli">{cmd.command_path} '
f'<a href="{cmd.file_path}">--help</a></code>'
)
lines.append("")
return lines
def _generate_ts_section(self) -> List[str]:
"""Generate TypeScript exports section."""
lines = [
"# TypeScript",
"",
"Types/Exports:",
"```ts",
]
# Group by source file
by_source: Dict[str, List[TSExport]] = {}
for export in self.ts_exports:
if export.source_file not in by_source:
by_source[export.source_file] = []
by_source[export.source_file].append(export)
for source in sorted(by_source.keys()):
exports = by_source[source]
regular = [e.name for e in exports if not e.is_type]
types = [e.name for e in exports if e.is_type]
if regular:
lines.append(f'export {{ {", ".join(sorted(regular))} }} from "{source}";')
if types:
lines.append(f'export type {{ {", ".join(sorted(types))} }} from "{source}";')
lines.append("```")
lines.append("")
return lines
def _generate_plugins_section(self) -> List[str]:
"""Generate optional plugins section."""
return [
"# Optional Plugins",
"",
"External tools are available via `praisonai-tools` package:",
"",
"```bash",
"pip install praisonai-tools",
"```",
"",
"See [PraisonAI-tools](https://github.com/MervinPraison/PraisonAI-tools) for available tools.",
"",
]
def generate_api_md(
repo_root: Optional[Path] = None,
output_path: Optional[Path] = None,
check: bool = False,
stdout: bool = False
) -> int:
"""
Generate api.md file.
Args:
repo_root: Repository root path (auto-detected if None)
output_path: Output file path (defaults to repo_root/api.md)
check: If True, check if existing file matches (exit 1 if different)
stdout: If True, print to stdout instead of writing file
Returns:
Exit code (0 for success, 1 for check failure)
"""
generator = ApiMdGenerator(repo_root)
content = generator.generate()
if stdout:
print(content)
return 0
if output_path is None:
output_path = generator.repo_root / "api.md"
if check:
if output_path.exists():
with open(output_path, 'r', encoding='utf-8') as f:
existing = f.read()
if existing == content:
print(f"✓ {output_path} is up to date")
return 0
else:
print(f"✗ {output_path} is out of date")
print("Run `praisonai docs api-md --write` to regenerate")
return 1
else:
print(f"✗ {output_path} does not exist")
print("Run `praisonai docs api-md --write` to generate")
return 1
# Write the file
with open(output_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"✓ Generated {output_path}")
return 0
def main():
"""CLI entry point for the generator."""
import argparse
parser = argparse.ArgumentParser(
description="Generate api.md for PraisonAI"
)
parser.add_argument(
'--write', action='store_true', default=True,
help='Write api.md file (default)'
)
parser.add_argument(
'--check', action='store_true',
help='Check if api.md is up to date (exit 1 if not)'
)
parser.add_argument(
'--stdout', action='store_true',
help='Print to stdout instead of writing file'
)
parser.add_argument(
'--output', '-o', type=Path,
help='Output file path (default: repo_root/api.md)'
)
args = parser.parse_args()
exit_code = generate_api_md(
output_path=args.output,
check=args.check,
stdout=args.stdout
)
sys.exit(exit_code)
if __name__ == '__main__':
main()
+58 -4
View File
@@ -93,8 +93,45 @@ def chat_main(
is_flag=False,
flag_value="true",
),
# NEW: Additional consolidated params for ALL GREEN feature parity
planning: Optional[str] = typer.Option(
None, "--planning",
help="Enable planning mode. Use --planning for default, --planning=thorough for preset",
is_flag=False,
flag_value="true",
),
context: Optional[str] = typer.Option(
None, "--context",
help="Enable context management. Use --context for default",
is_flag=False,
flag_value="true",
),
output: Optional[str] = typer.Option(
None, "--output",
help="Output preset. Use --output=verbose, --output=minimal, --output=silent",
),
execution: Optional[str] = typer.Option(
None, "--execution",
help="Execution preset. Use --execution=fast, --execution=thorough, --execution=unlimited",
),
hooks: Optional[str] = typer.Option(
None, "--hooks",
help="Hooks config file path for lifecycle callbacks",
),
caching: Optional[str] = typer.Option(
None, "--caching",
help="Enable caching. Use --caching for default, --caching=redis for preset",
is_flag=False,
flag_value="true",
),
profile: bool = typer.Option(False, "--profile", help="Enable CLI profiling (timing breakdown)"),
profile_deep: bool = typer.Option(False, "--profile-deep", help="Enable deep profiling (cProfile stats, higher overhead)"),
# UI backend selection
ui: str = typer.Option("auto", "--ui", help="UI backend: auto, plain, rich, mg (middle-ground)"),
json_output: bool = typer.Option(False, "--json", help="Output JSON (forces plain backend)"),
no_color: bool = typer.Option(False, "--no-color", help="Disable colors"),
theme: str = typer.Option("default", "--theme", help="UI theme: default, dark, light, minimal"),
compact: bool = typer.Option(False, "--compact", help="Compact output mode"),
):
"""
Start terminal-native interactive chat mode.
@@ -119,6 +156,18 @@ def chat_main(
if workspace:
os.environ["PRAISONAI_WORKSPACE"] = workspace
# Configure UI backend
from praisonai.cli.ui import select_backend, UIConfig
ui_config = UIConfig(
ui_backend=ui,
json_output=json_output,
no_color=no_color,
theme=theme,
compact=compact,
verbose=verbose,
)
backend = select_backend(ui_config)
# Handle profiling for single prompt mode
if prompt and (profile or profile_deep):
_run_profiled_chat(
@@ -156,17 +205,22 @@ def chat_main(
core = InteractiveCore(config=config)
if prompt:
# Single prompt mode
# Single prompt mode - use backend for output
from praisonai.cli.ui.events import UIEventType
async def run_prompt():
if continue_session:
core.continue_session()
backend.emit(UIEventType.MESSAGE_START, {})
response = await core.prompt(prompt)
print(response)
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': response})
backend.emit(UIEventType.MESSAGE_END, {})
asyncio.run(run_prompt())
else:
# Interactive REPL mode
frontend = RichFrontend(core=core, config=config)
# Interactive REPL mode - pass ui_config to frontend
frontend = RichFrontend(core=core, config=config, ui_config=ui_config)
asyncio.run(frontend.run())
except ImportError:
@@ -813,3 +813,47 @@ def docs_report(
typer.echo(output)
raise typer.Exit(exit_code)
@app.command("api-md")
def docs_api_md(
write: bool = typer.Option(
True,
"--write", "-w",
help="Write api.md file (default)",
),
check: bool = typer.Option(
False,
"--check", "-c",
help="Check if api.md is up to date (exit 1 if not)",
),
stdout: bool = typer.Option(
False,
"--stdout", "-s",
help="Print to stdout instead of writing file",
),
output: Optional[Path] = typer.Option(
None,
"--output", "-o",
help="Output file path (default: repo_root/api.md)",
),
):
"""
Generate or check the api.md API reference file.
This command generates a comprehensive API reference document
covering all public exports from praisonaiagents, praisonai, CLI, and TypeScript.
Examples:
praisonai docs api-md # Generate api.md
praisonai docs api-md --check # Check if api.md is up to date
praisonai docs api-md --stdout # Print to stdout
"""
from praisonai._dev.api_md import generate_api_md
exit_code = generate_api_md(
output_path=output,
check=check,
stdout=stdout
)
raise typer.Exit(exit_code)
@@ -31,14 +31,21 @@ class RichFrontend:
- `praisonai chat`
"""
def __init__(self, core: Optional[InteractiveCore] = None, config: Optional[InteractiveConfig] = None):
def __init__(
self,
core: Optional[InteractiveCore] = None,
config: Optional[InteractiveConfig] = None,
ui_config: Optional['UIConfig'] = None,
):
"""Initialize the Rich frontend.
Args:
core: InteractiveCore instance. If None, creates one.
config: Configuration. Used if core is None.
ui_config: UI backend configuration for rendering options.
"""
self.core = core or InteractiveCore(config=config)
self._ui_config = ui_config
self._running = False
self._current_response = ""
self._pending_approval: Optional[ApprovalRequest] = None
+162
View File
@@ -0,0 +1,162 @@
"""
UI Backend system for PraisonAI CLI.
Provides multiple rendering backends for interactive mode:
- PlainBackend: No dependencies, works everywhere
- RichBackend: Rich-based rendering (current default)
- MiddleGroundBackend: Enhanced Rich + prompt_toolkit (new default when deps available)
All backends implement the UIBackend protocol and are selected automatically
based on environment, or explicitly via --ui flag.
"""
import os
import sys
from typing import Optional
__all__ = [
'UIConfig',
'UIEventType',
'select_backend',
'PlainBackend',
'RichBackend',
'MiddleGroundBackend',
]
_lazy_cache = {}
def __getattr__(name: str):
"""Lazy load UI components."""
if name in _lazy_cache:
return _lazy_cache[name]
if name == 'UIConfig':
from .config import UIConfig
_lazy_cache[name] = UIConfig
return UIConfig
if name == 'UIEventType':
from .events import UIEventType
_lazy_cache[name] = UIEventType
return UIEventType
if name == 'PlainBackend':
from .plain import PlainBackend
_lazy_cache[name] = PlainBackend
return PlainBackend
if name == 'RichBackend':
from .rich_backend import RichBackend
_lazy_cache[name] = RichBackend
return RichBackend
if name == 'MiddleGroundBackend':
from .mg_backend import MiddleGroundBackend
_lazy_cache[name] = MiddleGroundBackend
return MiddleGroundBackend
if name == 'select_backend':
from .selector import select_backend
_lazy_cache[name] = select_backend
return select_backend
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def select_backend(config: Optional['UIConfig'] = None):
"""
Select the appropriate UI backend based on config and environment.
Selection priority:
1. Explicit config.ui_backend override
2. PRAISONAI_UI_SAFE=1 → PlainBackend
3. config.json_output=True → PlainBackend
4. Non-TTY stdout → PlainBackend
5. Auto-detect based on available deps:
- MiddleGroundBackend if rich + prompt_toolkit available
- RichBackend if rich available
- PlainBackend otherwise
Args:
config: UIConfig instance with settings
Returns:
UIBackend instance
"""
from .config import UIConfig
from .plain import PlainBackend
if config is None:
config = UIConfig()
# Explicit override
if config.ui_backend and config.ui_backend != 'auto':
return _get_backend_by_name(config.ui_backend, config)
# Environment override for safe mode
if os.environ.get('PRAISONAI_UI_SAFE') == '1':
return PlainBackend(config)
# JSON output forces plain
if config.json_output:
return PlainBackend(config)
# Non-TTY forces plain
if not sys.stdout.isatty():
return PlainBackend(config)
# Auto-detect based on available deps
return _auto_select_backend(config)
def _get_backend_by_name(name: str, config: 'UIConfig'):
"""Get backend by name."""
from .plain import PlainBackend
if name == 'plain':
return PlainBackend(config)
if name == 'rich':
try:
from .rich_backend import RichBackend
return RichBackend(config)
except ImportError:
return PlainBackend(config)
if name == 'mg':
try:
from .mg_backend import MiddleGroundBackend
return MiddleGroundBackend(config)
except ImportError:
# Fallback to rich, then plain
try:
from .rich_backend import RichBackend
return RichBackend(config)
except ImportError:
return PlainBackend(config)
# Unknown backend, use plain
return PlainBackend(config)
def _auto_select_backend(config: 'UIConfig'):
"""Auto-select best available backend."""
from .plain import PlainBackend
# Try MiddleGroundBackend first (requires rich + prompt_toolkit)
try:
from .mg_backend import MiddleGroundBackend
return MiddleGroundBackend(config)
except ImportError:
pass
# Try RichBackend (requires rich)
try:
from .rich_backend import RichBackend
return RichBackend(config)
except ImportError:
pass
# Fallback to PlainBackend
return PlainBackend(config)
+46
View File
@@ -0,0 +1,46 @@
"""
UI Configuration for PraisonAI CLI.
Provides unified configuration for all UI backends.
"""
from dataclasses import dataclass, field
from typing import Dict, Any
@dataclass
class UIConfig:
"""Configuration for UI backends."""
# Backend selection
ui_backend: str = 'auto' # auto, plain, rich, mg
# Output modes
json_output: bool = False
compact: bool = False
verbose: bool = False
# Styling
no_color: bool = False
no_unicode: bool = False
theme: str = 'default'
# Behavior
no_fullscreen: bool = False
# Additional settings
extra: Dict[str, Any] = field(default_factory=dict)
@classmethod
def from_cli_args(cls, **kwargs) -> 'UIConfig':
"""Create UIConfig from CLI arguments."""
return cls(
ui_backend=kwargs.get('ui', 'auto'),
json_output=kwargs.get('json', False),
compact=kwargs.get('compact', False),
verbose=kwargs.get('verbose', False),
no_color=kwargs.get('no_color', False),
no_unicode=kwargs.get('no_unicode', False),
theme=kwargs.get('theme', 'default'),
no_fullscreen=kwargs.get('no_fullscreen', False),
)
+61
View File
@@ -0,0 +1,61 @@
"""
UI Event types for backend communication.
Events flow from InteractiveCore to UI backends for rendering.
"""
from enum import Enum
from dataclasses import dataclass, field
from typing import Any, Dict, Optional
class UIEventType(Enum):
"""Event types emitted by InteractiveCore to UI backends."""
# Message lifecycle
MESSAGE_START = "message.start"
MESSAGE_CHUNK = "message.chunk"
MESSAGE_END = "message.end"
# Tool lifecycle
TOOL_START = "tool.start"
TOOL_PROGRESS = "tool.progress"
TOOL_END = "tool.end"
# Approval flow
APPROVAL_REQUEST = "approval.request"
APPROVAL_RESPONSE = "approval.response"
# Status updates
STATUS_UPDATE = "status.update"
STATUS_CLEAR = "status.clear"
# Errors
ERROR = "error"
WARNING = "warning"
# Session events
SESSION_START = "session.start"
SESSION_END = "session.end"
# Agent events (multi-agent)
AGENT_SWITCH = "agent.switch"
# Input events
INPUT_REQUEST = "input.request"
INPUT_RECEIVED = "input.received"
@dataclass
class UIEvent:
"""A UI event with type and data payload."""
event_type: UIEventType
data: Dict[str, Any] = field(default_factory=dict)
agent_name: Optional[str] = None
timestamp: Optional[float] = None
def __post_init__(self):
if self.timestamp is None:
import time
self.timestamp = time.time()
@@ -0,0 +1,342 @@
"""
MiddleGroundBackend - Enhanced Rich + prompt_toolkit UI backend.
The "middle ground" between plain text and full TUI:
- Rich for formatted output (panels, markdown, syntax highlighting)
- prompt_toolkit for advanced input (history, completion, key bindings)
- Streaming markdown with no-flicker approach (stable lines + live window)
- Terminal cleanup on exit/crash
Key design: Uses Aider-style streaming where stable lines go to console
above a small Live window, preserving terminal scrollback.
"""
import sys
import time
import atexit
import signal
from typing import Dict, Any, Optional, List, TYPE_CHECKING
if TYPE_CHECKING:
from .config import UIConfig
from .events import UIEventType
try:
from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
from rich.live import Live
from rich.text import Text
RICH_AVAILABLE = True
except ImportError:
RICH_AVAILABLE = False
try:
from prompt_toolkit import PromptSession
from prompt_toolkit.history import InMemoryHistory
PROMPT_TOOLKIT_AVAILABLE = True
except ImportError:
PROMPT_TOOLKIT_AVAILABLE = False
class MarkdownStreamer:
"""Streaming markdown renderer with no-flicker approach.
Splits output into stable lines (printed above) and unstable lines
(in Live window). This preserves terminal scrollback while allowing
smooth streaming updates.
"""
def __init__(self, console: 'Console', live_window: int = 6):
"""Initialize streamer.
Args:
console: Rich Console instance
live_window: Number of lines to keep in live area
"""
self._console = console
self._live_window = live_window
self._live: Optional[Live] = None
self._printed_lines: List[str] = []
self._current_text = ""
self._min_delay = 1.0 / 20 # 20 FPS max
self._last_update = 0.0
def start(self) -> None:
"""Start the streaming display."""
self._live = Live(Text(""), console=self._console, refresh_per_second=20)
self._live.start()
self._printed_lines = []
self._current_text = ""
def update(self, text: str, final: bool = False) -> None:
"""Update the displayed content.
Args:
text: Full markdown text so far
final: If True, this is the final update
"""
if not self._live:
return
now = time.time()
if not final and now - self._last_update < self._min_delay:
return
self._last_update = now
self._current_text = text
lines = text.splitlines(keepends=True)
num_lines = len(lines)
# How many lines are stable (not in live window)?
stable_count = num_lines - self._live_window if not final else num_lines
if stable_count > len(self._printed_lines):
# Print new stable lines above live area
new_lines = lines[len(self._printed_lines):stable_count]
for line in new_lines:
self._console.print(line.rstrip('\n'))
self._printed_lines = lines[:stable_count]
if final:
self._live.update(Text(""))
self._live.stop()
self._live = None
else:
# Update live window with remaining lines
rest = lines[stable_count:]
rest_text = "".join(rest)
self._live.update(Text(rest_text))
def stop(self) -> None:
"""Stop the streaming display."""
if self._live:
self._live.stop()
self._live = None
class MiddleGroundBackend:
"""Enhanced Rich + prompt_toolkit UI backend."""
def __init__(self, config: Optional['UIConfig'] = None):
"""Initialize MiddleGroundBackend.
Args:
config: Optional UIConfig instance
Raises:
ImportError: If required deps not installed
"""
if not RICH_AVAILABLE:
raise ImportError("Rich is required. Install with: pip install rich")
if not PROMPT_TOOLKIT_AVAILABLE:
raise ImportError("prompt_toolkit is required. Install with: pip install prompt_toolkit")
self._config = config
self._current_message = ""
self._in_stream = False
# Configure console
no_color = config.no_color if config else False
self._console = Console(
force_terminal=True,
no_color=no_color,
highlight=not no_color,
)
# Markdown streamer for no-flicker streaming
self._streamer: Optional[MarkdownStreamer] = None
# prompt_toolkit session
self._prompt_session: Optional[PromptSession] = None
self._history = InMemoryHistory()
# Register cleanup handlers
self._register_cleanup()
def _register_cleanup(self) -> None:
"""Register terminal cleanup handlers."""
def cleanup():
self._cleanup_terminal()
atexit.register(cleanup)
# Handle signals
def signal_handler(signum, frame):
self._cleanup_terminal()
sys.exit(128 + signum)
try:
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
except (ValueError, OSError):
# Can't set signal handlers in some contexts
pass
def _cleanup_terminal(self) -> None:
"""Restore terminal state."""
try:
# Stop any active streaming
if self._streamer:
self._streamer.stop()
# Restore cursor
sys.stdout.write("\033[?25h") # Show cursor
sys.stdout.write("\033[0m") # Reset colors
sys.stdout.flush()
except Exception:
pass
def emit(self, event_type: UIEventType, data: Dict[str, Any]) -> None:
"""Emit an event to the UI.
Args:
event_type: Type of event
data: Event data payload
"""
if event_type == UIEventType.MESSAGE_START:
self._in_stream = True
self._current_message = ""
agent_name = data.get('agent_name', '')
if agent_name:
self._console.print(f"[bold cyan][{agent_name}][/bold cyan]")
# Start streaming display
self._streamer = MarkdownStreamer(self._console)
self._streamer.start()
elif event_type == UIEventType.MESSAGE_CHUNK:
content = data.get('content', '')
self._current_message += content
if self._streamer:
self._streamer.update(self._current_message)
elif event_type == UIEventType.MESSAGE_END:
self._in_stream = False
if self._streamer:
self._streamer.update(self._current_message, final=True)
self._streamer = None
self._current_message = ""
elif event_type == UIEventType.TOOL_START:
tool_name = data.get('tool_name', 'tool')
args = data.get('args', '')
self._console.print(f"\n[dim]▶ {tool_name}[/dim]", end="")
if args and self._config and self._config.verbose:
self._console.print(f" [dim]{args}[/dim]")
else:
self._console.print()
elif event_type == UIEventType.TOOL_END:
result = data.get('result', '')
success = data.get('success', True)
if result:
style = "green" if success else "red"
# Truncate long results
if len(result) > 200 and not (self._config and self._config.verbose):
result = result[:200] + "..."
self._console.print(f"[{style}]{result}[/{style}]")
elif event_type == UIEventType.ERROR:
message = data.get('message', 'Unknown error')
self._console.print(Panel(
f"[bold red]{message}[/bold red]",
title="Error",
border_style="red",
))
elif event_type == UIEventType.WARNING:
message = data.get('message', '')
self._console.print(f"[yellow]⚠ {message}[/yellow]")
elif event_type == UIEventType.STATUS_UPDATE:
status = data.get('status', '')
if status:
# Use transient status (overwrite with \r)
sys.stdout.write(f"\r[{status}]")
sys.stdout.flush()
elif event_type == UIEventType.STATUS_CLEAR:
sys.stdout.write("\r" + " " * 40 + "\r")
sys.stdout.flush()
elif event_type == UIEventType.APPROVAL_REQUEST:
prompt = data.get('prompt', 'Approve?')
action = data.get('action', '')
self._console.print()
self._console.print(Panel(
f"{prompt}\n\n[dim]{action}[/dim]" if action else prompt,
title="[bold yellow]Approval Required[/bold yellow]",
border_style="yellow",
))
self._console.print("[bold][y][/bold]es / [bold][n][/bold]o / [bold][a][/bold]lways: ", end="")
def prompt(self, message: str = "") -> str:
"""Get user input with prompt_toolkit.
Args:
message: Optional prompt message
Returns:
User input string
"""
if not self._prompt_session:
self._prompt_session = PromptSession(
history=self._history,
enable_history_search=True,
)
try:
return self._prompt_session.prompt(message)
except EOFError:
return ""
except KeyboardInterrupt:
return ""
async def prompt_async(self, message: str = "") -> str:
"""Get user input asynchronously.
Args:
message: Optional prompt message
Returns:
User input string
"""
if not self._prompt_session:
self._prompt_session = PromptSession(
history=self._history,
enable_history_search=True,
)
try:
return await self._prompt_session.prompt_async(message)
except EOFError:
return ""
except KeyboardInterrupt:
return ""
def is_tty(self) -> bool:
"""Check if running in a TTY.
Returns:
True if stdout is a TTY
"""
return sys.stdout.isatty()
def render_markdown(self, text: str) -> None:
"""Render markdown text.
Args:
text: Markdown text to render
"""
md = Markdown(text)
self._console.print(md)
def cleanup(self) -> None:
"""Cleanup resources."""
self._cleanup_terminal()
+133
View File
@@ -0,0 +1,133 @@
"""
PlainBackend - Zero-dependency UI backend.
Works in any environment: non-TTY, piped output, dumb terminals.
No colors, no formatting, just plain text output.
"""
import sys
from typing import Dict, Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from .config import UIConfig
from .events import UIEventType
class PlainBackend:
"""Plain text UI backend with no dependencies."""
def __init__(self, config: Optional['UIConfig'] = None):
"""Initialize PlainBackend.
Args:
config: Optional UIConfig instance
"""
self._config = config
self._current_message = ""
self._in_stream = False
def emit(self, event_type: UIEventType, data: Dict[str, Any]) -> None:
"""Emit an event to the UI.
Args:
event_type: Type of event
data: Event data payload
"""
if event_type == UIEventType.MESSAGE_START:
self._in_stream = True
self._current_message = ""
agent_name = data.get('agent_name', '')
if agent_name:
sys.stdout.write(f"[{agent_name}] ")
sys.stdout.flush()
elif event_type == UIEventType.MESSAGE_CHUNK:
content = data.get('content', '')
sys.stdout.write(content)
sys.stdout.flush()
self._current_message += content
elif event_type == UIEventType.MESSAGE_END:
self._in_stream = False
if not self._current_message.endswith('\n'):
sys.stdout.write('\n')
sys.stdout.flush()
self._current_message = ""
elif event_type == UIEventType.TOOL_START:
tool_name = data.get('tool_name', 'tool')
sys.stdout.write(f"\n[Tool: {tool_name}]\n")
sys.stdout.flush()
elif event_type == UIEventType.TOOL_END:
result = data.get('result', '')
if result:
sys.stdout.write(f"{result}\n")
sys.stdout.flush()
elif event_type == UIEventType.ERROR:
message = data.get('message', 'Unknown error')
sys.stderr.write(f"Error: {message}\n")
sys.stderr.flush()
elif event_type == UIEventType.WARNING:
message = data.get('message', '')
sys.stderr.write(f"Warning: {message}\n")
sys.stderr.flush()
elif event_type == UIEventType.STATUS_UPDATE:
status = data.get('status', '')
if status and self._config and self._config.verbose:
sys.stderr.write(f"[{status}]\n")
sys.stderr.flush()
elif event_type == UIEventType.APPROVAL_REQUEST:
prompt = data.get('prompt', 'Approve?')
sys.stdout.write(f"\n{prompt} [y/n]: ")
sys.stdout.flush()
def prompt(self, message: str = "") -> str:
"""Get user input.
Args:
message: Optional prompt message
Returns:
User input string
"""
if message:
sys.stdout.write(message)
sys.stdout.flush()
try:
return input()
except EOFError:
return ""
except KeyboardInterrupt:
return ""
async def prompt_async(self, message: str = "") -> str:
"""Get user input asynchronously.
For PlainBackend, this is just a sync call wrapped.
Args:
message: Optional prompt message
Returns:
User input string
"""
return self.prompt(message)
def is_tty(self) -> bool:
"""Check if running in a TTY.
Returns:
True if stdout is a TTY
"""
return sys.stdout.isatty()
def cleanup(self) -> None:
"""Cleanup resources. No-op for PlainBackend."""
pass
@@ -0,0 +1,162 @@
"""
RichBackend - Rich-based UI backend.
Uses Rich library for formatted terminal output with colors,
panels, markdown rendering, and syntax highlighting.
"""
import sys
from typing import Dict, Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from .config import UIConfig
from .events import UIEventType
try:
from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
RICH_AVAILABLE = True
except ImportError:
RICH_AVAILABLE = False
class RichBackend:
"""Rich-based UI backend with formatted output."""
def __init__(self, config: Optional['UIConfig'] = None):
"""Initialize RichBackend.
Args:
config: Optional UIConfig instance
Raises:
ImportError: If rich is not installed
"""
if not RICH_AVAILABLE:
raise ImportError("Rich is required for RichBackend. Install with: pip install rich")
self._config = config
self._current_message = ""
self._in_stream = False
# Configure console
no_color = config.no_color if config else False
self._console = Console(
force_terminal=True,
no_color=no_color,
highlight=not no_color,
)
def emit(self, event_type: UIEventType, data: Dict[str, Any]) -> None:
"""Emit an event to the UI.
Args:
event_type: Type of event
data: Event data payload
"""
if event_type == UIEventType.MESSAGE_START:
self._in_stream = True
self._current_message = ""
agent_name = data.get('agent_name', '')
if agent_name:
self._console.print(f"[bold cyan][{agent_name}][/bold cyan] ", end="")
elif event_type == UIEventType.MESSAGE_CHUNK:
content = data.get('content', '')
# For streaming, use raw print to avoid Rich overhead
sys.stdout.write(content)
sys.stdout.flush()
self._current_message += content
elif event_type == UIEventType.MESSAGE_END:
self._in_stream = False
if not self._current_message.endswith('\n'):
sys.stdout.write('\n')
sys.stdout.flush()
self._current_message = ""
elif event_type == UIEventType.TOOL_START:
tool_name = data.get('tool_name', 'tool')
self._console.print(f"\n[dim]▶ {tool_name}[/dim]")
elif event_type == UIEventType.TOOL_END:
result = data.get('result', '')
success = data.get('success', True)
if result:
style = "green" if success else "red"
self._console.print(f"[{style}]{result}[/{style}]")
elif event_type == UIEventType.ERROR:
message = data.get('message', 'Unknown error')
self._console.print(f"[bold red]Error:[/bold red] {message}")
elif event_type == UIEventType.WARNING:
message = data.get('message', '')
self._console.print(f"[yellow]Warning:[/yellow] {message}")
elif event_type == UIEventType.STATUS_UPDATE:
status = data.get('status', '')
if status and self._config and self._config.verbose:
self._console.print(f"[dim][{status}][/dim]")
elif event_type == UIEventType.APPROVAL_REQUEST:
prompt = data.get('prompt', 'Approve?')
self._console.print(Panel(
prompt,
title="[bold yellow]Approval Required[/bold yellow]",
border_style="yellow",
))
self._console.print("[y]es / [n]o / [a]lways: ", end="")
def prompt(self, message: str = "") -> str:
"""Get user input.
Args:
message: Optional prompt message
Returns:
User input string
"""
if message:
self._console.print(message, end="")
try:
return input()
except EOFError:
return ""
except KeyboardInterrupt:
return ""
async def prompt_async(self, message: str = "") -> str:
"""Get user input asynchronously.
Args:
message: Optional prompt message
Returns:
User input string
"""
return self.prompt(message)
def is_tty(self) -> bool:
"""Check if running in a TTY.
Returns:
True if stdout is a TTY
"""
return sys.stdout.isatty()
def render_markdown(self, text: str) -> None:
"""Render markdown text.
Args:
text: Markdown text to render
"""
md = Markdown(text)
self._console.print(md)
def cleanup(self) -> None:
"""Cleanup resources."""
pass
+1 -1
View File
@@ -57,7 +57,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==3.6.2 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==3.7.1 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+1 -1
View File
@@ -1 +1 @@
__version__ = "3.6.2"
__version__ = "3.7.1"
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.10.10",
"praisonaiagents>=0.11.1",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""
Generate api.md for PraisonAI.
This script generates a comprehensive API reference document
covering all public exports from praisonaiagents, praisonai, CLI, and TypeScript.
Usage:
./scripts/generate_api_md.py # Generate api.md
./scripts/generate_api_md.py --check # Check if api.md is up to date
./scripts/generate_api_md.py --stdout # Print to stdout
"""
import sys
from pathlib import Path
# Add the praisonai package to path
# Script is in src/praisonai/scripts/, so go up 3 levels to repo root
repo_root = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(repo_root / "src" / "praisonai"))
from praisonai._dev.api_md import generate_api_md
if __name__ == '__main__':
# Parse simple args
check = '--check' in sys.argv
stdout = '--stdout' in sys.argv
exit_code = generate_api_md(
repo_root=repo_root,
check=check,
stdout=stdout
)
sys.exit(exit_code)
@@ -0,0 +1 @@
"""Tests for CLI UI backends."""
@@ -0,0 +1,143 @@
"""
Tests for UI backend selection logic.
Verifies:
1. Auto-selection picks correct backend based on environment
2. Non-TTY forces PlainBackend
3. --json forces PlainBackend
4. PRAISONAI_UI_SAFE=1 forces PlainBackend
5. Explicit --ui flag overrides auto-selection
6. Fallback chain works when deps missing
"""
import os
import sys
from unittest.mock import patch
class TestBackendSelection:
"""Test suite for UI backend auto-selection."""
def test_select_backend_returns_plain_for_non_tty(self):
"""Non-TTY stdout should select PlainBackend."""
from praisonai.cli.ui import select_backend, UIConfig
with patch.object(sys.stdout, 'isatty', return_value=False):
config = UIConfig()
backend = select_backend(config)
assert backend.__class__.__name__ == 'PlainBackend'
def test_select_backend_returns_plain_for_json_mode(self):
"""--json flag should force PlainBackend."""
from praisonai.cli.ui import select_backend, UIConfig
config = UIConfig(json_output=True)
backend = select_backend(config)
assert backend.__class__.__name__ == 'PlainBackend'
def test_select_backend_returns_plain_for_safe_mode(self):
"""PRAISONAI_UI_SAFE=1 should force PlainBackend."""
from praisonai.cli.ui import select_backend, UIConfig
with patch.dict(os.environ, {'PRAISONAI_UI_SAFE': '1'}):
config = UIConfig()
backend = select_backend(config)
assert backend.__class__.__name__ == 'PlainBackend'
def test_select_backend_explicit_override(self):
"""Explicit --ui flag should override auto-selection."""
from praisonai.cli.ui import select_backend, UIConfig
config = UIConfig(ui_backend='plain')
backend = select_backend(config)
assert backend.__class__.__name__ == 'PlainBackend'
def test_select_backend_auto_with_deps(self):
"""Auto-selection with deps available should select MiddleGroundBackend."""
from praisonai.cli.ui import select_backend, UIConfig
with patch.object(sys.stdout, 'isatty', return_value=True):
with patch.dict(os.environ, {}, clear=False):
# Remove PRAISONAI_UI_SAFE if present
os.environ.pop('PRAISONAI_UI_SAFE', None)
config = UIConfig()
backend = select_backend(config)
# Should be MiddleGroundBackend or RichBackend depending on deps
assert backend.__class__.__name__ in ('MiddleGroundBackend', 'RichBackend', 'PlainBackend')
def test_select_backend_fallback_chain(self):
"""When deps missing, should fallback gracefully."""
from praisonai.cli.ui import select_backend, UIConfig
with patch.object(sys.stdout, 'isatty', return_value=True):
config = UIConfig(ui_backend='auto')
backend = select_backend(config)
# Should return some valid backend
assert hasattr(backend, 'emit')
assert hasattr(backend, 'prompt')
class TestUIConfig:
"""Test UIConfig dataclass."""
def test_ui_config_defaults(self):
"""UIConfig should have sensible defaults."""
from praisonai.cli.ui import UIConfig
config = UIConfig()
assert config.ui_backend == 'auto'
assert config.json_output is False
assert config.no_color is False
assert config.theme == 'default'
def test_ui_config_from_cli_args(self):
"""UIConfig should be constructible from CLI args."""
from praisonai.cli.ui import UIConfig
config = UIConfig(
ui_backend='mg',
json_output=True,
no_color=True,
theme='dark',
compact=True,
)
assert config.ui_backend == 'mg'
assert config.json_output is True
assert config.no_color is True
assert config.theme == 'dark'
assert config.compact is True
class TestBackendProtocol:
"""Test that backends implement the UIBackend protocol."""
def test_plain_backend_has_required_methods(self):
"""PlainBackend should implement UIBackend protocol."""
from praisonai.cli.ui.plain import PlainBackend
backend = PlainBackend()
assert hasattr(backend, 'emit')
assert hasattr(backend, 'prompt')
assert hasattr(backend, 'is_tty')
assert callable(backend.emit)
assert callable(backend.prompt)
assert callable(backend.is_tty)
def test_plain_backend_is_tty_returns_false_for_non_tty(self):
"""PlainBackend.is_tty() should return False for non-TTY."""
from praisonai.cli.ui.plain import PlainBackend
with patch.object(sys.stdout, 'isatty', return_value=False):
backend = PlainBackend()
assert backend.is_tty() is False
def test_plain_backend_emit_writes_to_stdout(self, capsys):
"""PlainBackend.emit() should write to stdout."""
from praisonai.cli.ui.plain import PlainBackend
from praisonai.cli.ui.events import UIEventType
backend = PlainBackend()
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': 'Hello'})
captured = capsys.readouterr()
assert 'Hello' in captured.out
@@ -0,0 +1,178 @@
"""
Tests for UI event routing.
Verifies:
1. Events are properly routed from InteractiveCore to backends
2. All event types are handled correctly
3. Streaming events work properly
4. Multi-agent events include agent name
"""
from praisonai.cli.ui.events import UIEventType, UIEvent
from praisonai.cli.ui.plain import PlainBackend
class TestUIEventType:
"""Test UIEventType enum."""
def test_message_events_exist(self):
"""Message lifecycle events should exist."""
assert UIEventType.MESSAGE_START
assert UIEventType.MESSAGE_CHUNK
assert UIEventType.MESSAGE_END
def test_tool_events_exist(self):
"""Tool lifecycle events should exist."""
assert UIEventType.TOOL_START
assert UIEventType.TOOL_PROGRESS
assert UIEventType.TOOL_END
def test_approval_events_exist(self):
"""Approval flow events should exist."""
assert UIEventType.APPROVAL_REQUEST
assert UIEventType.APPROVAL_RESPONSE
def test_status_events_exist(self):
"""Status events should exist."""
assert UIEventType.STATUS_UPDATE
assert UIEventType.STATUS_CLEAR
def test_error_events_exist(self):
"""Error events should exist."""
assert UIEventType.ERROR
assert UIEventType.WARNING
class TestUIEvent:
"""Test UIEvent dataclass."""
def test_event_has_timestamp(self):
"""UIEvent should auto-generate timestamp."""
event = UIEvent(event_type=UIEventType.MESSAGE_START)
assert event.timestamp is not None
assert event.timestamp > 0
def test_event_with_data(self):
"""UIEvent should accept data payload."""
event = UIEvent(
event_type=UIEventType.MESSAGE_CHUNK,
data={'content': 'Hello'}
)
assert event.data['content'] == 'Hello'
def test_event_with_agent_name(self):
"""UIEvent should accept agent name for multi-agent."""
event = UIEvent(
event_type=UIEventType.MESSAGE_START,
agent_name='Researcher'
)
assert event.agent_name == 'Researcher'
class TestPlainBackendEventRouting:
"""Test PlainBackend event handling."""
def test_message_start_with_agent_name(self, capsys):
"""MESSAGE_START with agent name should print agent prefix."""
backend = PlainBackend()
backend.emit(UIEventType.MESSAGE_START, {'agent_name': 'Coder'})
captured = capsys.readouterr()
assert '[Coder]' in captured.out
def test_message_chunk_appends_content(self, capsys):
"""MESSAGE_CHUNK should append content to output."""
backend = PlainBackend()
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': 'Hello '})
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': 'World'})
captured = capsys.readouterr()
assert 'Hello World' in captured.out
def test_message_end_adds_newline(self, capsys):
"""MESSAGE_END should add newline if needed."""
backend = PlainBackend()
backend.emit(UIEventType.MESSAGE_START, {})
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': 'Test'})
backend.emit(UIEventType.MESSAGE_END, {})
captured = capsys.readouterr()
assert captured.out.endswith('\n')
def test_tool_start_shows_tool_name(self, capsys):
"""TOOL_START should show tool name."""
backend = PlainBackend()
backend.emit(UIEventType.TOOL_START, {'tool_name': 'read_file'})
captured = capsys.readouterr()
assert 'read_file' in captured.out
def test_tool_end_shows_result(self, capsys):
"""TOOL_END should show result."""
backend = PlainBackend()
backend.emit(UIEventType.TOOL_END, {'result': 'File contents here'})
captured = capsys.readouterr()
assert 'File contents here' in captured.out
def test_error_writes_to_stderr(self, capsys):
"""ERROR should write to stderr."""
backend = PlainBackend()
backend.emit(UIEventType.ERROR, {'message': 'Something went wrong'})
captured = capsys.readouterr()
assert 'Something went wrong' in captured.err
assert 'Error' in captured.err
def test_warning_writes_to_stderr(self, capsys):
"""WARNING should write to stderr."""
backend = PlainBackend()
backend.emit(UIEventType.WARNING, {'message': 'Be careful'})
captured = capsys.readouterr()
assert 'Be careful' in captured.err
assert 'Warning' in captured.err
def test_approval_request_shows_prompt(self, capsys):
"""APPROVAL_REQUEST should show prompt."""
backend = PlainBackend()
backend.emit(UIEventType.APPROVAL_REQUEST, {'prompt': 'Allow file write?'})
captured = capsys.readouterr()
assert 'Allow file write?' in captured.out
assert '[y/n]' in captured.out
class TestStreamingBehavior:
"""Test streaming message behavior."""
def test_streaming_preserves_partial_content(self, capsys):
"""Streaming should preserve partial content between chunks."""
backend = PlainBackend()
backend.emit(UIEventType.MESSAGE_START, {})
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': 'Hello '})
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': 'World '})
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': '!'})
backend.emit(UIEventType.MESSAGE_END, {})
captured = capsys.readouterr()
assert 'Hello World !' in captured.out
def test_multiple_messages_separated(self, capsys):
"""Multiple messages should be properly separated."""
backend = PlainBackend()
# First message
backend.emit(UIEventType.MESSAGE_START, {})
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': 'First'})
backend.emit(UIEventType.MESSAGE_END, {})
# Second message
backend.emit(UIEventType.MESSAGE_START, {})
backend.emit(UIEventType.MESSAGE_CHUNK, {'content': 'Second'})
backend.emit(UIEventType.MESSAGE_END, {})
captured = capsys.readouterr()
assert 'First' in captured.out
assert 'Second' in captured.out
+1
View File
@@ -0,0 +1 @@
# Tests for development utilities
+312
View File
@@ -0,0 +1,312 @@
"""
Tests for the API markdown generator.
Tests cover:
- Symbol discovery (respects __all__)
- Lazy-loaded symbol discovery from __getattr__
- Link generation (relative paths correct)
- Stable ordering (deterministic)
- TypeScript export parsing
- CLI command discovery
"""
import sys
from pathlib import Path
import pytest
# Add the praisonai package to path
repo_root = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(repo_root / "src" / "praisonai"))
from praisonai._dev.api_md import ApiMdGenerator, generate_api_md
class TestApiMdGenerator:
"""Tests for ApiMdGenerator class."""
@pytest.fixture
def generator(self):
"""Create a generator instance."""
return ApiMdGenerator(repo_root=repo_root)
def test_init_finds_repo_root(self, generator):
"""Test that generator finds the repository root."""
assert generator.repo_root.exists()
assert (generator.repo_root / ".git").exists()
def test_init_finds_package_paths(self, generator):
"""Test that generator finds package paths."""
assert generator.agents_pkg.exists()
assert generator.wrapper_pkg.exists()
assert generator.ts_pkg.exists()
def test_discover_all_finds_symbols(self, generator):
"""Test that discover_all finds symbols from all packages."""
generator.discover_all()
# Should find symbols from praisonaiagents
assert len(generator.agents_symbols) > 0
# Should find CLI commands
assert len(generator.cli_commands) > 0
# Should find TypeScript exports
assert len(generator.ts_exports) > 0
def test_discovers_agent_class(self, generator):
"""Test that Agent class is discovered."""
generator.discover_all()
assert "Agent" in generator.agents_symbols
agent_info = generator.agents_symbols["Agent"]
assert agent_info.kind == "class"
assert "agent.py" in agent_info.file_path
def test_discovers_agents_class(self, generator):
"""Test that Agents class is discovered."""
generator.discover_all()
assert "Agents" in generator.agents_symbols
agents_info = generator.agents_symbols["Agents"]
assert agents_info.kind == "class"
def test_discovers_workflow_exports(self, generator):
"""Test that Workflow exports are discovered."""
generator.discover_all()
# Check for workflow-related exports
workflow_exports = ["Workflow", "WorkflowStep", "Pipeline"]
found = [e for e in workflow_exports if e in generator.agents_symbols]
assert len(found) > 0, f"Expected some workflow exports, found: {list(generator.agents_symbols.keys())[:20]}"
def test_discovers_tool_exports(self, generator):
"""Test that tool exports are discovered."""
generator.discover_all()
# Check for tool-related exports
assert "Tools" in generator.agents_symbols or "BaseTool" in generator.agents_symbols
def test_discovers_lazy_loaded_symbols(self, generator):
"""Test that lazy-loaded symbols from __getattr__ are discovered."""
generator.discover_all()
# These are all lazy-loaded in __getattr__
lazy_symbols = ["Agent", "Agents", "Memory", "Knowledge", "Session"]
found = [s for s in lazy_symbols if s in generator.agents_symbols]
assert len(found) >= 3, f"Expected at least 3 lazy symbols, found: {found}"
def test_symbol_has_file_path(self, generator):
"""Test that discovered symbols have valid file paths."""
generator.discover_all()
for name, info in list(generator.agents_symbols.items())[:10]:
assert info.file_path, f"Symbol {name} missing file_path"
assert info.file_path.startswith("./"), f"Path should be relative: {info.file_path}"
def test_class_has_methods(self, generator):
"""Test that class symbols have methods extracted."""
generator.discover_all()
if "Agent" in generator.agents_symbols:
agent_info = generator.agents_symbols["Agent"]
assert len(agent_info.methods) > 0, "Agent class should have methods"
# Check for common methods
method_names = [m.name for m in agent_info.methods]
assert "start" in method_names or "run" in method_names or "chat" in method_names
class TestTypescriptDiscovery:
"""Tests for TypeScript export discovery."""
@pytest.fixture
def generator(self):
return ApiMdGenerator(repo_root=repo_root)
def test_discovers_ts_exports(self, generator):
"""Test that TypeScript exports are discovered."""
generator.discover_all()
assert len(generator.ts_exports) > 0
def test_ts_exports_have_source_file(self, generator):
"""Test that TS exports have source file info."""
generator.discover_all()
for export in generator.ts_exports[:10]:
assert export.source_file, f"Export {export.name} missing source_file"
def test_discovers_agent_ts_export(self, generator):
"""Test that Agent is exported from TypeScript."""
generator.discover_all()
export_names = [e.name for e in generator.ts_exports]
assert "Agent" in export_names
class TestCLIDiscovery:
"""Tests for CLI command discovery."""
@pytest.fixture
def generator(self):
return ApiMdGenerator(repo_root=repo_root)
def test_discovers_cli_commands(self, generator):
"""Test that CLI commands are discovered."""
generator.discover_all()
assert len(generator.cli_commands) > 0
def test_cli_commands_have_file_path(self, generator):
"""Test that CLI commands have file paths."""
generator.discover_all()
for cmd in generator.cli_commands[:10]:
assert cmd.file_path, f"Command {cmd.command_path} missing file_path"
class TestMarkdownGeneration:
"""Tests for markdown output generation."""
@pytest.fixture
def generator(self):
return ApiMdGenerator(repo_root=repo_root)
def test_generate_returns_string(self, generator):
"""Test that generate returns a string."""
content = generator.generate()
assert isinstance(content, str)
assert len(content) > 0
def test_generate_has_header(self, generator):
"""Test that generated content has header."""
content = generator.generate()
assert "# PraisonAI API Reference" in content
def test_generate_has_regenerate_instruction(self, generator):
"""Test that generated content has regeneration instruction."""
content = generator.generate()
assert "praisonai docs api-md" in content
def test_generate_has_types_block(self, generator):
"""Test that generated content has Types blocks."""
content = generator.generate()
assert "Types:" in content
assert "```python" in content
def test_generate_has_methods_block(self, generator):
"""Test that generated content has Methods blocks."""
content = generator.generate()
assert "Methods:" in content
def test_generate_has_typescript_section(self, generator):
"""Test that generated content has TypeScript section."""
content = generator.generate()
assert "# TypeScript" in content
assert "```ts" in content
def test_generate_has_cli_section(self, generator):
"""Test that generated content has CLI section."""
content = generator.generate()
assert "# CLI" in content
def test_generate_has_plugins_section(self, generator):
"""Test that generated content has Optional Plugins section."""
content = generator.generate()
assert "# Optional Plugins" in content
assert "praisonai-tools" in content
class TestDeterministicOutput:
"""Tests for deterministic/stable output."""
@pytest.fixture
def generator(self):
return ApiMdGenerator(repo_root=repo_root)
def test_generate_is_deterministic(self, generator):
"""Test that two runs produce identical output."""
content1 = generator.generate()
# Create a new generator and generate again
generator2 = ApiMdGenerator(repo_root=repo_root)
content2 = generator2.generate()
assert content1 == content2, "Output should be deterministic"
def test_symbols_are_sorted(self, generator):
"""Test that symbols are sorted alphabetically."""
generator.discover_all()
symbol_names = list(generator.agents_symbols.keys())
assert symbol_names == sorted(symbol_names), "Symbols should be sorted"
class TestGenerateApiMdFunction:
"""Tests for the generate_api_md function."""
def test_generate_to_stdout(self, capsys):
"""Test generating to stdout."""
exit_code = generate_api_md(repo_root=repo_root, stdout=True)
assert exit_code == 0
captured = capsys.readouterr()
assert "# PraisonAI API Reference" in captured.out
def test_check_mode_passes_when_current(self, tmp_path):
"""Test that check mode passes when file is current."""
# Generate to a temp file
output_path = tmp_path / "api.md"
exit_code = generate_api_md(repo_root=repo_root, output_path=output_path)
assert exit_code == 0
# Check should pass
exit_code = generate_api_md(repo_root=repo_root, output_path=output_path, check=True)
assert exit_code == 0
def test_check_mode_fails_when_outdated(self, tmp_path):
"""Test that check mode fails when file is outdated."""
# Create an outdated file
output_path = tmp_path / "api.md"
output_path.write_text("# Old content")
# Check should fail
exit_code = generate_api_md(repo_root=repo_root, output_path=output_path, check=True)
assert exit_code == 1
def test_check_mode_fails_when_missing(self, tmp_path):
"""Test that check mode fails when file is missing."""
output_path = tmp_path / "api.md"
# Check should fail
exit_code = generate_api_md(repo_root=repo_root, output_path=output_path, check=True)
assert exit_code == 1
class TestLinkGeneration:
"""Tests for link generation in output."""
@pytest.fixture
def generator(self):
return ApiMdGenerator(repo_root=repo_root)
def test_links_are_relative(self, generator):
"""Test that links are relative to repo root."""
content = generator.generate()
# All href links should start with ./
import re
hrefs = re.findall(r'href="([^"]+)"', content)
for href in hrefs:
assert href.startswith("./"), f"Link should be relative: {href}"
def test_links_point_to_existing_files(self, generator):
"""Test that links point to existing files."""
generator.discover_all()
for name, info in list(generator.agents_symbols.items())[:10]:
if info.file_path:
# Remove ./ prefix
rel_path = info.file_path[2:] if info.file_path.startswith("./") else info.file_path
full_path = generator.repo_root / rel_path
assert full_path.exists(), f"File not found: {full_path}"
+4 -4
View File
@@ -4276,7 +4276,7 @@ requires-dist = [
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.1.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen-v4'", specifier = ">=0.1.0" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.1.0" },
{ name = "praisonaiagents", specifier = ">=0.10.10" },
{ name = "praisonaiagents", specifier = ">=0.11.1" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = "==0.2.29" },
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
@@ -4324,7 +4324,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.10.10"
version = "0.11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -4333,9 +4333,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/56/4f/d01c588827a74ddc58a8bf30cb7eb97671d210b7f5ef46f9eb2a7249f919/praisonaiagents-0.10.10.tar.gz", hash = "sha256:b6cfea93fef40b898575d0c1574134fdc9ec49754d2287e9ade90ba4381ca3d7", size = 962453, upload-time = "2026-01-10T08:57:35.82Z" }
sdist = { url = "https://files.pythonhosted.org/packages/40/42/8fe814f73b19a7decd12bca9e3c720a97906658bf993fb4c3063505531fb/praisonaiagents-0.11.1.tar.gz", hash = "sha256:b0f3720d7d5334fb9aaeb8b8e62cc1a20b9ce9c1cad489031414326479a3c23f", size = 963761, upload-time = "2026-01-10T14:23:57.185Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/07/fe/edd179c792d7f6143d3278c01daf37015fe4a2d98c5dc326fb309b263336/praisonaiagents-0.10.10-py3-none-any.whl", hash = "sha256:fa9bc6db1fb12ae25ea491db8e30eb98ac16c1e9798952cf617594c09999b826", size = 941797, upload-time = "2026-01-10T08:57:34.07Z" },
{ url = "https://files.pythonhosted.org/packages/0f/97/d7d465eec9443b80fd79daa57bbac7292b680a8702b5d6ef3fb82899bf7d/praisonaiagents-0.11.1-py3-none-any.whl", hash = "sha256:3792747bd2f449995bb4f77eb9086c1b9a88bc2f89414dd447e3d687449c389b", size = 943019, upload-time = "2026-01-10T14:23:54.691Z" },
]
[[package]]