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/bots/agent.py
ADDED
|
@@ -0,0 +1,1129 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
from typing import Dict, List, Tuple, Any, Optional, Union, Callable
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import aiofiles
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from navconfig import BASE_DIR
|
|
9
|
+
from navconfig.logging import logging
|
|
10
|
+
from ..models.responses import AIMessage, AgentResponse
|
|
11
|
+
from ..clients.google import GoogleGenAIClient
|
|
12
|
+
from .chatbot import Chatbot
|
|
13
|
+
from .prompts import AGENT_PROMPT
|
|
14
|
+
from ..tools.abstract import AbstractTool
|
|
15
|
+
from ..tools.pythonrepl import PythonREPLTool
|
|
16
|
+
from ..tools.pythonpandas import PythonPandasTool
|
|
17
|
+
from ..tools.pdfprint import PDFPrintTool
|
|
18
|
+
from ..tools.powerpoint import PowerPointTool
|
|
19
|
+
from ..tools.agent import AgentTool, AgentContext
|
|
20
|
+
from ..models.google import (
|
|
21
|
+
ConversationalScriptConfig,
|
|
22
|
+
FictionalSpeaker
|
|
23
|
+
)
|
|
24
|
+
# MCP Integration
|
|
25
|
+
from ..mcp import (
|
|
26
|
+
MCPEnabledMixin,
|
|
27
|
+
MCPServerConfig,
|
|
28
|
+
MCPToolManager,
|
|
29
|
+
create_http_mcp_server,
|
|
30
|
+
create_local_mcp_server,
|
|
31
|
+
create_api_key_mcp_server
|
|
32
|
+
)
|
|
33
|
+
from ..conf import STATIC_DIR, AGENTS_DIR
|
|
34
|
+
from ..notifications import NotificationMixin
|
|
35
|
+
from ..memory import AgentMemory
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BasicAgent(MCPEnabledMixin, Chatbot, NotificationMixin):
|
|
39
|
+
"""Represents an Agent in Navigator.
|
|
40
|
+
|
|
41
|
+
Agents are chatbots that can access to Tools and execute commands.
|
|
42
|
+
Each Agent has a name, a role, a goal, a backstory,
|
|
43
|
+
and an optional language model (llm).
|
|
44
|
+
|
|
45
|
+
These agents are designed to interact with structured and unstructured data sources.
|
|
46
|
+
|
|
47
|
+
Features:
|
|
48
|
+
- Built-in MCP server support (no separate mixin needed)
|
|
49
|
+
- Can connect to HTTP, OAuth, API-key authenticated, and local MCP servers
|
|
50
|
+
- Automatic tool registration from MCP servers
|
|
51
|
+
- Compatible with all existing agent functionality
|
|
52
|
+
- Notification capabilities through various channels (e.g., email, Slack, Teams)
|
|
53
|
+
"""
|
|
54
|
+
agent_id: Optional[str] = None
|
|
55
|
+
agent_name: Optional[str] = None
|
|
56
|
+
_agent_response = AgentResponse
|
|
57
|
+
speech_context: str = ""
|
|
58
|
+
speech_system_prompt: str = ""
|
|
59
|
+
podcast_system_instruction: str = None
|
|
60
|
+
speech_length: int = 20 # Default length for the speech report
|
|
61
|
+
num_speakers: int = 1 # Default number of speakers for the podcast
|
|
62
|
+
speakers: Dict[str, str] = {
|
|
63
|
+
"interviewer": {
|
|
64
|
+
"name": "Lydia",
|
|
65
|
+
"role": "interviewer",
|
|
66
|
+
"characteristic": "Bright",
|
|
67
|
+
"gender": "female"
|
|
68
|
+
},
|
|
69
|
+
"interviewee": {
|
|
70
|
+
"name": "Brian",
|
|
71
|
+
"role": "interviewee",
|
|
72
|
+
"characteristic": "Informative",
|
|
73
|
+
"gender": "male"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
max_tokens: int = None # Use default max tokens from Chatbot
|
|
77
|
+
report_template: str = "report_template.html"
|
|
78
|
+
system_prompt_template: str = AGENT_PROMPT
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
name: str = 'Agent',
|
|
83
|
+
agent_id: str = 'agent',
|
|
84
|
+
use_llm: str = 'google',
|
|
85
|
+
llm: str = None,
|
|
86
|
+
tools: List[AbstractTool] = None,
|
|
87
|
+
system_prompt: str = None,
|
|
88
|
+
human_prompt: str = None,
|
|
89
|
+
use_tools: bool = True,
|
|
90
|
+
instructions: Optional[str] = None,
|
|
91
|
+
dataframes: Optional[Dict[str, pd.DataFrame]] = None,
|
|
92
|
+
**kwargs
|
|
93
|
+
):
|
|
94
|
+
# to work with dataframes:
|
|
95
|
+
self.dataframes = dataframes or {}
|
|
96
|
+
self._dataframe_info_cache = None
|
|
97
|
+
self.agent_id = self.agent_id or agent_id
|
|
98
|
+
self.agent_name = self.agent_name or name
|
|
99
|
+
tools = self._get_default_tools(tools)
|
|
100
|
+
super().__init__(
|
|
101
|
+
name=name,
|
|
102
|
+
llm=llm,
|
|
103
|
+
use_llm=use_llm,
|
|
104
|
+
system_prompt=system_prompt,
|
|
105
|
+
human_prompt=human_prompt,
|
|
106
|
+
tools=tools,
|
|
107
|
+
use_tools=use_tools,
|
|
108
|
+
**kwargs
|
|
109
|
+
)
|
|
110
|
+
if instructions:
|
|
111
|
+
self.goal = instructions
|
|
112
|
+
self.enable_tools = True # Enable tools by default
|
|
113
|
+
self.operation_mode = 'agentic' # Default operation mode
|
|
114
|
+
self.auto_tool_detection = True # Enable auto tool detection by default
|
|
115
|
+
## Logging:
|
|
116
|
+
self.logger = logging.getLogger(
|
|
117
|
+
f'{self.name}.Agent'
|
|
118
|
+
)
|
|
119
|
+
## Google GenAI Client (for multi-modal responses and TTS generation):
|
|
120
|
+
self.client = GoogleGenAIClient()
|
|
121
|
+
# Initialize the underlying AbstractBot LLM with the same client
|
|
122
|
+
if not self._llm:
|
|
123
|
+
self._llm = self.client
|
|
124
|
+
# install agent-specific tools:
|
|
125
|
+
self.tools = self.agent_tools()
|
|
126
|
+
self.tool_manager.register_tools(self.tools)
|
|
127
|
+
# Initialize MCP support
|
|
128
|
+
self.mcp_manager = MCPToolManager(
|
|
129
|
+
self.tool_manager
|
|
130
|
+
)
|
|
131
|
+
self.agent_memory = AgentMemory(
|
|
132
|
+
agent_id=self.agent_id
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _get_default_tools(self, tools: list) -> List[AbstractTool]:
|
|
136
|
+
"""Return Agent-specific tools."""
|
|
137
|
+
if not tools:
|
|
138
|
+
tools = []
|
|
139
|
+
tools.extend(
|
|
140
|
+
[
|
|
141
|
+
PythonREPLTool(
|
|
142
|
+
report_dir=AGENTS_DIR.joinpath(self.agent_id, 'documents')
|
|
143
|
+
),
|
|
144
|
+
]
|
|
145
|
+
)
|
|
146
|
+
return tools
|
|
147
|
+
|
|
148
|
+
def agent_tools(self) -> List[AbstractTool]:
|
|
149
|
+
"""Return the agent-specific tools."""
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
def set_response(self, response: AgentResponse):
|
|
153
|
+
"""Set the response for the agent."""
|
|
154
|
+
self._agent_response = response
|
|
155
|
+
|
|
156
|
+
async def setup_mcp_servers(self, configurations: List[MCPServerConfig]) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Setup multiple MCP servers during initialization.
|
|
159
|
+
|
|
160
|
+
This is useful for configuring an agent with multiple MCP servers
|
|
161
|
+
at once, typically during agent creation or from configuration files.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
configurations: List of MCPServerConfig objects
|
|
165
|
+
|
|
166
|
+
Example:
|
|
167
|
+
>>> configs = [
|
|
168
|
+
... create_http_mcp_server("weather", "https://api.weather.com/mcp"),
|
|
169
|
+
... create_local_mcp_server("files", "./mcp_servers/files.py")
|
|
170
|
+
... ]
|
|
171
|
+
>>> await agent.setup_mcp_servers(configs)
|
|
172
|
+
"""
|
|
173
|
+
for config in configurations:
|
|
174
|
+
try:
|
|
175
|
+
tools = await self.add_mcp_server(config)
|
|
176
|
+
self.logger.info(
|
|
177
|
+
f"Added MCP server '{config.name}' with tools: {tools}"
|
|
178
|
+
)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
self.logger.error(
|
|
181
|
+
f"Failed to add MCP server '{config.name}': {e}",
|
|
182
|
+
exc_info=True
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _create_filename(self, prefix: str = 'report', extension: str = 'pdf') -> str:
|
|
186
|
+
"""Create a unique filename for the report."""
|
|
187
|
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
188
|
+
return f"{prefix}_{timestamp}.{extension}"
|
|
189
|
+
|
|
190
|
+
async def save_document(
|
|
191
|
+
self,
|
|
192
|
+
content: str,
|
|
193
|
+
prefix: str = 'report',
|
|
194
|
+
extension: str = 'txt',
|
|
195
|
+
directory: Optional[Path] = None,
|
|
196
|
+
subdir: str = 'documents'
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Save the document to a file."""
|
|
199
|
+
report_filename = self._create_filename(
|
|
200
|
+
prefix=prefix, extension=extension
|
|
201
|
+
)
|
|
202
|
+
if not directory:
|
|
203
|
+
directory = STATIC_DIR.joinpath(self.agent_id, subdir)
|
|
204
|
+
try:
|
|
205
|
+
async with aiofiles.open(
|
|
206
|
+
directory.joinpath(report_filename),
|
|
207
|
+
'w'
|
|
208
|
+
) as report_file:
|
|
209
|
+
await report_file.write(content)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
self.logger.error(
|
|
212
|
+
f"Failed to save document {report_filename}: {e}"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
async def open_prompt(self, prompt_file: str = None) -> str:
|
|
216
|
+
"""
|
|
217
|
+
Opens a prompt file and returns its content.
|
|
218
|
+
"""
|
|
219
|
+
if not prompt_file:
|
|
220
|
+
raise ValueError("No prompt file specified.")
|
|
221
|
+
file = AGENTS_DIR.joinpath(self.agent_id, 'prompts', prompt_file)
|
|
222
|
+
try:
|
|
223
|
+
async with aiofiles.open(file, 'r') as f:
|
|
224
|
+
content = await f.read()
|
|
225
|
+
return content
|
|
226
|
+
except Exception as e:
|
|
227
|
+
self.logger.error(
|
|
228
|
+
f"Failed to read prompt file {prompt_file}: {e}"
|
|
229
|
+
)
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
async def open_query(self, query: str, directory: Optional[Path] = None, **kwargs) -> str:
|
|
233
|
+
"""
|
|
234
|
+
Opens a query string and formats it with provided keyword arguments.
|
|
235
|
+
"""
|
|
236
|
+
if not query:
|
|
237
|
+
raise ValueError("No query specified.")
|
|
238
|
+
if not directory:
|
|
239
|
+
directory = AGENTS_DIR.joinpath(self.agent_id, 'queries')
|
|
240
|
+
try:
|
|
241
|
+
query_file = directory.joinpath(query)
|
|
242
|
+
return query_file.read_text().format(**kwargs)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
self.logger.error(
|
|
245
|
+
f"Failed to format query: {e}"
|
|
246
|
+
)
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
async def generate_report(
|
|
250
|
+
self,
|
|
251
|
+
prompt_file: str,
|
|
252
|
+
save: bool = False,
|
|
253
|
+
directory: Optional[Path] = None,
|
|
254
|
+
**kwargs
|
|
255
|
+
) -> Tuple[AIMessage, AgentResponse]:
|
|
256
|
+
"""Generate a report based on the provided prompt."""
|
|
257
|
+
try:
|
|
258
|
+
query = await self.open_prompt(prompt_file)
|
|
259
|
+
query = textwrap.dedent(query)
|
|
260
|
+
except (ValueError, RuntimeError) as e:
|
|
261
|
+
self.logger.error(f"Error opening prompt file: {e}")
|
|
262
|
+
return str(e)
|
|
263
|
+
# Format the question based on keyword arguments:
|
|
264
|
+
question = query.format(**kwargs)
|
|
265
|
+
if not directory:
|
|
266
|
+
directory = STATIC_DIR.joinpath(self.agent_id, 'documents')
|
|
267
|
+
try:
|
|
268
|
+
response = await self.invoke(
|
|
269
|
+
question=question,
|
|
270
|
+
)
|
|
271
|
+
# Create the response object
|
|
272
|
+
final_report = response.output.strip()
|
|
273
|
+
if not final_report:
|
|
274
|
+
raise ValueError("The generated report is empty.")
|
|
275
|
+
response_data = self._agent_response(
|
|
276
|
+
session_id=response.turn_id,
|
|
277
|
+
data=final_report,
|
|
278
|
+
agent_name=self.name,
|
|
279
|
+
agent_id=self.agent_id,
|
|
280
|
+
response=response,
|
|
281
|
+
status="success",
|
|
282
|
+
created_at=datetime.now(),
|
|
283
|
+
output=response.output,
|
|
284
|
+
**kwargs
|
|
285
|
+
)
|
|
286
|
+
# before returning, we can save the report if needed:
|
|
287
|
+
if save:
|
|
288
|
+
try:
|
|
289
|
+
report_filename = self._create_filename(
|
|
290
|
+
prefix='report', extension='txt'
|
|
291
|
+
)
|
|
292
|
+
async with aiofiles.open(
|
|
293
|
+
directory.joinpath(report_filename),
|
|
294
|
+
'w'
|
|
295
|
+
) as report_file:
|
|
296
|
+
await report_file.write(final_report)
|
|
297
|
+
response_data.document_path = report_filename
|
|
298
|
+
self.logger.info(f"Report saved as {report_filename}")
|
|
299
|
+
except Exception as e:
|
|
300
|
+
self.logger.error(f"Error saving report: {e}")
|
|
301
|
+
return response, response_data
|
|
302
|
+
except Exception as e:
|
|
303
|
+
self.logger.error(f"Error generating report: {e}")
|
|
304
|
+
return str(e)
|
|
305
|
+
|
|
306
|
+
async def save_transcript(
|
|
307
|
+
self,
|
|
308
|
+
transcript: str,
|
|
309
|
+
filename: str = None,
|
|
310
|
+
prefix: str = 'transcript',
|
|
311
|
+
directory: Optional[str] = None,
|
|
312
|
+
subdir='transcripts'
|
|
313
|
+
) -> str:
|
|
314
|
+
"""Save the transcript to a file."""
|
|
315
|
+
if not directory:
|
|
316
|
+
directory = STATIC_DIR.joinpath(self.agent_id, subdir)
|
|
317
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
318
|
+
# Create a unique filename if not provided
|
|
319
|
+
if not filename:
|
|
320
|
+
filename = self._create_filename(prefix=prefix, extension='txt')
|
|
321
|
+
file_path = directory.joinpath(filename)
|
|
322
|
+
try:
|
|
323
|
+
async with aiofiles.open(file_path, 'w') as f:
|
|
324
|
+
await f.write(transcript)
|
|
325
|
+
self.logger.info(f"Transcript saved to {file_path}")
|
|
326
|
+
return file_path
|
|
327
|
+
except Exception as e:
|
|
328
|
+
self.logger.error(f"Error saving transcript: {e}")
|
|
329
|
+
raise RuntimeError(
|
|
330
|
+
f"Failed to save transcript: {e}"
|
|
331
|
+
) from e
|
|
332
|
+
|
|
333
|
+
async def pdf_report(
|
|
334
|
+
self,
|
|
335
|
+
content: str,
|
|
336
|
+
filename_prefix: str = 'report',
|
|
337
|
+
directory: Optional[Path] = None,
|
|
338
|
+
title: str = None,
|
|
339
|
+
**kwargs
|
|
340
|
+
) -> str:
|
|
341
|
+
"""Generate a report based on the provided prompt."""
|
|
342
|
+
# Create a unique filename for the report
|
|
343
|
+
if not directory:
|
|
344
|
+
directory = STATIC_DIR.joinpath(self.agent_id, 'documents')
|
|
345
|
+
pdf_tool = PDFPrintTool(
|
|
346
|
+
templates_dir=BASE_DIR.joinpath('templates'),
|
|
347
|
+
output_dir=directory
|
|
348
|
+
)
|
|
349
|
+
return await pdf_tool.execute(
|
|
350
|
+
text=content,
|
|
351
|
+
template_vars={"title": title or 'Report'},
|
|
352
|
+
template_name=self.report_template,
|
|
353
|
+
file_prefix=filename_prefix,
|
|
354
|
+
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
async def markdown_report(
|
|
358
|
+
self,
|
|
359
|
+
content: str,
|
|
360
|
+
filename: Optional[str] = None,
|
|
361
|
+
filename_prefix: str = 'report',
|
|
362
|
+
directory: Optional[Path] = None,
|
|
363
|
+
subdir: str = 'documents',
|
|
364
|
+
**kwargs
|
|
365
|
+
) -> str:
|
|
366
|
+
"""Saving Markdown report based on provided file."""
|
|
367
|
+
# Create a unique filename for the report
|
|
368
|
+
if not directory:
|
|
369
|
+
directory = STATIC_DIR.joinpath(self.agent_id, subdir)
|
|
370
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
371
|
+
# Create a unique filename if not provided
|
|
372
|
+
if not filename:
|
|
373
|
+
filename = self._create_filename(prefix=filename_prefix, extension='md')
|
|
374
|
+
file_path = directory.joinpath(filename)
|
|
375
|
+
try:
|
|
376
|
+
async with aiofiles.open(file_path, 'w') as f:
|
|
377
|
+
await f.write(content)
|
|
378
|
+
self.logger.info(f"Transcript saved to {file_path}")
|
|
379
|
+
return file_path
|
|
380
|
+
except Exception as e:
|
|
381
|
+
self.logger.error(f"Error saving transcript: {e}")
|
|
382
|
+
raise RuntimeError(
|
|
383
|
+
f"Failed to save transcript: {e}"
|
|
384
|
+
) from e
|
|
385
|
+
|
|
386
|
+
async def speech_report(
|
|
387
|
+
self,
|
|
388
|
+
report: str,
|
|
389
|
+
max_lines: int = 15,
|
|
390
|
+
num_speakers: int = 2,
|
|
391
|
+
podcast_instructions: Optional[str] = 'for_podcast.txt',
|
|
392
|
+
directory: Optional[Path] = None,
|
|
393
|
+
**kwargs
|
|
394
|
+
) -> Dict[str, Any]:
|
|
395
|
+
"""Generate a Transcript Report and a Podcast based on findings."""
|
|
396
|
+
if directory:
|
|
397
|
+
output_directory = directory
|
|
398
|
+
else:
|
|
399
|
+
output_directory = STATIC_DIR.joinpath(self.agent_id, 'generated_scripts')
|
|
400
|
+
output_directory.mkdir(parents=True, exist_ok=True)
|
|
401
|
+
script_name = self._create_filename(prefix='script', extension='txt')
|
|
402
|
+
# creation of speakers:
|
|
403
|
+
speakers = []
|
|
404
|
+
for _, speaker in self.speakers.items():
|
|
405
|
+
speaker['gender'] = speaker.get('gender', 'neutral').lower()
|
|
406
|
+
speakers.append(FictionalSpeaker(**speaker))
|
|
407
|
+
if len(speakers) > num_speakers:
|
|
408
|
+
self.logger.warning(
|
|
409
|
+
f"Too many speakers defined, limiting to {num_speakers}."
|
|
410
|
+
)
|
|
411
|
+
break
|
|
412
|
+
|
|
413
|
+
# 1. Define the script configuration
|
|
414
|
+
# Check if podcast_instructions is content or filename
|
|
415
|
+
if podcast_instructions and (
|
|
416
|
+
'\n' in podcast_instructions or len(podcast_instructions) > 100
|
|
417
|
+
):
|
|
418
|
+
# It's likely content (has newlines or is long), use it directly
|
|
419
|
+
podcast_instruction = podcast_instructions
|
|
420
|
+
else:
|
|
421
|
+
# It's a filename, load it
|
|
422
|
+
podcast_instruction = await self.open_prompt(
|
|
423
|
+
podcast_instructions or 'for_podcast.txt'
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Format the instruction with report text if it has placeholders
|
|
427
|
+
if podcast_instruction and '{report_text}' in podcast_instruction:
|
|
428
|
+
podcast_instruction = podcast_instruction.format(report_text=report)
|
|
429
|
+
script_config = ConversationalScriptConfig(
|
|
430
|
+
context=self.speech_context,
|
|
431
|
+
speakers=speakers,
|
|
432
|
+
report_text=report,
|
|
433
|
+
system_prompt=self.speech_system_prompt,
|
|
434
|
+
length=self.speech_length, # Use the speech_length attribute
|
|
435
|
+
system_instruction=podcast_instruction or None
|
|
436
|
+
)
|
|
437
|
+
async with self.client as client:
|
|
438
|
+
# 2. Generate the conversational script
|
|
439
|
+
response = await client.create_conversation_script(
|
|
440
|
+
report_data=script_config,
|
|
441
|
+
max_lines=max_lines, # Limit to 15 lines for brevity,
|
|
442
|
+
use_structured_output=True # Use structured output for TTS
|
|
443
|
+
)
|
|
444
|
+
voice_prompt = response.output
|
|
445
|
+
# 3. Save the script to a File:
|
|
446
|
+
script_output_path = output_directory.joinpath(script_name)
|
|
447
|
+
async with aiofiles.open(script_output_path, 'w') as script_file:
|
|
448
|
+
await script_file.write(voice_prompt.prompt)
|
|
449
|
+
self.logger.info(f"Script saved to {script_output_path}")
|
|
450
|
+
# 4. Generate the audio podcast
|
|
451
|
+
output_directory = STATIC_DIR.joinpath(self.agent_id, 'podcasts')
|
|
452
|
+
output_directory.mkdir(parents=True, exist_ok=True)
|
|
453
|
+
async with self.client as client:
|
|
454
|
+
speech_result = await client.generate_speech(
|
|
455
|
+
prompt_data=voice_prompt,
|
|
456
|
+
output_directory=output_directory,
|
|
457
|
+
)
|
|
458
|
+
if speech_result and speech_result.files:
|
|
459
|
+
print(f"✅ Multi-voice speech saved to: {speech_result.files[0]}")
|
|
460
|
+
# 5 Return the script and audio file paths
|
|
461
|
+
return {
|
|
462
|
+
'script_path': script_output_path,
|
|
463
|
+
'podcast_path': speech_result.files[0] if speech_result.files else None
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async def report(self, prompt_file: str, **kwargs) -> AgentResponse:
|
|
467
|
+
"""Generate a report based on the provided prompt."""
|
|
468
|
+
query = await self.open_prompt(prompt_file)
|
|
469
|
+
question = query.format(
|
|
470
|
+
**kwargs
|
|
471
|
+
)
|
|
472
|
+
try:
|
|
473
|
+
response = await self.conversation(
|
|
474
|
+
question=question,
|
|
475
|
+
max_tokens=8192
|
|
476
|
+
)
|
|
477
|
+
if isinstance(response, Exception):
|
|
478
|
+
raise response
|
|
479
|
+
except Exception as e:
|
|
480
|
+
print(f"Error invoking agent: {e}")
|
|
481
|
+
raise RuntimeError(
|
|
482
|
+
f"Failed to generate report due to an error in the agent invocation: {e}"
|
|
483
|
+
)
|
|
484
|
+
# Prepare the response object:
|
|
485
|
+
final_report = response.output.strip()
|
|
486
|
+
for key, value in kwargs.items():
|
|
487
|
+
if hasattr(response, key):
|
|
488
|
+
setattr(response, key, value)
|
|
489
|
+
response = self._agent_response(
|
|
490
|
+
user_id=str(kwargs.get('user_id', 1)),
|
|
491
|
+
agent_name=self.name,
|
|
492
|
+
attributes=kwargs.pop('attributes', {}),
|
|
493
|
+
data=final_report,
|
|
494
|
+
status="success",
|
|
495
|
+
created_at=datetime.now(),
|
|
496
|
+
output=response.output,
|
|
497
|
+
response=response,
|
|
498
|
+
**kwargs
|
|
499
|
+
)
|
|
500
|
+
return await self._generate_report(response)
|
|
501
|
+
|
|
502
|
+
async def _generate_report(
|
|
503
|
+
self,
|
|
504
|
+
response: AgentResponse,
|
|
505
|
+
with_speech: bool = True
|
|
506
|
+
) -> AgentResponse:
|
|
507
|
+
"""Generate a report from the response data."""
|
|
508
|
+
final_report = response.output.strip()
|
|
509
|
+
if not final_report:
|
|
510
|
+
response.output = "No report generated."
|
|
511
|
+
response.status = "error"
|
|
512
|
+
return response
|
|
513
|
+
response.transcript = final_report
|
|
514
|
+
try:
|
|
515
|
+
_path = await self.save_transcript(
|
|
516
|
+
transcript=final_report,
|
|
517
|
+
)
|
|
518
|
+
response.add_document(_path)
|
|
519
|
+
except Exception as e:
|
|
520
|
+
self.logger.error(f"Error generating transcript: {e}")
|
|
521
|
+
# generate the PDF file:
|
|
522
|
+
try:
|
|
523
|
+
pdf_output = await self.pdf_report(
|
|
524
|
+
content=final_report
|
|
525
|
+
)
|
|
526
|
+
response.set_pdf_path(
|
|
527
|
+
pdf_output.result.get('file_path', None)
|
|
528
|
+
)
|
|
529
|
+
except Exception as e:
|
|
530
|
+
self.logger.error(f"Error generating PDF: {e}")
|
|
531
|
+
# generate the podcast file:
|
|
532
|
+
if with_speech:
|
|
533
|
+
try:
|
|
534
|
+
podcast_output = await self.speech_report(
|
|
535
|
+
report=final_report,
|
|
536
|
+
max_lines=self.speech_length,
|
|
537
|
+
num_speakers=self.num_speakers
|
|
538
|
+
)
|
|
539
|
+
response.podcast_path = str(podcast_output.get('podcast_path', None))
|
|
540
|
+
response.script_path = str(podcast_output.get('script_path', None))
|
|
541
|
+
response.set_podcast_path(podcast_output.get('podcast_path', None))
|
|
542
|
+
except Exception as e:
|
|
543
|
+
self.logger.error(
|
|
544
|
+
f"Error generating podcast: {e}"
|
|
545
|
+
)
|
|
546
|
+
# Save the final report to the response
|
|
547
|
+
response.output = textwrap.fill(final_report, width=80)
|
|
548
|
+
response.status = "success"
|
|
549
|
+
return response
|
|
550
|
+
|
|
551
|
+
async def generate_presentation(
|
|
552
|
+
self,
|
|
553
|
+
content: str,
|
|
554
|
+
filename_prefix: str = 'report',
|
|
555
|
+
template_name: Optional[str] = None,
|
|
556
|
+
pptx_template: str = "corporate_template.pptx",
|
|
557
|
+
output_dir: Optional[Path] = None,
|
|
558
|
+
title: str = None,
|
|
559
|
+
**kwargs
|
|
560
|
+
):
|
|
561
|
+
"""Generate a PowerPoint presentation using the provided tool."""
|
|
562
|
+
if not output_dir:
|
|
563
|
+
output_dir = STATIC_DIR.joinpath(self.agent_id, 'documents')
|
|
564
|
+
tool = PowerPointTool(
|
|
565
|
+
templates_dir=BASE_DIR.joinpath('templates'),
|
|
566
|
+
output_dir=output_dir
|
|
567
|
+
)
|
|
568
|
+
return await tool.execute(
|
|
569
|
+
content=content,
|
|
570
|
+
template_name=None, # Explicitly disable HTML template
|
|
571
|
+
template_vars=None, # No template variables
|
|
572
|
+
split_by_headings=True, # Ensure heading-based splitting is enabled
|
|
573
|
+
pptx_template=pptx_template,
|
|
574
|
+
slide_layout=1,
|
|
575
|
+
title_styles={
|
|
576
|
+
"font_name": "Segoe UI",
|
|
577
|
+
"font_size": 24,
|
|
578
|
+
"bold": True,
|
|
579
|
+
"font_color": "#1f497d"
|
|
580
|
+
},
|
|
581
|
+
content_styles={
|
|
582
|
+
"font_name": "Segoe UI",
|
|
583
|
+
"font_size": 14,
|
|
584
|
+
"alignment": "left",
|
|
585
|
+
"font_color": "#333333"
|
|
586
|
+
},
|
|
587
|
+
max_slides=20,
|
|
588
|
+
file_prefix=filename_prefix,
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
async def create_speech(
|
|
592
|
+
self,
|
|
593
|
+
content: str,
|
|
594
|
+
language: str = "en-US",
|
|
595
|
+
only_script: bool = False,
|
|
596
|
+
**kwargs
|
|
597
|
+
) -> Dict[str, Any]:
|
|
598
|
+
"""Generate a Transcript Report and a Podcast based on findings."""
|
|
599
|
+
output_directory = STATIC_DIR.joinpath(self.agent_id, 'documents')
|
|
600
|
+
output_directory.mkdir(parents=True, exist_ok=True)
|
|
601
|
+
script_name = self._create_filename(prefix='script', extension='txt')
|
|
602
|
+
podcast_name = self._create_filename(prefix='podcast', extension='wav')
|
|
603
|
+
try:
|
|
604
|
+
async with self.client as client:
|
|
605
|
+
# 1. Generate the conversational script and podcast:
|
|
606
|
+
return await client.create_speech(
|
|
607
|
+
content=content,
|
|
608
|
+
output_directory=output_directory,
|
|
609
|
+
only_script=only_script,
|
|
610
|
+
script_file=script_name,
|
|
611
|
+
podcast_file=podcast_name,
|
|
612
|
+
language=language,
|
|
613
|
+
)
|
|
614
|
+
except Exception as e:
|
|
615
|
+
self.logger.error(
|
|
616
|
+
f"Error generating speech: {e}"
|
|
617
|
+
)
|
|
618
|
+
raise RuntimeError(
|
|
619
|
+
f"Failed to generate speech: {e}"
|
|
620
|
+
) from e
|
|
621
|
+
|
|
622
|
+
# =================================================================
|
|
623
|
+
# MCP Server Management Methods
|
|
624
|
+
# =================================================================
|
|
625
|
+
|
|
626
|
+
async def add_mcp_server(self, config: MCPServerConfig) -> List[str]:
|
|
627
|
+
"""
|
|
628
|
+
Add an MCP server and register its tools.
|
|
629
|
+
|
|
630
|
+
Args:
|
|
631
|
+
config: MCPServerConfig with connection details
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
List of registered tool names
|
|
635
|
+
|
|
636
|
+
Example:
|
|
637
|
+
>>> config = MCPServerConfig(
|
|
638
|
+
... name="weather_api",
|
|
639
|
+
... url="https://api.example.com/mcp",
|
|
640
|
+
... auth_type="api_key",
|
|
641
|
+
... auth_config={"api_key": "xxx"}
|
|
642
|
+
... )
|
|
643
|
+
>>> tools = await agent.add_mcp_server(config)
|
|
644
|
+
"""
|
|
645
|
+
return await self.mcp_manager.add_mcp_server(config)
|
|
646
|
+
|
|
647
|
+
async def add_mcp_server_url(
|
|
648
|
+
self,
|
|
649
|
+
name: str,
|
|
650
|
+
url: str,
|
|
651
|
+
auth_type: Optional[str] = None,
|
|
652
|
+
auth_config: Optional[Dict[str, Any]] = None,
|
|
653
|
+
headers: Optional[Dict[str, str]] = None,
|
|
654
|
+
allowed_tools: Optional[List[str]] = None,
|
|
655
|
+
blocked_tools: Optional[List[str]] = None,
|
|
656
|
+
**kwargs
|
|
657
|
+
) -> List[str]:
|
|
658
|
+
"""
|
|
659
|
+
Convenience method to add a public URL-based MCP server.
|
|
660
|
+
|
|
661
|
+
This is a simplified interface for adding HTTP MCP servers
|
|
662
|
+
without manually creating MCPServerConfig objects.
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
name: Unique name for the MCP server
|
|
666
|
+
url: Base URL of the MCP server
|
|
667
|
+
auth_type: Optional authentication type ('api_key', 'bearer', 'oauth', 'basic')
|
|
668
|
+
auth_config: Authentication configuration dict
|
|
669
|
+
headers: Additional HTTP headers
|
|
670
|
+
allowed_tools: Whitelist of tool names to register
|
|
671
|
+
blocked_tools: Blacklist of tool names to skip
|
|
672
|
+
**kwargs: Additional MCPServerConfig parameters
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
List of registered tool names
|
|
676
|
+
|
|
677
|
+
Examples:
|
|
678
|
+
>>> # Public server with no auth
|
|
679
|
+
>>> tools = await agent.add_mcp_server_url(
|
|
680
|
+
... "public_api",
|
|
681
|
+
... "https://api.example.com/mcp"
|
|
682
|
+
... )
|
|
683
|
+
|
|
684
|
+
>>> # API key authenticated server
|
|
685
|
+
>>> tools = await agent.add_mcp_server_url(
|
|
686
|
+
... "weather",
|
|
687
|
+
... "https://weather.example.com/mcp",
|
|
688
|
+
... auth_type="api_key",
|
|
689
|
+
... auth_config={"api_key": "your-key-here"}
|
|
690
|
+
... )
|
|
691
|
+
|
|
692
|
+
>>> # Server with custom headers and tool filtering
|
|
693
|
+
>>> tools = await agent.add_mcp_server_url(
|
|
694
|
+
... "finance",
|
|
695
|
+
... "https://finance.example.com/mcp",
|
|
696
|
+
... headers={"User-Agent": "AI-Parrot/1.0"},
|
|
697
|
+
... allowed_tools=["get_stock_price", "get_market_data"]
|
|
698
|
+
... )
|
|
699
|
+
"""
|
|
700
|
+
config = create_http_mcp_server(
|
|
701
|
+
name=name,
|
|
702
|
+
url=url,
|
|
703
|
+
auth_type=auth_type,
|
|
704
|
+
auth_config=auth_config,
|
|
705
|
+
headers=headers,
|
|
706
|
+
**kwargs
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
# Apply tool filtering if specified
|
|
710
|
+
if allowed_tools:
|
|
711
|
+
config.allowed_tools = allowed_tools
|
|
712
|
+
if blocked_tools:
|
|
713
|
+
config.blocked_tools = blocked_tools
|
|
714
|
+
|
|
715
|
+
return await self.add_mcp_server(config)
|
|
716
|
+
|
|
717
|
+
async def add_local_mcp_server(
|
|
718
|
+
self,
|
|
719
|
+
name: str,
|
|
720
|
+
script_path: Union[str, Path],
|
|
721
|
+
interpreter: str = "python",
|
|
722
|
+
**kwargs
|
|
723
|
+
) -> List[str]:
|
|
724
|
+
"""
|
|
725
|
+
Add a local stdio MCP server.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
name: Unique name for the MCP server
|
|
729
|
+
script_path: Path to the MCP server script
|
|
730
|
+
interpreter: Interpreter to use (default: "python")
|
|
731
|
+
**kwargs: Additional MCPServerConfig parameters
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
List of registered tool names
|
|
735
|
+
|
|
736
|
+
Example:
|
|
737
|
+
>>> tools = await agent.add_local_mcp_server(
|
|
738
|
+
... "local_tools",
|
|
739
|
+
... "/path/to/mcp_server.py"
|
|
740
|
+
... )
|
|
741
|
+
"""
|
|
742
|
+
config = create_local_mcp_server(name, script_path, interpreter, **kwargs)
|
|
743
|
+
return await self.add_mcp_server(config)
|
|
744
|
+
|
|
745
|
+
async def add_http_mcp_server(
|
|
746
|
+
self,
|
|
747
|
+
name: str,
|
|
748
|
+
url: str,
|
|
749
|
+
auth_type: Optional[str] = None,
|
|
750
|
+
auth_config: Optional[Dict[str, Any]] = None,
|
|
751
|
+
headers: Optional[Dict[str, str]] = None,
|
|
752
|
+
**kwargs
|
|
753
|
+
) -> List[str]:
|
|
754
|
+
"""
|
|
755
|
+
Add an HTTP MCP server with optional authentication.
|
|
756
|
+
|
|
757
|
+
This is an alias for add_mcp_server_url for backward compatibility.
|
|
758
|
+
|
|
759
|
+
Args:
|
|
760
|
+
name: Unique name for the MCP server
|
|
761
|
+
url: Base URL of the MCP server
|
|
762
|
+
auth_type: Optional authentication type
|
|
763
|
+
auth_config: Authentication configuration
|
|
764
|
+
headers: Additional HTTP headers
|
|
765
|
+
**kwargs: Additional MCPServerConfig parameters
|
|
766
|
+
|
|
767
|
+
Returns:
|
|
768
|
+
List of registered tool names
|
|
769
|
+
"""
|
|
770
|
+
config = create_http_mcp_server(
|
|
771
|
+
name, url, auth_type, auth_config, headers, **kwargs
|
|
772
|
+
)
|
|
773
|
+
return await self.add_mcp_server(config)
|
|
774
|
+
|
|
775
|
+
async def add_api_key_mcp_server(
|
|
776
|
+
self,
|
|
777
|
+
name: str,
|
|
778
|
+
url: str,
|
|
779
|
+
api_key: str,
|
|
780
|
+
header_name: str = "X-API-Key",
|
|
781
|
+
use_bearer_prefix: bool = False,
|
|
782
|
+
**kwargs
|
|
783
|
+
) -> List[str]:
|
|
784
|
+
"""
|
|
785
|
+
Add an API-key authenticated MCP server.
|
|
786
|
+
|
|
787
|
+
Args:
|
|
788
|
+
name: Unique name for the MCP server
|
|
789
|
+
url: Base URL of the MCP server
|
|
790
|
+
api_key: API key for authentication
|
|
791
|
+
header_name: Header name for the API key (default: "X-API-Key")
|
|
792
|
+
use_bearer_prefix: If True, prepend "Bearer " to the API key value (default: False)
|
|
793
|
+
**kwargs: Additional MCPServerConfig parameters
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
List of registered tool names
|
|
797
|
+
|
|
798
|
+
Example:
|
|
799
|
+
>>> tools = await agent.add_api_key_mcp_server(
|
|
800
|
+
... "weather_api",
|
|
801
|
+
... "https://api.weather.com/mcp",
|
|
802
|
+
... api_key="your-api-key",
|
|
803
|
+
... header_name="Authorization"
|
|
804
|
+
... )
|
|
805
|
+
|
|
806
|
+
>>> # For Bearer token format (e.g., Fireflies API)
|
|
807
|
+
>>> tools = await agent.add_api_key_mcp_server(
|
|
808
|
+
... "fireflies",
|
|
809
|
+
... "https://api.fireflies.ai/mcp",
|
|
810
|
+
... api_key="your-api-key",
|
|
811
|
+
... header_name="Authorization",
|
|
812
|
+
... use_bearer_prefix=True
|
|
813
|
+
... )
|
|
814
|
+
"""
|
|
815
|
+
config = create_api_key_mcp_server(
|
|
816
|
+
name=name,
|
|
817
|
+
url=url,
|
|
818
|
+
api_key=api_key,
|
|
819
|
+
header_name=header_name,
|
|
820
|
+
use_bearer_prefix=use_bearer_prefix,
|
|
821
|
+
**kwargs
|
|
822
|
+
)
|
|
823
|
+
return await self.add_mcp_server(config)
|
|
824
|
+
|
|
825
|
+
async def remove_mcp_server(self, server_name: str):
|
|
826
|
+
"""
|
|
827
|
+
Remove an MCP server and unregister its tools.
|
|
828
|
+
|
|
829
|
+
Args:
|
|
830
|
+
server_name: Name of the MCP server to remove
|
|
831
|
+
"""
|
|
832
|
+
await self.mcp_manager.remove_mcp_server(server_name)
|
|
833
|
+
self.logger.info(f"Removed MCP server: {server_name}")
|
|
834
|
+
|
|
835
|
+
def list_mcp_servers(self) -> List[str]:
|
|
836
|
+
"""
|
|
837
|
+
List all connected MCP servers.
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
List of MCP server names
|
|
841
|
+
"""
|
|
842
|
+
return self.mcp_manager.list_mcp_servers()
|
|
843
|
+
|
|
844
|
+
def get_mcp_client(self, server_name: str):
|
|
845
|
+
"""
|
|
846
|
+
Get the MCP client for a specific server.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
server_name: Name of the MCP server
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
MCPClient instance or None
|
|
853
|
+
"""
|
|
854
|
+
return self.mcp_manager.get_mcp_client(server_name)
|
|
855
|
+
|
|
856
|
+
async def shutdown(self, **kwargs):
|
|
857
|
+
"""
|
|
858
|
+
Shutdown the agent and disconnect all MCP servers.
|
|
859
|
+
"""
|
|
860
|
+
if hasattr(self, 'mcp_manager'):
|
|
861
|
+
await self.mcp_manager.disconnect_all()
|
|
862
|
+
self.logger.info("Disconnected all MCP servers")
|
|
863
|
+
|
|
864
|
+
if hasattr(super(), 'shutdown'):
|
|
865
|
+
await super().shutdown(**kwargs)
|
|
866
|
+
|
|
867
|
+
def as_tool(
|
|
868
|
+
self,
|
|
869
|
+
tool_name: str = None,
|
|
870
|
+
tool_description: str = None,
|
|
871
|
+
use_conversation_method: bool = True,
|
|
872
|
+
context_filter: Optional[Callable[[AgentContext], AgentContext]] = None
|
|
873
|
+
) -> 'AgentTool':
|
|
874
|
+
"""
|
|
875
|
+
Convert this agent into an AgentTool that can be used by other agents.
|
|
876
|
+
|
|
877
|
+
This allows agents to be composed and used as tools in orchestration scenarios.
|
|
878
|
+
|
|
879
|
+
Args:
|
|
880
|
+
tool_name: Custom name for the tool (defaults to agent name)
|
|
881
|
+
tool_description: Description of what this agent does
|
|
882
|
+
use_conversation_method: Whether to use conversation() or invoke()
|
|
883
|
+
context_filter: Optional function to filter context before execution
|
|
884
|
+
question_description: Custom description for the query parameter
|
|
885
|
+
context_description: Custom description for the context parameter
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
AgentTool: Tool wrapper for this agent
|
|
889
|
+
|
|
890
|
+
Example:
|
|
891
|
+
>>> hr_agent = BasicAgent(name="HRAgent", ...)
|
|
892
|
+
>>> hr_tool = hr_agent.as_tool(
|
|
893
|
+
... tool_description="Handles HR policy questions"
|
|
894
|
+
... )
|
|
895
|
+
>>> orchestrator.tool_manager.add_tool(hr_tool)
|
|
896
|
+
"""
|
|
897
|
+
# Default descriptions based on agent properties
|
|
898
|
+
default_description = (
|
|
899
|
+
f"Specialized agent: {self.name}. "
|
|
900
|
+
f"Role: {self.role}. "
|
|
901
|
+
f"Goal: {self.goal}."
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
return AgentTool(
|
|
905
|
+
agent=self,
|
|
906
|
+
tool_name=tool_name,
|
|
907
|
+
tool_description=tool_description or default_description,
|
|
908
|
+
use_conversation_method=use_conversation_method,
|
|
909
|
+
context_filter=context_filter,
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
def register_as_tool(
|
|
913
|
+
self,
|
|
914
|
+
target_agent: 'BasicAgent',
|
|
915
|
+
tool_name: str = None,
|
|
916
|
+
tool_description: str = None,
|
|
917
|
+
**kwargs
|
|
918
|
+
) -> None:
|
|
919
|
+
"""
|
|
920
|
+
Register this agent as a tool in another agent's tool manager.
|
|
921
|
+
|
|
922
|
+
This is a convenience method that combines as_tool() and registration.
|
|
923
|
+
|
|
924
|
+
Args:
|
|
925
|
+
target_agent: The agent to register this tool with
|
|
926
|
+
tool_name: Custom name for the tool
|
|
927
|
+
tool_description: Description of what this agent does
|
|
928
|
+
**kwargs: Additional arguments for as_tool()
|
|
929
|
+
|
|
930
|
+
Example:
|
|
931
|
+
>>> hr_agent = BasicAgent(name="HRAgent", ...)
|
|
932
|
+
>>> employee_agent = BasicAgent(name="EmployeeAgent", ...)
|
|
933
|
+
>>> orchestrator = OrchestratorAgent(name="Orchestrator")
|
|
934
|
+
>>>
|
|
935
|
+
>>> hr_agent.register_as_tool(
|
|
936
|
+
... orchestrator,
|
|
937
|
+
... tool_description="Handles HR policies and procedures"
|
|
938
|
+
... )
|
|
939
|
+
>>> employee_agent.register_as_tool(
|
|
940
|
+
... orchestrator,
|
|
941
|
+
... tool_description="Manages employee data"
|
|
942
|
+
... )
|
|
943
|
+
"""
|
|
944
|
+
agent_tool = self.as_tool(
|
|
945
|
+
tool_name=tool_name,
|
|
946
|
+
tool_description=tool_description,
|
|
947
|
+
**kwargs
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
# Register in bot's tool manager
|
|
951
|
+
target_agent.tool_manager.add_tool(agent_tool)
|
|
952
|
+
|
|
953
|
+
# CRITICAL: Sync tools to LLM after registration
|
|
954
|
+
if hasattr(target_agent, '_sync_tools_to_llm'):
|
|
955
|
+
target_agent._sync_tools_to_llm()
|
|
956
|
+
|
|
957
|
+
self.logger.info(
|
|
958
|
+
f"Registered {self.name} as tool '{agent_tool.name}' "
|
|
959
|
+
f"in {target_agent.name}'s tool manager"
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
def add_dataframe(self, df, name: str = None):
|
|
963
|
+
"""
|
|
964
|
+
Add a dataframe to the agent and configure PythonPandasTool.
|
|
965
|
+
|
|
966
|
+
Args:
|
|
967
|
+
df: pandas DataFrame to add
|
|
968
|
+
name: Optional name for the dataframe. If None, uses df{index}
|
|
969
|
+
"""
|
|
970
|
+
if not isinstance(df, pd.DataFrame):
|
|
971
|
+
raise ValueError("Input must be a pandas DataFrame")
|
|
972
|
+
|
|
973
|
+
# Generate name if not provided
|
|
974
|
+
if name is None:
|
|
975
|
+
name = f"df{len(self.dataframes)}"
|
|
976
|
+
|
|
977
|
+
# Store dataframe
|
|
978
|
+
self.dataframes[name] = df
|
|
979
|
+
|
|
980
|
+
# Clear cache to regenerate dataframe info
|
|
981
|
+
self._dataframe_info_cache = None
|
|
982
|
+
|
|
983
|
+
# Add or update PythonPandasTool
|
|
984
|
+
self._configure_pandas_tool()
|
|
985
|
+
|
|
986
|
+
# Update system prompt
|
|
987
|
+
self._update_system_prompt_with_dataframes()
|
|
988
|
+
|
|
989
|
+
return self
|
|
990
|
+
|
|
991
|
+
def _configure_pandas_tool(self):
|
|
992
|
+
"""Add or reconfigure PythonPandasTool with current dataframes."""
|
|
993
|
+
# Check if tool already exists
|
|
994
|
+
pandas_tool = next(
|
|
995
|
+
(tool for tool in self.tools if isinstance(tool, PythonPandasTool)),
|
|
996
|
+
None,
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
if pandas_tool is None:
|
|
1000
|
+
# Create new PythonPandasTool
|
|
1001
|
+
pandas_tool = PythonPandasTool(
|
|
1002
|
+
dataframes=self.dataframes
|
|
1003
|
+
)
|
|
1004
|
+
self.tool_manager.add_tool(pandas_tool)
|
|
1005
|
+
else:
|
|
1006
|
+
# Update existing tool with new dataframes
|
|
1007
|
+
pandas_tool.dataframes = self.dataframes
|
|
1008
|
+
|
|
1009
|
+
def _generate_dataframe_info(self) -> str:
|
|
1010
|
+
"""Generate dataframe information for system prompt."""
|
|
1011
|
+
if not self.dataframes:
|
|
1012
|
+
return ""
|
|
1013
|
+
|
|
1014
|
+
if self._dataframe_info_cache is not None:
|
|
1015
|
+
return self._dataframe_info_cache
|
|
1016
|
+
|
|
1017
|
+
info_parts = ["# Available DataFrames\n"]
|
|
1018
|
+
|
|
1019
|
+
for name, df in self.dataframes.items():
|
|
1020
|
+
info_parts.extend(
|
|
1021
|
+
(
|
|
1022
|
+
f"\n## DataFrame: `{name}`",
|
|
1023
|
+
f"- Shape: {df.shape[0]} rows × {df.shape[1]} columns",
|
|
1024
|
+
f"- Columns: {', '.join(df.columns.tolist())}",
|
|
1025
|
+
"- Column Types:",
|
|
1026
|
+
)
|
|
1027
|
+
)
|
|
1028
|
+
info_parts.extend(f" - {col}: {dtype}" for col, dtype in df.dtypes.items())
|
|
1029
|
+
|
|
1030
|
+
# Add sample statistics for numeric columns
|
|
1031
|
+
numeric_cols = df.select_dtypes(include=['number']).columns
|
|
1032
|
+
if len(numeric_cols) > 0:
|
|
1033
|
+
info_parts.append("- Summary Statistics (numeric columns):")
|
|
1034
|
+
info_parts.extend(
|
|
1035
|
+
f" - {col}: min={df[col].min():.2f}, max={df[col].max():.2f}, mean={df[col].mean():.2f}"
|
|
1036
|
+
for col in numeric_cols[:5]
|
|
1037
|
+
)
|
|
1038
|
+
# Add sample rows
|
|
1039
|
+
info_parts.append(f"- Sample (first 3 rows):\n{df.head(3).to_string()}")
|
|
1040
|
+
|
|
1041
|
+
info_parts.append("\nUse PythonPandasTool to query and analyze these dataframes.")
|
|
1042
|
+
|
|
1043
|
+
self._dataframe_info_cache = "\n".join(info_parts)
|
|
1044
|
+
return self._dataframe_info_cache
|
|
1045
|
+
|
|
1046
|
+
def _update_system_prompt_with_dataframes(self):
|
|
1047
|
+
"""Inject dataframe information into system prompt."""
|
|
1048
|
+
df_info = self._generate_dataframe_info()
|
|
1049
|
+
|
|
1050
|
+
if not df_info:
|
|
1051
|
+
return
|
|
1052
|
+
|
|
1053
|
+
# Find the position to inject (before $pre_context or $context)
|
|
1054
|
+
if "$pre_context" in self.system_prompt_template:
|
|
1055
|
+
marker = "$pre_context"
|
|
1056
|
+
elif "$context" in self.system_prompt_template:
|
|
1057
|
+
marker = "$context"
|
|
1058
|
+
else:
|
|
1059
|
+
# Append at the end if no markers found
|
|
1060
|
+
self.system_prompt_template += f"\n\n{df_info}"
|
|
1061
|
+
return
|
|
1062
|
+
|
|
1063
|
+
# Inject before the marker
|
|
1064
|
+
parts = self.system_prompt_template.split(marker, 1)
|
|
1065
|
+
self.system_prompt_template = f"{parts[0]}{df_info}\n\n{marker}{parts[1]}"
|
|
1066
|
+
|
|
1067
|
+
def remove_dataframe(self, name: str):
|
|
1068
|
+
"""Remove a dataframe by name."""
|
|
1069
|
+
if name in self.dataframes:
|
|
1070
|
+
del self.dataframes[name]
|
|
1071
|
+
self._dataframe_info_cache = None
|
|
1072
|
+
self._configure_pandas_tool()
|
|
1073
|
+
self._update_system_prompt_with_dataframes()
|
|
1074
|
+
|
|
1075
|
+
async def followup(
|
|
1076
|
+
self,
|
|
1077
|
+
question: str,
|
|
1078
|
+
turn_id: str,
|
|
1079
|
+
data: Any,
|
|
1080
|
+
session_id: Optional[str] = None,
|
|
1081
|
+
user_id: Optional[str] = None,
|
|
1082
|
+
use_conversation_history: bool = True,
|
|
1083
|
+
memory: Optional[Any] = None,
|
|
1084
|
+
ctx: Optional[Any] = None,
|
|
1085
|
+
structured_output: Optional[Any] = None,
|
|
1086
|
+
output_mode: Any = None,
|
|
1087
|
+
format_kwargs: dict = None,
|
|
1088
|
+
return_structured: bool = True,
|
|
1089
|
+
**kwargs
|
|
1090
|
+
) -> AIMessage:
|
|
1091
|
+
"""Generate a follow-up question using a previous turn as context."""
|
|
1092
|
+
if not turn_id:
|
|
1093
|
+
raise ValueError("turn_id is required for follow-up questions")
|
|
1094
|
+
|
|
1095
|
+
session_id = session_id or str(uuid.uuid4())
|
|
1096
|
+
user_id = user_id or "anonymous"
|
|
1097
|
+
|
|
1098
|
+
previous_interaction = await self.agent_memory.get(turn_id)
|
|
1099
|
+
if not previous_interaction:
|
|
1100
|
+
raise ValueError(f"No conversation turn found for turn_id {turn_id}")
|
|
1101
|
+
|
|
1102
|
+
context_str = data if isinstance(data, str) else str(data)
|
|
1103
|
+
followup_prompt = (
|
|
1104
|
+
"Based on the previous question "
|
|
1105
|
+
f"{previous_interaction['question']} and answer {previous_interaction['answer']} "
|
|
1106
|
+
f"and using this data as context {context_str}, you need to answer this question:\n"
|
|
1107
|
+
f"{question}"
|
|
1108
|
+
)
|
|
1109
|
+
|
|
1110
|
+
return await self.ask(
|
|
1111
|
+
question=followup_prompt,
|
|
1112
|
+
session_id=session_id,
|
|
1113
|
+
user_id=user_id,
|
|
1114
|
+
use_conversation_history=use_conversation_history,
|
|
1115
|
+
memory=memory,
|
|
1116
|
+
ctx=ctx,
|
|
1117
|
+
structured_output=structured_output,
|
|
1118
|
+
output_mode=output_mode,
|
|
1119
|
+
format_kwargs=format_kwargs,
|
|
1120
|
+
return_structured=return_structured,
|
|
1121
|
+
**kwargs,
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
class Agent(BasicAgent):
|
|
1125
|
+
"""A general-purpose agent with no additional tools."""
|
|
1126
|
+
|
|
1127
|
+
def agent_tools(self) -> List[AbstractTool]:
|
|
1128
|
+
"""Return the agent-specific tools."""
|
|
1129
|
+
return []
|