ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.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 (535) hide show
  1. agentui/.prettierrc +15 -0
  2. agentui/QUICKSTART.md +272 -0
  3. agentui/README.md +59 -0
  4. agentui/env.example +16 -0
  5. agentui/jsconfig.json +14 -0
  6. agentui/package-lock.json +4242 -0
  7. agentui/package.json +34 -0
  8. agentui/scripts/postinstall/apply-patches.mjs +260 -0
  9. agentui/src/app.css +61 -0
  10. agentui/src/app.d.ts +13 -0
  11. agentui/src/app.html +12 -0
  12. agentui/src/components/LoadingSpinner.svelte +64 -0
  13. agentui/src/components/ThemeSwitcher.svelte +159 -0
  14. agentui/src/components/index.js +4 -0
  15. agentui/src/lib/api/bots.ts +60 -0
  16. agentui/src/lib/api/chat.ts +22 -0
  17. agentui/src/lib/api/http.ts +25 -0
  18. agentui/src/lib/components/BotCard.svelte +33 -0
  19. agentui/src/lib/components/ChatBubble.svelte +63 -0
  20. agentui/src/lib/components/Toast.svelte +21 -0
  21. agentui/src/lib/config.ts +20 -0
  22. agentui/src/lib/stores/auth.svelte.ts +73 -0
  23. agentui/src/lib/stores/theme.svelte.js +64 -0
  24. agentui/src/lib/stores/toast.svelte.ts +31 -0
  25. agentui/src/lib/utils/conversation.ts +39 -0
  26. agentui/src/routes/+layout.svelte +20 -0
  27. agentui/src/routes/+page.svelte +232 -0
  28. agentui/src/routes/login/+page.svelte +200 -0
  29. agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
  30. agentui/src/routes/talk/[agentId]/+page.ts +7 -0
  31. agentui/static/README.md +1 -0
  32. agentui/svelte.config.js +11 -0
  33. agentui/tailwind.config.ts +53 -0
  34. agentui/tsconfig.json +3 -0
  35. agentui/vite.config.ts +10 -0
  36. ai_parrot-0.17.2.dist-info/METADATA +472 -0
  37. ai_parrot-0.17.2.dist-info/RECORD +535 -0
  38. ai_parrot-0.17.2.dist-info/WHEEL +6 -0
  39. ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
  40. ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
  41. ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
  42. crew-builder/.prettierrc +15 -0
  43. crew-builder/QUICKSTART.md +259 -0
  44. crew-builder/README.md +113 -0
  45. crew-builder/env.example +17 -0
  46. crew-builder/jsconfig.json +14 -0
  47. crew-builder/package-lock.json +4182 -0
  48. crew-builder/package.json +37 -0
  49. crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
  50. crew-builder/src/app.css +62 -0
  51. crew-builder/src/app.d.ts +13 -0
  52. crew-builder/src/app.html +12 -0
  53. crew-builder/src/components/LoadingSpinner.svelte +64 -0
  54. crew-builder/src/components/ThemeSwitcher.svelte +149 -0
  55. crew-builder/src/components/index.js +9 -0
  56. crew-builder/src/lib/api/bots.ts +60 -0
  57. crew-builder/src/lib/api/chat.ts +80 -0
  58. crew-builder/src/lib/api/client.ts +56 -0
  59. crew-builder/src/lib/api/crew/crew.ts +136 -0
  60. crew-builder/src/lib/api/index.ts +5 -0
  61. crew-builder/src/lib/api/o365/auth.ts +65 -0
  62. crew-builder/src/lib/auth/auth.ts +54 -0
  63. crew-builder/src/lib/components/AgentNode.svelte +43 -0
  64. crew-builder/src/lib/components/BotCard.svelte +33 -0
  65. crew-builder/src/lib/components/ChatBubble.svelte +67 -0
  66. crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
  67. crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
  68. crew-builder/src/lib/components/JsonViewer.svelte +24 -0
  69. crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
  70. crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
  71. crew-builder/src/lib/components/Toast.svelte +67 -0
  72. crew-builder/src/lib/components/Toolbar.svelte +157 -0
  73. crew-builder/src/lib/components/index.ts +10 -0
  74. crew-builder/src/lib/config.ts +8 -0
  75. crew-builder/src/lib/stores/auth.svelte.ts +228 -0
  76. crew-builder/src/lib/stores/crewStore.ts +369 -0
  77. crew-builder/src/lib/stores/theme.svelte.js +145 -0
  78. crew-builder/src/lib/stores/toast.svelte.ts +69 -0
  79. crew-builder/src/lib/utils/conversation.ts +39 -0
  80. crew-builder/src/lib/utils/markdown.ts +122 -0
  81. crew-builder/src/lib/utils/talkHistory.ts +47 -0
  82. crew-builder/src/routes/+layout.svelte +20 -0
  83. crew-builder/src/routes/+page.svelte +539 -0
  84. crew-builder/src/routes/agents/+page.svelte +247 -0
  85. crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
  86. crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
  87. crew-builder/src/routes/builder/+page.svelte +204 -0
  88. crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
  89. crew-builder/src/routes/crew/ask/+page.ts +1 -0
  90. crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
  91. crew-builder/src/routes/login/+page.svelte +197 -0
  92. crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
  93. crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
  94. crew-builder/static/README.md +1 -0
  95. crew-builder/svelte.config.js +11 -0
  96. crew-builder/tailwind.config.ts +53 -0
  97. crew-builder/tsconfig.json +3 -0
  98. crew-builder/vite.config.ts +10 -0
  99. mcp_servers/calculator_server.py +309 -0
  100. parrot/__init__.py +27 -0
  101. parrot/__pycache__/__init__.cpython-310.pyc +0 -0
  102. parrot/__pycache__/version.cpython-310.pyc +0 -0
  103. parrot/_version.py +34 -0
  104. parrot/a2a/__init__.py +48 -0
  105. parrot/a2a/client.py +658 -0
  106. parrot/a2a/discovery.py +89 -0
  107. parrot/a2a/mixin.py +257 -0
  108. parrot/a2a/models.py +376 -0
  109. parrot/a2a/server.py +770 -0
  110. parrot/agents/__init__.py +29 -0
  111. parrot/bots/__init__.py +12 -0
  112. parrot/bots/a2a_agent.py +19 -0
  113. parrot/bots/abstract.py +3139 -0
  114. parrot/bots/agent.py +1129 -0
  115. parrot/bots/basic.py +9 -0
  116. parrot/bots/chatbot.py +669 -0
  117. parrot/bots/data.py +1618 -0
  118. parrot/bots/database/__init__.py +5 -0
  119. parrot/bots/database/abstract.py +3071 -0
  120. parrot/bots/database/cache.py +286 -0
  121. parrot/bots/database/models.py +468 -0
  122. parrot/bots/database/prompts.py +154 -0
  123. parrot/bots/database/retries.py +98 -0
  124. parrot/bots/database/router.py +269 -0
  125. parrot/bots/database/sql.py +41 -0
  126. parrot/bots/db/__init__.py +6 -0
  127. parrot/bots/db/abstract.py +556 -0
  128. parrot/bots/db/bigquery.py +602 -0
  129. parrot/bots/db/cache.py +85 -0
  130. parrot/bots/db/documentdb.py +668 -0
  131. parrot/bots/db/elastic.py +1014 -0
  132. parrot/bots/db/influx.py +898 -0
  133. parrot/bots/db/mock.py +96 -0
  134. parrot/bots/db/multi.py +783 -0
  135. parrot/bots/db/prompts.py +185 -0
  136. parrot/bots/db/sql.py +1255 -0
  137. parrot/bots/db/tools.py +212 -0
  138. parrot/bots/document.py +680 -0
  139. parrot/bots/hrbot.py +15 -0
  140. parrot/bots/kb.py +170 -0
  141. parrot/bots/mcp.py +36 -0
  142. parrot/bots/orchestration/README.md +463 -0
  143. parrot/bots/orchestration/__init__.py +1 -0
  144. parrot/bots/orchestration/agent.py +155 -0
  145. parrot/bots/orchestration/crew.py +3330 -0
  146. parrot/bots/orchestration/fsm.py +1179 -0
  147. parrot/bots/orchestration/hr.py +434 -0
  148. parrot/bots/orchestration/storage/__init__.py +4 -0
  149. parrot/bots/orchestration/storage/memory.py +100 -0
  150. parrot/bots/orchestration/storage/mixin.py +119 -0
  151. parrot/bots/orchestration/verify.py +202 -0
  152. parrot/bots/product.py +204 -0
  153. parrot/bots/prompts/__init__.py +96 -0
  154. parrot/bots/prompts/agents.py +155 -0
  155. parrot/bots/prompts/data.py +216 -0
  156. parrot/bots/prompts/output_generation.py +8 -0
  157. parrot/bots/scraper/__init__.py +3 -0
  158. parrot/bots/scraper/models.py +122 -0
  159. parrot/bots/scraper/scraper.py +1173 -0
  160. parrot/bots/scraper/templates.py +115 -0
  161. parrot/bots/stores/__init__.py +5 -0
  162. parrot/bots/stores/local.py +172 -0
  163. parrot/bots/webdev.py +81 -0
  164. parrot/cli.py +17 -0
  165. parrot/clients/__init__.py +16 -0
  166. parrot/clients/base.py +1491 -0
  167. parrot/clients/claude.py +1191 -0
  168. parrot/clients/factory.py +129 -0
  169. parrot/clients/google.py +4567 -0
  170. parrot/clients/gpt.py +1975 -0
  171. parrot/clients/grok.py +432 -0
  172. parrot/clients/groq.py +986 -0
  173. parrot/clients/hf.py +582 -0
  174. parrot/clients/models.py +18 -0
  175. parrot/conf.py +395 -0
  176. parrot/embeddings/__init__.py +9 -0
  177. parrot/embeddings/base.py +157 -0
  178. parrot/embeddings/google.py +98 -0
  179. parrot/embeddings/huggingface.py +74 -0
  180. parrot/embeddings/openai.py +84 -0
  181. parrot/embeddings/processor.py +88 -0
  182. parrot/exceptions.c +13868 -0
  183. parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
  184. parrot/exceptions.pxd +22 -0
  185. parrot/exceptions.pxi +15 -0
  186. parrot/exceptions.pyx +44 -0
  187. parrot/generators/__init__.py +29 -0
  188. parrot/generators/base.py +200 -0
  189. parrot/generators/html.py +293 -0
  190. parrot/generators/react.py +205 -0
  191. parrot/generators/streamlit.py +203 -0
  192. parrot/generators/template.py +105 -0
  193. parrot/handlers/__init__.py +4 -0
  194. parrot/handlers/agent.py +861 -0
  195. parrot/handlers/agents/__init__.py +1 -0
  196. parrot/handlers/agents/abstract.py +900 -0
  197. parrot/handlers/bots.py +338 -0
  198. parrot/handlers/chat.py +915 -0
  199. parrot/handlers/creation.sql +192 -0
  200. parrot/handlers/crew/ARCHITECTURE.md +362 -0
  201. parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
  202. parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
  203. parrot/handlers/crew/__init__.py +0 -0
  204. parrot/handlers/crew/handler.py +801 -0
  205. parrot/handlers/crew/models.py +229 -0
  206. parrot/handlers/crew/redis_persistence.py +523 -0
  207. parrot/handlers/jobs/__init__.py +10 -0
  208. parrot/handlers/jobs/job.py +384 -0
  209. parrot/handlers/jobs/mixin.py +627 -0
  210. parrot/handlers/jobs/models.py +115 -0
  211. parrot/handlers/jobs/worker.py +31 -0
  212. parrot/handlers/models.py +596 -0
  213. parrot/handlers/o365_auth.py +105 -0
  214. parrot/handlers/stream.py +337 -0
  215. parrot/interfaces/__init__.py +6 -0
  216. parrot/interfaces/aws.py +143 -0
  217. parrot/interfaces/credentials.py +113 -0
  218. parrot/interfaces/database.py +27 -0
  219. parrot/interfaces/google.py +1123 -0
  220. parrot/interfaces/hierarchy.py +1227 -0
  221. parrot/interfaces/http.py +651 -0
  222. parrot/interfaces/images/__init__.py +0 -0
  223. parrot/interfaces/images/plugins/__init__.py +24 -0
  224. parrot/interfaces/images/plugins/abstract.py +58 -0
  225. parrot/interfaces/images/plugins/analisys.py +148 -0
  226. parrot/interfaces/images/plugins/classify.py +150 -0
  227. parrot/interfaces/images/plugins/classifybase.py +182 -0
  228. parrot/interfaces/images/plugins/detect.py +150 -0
  229. parrot/interfaces/images/plugins/exif.py +1103 -0
  230. parrot/interfaces/images/plugins/hash.py +52 -0
  231. parrot/interfaces/images/plugins/vision.py +104 -0
  232. parrot/interfaces/images/plugins/yolo.py +66 -0
  233. parrot/interfaces/images/plugins/zerodetect.py +197 -0
  234. parrot/interfaces/o365.py +978 -0
  235. parrot/interfaces/onedrive.py +822 -0
  236. parrot/interfaces/sharepoint.py +1435 -0
  237. parrot/interfaces/soap.py +257 -0
  238. parrot/loaders/__init__.py +8 -0
  239. parrot/loaders/abstract.py +1131 -0
  240. parrot/loaders/audio.py +199 -0
  241. parrot/loaders/basepdf.py +53 -0
  242. parrot/loaders/basevideo.py +1568 -0
  243. parrot/loaders/csv.py +409 -0
  244. parrot/loaders/docx.py +116 -0
  245. parrot/loaders/epubloader.py +316 -0
  246. parrot/loaders/excel.py +199 -0
  247. parrot/loaders/factory.py +55 -0
  248. parrot/loaders/files/__init__.py +0 -0
  249. parrot/loaders/files/abstract.py +39 -0
  250. parrot/loaders/files/html.py +26 -0
  251. parrot/loaders/files/text.py +63 -0
  252. parrot/loaders/html.py +152 -0
  253. parrot/loaders/markdown.py +442 -0
  254. parrot/loaders/pdf.py +373 -0
  255. parrot/loaders/pdfmark.py +320 -0
  256. parrot/loaders/pdftables.py +506 -0
  257. parrot/loaders/ppt.py +476 -0
  258. parrot/loaders/qa.py +63 -0
  259. parrot/loaders/splitters/__init__.py +10 -0
  260. parrot/loaders/splitters/base.py +138 -0
  261. parrot/loaders/splitters/md.py +228 -0
  262. parrot/loaders/splitters/token.py +143 -0
  263. parrot/loaders/txt.py +26 -0
  264. parrot/loaders/video.py +89 -0
  265. parrot/loaders/videolocal.py +218 -0
  266. parrot/loaders/videounderstanding.py +377 -0
  267. parrot/loaders/vimeo.py +167 -0
  268. parrot/loaders/web.py +599 -0
  269. parrot/loaders/youtube.py +504 -0
  270. parrot/manager/__init__.py +5 -0
  271. parrot/manager/manager.py +1030 -0
  272. parrot/mcp/__init__.py +28 -0
  273. parrot/mcp/adapter.py +105 -0
  274. parrot/mcp/cli.py +174 -0
  275. parrot/mcp/client.py +119 -0
  276. parrot/mcp/config.py +75 -0
  277. parrot/mcp/integration.py +842 -0
  278. parrot/mcp/oauth.py +933 -0
  279. parrot/mcp/server.py +225 -0
  280. parrot/mcp/transports/__init__.py +3 -0
  281. parrot/mcp/transports/base.py +279 -0
  282. parrot/mcp/transports/grpc_session.py +163 -0
  283. parrot/mcp/transports/http.py +312 -0
  284. parrot/mcp/transports/mcp.proto +108 -0
  285. parrot/mcp/transports/quic.py +1082 -0
  286. parrot/mcp/transports/sse.py +330 -0
  287. parrot/mcp/transports/stdio.py +309 -0
  288. parrot/mcp/transports/unix.py +395 -0
  289. parrot/mcp/transports/websocket.py +547 -0
  290. parrot/memory/__init__.py +16 -0
  291. parrot/memory/abstract.py +209 -0
  292. parrot/memory/agent.py +32 -0
  293. parrot/memory/cache.py +175 -0
  294. parrot/memory/core.py +555 -0
  295. parrot/memory/file.py +153 -0
  296. parrot/memory/mem.py +131 -0
  297. parrot/memory/redis.py +613 -0
  298. parrot/models/__init__.py +46 -0
  299. parrot/models/basic.py +118 -0
  300. parrot/models/compliance.py +208 -0
  301. parrot/models/crew.py +395 -0
  302. parrot/models/detections.py +654 -0
  303. parrot/models/generation.py +85 -0
  304. parrot/models/google.py +223 -0
  305. parrot/models/groq.py +23 -0
  306. parrot/models/openai.py +30 -0
  307. parrot/models/outputs.py +285 -0
  308. parrot/models/responses.py +938 -0
  309. parrot/notifications/__init__.py +743 -0
  310. parrot/openapi/__init__.py +3 -0
  311. parrot/openapi/components.yaml +641 -0
  312. parrot/openapi/config.py +322 -0
  313. parrot/outputs/__init__.py +32 -0
  314. parrot/outputs/formats/__init__.py +108 -0
  315. parrot/outputs/formats/altair.py +359 -0
  316. parrot/outputs/formats/application.py +122 -0
  317. parrot/outputs/formats/base.py +351 -0
  318. parrot/outputs/formats/bokeh.py +356 -0
  319. parrot/outputs/formats/card.py +424 -0
  320. parrot/outputs/formats/chart.py +436 -0
  321. parrot/outputs/formats/d3.py +255 -0
  322. parrot/outputs/formats/echarts.py +310 -0
  323. parrot/outputs/formats/generators/__init__.py +0 -0
  324. parrot/outputs/formats/generators/abstract.py +61 -0
  325. parrot/outputs/formats/generators/panel.py +145 -0
  326. parrot/outputs/formats/generators/streamlit.py +86 -0
  327. parrot/outputs/formats/generators/terminal.py +63 -0
  328. parrot/outputs/formats/holoviews.py +310 -0
  329. parrot/outputs/formats/html.py +147 -0
  330. parrot/outputs/formats/jinja2.py +46 -0
  331. parrot/outputs/formats/json.py +87 -0
  332. parrot/outputs/formats/map.py +933 -0
  333. parrot/outputs/formats/markdown.py +172 -0
  334. parrot/outputs/formats/matplotlib.py +237 -0
  335. parrot/outputs/formats/mixins/__init__.py +0 -0
  336. parrot/outputs/formats/mixins/emaps.py +855 -0
  337. parrot/outputs/formats/plotly.py +341 -0
  338. parrot/outputs/formats/seaborn.py +310 -0
  339. parrot/outputs/formats/table.py +397 -0
  340. parrot/outputs/formats/template_report.py +138 -0
  341. parrot/outputs/formats/yaml.py +125 -0
  342. parrot/outputs/formatter.py +152 -0
  343. parrot/outputs/templates/__init__.py +95 -0
  344. parrot/pipelines/__init__.py +0 -0
  345. parrot/pipelines/abstract.py +210 -0
  346. parrot/pipelines/detector.py +124 -0
  347. parrot/pipelines/models.py +90 -0
  348. parrot/pipelines/planogram.py +3002 -0
  349. parrot/pipelines/table.sql +97 -0
  350. parrot/plugins/__init__.py +106 -0
  351. parrot/plugins/importer.py +80 -0
  352. parrot/py.typed +0 -0
  353. parrot/registry/__init__.py +18 -0
  354. parrot/registry/registry.py +594 -0
  355. parrot/scheduler/__init__.py +1189 -0
  356. parrot/scheduler/models.py +60 -0
  357. parrot/security/__init__.py +16 -0
  358. parrot/security/prompt_injection.py +268 -0
  359. parrot/security/security_events.sql +25 -0
  360. parrot/services/__init__.py +1 -0
  361. parrot/services/mcp/__init__.py +8 -0
  362. parrot/services/mcp/config.py +13 -0
  363. parrot/services/mcp/server.py +295 -0
  364. parrot/services/o365_remote_auth.py +235 -0
  365. parrot/stores/__init__.py +7 -0
  366. parrot/stores/abstract.py +352 -0
  367. parrot/stores/arango.py +1090 -0
  368. parrot/stores/bigquery.py +1377 -0
  369. parrot/stores/cache.py +106 -0
  370. parrot/stores/empty.py +10 -0
  371. parrot/stores/faiss_store.py +1157 -0
  372. parrot/stores/kb/__init__.py +9 -0
  373. parrot/stores/kb/abstract.py +68 -0
  374. parrot/stores/kb/cache.py +165 -0
  375. parrot/stores/kb/doc.py +325 -0
  376. parrot/stores/kb/hierarchy.py +346 -0
  377. parrot/stores/kb/local.py +457 -0
  378. parrot/stores/kb/prompt.py +28 -0
  379. parrot/stores/kb/redis.py +659 -0
  380. parrot/stores/kb/store.py +115 -0
  381. parrot/stores/kb/user.py +374 -0
  382. parrot/stores/models.py +59 -0
  383. parrot/stores/pgvector.py +3 -0
  384. parrot/stores/postgres.py +2853 -0
  385. parrot/stores/utils/__init__.py +0 -0
  386. parrot/stores/utils/chunking.py +197 -0
  387. parrot/telemetry/__init__.py +3 -0
  388. parrot/telemetry/mixin.py +111 -0
  389. parrot/template/__init__.py +3 -0
  390. parrot/template/engine.py +259 -0
  391. parrot/tools/__init__.py +23 -0
  392. parrot/tools/abstract.py +644 -0
  393. parrot/tools/agent.py +363 -0
  394. parrot/tools/arangodbsearch.py +537 -0
  395. parrot/tools/arxiv_tool.py +188 -0
  396. parrot/tools/calculator/__init__.py +3 -0
  397. parrot/tools/calculator/operations/__init__.py +38 -0
  398. parrot/tools/calculator/operations/calculus.py +80 -0
  399. parrot/tools/calculator/operations/statistics.py +76 -0
  400. parrot/tools/calculator/tool.py +150 -0
  401. parrot/tools/cloudwatch.py +988 -0
  402. parrot/tools/codeinterpreter/__init__.py +127 -0
  403. parrot/tools/codeinterpreter/executor.py +371 -0
  404. parrot/tools/codeinterpreter/internals.py +473 -0
  405. parrot/tools/codeinterpreter/models.py +643 -0
  406. parrot/tools/codeinterpreter/prompts.py +224 -0
  407. parrot/tools/codeinterpreter/tool.py +664 -0
  408. parrot/tools/company_info/__init__.py +6 -0
  409. parrot/tools/company_info/tool.py +1138 -0
  410. parrot/tools/correlationanalysis.py +437 -0
  411. parrot/tools/database/abstract.py +286 -0
  412. parrot/tools/database/bq.py +115 -0
  413. parrot/tools/database/cache.py +284 -0
  414. parrot/tools/database/models.py +95 -0
  415. parrot/tools/database/pg.py +343 -0
  416. parrot/tools/databasequery.py +1159 -0
  417. parrot/tools/db.py +1800 -0
  418. parrot/tools/ddgo.py +370 -0
  419. parrot/tools/decorators.py +271 -0
  420. parrot/tools/dftohtml.py +282 -0
  421. parrot/tools/document.py +549 -0
  422. parrot/tools/ecs.py +819 -0
  423. parrot/tools/edareport.py +368 -0
  424. parrot/tools/elasticsearch.py +1049 -0
  425. parrot/tools/employees.py +462 -0
  426. parrot/tools/epson/__init__.py +96 -0
  427. parrot/tools/excel.py +683 -0
  428. parrot/tools/file/__init__.py +13 -0
  429. parrot/tools/file/abstract.py +76 -0
  430. parrot/tools/file/gcs.py +378 -0
  431. parrot/tools/file/local.py +284 -0
  432. parrot/tools/file/s3.py +511 -0
  433. parrot/tools/file/tmp.py +309 -0
  434. parrot/tools/file/tool.py +501 -0
  435. parrot/tools/file_reader.py +129 -0
  436. parrot/tools/flowtask/__init__.py +19 -0
  437. parrot/tools/flowtask/tool.py +761 -0
  438. parrot/tools/gittoolkit.py +508 -0
  439. parrot/tools/google/__init__.py +18 -0
  440. parrot/tools/google/base.py +169 -0
  441. parrot/tools/google/tools.py +1251 -0
  442. parrot/tools/googlelocation.py +5 -0
  443. parrot/tools/googleroutes.py +5 -0
  444. parrot/tools/googlesearch.py +5 -0
  445. parrot/tools/googlesitesearch.py +5 -0
  446. parrot/tools/googlevoice.py +2 -0
  447. parrot/tools/gvoice.py +695 -0
  448. parrot/tools/ibisworld/README.md +225 -0
  449. parrot/tools/ibisworld/__init__.py +11 -0
  450. parrot/tools/ibisworld/tool.py +366 -0
  451. parrot/tools/jiratoolkit.py +1718 -0
  452. parrot/tools/manager.py +1098 -0
  453. parrot/tools/math.py +152 -0
  454. parrot/tools/metadata.py +476 -0
  455. parrot/tools/msteams.py +1621 -0
  456. parrot/tools/msword.py +635 -0
  457. parrot/tools/multidb.py +580 -0
  458. parrot/tools/multistoresearch.py +369 -0
  459. parrot/tools/networkninja.py +167 -0
  460. parrot/tools/nextstop/__init__.py +4 -0
  461. parrot/tools/nextstop/base.py +286 -0
  462. parrot/tools/nextstop/employee.py +733 -0
  463. parrot/tools/nextstop/store.py +462 -0
  464. parrot/tools/notification.py +435 -0
  465. parrot/tools/o365/__init__.py +42 -0
  466. parrot/tools/o365/base.py +295 -0
  467. parrot/tools/o365/bundle.py +522 -0
  468. parrot/tools/o365/events.py +554 -0
  469. parrot/tools/o365/mail.py +992 -0
  470. parrot/tools/o365/onedrive.py +497 -0
  471. parrot/tools/o365/sharepoint.py +641 -0
  472. parrot/tools/openapi_toolkit.py +904 -0
  473. parrot/tools/openweather.py +527 -0
  474. parrot/tools/pdfprint.py +1001 -0
  475. parrot/tools/powerbi.py +518 -0
  476. parrot/tools/powerpoint.py +1113 -0
  477. parrot/tools/pricestool.py +146 -0
  478. parrot/tools/products/__init__.py +246 -0
  479. parrot/tools/prophet_tool.py +171 -0
  480. parrot/tools/pythonpandas.py +630 -0
  481. parrot/tools/pythonrepl.py +910 -0
  482. parrot/tools/qsource.py +436 -0
  483. parrot/tools/querytoolkit.py +395 -0
  484. parrot/tools/quickeda.py +827 -0
  485. parrot/tools/resttool.py +553 -0
  486. parrot/tools/retail/__init__.py +0 -0
  487. parrot/tools/retail/bby.py +528 -0
  488. parrot/tools/sandboxtool.py +703 -0
  489. parrot/tools/sassie/__init__.py +352 -0
  490. parrot/tools/scraping/__init__.py +7 -0
  491. parrot/tools/scraping/docs/select.md +466 -0
  492. parrot/tools/scraping/documentation.md +1278 -0
  493. parrot/tools/scraping/driver.py +436 -0
  494. parrot/tools/scraping/models.py +576 -0
  495. parrot/tools/scraping/options.py +85 -0
  496. parrot/tools/scraping/orchestrator.py +517 -0
  497. parrot/tools/scraping/readme.md +740 -0
  498. parrot/tools/scraping/tool.py +3115 -0
  499. parrot/tools/seasonaldetection.py +642 -0
  500. parrot/tools/shell_tool/__init__.py +5 -0
  501. parrot/tools/shell_tool/actions.py +408 -0
  502. parrot/tools/shell_tool/engine.py +155 -0
  503. parrot/tools/shell_tool/models.py +322 -0
  504. parrot/tools/shell_tool/tool.py +442 -0
  505. parrot/tools/site_search.py +214 -0
  506. parrot/tools/textfile.py +418 -0
  507. parrot/tools/think.py +378 -0
  508. parrot/tools/toolkit.py +298 -0
  509. parrot/tools/webapp_tool.py +187 -0
  510. parrot/tools/whatif.py +1279 -0
  511. parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
  512. parrot/tools/workday/__init__.py +6 -0
  513. parrot/tools/workday/models.py +1389 -0
  514. parrot/tools/workday/tool.py +1293 -0
  515. parrot/tools/yfinance_tool.py +306 -0
  516. parrot/tools/zipcode.py +217 -0
  517. parrot/utils/__init__.py +2 -0
  518. parrot/utils/helpers.py +73 -0
  519. parrot/utils/parsers/__init__.py +5 -0
  520. parrot/utils/parsers/toml.c +12078 -0
  521. parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
  522. parrot/utils/parsers/toml.pyx +21 -0
  523. parrot/utils/toml.py +11 -0
  524. parrot/utils/types.cpp +20936 -0
  525. parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
  526. parrot/utils/types.pyx +213 -0
  527. parrot/utils/uv.py +11 -0
  528. parrot/version.py +10 -0
  529. parrot/yaml-rs/Cargo.lock +350 -0
  530. parrot/yaml-rs/Cargo.toml +19 -0
  531. parrot/yaml-rs/pyproject.toml +19 -0
  532. parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
  533. parrot/yaml-rs/src/lib.rs +222 -0
  534. requirements/docker-compose.yml +24 -0
  535. requirements/requirements-dev.txt +21 -0
@@ -0,0 +1,1179 @@
1
+ """
2
+ Agent Crew with Finite State Machine Orchestration
3
+ ==================================================
4
+ Enhanced workflow orchestration using python-statemachine for complex agent flows
5
+ with conditional transitions, error handling, and state-based execution control.
6
+
7
+ Features:
8
+ - State-based agent lifecycle (idle → ready → running → completed/failed)
9
+ - Conditional transitions with custom predicates
10
+ - Error recovery with fallback agents
11
+ - Dynamic prompt building based on context and dependencies
12
+ - Shared results and context across agents
13
+ - On-success and on-error routing
14
+ """
15
+ from __future__ import annotations
16
+ from typing import (
17
+ List, Dict, Any, Union, Optional, Set, Callable, Iterable, Awaitable
18
+ )
19
+ from dataclasses import dataclass, field
20
+ from enum import Enum
21
+ import asyncio
22
+ import uuid
23
+ from datetime import datetime
24
+ from statemachine import State, StateMachine
25
+ from navconfig.logging import logging
26
+
27
+ from ..agent import BasicAgent
28
+ from ..abstract import AbstractBot
29
+ from ...tools.manager import ToolManager
30
+ from ...tools.agent import AgentContext
31
+ from ...models.responses import AIMessage, AgentResponse
32
+ from ...models.crew import (
33
+ CrewResult,
34
+ AgentExecutionInfo,
35
+ build_agent_metadata,
36
+ determine_run_status,
37
+ )
38
+
39
+
40
+ # Type aliases for better readability
41
+ AgentRef = Union[str, BasicAgent, AbstractBot]
42
+ DependencyResults = Dict[str, str]
43
+ PromptBuilder = Callable[[AgentContext, DependencyResults], Union[str, Awaitable[str]]]
44
+
45
+
46
+ class TransitionCondition(str, Enum):
47
+ """Predefined transition conditions."""
48
+ ON_SUCCESS = "on_success"
49
+ ON_ERROR = "on_error"
50
+ ON_TIMEOUT = "on_timeout"
51
+ ON_CONDITION = "on_condition" # Custom condition
52
+ ALWAYS = "always" # Unconditional transition
53
+
54
+
55
+ class AgentTaskMachine(StateMachine):
56
+ """
57
+ Finite state machine describing the lifecycle of an agent execution.
58
+
59
+ States:
60
+ idle: Agent is created but not scheduled
61
+ ready: All dependencies satisfied, ready to execute
62
+ running: Agent is currently executing
63
+ completed: Agent finished successfully
64
+ failed: Agent execution failed
65
+ blocked: Agent cannot proceed (missing dependencies or resources)
66
+
67
+ Transitions:
68
+ schedule: idle → ready (when dependencies are met)
69
+ start: ready → running (begin execution)
70
+ succeed: running → completed (successful completion)
71
+ fail: running/ready/idle → failed (error occurred)
72
+ block: idle/ready → blocked (dependencies not met)
73
+ unblock: blocked → ready (dependencies now satisfied)
74
+ retry: failed → ready (retry after failure)
75
+ """
76
+
77
+ idle = State("idle", initial=True)
78
+ ready = State("ready")
79
+ running = State("running")
80
+ completed = State("completed", final=True)
81
+ failed = State("failed")
82
+ blocked = State("blocked")
83
+
84
+ # Primary transitions
85
+ schedule = idle.to(ready)
86
+ start = ready.to(running)
87
+ succeed = running.to(completed)
88
+ fail = running.to(failed) | ready.to(failed) | idle.to(failed)
89
+ block = idle.to(blocked) | ready.to(blocked)
90
+ unblock = blocked.to(ready)
91
+ retry = failed.to(ready)
92
+
93
+ def __init__(self, agent_name: str, **kwargs):
94
+ self.agent_name = agent_name
95
+ super().__init__(**kwargs)
96
+
97
+ def on_enter_running(self):
98
+ """Called when entering running state."""
99
+ logging.debug(f"Agent {self.agent_name} started execution")
100
+
101
+ def on_enter_completed(self):
102
+ """Called when entering completed state."""
103
+ logging.info(f"Agent {self.agent_name} completed successfully")
104
+
105
+ def on_enter_failed(self):
106
+ """Called when entering failed state."""
107
+ logging.error(f"Agent {self.agent_name} execution failed")
108
+
109
+
110
+ @dataclass
111
+ class FlowTransition:
112
+ """
113
+ Represents a transition from one agent to another with conditions.
114
+
115
+ A transition defines:
116
+ - What triggers it (condition)
117
+ - Where it goes (target agents)
118
+ - How to prepare the input (instruction/prompt_builder)
119
+ - What to do on success/failure
120
+ """
121
+
122
+ source: str # Source agent name
123
+ targets: Set[str] # Target agent names
124
+ condition: TransitionCondition = TransitionCondition.ON_SUCCESS
125
+ instruction: Optional[str] = None
126
+ prompt_builder: Optional[PromptBuilder] = None
127
+ predicate: Optional[Callable[[Any], Union[bool, Awaitable[bool]]]] = None
128
+ priority: int = 0 # Higher priority transitions are evaluated first
129
+ metadata: Optional[AgentExecutionInfo] = None
130
+
131
+ async def should_activate(self, result: Any, error: Optional[Exception] = None) -> bool:
132
+ """
133
+ Determine if this transition should be activated based on the condition.
134
+
135
+ Args:
136
+ result: The result from the source agent
137
+ error: Any error that occurred during source agent execution
138
+
139
+ Returns:
140
+ True if the transition should be activated
141
+ """
142
+ if self.condition == TransitionCondition.ALWAYS:
143
+ return True
144
+
145
+ if self.condition == TransitionCondition.ON_SUCCESS:
146
+ return error is None
147
+
148
+ if self.condition == TransitionCondition.ON_ERROR:
149
+ return error is not None
150
+
151
+ if self.condition == TransitionCondition.ON_CONDITION and self.predicate:
152
+ pred_result = self.predicate(result)
153
+ if asyncio.iscoroutine(pred_result):
154
+ return await pred_result
155
+ return bool(pred_result)
156
+
157
+ return False
158
+
159
+ async def build_prompt(
160
+ self,
161
+ context: AgentContext,
162
+ dependencies: DependencyResults
163
+ ) -> str:
164
+ """
165
+ Build the prompt for target agents using the prompt_builder or instruction.
166
+
167
+ Args:
168
+ context: The execution context
169
+ dependencies: Results from dependency agents
170
+
171
+ Returns:
172
+ The constructed prompt string
173
+ """
174
+ if self.prompt_builder:
175
+ prompt = self.prompt_builder(context, dependencies)
176
+ return await prompt if asyncio.iscoroutine(prompt) else prompt
177
+
178
+ if self.instruction:
179
+ return self.instruction
180
+
181
+ # Default: use original query with dependency context
182
+ parts = [f"Task: {context.original_query}"]
183
+
184
+ if dependencies:
185
+ parts.append("\nContext from previous agents:")
186
+ for agent_name, result in dependencies.items():
187
+ parts.extend((f"\n--- {agent_name} ---", str(result)))
188
+
189
+ return "\n".join(parts)
190
+
191
+
192
+ @dataclass
193
+ class FlowNode:
194
+ """
195
+ Represents an agent in the FSM-based workflow.
196
+
197
+ A FlowNode wraps an agent with:
198
+ - State machine for lifecycle management
199
+ - Dependencies on other agents
200
+ - Transitions to other agents
201
+ - Execution metadata and results
202
+ """
203
+
204
+ agent: Union[BasicAgent, AbstractBot]
205
+ fsm: AgentTaskMachine
206
+ dependencies: Set[str] = field(default_factory=set)
207
+ outgoing_transitions: List[FlowTransition] = field(default_factory=list)
208
+ result: Optional[Any] = None
209
+ response: Optional[Any] = None
210
+ error: Optional[Exception] = None
211
+ execution_time: float = 0.0
212
+ started_at: Optional[datetime] = None
213
+ completed_at: Optional[datetime] = None
214
+ retry_count: int = 0
215
+ max_retries: int = 3
216
+ metadata: Dict[str, Any] = field(default_factory=dict)
217
+ agent_info: Optional[AgentExecutionInfo] = None
218
+ transitions_processed: bool = False # Track if transitions have been activated
219
+
220
+ @property
221
+ def name(self) -> str:
222
+ """Get the agent name."""
223
+ return self.agent.name
224
+
225
+ @property
226
+ def is_terminal(self) -> bool:
227
+ """Check if this node has no outgoing transitions."""
228
+ return len(self.outgoing_transitions) == 0
229
+
230
+ @property
231
+ def can_retry(self) -> bool:
232
+ """Check if this node can be retried."""
233
+ return self.retry_count < self.max_retries and self.fsm.current_state == self.fsm.failed
234
+
235
+ def add_transition(self, transition: FlowTransition) -> None:
236
+ """Add an outgoing transition from this node."""
237
+ self.outgoing_transitions.append(transition)
238
+ # Sort by priority (descending)
239
+ self.outgoing_transitions.sort(key=lambda t: t.priority, reverse=True)
240
+
241
+ async def get_active_transitions(
242
+ self,
243
+ error: Optional[Exception] = None
244
+ ) -> List[FlowTransition]:
245
+ """
246
+ Get all transitions that should be activated based on current result/error.
247
+
248
+ Returns:
249
+ List of transitions that match their activation conditions
250
+ """
251
+ active = []
252
+ for transition in self.outgoing_transitions:
253
+ if await transition.should_activate(self.result, error):
254
+ active.append(transition)
255
+ return active
256
+
257
+ async def execute(self, prompt:str, ctx: Dict[str, Any]) -> Any:
258
+ """Execute the agent with context from previous agents."""
259
+ return await self.agent.ask(
260
+ question=prompt,
261
+ **ctx
262
+ )
263
+
264
+
265
+ class AgentsFlow:
266
+ """
267
+ Enhanced Agent Crew with Finite State Machine orchestration.
268
+
269
+ This implementation provides sophisticated workflow control with:
270
+ - State-based agent lifecycle management
271
+ - Conditional transitions (on_success, on_error, custom conditions)
272
+ - Error recovery and retry logic
273
+ - Dynamic prompt building
274
+ - Parallel execution where possible
275
+ - Detailed execution tracking and logging
276
+
277
+ Example:
278
+ >>> crew = AgentsFlow(name="ResearchCrew")
279
+ >>>
280
+ >>> # Add agents
281
+ >>> researcher = crew.add_agent(research_agent)
282
+ >>> analyzer = crew.add_agent(analysis_agent)
283
+ >>> writer = crew.add_agent(writer_agent)
284
+ >>> error_handler = crew.add_agent(recovery_agent)
285
+ >>>
286
+ >>> # Define flow with conditional transitions
287
+ >>> crew.task_flow(
288
+ ... source=researcher,
289
+ ... targets=analyzer,
290
+ ... instruction="Analyze the research findings"
291
+ ... )
292
+ >>>
293
+ >>> # Add error handling
294
+ >>> crew.task_flow(
295
+ ... source=analyzer,
296
+ ... targets=error_handler,
297
+ ... condition=TransitionCondition.ON_ERROR
298
+ ... )
299
+ >>>
300
+ >>> # Execute workflow
301
+ >>> result = await crew.run_flow("Research AI trends in 2025")
302
+ """
303
+
304
+ def __init__(
305
+ self,
306
+ name: str = "AgentsFlow",
307
+ agents: Optional[List[Union[BasicAgent, AbstractBot]]] = None,
308
+ shared_tool_manager: Optional[ToolManager] = None,
309
+ max_parallel_tasks: int = 10,
310
+ default_max_retries: int = 3,
311
+ execution_timeout: Optional[float] = None,
312
+ truncation_length: Optional[int] = None,
313
+ ):
314
+ """
315
+ Initialize the FSM-based Agent Crew.
316
+
317
+ Args:
318
+ name: Name of the crew
319
+ agents: List of agents to add initially
320
+ shared_tool_manager: Shared tool manager for all agents
321
+ max_parallel_tasks: Maximum concurrent agent executions
322
+ default_max_retries: Default retry count for failed agents
323
+ execution_timeout: Maximum time (seconds) for workflow execution
324
+ """
325
+ self.name = name
326
+ self.nodes: Dict[str, FlowNode] = {}
327
+ self.shared_tool_manager = shared_tool_manager or ToolManager()
328
+ self.max_parallel_tasks = max_parallel_tasks
329
+ self.default_max_retries = default_max_retries
330
+ self.execution_timeout = execution_timeout
331
+ self.logger = logging.getLogger(f"parrot.crews.fsm.{self.name}")
332
+ self.semaphore = asyncio.Semaphore(max_parallel_tasks)
333
+ self.truncation_length = truncation_length or 200
334
+ # Execution tracking
335
+ self.execution_log: List[Dict[str, Any]] = []
336
+ self.current_context: Optional[AgentContext] = None
337
+ self._agent_locks: Dict[int, asyncio.Lock] = {}
338
+
339
+ # Add initial agents
340
+ if agents:
341
+ for agent in agents:
342
+ self.add_agent(agent)
343
+
344
+ def add_agent(
345
+ self,
346
+ agent: Union[BasicAgent, AbstractBot],
347
+ agent_id: Optional[str] = None,
348
+ max_retries: Optional[int] = None
349
+ ) -> FlowNode:
350
+ """
351
+ Add an agent to the crew and return its FlowNode.
352
+
353
+ Args:
354
+ agent: The agent to add
355
+ agent_id: Optional custom ID (defaults to agent.name)
356
+ max_retries: Maximum retry attempts for this agent
357
+
358
+ Returns:
359
+ The created FlowNode for this agent
360
+ """
361
+ agent_id = agent_id or agent.name
362
+
363
+ if agent_id in self.nodes:
364
+ self.logger.warning(f"Agent '{agent_id}' already exists, skipping")
365
+ return self.nodes[agent_id]
366
+
367
+ # Create FSM for this agent
368
+ fsm = AgentTaskMachine(agent_name=agent_id)
369
+
370
+ # Create FlowNode
371
+ node = FlowNode(
372
+ agent=agent,
373
+ fsm=fsm,
374
+ max_retries=max_retries or self.default_max_retries
375
+ )
376
+
377
+ self.nodes[agent_id] = node
378
+
379
+ # Share tools with new agent
380
+ if self.shared_tool_manager:
381
+ for tool_name in self.shared_tool_manager.list_tools():
382
+ tool = self.shared_tool_manager.get_tool(tool_name)
383
+ if tool and not agent.tool_manager.get_tool(tool_name):
384
+ agent.tool_manager.add_tool(tool, tool_name)
385
+
386
+ self.logger.info(f"Added agent '{agent_id}' to crew")
387
+ return node
388
+
389
+ def _resolve_agent_ref(self, ref: AgentRef) -> str:
390
+ """Convert an AgentRef to an agent name string."""
391
+ return ref if isinstance(ref, str) else ref.name
392
+
393
+ def task_flow(
394
+ self,
395
+ source: AgentRef,
396
+ targets: Optional[Union[AgentRef, Iterable[AgentRef]]] = None,
397
+ *,
398
+ condition: TransitionCondition = TransitionCondition.ON_SUCCESS,
399
+ instruction: Optional[str] = None,
400
+ prompt_builder: Optional[PromptBuilder] = None,
401
+ predicate: Optional[Callable[[Any], Union[bool, Awaitable[bool]]]] = None,
402
+ priority: int = 0,
403
+ **metadata
404
+ ) -> FlowNode:
405
+ """
406
+ Define transitions from source agent to target agent(s).
407
+
408
+ This method builds the workflow graph by defining how agents connect
409
+ and under what conditions. It supports:
410
+ - Unconditional transitions (always execute targets after source)
411
+ - Success-based transitions (only execute targets if source succeeds)
412
+ - Error-based transitions (only execute targets if source fails)
413
+ - Custom conditional transitions (with predicate function)
414
+
415
+ Args:
416
+ source: Source agent (agent object, name, or FlowNode)
417
+ targets: Target agent(s) to transition to (None for terminal node)
418
+ condition: When to activate this transition
419
+ instruction: Static instruction/prompt for target agents
420
+ prompt_builder: Dynamic prompt builder function
421
+ predicate: Custom condition predicate for ON_CONDITION transitions
422
+ priority: Transition priority (higher = evaluated first)
423
+ **metadata: Additional metadata for this transition
424
+
425
+ Returns:
426
+ The source FlowNode for method chaining
427
+
428
+ Examples:
429
+ # Simple success-based transition
430
+ crew.task_flow(researcher, analyzer)
431
+
432
+ # Error handling transition
433
+ crew.task_flow(
434
+ analyzer,
435
+ error_handler,
436
+ condition=TransitionCondition.ON_ERROR,
437
+ instruction="Handle the error and retry"
438
+ )
439
+
440
+ # Conditional branching with predicate
441
+ crew.task_flow(
442
+ classifier,
443
+ [processor_a, processor_b],
444
+ condition=TransitionCondition.ON_CONDITION,
445
+ predicate=lambda result: "category_a" in result.lower()
446
+ )
447
+
448
+ # Dynamic prompt building
449
+ def build_analysis_prompt(ctx, deps):
450
+ research = deps.get('researcher', '')
451
+ return f"Analyze this research in detail:\n{research}"
452
+
453
+ crew.task_flow(
454
+ researcher,
455
+ analyzer,
456
+ prompt_builder=build_analysis_prompt
457
+ )
458
+
459
+ # Method chaining for complex flows
460
+ crew.task_flow(start, process).task_flow(process, analyze).task_flow(analyze, end)
461
+ """
462
+ source_name = self._resolve_agent_ref(source)
463
+
464
+ if source_name not in self.nodes:
465
+ raise ValueError(f"Source agent '{source_name}' not found in crew")
466
+
467
+ source_node = self.nodes[source_name]
468
+
469
+ # Handle terminal node (no targets)
470
+ if targets is None:
471
+ self.logger.info(f"Agent '{source_name}' is a terminal node")
472
+ return source_node
473
+
474
+ # Normalize targets to a list
475
+ if not isinstance(targets, (list, tuple, set)):
476
+ targets = [targets]
477
+
478
+ target_names = {self._resolve_agent_ref(t) for t in targets}
479
+
480
+ # Validate all targets exist
481
+ for target_name in target_names:
482
+ if target_name not in self.nodes:
483
+ raise ValueError(f"Target agent '{target_name}' not found in crew")
484
+
485
+ # Create transition
486
+ transition = FlowTransition(
487
+ source=source_name,
488
+ targets=target_names,
489
+ condition=condition,
490
+ instruction=instruction,
491
+ prompt_builder=prompt_builder,
492
+ predicate=predicate,
493
+ priority=priority,
494
+ metadata=metadata
495
+ )
496
+
497
+ source_node.add_transition(transition)
498
+
499
+ # Update dependencies in target nodes
500
+ for target_name in target_names:
501
+ target_node = self.nodes[target_name]
502
+
503
+ if self._would_create_cycle(source_name, target_name):
504
+ self.logger.debug(
505
+ "Skipping dependency %s → %s to avoid circular dependency",
506
+ source_name,
507
+ target_name
508
+ )
509
+ continue
510
+
511
+ target_node.dependencies.add(source_name)
512
+
513
+ self.logger.info(
514
+ f"Added {condition.value} transition: {source_name} → {target_names}"
515
+ )
516
+
517
+ return source_node
518
+
519
+ def on_success(
520
+ self,
521
+ source: AgentRef,
522
+ targets: Union[AgentRef, Iterable[AgentRef]],
523
+ **kwargs
524
+ ) -> FlowNode:
525
+ """Convenience method for ON_SUCCESS transitions."""
526
+ return self.task_flow(
527
+ source,
528
+ targets,
529
+ condition=TransitionCondition.ON_SUCCESS,
530
+ **kwargs
531
+ )
532
+
533
+ def on_error(
534
+ self,
535
+ source: AgentRef,
536
+ targets: Union[AgentRef, Iterable[AgentRef]],
537
+ **kwargs
538
+ ) -> FlowNode:
539
+ """Convenience method for ON_ERROR transitions."""
540
+ return self.task_flow(
541
+ source,
542
+ targets,
543
+ condition=TransitionCondition.ON_ERROR,
544
+ **kwargs
545
+ )
546
+
547
+ def on_condition(
548
+ self,
549
+ source: AgentRef,
550
+ targets: Union[AgentRef, Iterable[AgentRef]],
551
+ predicate: Callable[[Any], Union[bool, Awaitable[bool]]],
552
+ **kwargs
553
+ ) -> FlowNode:
554
+ """Convenience method for ON_CONDITION transitions."""
555
+ return self.task_flow(
556
+ source,
557
+ targets,
558
+ condition=TransitionCondition.ON_CONDITION,
559
+ predicate=predicate,
560
+ **kwargs
561
+ )
562
+
563
+ async def run_flow(
564
+ self,
565
+ initial_task: str,
566
+ entry_point: Optional[AgentRef] = None,
567
+ user_id: Optional[str] = None,
568
+ session_id: Optional[str] = None,
569
+ max_iterations: int = 100,
570
+ **shared_data
571
+ ) -> CrewResult:
572
+ """
573
+ Execute the workflow starting from the entry point.
574
+
575
+ The workflow execution follows these steps:
576
+ 1. Initialize all agents to idle state
577
+ 2. Schedule entry point agent(s) to ready state
578
+ 3. Execute ready agents (respecting max_parallel_tasks)
579
+ 4. Evaluate transitions based on results/errors
580
+ 5. Schedule next agents based on activated transitions
581
+ 6. Repeat until all terminal nodes complete or max_iterations reached
582
+
583
+ Args:
584
+ initial_task: The initial task/prompt for the workflow
585
+ entry_point: Starting agent(s) (defaults to agents with no dependencies)
586
+ user_id: User identifier for tracking
587
+ session_id: Session identifier
588
+ max_iterations: Maximum execution rounds (safety limit)
589
+ **shared_data: Additional shared data for all agents
590
+
591
+ Returns:
592
+ CrewResult: Standardized execution payload containing outputs,
593
+ metadata, and execution logs.
594
+
595
+ Raises:
596
+ RuntimeError: If workflow gets stuck or exceeds max_iterations
597
+ TimeoutError: If execution_timeout is exceeded
598
+ """
599
+ session_id = session_id or str(uuid.uuid4())
600
+ user_id = user_id or 'crew_user'
601
+
602
+ # Initialize execution context
603
+ self.current_context = AgentContext(
604
+ user_id=user_id,
605
+ session_id=session_id,
606
+ original_query=initial_task,
607
+ shared_data=shared_data,
608
+ agent_results={}
609
+ )
610
+
611
+ self.execution_log = []
612
+ start_time = asyncio.get_event_loop().time()
613
+
614
+ # Reset all agents to idle state by creating new FSM instances
615
+ for node in self.nodes.values():
616
+ # Create a new FSM instance to reset to initial state
617
+ node.fsm = AgentTaskMachine(agent_name=node.agent.name)
618
+ node.result = None
619
+ node.response = None
620
+ node.error = None
621
+ node.retry_count = 0
622
+ node.transitions_processed = False # Reset transition processing flag
623
+ node.metadata = None
624
+
625
+ # Determine entry points
626
+ entry_agents = self._get_entry_agents(entry_point)
627
+
628
+ if not entry_agents:
629
+ raise ValueError("No entry point agents found. Specify entry_point or add agents with no dependencies.")
630
+
631
+ self.logger.info(f"Starting workflow with entry points: {entry_agents}")
632
+
633
+ # Schedule entry point agents
634
+ for agent_name in entry_agents:
635
+ node = self.nodes[agent_name]
636
+ node.fsm.schedule()
637
+
638
+ # Main execution loop
639
+ iteration = 0
640
+ while iteration < max_iterations:
641
+ # Check timeout
642
+ if self.execution_timeout:
643
+ elapsed = asyncio.get_event_loop().time() - start_time
644
+ if elapsed > self.execution_timeout:
645
+ raise TimeoutError(
646
+ f"Workflow execution exceeded timeout of {self.execution_timeout}s"
647
+ )
648
+
649
+ # Get agents ready to execute
650
+ ready_agents = self._get_ready_agents()
651
+
652
+ if not ready_agents:
653
+ # Check if we're done
654
+ if self._is_workflow_complete():
655
+ break
656
+
657
+ # Check if we're stuck
658
+ if not self._has_active_agents():
659
+ raise RuntimeError(
660
+ f"Workflow is stuck at iteration {iteration}. "
661
+ f"No ready agents and no active agents. "
662
+ f"This may indicate missing transitions or unsatisfied dependencies."
663
+ )
664
+
665
+ # Wait for active agents
666
+ await asyncio.sleep(0.1)
667
+ iteration += 1
668
+ continue
669
+
670
+ # Execute ready agents in parallel
671
+ await self._execute_agents_parallel(ready_agents)
672
+
673
+ # Process completed agents and activate transitions
674
+ await self._process_transitions()
675
+
676
+ iteration += 1
677
+
678
+ if iteration >= max_iterations:
679
+ raise RuntimeError(
680
+ f"Workflow exceeded max iterations ({max_iterations}). "
681
+ f"Possible infinite loop or very complex workflow."
682
+ )
683
+
684
+ end_time = asyncio.get_event_loop().time()
685
+
686
+ agent_ids: List[str] = []
687
+ for entry in self.execution_log:
688
+ agent_name = entry.get("agent_id") or entry.get("agent_name")
689
+ if agent_name and agent_name not in agent_ids:
690
+ agent_ids.append(agent_name)
691
+
692
+ for agent_name in self.nodes:
693
+ if agent_name not in agent_ids:
694
+ agent_ids.append(agent_name)
695
+
696
+ responses: Dict[str, Any] = {}
697
+ agents_info: List[AgentExecutionInfo] = []
698
+ results_payload: List[Any] = []
699
+ errors: Dict[str, str] = {}
700
+ last_output: Optional[Any] = None
701
+
702
+ for agent_name in agent_ids:
703
+ node = self.nodes.get(agent_name)
704
+ if not node:
705
+ continue
706
+
707
+ if node.result is not None:
708
+ results_payload.append(node.result)
709
+ responses[agent_name] = node.response
710
+ metadata = (
711
+ node.metadata
712
+ if isinstance(node.metadata, AgentExecutionInfo)
713
+ else build_agent_metadata(
714
+ agent_name,
715
+ node.agent,
716
+ node.response,
717
+ node.result,
718
+ node.execution_time,
719
+ 'completed'
720
+ )
721
+ )
722
+ agents_info.append(metadata)
723
+ last_output = node.result
724
+ else:
725
+ results_payload.append(node.result)
726
+ responses[agent_name] = node.response
727
+ status_value = 'failed' if node.error is not None else 'pending'
728
+ error_message = str(node.error) if node.error else None
729
+ if error_message:
730
+ errors[agent_name] = error_message
731
+ metadata = (
732
+ node.metadata
733
+ if isinstance(node.metadata, AgentExecutionInfo)
734
+ else build_agent_metadata(
735
+ agent_name,
736
+ node.agent,
737
+ node.response,
738
+ node.result,
739
+ node.execution_time,
740
+ status_value,
741
+ error_message
742
+ )
743
+ )
744
+ agents_info.append(metadata)
745
+
746
+ success_count = sum(info.status == 'completed' for info in agents_info)
747
+ failure_count = sum(info.status == 'failed' for info in agents_info)
748
+ status = determine_run_status(success_count, failure_count)
749
+
750
+ # Get final output from terminal nodes
751
+ terminal_results = [
752
+ node.result
753
+ for node in self.nodes.values()
754
+ if node.is_terminal and node.fsm.current_state == node.fsm.completed
755
+ ]
756
+ final_output = terminal_results[-1] if terminal_results else ''
757
+
758
+ return CrewResult(
759
+ output=final_output or last_output,
760
+ response=responses,
761
+ results=results_payload,
762
+ agent_ids=agent_ids,
763
+ agents=agents_info,
764
+ errors=errors,
765
+ execution_log=self.execution_log,
766
+ total_time=end_time - start_time,
767
+ status=status,
768
+ metadata={
769
+ 'mode': 'fsm',
770
+ 'iterations': iteration,
771
+ 'completed': success_count,
772
+ 'failed': failure_count
773
+ }
774
+ )
775
+
776
+
777
+ def _get_entry_agents(self, entry_point: Optional[AgentRef]) -> Set[str]:
778
+ """Determine which agents should be entry points."""
779
+ if entry_point:
780
+ if isinstance(entry_point, (list, tuple, set)):
781
+ return {self._resolve_agent_ref(e) for e in entry_point}
782
+ return {self._resolve_agent_ref(entry_point)}
783
+
784
+ # Find agents with no dependencies
785
+ return {
786
+ name for name, node in self.nodes.items()
787
+ if not node.dependencies
788
+ }
789
+
790
+ def _get_ready_agents(self) -> Set[str]:
791
+ """Get all agents in ready state."""
792
+ return {
793
+ name for name, node in self.nodes.items()
794
+ if node.fsm.current_state == node.fsm.ready
795
+ }
796
+
797
+ def _has_active_agents(self) -> bool:
798
+ """Check if any agents are currently running."""
799
+ return any(
800
+ node.fsm.current_state == node.fsm.running
801
+ for node in self.nodes.values()
802
+ )
803
+
804
+ def _truncate_text(self, text: Optional[str], *, enabled: bool = True) -> str:
805
+ """Truncate text using configured length."""
806
+ if text is None or not enabled:
807
+ return text or ""
808
+
809
+ if self.truncation_length is None or self.truncation_length <= 0:
810
+ return text
811
+
812
+ if len(text) <= self.truncation_length:
813
+ return text
814
+
815
+ return f"{text[:self.truncation_length]}..."
816
+
817
+ def _is_workflow_complete(self) -> bool:
818
+ """Check if all terminal nodes have completed or failed (without retries)."""
819
+ terminal_nodes = [
820
+ node for node in self.nodes.values() if node.is_terminal
821
+ ]
822
+
823
+ if terminal_nodes:
824
+ # Terminal nodes are complete if they're in completed state OR
825
+ # in failed state with no retries remaining
826
+ return all(
827
+ node.fsm.current_state == node.fsm.completed or
828
+ (node.fsm.current_state == node.fsm.failed and not node.can_retry)
829
+ for node in terminal_nodes
830
+ )
831
+
832
+ # If no terminal nodes defined, check if all nodes are done
833
+ return all(
834
+ node.fsm.current_state == node.fsm.completed or
835
+ (node.fsm.current_state == node.fsm.failed and not node.can_retry)
836
+ for node in self.nodes.values()
837
+ )
838
+
839
+ async def _execute_agents_parallel(self, agent_names: Set[str]) -> None:
840
+ """Execute multiple agents in parallel."""
841
+ tasks = []
842
+
843
+ for agent_name in agent_names:
844
+ node = self.nodes[agent_name]
845
+ node.fsm.start() # Transition to running state
846
+ tasks.append(self._execute_single_agent(agent_name))
847
+
848
+ await asyncio.gather(*tasks, return_exceptions=True)
849
+
850
+ async def _execute_single_agent(self, agent_name: str) -> None:
851
+ """Execute a single agent and update its state."""
852
+ node = self.nodes[agent_name]
853
+ node.started_at = datetime.now()
854
+
855
+ try:
856
+ # Ensure agent is configured
857
+ await self._ensure_agent_ready(node.agent)
858
+
859
+ # Build prompt based on dependencies
860
+ prompt = await self._build_agent_prompt(node)
861
+
862
+ # Execute with semaphore for rate limiting
863
+ async with self.semaphore:
864
+ start_time = asyncio.get_event_loop().time()
865
+
866
+ response = await node.execute(
867
+ prompt=prompt,
868
+ ctx={
869
+ "session_id": self.current_context.session_id,
870
+ "user_id": self.current_context.user_id,
871
+ **self.current_context.shared_data
872
+ }
873
+ )
874
+
875
+ end_time = asyncio.get_event_loop().time()
876
+ node.execution_time = end_time - start_time
877
+
878
+ # Extract result
879
+ result = self._extract_result(response)
880
+ node.result = result
881
+ node.response = response
882
+ node.completed_at = datetime.now()
883
+ # Build agent execution info
884
+ node.agent_info = build_agent_metadata(
885
+ agent_id=agent_name,
886
+ agent=node.agent,
887
+ response=response,
888
+ output=result,
889
+ execution_time=node.execution_time,
890
+ status='completed',
891
+ error=None
892
+ )
893
+
894
+ # Store in context
895
+ self.current_context.agent_results[agent_name] = result
896
+
897
+ # Transition to completed
898
+ node.fsm.succeed()
899
+
900
+ # Log execution
901
+ self.execution_log.append({
902
+ "agent_id": agent_name,
903
+ "agent_name": agent_name,
904
+ "state": "completed",
905
+ "execution_time": node.execution_time,
906
+ "input": self._truncate_text(prompt),
907
+ "output": self._truncate_text(result),
908
+ "started_at": node.started_at.isoformat(),
909
+ "completed_at": node.completed_at.isoformat(),
910
+ "result_length": len(str(result)),
911
+ "success": True
912
+ })
913
+
914
+ self.logger.info(
915
+ f"Agent '{agent_name}' completed in {node.execution_time:.2f}s"
916
+ )
917
+
918
+ except Exception as e:
919
+ node.error = e
920
+ node.completed_at = datetime.now()
921
+ node.fsm.fail()
922
+ node.response = None
923
+ # Build agent execution info for failure
924
+ node.agent_info = build_agent_metadata(
925
+ agent_id=agent_name,
926
+ agent=node.agent,
927
+ response=None,
928
+ output=None,
929
+ execution_time=node.execution_time or 0.0,
930
+ status='failed',
931
+ error=str(e)
932
+ )
933
+
934
+ self.execution_log.append({
935
+ "agent_id": agent_name,
936
+ "agent_name": agent_name,
937
+ "state": "failed",
938
+ "error": str(e),
939
+ "started_at": node.started_at.isoformat() if node.started_at else None,
940
+ "completed_at": node.completed_at.isoformat(),
941
+ "retry_count": node.retry_count,
942
+ "success": False
943
+ })
944
+
945
+ self.logger.error(
946
+ f"Agent '{agent_name}' failed: {e}",
947
+ exc_info=True
948
+ )
949
+
950
+ async def _process_transitions(self) -> None:
951
+ """Process transitions for all completed/failed agents."""
952
+ for agent_name, node in self.nodes.items():
953
+ # Only process nodes that just completed or failed AND haven't been processed yet
954
+ if node.fsm.current_state not in [node.fsm.completed, node.fsm.failed]:
955
+ continue
956
+
957
+ # Skip if transitions already processed
958
+ if node.transitions_processed:
959
+ continue
960
+
961
+ # Get active transitions
962
+ error = node.error if node.fsm.current_state == node.fsm.failed else None
963
+ active_transitions = await node.get_active_transitions(error)
964
+
965
+ if not active_transitions:
966
+ # Check for retry on failure
967
+ if node.fsm.current_state == node.fsm.failed and node.can_retry:
968
+ self.logger.info(
969
+ f"Retrying agent '{agent_name}' (attempt {node.retry_count + 1}/{node.max_retries})"
970
+ )
971
+ node.retry_count += 1
972
+ node.fsm.retry()
973
+ # Don't mark as processed - allow retry to execute
974
+ node.transitions_processed = False
975
+ else:
976
+ # Mark as processed if no transitions and no retry
977
+ node.transitions_processed = True
978
+ continue
979
+
980
+ # Activate transitions
981
+ transition_activated = False
982
+ for transition in active_transitions:
983
+ if await self._activate_transition(transition):
984
+ transition_activated = True
985
+
986
+ # Mark transitions as processed only when activation succeeded
987
+ node.transitions_processed = transition_activated
988
+
989
+ async def _activate_transition(self, transition: FlowTransition) -> bool:
990
+ """Activate a transition and schedule target agents.
991
+
992
+ Returns:
993
+ True if at least one target agent was scheduled or reactivated.
994
+ """
995
+ scheduled_any = False
996
+
997
+ for target_name in transition.targets:
998
+ target_node = self.nodes[target_name]
999
+ scheduled = False
1000
+
1001
+ if not self._are_dependencies_satisfied(target_node):
1002
+ self.logger.debug(
1003
+ "Dependencies for '%s' not yet satisfied after transition from '%s'",
1004
+ target_name,
1005
+ transition.source
1006
+ )
1007
+ continue
1008
+
1009
+ if target_node.fsm.current_state == target_node.fsm.idle:
1010
+ target_node.fsm.schedule()
1011
+ scheduled = True
1012
+ elif target_node.fsm.current_state == target_node.fsm.blocked:
1013
+ target_node.fsm.unblock()
1014
+ scheduled = True
1015
+ elif target_node.fsm.current_state == target_node.fsm.failed and target_node.can_retry:
1016
+ target_node.fsm.retry()
1017
+ scheduled = True
1018
+ elif target_node.fsm.current_state == target_node.fsm.ready:
1019
+ scheduled = True
1020
+
1021
+ if scheduled:
1022
+ self.logger.debug(
1023
+ f"Scheduled agent '{target_name}' via transition from '{transition.source}'"
1024
+ )
1025
+ scheduled_any = True
1026
+
1027
+ return scheduled_any
1028
+
1029
+ def _would_create_cycle(self, source_name: str, target_name: str) -> bool:
1030
+ """Check if adding a dependency would introduce a cycle."""
1031
+ if source_name == target_name:
1032
+ return True
1033
+
1034
+ visited = set()
1035
+ stack = [source_name]
1036
+
1037
+ while stack:
1038
+ current = stack.pop()
1039
+ if current == target_name:
1040
+ return True
1041
+ if current in visited:
1042
+ continue
1043
+ visited.add(current)
1044
+
1045
+ current_node = self.nodes.get(current)
1046
+ if not current_node:
1047
+ continue
1048
+
1049
+ stack.extend(current_node.dependencies)
1050
+
1051
+ return False
1052
+
1053
+ def _are_dependencies_satisfied(self, node: FlowNode) -> bool:
1054
+ """Check if all dependencies for a node are satisfied."""
1055
+ for dep_name in node.dependencies:
1056
+ dep_node = self.nodes[dep_name]
1057
+ if dep_node.fsm.current_state != dep_node.fsm.completed:
1058
+ return False
1059
+ return True
1060
+
1061
+ async def _build_agent_prompt(self, node: FlowNode) -> str:
1062
+ """Build the prompt for an agent based on its dependencies and transitions."""
1063
+ # Gather results from dependencies
1064
+ dependencies = {}
1065
+ for dep_name in node.dependencies:
1066
+ dep_node = self.nodes[dep_name]
1067
+ if dep_node.result is not None:
1068
+ dependencies[dep_name] = dep_node.result
1069
+
1070
+ # Find the transition that activated this agent
1071
+ activating_transition = None
1072
+ for dep_name in node.dependencies:
1073
+ dep_node = self.nodes[dep_name]
1074
+ for transition in dep_node.outgoing_transitions:
1075
+ if node.name in transition.targets:
1076
+ activating_transition = transition
1077
+ break
1078
+ if activating_transition:
1079
+ break
1080
+
1081
+ # Use transition's prompt builder if available
1082
+ if activating_transition:
1083
+ return await activating_transition.build_prompt(
1084
+ self.current_context,
1085
+ dependencies
1086
+ )
1087
+
1088
+ # Default prompt building
1089
+ if not dependencies:
1090
+ return self.current_context.original_query
1091
+
1092
+ parts = [
1093
+ f"Task: {self.current_context.original_query}\n",
1094
+ "\nContext from previous agents:",
1095
+ ]
1096
+
1097
+ for dep_name, result in dependencies.items():
1098
+ parts.extend((f"\n--- {dep_name} ---", str(result)))
1099
+
1100
+ return "\n".join(parts)
1101
+
1102
+ async def _ensure_agent_ready(self, agent: Union[BasicAgent, AbstractBot]) -> None:
1103
+ """Ensure agent is configured before execution."""
1104
+ if hasattr(agent, "is_configured") and agent.is_configured:
1105
+ return
1106
+
1107
+ agent_id = id(agent)
1108
+ lock = self._agent_locks.get(agent_id)
1109
+ if lock is None:
1110
+ lock = asyncio.Lock()
1111
+ self._agent_locks[agent_id] = lock
1112
+
1113
+ async with lock:
1114
+ if not (hasattr(agent, "is_configured") and agent.is_configured):
1115
+ self.logger.info(f"Auto-configuring agent '{agent.name}'")
1116
+ await agent.configure()
1117
+
1118
+ def _extract_result(self, response: Any) -> str:
1119
+ """Extract result string from response."""
1120
+ if isinstance(response, (AIMessage, AgentResponse)) or hasattr(response, 'content'):
1121
+ return response.content
1122
+ return str(response)
1123
+
1124
+ def visualize_workflow(self, format: str = "mermaid") -> str:
1125
+ """
1126
+ Generate a visual representation of the workflow.
1127
+
1128
+ Args:
1129
+ format: Output format ("mermaid" for Mermaid diagrams)
1130
+
1131
+ Returns:
1132
+ String representation of the workflow diagram
1133
+ """
1134
+ if format == "mermaid":
1135
+ lines = ["graph TD"]
1136
+
1137
+ for agent_name, node in self.nodes.items():
1138
+ # Node style based on state
1139
+ if node.fsm.current_state == node.fsm.completed:
1140
+ lines.append(f" {agent_name}[{agent_name}]:::completed")
1141
+ elif node.fsm.current_state == node.fsm.failed:
1142
+ lines.append(f" {agent_name}[{agent_name}]:::failed")
1143
+ elif node.fsm.current_state == node.fsm.running:
1144
+ lines.append(f" {agent_name}[{agent_name}]:::running")
1145
+ else:
1146
+ lines.append(f" {agent_name}[{agent_name}]")
1147
+
1148
+ # Transitions
1149
+ for transition in node.outgoing_transitions:
1150
+ for target in transition.targets:
1151
+ arrow_label = transition.condition.value
1152
+ lines.append(f" {agent_name} -->|{arrow_label}| {target}")
1153
+
1154
+ # Styles
1155
+ lines.extend([
1156
+ " classDef completed fill:#90EE90",
1157
+ " classDef failed fill:#FFB6C1",
1158
+ " classDef running fill:#87CEEB"
1159
+ ])
1160
+
1161
+ return "\n".join(lines)
1162
+
1163
+ raise ValueError(f"Unsupported format: {format}")
1164
+
1165
+ def get_workflow_stats(self) -> Dict[str, Any]:
1166
+ """Get statistics about the workflow."""
1167
+ total_nodes = len(self.nodes)
1168
+ states_count = {}
1169
+
1170
+ for node in self.nodes.values():
1171
+ state_name = node.fsm.current_state.name
1172
+ states_count[state_name] = states_count.get(state_name, 0) + 1
1173
+
1174
+ return {
1175
+ "total_agents": total_nodes,
1176
+ "states": states_count,
1177
+ "execution_log_entries": len(self.execution_log),
1178
+ "has_context": self.current_context is not None
1179
+ }