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.
- agentui/.prettierrc +15 -0
- agentui/QUICKSTART.md +272 -0
- agentui/README.md +59 -0
- agentui/env.example +16 -0
- agentui/jsconfig.json +14 -0
- agentui/package-lock.json +4242 -0
- agentui/package.json +34 -0
- agentui/scripts/postinstall/apply-patches.mjs +260 -0
- agentui/src/app.css +61 -0
- agentui/src/app.d.ts +13 -0
- agentui/src/app.html +12 -0
- agentui/src/components/LoadingSpinner.svelte +64 -0
- agentui/src/components/ThemeSwitcher.svelte +159 -0
- agentui/src/components/index.js +4 -0
- agentui/src/lib/api/bots.ts +60 -0
- agentui/src/lib/api/chat.ts +22 -0
- agentui/src/lib/api/http.ts +25 -0
- agentui/src/lib/components/BotCard.svelte +33 -0
- agentui/src/lib/components/ChatBubble.svelte +63 -0
- agentui/src/lib/components/Toast.svelte +21 -0
- agentui/src/lib/config.ts +20 -0
- agentui/src/lib/stores/auth.svelte.ts +73 -0
- agentui/src/lib/stores/theme.svelte.js +64 -0
- agentui/src/lib/stores/toast.svelte.ts +31 -0
- agentui/src/lib/utils/conversation.ts +39 -0
- agentui/src/routes/+layout.svelte +20 -0
- agentui/src/routes/+page.svelte +232 -0
- agentui/src/routes/login/+page.svelte +200 -0
- agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
- agentui/src/routes/talk/[agentId]/+page.ts +7 -0
- agentui/static/README.md +1 -0
- agentui/svelte.config.js +11 -0
- agentui/tailwind.config.ts +53 -0
- agentui/tsconfig.json +3 -0
- agentui/vite.config.ts +10 -0
- ai_parrot-0.17.2.dist-info/METADATA +472 -0
- ai_parrot-0.17.2.dist-info/RECORD +535 -0
- ai_parrot-0.17.2.dist-info/WHEEL +6 -0
- ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
- ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
- ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
- crew-builder/.prettierrc +15 -0
- crew-builder/QUICKSTART.md +259 -0
- crew-builder/README.md +113 -0
- crew-builder/env.example +17 -0
- crew-builder/jsconfig.json +14 -0
- crew-builder/package-lock.json +4182 -0
- crew-builder/package.json +37 -0
- crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
- crew-builder/src/app.css +62 -0
- crew-builder/src/app.d.ts +13 -0
- crew-builder/src/app.html +12 -0
- crew-builder/src/components/LoadingSpinner.svelte +64 -0
- crew-builder/src/components/ThemeSwitcher.svelte +149 -0
- crew-builder/src/components/index.js +9 -0
- crew-builder/src/lib/api/bots.ts +60 -0
- crew-builder/src/lib/api/chat.ts +80 -0
- crew-builder/src/lib/api/client.ts +56 -0
- crew-builder/src/lib/api/crew/crew.ts +136 -0
- crew-builder/src/lib/api/index.ts +5 -0
- crew-builder/src/lib/api/o365/auth.ts +65 -0
- crew-builder/src/lib/auth/auth.ts +54 -0
- crew-builder/src/lib/components/AgentNode.svelte +43 -0
- crew-builder/src/lib/components/BotCard.svelte +33 -0
- crew-builder/src/lib/components/ChatBubble.svelte +67 -0
- crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
- crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
- crew-builder/src/lib/components/JsonViewer.svelte +24 -0
- crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
- crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
- crew-builder/src/lib/components/Toast.svelte +67 -0
- crew-builder/src/lib/components/Toolbar.svelte +157 -0
- crew-builder/src/lib/components/index.ts +10 -0
- crew-builder/src/lib/config.ts +8 -0
- crew-builder/src/lib/stores/auth.svelte.ts +228 -0
- crew-builder/src/lib/stores/crewStore.ts +369 -0
- crew-builder/src/lib/stores/theme.svelte.js +145 -0
- crew-builder/src/lib/stores/toast.svelte.ts +69 -0
- crew-builder/src/lib/utils/conversation.ts +39 -0
- crew-builder/src/lib/utils/markdown.ts +122 -0
- crew-builder/src/lib/utils/talkHistory.ts +47 -0
- crew-builder/src/routes/+layout.svelte +20 -0
- crew-builder/src/routes/+page.svelte +539 -0
- crew-builder/src/routes/agents/+page.svelte +247 -0
- crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
- crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
- crew-builder/src/routes/builder/+page.svelte +204 -0
- crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
- crew-builder/src/routes/crew/ask/+page.ts +1 -0
- crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
- crew-builder/src/routes/login/+page.svelte +197 -0
- crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
- crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
- crew-builder/static/README.md +1 -0
- crew-builder/svelte.config.js +11 -0
- crew-builder/tailwind.config.ts +53 -0
- crew-builder/tsconfig.json +3 -0
- crew-builder/vite.config.ts +10 -0
- mcp_servers/calculator_server.py +309 -0
- parrot/__init__.py +27 -0
- parrot/__pycache__/__init__.cpython-310.pyc +0 -0
- parrot/__pycache__/version.cpython-310.pyc +0 -0
- parrot/_version.py +34 -0
- parrot/a2a/__init__.py +48 -0
- parrot/a2a/client.py +658 -0
- parrot/a2a/discovery.py +89 -0
- parrot/a2a/mixin.py +257 -0
- parrot/a2a/models.py +376 -0
- parrot/a2a/server.py +770 -0
- parrot/agents/__init__.py +29 -0
- parrot/bots/__init__.py +12 -0
- parrot/bots/a2a_agent.py +19 -0
- parrot/bots/abstract.py +3139 -0
- parrot/bots/agent.py +1129 -0
- parrot/bots/basic.py +9 -0
- parrot/bots/chatbot.py +669 -0
- parrot/bots/data.py +1618 -0
- parrot/bots/database/__init__.py +5 -0
- parrot/bots/database/abstract.py +3071 -0
- parrot/bots/database/cache.py +286 -0
- parrot/bots/database/models.py +468 -0
- parrot/bots/database/prompts.py +154 -0
- parrot/bots/database/retries.py +98 -0
- parrot/bots/database/router.py +269 -0
- parrot/bots/database/sql.py +41 -0
- parrot/bots/db/__init__.py +6 -0
- parrot/bots/db/abstract.py +556 -0
- parrot/bots/db/bigquery.py +602 -0
- parrot/bots/db/cache.py +85 -0
- parrot/bots/db/documentdb.py +668 -0
- parrot/bots/db/elastic.py +1014 -0
- parrot/bots/db/influx.py +898 -0
- parrot/bots/db/mock.py +96 -0
- parrot/bots/db/multi.py +783 -0
- parrot/bots/db/prompts.py +185 -0
- parrot/bots/db/sql.py +1255 -0
- parrot/bots/db/tools.py +212 -0
- parrot/bots/document.py +680 -0
- parrot/bots/hrbot.py +15 -0
- parrot/bots/kb.py +170 -0
- parrot/bots/mcp.py +36 -0
- parrot/bots/orchestration/README.md +463 -0
- parrot/bots/orchestration/__init__.py +1 -0
- parrot/bots/orchestration/agent.py +155 -0
- parrot/bots/orchestration/crew.py +3330 -0
- parrot/bots/orchestration/fsm.py +1179 -0
- parrot/bots/orchestration/hr.py +434 -0
- parrot/bots/orchestration/storage/__init__.py +4 -0
- parrot/bots/orchestration/storage/memory.py +100 -0
- parrot/bots/orchestration/storage/mixin.py +119 -0
- parrot/bots/orchestration/verify.py +202 -0
- parrot/bots/product.py +204 -0
- parrot/bots/prompts/__init__.py +96 -0
- parrot/bots/prompts/agents.py +155 -0
- parrot/bots/prompts/data.py +216 -0
- parrot/bots/prompts/output_generation.py +8 -0
- parrot/bots/scraper/__init__.py +3 -0
- parrot/bots/scraper/models.py +122 -0
- parrot/bots/scraper/scraper.py +1173 -0
- parrot/bots/scraper/templates.py +115 -0
- parrot/bots/stores/__init__.py +5 -0
- parrot/bots/stores/local.py +172 -0
- parrot/bots/webdev.py +81 -0
- parrot/cli.py +17 -0
- parrot/clients/__init__.py +16 -0
- parrot/clients/base.py +1491 -0
- parrot/clients/claude.py +1191 -0
- parrot/clients/factory.py +129 -0
- parrot/clients/google.py +4567 -0
- parrot/clients/gpt.py +1975 -0
- parrot/clients/grok.py +432 -0
- parrot/clients/groq.py +986 -0
- parrot/clients/hf.py +582 -0
- parrot/clients/models.py +18 -0
- parrot/conf.py +395 -0
- parrot/embeddings/__init__.py +9 -0
- parrot/embeddings/base.py +157 -0
- parrot/embeddings/google.py +98 -0
- parrot/embeddings/huggingface.py +74 -0
- parrot/embeddings/openai.py +84 -0
- parrot/embeddings/processor.py +88 -0
- parrot/exceptions.c +13868 -0
- parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/exceptions.pxd +22 -0
- parrot/exceptions.pxi +15 -0
- parrot/exceptions.pyx +44 -0
- parrot/generators/__init__.py +29 -0
- parrot/generators/base.py +200 -0
- parrot/generators/html.py +293 -0
- parrot/generators/react.py +205 -0
- parrot/generators/streamlit.py +203 -0
- parrot/generators/template.py +105 -0
- parrot/handlers/__init__.py +4 -0
- parrot/handlers/agent.py +861 -0
- parrot/handlers/agents/__init__.py +1 -0
- parrot/handlers/agents/abstract.py +900 -0
- parrot/handlers/bots.py +338 -0
- parrot/handlers/chat.py +915 -0
- parrot/handlers/creation.sql +192 -0
- parrot/handlers/crew/ARCHITECTURE.md +362 -0
- parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
- parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
- parrot/handlers/crew/__init__.py +0 -0
- parrot/handlers/crew/handler.py +801 -0
- parrot/handlers/crew/models.py +229 -0
- parrot/handlers/crew/redis_persistence.py +523 -0
- parrot/handlers/jobs/__init__.py +10 -0
- parrot/handlers/jobs/job.py +384 -0
- parrot/handlers/jobs/mixin.py +627 -0
- parrot/handlers/jobs/models.py +115 -0
- parrot/handlers/jobs/worker.py +31 -0
- parrot/handlers/models.py +596 -0
- parrot/handlers/o365_auth.py +105 -0
- parrot/handlers/stream.py +337 -0
- parrot/interfaces/__init__.py +6 -0
- parrot/interfaces/aws.py +143 -0
- parrot/interfaces/credentials.py +113 -0
- parrot/interfaces/database.py +27 -0
- parrot/interfaces/google.py +1123 -0
- parrot/interfaces/hierarchy.py +1227 -0
- parrot/interfaces/http.py +651 -0
- parrot/interfaces/images/__init__.py +0 -0
- parrot/interfaces/images/plugins/__init__.py +24 -0
- parrot/interfaces/images/plugins/abstract.py +58 -0
- parrot/interfaces/images/plugins/analisys.py +148 -0
- parrot/interfaces/images/plugins/classify.py +150 -0
- parrot/interfaces/images/plugins/classifybase.py +182 -0
- parrot/interfaces/images/plugins/detect.py +150 -0
- parrot/interfaces/images/plugins/exif.py +1103 -0
- parrot/interfaces/images/plugins/hash.py +52 -0
- parrot/interfaces/images/plugins/vision.py +104 -0
- parrot/interfaces/images/plugins/yolo.py +66 -0
- parrot/interfaces/images/plugins/zerodetect.py +197 -0
- parrot/interfaces/o365.py +978 -0
- parrot/interfaces/onedrive.py +822 -0
- parrot/interfaces/sharepoint.py +1435 -0
- parrot/interfaces/soap.py +257 -0
- parrot/loaders/__init__.py +8 -0
- parrot/loaders/abstract.py +1131 -0
- parrot/loaders/audio.py +199 -0
- parrot/loaders/basepdf.py +53 -0
- parrot/loaders/basevideo.py +1568 -0
- parrot/loaders/csv.py +409 -0
- parrot/loaders/docx.py +116 -0
- parrot/loaders/epubloader.py +316 -0
- parrot/loaders/excel.py +199 -0
- parrot/loaders/factory.py +55 -0
- parrot/loaders/files/__init__.py +0 -0
- parrot/loaders/files/abstract.py +39 -0
- parrot/loaders/files/html.py +26 -0
- parrot/loaders/files/text.py +63 -0
- parrot/loaders/html.py +152 -0
- parrot/loaders/markdown.py +442 -0
- parrot/loaders/pdf.py +373 -0
- parrot/loaders/pdfmark.py +320 -0
- parrot/loaders/pdftables.py +506 -0
- parrot/loaders/ppt.py +476 -0
- parrot/loaders/qa.py +63 -0
- parrot/loaders/splitters/__init__.py +10 -0
- parrot/loaders/splitters/base.py +138 -0
- parrot/loaders/splitters/md.py +228 -0
- parrot/loaders/splitters/token.py +143 -0
- parrot/loaders/txt.py +26 -0
- parrot/loaders/video.py +89 -0
- parrot/loaders/videolocal.py +218 -0
- parrot/loaders/videounderstanding.py +377 -0
- parrot/loaders/vimeo.py +167 -0
- parrot/loaders/web.py +599 -0
- parrot/loaders/youtube.py +504 -0
- parrot/manager/__init__.py +5 -0
- parrot/manager/manager.py +1030 -0
- parrot/mcp/__init__.py +28 -0
- parrot/mcp/adapter.py +105 -0
- parrot/mcp/cli.py +174 -0
- parrot/mcp/client.py +119 -0
- parrot/mcp/config.py +75 -0
- parrot/mcp/integration.py +842 -0
- parrot/mcp/oauth.py +933 -0
- parrot/mcp/server.py +225 -0
- parrot/mcp/transports/__init__.py +3 -0
- parrot/mcp/transports/base.py +279 -0
- parrot/mcp/transports/grpc_session.py +163 -0
- parrot/mcp/transports/http.py +312 -0
- parrot/mcp/transports/mcp.proto +108 -0
- parrot/mcp/transports/quic.py +1082 -0
- parrot/mcp/transports/sse.py +330 -0
- parrot/mcp/transports/stdio.py +309 -0
- parrot/mcp/transports/unix.py +395 -0
- parrot/mcp/transports/websocket.py +547 -0
- parrot/memory/__init__.py +16 -0
- parrot/memory/abstract.py +209 -0
- parrot/memory/agent.py +32 -0
- parrot/memory/cache.py +175 -0
- parrot/memory/core.py +555 -0
- parrot/memory/file.py +153 -0
- parrot/memory/mem.py +131 -0
- parrot/memory/redis.py +613 -0
- parrot/models/__init__.py +46 -0
- parrot/models/basic.py +118 -0
- parrot/models/compliance.py +208 -0
- parrot/models/crew.py +395 -0
- parrot/models/detections.py +654 -0
- parrot/models/generation.py +85 -0
- parrot/models/google.py +223 -0
- parrot/models/groq.py +23 -0
- parrot/models/openai.py +30 -0
- parrot/models/outputs.py +285 -0
- parrot/models/responses.py +938 -0
- parrot/notifications/__init__.py +743 -0
- parrot/openapi/__init__.py +3 -0
- parrot/openapi/components.yaml +641 -0
- parrot/openapi/config.py +322 -0
- parrot/outputs/__init__.py +32 -0
- parrot/outputs/formats/__init__.py +108 -0
- parrot/outputs/formats/altair.py +359 -0
- parrot/outputs/formats/application.py +122 -0
- parrot/outputs/formats/base.py +351 -0
- parrot/outputs/formats/bokeh.py +356 -0
- parrot/outputs/formats/card.py +424 -0
- parrot/outputs/formats/chart.py +436 -0
- parrot/outputs/formats/d3.py +255 -0
- parrot/outputs/formats/echarts.py +310 -0
- parrot/outputs/formats/generators/__init__.py +0 -0
- parrot/outputs/formats/generators/abstract.py +61 -0
- parrot/outputs/formats/generators/panel.py +145 -0
- parrot/outputs/formats/generators/streamlit.py +86 -0
- parrot/outputs/formats/generators/terminal.py +63 -0
- parrot/outputs/formats/holoviews.py +310 -0
- parrot/outputs/formats/html.py +147 -0
- parrot/outputs/formats/jinja2.py +46 -0
- parrot/outputs/formats/json.py +87 -0
- parrot/outputs/formats/map.py +933 -0
- parrot/outputs/formats/markdown.py +172 -0
- parrot/outputs/formats/matplotlib.py +237 -0
- parrot/outputs/formats/mixins/__init__.py +0 -0
- parrot/outputs/formats/mixins/emaps.py +855 -0
- parrot/outputs/formats/plotly.py +341 -0
- parrot/outputs/formats/seaborn.py +310 -0
- parrot/outputs/formats/table.py +397 -0
- parrot/outputs/formats/template_report.py +138 -0
- parrot/outputs/formats/yaml.py +125 -0
- parrot/outputs/formatter.py +152 -0
- parrot/outputs/templates/__init__.py +95 -0
- parrot/pipelines/__init__.py +0 -0
- parrot/pipelines/abstract.py +210 -0
- parrot/pipelines/detector.py +124 -0
- parrot/pipelines/models.py +90 -0
- parrot/pipelines/planogram.py +3002 -0
- parrot/pipelines/table.sql +97 -0
- parrot/plugins/__init__.py +106 -0
- parrot/plugins/importer.py +80 -0
- parrot/py.typed +0 -0
- parrot/registry/__init__.py +18 -0
- parrot/registry/registry.py +594 -0
- parrot/scheduler/__init__.py +1189 -0
- parrot/scheduler/models.py +60 -0
- parrot/security/__init__.py +16 -0
- parrot/security/prompt_injection.py +268 -0
- parrot/security/security_events.sql +25 -0
- parrot/services/__init__.py +1 -0
- parrot/services/mcp/__init__.py +8 -0
- parrot/services/mcp/config.py +13 -0
- parrot/services/mcp/server.py +295 -0
- parrot/services/o365_remote_auth.py +235 -0
- parrot/stores/__init__.py +7 -0
- parrot/stores/abstract.py +352 -0
- parrot/stores/arango.py +1090 -0
- parrot/stores/bigquery.py +1377 -0
- parrot/stores/cache.py +106 -0
- parrot/stores/empty.py +10 -0
- parrot/stores/faiss_store.py +1157 -0
- parrot/stores/kb/__init__.py +9 -0
- parrot/stores/kb/abstract.py +68 -0
- parrot/stores/kb/cache.py +165 -0
- parrot/stores/kb/doc.py +325 -0
- parrot/stores/kb/hierarchy.py +346 -0
- parrot/stores/kb/local.py +457 -0
- parrot/stores/kb/prompt.py +28 -0
- parrot/stores/kb/redis.py +659 -0
- parrot/stores/kb/store.py +115 -0
- parrot/stores/kb/user.py +374 -0
- parrot/stores/models.py +59 -0
- parrot/stores/pgvector.py +3 -0
- parrot/stores/postgres.py +2853 -0
- parrot/stores/utils/__init__.py +0 -0
- parrot/stores/utils/chunking.py +197 -0
- parrot/telemetry/__init__.py +3 -0
- parrot/telemetry/mixin.py +111 -0
- parrot/template/__init__.py +3 -0
- parrot/template/engine.py +259 -0
- parrot/tools/__init__.py +23 -0
- parrot/tools/abstract.py +644 -0
- parrot/tools/agent.py +363 -0
- parrot/tools/arangodbsearch.py +537 -0
- parrot/tools/arxiv_tool.py +188 -0
- parrot/tools/calculator/__init__.py +3 -0
- parrot/tools/calculator/operations/__init__.py +38 -0
- parrot/tools/calculator/operations/calculus.py +80 -0
- parrot/tools/calculator/operations/statistics.py +76 -0
- parrot/tools/calculator/tool.py +150 -0
- parrot/tools/cloudwatch.py +988 -0
- parrot/tools/codeinterpreter/__init__.py +127 -0
- parrot/tools/codeinterpreter/executor.py +371 -0
- parrot/tools/codeinterpreter/internals.py +473 -0
- parrot/tools/codeinterpreter/models.py +643 -0
- parrot/tools/codeinterpreter/prompts.py +224 -0
- parrot/tools/codeinterpreter/tool.py +664 -0
- parrot/tools/company_info/__init__.py +6 -0
- parrot/tools/company_info/tool.py +1138 -0
- parrot/tools/correlationanalysis.py +437 -0
- parrot/tools/database/abstract.py +286 -0
- parrot/tools/database/bq.py +115 -0
- parrot/tools/database/cache.py +284 -0
- parrot/tools/database/models.py +95 -0
- parrot/tools/database/pg.py +343 -0
- parrot/tools/databasequery.py +1159 -0
- parrot/tools/db.py +1800 -0
- parrot/tools/ddgo.py +370 -0
- parrot/tools/decorators.py +271 -0
- parrot/tools/dftohtml.py +282 -0
- parrot/tools/document.py +549 -0
- parrot/tools/ecs.py +819 -0
- parrot/tools/edareport.py +368 -0
- parrot/tools/elasticsearch.py +1049 -0
- parrot/tools/employees.py +462 -0
- parrot/tools/epson/__init__.py +96 -0
- parrot/tools/excel.py +683 -0
- parrot/tools/file/__init__.py +13 -0
- parrot/tools/file/abstract.py +76 -0
- parrot/tools/file/gcs.py +378 -0
- parrot/tools/file/local.py +284 -0
- parrot/tools/file/s3.py +511 -0
- parrot/tools/file/tmp.py +309 -0
- parrot/tools/file/tool.py +501 -0
- parrot/tools/file_reader.py +129 -0
- parrot/tools/flowtask/__init__.py +19 -0
- parrot/tools/flowtask/tool.py +761 -0
- parrot/tools/gittoolkit.py +508 -0
- parrot/tools/google/__init__.py +18 -0
- parrot/tools/google/base.py +169 -0
- parrot/tools/google/tools.py +1251 -0
- parrot/tools/googlelocation.py +5 -0
- parrot/tools/googleroutes.py +5 -0
- parrot/tools/googlesearch.py +5 -0
- parrot/tools/googlesitesearch.py +5 -0
- parrot/tools/googlevoice.py +2 -0
- parrot/tools/gvoice.py +695 -0
- parrot/tools/ibisworld/README.md +225 -0
- parrot/tools/ibisworld/__init__.py +11 -0
- parrot/tools/ibisworld/tool.py +366 -0
- parrot/tools/jiratoolkit.py +1718 -0
- parrot/tools/manager.py +1098 -0
- parrot/tools/math.py +152 -0
- parrot/tools/metadata.py +476 -0
- parrot/tools/msteams.py +1621 -0
- parrot/tools/msword.py +635 -0
- parrot/tools/multidb.py +580 -0
- parrot/tools/multistoresearch.py +369 -0
- parrot/tools/networkninja.py +167 -0
- parrot/tools/nextstop/__init__.py +4 -0
- parrot/tools/nextstop/base.py +286 -0
- parrot/tools/nextstop/employee.py +733 -0
- parrot/tools/nextstop/store.py +462 -0
- parrot/tools/notification.py +435 -0
- parrot/tools/o365/__init__.py +42 -0
- parrot/tools/o365/base.py +295 -0
- parrot/tools/o365/bundle.py +522 -0
- parrot/tools/o365/events.py +554 -0
- parrot/tools/o365/mail.py +992 -0
- parrot/tools/o365/onedrive.py +497 -0
- parrot/tools/o365/sharepoint.py +641 -0
- parrot/tools/openapi_toolkit.py +904 -0
- parrot/tools/openweather.py +527 -0
- parrot/tools/pdfprint.py +1001 -0
- parrot/tools/powerbi.py +518 -0
- parrot/tools/powerpoint.py +1113 -0
- parrot/tools/pricestool.py +146 -0
- parrot/tools/products/__init__.py +246 -0
- parrot/tools/prophet_tool.py +171 -0
- parrot/tools/pythonpandas.py +630 -0
- parrot/tools/pythonrepl.py +910 -0
- parrot/tools/qsource.py +436 -0
- parrot/tools/querytoolkit.py +395 -0
- parrot/tools/quickeda.py +827 -0
- parrot/tools/resttool.py +553 -0
- parrot/tools/retail/__init__.py +0 -0
- parrot/tools/retail/bby.py +528 -0
- parrot/tools/sandboxtool.py +703 -0
- parrot/tools/sassie/__init__.py +352 -0
- parrot/tools/scraping/__init__.py +7 -0
- parrot/tools/scraping/docs/select.md +466 -0
- parrot/tools/scraping/documentation.md +1278 -0
- parrot/tools/scraping/driver.py +436 -0
- parrot/tools/scraping/models.py +576 -0
- parrot/tools/scraping/options.py +85 -0
- parrot/tools/scraping/orchestrator.py +517 -0
- parrot/tools/scraping/readme.md +740 -0
- parrot/tools/scraping/tool.py +3115 -0
- parrot/tools/seasonaldetection.py +642 -0
- parrot/tools/shell_tool/__init__.py +5 -0
- parrot/tools/shell_tool/actions.py +408 -0
- parrot/tools/shell_tool/engine.py +155 -0
- parrot/tools/shell_tool/models.py +322 -0
- parrot/tools/shell_tool/tool.py +442 -0
- parrot/tools/site_search.py +214 -0
- parrot/tools/textfile.py +418 -0
- parrot/tools/think.py +378 -0
- parrot/tools/toolkit.py +298 -0
- parrot/tools/webapp_tool.py +187 -0
- parrot/tools/whatif.py +1279 -0
- parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
- parrot/tools/workday/__init__.py +6 -0
- parrot/tools/workday/models.py +1389 -0
- parrot/tools/workday/tool.py +1293 -0
- parrot/tools/yfinance_tool.py +306 -0
- parrot/tools/zipcode.py +217 -0
- parrot/utils/__init__.py +2 -0
- parrot/utils/helpers.py +73 -0
- parrot/utils/parsers/__init__.py +5 -0
- parrot/utils/parsers/toml.c +12078 -0
- parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/parsers/toml.pyx +21 -0
- parrot/utils/toml.py +11 -0
- parrot/utils/types.cpp +20936 -0
- parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/types.pyx +213 -0
- parrot/utils/uv.py +11 -0
- parrot/version.py +10 -0
- parrot/yaml-rs/Cargo.lock +350 -0
- parrot/yaml-rs/Cargo.toml +19 -0
- parrot/yaml-rs/pyproject.toml +19 -0
- parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
- parrot/yaml-rs/src/lib.rs +222 -0
- requirements/docker-compose.yml +24 -0
- 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
|
+
"""
|