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,893 @@
1
+ """
2
+ Recipe Core API
3
+
4
+ Provides the main recipe execution functions.
5
+ """
6
+
7
+ import os
8
+ import time
9
+ import uuid
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Iterator, List, Optional, Union
13
+
14
+ from .models import (
15
+ RecipeResult,
16
+ RecipeEvent,
17
+ RecipeConfig,
18
+ RecipeInfo,
19
+ RecipeStatus,
20
+ ValidationResult,
21
+ )
22
+ from .exceptions import (
23
+ RecipeError,
24
+ RecipeNotFoundError,
25
+ RecipeDependencyError,
26
+ RecipePolicyError,
27
+ RecipeTimeoutError,
28
+ )
29
+
30
+
31
+ # Default denied tools (dangerous by default)
32
+ DEFAULT_DENIED_TOOLS = [
33
+ "shell.exec",
34
+ "shell.run",
35
+ "shell_tool",
36
+ "file.write",
37
+ "file.delete",
38
+ "fs.write",
39
+ "fs.delete",
40
+ "network.unrestricted",
41
+ "db.write",
42
+ "db.delete",
43
+ "execute_command",
44
+ ]
45
+
46
+
47
+ def _generate_run_id() -> str:
48
+ """Generate a unique run ID."""
49
+ return f"run-{uuid.uuid4().hex[:12]}"
50
+
51
+
52
+ def _generate_trace_id() -> str:
53
+ """Generate a trace ID for distributed tracing."""
54
+ return uuid.uuid4().hex
55
+
56
+
57
+ def _get_timestamp() -> str:
58
+ """Get current timestamp in ISO format."""
59
+ return datetime.now(timezone.utc).isoformat()
60
+
61
+
62
+ def get_template_search_paths() -> List[Path]:
63
+ """
64
+ Get list of paths to search for recipe templates.
65
+
66
+ Precedence order:
67
+ 1. PRAISONAI_RECIPE_PATH environment variable (colon-separated)
68
+ 2. Current working directory ./recipes
69
+ 3. User home ~/.praison/recipes
70
+ 4. Agent-Recipes package templates (if installed)
71
+ 5. Built-in templates
72
+
73
+ Returns:
74
+ List of Path objects to search for templates
75
+ """
76
+ paths = []
77
+
78
+ # 1. Environment variable
79
+ env_path = os.environ.get("PRAISONAI_RECIPE_PATH")
80
+ if env_path:
81
+ for p in env_path.split(os.pathsep):
82
+ path = Path(p).expanduser()
83
+ if path.exists():
84
+ paths.append(path)
85
+
86
+ # 2. Current working directory
87
+ cwd_recipes = Path.cwd() / "recipes"
88
+ if cwd_recipes.exists():
89
+ paths.append(cwd_recipes)
90
+
91
+ # 3. User home
92
+ home_recipes = Path.home() / ".praison" / "recipes"
93
+ if home_recipes.exists():
94
+ paths.append(home_recipes)
95
+
96
+ # 4. Agent-Recipes package (lazy import)
97
+ try:
98
+ import agent_recipes
99
+ if hasattr(agent_recipes, 'get_template_path'):
100
+ agent_path = Path(agent_recipes.get_template_path(""))
101
+ if agent_path.parent.exists():
102
+ paths.append(agent_path.parent)
103
+ elif hasattr(agent_recipes, '__file__'):
104
+ agent_templates = Path(agent_recipes.__file__).parent / "templates"
105
+ if agent_templates.exists():
106
+ paths.append(agent_templates)
107
+ except ImportError:
108
+ pass
109
+
110
+ # 5. Built-in templates (relative to this package)
111
+ builtin = Path(__file__).parent.parent / "templates"
112
+ if builtin.exists():
113
+ paths.append(builtin)
114
+
115
+ return paths
116
+
117
+
118
+ def reload_registry():
119
+ """
120
+ Reload the recipe registry, clearing any cached templates.
121
+
122
+ This is useful for hot-reloading recipes during development
123
+ or when using the /admin/reload endpoint.
124
+ """
125
+ global _recipe_cache
126
+ if '_recipe_cache' in globals():
127
+ _recipe_cache.clear()
128
+
129
+ # Also clear any module-level caches
130
+ import importlib
131
+ try:
132
+ from praisonai import recipe as recipe_module
133
+ importlib.reload(recipe_module)
134
+ except Exception:
135
+ pass
136
+
137
+
138
+ # Recipe cache for performance
139
+ _recipe_cache: Dict[str, Any] = {}
140
+
141
+
142
+ def run(
143
+ name: str,
144
+ input: Union[str, Dict[str, Any]] = None,
145
+ config: Optional[Dict[str, Any]] = None,
146
+ session_id: Optional[str] = None,
147
+ options: Optional[Dict[str, Any]] = None,
148
+ ) -> RecipeResult:
149
+ """
150
+ Run a recipe by name.
151
+
152
+ Args:
153
+ name: Recipe name or URI (e.g., "support-reply", "github:user/repo/recipe")
154
+ input: Input data (string path or dict)
155
+ config: Optional config overrides
156
+ session_id: Optional session ID for state grouping
157
+ options: Execution options:
158
+ - timeout_sec: Timeout in seconds (default: 300)
159
+ - dry_run: Validate without executing (default: False)
160
+ - verbose: Enable verbose output (default: False)
161
+ - force: Force execution even with missing deps (default: False)
162
+ - offline: Use only cached templates (default: False)
163
+ - mode: Execution mode - "dev" or "prod" (default: "dev")
164
+ - allow_dangerous_tools: Allow dangerous tools (default: False)
165
+
166
+ Returns:
167
+ RecipeResult with run_id, status, output, metrics, trace
168
+
169
+ Example:
170
+ >>> from praisonai import recipe
171
+ >>> result = recipe.run("support-reply", input={"ticket_id": "T-123"})
172
+ >>> if result.ok:
173
+ ... print(result.output["reply"])
174
+ """
175
+ options = options or {}
176
+ config = config or {}
177
+ input = input or {}
178
+
179
+ # Generate identifiers
180
+ run_id = _generate_run_id()
181
+ trace_id = _generate_trace_id()
182
+ session_id = session_id or f"session-{uuid.uuid4().hex[:8]}"
183
+
184
+ start_time = time.time()
185
+
186
+ trace = {
187
+ "run_id": run_id,
188
+ "trace_id": trace_id,
189
+ "session_id": session_id,
190
+ }
191
+
192
+ try:
193
+ # Load template
194
+ recipe_config = _load_recipe(name, offline=options.get("offline", False))
195
+
196
+ if recipe_config is None:
197
+ return RecipeResult(
198
+ run_id=run_id,
199
+ recipe=name,
200
+ version="unknown",
201
+ status=RecipeStatus.FAILED,
202
+ error=f"Recipe not found: {name}",
203
+ metrics={"duration_sec": time.time() - start_time},
204
+ trace=trace,
205
+ )
206
+
207
+ # Check dependencies
208
+ if not options.get("force", False):
209
+ dep_result = _check_dependencies(recipe_config)
210
+ if not dep_result["all_satisfied"]:
211
+ missing = _format_missing_deps(dep_result)
212
+ return RecipeResult(
213
+ run_id=run_id,
214
+ recipe=recipe_config.name,
215
+ version=recipe_config.version,
216
+ status=RecipeStatus.MISSING_DEPS,
217
+ error=f"Missing dependencies: {', '.join(missing)}",
218
+ metrics={"duration_sec": time.time() - start_time},
219
+ trace=trace,
220
+ )
221
+
222
+ # Check tool permissions
223
+ if not options.get("allow_dangerous_tools", False):
224
+ policy_error = _check_tool_policy(recipe_config)
225
+ if policy_error:
226
+ return RecipeResult(
227
+ run_id=run_id,
228
+ recipe=recipe_config.name,
229
+ version=recipe_config.version,
230
+ status=RecipeStatus.POLICY_DENIED,
231
+ error=policy_error,
232
+ metrics={"duration_sec": time.time() - start_time},
233
+ trace=trace,
234
+ )
235
+
236
+ # Dry run mode
237
+ if options.get("dry_run", False):
238
+ return RecipeResult(
239
+ run_id=run_id,
240
+ recipe=recipe_config.name,
241
+ version=recipe_config.version,
242
+ status=RecipeStatus.DRY_RUN,
243
+ output={
244
+ "plan": "Would execute recipe with provided config",
245
+ "recipe": recipe_config.name,
246
+ "config": {**recipe_config.defaults, **config},
247
+ },
248
+ metrics={"duration_sec": time.time() - start_time},
249
+ trace=trace,
250
+ )
251
+
252
+ # Merge input and config
253
+ if isinstance(input, str):
254
+ merged_config = {**recipe_config.defaults, "input": input, **config}
255
+ else:
256
+ merged_config = {**recipe_config.defaults, **input, **config}
257
+
258
+ # Execute recipe
259
+ output = _execute_recipe(
260
+ recipe_config,
261
+ merged_config,
262
+ session_id,
263
+ options,
264
+ )
265
+
266
+ duration = time.time() - start_time
267
+
268
+ return RecipeResult(
269
+ run_id=run_id,
270
+ recipe=recipe_config.name,
271
+ version=recipe_config.version,
272
+ status=RecipeStatus.SUCCESS,
273
+ output=output,
274
+ metrics={"duration_sec": round(duration, 2)},
275
+ trace=trace,
276
+ )
277
+
278
+ except RecipeNotFoundError as e:
279
+ return RecipeResult(
280
+ run_id=run_id,
281
+ recipe=name,
282
+ version="unknown",
283
+ status=RecipeStatus.FAILED,
284
+ error=str(e),
285
+ metrics={"duration_sec": time.time() - start_time},
286
+ trace=trace,
287
+ )
288
+ except RecipeDependencyError as e:
289
+ return RecipeResult(
290
+ run_id=run_id,
291
+ recipe=e.recipe or name,
292
+ version="unknown",
293
+ status=RecipeStatus.MISSING_DEPS,
294
+ error=str(e),
295
+ metrics={"duration_sec": time.time() - start_time},
296
+ trace=trace,
297
+ )
298
+ except RecipePolicyError as e:
299
+ return RecipeResult(
300
+ run_id=run_id,
301
+ recipe=e.recipe or name,
302
+ version="unknown",
303
+ status=RecipeStatus.POLICY_DENIED,
304
+ error=str(e),
305
+ metrics={"duration_sec": time.time() - start_time},
306
+ trace=trace,
307
+ )
308
+ except RecipeTimeoutError as e:
309
+ return RecipeResult(
310
+ run_id=run_id,
311
+ recipe=name,
312
+ version="unknown",
313
+ status=RecipeStatus.TIMEOUT,
314
+ error=str(e),
315
+ metrics={"duration_sec": time.time() - start_time},
316
+ trace=trace,
317
+ )
318
+ except Exception as e:
319
+ return RecipeResult(
320
+ run_id=run_id,
321
+ recipe=name,
322
+ version="unknown",
323
+ status=RecipeStatus.FAILED,
324
+ error=str(e),
325
+ metrics={"duration_sec": time.time() - start_time},
326
+ trace=trace,
327
+ )
328
+
329
+
330
+ def run_stream(
331
+ name: str,
332
+ input: Union[str, Dict[str, Any]] = None,
333
+ config: Optional[Dict[str, Any]] = None,
334
+ session_id: Optional[str] = None,
335
+ options: Optional[Dict[str, Any]] = None,
336
+ ) -> Iterator[RecipeEvent]:
337
+ """
338
+ Run a recipe with streaming events.
339
+
340
+ Yields RecipeEvent objects for progress tracking.
341
+
342
+ Args:
343
+ name: Recipe name or URI
344
+ input: Input data
345
+ config: Optional config overrides
346
+ session_id: Optional session ID
347
+ options: Execution options
348
+
349
+ Yields:
350
+ RecipeEvent objects with event_type and data
351
+
352
+ Example:
353
+ >>> for event in recipe.run_stream("transcript-generator", input="audio.mp3"):
354
+ ... print(f"[{event.event_type}] {event.data}")
355
+ """
356
+ options = options or {}
357
+ config = config or {}
358
+ input = input or {}
359
+
360
+ run_id = _generate_run_id()
361
+ trace_id = _generate_trace_id()
362
+ session_id = session_id or f"session-{uuid.uuid4().hex[:8]}"
363
+
364
+ # Started event
365
+ yield RecipeEvent(
366
+ event_type="started",
367
+ data={
368
+ "run_id": run_id,
369
+ "recipe": name,
370
+ "trace_id": trace_id,
371
+ "session_id": session_id,
372
+ },
373
+ )
374
+
375
+ try:
376
+ # Load recipe
377
+ yield RecipeEvent(
378
+ event_type="progress",
379
+ data={"step": "loading", "message": f"Loading recipe: {name}"},
380
+ )
381
+
382
+ recipe_config = _load_recipe(name, offline=options.get("offline", False))
383
+
384
+ if recipe_config is None:
385
+ yield RecipeEvent(
386
+ event_type="error",
387
+ data={"code": "not_found", "message": f"Recipe not found: {name}"},
388
+ )
389
+ return
390
+
391
+ # Check dependencies
392
+ yield RecipeEvent(
393
+ event_type="progress",
394
+ data={"step": "checking_deps", "message": "Checking dependencies"},
395
+ )
396
+
397
+ if not options.get("force", False):
398
+ dep_result = _check_dependencies(recipe_config)
399
+ if not dep_result["all_satisfied"]:
400
+ missing = _format_missing_deps(dep_result)
401
+ yield RecipeEvent(
402
+ event_type="error",
403
+ data={
404
+ "code": "missing_deps",
405
+ "message": f"Missing dependencies: {', '.join(missing)}",
406
+ "missing": missing,
407
+ },
408
+ )
409
+ return
410
+
411
+ # Dry run
412
+ if options.get("dry_run", False):
413
+ yield RecipeEvent(
414
+ event_type="completed",
415
+ data={
416
+ "run_id": run_id,
417
+ "status": RecipeStatus.DRY_RUN,
418
+ "message": "Dry run completed",
419
+ },
420
+ )
421
+ return
422
+
423
+ # Execute
424
+ yield RecipeEvent(
425
+ event_type="progress",
426
+ data={"step": "executing", "message": "Executing recipe"},
427
+ )
428
+
429
+ # Merge config
430
+ if isinstance(input, str):
431
+ merged_config = {**recipe_config.defaults, "input": input, **config}
432
+ else:
433
+ merged_config = {**recipe_config.defaults, **input, **config}
434
+
435
+ start_time = time.time()
436
+ output = _execute_recipe(recipe_config, merged_config, session_id, options)
437
+ duration = time.time() - start_time
438
+
439
+ # Output event
440
+ yield RecipeEvent(
441
+ event_type="output",
442
+ data={"output": output},
443
+ )
444
+
445
+ # Completed event
446
+ yield RecipeEvent(
447
+ event_type="completed",
448
+ data={
449
+ "run_id": run_id,
450
+ "status": RecipeStatus.SUCCESS,
451
+ "duration_sec": round(duration, 2),
452
+ },
453
+ )
454
+
455
+ except Exception as e:
456
+ yield RecipeEvent(
457
+ event_type="error",
458
+ data={"code": "execution_error", "message": str(e)},
459
+ )
460
+
461
+
462
+ def validate(name: str, offline: bool = False) -> ValidationResult:
463
+ """
464
+ Validate a recipe and check dependencies.
465
+
466
+ Args:
467
+ name: Recipe name or URI
468
+ offline: Use only cached templates
469
+
470
+ Returns:
471
+ ValidationResult with valid status, errors, warnings, dependencies
472
+
473
+ Example:
474
+ >>> result = recipe.validate("support-reply")
475
+ >>> if result.valid:
476
+ ... print("Recipe is valid")
477
+ >>> else:
478
+ ... print(f"Errors: {result.errors}")
479
+ """
480
+ errors = []
481
+ warnings = []
482
+
483
+ try:
484
+ recipe_config = _load_recipe(name, offline=offline)
485
+
486
+ if recipe_config is None:
487
+ return ValidationResult(
488
+ valid=False,
489
+ recipe=name,
490
+ version="unknown",
491
+ errors=[f"Recipe not found: {name}"],
492
+ )
493
+
494
+ # Check dependencies
495
+ dep_result = _check_dependencies(recipe_config)
496
+
497
+ # Check for missing required deps
498
+ for pkg in dep_result.get("packages", []):
499
+ if not pkg.get("available", False):
500
+ errors.append(f"Missing package: {pkg['name']}")
501
+
502
+ for env in dep_result.get("env", []):
503
+ if not env.get("available", False):
504
+ errors.append(f"Missing env var: ${env['name']}")
505
+
506
+ for ext in dep_result.get("external", []):
507
+ if not ext.get("available", False):
508
+ warnings.append(f"Missing external tool: {ext['name']}")
509
+
510
+ # Check tool policy
511
+ policy_error = _check_tool_policy(recipe_config)
512
+ if policy_error:
513
+ warnings.append(f"Tool policy: {policy_error}")
514
+
515
+ return ValidationResult(
516
+ valid=len(errors) == 0,
517
+ recipe=recipe_config.name,
518
+ version=recipe_config.version,
519
+ errors=errors,
520
+ warnings=warnings,
521
+ dependencies=dep_result,
522
+ )
523
+
524
+ except Exception as e:
525
+ return ValidationResult(
526
+ valid=False,
527
+ recipe=name,
528
+ version="unknown",
529
+ errors=[str(e)],
530
+ )
531
+
532
+
533
+ def list_recipes(
534
+ source_filter: Optional[str] = None,
535
+ tags: Optional[List[str]] = None,
536
+ offline: bool = False,
537
+ ) -> List[RecipeInfo]:
538
+ """
539
+ List available recipes.
540
+
541
+ Args:
542
+ source_filter: Filter by source (local, package, github)
543
+ tags: Filter by tags
544
+ offline: Use only cached templates
545
+
546
+ Returns:
547
+ List of RecipeInfo objects
548
+
549
+ Example:
550
+ >>> recipes = recipe.list_recipes(tags=["video"])
551
+ >>> for r in recipes:
552
+ ... print(f"{r.name}: {r.description}")
553
+ """
554
+ try:
555
+ from praisonai.templates import TemplateDiscovery
556
+
557
+ discovery = TemplateDiscovery()
558
+ templates = discovery.list_templates(source_filter=source_filter)
559
+
560
+ recipes = []
561
+ for t in templates:
562
+ # Filter by tags if specified
563
+ if tags:
564
+ template_tags = getattr(t, 'tags', []) or []
565
+ if not any(tag in template_tags for tag in tags):
566
+ continue
567
+
568
+ recipes.append(RecipeInfo(
569
+ name=t.name,
570
+ version=getattr(t, 'version', '1.0.0'),
571
+ description=getattr(t, 'description', ''),
572
+ tags=getattr(t, 'tags', []) or [],
573
+ path=str(t.path) if hasattr(t, 'path') else '',
574
+ source=getattr(t, 'source', 'local'),
575
+ ))
576
+
577
+ return recipes
578
+
579
+ except Exception:
580
+ return []
581
+
582
+
583
+ def describe(name: str, offline: bool = False) -> Optional[RecipeConfig]:
584
+ """
585
+ Get detailed information about a recipe.
586
+
587
+ Args:
588
+ name: Recipe name or URI
589
+ offline: Use only cached templates
590
+
591
+ Returns:
592
+ RecipeConfig with full recipe details, or None if not found
593
+
594
+ Example:
595
+ >>> info = recipe.describe("support-reply")
596
+ >>> print(f"Required env vars: {info.get_required_env()}")
597
+ """
598
+ return _load_recipe(name, offline=offline)
599
+
600
+
601
+ # --- Internal Functions ---
602
+
603
+ def _load_recipe(name: str, offline: bool = False) -> Optional[RecipeConfig]:
604
+ """Load a recipe by name or URI."""
605
+ try:
606
+ from praisonai.templates import TemplateDiscovery, TemplateLoader
607
+
608
+ discovery = TemplateDiscovery()
609
+ discovered = discovery.find_template(name)
610
+
611
+ if discovered:
612
+ loader = TemplateLoader(offline=offline)
613
+ template = loader.load(str(discovered.path))
614
+
615
+ return RecipeConfig(
616
+ name=template.name,
617
+ version=template.version,
618
+ description=template.description,
619
+ author=template.author,
620
+ license=template.license,
621
+ tags=template.tags,
622
+ requires=template.requires,
623
+ tools=template.raw.get("tools", {}),
624
+ config_schema=template.config_schema,
625
+ defaults=template.defaults,
626
+ outputs=template.raw.get("outputs", []),
627
+ governance=template.raw.get("governance", {}),
628
+ data_policy=template.raw.get("data_policy", {}),
629
+ path=str(template.path) if template.path else None,
630
+ raw=template.raw,
631
+ )
632
+
633
+ # Try loading directly as URI
634
+ loader = TemplateLoader(offline=offline)
635
+ try:
636
+ template = loader.load(name)
637
+ return RecipeConfig(
638
+ name=template.name,
639
+ version=template.version,
640
+ description=template.description,
641
+ author=template.author,
642
+ license=template.license,
643
+ tags=template.tags,
644
+ requires=template.requires,
645
+ tools=template.raw.get("tools", {}),
646
+ config_schema=template.config_schema,
647
+ defaults=template.defaults,
648
+ outputs=template.raw.get("outputs", []),
649
+ governance=template.raw.get("governance", {}),
650
+ data_policy=template.raw.get("data_policy", {}),
651
+ path=str(template.path) if template.path else None,
652
+ raw=template.raw,
653
+ )
654
+ except Exception:
655
+ pass
656
+
657
+ return None
658
+
659
+ except Exception:
660
+ return None
661
+
662
+
663
+ def _check_dependencies(recipe_config: RecipeConfig) -> Dict[str, Any]:
664
+ """Check if recipe dependencies are satisfied."""
665
+ result = {
666
+ "all_satisfied": True,
667
+ "packages": [],
668
+ "env": [],
669
+ "tools": [],
670
+ "external": [],
671
+ }
672
+
673
+ # Check Python packages
674
+ for pkg in recipe_config.get_required_packages():
675
+ try:
676
+ __import__(pkg.replace("-", "_"))
677
+ result["packages"].append({"name": pkg, "available": True})
678
+ except ImportError:
679
+ result["packages"].append({"name": pkg, "available": False})
680
+ result["all_satisfied"] = False
681
+
682
+ # Check environment variables
683
+ for env_var in recipe_config.get_required_env():
684
+ available = env_var in os.environ
685
+ result["env"].append({"name": env_var, "available": available})
686
+ if not available:
687
+ result["all_satisfied"] = False
688
+
689
+ # Check external tools
690
+ import shutil
691
+ for ext in recipe_config.get_external_deps():
692
+ ext_name = ext.get("name", ext) if isinstance(ext, dict) else ext
693
+ available = shutil.which(ext_name) is not None
694
+ result["external"].append({"name": ext_name, "available": available})
695
+ # External tools are warnings, not errors
696
+
697
+ return result
698
+
699
+
700
+ def _format_missing_deps(dep_result: Dict[str, Any]) -> List[str]:
701
+ """Format missing dependencies as a list of strings."""
702
+ missing = []
703
+
704
+ for pkg in dep_result.get("packages", []):
705
+ if not pkg.get("available", False):
706
+ missing.append(pkg["name"])
707
+
708
+ for env in dep_result.get("env", []):
709
+ if not env.get("available", False):
710
+ missing.append(f"${env['name']}")
711
+
712
+ return missing
713
+
714
+
715
+ def _check_tool_policy(recipe_config: RecipeConfig) -> Optional[str]:
716
+ """Check if recipe uses denied tools. Returns error message or None."""
717
+ allowed = set(recipe_config.get_allowed_tools())
718
+ denied = set(recipe_config.get_denied_tools())
719
+ required = set(recipe_config.get_required_tools())
720
+
721
+ # Check if any required tools are in default denied list
722
+ for tool in required:
723
+ if tool in DEFAULT_DENIED_TOOLS and tool not in allowed:
724
+ return f"Tool '{tool}' is denied by default. Use allow_dangerous_tools=True to override."
725
+
726
+ # Check explicit denials
727
+ for tool in required:
728
+ if tool in denied:
729
+ return f"Tool '{tool}' is explicitly denied by recipe policy."
730
+
731
+ return None
732
+
733
+
734
+ def _execute_recipe(
735
+ recipe_config: RecipeConfig,
736
+ merged_config: Dict[str, Any],
737
+ session_id: str,
738
+ options: Dict[str, Any],
739
+ ) -> Any:
740
+ """Execute the recipe workflow."""
741
+ try:
742
+ from praisonai.templates import TemplateLoader
743
+ from praisonai.templates.tool_override import create_tool_registry_with_overrides
744
+
745
+ loader = TemplateLoader()
746
+
747
+ # Load workflow config
748
+ from praisonai.templates.loader import TemplateConfig as LoaderTemplateConfig
749
+
750
+ # Create a TemplateConfig compatible with loader
751
+ template_path = Path(recipe_config.path) if recipe_config.path else None
752
+
753
+ loader_config = LoaderTemplateConfig(
754
+ name=recipe_config.name,
755
+ description=recipe_config.description,
756
+ version=recipe_config.version,
757
+ author=recipe_config.author,
758
+ license=recipe_config.license,
759
+ tags=recipe_config.tags,
760
+ requires=recipe_config.requires,
761
+ workflow_file=recipe_config.raw.get("workflow", "workflow.yaml"),
762
+ agents_file=recipe_config.raw.get("agents", "agents.yaml"),
763
+ config_schema=recipe_config.config_schema,
764
+ defaults=merged_config,
765
+ skills=recipe_config.raw.get("skills", []),
766
+ cli=recipe_config.raw.get("cli", {}),
767
+ raw=recipe_config.raw,
768
+ path=template_path,
769
+ )
770
+
771
+ workflow_config = loader.load_workflow_config(loader_config)
772
+
773
+ # Build tool registry
774
+ tool_registry = create_tool_registry_with_overrides(
775
+ include_defaults=True,
776
+ template_dir=recipe_config.path,
777
+ )
778
+
779
+ # Execute based on workflow type
780
+ if "agents" in workflow_config and "tasks" in workflow_config:
781
+ return _execute_praisonai_workflow(
782
+ workflow_config, merged_config, tool_registry, options
783
+ )
784
+ elif "steps" in workflow_config:
785
+ return _execute_steps_workflow(
786
+ workflow_config, merged_config, tool_registry, options
787
+ )
788
+ else:
789
+ # Simple agent execution
790
+ return _execute_simple_agent(
791
+ workflow_config, merged_config, tool_registry, options
792
+ )
793
+
794
+ except ImportError as e:
795
+ raise RecipeError(f"Missing dependency for recipe execution: {e}")
796
+ except Exception as e:
797
+ raise RecipeError(f"Recipe execution failed: {e}")
798
+
799
+
800
+ def _execute_praisonai_workflow(
801
+ workflow_config: Dict[str, Any],
802
+ config: Dict[str, Any],
803
+ tool_registry: Any,
804
+ options: Dict[str, Any],
805
+ ) -> Any:
806
+ """Execute a PraisonAI agents/tasks workflow."""
807
+ from praisonaiagents import Agent, Task, PraisonAIAgents
808
+ from praisonai.templates.tool_override import resolve_tools
809
+
810
+ agents = []
811
+ agent_map = {}
812
+
813
+ for agent_cfg in workflow_config.get("agents", []):
814
+ agent_tools = resolve_tools(
815
+ agent_cfg.get("tools", []),
816
+ registry=tool_registry,
817
+ )
818
+
819
+ agent = Agent(
820
+ name=agent_cfg.get("name", "Agent"),
821
+ role=agent_cfg.get("role", ""),
822
+ goal=agent_cfg.get("goal", ""),
823
+ backstory=agent_cfg.get("backstory", ""),
824
+ tools=agent_tools if agent_tools else None,
825
+ llm=agent_cfg.get("llm"),
826
+ verbose=options.get("verbose", False),
827
+ )
828
+ agents.append(agent)
829
+ agent_map[agent_cfg.get("name")] = agent
830
+
831
+ tasks = []
832
+ for task_cfg in workflow_config.get("tasks", []):
833
+ agent_name = task_cfg.get("agent")
834
+ agent = agent_map.get(agent_name, agents[0] if agents else None)
835
+
836
+ # Substitute config values in description
837
+ description = task_cfg.get("description", "")
838
+ try:
839
+ description = description.format(**config)
840
+ except KeyError:
841
+ pass
842
+
843
+ task = Task(
844
+ name=task_cfg.get("name", "Task"),
845
+ description=description,
846
+ expected_output=task_cfg.get("expected_output", ""),
847
+ agent=agent,
848
+ )
849
+ tasks.append(task)
850
+
851
+ praison = PraisonAIAgents(
852
+ agents=agents,
853
+ tasks=tasks,
854
+ process=workflow_config.get("process", "sequential"),
855
+ verbose=options.get("verbose", 1) if options.get("verbose") else 0,
856
+ )
857
+
858
+ return praison.start()
859
+
860
+
861
+ def _execute_steps_workflow(
862
+ workflow_config: Dict[str, Any],
863
+ config: Dict[str, Any],
864
+ tool_registry: Any,
865
+ options: Dict[str, Any],
866
+ ) -> Any:
867
+ """Execute a steps-based workflow."""
868
+ # For now, convert to simple execution
869
+ return {"message": "Steps workflow executed", "config": config}
870
+
871
+
872
+ def _execute_simple_agent(
873
+ workflow_config: Dict[str, Any],
874
+ config: Dict[str, Any],
875
+ tool_registry: Any,
876
+ options: Dict[str, Any],
877
+ ) -> Any:
878
+ """Execute a simple single-agent workflow."""
879
+ from praisonaiagents import Agent
880
+
881
+ agent = Agent(
882
+ name=workflow_config.get("name", "RecipeAgent"),
883
+ role=workflow_config.get("role", "AI Assistant"),
884
+ goal=workflow_config.get("goal", "Complete the task"),
885
+ backstory=workflow_config.get("backstory", ""),
886
+ verbose=options.get("verbose", False),
887
+ )
888
+
889
+ prompt = config.get("input", config.get("prompt", ""))
890
+ if prompt:
891
+ return agent.chat(prompt)
892
+
893
+ return {"message": "Recipe executed", "config": config}