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,286 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
|
5
|
+
from sqlalchemy.orm import sessionmaker
|
|
6
|
+
from navconfig.logging import logging
|
|
7
|
+
from ..abstract import (
|
|
8
|
+
AbstractTool,
|
|
9
|
+
ToolResult,
|
|
10
|
+
AbstractToolArgsSchema
|
|
11
|
+
)
|
|
12
|
+
from ...stores.abstract import AbstractStore
|
|
13
|
+
from ...conf import asyncpg_sqlalchemy_url
|
|
14
|
+
from .models import TableMetadata
|
|
15
|
+
from .cache import SchemaMetadataCache
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SchemaSearchArgs(AbstractToolArgsSchema):
|
|
19
|
+
"""Arguments for schema search tool."""
|
|
20
|
+
search_term: str = Field(
|
|
21
|
+
description="Term to search for in table names, column names, or descriptions"
|
|
22
|
+
)
|
|
23
|
+
schema_name: Optional[str] = Field(
|
|
24
|
+
default=None,
|
|
25
|
+
description="Schema name to search in"
|
|
26
|
+
)
|
|
27
|
+
table_name: Optional[str] = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Table name to search in"
|
|
30
|
+
)
|
|
31
|
+
search_type: str = Field(
|
|
32
|
+
default="all",
|
|
33
|
+
description="Type of search: 'tables', 'columns', 'descriptions', or 'all'"
|
|
34
|
+
)
|
|
35
|
+
limit: int = Field(
|
|
36
|
+
default=5,
|
|
37
|
+
description="Maximum number of results to return"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AbstractSchemaManagerTool(AbstractTool, ABC):
|
|
42
|
+
"""
|
|
43
|
+
Abstract base for database-specific schema management tools.
|
|
44
|
+
|
|
45
|
+
Handles all schema-related operations:
|
|
46
|
+
- Schema analysis and metadata extraction
|
|
47
|
+
- Schema search and discovery
|
|
48
|
+
- Metadata caching and retrieval
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
name = "SchemaManagerTool"
|
|
52
|
+
description = "Comprehensive schema management for database operations"
|
|
53
|
+
args_schema = SchemaSearchArgs
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
allowed_schemas: List[str],
|
|
58
|
+
engine: AsyncEngine = None,
|
|
59
|
+
metadata_cache: SchemaMetadataCache = None,
|
|
60
|
+
vector_store: Optional[AbstractStore] = None,
|
|
61
|
+
dsn: Optional[str] = None,
|
|
62
|
+
database_type: str = "postgresql",
|
|
63
|
+
session_maker: Optional[sessionmaker] = None,
|
|
64
|
+
**kwargs
|
|
65
|
+
):
|
|
66
|
+
super().__init__(**kwargs)
|
|
67
|
+
self.dsn = dsn or asyncpg_sqlalchemy_url
|
|
68
|
+
self.database_type = database_type
|
|
69
|
+
self.allowed_schemas = allowed_schemas
|
|
70
|
+
# Database components
|
|
71
|
+
self.engine: Optional[AsyncEngine] = engine or self._get_engine(
|
|
72
|
+
dsn=self.dsn,
|
|
73
|
+
search_path=",".join(allowed_schemas)
|
|
74
|
+
)
|
|
75
|
+
# Schema-aware components
|
|
76
|
+
self.metadata_cache = metadata_cache or SchemaMetadataCache(
|
|
77
|
+
vector_store=vector_store, # Optional - can be None
|
|
78
|
+
lru_maxsize=500, # Large cache for many tables
|
|
79
|
+
lru_ttl=1800 # 30 minutes
|
|
80
|
+
)
|
|
81
|
+
# Vector Store:
|
|
82
|
+
self.knowledge_store = vector_store
|
|
83
|
+
|
|
84
|
+
if session_maker:
|
|
85
|
+
self.session_maker = session_maker
|
|
86
|
+
else:
|
|
87
|
+
self.session_maker = sessionmaker(
|
|
88
|
+
self.engine,
|
|
89
|
+
class_=AsyncSession,
|
|
90
|
+
expire_on_commit=False
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
self.logger = logging.getLogger(f"{self.__class__.__name__}")
|
|
94
|
+
self.logger.debug(
|
|
95
|
+
f"Initialized with {len(allowed_schemas)} schemas: {allowed_schemas}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _get_engine(self, dsn: str, search_path: str) -> AsyncEngine:
|
|
99
|
+
"""Create and return an AsyncEngine for the given DSN."""
|
|
100
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
101
|
+
return create_async_engine(
|
|
102
|
+
dsn,
|
|
103
|
+
echo=False,
|
|
104
|
+
pool_pre_ping=True,
|
|
105
|
+
pool_recycle=3600,
|
|
106
|
+
# Multi-schema search path
|
|
107
|
+
connect_args={
|
|
108
|
+
"server_settings": {
|
|
109
|
+
"search_path": search_path
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
async def _execute(
|
|
115
|
+
self,
|
|
116
|
+
search_term: str,
|
|
117
|
+
search_type: str = "all",
|
|
118
|
+
limit: int = 10
|
|
119
|
+
) -> ToolResult:
|
|
120
|
+
"""Execute schema search with the provided parameters."""
|
|
121
|
+
try:
|
|
122
|
+
raw_results = await self.search_schema(search_term, search_type, limit)
|
|
123
|
+
|
|
124
|
+
formatted_results = []
|
|
125
|
+
for table in raw_results:
|
|
126
|
+
formatted_result = await self._format_table_result(table, search_term, search_type)
|
|
127
|
+
if formatted_result:
|
|
128
|
+
formatted_results.append(formatted_result)
|
|
129
|
+
|
|
130
|
+
return ToolResult(
|
|
131
|
+
status="success",
|
|
132
|
+
result=formatted_results,
|
|
133
|
+
metadata={
|
|
134
|
+
"search_term": search_term,
|
|
135
|
+
"search_type": search_type,
|
|
136
|
+
"results_count": len(formatted_results),
|
|
137
|
+
"searched_schemas": self.allowed_schemas
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
except Exception as e:
|
|
141
|
+
self.logger.error(f"Schema search failed: {e}")
|
|
142
|
+
return ToolResult(
|
|
143
|
+
status="error",
|
|
144
|
+
result=None,
|
|
145
|
+
error=str(e),
|
|
146
|
+
metadata={"search_term": search_term}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
async def analyze_all_schemas(self) -> Dict[str, int]:
|
|
150
|
+
"""
|
|
151
|
+
Analyze all allowed schemas and populate metadata cache.
|
|
152
|
+
Returns dict of schema_name -> table_count.
|
|
153
|
+
"""
|
|
154
|
+
self.logger.info(f"Analyzing schemas: {self.allowed_schemas}")
|
|
155
|
+
|
|
156
|
+
results = {}
|
|
157
|
+
total_tables = 0
|
|
158
|
+
|
|
159
|
+
for schema_name in self.allowed_schemas:
|
|
160
|
+
try:
|
|
161
|
+
table_count = await self.analyze_schema(schema_name)
|
|
162
|
+
results[schema_name] = table_count
|
|
163
|
+
total_tables += table_count
|
|
164
|
+
self.logger.info(f"Schema '{schema_name}': {table_count} tables/views analyzed")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
self.logger.warning(f"Failed to analyze schema '{schema_name}': {e}")
|
|
167
|
+
results[schema_name] = 0
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
self.logger.info(
|
|
171
|
+
f"Analysis completed. Total: {total_tables} tables across {len(self.allowed_schemas)} schemas"
|
|
172
|
+
)
|
|
173
|
+
return results
|
|
174
|
+
|
|
175
|
+
@abstractmethod
|
|
176
|
+
async def analyze_schema(self, schema_name: str) -> int:
|
|
177
|
+
"""
|
|
178
|
+
Analyze individual schema and return table count.
|
|
179
|
+
Must be implemented by database-specific subclasses.
|
|
180
|
+
"""
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
@abstractmethod
|
|
184
|
+
async def analyze_table(
|
|
185
|
+
self,
|
|
186
|
+
session: AsyncSession,
|
|
187
|
+
schema_name: str,
|
|
188
|
+
table_name: str,
|
|
189
|
+
table_type: str,
|
|
190
|
+
comment: Optional[str]
|
|
191
|
+
) -> TableMetadata:
|
|
192
|
+
"""
|
|
193
|
+
Analyze individual table metadata.
|
|
194
|
+
Must be implemented by database-specific subclasses.
|
|
195
|
+
"""
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
async def search_schema(
|
|
199
|
+
self,
|
|
200
|
+
search_term: str,
|
|
201
|
+
search_type: str = "all",
|
|
202
|
+
limit: int = 10
|
|
203
|
+
) -> List[TableMetadata]:
|
|
204
|
+
"""Search database schema - returns raw TableMetadata for agent use."""
|
|
205
|
+
self.logger.debug(
|
|
206
|
+
f"🔍 SCHEMA SEARCH: '{search_term}' (type: {search_type}, limit: {limit})")
|
|
207
|
+
|
|
208
|
+
tables = await self.metadata_cache.search_similar_tables(
|
|
209
|
+
schema_names=self.allowed_schemas,
|
|
210
|
+
query=search_term,
|
|
211
|
+
limit=limit
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
self.logger.info(f"✅ SEARCH COMPLETE: Found {len(tables)} results")
|
|
215
|
+
return tables
|
|
216
|
+
|
|
217
|
+
async def _format_table_result(
|
|
218
|
+
self,
|
|
219
|
+
table: TableMetadata,
|
|
220
|
+
search_term: str,
|
|
221
|
+
search_type: str
|
|
222
|
+
) -> Optional[Dict[str, Any]]:
|
|
223
|
+
"""Format a table metadata object into a search result."""
|
|
224
|
+
|
|
225
|
+
# Always return since cache already did filtering
|
|
226
|
+
return {
|
|
227
|
+
"type": "table",
|
|
228
|
+
"schema": table.schema,
|
|
229
|
+
"tablename": table.tablename,
|
|
230
|
+
"full_name": table.full_name,
|
|
231
|
+
"table_type": table.table_type,
|
|
232
|
+
"description": table.comment,
|
|
233
|
+
"columns": [
|
|
234
|
+
{
|
|
235
|
+
"name": col.get('name'),
|
|
236
|
+
"type": col.get('type'),
|
|
237
|
+
"nullable": col.get('nullable', True),
|
|
238
|
+
"description": col.get('description')
|
|
239
|
+
}
|
|
240
|
+
for col in table.columns
|
|
241
|
+
],
|
|
242
|
+
"row_count": table.row_count,
|
|
243
|
+
"sample_data": table.sample_data[:3] if table.sample_data else [],
|
|
244
|
+
"search_term": search_term,
|
|
245
|
+
"search_type": search_type
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async def get_table_details(
|
|
249
|
+
self,
|
|
250
|
+
schema: str,
|
|
251
|
+
tablename: str
|
|
252
|
+
) -> Optional[TableMetadata]:
|
|
253
|
+
"""Get detailed metadata for a specific table."""
|
|
254
|
+
if schema not in self.allowed_schemas:
|
|
255
|
+
self.logger.warning(
|
|
256
|
+
f"Schema '{schema}' not in allowed schemas: {self.allowed_schemas}"
|
|
257
|
+
)
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
return await self.metadata_cache.get_table_metadata(schema, tablename)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
self.logger.error(f"Failed to get table details for {schema}.{tablename}: {e}")
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
async def get_schema_overview(self, schema_name: str) -> Optional[Dict[str, Any]]:
|
|
267
|
+
"""Get overview of a specific schema."""
|
|
268
|
+
if schema_name not in self.allowed_schemas:
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
if schema_meta := self.metadata_cache.get_schema_overview(schema_name):
|
|
272
|
+
return {
|
|
273
|
+
"schema": schema_meta.schema,
|
|
274
|
+
"database_name": schema_meta.database_name,
|
|
275
|
+
"table_count": schema_meta.table_count,
|
|
276
|
+
"view_count": schema_meta.view_count,
|
|
277
|
+
"total_rows": schema_meta.total_rows,
|
|
278
|
+
"last_analyzed": schema_meta.last_analyzed.isoformat() if schema_meta.last_analyzed else None,
|
|
279
|
+
"tables": list(schema_meta.tables.keys()),
|
|
280
|
+
"views": list(schema_meta.views.keys())
|
|
281
|
+
}
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
def get_allowed_schemas(self) -> List[str]:
|
|
285
|
+
"""Get the list of schemas this tool can search."""
|
|
286
|
+
return self.allowed_schemas.copy()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from sqlalchemy import text
|
|
3
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from .abstract import AbstractSchemaManagerTool
|
|
6
|
+
from .models import TableMetadata
|
|
7
|
+
|
|
8
|
+
class BQSchemaSearchTool(AbstractSchemaManagerTool):
|
|
9
|
+
"""BigQuery-specific schema manager tool."""
|
|
10
|
+
|
|
11
|
+
name = "BQSchemaSearchTool"
|
|
12
|
+
description = "Schema management for BigQuery databases"
|
|
13
|
+
|
|
14
|
+
async def analyze_schema(self, schema_name: str) -> int:
|
|
15
|
+
"""Analyze individual BigQuery schema (dataset) and return table count."""
|
|
16
|
+
async with self.session_maker() as session:
|
|
17
|
+
try:
|
|
18
|
+
# Query INFORMATION_SCHEMA.TABLES for the dataset
|
|
19
|
+
query_str = f"SELECT table_name, table_type FROM `{schema_name}.INFORMATION_SCHEMA.TABLES` WHERE table_type IN ('BASE TABLE', 'VIEW')"
|
|
20
|
+
|
|
21
|
+
result = await session.execute(text(query_str))
|
|
22
|
+
tables_data = result.fetchall()
|
|
23
|
+
|
|
24
|
+
for table_row in tables_data:
|
|
25
|
+
table_name = table_row.table_name
|
|
26
|
+
table_type = table_row.table_type
|
|
27
|
+
comment = None
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
table_metadata = await self.analyze_table(
|
|
31
|
+
session, schema_name, table_name, table_type, comment
|
|
32
|
+
)
|
|
33
|
+
await self.metadata_cache.store_table_metadata(table_metadata)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
self.logger.warning(
|
|
36
|
+
f"Failed to analyze table {schema_name}.{table_name}: {e}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return len(tables_data)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
self.logger.error(f"Error accessing schema {schema_name}: {e}")
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
async def analyze_table(
|
|
45
|
+
self,
|
|
46
|
+
session: AsyncSession,
|
|
47
|
+
schema_name: str,
|
|
48
|
+
table_name: str,
|
|
49
|
+
table_type: str,
|
|
50
|
+
comment: Optional[str]
|
|
51
|
+
) -> TableMetadata:
|
|
52
|
+
"""Analyze individual BigQuery table metadata."""
|
|
53
|
+
|
|
54
|
+
# Get columns
|
|
55
|
+
columns_query = f"""
|
|
56
|
+
SELECT
|
|
57
|
+
column_name,
|
|
58
|
+
data_type,
|
|
59
|
+
is_nullable,
|
|
60
|
+
NULL as column_default,
|
|
61
|
+
NULL as character_maximum_length,
|
|
62
|
+
NULL as comment
|
|
63
|
+
FROM `{schema_name}.INFORMATION_SCHEMA.COLUMNS`
|
|
64
|
+
WHERE table_name = :table_name
|
|
65
|
+
ORDER BY ordinal_position
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
result = await session.execute(
|
|
69
|
+
text(columns_query),
|
|
70
|
+
{"table_name": table_name}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
columns = []
|
|
74
|
+
for col_row in result.fetchall():
|
|
75
|
+
columns.append({
|
|
76
|
+
"name": col_row.column_name,
|
|
77
|
+
"type": col_row.data_type,
|
|
78
|
+
"nullable": col_row.is_nullable == "YES",
|
|
79
|
+
"default": col_row.column_default,
|
|
80
|
+
"max_length": col_row.character_maximum_length,
|
|
81
|
+
"comment": col_row.comment
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
# Primary keys are generally not as critical/available in standard BQ metadata queries
|
|
85
|
+
# in the same way as Postgres, so leaving empty for now.
|
|
86
|
+
primary_keys = []
|
|
87
|
+
|
|
88
|
+
row_count = None
|
|
89
|
+
|
|
90
|
+
sample_data = []
|
|
91
|
+
if table_type == 'BASE TABLE':
|
|
92
|
+
try:
|
|
93
|
+
sample_query = f'SELECT * FROM `{schema_name}.{table_name}` LIMIT 3'
|
|
94
|
+
sample_result = await session.execute(text(sample_query))
|
|
95
|
+
rows = sample_result.fetchall()
|
|
96
|
+
if rows:
|
|
97
|
+
column_names = list(sample_result.keys())
|
|
98
|
+
sample_data = [dict(zip(column_names, row)) for row in rows]
|
|
99
|
+
except Exception as e:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
return TableMetadata(
|
|
103
|
+
schema=schema_name,
|
|
104
|
+
tablename=table_name,
|
|
105
|
+
table_type=table_type,
|
|
106
|
+
full_name=f'`{schema_name}.{table_name}`',
|
|
107
|
+
comment=comment,
|
|
108
|
+
columns=columns,
|
|
109
|
+
primary_keys=primary_keys,
|
|
110
|
+
foreign_keys=[],
|
|
111
|
+
indexes=[],
|
|
112
|
+
row_count=row_count,
|
|
113
|
+
sample_data=sample_data,
|
|
114
|
+
last_accessed=datetime.now()
|
|
115
|
+
)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# SCHEMA-AWARE METADATA CACHE
|
|
3
|
+
# ============================================================================
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
import re
|
|
6
|
+
from cachetools import TTLCache
|
|
7
|
+
from navconfig.logging import logging
|
|
8
|
+
from .models import SchemaMetadata, TableMetadata
|
|
9
|
+
from ...stores.abstract import AbstractStore
|
|
10
|
+
|
|
11
|
+
class SchemaMetadataCache:
|
|
12
|
+
"""Two-tier caching: LRU (hot data) + Optional Vector Store (cold/searchable data)."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
vector_store: Optional[AbstractStore] = None,
|
|
17
|
+
lru_maxsize: int = 500, # Increased for large schema count
|
|
18
|
+
lru_ttl: int = 1800 # 30 minutes
|
|
19
|
+
):
|
|
20
|
+
# Tier 1: LRU Cache for frequently accessed metadata
|
|
21
|
+
self.hot_cache = TTLCache(maxsize=lru_maxsize, ttl=lru_ttl)
|
|
22
|
+
|
|
23
|
+
# Tier 2: Optional Vector Store for similarity search and persistence
|
|
24
|
+
self.vector_store = vector_store
|
|
25
|
+
self.vector_enabled = vector_store is not None
|
|
26
|
+
|
|
27
|
+
# Schema-level caches
|
|
28
|
+
self.schema_cache: Dict[str, SchemaMetadata] = {}
|
|
29
|
+
self.table_access_stats: Dict[str, int] = {}
|
|
30
|
+
|
|
31
|
+
self.logger = logging.getLogger("Parrot.SchemaMetadataCache")
|
|
32
|
+
|
|
33
|
+
if not self.vector_enabled:
|
|
34
|
+
print("Vector store not provided - using LRU cache only")
|
|
35
|
+
|
|
36
|
+
def _table_cache_key(self, schema_name: str, table_name: str) -> str:
|
|
37
|
+
"""Generate cache key for table metadata."""
|
|
38
|
+
return f"table:{schema_name}:{table_name}"
|
|
39
|
+
|
|
40
|
+
def _schema_cache_key(self, schema_name: str) -> str:
|
|
41
|
+
"""Generate cache key for schema metadata."""
|
|
42
|
+
return f"schema:{schema_name}"
|
|
43
|
+
|
|
44
|
+
async def get_table_metadata(
|
|
45
|
+
self,
|
|
46
|
+
schema_name: str,
|
|
47
|
+
table_name: str
|
|
48
|
+
) -> Optional[TableMetadata]:
|
|
49
|
+
"""Get table metadata with access tracking."""
|
|
50
|
+
cache_key = self._table_cache_key(schema_name, table_name)
|
|
51
|
+
|
|
52
|
+
# Check hot cache first
|
|
53
|
+
if cache_key in self.hot_cache:
|
|
54
|
+
metadata = self.hot_cache[cache_key]
|
|
55
|
+
self._track_access(cache_key)
|
|
56
|
+
return metadata
|
|
57
|
+
|
|
58
|
+
# Check schema cache
|
|
59
|
+
if schema_name in self.schema_cache:
|
|
60
|
+
schema_meta = self.schema_cache[schema_name]
|
|
61
|
+
all_objects = schema_meta.get_all_objects()
|
|
62
|
+
if table_name in all_objects:
|
|
63
|
+
metadata = all_objects[table_name]
|
|
64
|
+
# Promote to hot cache
|
|
65
|
+
self.hot_cache[cache_key] = metadata
|
|
66
|
+
self._track_access(cache_key)
|
|
67
|
+
return metadata
|
|
68
|
+
|
|
69
|
+
# Check vector store only if enabled
|
|
70
|
+
if self.vector_enabled:
|
|
71
|
+
search_results = await self._search_vector_store(schema_name, table_name)
|
|
72
|
+
if search_results:
|
|
73
|
+
# Store in hot cache
|
|
74
|
+
self.hot_cache[cache_key] = search_results
|
|
75
|
+
self._track_access(cache_key)
|
|
76
|
+
return search_results
|
|
77
|
+
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
async def store_table_metadata(self, metadata: TableMetadata):
|
|
81
|
+
"""Store table metadata in available cache tiers."""
|
|
82
|
+
cache_key = self._table_cache_key(metadata.schema, metadata.tablename)
|
|
83
|
+
|
|
84
|
+
# Store in hot cache
|
|
85
|
+
self.hot_cache[cache_key] = metadata
|
|
86
|
+
|
|
87
|
+
# Update schema cache
|
|
88
|
+
if metadata.schema not in self.schema_cache:
|
|
89
|
+
self.schema_cache[metadata.schema] = SchemaMetadata(
|
|
90
|
+
schema=metadata.schema,
|
|
91
|
+
database_name="navigator", # Could be dynamic
|
|
92
|
+
table_count=0,
|
|
93
|
+
view_count=0
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
schema_meta = self.schema_cache[metadata.schema]
|
|
97
|
+
if metadata.table_type == 'BASE TABLE':
|
|
98
|
+
schema_meta.tables[metadata.tablename] = metadata
|
|
99
|
+
else:
|
|
100
|
+
schema_meta.views[metadata.tablename] = metadata
|
|
101
|
+
|
|
102
|
+
# Store in vector store only if enabled
|
|
103
|
+
if self.vector_enabled:
|
|
104
|
+
await self._store_in_vector_store(metadata)
|
|
105
|
+
|
|
106
|
+
async def search_similar_tables(
|
|
107
|
+
self,
|
|
108
|
+
schema_names: List[str],
|
|
109
|
+
query: str,
|
|
110
|
+
limit: int = 5
|
|
111
|
+
) -> List[TableMetadata]:
|
|
112
|
+
"""Search for similar tables within allowed schemas."""
|
|
113
|
+
if not self.vector_enabled:
|
|
114
|
+
# Fallback: search in LRU cache and schema cache
|
|
115
|
+
return self._search_cache_only(schema_names, query, limit)
|
|
116
|
+
|
|
117
|
+
# Search with multi-schema filter
|
|
118
|
+
search_query = f"schemas:{','.join(schema_names)} {query}"
|
|
119
|
+
try:
|
|
120
|
+
results = await self.vector_store.similarity_search(
|
|
121
|
+
search_query,
|
|
122
|
+
k=limit,
|
|
123
|
+
filter={"schema_name": {"$in": schema_names}} # Multi-schema filter
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Convert results back to TableMetadata
|
|
127
|
+
return await self._convert_vector_results(results)
|
|
128
|
+
except Exception:
|
|
129
|
+
# Fallback to cache-only search
|
|
130
|
+
return self._search_cache_only(schema_names, query, limit)
|
|
131
|
+
|
|
132
|
+
def _search_cache_only(
|
|
133
|
+
self,
|
|
134
|
+
schema_names: List[str],
|
|
135
|
+
query: str,
|
|
136
|
+
limit: int
|
|
137
|
+
) -> List[TableMetadata]:
|
|
138
|
+
"""Fallback search using only cache when vector store unavailable."""
|
|
139
|
+
results = []
|
|
140
|
+
query_lower = query.lower()
|
|
141
|
+
keywords = self._extract_search_keywords(query_lower)
|
|
142
|
+
|
|
143
|
+
self.logger.notice(
|
|
144
|
+
f"🔍 SEARCH: Extracted keywords from '{query}': {keywords}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Search through schema caches
|
|
148
|
+
for schema_name in schema_names:
|
|
149
|
+
if schema_name in self.schema_cache:
|
|
150
|
+
schema_meta = self.schema_cache[schema_name]
|
|
151
|
+
all_objects = schema_meta.get_all_objects()
|
|
152
|
+
|
|
153
|
+
self.logger.debug(
|
|
154
|
+
f"🔍 SEARCHING SCHEMA '{schema_name}': {len(all_objects)} tables available"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
for table_name, table_meta in all_objects.items():
|
|
158
|
+
score = self._calculate_relevance_score(table_name, table_meta, keywords)
|
|
159
|
+
|
|
160
|
+
if score > 0:
|
|
161
|
+
self.logger.debug(
|
|
162
|
+
f"🔍 MATCH: {table_name} scored {score}"
|
|
163
|
+
)
|
|
164
|
+
# IMPORTANT: Return the actual TableMetadata object, not a tuple
|
|
165
|
+
results.append(table_meta) # FIX: was (table_meta_copy, score)
|
|
166
|
+
|
|
167
|
+
if len(results) >= limit:
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
if len(results) >= limit:
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
# FIX: Sort by a different method since we're not storing scores anymore
|
|
174
|
+
# For now, just return in order found (cache already does some relevance filtering)
|
|
175
|
+
final_results = results[:limit]
|
|
176
|
+
|
|
177
|
+
self.logger.info(f"🔍 SEARCH: Found {len(final_results)} results")
|
|
178
|
+
|
|
179
|
+
# DEBUG: Log what we're returning
|
|
180
|
+
for i, table in enumerate(final_results):
|
|
181
|
+
self.logger.debug(f" Result {i+1}: {table.schema}.{table.tablename}")
|
|
182
|
+
|
|
183
|
+
return final_results
|
|
184
|
+
|
|
185
|
+
def _extract_search_keywords(self, query: str) -> List[str]:
|
|
186
|
+
"""Extract meaningful keywords from a natural language query."""
|
|
187
|
+
# Convert to lowercase and remove common stop words
|
|
188
|
+
stop_words = {
|
|
189
|
+
'get', 'show', 'find', 'list', 'select', 'by', 'from', 'the', 'a', 'an',
|
|
190
|
+
'and', 'or', 'of', 'to', 'in', 'on', 'at', 'for', 'with', 'top', 'all'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Split on non-alphanumeric characters and filter
|
|
194
|
+
words = re.findall(r'\b[a-zA-Z]+\b', query.lower())
|
|
195
|
+
return [word for word in words if word not in stop_words and len(word) > 2]
|
|
196
|
+
|
|
197
|
+
def _calculate_relevance_score(
|
|
198
|
+
self,
|
|
199
|
+
table_name: str,
|
|
200
|
+
table_meta: TableMetadata,
|
|
201
|
+
keywords: List[str]
|
|
202
|
+
) -> float:
|
|
203
|
+
"""Calculate relevance score for a table based on keywords."""
|
|
204
|
+
score = 0.0
|
|
205
|
+
|
|
206
|
+
table_name_lower = table_name.lower()
|
|
207
|
+
column_names = [col['name'].lower() for col in table_meta.columns]
|
|
208
|
+
|
|
209
|
+
for keyword in keywords:
|
|
210
|
+
keyword_lower = keyword.lower()
|
|
211
|
+
|
|
212
|
+
if keyword_lower == table_name_lower:
|
|
213
|
+
score += 10.0
|
|
214
|
+
self.logger.debug(f"Exact table match: '{keyword}' == '{table_name}'")
|
|
215
|
+
|
|
216
|
+
elif keyword_lower in table_name_lower:
|
|
217
|
+
score += 5.0
|
|
218
|
+
self.logger.debug(f"Partial table match: '{keyword}' in '{table_name}'")
|
|
219
|
+
|
|
220
|
+
elif keyword_lower in column_names:
|
|
221
|
+
score += 8.0
|
|
222
|
+
self.logger.debug(f"Column match: '{keyword}' found in columns")
|
|
223
|
+
|
|
224
|
+
elif any(keyword_lower in col_name for col_name in column_names):
|
|
225
|
+
score += 3.0
|
|
226
|
+
self.logger.debug(f"Partial column match: '{keyword}' partially matches column")
|
|
227
|
+
|
|
228
|
+
elif table_meta.comment and keyword_lower in table_meta.comment.lower():
|
|
229
|
+
score += 2.0
|
|
230
|
+
self.logger.debug(f"Comment match: '{keyword}' in table comment")
|
|
231
|
+
|
|
232
|
+
return score
|
|
233
|
+
|
|
234
|
+
def get_schema_overview(self, schema_name: str) -> Optional[SchemaMetadata]:
|
|
235
|
+
"""Get complete schema overview."""
|
|
236
|
+
return self.schema_cache.get(schema_name)
|
|
237
|
+
|
|
238
|
+
def get_hot_tables(self, schema_names: List[str], limit: int = 10) -> List[tuple[str, str, int]]:
|
|
239
|
+
"""Get most frequently accessed tables across allowed schemas."""
|
|
240
|
+
schema_access = []
|
|
241
|
+
|
|
242
|
+
for schema_name in schema_names:
|
|
243
|
+
schema_prefix = f"table:{schema_name}:"
|
|
244
|
+
for key, count in self.table_access_stats.items():
|
|
245
|
+
if key.startswith(schema_prefix):
|
|
246
|
+
table_name = key.replace(schema_prefix, "")
|
|
247
|
+
schema_access.append((schema_name, table_name, count))
|
|
248
|
+
|
|
249
|
+
return sorted(schema_access, key=lambda x: x[2], reverse=True)[:limit]
|
|
250
|
+
|
|
251
|
+
def _track_access(self, cache_key: str):
|
|
252
|
+
"""Track table access for hot table identification."""
|
|
253
|
+
self.table_access_stats[cache_key] = self.table_access_stats.get(cache_key, 0) + 1
|
|
254
|
+
|
|
255
|
+
async def _search_vector_store(self, schema_name: str, table_name: str) -> Optional[TableMetadata]:
|
|
256
|
+
"""Search vector store for specific table."""
|
|
257
|
+
# Implementation depends on your vector store
|
|
258
|
+
if not self.vector_enabled:
|
|
259
|
+
return None
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
async def _store_in_vector_store(self, metadata: TableMetadata):
|
|
263
|
+
"""Store metadata in vector store."""
|
|
264
|
+
if not self.vector_enabled:
|
|
265
|
+
return
|
|
266
|
+
try:
|
|
267
|
+
document = {
|
|
268
|
+
"content": metadata.to_yaml_context(),
|
|
269
|
+
"metadata": {
|
|
270
|
+
"type": "table_metadata",
|
|
271
|
+
"schema": metadata.schema,
|
|
272
|
+
"tablename": metadata.tablename,
|
|
273
|
+
"table_type": metadata.table_type,
|
|
274
|
+
"full_name": metadata.full_name
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
await self.vector_store.add_documents([document])
|
|
278
|
+
except Exception:
|
|
279
|
+
pass # Silent failure for vector store issues
|
|
280
|
+
|
|
281
|
+
async def _convert_vector_results(self, results) -> List[TableMetadata]:
|
|
282
|
+
"""Convert vector store results to TableMetadata objects."""
|
|
283
|
+
# Implementation depends on your vector store format
|
|
284
|
+
return []
|