ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentui/.prettierrc +15 -0
- agentui/QUICKSTART.md +272 -0
- agentui/README.md +59 -0
- agentui/env.example +16 -0
- agentui/jsconfig.json +14 -0
- agentui/package-lock.json +4242 -0
- agentui/package.json +34 -0
- agentui/scripts/postinstall/apply-patches.mjs +260 -0
- agentui/src/app.css +61 -0
- agentui/src/app.d.ts +13 -0
- agentui/src/app.html +12 -0
- agentui/src/components/LoadingSpinner.svelte +64 -0
- agentui/src/components/ThemeSwitcher.svelte +159 -0
- agentui/src/components/index.js +4 -0
- agentui/src/lib/api/bots.ts +60 -0
- agentui/src/lib/api/chat.ts +22 -0
- agentui/src/lib/api/http.ts +25 -0
- agentui/src/lib/components/BotCard.svelte +33 -0
- agentui/src/lib/components/ChatBubble.svelte +63 -0
- agentui/src/lib/components/Toast.svelte +21 -0
- agentui/src/lib/config.ts +20 -0
- agentui/src/lib/stores/auth.svelte.ts +73 -0
- agentui/src/lib/stores/theme.svelte.js +64 -0
- agentui/src/lib/stores/toast.svelte.ts +31 -0
- agentui/src/lib/utils/conversation.ts +39 -0
- agentui/src/routes/+layout.svelte +20 -0
- agentui/src/routes/+page.svelte +232 -0
- agentui/src/routes/login/+page.svelte +200 -0
- agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
- agentui/src/routes/talk/[agentId]/+page.ts +7 -0
- agentui/static/README.md +1 -0
- agentui/svelte.config.js +11 -0
- agentui/tailwind.config.ts +53 -0
- agentui/tsconfig.json +3 -0
- agentui/vite.config.ts +10 -0
- ai_parrot-0.17.2.dist-info/METADATA +472 -0
- ai_parrot-0.17.2.dist-info/RECORD +535 -0
- ai_parrot-0.17.2.dist-info/WHEEL +6 -0
- ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
- ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
- ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
- crew-builder/.prettierrc +15 -0
- crew-builder/QUICKSTART.md +259 -0
- crew-builder/README.md +113 -0
- crew-builder/env.example +17 -0
- crew-builder/jsconfig.json +14 -0
- crew-builder/package-lock.json +4182 -0
- crew-builder/package.json +37 -0
- crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
- crew-builder/src/app.css +62 -0
- crew-builder/src/app.d.ts +13 -0
- crew-builder/src/app.html +12 -0
- crew-builder/src/components/LoadingSpinner.svelte +64 -0
- crew-builder/src/components/ThemeSwitcher.svelte +149 -0
- crew-builder/src/components/index.js +9 -0
- crew-builder/src/lib/api/bots.ts +60 -0
- crew-builder/src/lib/api/chat.ts +80 -0
- crew-builder/src/lib/api/client.ts +56 -0
- crew-builder/src/lib/api/crew/crew.ts +136 -0
- crew-builder/src/lib/api/index.ts +5 -0
- crew-builder/src/lib/api/o365/auth.ts +65 -0
- crew-builder/src/lib/auth/auth.ts +54 -0
- crew-builder/src/lib/components/AgentNode.svelte +43 -0
- crew-builder/src/lib/components/BotCard.svelte +33 -0
- crew-builder/src/lib/components/ChatBubble.svelte +67 -0
- crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
- crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
- crew-builder/src/lib/components/JsonViewer.svelte +24 -0
- crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
- crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
- crew-builder/src/lib/components/Toast.svelte +67 -0
- crew-builder/src/lib/components/Toolbar.svelte +157 -0
- crew-builder/src/lib/components/index.ts +10 -0
- crew-builder/src/lib/config.ts +8 -0
- crew-builder/src/lib/stores/auth.svelte.ts +228 -0
- crew-builder/src/lib/stores/crewStore.ts +369 -0
- crew-builder/src/lib/stores/theme.svelte.js +145 -0
- crew-builder/src/lib/stores/toast.svelte.ts +69 -0
- crew-builder/src/lib/utils/conversation.ts +39 -0
- crew-builder/src/lib/utils/markdown.ts +122 -0
- crew-builder/src/lib/utils/talkHistory.ts +47 -0
- crew-builder/src/routes/+layout.svelte +20 -0
- crew-builder/src/routes/+page.svelte +539 -0
- crew-builder/src/routes/agents/+page.svelte +247 -0
- crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
- crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
- crew-builder/src/routes/builder/+page.svelte +204 -0
- crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
- crew-builder/src/routes/crew/ask/+page.ts +1 -0
- crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
- crew-builder/src/routes/login/+page.svelte +197 -0
- crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
- crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
- crew-builder/static/README.md +1 -0
- crew-builder/svelte.config.js +11 -0
- crew-builder/tailwind.config.ts +53 -0
- crew-builder/tsconfig.json +3 -0
- crew-builder/vite.config.ts +10 -0
- mcp_servers/calculator_server.py +309 -0
- parrot/__init__.py +27 -0
- parrot/__pycache__/__init__.cpython-310.pyc +0 -0
- parrot/__pycache__/version.cpython-310.pyc +0 -0
- parrot/_version.py +34 -0
- parrot/a2a/__init__.py +48 -0
- parrot/a2a/client.py +658 -0
- parrot/a2a/discovery.py +89 -0
- parrot/a2a/mixin.py +257 -0
- parrot/a2a/models.py +376 -0
- parrot/a2a/server.py +770 -0
- parrot/agents/__init__.py +29 -0
- parrot/bots/__init__.py +12 -0
- parrot/bots/a2a_agent.py +19 -0
- parrot/bots/abstract.py +3139 -0
- parrot/bots/agent.py +1129 -0
- parrot/bots/basic.py +9 -0
- parrot/bots/chatbot.py +669 -0
- parrot/bots/data.py +1618 -0
- parrot/bots/database/__init__.py +5 -0
- parrot/bots/database/abstract.py +3071 -0
- parrot/bots/database/cache.py +286 -0
- parrot/bots/database/models.py +468 -0
- parrot/bots/database/prompts.py +154 -0
- parrot/bots/database/retries.py +98 -0
- parrot/bots/database/router.py +269 -0
- parrot/bots/database/sql.py +41 -0
- parrot/bots/db/__init__.py +6 -0
- parrot/bots/db/abstract.py +556 -0
- parrot/bots/db/bigquery.py +602 -0
- parrot/bots/db/cache.py +85 -0
- parrot/bots/db/documentdb.py +668 -0
- parrot/bots/db/elastic.py +1014 -0
- parrot/bots/db/influx.py +898 -0
- parrot/bots/db/mock.py +96 -0
- parrot/bots/db/multi.py +783 -0
- parrot/bots/db/prompts.py +185 -0
- parrot/bots/db/sql.py +1255 -0
- parrot/bots/db/tools.py +212 -0
- parrot/bots/document.py +680 -0
- parrot/bots/hrbot.py +15 -0
- parrot/bots/kb.py +170 -0
- parrot/bots/mcp.py +36 -0
- parrot/bots/orchestration/README.md +463 -0
- parrot/bots/orchestration/__init__.py +1 -0
- parrot/bots/orchestration/agent.py +155 -0
- parrot/bots/orchestration/crew.py +3330 -0
- parrot/bots/orchestration/fsm.py +1179 -0
- parrot/bots/orchestration/hr.py +434 -0
- parrot/bots/orchestration/storage/__init__.py +4 -0
- parrot/bots/orchestration/storage/memory.py +100 -0
- parrot/bots/orchestration/storage/mixin.py +119 -0
- parrot/bots/orchestration/verify.py +202 -0
- parrot/bots/product.py +204 -0
- parrot/bots/prompts/__init__.py +96 -0
- parrot/bots/prompts/agents.py +155 -0
- parrot/bots/prompts/data.py +216 -0
- parrot/bots/prompts/output_generation.py +8 -0
- parrot/bots/scraper/__init__.py +3 -0
- parrot/bots/scraper/models.py +122 -0
- parrot/bots/scraper/scraper.py +1173 -0
- parrot/bots/scraper/templates.py +115 -0
- parrot/bots/stores/__init__.py +5 -0
- parrot/bots/stores/local.py +172 -0
- parrot/bots/webdev.py +81 -0
- parrot/cli.py +17 -0
- parrot/clients/__init__.py +16 -0
- parrot/clients/base.py +1491 -0
- parrot/clients/claude.py +1191 -0
- parrot/clients/factory.py +129 -0
- parrot/clients/google.py +4567 -0
- parrot/clients/gpt.py +1975 -0
- parrot/clients/grok.py +432 -0
- parrot/clients/groq.py +986 -0
- parrot/clients/hf.py +582 -0
- parrot/clients/models.py +18 -0
- parrot/conf.py +395 -0
- parrot/embeddings/__init__.py +9 -0
- parrot/embeddings/base.py +157 -0
- parrot/embeddings/google.py +98 -0
- parrot/embeddings/huggingface.py +74 -0
- parrot/embeddings/openai.py +84 -0
- parrot/embeddings/processor.py +88 -0
- parrot/exceptions.c +13868 -0
- parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/exceptions.pxd +22 -0
- parrot/exceptions.pxi +15 -0
- parrot/exceptions.pyx +44 -0
- parrot/generators/__init__.py +29 -0
- parrot/generators/base.py +200 -0
- parrot/generators/html.py +293 -0
- parrot/generators/react.py +205 -0
- parrot/generators/streamlit.py +203 -0
- parrot/generators/template.py +105 -0
- parrot/handlers/__init__.py +4 -0
- parrot/handlers/agent.py +861 -0
- parrot/handlers/agents/__init__.py +1 -0
- parrot/handlers/agents/abstract.py +900 -0
- parrot/handlers/bots.py +338 -0
- parrot/handlers/chat.py +915 -0
- parrot/handlers/creation.sql +192 -0
- parrot/handlers/crew/ARCHITECTURE.md +362 -0
- parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
- parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
- parrot/handlers/crew/__init__.py +0 -0
- parrot/handlers/crew/handler.py +801 -0
- parrot/handlers/crew/models.py +229 -0
- parrot/handlers/crew/redis_persistence.py +523 -0
- parrot/handlers/jobs/__init__.py +10 -0
- parrot/handlers/jobs/job.py +384 -0
- parrot/handlers/jobs/mixin.py +627 -0
- parrot/handlers/jobs/models.py +115 -0
- parrot/handlers/jobs/worker.py +31 -0
- parrot/handlers/models.py +596 -0
- parrot/handlers/o365_auth.py +105 -0
- parrot/handlers/stream.py +337 -0
- parrot/interfaces/__init__.py +6 -0
- parrot/interfaces/aws.py +143 -0
- parrot/interfaces/credentials.py +113 -0
- parrot/interfaces/database.py +27 -0
- parrot/interfaces/google.py +1123 -0
- parrot/interfaces/hierarchy.py +1227 -0
- parrot/interfaces/http.py +651 -0
- parrot/interfaces/images/__init__.py +0 -0
- parrot/interfaces/images/plugins/__init__.py +24 -0
- parrot/interfaces/images/plugins/abstract.py +58 -0
- parrot/interfaces/images/plugins/analisys.py +148 -0
- parrot/interfaces/images/plugins/classify.py +150 -0
- parrot/interfaces/images/plugins/classifybase.py +182 -0
- parrot/interfaces/images/plugins/detect.py +150 -0
- parrot/interfaces/images/plugins/exif.py +1103 -0
- parrot/interfaces/images/plugins/hash.py +52 -0
- parrot/interfaces/images/plugins/vision.py +104 -0
- parrot/interfaces/images/plugins/yolo.py +66 -0
- parrot/interfaces/images/plugins/zerodetect.py +197 -0
- parrot/interfaces/o365.py +978 -0
- parrot/interfaces/onedrive.py +822 -0
- parrot/interfaces/sharepoint.py +1435 -0
- parrot/interfaces/soap.py +257 -0
- parrot/loaders/__init__.py +8 -0
- parrot/loaders/abstract.py +1131 -0
- parrot/loaders/audio.py +199 -0
- parrot/loaders/basepdf.py +53 -0
- parrot/loaders/basevideo.py +1568 -0
- parrot/loaders/csv.py +409 -0
- parrot/loaders/docx.py +116 -0
- parrot/loaders/epubloader.py +316 -0
- parrot/loaders/excel.py +199 -0
- parrot/loaders/factory.py +55 -0
- parrot/loaders/files/__init__.py +0 -0
- parrot/loaders/files/abstract.py +39 -0
- parrot/loaders/files/html.py +26 -0
- parrot/loaders/files/text.py +63 -0
- parrot/loaders/html.py +152 -0
- parrot/loaders/markdown.py +442 -0
- parrot/loaders/pdf.py +373 -0
- parrot/loaders/pdfmark.py +320 -0
- parrot/loaders/pdftables.py +506 -0
- parrot/loaders/ppt.py +476 -0
- parrot/loaders/qa.py +63 -0
- parrot/loaders/splitters/__init__.py +10 -0
- parrot/loaders/splitters/base.py +138 -0
- parrot/loaders/splitters/md.py +228 -0
- parrot/loaders/splitters/token.py +143 -0
- parrot/loaders/txt.py +26 -0
- parrot/loaders/video.py +89 -0
- parrot/loaders/videolocal.py +218 -0
- parrot/loaders/videounderstanding.py +377 -0
- parrot/loaders/vimeo.py +167 -0
- parrot/loaders/web.py +599 -0
- parrot/loaders/youtube.py +504 -0
- parrot/manager/__init__.py +5 -0
- parrot/manager/manager.py +1030 -0
- parrot/mcp/__init__.py +28 -0
- parrot/mcp/adapter.py +105 -0
- parrot/mcp/cli.py +174 -0
- parrot/mcp/client.py +119 -0
- parrot/mcp/config.py +75 -0
- parrot/mcp/integration.py +842 -0
- parrot/mcp/oauth.py +933 -0
- parrot/mcp/server.py +225 -0
- parrot/mcp/transports/__init__.py +3 -0
- parrot/mcp/transports/base.py +279 -0
- parrot/mcp/transports/grpc_session.py +163 -0
- parrot/mcp/transports/http.py +312 -0
- parrot/mcp/transports/mcp.proto +108 -0
- parrot/mcp/transports/quic.py +1082 -0
- parrot/mcp/transports/sse.py +330 -0
- parrot/mcp/transports/stdio.py +309 -0
- parrot/mcp/transports/unix.py +395 -0
- parrot/mcp/transports/websocket.py +547 -0
- parrot/memory/__init__.py +16 -0
- parrot/memory/abstract.py +209 -0
- parrot/memory/agent.py +32 -0
- parrot/memory/cache.py +175 -0
- parrot/memory/core.py +555 -0
- parrot/memory/file.py +153 -0
- parrot/memory/mem.py +131 -0
- parrot/memory/redis.py +613 -0
- parrot/models/__init__.py +46 -0
- parrot/models/basic.py +118 -0
- parrot/models/compliance.py +208 -0
- parrot/models/crew.py +395 -0
- parrot/models/detections.py +654 -0
- parrot/models/generation.py +85 -0
- parrot/models/google.py +223 -0
- parrot/models/groq.py +23 -0
- parrot/models/openai.py +30 -0
- parrot/models/outputs.py +285 -0
- parrot/models/responses.py +938 -0
- parrot/notifications/__init__.py +743 -0
- parrot/openapi/__init__.py +3 -0
- parrot/openapi/components.yaml +641 -0
- parrot/openapi/config.py +322 -0
- parrot/outputs/__init__.py +32 -0
- parrot/outputs/formats/__init__.py +108 -0
- parrot/outputs/formats/altair.py +359 -0
- parrot/outputs/formats/application.py +122 -0
- parrot/outputs/formats/base.py +351 -0
- parrot/outputs/formats/bokeh.py +356 -0
- parrot/outputs/formats/card.py +424 -0
- parrot/outputs/formats/chart.py +436 -0
- parrot/outputs/formats/d3.py +255 -0
- parrot/outputs/formats/echarts.py +310 -0
- parrot/outputs/formats/generators/__init__.py +0 -0
- parrot/outputs/formats/generators/abstract.py +61 -0
- parrot/outputs/formats/generators/panel.py +145 -0
- parrot/outputs/formats/generators/streamlit.py +86 -0
- parrot/outputs/formats/generators/terminal.py +63 -0
- parrot/outputs/formats/holoviews.py +310 -0
- parrot/outputs/formats/html.py +147 -0
- parrot/outputs/formats/jinja2.py +46 -0
- parrot/outputs/formats/json.py +87 -0
- parrot/outputs/formats/map.py +933 -0
- parrot/outputs/formats/markdown.py +172 -0
- parrot/outputs/formats/matplotlib.py +237 -0
- parrot/outputs/formats/mixins/__init__.py +0 -0
- parrot/outputs/formats/mixins/emaps.py +855 -0
- parrot/outputs/formats/plotly.py +341 -0
- parrot/outputs/formats/seaborn.py +310 -0
- parrot/outputs/formats/table.py +397 -0
- parrot/outputs/formats/template_report.py +138 -0
- parrot/outputs/formats/yaml.py +125 -0
- parrot/outputs/formatter.py +152 -0
- parrot/outputs/templates/__init__.py +95 -0
- parrot/pipelines/__init__.py +0 -0
- parrot/pipelines/abstract.py +210 -0
- parrot/pipelines/detector.py +124 -0
- parrot/pipelines/models.py +90 -0
- parrot/pipelines/planogram.py +3002 -0
- parrot/pipelines/table.sql +97 -0
- parrot/plugins/__init__.py +106 -0
- parrot/plugins/importer.py +80 -0
- parrot/py.typed +0 -0
- parrot/registry/__init__.py +18 -0
- parrot/registry/registry.py +594 -0
- parrot/scheduler/__init__.py +1189 -0
- parrot/scheduler/models.py +60 -0
- parrot/security/__init__.py +16 -0
- parrot/security/prompt_injection.py +268 -0
- parrot/security/security_events.sql +25 -0
- parrot/services/__init__.py +1 -0
- parrot/services/mcp/__init__.py +8 -0
- parrot/services/mcp/config.py +13 -0
- parrot/services/mcp/server.py +295 -0
- parrot/services/o365_remote_auth.py +235 -0
- parrot/stores/__init__.py +7 -0
- parrot/stores/abstract.py +352 -0
- parrot/stores/arango.py +1090 -0
- parrot/stores/bigquery.py +1377 -0
- parrot/stores/cache.py +106 -0
- parrot/stores/empty.py +10 -0
- parrot/stores/faiss_store.py +1157 -0
- parrot/stores/kb/__init__.py +9 -0
- parrot/stores/kb/abstract.py +68 -0
- parrot/stores/kb/cache.py +165 -0
- parrot/stores/kb/doc.py +325 -0
- parrot/stores/kb/hierarchy.py +346 -0
- parrot/stores/kb/local.py +457 -0
- parrot/stores/kb/prompt.py +28 -0
- parrot/stores/kb/redis.py +659 -0
- parrot/stores/kb/store.py +115 -0
- parrot/stores/kb/user.py +374 -0
- parrot/stores/models.py +59 -0
- parrot/stores/pgvector.py +3 -0
- parrot/stores/postgres.py +2853 -0
- parrot/stores/utils/__init__.py +0 -0
- parrot/stores/utils/chunking.py +197 -0
- parrot/telemetry/__init__.py +3 -0
- parrot/telemetry/mixin.py +111 -0
- parrot/template/__init__.py +3 -0
- parrot/template/engine.py +259 -0
- parrot/tools/__init__.py +23 -0
- parrot/tools/abstract.py +644 -0
- parrot/tools/agent.py +363 -0
- parrot/tools/arangodbsearch.py +537 -0
- parrot/tools/arxiv_tool.py +188 -0
- parrot/tools/calculator/__init__.py +3 -0
- parrot/tools/calculator/operations/__init__.py +38 -0
- parrot/tools/calculator/operations/calculus.py +80 -0
- parrot/tools/calculator/operations/statistics.py +76 -0
- parrot/tools/calculator/tool.py +150 -0
- parrot/tools/cloudwatch.py +988 -0
- parrot/tools/codeinterpreter/__init__.py +127 -0
- parrot/tools/codeinterpreter/executor.py +371 -0
- parrot/tools/codeinterpreter/internals.py +473 -0
- parrot/tools/codeinterpreter/models.py +643 -0
- parrot/tools/codeinterpreter/prompts.py +224 -0
- parrot/tools/codeinterpreter/tool.py +664 -0
- parrot/tools/company_info/__init__.py +6 -0
- parrot/tools/company_info/tool.py +1138 -0
- parrot/tools/correlationanalysis.py +437 -0
- parrot/tools/database/abstract.py +286 -0
- parrot/tools/database/bq.py +115 -0
- parrot/tools/database/cache.py +284 -0
- parrot/tools/database/models.py +95 -0
- parrot/tools/database/pg.py +343 -0
- parrot/tools/databasequery.py +1159 -0
- parrot/tools/db.py +1800 -0
- parrot/tools/ddgo.py +370 -0
- parrot/tools/decorators.py +271 -0
- parrot/tools/dftohtml.py +282 -0
- parrot/tools/document.py +549 -0
- parrot/tools/ecs.py +819 -0
- parrot/tools/edareport.py +368 -0
- parrot/tools/elasticsearch.py +1049 -0
- parrot/tools/employees.py +462 -0
- parrot/tools/epson/__init__.py +96 -0
- parrot/tools/excel.py +683 -0
- parrot/tools/file/__init__.py +13 -0
- parrot/tools/file/abstract.py +76 -0
- parrot/tools/file/gcs.py +378 -0
- parrot/tools/file/local.py +284 -0
- parrot/tools/file/s3.py +511 -0
- parrot/tools/file/tmp.py +309 -0
- parrot/tools/file/tool.py +501 -0
- parrot/tools/file_reader.py +129 -0
- parrot/tools/flowtask/__init__.py +19 -0
- parrot/tools/flowtask/tool.py +761 -0
- parrot/tools/gittoolkit.py +508 -0
- parrot/tools/google/__init__.py +18 -0
- parrot/tools/google/base.py +169 -0
- parrot/tools/google/tools.py +1251 -0
- parrot/tools/googlelocation.py +5 -0
- parrot/tools/googleroutes.py +5 -0
- parrot/tools/googlesearch.py +5 -0
- parrot/tools/googlesitesearch.py +5 -0
- parrot/tools/googlevoice.py +2 -0
- parrot/tools/gvoice.py +695 -0
- parrot/tools/ibisworld/README.md +225 -0
- parrot/tools/ibisworld/__init__.py +11 -0
- parrot/tools/ibisworld/tool.py +366 -0
- parrot/tools/jiratoolkit.py +1718 -0
- parrot/tools/manager.py +1098 -0
- parrot/tools/math.py +152 -0
- parrot/tools/metadata.py +476 -0
- parrot/tools/msteams.py +1621 -0
- parrot/tools/msword.py +635 -0
- parrot/tools/multidb.py +580 -0
- parrot/tools/multistoresearch.py +369 -0
- parrot/tools/networkninja.py +167 -0
- parrot/tools/nextstop/__init__.py +4 -0
- parrot/tools/nextstop/base.py +286 -0
- parrot/tools/nextstop/employee.py +733 -0
- parrot/tools/nextstop/store.py +462 -0
- parrot/tools/notification.py +435 -0
- parrot/tools/o365/__init__.py +42 -0
- parrot/tools/o365/base.py +295 -0
- parrot/tools/o365/bundle.py +522 -0
- parrot/tools/o365/events.py +554 -0
- parrot/tools/o365/mail.py +992 -0
- parrot/tools/o365/onedrive.py +497 -0
- parrot/tools/o365/sharepoint.py +641 -0
- parrot/tools/openapi_toolkit.py +904 -0
- parrot/tools/openweather.py +527 -0
- parrot/tools/pdfprint.py +1001 -0
- parrot/tools/powerbi.py +518 -0
- parrot/tools/powerpoint.py +1113 -0
- parrot/tools/pricestool.py +146 -0
- parrot/tools/products/__init__.py +246 -0
- parrot/tools/prophet_tool.py +171 -0
- parrot/tools/pythonpandas.py +630 -0
- parrot/tools/pythonrepl.py +910 -0
- parrot/tools/qsource.py +436 -0
- parrot/tools/querytoolkit.py +395 -0
- parrot/tools/quickeda.py +827 -0
- parrot/tools/resttool.py +553 -0
- parrot/tools/retail/__init__.py +0 -0
- parrot/tools/retail/bby.py +528 -0
- parrot/tools/sandboxtool.py +703 -0
- parrot/tools/sassie/__init__.py +352 -0
- parrot/tools/scraping/__init__.py +7 -0
- parrot/tools/scraping/docs/select.md +466 -0
- parrot/tools/scraping/documentation.md +1278 -0
- parrot/tools/scraping/driver.py +436 -0
- parrot/tools/scraping/models.py +576 -0
- parrot/tools/scraping/options.py +85 -0
- parrot/tools/scraping/orchestrator.py +517 -0
- parrot/tools/scraping/readme.md +740 -0
- parrot/tools/scraping/tool.py +3115 -0
- parrot/tools/seasonaldetection.py +642 -0
- parrot/tools/shell_tool/__init__.py +5 -0
- parrot/tools/shell_tool/actions.py +408 -0
- parrot/tools/shell_tool/engine.py +155 -0
- parrot/tools/shell_tool/models.py +322 -0
- parrot/tools/shell_tool/tool.py +442 -0
- parrot/tools/site_search.py +214 -0
- parrot/tools/textfile.py +418 -0
- parrot/tools/think.py +378 -0
- parrot/tools/toolkit.py +298 -0
- parrot/tools/webapp_tool.py +187 -0
- parrot/tools/whatif.py +1279 -0
- parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
- parrot/tools/workday/__init__.py +6 -0
- parrot/tools/workday/models.py +1389 -0
- parrot/tools/workday/tool.py +1293 -0
- parrot/tools/yfinance_tool.py +306 -0
- parrot/tools/zipcode.py +217 -0
- parrot/utils/__init__.py +2 -0
- parrot/utils/helpers.py +73 -0
- parrot/utils/parsers/__init__.py +5 -0
- parrot/utils/parsers/toml.c +12078 -0
- parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/parsers/toml.pyx +21 -0
- parrot/utils/toml.py +11 -0
- parrot/utils/types.cpp +20936 -0
- parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/types.pyx +213 -0
- parrot/utils/uv.py +11 -0
- parrot/version.py +10 -0
- parrot/yaml-rs/Cargo.lock +350 -0
- parrot/yaml-rs/Cargo.toml +19 -0
- parrot/yaml-rs/pyproject.toml +19 -0
- parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
- parrot/yaml-rs/src/lib.rs +222 -0
- requirements/docker-compose.yml +24 -0
- requirements/requirements-dev.txt +21 -0
parrot/tools/manager.py
ADDED
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, List, Any, Union, Optional, Callable
|
|
3
|
+
from collections.abc import Generator
|
|
4
|
+
import asyncio
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import logging
|
|
7
|
+
from enum import Enum
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from .math import MathTool
|
|
10
|
+
from .abstract import AbstractTool, ToolResult
|
|
11
|
+
import aiohttp
|
|
12
|
+
from ..a2a.models import RegisteredAgent, AgentCard
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ToolDefinition:
|
|
17
|
+
"""Data structure for tool definition."""
|
|
18
|
+
"""Defines a tool with its name, description, input schema, and function."""
|
|
19
|
+
__slots__ = ('name', 'description', 'input_schema', 'function')
|
|
20
|
+
name: str
|
|
21
|
+
description: str
|
|
22
|
+
input_schema: Dict[str, Any]
|
|
23
|
+
function: Callable
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ToolFormat(Enum):
|
|
27
|
+
"""Enum for different tool format requirements by LLM providers."""
|
|
28
|
+
OPENAI = "openai"
|
|
29
|
+
ANTHROPIC = "anthropic"
|
|
30
|
+
GOOGLE = "google"
|
|
31
|
+
GROQ = "groq"
|
|
32
|
+
VERTEX = "vertex"
|
|
33
|
+
GENERIC = "generic"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ToolSchemaAdapter:
|
|
37
|
+
"""
|
|
38
|
+
Adapter class to convert tool schemas between different LLM provider formats.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def clean_schema_for_provider(
|
|
43
|
+
schema: Dict[str, Any],
|
|
44
|
+
provider: ToolFormat
|
|
45
|
+
) -> Dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
Clean and adapt tool schema for specific LLM provider requirements.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
schema: Original tool schema
|
|
51
|
+
provider: Target LLM provider format
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Cleaned schema compatible with the provider
|
|
55
|
+
"""
|
|
56
|
+
cleaned_schema = schema.copy()
|
|
57
|
+
|
|
58
|
+
# Remove internal metadata
|
|
59
|
+
cleaned_schema.pop('_tool_instance', None)
|
|
60
|
+
|
|
61
|
+
if provider in [ToolFormat.GOOGLE, ToolFormat.VERTEX]:
|
|
62
|
+
# Google/Vertex AI specific cleaning
|
|
63
|
+
return ToolSchemaAdapter._clean_for_google(cleaned_schema)
|
|
64
|
+
elif provider == ToolFormat.GROQ:
|
|
65
|
+
# Groq specific cleaning
|
|
66
|
+
return ToolSchemaAdapter._clean_for_groq(cleaned_schema)
|
|
67
|
+
elif provider in [ToolFormat.OPENAI, ToolFormat.ANTHROPIC]:
|
|
68
|
+
# OpenAI/Anthropic specific cleaning
|
|
69
|
+
return ToolSchemaAdapter._clean_for_openai(cleaned_schema)
|
|
70
|
+
else:
|
|
71
|
+
# Generic cleaning
|
|
72
|
+
return ToolSchemaAdapter._clean_generic(cleaned_schema)
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _clean_for_google(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
76
|
+
"""Clean schema for Google/Vertex AI compatibility."""
|
|
77
|
+
cleaned = schema.copy()
|
|
78
|
+
|
|
79
|
+
# Remove additionalProperties recursively
|
|
80
|
+
def remove_additional_properties(obj):
|
|
81
|
+
if isinstance(obj, dict):
|
|
82
|
+
# Remove additionalProperties
|
|
83
|
+
obj.pop('additionalProperties', None)
|
|
84
|
+
# Remove other unsupported properties
|
|
85
|
+
obj.pop('title', None) # Google doesn't use title in parameters
|
|
86
|
+
|
|
87
|
+
# Recursively clean nested objects
|
|
88
|
+
for _, value in obj.items():
|
|
89
|
+
remove_additional_properties(value)
|
|
90
|
+
elif isinstance(obj, list):
|
|
91
|
+
for item in obj:
|
|
92
|
+
remove_additional_properties(item)
|
|
93
|
+
|
|
94
|
+
if 'parameters' in cleaned:
|
|
95
|
+
remove_additional_properties(cleaned['parameters'])
|
|
96
|
+
|
|
97
|
+
return cleaned
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _clean_for_groq(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
101
|
+
"""Clean schema for Groq compatibility."""
|
|
102
|
+
cleaned = schema.copy()
|
|
103
|
+
|
|
104
|
+
def remove_unsupported_constraints(obj):
|
|
105
|
+
if isinstance(obj, dict):
|
|
106
|
+
# Remove validation constraints that Groq doesn't support
|
|
107
|
+
unsupported = [
|
|
108
|
+
"minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum",
|
|
109
|
+
"minLength", "maxLength", "pattern", "format",
|
|
110
|
+
"minItems", "maxItems", "uniqueItems",
|
|
111
|
+
"minProperties", "maxProperties"
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
for constraint in unsupported:
|
|
115
|
+
obj.pop(constraint, None)
|
|
116
|
+
|
|
117
|
+
# Set additionalProperties to false for objects
|
|
118
|
+
if obj.get("type") == "object":
|
|
119
|
+
obj["additionalProperties"] = False
|
|
120
|
+
|
|
121
|
+
# Recursively clean nested objects
|
|
122
|
+
for key, value in obj.items():
|
|
123
|
+
remove_unsupported_constraints(value)
|
|
124
|
+
elif isinstance(obj, list):
|
|
125
|
+
for item in obj:
|
|
126
|
+
remove_unsupported_constraints(item)
|
|
127
|
+
|
|
128
|
+
if 'parameters' in cleaned:
|
|
129
|
+
remove_unsupported_constraints(cleaned['parameters'])
|
|
130
|
+
|
|
131
|
+
return cleaned
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _clean_for_openai(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
135
|
+
"""Clean schema for OpenAI/Anthropic compatibility."""
|
|
136
|
+
cleaned = schema.copy()
|
|
137
|
+
|
|
138
|
+
def ensure_openai_object(obj):
|
|
139
|
+
if isinstance(obj, dict):
|
|
140
|
+
if obj.get("type") == "object":
|
|
141
|
+
props = obj.get("properties", {}) or {}
|
|
142
|
+
|
|
143
|
+
# Ensure additionalProperties is set
|
|
144
|
+
if "additionalProperties" not in obj:
|
|
145
|
+
obj["additionalProperties"] = False
|
|
146
|
+
|
|
147
|
+
# 🔑 Ensure 'required' exists and includes ALL properties
|
|
148
|
+
prop_keys = list(props.keys())
|
|
149
|
+
if "required" not in obj:
|
|
150
|
+
obj["required"] = prop_keys
|
|
151
|
+
else:
|
|
152
|
+
required = obj.get("required") or []
|
|
153
|
+
missing = [k for k in prop_keys if k not in required]
|
|
154
|
+
obj["required"] = required + missing
|
|
155
|
+
|
|
156
|
+
# Recurse into nested dicts/lists
|
|
157
|
+
for _, value in obj.items():
|
|
158
|
+
ensure_openai_object(value)
|
|
159
|
+
|
|
160
|
+
elif isinstance(obj, list):
|
|
161
|
+
for item in obj:
|
|
162
|
+
ensure_openai_object(item)
|
|
163
|
+
|
|
164
|
+
if 'parameters' in cleaned:
|
|
165
|
+
ensure_openai_object(cleaned['parameters'])
|
|
166
|
+
|
|
167
|
+
return cleaned
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def _clean_generic(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
171
|
+
"""Generic schema cleaning."""
|
|
172
|
+
cleaned = schema.copy()
|
|
173
|
+
|
|
174
|
+
# Remove internal metadata and ensure basic structure
|
|
175
|
+
cleaned.pop('_tool_instance', None)
|
|
176
|
+
|
|
177
|
+
# Ensure required fields exist
|
|
178
|
+
if 'parameters' not in cleaned:
|
|
179
|
+
cleaned['parameters'] = {
|
|
180
|
+
"type": "object",
|
|
181
|
+
"properties": {},
|
|
182
|
+
"required": []
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return cleaned
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ToolManager:
|
|
189
|
+
"""
|
|
190
|
+
Unified tool manager for handling tools across AbstractBot and AbstractClient.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(
|
|
194
|
+
self,
|
|
195
|
+
logger: Optional[logging.Logger] = None,
|
|
196
|
+
debug: bool = False
|
|
197
|
+
):
|
|
198
|
+
"""
|
|
199
|
+
Initialize tool manager.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
logger: Logger instance
|
|
203
|
+
"""
|
|
204
|
+
self._shared: Dict[str, Any] = {"dataframes": {}} # name -> (df, meta)
|
|
205
|
+
self._registered_agents: Dict[str, RegisteredAgent] = {}
|
|
206
|
+
self._result_hooks: List[Callable[[str, Any, Dict[str, Any]], None]] = []
|
|
207
|
+
self.logger = logger or logging.getLogger(self.__class__.__name__)
|
|
208
|
+
self._debug: bool = debug
|
|
209
|
+
self._tools: Dict[str, Union[ToolDefinition, AbstractTool]] = {}
|
|
210
|
+
self._categories: Dict[str, List[str]] = {}
|
|
211
|
+
# policy (tweak as required)
|
|
212
|
+
self.auto_share_dataframes: bool = True
|
|
213
|
+
self.auto_push_to_pandas: bool = True
|
|
214
|
+
self.pandas_tool_name: str = "python_pandas"
|
|
215
|
+
|
|
216
|
+
# Self-register the search tool
|
|
217
|
+
self.register_tool(
|
|
218
|
+
name="search_tools",
|
|
219
|
+
description="Search for available tools by name or description. Use this to find tools that can help with your task.",
|
|
220
|
+
input_schema={
|
|
221
|
+
"type": "object",
|
|
222
|
+
"properties": {
|
|
223
|
+
"query": {
|
|
224
|
+
"type": "string",
|
|
225
|
+
"description": "The search query to match against tool names and descriptions"
|
|
226
|
+
},
|
|
227
|
+
"limit": {
|
|
228
|
+
"type": "integer",
|
|
229
|
+
"description": "Maximum number of results to return",
|
|
230
|
+
"default": 15
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"required": ["query"]
|
|
234
|
+
},
|
|
235
|
+
function=self.search_tools
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def search_tools(self, query: str, limit: int = 15) -> str:
|
|
239
|
+
"""
|
|
240
|
+
Search for tools by name or description.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
query: Search query
|
|
244
|
+
limit: Max results
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
JSON string list of matching tools with descriptions
|
|
248
|
+
"""
|
|
249
|
+
query = query.lower().strip()
|
|
250
|
+
matches = []
|
|
251
|
+
|
|
252
|
+
for name, tool in self._tools.items():
|
|
253
|
+
if name == "search_tools":
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
# Get description
|
|
257
|
+
desc = ""
|
|
258
|
+
if hasattr(tool, 'description'):
|
|
259
|
+
desc = tool.description
|
|
260
|
+
elif isinstance(tool, dict):
|
|
261
|
+
desc = tool.get('description', '')
|
|
262
|
+
|
|
263
|
+
# Check match
|
|
264
|
+
if query in name.lower() or query in desc.lower():
|
|
265
|
+
matches.append({
|
|
266
|
+
"name": name,
|
|
267
|
+
"description": desc
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
# Sort by name and limit
|
|
271
|
+
matches.sort(key=lambda x: x['name'])
|
|
272
|
+
matches = matches[:limit]
|
|
273
|
+
|
|
274
|
+
if not matches:
|
|
275
|
+
return f"No tools found matching '{query}'. Try a different search term."
|
|
276
|
+
|
|
277
|
+
import json
|
|
278
|
+
return json.dumps(matches, indent=2)
|
|
279
|
+
|
|
280
|
+
def default_tools(self, tools: list = None) -> List[AbstractTool]:
|
|
281
|
+
if not tools:
|
|
282
|
+
tools = [
|
|
283
|
+
MathTool(),
|
|
284
|
+
]
|
|
285
|
+
self.register_tools(tools)
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def tools(self) -> List[AbstractTool]:
|
|
289
|
+
"""Get list of registered tool instances."""
|
|
290
|
+
return self._tools
|
|
291
|
+
|
|
292
|
+
def sync(self, other_manager: 'ToolManager') -> None:
|
|
293
|
+
"""
|
|
294
|
+
Sync tools from another ToolManager instance.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
other_manager: Another ToolManager instance to sync from
|
|
298
|
+
"""
|
|
299
|
+
if not isinstance(other_manager, ToolManager):
|
|
300
|
+
self.logger.error("Can only sync from another ToolManager instance")
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
for tool_name, tool in other_manager._tools.items():
|
|
304
|
+
if tool_name not in self._tools:
|
|
305
|
+
self._tools[tool_name] = tool
|
|
306
|
+
self.logger.debug(
|
|
307
|
+
f"Synchronized tool: {tool_name}"
|
|
308
|
+
)
|
|
309
|
+
else:
|
|
310
|
+
self.logger.debug(
|
|
311
|
+
f"Tool already exists, skipping: {tool_name}"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def add_tool(self, tool: Union[ToolDefinition, AbstractTool], name: Optional[str] = None) -> None:
|
|
315
|
+
"""
|
|
316
|
+
Add a tool to the manager.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
tool: Tool instance (AbstractTool or ToolDefinition)
|
|
320
|
+
name: Optional custom name for the tool
|
|
321
|
+
"""
|
|
322
|
+
tool_name = name or getattr(tool, 'name', None) or tool.__class__.__name__
|
|
323
|
+
if isinstance(tool, AbstractTool) or isinstance(tool, ToolDefinition):
|
|
324
|
+
self._tools[tool_name] = tool
|
|
325
|
+
self.logger.debug(
|
|
326
|
+
f"Registered tool: {tool_name}"
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
self.logger.error(
|
|
330
|
+
f"Unsupported tool type: {type(tool)}"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def register_tool(
|
|
334
|
+
self,
|
|
335
|
+
tool: Union[dict, ToolDefinition, AbstractTool] = None,
|
|
336
|
+
name: str = None,
|
|
337
|
+
description: str = None,
|
|
338
|
+
input_schema: Dict[str, Any] = None,
|
|
339
|
+
function: Callable = None,
|
|
340
|
+
) -> None:
|
|
341
|
+
"""
|
|
342
|
+
Register a tool in the unified format.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
tool: Tool instance (AbstractTool, ToolDefinition, or dict)
|
|
346
|
+
name: Optional custom name for the tool
|
|
347
|
+
"""
|
|
348
|
+
tool_name = tool.name if isinstance(tool, (ToolDefinition, AbstractTool)) else name
|
|
349
|
+
if tool_name in self._tools:
|
|
350
|
+
self.logger.warning(
|
|
351
|
+
f"Tool '{tool_name}' is already registered."
|
|
352
|
+
)
|
|
353
|
+
return
|
|
354
|
+
try:
|
|
355
|
+
if isinstance(tool, (ToolDefinition, AbstractTool)):
|
|
356
|
+
self._tools[tool_name] = tool
|
|
357
|
+
elif isinstance(tool, dict):
|
|
358
|
+
tool_name = tool.get('name')
|
|
359
|
+
if tool_name in self._tools:
|
|
360
|
+
self.logger.warning(f"Tool '{tool_name}' is already registered.")
|
|
361
|
+
return
|
|
362
|
+
self._tools[tool_name] = ToolDefinition(
|
|
363
|
+
name=tool_name,
|
|
364
|
+
description=tool.get('description', ''),
|
|
365
|
+
input_schema=tool.get('parameters', {}),
|
|
366
|
+
function=tool.get('_tool_instance')
|
|
367
|
+
)
|
|
368
|
+
elif name and description and input_schema and function:
|
|
369
|
+
# Create a ToolDefinition from the provided parameters
|
|
370
|
+
self._tools[tool_name] = ToolDefinition(
|
|
371
|
+
name=name,
|
|
372
|
+
description=description,
|
|
373
|
+
input_schema=input_schema,
|
|
374
|
+
function=function
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
# TODO: if provided a function and a name, create the input_schema based on instrospection
|
|
378
|
+
if not (name and description and input_schema and function):
|
|
379
|
+
self.logger.error(
|
|
380
|
+
f"Tool '{tool_name}' must be a ToolDefinition, AbstractTool, or provide all parameters: "
|
|
381
|
+
"name, description, input_schema, function."
|
|
382
|
+
)
|
|
383
|
+
raise ValueError(
|
|
384
|
+
"Tool must be a ToolDefinition, AbstractTool, or provide all parameters: "
|
|
385
|
+
"name, description, input_schema, function."
|
|
386
|
+
)
|
|
387
|
+
self.logger.debug(
|
|
388
|
+
f"Registered tool: {tool_name}"
|
|
389
|
+
)
|
|
390
|
+
except Exception as e:
|
|
391
|
+
self.logger.error(
|
|
392
|
+
f"Error registering tool: {e}"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
def register_tools(
|
|
396
|
+
self,
|
|
397
|
+
tools: List[Union[ToolDefinition, AbstractTool]]
|
|
398
|
+
) -> None:
|
|
399
|
+
"""
|
|
400
|
+
Register multiple tools from list or dictionary.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
tools: List of tools or dictionary of tools
|
|
404
|
+
"""
|
|
405
|
+
if not tools:
|
|
406
|
+
return
|
|
407
|
+
for tool in tools:
|
|
408
|
+
if isinstance(tool, str):
|
|
409
|
+
# If tool is a string, load it by name
|
|
410
|
+
self.load_tool(tool)
|
|
411
|
+
elif isinstance(tool, AbstractTool):
|
|
412
|
+
# Register AbstractTool instance directly
|
|
413
|
+
self.register_tool(tool)
|
|
414
|
+
elif isinstance(tool, ToolDefinition):
|
|
415
|
+
# Register ToolDefinition instance directly
|
|
416
|
+
self.register_tool(tool, tool.name)
|
|
417
|
+
elif isinstance(tool, dict):
|
|
418
|
+
# Register dictionary as a tool
|
|
419
|
+
self.register_tool(tool)
|
|
420
|
+
elif hasattr(tool, 'name'):
|
|
421
|
+
self.register_tool(tool, tool.name)
|
|
422
|
+
else:
|
|
423
|
+
self.logger.error(
|
|
424
|
+
f"Unsupported tool type: {type(tool)}"
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def load_tool(self, tool_name: str, **kwargs) -> bool:
|
|
428
|
+
"""
|
|
429
|
+
Load a tool by name.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
tool_name: Name of the tool to load
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Tool instance or None if not found
|
|
436
|
+
"""
|
|
437
|
+
if tool_name in self._tools:
|
|
438
|
+
return self._tools[tool_name]
|
|
439
|
+
|
|
440
|
+
tool_file = tool_name.lower().replace('tool', '')
|
|
441
|
+
try:
|
|
442
|
+
module = __import__(f"parrot.tools.{tool_file}", fromlist=[tool_name])
|
|
443
|
+
cls = getattr(module, tool_name)
|
|
444
|
+
self._tools[tool_name] = cls(**kwargs)
|
|
445
|
+
return True
|
|
446
|
+
except (ImportError, AttributeError) as e:
|
|
447
|
+
self.logger.error(
|
|
448
|
+
f"Error loading tool {tool_name}: {e}"
|
|
449
|
+
)
|
|
450
|
+
return False
|
|
451
|
+
|
|
452
|
+
def get_tool_schemas(
|
|
453
|
+
self,
|
|
454
|
+
provider_format: ToolFormat = ToolFormat.GENERIC
|
|
455
|
+
) -> List[Dict[str, Any]]:
|
|
456
|
+
"""
|
|
457
|
+
Get tool schemas formatted for specific LLM provider.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
provider_format: Target provider format
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
List of tool schemas compatible with the provider
|
|
464
|
+
"""
|
|
465
|
+
if not self._tools:
|
|
466
|
+
return []
|
|
467
|
+
|
|
468
|
+
client_tools = []
|
|
469
|
+
|
|
470
|
+
for tool_name, tool in self._tools.items():
|
|
471
|
+
try:
|
|
472
|
+
# Get tool schema
|
|
473
|
+
schema = self._extract_tool_schema(tool, tool_name)
|
|
474
|
+
|
|
475
|
+
if schema:
|
|
476
|
+
# Add tool instance reference for execution
|
|
477
|
+
schema['_tool_instance'] = tool
|
|
478
|
+
# Clean schema for provider compatibility
|
|
479
|
+
cleaned_schema = ToolSchemaAdapter.clean_schema_for_provider(
|
|
480
|
+
schema, provider_format
|
|
481
|
+
)
|
|
482
|
+
# Re-add tool instance after cleaning
|
|
483
|
+
cleaned_schema['_tool_instance'] = tool
|
|
484
|
+
client_tools.append(cleaned_schema)
|
|
485
|
+
|
|
486
|
+
except Exception as e:
|
|
487
|
+
self.logger.error(f"Error preparing tool {tool_name}: {e}")
|
|
488
|
+
|
|
489
|
+
return client_tools
|
|
490
|
+
|
|
491
|
+
def _extract_tool_schema(self, tool: Any, tool_name: str) -> Optional[Dict[str, Any]]:
|
|
492
|
+
"""
|
|
493
|
+
Extract schema from various tool formats.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
tool: Tool instance
|
|
497
|
+
tool_name: Tool name
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
Tool schema dictionary or None
|
|
501
|
+
"""
|
|
502
|
+
try:
|
|
503
|
+
# AbstractTool with get_tool_schema method
|
|
504
|
+
if hasattr(tool, 'get_tool_schema'):
|
|
505
|
+
return tool.get_tool_schema()
|
|
506
|
+
|
|
507
|
+
# ToolDefinition with input_schema
|
|
508
|
+
elif hasattr(tool, 'input_schema') and hasattr(tool, 'description'):
|
|
509
|
+
return {
|
|
510
|
+
"name": tool_name,
|
|
511
|
+
"description": tool.description,
|
|
512
|
+
"parameters": tool.input_schema
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
# Dictionary format
|
|
516
|
+
elif isinstance(tool, dict):
|
|
517
|
+
if 'name' in tool and 'parameters' in tool:
|
|
518
|
+
return tool
|
|
519
|
+
else:
|
|
520
|
+
# Try to construct from available fields
|
|
521
|
+
return {
|
|
522
|
+
"name": tool.get('name', tool_name),
|
|
523
|
+
"description": tool.get('description', f"Tool: {tool_name}"),
|
|
524
|
+
"parameters": tool.get('parameters', tool.get('input_schema', {}))
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
# Legacy format with name, description, input_schema attributes
|
|
528
|
+
elif hasattr(tool, 'name') and hasattr(tool, 'description'):
|
|
529
|
+
schema = getattr(tool, 'input_schema', {})
|
|
530
|
+
return {
|
|
531
|
+
"name": tool.name,
|
|
532
|
+
"description": tool.description,
|
|
533
|
+
"parameters": schema
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
else:
|
|
537
|
+
self.logger.warning(f"Unknown tool format for: {tool_name}")
|
|
538
|
+
return None
|
|
539
|
+
|
|
540
|
+
except Exception as e:
|
|
541
|
+
self.logger.error(
|
|
542
|
+
f"Error extracting schema for {tool_name}: {e}"
|
|
543
|
+
)
|
|
544
|
+
return None
|
|
545
|
+
|
|
546
|
+
def get_tool(self, tool_name: str) -> Optional[Any]:
|
|
547
|
+
"""
|
|
548
|
+
Get tool instance by name.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
tool_name: Name of the tool
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
Tool instance or None
|
|
555
|
+
"""
|
|
556
|
+
return self._tools.get(tool_name)
|
|
557
|
+
|
|
558
|
+
def list_categories(self) -> List[str]:
|
|
559
|
+
"""List available tool categories."""
|
|
560
|
+
return list(self._categories.keys())
|
|
561
|
+
|
|
562
|
+
def get_tools_by_category(self, category: str) -> List[str]:
|
|
563
|
+
"""Get tools by category."""
|
|
564
|
+
return self._categories.get(category, [])
|
|
565
|
+
|
|
566
|
+
def list_tools(self) -> List[str]:
|
|
567
|
+
"""Get list of registered tool names."""
|
|
568
|
+
return list(self._tools.keys())
|
|
569
|
+
|
|
570
|
+
def get_tools(self) -> Dict[str, Any]:
|
|
571
|
+
"""Get all registered tools."""
|
|
572
|
+
return self._tools.values()
|
|
573
|
+
|
|
574
|
+
def get_all_tools(self) -> List[Union[ToolDefinition, AbstractTool]]:
|
|
575
|
+
"""Get all registered tool instances."""
|
|
576
|
+
return list(self._tools.values())
|
|
577
|
+
|
|
578
|
+
def all_tools(self) -> Generator[Any, Any, Any]:
|
|
579
|
+
"""
|
|
580
|
+
Get all registered tools with their schemas as a generator.
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
List of tool schemas
|
|
584
|
+
"""
|
|
585
|
+
for tool in self._tools.values():
|
|
586
|
+
yield tool
|
|
587
|
+
|
|
588
|
+
def unregister_tool(self, tool_name: str) -> bool:
|
|
589
|
+
"""Unregister a tool by name."""
|
|
590
|
+
if tool_name in self._tools:
|
|
591
|
+
del self._tools[tool_name]
|
|
592
|
+
return True
|
|
593
|
+
return False
|
|
594
|
+
|
|
595
|
+
def clear_tools(self) -> None:
|
|
596
|
+
"""Clear all registered tools."""
|
|
597
|
+
self._tools.clear()
|
|
598
|
+
self._categories.clear()
|
|
599
|
+
self.logger.debug("Cleared all tools")
|
|
600
|
+
|
|
601
|
+
def remove_tool(self, tool_name: str) -> None:
|
|
602
|
+
"""
|
|
603
|
+
Remove a tool by name.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
tool_name: Name of the tool to remove
|
|
607
|
+
"""
|
|
608
|
+
if tool_name in self._tools:
|
|
609
|
+
del self._tools[tool_name]
|
|
610
|
+
self.logger.debug(
|
|
611
|
+
f"Removed tool: {tool_name}"
|
|
612
|
+
)
|
|
613
|
+
else:
|
|
614
|
+
self.logger.warning(f"Tool not found: {tool_name}")
|
|
615
|
+
|
|
616
|
+
def __repr__(self) -> str:
|
|
617
|
+
"""String representation of the ToolManager."""
|
|
618
|
+
return f"ToolManager(tools={list(self._tools.keys())})"
|
|
619
|
+
|
|
620
|
+
def __len__(self) -> int:
|
|
621
|
+
"""Get number of registered tools."""
|
|
622
|
+
return len(self._tools)
|
|
623
|
+
|
|
624
|
+
def build_tools_description(
|
|
625
|
+
self,
|
|
626
|
+
format_style: str = "compact",
|
|
627
|
+
include_parameters: bool = True,
|
|
628
|
+
include_examples: bool = False,
|
|
629
|
+
max_tools: Optional[int] = None
|
|
630
|
+
) -> str:
|
|
631
|
+
"""
|
|
632
|
+
Build formatted tool descriptions for system prompts.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
format_style: Style of formatting ("detailed", "compact", "list", "markdown")
|
|
636
|
+
include_parameters: Whether to include parameter details
|
|
637
|
+
include_examples: Whether to include usage examples
|
|
638
|
+
max_tools: Maximum number of tools to include (None for all)
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
Formatted string describing all available tools
|
|
642
|
+
"""
|
|
643
|
+
if not self._tools:
|
|
644
|
+
return "No tools available."
|
|
645
|
+
|
|
646
|
+
# Get tools to describe (limit if specified)
|
|
647
|
+
tools_to_describe = list(self._tools.items())
|
|
648
|
+
if max_tools:
|
|
649
|
+
tools_to_describe = tools_to_describe[:max_tools]
|
|
650
|
+
|
|
651
|
+
if format_style == "detailed":
|
|
652
|
+
return self._build_detailed_description(
|
|
653
|
+
tools_to_describe,
|
|
654
|
+
include_parameters,
|
|
655
|
+
include_examples
|
|
656
|
+
)
|
|
657
|
+
elif format_style == "compact":
|
|
658
|
+
return self._build_compact_description(tools_to_describe, include_parameters)
|
|
659
|
+
elif format_style == "list":
|
|
660
|
+
return self._build_list_description(tools_to_describe)
|
|
661
|
+
elif format_style == "markdown":
|
|
662
|
+
return self._build_markdown_description(
|
|
663
|
+
tools_to_describe,
|
|
664
|
+
include_parameters,
|
|
665
|
+
include_examples
|
|
666
|
+
)
|
|
667
|
+
else:
|
|
668
|
+
return self._build_detailed_description(
|
|
669
|
+
tools_to_describe,
|
|
670
|
+
include_parameters,
|
|
671
|
+
include_examples
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
def _build_detailed_description(
|
|
675
|
+
self,
|
|
676
|
+
tools: List[tuple],
|
|
677
|
+
include_parameters: bool,
|
|
678
|
+
include_examples: bool
|
|
679
|
+
) -> str:
|
|
680
|
+
"""Build detailed tool descriptions."""
|
|
681
|
+
descriptions = ["=== AVAILABLE TOOLS ===\n"]
|
|
682
|
+
|
|
683
|
+
for i, (tool_name, tool) in enumerate(tools, 1):
|
|
684
|
+
try:
|
|
685
|
+
schema = self._extract_tool_schema(tool, tool_name)
|
|
686
|
+
if not schema:
|
|
687
|
+
continue
|
|
688
|
+
|
|
689
|
+
# Tool header
|
|
690
|
+
descriptions.append(f"{i}. {schema['name']}: {schema['description']}")
|
|
691
|
+
|
|
692
|
+
# Parameters section
|
|
693
|
+
if include_parameters and 'parameters' in schema:
|
|
694
|
+
params = schema['parameters'].get('properties', {})
|
|
695
|
+
required = schema['parameters'].get('required', [])
|
|
696
|
+
|
|
697
|
+
if params:
|
|
698
|
+
descriptions.append(" Parameters:")
|
|
699
|
+
for param_name, param_info in params.items():
|
|
700
|
+
param_type = param_info.get('type', 'unknown')
|
|
701
|
+
param_desc = param_info.get('description', 'No description')
|
|
702
|
+
required_marker = " (required)" if param_name in required else " (optional)"
|
|
703
|
+
descriptions.append(f" - {param_name} ({param_type}){required_marker}: {param_desc}")
|
|
704
|
+
|
|
705
|
+
# Usage example
|
|
706
|
+
if include_examples:
|
|
707
|
+
descriptions.append(f" Usage: Call {schema['name']} when you need to {schema['description'].lower()}")
|
|
708
|
+
|
|
709
|
+
descriptions.append("") # Empty line between tools
|
|
710
|
+
|
|
711
|
+
except Exception as e:
|
|
712
|
+
self.logger.error(f"Error building description for {tool_name}: {e}")
|
|
713
|
+
descriptions.append(f"{i}. {tool_name}: Error getting tool information")
|
|
714
|
+
descriptions.append("")
|
|
715
|
+
|
|
716
|
+
descriptions.append(
|
|
717
|
+
"Use these tools when appropriate to answer the question effectively."
|
|
718
|
+
)
|
|
719
|
+
return "\n".join(descriptions)
|
|
720
|
+
|
|
721
|
+
def _build_compact_description(self, tools: List[tuple], include_parameters: bool) -> str:
|
|
722
|
+
"""Build compact tool descriptions."""
|
|
723
|
+
descriptions = ["Available tools: "]
|
|
724
|
+
tool_summaries = []
|
|
725
|
+
|
|
726
|
+
for tool_name, tool in tools:
|
|
727
|
+
try:
|
|
728
|
+
schema = self._extract_tool_schema(tool, tool_name)
|
|
729
|
+
if not schema:
|
|
730
|
+
continue
|
|
731
|
+
|
|
732
|
+
summary = f"{schema['name']}"
|
|
733
|
+
|
|
734
|
+
if include_parameters and 'parameters' in schema:
|
|
735
|
+
params = schema['parameters'].get('properties', {})
|
|
736
|
+
if params:
|
|
737
|
+
param_names = list(params.keys())[:3] # First 3 params
|
|
738
|
+
param_str = ", ".join(param_names)
|
|
739
|
+
if len(params) > 3:
|
|
740
|
+
param_str += "..."
|
|
741
|
+
summary += f"({param_str})"
|
|
742
|
+
|
|
743
|
+
summary += f" - {schema['description']}"
|
|
744
|
+
tool_summaries.append(summary)
|
|
745
|
+
|
|
746
|
+
except Exception as e:
|
|
747
|
+
self.logger.error(f"Error building compact description for {tool_name}: {e}")
|
|
748
|
+
tool_summaries.append(f"{tool_name} - Tool information unavailable")
|
|
749
|
+
|
|
750
|
+
descriptions.extend(tool_summaries)
|
|
751
|
+
return "; ".join(descriptions) + "."
|
|
752
|
+
|
|
753
|
+
def _build_list_description(self, tools: List[tuple]) -> str:
|
|
754
|
+
"""Build simple list of tool names and descriptions."""
|
|
755
|
+
descriptions = ["Available tools:\n"]
|
|
756
|
+
|
|
757
|
+
for tool_name, tool in tools:
|
|
758
|
+
try:
|
|
759
|
+
schema = self._extract_tool_schema(tool, tool_name)
|
|
760
|
+
if schema:
|
|
761
|
+
descriptions.append(f"• {schema['name']}: {schema['description']}")
|
|
762
|
+
else:
|
|
763
|
+
descriptions.append(f"• {tool_name}: Description unavailable")
|
|
764
|
+
except Exception as e:
|
|
765
|
+
self.logger.error(f"Error building list description for {tool_name}: {e}")
|
|
766
|
+
descriptions.append(f"• {tool_name}: Error getting information")
|
|
767
|
+
|
|
768
|
+
return "\n".join(descriptions)
|
|
769
|
+
|
|
770
|
+
def _build_markdown_description(
|
|
771
|
+
self,
|
|
772
|
+
tools: List[tuple],
|
|
773
|
+
include_parameters: bool,
|
|
774
|
+
include_examples: bool
|
|
775
|
+
) -> str:
|
|
776
|
+
"""Build markdown-formatted tool descriptions."""
|
|
777
|
+
descriptions = ["## Available Tools\n"]
|
|
778
|
+
|
|
779
|
+
for tool_name, tool in tools:
|
|
780
|
+
try:
|
|
781
|
+
schema = self._extract_tool_schema(tool, tool_name)
|
|
782
|
+
if not schema:
|
|
783
|
+
continue
|
|
784
|
+
|
|
785
|
+
# Tool header
|
|
786
|
+
descriptions.append(f"### {schema['name']}")
|
|
787
|
+
descriptions.append(f"**Description:** {schema['description']}\n")
|
|
788
|
+
|
|
789
|
+
# Parameters section
|
|
790
|
+
if include_parameters and 'parameters' in schema:
|
|
791
|
+
params = schema['parameters'].get('properties', {})
|
|
792
|
+
required = schema['parameters'].get('required', [])
|
|
793
|
+
|
|
794
|
+
if params:
|
|
795
|
+
descriptions.append("**Parameters:**")
|
|
796
|
+
for param_name, param_info in params.items():
|
|
797
|
+
param_type = param_info.get('type', 'unknown')
|
|
798
|
+
param_desc = param_info.get('description', 'No description')
|
|
799
|
+
required_marker = "**required**" if param_name in required else "*optional*"
|
|
800
|
+
descriptions.append(f"- `{param_name}` ({param_type}) - {required_marker}: {param_desc}")
|
|
801
|
+
descriptions.append("")
|
|
802
|
+
|
|
803
|
+
# Usage example
|
|
804
|
+
if include_examples:
|
|
805
|
+
descriptions.append(f"**Usage:** Call `{schema['name']}` when you need to {schema['description'].lower()}\n")
|
|
806
|
+
|
|
807
|
+
except Exception as e:
|
|
808
|
+
self.logger.error(f"Error building markdown description for {tool_name}: {e}")
|
|
809
|
+
descriptions.append(f"### {tool_name}\n**Error:** Could not retrieve tool information\n")
|
|
810
|
+
|
|
811
|
+
return "\n".join(descriptions)
|
|
812
|
+
|
|
813
|
+
def get_tools_summary(self) -> Dict[str, Any]:
|
|
814
|
+
"""
|
|
815
|
+
Get a summary of all registered tools.
|
|
816
|
+
|
|
817
|
+
Returns:
|
|
818
|
+
Dictionary with tool count and basic information
|
|
819
|
+
"""
|
|
820
|
+
if not self._tools:
|
|
821
|
+
return {"count": 0, "tools": []}
|
|
822
|
+
|
|
823
|
+
tools_info = []
|
|
824
|
+
for tool_name, tool in self._tools.items():
|
|
825
|
+
try:
|
|
826
|
+
schema = self._extract_tool_schema(tool, tool_name)
|
|
827
|
+
tool_info = {
|
|
828
|
+
"name": tool_name,
|
|
829
|
+
"description": schema.get(
|
|
830
|
+
'description', 'No description'
|
|
831
|
+
) if schema else 'Schema unavailable',
|
|
832
|
+
"parameters_count": len(
|
|
833
|
+
schema.get('parameters', {}).get('properties', {})
|
|
834
|
+
) if schema else 0
|
|
835
|
+
}
|
|
836
|
+
tools_info.append(tool_info)
|
|
837
|
+
except Exception as e:
|
|
838
|
+
self.logger.error(f"Error getting summary for {tool_name}: {e}")
|
|
839
|
+
tools_info.append({
|
|
840
|
+
"name": tool_name,
|
|
841
|
+
"description": "Error getting information",
|
|
842
|
+
"parameters_count": 0
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
return {
|
|
846
|
+
"count": len(self._tools),
|
|
847
|
+
"tools": tools_info
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async def execute_tool(
|
|
851
|
+
self,
|
|
852
|
+
tool_name: str,
|
|
853
|
+
parameters: Dict[str, Any]
|
|
854
|
+
) -> Any:
|
|
855
|
+
"""Execute a registered tool function."""
|
|
856
|
+
|
|
857
|
+
if tool_name not in self._tools:
|
|
858
|
+
raise ValueError(
|
|
859
|
+
f"Tool '{tool_name}' not registered"
|
|
860
|
+
)
|
|
861
|
+
try:
|
|
862
|
+
tool = self._tools[tool_name]
|
|
863
|
+
if isinstance(tool, ToolDefinition):
|
|
864
|
+
if asyncio.iscoroutinefunction(tool.function):
|
|
865
|
+
result = await tool.function(**parameters)
|
|
866
|
+
else:
|
|
867
|
+
result = tool.function(**parameters)
|
|
868
|
+
|
|
869
|
+
self.logger.debug(
|
|
870
|
+
f"Executed tool '{tool_name}' with parameters: {parameters}"
|
|
871
|
+
)
|
|
872
|
+
return result
|
|
873
|
+
|
|
874
|
+
elif isinstance(tool, AbstractTool):
|
|
875
|
+
# Handle AbstractTool (new)
|
|
876
|
+
result = await tool.execute(**parameters)
|
|
877
|
+
# Handle ToolResult objects
|
|
878
|
+
if isinstance(result, ToolResult):
|
|
879
|
+
if result.status == "error":
|
|
880
|
+
raise ValueError(result.error)
|
|
881
|
+
out = result.result
|
|
882
|
+
meta = getattr(result, "metadata", {}) or {}
|
|
883
|
+
else:
|
|
884
|
+
out = result
|
|
885
|
+
meta = {}
|
|
886
|
+
self._postprocess_result(tool_name, out, meta)
|
|
887
|
+
self._run_result_hooks(tool_name, out, meta)
|
|
888
|
+
return out
|
|
889
|
+
else:
|
|
890
|
+
raise ValueError(
|
|
891
|
+
f"Unknown tool type: {type(tool)}"
|
|
892
|
+
)
|
|
893
|
+
except Exception as e:
|
|
894
|
+
self.logger.error(
|
|
895
|
+
f"Error executing tool {tool_name}: {e}"
|
|
896
|
+
)
|
|
897
|
+
raise
|
|
898
|
+
|
|
899
|
+
async def register_a2a_agent(self, url: str) -> RegisteredAgent:
|
|
900
|
+
"""
|
|
901
|
+
Register an A2A agent by its URL.
|
|
902
|
+
|
|
903
|
+
Args:
|
|
904
|
+
url (str): The base URL of the A2A agent.
|
|
905
|
+
|
|
906
|
+
Returns:
|
|
907
|
+
RegisteredAgent: The registered agent object.
|
|
908
|
+
|
|
909
|
+
Raises:
|
|
910
|
+
Exception: If registration fails or agent is unreachable.
|
|
911
|
+
"""
|
|
912
|
+
url = url.rstrip('/')
|
|
913
|
+
agent_url = f"{url}/.well-known/agent.json"
|
|
914
|
+
|
|
915
|
+
try:
|
|
916
|
+
async with aiohttp.ClientSession() as session:
|
|
917
|
+
async with session.get(agent_url) as response:
|
|
918
|
+
if response.status != 200:
|
|
919
|
+
raise ValueError(f"Failed to fetch agent card: {response.status}")
|
|
920
|
+
|
|
921
|
+
data = await response.json()
|
|
922
|
+
card = AgentCard.from_dict(data)
|
|
923
|
+
card.url = url # Ensure URL is set to the base URL
|
|
924
|
+
|
|
925
|
+
agent = RegisteredAgent(
|
|
926
|
+
url=url,
|
|
927
|
+
card=card
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
self._registered_agents[card.name] = agent
|
|
931
|
+
self.logger.info(f"Registered A2A agent: {card.name} ({url})")
|
|
932
|
+
return agent
|
|
933
|
+
|
|
934
|
+
except Exception as e:
|
|
935
|
+
self.logger.error(f"Error registering A2A agent from {url}: {e}")
|
|
936
|
+
raise
|
|
937
|
+
|
|
938
|
+
def get_a2a_agents(self) -> List[RegisteredAgent]:
|
|
939
|
+
"""Get all registered A2A agents."""
|
|
940
|
+
return list(self._registered_agents.values())
|
|
941
|
+
|
|
942
|
+
def get_by_skill(self, skill: str) -> List[RegisteredAgent]:
|
|
943
|
+
"""
|
|
944
|
+
Get agents that have a specific skill (by ID or name substring).
|
|
945
|
+
"""
|
|
946
|
+
results = []
|
|
947
|
+
skill_lower = skill.lower()
|
|
948
|
+
for agent in self._registered_agents.values():
|
|
949
|
+
for s in agent.card.skills:
|
|
950
|
+
if skill_lower in s.id.lower() or skill_lower in s.name.lower():
|
|
951
|
+
results.append(agent)
|
|
952
|
+
break
|
|
953
|
+
return results
|
|
954
|
+
|
|
955
|
+
def get_by_tag(self, tag: str) -> List[RegisteredAgent]:
|
|
956
|
+
"""Get agents that have a specific tag."""
|
|
957
|
+
results = []
|
|
958
|
+
tag_lower = tag.lower()
|
|
959
|
+
for agent in self._registered_agents.values():
|
|
960
|
+
# Check agent tags
|
|
961
|
+
if any(tag_lower == t.lower() for t in agent.card.tags):
|
|
962
|
+
results.append(agent)
|
|
963
|
+
continue
|
|
964
|
+
# Check skill tags
|
|
965
|
+
for s in agent.card.skills:
|
|
966
|
+
if any(tag_lower == t.lower() for t in s.tags):
|
|
967
|
+
results.append(agent)
|
|
968
|
+
break
|
|
969
|
+
return results
|
|
970
|
+
|
|
971
|
+
def search_a2a_agents(self, query: str) -> List[RegisteredAgent]:
|
|
972
|
+
"""
|
|
973
|
+
Search agents by name, description, tags, or skills.
|
|
974
|
+
"""
|
|
975
|
+
results = []
|
|
976
|
+
q = query.lower()
|
|
977
|
+
for agent in self._registered_agents.values():
|
|
978
|
+
# Search in agent metadata and tags
|
|
979
|
+
if (q in agent.card.name.lower() or
|
|
980
|
+
q in agent.card.description.lower() or
|
|
981
|
+
any(q in t.lower() for t in agent.card.tags)):
|
|
982
|
+
results.append(agent)
|
|
983
|
+
continue
|
|
984
|
+
|
|
985
|
+
# Search in skills
|
|
986
|
+
found_in_skills = False
|
|
987
|
+
for s in agent.card.skills:
|
|
988
|
+
if (q in s.name.lower() or
|
|
989
|
+
q in s.description.lower() or
|
|
990
|
+
any(q in t.lower() for t in s.tags)):
|
|
991
|
+
found_in_skills = True
|
|
992
|
+
break
|
|
993
|
+
|
|
994
|
+
if found_in_skills:
|
|
995
|
+
results.append(agent)
|
|
996
|
+
|
|
997
|
+
return results
|
|
998
|
+
|
|
999
|
+
def list_a2a_agents(self) -> List[str]:
|
|
1000
|
+
"""List names of registered A2A agents."""
|
|
1001
|
+
return list(self._registered_agents.keys())
|
|
1002
|
+
|
|
1003
|
+
async def execute_tool_call(
|
|
1004
|
+
self,
|
|
1005
|
+
content_block: Dict[str, Any]
|
|
1006
|
+
) -> Dict[str, Any]:
|
|
1007
|
+
"""Execute a single tool call and return the result."""
|
|
1008
|
+
tool_name = content_block["name"]
|
|
1009
|
+
tool_input = content_block["input"]
|
|
1010
|
+
tool_id = content_block["id"]
|
|
1011
|
+
|
|
1012
|
+
try:
|
|
1013
|
+
tool_result = await self.execute_tool(tool_name, tool_input)
|
|
1014
|
+
return {
|
|
1015
|
+
"type": "tool_result",
|
|
1016
|
+
"tool_use_id": tool_id,
|
|
1017
|
+
"content": str(tool_result)
|
|
1018
|
+
}
|
|
1019
|
+
except Exception as e:
|
|
1020
|
+
return {
|
|
1021
|
+
"type": "tool_result",
|
|
1022
|
+
"tool_use_id": tool_id,
|
|
1023
|
+
"is_error": True,
|
|
1024
|
+
"content": str(e)
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
def _postprocess_result(self, tool_name: str, out: Any, meta: Dict[str, Any]) -> None:
|
|
1028
|
+
"""Auto-share DataFrame outputs and push to PythonPandasTool."""
|
|
1029
|
+
try:
|
|
1030
|
+
if not self.auto_share_dataframes:
|
|
1031
|
+
return
|
|
1032
|
+
# Handle single DF
|
|
1033
|
+
if isinstance(out, pd.DataFrame):
|
|
1034
|
+
# Prefer a semantic name when metadata exists
|
|
1035
|
+
base = meta.get("query_slug") or meta.get("name") or tool_name
|
|
1036
|
+
df_name = self._unique_df_name(base)
|
|
1037
|
+
self.share_dataframe(df_name, out, meta)
|
|
1038
|
+
return
|
|
1039
|
+
# Handle tuple or dict containers that embed a DataFrame under 'result'
|
|
1040
|
+
if isinstance(out, dict) and "result" in out and isinstance(out["result"], pd.DataFrame):
|
|
1041
|
+
base = meta.get("query_slug") or meta.get("name") or tool_name
|
|
1042
|
+
df_name = self._unique_df_name(base)
|
|
1043
|
+
self.share_dataframe(df_name, out["result"], meta)
|
|
1044
|
+
except Exception as e:
|
|
1045
|
+
self.logger.debug(f"No DF shared for {tool_name}: {e}")
|
|
1046
|
+
|
|
1047
|
+
def _unique_df_name(self, base: str) -> str:
|
|
1048
|
+
base = (base or "df").replace(" ", "_").lower()
|
|
1049
|
+
name = base
|
|
1050
|
+
i = 1
|
|
1051
|
+
while name in self._shared["dataframes"]:
|
|
1052
|
+
i += 1
|
|
1053
|
+
name = f"{base}_{i}"
|
|
1054
|
+
return name
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def tool_count(self) -> int:
|
|
1058
|
+
"""Get the number of registered tools."""
|
|
1059
|
+
return len(self._tools)
|
|
1060
|
+
|
|
1061
|
+
def share_dataframe(self, name: str, df: "pd.DataFrame", meta: Dict[str, Any] = None) -> str:
|
|
1062
|
+
"""Store df in shared context and push into python_pandas if present."""
|
|
1063
|
+
if not isinstance(df, pd.DataFrame):
|
|
1064
|
+
raise ValueError("share_dataframe expects a pandas.DataFrame")
|
|
1065
|
+
safe = name or f"df_{len(self._shared['dataframes'])+1}"
|
|
1066
|
+
self._shared["dataframes"][safe] = (df, meta or {})
|
|
1067
|
+
# auto-push into python_pandas
|
|
1068
|
+
if self.auto_push_to_pandas:
|
|
1069
|
+
pandas_tool = self.get_tool(self.pandas_tool_name)
|
|
1070
|
+
if pandas_tool:
|
|
1071
|
+
try:
|
|
1072
|
+
msg = pandas_tool.add_dataframe(safe, df, regenerate_guide=True)
|
|
1073
|
+
self.logger.debug(f"PandasTool: {msg}")
|
|
1074
|
+
except Exception as e:
|
|
1075
|
+
self.logger.warning(f"Could not push DF into {self.pandas_tool_name}: {e}")
|
|
1076
|
+
return safe
|
|
1077
|
+
|
|
1078
|
+
def get_shared_dataframe(self, name: str) -> "pd.DataFrame":
|
|
1079
|
+
df, _ = self._shared["dataframes"][name]
|
|
1080
|
+
return df
|
|
1081
|
+
|
|
1082
|
+
def list_shared_dataframes(self) -> List[str]:
|
|
1083
|
+
return list(self._shared["dataframes"].keys())
|
|
1084
|
+
|
|
1085
|
+
def clear_shared(self) -> None:
|
|
1086
|
+
self._shared = {"dataframes": {}}
|
|
1087
|
+
|
|
1088
|
+
# == Hooks ==
|
|
1089
|
+
def add_result_hook(self, fn: Callable[[str, Any, Dict[str, Any]], None]) -> None:
|
|
1090
|
+
"""Register a function(tool_name, result, metadata) -> None run after each tool."""
|
|
1091
|
+
self._result_hooks.append(fn)
|
|
1092
|
+
|
|
1093
|
+
def _run_result_hooks(self, tool_name: str, result: Any, metadata: Dict[str, Any]) -> None:
|
|
1094
|
+
for fn in self._result_hooks:
|
|
1095
|
+
try:
|
|
1096
|
+
fn(tool_name, result, metadata)
|
|
1097
|
+
except Exception as e:
|
|
1098
|
+
self.logger.warning(f"Result hook error in {fn}: {e}")
|