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,152 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any, Optional, Tuple
|
|
4
|
+
from .formats import get_renderer, get_output_prompt, has_system_prompt
|
|
5
|
+
from ..models.outputs import OutputMode
|
|
6
|
+
from ..template.engine import TemplateEngine
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OutputFormatter:
|
|
10
|
+
"""
|
|
11
|
+
Formatter for AI responses supporting multiple output modes.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, template_engine: Optional[TemplateEngine] = None):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the OutputFormatter.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
template_engine: Optional TemplateEngine instance for template-based rendering.
|
|
20
|
+
If not provided, a new one will be created when needed.
|
|
21
|
+
"""
|
|
22
|
+
self._is_ipython = self._detect_ipython()
|
|
23
|
+
self._is_notebook = self._detect_notebook()
|
|
24
|
+
self._environment = self._detect_environment()
|
|
25
|
+
self._renderers = {}
|
|
26
|
+
self._template_engine = template_engine
|
|
27
|
+
|
|
28
|
+
def _detect_environment(self) -> str:
|
|
29
|
+
if self._is_ipython:
|
|
30
|
+
return "jupyter" if self._is_notebook else "ipython"
|
|
31
|
+
return "terminal"
|
|
32
|
+
|
|
33
|
+
def _detect_ipython(self) -> bool:
|
|
34
|
+
try:
|
|
35
|
+
if "IPython" not in sys.modules:
|
|
36
|
+
return False
|
|
37
|
+
from IPython import get_ipython
|
|
38
|
+
return get_ipython() is not None
|
|
39
|
+
except (ImportError, NameError):
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
def _detect_notebook(self) -> bool:
|
|
43
|
+
try:
|
|
44
|
+
from IPython import get_ipython
|
|
45
|
+
ipython = get_ipython()
|
|
46
|
+
return ipython is not None and "IPKernelApp" in ipython.config
|
|
47
|
+
except Exception:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
def _get_renderer(self, mode: OutputMode):
|
|
51
|
+
"""
|
|
52
|
+
Get or create a renderer instance for the specified output mode.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
mode: OutputMode enum value
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Renderer instance for the given mode
|
|
59
|
+
"""
|
|
60
|
+
if mode not in self._renderers:
|
|
61
|
+
renderer_cls = get_renderer(mode)
|
|
62
|
+
# Special handling for TEMPLATE_REPORT renderer to pass TemplateEngine
|
|
63
|
+
if mode == OutputMode.TEMPLATE_REPORT:
|
|
64
|
+
# Lazy initialize TemplateEngine if not provided
|
|
65
|
+
if self._template_engine is None:
|
|
66
|
+
self._template_engine = TemplateEngine()
|
|
67
|
+
self._renderers[mode] = renderer_cls(
|
|
68
|
+
template_engine=self._template_engine
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
self._renderers[mode] = renderer_cls()
|
|
72
|
+
return self._renderers[mode]
|
|
73
|
+
|
|
74
|
+
def get_system_prompt(self, mode: OutputMode) -> Optional[str]:
|
|
75
|
+
"""
|
|
76
|
+
Get the system prompt for a given output mode.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
mode: OutputMode enum value
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
System prompt string or None if mode has no specific prompt
|
|
83
|
+
"""
|
|
84
|
+
print(f"Getting system prompt for mode: {mode}")
|
|
85
|
+
return get_output_prompt(mode)
|
|
86
|
+
|
|
87
|
+
def has_system_prompt(self, mode: OutputMode) -> bool:
|
|
88
|
+
"""
|
|
89
|
+
Check if an output mode has a registered system prompt.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
mode: OutputMode enum value
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True if mode has a system prompt
|
|
96
|
+
"""
|
|
97
|
+
return has_system_prompt(mode)
|
|
98
|
+
|
|
99
|
+
async def format(
|
|
100
|
+
self,
|
|
101
|
+
mode: OutputMode,
|
|
102
|
+
data: Any,
|
|
103
|
+
**kwargs
|
|
104
|
+
) -> Tuple[str, Optional[str]]:
|
|
105
|
+
"""
|
|
106
|
+
Format output based on mode
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Tuple[str, Optional[str]]: (content, wrapped_content)
|
|
110
|
+
- content: main formatted output
|
|
111
|
+
- wrapped_content: optional wrapped version (e.g., HTML)
|
|
112
|
+
"""
|
|
113
|
+
if mode == OutputMode.DEFAULT:
|
|
114
|
+
return data, None
|
|
115
|
+
|
|
116
|
+
renderer = self._get_renderer(mode)
|
|
117
|
+
render_method = getattr(renderer, "render_async", renderer.render)
|
|
118
|
+
|
|
119
|
+
# Call renderer and get tuple response
|
|
120
|
+
return await render_method(
|
|
121
|
+
data,
|
|
122
|
+
environment=self._environment,
|
|
123
|
+
is_ipython=self._is_ipython,
|
|
124
|
+
is_notebook=self._is_notebook,
|
|
125
|
+
**kwargs,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def add_template(self, name: str, content: str) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Add an in-memory template for use with TEMPLATE_REPORT mode.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
name: Template name (e.g., 'report.html', 'summary.md')
|
|
134
|
+
content: Jinja2 template content
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
formatter = OutputFormatter()
|
|
138
|
+
formatter.add_template('report.html', '<h1>{{ title }}</h1>')
|
|
139
|
+
result = await formatter.format_async(
|
|
140
|
+
OutputMode.TEMPLATE_REPORT,
|
|
141
|
+
{"title": "My Report"},
|
|
142
|
+
template="report.html"
|
|
143
|
+
)
|
|
144
|
+
"""
|
|
145
|
+
# Ensure TemplateEngine is initialized
|
|
146
|
+
if self._template_engine is None:
|
|
147
|
+
self._template_engine = TemplateEngine()
|
|
148
|
+
|
|
149
|
+
# Get or create the TEMPLATE_REPORT renderer to add the template
|
|
150
|
+
renderer = self._get_renderer(OutputMode.TEMPLATE_REPORT)
|
|
151
|
+
if hasattr(renderer, 'add_template'):
|
|
152
|
+
renderer.add_template(name, content)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Optional
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from jinja2 import Environment, FileSystemLoader, Template
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
class TemplateSection(BaseModel):
|
|
7
|
+
"""Defines a fillable section in the template."""
|
|
8
|
+
name: str
|
|
9
|
+
description: str
|
|
10
|
+
content_type: str # 'text', 'table', 'chart', 'image', 'list'
|
|
11
|
+
required: bool = True
|
|
12
|
+
example: Optional[str] = None
|
|
13
|
+
|
|
14
|
+
class ReportTemplate(BaseModel):
|
|
15
|
+
"""Defines a report template structure."""
|
|
16
|
+
name: str
|
|
17
|
+
description: str
|
|
18
|
+
template_path: Path
|
|
19
|
+
sections: List[TemplateSection]
|
|
20
|
+
css_path: Optional[Path] = None
|
|
21
|
+
|
|
22
|
+
def get_section_prompts(self) -> Dict[str, str]:
|
|
23
|
+
"""Generate prompts for LLM to fill each section."""
|
|
24
|
+
prompts = {}
|
|
25
|
+
for section in self.sections:
|
|
26
|
+
prompt = (
|
|
27
|
+
f"Generate {section.content_type} content for the "
|
|
28
|
+
f"'{section.name}' section.\n"
|
|
29
|
+
f"Description: {section.description}\n"
|
|
30
|
+
)
|
|
31
|
+
if section.example:
|
|
32
|
+
prompt += f"Example format:\n{section.example}\n"
|
|
33
|
+
prompts[section.name] = prompt
|
|
34
|
+
return prompts
|
|
35
|
+
|
|
36
|
+
class TemplateRegistry:
|
|
37
|
+
"""Registry of available templates."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, templates_dir: Path):
|
|
40
|
+
self.templates_dir = templates_dir
|
|
41
|
+
self.env = Environment(
|
|
42
|
+
loader=FileSystemLoader(templates_dir)
|
|
43
|
+
)
|
|
44
|
+
self._templates: Dict[str, ReportTemplate] = {}
|
|
45
|
+
|
|
46
|
+
def register(self, template: ReportTemplate):
|
|
47
|
+
"""Register a template."""
|
|
48
|
+
self._templates[template.name] = template
|
|
49
|
+
|
|
50
|
+
def get(self, name: str) -> ReportTemplate:
|
|
51
|
+
"""Get a template by name."""
|
|
52
|
+
return self._templates.get(name)
|
|
53
|
+
|
|
54
|
+
def list(self) -> List[str]:
|
|
55
|
+
"""List available templates."""
|
|
56
|
+
return list(self._templates.keys())
|
|
57
|
+
|
|
58
|
+
# Built-in templates
|
|
59
|
+
EXECUTIVE_SUMMARY_TEMPLATE = ReportTemplate(
|
|
60
|
+
name="executive_summary",
|
|
61
|
+
description="Executive summary report with key findings",
|
|
62
|
+
template_path=Path("templates/executive_summary.html"),
|
|
63
|
+
sections=[
|
|
64
|
+
TemplateSection(
|
|
65
|
+
name="title",
|
|
66
|
+
description="Report title",
|
|
67
|
+
content_type="text"
|
|
68
|
+
),
|
|
69
|
+
TemplateSection(
|
|
70
|
+
name="executive_summary",
|
|
71
|
+
description="High-level overview (2-3 paragraphs)",
|
|
72
|
+
content_type="text"
|
|
73
|
+
),
|
|
74
|
+
TemplateSection(
|
|
75
|
+
name="key_findings",
|
|
76
|
+
description="Bullet points of main findings",
|
|
77
|
+
content_type="list"
|
|
78
|
+
),
|
|
79
|
+
TemplateSection(
|
|
80
|
+
name="data_table",
|
|
81
|
+
description="Supporting data in table format",
|
|
82
|
+
content_type="table"
|
|
83
|
+
),
|
|
84
|
+
TemplateSection(
|
|
85
|
+
name="visualization",
|
|
86
|
+
description="Chart showing trends",
|
|
87
|
+
content_type="chart"
|
|
88
|
+
),
|
|
89
|
+
TemplateSection(
|
|
90
|
+
name="recommendations",
|
|
91
|
+
description="Actionable recommendations",
|
|
92
|
+
content_type="list"
|
|
93
|
+
)
|
|
94
|
+
]
|
|
95
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, Optional, List, Tuple, Union
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import io
|
|
5
|
+
from PIL import (
|
|
6
|
+
Image,
|
|
7
|
+
ImageDraw,
|
|
8
|
+
ImageFont,
|
|
9
|
+
ImageEnhance,
|
|
10
|
+
ImageOps
|
|
11
|
+
)
|
|
12
|
+
from navconfig.logging import logging
|
|
13
|
+
from datamodel.parsers.json import JSONContent # pylint: disable=E0611
|
|
14
|
+
from ..clients.factory import SUPPORTED_CLIENTS
|
|
15
|
+
from ..clients.google import GoogleGenAIClient, GoogleModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logging.getLogger('pytesseract').setLevel(logging.WARNING)
|
|
19
|
+
|
|
20
|
+
class AbstractPipeline(ABC):
|
|
21
|
+
"""Abstract base class for all pipelines."""
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
llm: Any = None,
|
|
25
|
+
llm_provider: str = "google",
|
|
26
|
+
llm_model: Optional[str] = None,
|
|
27
|
+
**kwargs: Any
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Initialize the 3-step pipeline
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
llm_provider: LLM provider for identification
|
|
34
|
+
llm_model: Specific LLM model
|
|
35
|
+
api_key: API key
|
|
36
|
+
detection_model: Object detection model to use
|
|
37
|
+
"""
|
|
38
|
+
self.llm = llm
|
|
39
|
+
self.llm_provider = None
|
|
40
|
+
self.logger = logging.getLogger(f'parrot.pipelines.{self.__class__.__name__}')
|
|
41
|
+
self._json = JSONContent()
|
|
42
|
+
if not llm:
|
|
43
|
+
self.llm_provider = llm_provider.lower()
|
|
44
|
+
self.llm = self._get_llm(
|
|
45
|
+
llm_provider,
|
|
46
|
+
llm_model,
|
|
47
|
+
**kwargs
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
self.llm_provider = llm.client_name.lower()
|
|
51
|
+
# Ensure a Google Client for multi-modal capabilities:
|
|
52
|
+
self.roi_client = GoogleGenAIClient(
|
|
53
|
+
model=GoogleModel.GEMINI_2_5_FLASH,
|
|
54
|
+
temperature=0.0,
|
|
55
|
+
max_retries=2,
|
|
56
|
+
timeout=20
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def _get_llm(
|
|
60
|
+
self,
|
|
61
|
+
provider: str,
|
|
62
|
+
model: Optional[str] = None,
|
|
63
|
+
**kwargs: Any
|
|
64
|
+
) -> Any:
|
|
65
|
+
"""
|
|
66
|
+
Get the LLM client based on provider and model
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
provider: LLM provider name
|
|
70
|
+
model: Specific model to use
|
|
71
|
+
**kwargs: Additional parameters for client initialization
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Initialized LLM client
|
|
75
|
+
"""
|
|
76
|
+
if provider not in SUPPORTED_CLIENTS:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"Unsupported LLM provider: {provider}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
client_class = SUPPORTED_CLIENTS[provider]
|
|
82
|
+
client = client_class(model=model, **kwargs)
|
|
83
|
+
self.llm_provider = client.client_name.lower()
|
|
84
|
+
return client
|
|
85
|
+
|
|
86
|
+
def open_image(self, image_path: Union[Path, Image.Image]) -> Image.Image:
|
|
87
|
+
"""Open an image from a file path."""
|
|
88
|
+
try:
|
|
89
|
+
if isinstance(image_path, (str, Path)):
|
|
90
|
+
img = Image.open(str(image_path))
|
|
91
|
+
else:
|
|
92
|
+
img = image_path
|
|
93
|
+
if img.mode != "RGB":
|
|
94
|
+
img = img.convert("RGB")
|
|
95
|
+
img = self._enhance_image(img)
|
|
96
|
+
self.logger.debug(
|
|
97
|
+
f"Opened image {image_path} with size {img.size} and mode {img.mode}"
|
|
98
|
+
)
|
|
99
|
+
return img
|
|
100
|
+
except Exception as e:
|
|
101
|
+
self.logger.error(f"Error opening image {image_path}: {e}")
|
|
102
|
+
raise
|
|
103
|
+
|
|
104
|
+
def _clamp(self, w, h, x1, y1, x2, y2):
|
|
105
|
+
x1, x2 = int(max(0, min(w - 1, min(x1, x2)))), int(max(0, min(w - 1, max(x1, x2))))
|
|
106
|
+
y1, y2 = int(max(0, min(h - 1, min(y1, y2)))), int(max(0, min(h - 1, max(y1, y2))))
|
|
107
|
+
return x1, y1, x2, y2
|
|
108
|
+
|
|
109
|
+
def _save_detections(
|
|
110
|
+
self,
|
|
111
|
+
pil_image: Image.Image,
|
|
112
|
+
poster_bounds: Tuple[int, int, int, int],
|
|
113
|
+
detections: List[dict],
|
|
114
|
+
save_path: str,
|
|
115
|
+
poster_label: str = 'poster_panel',
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Save debug image showing poster detection results"""
|
|
118
|
+
try:
|
|
119
|
+
debug_img = pil_image.copy()
|
|
120
|
+
draw = ImageDraw.Draw(debug_img)
|
|
121
|
+
# draw the detections:
|
|
122
|
+
for det in detections:
|
|
123
|
+
label = det.label
|
|
124
|
+
conf = float(det.confidence or 0.0)
|
|
125
|
+
bbox = det.bbox
|
|
126
|
+
x1 = int(bbox.x1 * debug_img.width)
|
|
127
|
+
y1 = int(bbox.y1 * debug_img.height)
|
|
128
|
+
x2 = int(bbox.x2 * debug_img.width)
|
|
129
|
+
y2 = int(bbox.y2 * debug_img.height)
|
|
130
|
+
x1, y1, x2, y2 = self._clamp(debug_img.width, debug_img.height, x1, y1, x2, y2)
|
|
131
|
+
|
|
132
|
+
color = (255, 165, 0) if label == poster_label else (0, 255, 255)
|
|
133
|
+
draw.rectangle(
|
|
134
|
+
[(x1, y1), (x2, y2)],
|
|
135
|
+
outline=color,
|
|
136
|
+
width=3
|
|
137
|
+
)
|
|
138
|
+
draw.text(
|
|
139
|
+
(x1, y1 - 20),
|
|
140
|
+
f"{label} {conf:.2f}",
|
|
141
|
+
fill=color
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Draw final poster bounds in bright green
|
|
145
|
+
x1, y1, x2, y2 = poster_bounds
|
|
146
|
+
draw.rectangle(
|
|
147
|
+
[(x1, y1), (x2, y2)],
|
|
148
|
+
outline=(0, 255, 0),
|
|
149
|
+
width=4
|
|
150
|
+
)
|
|
151
|
+
draw.text(
|
|
152
|
+
(x1, y1 - 45),
|
|
153
|
+
f"POSTER: {x2-x1}x{y2-y1}",
|
|
154
|
+
fill=(0, 255, 0)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Save debug image
|
|
158
|
+
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
debug_img.save(save_path, quality=95)
|
|
160
|
+
self.logger.debug(
|
|
161
|
+
f"Saved poster debug image to {save_path}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
self.logger.error(f"Failed to save debug image: {e}")
|
|
166
|
+
|
|
167
|
+
def _enhance_image(
|
|
168
|
+
self,
|
|
169
|
+
pil_img: "Image.Image",
|
|
170
|
+
brightness: float = 1.10,
|
|
171
|
+
contrast: float = 1.20
|
|
172
|
+
) -> "Image.Image":
|
|
173
|
+
"""
|
|
174
|
+
Enhances a PIL image by adjusting brightness and contrast.
|
|
175
|
+
This generic utility can be used by any pipeline subclass.
|
|
176
|
+
"""
|
|
177
|
+
self.logger.debug("Applying generic image enhancement...")
|
|
178
|
+
# Brightness/contrast + autocontrast; tweak if needed
|
|
179
|
+
pil = ImageEnhance.Brightness(pil_img).enhance(brightness)
|
|
180
|
+
pil = ImageEnhance.Contrast(pil).enhance(contrast)
|
|
181
|
+
pil = ImageOps.autocontrast(pil)
|
|
182
|
+
return pil
|
|
183
|
+
|
|
184
|
+
def _downscale_image(self, img: Image.Image, max_side=1024, quality=82) -> Image.Image:
|
|
185
|
+
if img.mode != "RGB":
|
|
186
|
+
img = img.convert("RGB")
|
|
187
|
+
w, h = img.size
|
|
188
|
+
s = max(w, h)
|
|
189
|
+
if s > max_side:
|
|
190
|
+
scale = max_side / float(s)
|
|
191
|
+
img = img.resize((int(w * scale), int(h * scale)), Image.Resampling.LANCZOS)
|
|
192
|
+
# (Optional) strip metadata by re-encoding
|
|
193
|
+
bio = io.BytesIO()
|
|
194
|
+
img.save(bio, format="JPEG", quality=quality, optimize=True)
|
|
195
|
+
bio.seek(0)
|
|
196
|
+
return Image.open(bio)
|
|
197
|
+
|
|
198
|
+
@abstractmethod
|
|
199
|
+
async def run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
|
200
|
+
"""
|
|
201
|
+
Run the pipeline with the provided arguments
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
*args: Positional arguments for the pipeline
|
|
205
|
+
**kwargs: Keyword arguments for the pipeline
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Dictionary with results of the pipeline execution
|
|
209
|
+
"""
|
|
210
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, Optional, List, Tuple
|
|
3
|
+
import cv2
|
|
4
|
+
import torch
|
|
5
|
+
from PIL import Image
|
|
6
|
+
from transformers import CLIPProcessor, CLIPModel
|
|
7
|
+
from navconfig.logging import logging
|
|
8
|
+
try:
|
|
9
|
+
from ultralytics import YOLO # yolo12m works with this API
|
|
10
|
+
except Exception:
|
|
11
|
+
YOLO = None
|
|
12
|
+
from ..models.detections import DetectionBox
|
|
13
|
+
|
|
14
|
+
class AbstractDetector(ABC):
|
|
15
|
+
"""Abstract base class for all detectors."""
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
yolo_model: str = "yolo12l.pt",
|
|
19
|
+
conf: float = 0.15,
|
|
20
|
+
iou: float = 0.5,
|
|
21
|
+
device: str = "cuda" if torch.cuda.is_available() else "cpu",
|
|
22
|
+
**kwargs
|
|
23
|
+
):
|
|
24
|
+
if isinstance(yolo_model, str):
|
|
25
|
+
assert YOLO is not None, "ultralytics is required"
|
|
26
|
+
self.yolo = YOLO(yolo_model)
|
|
27
|
+
else:
|
|
28
|
+
self.yolo = yolo_model
|
|
29
|
+
self.conf = conf
|
|
30
|
+
self.iou = iou
|
|
31
|
+
self.device = device
|
|
32
|
+
self.logger = logging.getLogger(
|
|
33
|
+
f'parrot.pipelines.{self.__class__.__name__}'
|
|
34
|
+
)
|
|
35
|
+
self._define_clip()
|
|
36
|
+
|
|
37
|
+
# ----------------------- enhancement & CLIP -------------------------------
|
|
38
|
+
def _embed_image(self, path: Optional[str]):
|
|
39
|
+
if not path:
|
|
40
|
+
return None
|
|
41
|
+
im = Image.open(path).convert("RGB")
|
|
42
|
+
with torch.no_grad():
|
|
43
|
+
inputs = self.proc(images=im, return_tensors="pt").to(self.device)
|
|
44
|
+
feat = self.clip.get_image_features(**inputs)
|
|
45
|
+
feat = feat / feat.norm(dim=-1, keepdim=True)
|
|
46
|
+
return feat
|
|
47
|
+
|
|
48
|
+
def _define_clip(self):
|
|
49
|
+
self.clip = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(self.device)
|
|
50
|
+
self.proc = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
|
51
|
+
|
|
52
|
+
def _iou(self, a: DetectionBox, b: DetectionBox) -> float:
|
|
53
|
+
ix1, iy1 = max(a.x1, b.x1), max(a.y1, b.y1)
|
|
54
|
+
ix2, iy2 = min(a.x2, b.x2), min(a.y2, b.y2)
|
|
55
|
+
iw, ih = max(0, ix2 - ix1), max(0, iy2 - iy1)
|
|
56
|
+
inter = iw * ih
|
|
57
|
+
if inter <= 0:
|
|
58
|
+
return 0.0
|
|
59
|
+
ua = a.area + b.area - inter
|
|
60
|
+
return inter / float(max(1, ua))
|
|
61
|
+
|
|
62
|
+
def _normalized_y(self, b, h: int) -> Tuple[float, float, float]:
|
|
63
|
+
y1n, y2n = b.y1 / h, b.y2 / h
|
|
64
|
+
yc = 0.5 * (y1n + y2n)
|
|
65
|
+
return y1n, yc, y2n
|
|
66
|
+
|
|
67
|
+
def _iou_box_tuple(self, d: DetectionBox, box: tuple[int,int,int,int]) -> float:
|
|
68
|
+
ax1, ay1, ax2, ay2 = box
|
|
69
|
+
ix1, iy1 = max(d.x1, ax1), max(d.y1, ay1)
|
|
70
|
+
ix2, iy2 = min(d.x2, ax2), min(d.y2, ay2)
|
|
71
|
+
if ix2 <= ix1 or iy2 <= iy1:
|
|
72
|
+
return 0.0
|
|
73
|
+
inter = (ix2 - ix1) * (iy2 - iy1)
|
|
74
|
+
return inter / float(d.area + (ax2-ax1)*(ay2-ay1) - inter + 1e-6)
|
|
75
|
+
|
|
76
|
+
def _coerce_bbox(self, bbox, W, H):
|
|
77
|
+
if bbox is None:
|
|
78
|
+
return None
|
|
79
|
+
if isinstance(bbox, (list, tuple)) and len(bbox) == 4:
|
|
80
|
+
x1, y1, x2, y2 = map(float, bbox)
|
|
81
|
+
elif isinstance(bbox, dict):
|
|
82
|
+
if {"x1","y1","x2","y2"} <= bbox.keys():
|
|
83
|
+
x1, y1, x2, y2 = map(float, (bbox["x1"], bbox["y1"], bbox["x2"], bbox["y2"]))
|
|
84
|
+
elif {"x","y","w","h"} <= bbox.keys():
|
|
85
|
+
x1, y1 = float(bbox["x"]), float(bbox["y"])
|
|
86
|
+
x2, y2 = x1 + float(bbox["w"]), y1 + float(bbox["h"])
|
|
87
|
+
else:
|
|
88
|
+
return None
|
|
89
|
+
else:
|
|
90
|
+
return None
|
|
91
|
+
def to_px(v, M):
|
|
92
|
+
return int(round(v * M)) if v <= 1.5 else int(round(v))
|
|
93
|
+
x1, y1, x2, y2 = to_px(x1, W), to_px(y1, H), to_px(x2, W), to_px(y2, H)
|
|
94
|
+
if x2 < x1:
|
|
95
|
+
x1, x2 = x2, x1
|
|
96
|
+
if y2 < y1:
|
|
97
|
+
y1, y2 = y2, y1
|
|
98
|
+
x1 = max(0, min(W-1, x1))
|
|
99
|
+
x2 = max(0, min(W-1, x2))
|
|
100
|
+
y1 = max(0, min(H-1, y1))
|
|
101
|
+
y2 = max(0, min(H-1, y2))
|
|
102
|
+
if (x2-x1) < 4 or (y2-y1) < 4:
|
|
103
|
+
return None
|
|
104
|
+
return (x1, y1, x2, y2)
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
async def detect(
|
|
108
|
+
self,
|
|
109
|
+
image: Any,
|
|
110
|
+
image_array: Any,
|
|
111
|
+
**kwargs: Any
|
|
112
|
+
) -> Tuple[Any, List[Any]]:
|
|
113
|
+
"""
|
|
114
|
+
Abstract method for detecting objects in an image.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
image: The input image.
|
|
118
|
+
image_array: The input image as a numpy array.
|
|
119
|
+
**kwargs: Additional keyword arguments.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
A tuple containing the processed image and a list of detections.
|
|
123
|
+
"""
|
|
124
|
+
pass
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import Optional, Dict, List, Any, Union
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from PIL import Image
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from ..models.detections import (
|
|
7
|
+
PlanogramDescription,
|
|
8
|
+
PlanogramDescriptionFactory,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
class EndcapGeometry(BaseModel):
|
|
12
|
+
"""Configurable endcap geometry parameters"""
|
|
13
|
+
aspect_ratio: float = Field(default=1.35, description="Endcap width/height ratio")
|
|
14
|
+
left_margin_ratio: float = Field(default=0.01, description="Left margin as ratio of panel width")
|
|
15
|
+
right_margin_ratio: float = Field(default=0.03, description="Right margin as ratio of panel width")
|
|
16
|
+
top_margin_ratio: float = Field(default=0.02, description="Top margin as ratio of panel height")
|
|
17
|
+
|
|
18
|
+
# NEW: Additional margin controls for better shelf separation
|
|
19
|
+
bottom_margin_ratio: float = Field(default=0.05, description="Bottom margin as ratio of panel height")
|
|
20
|
+
inter_shelf_padding: float = Field(default=0.02, description="Padding between shelves as ratio of ROI height")
|
|
21
|
+
|
|
22
|
+
# ROI detection specific margins
|
|
23
|
+
width_margin_percent: float = Field(default=0.25, description="Panel width margin percentage")
|
|
24
|
+
height_margin_percent: float = Field(default=0.30, description="Panel height margin percentage")
|
|
25
|
+
top_margin_percent: float = Field(default=0.05, description="Panel top margin percentage")
|
|
26
|
+
side_margin_percent: float = Field(default=0.05, description="Panel side margin percentage")
|
|
27
|
+
|
|
28
|
+
class PlanogramConfig(BaseModel):
|
|
29
|
+
"""
|
|
30
|
+
Complete configuration for planogram analysis pipeline.
|
|
31
|
+
Contains planogram description, prompts, and reference images.
|
|
32
|
+
"""
|
|
33
|
+
planogram_id: Optional[int] = Field(
|
|
34
|
+
default=None,
|
|
35
|
+
description="Optional unique identifier for the planogram (if any)"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
config_name: str = Field(
|
|
39
|
+
default="default_planogram_config",
|
|
40
|
+
description="Name of the planogram configuration"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Core planogram configuration
|
|
44
|
+
planogram_config: Dict[str, Any] = Field(
|
|
45
|
+
description="Planogram configuration dictionary (gets converted to PlanogramDescription)"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# ROI Detection prompt
|
|
49
|
+
roi_detection_prompt: str = Field(
|
|
50
|
+
description="Prompt for ROI detection phase (used by _find_poster method)"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Object identification prompt
|
|
54
|
+
object_identification_prompt: str = Field(
|
|
55
|
+
description="Prompt for Phase 2 object identification (used by _identify_objects method)"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Reference images
|
|
59
|
+
reference_images: Dict[str, Union[str, Path, Image.Image]] = Field(
|
|
60
|
+
default_factory=dict,
|
|
61
|
+
description="Reference images for object identification"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Optional: Additional detection parameters
|
|
65
|
+
confidence_threshold: float = Field(
|
|
66
|
+
default=0.25,
|
|
67
|
+
description="YOLO detection confidence threshold"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
detection_model: str = Field(
|
|
71
|
+
default="yolo11l.pt",
|
|
72
|
+
description="YOLO model to use for detection"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
endcap_geometry: EndcapGeometry = Field(
|
|
76
|
+
default_factory=EndcapGeometry,
|
|
77
|
+
description="Endcap geometry and margin configuration"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
class Config:
|
|
81
|
+
"""Pydantic configuration."""
|
|
82
|
+
arbitrary_types_allowed = True
|
|
83
|
+
|
|
84
|
+
def get_planogram_description(self) -> PlanogramDescription:
|
|
85
|
+
"""
|
|
86
|
+
Load and validate a planogram description from a configuration dictionary.
|
|
87
|
+
Uses PlanogramDescriptionFactory to parse and validate the config.
|
|
88
|
+
"""
|
|
89
|
+
factory = PlanogramDescriptionFactory()
|
|
90
|
+
return factory.create_planogram_description(self.planogram_config)
|