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/handlers/chat.py
ADDED
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
import re
|
|
4
|
+
import uuid
|
|
5
|
+
import asyncio
|
|
6
|
+
import importlib
|
|
7
|
+
import inspect
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from aiohttp import web
|
|
10
|
+
from asyncdb.exceptions.exceptions import NoDataFound # pylint: disable=E0611 # noqa
|
|
11
|
+
from datamodel.exceptions import ValidationError # pylint: disable=E0611 # noqa
|
|
12
|
+
from datamodel.parsers.json import json_encoder # noqa pylint: disable=E0611
|
|
13
|
+
from navigator_auth.decorators import (
|
|
14
|
+
is_authenticated,
|
|
15
|
+
user_session,
|
|
16
|
+
allowed_organizations
|
|
17
|
+
)
|
|
18
|
+
from navigator.views import BaseView
|
|
19
|
+
from ..bots.abstract import AbstractBot
|
|
20
|
+
from ..loaders.abstract import AbstractLoader
|
|
21
|
+
from ..loaders.factory import AVAILABLE_LOADERS
|
|
22
|
+
from ..loaders.markdown import MarkdownLoader
|
|
23
|
+
from .models import BotModel
|
|
24
|
+
from ..models.responses import AIMessage
|
|
25
|
+
from ..outputs import OutputFormatter, OutputMode
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@is_authenticated()
|
|
30
|
+
@user_session()
|
|
31
|
+
class ChatHandler(BaseView):
|
|
32
|
+
"""
|
|
33
|
+
ChatHandler.
|
|
34
|
+
description: Chat Handler for Parrot Application.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
async def get(self, **kwargs):
|
|
38
|
+
"""
|
|
39
|
+
Obtener información de un chatbot
|
|
40
|
+
---
|
|
41
|
+
tags:
|
|
42
|
+
- chatbots
|
|
43
|
+
summary: Info de un chatbot o bienvenida al servicio
|
|
44
|
+
description: |
|
|
45
|
+
Si no se especifica nombre de chatbot, retorna mensaje de bienvenida.
|
|
46
|
+
Si se especifica nombre, retorna configuración y detalles del chatbot.
|
|
47
|
+
operationId: getChatbotInfo
|
|
48
|
+
parameters:
|
|
49
|
+
- $ref: "#/components/parameters/ChatbotName"
|
|
50
|
+
responses:
|
|
51
|
+
"200":
|
|
52
|
+
description: Información del chatbot o mensaje de bienvenida
|
|
53
|
+
content:
|
|
54
|
+
application/json:
|
|
55
|
+
schema:
|
|
56
|
+
oneOf:
|
|
57
|
+
- type: object
|
|
58
|
+
properties:
|
|
59
|
+
message:
|
|
60
|
+
type: string
|
|
61
|
+
example: "Welcome to Parrot Chatbot Service."
|
|
62
|
+
- $ref: "#/components/schemas/ChatbotInfo"
|
|
63
|
+
examples:
|
|
64
|
+
welcome:
|
|
65
|
+
summary: Sin chatbot especificado
|
|
66
|
+
value:
|
|
67
|
+
message: "Welcome to Parrot Chatbot Service."
|
|
68
|
+
chatbot_info:
|
|
69
|
+
summary: Con chatbot especificado
|
|
70
|
+
value:
|
|
71
|
+
chatbot: "nextstop"
|
|
72
|
+
description: "Travel planning assistant"
|
|
73
|
+
role: "You are a helpful travel agent"
|
|
74
|
+
embedding_model: "text-embedding-3-small"
|
|
75
|
+
llm: "ChatOpenAI(model='gpt-4', temperature=0.7)"
|
|
76
|
+
temperature: 0.7
|
|
77
|
+
config_file: "/config/bots/nextstop.yaml"
|
|
78
|
+
"404":
|
|
79
|
+
$ref: "#/components/responses/NotFound"
|
|
80
|
+
"401":
|
|
81
|
+
$ref: "#/components/responses/Unauthorized"
|
|
82
|
+
security:
|
|
83
|
+
- sessionAuth: []
|
|
84
|
+
"""
|
|
85
|
+
if (name := self.request.match_info.get('chatbot_name', None)):
|
|
86
|
+
# retrieve chatbof information:
|
|
87
|
+
manager = self.request.app['bot_manager']
|
|
88
|
+
chatbot = await manager.get_bot(name)
|
|
89
|
+
if not chatbot:
|
|
90
|
+
return self.error(
|
|
91
|
+
f"Chatbot {name} not found.",
|
|
92
|
+
status=404
|
|
93
|
+
)
|
|
94
|
+
config_file = getattr(chatbot, 'config_file', None)
|
|
95
|
+
return self.json_response({
|
|
96
|
+
"chatbot": chatbot.name,
|
|
97
|
+
"description": chatbot.description,
|
|
98
|
+
"role": chatbot.role,
|
|
99
|
+
"embedding_model": chatbot.embedding_model,
|
|
100
|
+
"llm": f"{chatbot.llm!r}",
|
|
101
|
+
"temperature": chatbot.llm.temperature,
|
|
102
|
+
"config_file": config_file
|
|
103
|
+
})
|
|
104
|
+
else:
|
|
105
|
+
return self.json_response({
|
|
106
|
+
"message": "Welcome to Parrot Chatbot Service."
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
def _check_methods(self, bot: AbstractBot, method_name: str):
|
|
110
|
+
"""Check if the method exists in the bot and is callable."""
|
|
111
|
+
forbidden_methods = {
|
|
112
|
+
'__init__', '__del__', '__getattribute__', '__setattr__',
|
|
113
|
+
'configure', '_setup_database_tools', 'save', 'delete',
|
|
114
|
+
'update', 'insert', '__dict__', '__class__', 'retrieval',
|
|
115
|
+
'_define_prompt', 'configure_llm', 'configure_store', 'default_tools'
|
|
116
|
+
}
|
|
117
|
+
if not method_name:
|
|
118
|
+
return None
|
|
119
|
+
if method_name.startswith('_') or method_name in forbidden_methods:
|
|
120
|
+
raise AttributeError(
|
|
121
|
+
f"Method {method_name} error, not found or forbidden."
|
|
122
|
+
)
|
|
123
|
+
if not hasattr(bot, method_name):
|
|
124
|
+
raise AttributeError(
|
|
125
|
+
f"Method {method_name} error, not found or forbidden."
|
|
126
|
+
)
|
|
127
|
+
method = getattr(bot, method_name)
|
|
128
|
+
if not callable(method):
|
|
129
|
+
raise TypeError(
|
|
130
|
+
f"Attribute {method_name} is not callable in bot {bot.name}."
|
|
131
|
+
)
|
|
132
|
+
return method
|
|
133
|
+
|
|
134
|
+
async def post(self, *args, **kwargs):
|
|
135
|
+
"""
|
|
136
|
+
Interactuar con un chatbot
|
|
137
|
+
---
|
|
138
|
+
tags:
|
|
139
|
+
- chatbots
|
|
140
|
+
summary: Enviar mensaje a un chatbot
|
|
141
|
+
description: |
|
|
142
|
+
Endpoint principal para interactuar con chatbots. Soporta:
|
|
143
|
+
|
|
144
|
+
- Conversaciones con contexto (RAG)
|
|
145
|
+
- Búsqueda en vector store (similarity, MMR, ensemble)
|
|
146
|
+
- Override de LLM y modelo
|
|
147
|
+
- Upload de archivos (multipart/form-data)
|
|
148
|
+
- Invocación de métodos personalizados del bot
|
|
149
|
+
|
|
150
|
+
## Modos de uso
|
|
151
|
+
|
|
152
|
+
### 1. Chat básico
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"query": "¿Cuál es la capital de Francia?"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 2. Chat con configuración personalizada
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"query": "Explica el concepto de RAG",
|
|
163
|
+
"llm": "openai",
|
|
164
|
+
"model": "gpt-4-turbo",
|
|
165
|
+
"temperature": 0.3,
|
|
166
|
+
"search_type": "mmr",
|
|
167
|
+
"return_sources": true
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 3. Invocar método personalizado
|
|
172
|
+
```
|
|
173
|
+
POST /api/v1/chat/{chatbot_name}/summarize
|
|
174
|
+
{
|
|
175
|
+
"text": "Long text to summarize...",
|
|
176
|
+
"max_length": 100
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 4. Upload de archivos
|
|
181
|
+
```
|
|
182
|
+
Content-Type: multipart/form-data
|
|
183
|
+
|
|
184
|
+
query: "Analiza este documento"
|
|
185
|
+
file: [documento.pdf]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
operationId: chatWithBot
|
|
189
|
+
parameters:
|
|
190
|
+
- $ref: "#/components/parameters/ChatbotName"
|
|
191
|
+
- $ref: "#/components/parameters/MethodName"
|
|
192
|
+
requestBody:
|
|
193
|
+
required: true
|
|
194
|
+
description: Mensaje y configuración de la conversación
|
|
195
|
+
content:
|
|
196
|
+
application/json:
|
|
197
|
+
schema:
|
|
198
|
+
$ref: "#/components/schemas/ChatRequest"
|
|
199
|
+
examples:
|
|
200
|
+
basic_chat:
|
|
201
|
+
summary: Chat básico
|
|
202
|
+
value:
|
|
203
|
+
query: "¿Cuál es el mejor momento para visitar Japón?"
|
|
204
|
+
|
|
205
|
+
advanced_chat:
|
|
206
|
+
summary: Chat con opciones avanzadas
|
|
207
|
+
value:
|
|
208
|
+
query: "Explícame sobre inteligencia artificial"
|
|
209
|
+
search_type: "mmr"
|
|
210
|
+
return_sources: true
|
|
211
|
+
return_context: false
|
|
212
|
+
llm: "openai"
|
|
213
|
+
model: "gpt-4-turbo"
|
|
214
|
+
temperature: 0.5
|
|
215
|
+
max_tokens: 1000
|
|
216
|
+
session_id: "session_abc123"
|
|
217
|
+
|
|
218
|
+
contextual_chat:
|
|
219
|
+
summary: Chat con contexto de sesión
|
|
220
|
+
value:
|
|
221
|
+
query: "¿Y cuál es el clima típico?"
|
|
222
|
+
session_id: "session_abc123"
|
|
223
|
+
search_type: "similarity"
|
|
224
|
+
|
|
225
|
+
multipart/form-data:
|
|
226
|
+
schema:
|
|
227
|
+
type: object
|
|
228
|
+
required:
|
|
229
|
+
- query
|
|
230
|
+
properties:
|
|
231
|
+
query:
|
|
232
|
+
type: string
|
|
233
|
+
description: Pregunta o mensaje
|
|
234
|
+
file:
|
|
235
|
+
type: string
|
|
236
|
+
format: binary
|
|
237
|
+
description: Archivo a procesar (PDF, TXT, MD, etc.)
|
|
238
|
+
search_type:
|
|
239
|
+
type: string
|
|
240
|
+
enum: [similarity, mmr, ensemble]
|
|
241
|
+
return_sources:
|
|
242
|
+
type: boolean
|
|
243
|
+
|
|
244
|
+
responses:
|
|
245
|
+
"200":
|
|
246
|
+
description: Respuesta exitosa del chatbot
|
|
247
|
+
content:
|
|
248
|
+
application/json:
|
|
249
|
+
schema:
|
|
250
|
+
$ref: "#/components/schemas/ChatResponse"
|
|
251
|
+
examples:
|
|
252
|
+
simple_response:
|
|
253
|
+
summary: Respuesta simple
|
|
254
|
+
value:
|
|
255
|
+
response: "La mejor época para visitar Japón es durante la primavera (marzo-mayo) para ver los cerezos en flor, o en otoño (septiembre-noviembre) por el clima agradable y los colores del follaje."
|
|
256
|
+
session_id: "session_abc123"
|
|
257
|
+
|
|
258
|
+
response_with_sources:
|
|
259
|
+
summary: Respuesta con fuentes
|
|
260
|
+
value:
|
|
261
|
+
response: "La mejor época para visitar Japón es durante la primavera..."
|
|
262
|
+
sources:
|
|
263
|
+
- content: "Japan's cherry blossom season typically occurs in March..."
|
|
264
|
+
metadata:
|
|
265
|
+
source: "japan_travel_guide.pdf"
|
|
266
|
+
page: 12
|
|
267
|
+
title: "Best Times to Visit Japan"
|
|
268
|
+
score: 0.92
|
|
269
|
+
- content: "Autumn in Japan offers spectacular foliage..."
|
|
270
|
+
metadata:
|
|
271
|
+
source: "seasonal_travel.pdf"
|
|
272
|
+
page: 5
|
|
273
|
+
score: 0.87
|
|
274
|
+
session_id: "session_abc123"
|
|
275
|
+
metadata:
|
|
276
|
+
model: "gpt-4-turbo"
|
|
277
|
+
temperature: 0.7
|
|
278
|
+
tokens_used: 245
|
|
279
|
+
response_time: 1.23
|
|
280
|
+
|
|
281
|
+
"400":
|
|
282
|
+
$ref: "#/components/responses/BadRequest"
|
|
283
|
+
"401":
|
|
284
|
+
$ref: "#/components/responses/Unauthorized"
|
|
285
|
+
"404":
|
|
286
|
+
description: Chatbot no encontrado
|
|
287
|
+
content:
|
|
288
|
+
application/json:
|
|
289
|
+
schema:
|
|
290
|
+
$ref: "#/components/schemas/ErrorResponse"
|
|
291
|
+
example:
|
|
292
|
+
error: "Not Found"
|
|
293
|
+
message: "Chatbot 'travel_bot' not found."
|
|
294
|
+
"422":
|
|
295
|
+
$ref: "#/components/responses/ValidationError"
|
|
296
|
+
"500":
|
|
297
|
+
$ref: "#/components/responses/InternalError"
|
|
298
|
+
|
|
299
|
+
security:
|
|
300
|
+
- sessionAuth: []
|
|
301
|
+
"""
|
|
302
|
+
app = self.request.app
|
|
303
|
+
name = self.request.match_info.get('chatbot_name', None)
|
|
304
|
+
method_name = self.request.match_info.get('method_name', None)
|
|
305
|
+
qs = self.query_parameters(self.request)
|
|
306
|
+
try:
|
|
307
|
+
attachments, data = await self.handle_upload()
|
|
308
|
+
except web.HTTPUnsupportedMediaType:
|
|
309
|
+
# if no file is provided, then is a JSON request:
|
|
310
|
+
data = await self.request.json()
|
|
311
|
+
attachments = {}
|
|
312
|
+
if 'llm' in qs:
|
|
313
|
+
# passing another LLM to the Chatbot:
|
|
314
|
+
llm = data.pop('llm')
|
|
315
|
+
model = data.pop('model', None)
|
|
316
|
+
else:
|
|
317
|
+
llm = None
|
|
318
|
+
model = None
|
|
319
|
+
try:
|
|
320
|
+
manager = app['bot_manager']
|
|
321
|
+
except KeyError:
|
|
322
|
+
return self.json_response(
|
|
323
|
+
{
|
|
324
|
+
"message": "Chatbot Manager is not installed."
|
|
325
|
+
},
|
|
326
|
+
status=404
|
|
327
|
+
)
|
|
328
|
+
try:
|
|
329
|
+
chatbot: AbstractBot = await manager.get_bot(name)
|
|
330
|
+
if not chatbot:
|
|
331
|
+
raise KeyError(
|
|
332
|
+
f"Chatbot {name} not found."
|
|
333
|
+
)
|
|
334
|
+
except (TypeError, KeyError):
|
|
335
|
+
return self.json_response(
|
|
336
|
+
{
|
|
337
|
+
"message": f"Chatbot {name} not found."
|
|
338
|
+
},
|
|
339
|
+
status=404
|
|
340
|
+
)
|
|
341
|
+
# getting the question:
|
|
342
|
+
question = data.pop('query', None)
|
|
343
|
+
search_type = data.pop('search_type', 'similarity')
|
|
344
|
+
return_sources = data.pop('return_sources', True)
|
|
345
|
+
return_context = data.pop('return_context', False)
|
|
346
|
+
try:
|
|
347
|
+
session = self.request.session
|
|
348
|
+
except AttributeError:
|
|
349
|
+
session = None
|
|
350
|
+
if not session:
|
|
351
|
+
return self.json_response(
|
|
352
|
+
{
|
|
353
|
+
"message": "User Session is required to interact with a Chatbot."
|
|
354
|
+
},
|
|
355
|
+
status=400
|
|
356
|
+
)
|
|
357
|
+
stream = data.pop('stream', False)
|
|
358
|
+
if isinstance(stream, str):
|
|
359
|
+
stream = stream.lower() == 'true'
|
|
360
|
+
try:
|
|
361
|
+
async with chatbot.retrieval(self.request, app=app, llm=llm) as bot:
|
|
362
|
+
session_id = session.get('session_id', None)
|
|
363
|
+
user_id = session.get('user_id', None)
|
|
364
|
+
if not session_id:
|
|
365
|
+
session_id = str(uuid.uuid4())
|
|
366
|
+
if method:= self._check_methods(bot, method_name):
|
|
367
|
+
sig = inspect.signature(method)
|
|
368
|
+
method_params = {}
|
|
369
|
+
missing_required = []
|
|
370
|
+
for param_name, param in sig.parameters.items():
|
|
371
|
+
if param_name == 'self' or param_name in 'kwargs':
|
|
372
|
+
continue
|
|
373
|
+
# Handle different parameter types
|
|
374
|
+
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
|
375
|
+
# *args - skip, we don't handle positional args via JSON
|
|
376
|
+
continue
|
|
377
|
+
elif param.kind == inspect.Parameter.VAR_KEYWORD:
|
|
378
|
+
# **kwargs - pass all remaining data that wasn't matched
|
|
379
|
+
continue
|
|
380
|
+
# Regular parameters
|
|
381
|
+
if param_name in data:
|
|
382
|
+
method_params[param_name] = data[param_name]
|
|
383
|
+
elif param.default == inspect.Parameter.empty:
|
|
384
|
+
# Required parameter missing
|
|
385
|
+
missing_required.append(param_name)
|
|
386
|
+
if param_name in attachments:
|
|
387
|
+
files = attachments[param_name]
|
|
388
|
+
if hasattr(param.annotation, '__origin__'):
|
|
389
|
+
# If the parameter is a file upload, handle accordingly
|
|
390
|
+
method_params[param_name] = files
|
|
391
|
+
else:
|
|
392
|
+
method_params[param_name] = files[0] if files else None
|
|
393
|
+
if missing_required:
|
|
394
|
+
return self.json_response(
|
|
395
|
+
{
|
|
396
|
+
"message": f"Required parameters missing: {', '.join(missing_required)}",
|
|
397
|
+
"required_params": [p for p in sig.parameters.keys() if p != 'self']
|
|
398
|
+
},
|
|
399
|
+
status=400
|
|
400
|
+
)
|
|
401
|
+
try:
|
|
402
|
+
method_params = {**method_params, **data}
|
|
403
|
+
response = await method(
|
|
404
|
+
**method_params
|
|
405
|
+
)
|
|
406
|
+
if isinstance(response, web.Response):
|
|
407
|
+
return response
|
|
408
|
+
return web.json_response(
|
|
409
|
+
response, dumps=json_encoder
|
|
410
|
+
)
|
|
411
|
+
except Exception as exc:
|
|
412
|
+
self.error(
|
|
413
|
+
f"Error invoking method {method_name} on chatbot {name}: {exc}",
|
|
414
|
+
exception=exc,
|
|
415
|
+
status=400
|
|
416
|
+
)
|
|
417
|
+
if not question:
|
|
418
|
+
return self.json_response(
|
|
419
|
+
{
|
|
420
|
+
"message": "Query parameter is required to interact with the chatbot."
|
|
421
|
+
},
|
|
422
|
+
status=400
|
|
423
|
+
)
|
|
424
|
+
if stream:
|
|
425
|
+
response = web.StreamResponse(
|
|
426
|
+
status=200,
|
|
427
|
+
headers={
|
|
428
|
+
'Content-Type': 'text/event-stream',
|
|
429
|
+
'Cache-Control': 'no-cache',
|
|
430
|
+
'Connection': 'keep-alive'
|
|
431
|
+
}
|
|
432
|
+
)
|
|
433
|
+
await response.prepare(self.request)
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
async for event in await bot.ask_stream(
|
|
437
|
+
question=question,
|
|
438
|
+
session_id=session_id,
|
|
439
|
+
user_id=user_id,
|
|
440
|
+
search_type=search_type,
|
|
441
|
+
llm=llm,
|
|
442
|
+
model=model,
|
|
443
|
+
return_sources=return_sources,
|
|
444
|
+
return_context=return_context,
|
|
445
|
+
request=self.request,
|
|
446
|
+
**data
|
|
447
|
+
):
|
|
448
|
+
payload = json_encoder(event)
|
|
449
|
+
message = f"data: {payload}\n\n"
|
|
450
|
+
await response.write(message.encode('utf-8'))
|
|
451
|
+
except Exception as exc:
|
|
452
|
+
error_payload = json_encoder({
|
|
453
|
+
"event": "error",
|
|
454
|
+
"data": str(exc)
|
|
455
|
+
})
|
|
456
|
+
await response.write(f"data: {error_payload}\n\n".encode('utf-8'))
|
|
457
|
+
raise
|
|
458
|
+
finally:
|
|
459
|
+
await response.write_eof()
|
|
460
|
+
return response
|
|
461
|
+
|
|
462
|
+
response = await bot.conversation(
|
|
463
|
+
question=question,
|
|
464
|
+
session_id=session_id,
|
|
465
|
+
user_id=user_id,
|
|
466
|
+
search_type=search_type,
|
|
467
|
+
llm=llm,
|
|
468
|
+
model=model,
|
|
469
|
+
return_sources=return_sources,
|
|
470
|
+
return_context=return_context,
|
|
471
|
+
request=self.request,
|
|
472
|
+
**data
|
|
473
|
+
)
|
|
474
|
+
return web.json_response(
|
|
475
|
+
response,
|
|
476
|
+
dumps=json_encoder
|
|
477
|
+
)
|
|
478
|
+
except ValueError as exc:
|
|
479
|
+
return self.error(
|
|
480
|
+
f"{exc}",
|
|
481
|
+
exception=exc,
|
|
482
|
+
status=400
|
|
483
|
+
)
|
|
484
|
+
except web.HTTPException as exc:
|
|
485
|
+
return self.error(
|
|
486
|
+
f"{exc}",
|
|
487
|
+
exception=exc,
|
|
488
|
+
status=400
|
|
489
|
+
)
|
|
490
|
+
except Exception as exc:
|
|
491
|
+
return self.error(
|
|
492
|
+
f"Error invoking chatbot {name}: {exc}",
|
|
493
|
+
exception=exc,
|
|
494
|
+
status=400
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@is_authenticated()
|
|
499
|
+
@user_session()
|
|
500
|
+
class BotHandler(BaseView):
|
|
501
|
+
"""BotHandler.
|
|
502
|
+
description: Bot Handler for Parrot Application.
|
|
503
|
+
Use this handler to interact with a brand new chatbot, consuming a configuration.
|
|
504
|
+
"""
|
|
505
|
+
async def _create_bot(self, name: str, data: dict):
|
|
506
|
+
"""Create a New Bot (passing a configuration).
|
|
507
|
+
"""
|
|
508
|
+
db = self.request.app['database']
|
|
509
|
+
async with await db.acquire() as conn:
|
|
510
|
+
BotModel.Meta.connection = conn
|
|
511
|
+
# check first if chatbot already exists:
|
|
512
|
+
exists = None
|
|
513
|
+
try:
|
|
514
|
+
exists = await BotModel.get(name=name)
|
|
515
|
+
except NoDataFound:
|
|
516
|
+
exists = False
|
|
517
|
+
if exists:
|
|
518
|
+
return self.json_response(
|
|
519
|
+
{
|
|
520
|
+
"message": f"Chatbot {name} already exists with id {exists.chatbot_id}"
|
|
521
|
+
},
|
|
522
|
+
status=202
|
|
523
|
+
)
|
|
524
|
+
try:
|
|
525
|
+
chatbot_model = BotModel(
|
|
526
|
+
name=name,
|
|
527
|
+
**data
|
|
528
|
+
)
|
|
529
|
+
chatbot_model = await chatbot_model.insert()
|
|
530
|
+
return chatbot_model
|
|
531
|
+
except ValidationError:
|
|
532
|
+
raise
|
|
533
|
+
except Exception:
|
|
534
|
+
raise
|
|
535
|
+
|
|
536
|
+
async def put(self):
|
|
537
|
+
"""Create a New Bot (passing a configuration).
|
|
538
|
+
"""
|
|
539
|
+
try:
|
|
540
|
+
manager = self.request.app['bot_manager']
|
|
541
|
+
except KeyError:
|
|
542
|
+
return self.json_response(
|
|
543
|
+
{
|
|
544
|
+
"message": "Chatbot Manager is not installed."
|
|
545
|
+
},
|
|
546
|
+
status=404
|
|
547
|
+
)
|
|
548
|
+
# TODO: Making a Validation of data
|
|
549
|
+
data = await self.request.json()
|
|
550
|
+
name = data.pop('name', None)
|
|
551
|
+
if not name:
|
|
552
|
+
return self.json_response(
|
|
553
|
+
{
|
|
554
|
+
"message": "Name for Bot Creation is required."
|
|
555
|
+
},
|
|
556
|
+
status=400
|
|
557
|
+
)
|
|
558
|
+
try:
|
|
559
|
+
bot = manager.create_bot(name=name, **data)
|
|
560
|
+
except Exception as exc:
|
|
561
|
+
print(exc.__traceback__)
|
|
562
|
+
return self.error(
|
|
563
|
+
response={
|
|
564
|
+
"message": f"Error creating chatbot {name}.",
|
|
565
|
+
"exception": str(exc),
|
|
566
|
+
"stacktrace": str(exc.__traceback__)
|
|
567
|
+
},
|
|
568
|
+
exception=exc,
|
|
569
|
+
status=400
|
|
570
|
+
)
|
|
571
|
+
try:
|
|
572
|
+
# if bot is created:
|
|
573
|
+
await self._create_bot(name=name, data=data)
|
|
574
|
+
except ValidationError as exc:
|
|
575
|
+
return self.error(
|
|
576
|
+
f"Validation Error for {name}: {exc}",
|
|
577
|
+
exception=exc.payload,
|
|
578
|
+
status=400
|
|
579
|
+
)
|
|
580
|
+
except Exception as exc:
|
|
581
|
+
print(exc.__traceback__)
|
|
582
|
+
return self.error(
|
|
583
|
+
response={
|
|
584
|
+
"message": f"Error creating chatbot {name}.",
|
|
585
|
+
"exception": str(exc),
|
|
586
|
+
"stacktrace": str(exc.__traceback__)
|
|
587
|
+
},
|
|
588
|
+
exception=exc,
|
|
589
|
+
status=400
|
|
590
|
+
)
|
|
591
|
+
try:
|
|
592
|
+
# Then Configure the bot:
|
|
593
|
+
await bot.configure(app=self.request.app)
|
|
594
|
+
return self.json_response(
|
|
595
|
+
{
|
|
596
|
+
"message": f"Chatbot {name} created successfully."
|
|
597
|
+
}
|
|
598
|
+
)
|
|
599
|
+
except Exception as exc:
|
|
600
|
+
return self.error(
|
|
601
|
+
f"Error on chatbot configuration: {name}: {exc}",
|
|
602
|
+
exception=exc,
|
|
603
|
+
status=400
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
@is_authenticated()
|
|
608
|
+
@user_session()
|
|
609
|
+
class BotManagement(BaseView):
|
|
610
|
+
"""BotManagement.
|
|
611
|
+
description: Bot Management Handler for Parrot Application.
|
|
612
|
+
Use this handler to list all available chatbots, upload files, and delete chatbots.
|
|
613
|
+
"""
|
|
614
|
+
async def get(self):
|
|
615
|
+
"""List all available chatbots.
|
|
616
|
+
"""
|
|
617
|
+
try:
|
|
618
|
+
manager = self.request.app['bot_manager']
|
|
619
|
+
except KeyError:
|
|
620
|
+
return self.json_response(
|
|
621
|
+
{
|
|
622
|
+
"message": "Chatbot Manager is not installed."
|
|
623
|
+
},
|
|
624
|
+
status=404
|
|
625
|
+
)
|
|
626
|
+
try:
|
|
627
|
+
all_bots = manager.get_bots()
|
|
628
|
+
bots = []
|
|
629
|
+
for bot_name, bot in all_bots.items():
|
|
630
|
+
bots.append({
|
|
631
|
+
"name": bot_name,
|
|
632
|
+
"chatbot_id": bot.chatbot_id,
|
|
633
|
+
"bot_class": str(bot.__class__.__name__),
|
|
634
|
+
"description": bot.description,
|
|
635
|
+
"backstory": bot.backstory,
|
|
636
|
+
"role": bot.role,
|
|
637
|
+
"embedding_model": bot.embedding_model,
|
|
638
|
+
"llm": f"{bot.llm!r}",
|
|
639
|
+
"temperature": bot.llm.temperature,
|
|
640
|
+
"documents": bot.get_vector_store()
|
|
641
|
+
})
|
|
642
|
+
except Exception as exc:
|
|
643
|
+
return self.error(
|
|
644
|
+
response={
|
|
645
|
+
"message": f"Error retrieving chatbots.",
|
|
646
|
+
"exception": str(exc),
|
|
647
|
+
"stacktrace": str(exc.__traceback__)
|
|
648
|
+
},
|
|
649
|
+
exception=exc,
|
|
650
|
+
status=400
|
|
651
|
+
)
|
|
652
|
+
return self.json_response(
|
|
653
|
+
{
|
|
654
|
+
"bots": bots
|
|
655
|
+
}
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
def _get_loader(self, loader_name: Union[str, type]) -> type:
|
|
659
|
+
"""Get the loader class by name."""
|
|
660
|
+
if isinstance(loader_name, type) and issubclass(loader_name, AbstractLoader):
|
|
661
|
+
return loader_name
|
|
662
|
+
if not loader_name:
|
|
663
|
+
return None
|
|
664
|
+
try:
|
|
665
|
+
module = importlib.import_module('parrot.loaders', package=None)
|
|
666
|
+
loader_cls = getattr(module, loader_name, None)
|
|
667
|
+
if not loader_cls:
|
|
668
|
+
raise ValueError(f"Loader not found: {loader_name}")
|
|
669
|
+
if isinstance(loader_cls, type) and issubclass(loader_cls, AbstractLoader):
|
|
670
|
+
return loader_cls
|
|
671
|
+
except Exception:
|
|
672
|
+
pass
|
|
673
|
+
# try submodule guess
|
|
674
|
+
base = loader_name[:-6] if loader_name.endswith("Loader") else loader_name
|
|
675
|
+
candidates = [
|
|
676
|
+
f"parrot.loaders.{base.lower()}",
|
|
677
|
+
f"parrot.loaders.{re.sub(r'(?<!^)(?=[A-Z])','_',base).lower()}",
|
|
678
|
+
]
|
|
679
|
+
for mod_name in candidates:
|
|
680
|
+
try:
|
|
681
|
+
mod = importlib.import_module(mod_name)
|
|
682
|
+
loader_cls = getattr(mod, loader_name, None)
|
|
683
|
+
if isinstance(loader_cls, type) and issubclass(loader_cls, AbstractLoader):
|
|
684
|
+
return loader_cls
|
|
685
|
+
except Exception:
|
|
686
|
+
continue
|
|
687
|
+
return None
|
|
688
|
+
|
|
689
|
+
def _group_attachments_by_loader(self, attachments, default_loader_cls=None):
|
|
690
|
+
"""
|
|
691
|
+
Returns dict[LoaderClass, list[Path]]
|
|
692
|
+
If default_loader_cls is provided, all files go to that loader.
|
|
693
|
+
Otherwise, choose per-file from AVAILABLE_LOADERS by extension, fallback to MarkdownLoader.
|
|
694
|
+
"""
|
|
695
|
+
by_loader = defaultdict(list)
|
|
696
|
+
files = []
|
|
697
|
+
for _, values in attachments.items():
|
|
698
|
+
for a in values or []:
|
|
699
|
+
p = a.get("file_path")
|
|
700
|
+
if p is None:
|
|
701
|
+
continue
|
|
702
|
+
files.append(Path(p))
|
|
703
|
+
|
|
704
|
+
if default_loader_cls:
|
|
705
|
+
if not issubclass(default_loader_cls, AbstractLoader):
|
|
706
|
+
raise TypeError(
|
|
707
|
+
f"Default loader must subclass AbstractLoader, got {default_loader_cls}"
|
|
708
|
+
)
|
|
709
|
+
if files:
|
|
710
|
+
by_loader[default_loader_cls].extend(files)
|
|
711
|
+
return by_loader
|
|
712
|
+
|
|
713
|
+
# No default → pick by extension
|
|
714
|
+
for p in files:
|
|
715
|
+
ext = p.suffix.lower()
|
|
716
|
+
loader_cls = AVAILABLE_LOADERS.get(ext, MarkdownLoader)
|
|
717
|
+
by_loader[loader_cls].append(p)
|
|
718
|
+
|
|
719
|
+
return by_loader
|
|
720
|
+
|
|
721
|
+
async def put(self):
|
|
722
|
+
"""Upload a file to a chatbot.
|
|
723
|
+
"""
|
|
724
|
+
try:
|
|
725
|
+
attachments, form_data = await self.handle_upload()
|
|
726
|
+
except web.HTTPUnsupportedMediaType:
|
|
727
|
+
# if no file is provided, then is a JSON request:
|
|
728
|
+
form_data = await self.request.json()
|
|
729
|
+
attachments = {}
|
|
730
|
+
try:
|
|
731
|
+
manager = self.request.app['bot_manager']
|
|
732
|
+
except KeyError:
|
|
733
|
+
return self.json_response(
|
|
734
|
+
{
|
|
735
|
+
"message": "Chatbot Manager is not installed."
|
|
736
|
+
},
|
|
737
|
+
status=404
|
|
738
|
+
)
|
|
739
|
+
params = self.get_arguments(self.request)
|
|
740
|
+
chatbot_name = params.get('bot', None)
|
|
741
|
+
if not chatbot_name:
|
|
742
|
+
return self.json_response(
|
|
743
|
+
{
|
|
744
|
+
"message": "Chatbot name is required."
|
|
745
|
+
},
|
|
746
|
+
status=400
|
|
747
|
+
)
|
|
748
|
+
try:
|
|
749
|
+
manager = self.request.app['bot_manager']
|
|
750
|
+
except KeyError:
|
|
751
|
+
return self.json_response(
|
|
752
|
+
{
|
|
753
|
+
"message": "Chatbot Manager is not installed."
|
|
754
|
+
},
|
|
755
|
+
status=404
|
|
756
|
+
)
|
|
757
|
+
try:
|
|
758
|
+
chatbot: AbstractBot = await manager.get_bot(chatbot_name)
|
|
759
|
+
if not chatbot:
|
|
760
|
+
raise KeyError(
|
|
761
|
+
f"Chatbot {chatbot_name} not found."
|
|
762
|
+
)
|
|
763
|
+
except (TypeError, KeyError):
|
|
764
|
+
return self.json_response(
|
|
765
|
+
{
|
|
766
|
+
"message": f"Chatbot {chatbot_name} not found."
|
|
767
|
+
},
|
|
768
|
+
status=404
|
|
769
|
+
)
|
|
770
|
+
# Check if Store is loaded, if not, return error:
|
|
771
|
+
if not chatbot.get_vector_store():
|
|
772
|
+
return self.json_response(
|
|
773
|
+
{
|
|
774
|
+
"message": f"Chatbot {chatbot_name} has no Vector Store configured."
|
|
775
|
+
},
|
|
776
|
+
status=400
|
|
777
|
+
)
|
|
778
|
+
# Check if chatbot.store is available:
|
|
779
|
+
if chatbot.store is None:
|
|
780
|
+
# Load the store:
|
|
781
|
+
try:
|
|
782
|
+
store = chatbot.get_vector_store()
|
|
783
|
+
# change "name" to "vector_vector_store"
|
|
784
|
+
if 'name' in store:
|
|
785
|
+
store['vector_store'] = store.pop('name')
|
|
786
|
+
chatbot.define_store(
|
|
787
|
+
**store
|
|
788
|
+
)
|
|
789
|
+
chatbot.configure_store()
|
|
790
|
+
except Exception as e:
|
|
791
|
+
return self.json_response(
|
|
792
|
+
{
|
|
793
|
+
"message": f"Failed to configure store for chatbot {chatbot_name}: {e}"
|
|
794
|
+
},
|
|
795
|
+
status=500
|
|
796
|
+
)
|
|
797
|
+
default_loader = form_data.pop('loader', 'MarkdownLoader')
|
|
798
|
+
source_type = form_data.pop('source_type', 'file')
|
|
799
|
+
# Any extra kwargs for loaders (excluding 'loader' key)
|
|
800
|
+
loader_kwargs = {k: v for k, v in (form_data or {}).items() if k != "loader"}
|
|
801
|
+
loader_cls = self._get_loader(default_loader)
|
|
802
|
+
# --- Group all attachments by loader ---
|
|
803
|
+
by_loader = self._group_attachments_by_loader(
|
|
804
|
+
attachments,
|
|
805
|
+
default_loader_cls=loader_cls
|
|
806
|
+
)
|
|
807
|
+
files_list = []
|
|
808
|
+
loaders_used = []
|
|
809
|
+
if not by_loader and not attachments:
|
|
810
|
+
# if no files were uploaded, using the form_data as a source:
|
|
811
|
+
source = form_data.pop('source', None)
|
|
812
|
+
# Any extra kwargs for loaders (excluding control keys)
|
|
813
|
+
loader_kwargs = {k: v for k, v in (form_data or {}).items()
|
|
814
|
+
if k not in {'loader', 'source_type', 'source'}}
|
|
815
|
+
if not source:
|
|
816
|
+
return self.json_response(
|
|
817
|
+
{"message": "No files/URLs were uploaded and no source provided."},
|
|
818
|
+
status=400
|
|
819
|
+
)
|
|
820
|
+
# If loader not resolved, try to infer: YouTube vs Web
|
|
821
|
+
if loader_cls is None:
|
|
822
|
+
if any('youtu' in u for u in source):
|
|
823
|
+
loader_cls = self._get_loader('YoutubeLoader')
|
|
824
|
+
else:
|
|
825
|
+
loader_cls = self._get_loader('WebLoader')
|
|
826
|
+
documents: list = []
|
|
827
|
+
errors: list = []
|
|
828
|
+
if loader_cls is None:
|
|
829
|
+
return self.json_response(
|
|
830
|
+
{"message": "Loader not found or not specified for URL sources."},
|
|
831
|
+
status=400
|
|
832
|
+
)
|
|
833
|
+
try:
|
|
834
|
+
loader = loader_cls(
|
|
835
|
+
source=source,
|
|
836
|
+
source_type=source_type,
|
|
837
|
+
**loader_kwargs
|
|
838
|
+
)
|
|
839
|
+
docs = await loader.load()
|
|
840
|
+
if isinstance(docs, list):
|
|
841
|
+
documents.extend(docs)
|
|
842
|
+
except Exception as exc:
|
|
843
|
+
errors.append(str(exc))
|
|
844
|
+
loaders_used = [loader_cls.__name__]
|
|
845
|
+
files_list = source
|
|
846
|
+
if attachments:
|
|
847
|
+
if not by_loader:
|
|
848
|
+
return self.json_response(
|
|
849
|
+
{"message": "No supported files found."},
|
|
850
|
+
status=400
|
|
851
|
+
)
|
|
852
|
+
tasks = []
|
|
853
|
+
for loader_cls, files in by_loader.items():
|
|
854
|
+
print(
|
|
855
|
+
f"Loading {len(files)} files with {loader_cls.__name__}"
|
|
856
|
+
)
|
|
857
|
+
try:
|
|
858
|
+
# Each loader receives the full list for that type (avoid per-file loops)
|
|
859
|
+
loader = loader_cls(
|
|
860
|
+
source=files,
|
|
861
|
+
source_type=source_type,
|
|
862
|
+
**loader_kwargs
|
|
863
|
+
)
|
|
864
|
+
tasks.append(loader.load())
|
|
865
|
+
except Exception as exc:
|
|
866
|
+
return self.error(
|
|
867
|
+
f"Error initializing {loader_cls} for chatbot {chatbot_name}: {exc}",
|
|
868
|
+
exception=exc,
|
|
869
|
+
status=400
|
|
870
|
+
)
|
|
871
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
872
|
+
# Flatten and handle errors without aborting the whole batch
|
|
873
|
+
documents = []
|
|
874
|
+
errors = []
|
|
875
|
+
try:
|
|
876
|
+
for res in results:
|
|
877
|
+
if isinstance(res, Exception):
|
|
878
|
+
errors.append(str(res))
|
|
879
|
+
elif isinstance(res, list):
|
|
880
|
+
documents.extend(res)
|
|
881
|
+
except Exception as exc:
|
|
882
|
+
return self.error(
|
|
883
|
+
f"Error adding documents to chatbot {chatbot_name}: {exc}",
|
|
884
|
+
exception=exc,
|
|
885
|
+
status=400
|
|
886
|
+
)
|
|
887
|
+
files_list = []
|
|
888
|
+
for _, values in attachments.items():
|
|
889
|
+
for a in values or []:
|
|
890
|
+
p = a.get("file_path")
|
|
891
|
+
if p is None:
|
|
892
|
+
continue
|
|
893
|
+
files_list.append(str(p))
|
|
894
|
+
loaders_used = [cls.__name__ for cls in by_loader.keys()]
|
|
895
|
+
# Load documents into the chatbot
|
|
896
|
+
try:
|
|
897
|
+
if documents:
|
|
898
|
+
await chatbot.store.add_documents(documents)
|
|
899
|
+
except Exception as exc:
|
|
900
|
+
return self.error(
|
|
901
|
+
f"Error adding documents to chatbot {chatbot_name}: {exc}",
|
|
902
|
+
exception=exc,
|
|
903
|
+
status=400
|
|
904
|
+
)
|
|
905
|
+
payload = {
|
|
906
|
+
"bot": chatbot_name,
|
|
907
|
+
"files": files_list,
|
|
908
|
+
"loaders": loaders_used,
|
|
909
|
+
"documents": len(documents),
|
|
910
|
+
"errors": errors,
|
|
911
|
+
}
|
|
912
|
+
return self.json_response(
|
|
913
|
+
payload,
|
|
914
|
+
status=207 if errors else 200
|
|
915
|
+
)
|