PraisonAI 3.0.0__py3-none-any.whl

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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,558 @@
1
+ """
2
+ Core profiler implementation for PraisonAI CLI.
3
+
4
+ Provides cProfile-based profiling with detailed per-file/per-function timing,
5
+ call graphs, and import time analysis.
6
+ """
7
+
8
+ import cProfile
9
+ import io
10
+ import json
11
+ import os
12
+ import pstats
13
+ import re
14
+ import subprocess
15
+ import sys
16
+ import time
17
+ from dataclasses import dataclass, field
18
+ from datetime import datetime
19
+ from pathlib import Path
20
+ from typing import Any, Dict, List, Optional, Tuple
21
+
22
+
23
+ # Patterns for secrets to redact
24
+ SECRET_PATTERNS = [
25
+ r'sk-[a-zA-Z0-9]{20,}', # OpenAI keys
26
+ r'sk-ant-[a-zA-Z0-9-]{20,}', # Anthropic keys
27
+ r'AIza[a-zA-Z0-9_-]{35}', # Google API keys
28
+ r'tvly-[a-zA-Z0-9-]{20,}', # Tavily keys
29
+ r'[a-zA-Z0-9_-]{32,}', # Generic long tokens (be careful)
30
+ ]
31
+
32
+
33
+ def redact_secrets(text: str) -> str:
34
+ """Redact potential secrets from text."""
35
+ result = text
36
+ for pattern in SECRET_PATTERNS[:4]: # Skip generic pattern for safety
37
+ result = re.sub(pattern, '[REDACTED]', result)
38
+ return result
39
+
40
+
41
+ @dataclass
42
+ class ProfilerConfig:
43
+ """Configuration for profiler."""
44
+ deep: bool = False # Enable deep call tracing
45
+ limit: int = 30 # Top N functions to show
46
+ sort_by: str = "cumulative" # cumulative or tottime
47
+ show_files: bool = False # Group by file
48
+ show_callers: bool = False # Show callers
49
+ show_callees: bool = False # Show callees
50
+ importtime: bool = False # Show import timing
51
+ first_token: bool = False # Track time to first token
52
+ save_path: Optional[str] = None # Path to save artifacts
53
+ output_format: str = "text" # text or json
54
+ stream: bool = False # Streaming mode
55
+
56
+
57
+ @dataclass
58
+ class TimingBreakdown:
59
+ """Timing breakdown for profiling."""
60
+ cli_parse_ms: float = 0.0
61
+ imports_ms: float = 0.0
62
+ agent_construction_ms: float = 0.0
63
+ model_init_ms: float = 0.0
64
+ first_token_ms: float = 0.0
65
+ total_run_ms: float = 0.0
66
+
67
+
68
+ @dataclass
69
+ class FunctionStats:
70
+ """Statistics for a single function."""
71
+ name: str
72
+ filename: str
73
+ lineno: int
74
+ calls: int
75
+ tottime: float # Time in function excluding subcalls
76
+ cumtime: float # Time in function including subcalls
77
+
78
+ def to_dict(self) -> Dict[str, Any]:
79
+ return {
80
+ "name": self.name,
81
+ "filename": self.filename,
82
+ "lineno": self.lineno,
83
+ "calls": self.calls,
84
+ "tottime_ms": self.tottime * 1000,
85
+ "cumtime_ms": self.cumtime * 1000,
86
+ }
87
+
88
+
89
+ @dataclass
90
+ class FileStats:
91
+ """Statistics aggregated by file."""
92
+ filename: str
93
+ total_time: float
94
+ functions: List[FunctionStats] = field(default_factory=list)
95
+
96
+ def to_dict(self) -> Dict[str, Any]:
97
+ return {
98
+ "filename": self.filename,
99
+ "total_time_ms": self.total_time * 1000,
100
+ "function_count": len(self.functions),
101
+ }
102
+
103
+
104
+ @dataclass
105
+ class ProfilerResult:
106
+ """Result from a profiling run."""
107
+ prompt: str
108
+ response: str
109
+ timing: TimingBreakdown
110
+ function_stats: List[FunctionStats]
111
+ file_stats: List[FileStats]
112
+ callers: Dict[str, List[str]] = field(default_factory=dict)
113
+ callees: Dict[str, List[str]] = field(default_factory=dict)
114
+ import_times: List[Tuple[str, float]] = field(default_factory=list)
115
+ metadata: Dict[str, Any] = field(default_factory=dict)
116
+ timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat() + "Z")
117
+
118
+ def to_dict(self) -> Dict[str, Any]:
119
+ return {
120
+ "timestamp": self.timestamp,
121
+ "metadata": self.metadata,
122
+ "prompt": self.prompt[:200] + "..." if len(self.prompt) > 200 else self.prompt,
123
+ "response_preview": self.response[:200] + "..." if len(self.response) > 200 else self.response,
124
+ "timing": {
125
+ "cli_parse_ms": self.timing.cli_parse_ms,
126
+ "imports_ms": self.timing.imports_ms,
127
+ "agent_construction_ms": self.timing.agent_construction_ms,
128
+ "model_init_ms": self.timing.model_init_ms,
129
+ "first_token_ms": self.timing.first_token_ms,
130
+ "total_run_ms": self.timing.total_run_ms,
131
+ },
132
+ "top_functions": [f.to_dict() for f in self.function_stats[:20]],
133
+ "top_files": [f.to_dict() for f in self.file_stats[:10]],
134
+ }
135
+
136
+
137
+ class QueryProfiler:
138
+ """
139
+ Profiler for query execution.
140
+
141
+ Uses cProfile for function-level profiling and optional sys.setprofile
142
+ for deep call tracing.
143
+ """
144
+
145
+ def __init__(self, config: Optional[ProfilerConfig] = None):
146
+ self.config = config or ProfilerConfig()
147
+ self.profiler = cProfile.Profile()
148
+ self._first_token_time: Optional[float] = None
149
+ self._start_time: float = 0.0
150
+ self._deep_trace_data: Dict[str, Dict] = {}
151
+
152
+ def _on_first_token(self):
153
+ """Called when first token is received (for streaming)."""
154
+ if self._first_token_time is None:
155
+ self._first_token_time = time.perf_counter()
156
+
157
+ def _deep_trace_callback(self, frame, event, arg):
158
+ """Callback for sys.setprofile deep tracing."""
159
+ if event not in ('call', 'return'):
160
+ return
161
+
162
+ filename = frame.f_code.co_filename
163
+ funcname = frame.f_code.co_name
164
+ lineno = frame.f_lineno
165
+
166
+ key = f"{filename}:{funcname}:{lineno}"
167
+
168
+ if key not in self._deep_trace_data:
169
+ self._deep_trace_data[key] = {
170
+ 'filename': filename,
171
+ 'funcname': funcname,
172
+ 'lineno': lineno,
173
+ 'calls': 0,
174
+ 'total_time': 0.0,
175
+ 'start_times': [],
176
+ }
177
+
178
+ if event == 'call':
179
+ self._deep_trace_data[key]['calls'] += 1
180
+ self._deep_trace_data[key]['start_times'].append(time.perf_counter())
181
+ elif event == 'return' and self._deep_trace_data[key]['start_times']:
182
+ start = self._deep_trace_data[key]['start_times'].pop()
183
+ self._deep_trace_data[key]['total_time'] += time.perf_counter() - start
184
+
185
+ def profile_query(
186
+ self,
187
+ prompt: str,
188
+ model: Optional[str] = None,
189
+ stream: bool = False,
190
+ ) -> ProfilerResult:
191
+ """
192
+ Profile a query execution.
193
+
194
+ Args:
195
+ prompt: The prompt to execute
196
+ model: Optional model to use
197
+ stream: Whether to use streaming mode
198
+
199
+ Returns:
200
+ ProfilerResult with detailed timing and statistics
201
+ """
202
+ timing = TimingBreakdown()
203
+ response = ""
204
+
205
+ # Metadata
206
+ metadata = self._collect_metadata(model)
207
+
208
+ # Time CLI parsing (simulated - actual parsing happens before this)
209
+ cli_start = time.perf_counter()
210
+ timing.cli_parse_ms = (time.perf_counter() - cli_start) * 1000
211
+
212
+ # Time imports
213
+ import_start = time.perf_counter()
214
+ try:
215
+ from praisonaiagents import Agent
216
+ except ImportError:
217
+ raise ImportError("praisonaiagents not installed")
218
+ timing.imports_ms = (time.perf_counter() - import_start) * 1000
219
+
220
+ # Time agent construction
221
+ construct_start = time.perf_counter()
222
+ agent_config = {
223
+ "name": "ProfilerAgent",
224
+ "role": "Assistant",
225
+ "goal": "Complete the task",
226
+ "verbose": False,
227
+ }
228
+ if model:
229
+ agent_config["llm"] = model
230
+
231
+ agent = Agent(**agent_config)
232
+ timing.agent_construction_ms = (time.perf_counter() - construct_start) * 1000
233
+
234
+ # Time model initialization (first call)
235
+ model_init_start = time.perf_counter()
236
+ timing.model_init_ms = (time.perf_counter() - model_init_start) * 1000
237
+
238
+ # Profile the actual execution
239
+ self._start_time = time.perf_counter()
240
+ self._first_token_time = None
241
+
242
+ if self.config.deep:
243
+ sys.setprofile(self._deep_trace_callback)
244
+
245
+ self.profiler.enable()
246
+
247
+ try:
248
+ if stream and hasattr(agent, '_start_stream'):
249
+ # Streaming mode with first token tracking
250
+ chunks = []
251
+ for chunk in agent._start_stream(prompt):
252
+ if not chunks:
253
+ self._on_first_token()
254
+ chunks.append(chunk)
255
+ response = ''.join(chunks)
256
+ else:
257
+ # Non-streaming mode
258
+ response = agent.start(prompt)
259
+ if response is None:
260
+ response = ""
261
+ finally:
262
+ self.profiler.disable()
263
+ if self.config.deep:
264
+ sys.setprofile(None)
265
+
266
+ end_time = time.perf_counter()
267
+ timing.total_run_ms = (end_time - self._start_time) * 1000
268
+
269
+ if self._first_token_time:
270
+ timing.first_token_ms = (self._first_token_time - self._start_time) * 1000
271
+
272
+ # Extract statistics
273
+ function_stats = self._extract_function_stats()
274
+ file_stats = self._extract_file_stats(function_stats)
275
+ callers, callees = self._extract_call_graph()
276
+
277
+ # Get import times if requested
278
+ import_times = []
279
+ if self.config.importtime:
280
+ import_times = self._get_import_times()
281
+
282
+ return ProfilerResult(
283
+ prompt=prompt,
284
+ response=str(response),
285
+ timing=timing,
286
+ function_stats=function_stats,
287
+ file_stats=file_stats,
288
+ callers=callers if self.config.show_callers else {},
289
+ callees=callees if self.config.show_callees else {},
290
+ import_times=import_times,
291
+ metadata=metadata,
292
+ )
293
+
294
+ def _collect_metadata(self, model: Optional[str]) -> Dict[str, Any]:
295
+ """Collect system metadata."""
296
+ import platform
297
+ try:
298
+ from praisonai.version import __version__ as praisonai_version
299
+ except ImportError:
300
+ praisonai_version = "unknown"
301
+
302
+ return {
303
+ "python_version": platform.python_version(),
304
+ "platform": platform.platform(),
305
+ "praisonai_version": praisonai_version,
306
+ "model": model or "default",
307
+ "timestamp": datetime.utcnow().isoformat() + "Z",
308
+ }
309
+
310
+ def _extract_function_stats(self) -> List[FunctionStats]:
311
+ """Extract function statistics from profiler."""
312
+ stats_stream = io.StringIO()
313
+ stats = pstats.Stats(self.profiler, stream=stats_stream)
314
+
315
+ # Sort by configured key
316
+ sort_key = pstats.SortKey.CUMULATIVE if self.config.sort_by == "cumulative" else pstats.SortKey.TIME
317
+ stats.sort_stats(sort_key)
318
+
319
+ function_stats = []
320
+ for (filename, lineno, funcname), (cc, nc, tt, ct, callers) in stats.stats.items():
321
+ function_stats.append(FunctionStats(
322
+ name=funcname,
323
+ filename=filename,
324
+ lineno=lineno,
325
+ calls=nc,
326
+ tottime=tt,
327
+ cumtime=ct,
328
+ ))
329
+
330
+ # Sort and limit
331
+ if self.config.sort_by == "cumulative":
332
+ function_stats.sort(key=lambda x: x.cumtime, reverse=True)
333
+ else:
334
+ function_stats.sort(key=lambda x: x.tottime, reverse=True)
335
+
336
+ return function_stats[:self.config.limit]
337
+
338
+ def _extract_file_stats(self, function_stats: List[FunctionStats]) -> List[FileStats]:
339
+ """Aggregate statistics by file."""
340
+ file_map: Dict[str, FileStats] = {}
341
+
342
+ for func in function_stats:
343
+ if func.filename not in file_map:
344
+ file_map[func.filename] = FileStats(
345
+ filename=func.filename,
346
+ total_time=0.0,
347
+ functions=[],
348
+ )
349
+ file_map[func.filename].total_time += func.cumtime
350
+ file_map[func.filename].functions.append(func)
351
+
352
+ file_stats = list(file_map.values())
353
+ file_stats.sort(key=lambda x: x.total_time, reverse=True)
354
+
355
+ return file_stats[:self.config.limit]
356
+
357
+ def _extract_call_graph(self) -> Tuple[Dict[str, List[str]], Dict[str, List[str]]]:
358
+ """Extract caller/callee relationships."""
359
+ callers: Dict[str, List[str]] = {}
360
+ callees: Dict[str, List[str]] = {}
361
+
362
+ stats = pstats.Stats(self.profiler)
363
+
364
+ for (filename, lineno, funcname), (cc, nc, tt, ct, caller_dict) in stats.stats.items():
365
+ func_key = f"{funcname} ({os.path.basename(filename)}:{lineno})"
366
+
367
+ if caller_dict:
368
+ callers[func_key] = []
369
+ for (caller_file, caller_line, caller_name), _ in caller_dict.items():
370
+ caller_key = f"{caller_name} ({os.path.basename(caller_file)}:{caller_line})"
371
+ callers[func_key].append(caller_key)
372
+
373
+ if caller_key not in callees:
374
+ callees[caller_key] = []
375
+ callees[caller_key].append(func_key)
376
+
377
+ return callers, callees
378
+
379
+ def _get_import_times(self) -> List[Tuple[str, float]]:
380
+ """Get import timing by running subprocess with -X importtime."""
381
+ try:
382
+ result = subprocess.run(
383
+ [sys.executable, "-X", "importtime", "-c", "import praisonaiagents"],
384
+ capture_output=True,
385
+ text=True,
386
+ timeout=30,
387
+ )
388
+
389
+ import_times = []
390
+ for line in result.stderr.split('\n'):
391
+ if 'import time:' in line:
392
+ # Parse: "import time: 123 | 456 | module_name"
393
+ match = re.search(r'import time:\s+(\d+)\s+\|\s+(\d+)\s+\|\s+(.+)', line)
394
+ if match:
395
+ self_time = int(match.group(1))
396
+ cumulative = int(match.group(2))
397
+ module = match.group(3).strip()
398
+ import_times.append((module, cumulative / 1000000)) # Convert to seconds
399
+
400
+ # Sort by time descending
401
+ import_times.sort(key=lambda x: x[1], reverse=True)
402
+ return import_times[:20] # Top 20
403
+
404
+ except Exception:
405
+ return []
406
+
407
+ def save_artifacts(self, result: ProfilerResult, base_path: str):
408
+ """Save profiling artifacts to files."""
409
+ base = Path(base_path)
410
+ base.parent.mkdir(parents=True, exist_ok=True)
411
+
412
+ # Save .prof binary
413
+ prof_path = str(base) + ".prof"
414
+ self.profiler.dump_stats(prof_path)
415
+
416
+ # Save .txt report
417
+ txt_path = str(base) + ".txt"
418
+ with open(txt_path, 'w') as f:
419
+ f.write(format_profile_report(result, self.config))
420
+
421
+ # Save .json if requested
422
+ if self.config.output_format == "json":
423
+ json_path = str(base) + ".json"
424
+ with open(json_path, 'w') as f:
425
+ json.dump(result.to_dict(), f, indent=2, default=str)
426
+
427
+ return prof_path, txt_path
428
+
429
+
430
+ def run_profiled_query(
431
+ prompt: str,
432
+ config: Optional[ProfilerConfig] = None,
433
+ model: Optional[str] = None,
434
+ ) -> ProfilerResult:
435
+ """
436
+ Run a profiled query.
437
+
438
+ Args:
439
+ prompt: The prompt to execute
440
+ config: Profiler configuration
441
+ model: Optional model to use
442
+
443
+ Returns:
444
+ ProfilerResult with detailed timing and statistics
445
+ """
446
+ profiler = QueryProfiler(config)
447
+ return profiler.profile_query(prompt, model=model, stream=config.stream if config else False)
448
+
449
+
450
+ def format_profile_report(result: ProfilerResult, config: Optional[ProfilerConfig] = None) -> str:
451
+ """
452
+ Format a profile result as a text report.
453
+
454
+ Args:
455
+ result: ProfilerResult to format
456
+ config: Optional profiler config
457
+
458
+ Returns:
459
+ Formatted text report
460
+ """
461
+ config = config or ProfilerConfig()
462
+ lines = []
463
+
464
+ # Header
465
+ lines.append("=" * 70)
466
+ lines.append("PraisonAI Profile Report")
467
+ lines.append("=" * 70)
468
+ lines.append("")
469
+
470
+ # Metadata
471
+ lines.append("## System Information")
472
+ lines.append(f" Timestamp: {result.timestamp}")
473
+ lines.append(f" Python Version: {result.metadata.get('python_version', 'N/A')}")
474
+ lines.append(f" Platform: {result.metadata.get('platform', 'N/A')}")
475
+ lines.append(f" PraisonAI: {result.metadata.get('praisonai_version', 'N/A')}")
476
+ lines.append(f" Model: {result.metadata.get('model', 'N/A')}")
477
+ lines.append("")
478
+
479
+ # Timing breakdown
480
+ lines.append("## Timing Breakdown")
481
+ lines.append(f" CLI Parse: {result.timing.cli_parse_ms:>10.2f} ms")
482
+ lines.append(f" Imports: {result.timing.imports_ms:>10.2f} ms")
483
+ lines.append(f" Agent Construct: {result.timing.agent_construction_ms:>10.2f} ms")
484
+ lines.append(f" Model Init: {result.timing.model_init_ms:>10.2f} ms")
485
+ if result.timing.first_token_ms > 0:
486
+ lines.append(f" First Token: {result.timing.first_token_ms:>10.2f} ms")
487
+ lines.append(f" Total Run: {result.timing.total_run_ms:>10.2f} ms")
488
+ lines.append("")
489
+
490
+ # Per-file timing (if requested)
491
+ if config.show_files and result.file_stats:
492
+ lines.append("## Per-File Timing (Top Files)")
493
+ lines.append("-" * 70)
494
+ lines.append(f"{'File':<50} {'Time (ms)':>15}")
495
+ lines.append("-" * 70)
496
+ for fs in result.file_stats[:15]:
497
+ filename = os.path.basename(fs.filename)
498
+ if len(filename) > 48:
499
+ filename = "..." + filename[-45:]
500
+ lines.append(f"{filename:<50} {fs.total_time * 1000:>15.2f}")
501
+ lines.append("")
502
+
503
+ # Per-function timing
504
+ lines.append("## Per-Function Timing (Top Functions)")
505
+ lines.append("-" * 70)
506
+ sort_label = "Cumulative" if config.sort_by == "cumulative" else "Total"
507
+ lines.append(f"{'Function':<35} {'Calls':>8} {sort_label + ' (ms)':>12} {'Self (ms)':>12}")
508
+ lines.append("-" * 70)
509
+ for fs in result.function_stats[:config.limit]:
510
+ funcname = fs.name
511
+ if len(funcname) > 33:
512
+ funcname = funcname[:30] + "..."
513
+ lines.append(f"{funcname:<35} {fs.calls:>8} {fs.cumtime * 1000:>12.2f} {fs.tottime * 1000:>12.2f}")
514
+ lines.append("")
515
+
516
+ # Callers (if requested)
517
+ if config.show_callers and result.callers:
518
+ lines.append("## Callers (Who called each function)")
519
+ lines.append("-" * 70)
520
+ for func, caller_list in list(result.callers.items())[:10]:
521
+ lines.append(f" {func}:")
522
+ for caller in caller_list[:5]:
523
+ lines.append(f" <- {caller}")
524
+ lines.append("")
525
+
526
+ # Callees (if requested)
527
+ if config.show_callees and result.callees:
528
+ lines.append("## Callees (What each function called)")
529
+ lines.append("-" * 70)
530
+ for func, callee_list in list(result.callees.items())[:10]:
531
+ lines.append(f" {func}:")
532
+ for callee in callee_list[:5]:
533
+ lines.append(f" -> {callee}")
534
+ lines.append("")
535
+
536
+ # Import times (if requested)
537
+ if config.importtime and result.import_times:
538
+ lines.append("## Import Times (Top Modules)")
539
+ lines.append("-" * 70)
540
+ lines.append(f"{'Module':<50} {'Time (ms)':>15}")
541
+ lines.append("-" * 70)
542
+ for module, time_sec in result.import_times[:15]:
543
+ module_name = module
544
+ if len(module_name) > 48:
545
+ module_name = "..." + module_name[-45:]
546
+ lines.append(f"{module_name:<50} {time_sec * 1000:>15.2f}")
547
+ lines.append("")
548
+
549
+ # Response preview
550
+ lines.append("## Response Preview")
551
+ lines.append("-" * 70)
552
+ preview = result.response[:500] + "..." if len(result.response) > 500 else result.response
553
+ lines.append(preview)
554
+ lines.append("")
555
+
556
+ lines.append("=" * 70)
557
+
558
+ return "\n".join(lines)