PraisonAI 2.3.74__tar.gz → 2.3.76__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. {praisonai-2.3.74 → praisonai-2.3.76}/PKG-INFO +2 -2
  2. {praisonai-2.3.74 → praisonai-2.3.76}/PraisonAI.egg-info/PKG-INFO +2 -2
  3. {praisonai-2.3.74 → praisonai-2.3.76}/PraisonAI.egg-info/SOURCES.txt +1 -0
  4. {praisonai-2.3.74 → praisonai-2.3.76}/PraisonAI.egg-info/requires.txt +1 -1
  5. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/auto_memory.py +29 -20
  6. praisonai-2.3.76/praisonai/cli/features/profiling.py +350 -0
  7. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/main.py +80 -7
  8. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/deploy.py +1 -1
  9. praisonai-2.3.76/praisonai/profiler.py +1214 -0
  10. praisonai-2.3.76/praisonai/version.py +1 -0
  11. {praisonai-2.3.74 → praisonai-2.3.76}/pyproject.toml +1 -1
  12. praisonai-2.3.74/praisonai/profiler.py +0 -465
  13. praisonai-2.3.74/praisonai/version.py +0 -1
  14. {praisonai-2.3.74 → praisonai-2.3.76}/PraisonAI.egg-info/dependency_links.txt +0 -0
  15. {praisonai-2.3.74 → praisonai-2.3.76}/PraisonAI.egg-info/entry_points.txt +0 -0
  16. {praisonai-2.3.74 → praisonai-2.3.76}/PraisonAI.egg-info/top_level.txt +0 -0
  17. {praisonai-2.3.74 → praisonai-2.3.76}/README.md +0 -0
  18. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/__init__.py +0 -0
  19. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/__main__.py +0 -0
  20. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/agent_scheduler.py +0 -0
  21. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/agents_generator.py +0 -0
  22. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/api/call.py +0 -0
  23. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/auto.py +0 -0
  24. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/chainlit_ui.py +0 -0
  25. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/__init__.py +0 -0
  26. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/__init__.py +0 -0
  27. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/agent_scheduler.py +0 -0
  28. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/at_mentions.py +0 -0
  29. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/autonomy_mode.py +0 -0
  30. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/base.py +0 -0
  31. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/compare.py +0 -0
  32. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/cost_tracker.py +0 -0
  33. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/eval.py +0 -0
  34. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/external_agents.py +0 -0
  35. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/fast_context.py +0 -0
  36. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/flow_display.py +0 -0
  37. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/git_integration.py +0 -0
  38. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/guardrail.py +0 -0
  39. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/handoff.py +0 -0
  40. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/image.py +0 -0
  41. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/interactive_tui.py +0 -0
  42. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/knowledge.py +0 -0
  43. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/mcp.py +0 -0
  44. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/message_queue.py +0 -0
  45. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/metrics.py +0 -0
  46. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/n8n.py +0 -0
  47. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/repo_map.py +0 -0
  48. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/router.py +0 -0
  49. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/sandbox_executor.py +0 -0
  50. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/session.py +0 -0
  51. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/skills.py +0 -0
  52. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/slash_commands.py +0 -0
  53. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/telemetry.py +0 -0
  54. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/todo.py +0 -0
  55. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/tools.py +0 -0
  56. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/features/workflow.py +0 -0
  57. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/cli/schedule_cli.py +0 -0
  58. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/__init__.py +0 -0
  59. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/agent_tools.py +0 -0
  60. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/diff/__init__.py +0 -0
  61. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/diff/diff_strategy.py +0 -0
  62. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/tools/__init__.py +0 -0
  63. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/tools/apply_diff.py +0 -0
  64. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/tools/execute_command.py +0 -0
  65. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/tools/list_files.py +0 -0
  66. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/tools/read_file.py +0 -0
  67. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/tools/search_replace.py +0 -0
  68. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/tools/write_file.py +0 -0
  69. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/utils/__init__.py +0 -0
  70. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/utils/file_utils.py +0 -0
  71. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/utils/ignore_utils.py +0 -0
  72. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/code/utils/text_utils.py +0 -0
  73. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/inbuilt_tools/__init__.py +0 -0
  74. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
  75. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/inc/__init__.py +0 -0
  76. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/inc/config.py +0 -0
  77. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/inc/models.py +0 -0
  78. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/integrations/__init__.py +0 -0
  79. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/integrations/base.py +0 -0
  80. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/integrations/claude_code.py +0 -0
  81. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/integrations/codex_cli.py +0 -0
  82. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/integrations/cursor_cli.py +0 -0
  83. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/integrations/gemini_cli.py +0 -0
  84. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/scheduler/__init__.py +0 -0
  85. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/scheduler/agent_scheduler.py +0 -0
  86. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/scheduler/base.py +0 -0
  87. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/scheduler/daemon_manager.py +0 -0
  88. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/scheduler/state_manager.py +0 -0
  89. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/scheduler/yaml_loader.py +0 -0
  90. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/scheduler.py +0 -0
  91. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/setup/__init__.py +0 -0
  92. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/setup/build.py +0 -0
  93. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/setup/post_install.py +0 -0
  94. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/setup/setup_conda_env.py +0 -0
  95. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/setup.py +0 -0
  96. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/test.py +0 -0
  97. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/train.py +0 -0
  98. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/train_vision.py +0 -0
  99. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/agents.py +0 -0
  100. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/callbacks.py +0 -0
  101. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/chat.py +0 -0
  102. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/code.py +0 -0
  103. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/colab.py +0 -0
  104. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/colab_chainlit.py +0 -0
  105. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/components/aicoder.py +0 -0
  106. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/context.py +0 -0
  107. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/database_config.py +0 -0
  108. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/db.py +0 -0
  109. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/realtime.py +0 -0
  110. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/realtimeclient/__init__.py +0 -0
  111. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/realtimeclient/tools.py +0 -0
  112. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/ui/sql_alchemy.py +0 -0
  113. {praisonai-2.3.74 → praisonai-2.3.76}/praisonai/upload_vision.py +0 -0
  114. {praisonai-2.3.74 → praisonai-2.3.76}/setup.cfg +0 -0
  115. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test.py +0 -0
  116. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_agents_playbook.py +0 -0
  117. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_approval_system.py +0 -0
  118. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_basic.py +0 -0
  119. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_cli_features.py +0 -0
  120. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_custom_tools_fix.py +0 -0
  121. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_double_api_fix.py +0 -0
  122. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_n8n_integration.py +0 -0
  123. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_phase2_refactor.py +0 -0
  124. {praisonai-2.3.74 → praisonai-2.3.76}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PraisonAI
3
- Version: 2.3.74
3
+ Version: 2.3.76
4
4
  Summary: PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration.
5
5
  Author: Mervin Praison
6
6
  License: MIT
@@ -11,7 +11,7 @@ Description-Content-Type: text/markdown
11
11
  Requires-Dist: rich>=13.7
12
12
  Requires-Dist: markdown>=3.5
13
13
  Requires-Dist: pyparsing>=3.0.0
14
- Requires-Dist: praisonaiagents>=0.1.23
14
+ Requires-Dist: praisonaiagents>=0.1.24
15
15
  Requires-Dist: python-dotenv>=0.19.0
16
16
  Requires-Dist: instructor>=1.3.3
17
17
  Requires-Dist: PyYAML>=6.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PraisonAI
3
- Version: 2.3.74
3
+ Version: 2.3.76
4
4
  Summary: PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration.
5
5
  Author: Mervin Praison
6
6
  License: MIT
@@ -11,7 +11,7 @@ Description-Content-Type: text/markdown
11
11
  Requires-Dist: rich>=13.7
12
12
  Requires-Dist: markdown>=3.5
13
13
  Requires-Dist: pyparsing>=3.0.0
14
- Requires-Dist: praisonaiagents>=0.1.23
14
+ Requires-Dist: praisonaiagents>=0.1.24
15
15
  Requires-Dist: python-dotenv>=0.19.0
16
16
  Requires-Dist: instructor>=1.3.3
17
17
  Requires-Dist: PyYAML>=6.0
@@ -47,6 +47,7 @@ praisonai/cli/features/mcp.py
47
47
  praisonai/cli/features/message_queue.py
48
48
  praisonai/cli/features/metrics.py
49
49
  praisonai/cli/features/n8n.py
50
+ praisonai/cli/features/profiling.py
50
51
  praisonai/cli/features/repo_map.py
51
52
  praisonai/cli/features/router.py
52
53
  praisonai/cli/features/sandbox_executor.py
@@ -1,7 +1,7 @@
1
1
  rich>=13.7
2
2
  markdown>=3.5
3
3
  pyparsing>=3.0.0
4
- praisonaiagents>=0.1.23
4
+ praisonaiagents>=0.1.24
5
5
  python-dotenv>=0.19.0
6
6
  instructor>=1.3.3
7
7
  PyYAML>=6.0
@@ -45,8 +45,10 @@ class AutoMemoryHandler(FlagHandler):
45
45
  def _get_auto_memory(self, user_id: str = None):
46
46
  """Get AutoMemory instance lazily."""
47
47
  try:
48
- from praisonaiagents.memory import AutoMemory
49
- return AutoMemory(user_id=user_id or "default")
48
+ from praisonaiagents.memory import AutoMemory, FileMemory
49
+ # AutoMemory requires a FileMemory instance
50
+ memory = FileMemory(user_id=user_id or "default")
51
+ return AutoMemory(memory=memory)
50
52
  except ImportError:
51
53
  self.print_status(
52
54
  "AutoMemory requires praisonaiagents. Install with: pip install praisonaiagents",
@@ -71,33 +73,36 @@ class AutoMemoryHandler(FlagHandler):
71
73
  config['auto_memory_user_id'] = flag_value.get('user_id', 'default')
72
74
  return config
73
75
 
74
- def extract_memories(self, text: str, user_id: str = None) -> Dict[str, Any]:
76
+ def extract_memories(self, text: str, user_id: str = None, user_message: str = None) -> list:
75
77
  """
76
- Extract memories from text.
78
+ Extract and store memories from text.
77
79
 
78
80
  Args:
79
- text: Text to extract memories from
81
+ text: Assistant response text to extract memories from
80
82
  user_id: User ID for memory isolation
83
+ user_message: Original user message (for context)
81
84
 
82
85
  Returns:
83
- Dictionary of extracted memories
86
+ List of extracted memories
84
87
  """
85
88
  auto_memory = self._get_auto_memory(user_id)
86
89
  if not auto_memory:
87
- return {}
90
+ return []
88
91
 
89
92
  try:
90
- if hasattr(auto_memory, 'extract'):
91
- memories = auto_memory.extract(text)
92
- self.print_status(f"🧠 Extracted {len(memories)} memories", "success")
93
- return memories
94
- elif hasattr(auto_memory, 'process'):
95
- result = auto_memory.process(text)
96
- return result
93
+ # Use process_interaction which extracts AND stores memories
94
+ memories = auto_memory.process_interaction(
95
+ user_message=user_message or text,
96
+ assistant_response=text if user_message else None,
97
+ store=True # Store the memories
98
+ )
99
+ if memories:
100
+ self.print_status(f"🧠 Extracted and stored {len(memories)} memories", "success")
101
+ return memories
97
102
  except Exception as e:
98
103
  self.log(f"Memory extraction failed: {e}", "error")
99
104
 
100
- return {}
105
+ return []
101
106
 
102
107
  def store_memory(self, content: str, user_id: str = None, importance: float = 0.5) -> bool:
103
108
  """
@@ -127,13 +132,14 @@ class AutoMemoryHandler(FlagHandler):
127
132
  self.log(f"Memory storage failed: {e}", "error")
128
133
  return False
129
134
 
130
- def post_process_result(self, result: Any, flag_value: Any) -> Any:
135
+ def post_process_result(self, result: Any, flag_value: Any, user_message: str = None) -> Any:
131
136
  """
132
137
  Post-process result to extract and store memories.
133
138
 
134
139
  Args:
135
140
  result: Agent output
136
141
  flag_value: Boolean or dict with configuration
142
+ user_message: Original user message for context
137
143
 
138
144
  Returns:
139
145
  Original result (memories are stored)
@@ -144,15 +150,18 @@ class AutoMemoryHandler(FlagHandler):
144
150
  user_id = None
145
151
  if isinstance(flag_value, dict):
146
152
  user_id = flag_value.get('user_id')
153
+ user_message = flag_value.get('user_message', user_message)
147
154
 
148
- # Extract memories from result
155
+ # Extract and store memories from result
149
156
  text = str(result)
150
- memories = self.extract_memories(text, user_id)
157
+ memories = self.extract_memories(text, user_id, user_message=user_message)
151
158
 
152
159
  if memories:
153
160
  self.print_status("\n🧠 Auto-extracted Memories:", "info")
154
- for key, value in memories.items():
155
- self.print_status(f" • {key}: {value[:50]}...", "info")
161
+ for mem in memories:
162
+ mem_type = mem.get('type', 'unknown')
163
+ content = mem.get('content', str(mem))[:50]
164
+ self.print_status(f" • {mem_type}: {content}...", "info")
156
165
 
157
166
  return result
158
167
 
@@ -0,0 +1,350 @@
1
+ """
2
+ PraisonAI CLI Profiling Commands
3
+
4
+ Provides CLI commands for profiling agent execution.
5
+
6
+ Usage:
7
+ praisonai profile run "task" --output report.html
8
+ praisonai profile report --format json
9
+ praisonai profile benchmark --iterations 10
10
+ """
11
+
12
+ import time
13
+ import json
14
+ from typing import Optional, Dict, Any
15
+
16
+
17
+ class ProfilingHandler:
18
+ """
19
+ Handler for CLI profiling commands.
20
+
21
+ Features:
22
+ - Run tasks with profiling enabled
23
+ - Generate reports (console, JSON, HTML)
24
+ - Benchmark agent performance
25
+ - Export flamegraphs
26
+ """
27
+
28
+ def __init__(self):
29
+ self._profiler = None
30
+
31
+ @property
32
+ def profiler(self):
33
+ """Lazy load profiler."""
34
+ if self._profiler is None:
35
+ from praisonai.profiler import Profiler
36
+ self._profiler = Profiler
37
+ return self._profiler
38
+
39
+ def enable(self) -> None:
40
+ """Enable profiling."""
41
+ self.profiler.enable()
42
+
43
+ def disable(self) -> None:
44
+ """Disable profiling."""
45
+ self.profiler.disable()
46
+
47
+ def clear(self) -> None:
48
+ """Clear profiling data."""
49
+ self.profiler.clear()
50
+
51
+ def run_with_profiling(self, task: str, config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
52
+ """
53
+ Run a task with profiling enabled.
54
+
55
+ Args:
56
+ task: Task description or prompt
57
+ config: Optional configuration dict
58
+
59
+ Returns:
60
+ Dict with result and profiling data
61
+ """
62
+ self.profiler.enable()
63
+ self.profiler.clear()
64
+
65
+ result = None
66
+ error = None
67
+
68
+ try:
69
+ # Import and run PraisonAI
70
+ from praisonai import PraisonAI
71
+
72
+ with self.profiler.block("total_execution"):
73
+ with self.profiler.block("initialization"):
74
+ praison = PraisonAI(auto=task, **(config or {}))
75
+
76
+ with self.profiler.block("agent_run"):
77
+ result = praison.run()
78
+ except Exception as e:
79
+ error = str(e)
80
+ finally:
81
+ self.profiler.disable()
82
+
83
+ return {
84
+ 'result': result,
85
+ 'error': error,
86
+ 'summary': self.profiler.get_summary(),
87
+ 'statistics': self.profiler.get_statistics()
88
+ }
89
+
90
+ def report(self, format: str = "console", output: Optional[str] = None) -> str:
91
+ """
92
+ Generate profiling report.
93
+
94
+ Args:
95
+ format: Output format (console, json, html)
96
+ output: Optional file path to save report
97
+
98
+ Returns:
99
+ Report content as string
100
+ """
101
+ if format == "console":
102
+ content = self.profiler.report(output="console")
103
+ elif format == "json":
104
+ content = self.profiler.export_json()
105
+ elif format == "html":
106
+ content = self.profiler.export_html()
107
+ else:
108
+ raise ValueError(f"Unknown format: {format}")
109
+
110
+ if output:
111
+ with open(output, 'w') as f:
112
+ f.write(content)
113
+ print(f"Report saved to: {output}")
114
+
115
+ return content
116
+
117
+ def benchmark(self, task: str, iterations: int = 5,
118
+ warmup: int = 1, config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
119
+ """
120
+ Benchmark agent performance.
121
+
122
+ Args:
123
+ task: Task to benchmark
124
+ iterations: Number of iterations
125
+ warmup: Number of warmup runs
126
+ config: Optional configuration
127
+
128
+ Returns:
129
+ Benchmark results with statistics
130
+ """
131
+ from praisonai.profiler import Profiler
132
+
133
+ times = []
134
+ errors = []
135
+
136
+ # Warmup runs
137
+ for i in range(warmup):
138
+ print(f"Warmup {i + 1}/{warmup}...")
139
+ try:
140
+ self.run_with_profiling(task, config)
141
+ except Exception as e:
142
+ print(f"Warmup error: {e}")
143
+
144
+ # Benchmark runs
145
+ for i in range(iterations):
146
+ print(f"Iteration {i + 1}/{iterations}...")
147
+ Profiler.clear()
148
+
149
+ start = time.perf_counter()
150
+ try:
151
+ self.run_with_profiling(task, config)
152
+ duration_ms = (time.perf_counter() - start) * 1000
153
+ times.append(duration_ms)
154
+ except Exception as e:
155
+ errors.append(str(e))
156
+ duration_ms = (time.perf_counter() - start) * 1000
157
+ times.append(duration_ms)
158
+
159
+ # Calculate statistics
160
+ if times:
161
+ sorted_times = sorted(times)
162
+ n = len(sorted_times)
163
+
164
+ results = {
165
+ 'iterations': iterations,
166
+ 'successful': iterations - len(errors),
167
+ 'failed': len(errors),
168
+ 'times_ms': times,
169
+ 'mean_ms': sum(times) / n,
170
+ 'min_ms': min(times),
171
+ 'max_ms': max(times),
172
+ 'p50_ms': sorted_times[n // 2],
173
+ 'p95_ms': sorted_times[int(n * 0.95)] if n > 1 else sorted_times[-1],
174
+ 'p99_ms': sorted_times[int(n * 0.99)] if n > 1 else sorted_times[-1],
175
+ 'errors': errors
176
+ }
177
+ else:
178
+ results = {
179
+ 'iterations': iterations,
180
+ 'successful': 0,
181
+ 'failed': len(errors),
182
+ 'errors': errors
183
+ }
184
+
185
+ return results
186
+
187
+ def export_flamegraph(self, filepath: str) -> None:
188
+ """Export flamegraph to SVG file."""
189
+ self.profiler.export_flamegraph(filepath)
190
+ print(f"Flamegraph saved to: {filepath}")
191
+
192
+ def get_summary(self) -> Dict[str, Any]:
193
+ """Get profiling summary."""
194
+ return self.profiler.get_summary()
195
+
196
+ def get_statistics(self, category: Optional[str] = None) -> Dict[str, float]:
197
+ """Get profiling statistics."""
198
+ return self.profiler.get_statistics(category)
199
+
200
+ def print_summary(self) -> None:
201
+ """Print profiling summary to console."""
202
+ summary = self.get_summary()
203
+ stats = self.get_statistics()
204
+
205
+ print("\n" + "=" * 60)
206
+ print("PROFILING SUMMARY")
207
+ print("=" * 60)
208
+
209
+ print(f"\nTotal Time: {summary['total_time_ms']:.2f}ms")
210
+ print(f"Operations: {summary['timing_count']}")
211
+ print(f"Imports: {summary['import_count']}")
212
+ print(f"Flow Steps: {summary['flow_steps']}")
213
+
214
+ print("\nStatistics:")
215
+ print(f" P50 (Median): {stats['p50']:.2f}ms")
216
+ print(f" P95: {stats['p95']:.2f}ms")
217
+ print(f" P99: {stats['p99']:.2f}ms")
218
+ print(f" Mean: {stats['mean']:.2f}ms")
219
+ print(f" Std Dev: {stats['std_dev']:.2f}ms")
220
+
221
+ print("\nSlowest Operations:")
222
+ for name, duration in summary['slowest_operations'][:5]:
223
+ print(f" {name}: {duration:.2f}ms")
224
+
225
+ print("=" * 60)
226
+
227
+
228
+ def handle_profile_command(args) -> int:
229
+ """
230
+ Handle profile CLI command.
231
+
232
+ Args:
233
+ args: Parsed command line arguments
234
+
235
+ Returns:
236
+ Exit code (0 for success)
237
+ """
238
+ handler = ProfilingHandler()
239
+
240
+ subcommand = getattr(args, 'profile_command', None)
241
+
242
+ if subcommand == 'run':
243
+ task = getattr(args, 'task', '')
244
+ output = getattr(args, 'output', None)
245
+ format_type = getattr(args, 'format', 'console')
246
+
247
+ print(f"Running with profiling: {task[:50]}...")
248
+ result = handler.run_with_profiling(task)
249
+
250
+ if result['error']:
251
+ print(f"Error: {result['error']}")
252
+
253
+ handler.report(format=format_type, output=output)
254
+ return 0 if not result['error'] else 1
255
+
256
+ elif subcommand == 'report':
257
+ output = getattr(args, 'output', None)
258
+ format_type = getattr(args, 'format', 'console')
259
+
260
+ handler.report(format=format_type, output=output)
261
+ return 0
262
+
263
+ elif subcommand == 'benchmark':
264
+ task = getattr(args, 'task', '')
265
+ iterations = getattr(args, 'iterations', 5)
266
+ warmup = getattr(args, 'warmup', 1)
267
+ output = getattr(args, 'output', None)
268
+
269
+ print(f"Benchmarking: {task[:50]}...")
270
+ results = handler.benchmark(task, iterations=iterations, warmup=warmup)
271
+
272
+ print("\n" + "=" * 60)
273
+ print("BENCHMARK RESULTS")
274
+ print("=" * 60)
275
+ print(f"Iterations: {results['iterations']}")
276
+ print(f"Successful: {results['successful']}")
277
+ print(f"Failed: {results['failed']}")
278
+
279
+ if 'mean_ms' in results:
280
+ print("\nTiming:")
281
+ print(f" Mean: {results['mean_ms']:.2f}ms")
282
+ print(f" Min: {results['min_ms']:.2f}ms")
283
+ print(f" Max: {results['max_ms']:.2f}ms")
284
+ print(f" P50: {results['p50_ms']:.2f}ms")
285
+ print(f" P95: {results['p95_ms']:.2f}ms")
286
+
287
+ if output:
288
+ with open(output, 'w') as f:
289
+ json.dump(results, f, indent=2)
290
+ print(f"\nResults saved to: {output}")
291
+
292
+ return 0
293
+
294
+ elif subcommand == 'flamegraph':
295
+ output = getattr(args, 'output', 'profile.svg')
296
+ handler.export_flamegraph(output)
297
+ return 0
298
+
299
+ elif subcommand == 'summary':
300
+ handler.print_summary()
301
+ return 0
302
+
303
+ else:
304
+ print("Usage: praisonai profile <run|report|benchmark|flamegraph|summary>")
305
+ return 1
306
+
307
+
308
+ def add_profile_parser(subparsers) -> None:
309
+ """Add profile subcommand to argument parser."""
310
+ profile_parser = subparsers.add_parser(
311
+ 'profile',
312
+ help='Profile agent execution'
313
+ )
314
+
315
+ profile_subparsers = profile_parser.add_subparsers(dest='profile_command')
316
+
317
+ # Run with profiling
318
+ run_parser = profile_subparsers.add_parser('run', help='Run task with profiling')
319
+ run_parser.add_argument('task', help='Task to run')
320
+ run_parser.add_argument('--output', '-o', help='Output file path')
321
+ run_parser.add_argument('--format', '-f', choices=['console', 'json', 'html'],
322
+ default='console', help='Output format')
323
+
324
+ # Generate report
325
+ report_parser = profile_subparsers.add_parser('report', help='Generate profiling report')
326
+ report_parser.add_argument('--output', '-o', help='Output file path')
327
+ report_parser.add_argument('--format', '-f', choices=['console', 'json', 'html'],
328
+ default='console', help='Output format')
329
+
330
+ # Benchmark
331
+ bench_parser = profile_subparsers.add_parser('benchmark', help='Benchmark agent performance')
332
+ bench_parser.add_argument('task', help='Task to benchmark')
333
+ bench_parser.add_argument('--iterations', '-n', type=int, default=5, help='Number of iterations')
334
+ bench_parser.add_argument('--warmup', '-w', type=int, default=1, help='Warmup runs')
335
+ bench_parser.add_argument('--output', '-o', help='Output file for results')
336
+
337
+ # Flamegraph
338
+ flame_parser = profile_subparsers.add_parser('flamegraph', help='Export flamegraph')
339
+ flame_parser.add_argument('--output', '-o', default='profile.svg', help='Output SVG file')
340
+
341
+ # Summary
342
+ profile_subparsers.add_parser('summary', help='Print profiling summary') # noqa: F841
343
+
344
+
345
+ # Exports
346
+ __all__ = [
347
+ 'ProfilingHandler',
348
+ 'handle_profile_command',
349
+ 'add_profile_parser',
350
+ ]
@@ -5,6 +5,42 @@ import argparse
5
5
  import warnings
6
6
  import os
7
7
 
8
+ # Suppress Pydantic serialization warnings from LiteLLM BEFORE any imports
9
+ # These warnings occur when LiteLLM's response objects have field mismatches
10
+ # Using both filterwarnings AND patching warnings.warn for complete suppression
11
+
12
+ warnings.filterwarnings("ignore", message=".*Pydantic serializer warnings.*")
13
+ warnings.filterwarnings("ignore", message=".*PydanticSerializationUnexpectedValue.*")
14
+ warnings.filterwarnings("ignore", message=".*Expected \\d+ fields but got.*")
15
+ warnings.filterwarnings("ignore", message=".*Expected `StreamingChoices`.*")
16
+ warnings.filterwarnings("ignore", message=".*Expected `Message`.*")
17
+ warnings.filterwarnings("ignore", message=".*serialized value may not be as expected.*")
18
+ warnings.filterwarnings("ignore", category=UserWarning, module="pydantic.*")
19
+
20
+ # Patch warnings.showwarning to intercept ALL warnings including those from crewai's patched warn
21
+ # This is the final output function that actually displays warnings
22
+ _SUPPRESSED_PATTERNS = [
23
+ "Pydantic serializer warnings",
24
+ "PydanticSerializationUnexpectedValue",
25
+ "Expected", # Catches "Expected N fields but got M"
26
+ "StreamingChoices",
27
+ "serialized value may not be as expected",
28
+ "duckduckgo_search", # Suppress duckduckgo rename warning
29
+ ]
30
+
31
+ _original_showwarning = warnings.showwarning
32
+
33
+ def _patched_showwarning(message, category, filename, lineno, file=None, line=None):
34
+ msg_str = str(message)
35
+ for pattern in _SUPPRESSED_PATTERNS:
36
+ if pattern in msg_str:
37
+ return
38
+ if category is UserWarning and "pydantic" in filename.lower():
39
+ return
40
+ _original_showwarning(message, category, filename, lineno, file, line)
41
+
42
+ warnings.showwarning = _patched_showwarning
43
+
8
44
  # Suppress crewai RuntimeWarning about module loading order (only in non-debug mode)
9
45
  # This warning is harmless and occurs when running as `python -m praisonai.cli.main`
10
46
  if os.environ.get('LOGLEVEL', 'INFO').upper() != 'DEBUG':
@@ -1342,12 +1378,47 @@ class PraisonAI:
1342
1378
 
1343
1379
  console.print(table)
1344
1380
 
1345
- # Show recent memories
1381
+ # Show recent short-term memories
1346
1382
  print("\n[bold]Recent Short-term Memories:[/bold]")
1347
1383
  short_term = memory.get_short_term(limit=5)
1348
- for i, item in enumerate(short_term, 1):
1349
- content = item.get('content', str(item))[:100]
1350
- print(f" {i}. {content}")
1384
+ if short_term:
1385
+ for i, item in enumerate(short_term, 1):
1386
+ content = item.get('content', str(item))[:100]
1387
+ print(f" {i}. {content}")
1388
+ else:
1389
+ print(" [dim]No short-term memories[/dim]")
1390
+
1391
+ # Show long-term memories
1392
+ print("\n[bold]Long-term Memories:[/bold]")
1393
+ long_term = memory.get_long_term(limit=10)
1394
+ if long_term:
1395
+ for i, item in enumerate(long_term, 1):
1396
+ # Handle both dict and MemoryItem objects
1397
+ if hasattr(item, 'content'):
1398
+ content = str(item.content)[:100]
1399
+ importance = getattr(item, 'importance', 0)
1400
+ else:
1401
+ content = item.get('content', str(item))[:100]
1402
+ importance = item.get('importance', 0)
1403
+ print(f" {i}. [{importance:.1f}] {content}")
1404
+ else:
1405
+ print(" [dim]No long-term memories[/dim]")
1406
+
1407
+ # Show entities
1408
+ print("\n[bold]Entities:[/bold]")
1409
+ entities = memory.get_all_entities()
1410
+ if entities:
1411
+ for entity in entities[:10]:
1412
+ # Handle both dict and Entity objects
1413
+ if hasattr(entity, 'name'):
1414
+ name = entity.name
1415
+ entity_type = getattr(entity, 'entity_type', 'unknown')
1416
+ else:
1417
+ name = entity.get('name', 'Unknown')
1418
+ entity_type = entity.get('entity_type', 'unknown')
1419
+ print(f" • {name} ({entity_type})")
1420
+ else:
1421
+ print(" [dim]No entities[/dim]")
1351
1422
 
1352
1423
  elif action == 'add':
1353
1424
  if not action_args:
@@ -3118,9 +3189,8 @@ Provide ONLY the commit message, no explanations."""
3118
3189
  telemetry = TelemetryHandler(verbose=getattr(self.args, 'verbose', False))
3119
3190
  telemetry.enable()
3120
3191
 
3121
- # Auto Memory - Automatic memory extraction
3192
+ # Auto Memory - Automatic memory extraction (handled post-processing, not as Agent param)
3122
3193
  if getattr(self.args, 'auto_memory', False):
3123
- agent_config["auto_memory"] = True
3124
3194
  print("[bold cyan]Auto Memory enabled - will extract and store memories[/bold cyan]")
3125
3195
 
3126
3196
  # MCP - Model Context Protocol tools
@@ -3266,7 +3336,10 @@ Provide ONLY the commit message, no explanations."""
3266
3336
  if hasattr(self, 'args') and getattr(self.args, 'auto_memory', False):
3267
3337
  from .features.auto_memory import AutoMemoryHandler
3268
3338
  auto_mem = AutoMemoryHandler(verbose=getattr(self.args, 'verbose', False))
3269
- auto_mem.post_process_result(result, {'user_id': getattr(self.args, 'user_id', None)})
3339
+ auto_mem.post_process_result(
3340
+ result,
3341
+ {'user_id': getattr(self.args, 'user_id', None), 'user_message': prompt}
3342
+ )
3270
3343
 
3271
3344
  # Todo - Generate todo list from response
3272
3345
  if hasattr(self, 'args') and getattr(self.args, 'todo', False):
@@ -57,7 +57,7 @@ class CloudDeployer:
57
57
  file.write("FROM python:3.11-slim\n")
58
58
  file.write("WORKDIR /app\n")
59
59
  file.write("COPY . .\n")
60
- file.write("RUN pip install flask praisonai==2.3.74 gunicorn markdown\n")
60
+ file.write("RUN pip install flask praisonai==2.3.76 gunicorn markdown\n")
61
61
  file.write("EXPOSE 8080\n")
62
62
  file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
63
63