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,1014 @@
1
+ """
2
+ Elasticsearch/DocumentDB Agent Implementation for AI-Parrot.
3
+
4
+ Concrete implementation of AbstractDbAgent for Elasticsearch
5
+ with support for document-based queries and aggregations.
6
+ """
7
+
8
+ from typing import Dict, Any, List, Optional, Union
9
+ import asyncio
10
+ import json
11
+ from datetime import datetime
12
+ from urllib.parse import urlparse
13
+ from pydantic import Field
14
+ from elasticsearch import AsyncElasticsearch
15
+ from .abstract import (
16
+ AbstractDBAgent,
17
+ DatabaseSchema,
18
+ TableMetadata,
19
+ QueryGenerationArgs
20
+ )
21
+ from ...tools.abstract import AbstractTool, ToolResult, AbstractToolArgsSchema
22
+
23
+
24
+ class ElasticsearchQueryArgs(AbstractToolArgsSchema):
25
+ """Arguments for Elasticsearch query execution."""
26
+ query: Union[str, Dict[str, Any]] = Field(description="Elasticsearch query (JSON or Query DSL)")
27
+ index: Optional[str] = Field(default=None, description="Specific index to query")
28
+ size: int = Field(default=100, description="Maximum number of documents to return")
29
+ timeout: str = Field(default="30s", description="Query timeout")
30
+ explain: bool = Field(default=False, description="Include query explanation")
31
+
32
+
33
+ class IndexMetadata:
34
+ """Metadata for an Elasticsearch index (equivalent to a table)."""
35
+ def __init__(
36
+ self,
37
+ name: str,
38
+ mapping: Dict[str, Any],
39
+ settings: Dict[str, Any],
40
+ aliases: List[str] = None,
41
+ doc_count: int = 0,
42
+ size_in_bytes: int = 0,
43
+ sample_documents: List[Dict[str, Any]] = None
44
+ ):
45
+ self.name = name
46
+ self.mapping = mapping
47
+ self.settings = settings
48
+ self.aliases = aliases or []
49
+ self.doc_count = doc_count
50
+ self.size_in_bytes = size_in_bytes
51
+ self.sample_documents = sample_documents or []
52
+
53
+
54
+ class ElasticDbAgent(AbstractDBAgent):
55
+ """
56
+ Elasticsearch Agent for document database introspection and query generation.
57
+
58
+ Supports Elasticsearch with Query DSL and aggregations.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ name: str = "ElasticsearchAgent",
64
+ connection_string: str = None,
65
+ username: str = None,
66
+ password: str = None,
67
+ api_key: str = None,
68
+ cloud_id: str = None,
69
+ max_sample_docs: int = 10,
70
+ verify_certs: bool = True,
71
+ **kwargs
72
+ ):
73
+ """
74
+ Initialize Elasticsearch Agent.
75
+
76
+ Args:
77
+ name: Agent name
78
+ connection_string: Elasticsearch URL (e.g., 'http://localhost:9200')
79
+ username: Username for authentication
80
+ password: Password for authentication
81
+ api_key: API key for authentication (alternative to username/password)
82
+ cloud_id: Elastic Cloud ID (for Elastic Cloud)
83
+ max_sample_docs: Maximum sample documents per index
84
+ verify_certs: Whether to verify SSL certificates
85
+ """
86
+ self.username = username
87
+ self.password = password
88
+ self.api_key = api_key
89
+ self.cloud_id = cloud_id
90
+ self.max_sample_docs = max_sample_docs
91
+ self.verify_certs = verify_certs
92
+ self.client: Optional[AsyncElasticsearch] = None
93
+ self.indices_cache: Dict[str, IndexMetadata] = {}
94
+
95
+ super().__init__(
96
+ name=name,
97
+ connection_string=connection_string,
98
+ schema_name="elasticsearch", # Elasticsearch doesn't have schemas like SQL
99
+ **kwargs
100
+ )
101
+
102
+ # Add Elasticsearch-specific tools
103
+ self._setup_elastic_tools()
104
+
105
+ def _setup_elastic_tools(self):
106
+ """Setup Elasticsearch-specific tools."""
107
+ # Add query execution tool
108
+ es_query_tool = ElasticsearchQueryTool(agent=self)
109
+ self.add_tool(es_query_tool)
110
+
111
+ # Add index exploration tool
112
+ index_exploration_tool = IndexExplorationTool(agent=self)
113
+ self.add_tool(index_exploration_tool)
114
+
115
+ # Add aggregation tool
116
+ aggregation_tool = AggregationTool(agent=self)
117
+ self.add_tool(aggregation_tool)
118
+
119
+ async def connect_database(self) -> None:
120
+ """Connect to Elasticsearch using async client."""
121
+ try:
122
+ # Prepare connection parameters
123
+ connection_params = {
124
+ "verify_certs": self.verify_certs,
125
+ "request_timeout": 30,
126
+ "retry_on_timeout": True,
127
+ "max_retries": 3
128
+ }
129
+
130
+ # Authentication setup
131
+ if self.api_key:
132
+ connection_params["api_key"] = self.api_key
133
+ elif self.username and self.password:
134
+ connection_params["basic_auth"] = (self.username, self.password)
135
+
136
+ # Connection setup
137
+ if self.cloud_id:
138
+ connection_params["cloud_id"] = self.cloud_id
139
+ elif self.connection_string:
140
+ connection_params["hosts"] = [self.connection_string]
141
+ else:
142
+ raise ValueError("Either connection_string or cloud_id must be provided")
143
+
144
+ # Create client
145
+ self.client = AsyncElasticsearch(**connection_params)
146
+
147
+ # Test connection
148
+ cluster_info = await self.client.info()
149
+ self.logger.info(f"Connected to Elasticsearch cluster: {cluster_info['cluster_name']}")
150
+
151
+ except Exception as e:
152
+ self.logger.error(f"Failed to connect to Elasticsearch: {e}")
153
+ raise
154
+
155
+ async def extract_schema_metadata(self) -> DatabaseSchema:
156
+ """Extract schema metadata from Elasticsearch (indices, mappings, settings)."""
157
+ if not self.client:
158
+ await self.connect_database()
159
+
160
+ try:
161
+ # Get all indices
162
+ indices_info = await self.client.indices.get(index="*", ignore_unavailable=True)
163
+
164
+ # Get cluster stats for additional metadata
165
+ cluster_stats = await self.client.cluster.stats()
166
+
167
+ # Extract metadata for each index
168
+ indices_metadata = []
169
+ for index_name, index_info in indices_info.items():
170
+ # Skip system indices by default
171
+ if index_name.startswith('.') and not index_name.startswith('.custom'):
172
+ continue
173
+
174
+ index_metadata = await self._extract_index_metadata(index_name, index_info)
175
+ indices_metadata.append(index_metadata)
176
+
177
+ # Cache for later use
178
+ self.indices_cache[index_name] = index_metadata
179
+
180
+ # Convert indices to TableMetadata format
181
+ tables = self._convert_indices_to_tables(indices_metadata)
182
+
183
+ schema_metadata = DatabaseSchema(
184
+ database_name=cluster_stats["cluster_name"],
185
+ database_type="elasticsearch",
186
+ tables=tables,
187
+ views=[], # Elasticsearch doesn't have views, but could include index templates
188
+ functions=[], # Elasticsearch doesn't have stored functions
189
+ procedures=[], # Elasticsearch doesn't have stored procedures
190
+ metadata={
191
+ "cluster_name": cluster_stats["cluster_name"],
192
+ "cluster_version": cluster_stats["nodes"]["versions"],
193
+ "total_indices": len(indices_metadata),
194
+ "total_documents": cluster_stats["indices"]["count"],
195
+ "total_size_bytes": cluster_stats["indices"]["store"]["size_in_bytes"],
196
+ "extraction_timestamp": datetime.now().isoformat()
197
+ }
198
+ )
199
+
200
+ self.logger.info(f"Extracted metadata for {len(indices_metadata)} indices")
201
+
202
+ return schema_metadata
203
+
204
+ except Exception as e:
205
+ self.logger.error(f"Failed to extract Elasticsearch schema metadata: {e}")
206
+ raise
207
+
208
+ async def _extract_index_metadata(
209
+ self,
210
+ index_name: str,
211
+ index_info: Dict[str, Any]
212
+ ) -> IndexMetadata:
213
+ """Extract detailed metadata for a specific index."""
214
+ try:
215
+ # Get mapping
216
+ mapping = index_info.get("mappings", {})
217
+
218
+ # Get settings
219
+ settings = index_info.get("settings", {})
220
+
221
+ # Get aliases
222
+ aliases = list(index_info.get("aliases", {}).keys())
223
+
224
+ # Get index stats
225
+ stats_response = await self.client.indices.stats(index=index_name)
226
+ index_stats = stats_response["indices"].get(index_name, {})
227
+
228
+ doc_count = index_stats.get("total", {}).get("docs", {}).get("count", 0)
229
+ size_in_bytes = index_stats.get("total", {}).get("store", {}).get("size_in_bytes", 0)
230
+
231
+ # Get sample documents
232
+ sample_documents = await self._get_sample_documents(index_name)
233
+
234
+ return IndexMetadata(
235
+ name=index_name,
236
+ mapping=mapping,
237
+ settings=settings,
238
+ aliases=aliases,
239
+ doc_count=doc_count,
240
+ size_in_bytes=size_in_bytes,
241
+ sample_documents=sample_documents
242
+ )
243
+
244
+ except Exception as e:
245
+ self.logger.warning(f"Could not extract metadata for index {index_name}: {e}")
246
+ return IndexMetadata(
247
+ name=index_name,
248
+ mapping={},
249
+ settings={},
250
+ aliases=[],
251
+ doc_count=0,
252
+ size_in_bytes=0,
253
+ sample_documents=[]
254
+ )
255
+
256
+ async def _get_sample_documents(self, index_name: str) -> List[Dict[str, Any]]:
257
+ """Get sample documents from an index."""
258
+ try:
259
+ search_response = await self.client.search(
260
+ index=index_name,
261
+ body={
262
+ "query": {"match_all": {}},
263
+ "size": self.max_sample_docs
264
+ }
265
+ )
266
+
267
+ documents = []
268
+ for hit in search_response["hits"]["hits"]:
269
+ doc = {
270
+ "_id": hit["_id"],
271
+ "_source": hit["_source"]
272
+ }
273
+ documents.append(doc)
274
+
275
+ return documents
276
+
277
+ except Exception as e:
278
+ self.logger.warning(f"Could not get sample documents for {index_name}: {e}")
279
+ return []
280
+
281
+ def _convert_indices_to_tables(self, indices: List[IndexMetadata]) -> List[TableMetadata]:
282
+ """Convert Elasticsearch indices to TableMetadata format."""
283
+ tables = []
284
+
285
+ for index in indices:
286
+ # Extract field information from mapping
287
+ columns = self._extract_fields_from_mapping(index.mapping)
288
+
289
+ # Create table metadata
290
+ table_metadata = TableMetadata(
291
+ name=index.name,
292
+ schema="elasticsearch",
293
+ columns=columns,
294
+ primary_keys=["_id"], # Document ID is the primary key
295
+ foreign_keys=[], # Elasticsearch doesn't have foreign keys
296
+ indexes=[], # All fields are potentially indexed in Elasticsearch
297
+ description=f"Elasticsearch index with {index.doc_count} documents ({index.size_in_bytes} bytes)",
298
+ sample_data=[doc["_source"] for doc in index.sample_documents]
299
+ )
300
+
301
+ tables.append(table_metadata)
302
+
303
+ return tables
304
+
305
+ def _extract_fields_from_mapping(self, mapping: Dict[str, Any]) -> List[Dict[str, Any]]:
306
+ """Extract field information from Elasticsearch mapping."""
307
+ columns = []
308
+
309
+ # Add standard Elasticsearch fields
310
+ columns.append({
311
+ "name": "_id",
312
+ "type": "keyword",
313
+ "nullable": False,
314
+ "description": "Document ID"
315
+ })
316
+
317
+ columns.append({
318
+ "name": "_source",
319
+ "type": "object",
320
+ "nullable": False,
321
+ "description": "Document source"
322
+ })
323
+
324
+ # Extract fields from properties
325
+ properties = mapping.get("properties", {})
326
+ self._extract_properties_recursive(properties, columns)
327
+
328
+ return columns
329
+
330
+ def _extract_properties_recursive(
331
+ self,
332
+ properties: Dict[str, Any],
333
+ columns: List[Dict[str, Any]],
334
+ prefix: str = ""
335
+ ):
336
+ """Recursively extract properties from mapping."""
337
+ for field_name, field_config in properties.items():
338
+ full_field_name = f"{prefix}.{field_name}" if prefix else field_name
339
+
340
+ field_type = field_config.get("type", "object")
341
+
342
+ column_info = {
343
+ "name": full_field_name,
344
+ "type": field_type,
345
+ "nullable": True, # Most Elasticsearch fields are nullable
346
+ "description": f"Elasticsearch field of type {field_type}"
347
+ }
348
+
349
+ # Add additional field properties
350
+ if "analyzer" in field_config:
351
+ column_info["analyzer"] = field_config["analyzer"]
352
+ if "format" in field_config:
353
+ column_info["format"] = field_config["format"]
354
+
355
+ columns.append(column_info)
356
+
357
+ # Handle nested objects and nested types
358
+ if field_type in ["object", "nested"] and "properties" in field_config:
359
+ self._extract_properties_recursive(
360
+ field_config["properties"],
361
+ columns,
362
+ full_field_name
363
+ )
364
+
365
+ async def generate_query(
366
+ self,
367
+ natural_language_query: str,
368
+ target_tables: Optional[List[str]] = None,
369
+ query_type: str = "search"
370
+ ) -> Dict[str, Any]:
371
+ """Generate Elasticsearch query from natural language."""
372
+ try:
373
+ # Get schema context for the query
374
+ schema_context = await self._get_schema_context_for_query(
375
+ natural_language_query, target_tables
376
+ )
377
+
378
+ # Build Elasticsearch query generation prompt
379
+ prompt = self._build_es_query_prompt(
380
+ natural_language_query=natural_language_query,
381
+ schema_context=schema_context,
382
+ query_type=query_type
383
+ )
384
+
385
+ # Generate query using LLM
386
+ response = await self.llm_client.generate_response(
387
+ prompt=prompt,
388
+ model=self.model_name,
389
+ temperature=0.1
390
+ )
391
+
392
+ # Extract and parse Elasticsearch query
393
+ es_query = self._extract_es_query_from_response(response.output)
394
+
395
+ # Validate query structure
396
+ validation_result = self._validate_es_query(es_query)
397
+
398
+ result = {
399
+ "query": es_query,
400
+ "query_type": query_type,
401
+ "indices_used": target_tables or self._extract_indices_from_context(schema_context),
402
+ "schema_context_used": len(schema_context),
403
+ "validation": validation_result,
404
+ "natural_language_input": natural_language_query
405
+ }
406
+
407
+ return result
408
+
409
+ except Exception as e:
410
+ self.logger.error(f"Failed to generate Elasticsearch query: {e}")
411
+ raise
412
+
413
+ def _build_es_query_prompt(
414
+ self,
415
+ natural_language_query: str,
416
+ schema_context: List[Dict[str, Any]],
417
+ query_type: str
418
+ ) -> str:
419
+ """Build prompt for Elasticsearch query generation."""
420
+ prompt = f"""
421
+ You are an expert Elasticsearch developer.
422
+ Generate an Elasticsearch Query DSL based on the natural language request and the provided index schema information.
423
+
424
+ Natural Language Request: {natural_language_query}
425
+
426
+ Available Indices and Schema:
427
+ """
428
+
429
+ for i, context in enumerate(schema_context[:3], 1):
430
+ prompt += f"\n{i}. {context.get('content', '')}\n"
431
+
432
+ prompt += f"""
433
+
434
+ Elasticsearch Query DSL Guidelines:
435
+ 1. Use proper Query DSL JSON structure
436
+ 2. Common query types: match, term, bool, range, exists, wildcard, fuzzy
437
+ 3. Use aggregations for analytics: terms, date_histogram, avg, sum, etc.
438
+ 4. Use filters for exact matches and queries for scoring
439
+ 5. Consider performance - use filters when possible
440
+ 6. Return valid JSON that can be used directly with Elasticsearch
441
+ 7. Do not include index name in the query body (it will be specified separately)
442
+
443
+ Query Type: {query_type}
444
+
445
+ Example structures:
446
+ - Search: {{"query": {{"match": {{"field": "value"}}}}, "size": 10}}
447
+ - Aggregation: {{"aggs": {{"my_agg": {{"terms": {{"field": "category"}}}}}}}}
448
+ - Complex: {{"query": {{"bool": {{"must": [...], "filter": [...]}}}}}}
449
+
450
+ Return only the JSON query without explanations:"""
451
+
452
+ return prompt
453
+
454
+ def _extract_es_query_from_response(self, response_text: str) -> Dict[str, Any]:
455
+ """Extract and parse Elasticsearch query from LLM response."""
456
+ try:
457
+ # Remove markdown code blocks if present
458
+ if "```json" in response_text:
459
+ lines = response_text.split('\n')
460
+ json_lines = []
461
+ in_json_block = False
462
+
463
+ for line in lines:
464
+ if line.strip().startswith("```json"):
465
+ in_json_block = True
466
+ continue
467
+ elif line.strip() == "```" and in_json_block:
468
+ break
469
+ elif in_json_block:
470
+ json_lines.append(line)
471
+
472
+ json_text = '\n'.join(json_lines).strip()
473
+ else:
474
+ json_text = response_text.strip()
475
+
476
+ # Parse JSON
477
+ return json.loads(json_text)
478
+
479
+ except json.JSONDecodeError as e:
480
+ self.logger.warning(f"Failed to parse JSON from response: {e}")
481
+ # Return a basic match_all query as fallback
482
+ return {"query": {"match_all": {}}}
483
+
484
+ def _validate_es_query(self, query: Dict[str, Any]) -> Dict[str, Any]:
485
+ """Validate Elasticsearch query structure."""
486
+ try:
487
+ if not isinstance(query, dict):
488
+ return {
489
+ "valid": False,
490
+ "error": "Query must be a JSON object",
491
+ "message": "Invalid query structure"
492
+ }
493
+
494
+ # Basic structure validation
495
+ valid_top_level_keys = [
496
+ "query", "aggs", "aggregations", "size", "from",
497
+ "sort", "_source", "highlight", "suggest"
498
+ ]
499
+
500
+ for key in query.keys():
501
+ if key not in valid_top_level_keys:
502
+ return {
503
+ "valid": False,
504
+ "error": f"Invalid top-level key: {key}",
505
+ "message": "Query contains invalid keys"
506
+ }
507
+
508
+ return {
509
+ "valid": True,
510
+ "error": None,
511
+ "message": "Query structure is valid"
512
+ }
513
+
514
+ except Exception as e:
515
+ return {
516
+ "valid": False,
517
+ "error": str(e),
518
+ "message": "Query validation error"
519
+ }
520
+
521
+ def _extract_indices_from_context(self, schema_context: List[Dict[str, Any]]) -> List[str]:
522
+ """Extract index names from schema context."""
523
+ indices = []
524
+ for context in schema_context:
525
+ if context.get("type") == "table" and context.get("name"):
526
+ indices.append(context["name"])
527
+ return indices
528
+
529
+ async def execute_query(
530
+ self,
531
+ query: Union[str, Dict[str, Any]],
532
+ index: Optional[str] = None,
533
+ size: int = 100
534
+ ) -> Dict[str, Any]:
535
+ """Execute Elasticsearch query."""
536
+ try:
537
+ if not self.client:
538
+ await self.connect_database()
539
+
540
+ # Parse query if it's a string
541
+ if isinstance(query, str):
542
+ query = json.loads(query)
543
+
544
+ # Set default size if not specified
545
+ if "size" not in query:
546
+ query["size"] = size
547
+
548
+ # Determine target index
549
+ target_index = index or "_all"
550
+
551
+ # Execute search
552
+ response = await self.client.search(
553
+ index=target_index,
554
+ body=query
555
+ )
556
+
557
+ # Process results
558
+ hits = response["hits"]["hits"]
559
+ documents = []
560
+
561
+ for hit in hits:
562
+ doc = {
563
+ "_index": hit["_index"],
564
+ "_id": hit["_id"],
565
+ "_score": hit.get("_score"),
566
+ **hit["_source"]
567
+ }
568
+ documents.append(doc)
569
+
570
+ # Process aggregations if present
571
+ aggregations = response.get("aggregations", {})
572
+
573
+ result = {
574
+ "success": True,
575
+ "documents": documents,
576
+ "total_hits": response["hits"]["total"]["value"],
577
+ "max_score": response["hits"]["max_score"],
578
+ "took_ms": response["took"],
579
+ "aggregations": aggregations,
580
+ "query": query,
581
+ "target_index": target_index
582
+ }
583
+
584
+ return result
585
+
586
+ except Exception as e:
587
+ self.logger.error(f"Elasticsearch query execution failed: {e}")
588
+ return {
589
+ "success": False,
590
+ "error": str(e),
591
+ "query": query,
592
+ "target_index": index
593
+ }
594
+
595
+ async def close(self):
596
+ """Close Elasticsearch client connection."""
597
+ if self.client:
598
+ await self.client.close()
599
+
600
+
601
+ class ElasticsearchQueryTool(AbstractTool):
602
+ """Tool for executing Elasticsearch queries."""
603
+
604
+ name = "execute_elasticsearch_query"
605
+ description = "Execute Elasticsearch Query DSL against the cluster"
606
+ args_schema = ElasticsearchQueryArgs
607
+
608
+ def __init__(self, agent: ElasticDbAgent, **kwargs):
609
+ super().__init__(**kwargs)
610
+ self.agent = agent
611
+
612
+ async def _execute(
613
+ self,
614
+ query: Union[str, Dict[str, Any]],
615
+ index: Optional[str] = None,
616
+ size: int = 100,
617
+ timeout: str = "30s",
618
+ explain: bool = False
619
+ ) -> ToolResult:
620
+ """Execute Elasticsearch query."""
621
+ try:
622
+ result = await self.agent.execute_query(query, index, size)
623
+
624
+ if explain and result["success"]:
625
+ # Add query explanation
626
+ try:
627
+ if isinstance(query, str):
628
+ query_dict = json.loads(query)
629
+ else:
630
+ query_dict = query
631
+
632
+ explain_response = await self.agent.client.explain(
633
+ index=index or "_all",
634
+ id=result["documents"][0]["_id"] if result["documents"] else "dummy",
635
+ body={"query": query_dict.get("query", {"match_all": {}})}
636
+ )
637
+ result["explanation"] = explain_response
638
+ except:
639
+ pass # Explanation is optional
640
+
641
+ return ToolResult(
642
+ status="success" if result["success"] else "error",
643
+ result=result,
644
+ error=result.get("error"),
645
+ metadata={
646
+ "query": str(query)[:500], # Truncate for metadata
647
+ "index": index,
648
+ "size": size,
649
+ "timeout": timeout
650
+ }
651
+ )
652
+
653
+ except Exception as e:
654
+ return ToolResult(
655
+ status="error",
656
+ result=None,
657
+ error=str(e),
658
+ metadata={"query": str(query)[:500]}
659
+ )
660
+
661
+
662
+ class IndexExplorationTool(AbstractTool):
663
+ """Tool for exploring Elasticsearch indices and their structure."""
664
+
665
+ name = "explore_indices"
666
+ description = "Explore available indices, mappings, and document structure"
667
+
668
+ class IndexExplorationArgs(AbstractToolArgsSchema):
669
+ """Arguments for index exploration."""
670
+ index_pattern: Optional[str] = Field(default="*", description="Index pattern to explore")
671
+ include_mappings: bool = Field(default=True, description="Include field mappings")
672
+ include_sample_docs: bool = Field(default=True, description="Include sample documents")
673
+ include_stats: bool = Field(default=True, description="Include index statistics")
674
+
675
+ args_schema = IndexExplorationArgs
676
+
677
+ def __init__(self, agent: ElasticDbAgent, **kwargs):
678
+ super().__init__(**kwargs)
679
+ self.agent = agent
680
+
681
+ async def _execute(
682
+ self,
683
+ index_pattern: str = "*",
684
+ include_mappings: bool = True,
685
+ include_sample_docs: bool = True,
686
+ include_stats: bool = True
687
+ ) -> ToolResult:
688
+ """Explore Elasticsearch indices."""
689
+ try:
690
+ if not self.agent.client:
691
+ await self.agent.connect_database()
692
+
693
+ # Get indices matching pattern
694
+ indices_response = await self.agent.client.indices.get(
695
+ index=index_pattern,
696
+ ignore_unavailable=True
697
+ )
698
+
699
+ exploration_result = {
700
+ "indices": [],
701
+ "total_indices": len(indices_response),
702
+ "pattern": index_pattern
703
+ }
704
+
705
+ for index_name, index_info in indices_response.items():
706
+ index_data = {
707
+ "name": index_name,
708
+ "aliases": list(index_info.get("aliases", {}).keys())
709
+ }
710
+
711
+ if include_mappings:
712
+ index_data["mappings"] = index_info.get("mappings", {})
713
+
714
+ # Extract field summary
715
+ properties = index_info.get("mappings", {}).get("properties", {})
716
+ index_data["field_count"] = len(properties)
717
+ index_data["field_types"] = self._get_field_type_summary(properties)
718
+
719
+ if include_stats:
720
+ try:
721
+ stats_response = await self.agent.client.indices.stats(index=index_name)
722
+ index_stats = stats_response["indices"].get(index_name, {})
723
+ index_data["stats"] = {
724
+ "doc_count": index_stats.get("total", {}).get("docs", {}).get("count", 0),
725
+ "size_bytes": index_stats.get("total", {}).get("store", {}).get("size_in_bytes", 0),
726
+ "primary_shards": index_stats.get("primaries", {}).get("docs", {}).get("count", 0)
727
+ }
728
+ except:
729
+ index_data["stats"] = {"error": "Could not retrieve stats"}
730
+
731
+ if include_sample_docs:
732
+ try:
733
+ sample_response = await self.agent.client.search(
734
+ index=index_name,
735
+ body={"query": {"match_all": {}}, "size": 3}
736
+ )
737
+ index_data["sample_documents"] = [
738
+ hit["_source"] for hit in sample_response["hits"]["hits"]
739
+ ]
740
+ except:
741
+ index_data["sample_documents"] = []
742
+
743
+ exploration_result["indices"].append(index_data)
744
+
745
+ return ToolResult(
746
+ status="success",
747
+ result=exploration_result,
748
+ metadata={
749
+ "index_pattern": index_pattern,
750
+ "include_mappings": include_mappings,
751
+ "include_sample_docs": include_sample_docs,
752
+ "include_stats": include_stats
753
+ }
754
+ )
755
+
756
+ except Exception as e:
757
+ return ToolResult(
758
+ status="error",
759
+ result=None,
760
+ error=str(e),
761
+ metadata={"index_pattern": index_pattern}
762
+ )
763
+
764
+ def _get_field_type_summary(self, properties: Dict[str, Any]) -> Dict[str, int]:
765
+ """Get summary of field types in the mapping."""
766
+ type_counts = {}
767
+
768
+ def count_types_recursive(props):
769
+ for field_name, field_config in props.items():
770
+ field_type = field_config.get("type", "object")
771
+ type_counts[field_type] = type_counts.get(field_type, 0) + 1
772
+
773
+ if field_type in ["object", "nested"] and "properties" in field_config:
774
+ count_types_recursive(field_config["properties"])
775
+
776
+ count_types_recursive(properties)
777
+ return type_counts
778
+
779
+
780
+ class AggregationTool(AbstractTool):
781
+ """Tool for running Elasticsearch aggregations and analytics."""
782
+
783
+ name = "run_aggregation"
784
+ description = "Run aggregations for analytics and data insights"
785
+
786
+ class AggregationArgs(AbstractToolArgsSchema):
787
+ """Arguments for running Elasticsearch aggregations."""
788
+ index: str = Field(description="Index to run aggregation against")
789
+ aggregation_type: str = Field(description="Type of aggregation (terms, date_histogram, avg, sum, etc.)")
790
+ field: str = Field(description="Field to aggregate on")
791
+ size: int = Field(default=10, description="Number of buckets/results to return")
792
+ query_filter: Optional[Dict[str, Any]] = Field(default=None, description="Optional query to filter data")
793
+
794
+ args_schema = AggregationArgs
795
+
796
+ def __init__(self, agent: ElasticDbAgent, **kwargs):
797
+ super().__init__(**kwargs)
798
+ self.agent = agent
799
+
800
+ async def _execute(
801
+ self,
802
+ index: str,
803
+ aggregation_type: str,
804
+ field: str,
805
+ size: int = 10,
806
+ query_filter: Optional[Dict[str, Any]] = None
807
+ ) -> ToolResult:
808
+ """Run aggregation query."""
809
+ try:
810
+ # Build aggregation query
811
+ agg_body = self._build_aggregation_query(
812
+ aggregation_type, field, size, query_filter
813
+ )
814
+
815
+ result = await self.agent.execute_query(agg_body, index)
816
+
817
+ if result["success"]:
818
+ # Extract and format aggregation results
819
+ agg_results = self._format_aggregation_results(
820
+ result["aggregations"], aggregation_type
821
+ )
822
+ result["formatted_aggregations"] = agg_results
823
+
824
+ return ToolResult(
825
+ status="success" if result["success"] else "error",
826
+ result=result,
827
+ error=result.get("error"),
828
+ metadata={
829
+ "index": index,
830
+ "aggregation_type": aggregation_type,
831
+ "field": field,
832
+ "size": size
833
+ }
834
+ )
835
+
836
+ except Exception as e:
837
+ return ToolResult(
838
+ status="error",
839
+ result=None,
840
+ error=str(e),
841
+ metadata={
842
+ "index": index,
843
+ "aggregation_type": aggregation_type,
844
+ "field": field
845
+ }
846
+ )
847
+
848
+ def _build_aggregation_query(
849
+ self,
850
+ agg_type: str,
851
+ field: str,
852
+ size: int,
853
+ query_filter: Optional[Dict[str, Any]]
854
+ ) -> Dict[str, Any]:
855
+ """Build aggregation query based on type."""
856
+ # Base query structure
857
+ query_body = {
858
+ "size": 0, # We only want aggregation results, not documents
859
+ "aggs": {
860
+ "main_agg": {}
861
+ }
862
+ }
863
+
864
+ # Add query filter if provided
865
+ if query_filter:
866
+ query_body["query"] = query_filter
867
+
868
+ # Build aggregation based on type
869
+ if agg_type == "terms":
870
+ query_body["aggs"]["main_agg"] = {
871
+ "terms": {
872
+ "field": field,
873
+ "size": size
874
+ }
875
+ }
876
+ elif agg_type == "date_histogram":
877
+ query_body["aggs"]["main_agg"] = {
878
+ "date_histogram": {
879
+ "field": field,
880
+ "calendar_interval": "day"
881
+ }
882
+ }
883
+ elif agg_type in ["avg", "sum", "min", "max"]:
884
+ query_body["aggs"]["main_agg"] = {
885
+ agg_type: {
886
+ "field": field
887
+ }
888
+ }
889
+ elif agg_type == "cardinality":
890
+ query_body["aggs"]["main_agg"] = {
891
+ "cardinality": {
892
+ "field": field
893
+ }
894
+ }
895
+ else:
896
+ # Default to terms aggregation
897
+ query_body["aggs"]["main_agg"] = {
898
+ "terms": {
899
+ "field": field,
900
+ "size": size
901
+ }
902
+ }
903
+
904
+ return query_body
905
+
906
+ def _format_aggregation_results(
907
+ self,
908
+ aggregations: Dict[str, Any],
909
+ agg_type: str
910
+ ) -> Dict[str, Any]:
911
+ """Format aggregation results for better readability."""
912
+ if "main_agg" not in aggregations:
913
+ return {}
914
+
915
+ main_agg = aggregations["main_agg"]
916
+
917
+ if agg_type == "terms":
918
+ return {
919
+ "buckets": main_agg.get("buckets", []),
920
+ "total_buckets": len(main_agg.get("buckets", [])),
921
+ "sum_other_doc_count": main_agg.get("sum_other_doc_count", 0)
922
+ }
923
+ elif agg_type == "date_histogram":
924
+ return {
925
+ "buckets": main_agg.get("buckets", []),
926
+ "total_buckets": len(main_agg.get("buckets", []))
927
+ }
928
+ elif agg_type in ["avg", "sum", "min", "max", "cardinality"]:
929
+ return {
930
+ "value": main_agg.get("value"),
931
+ "value_as_string": main_agg.get("value_as_string")
932
+ }
933
+ else:
934
+ return main_agg
935
+
936
+
937
+ # Factory function for creating Elasticsearch agents
938
+ def create_elasticsearch_agent(
939
+ url: str = None,
940
+ username: str = None,
941
+ password: str = None,
942
+ api_key: str = None,
943
+ cloud_id: str = None,
944
+ **kwargs
945
+ ) -> ElasticDbAgent:
946
+ """
947
+ Factory function to create Elasticsearch agents.
948
+
949
+ Args:
950
+ url: Elasticsearch URL (e.g., 'http://localhost:9200')
951
+ username: Username for authentication
952
+ password: Password for authentication
953
+ api_key: API key for authentication
954
+ cloud_id: Elastic Cloud ID
955
+ **kwargs: Additional arguments for the agent
956
+
957
+ Returns:
958
+ Configured ElasticDbAgent instance
959
+ """
960
+ return ElasticDbAgent(
961
+ connection_string=url,
962
+ username=username,
963
+ password=password,
964
+ api_key=api_key,
965
+ cloud_id=cloud_id,
966
+ **kwargs
967
+ )
968
+
969
+
970
+ # Example usage
971
+ """
972
+ # Create Elasticsearch agent with username/password
973
+ es_agent = create_elasticsearch_agent(
974
+ url='http://localhost:9200',
975
+ username='elastic',
976
+ password='your-password'
977
+ )
978
+
979
+ # Or with API key
980
+ es_agent = create_elasticsearch_agent(
981
+ url='http://localhost:9200',
982
+ api_key='your-api-key'
983
+ )
984
+
985
+ # Or with Elastic Cloud
986
+ es_agent = create_elasticsearch_agent(
987
+ cloud_id='your-cloud-id',
988
+ api_key='your-api-key'
989
+ )
990
+
991
+ # Initialize schema
992
+ await es_agent.initialize_schema()
993
+
994
+ # Generate query from natural language
995
+ result = await es_agent.generate_query(
996
+ "Find all documents where status is active and created in the last 30 days"
997
+ )
998
+
999
+ # Execute the generated query
1000
+ execution_result = await es_agent.execute_query(result['query'])
1001
+
1002
+ # Run aggregations
1003
+ agg_tool = AggregationTool(agent=es_agent)
1004
+ agg_result = await agg_tool._arun(
1005
+ index='my-index',
1006
+ aggregation_type='terms',
1007
+ field='category.keyword',
1008
+ size=10
1009
+ )
1010
+
1011
+ # Explore indices
1012
+ exploration_tool = IndexExplorationTool(agent=es_agent)
1013
+ exploration_result = await exploration_tool._arun(index_pattern='log-*')
1014
+ """