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/memory/core.py
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
# ai_parrot/memory/agent_core_memory.py
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import pickle
|
|
5
|
+
import json
|
|
6
|
+
from typing import List, Dict, Optional, Tuple
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import redis.asyncio as redis
|
|
12
|
+
import numpy as np
|
|
13
|
+
from sentence_transformers import SentenceTransformer
|
|
14
|
+
from bm25s import BM25
|
|
15
|
+
from transformers import pipeline
|
|
16
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
17
|
+
from navconfig import BASE_DIR
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class MemoryEntry:
|
|
22
|
+
"""Memory Entry structure."""
|
|
23
|
+
id: str
|
|
24
|
+
query: str
|
|
25
|
+
response_summary: str
|
|
26
|
+
tools_used: List[str]
|
|
27
|
+
outcome: str
|
|
28
|
+
embedding: np.ndarray
|
|
29
|
+
metadata: Dict
|
|
30
|
+
timestamp: datetime
|
|
31
|
+
access_count: int = 0
|
|
32
|
+
source: str = "memory" # for ranking
|
|
33
|
+
score: float = 0.0
|
|
34
|
+
final_score: float = 0.0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ValueScorer:
|
|
38
|
+
"""Heurísticas para determinar valor de una interacción"""
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def score_interaction(
|
|
42
|
+
query: str,
|
|
43
|
+
response: str,
|
|
44
|
+
tools_used: List[str],
|
|
45
|
+
outcome: str,
|
|
46
|
+
user_feedback: Optional[str] = None
|
|
47
|
+
) -> float:
|
|
48
|
+
score = 0.0
|
|
49
|
+
|
|
50
|
+
# Outcome positivo
|
|
51
|
+
if outcome in {'resolved', 'successful'}:
|
|
52
|
+
score += 0.3
|
|
53
|
+
|
|
54
|
+
# Usó herramientas (no fue solo conversacional)
|
|
55
|
+
if tools_used:
|
|
56
|
+
score += 0.2
|
|
57
|
+
|
|
58
|
+
# Query substantiva
|
|
59
|
+
if len(query.split()) > 5:
|
|
60
|
+
score += 0.1
|
|
61
|
+
|
|
62
|
+
# Respuesta substantiva
|
|
63
|
+
if len(response) > 100:
|
|
64
|
+
score += 0.2
|
|
65
|
+
|
|
66
|
+
# Feedback explícito del usuario
|
|
67
|
+
if user_feedback == 'positive':
|
|
68
|
+
score += 0.3
|
|
69
|
+
elif user_feedback == 'negative':
|
|
70
|
+
score -= 0.2
|
|
71
|
+
|
|
72
|
+
return max(0.0, min(score, 1.0))
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def is_valuable(score: float) -> bool:
|
|
76
|
+
return score >= 0.4 # threshold ajustable
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AgentCoreMemory:
|
|
80
|
+
"""
|
|
81
|
+
Memoria compartida híbrida: Redis (hot) + PgVector (cold)
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
redis_client: redis.Redis,
|
|
87
|
+
pgvector_store, # tu implementación actual de PgVector
|
|
88
|
+
embedder_model: str = "all-MiniLM-L6-v2",
|
|
89
|
+
summarizer_model: str = "facebook/bart-large-cnn",
|
|
90
|
+
redis_ttl_days: int = 7,
|
|
91
|
+
distill_schedule_hours: int = 24
|
|
92
|
+
):
|
|
93
|
+
self.redis = redis_client
|
|
94
|
+
self.pgvector = pgvector_store
|
|
95
|
+
|
|
96
|
+
# Embeddings
|
|
97
|
+
self.embedder = SentenceTransformer(embedder_model)
|
|
98
|
+
|
|
99
|
+
# Summarization
|
|
100
|
+
self.summarizer = pipeline(
|
|
101
|
+
"summarization",
|
|
102
|
+
model=summarizer_model,
|
|
103
|
+
device=0 if self._has_gpu() else -1
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# BM25 search (lo construimos lazy)
|
|
107
|
+
self._bm25_index: Dict[str, BM25] = {} # {agent_type: BM25}
|
|
108
|
+
self.bm25_cache_dir = Path(BASE_DIR) / "cache" / "bm25"
|
|
109
|
+
self.bm25_cache_dir.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
|
|
111
|
+
# Config
|
|
112
|
+
self.redis_ttl = redis_ttl_days * 24 * 3600
|
|
113
|
+
|
|
114
|
+
# Agent registry
|
|
115
|
+
self.agent_registry: Dict[str, Dict] = {}
|
|
116
|
+
|
|
117
|
+
# Scheduler para destilación periódica
|
|
118
|
+
self.scheduler = AsyncIOScheduler()
|
|
119
|
+
self.scheduler.add_job(
|
|
120
|
+
self._distillation_job,
|
|
121
|
+
'interval',
|
|
122
|
+
hours=distill_schedule_hours,
|
|
123
|
+
id='memory_distillation'
|
|
124
|
+
)
|
|
125
|
+
# load bm25 indexes on startup:
|
|
126
|
+
self._load_bm25_indices()
|
|
127
|
+
|
|
128
|
+
def _get_bm25_cache_path(self, agent_type: str) -> Path:
|
|
129
|
+
return self.bm25_cache_dir / f"{agent_type}_bm25.pkl"
|
|
130
|
+
|
|
131
|
+
def _load_bm25_indices(self):
|
|
132
|
+
"""Carga índices BM25 desde disco al inicio"""
|
|
133
|
+
for agent_type in self.agent_registry.keys():
|
|
134
|
+
cache_path = self._get_bm25_cache_path(agent_type)
|
|
135
|
+
if cache_path.exists():
|
|
136
|
+
try:
|
|
137
|
+
with open(cache_path, 'rb') as f:
|
|
138
|
+
self._bm25_index[agent_type] = pickle.load(f)
|
|
139
|
+
print(f"[BM25] Loaded index for {agent_type}")
|
|
140
|
+
except Exception as e:
|
|
141
|
+
print(f"[BM25] Failed to load index for {agent_type}: {e}")
|
|
142
|
+
|
|
143
|
+
async def _save_bm25_index(self, agent_type: str):
|
|
144
|
+
"""Persiste índice BM25 a disco"""
|
|
145
|
+
if agent_type not in self._bm25_index:
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
cache_path = self._get_bm25_cache_path(agent_type)
|
|
149
|
+
try:
|
|
150
|
+
with open(cache_path, 'wb') as f:
|
|
151
|
+
pickle.dump(self._bm25_index[agent_type], f)
|
|
152
|
+
print(f"[BM25] Saved index for {agent_type}")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"[BM25] Failed to save index for {agent_type}: {e}")
|
|
155
|
+
|
|
156
|
+
async def _rebuild_and_save_bm25(self, agent_type: str):
|
|
157
|
+
"""Reconstruye y persiste BM25 index"""
|
|
158
|
+
# Fetch todas las memorias
|
|
159
|
+
pattern = f"agentcore:{agent_type}:memory:*"
|
|
160
|
+
texts = []
|
|
161
|
+
|
|
162
|
+
async for key in self.redis.scan_iter(match=pattern):
|
|
163
|
+
data = await self.redis.hgetall(key)
|
|
164
|
+
if data and b'query' in data:
|
|
165
|
+
texts.append(data[b'query'].decode())
|
|
166
|
+
|
|
167
|
+
if texts:
|
|
168
|
+
# Rebuild index
|
|
169
|
+
self._bm25_index[agent_type] = BM25()
|
|
170
|
+
self._bm25_index[agent_type].index(texts)
|
|
171
|
+
|
|
172
|
+
# Save to disk
|
|
173
|
+
await self._save_bm25_index(agent_type)
|
|
174
|
+
|
|
175
|
+
def start(self):
|
|
176
|
+
"""Inicia el scheduler de destilación"""
|
|
177
|
+
self.scheduler.start()
|
|
178
|
+
|
|
179
|
+
def shutdown(self):
|
|
180
|
+
"""Detiene el scheduler"""
|
|
181
|
+
self.scheduler.shutdown()
|
|
182
|
+
|
|
183
|
+
def register_agent_expertise(
|
|
184
|
+
self,
|
|
185
|
+
agent_type: str,
|
|
186
|
+
expertise_domains: List[str],
|
|
187
|
+
expertise_description: str
|
|
188
|
+
):
|
|
189
|
+
"""Registra dominios de experiencia de un agente"""
|
|
190
|
+
self.agent_registry[agent_type] = {
|
|
191
|
+
'domains': expertise_domains,
|
|
192
|
+
'description': expertise_description,
|
|
193
|
+
'embedding': self.embedder.encode(expertise_description)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# ==================== WRITE: Store Memory ====================
|
|
197
|
+
|
|
198
|
+
async def store_interaction(
|
|
199
|
+
self,
|
|
200
|
+
agent_type: str,
|
|
201
|
+
query: str,
|
|
202
|
+
response: str,
|
|
203
|
+
tools_used: List[str] = None,
|
|
204
|
+
outcome: str = 'unknown',
|
|
205
|
+
user_feedback: Optional[str] = None,
|
|
206
|
+
metadata: Dict = None
|
|
207
|
+
) -> Optional[str]:
|
|
208
|
+
"""
|
|
209
|
+
Almacena una interacción en Redis si es valiosa
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
memory_id si se almacenó, None si se descartó
|
|
213
|
+
"""
|
|
214
|
+
tools_used = tools_used or []
|
|
215
|
+
metadata = metadata or {}
|
|
216
|
+
|
|
217
|
+
# 1. Evaluar valor
|
|
218
|
+
value_score = ValueScorer.score_interaction(
|
|
219
|
+
query=query,
|
|
220
|
+
response=response,
|
|
221
|
+
tools_used=tools_used,
|
|
222
|
+
outcome=outcome,
|
|
223
|
+
user_feedback=user_feedback
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if not ValueScorer.is_valuable(value_score):
|
|
227
|
+
return None # Descartamos interacción de bajo valor
|
|
228
|
+
|
|
229
|
+
# 2. Generar embedding
|
|
230
|
+
embedding = self.embedder.encode(query)
|
|
231
|
+
|
|
232
|
+
# 3. Crear memory entry
|
|
233
|
+
memory_id = str(uuid4())
|
|
234
|
+
key = f"agentcore:{agent_type}:memory:{memory_id}"
|
|
235
|
+
|
|
236
|
+
# 4. Summarizar respuesta si es muy larga
|
|
237
|
+
response_summary = response
|
|
238
|
+
if len(response) > 500:
|
|
239
|
+
response_summary = await self._summarize_text(response, max_length=150)
|
|
240
|
+
|
|
241
|
+
memory_data = {
|
|
242
|
+
"id": memory_id,
|
|
243
|
+
"query": query,
|
|
244
|
+
"response_summary": response_summary,
|
|
245
|
+
"tools_used": json.dumps(tools_used),
|
|
246
|
+
"outcome": outcome,
|
|
247
|
+
"embedding": pickle.dumps(embedding),
|
|
248
|
+
"metadata": json.dumps(metadata),
|
|
249
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
250
|
+
"access_count": 0,
|
|
251
|
+
"value_score": value_score
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# 5. Store en Redis con TTL
|
|
255
|
+
await self.redis.hset(key, mapping=memory_data)
|
|
256
|
+
await self.redis.expire(key, self.redis_ttl)
|
|
257
|
+
|
|
258
|
+
# Invalidar BM25 index para reconstruir
|
|
259
|
+
if agent_type in self._bm25_index:
|
|
260
|
+
del self._bm25_index[agent_type]
|
|
261
|
+
|
|
262
|
+
# Trigger rebuild y save en background
|
|
263
|
+
asyncio.create_task(
|
|
264
|
+
self._rebuild_and_save_bm25(agent_type)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return memory_id
|
|
268
|
+
|
|
269
|
+
# ==================== READ: Retrieve Memory ====================
|
|
270
|
+
|
|
271
|
+
async def retrieve_memory(
|
|
272
|
+
self,
|
|
273
|
+
query: str,
|
|
274
|
+
agent_type: str,
|
|
275
|
+
top_k: int = 5,
|
|
276
|
+
include_cross_domain: bool = True
|
|
277
|
+
) -> List[MemoryEntry]:
|
|
278
|
+
"""
|
|
279
|
+
Recupera memorias relevantes usando búsqueda híbrida
|
|
280
|
+
BM25 (lexical) + Semantic (embeddings)
|
|
281
|
+
"""
|
|
282
|
+
# 1. Determinar agent types relevantes
|
|
283
|
+
agent_types = [agent_type]
|
|
284
|
+
if include_cross_domain:
|
|
285
|
+
cross_agents = await self._route_cross_domain(query, agent_type)
|
|
286
|
+
agent_types.extend(cross_agents)
|
|
287
|
+
|
|
288
|
+
# 2. Búsqueda híbrida por cada agent type
|
|
289
|
+
all_memories = []
|
|
290
|
+
for atype in agent_types:
|
|
291
|
+
memories = await self._hybrid_search(
|
|
292
|
+
query=query,
|
|
293
|
+
agent_type=atype,
|
|
294
|
+
top_k=20 # Recuperamos más para re-ranking
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Aplicar decay si es cross-domain
|
|
298
|
+
decay_factor = 1.0 if atype == agent_type else 0.6
|
|
299
|
+
for mem in memories:
|
|
300
|
+
mem.source = "memory_same" if atype == agent_type else "memory_cross"
|
|
301
|
+
mem.score *= decay_factor
|
|
302
|
+
|
|
303
|
+
all_memories.extend(memories)
|
|
304
|
+
|
|
305
|
+
# 3. Re-ranking final
|
|
306
|
+
ranked_memories = self._rerank_memories(all_memories)
|
|
307
|
+
|
|
308
|
+
# 4. Actualizar access_count en Redis (para LRU)
|
|
309
|
+
await self._update_access_counts(ranked_memories[:top_k])
|
|
310
|
+
|
|
311
|
+
return ranked_memories[:top_k]
|
|
312
|
+
|
|
313
|
+
async def _hybrid_search(
|
|
314
|
+
self,
|
|
315
|
+
query: str,
|
|
316
|
+
agent_type: str,
|
|
317
|
+
top_k: int = 20
|
|
318
|
+
) -> List[MemoryEntry]:
|
|
319
|
+
"""Búsqueda híbrida: BM25 + Semantic"""
|
|
320
|
+
|
|
321
|
+
# Fetch todas las memorias de este agent_type
|
|
322
|
+
pattern = f"agentcore:{agent_type}:memory:*"
|
|
323
|
+
keys = []
|
|
324
|
+
async for key in self.redis.scan_iter(match=pattern):
|
|
325
|
+
keys.append(key)
|
|
326
|
+
|
|
327
|
+
if not keys:
|
|
328
|
+
return []
|
|
329
|
+
|
|
330
|
+
# Fetch datos en batch
|
|
331
|
+
pipeline = self.redis.pipeline()
|
|
332
|
+
for key in keys:
|
|
333
|
+
pipeline.hgetall(key)
|
|
334
|
+
results = await pipeline.execute()
|
|
335
|
+
|
|
336
|
+
memories = []
|
|
337
|
+
texts = []
|
|
338
|
+
embeddings = []
|
|
339
|
+
|
|
340
|
+
for key, data in zip(keys, results):
|
|
341
|
+
if not data:
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
mem = MemoryEntry(
|
|
345
|
+
id=data[b'id'].decode(),
|
|
346
|
+
query=data[b'query'].decode(),
|
|
347
|
+
response_summary=data[b'response_summary'].decode(),
|
|
348
|
+
tools_used=json.loads(data[b'tools_used']),
|
|
349
|
+
outcome=data[b'outcome'].decode(),
|
|
350
|
+
embedding=pickle.loads(data[b'embedding']),
|
|
351
|
+
metadata=json.loads(data[b'metadata']),
|
|
352
|
+
timestamp=datetime.fromisoformat(data[b'timestamp'].decode()),
|
|
353
|
+
access_count=int(data[b'access_count'])
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
memories.append(mem)
|
|
357
|
+
texts.append(mem.query)
|
|
358
|
+
embeddings.append(mem.embedding)
|
|
359
|
+
|
|
360
|
+
if not memories:
|
|
361
|
+
return []
|
|
362
|
+
|
|
363
|
+
# Stage 1: BM25 scoring
|
|
364
|
+
bm25_scores = await self._bm25_score(query, texts, agent_type)
|
|
365
|
+
|
|
366
|
+
# Stage 2: Semantic scoring
|
|
367
|
+
query_embedding = self.embedder.encode(query)
|
|
368
|
+
semantic_scores = [
|
|
369
|
+
self._cosine_similarity(query_embedding, emb)
|
|
370
|
+
for emb in embeddings
|
|
371
|
+
]
|
|
372
|
+
|
|
373
|
+
# Combine scores (40% BM25, 60% Semantic)
|
|
374
|
+
for i, mem in enumerate(memories):
|
|
375
|
+
mem.score = 0.4 * bm25_scores[i] + 0.6 * semantic_scores[i]
|
|
376
|
+
|
|
377
|
+
# Sort y return top_k
|
|
378
|
+
memories.sort(key=lambda x: x.score, reverse=True)
|
|
379
|
+
return memories[:top_k]
|
|
380
|
+
|
|
381
|
+
async def _bm25_score(
|
|
382
|
+
self,
|
|
383
|
+
query: str,
|
|
384
|
+
corpus_texts: List[str],
|
|
385
|
+
agent_type: str
|
|
386
|
+
) -> List[float]:
|
|
387
|
+
"""BM25 scoring usando bm25s"""
|
|
388
|
+
|
|
389
|
+
# Lazy build index si no existe
|
|
390
|
+
if agent_type not in self._bm25_index:
|
|
391
|
+
self._bm25_index[agent_type] = BM25()
|
|
392
|
+
self._bm25_index[agent_type].index(corpus_texts)
|
|
393
|
+
|
|
394
|
+
scores = self._bm25_index[agent_type].score(query)
|
|
395
|
+
|
|
396
|
+
# Normalizar scores a [0, 1]
|
|
397
|
+
if len(scores) > 0:
|
|
398
|
+
max_score = max(scores)
|
|
399
|
+
if max_score > 0:
|
|
400
|
+
scores = [s / max_score for s in scores]
|
|
401
|
+
|
|
402
|
+
return scores
|
|
403
|
+
|
|
404
|
+
def _rerank_memories(self, memories: List[MemoryEntry]) -> List[MemoryEntry]:
|
|
405
|
+
"""
|
|
406
|
+
Re-ranking con source priority:
|
|
407
|
+
- memory_same: score * 0.8
|
|
408
|
+
- memory_cross: score * 0.6
|
|
409
|
+
"""
|
|
410
|
+
for mem in memories:
|
|
411
|
+
if mem.source == "memory_same":
|
|
412
|
+
mem.final_score = mem.score * 0.8
|
|
413
|
+
else: # memory_cross
|
|
414
|
+
mem.final_score = mem.score * 0.6
|
|
415
|
+
|
|
416
|
+
return sorted(memories, key=lambda x: x.final_score, reverse=True)
|
|
417
|
+
|
|
418
|
+
async def _update_access_counts(self, memories: List[MemoryEntry]):
|
|
419
|
+
"""Incrementa access_count para LRU (más accesados sobreviven)"""
|
|
420
|
+
pipeline = self.redis.pipeline()
|
|
421
|
+
for mem in memories:
|
|
422
|
+
key = f"agentcore:{mem.source.split('_')[1] if '_' in mem.source else 'unknown'}:memory:{mem.id}"
|
|
423
|
+
# Buscar el agent_type correcto - esto es un hack, deberíamos almacenarlo
|
|
424
|
+
# Por ahora, buscamos en todos los agent_types registrados
|
|
425
|
+
for agent_type in self.agent_registry.keys():
|
|
426
|
+
potential_key = f"agentcore:{agent_type}:memory:{mem.id}"
|
|
427
|
+
pipeline.hincrby(potential_key, "access_count", 1)
|
|
428
|
+
|
|
429
|
+
await pipeline.execute()
|
|
430
|
+
|
|
431
|
+
# ==================== ROUTING ====================
|
|
432
|
+
|
|
433
|
+
async def _route_cross_domain(
|
|
434
|
+
self,
|
|
435
|
+
query: str,
|
|
436
|
+
current_agent_type: str,
|
|
437
|
+
threshold: float = 0.5
|
|
438
|
+
) -> List[str]:
|
|
439
|
+
"""Routing semántico a otros agent types"""
|
|
440
|
+
query_emb = self.embedder.encode(query)
|
|
441
|
+
|
|
442
|
+
relevant_agents = []
|
|
443
|
+
for agent_type, meta in self.agent_registry.items():
|
|
444
|
+
if agent_type == current_agent_type:
|
|
445
|
+
continue
|
|
446
|
+
|
|
447
|
+
similarity = self._cosine_similarity(query_emb, meta['embedding'])
|
|
448
|
+
|
|
449
|
+
if similarity > threshold:
|
|
450
|
+
relevant_agents.append((agent_type, similarity))
|
|
451
|
+
|
|
452
|
+
# Return top 2
|
|
453
|
+
relevant_agents.sort(key=lambda x: x[1], reverse=True)
|
|
454
|
+
return [agent for agent, _ in relevant_agents[:2]]
|
|
455
|
+
|
|
456
|
+
# ==================== DISTILLATION ====================
|
|
457
|
+
|
|
458
|
+
async def _distillation_job(self):
|
|
459
|
+
"""
|
|
460
|
+
Job periódico: mueve memorias antiguas de Redis a PgVector
|
|
461
|
+
"""
|
|
462
|
+
print(f"[Distillation] Starting job at {datetime.utcnow()}")
|
|
463
|
+
|
|
464
|
+
for agent_type in self.agent_registry.keys():
|
|
465
|
+
await self._distill_agent_memories(agent_type)
|
|
466
|
+
|
|
467
|
+
print(f"[Distillation] Job completed")
|
|
468
|
+
|
|
469
|
+
async def _distill_agent_memories(self, agent_type: str):
|
|
470
|
+
"""
|
|
471
|
+
Identifica memorias antiguas (idle time alto) y las mueve a PgVector
|
|
472
|
+
"""
|
|
473
|
+
pattern = f"agentcore:{agent_type}:memory:*"
|
|
474
|
+
|
|
475
|
+
# 1. Scan todas las keys
|
|
476
|
+
old_memories = []
|
|
477
|
+
async for key in self.redis.scan_iter(match=pattern):
|
|
478
|
+
# Check idle time (tiempo sin acceso)
|
|
479
|
+
idle_time = await self.redis.object('IDLETIME', key)
|
|
480
|
+
|
|
481
|
+
# Si lleva más de 5 días sin acceso, candidato a destilación
|
|
482
|
+
if idle_time and idle_time > (5 * 24 * 3600):
|
|
483
|
+
data = await self.redis.hgetall(key)
|
|
484
|
+
if data:
|
|
485
|
+
old_memories.append((key, data))
|
|
486
|
+
|
|
487
|
+
if not old_memories:
|
|
488
|
+
return
|
|
489
|
+
|
|
490
|
+
print(f"[Distillation] Found {len(old_memories)} old memories for {agent_type}")
|
|
491
|
+
|
|
492
|
+
# 2. Cluster por similitud (opcional, para agrupar antes de sumarizar)
|
|
493
|
+
# Por simplicidad, lo omitimos en v1 y sumariamos individualmente
|
|
494
|
+
|
|
495
|
+
# 3. Summarize y store en PgVector
|
|
496
|
+
for key, data in old_memories:
|
|
497
|
+
try:
|
|
498
|
+
query = data[b'query'].decode()
|
|
499
|
+
response = data[b'response_summary'].decode()
|
|
500
|
+
|
|
501
|
+
# Crear un knowledge summary
|
|
502
|
+
combined_text = f"Query: {query}\nResponse: {response}"
|
|
503
|
+
summary = await self._summarize_text(combined_text, max_length=200)
|
|
504
|
+
|
|
505
|
+
# Store en PgVector
|
|
506
|
+
await self.pgvector.store(
|
|
507
|
+
collection=f"agent_longterm_{agent_type}",
|
|
508
|
+
content=summary,
|
|
509
|
+
embedding=self.embedder.encode(summary),
|
|
510
|
+
metadata={
|
|
511
|
+
'agent_type': agent_type,
|
|
512
|
+
'source': 'distilled_memory',
|
|
513
|
+
'original_query': query,
|
|
514
|
+
'timestamp': datetime.utcnow().isoformat()
|
|
515
|
+
}
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
# Delete from Redis
|
|
519
|
+
await self.redis.delete(key)
|
|
520
|
+
|
|
521
|
+
except Exception as e:
|
|
522
|
+
print(f"[Distillation] Error processing {key}: {e}")
|
|
523
|
+
continue
|
|
524
|
+
|
|
525
|
+
print(f"[Distillation] Distilled {len(old_memories)} memories for {agent_type}")
|
|
526
|
+
|
|
527
|
+
# ==================== UTILS ====================
|
|
528
|
+
|
|
529
|
+
async def _summarize_text(self, text: str, max_length: int = 150) -> str:
|
|
530
|
+
"""Sumariza texto usando BART"""
|
|
531
|
+
try:
|
|
532
|
+
result = self.summarizer(
|
|
533
|
+
text,
|
|
534
|
+
max_length=max_length,
|
|
535
|
+
min_length=30,
|
|
536
|
+
do_sample=False
|
|
537
|
+
)
|
|
538
|
+
return result[0]['summary_text']
|
|
539
|
+
except Exception as e:
|
|
540
|
+
print(f"[Summarization] Error: {e}")
|
|
541
|
+
return text[:max_length] # Fallback: truncate
|
|
542
|
+
|
|
543
|
+
@staticmethod
|
|
544
|
+
def _cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
|
|
545
|
+
"""Cosine similarity entre dos vectores"""
|
|
546
|
+
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
|
|
547
|
+
|
|
548
|
+
@staticmethod
|
|
549
|
+
def _has_gpu() -> bool:
|
|
550
|
+
"""Check si hay GPU disponible"""
|
|
551
|
+
try:
|
|
552
|
+
import torch
|
|
553
|
+
return torch.cuda.is_available()
|
|
554
|
+
except ImportError:
|
|
555
|
+
return False
|
parrot/memory/file.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from typing import Dict, List, Optional, Any
|
|
2
|
+
import asyncio
|
|
3
|
+
import aiofiles
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from datamodel.parsers.json import json_encoder, json_decoder # pylint: disable=E0611 # noqa
|
|
6
|
+
from .abstract import ConversationMemory, ConversationHistory, ConversationTurn
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FileConversationMemory(ConversationMemory):
|
|
10
|
+
"""File-based implementation of conversation memory."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, base_path: str = "./conversations"):
|
|
13
|
+
self.base_path = Path(base_path)
|
|
14
|
+
self.base_path.mkdir(exist_ok=True)
|
|
15
|
+
self._lock = asyncio.Lock()
|
|
16
|
+
|
|
17
|
+
def _get_file_path(
|
|
18
|
+
self,
|
|
19
|
+
user_id: str,
|
|
20
|
+
session_id: str,
|
|
21
|
+
chatbot_id: Optional[str] = None
|
|
22
|
+
) -> Path:
|
|
23
|
+
"""Get file path for a conversation history."""
|
|
24
|
+
user_dir = self.base_path / user_id
|
|
25
|
+
if chatbot_id:
|
|
26
|
+
user_dir = user_dir / str(chatbot_id)
|
|
27
|
+
user_dir.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
return user_dir / f"{session_id}.json"
|
|
29
|
+
|
|
30
|
+
async def create_history(
|
|
31
|
+
self,
|
|
32
|
+
user_id: str,
|
|
33
|
+
session_id: str,
|
|
34
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
35
|
+
chatbot_id: Optional[str] = None
|
|
36
|
+
) -> ConversationHistory:
|
|
37
|
+
"""Create a new conversation history."""
|
|
38
|
+
async with self._lock:
|
|
39
|
+
history = ConversationHistory(
|
|
40
|
+
session_id=session_id,
|
|
41
|
+
user_id=user_id,
|
|
42
|
+
chatbot_id=chatbot_id,
|
|
43
|
+
metadata=metadata or {}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
file_path = self._get_file_path(user_id, session_id, chatbot_id)
|
|
47
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
48
|
+
json_encoder(history.to_dict(), f, indent=2, ensure_ascii=False)
|
|
49
|
+
|
|
50
|
+
return history
|
|
51
|
+
|
|
52
|
+
async def get_history(
|
|
53
|
+
self,
|
|
54
|
+
user_id: str,
|
|
55
|
+
session_id: str,
|
|
56
|
+
chatbot_id: Optional[str] = None
|
|
57
|
+
) -> Optional[ConversationHistory]:
|
|
58
|
+
"""Get a conversation history."""
|
|
59
|
+
async with self._lock:
|
|
60
|
+
file_path = self._get_file_path(user_id, session_id, chatbot_id)
|
|
61
|
+
if not file_path.exists():
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
async with aiofiles.open(file_path, 'r', encoding='utf-8') as f:
|
|
66
|
+
data = await json_decoder(f)
|
|
67
|
+
return ConversationHistory.from_dict(data)
|
|
68
|
+
except (TypeError, KeyError, ValueError):
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
async def update_history(self, history: ConversationHistory) -> None:
|
|
72
|
+
"""Update a conversation history."""
|
|
73
|
+
async with self._lock:
|
|
74
|
+
file_path = self._get_file_path(
|
|
75
|
+
history.user_id,
|
|
76
|
+
history.session_id,
|
|
77
|
+
history.chatbot_id
|
|
78
|
+
)
|
|
79
|
+
async with aiofiles.open(file_path, 'w', encoding='utf-8') as f:
|
|
80
|
+
await json_encoder(history.to_dict(), f, indent=2, ensure_ascii=False)
|
|
81
|
+
|
|
82
|
+
async def add_turn(
|
|
83
|
+
self,
|
|
84
|
+
user_id: str,
|
|
85
|
+
session_id: str,
|
|
86
|
+
turn: ConversationTurn,
|
|
87
|
+
chatbot_id: Optional[str] = None
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Add a turn to the conversation."""
|
|
90
|
+
history = await self.get_history(user_id, session_id, chatbot_id)
|
|
91
|
+
if history:
|
|
92
|
+
history.add_turn(turn)
|
|
93
|
+
await self.update_history(history)
|
|
94
|
+
|
|
95
|
+
async def clear_history(
|
|
96
|
+
self,
|
|
97
|
+
user_id: str,
|
|
98
|
+
session_id: str,
|
|
99
|
+
chatbot_id: Optional[str] = None
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Clear a conversation history."""
|
|
102
|
+
history = await self.get_history(user_id, session_id, chatbot_id)
|
|
103
|
+
if history:
|
|
104
|
+
history.clear_turns()
|
|
105
|
+
await self.update_history(history)
|
|
106
|
+
|
|
107
|
+
async def list_sessions(
|
|
108
|
+
self,
|
|
109
|
+
user_id: str,
|
|
110
|
+
chatbot_id: Optional[str] = None
|
|
111
|
+
) -> List[str]:
|
|
112
|
+
"""List all session IDs for a user."""
|
|
113
|
+
async with self._lock:
|
|
114
|
+
base_user_dir = self.base_path / user_id
|
|
115
|
+
if not base_user_dir.exists():
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
sessions: List[str] = []
|
|
119
|
+
seen = set()
|
|
120
|
+
if chatbot_id is None:
|
|
121
|
+
for file_path in base_user_dir.glob("*.json"):
|
|
122
|
+
if file_path.stem not in seen:
|
|
123
|
+
seen.add(file_path.stem)
|
|
124
|
+
sessions.append(file_path.stem)
|
|
125
|
+
for subdir in base_user_dir.iterdir():
|
|
126
|
+
if subdir.is_dir():
|
|
127
|
+
for file_path in subdir.glob("*.json"):
|
|
128
|
+
if file_path.stem not in seen:
|
|
129
|
+
seen.add(file_path.stem)
|
|
130
|
+
sessions.append(file_path.stem)
|
|
131
|
+
else:
|
|
132
|
+
chatbot_dir = base_user_dir / str(chatbot_id)
|
|
133
|
+
if chatbot_dir.exists():
|
|
134
|
+
for file_path in chatbot_dir.glob("*.json"):
|
|
135
|
+
if file_path.stem not in seen:
|
|
136
|
+
seen.add(file_path.stem)
|
|
137
|
+
sessions.append(file_path.stem)
|
|
138
|
+
|
|
139
|
+
return sessions
|
|
140
|
+
|
|
141
|
+
async def delete_history(
|
|
142
|
+
self,
|
|
143
|
+
user_id: str,
|
|
144
|
+
session_id: str,
|
|
145
|
+
chatbot_id: Optional[str] = None
|
|
146
|
+
) -> bool:
|
|
147
|
+
"""Delete a conversation history entirely."""
|
|
148
|
+
async with self._lock:
|
|
149
|
+
file_path = self._get_file_path(user_id, session_id, chatbot_id)
|
|
150
|
+
if file_path.exists():
|
|
151
|
+
file_path.unlink()
|
|
152
|
+
return True
|
|
153
|
+
return False
|