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
parrot/tools/powerbi.py
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
# parrot/tools/powerbi.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
import os
|
|
5
|
+
import asyncio
|
|
6
|
+
import random
|
|
7
|
+
import logging
|
|
8
|
+
import io
|
|
9
|
+
import csv
|
|
10
|
+
import time
|
|
11
|
+
import requests
|
|
12
|
+
import aiohttp
|
|
13
|
+
import pyarrow as pa
|
|
14
|
+
import pyarrow.parquet as pq
|
|
15
|
+
import pandas as pd
|
|
16
|
+
from pydantic import BaseModel, Field, model_validator, PrivateAttr, ConfigDict
|
|
17
|
+
from .abstract import AbstractTool, AbstractToolArgsSchema, ToolResult
|
|
18
|
+
|
|
19
|
+
POWERBI_BASE_URL = os.getenv("POWERBI_BASE_URL", "https://api.powerbi.com/v1.0/myorg")
|
|
20
|
+
PBI_SCOPE = "https://analysis.windows.net/powerbi/api/.default"
|
|
21
|
+
|
|
22
|
+
# ---------------- Utilities ----------------
|
|
23
|
+
|
|
24
|
+
def _fix_table_name(table: str) -> str:
|
|
25
|
+
"""Add single quotes around table names that contain spaces (DAX)."""
|
|
26
|
+
t = (table or "").strip()
|
|
27
|
+
if " " in t and not (t.startswith("'") and t.endswith("'")):
|
|
28
|
+
return f"'{t}'"
|
|
29
|
+
return t
|
|
30
|
+
|
|
31
|
+
def _json_rows_to_markdown(rows: List[Dict[str, Any]], table_name: Optional[str] = None) -> str:
|
|
32
|
+
if not rows:
|
|
33
|
+
return ""
|
|
34
|
+
headers = list(rows[0].keys())
|
|
35
|
+
|
|
36
|
+
def clean(h: str) -> str:
|
|
37
|
+
h2 = h.replace("[", ".").replace("]", "")
|
|
38
|
+
if table_name:
|
|
39
|
+
pref = f"{table_name}."
|
|
40
|
+
if h2.startswith(pref):
|
|
41
|
+
return h2[len(pref):]
|
|
42
|
+
return h2
|
|
43
|
+
|
|
44
|
+
hdrs = [clean(h) for h in headers]
|
|
45
|
+
out = "|" + "|".join(f" {h} " for h in hdrs) + "|\n"
|
|
46
|
+
out += "|" + "|".join("---" for _ in hdrs) + "|\n"
|
|
47
|
+
for row in rows:
|
|
48
|
+
out += "|" + "|".join(f" {row.get(h, '')} " for h in headers) + "|\n"
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
def _rows_to_csv_string(rows: List[Dict[str, Any]]) -> str:
|
|
52
|
+
if not rows:
|
|
53
|
+
return ""
|
|
54
|
+
output = io.StringIO()
|
|
55
|
+
writer = csv.DictWriter(output, fieldnames=list(rows[0].keys()))
|
|
56
|
+
writer.writeheader()
|
|
57
|
+
writer.writerows(rows)
|
|
58
|
+
return output.getvalue()
|
|
59
|
+
|
|
60
|
+
def _rows_to_dataframe(rows: List[Dict[str, Any]]):
|
|
61
|
+
return pd.DataFrame(rows)
|
|
62
|
+
|
|
63
|
+
# near other helpers at the top
|
|
64
|
+
def _rows_to_arrow_table(rows: List[Dict[str, Any]]):
|
|
65
|
+
# pyarrow infers schema well from pylist of dicts
|
|
66
|
+
return pa.Table.from_pylist(rows)
|
|
67
|
+
|
|
68
|
+
def _write_parquet(rows: List[Dict[str, Any]], path: str) -> str:
|
|
69
|
+
# prefer pyarrow directly; fallback to pandas if pyarrow not available
|
|
70
|
+
try:
|
|
71
|
+
table = pa.Table.from_pylist(rows)
|
|
72
|
+
pq.write_table(table, path)
|
|
73
|
+
return path
|
|
74
|
+
except ImportError:
|
|
75
|
+
# fallback via pandas (requires pandas + either pyarrow or fastparquet installed)
|
|
76
|
+
df = pd.DataFrame(rows)
|
|
77
|
+
df.to_parquet(path) # pandas will pick available parquet engine
|
|
78
|
+
return path
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------------- Core Client ----------------
|
|
82
|
+
class PowerBIDatasetClient(BaseModel):
|
|
83
|
+
"""Client for executing DAX queries against a Power BI dataset."""
|
|
84
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
85
|
+
_logger: logging.Logger = PrivateAttr(default=logging.getLogger("PowerBIDatasetClient"))
|
|
86
|
+
|
|
87
|
+
dataset_id: str
|
|
88
|
+
table_names: List[str] = Field(default_factory=list)
|
|
89
|
+
group_id: Optional[str] = None
|
|
90
|
+
|
|
91
|
+
# Auth
|
|
92
|
+
token: Optional[str] = None
|
|
93
|
+
credential: Optional[Any] = None # azure.core.credentials.TokenCredential
|
|
94
|
+
impersonated_user_name: Optional[str] = None
|
|
95
|
+
|
|
96
|
+
# Sampling for table info
|
|
97
|
+
sample_rows_in_table_info: int = Field(default=1, gt=0, le=10)
|
|
98
|
+
|
|
99
|
+
# Retry settings (NEW)
|
|
100
|
+
max_attempts: int = Field(default=5, ge=1, le=10)
|
|
101
|
+
base_backoff: float = Field(default=0.5, ge=0.0) # seconds
|
|
102
|
+
max_backoff: float = Field(default=10.0, ge=0.0) # seconds
|
|
103
|
+
|
|
104
|
+
_schemas: Dict[str, str] = PrivateAttr(default_factory=dict)
|
|
105
|
+
_aiosession: Optional[aiohttp.ClientSession] = PrivateAttr(default=None)
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="before")
|
|
108
|
+
@classmethod
|
|
109
|
+
def _validate_auth(cls, v: Dict[str, Any]) -> Dict[str, Any]:
|
|
110
|
+
if not v.get("token") and not v.get("credential"):
|
|
111
|
+
raise ValueError("Please provide either a credential or a token.")
|
|
112
|
+
return v
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def request_url(self) -> str:
|
|
116
|
+
if self.group_id:
|
|
117
|
+
return f"{POWERBI_BASE_URL}/groups/{self.group_id}/datasets/{self.dataset_id}/executeQueries"
|
|
118
|
+
return f"{POWERBI_BASE_URL}/datasets/{self.dataset_id}/executeQueries"
|
|
119
|
+
|
|
120
|
+
def _headers(self) -> Dict[str, str]:
|
|
121
|
+
if self.token:
|
|
122
|
+
return {"Content-Type": "application/json", "Authorization": f"Bearer {self.token}"}
|
|
123
|
+
try:
|
|
124
|
+
token = self.credential.get_token(PBI_SCOPE).token # type: ignore[attr-defined]
|
|
125
|
+
return {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
raise RuntimeError("Could not get a token from the supplied credentials") from exc
|
|
128
|
+
|
|
129
|
+
def _payload(self, command: str) -> Dict[str, Any]:
|
|
130
|
+
return {
|
|
131
|
+
"queries": [{"query": command}],
|
|
132
|
+
"impersonatedUserName": self.impersonated_user_name,
|
|
133
|
+
"serializerSettings": {"includeNulls": True},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# ---------- Retry helpers (NEW) ----------
|
|
137
|
+
|
|
138
|
+
def _compute_sleep(self, attempt: int, retry_after: Optional[float]) -> float:
|
|
139
|
+
if retry_after is not None:
|
|
140
|
+
return min(retry_after, self.max_backoff)
|
|
141
|
+
# exponential backoff with jitter
|
|
142
|
+
backoff = min(self.base_backoff * (2 ** (attempt - 1)), self.max_backoff)
|
|
143
|
+
jitter = random.uniform(0, backoff / 2)
|
|
144
|
+
return backoff + jitter
|
|
145
|
+
|
|
146
|
+
# ---------- Sync ----------
|
|
147
|
+
|
|
148
|
+
def run(self, command: str, timeout: int = 30) -> Dict[str, Any]:
|
|
149
|
+
headers = self._headers()
|
|
150
|
+
payload = self._payload(command)
|
|
151
|
+
|
|
152
|
+
attempt = 1
|
|
153
|
+
while True:
|
|
154
|
+
resp = requests.post(self.request_url, json=payload, headers=headers, timeout=timeout)
|
|
155
|
+
status = resp.status_code
|
|
156
|
+
if status == 403:
|
|
157
|
+
return {"error": "TokenError: Could not login to Power BI (403)."}
|
|
158
|
+
|
|
159
|
+
if status < 400:
|
|
160
|
+
try:
|
|
161
|
+
return resp.json()
|
|
162
|
+
except Exception as exc:
|
|
163
|
+
raise RuntimeError(f"Invalid JSON from Power BI: {exc}") from exc
|
|
164
|
+
|
|
165
|
+
# retry on 429/5xx
|
|
166
|
+
if status in {429, 500, 502, 503, 504} and attempt < self.max_attempts:
|
|
167
|
+
retry_after_header = resp.headers.get("Retry-After")
|
|
168
|
+
retry_after = None
|
|
169
|
+
if retry_after_header:
|
|
170
|
+
try:
|
|
171
|
+
retry_after = float(retry_after_header)
|
|
172
|
+
except ValueError:
|
|
173
|
+
retry_after = None
|
|
174
|
+
sleep_s = self._compute_sleep(attempt, retry_after)
|
|
175
|
+
self._logger.warning(
|
|
176
|
+
"PowerBI %s; retrying in %.2fs (attempt %d/%d)", status, sleep_s, attempt, self.max_attempts
|
|
177
|
+
)
|
|
178
|
+
time.sleep(sleep_s)
|
|
179
|
+
attempt += 1
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
# give up
|
|
183
|
+
try:
|
|
184
|
+
err = resp.json()
|
|
185
|
+
except Exception:
|
|
186
|
+
err = {"message": resp.text}
|
|
187
|
+
return {"error": f"HTTP {status}", "details": err}
|
|
188
|
+
|
|
189
|
+
# ---------- Async ----------
|
|
190
|
+
async def arun(self, command: str, timeout: int = 30) -> Dict[str, Any]:
|
|
191
|
+
headers = self._headers()
|
|
192
|
+
payload = self._payload(command)
|
|
193
|
+
|
|
194
|
+
attempt = 1
|
|
195
|
+
while True:
|
|
196
|
+
if self._aiosession:
|
|
197
|
+
async with self._aiosession.post(
|
|
198
|
+
self.request_url, headers=headers, json=payload, timeout=timeout
|
|
199
|
+
) as resp:
|
|
200
|
+
status = resp.status
|
|
201
|
+
if status == 403:
|
|
202
|
+
return {"error": "TokenError: Could not login to Power BI (403)."}
|
|
203
|
+
if status < 400:
|
|
204
|
+
return await resp.json(content_type=resp.content_type)
|
|
205
|
+
# retry on 429/5xx
|
|
206
|
+
if status in (429, 500, 502, 503, 504) and attempt < self.max_attempts:
|
|
207
|
+
retry_after_header = resp.headers.get("Retry-After")
|
|
208
|
+
retry_after = None
|
|
209
|
+
if retry_after_header:
|
|
210
|
+
try:
|
|
211
|
+
retry_after = float(retry_after_header)
|
|
212
|
+
except ValueError:
|
|
213
|
+
retry_after = None
|
|
214
|
+
sleep_s = self._compute_sleep(attempt, retry_after)
|
|
215
|
+
self._logger.warning(
|
|
216
|
+
"PowerBI %s; retrying in %.2fs (attempt %d/%d)", status, sleep_s, attempt, self.max_attempts
|
|
217
|
+
)
|
|
218
|
+
await asyncio.sleep(sleep_s)
|
|
219
|
+
attempt += 1
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
err = await resp.json(content_type=resp.content_type)
|
|
224
|
+
except Exception:
|
|
225
|
+
err = {"message": await resp.text()}
|
|
226
|
+
return {"error": f"HTTP {status}", "details": err}
|
|
227
|
+
|
|
228
|
+
async with aiohttp.ClientSession() as session:
|
|
229
|
+
async with session.post(self.request_url, headers=headers, json=payload, timeout=timeout) as resp:
|
|
230
|
+
status = resp.status
|
|
231
|
+
if status == 403:
|
|
232
|
+
return {"error": "TokenError: Could not login to Power BI (403)."}
|
|
233
|
+
if status < 400:
|
|
234
|
+
return await resp.json(content_type=resp.content_type)
|
|
235
|
+
if status in (429, 500, 502, 503, 504) and attempt < self.max_attempts:
|
|
236
|
+
retry_after_header = resp.headers.get("Retry-After")
|
|
237
|
+
retry_after = None
|
|
238
|
+
if retry_after_header:
|
|
239
|
+
try:
|
|
240
|
+
retry_after = float(retry_after_header)
|
|
241
|
+
except ValueError:
|
|
242
|
+
retry_after = None
|
|
243
|
+
sleep_s = self._compute_sleep(attempt, retry_after)
|
|
244
|
+
self._logger.warning("PowerBI %s; retrying in %.2fs (attempt %d/%d)", status, sleep_s, attempt, self.max_attempts)
|
|
245
|
+
await asyncio.sleep(sleep_s)
|
|
246
|
+
attempt += 1
|
|
247
|
+
continue
|
|
248
|
+
try:
|
|
249
|
+
err = await resp.json(content_type=resp.content_type)
|
|
250
|
+
except Exception:
|
|
251
|
+
err = {"message": await resp.text()}
|
|
252
|
+
return {"error": f"HTTP {status}", "details": err}
|
|
253
|
+
|
|
254
|
+
# ---------- Schema-like preview (TOPN sampling) ----------
|
|
255
|
+
def get_table_info(self, tables: Optional[Union[str, List[str]]] = None) -> str:
|
|
256
|
+
requested = self._normalize_tables(tables)
|
|
257
|
+
if not requested:
|
|
258
|
+
return "No (valid) tables requested."
|
|
259
|
+
todo = [t for t in requested if t not in self._schemas]
|
|
260
|
+
for t in todo:
|
|
261
|
+
try:
|
|
262
|
+
js = self.run(f"EVALUATE TOPN({self.sample_rows_in_table_info}, {t})")
|
|
263
|
+
rows = (js or {}).get("results", [{}])[0].get("tables", [{}])[0].get("rows", [])
|
|
264
|
+
self._schemas[t] = _json_rows_to_markdown(rows, table_name=t.strip("'"))
|
|
265
|
+
except Exception as exc:
|
|
266
|
+
self._logger.warning("Error while getting table info for %s: %s", t, exc)
|
|
267
|
+
self._schemas[t] = "unknown"
|
|
268
|
+
return ", ".join([self._schemas.get(t, "unknown") for t in requested])
|
|
269
|
+
|
|
270
|
+
async def aget_table_info(self, tables: Optional[Union[str, List[str]]] = None) -> str:
|
|
271
|
+
requested = self._normalize_tables(tables)
|
|
272
|
+
if not requested:
|
|
273
|
+
return "No (valid) tables requested."
|
|
274
|
+
todo = [t for t in requested if t not in self._schemas]
|
|
275
|
+
|
|
276
|
+
async def _fetch(t: str) -> None:
|
|
277
|
+
try:
|
|
278
|
+
js = await self.arun(f"EVALUATE TOPN({self.sample_rows_in_table_info}, {t})")
|
|
279
|
+
rows = (js or {}).get("results", [{}])[0].get("tables", [{}])[0].get("rows", [])
|
|
280
|
+
self._schemas[t] = _json_rows_to_markdown(rows, table_name=t.strip("'"))
|
|
281
|
+
except Exception as exc:
|
|
282
|
+
self._logger.warning("Error while getting table info for %s: %s", t, exc)
|
|
283
|
+
self._schemas[t] = "unknown"
|
|
284
|
+
|
|
285
|
+
await asyncio.gather(*[_fetch(t) for t in todo])
|
|
286
|
+
return ", ".join([self._schemas.get(t, "unknown") for t in requested])
|
|
287
|
+
|
|
288
|
+
def _normalize_tables(self, tables: Optional[Union[str, List[str]]]) -> Optional[List[str]]:
|
|
289
|
+
if tables is None:
|
|
290
|
+
# FIX: no starred-unpack! Just map.
|
|
291
|
+
return [_fix_table_name(t) for t in self.table_names] if self.table_names else None
|
|
292
|
+
if isinstance(tables, str):
|
|
293
|
+
t = _fix_table_name(tables)
|
|
294
|
+
if self.table_names and t not in [_fix_table_name(x) for x in self.table_names]:
|
|
295
|
+
self.logger.warning("Table %s not found in dataset.", tables)
|
|
296
|
+
return None
|
|
297
|
+
return [t]
|
|
298
|
+
if isinstance(tables, list):
|
|
299
|
+
fixed = [_fix_table_name(x) for x in tables if x]
|
|
300
|
+
if self.table_names:
|
|
301
|
+
known = {_fix_table_name(x) for x in self.table_names}
|
|
302
|
+
fixed = [t for t in fixed if t in known]
|
|
303
|
+
if not fixed:
|
|
304
|
+
self._logger.warning("No valid tables found in requested list.")
|
|
305
|
+
return None
|
|
306
|
+
return fixed or None
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
class _BasePowerBIToolArgs(AbstractToolArgsSchema):
|
|
310
|
+
"""Base arguments for Power BI tools."""
|
|
311
|
+
dataset_id: str = Field(..., description="Power BI dataset (semantic model) ID")
|
|
312
|
+
group_id: Optional[str] = Field(None, description="Workspace (group) ID; omit for My workspace")
|
|
313
|
+
token: Optional[str] = Field(None, description="Bearer token (if not using Azure TokenCredential)")
|
|
314
|
+
impersonated_user_name: Optional[str] = Field(None, description="UPN to impersonate for RLS testing")
|
|
315
|
+
table_names: Optional[List[str]] = Field(default=None, description="Known table names for validation/preview")
|
|
316
|
+
sample_rows_in_table_info: int = Field(default=1, ge=1, le=10, description="Rows sampled in table preview")
|
|
317
|
+
timeout: int = Field(default=30, ge=1, le=300, description="HTTP timeout in seconds")
|
|
318
|
+
|
|
319
|
+
# Retry knobs
|
|
320
|
+
max_attempts: int = Field(default=5, ge=1, le=10, description="Max HTTP attempts on 429/5xx")
|
|
321
|
+
base_backoff: float = Field(default=0.5, ge=0.0, description="Base backoff seconds")
|
|
322
|
+
max_backoff: float = Field(default=10.0, ge=0.0, description="Max backoff seconds")
|
|
323
|
+
|
|
324
|
+
# Export knobs
|
|
325
|
+
export_csv: bool = Field(default=False, description="Write result rows to CSV")
|
|
326
|
+
export_csv_path: Optional[str] = Field(default=None, description="CSV path; if not provided, a temp name is used")
|
|
327
|
+
export_pandas: bool = Field(default=False, description="Return a pandas DataFrame in result (requires pandas)")
|
|
328
|
+
|
|
329
|
+
# DAX templating
|
|
330
|
+
template: Optional[str] = Field(default=None, description="DAX template using Python format syntax")
|
|
331
|
+
parameters: Optional[Dict[str, Any]] = Field(default=None, description="Values for template placeholders")
|
|
332
|
+
|
|
333
|
+
output_format: Optional[str] = Field(
|
|
334
|
+
default=None,
|
|
335
|
+
description="One of: row|rows|json|csv|dataframe|markdown|parquet|pyarrow.Table"
|
|
336
|
+
)
|
|
337
|
+
parquet_path: Optional[str] = Field(
|
|
338
|
+
default=None,
|
|
339
|
+
description="Where to write parquet if output_format='parquet'. Defaults to /tmp/..."
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class PowerBIQueryArgs(_BasePowerBIToolArgs):
|
|
344
|
+
"""Arguments for PowerBIQueryTool."""
|
|
345
|
+
command: Optional[str] = Field(
|
|
346
|
+
default=None,
|
|
347
|
+
description="DAX command to execute; ignored if 'template' is provided"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class PowerBIQueryTool(AbstractTool):
|
|
352
|
+
"""
|
|
353
|
+
Tool for executing DAX queries against a Power BI dataset.
|
|
354
|
+
"""
|
|
355
|
+
name = "powerbi_query"
|
|
356
|
+
description = "Execute DAX against a Power BI dataset and return rows"
|
|
357
|
+
args_schema = PowerBIQueryArgs
|
|
358
|
+
|
|
359
|
+
async def _execute(self, **kwargs) -> Any:
|
|
360
|
+
cred = kwargs.get("credential", None)
|
|
361
|
+
client = PowerBIDatasetClient(
|
|
362
|
+
dataset_id=kwargs["dataset_id"],
|
|
363
|
+
group_id=kwargs.get("group_id"),
|
|
364
|
+
token=kwargs.get("token"),
|
|
365
|
+
credential=cred,
|
|
366
|
+
impersonated_user_name=kwargs.get("impersonated_user_name"),
|
|
367
|
+
table_names=kwargs.get("table_names") or [],
|
|
368
|
+
sample_rows_in_table_info=kwargs.get("sample_rows_in_table_info", 1),
|
|
369
|
+
max_attempts=kwargs.get("max_attempts", 5),
|
|
370
|
+
base_backoff=kwargs.get("base_backoff", 0.5),
|
|
371
|
+
max_backoff=kwargs.get("max_backoff", 10.0),
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# ---- DAX templating (NEW)
|
|
375
|
+
command = kwargs.get("command")
|
|
376
|
+
template = kwargs.get("template")
|
|
377
|
+
params = kwargs.get("parameters") or {}
|
|
378
|
+
if template:
|
|
379
|
+
try:
|
|
380
|
+
# Users can escape literal braces with {{ and }}
|
|
381
|
+
command = template.format(**params)
|
|
382
|
+
except KeyError as exc:
|
|
383
|
+
return ToolResult(status="error", result=None, error=f"Missing template parameter: {exc}")
|
|
384
|
+
|
|
385
|
+
if not command:
|
|
386
|
+
return ToolResult(
|
|
387
|
+
status="error", result=None, error="No DAX command provided (command/template missing)"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
js = await client.arun(command, timeout=kwargs.get("timeout", 30))
|
|
391
|
+
if "error" in js:
|
|
392
|
+
return ToolResult(
|
|
393
|
+
status="error", result=None, error=js["error"]
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
rows = (js or {}).get("results", [{}])[0].get("tables", [{}])[0].get("rows", [])
|
|
397
|
+
md = _json_rows_to_markdown(rows)
|
|
398
|
+
fmt_raw = kwargs.get("output_format")
|
|
399
|
+
fmt = (fmt_raw or "").strip().lower() if fmt_raw else None
|
|
400
|
+
|
|
401
|
+
# ---- Exports
|
|
402
|
+
if not fmt:
|
|
403
|
+
if kwargs.get("export_csv"):
|
|
404
|
+
fmt = "csv"
|
|
405
|
+
elif kwargs.get("export_pandas"):
|
|
406
|
+
fmt = "dataframe"
|
|
407
|
+
|
|
408
|
+
# Normalize a few aliases
|
|
409
|
+
if fmt in ("row", "json"):
|
|
410
|
+
fmt = "rows"
|
|
411
|
+
if fmt in ("pyarrow", "arrow", "pyarrow.table"):
|
|
412
|
+
fmt = "pyarrow.Table"
|
|
413
|
+
|
|
414
|
+
result_payload: Dict[str, Any] = {
|
|
415
|
+
"raw": js,
|
|
416
|
+
"format": fmt or "default",
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
csv_path = None
|
|
420
|
+
df_obj = None
|
|
421
|
+
parquet_path = None
|
|
422
|
+
|
|
423
|
+
if fmt == "rows" or fmt is None:
|
|
424
|
+
# default behavior keeps both rows and markdown available
|
|
425
|
+
result_payload |= {
|
|
426
|
+
"rows": rows,
|
|
427
|
+
"markdown": md,
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
elif fmt == "markdown":
|
|
431
|
+
result_payload["markdown"] = md
|
|
432
|
+
|
|
433
|
+
elif fmt == "csv":
|
|
434
|
+
csv_text = _rows_to_csv_string(rows)
|
|
435
|
+
path = kwargs.get("export_csv_path") or f"/tmp/powerbi_{client.dataset_id[:8]}_{int(time.time())}.csv"
|
|
436
|
+
with open(path, "w", encoding="utf-8", newline="") as f:
|
|
437
|
+
f.write(csv_text)
|
|
438
|
+
csv_path = path
|
|
439
|
+
result_payload |= {
|
|
440
|
+
"csv_path": csv_path,
|
|
441
|
+
"csv_text": csv_text,
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
elif fmt == "dataframe":
|
|
445
|
+
try:
|
|
446
|
+
df_obj = _rows_to_dataframe(rows)
|
|
447
|
+
except Exception as exc:
|
|
448
|
+
return ToolResult(status="error", result=None, error=str(exc))
|
|
449
|
+
result_payload["dataframe"] = df_obj # note: not JSON-serializable
|
|
450
|
+
|
|
451
|
+
elif fmt == "parquet":
|
|
452
|
+
path = kwargs.get("parquet_path") or f"/tmp/powerbi_{client.dataset_id[:8]}_{int(time.time())}.parquet"
|
|
453
|
+
try:
|
|
454
|
+
parquet_path = _write_parquet(rows, path)
|
|
455
|
+
except Exception as exc:
|
|
456
|
+
return ToolResult(status="error", result=None, error=str(exc))
|
|
457
|
+
result_payload["parquet_path"] = parquet_path
|
|
458
|
+
|
|
459
|
+
elif fmt == "pyarrow.Table":
|
|
460
|
+
try:
|
|
461
|
+
table = _rows_to_arrow_table(rows)
|
|
462
|
+
except Exception as exc:
|
|
463
|
+
return ToolResult(status="error", result=None, error=str(exc))
|
|
464
|
+
result_payload["pyarrow_table"] = table # note: not JSON-serializable
|
|
465
|
+
|
|
466
|
+
else:
|
|
467
|
+
# Unknown selector → return a helpful error
|
|
468
|
+
return ToolResult(
|
|
469
|
+
status="error",
|
|
470
|
+
result=None,
|
|
471
|
+
error=f"Unsupported output_format='{fmt_raw}'. Use one of: row|rows|json|csv|dataframe|markdown|parquet|pyarrow.Table"
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
"status": "success",
|
|
476
|
+
"result": result_payload
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class PowerBITableInfoArgs(_BasePowerBIToolArgs):
|
|
481
|
+
tables: Optional[Union[str, List[str]]] = Field(
|
|
482
|
+
default=None,
|
|
483
|
+
description="Specific table(s) to preview; defaults to known list if provided"
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class PowerBITableInfoTool(AbstractTool):
|
|
488
|
+
"""
|
|
489
|
+
Tool for previewing table info (sample rows) from a Power BI dataset.
|
|
490
|
+
"""
|
|
491
|
+
name = "powerbi_table_info"
|
|
492
|
+
description = "Preview table info (sample rows) for a Power BI dataset"
|
|
493
|
+
args_schema = PowerBITableInfoArgs
|
|
494
|
+
|
|
495
|
+
async def _execute(self, **kwargs) -> Any:
|
|
496
|
+
cred = kwargs.get("credential", None)
|
|
497
|
+
client = PowerBIDatasetClient(
|
|
498
|
+
dataset_id=kwargs["dataset_id"],
|
|
499
|
+
group_id=kwargs.get("group_id"),
|
|
500
|
+
token=kwargs.get("token"),
|
|
501
|
+
credential=cred,
|
|
502
|
+
impersonated_user_name=kwargs.get("impersonated_user_name"),
|
|
503
|
+
table_names=kwargs.get("table_names") or [],
|
|
504
|
+
sample_rows_in_table_info=kwargs.get("sample_rows_in_table_info", 1),
|
|
505
|
+
max_attempts=kwargs.get("max_attempts", 5),
|
|
506
|
+
base_backoff=kwargs.get("base_backoff", 0.5),
|
|
507
|
+
max_backoff=kwargs.get("max_backoff", 10.0),
|
|
508
|
+
)
|
|
509
|
+
md = await client.aget_table_info(kwargs.get("tables"))
|
|
510
|
+
# Optional export of the preview as CSV/DF by stitching rows from TOPN calls is not included here,
|
|
511
|
+
# since get_table_info returns a joined markdown snapshot of multiple tables. If you want,
|
|
512
|
+
# we can extend this tool to return per-table rows to export individually.
|
|
513
|
+
return {
|
|
514
|
+
"status": "success",
|
|
515
|
+
"result": {
|
|
516
|
+
"markdown": md
|
|
517
|
+
}
|
|
518
|
+
}
|