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,602 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BigQuery Agent Implementation for AI-Parrot.
|
|
3
|
+
|
|
4
|
+
Concrete implementation of AbstractDBAgent for Google BigQuery
|
|
5
|
+
with support for SQL query language and BigQuery-specific features.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Any, List, Optional, Union
|
|
9
|
+
import asyncio
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
from google.cloud import bigquery as bq
|
|
13
|
+
from google.oauth2 import service_account
|
|
14
|
+
from google.cloud.exceptions import NotFound
|
|
15
|
+
from navconfig import config
|
|
16
|
+
|
|
17
|
+
from .abstract import (
|
|
18
|
+
AbstractDBAgent,
|
|
19
|
+
DatabaseSchema,
|
|
20
|
+
TableMetadata,
|
|
21
|
+
)
|
|
22
|
+
from ...tools.abstract import AbstractTool, ToolResult, AbstractToolArgsSchema
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BigQueryQueryExecutionArgs(AbstractToolArgsSchema):
|
|
26
|
+
"""Arguments for BigQuery query execution."""
|
|
27
|
+
query: str = Field(description="SQL query to execute")
|
|
28
|
+
limit: Optional[int] = Field(
|
|
29
|
+
default=1000, description="Maximum number of rows to return"
|
|
30
|
+
)
|
|
31
|
+
use_legacy_sql: bool = Field(
|
|
32
|
+
default=False, description="Use legacy SQL instead of standard SQL"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DatasetMetadata:
|
|
37
|
+
"""Metadata for BigQuery datasets."""
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
dataset_id: str,
|
|
41
|
+
project_id: str,
|
|
42
|
+
location: str,
|
|
43
|
+
tables: List[str],
|
|
44
|
+
description: str = None
|
|
45
|
+
):
|
|
46
|
+
self.dataset_id = dataset_id
|
|
47
|
+
self.project_id = project_id
|
|
48
|
+
self.location = location
|
|
49
|
+
self.tables = tables
|
|
50
|
+
self.description = description
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BigQueryAgent(AbstractDBAgent):
|
|
54
|
+
"""
|
|
55
|
+
BigQuery Agent for data warehouse introspection and SQL query generation.
|
|
56
|
+
|
|
57
|
+
Supports Google BigQuery with standard SQL and BigQuery-specific features.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
name: str = "BigQueryAgent",
|
|
63
|
+
project_id: str = None,
|
|
64
|
+
credentials_file: str = None,
|
|
65
|
+
credentials: Union[str, Dict[str, Any]] = None,
|
|
66
|
+
dataset: str = None,
|
|
67
|
+
location: str = "US",
|
|
68
|
+
max_sample_rows: int = 10,
|
|
69
|
+
**kwargs
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
Initialize BigQuery Agent.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
name: Agent name
|
|
76
|
+
project_id: Google Cloud project ID
|
|
77
|
+
credentials_file: Path to service account credentials JSON file
|
|
78
|
+
credentials: Credentials dict or connection string (overrides individual params)
|
|
79
|
+
dataset: Default dataset name
|
|
80
|
+
location: BigQuery location/region (default: US)
|
|
81
|
+
max_sample_rows: Maximum sample rows per table
|
|
82
|
+
"""
|
|
83
|
+
self.project_id = project_id
|
|
84
|
+
self.credentials_file = credentials_file
|
|
85
|
+
self.dataset = dataset
|
|
86
|
+
self.location = location
|
|
87
|
+
self.max_sample_rows = max_sample_rows
|
|
88
|
+
self.client: Optional[bq.Client] = None
|
|
89
|
+
self.credentials_obj = None
|
|
90
|
+
self.datasets_cache: Dict[str, DatasetMetadata] = {}
|
|
91
|
+
|
|
92
|
+
# Get default credentials if not provided
|
|
93
|
+
if not credentials and not all([project_id, credentials_file]):
|
|
94
|
+
credentials = self._get_default_credentials()
|
|
95
|
+
|
|
96
|
+
super().__init__(
|
|
97
|
+
name=name,
|
|
98
|
+
credentials=credentials,
|
|
99
|
+
schema_name=dataset,
|
|
100
|
+
**kwargs
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Add BigQuery-specific tools
|
|
104
|
+
self._setup_bigquery_tools()
|
|
105
|
+
|
|
106
|
+
def _get_default_credentials(self) -> Dict[str, Any]:
|
|
107
|
+
"""Get default credentials from config (similar to DatabaseQueryTool)."""
|
|
108
|
+
return {
|
|
109
|
+
'credentials_file': config.get('GOOGLE_APPLICATION_CREDENTIALS'),
|
|
110
|
+
'project_id': config.get('GOOGLE_CLOUD_PROJECT'),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
def _setup_bigquery_tools(self):
|
|
114
|
+
"""Setup BigQuery-specific tools."""
|
|
115
|
+
# Add query execution tool
|
|
116
|
+
query_execution_tool = BigQueryQueryExecutionTool(agent=self)
|
|
117
|
+
self.tool_manager.register_tool(query_execution_tool)
|
|
118
|
+
|
|
119
|
+
# Add dataset exploration tool
|
|
120
|
+
dataset_exploration_tool = DatasetExplorationTool(agent=self)
|
|
121
|
+
self.tool_manager.register_tool(dataset_exploration_tool)
|
|
122
|
+
|
|
123
|
+
async def connect_database(self) -> None:
|
|
124
|
+
"""Connect to BigQuery using service account credentials."""
|
|
125
|
+
# Parse credentials
|
|
126
|
+
if isinstance(self.credentials, dict):
|
|
127
|
+
creds = self.credentials.copy()
|
|
128
|
+
self.project_id = creds.get('project_id', self.project_id)
|
|
129
|
+
self.credentials_file = creds.get('credentials_file', self.credentials_file)
|
|
130
|
+
self.dataset = creds.get('dataset', self.dataset)
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
# Initialize BigQuery client
|
|
134
|
+
if self.credentials_file:
|
|
135
|
+
self.credentials_obj = service_account.Credentials.from_service_account_file(
|
|
136
|
+
self.credentials_file
|
|
137
|
+
)
|
|
138
|
+
if not self.project_id:
|
|
139
|
+
self.project_id = self.credentials_obj.project_id
|
|
140
|
+
|
|
141
|
+
self.client = bq.Client(
|
|
142
|
+
credentials=self.credentials_obj,
|
|
143
|
+
project=self.project_id
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
# Use application default credentials
|
|
147
|
+
if not self.project_id:
|
|
148
|
+
raise ValueError("BigQuery project_id is required")
|
|
149
|
+
self.client = bq.Client(project=self.project_id)
|
|
150
|
+
|
|
151
|
+
# Test connection by listing datasets (limit to 1)
|
|
152
|
+
datasets = list(self.client.list_datasets(max_results=1))
|
|
153
|
+
|
|
154
|
+
self.logger.info(
|
|
155
|
+
f"Successfully connected to BigQuery project: {self.project_id}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
self.logger.error(f"Failed to connect to BigQuery: {e}")
|
|
160
|
+
raise
|
|
161
|
+
|
|
162
|
+
async def extract_schema_metadata(self) -> DatabaseSchema:
|
|
163
|
+
"""Extract schema metadata from BigQuery (datasets, tables, columns)."""
|
|
164
|
+
if not self.client:
|
|
165
|
+
await self.connect_database()
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
# Get datasets to analyze
|
|
169
|
+
datasets_to_analyze = []
|
|
170
|
+
if self.dataset:
|
|
171
|
+
datasets_to_analyze = [self.dataset]
|
|
172
|
+
else:
|
|
173
|
+
# List all datasets in the project
|
|
174
|
+
datasets = self.client.list_datasets()
|
|
175
|
+
datasets_to_analyze = [dataset.dataset_id for dataset in datasets]
|
|
176
|
+
|
|
177
|
+
# Extract tables from each dataset
|
|
178
|
+
all_tables = []
|
|
179
|
+
for dataset_id in datasets_to_analyze:
|
|
180
|
+
tables = await self._extract_tables_from_dataset(dataset_id)
|
|
181
|
+
all_tables.extend(tables)
|
|
182
|
+
|
|
183
|
+
schema_metadata = DatabaseSchema(
|
|
184
|
+
database_name=self.project_id,
|
|
185
|
+
database_type="bigquery",
|
|
186
|
+
tables=all_tables,
|
|
187
|
+
views=[], # Views are included in tables
|
|
188
|
+
functions=[],
|
|
189
|
+
procedures=[],
|
|
190
|
+
metadata={
|
|
191
|
+
"datasets_analyzed": datasets_to_analyze,
|
|
192
|
+
"total_tables": len(all_tables),
|
|
193
|
+
"extraction_timestamp": datetime.now().isoformat(),
|
|
194
|
+
"location": self.location
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
self.logger.info(
|
|
199
|
+
f"Extracted metadata for {len(all_tables)} tables from {len(datasets_to_analyze)} datasets"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return schema_metadata
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
self.logger.error(f"Failed to extract BigQuery schema metadata: {e}")
|
|
206
|
+
raise
|
|
207
|
+
|
|
208
|
+
async def _extract_tables_from_dataset(
|
|
209
|
+
self,
|
|
210
|
+
dataset_id: str
|
|
211
|
+
) -> List[TableMetadata]:
|
|
212
|
+
"""Extract all tables from a specific dataset."""
|
|
213
|
+
try:
|
|
214
|
+
dataset_ref = f"{self.project_id}.{dataset_id}"
|
|
215
|
+
tables = self.client.list_tables(dataset_ref)
|
|
216
|
+
|
|
217
|
+
table_metadata_list = []
|
|
218
|
+
for table in tables:
|
|
219
|
+
# Get full table details
|
|
220
|
+
table_ref = self.client.get_table(table.reference)
|
|
221
|
+
|
|
222
|
+
# Extract table metadata
|
|
223
|
+
table_metadata = await self._extract_table_metadata(
|
|
224
|
+
dataset_id, table_ref
|
|
225
|
+
)
|
|
226
|
+
table_metadata_list.append(table_metadata)
|
|
227
|
+
|
|
228
|
+
return table_metadata_list
|
|
229
|
+
|
|
230
|
+
except NotFound:
|
|
231
|
+
self.logger.warning(f"Dataset {dataset_id} not found")
|
|
232
|
+
return []
|
|
233
|
+
except Exception as e:
|
|
234
|
+
self.logger.warning(f"Could not extract tables from dataset {dataset_id}: {e}")
|
|
235
|
+
return []
|
|
236
|
+
|
|
237
|
+
async def _extract_table_metadata(
|
|
238
|
+
self,
|
|
239
|
+
dataset_id: str,
|
|
240
|
+
table: bq.Table
|
|
241
|
+
) -> TableMetadata:
|
|
242
|
+
"""Extract detailed metadata for a specific table."""
|
|
243
|
+
try:
|
|
244
|
+
# Extract columns
|
|
245
|
+
columns = []
|
|
246
|
+
for field in table.schema:
|
|
247
|
+
columns.append({
|
|
248
|
+
"name": field.name,
|
|
249
|
+
"type": field.field_type,
|
|
250
|
+
"nullable": field.mode != "REQUIRED",
|
|
251
|
+
"description": field.description or ""
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
# Get sample data
|
|
255
|
+
sample_data = []
|
|
256
|
+
try:
|
|
257
|
+
query = f"""
|
|
258
|
+
SELECT *
|
|
259
|
+
FROM `{self.project_id}.{dataset_id}.{table.table_id}`
|
|
260
|
+
LIMIT {self.max_sample_rows}
|
|
261
|
+
"""
|
|
262
|
+
query_job = self.client.query(query)
|
|
263
|
+
results = query_job.result()
|
|
264
|
+
|
|
265
|
+
for row in results:
|
|
266
|
+
sample_data.append(dict(row))
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
self.logger.debug(f"Could not get sample data for {table.table_id}: {e}")
|
|
270
|
+
|
|
271
|
+
# Determine if it's a view
|
|
272
|
+
is_view = table.table_type == "VIEW"
|
|
273
|
+
|
|
274
|
+
return TableMetadata(
|
|
275
|
+
name=table.table_id,
|
|
276
|
+
schema=dataset_id,
|
|
277
|
+
columns=columns,
|
|
278
|
+
primary_keys=[], # BigQuery doesn't enforce primary keys
|
|
279
|
+
foreign_keys=[], # BigQuery doesn't enforce foreign keys
|
|
280
|
+
indexes=[], # BigQuery handles indexing automatically
|
|
281
|
+
description=table.description or f"BigQuery {'view' if is_view else 'table'} in dataset {dataset_id}",
|
|
282
|
+
sample_data=sample_data
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
self.logger.warning(
|
|
287
|
+
f"Could not extract metadata for table {table.table_id}: {e}"
|
|
288
|
+
)
|
|
289
|
+
return TableMetadata(
|
|
290
|
+
name=table.table_id,
|
|
291
|
+
schema=dataset_id,
|
|
292
|
+
columns=[],
|
|
293
|
+
primary_keys=[],
|
|
294
|
+
foreign_keys=[],
|
|
295
|
+
indexes=[],
|
|
296
|
+
description="",
|
|
297
|
+
sample_data=[]
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
async def generate_query(
|
|
301
|
+
self,
|
|
302
|
+
natural_language_query: str,
|
|
303
|
+
target_tables: Optional[List[str]] = None,
|
|
304
|
+
query_type: str = "SELECT"
|
|
305
|
+
) -> Dict[str, Any]:
|
|
306
|
+
"""Generate BigQuery SQL from natural language."""
|
|
307
|
+
try:
|
|
308
|
+
# Get schema context for the query
|
|
309
|
+
schema_context = []
|
|
310
|
+
if self.schema_metadata:
|
|
311
|
+
# Filter tables based on target_tables
|
|
312
|
+
tables_to_use = self.schema_metadata.tables
|
|
313
|
+
if target_tables:
|
|
314
|
+
tables_to_use = [
|
|
315
|
+
t for t in tables_to_use if t.name in target_tables
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
for table in tables_to_use[:5]: # Limit to top 5
|
|
319
|
+
schema_context.append({
|
|
320
|
+
'table': f"{table.schema}.{table.name}",
|
|
321
|
+
'columns': [
|
|
322
|
+
{'name': col['name'], 'type': col['type']}
|
|
323
|
+
for col in table.columns
|
|
324
|
+
],
|
|
325
|
+
'sample_count': len(table.sample_data)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
# Build SQL query generation prompt
|
|
329
|
+
prompt = self._build_sql_query_prompt(
|
|
330
|
+
natural_language_query=natural_language_query,
|
|
331
|
+
schema_context=schema_context
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Generate query using LLM
|
|
335
|
+
async with self._llm as client:
|
|
336
|
+
response = await client.ask(
|
|
337
|
+
prompt=prompt,
|
|
338
|
+
model=self._llm_model,
|
|
339
|
+
temperature=0.1
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Extract SQL query from response
|
|
343
|
+
sql_query = self._extract_sql_from_response(response.output or response.response)
|
|
344
|
+
|
|
345
|
+
result = {
|
|
346
|
+
"query": sql_query,
|
|
347
|
+
"query_type": "sql",
|
|
348
|
+
"tables_used": target_tables or [],
|
|
349
|
+
"schema_context_used": len(schema_context),
|
|
350
|
+
"natural_language_input": natural_language_query
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return result
|
|
354
|
+
|
|
355
|
+
except Exception as e:
|
|
356
|
+
self.logger.error(f"Failed to generate SQL query: {e}")
|
|
357
|
+
raise
|
|
358
|
+
|
|
359
|
+
def _build_sql_query_prompt(
|
|
360
|
+
self,
|
|
361
|
+
natural_language_query: str,
|
|
362
|
+
schema_context: List[Dict[str, Any]]
|
|
363
|
+
) -> str:
|
|
364
|
+
"""Build prompt for SQL query generation."""
|
|
365
|
+
prompt = f"""
|
|
366
|
+
You are an expert BigQuery SQL developer.
|
|
367
|
+
Generate a BigQuery SQL query based on the natural language request and the provided schema information.
|
|
368
|
+
|
|
369
|
+
Natural Language Request: {natural_language_query}
|
|
370
|
+
|
|
371
|
+
Available Tables and Schema:
|
|
372
|
+
"""
|
|
373
|
+
for i, context in enumerate(schema_context, 1):
|
|
374
|
+
prompt += f"\n{i}. Table: {context['table']}\n"
|
|
375
|
+
prompt += " Columns:\n"
|
|
376
|
+
for col in context['columns'][:10]: # Limit columns shown
|
|
377
|
+
prompt += f" - {col['name']} ({col['type']})\n"
|
|
378
|
+
|
|
379
|
+
prompt += f"""
|
|
380
|
+
|
|
381
|
+
BigQuery SQL Guidelines:
|
|
382
|
+
1. Use standard SQL syntax (not legacy SQL)
|
|
383
|
+
2. Fully qualify table names: `project.dataset.table`
|
|
384
|
+
3. Use backticks for table/column names with special characters
|
|
385
|
+
4. Common BigQuery functions: STRING_AGG, ARRAY_AGG, APPROX_COUNT_DISTINCT
|
|
386
|
+
5. Use STRUCT and ARRAY types when appropriate
|
|
387
|
+
6. For date/time: CURRENT_TIMESTAMP(), DATE_SUB(), TIMESTAMP_DIFF()
|
|
388
|
+
7. Use WITH clauses (CTEs) for complex queries
|
|
389
|
+
8. Return only the SQL query without explanations or markdown formatting
|
|
390
|
+
|
|
391
|
+
Project ID: {self.project_id}
|
|
392
|
+
|
|
393
|
+
SQL Query:"""
|
|
394
|
+
|
|
395
|
+
return prompt
|
|
396
|
+
|
|
397
|
+
def _extract_sql_from_response(self, response_text: str) -> str:
|
|
398
|
+
"""Extract SQL query from LLM response."""
|
|
399
|
+
# Remove markdown code blocks if present
|
|
400
|
+
if "```" in response_text:
|
|
401
|
+
lines = response_text.split('\n')
|
|
402
|
+
sql_lines = []
|
|
403
|
+
in_code_block = False
|
|
404
|
+
|
|
405
|
+
for line in lines:
|
|
406
|
+
if line.strip().startswith("```"):
|
|
407
|
+
in_code_block = not in_code_block
|
|
408
|
+
continue
|
|
409
|
+
elif in_code_block:
|
|
410
|
+
sql_lines.append(line)
|
|
411
|
+
|
|
412
|
+
return '\n'.join(sql_lines).strip()
|
|
413
|
+
else:
|
|
414
|
+
return response_text.strip()
|
|
415
|
+
|
|
416
|
+
async def execute_query(
|
|
417
|
+
self,
|
|
418
|
+
query: str,
|
|
419
|
+
limit: Optional[int] = 1000
|
|
420
|
+
) -> Dict[str, Any]:
|
|
421
|
+
"""Execute SQL query against BigQuery."""
|
|
422
|
+
try:
|
|
423
|
+
if not self.client:
|
|
424
|
+
await self.connect_database()
|
|
425
|
+
|
|
426
|
+
# Add limit if not present
|
|
427
|
+
query_upper = query.upper().strip()
|
|
428
|
+
if limit and 'LIMIT' not in query_upper:
|
|
429
|
+
query = f"{query.rstrip(';')} LIMIT {limit}"
|
|
430
|
+
|
|
431
|
+
# Configure query job
|
|
432
|
+
job_config = bq.QueryJobConfig()
|
|
433
|
+
job_config.use_legacy_sql = False
|
|
434
|
+
|
|
435
|
+
# Execute query
|
|
436
|
+
query_job = self.client.query(query, job_config=job_config)
|
|
437
|
+
results = query_job.result()
|
|
438
|
+
|
|
439
|
+
# Process results
|
|
440
|
+
rows = []
|
|
441
|
+
columns = []
|
|
442
|
+
|
|
443
|
+
if results.schema:
|
|
444
|
+
columns = [field.name for field in results.schema]
|
|
445
|
+
|
|
446
|
+
for row in results:
|
|
447
|
+
rows.append(dict(row))
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
"success": True,
|
|
451
|
+
"data": rows,
|
|
452
|
+
"columns": columns,
|
|
453
|
+
"record_count": len(rows),
|
|
454
|
+
"query": query,
|
|
455
|
+
"total_bytes_processed": query_job.total_bytes_processed,
|
|
456
|
+
"total_bytes_billed": query_job.total_bytes_billed
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
except Exception as e:
|
|
460
|
+
self.logger.error(f"BigQuery query execution failed: {e}")
|
|
461
|
+
return {
|
|
462
|
+
"success": False,
|
|
463
|
+
"error": str(e),
|
|
464
|
+
"query": query
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async def close(self):
|
|
468
|
+
"""Close BigQuery client connection."""
|
|
469
|
+
if self.client:
|
|
470
|
+
self.client.close()
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
class BigQueryQueryExecutionTool(AbstractTool):
|
|
474
|
+
"""Tool for executing SQL queries against BigQuery."""
|
|
475
|
+
|
|
476
|
+
name = "execute_bigquery_query"
|
|
477
|
+
description = "Execute SQL queries against Google BigQuery"
|
|
478
|
+
args_schema = BigQueryQueryExecutionArgs
|
|
479
|
+
|
|
480
|
+
def __init__(self, agent: BigQueryAgent, **kwargs):
|
|
481
|
+
super().__init__(**kwargs)
|
|
482
|
+
self.agent = agent
|
|
483
|
+
|
|
484
|
+
async def _execute(
|
|
485
|
+
self,
|
|
486
|
+
query: str,
|
|
487
|
+
limit: Optional[int] = 1000,
|
|
488
|
+
use_legacy_sql: bool = False
|
|
489
|
+
) -> ToolResult:
|
|
490
|
+
"""Execute BigQuery query."""
|
|
491
|
+
try:
|
|
492
|
+
result = await self.agent.execute_query(query, limit)
|
|
493
|
+
|
|
494
|
+
return ToolResult(
|
|
495
|
+
status="success" if result["success"] else "error",
|
|
496
|
+
result=result,
|
|
497
|
+
error=result.get("error"),
|
|
498
|
+
metadata={
|
|
499
|
+
"query": query,
|
|
500
|
+
"limit": limit,
|
|
501
|
+
"bytes_processed": result.get("total_bytes_processed"),
|
|
502
|
+
"bytes_billed": result.get("total_bytes_billed")
|
|
503
|
+
}
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
except Exception as e:
|
|
507
|
+
return ToolResult(
|
|
508
|
+
status="error",
|
|
509
|
+
result=None,
|
|
510
|
+
error=str(e),
|
|
511
|
+
metadata={"query": query}
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class DatasetExplorationTool(AbstractTool):
|
|
516
|
+
"""Tool for exploring BigQuery datasets and tables."""
|
|
517
|
+
|
|
518
|
+
name = "explore_bigquery_datasets"
|
|
519
|
+
description = "Explore available datasets and tables in BigQuery"
|
|
520
|
+
|
|
521
|
+
class ExplorationArgs(AbstractToolArgsSchema):
|
|
522
|
+
"""Exploration arguments schema."""
|
|
523
|
+
dataset: Optional[str] = Field(
|
|
524
|
+
default=None, description="Specific dataset to explore"
|
|
525
|
+
)
|
|
526
|
+
table: Optional[str] = Field(
|
|
527
|
+
default=None, description="Specific table to explore"
|
|
528
|
+
)
|
|
529
|
+
show_sample_data: bool = Field(
|
|
530
|
+
default=True, description="Include sample data in results"
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
args_schema = ExplorationArgs
|
|
534
|
+
|
|
535
|
+
def __init__(self, agent: BigQueryAgent, **kwargs):
|
|
536
|
+
super().__init__(**kwargs)
|
|
537
|
+
self.agent = agent
|
|
538
|
+
|
|
539
|
+
async def _execute(
|
|
540
|
+
self,
|
|
541
|
+
dataset: Optional[str] = None,
|
|
542
|
+
table: Optional[str] = None,
|
|
543
|
+
show_sample_data: bool = True
|
|
544
|
+
) -> ToolResult:
|
|
545
|
+
"""Explore datasets/tables in BigQuery."""
|
|
546
|
+
try:
|
|
547
|
+
if not self.agent.schema_metadata:
|
|
548
|
+
await self.agent.extract_schema_metadata()
|
|
549
|
+
|
|
550
|
+
exploration_result = {
|
|
551
|
+
"datasets": [],
|
|
552
|
+
"tables": [],
|
|
553
|
+
"total_tables": 0
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
# Filter tables
|
|
557
|
+
tables_to_explore = self.agent.schema_metadata.tables
|
|
558
|
+
if dataset:
|
|
559
|
+
tables_to_explore = [t for t in tables_to_explore if t.schema == dataset]
|
|
560
|
+
if table:
|
|
561
|
+
tables_to_explore = [t for t in tables_to_explore if t.name == table]
|
|
562
|
+
|
|
563
|
+
# Get unique datasets
|
|
564
|
+
datasets = list(set(t.schema for t in tables_to_explore))
|
|
565
|
+
exploration_result["datasets"] = datasets
|
|
566
|
+
|
|
567
|
+
# Build table information
|
|
568
|
+
for tbl in tables_to_explore:
|
|
569
|
+
table_info = {
|
|
570
|
+
"name": tbl.name,
|
|
571
|
+
"dataset": tbl.schema,
|
|
572
|
+
"full_name": f"{tbl.schema}.{tbl.name}",
|
|
573
|
+
"columns": [
|
|
574
|
+
{'name': col['name'], 'type': col['type']}
|
|
575
|
+
for col in tbl.columns
|
|
576
|
+
],
|
|
577
|
+
"description": tbl.description
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if show_sample_data and tbl.sample_data:
|
|
581
|
+
table_info["sample_data"] = tbl.sample_data[:3]
|
|
582
|
+
|
|
583
|
+
exploration_result["tables"].append(table_info)
|
|
584
|
+
|
|
585
|
+
exploration_result["total_tables"] = len(exploration_result["tables"])
|
|
586
|
+
|
|
587
|
+
return ToolResult(
|
|
588
|
+
status="success",
|
|
589
|
+
result=exploration_result,
|
|
590
|
+
metadata={
|
|
591
|
+
"project": self.agent.project_id,
|
|
592
|
+
"dataset_filter": dataset,
|
|
593
|
+
"table_filter": table
|
|
594
|
+
}
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
except Exception as e:
|
|
598
|
+
return ToolResult(
|
|
599
|
+
status="error",
|
|
600
|
+
result=None,
|
|
601
|
+
error=str(e)
|
|
602
|
+
)
|
parrot/bots/db/cache.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Optional, Union
|
|
2
|
+
import hashlib
|
|
3
|
+
from redis.asyncio import Redis
|
|
4
|
+
from datamodel.parsers.json import json_encoder, json_decoder # pylint: disable=E0611 # noqa
|
|
5
|
+
from querysource.conf import CACHE_URL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SchemaCache:
|
|
9
|
+
"""Redis-based LRU cache for schema metadata."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, redis_url: str = None, key_prefix: str = "schema_cache", ttl: int = 3600):
|
|
12
|
+
self.redis_url = redis_url or CACHE_URL
|
|
13
|
+
self.key_prefix = key_prefix
|
|
14
|
+
self.ttl = ttl # Time to live in seconds
|
|
15
|
+
self._redis = None
|
|
16
|
+
|
|
17
|
+
async def _get_redis(self) -> Redis:
|
|
18
|
+
"""Get or create Redis connection."""
|
|
19
|
+
if self._redis is None:
|
|
20
|
+
self._redis = Redis.from_url(
|
|
21
|
+
self.redis_url,
|
|
22
|
+
decode_responses=True,
|
|
23
|
+
encoding="utf-8",
|
|
24
|
+
socket_connect_timeout=5,
|
|
25
|
+
socket_timeout=5,
|
|
26
|
+
retry_on_timeout=True
|
|
27
|
+
)
|
|
28
|
+
return self._redis
|
|
29
|
+
|
|
30
|
+
def _make_cache_key(self, search_term: str, search_type: str = "all", limit: int = 10) -> str:
|
|
31
|
+
"""Create a cache key for the search parameters."""
|
|
32
|
+
# Create a hash of the parameters for consistent key generation
|
|
33
|
+
params = f"{search_term}:{search_type}:{limit}"
|
|
34
|
+
hash_key = hashlib.md5(params.encode()).hexdigest()
|
|
35
|
+
return f"{self.key_prefix}:search:{hash_key}"
|
|
36
|
+
|
|
37
|
+
async def get(
|
|
38
|
+
self,
|
|
39
|
+
search_term: str,
|
|
40
|
+
search_type: str = "all",
|
|
41
|
+
limit: int = 10
|
|
42
|
+
) -> Optional[List[Dict[str, Any]]]:
|
|
43
|
+
"""Get cached search results."""
|
|
44
|
+
try:
|
|
45
|
+
redis = await self._get_redis()
|
|
46
|
+
cache_key = self._make_cache_key(search_term, search_type, limit)
|
|
47
|
+
data = await redis.get(cache_key)
|
|
48
|
+
if data:
|
|
49
|
+
return json_decoder(data)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
# Log error but don't fail the operation
|
|
52
|
+
print(f"Cache get error: {e}")
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
async def set(
|
|
56
|
+
self,
|
|
57
|
+
search_term: str,
|
|
58
|
+
search_type: str,
|
|
59
|
+
limit: int,
|
|
60
|
+
results: List[Dict[str, Any]]
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Set cached search results with TTL."""
|
|
63
|
+
try:
|
|
64
|
+
redis = await self._get_redis()
|
|
65
|
+
cache_key = self._make_cache_key(search_term, search_type, limit)
|
|
66
|
+
data = json_encoder(results)
|
|
67
|
+
await redis.setex(cache_key, self.ttl, data)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
# Log error but don't fail the operation
|
|
70
|
+
print(f"Cache set error: {e}")
|
|
71
|
+
|
|
72
|
+
async def invalidate_all(self) -> None:
|
|
73
|
+
"""Invalidate all cache entries."""
|
|
74
|
+
try:
|
|
75
|
+
redis = await self._get_redis()
|
|
76
|
+
keys = await redis.keys(f"{self.key_prefix}:*")
|
|
77
|
+
if keys:
|
|
78
|
+
await redis.delete(*keys)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
print(f"Cache invalidation error: {e}")
|
|
81
|
+
|
|
82
|
+
async def close(self) -> None:
|
|
83
|
+
"""Close Redis connection."""
|
|
84
|
+
if self._redis:
|
|
85
|
+
await self._redis.close()
|