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
parrot/bots/agent.py ADDED
@@ -0,0 +1,1129 @@
1
+ import textwrap
2
+ from typing import Dict, List, Tuple, Any, Optional, Union, Callable
3
+ from datetime import datetime
4
+ import uuid
5
+ from pathlib import Path
6
+ import aiofiles
7
+ import pandas as pd
8
+ from navconfig import BASE_DIR
9
+ from navconfig.logging import logging
10
+ from ..models.responses import AIMessage, AgentResponse
11
+ from ..clients.google import GoogleGenAIClient
12
+ from .chatbot import Chatbot
13
+ from .prompts import AGENT_PROMPT
14
+ from ..tools.abstract import AbstractTool
15
+ from ..tools.pythonrepl import PythonREPLTool
16
+ from ..tools.pythonpandas import PythonPandasTool
17
+ from ..tools.pdfprint import PDFPrintTool
18
+ from ..tools.powerpoint import PowerPointTool
19
+ from ..tools.agent import AgentTool, AgentContext
20
+ from ..models.google import (
21
+ ConversationalScriptConfig,
22
+ FictionalSpeaker
23
+ )
24
+ # MCP Integration
25
+ from ..mcp import (
26
+ MCPEnabledMixin,
27
+ MCPServerConfig,
28
+ MCPToolManager,
29
+ create_http_mcp_server,
30
+ create_local_mcp_server,
31
+ create_api_key_mcp_server
32
+ )
33
+ from ..conf import STATIC_DIR, AGENTS_DIR
34
+ from ..notifications import NotificationMixin
35
+ from ..memory import AgentMemory
36
+
37
+
38
+ class BasicAgent(MCPEnabledMixin, Chatbot, NotificationMixin):
39
+ """Represents an Agent in Navigator.
40
+
41
+ Agents are chatbots that can access to Tools and execute commands.
42
+ Each Agent has a name, a role, a goal, a backstory,
43
+ and an optional language model (llm).
44
+
45
+ These agents are designed to interact with structured and unstructured data sources.
46
+
47
+ Features:
48
+ - Built-in MCP server support (no separate mixin needed)
49
+ - Can connect to HTTP, OAuth, API-key authenticated, and local MCP servers
50
+ - Automatic tool registration from MCP servers
51
+ - Compatible with all existing agent functionality
52
+ - Notification capabilities through various channels (e.g., email, Slack, Teams)
53
+ """
54
+ agent_id: Optional[str] = None
55
+ agent_name: Optional[str] = None
56
+ _agent_response = AgentResponse
57
+ speech_context: str = ""
58
+ speech_system_prompt: str = ""
59
+ podcast_system_instruction: str = None
60
+ speech_length: int = 20 # Default length for the speech report
61
+ num_speakers: int = 1 # Default number of speakers for the podcast
62
+ speakers: Dict[str, str] = {
63
+ "interviewer": {
64
+ "name": "Lydia",
65
+ "role": "interviewer",
66
+ "characteristic": "Bright",
67
+ "gender": "female"
68
+ },
69
+ "interviewee": {
70
+ "name": "Brian",
71
+ "role": "interviewee",
72
+ "characteristic": "Informative",
73
+ "gender": "male"
74
+ }
75
+ }
76
+ max_tokens: int = None # Use default max tokens from Chatbot
77
+ report_template: str = "report_template.html"
78
+ system_prompt_template: str = AGENT_PROMPT
79
+
80
+ def __init__(
81
+ self,
82
+ name: str = 'Agent',
83
+ agent_id: str = 'agent',
84
+ use_llm: str = 'google',
85
+ llm: str = None,
86
+ tools: List[AbstractTool] = None,
87
+ system_prompt: str = None,
88
+ human_prompt: str = None,
89
+ use_tools: bool = True,
90
+ instructions: Optional[str] = None,
91
+ dataframes: Optional[Dict[str, pd.DataFrame]] = None,
92
+ **kwargs
93
+ ):
94
+ # to work with dataframes:
95
+ self.dataframes = dataframes or {}
96
+ self._dataframe_info_cache = None
97
+ self.agent_id = self.agent_id or agent_id
98
+ self.agent_name = self.agent_name or name
99
+ tools = self._get_default_tools(tools)
100
+ super().__init__(
101
+ name=name,
102
+ llm=llm,
103
+ use_llm=use_llm,
104
+ system_prompt=system_prompt,
105
+ human_prompt=human_prompt,
106
+ tools=tools,
107
+ use_tools=use_tools,
108
+ **kwargs
109
+ )
110
+ if instructions:
111
+ self.goal = instructions
112
+ self.enable_tools = True # Enable tools by default
113
+ self.operation_mode = 'agentic' # Default operation mode
114
+ self.auto_tool_detection = True # Enable auto tool detection by default
115
+ ## Logging:
116
+ self.logger = logging.getLogger(
117
+ f'{self.name}.Agent'
118
+ )
119
+ ## Google GenAI Client (for multi-modal responses and TTS generation):
120
+ self.client = GoogleGenAIClient()
121
+ # Initialize the underlying AbstractBot LLM with the same client
122
+ if not self._llm:
123
+ self._llm = self.client
124
+ # install agent-specific tools:
125
+ self.tools = self.agent_tools()
126
+ self.tool_manager.register_tools(self.tools)
127
+ # Initialize MCP support
128
+ self.mcp_manager = MCPToolManager(
129
+ self.tool_manager
130
+ )
131
+ self.agent_memory = AgentMemory(
132
+ agent_id=self.agent_id
133
+ )
134
+
135
+ def _get_default_tools(self, tools: list) -> List[AbstractTool]:
136
+ """Return Agent-specific tools."""
137
+ if not tools:
138
+ tools = []
139
+ tools.extend(
140
+ [
141
+ PythonREPLTool(
142
+ report_dir=AGENTS_DIR.joinpath(self.agent_id, 'documents')
143
+ ),
144
+ ]
145
+ )
146
+ return tools
147
+
148
+ def agent_tools(self) -> List[AbstractTool]:
149
+ """Return the agent-specific tools."""
150
+ return []
151
+
152
+ def set_response(self, response: AgentResponse):
153
+ """Set the response for the agent."""
154
+ self._agent_response = response
155
+
156
+ async def setup_mcp_servers(self, configurations: List[MCPServerConfig]) -> None:
157
+ """
158
+ Setup multiple MCP servers during initialization.
159
+
160
+ This is useful for configuring an agent with multiple MCP servers
161
+ at once, typically during agent creation or from configuration files.
162
+
163
+ Args:
164
+ configurations: List of MCPServerConfig objects
165
+
166
+ Example:
167
+ >>> configs = [
168
+ ... create_http_mcp_server("weather", "https://api.weather.com/mcp"),
169
+ ... create_local_mcp_server("files", "./mcp_servers/files.py")
170
+ ... ]
171
+ >>> await agent.setup_mcp_servers(configs)
172
+ """
173
+ for config in configurations:
174
+ try:
175
+ tools = await self.add_mcp_server(config)
176
+ self.logger.info(
177
+ f"Added MCP server '{config.name}' with tools: {tools}"
178
+ )
179
+ except Exception as e:
180
+ self.logger.error(
181
+ f"Failed to add MCP server '{config.name}': {e}",
182
+ exc_info=True
183
+ )
184
+
185
+ def _create_filename(self, prefix: str = 'report', extension: str = 'pdf') -> str:
186
+ """Create a unique filename for the report."""
187
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
188
+ return f"{prefix}_{timestamp}.{extension}"
189
+
190
+ async def save_document(
191
+ self,
192
+ content: str,
193
+ prefix: str = 'report',
194
+ extension: str = 'txt',
195
+ directory: Optional[Path] = None,
196
+ subdir: str = 'documents'
197
+ ) -> None:
198
+ """Save the document to a file."""
199
+ report_filename = self._create_filename(
200
+ prefix=prefix, extension=extension
201
+ )
202
+ if not directory:
203
+ directory = STATIC_DIR.joinpath(self.agent_id, subdir)
204
+ try:
205
+ async with aiofiles.open(
206
+ directory.joinpath(report_filename),
207
+ 'w'
208
+ ) as report_file:
209
+ await report_file.write(content)
210
+ except Exception as e:
211
+ self.logger.error(
212
+ f"Failed to save document {report_filename}: {e}"
213
+ )
214
+
215
+ async def open_prompt(self, prompt_file: str = None) -> str:
216
+ """
217
+ Opens a prompt file and returns its content.
218
+ """
219
+ if not prompt_file:
220
+ raise ValueError("No prompt file specified.")
221
+ file = AGENTS_DIR.joinpath(self.agent_id, 'prompts', prompt_file)
222
+ try:
223
+ async with aiofiles.open(file, 'r') as f:
224
+ content = await f.read()
225
+ return content
226
+ except Exception as e:
227
+ self.logger.error(
228
+ f"Failed to read prompt file {prompt_file}: {e}"
229
+ )
230
+ return None
231
+
232
+ async def open_query(self, query: str, directory: Optional[Path] = None, **kwargs) -> str:
233
+ """
234
+ Opens a query string and formats it with provided keyword arguments.
235
+ """
236
+ if not query:
237
+ raise ValueError("No query specified.")
238
+ if not directory:
239
+ directory = AGENTS_DIR.joinpath(self.agent_id, 'queries')
240
+ try:
241
+ query_file = directory.joinpath(query)
242
+ return query_file.read_text().format(**kwargs)
243
+ except Exception as e:
244
+ self.logger.error(
245
+ f"Failed to format query: {e}"
246
+ )
247
+ return None
248
+
249
+ async def generate_report(
250
+ self,
251
+ prompt_file: str,
252
+ save: bool = False,
253
+ directory: Optional[Path] = None,
254
+ **kwargs
255
+ ) -> Tuple[AIMessage, AgentResponse]:
256
+ """Generate a report based on the provided prompt."""
257
+ try:
258
+ query = await self.open_prompt(prompt_file)
259
+ query = textwrap.dedent(query)
260
+ except (ValueError, RuntimeError) as e:
261
+ self.logger.error(f"Error opening prompt file: {e}")
262
+ return str(e)
263
+ # Format the question based on keyword arguments:
264
+ question = query.format(**kwargs)
265
+ if not directory:
266
+ directory = STATIC_DIR.joinpath(self.agent_id, 'documents')
267
+ try:
268
+ response = await self.invoke(
269
+ question=question,
270
+ )
271
+ # Create the response object
272
+ final_report = response.output.strip()
273
+ if not final_report:
274
+ raise ValueError("The generated report is empty.")
275
+ response_data = self._agent_response(
276
+ session_id=response.turn_id,
277
+ data=final_report,
278
+ agent_name=self.name,
279
+ agent_id=self.agent_id,
280
+ response=response,
281
+ status="success",
282
+ created_at=datetime.now(),
283
+ output=response.output,
284
+ **kwargs
285
+ )
286
+ # before returning, we can save the report if needed:
287
+ if save:
288
+ try:
289
+ report_filename = self._create_filename(
290
+ prefix='report', extension='txt'
291
+ )
292
+ async with aiofiles.open(
293
+ directory.joinpath(report_filename),
294
+ 'w'
295
+ ) as report_file:
296
+ await report_file.write(final_report)
297
+ response_data.document_path = report_filename
298
+ self.logger.info(f"Report saved as {report_filename}")
299
+ except Exception as e:
300
+ self.logger.error(f"Error saving report: {e}")
301
+ return response, response_data
302
+ except Exception as e:
303
+ self.logger.error(f"Error generating report: {e}")
304
+ return str(e)
305
+
306
+ async def save_transcript(
307
+ self,
308
+ transcript: str,
309
+ filename: str = None,
310
+ prefix: str = 'transcript',
311
+ directory: Optional[str] = None,
312
+ subdir='transcripts'
313
+ ) -> str:
314
+ """Save the transcript to a file."""
315
+ if not directory:
316
+ directory = STATIC_DIR.joinpath(self.agent_id, subdir)
317
+ directory.mkdir(parents=True, exist_ok=True)
318
+ # Create a unique filename if not provided
319
+ if not filename:
320
+ filename = self._create_filename(prefix=prefix, extension='txt')
321
+ file_path = directory.joinpath(filename)
322
+ try:
323
+ async with aiofiles.open(file_path, 'w') as f:
324
+ await f.write(transcript)
325
+ self.logger.info(f"Transcript saved to {file_path}")
326
+ return file_path
327
+ except Exception as e:
328
+ self.logger.error(f"Error saving transcript: {e}")
329
+ raise RuntimeError(
330
+ f"Failed to save transcript: {e}"
331
+ ) from e
332
+
333
+ async def pdf_report(
334
+ self,
335
+ content: str,
336
+ filename_prefix: str = 'report',
337
+ directory: Optional[Path] = None,
338
+ title: str = None,
339
+ **kwargs
340
+ ) -> str:
341
+ """Generate a report based on the provided prompt."""
342
+ # Create a unique filename for the report
343
+ if not directory:
344
+ directory = STATIC_DIR.joinpath(self.agent_id, 'documents')
345
+ pdf_tool = PDFPrintTool(
346
+ templates_dir=BASE_DIR.joinpath('templates'),
347
+ output_dir=directory
348
+ )
349
+ return await pdf_tool.execute(
350
+ text=content,
351
+ template_vars={"title": title or 'Report'},
352
+ template_name=self.report_template,
353
+ file_prefix=filename_prefix,
354
+
355
+ )
356
+
357
+ async def markdown_report(
358
+ self,
359
+ content: str,
360
+ filename: Optional[str] = None,
361
+ filename_prefix: str = 'report',
362
+ directory: Optional[Path] = None,
363
+ subdir: str = 'documents',
364
+ **kwargs
365
+ ) -> str:
366
+ """Saving Markdown report based on provided file."""
367
+ # Create a unique filename for the report
368
+ if not directory:
369
+ directory = STATIC_DIR.joinpath(self.agent_id, subdir)
370
+ directory.mkdir(parents=True, exist_ok=True)
371
+ # Create a unique filename if not provided
372
+ if not filename:
373
+ filename = self._create_filename(prefix=filename_prefix, extension='md')
374
+ file_path = directory.joinpath(filename)
375
+ try:
376
+ async with aiofiles.open(file_path, 'w') as f:
377
+ await f.write(content)
378
+ self.logger.info(f"Transcript saved to {file_path}")
379
+ return file_path
380
+ except Exception as e:
381
+ self.logger.error(f"Error saving transcript: {e}")
382
+ raise RuntimeError(
383
+ f"Failed to save transcript: {e}"
384
+ ) from e
385
+
386
+ async def speech_report(
387
+ self,
388
+ report: str,
389
+ max_lines: int = 15,
390
+ num_speakers: int = 2,
391
+ podcast_instructions: Optional[str] = 'for_podcast.txt',
392
+ directory: Optional[Path] = None,
393
+ **kwargs
394
+ ) -> Dict[str, Any]:
395
+ """Generate a Transcript Report and a Podcast based on findings."""
396
+ if directory:
397
+ output_directory = directory
398
+ else:
399
+ output_directory = STATIC_DIR.joinpath(self.agent_id, 'generated_scripts')
400
+ output_directory.mkdir(parents=True, exist_ok=True)
401
+ script_name = self._create_filename(prefix='script', extension='txt')
402
+ # creation of speakers:
403
+ speakers = []
404
+ for _, speaker in self.speakers.items():
405
+ speaker['gender'] = speaker.get('gender', 'neutral').lower()
406
+ speakers.append(FictionalSpeaker(**speaker))
407
+ if len(speakers) > num_speakers:
408
+ self.logger.warning(
409
+ f"Too many speakers defined, limiting to {num_speakers}."
410
+ )
411
+ break
412
+
413
+ # 1. Define the script configuration
414
+ # Check if podcast_instructions is content or filename
415
+ if podcast_instructions and (
416
+ '\n' in podcast_instructions or len(podcast_instructions) > 100
417
+ ):
418
+ # It's likely content (has newlines or is long), use it directly
419
+ podcast_instruction = podcast_instructions
420
+ else:
421
+ # It's a filename, load it
422
+ podcast_instruction = await self.open_prompt(
423
+ podcast_instructions or 'for_podcast.txt'
424
+ )
425
+
426
+ # Format the instruction with report text if it has placeholders
427
+ if podcast_instruction and '{report_text}' in podcast_instruction:
428
+ podcast_instruction = podcast_instruction.format(report_text=report)
429
+ script_config = ConversationalScriptConfig(
430
+ context=self.speech_context,
431
+ speakers=speakers,
432
+ report_text=report,
433
+ system_prompt=self.speech_system_prompt,
434
+ length=self.speech_length, # Use the speech_length attribute
435
+ system_instruction=podcast_instruction or None
436
+ )
437
+ async with self.client as client:
438
+ # 2. Generate the conversational script
439
+ response = await client.create_conversation_script(
440
+ report_data=script_config,
441
+ max_lines=max_lines, # Limit to 15 lines for brevity,
442
+ use_structured_output=True # Use structured output for TTS
443
+ )
444
+ voice_prompt = response.output
445
+ # 3. Save the script to a File:
446
+ script_output_path = output_directory.joinpath(script_name)
447
+ async with aiofiles.open(script_output_path, 'w') as script_file:
448
+ await script_file.write(voice_prompt.prompt)
449
+ self.logger.info(f"Script saved to {script_output_path}")
450
+ # 4. Generate the audio podcast
451
+ output_directory = STATIC_DIR.joinpath(self.agent_id, 'podcasts')
452
+ output_directory.mkdir(parents=True, exist_ok=True)
453
+ async with self.client as client:
454
+ speech_result = await client.generate_speech(
455
+ prompt_data=voice_prompt,
456
+ output_directory=output_directory,
457
+ )
458
+ if speech_result and speech_result.files:
459
+ print(f"✅ Multi-voice speech saved to: {speech_result.files[0]}")
460
+ # 5 Return the script and audio file paths
461
+ return {
462
+ 'script_path': script_output_path,
463
+ 'podcast_path': speech_result.files[0] if speech_result.files else None
464
+ }
465
+
466
+ async def report(self, prompt_file: str, **kwargs) -> AgentResponse:
467
+ """Generate a report based on the provided prompt."""
468
+ query = await self.open_prompt(prompt_file)
469
+ question = query.format(
470
+ **kwargs
471
+ )
472
+ try:
473
+ response = await self.conversation(
474
+ question=question,
475
+ max_tokens=8192
476
+ )
477
+ if isinstance(response, Exception):
478
+ raise response
479
+ except Exception as e:
480
+ print(f"Error invoking agent: {e}")
481
+ raise RuntimeError(
482
+ f"Failed to generate report due to an error in the agent invocation: {e}"
483
+ )
484
+ # Prepare the response object:
485
+ final_report = response.output.strip()
486
+ for key, value in kwargs.items():
487
+ if hasattr(response, key):
488
+ setattr(response, key, value)
489
+ response = self._agent_response(
490
+ user_id=str(kwargs.get('user_id', 1)),
491
+ agent_name=self.name,
492
+ attributes=kwargs.pop('attributes', {}),
493
+ data=final_report,
494
+ status="success",
495
+ created_at=datetime.now(),
496
+ output=response.output,
497
+ response=response,
498
+ **kwargs
499
+ )
500
+ return await self._generate_report(response)
501
+
502
+ async def _generate_report(
503
+ self,
504
+ response: AgentResponse,
505
+ with_speech: bool = True
506
+ ) -> AgentResponse:
507
+ """Generate a report from the response data."""
508
+ final_report = response.output.strip()
509
+ if not final_report:
510
+ response.output = "No report generated."
511
+ response.status = "error"
512
+ return response
513
+ response.transcript = final_report
514
+ try:
515
+ _path = await self.save_transcript(
516
+ transcript=final_report,
517
+ )
518
+ response.add_document(_path)
519
+ except Exception as e:
520
+ self.logger.error(f"Error generating transcript: {e}")
521
+ # generate the PDF file:
522
+ try:
523
+ pdf_output = await self.pdf_report(
524
+ content=final_report
525
+ )
526
+ response.set_pdf_path(
527
+ pdf_output.result.get('file_path', None)
528
+ )
529
+ except Exception as e:
530
+ self.logger.error(f"Error generating PDF: {e}")
531
+ # generate the podcast file:
532
+ if with_speech:
533
+ try:
534
+ podcast_output = await self.speech_report(
535
+ report=final_report,
536
+ max_lines=self.speech_length,
537
+ num_speakers=self.num_speakers
538
+ )
539
+ response.podcast_path = str(podcast_output.get('podcast_path', None))
540
+ response.script_path = str(podcast_output.get('script_path', None))
541
+ response.set_podcast_path(podcast_output.get('podcast_path', None))
542
+ except Exception as e:
543
+ self.logger.error(
544
+ f"Error generating podcast: {e}"
545
+ )
546
+ # Save the final report to the response
547
+ response.output = textwrap.fill(final_report, width=80)
548
+ response.status = "success"
549
+ return response
550
+
551
+ async def generate_presentation(
552
+ self,
553
+ content: str,
554
+ filename_prefix: str = 'report',
555
+ template_name: Optional[str] = None,
556
+ pptx_template: str = "corporate_template.pptx",
557
+ output_dir: Optional[Path] = None,
558
+ title: str = None,
559
+ **kwargs
560
+ ):
561
+ """Generate a PowerPoint presentation using the provided tool."""
562
+ if not output_dir:
563
+ output_dir = STATIC_DIR.joinpath(self.agent_id, 'documents')
564
+ tool = PowerPointTool(
565
+ templates_dir=BASE_DIR.joinpath('templates'),
566
+ output_dir=output_dir
567
+ )
568
+ return await tool.execute(
569
+ content=content,
570
+ template_name=None, # Explicitly disable HTML template
571
+ template_vars=None, # No template variables
572
+ split_by_headings=True, # Ensure heading-based splitting is enabled
573
+ pptx_template=pptx_template,
574
+ slide_layout=1,
575
+ title_styles={
576
+ "font_name": "Segoe UI",
577
+ "font_size": 24,
578
+ "bold": True,
579
+ "font_color": "#1f497d"
580
+ },
581
+ content_styles={
582
+ "font_name": "Segoe UI",
583
+ "font_size": 14,
584
+ "alignment": "left",
585
+ "font_color": "#333333"
586
+ },
587
+ max_slides=20,
588
+ file_prefix=filename_prefix,
589
+ )
590
+
591
+ async def create_speech(
592
+ self,
593
+ content: str,
594
+ language: str = "en-US",
595
+ only_script: bool = False,
596
+ **kwargs
597
+ ) -> Dict[str, Any]:
598
+ """Generate a Transcript Report and a Podcast based on findings."""
599
+ output_directory = STATIC_DIR.joinpath(self.agent_id, 'documents')
600
+ output_directory.mkdir(parents=True, exist_ok=True)
601
+ script_name = self._create_filename(prefix='script', extension='txt')
602
+ podcast_name = self._create_filename(prefix='podcast', extension='wav')
603
+ try:
604
+ async with self.client as client:
605
+ # 1. Generate the conversational script and podcast:
606
+ return await client.create_speech(
607
+ content=content,
608
+ output_directory=output_directory,
609
+ only_script=only_script,
610
+ script_file=script_name,
611
+ podcast_file=podcast_name,
612
+ language=language,
613
+ )
614
+ except Exception as e:
615
+ self.logger.error(
616
+ f"Error generating speech: {e}"
617
+ )
618
+ raise RuntimeError(
619
+ f"Failed to generate speech: {e}"
620
+ ) from e
621
+
622
+ # =================================================================
623
+ # MCP Server Management Methods
624
+ # =================================================================
625
+
626
+ async def add_mcp_server(self, config: MCPServerConfig) -> List[str]:
627
+ """
628
+ Add an MCP server and register its tools.
629
+
630
+ Args:
631
+ config: MCPServerConfig with connection details
632
+
633
+ Returns:
634
+ List of registered tool names
635
+
636
+ Example:
637
+ >>> config = MCPServerConfig(
638
+ ... name="weather_api",
639
+ ... url="https://api.example.com/mcp",
640
+ ... auth_type="api_key",
641
+ ... auth_config={"api_key": "xxx"}
642
+ ... )
643
+ >>> tools = await agent.add_mcp_server(config)
644
+ """
645
+ return await self.mcp_manager.add_mcp_server(config)
646
+
647
+ async def add_mcp_server_url(
648
+ self,
649
+ name: str,
650
+ url: str,
651
+ auth_type: Optional[str] = None,
652
+ auth_config: Optional[Dict[str, Any]] = None,
653
+ headers: Optional[Dict[str, str]] = None,
654
+ allowed_tools: Optional[List[str]] = None,
655
+ blocked_tools: Optional[List[str]] = None,
656
+ **kwargs
657
+ ) -> List[str]:
658
+ """
659
+ Convenience method to add a public URL-based MCP server.
660
+
661
+ This is a simplified interface for adding HTTP MCP servers
662
+ without manually creating MCPServerConfig objects.
663
+
664
+ Args:
665
+ name: Unique name for the MCP server
666
+ url: Base URL of the MCP server
667
+ auth_type: Optional authentication type ('api_key', 'bearer', 'oauth', 'basic')
668
+ auth_config: Authentication configuration dict
669
+ headers: Additional HTTP headers
670
+ allowed_tools: Whitelist of tool names to register
671
+ blocked_tools: Blacklist of tool names to skip
672
+ **kwargs: Additional MCPServerConfig parameters
673
+
674
+ Returns:
675
+ List of registered tool names
676
+
677
+ Examples:
678
+ >>> # Public server with no auth
679
+ >>> tools = await agent.add_mcp_server_url(
680
+ ... "public_api",
681
+ ... "https://api.example.com/mcp"
682
+ ... )
683
+
684
+ >>> # API key authenticated server
685
+ >>> tools = await agent.add_mcp_server_url(
686
+ ... "weather",
687
+ ... "https://weather.example.com/mcp",
688
+ ... auth_type="api_key",
689
+ ... auth_config={"api_key": "your-key-here"}
690
+ ... )
691
+
692
+ >>> # Server with custom headers and tool filtering
693
+ >>> tools = await agent.add_mcp_server_url(
694
+ ... "finance",
695
+ ... "https://finance.example.com/mcp",
696
+ ... headers={"User-Agent": "AI-Parrot/1.0"},
697
+ ... allowed_tools=["get_stock_price", "get_market_data"]
698
+ ... )
699
+ """
700
+ config = create_http_mcp_server(
701
+ name=name,
702
+ url=url,
703
+ auth_type=auth_type,
704
+ auth_config=auth_config,
705
+ headers=headers,
706
+ **kwargs
707
+ )
708
+
709
+ # Apply tool filtering if specified
710
+ if allowed_tools:
711
+ config.allowed_tools = allowed_tools
712
+ if blocked_tools:
713
+ config.blocked_tools = blocked_tools
714
+
715
+ return await self.add_mcp_server(config)
716
+
717
+ async def add_local_mcp_server(
718
+ self,
719
+ name: str,
720
+ script_path: Union[str, Path],
721
+ interpreter: str = "python",
722
+ **kwargs
723
+ ) -> List[str]:
724
+ """
725
+ Add a local stdio MCP server.
726
+
727
+ Args:
728
+ name: Unique name for the MCP server
729
+ script_path: Path to the MCP server script
730
+ interpreter: Interpreter to use (default: "python")
731
+ **kwargs: Additional MCPServerConfig parameters
732
+
733
+ Returns:
734
+ List of registered tool names
735
+
736
+ Example:
737
+ >>> tools = await agent.add_local_mcp_server(
738
+ ... "local_tools",
739
+ ... "/path/to/mcp_server.py"
740
+ ... )
741
+ """
742
+ config = create_local_mcp_server(name, script_path, interpreter, **kwargs)
743
+ return await self.add_mcp_server(config)
744
+
745
+ async def add_http_mcp_server(
746
+ self,
747
+ name: str,
748
+ url: str,
749
+ auth_type: Optional[str] = None,
750
+ auth_config: Optional[Dict[str, Any]] = None,
751
+ headers: Optional[Dict[str, str]] = None,
752
+ **kwargs
753
+ ) -> List[str]:
754
+ """
755
+ Add an HTTP MCP server with optional authentication.
756
+
757
+ This is an alias for add_mcp_server_url for backward compatibility.
758
+
759
+ Args:
760
+ name: Unique name for the MCP server
761
+ url: Base URL of the MCP server
762
+ auth_type: Optional authentication type
763
+ auth_config: Authentication configuration
764
+ headers: Additional HTTP headers
765
+ **kwargs: Additional MCPServerConfig parameters
766
+
767
+ Returns:
768
+ List of registered tool names
769
+ """
770
+ config = create_http_mcp_server(
771
+ name, url, auth_type, auth_config, headers, **kwargs
772
+ )
773
+ return await self.add_mcp_server(config)
774
+
775
+ async def add_api_key_mcp_server(
776
+ self,
777
+ name: str,
778
+ url: str,
779
+ api_key: str,
780
+ header_name: str = "X-API-Key",
781
+ use_bearer_prefix: bool = False,
782
+ **kwargs
783
+ ) -> List[str]:
784
+ """
785
+ Add an API-key authenticated MCP server.
786
+
787
+ Args:
788
+ name: Unique name for the MCP server
789
+ url: Base URL of the MCP server
790
+ api_key: API key for authentication
791
+ header_name: Header name for the API key (default: "X-API-Key")
792
+ use_bearer_prefix: If True, prepend "Bearer " to the API key value (default: False)
793
+ **kwargs: Additional MCPServerConfig parameters
794
+
795
+ Returns:
796
+ List of registered tool names
797
+
798
+ Example:
799
+ >>> tools = await agent.add_api_key_mcp_server(
800
+ ... "weather_api",
801
+ ... "https://api.weather.com/mcp",
802
+ ... api_key="your-api-key",
803
+ ... header_name="Authorization"
804
+ ... )
805
+
806
+ >>> # For Bearer token format (e.g., Fireflies API)
807
+ >>> tools = await agent.add_api_key_mcp_server(
808
+ ... "fireflies",
809
+ ... "https://api.fireflies.ai/mcp",
810
+ ... api_key="your-api-key",
811
+ ... header_name="Authorization",
812
+ ... use_bearer_prefix=True
813
+ ... )
814
+ """
815
+ config = create_api_key_mcp_server(
816
+ name=name,
817
+ url=url,
818
+ api_key=api_key,
819
+ header_name=header_name,
820
+ use_bearer_prefix=use_bearer_prefix,
821
+ **kwargs
822
+ )
823
+ return await self.add_mcp_server(config)
824
+
825
+ async def remove_mcp_server(self, server_name: str):
826
+ """
827
+ Remove an MCP server and unregister its tools.
828
+
829
+ Args:
830
+ server_name: Name of the MCP server to remove
831
+ """
832
+ await self.mcp_manager.remove_mcp_server(server_name)
833
+ self.logger.info(f"Removed MCP server: {server_name}")
834
+
835
+ def list_mcp_servers(self) -> List[str]:
836
+ """
837
+ List all connected MCP servers.
838
+
839
+ Returns:
840
+ List of MCP server names
841
+ """
842
+ return self.mcp_manager.list_mcp_servers()
843
+
844
+ def get_mcp_client(self, server_name: str):
845
+ """
846
+ Get the MCP client for a specific server.
847
+
848
+ Args:
849
+ server_name: Name of the MCP server
850
+
851
+ Returns:
852
+ MCPClient instance or None
853
+ """
854
+ return self.mcp_manager.get_mcp_client(server_name)
855
+
856
+ async def shutdown(self, **kwargs):
857
+ """
858
+ Shutdown the agent and disconnect all MCP servers.
859
+ """
860
+ if hasattr(self, 'mcp_manager'):
861
+ await self.mcp_manager.disconnect_all()
862
+ self.logger.info("Disconnected all MCP servers")
863
+
864
+ if hasattr(super(), 'shutdown'):
865
+ await super().shutdown(**kwargs)
866
+
867
+ def as_tool(
868
+ self,
869
+ tool_name: str = None,
870
+ tool_description: str = None,
871
+ use_conversation_method: bool = True,
872
+ context_filter: Optional[Callable[[AgentContext], AgentContext]] = None
873
+ ) -> 'AgentTool':
874
+ """
875
+ Convert this agent into an AgentTool that can be used by other agents.
876
+
877
+ This allows agents to be composed and used as tools in orchestration scenarios.
878
+
879
+ Args:
880
+ tool_name: Custom name for the tool (defaults to agent name)
881
+ tool_description: Description of what this agent does
882
+ use_conversation_method: Whether to use conversation() or invoke()
883
+ context_filter: Optional function to filter context before execution
884
+ question_description: Custom description for the query parameter
885
+ context_description: Custom description for the context parameter
886
+
887
+ Returns:
888
+ AgentTool: Tool wrapper for this agent
889
+
890
+ Example:
891
+ >>> hr_agent = BasicAgent(name="HRAgent", ...)
892
+ >>> hr_tool = hr_agent.as_tool(
893
+ ... tool_description="Handles HR policy questions"
894
+ ... )
895
+ >>> orchestrator.tool_manager.add_tool(hr_tool)
896
+ """
897
+ # Default descriptions based on agent properties
898
+ default_description = (
899
+ f"Specialized agent: {self.name}. "
900
+ f"Role: {self.role}. "
901
+ f"Goal: {self.goal}."
902
+ )
903
+
904
+ return AgentTool(
905
+ agent=self,
906
+ tool_name=tool_name,
907
+ tool_description=tool_description or default_description,
908
+ use_conversation_method=use_conversation_method,
909
+ context_filter=context_filter,
910
+ )
911
+
912
+ def register_as_tool(
913
+ self,
914
+ target_agent: 'BasicAgent',
915
+ tool_name: str = None,
916
+ tool_description: str = None,
917
+ **kwargs
918
+ ) -> None:
919
+ """
920
+ Register this agent as a tool in another agent's tool manager.
921
+
922
+ This is a convenience method that combines as_tool() and registration.
923
+
924
+ Args:
925
+ target_agent: The agent to register this tool with
926
+ tool_name: Custom name for the tool
927
+ tool_description: Description of what this agent does
928
+ **kwargs: Additional arguments for as_tool()
929
+
930
+ Example:
931
+ >>> hr_agent = BasicAgent(name="HRAgent", ...)
932
+ >>> employee_agent = BasicAgent(name="EmployeeAgent", ...)
933
+ >>> orchestrator = OrchestratorAgent(name="Orchestrator")
934
+ >>>
935
+ >>> hr_agent.register_as_tool(
936
+ ... orchestrator,
937
+ ... tool_description="Handles HR policies and procedures"
938
+ ... )
939
+ >>> employee_agent.register_as_tool(
940
+ ... orchestrator,
941
+ ... tool_description="Manages employee data"
942
+ ... )
943
+ """
944
+ agent_tool = self.as_tool(
945
+ tool_name=tool_name,
946
+ tool_description=tool_description,
947
+ **kwargs
948
+ )
949
+
950
+ # Register in bot's tool manager
951
+ target_agent.tool_manager.add_tool(agent_tool)
952
+
953
+ # CRITICAL: Sync tools to LLM after registration
954
+ if hasattr(target_agent, '_sync_tools_to_llm'):
955
+ target_agent._sync_tools_to_llm()
956
+
957
+ self.logger.info(
958
+ f"Registered {self.name} as tool '{agent_tool.name}' "
959
+ f"in {target_agent.name}'s tool manager"
960
+ )
961
+
962
+ def add_dataframe(self, df, name: str = None):
963
+ """
964
+ Add a dataframe to the agent and configure PythonPandasTool.
965
+
966
+ Args:
967
+ df: pandas DataFrame to add
968
+ name: Optional name for the dataframe. If None, uses df{index}
969
+ """
970
+ if not isinstance(df, pd.DataFrame):
971
+ raise ValueError("Input must be a pandas DataFrame")
972
+
973
+ # Generate name if not provided
974
+ if name is None:
975
+ name = f"df{len(self.dataframes)}"
976
+
977
+ # Store dataframe
978
+ self.dataframes[name] = df
979
+
980
+ # Clear cache to regenerate dataframe info
981
+ self._dataframe_info_cache = None
982
+
983
+ # Add or update PythonPandasTool
984
+ self._configure_pandas_tool()
985
+
986
+ # Update system prompt
987
+ self._update_system_prompt_with_dataframes()
988
+
989
+ return self
990
+
991
+ def _configure_pandas_tool(self):
992
+ """Add or reconfigure PythonPandasTool with current dataframes."""
993
+ # Check if tool already exists
994
+ pandas_tool = next(
995
+ (tool for tool in self.tools if isinstance(tool, PythonPandasTool)),
996
+ None,
997
+ )
998
+
999
+ if pandas_tool is None:
1000
+ # Create new PythonPandasTool
1001
+ pandas_tool = PythonPandasTool(
1002
+ dataframes=self.dataframes
1003
+ )
1004
+ self.tool_manager.add_tool(pandas_tool)
1005
+ else:
1006
+ # Update existing tool with new dataframes
1007
+ pandas_tool.dataframes = self.dataframes
1008
+
1009
+ def _generate_dataframe_info(self) -> str:
1010
+ """Generate dataframe information for system prompt."""
1011
+ if not self.dataframes:
1012
+ return ""
1013
+
1014
+ if self._dataframe_info_cache is not None:
1015
+ return self._dataframe_info_cache
1016
+
1017
+ info_parts = ["# Available DataFrames\n"]
1018
+
1019
+ for name, df in self.dataframes.items():
1020
+ info_parts.extend(
1021
+ (
1022
+ f"\n## DataFrame: `{name}`",
1023
+ f"- Shape: {df.shape[0]} rows × {df.shape[1]} columns",
1024
+ f"- Columns: {', '.join(df.columns.tolist())}",
1025
+ "- Column Types:",
1026
+ )
1027
+ )
1028
+ info_parts.extend(f" - {col}: {dtype}" for col, dtype in df.dtypes.items())
1029
+
1030
+ # Add sample statistics for numeric columns
1031
+ numeric_cols = df.select_dtypes(include=['number']).columns
1032
+ if len(numeric_cols) > 0:
1033
+ info_parts.append("- Summary Statistics (numeric columns):")
1034
+ info_parts.extend(
1035
+ f" - {col}: min={df[col].min():.2f}, max={df[col].max():.2f}, mean={df[col].mean():.2f}"
1036
+ for col in numeric_cols[:5]
1037
+ )
1038
+ # Add sample rows
1039
+ info_parts.append(f"- Sample (first 3 rows):\n{df.head(3).to_string()}")
1040
+
1041
+ info_parts.append("\nUse PythonPandasTool to query and analyze these dataframes.")
1042
+
1043
+ self._dataframe_info_cache = "\n".join(info_parts)
1044
+ return self._dataframe_info_cache
1045
+
1046
+ def _update_system_prompt_with_dataframes(self):
1047
+ """Inject dataframe information into system prompt."""
1048
+ df_info = self._generate_dataframe_info()
1049
+
1050
+ if not df_info:
1051
+ return
1052
+
1053
+ # Find the position to inject (before $pre_context or $context)
1054
+ if "$pre_context" in self.system_prompt_template:
1055
+ marker = "$pre_context"
1056
+ elif "$context" in self.system_prompt_template:
1057
+ marker = "$context"
1058
+ else:
1059
+ # Append at the end if no markers found
1060
+ self.system_prompt_template += f"\n\n{df_info}"
1061
+ return
1062
+
1063
+ # Inject before the marker
1064
+ parts = self.system_prompt_template.split(marker, 1)
1065
+ self.system_prompt_template = f"{parts[0]}{df_info}\n\n{marker}{parts[1]}"
1066
+
1067
+ def remove_dataframe(self, name: str):
1068
+ """Remove a dataframe by name."""
1069
+ if name in self.dataframes:
1070
+ del self.dataframes[name]
1071
+ self._dataframe_info_cache = None
1072
+ self._configure_pandas_tool()
1073
+ self._update_system_prompt_with_dataframes()
1074
+
1075
+ async def followup(
1076
+ self,
1077
+ question: str,
1078
+ turn_id: str,
1079
+ data: Any,
1080
+ session_id: Optional[str] = None,
1081
+ user_id: Optional[str] = None,
1082
+ use_conversation_history: bool = True,
1083
+ memory: Optional[Any] = None,
1084
+ ctx: Optional[Any] = None,
1085
+ structured_output: Optional[Any] = None,
1086
+ output_mode: Any = None,
1087
+ format_kwargs: dict = None,
1088
+ return_structured: bool = True,
1089
+ **kwargs
1090
+ ) -> AIMessage:
1091
+ """Generate a follow-up question using a previous turn as context."""
1092
+ if not turn_id:
1093
+ raise ValueError("turn_id is required for follow-up questions")
1094
+
1095
+ session_id = session_id or str(uuid.uuid4())
1096
+ user_id = user_id or "anonymous"
1097
+
1098
+ previous_interaction = await self.agent_memory.get(turn_id)
1099
+ if not previous_interaction:
1100
+ raise ValueError(f"No conversation turn found for turn_id {turn_id}")
1101
+
1102
+ context_str = data if isinstance(data, str) else str(data)
1103
+ followup_prompt = (
1104
+ "Based on the previous question "
1105
+ f"{previous_interaction['question']} and answer {previous_interaction['answer']} "
1106
+ f"and using this data as context {context_str}, you need to answer this question:\n"
1107
+ f"{question}"
1108
+ )
1109
+
1110
+ return await self.ask(
1111
+ question=followup_prompt,
1112
+ session_id=session_id,
1113
+ user_id=user_id,
1114
+ use_conversation_history=use_conversation_history,
1115
+ memory=memory,
1116
+ ctx=ctx,
1117
+ structured_output=structured_output,
1118
+ output_mode=output_mode,
1119
+ format_kwargs=format_kwargs,
1120
+ return_structured=return_structured,
1121
+ **kwargs,
1122
+ )
1123
+
1124
+ class Agent(BasicAgent):
1125
+ """A general-purpose agent with no additional tools."""
1126
+
1127
+ def agent_tools(self) -> List[AbstractTool]:
1128
+ """Return the agent-specific tools."""
1129
+ return []