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,1030 @@
1
+ """
2
+ Chatbot Manager.
3
+
4
+ Tool for instanciate, managing and interacting with Chatbot through APIs.
5
+ """
6
+ from typing import Any, Dict, Type, Optional, Tuple, List
7
+ from importlib import import_module
8
+ import contextlib
9
+ import time
10
+ import asyncio
11
+ import copy
12
+ from aiohttp import web
13
+ from datamodel.exceptions import ValidationError # pylint: disable=E0611 # noqa
14
+ # Navigator:
15
+ from navconfig.logging import logging
16
+ from asyncdb.exceptions import NoDataFound
17
+ from ..bots.abstract import AbstractBot
18
+ from ..bots.basic import BasicBot
19
+ from ..bots.chatbot import Chatbot
20
+ from ..bots.agent import BasicAgent
21
+ from ..handlers.chat import ChatHandler, BotHandler
22
+ from ..handlers.agent import AgentTalk
23
+ from ..handlers import ChatbotHandler
24
+ from ..handlers.models import BotModel
25
+ from ..handlers.stream import StreamHandler
26
+ from ..registry import agent_registry, AgentRegistry
27
+ # Crew:
28
+ from ..bots.orchestration.crew import AgentCrew
29
+ from ..handlers.crew.models import CrewDefinition, ExecutionMode
30
+ from ..handlers.crew.handler import CrewHandler
31
+ from ..handlers.crew.redis_persistence import CrewRedis
32
+ from ..openapi.config import setup_swagger
33
+ from ..conf import ENABLE_SWAGGER
34
+
35
+
36
+ class BotManager:
37
+ """BotManager.
38
+
39
+ Manage Bots/Agents and interact with them through via aiohttp App.
40
+ Deploy and manage chatbots and agents using a RESTful API.
41
+
42
+ """
43
+ app: web.Application = None
44
+
45
+ def __init__(self) -> None:
46
+ self.app = None
47
+ self._bots: Dict[str, AbstractBot] = {}
48
+ self._botdef: Dict[str, Type] = {} # Store class definitions for each bot
49
+ self._bot_expiration: Dict[str, float] = {} # Track expiration timestamps for temporary bots
50
+ self._cleanup_task: Optional[asyncio.Task] = None # Background cleanup task
51
+ self.logger = logging.getLogger(
52
+ name='Parrot.Manager'
53
+ )
54
+ self.registry: AgentRegistry = agent_registry
55
+ self._crews: Dict[str, Tuple[AgentCrew, CrewDefinition]] = {}
56
+ # Initialize Redis persistence for crews
57
+ self.crew_redis = CrewRedis()
58
+
59
+ def get_bot_class(self, bot_name: str) -> Optional[Type]:
60
+ """
61
+ Get bot class by name, searching in:
62
+ 1. parrot.bots (core bots)
63
+ 2. parrot.agents (plugin agents)
64
+
65
+ Args:
66
+ bot_name: Name of the bot/agent class
67
+
68
+ Returns:
69
+ Bot class if found, None otherwise
70
+ """
71
+ # First, try to import from core bots
72
+ with contextlib.suppress(ImportError, AttributeError):
73
+ module = import_module("parrot.bots")
74
+ if hasattr(module, bot_name):
75
+ return getattr(module, bot_name)
76
+
77
+ # Second, try to import from plugin agents
78
+ with contextlib.suppress(ImportError, AttributeError):
79
+ agent_module_name = f"parrot.agents.{bot_name.lower()}"
80
+ module = import_module(agent_module_name)
81
+ if hasattr(module, bot_name):
82
+ return getattr(module, bot_name)
83
+
84
+ # Third, try direct import from parrot.agents package
85
+ # (in case the agent is defined in plugins/agents/__init__.py)
86
+ with contextlib.suppress(ImportError, AttributeError):
87
+ module = import_module("parrot.agents")
88
+ if hasattr(module, bot_name):
89
+ return getattr(module, bot_name)
90
+
91
+ self.logger.warning(
92
+ f"Warning: Bot class '{bot_name}' not found in parrot.bots or parrot.agents"
93
+ )
94
+ return None
95
+
96
+ def get_or_create_bot(self, bot_name: str, **kwargs):
97
+ """
98
+ Get existing bot or create new one from class name.
99
+
100
+ Args:
101
+ bot_name: Name of the bot/agent class
102
+ **kwargs: Arguments to pass to bot constructor
103
+
104
+ Returns:
105
+ Bot instance
106
+ """
107
+ # Check if already instantiated
108
+ if bot_name in self._bots:
109
+ return self._bots[bot_name]
110
+
111
+ # Get the class and instantiate
112
+ bot_class = self.get_bot_class(bot_name)
113
+ if bot_class is None:
114
+ raise ValueError(f"Bot class '{bot_name}' not found")
115
+
116
+ return self.create_bot(class_name=bot_class, name=bot_name, **kwargs)
117
+
118
+ def _log_final_state(self) -> None:
119
+ """Log the final state of bot loading."""
120
+ registry_info = self.registry.get_registration_info()
121
+ self.logger.notice("=== Bot Loading Complete ===")
122
+ self.logger.notice(f"Registered agents: {registry_info['total_registered']}")
123
+ # self.logger.info(f"Startup agents: {startup_info['total_startup_agents']}")
124
+ self.logger.notice(f"Active bots: {len(self._bots)}")
125
+
126
+ async def _process_startup_results(self, startup_results: Dict[str, Any]) -> None:
127
+ """Process startup instantiation results."""
128
+ for agent_name, result in startup_results.items():
129
+ print('===========================================')
130
+ print('Agent startup result:', agent_name, result)
131
+ print('===========================================')
132
+ if result["status"] == "success":
133
+ if instance := result.get("instance"):
134
+ self._bots[agent_name] = instance
135
+ self.logger.info(
136
+ f"Added startup agent to active bots: {agent_name}"
137
+ )
138
+ else:
139
+ self.logger.error(
140
+ f"Startup agent {agent_name} failed: {result['error']}"
141
+ )
142
+
143
+ async def load_bots(self, app: web.Application) -> None:
144
+ """Enhanced bot loading using the registry."""
145
+ self.logger.info("Starting bot loading with global registry")
146
+
147
+ # Step 1: Import modules to trigger decorator registration
148
+ await self.registry.load_modules()
149
+
150
+ # Step 2: Register config-based agents
151
+ config_count = self.registry.discover_config_agents()
152
+ self.logger.info(
153
+ f"Registered {config_count} agents from config"
154
+ )
155
+
156
+ # Step 3: Instantiate startup agents
157
+ startup_results = await self.registry.instantiate_startup_agents(app)
158
+ await self._process_startup_results(startup_results)
159
+
160
+ # Step 4: Load database bots
161
+ await self._load_database_bots(app)
162
+
163
+ # Step 5: Report final state
164
+ self._log_final_state()
165
+
166
+ async def _load_database_bots(self, app: web.Application) -> None:
167
+ """Load bots from database."""
168
+ try:
169
+ # Import here to avoid circular imports
170
+ from ..handlers.models import BotModel # pylint: disable=import-outside-toplevel # noqa
171
+ db = app['database']
172
+ async with await db.acquire() as conn:
173
+ BotModel.Meta.connection = conn
174
+ try:
175
+ all_bots = await BotModel.filter(enabled=True)
176
+ except Exception as e:
177
+ self.logger.error(
178
+ f"Failed to load bots from DB: {e}"
179
+ )
180
+ return
181
+
182
+ for bot_model in all_bots:
183
+ self.logger.notice(
184
+ f"Loading bot '{bot_model.name}' (mode: {bot_model.operation_mode})..."
185
+ )
186
+ if bot_model.name in self._bots:
187
+ self.logger.debug(
188
+ f"Bot {bot_model.name} already active, skipping"
189
+ )
190
+ continue
191
+ try:
192
+ # Use the factory function from models.py or create bot directly
193
+ if hasattr(self, 'get_bot_class') and hasattr(bot_model, 'bot_class'):
194
+ # If you have a bot_class field and get_bot_class method
195
+ class_name = self.get_bot_class(getattr(bot_model, 'bot_class', None))
196
+ else:
197
+ # Default to BasicBot or your default bot class
198
+ class_name = BasicBot
199
+ bot_instance = class_name(
200
+ chatbot_id=bot_model.chatbot_id,
201
+ name=bot_model.name,
202
+ description=bot_model.description,
203
+ # LLM configuration
204
+ use_llm=bot_model.llm,
205
+ model_name=bot_model.model_name,
206
+ model_config=bot_model.model_config,
207
+ temperature=bot_model.temperature,
208
+ max_tokens=bot_model.max_tokens,
209
+ top_k=bot_model.top_k,
210
+ top_p=bot_model.top_p,
211
+ # Bot personality
212
+ role=bot_model.role,
213
+ goal=bot_model.goal,
214
+ backstory=bot_model.backstory,
215
+ rationale=bot_model.rationale,
216
+ capabilities=bot_model.capabilities,
217
+ # Prompt configuration
218
+ system_prompt=bot_model.system_prompt_template,
219
+ human_prompt=bot_model.human_prompt_template,
220
+ pre_instructions=bot_model.pre_instructions,
221
+ # Vector store configuration
222
+ embedding_model=bot_model.embedding_model,
223
+ use_vectorstore=bot_model.use_vector,
224
+ vector_store_config=bot_model.vector_store_config,
225
+ context_search_limit=bot_model.context_search_limit,
226
+ context_score_threshold=bot_model.context_score_threshold,
227
+ # Tool and agent configuration
228
+ tools_enabled=bot_model.tools_enabled,
229
+ auto_tool_detection=bot_model.auto_tool_detection,
230
+ tool_threshold=bot_model.tool_threshold,
231
+ available_tools=bot_model.tools,
232
+ operation_mode=bot_model.operation_mode,
233
+ # Memory configuration
234
+ memory_type=bot_model.memory_type,
235
+ memory_config=bot_model.memory_config,
236
+ max_context_turns=bot_model.max_context_turns,
237
+ use_conversation_history=bot_model.use_conversation_history,
238
+ # Security and permissions
239
+ permissions=bot_model.permissions,
240
+ # Metadata
241
+ language=bot_model.language,
242
+ disclaimer=bot_model.disclaimer,
243
+ )
244
+ # Set the model ID reference
245
+ bot_instance.model_id = bot_model.chatbot_id
246
+
247
+ await bot_instance.configure(app)
248
+ self.add_bot(bot_instance)
249
+ self.logger.info(
250
+ f"Successfully loaded bot '{bot_model.name}' "
251
+ f"with {len(bot_model.tools) if bot_model.tools else 0} tools"
252
+ )
253
+ except ValidationError as e:
254
+ self.logger.error(
255
+ f"Invalid configuration for bot '{bot_model.name}': {e}"
256
+ )
257
+ except Exception as e:
258
+ self.logger.error(
259
+ f"Failed to load database bot {bot_instance.name}: {str(e)}"
260
+ )
261
+ self.logger.info(
262
+ f":: Bots loaded successfully. Total active bots: {len(self._bots)}"
263
+ )
264
+ except Exception as e:
265
+ self.logger.error(
266
+ f"Database bot loading failed: {str(e)}"
267
+ )
268
+
269
+ # Alternative approach using the factory function from models.py
270
+ async def load_bots_with_factory(self, app: web.Application) -> None:
271
+ """Load all bots from DB using the factory function."""
272
+ self.logger.info("Loading bots from DB...")
273
+ db = app['database']
274
+ async with await db.acquire() as conn:
275
+ BotModel.Meta.connection = conn
276
+ try:
277
+ bot_models = await BotModel.filter(enabled=True)
278
+ except Exception as e:
279
+ self.logger.error(
280
+ f"Failed to load bots from DB: {e}"
281
+ )
282
+ return
283
+
284
+ for bot_model in bot_models:
285
+ self.logger.notice(
286
+ f"Loading bot '{bot_model.name}' (mode: {bot_model.operation_mode})..."
287
+ )
288
+
289
+ try:
290
+ # Use the factory function from models.py
291
+ # Determine bot class if you have custom classes
292
+ bot_class = None
293
+ if hasattr(self, 'get_bot_class') and hasattr(bot_model, 'bot_class'):
294
+ bot_class = self.get_bot_class(getattr(bot_model, 'bot_class', None))
295
+ else:
296
+ # Default to BasicBot or your default bot class
297
+ bot_class = BasicBot
298
+
299
+ # Create bot using factory function
300
+ chatbot = bot_class(bot_model, bot_class)
301
+
302
+ # Configure the bot
303
+ try:
304
+ await chatbot.configure(app=app)
305
+ self.add_bot(chatbot)
306
+ self.logger.info(
307
+ f"Successfully loaded bot '{bot_model.name}' "
308
+ f"with {len(bot_model.tools) if bot_model.tools else 0} tools"
309
+ )
310
+ except ValidationError as e:
311
+ self.logger.error(
312
+ f"Invalid configuration for bot '{bot_model.name}': {e}"
313
+ )
314
+ except Exception as e:
315
+ self.logger.error(
316
+ f"Failed to configure bot '{bot_model.name}': {e}"
317
+ )
318
+
319
+ except Exception as e:
320
+ self.logger.error(
321
+ f"Failed to create bot instance for '{bot_model.name}': {e}"
322
+ )
323
+ continue
324
+
325
+ self.logger.info(
326
+ f":: Bots loaded successfully. Total active bots: {len(self._bots)}"
327
+ )
328
+
329
+ def create_bot(self, class_name: Any = None, name: str = None, **kwargs) -> AbstractBot:
330
+ """Create a Bot and add it to the manager."""
331
+ if class_name is None:
332
+ class_name = Chatbot
333
+ chatbot = class_name(**kwargs)
334
+ chatbot.name = name
335
+ return chatbot
336
+
337
+ def add_bot(self, bot: AbstractBot) -> None:
338
+ """Add a Bot to the manager."""
339
+ self._bots[bot.name] = bot
340
+ # Store the class definition for future instance creation
341
+ self._botdef[bot.name] = bot.__class__
342
+
343
+ async def get_bot(
344
+ self,
345
+ name: str,
346
+ new: bool = False,
347
+ session_id: str = "",
348
+ **kwargs
349
+ ) -> AbstractBot:
350
+ """Get a Bot by name.
351
+
352
+ Args:
353
+ name: Name of the bot to get
354
+ new: If True, create a new instance instead of returning existing one
355
+ session_id: Session identifier for creating unique temporary instances
356
+ **kwargs: Additional arguments to pass to bot constructor when new=True
357
+
358
+ Returns:
359
+ Bot instance (existing or newly created)
360
+ """
361
+ # Handle new instance creation
362
+ if new:
363
+ # Get the class definition for this bot
364
+ cls = self._botdef.get(name, BasicAgent)
365
+
366
+ # Create unique name to avoid duplicates
367
+ new_name = f"{name}_{session_id}" if session_id else f"{name}_{int(time.time())}"
368
+
369
+ # Prepare configuration to inherit from base bot
370
+ base_bot = self._bots.get(name)
371
+ bot_kwargs = kwargs.copy()
372
+
373
+ if base_bot:
374
+ # 1. Inherit LLM Configuration if not explicitly provided
375
+ if 'use_llm' not in bot_kwargs and hasattr(base_bot, '_llm_raw'):
376
+ bot_kwargs['use_llm'] = base_bot._llm_raw
377
+
378
+ if 'model' not in bot_kwargs and hasattr(base_bot, '_llm_model'):
379
+ bot_kwargs['model'] = base_bot._llm_model
380
+
381
+ # 2. Clone Tools
382
+ if 'tools' not in bot_kwargs and hasattr(base_bot, 'tool_manager'):
383
+ try:
384
+ # Deep copy tools to ensure isolation
385
+ base_tools = base_bot.tool_manager.get_all_tools()
386
+ new_tools = []
387
+ for tool in base_tools:
388
+ try:
389
+ # Attempt deep copy
390
+ new_tool = copy.deepcopy(tool)
391
+ new_tools.append(new_tool)
392
+ except Exception as e:
393
+ self.logger.warning(
394
+ f"Failed to copy tool {tool.name}, sharing instance. Error: {e}"
395
+ )
396
+ # Fallback to shared instance
397
+ new_tools.append(tool)
398
+ bot_kwargs['tools'] = new_tools
399
+ except Exception as e:
400
+ self.logger.error(f"Error cloning tools from {name}: {e}")
401
+
402
+ # 3. Clone Vector Store Configuration
403
+ if 'vector_store_config' not in bot_kwargs and hasattr(base_bot, '_vector_store'):
404
+ try:
405
+ if base_bot._vector_store:
406
+ bot_kwargs['vector_store_config'] = copy.deepcopy(base_bot._vector_store)
407
+ except Exception as e:
408
+ self.logger.warning(f"Failed to copy vector store config: {e}")
409
+ bot_kwargs['vector_store_config'] = base_bot._vector_store
410
+
411
+ if 'use_vectorstore' not in bot_kwargs and hasattr(base_bot, '_use_vector'):
412
+ bot_kwargs['use_vectorstore'] = getattr(base_bot, '_use_vector', False)
413
+
414
+ # Create new instance with merged configuration
415
+ bot = cls(name=new_name, **bot_kwargs)
416
+
417
+ # Configure the bot
418
+ await bot.configure(self.app)
419
+
420
+ # Add to bots dictionary
421
+ self._bots[new_name] = bot
422
+
423
+ # Set expiration time (1 hour from now)
424
+ self._bot_expiration[new_name] = time.time() + 3600
425
+
426
+ self.logger.info(
427
+ f"Created new temporary bot instance '{new_name}' from '{name}' "
428
+ f"(expires in 1 hour)"
429
+ )
430
+
431
+ return bot
432
+
433
+ # Existing behavior for getting/creating bots
434
+ if name not in self._bots:
435
+ self.logger.warning(
436
+ f"Bot '{name}' not in _bots. Available: {list(self._bots.keys())}"
437
+ )
438
+ if name in self._bots:
439
+ _bot = self._bots[name]
440
+ if not getattr(_bot, 'is_configured', False):
441
+ self.logger.warning(f"Bot '{name}' found in _bots and is not configured.")
442
+ await _bot.configure(self.app)
443
+ return self._bots[name]
444
+ if self.registry.has(name):
445
+ try:
446
+ # Get instance (returns singleton if at_startup=True)
447
+ bot_instance = await self.registry.get_instance(name)
448
+ if bot_instance:
449
+ # Only configure if NOT already configured
450
+ if not getattr(bot_instance, 'is_configured', False):
451
+ self.logger.info(f"Configuring bot {name} on demand.")
452
+ await bot_instance.configure(self.app)
453
+ self.add_bot(bot_instance)
454
+ return bot_instance
455
+ except Exception as e:
456
+ self.logger.error(
457
+ f"Failed to get bot instance from registry: {e}"
458
+ )
459
+ return None
460
+
461
+ def remove_bot(self, name: str) -> None:
462
+ """Remove a Bot by name."""
463
+ del self._bots[name]
464
+ # Clean up expiration tracking if it exists (but keep class definition)
465
+ self._bot_expiration.pop(name, None)
466
+
467
+ def get_bots(self) -> Dict[str, AbstractBot]:
468
+ """Get all Bots declared on Manager."""
469
+ return self._bots
470
+
471
+ async def create_agent(self, class_name: Any = None, name: str = None, **kwargs) -> AbstractBot:
472
+ if class_name is None:
473
+ class_name = BasicAgent
474
+ return class_name(name=name, **kwargs)
475
+
476
+ def add_agent(self, agent: AbstractBot) -> None:
477
+ """Add a Agent to the manager."""
478
+ self._bots[str(agent.chatbot_id)] = agent
479
+
480
+ def remove_agent(self, agent: AbstractBot) -> None:
481
+ """Remove a Bot by name."""
482
+ del self._bots[str(agent.chatbot_id)]
483
+
484
+ async def save_agent(self, name: str, **kwargs) -> None:
485
+ """Save a Agent to the DB."""
486
+ self.logger.info(f"Saving Agent {name} into DB ...")
487
+ db = self.app['database']
488
+ async with await db.acquire() as conn:
489
+ BotModel.Meta.connection = conn
490
+ try:
491
+ try:
492
+ bot = await BotModel.get(name=name)
493
+ except NoDataFound:
494
+ bot = None
495
+ if bot:
496
+ self.logger.info(f"Bot {name} already exists.")
497
+ for key, val in kwargs.items():
498
+ bot.set(key, val)
499
+ await bot.update()
500
+ self.logger.info(f"Bot {name} updated.")
501
+ else:
502
+ self.logger.info(f"Bot {name} not found. Creating new one.")
503
+ # Create a new Bot
504
+ new_bot = BotModel(
505
+ name=name,
506
+ **kwargs
507
+ )
508
+ await new_bot.insert()
509
+ self.logger.info(f"Bot {name} saved into DB.")
510
+ return True
511
+ except Exception as e:
512
+ self.logger.error(
513
+ f"Failed to Create new Bot {name} from DB: {e}"
514
+ )
515
+ return None
516
+
517
+ def get_app(self) -> web.Application:
518
+ """Get the app."""
519
+ if self.app is None:
520
+ raise RuntimeError("App is not set.")
521
+ return self.app
522
+
523
+ def setup(self, app: web.Application) -> web.Application:
524
+ self.app = None
525
+ if app:
526
+ self.app = app if isinstance(app, web.Application) else app.get_app()
527
+ # register signals for startup and shutdown
528
+ self.app.on_startup.append(self.on_startup)
529
+ self.app.on_shutdown.append(self.on_shutdown)
530
+ # Add Manager to main Application:
531
+ self.app['bot_manager'] = self
532
+ ## Configure Routes
533
+ router = self.app.router
534
+ # Chat Information Router
535
+ router.add_view(
536
+ '/api/v1/chats',
537
+ ChatHandler
538
+ )
539
+ router.add_view(
540
+ '/api/v1/chat/{chatbot_name}',
541
+ ChatHandler
542
+ )
543
+ router.add_view(
544
+ '/api/v1/chat/{chatbot_name}/{method_name}',
545
+ ChatHandler
546
+ )
547
+ # Talk with agents:
548
+ router.add_view(
549
+ '/api/v1/agents/chat/{agent_id}',
550
+ AgentTalk
551
+ )
552
+ router.add_view(
553
+ '/api/v1/agents/chat/{agent_id}/{method_name}',
554
+ AgentTalk
555
+ )
556
+ # ChatBot Manager
557
+ ChatbotHandler.configure(self.app, '/api/v1/bots')
558
+ # Bot Handler
559
+ router.add_view(
560
+ '/api/v1/chatbots',
561
+ BotHandler
562
+ )
563
+ router.add_view(
564
+ '/api/v1/chatbots/{name}',
565
+ BotHandler
566
+ )
567
+ # Streaming Handler:
568
+ st = StreamHandler()
569
+ st.configure_routes(self.app)
570
+ # Crew Configuration
571
+ CrewHandler.configure(self.app, '/api/v1/crew')
572
+ if ENABLE_SWAGGER:
573
+ self.logger.info("Setting up OpenAPI documentation...")
574
+ setup_swagger(self.app)
575
+ self.logger.info("""
576
+ ✅ OpenAPI Documentation configured successfully!
577
+
578
+ Available documentation UIs:
579
+ - Swagger UI: http://localhost:5000/api/docs
580
+ - ReDoc: http://localhost:5000/api/docs/redoc
581
+ - RapiDoc: http://localhost:5000/api/docs/rapidoc
582
+ - OpenAPI Spec: http://localhost:5000/api/docs/swagger.json
583
+ """)
584
+ return self.app
585
+
586
+ async def _cleanup_expired_bots(self) -> None:
587
+ """Background task to cleanup expired temporary bot instances.
588
+
589
+ Runs every 5 minutes to check for and remove bot instances that have
590
+ exceeded their expiration time (typically 1 hour after creation).
591
+ """
592
+ while True:
593
+ try:
594
+ await asyncio.sleep(300) # Check every 5 minutes
595
+ current_time = time.time()
596
+
597
+ # Find all expired bots
598
+ expired = [
599
+ name for name, expiry in self._bot_expiration.items()
600
+ if current_time > expiry
601
+ ]
602
+
603
+ # Remove expired bots
604
+ for name in expired:
605
+ try:
606
+ self.logger.info(f"Removing expired bot instance: {name}")
607
+ self.remove_bot(name)
608
+ del self._bot_expiration[name]
609
+ except Exception as e:
610
+ self.logger.error(
611
+ f"Error removing expired bot '{name}': {e}"
612
+ )
613
+ # Remove from expiration tracking even if removal failed
614
+ self._bot_expiration.pop(name, None)
615
+
616
+ if expired:
617
+ self.logger.info(
618
+ f"Cleaned up {len(expired)} expired bot instance(s). "
619
+ f"Active bots: {len(self._bots)}, "
620
+ f"Tracked expirations: {len(self._bot_expiration)}"
621
+ )
622
+ except asyncio.CancelledError:
623
+ self.logger.info("Cleanup task cancelled")
624
+ raise
625
+ except Exception as e:
626
+ self.logger.error(
627
+ f"Error in cleanup task: {e}",
628
+ exc_info=True
629
+ )
630
+ # Continue running even if there's an error
631
+
632
+ async def on_startup(self, app: web.Application) -> None:
633
+ """On startup."""
634
+ # configure all pre-configured chatbots:
635
+ await self.load_bots(app)
636
+ # Load crews from Redis
637
+ await self.load_crews()
638
+ # Start background cleanup task for expired bots
639
+ self._cleanup_task = asyncio.create_task(self._cleanup_expired_bots())
640
+ self.logger.info("Started background cleanup task for temporary bot instances")
641
+
642
+ async def on_shutdown(self, app: web.Application) -> None:
643
+ """On shutdown."""
644
+ # Cancel background cleanup task
645
+ if self._cleanup_task:
646
+ self._cleanup_task.cancel()
647
+ try:
648
+ await self._cleanup_task
649
+ except asyncio.CancelledError:
650
+ pass
651
+ self.logger.info("Stopped background cleanup task")
652
+
653
+ async def add_crew(
654
+ self,
655
+ name: str,
656
+ crew: AgentCrew,
657
+ crew_def: CrewDefinition
658
+ ) -> None:
659
+ """
660
+ Register a crew in the manager and persist to Redis.
661
+
662
+ Args:
663
+ name: Unique name for the crew
664
+ crew: AgentCrew instance
665
+ crew_def: Crew definition containing metadata
666
+
667
+ Raises:
668
+ ValueError: If crew with same name already exists
669
+ """
670
+ if name in self._crews:
671
+ raise ValueError(f"Crew '{name}' already exists")
672
+
673
+ # Add to memory
674
+ self._crews[name] = (crew, crew_def)
675
+
676
+ # Persist to Redis
677
+ try:
678
+ await self.crew_redis.save_crew(crew_def)
679
+ self.logger.info(
680
+ f"Registered crew '{name}' with {len(crew.agents)} agents "
681
+ f"in {crew_def.execution_mode.value} mode and saved to Redis"
682
+ )
683
+ except Exception as e:
684
+ self.logger.error(f"Failed to save crew '{name}' to Redis: {e}")
685
+ # Don't fail the operation if Redis fails, crew is still in memory
686
+ self.logger.info(
687
+ f"Crew '{name}' registered in memory only (Redis persistence failed)"
688
+ )
689
+
690
+ async def get_crew(
691
+ self,
692
+ identifier: str
693
+ ) -> Optional[Tuple[AgentCrew, CrewDefinition]]:
694
+ """
695
+ Get a crew by name or ID. Loads from Redis if not in memory.
696
+
697
+ Args:
698
+ identifier: Crew name or crew_id
699
+
700
+ Returns:
701
+ Tuple of (AgentCrew, CrewDefinition) if found, None otherwise
702
+ """
703
+ # Try by name first in memory
704
+ if identifier in self._crews:
705
+ return self._crews[identifier]
706
+
707
+ # Try by crew_id in memory
708
+ for name, (crew, crew_def) in self._crews.items():
709
+ if crew_def.crew_id == identifier:
710
+ return (crew, crew_def)
711
+
712
+ # Not in memory - try to load from Redis
713
+ try:
714
+ # Try to load by name first
715
+ crew_def = await self.crew_redis.load_crew(identifier)
716
+
717
+ # If not found by name, try by ID
718
+ if not crew_def:
719
+ crew_def = await self.crew_redis.load_crew_by_id(identifier)
720
+
721
+ if crew_def:
722
+ # Reconstruct the crew from definition
723
+ crew = await self._create_crew_from_definition(crew_def)
724
+
725
+ # Cache in memory
726
+ self._crews[crew_def.name] = (crew, crew_def)
727
+
728
+ self.logger.info(
729
+ f"Loaded crew '{crew_def.name}' from Redis "
730
+ f"(ID: {crew_def.crew_id})"
731
+ )
732
+ return (crew, crew_def)
733
+ except Exception as e:
734
+ self.logger.error(
735
+ f"Error loading crew '{identifier}' from Redis: {e}"
736
+ )
737
+
738
+ return None
739
+
740
+ def list_crews(self) -> Dict[str, Tuple[AgentCrew, CrewDefinition]]:
741
+ """
742
+ List all registered crews.
743
+
744
+ Returns:
745
+ Dictionary mapping crew names to (AgentCrew, CrewDefinition) tuples
746
+ """
747
+ return self._crews.copy()
748
+
749
+ async def remove_crew(self, identifier: str) -> bool:
750
+ """
751
+ Remove a crew from the manager and Redis.
752
+
753
+ Args:
754
+ identifier: Crew name or crew_id
755
+
756
+ Returns:
757
+ True if removed, False if not found
758
+ """
759
+ crew_name = None
760
+ crew_def = None
761
+
762
+ # Try by name first
763
+ if identifier in self._crews:
764
+ crew_name = identifier
765
+ _, crew_def = self._crews[identifier]
766
+ del self._crews[identifier]
767
+ else:
768
+ # Try by crew_id
769
+ for name, (crew, def_) in list(self._crews.items()):
770
+ if def_.crew_id == identifier:
771
+ crew_name = name
772
+ crew_def = def_
773
+ del self._crews[name]
774
+ break
775
+
776
+ if crew_name and crew_def:
777
+ # Remove from Redis
778
+ try:
779
+ await self.crew_redis.delete_crew(crew_def.name)
780
+ self.logger.info(
781
+ f"Removed crew '{crew_name}' (ID: {crew_def.crew_id}) "
782
+ f"from memory and Redis"
783
+ )
784
+ except Exception as e:
785
+ self.logger.error(
786
+ f"Failed to delete crew '{crew_name}' from Redis: {e}"
787
+ )
788
+ self.logger.info(
789
+ f"Crew '{crew_name}' removed from memory only"
790
+ )
791
+ return True
792
+
793
+ return False
794
+
795
+ def update_crew(
796
+ self,
797
+ identifier: str,
798
+ crew: AgentCrew,
799
+ crew_def: CrewDefinition
800
+ ) -> bool:
801
+ """
802
+ Update an existing crew.
803
+
804
+ Args:
805
+ identifier: Crew name or crew_id
806
+ crew: Updated AgentCrew instance
807
+ crew_def: Updated crew definition
808
+
809
+ Returns:
810
+ True if updated, False if not found
811
+ """
812
+ # Find crew by name or ID
813
+ crew_name = None
814
+ if identifier in self._crews:
815
+ crew_name = identifier
816
+ else:
817
+ for name, (_, def_) in self._crews.items():
818
+ if def_.crew_id == identifier:
819
+ crew_name = name
820
+ break
821
+
822
+ if crew_name:
823
+ self._crews[crew_name] = (crew, crew_def)
824
+ self.logger.info(f"Updated crew '{crew_name}'")
825
+ return True
826
+
827
+ return False
828
+
829
+ async def load_crews(self) -> None:
830
+ """
831
+ Load all crews from Redis on startup.
832
+
833
+ This method is called during application startup to restore
834
+ all previously saved crews from Redis into memory.
835
+ """
836
+ try:
837
+ # Check Redis connection
838
+ if not await self.crew_redis.ping():
839
+ self.logger.warning("Redis connection failed, skipping crew loading")
840
+ return
841
+
842
+ # Get all crew definitions from Redis
843
+ crew_defs = await self.crew_redis.get_all_crews()
844
+
845
+ if not crew_defs:
846
+ self.logger.info("No crews found in Redis")
847
+ return
848
+
849
+ self.logger.info(f"Loading {len(crew_defs)} crews from Redis...")
850
+
851
+ loaded_count = 0
852
+ for crew_def in crew_defs:
853
+ try:
854
+ # Reconstruct the crew from definition
855
+ crew = await self._create_crew_from_definition(crew_def)
856
+
857
+ # Add to memory (without saving back to Redis)
858
+ self._crews[crew_def.name] = (crew, crew_def)
859
+
860
+ loaded_count += 1
861
+ self.logger.info(
862
+ f"Loaded crew '{crew_def.name}' with {len(crew_def.agents)} agents "
863
+ f"in {crew_def.execution_mode.value} mode"
864
+ )
865
+ except Exception as e:
866
+ self.logger.error(
867
+ f"Failed to load crew '{crew_def.name}': {e}",
868
+ exc_info=True
869
+ )
870
+
871
+ self.logger.info(
872
+ f":: Crews loaded successfully. Total active crews: {loaded_count}"
873
+ )
874
+ except Exception as e:
875
+ self.logger.error(
876
+ f"Failed to load crews from Redis: {e}",
877
+ exc_info=True
878
+ )
879
+
880
+ async def _create_crew_from_definition(
881
+ self,
882
+ crew_def: CrewDefinition
883
+ ) -> AgentCrew:
884
+ """
885
+ Create an AgentCrew instance from a CrewDefinition.
886
+
887
+ This method reconstructs a crew from its JSON definition,
888
+ creating all agents and setting up flow relations.
889
+
890
+ Args:
891
+ crew_def: Crew definition
892
+
893
+ Returns:
894
+ AgentCrew instance
895
+ """
896
+ from typing import List, Any
897
+
898
+ # Create agents
899
+ agents = []
900
+ for agent_def in crew_def.agents:
901
+ # Get agent class
902
+ agent_class = self.get_bot_class(agent_def.agent_class)
903
+ if not agent_class:
904
+ self.logger.warning(
905
+ f"Agent class '{agent_def.agent_class}' not found, "
906
+ f"using BasicAgent as fallback"
907
+ )
908
+ agent_class = BasicAgent
909
+
910
+ # Collect tools
911
+ tools = []
912
+ if agent_def.tools:
913
+ tools.extend(iter(agent_def.tools))
914
+
915
+ # Create agent instance
916
+ agent = agent_class(
917
+ name=agent_def.name or agent_def.agent_id,
918
+ tools=tools,
919
+ **agent_def.config
920
+ )
921
+
922
+ # Set system prompt if provided
923
+ if agent_def.system_prompt:
924
+ agent.system_prompt = agent_def.system_prompt
925
+
926
+ agents.append(agent)
927
+
928
+ # Create crew
929
+ crew = AgentCrew(
930
+ name=crew_def.name,
931
+ agents=agents,
932
+ max_parallel_tasks=crew_def.max_parallel_tasks
933
+ )
934
+
935
+ # Add shared tools
936
+ for tool_name in crew_def.shared_tools:
937
+ # Try to get tool from registry or bot manager
938
+ # This is a placeholder - implement tool retrieval as needed
939
+ try:
940
+ # You may need to implement get_tool method
941
+ # For now, we'll skip tools that aren't available
942
+ self.logger.debug(
943
+ f"Shared tool '{tool_name}' for crew '{crew_def.name}' "
944
+ f"(implement tool retrieval as needed)"
945
+ )
946
+ except Exception as e:
947
+ self.logger.warning(
948
+ f"Could not add shared tool '{tool_name}': {e}"
949
+ )
950
+
951
+ # Setup flow relations if in flow mode
952
+ if crew_def.execution_mode == ExecutionMode.FLOW and crew_def.flow_relations:
953
+ for relation in crew_def.flow_relations:
954
+ try:
955
+ # Convert agent IDs to agent objects
956
+ source_agents = self._get_agents_by_ids(
957
+ crew,
958
+ relation.source if isinstance(relation.source, list) else [relation.source]
959
+ )
960
+ target_agents = self._get_agents_by_ids(
961
+ crew,
962
+ relation.target if isinstance(relation.target, list) else [relation.target]
963
+ )
964
+
965
+ # Setup flow
966
+ crew.task_flow(
967
+ source_agents if len(source_agents) > 1 else source_agents[0],
968
+ target_agents if len(target_agents) > 1 else target_agents[0]
969
+ )
970
+ except Exception as e:
971
+ self.logger.error(
972
+ f"Failed to setup flow relation for crew '{crew_def.name}': {e}"
973
+ )
974
+
975
+ return crew
976
+
977
+ def _get_agents_by_ids(
978
+ self,
979
+ crew: AgentCrew,
980
+ agent_ids: List[str]
981
+ ) -> List[Any]:
982
+ """
983
+ Get agent objects from crew by their IDs.
984
+
985
+ Args:
986
+ crew: AgentCrew instance
987
+ agent_ids: List of agent IDs
988
+
989
+ Returns:
990
+ List of agent objects
991
+ """
992
+ agents = []
993
+ for agent_id in agent_ids:
994
+ if agent := crew.agents.get(agent_id):
995
+ agents.append(agent)
996
+ else:
997
+ self.logger.warning(f"Agent '{agent_id}' not found in crew")
998
+ return agents
999
+
1000
+ def get_crew_stats(self) -> Dict[str, Any]:
1001
+ """
1002
+ Get statistics about registered crews.
1003
+
1004
+ Returns:
1005
+ Dictionary with crew statistics
1006
+ """
1007
+ stats = {
1008
+ 'total_crews': len(self._crews),
1009
+ 'crews_by_mode': {
1010
+ 'sequential': 0,
1011
+ 'parallel': 0,
1012
+ 'flow': 0
1013
+ },
1014
+ 'total_agents': 0,
1015
+ 'crews': []
1016
+ }
1017
+
1018
+ for name, (crew, crew_def) in self._crews.items():
1019
+ mode = crew_def.execution_mode.value
1020
+ stats['crews_by_mode'][mode] = stats['crews_by_mode'].get(mode, 0) + 1
1021
+ stats['total_agents'] += len(crew.agents)
1022
+
1023
+ stats['crews'].append({
1024
+ 'name': name,
1025
+ 'crew_id': crew_def.crew_id,
1026
+ 'mode': mode,
1027
+ 'agent_count': len(crew.agents)
1028
+ })
1029
+
1030
+ return stats