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,1030 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chatbot Manager.
|
|
3
|
+
|
|
4
|
+
Tool for instanciate, managing and interacting with Chatbot through APIs.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Any, Dict, Type, Optional, Tuple, List
|
|
7
|
+
from importlib import import_module
|
|
8
|
+
import contextlib
|
|
9
|
+
import time
|
|
10
|
+
import asyncio
|
|
11
|
+
import copy
|
|
12
|
+
from aiohttp import web
|
|
13
|
+
from datamodel.exceptions import ValidationError # pylint: disable=E0611 # noqa
|
|
14
|
+
# Navigator:
|
|
15
|
+
from navconfig.logging import logging
|
|
16
|
+
from asyncdb.exceptions import NoDataFound
|
|
17
|
+
from ..bots.abstract import AbstractBot
|
|
18
|
+
from ..bots.basic import BasicBot
|
|
19
|
+
from ..bots.chatbot import Chatbot
|
|
20
|
+
from ..bots.agent import BasicAgent
|
|
21
|
+
from ..handlers.chat import ChatHandler, BotHandler
|
|
22
|
+
from ..handlers.agent import AgentTalk
|
|
23
|
+
from ..handlers import ChatbotHandler
|
|
24
|
+
from ..handlers.models import BotModel
|
|
25
|
+
from ..handlers.stream import StreamHandler
|
|
26
|
+
from ..registry import agent_registry, AgentRegistry
|
|
27
|
+
# Crew:
|
|
28
|
+
from ..bots.orchestration.crew import AgentCrew
|
|
29
|
+
from ..handlers.crew.models import CrewDefinition, ExecutionMode
|
|
30
|
+
from ..handlers.crew.handler import CrewHandler
|
|
31
|
+
from ..handlers.crew.redis_persistence import CrewRedis
|
|
32
|
+
from ..openapi.config import setup_swagger
|
|
33
|
+
from ..conf import ENABLE_SWAGGER
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BotManager:
|
|
37
|
+
"""BotManager.
|
|
38
|
+
|
|
39
|
+
Manage Bots/Agents and interact with them through via aiohttp App.
|
|
40
|
+
Deploy and manage chatbots and agents using a RESTful API.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
app: web.Application = None
|
|
44
|
+
|
|
45
|
+
def __init__(self) -> None:
|
|
46
|
+
self.app = None
|
|
47
|
+
self._bots: Dict[str, AbstractBot] = {}
|
|
48
|
+
self._botdef: Dict[str, Type] = {} # Store class definitions for each bot
|
|
49
|
+
self._bot_expiration: Dict[str, float] = {} # Track expiration timestamps for temporary bots
|
|
50
|
+
self._cleanup_task: Optional[asyncio.Task] = None # Background cleanup task
|
|
51
|
+
self.logger = logging.getLogger(
|
|
52
|
+
name='Parrot.Manager'
|
|
53
|
+
)
|
|
54
|
+
self.registry: AgentRegistry = agent_registry
|
|
55
|
+
self._crews: Dict[str, Tuple[AgentCrew, CrewDefinition]] = {}
|
|
56
|
+
# Initialize Redis persistence for crews
|
|
57
|
+
self.crew_redis = CrewRedis()
|
|
58
|
+
|
|
59
|
+
def get_bot_class(self, bot_name: str) -> Optional[Type]:
|
|
60
|
+
"""
|
|
61
|
+
Get bot class by name, searching in:
|
|
62
|
+
1. parrot.bots (core bots)
|
|
63
|
+
2. parrot.agents (plugin agents)
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
bot_name: Name of the bot/agent class
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Bot class if found, None otherwise
|
|
70
|
+
"""
|
|
71
|
+
# First, try to import from core bots
|
|
72
|
+
with contextlib.suppress(ImportError, AttributeError):
|
|
73
|
+
module = import_module("parrot.bots")
|
|
74
|
+
if hasattr(module, bot_name):
|
|
75
|
+
return getattr(module, bot_name)
|
|
76
|
+
|
|
77
|
+
# Second, try to import from plugin agents
|
|
78
|
+
with contextlib.suppress(ImportError, AttributeError):
|
|
79
|
+
agent_module_name = f"parrot.agents.{bot_name.lower()}"
|
|
80
|
+
module = import_module(agent_module_name)
|
|
81
|
+
if hasattr(module, bot_name):
|
|
82
|
+
return getattr(module, bot_name)
|
|
83
|
+
|
|
84
|
+
# Third, try direct import from parrot.agents package
|
|
85
|
+
# (in case the agent is defined in plugins/agents/__init__.py)
|
|
86
|
+
with contextlib.suppress(ImportError, AttributeError):
|
|
87
|
+
module = import_module("parrot.agents")
|
|
88
|
+
if hasattr(module, bot_name):
|
|
89
|
+
return getattr(module, bot_name)
|
|
90
|
+
|
|
91
|
+
self.logger.warning(
|
|
92
|
+
f"Warning: Bot class '{bot_name}' not found in parrot.bots or parrot.agents"
|
|
93
|
+
)
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def get_or_create_bot(self, bot_name: str, **kwargs):
|
|
97
|
+
"""
|
|
98
|
+
Get existing bot or create new one from class name.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
bot_name: Name of the bot/agent class
|
|
102
|
+
**kwargs: Arguments to pass to bot constructor
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Bot instance
|
|
106
|
+
"""
|
|
107
|
+
# Check if already instantiated
|
|
108
|
+
if bot_name in self._bots:
|
|
109
|
+
return self._bots[bot_name]
|
|
110
|
+
|
|
111
|
+
# Get the class and instantiate
|
|
112
|
+
bot_class = self.get_bot_class(bot_name)
|
|
113
|
+
if bot_class is None:
|
|
114
|
+
raise ValueError(f"Bot class '{bot_name}' not found")
|
|
115
|
+
|
|
116
|
+
return self.create_bot(class_name=bot_class, name=bot_name, **kwargs)
|
|
117
|
+
|
|
118
|
+
def _log_final_state(self) -> None:
|
|
119
|
+
"""Log the final state of bot loading."""
|
|
120
|
+
registry_info = self.registry.get_registration_info()
|
|
121
|
+
self.logger.notice("=== Bot Loading Complete ===")
|
|
122
|
+
self.logger.notice(f"Registered agents: {registry_info['total_registered']}")
|
|
123
|
+
# self.logger.info(f"Startup agents: {startup_info['total_startup_agents']}")
|
|
124
|
+
self.logger.notice(f"Active bots: {len(self._bots)}")
|
|
125
|
+
|
|
126
|
+
async def _process_startup_results(self, startup_results: Dict[str, Any]) -> None:
|
|
127
|
+
"""Process startup instantiation results."""
|
|
128
|
+
for agent_name, result in startup_results.items():
|
|
129
|
+
print('===========================================')
|
|
130
|
+
print('Agent startup result:', agent_name, result)
|
|
131
|
+
print('===========================================')
|
|
132
|
+
if result["status"] == "success":
|
|
133
|
+
if instance := result.get("instance"):
|
|
134
|
+
self._bots[agent_name] = instance
|
|
135
|
+
self.logger.info(
|
|
136
|
+
f"Added startup agent to active bots: {agent_name}"
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
self.logger.error(
|
|
140
|
+
f"Startup agent {agent_name} failed: {result['error']}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
async def load_bots(self, app: web.Application) -> None:
|
|
144
|
+
"""Enhanced bot loading using the registry."""
|
|
145
|
+
self.logger.info("Starting bot loading with global registry")
|
|
146
|
+
|
|
147
|
+
# Step 1: Import modules to trigger decorator registration
|
|
148
|
+
await self.registry.load_modules()
|
|
149
|
+
|
|
150
|
+
# Step 2: Register config-based agents
|
|
151
|
+
config_count = self.registry.discover_config_agents()
|
|
152
|
+
self.logger.info(
|
|
153
|
+
f"Registered {config_count} agents from config"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Step 3: Instantiate startup agents
|
|
157
|
+
startup_results = await self.registry.instantiate_startup_agents(app)
|
|
158
|
+
await self._process_startup_results(startup_results)
|
|
159
|
+
|
|
160
|
+
# Step 4: Load database bots
|
|
161
|
+
await self._load_database_bots(app)
|
|
162
|
+
|
|
163
|
+
# Step 5: Report final state
|
|
164
|
+
self._log_final_state()
|
|
165
|
+
|
|
166
|
+
async def _load_database_bots(self, app: web.Application) -> None:
|
|
167
|
+
"""Load bots from database."""
|
|
168
|
+
try:
|
|
169
|
+
# Import here to avoid circular imports
|
|
170
|
+
from ..handlers.models import BotModel # pylint: disable=import-outside-toplevel # noqa
|
|
171
|
+
db = app['database']
|
|
172
|
+
async with await db.acquire() as conn:
|
|
173
|
+
BotModel.Meta.connection = conn
|
|
174
|
+
try:
|
|
175
|
+
all_bots = await BotModel.filter(enabled=True)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
self.logger.error(
|
|
178
|
+
f"Failed to load bots from DB: {e}"
|
|
179
|
+
)
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
for bot_model in all_bots:
|
|
183
|
+
self.logger.notice(
|
|
184
|
+
f"Loading bot '{bot_model.name}' (mode: {bot_model.operation_mode})..."
|
|
185
|
+
)
|
|
186
|
+
if bot_model.name in self._bots:
|
|
187
|
+
self.logger.debug(
|
|
188
|
+
f"Bot {bot_model.name} already active, skipping"
|
|
189
|
+
)
|
|
190
|
+
continue
|
|
191
|
+
try:
|
|
192
|
+
# Use the factory function from models.py or create bot directly
|
|
193
|
+
if hasattr(self, 'get_bot_class') and hasattr(bot_model, 'bot_class'):
|
|
194
|
+
# If you have a bot_class field and get_bot_class method
|
|
195
|
+
class_name = self.get_bot_class(getattr(bot_model, 'bot_class', None))
|
|
196
|
+
else:
|
|
197
|
+
# Default to BasicBot or your default bot class
|
|
198
|
+
class_name = BasicBot
|
|
199
|
+
bot_instance = class_name(
|
|
200
|
+
chatbot_id=bot_model.chatbot_id,
|
|
201
|
+
name=bot_model.name,
|
|
202
|
+
description=bot_model.description,
|
|
203
|
+
# LLM configuration
|
|
204
|
+
use_llm=bot_model.llm,
|
|
205
|
+
model_name=bot_model.model_name,
|
|
206
|
+
model_config=bot_model.model_config,
|
|
207
|
+
temperature=bot_model.temperature,
|
|
208
|
+
max_tokens=bot_model.max_tokens,
|
|
209
|
+
top_k=bot_model.top_k,
|
|
210
|
+
top_p=bot_model.top_p,
|
|
211
|
+
# Bot personality
|
|
212
|
+
role=bot_model.role,
|
|
213
|
+
goal=bot_model.goal,
|
|
214
|
+
backstory=bot_model.backstory,
|
|
215
|
+
rationale=bot_model.rationale,
|
|
216
|
+
capabilities=bot_model.capabilities,
|
|
217
|
+
# Prompt configuration
|
|
218
|
+
system_prompt=bot_model.system_prompt_template,
|
|
219
|
+
human_prompt=bot_model.human_prompt_template,
|
|
220
|
+
pre_instructions=bot_model.pre_instructions,
|
|
221
|
+
# Vector store configuration
|
|
222
|
+
embedding_model=bot_model.embedding_model,
|
|
223
|
+
use_vectorstore=bot_model.use_vector,
|
|
224
|
+
vector_store_config=bot_model.vector_store_config,
|
|
225
|
+
context_search_limit=bot_model.context_search_limit,
|
|
226
|
+
context_score_threshold=bot_model.context_score_threshold,
|
|
227
|
+
# Tool and agent configuration
|
|
228
|
+
tools_enabled=bot_model.tools_enabled,
|
|
229
|
+
auto_tool_detection=bot_model.auto_tool_detection,
|
|
230
|
+
tool_threshold=bot_model.tool_threshold,
|
|
231
|
+
available_tools=bot_model.tools,
|
|
232
|
+
operation_mode=bot_model.operation_mode,
|
|
233
|
+
# Memory configuration
|
|
234
|
+
memory_type=bot_model.memory_type,
|
|
235
|
+
memory_config=bot_model.memory_config,
|
|
236
|
+
max_context_turns=bot_model.max_context_turns,
|
|
237
|
+
use_conversation_history=bot_model.use_conversation_history,
|
|
238
|
+
# Security and permissions
|
|
239
|
+
permissions=bot_model.permissions,
|
|
240
|
+
# Metadata
|
|
241
|
+
language=bot_model.language,
|
|
242
|
+
disclaimer=bot_model.disclaimer,
|
|
243
|
+
)
|
|
244
|
+
# Set the model ID reference
|
|
245
|
+
bot_instance.model_id = bot_model.chatbot_id
|
|
246
|
+
|
|
247
|
+
await bot_instance.configure(app)
|
|
248
|
+
self.add_bot(bot_instance)
|
|
249
|
+
self.logger.info(
|
|
250
|
+
f"Successfully loaded bot '{bot_model.name}' "
|
|
251
|
+
f"with {len(bot_model.tools) if bot_model.tools else 0} tools"
|
|
252
|
+
)
|
|
253
|
+
except ValidationError as e:
|
|
254
|
+
self.logger.error(
|
|
255
|
+
f"Invalid configuration for bot '{bot_model.name}': {e}"
|
|
256
|
+
)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
self.logger.error(
|
|
259
|
+
f"Failed to load database bot {bot_instance.name}: {str(e)}"
|
|
260
|
+
)
|
|
261
|
+
self.logger.info(
|
|
262
|
+
f":: Bots loaded successfully. Total active bots: {len(self._bots)}"
|
|
263
|
+
)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
self.logger.error(
|
|
266
|
+
f"Database bot loading failed: {str(e)}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Alternative approach using the factory function from models.py
|
|
270
|
+
async def load_bots_with_factory(self, app: web.Application) -> None:
|
|
271
|
+
"""Load all bots from DB using the factory function."""
|
|
272
|
+
self.logger.info("Loading bots from DB...")
|
|
273
|
+
db = app['database']
|
|
274
|
+
async with await db.acquire() as conn:
|
|
275
|
+
BotModel.Meta.connection = conn
|
|
276
|
+
try:
|
|
277
|
+
bot_models = await BotModel.filter(enabled=True)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
self.logger.error(
|
|
280
|
+
f"Failed to load bots from DB: {e}"
|
|
281
|
+
)
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
for bot_model in bot_models:
|
|
285
|
+
self.logger.notice(
|
|
286
|
+
f"Loading bot '{bot_model.name}' (mode: {bot_model.operation_mode})..."
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
# Use the factory function from models.py
|
|
291
|
+
# Determine bot class if you have custom classes
|
|
292
|
+
bot_class = None
|
|
293
|
+
if hasattr(self, 'get_bot_class') and hasattr(bot_model, 'bot_class'):
|
|
294
|
+
bot_class = self.get_bot_class(getattr(bot_model, 'bot_class', None))
|
|
295
|
+
else:
|
|
296
|
+
# Default to BasicBot or your default bot class
|
|
297
|
+
bot_class = BasicBot
|
|
298
|
+
|
|
299
|
+
# Create bot using factory function
|
|
300
|
+
chatbot = bot_class(bot_model, bot_class)
|
|
301
|
+
|
|
302
|
+
# Configure the bot
|
|
303
|
+
try:
|
|
304
|
+
await chatbot.configure(app=app)
|
|
305
|
+
self.add_bot(chatbot)
|
|
306
|
+
self.logger.info(
|
|
307
|
+
f"Successfully loaded bot '{bot_model.name}' "
|
|
308
|
+
f"with {len(bot_model.tools) if bot_model.tools else 0} tools"
|
|
309
|
+
)
|
|
310
|
+
except ValidationError as e:
|
|
311
|
+
self.logger.error(
|
|
312
|
+
f"Invalid configuration for bot '{bot_model.name}': {e}"
|
|
313
|
+
)
|
|
314
|
+
except Exception as e:
|
|
315
|
+
self.logger.error(
|
|
316
|
+
f"Failed to configure bot '{bot_model.name}': {e}"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
except Exception as e:
|
|
320
|
+
self.logger.error(
|
|
321
|
+
f"Failed to create bot instance for '{bot_model.name}': {e}"
|
|
322
|
+
)
|
|
323
|
+
continue
|
|
324
|
+
|
|
325
|
+
self.logger.info(
|
|
326
|
+
f":: Bots loaded successfully. Total active bots: {len(self._bots)}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def create_bot(self, class_name: Any = None, name: str = None, **kwargs) -> AbstractBot:
|
|
330
|
+
"""Create a Bot and add it to the manager."""
|
|
331
|
+
if class_name is None:
|
|
332
|
+
class_name = Chatbot
|
|
333
|
+
chatbot = class_name(**kwargs)
|
|
334
|
+
chatbot.name = name
|
|
335
|
+
return chatbot
|
|
336
|
+
|
|
337
|
+
def add_bot(self, bot: AbstractBot) -> None:
|
|
338
|
+
"""Add a Bot to the manager."""
|
|
339
|
+
self._bots[bot.name] = bot
|
|
340
|
+
# Store the class definition for future instance creation
|
|
341
|
+
self._botdef[bot.name] = bot.__class__
|
|
342
|
+
|
|
343
|
+
async def get_bot(
|
|
344
|
+
self,
|
|
345
|
+
name: str,
|
|
346
|
+
new: bool = False,
|
|
347
|
+
session_id: str = "",
|
|
348
|
+
**kwargs
|
|
349
|
+
) -> AbstractBot:
|
|
350
|
+
"""Get a Bot by name.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
name: Name of the bot to get
|
|
354
|
+
new: If True, create a new instance instead of returning existing one
|
|
355
|
+
session_id: Session identifier for creating unique temporary instances
|
|
356
|
+
**kwargs: Additional arguments to pass to bot constructor when new=True
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Bot instance (existing or newly created)
|
|
360
|
+
"""
|
|
361
|
+
# Handle new instance creation
|
|
362
|
+
if new:
|
|
363
|
+
# Get the class definition for this bot
|
|
364
|
+
cls = self._botdef.get(name, BasicAgent)
|
|
365
|
+
|
|
366
|
+
# Create unique name to avoid duplicates
|
|
367
|
+
new_name = f"{name}_{session_id}" if session_id else f"{name}_{int(time.time())}"
|
|
368
|
+
|
|
369
|
+
# Prepare configuration to inherit from base bot
|
|
370
|
+
base_bot = self._bots.get(name)
|
|
371
|
+
bot_kwargs = kwargs.copy()
|
|
372
|
+
|
|
373
|
+
if base_bot:
|
|
374
|
+
# 1. Inherit LLM Configuration if not explicitly provided
|
|
375
|
+
if 'use_llm' not in bot_kwargs and hasattr(base_bot, '_llm_raw'):
|
|
376
|
+
bot_kwargs['use_llm'] = base_bot._llm_raw
|
|
377
|
+
|
|
378
|
+
if 'model' not in bot_kwargs and hasattr(base_bot, '_llm_model'):
|
|
379
|
+
bot_kwargs['model'] = base_bot._llm_model
|
|
380
|
+
|
|
381
|
+
# 2. Clone Tools
|
|
382
|
+
if 'tools' not in bot_kwargs and hasattr(base_bot, 'tool_manager'):
|
|
383
|
+
try:
|
|
384
|
+
# Deep copy tools to ensure isolation
|
|
385
|
+
base_tools = base_bot.tool_manager.get_all_tools()
|
|
386
|
+
new_tools = []
|
|
387
|
+
for tool in base_tools:
|
|
388
|
+
try:
|
|
389
|
+
# Attempt deep copy
|
|
390
|
+
new_tool = copy.deepcopy(tool)
|
|
391
|
+
new_tools.append(new_tool)
|
|
392
|
+
except Exception as e:
|
|
393
|
+
self.logger.warning(
|
|
394
|
+
f"Failed to copy tool {tool.name}, sharing instance. Error: {e}"
|
|
395
|
+
)
|
|
396
|
+
# Fallback to shared instance
|
|
397
|
+
new_tools.append(tool)
|
|
398
|
+
bot_kwargs['tools'] = new_tools
|
|
399
|
+
except Exception as e:
|
|
400
|
+
self.logger.error(f"Error cloning tools from {name}: {e}")
|
|
401
|
+
|
|
402
|
+
# 3. Clone Vector Store Configuration
|
|
403
|
+
if 'vector_store_config' not in bot_kwargs and hasattr(base_bot, '_vector_store'):
|
|
404
|
+
try:
|
|
405
|
+
if base_bot._vector_store:
|
|
406
|
+
bot_kwargs['vector_store_config'] = copy.deepcopy(base_bot._vector_store)
|
|
407
|
+
except Exception as e:
|
|
408
|
+
self.logger.warning(f"Failed to copy vector store config: {e}")
|
|
409
|
+
bot_kwargs['vector_store_config'] = base_bot._vector_store
|
|
410
|
+
|
|
411
|
+
if 'use_vectorstore' not in bot_kwargs and hasattr(base_bot, '_use_vector'):
|
|
412
|
+
bot_kwargs['use_vectorstore'] = getattr(base_bot, '_use_vector', False)
|
|
413
|
+
|
|
414
|
+
# Create new instance with merged configuration
|
|
415
|
+
bot = cls(name=new_name, **bot_kwargs)
|
|
416
|
+
|
|
417
|
+
# Configure the bot
|
|
418
|
+
await bot.configure(self.app)
|
|
419
|
+
|
|
420
|
+
# Add to bots dictionary
|
|
421
|
+
self._bots[new_name] = bot
|
|
422
|
+
|
|
423
|
+
# Set expiration time (1 hour from now)
|
|
424
|
+
self._bot_expiration[new_name] = time.time() + 3600
|
|
425
|
+
|
|
426
|
+
self.logger.info(
|
|
427
|
+
f"Created new temporary bot instance '{new_name}' from '{name}' "
|
|
428
|
+
f"(expires in 1 hour)"
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
return bot
|
|
432
|
+
|
|
433
|
+
# Existing behavior for getting/creating bots
|
|
434
|
+
if name not in self._bots:
|
|
435
|
+
self.logger.warning(
|
|
436
|
+
f"Bot '{name}' not in _bots. Available: {list(self._bots.keys())}"
|
|
437
|
+
)
|
|
438
|
+
if name in self._bots:
|
|
439
|
+
_bot = self._bots[name]
|
|
440
|
+
if not getattr(_bot, 'is_configured', False):
|
|
441
|
+
self.logger.warning(f"Bot '{name}' found in _bots and is not configured.")
|
|
442
|
+
await _bot.configure(self.app)
|
|
443
|
+
return self._bots[name]
|
|
444
|
+
if self.registry.has(name):
|
|
445
|
+
try:
|
|
446
|
+
# Get instance (returns singleton if at_startup=True)
|
|
447
|
+
bot_instance = await self.registry.get_instance(name)
|
|
448
|
+
if bot_instance:
|
|
449
|
+
# Only configure if NOT already configured
|
|
450
|
+
if not getattr(bot_instance, 'is_configured', False):
|
|
451
|
+
self.logger.info(f"Configuring bot {name} on demand.")
|
|
452
|
+
await bot_instance.configure(self.app)
|
|
453
|
+
self.add_bot(bot_instance)
|
|
454
|
+
return bot_instance
|
|
455
|
+
except Exception as e:
|
|
456
|
+
self.logger.error(
|
|
457
|
+
f"Failed to get bot instance from registry: {e}"
|
|
458
|
+
)
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
def remove_bot(self, name: str) -> None:
|
|
462
|
+
"""Remove a Bot by name."""
|
|
463
|
+
del self._bots[name]
|
|
464
|
+
# Clean up expiration tracking if it exists (but keep class definition)
|
|
465
|
+
self._bot_expiration.pop(name, None)
|
|
466
|
+
|
|
467
|
+
def get_bots(self) -> Dict[str, AbstractBot]:
|
|
468
|
+
"""Get all Bots declared on Manager."""
|
|
469
|
+
return self._bots
|
|
470
|
+
|
|
471
|
+
async def create_agent(self, class_name: Any = None, name: str = None, **kwargs) -> AbstractBot:
|
|
472
|
+
if class_name is None:
|
|
473
|
+
class_name = BasicAgent
|
|
474
|
+
return class_name(name=name, **kwargs)
|
|
475
|
+
|
|
476
|
+
def add_agent(self, agent: AbstractBot) -> None:
|
|
477
|
+
"""Add a Agent to the manager."""
|
|
478
|
+
self._bots[str(agent.chatbot_id)] = agent
|
|
479
|
+
|
|
480
|
+
def remove_agent(self, agent: AbstractBot) -> None:
|
|
481
|
+
"""Remove a Bot by name."""
|
|
482
|
+
del self._bots[str(agent.chatbot_id)]
|
|
483
|
+
|
|
484
|
+
async def save_agent(self, name: str, **kwargs) -> None:
|
|
485
|
+
"""Save a Agent to the DB."""
|
|
486
|
+
self.logger.info(f"Saving Agent {name} into DB ...")
|
|
487
|
+
db = self.app['database']
|
|
488
|
+
async with await db.acquire() as conn:
|
|
489
|
+
BotModel.Meta.connection = conn
|
|
490
|
+
try:
|
|
491
|
+
try:
|
|
492
|
+
bot = await BotModel.get(name=name)
|
|
493
|
+
except NoDataFound:
|
|
494
|
+
bot = None
|
|
495
|
+
if bot:
|
|
496
|
+
self.logger.info(f"Bot {name} already exists.")
|
|
497
|
+
for key, val in kwargs.items():
|
|
498
|
+
bot.set(key, val)
|
|
499
|
+
await bot.update()
|
|
500
|
+
self.logger.info(f"Bot {name} updated.")
|
|
501
|
+
else:
|
|
502
|
+
self.logger.info(f"Bot {name} not found. Creating new one.")
|
|
503
|
+
# Create a new Bot
|
|
504
|
+
new_bot = BotModel(
|
|
505
|
+
name=name,
|
|
506
|
+
**kwargs
|
|
507
|
+
)
|
|
508
|
+
await new_bot.insert()
|
|
509
|
+
self.logger.info(f"Bot {name} saved into DB.")
|
|
510
|
+
return True
|
|
511
|
+
except Exception as e:
|
|
512
|
+
self.logger.error(
|
|
513
|
+
f"Failed to Create new Bot {name} from DB: {e}"
|
|
514
|
+
)
|
|
515
|
+
return None
|
|
516
|
+
|
|
517
|
+
def get_app(self) -> web.Application:
|
|
518
|
+
"""Get the app."""
|
|
519
|
+
if self.app is None:
|
|
520
|
+
raise RuntimeError("App is not set.")
|
|
521
|
+
return self.app
|
|
522
|
+
|
|
523
|
+
def setup(self, app: web.Application) -> web.Application:
|
|
524
|
+
self.app = None
|
|
525
|
+
if app:
|
|
526
|
+
self.app = app if isinstance(app, web.Application) else app.get_app()
|
|
527
|
+
# register signals for startup and shutdown
|
|
528
|
+
self.app.on_startup.append(self.on_startup)
|
|
529
|
+
self.app.on_shutdown.append(self.on_shutdown)
|
|
530
|
+
# Add Manager to main Application:
|
|
531
|
+
self.app['bot_manager'] = self
|
|
532
|
+
## Configure Routes
|
|
533
|
+
router = self.app.router
|
|
534
|
+
# Chat Information Router
|
|
535
|
+
router.add_view(
|
|
536
|
+
'/api/v1/chats',
|
|
537
|
+
ChatHandler
|
|
538
|
+
)
|
|
539
|
+
router.add_view(
|
|
540
|
+
'/api/v1/chat/{chatbot_name}',
|
|
541
|
+
ChatHandler
|
|
542
|
+
)
|
|
543
|
+
router.add_view(
|
|
544
|
+
'/api/v1/chat/{chatbot_name}/{method_name}',
|
|
545
|
+
ChatHandler
|
|
546
|
+
)
|
|
547
|
+
# Talk with agents:
|
|
548
|
+
router.add_view(
|
|
549
|
+
'/api/v1/agents/chat/{agent_id}',
|
|
550
|
+
AgentTalk
|
|
551
|
+
)
|
|
552
|
+
router.add_view(
|
|
553
|
+
'/api/v1/agents/chat/{agent_id}/{method_name}',
|
|
554
|
+
AgentTalk
|
|
555
|
+
)
|
|
556
|
+
# ChatBot Manager
|
|
557
|
+
ChatbotHandler.configure(self.app, '/api/v1/bots')
|
|
558
|
+
# Bot Handler
|
|
559
|
+
router.add_view(
|
|
560
|
+
'/api/v1/chatbots',
|
|
561
|
+
BotHandler
|
|
562
|
+
)
|
|
563
|
+
router.add_view(
|
|
564
|
+
'/api/v1/chatbots/{name}',
|
|
565
|
+
BotHandler
|
|
566
|
+
)
|
|
567
|
+
# Streaming Handler:
|
|
568
|
+
st = StreamHandler()
|
|
569
|
+
st.configure_routes(self.app)
|
|
570
|
+
# Crew Configuration
|
|
571
|
+
CrewHandler.configure(self.app, '/api/v1/crew')
|
|
572
|
+
if ENABLE_SWAGGER:
|
|
573
|
+
self.logger.info("Setting up OpenAPI documentation...")
|
|
574
|
+
setup_swagger(self.app)
|
|
575
|
+
self.logger.info("""
|
|
576
|
+
✅ OpenAPI Documentation configured successfully!
|
|
577
|
+
|
|
578
|
+
Available documentation UIs:
|
|
579
|
+
- Swagger UI: http://localhost:5000/api/docs
|
|
580
|
+
- ReDoc: http://localhost:5000/api/docs/redoc
|
|
581
|
+
- RapiDoc: http://localhost:5000/api/docs/rapidoc
|
|
582
|
+
- OpenAPI Spec: http://localhost:5000/api/docs/swagger.json
|
|
583
|
+
""")
|
|
584
|
+
return self.app
|
|
585
|
+
|
|
586
|
+
async def _cleanup_expired_bots(self) -> None:
|
|
587
|
+
"""Background task to cleanup expired temporary bot instances.
|
|
588
|
+
|
|
589
|
+
Runs every 5 minutes to check for and remove bot instances that have
|
|
590
|
+
exceeded their expiration time (typically 1 hour after creation).
|
|
591
|
+
"""
|
|
592
|
+
while True:
|
|
593
|
+
try:
|
|
594
|
+
await asyncio.sleep(300) # Check every 5 minutes
|
|
595
|
+
current_time = time.time()
|
|
596
|
+
|
|
597
|
+
# Find all expired bots
|
|
598
|
+
expired = [
|
|
599
|
+
name for name, expiry in self._bot_expiration.items()
|
|
600
|
+
if current_time > expiry
|
|
601
|
+
]
|
|
602
|
+
|
|
603
|
+
# Remove expired bots
|
|
604
|
+
for name in expired:
|
|
605
|
+
try:
|
|
606
|
+
self.logger.info(f"Removing expired bot instance: {name}")
|
|
607
|
+
self.remove_bot(name)
|
|
608
|
+
del self._bot_expiration[name]
|
|
609
|
+
except Exception as e:
|
|
610
|
+
self.logger.error(
|
|
611
|
+
f"Error removing expired bot '{name}': {e}"
|
|
612
|
+
)
|
|
613
|
+
# Remove from expiration tracking even if removal failed
|
|
614
|
+
self._bot_expiration.pop(name, None)
|
|
615
|
+
|
|
616
|
+
if expired:
|
|
617
|
+
self.logger.info(
|
|
618
|
+
f"Cleaned up {len(expired)} expired bot instance(s). "
|
|
619
|
+
f"Active bots: {len(self._bots)}, "
|
|
620
|
+
f"Tracked expirations: {len(self._bot_expiration)}"
|
|
621
|
+
)
|
|
622
|
+
except asyncio.CancelledError:
|
|
623
|
+
self.logger.info("Cleanup task cancelled")
|
|
624
|
+
raise
|
|
625
|
+
except Exception as e:
|
|
626
|
+
self.logger.error(
|
|
627
|
+
f"Error in cleanup task: {e}",
|
|
628
|
+
exc_info=True
|
|
629
|
+
)
|
|
630
|
+
# Continue running even if there's an error
|
|
631
|
+
|
|
632
|
+
async def on_startup(self, app: web.Application) -> None:
|
|
633
|
+
"""On startup."""
|
|
634
|
+
# configure all pre-configured chatbots:
|
|
635
|
+
await self.load_bots(app)
|
|
636
|
+
# Load crews from Redis
|
|
637
|
+
await self.load_crews()
|
|
638
|
+
# Start background cleanup task for expired bots
|
|
639
|
+
self._cleanup_task = asyncio.create_task(self._cleanup_expired_bots())
|
|
640
|
+
self.logger.info("Started background cleanup task for temporary bot instances")
|
|
641
|
+
|
|
642
|
+
async def on_shutdown(self, app: web.Application) -> None:
|
|
643
|
+
"""On shutdown."""
|
|
644
|
+
# Cancel background cleanup task
|
|
645
|
+
if self._cleanup_task:
|
|
646
|
+
self._cleanup_task.cancel()
|
|
647
|
+
try:
|
|
648
|
+
await self._cleanup_task
|
|
649
|
+
except asyncio.CancelledError:
|
|
650
|
+
pass
|
|
651
|
+
self.logger.info("Stopped background cleanup task")
|
|
652
|
+
|
|
653
|
+
async def add_crew(
|
|
654
|
+
self,
|
|
655
|
+
name: str,
|
|
656
|
+
crew: AgentCrew,
|
|
657
|
+
crew_def: CrewDefinition
|
|
658
|
+
) -> None:
|
|
659
|
+
"""
|
|
660
|
+
Register a crew in the manager and persist to Redis.
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
name: Unique name for the crew
|
|
664
|
+
crew: AgentCrew instance
|
|
665
|
+
crew_def: Crew definition containing metadata
|
|
666
|
+
|
|
667
|
+
Raises:
|
|
668
|
+
ValueError: If crew with same name already exists
|
|
669
|
+
"""
|
|
670
|
+
if name in self._crews:
|
|
671
|
+
raise ValueError(f"Crew '{name}' already exists")
|
|
672
|
+
|
|
673
|
+
# Add to memory
|
|
674
|
+
self._crews[name] = (crew, crew_def)
|
|
675
|
+
|
|
676
|
+
# Persist to Redis
|
|
677
|
+
try:
|
|
678
|
+
await self.crew_redis.save_crew(crew_def)
|
|
679
|
+
self.logger.info(
|
|
680
|
+
f"Registered crew '{name}' with {len(crew.agents)} agents "
|
|
681
|
+
f"in {crew_def.execution_mode.value} mode and saved to Redis"
|
|
682
|
+
)
|
|
683
|
+
except Exception as e:
|
|
684
|
+
self.logger.error(f"Failed to save crew '{name}' to Redis: {e}")
|
|
685
|
+
# Don't fail the operation if Redis fails, crew is still in memory
|
|
686
|
+
self.logger.info(
|
|
687
|
+
f"Crew '{name}' registered in memory only (Redis persistence failed)"
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
async def get_crew(
|
|
691
|
+
self,
|
|
692
|
+
identifier: str
|
|
693
|
+
) -> Optional[Tuple[AgentCrew, CrewDefinition]]:
|
|
694
|
+
"""
|
|
695
|
+
Get a crew by name or ID. Loads from Redis if not in memory.
|
|
696
|
+
|
|
697
|
+
Args:
|
|
698
|
+
identifier: Crew name or crew_id
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
Tuple of (AgentCrew, CrewDefinition) if found, None otherwise
|
|
702
|
+
"""
|
|
703
|
+
# Try by name first in memory
|
|
704
|
+
if identifier in self._crews:
|
|
705
|
+
return self._crews[identifier]
|
|
706
|
+
|
|
707
|
+
# Try by crew_id in memory
|
|
708
|
+
for name, (crew, crew_def) in self._crews.items():
|
|
709
|
+
if crew_def.crew_id == identifier:
|
|
710
|
+
return (crew, crew_def)
|
|
711
|
+
|
|
712
|
+
# Not in memory - try to load from Redis
|
|
713
|
+
try:
|
|
714
|
+
# Try to load by name first
|
|
715
|
+
crew_def = await self.crew_redis.load_crew(identifier)
|
|
716
|
+
|
|
717
|
+
# If not found by name, try by ID
|
|
718
|
+
if not crew_def:
|
|
719
|
+
crew_def = await self.crew_redis.load_crew_by_id(identifier)
|
|
720
|
+
|
|
721
|
+
if crew_def:
|
|
722
|
+
# Reconstruct the crew from definition
|
|
723
|
+
crew = await self._create_crew_from_definition(crew_def)
|
|
724
|
+
|
|
725
|
+
# Cache in memory
|
|
726
|
+
self._crews[crew_def.name] = (crew, crew_def)
|
|
727
|
+
|
|
728
|
+
self.logger.info(
|
|
729
|
+
f"Loaded crew '{crew_def.name}' from Redis "
|
|
730
|
+
f"(ID: {crew_def.crew_id})"
|
|
731
|
+
)
|
|
732
|
+
return (crew, crew_def)
|
|
733
|
+
except Exception as e:
|
|
734
|
+
self.logger.error(
|
|
735
|
+
f"Error loading crew '{identifier}' from Redis: {e}"
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
return None
|
|
739
|
+
|
|
740
|
+
def list_crews(self) -> Dict[str, Tuple[AgentCrew, CrewDefinition]]:
|
|
741
|
+
"""
|
|
742
|
+
List all registered crews.
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
Dictionary mapping crew names to (AgentCrew, CrewDefinition) tuples
|
|
746
|
+
"""
|
|
747
|
+
return self._crews.copy()
|
|
748
|
+
|
|
749
|
+
async def remove_crew(self, identifier: str) -> bool:
|
|
750
|
+
"""
|
|
751
|
+
Remove a crew from the manager and Redis.
|
|
752
|
+
|
|
753
|
+
Args:
|
|
754
|
+
identifier: Crew name or crew_id
|
|
755
|
+
|
|
756
|
+
Returns:
|
|
757
|
+
True if removed, False if not found
|
|
758
|
+
"""
|
|
759
|
+
crew_name = None
|
|
760
|
+
crew_def = None
|
|
761
|
+
|
|
762
|
+
# Try by name first
|
|
763
|
+
if identifier in self._crews:
|
|
764
|
+
crew_name = identifier
|
|
765
|
+
_, crew_def = self._crews[identifier]
|
|
766
|
+
del self._crews[identifier]
|
|
767
|
+
else:
|
|
768
|
+
# Try by crew_id
|
|
769
|
+
for name, (crew, def_) in list(self._crews.items()):
|
|
770
|
+
if def_.crew_id == identifier:
|
|
771
|
+
crew_name = name
|
|
772
|
+
crew_def = def_
|
|
773
|
+
del self._crews[name]
|
|
774
|
+
break
|
|
775
|
+
|
|
776
|
+
if crew_name and crew_def:
|
|
777
|
+
# Remove from Redis
|
|
778
|
+
try:
|
|
779
|
+
await self.crew_redis.delete_crew(crew_def.name)
|
|
780
|
+
self.logger.info(
|
|
781
|
+
f"Removed crew '{crew_name}' (ID: {crew_def.crew_id}) "
|
|
782
|
+
f"from memory and Redis"
|
|
783
|
+
)
|
|
784
|
+
except Exception as e:
|
|
785
|
+
self.logger.error(
|
|
786
|
+
f"Failed to delete crew '{crew_name}' from Redis: {e}"
|
|
787
|
+
)
|
|
788
|
+
self.logger.info(
|
|
789
|
+
f"Crew '{crew_name}' removed from memory only"
|
|
790
|
+
)
|
|
791
|
+
return True
|
|
792
|
+
|
|
793
|
+
return False
|
|
794
|
+
|
|
795
|
+
def update_crew(
|
|
796
|
+
self,
|
|
797
|
+
identifier: str,
|
|
798
|
+
crew: AgentCrew,
|
|
799
|
+
crew_def: CrewDefinition
|
|
800
|
+
) -> bool:
|
|
801
|
+
"""
|
|
802
|
+
Update an existing crew.
|
|
803
|
+
|
|
804
|
+
Args:
|
|
805
|
+
identifier: Crew name or crew_id
|
|
806
|
+
crew: Updated AgentCrew instance
|
|
807
|
+
crew_def: Updated crew definition
|
|
808
|
+
|
|
809
|
+
Returns:
|
|
810
|
+
True if updated, False if not found
|
|
811
|
+
"""
|
|
812
|
+
# Find crew by name or ID
|
|
813
|
+
crew_name = None
|
|
814
|
+
if identifier in self._crews:
|
|
815
|
+
crew_name = identifier
|
|
816
|
+
else:
|
|
817
|
+
for name, (_, def_) in self._crews.items():
|
|
818
|
+
if def_.crew_id == identifier:
|
|
819
|
+
crew_name = name
|
|
820
|
+
break
|
|
821
|
+
|
|
822
|
+
if crew_name:
|
|
823
|
+
self._crews[crew_name] = (crew, crew_def)
|
|
824
|
+
self.logger.info(f"Updated crew '{crew_name}'")
|
|
825
|
+
return True
|
|
826
|
+
|
|
827
|
+
return False
|
|
828
|
+
|
|
829
|
+
async def load_crews(self) -> None:
|
|
830
|
+
"""
|
|
831
|
+
Load all crews from Redis on startup.
|
|
832
|
+
|
|
833
|
+
This method is called during application startup to restore
|
|
834
|
+
all previously saved crews from Redis into memory.
|
|
835
|
+
"""
|
|
836
|
+
try:
|
|
837
|
+
# Check Redis connection
|
|
838
|
+
if not await self.crew_redis.ping():
|
|
839
|
+
self.logger.warning("Redis connection failed, skipping crew loading")
|
|
840
|
+
return
|
|
841
|
+
|
|
842
|
+
# Get all crew definitions from Redis
|
|
843
|
+
crew_defs = await self.crew_redis.get_all_crews()
|
|
844
|
+
|
|
845
|
+
if not crew_defs:
|
|
846
|
+
self.logger.info("No crews found in Redis")
|
|
847
|
+
return
|
|
848
|
+
|
|
849
|
+
self.logger.info(f"Loading {len(crew_defs)} crews from Redis...")
|
|
850
|
+
|
|
851
|
+
loaded_count = 0
|
|
852
|
+
for crew_def in crew_defs:
|
|
853
|
+
try:
|
|
854
|
+
# Reconstruct the crew from definition
|
|
855
|
+
crew = await self._create_crew_from_definition(crew_def)
|
|
856
|
+
|
|
857
|
+
# Add to memory (without saving back to Redis)
|
|
858
|
+
self._crews[crew_def.name] = (crew, crew_def)
|
|
859
|
+
|
|
860
|
+
loaded_count += 1
|
|
861
|
+
self.logger.info(
|
|
862
|
+
f"Loaded crew '{crew_def.name}' with {len(crew_def.agents)} agents "
|
|
863
|
+
f"in {crew_def.execution_mode.value} mode"
|
|
864
|
+
)
|
|
865
|
+
except Exception as e:
|
|
866
|
+
self.logger.error(
|
|
867
|
+
f"Failed to load crew '{crew_def.name}': {e}",
|
|
868
|
+
exc_info=True
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
self.logger.info(
|
|
872
|
+
f":: Crews loaded successfully. Total active crews: {loaded_count}"
|
|
873
|
+
)
|
|
874
|
+
except Exception as e:
|
|
875
|
+
self.logger.error(
|
|
876
|
+
f"Failed to load crews from Redis: {e}",
|
|
877
|
+
exc_info=True
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
async def _create_crew_from_definition(
|
|
881
|
+
self,
|
|
882
|
+
crew_def: CrewDefinition
|
|
883
|
+
) -> AgentCrew:
|
|
884
|
+
"""
|
|
885
|
+
Create an AgentCrew instance from a CrewDefinition.
|
|
886
|
+
|
|
887
|
+
This method reconstructs a crew from its JSON definition,
|
|
888
|
+
creating all agents and setting up flow relations.
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
crew_def: Crew definition
|
|
892
|
+
|
|
893
|
+
Returns:
|
|
894
|
+
AgentCrew instance
|
|
895
|
+
"""
|
|
896
|
+
from typing import List, Any
|
|
897
|
+
|
|
898
|
+
# Create agents
|
|
899
|
+
agents = []
|
|
900
|
+
for agent_def in crew_def.agents:
|
|
901
|
+
# Get agent class
|
|
902
|
+
agent_class = self.get_bot_class(agent_def.agent_class)
|
|
903
|
+
if not agent_class:
|
|
904
|
+
self.logger.warning(
|
|
905
|
+
f"Agent class '{agent_def.agent_class}' not found, "
|
|
906
|
+
f"using BasicAgent as fallback"
|
|
907
|
+
)
|
|
908
|
+
agent_class = BasicAgent
|
|
909
|
+
|
|
910
|
+
# Collect tools
|
|
911
|
+
tools = []
|
|
912
|
+
if agent_def.tools:
|
|
913
|
+
tools.extend(iter(agent_def.tools))
|
|
914
|
+
|
|
915
|
+
# Create agent instance
|
|
916
|
+
agent = agent_class(
|
|
917
|
+
name=agent_def.name or agent_def.agent_id,
|
|
918
|
+
tools=tools,
|
|
919
|
+
**agent_def.config
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
# Set system prompt if provided
|
|
923
|
+
if agent_def.system_prompt:
|
|
924
|
+
agent.system_prompt = agent_def.system_prompt
|
|
925
|
+
|
|
926
|
+
agents.append(agent)
|
|
927
|
+
|
|
928
|
+
# Create crew
|
|
929
|
+
crew = AgentCrew(
|
|
930
|
+
name=crew_def.name,
|
|
931
|
+
agents=agents,
|
|
932
|
+
max_parallel_tasks=crew_def.max_parallel_tasks
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
# Add shared tools
|
|
936
|
+
for tool_name in crew_def.shared_tools:
|
|
937
|
+
# Try to get tool from registry or bot manager
|
|
938
|
+
# This is a placeholder - implement tool retrieval as needed
|
|
939
|
+
try:
|
|
940
|
+
# You may need to implement get_tool method
|
|
941
|
+
# For now, we'll skip tools that aren't available
|
|
942
|
+
self.logger.debug(
|
|
943
|
+
f"Shared tool '{tool_name}' for crew '{crew_def.name}' "
|
|
944
|
+
f"(implement tool retrieval as needed)"
|
|
945
|
+
)
|
|
946
|
+
except Exception as e:
|
|
947
|
+
self.logger.warning(
|
|
948
|
+
f"Could not add shared tool '{tool_name}': {e}"
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
# Setup flow relations if in flow mode
|
|
952
|
+
if crew_def.execution_mode == ExecutionMode.FLOW and crew_def.flow_relations:
|
|
953
|
+
for relation in crew_def.flow_relations:
|
|
954
|
+
try:
|
|
955
|
+
# Convert agent IDs to agent objects
|
|
956
|
+
source_agents = self._get_agents_by_ids(
|
|
957
|
+
crew,
|
|
958
|
+
relation.source if isinstance(relation.source, list) else [relation.source]
|
|
959
|
+
)
|
|
960
|
+
target_agents = self._get_agents_by_ids(
|
|
961
|
+
crew,
|
|
962
|
+
relation.target if isinstance(relation.target, list) else [relation.target]
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
# Setup flow
|
|
966
|
+
crew.task_flow(
|
|
967
|
+
source_agents if len(source_agents) > 1 else source_agents[0],
|
|
968
|
+
target_agents if len(target_agents) > 1 else target_agents[0]
|
|
969
|
+
)
|
|
970
|
+
except Exception as e:
|
|
971
|
+
self.logger.error(
|
|
972
|
+
f"Failed to setup flow relation for crew '{crew_def.name}': {e}"
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
return crew
|
|
976
|
+
|
|
977
|
+
def _get_agents_by_ids(
|
|
978
|
+
self,
|
|
979
|
+
crew: AgentCrew,
|
|
980
|
+
agent_ids: List[str]
|
|
981
|
+
) -> List[Any]:
|
|
982
|
+
"""
|
|
983
|
+
Get agent objects from crew by their IDs.
|
|
984
|
+
|
|
985
|
+
Args:
|
|
986
|
+
crew: AgentCrew instance
|
|
987
|
+
agent_ids: List of agent IDs
|
|
988
|
+
|
|
989
|
+
Returns:
|
|
990
|
+
List of agent objects
|
|
991
|
+
"""
|
|
992
|
+
agents = []
|
|
993
|
+
for agent_id in agent_ids:
|
|
994
|
+
if agent := crew.agents.get(agent_id):
|
|
995
|
+
agents.append(agent)
|
|
996
|
+
else:
|
|
997
|
+
self.logger.warning(f"Agent '{agent_id}' not found in crew")
|
|
998
|
+
return agents
|
|
999
|
+
|
|
1000
|
+
def get_crew_stats(self) -> Dict[str, Any]:
|
|
1001
|
+
"""
|
|
1002
|
+
Get statistics about registered crews.
|
|
1003
|
+
|
|
1004
|
+
Returns:
|
|
1005
|
+
Dictionary with crew statistics
|
|
1006
|
+
"""
|
|
1007
|
+
stats = {
|
|
1008
|
+
'total_crews': len(self._crews),
|
|
1009
|
+
'crews_by_mode': {
|
|
1010
|
+
'sequential': 0,
|
|
1011
|
+
'parallel': 0,
|
|
1012
|
+
'flow': 0
|
|
1013
|
+
},
|
|
1014
|
+
'total_agents': 0,
|
|
1015
|
+
'crews': []
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
for name, (crew, crew_def) in self._crews.items():
|
|
1019
|
+
mode = crew_def.execution_mode.value
|
|
1020
|
+
stats['crews_by_mode'][mode] = stats['crews_by_mode'].get(mode, 0) + 1
|
|
1021
|
+
stats['total_agents'] += len(crew.agents)
|
|
1022
|
+
|
|
1023
|
+
stats['crews'].append({
|
|
1024
|
+
'name': name,
|
|
1025
|
+
'crew_id': crew_def.crew_id,
|
|
1026
|
+
'mode': mode,
|
|
1027
|
+
'agent_count': len(crew.agents)
|
|
1028
|
+
})
|
|
1029
|
+
|
|
1030
|
+
return stats
|