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/redis.py
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Optional
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
import json
|
|
4
|
+
from redis.asyncio import Redis
|
|
5
|
+
from datamodel.parsers.json import json_encoder, json_decoder # pylint: disable=E0611 # noqa
|
|
6
|
+
from .abstract import ConversationMemory, ConversationHistory, ConversationTurn
|
|
7
|
+
from ..conf import REDIS_HISTORY_URL
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RedisConversation(ConversationMemory):
|
|
11
|
+
"""Redis-based conversation memory with proper encoding handling."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
redis_url: str = None,
|
|
16
|
+
key_prefix: str = "conversation",
|
|
17
|
+
use_hash_storage: bool = True
|
|
18
|
+
):
|
|
19
|
+
self.redis_url = redis_url or REDIS_HISTORY_URL
|
|
20
|
+
self.key_prefix = key_prefix
|
|
21
|
+
self.use_hash_storage = use_hash_storage
|
|
22
|
+
self.redis = Redis.from_url(
|
|
23
|
+
self.redis_url,
|
|
24
|
+
decode_responses=True,
|
|
25
|
+
encoding="utf-8",
|
|
26
|
+
socket_connect_timeout=5,
|
|
27
|
+
socket_timeout=5,
|
|
28
|
+
retry_on_timeout=True
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def _get_key(
|
|
32
|
+
self,
|
|
33
|
+
user_id: str,
|
|
34
|
+
session_id: str,
|
|
35
|
+
chatbot_id: Optional[str] = None
|
|
36
|
+
) -> str:
|
|
37
|
+
"""Generate Redis key for conversation history."""
|
|
38
|
+
parts = [self.key_prefix]
|
|
39
|
+
if chatbot_id:
|
|
40
|
+
parts.append(str(chatbot_id))
|
|
41
|
+
parts.extend([user_id, session_id])
|
|
42
|
+
return ":".join(parts)
|
|
43
|
+
|
|
44
|
+
def _get_user_sessions_key(
|
|
45
|
+
self,
|
|
46
|
+
user_id: str,
|
|
47
|
+
chatbot_id: Optional[str] = None
|
|
48
|
+
) -> str:
|
|
49
|
+
"""Generate Redis key for user's session list."""
|
|
50
|
+
parts = [f"{self.key_prefix}_sessions"]
|
|
51
|
+
if chatbot_id:
|
|
52
|
+
parts.append(str(chatbot_id))
|
|
53
|
+
parts.append(user_id)
|
|
54
|
+
return ":".join(parts)
|
|
55
|
+
|
|
56
|
+
def _serialize_data(self, data: Any) -> str:
|
|
57
|
+
"""Serialize data to JSON string with proper encoding."""
|
|
58
|
+
try:
|
|
59
|
+
# Use standard json module with specific settings to avoid encoding issues
|
|
60
|
+
return json.dumps(data, ensure_ascii=False, separators=(',', ':'), default=str)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"Serialization error: {e}")
|
|
63
|
+
# Fallback to your custom encoder
|
|
64
|
+
return json_encoder(data)
|
|
65
|
+
|
|
66
|
+
def _deserialize_data(self, data: str) -> Any:
|
|
67
|
+
"""Deserialize JSON string to Python object."""
|
|
68
|
+
try:
|
|
69
|
+
# Use standard json module first
|
|
70
|
+
return json.loads(data)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"Deserialization error with standard json: {e}")
|
|
73
|
+
# Fallback to your custom decoder
|
|
74
|
+
try:
|
|
75
|
+
# Fallback to your custom decoder
|
|
76
|
+
return json_decoder(data)
|
|
77
|
+
except Exception as e2:
|
|
78
|
+
print(f"Deserialization error with custom decoder: {e2}")
|
|
79
|
+
print(f"Problematic data (first 200 chars): {data[:200]}")
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
async def create_history(
|
|
83
|
+
self,
|
|
84
|
+
user_id: str,
|
|
85
|
+
session_id: str,
|
|
86
|
+
chatbot_id: Optional[str] = None,
|
|
87
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
88
|
+
) -> ConversationHistory:
|
|
89
|
+
"""Create a new conversation history."""
|
|
90
|
+
history = ConversationHistory(
|
|
91
|
+
session_id=session_id,
|
|
92
|
+
user_id=user_id,
|
|
93
|
+
chatbot_id=chatbot_id,
|
|
94
|
+
metadata=metadata or {}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if self.use_hash_storage:
|
|
98
|
+
# Method 1: Using Redis Hash (RECOMMENDED for objects)
|
|
99
|
+
key = self._get_key(user_id, session_id, chatbot_id)
|
|
100
|
+
history_dict = history.to_dict()
|
|
101
|
+
|
|
102
|
+
# Store each field separately in a hash
|
|
103
|
+
mapping = {
|
|
104
|
+
'session_id': history_dict['session_id'],
|
|
105
|
+
'user_id': history_dict['user_id'],
|
|
106
|
+
'chatbot_id': chatbot_id,
|
|
107
|
+
'turns': self._serialize_data(history_dict['turns']),
|
|
108
|
+
'created_at': history_dict['created_at'],
|
|
109
|
+
'updated_at': history_dict['updated_at'],
|
|
110
|
+
'metadata': self._serialize_data(history_dict['metadata'])
|
|
111
|
+
}
|
|
112
|
+
if history_dict.get('chatbot_id') is not None:
|
|
113
|
+
mapping['chatbot_id'] = history_dict['chatbot_id']
|
|
114
|
+
await self.redis.hset(key, mapping=mapping)
|
|
115
|
+
else:
|
|
116
|
+
# Method 2: Using simple key-value storage
|
|
117
|
+
key = self._get_key(user_id, session_id, chatbot_id)
|
|
118
|
+
serialized_data = self._serialize_data(history.to_dict())
|
|
119
|
+
await self.redis.set(key, serialized_data)
|
|
120
|
+
|
|
121
|
+
# Add to user sessions set
|
|
122
|
+
await self.redis.sadd(
|
|
123
|
+
self._get_user_sessions_key(user_id, chatbot_id),
|
|
124
|
+
session_id
|
|
125
|
+
)
|
|
126
|
+
return history
|
|
127
|
+
|
|
128
|
+
async def get_history(
|
|
129
|
+
self,
|
|
130
|
+
user_id: str,
|
|
131
|
+
session_id: str,
|
|
132
|
+
chatbot_id: Optional[str] = None
|
|
133
|
+
) -> Optional[ConversationHistory]:
|
|
134
|
+
"""Get a conversation history."""
|
|
135
|
+
key = self._get_key(user_id, session_id, chatbot_id)
|
|
136
|
+
|
|
137
|
+
if self.use_hash_storage:
|
|
138
|
+
# Method 1: Get from Redis Hash
|
|
139
|
+
data = await self.redis.hgetall(key)
|
|
140
|
+
if not data:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
# Reconstruct the history dict
|
|
145
|
+
history_dict = {
|
|
146
|
+
'session_id': data['session_id'],
|
|
147
|
+
'user_id': data['user_id'],
|
|
148
|
+
'chatbot_id': data.get('chatbot_id', chatbot_id),
|
|
149
|
+
'turns': self._deserialize_data(data['turns']),
|
|
150
|
+
'created_at': data['created_at'],
|
|
151
|
+
'updated_at': data['updated_at'],
|
|
152
|
+
'metadata': self._deserialize_data(data['metadata'])
|
|
153
|
+
}
|
|
154
|
+
return ConversationHistory.from_dict(history_dict)
|
|
155
|
+
except (KeyError, ValueError) as e:
|
|
156
|
+
print(f"Error deserializing conversation history: {e}")
|
|
157
|
+
return None
|
|
158
|
+
else:
|
|
159
|
+
# Method 2: Get from simple key-value
|
|
160
|
+
data = await self.redis.get(key)
|
|
161
|
+
if data:
|
|
162
|
+
try:
|
|
163
|
+
history_dict = self._deserialize_data(data)
|
|
164
|
+
if history_dict is not None and chatbot_id and not history_dict.get('chatbot_id'):
|
|
165
|
+
history_dict['chatbot_id'] = chatbot_id
|
|
166
|
+
return ConversationHistory.from_dict(history_dict)
|
|
167
|
+
except (ValueError, KeyError) as e:
|
|
168
|
+
print(f"Error deserializing conversation history: {e}")
|
|
169
|
+
return None
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
async def update_history(self, history: ConversationHistory) -> None:
|
|
173
|
+
"""Update a conversation history."""
|
|
174
|
+
key = self._get_key(history.user_id, history.session_id, history.chatbot_id)
|
|
175
|
+
|
|
176
|
+
if self.use_hash_storage:
|
|
177
|
+
# Method 1: Update Redis Hash
|
|
178
|
+
history_dict = history.to_dict()
|
|
179
|
+
mapping = {
|
|
180
|
+
'session_id': history_dict['session_id'],
|
|
181
|
+
'user_id': history_dict['user_id'],
|
|
182
|
+
'turns': self._serialize_data(history_dict['turns']),
|
|
183
|
+
'created_at': history_dict['created_at'],
|
|
184
|
+
'updated_at': history_dict['updated_at'],
|
|
185
|
+
'metadata': self._serialize_data(history_dict['metadata'])
|
|
186
|
+
}
|
|
187
|
+
if history_dict.get('chatbot_id') is not None:
|
|
188
|
+
mapping['chatbot_id'] = history_dict['chatbot_id']
|
|
189
|
+
await self.redis.hset(key, mapping=mapping)
|
|
190
|
+
else:
|
|
191
|
+
# Method 2: Update simple key-value
|
|
192
|
+
serialized_data = self._serialize_data(history.to_dict())
|
|
193
|
+
await self.redis.set(key, serialized_data)
|
|
194
|
+
|
|
195
|
+
async def add_turn(
|
|
196
|
+
self,
|
|
197
|
+
user_id: str,
|
|
198
|
+
session_id: str,
|
|
199
|
+
turn: ConversationTurn,
|
|
200
|
+
chatbot_id: Optional[str] = None
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Add a turn to the conversation efficiently."""
|
|
203
|
+
if self.use_hash_storage:
|
|
204
|
+
# Optimized: Only update the turns field
|
|
205
|
+
key = self._get_key(user_id, session_id, chatbot_id)
|
|
206
|
+
|
|
207
|
+
# Get current turns
|
|
208
|
+
current_turns_data = await self.redis.hget(key, 'turns')
|
|
209
|
+
if current_turns_data:
|
|
210
|
+
turns = self._deserialize_data(current_turns_data)
|
|
211
|
+
else:
|
|
212
|
+
turns = []
|
|
213
|
+
|
|
214
|
+
# Add new turn
|
|
215
|
+
turns.append(turn.to_dict())
|
|
216
|
+
|
|
217
|
+
# Update only the turns and updated_at fields
|
|
218
|
+
mapping = {
|
|
219
|
+
'turns': self._serialize_data(turns),
|
|
220
|
+
'updated_at': datetime.now().isoformat()
|
|
221
|
+
}
|
|
222
|
+
if chatbot_id is not None:
|
|
223
|
+
mapping['chatbot_id'] = str(chatbot_id)
|
|
224
|
+
await self.redis.hset(key, mapping=mapping)
|
|
225
|
+
else:
|
|
226
|
+
# Fallback to full history update
|
|
227
|
+
history = await self.get_history(user_id, session_id, chatbot_id)
|
|
228
|
+
if history:
|
|
229
|
+
history.add_turn(turn)
|
|
230
|
+
await self.update_history(history)
|
|
231
|
+
|
|
232
|
+
async def clear_history(
|
|
233
|
+
self,
|
|
234
|
+
user_id: str,
|
|
235
|
+
session_id: str,
|
|
236
|
+
chatbot_id: Optional[str] = None
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Clear a conversation history."""
|
|
239
|
+
if self.use_hash_storage:
|
|
240
|
+
# Optimized: Only clear turns
|
|
241
|
+
key = self._get_key(user_id, session_id, chatbot_id)
|
|
242
|
+
# Reset turns to empty list and update updated_at
|
|
243
|
+
mapping = {
|
|
244
|
+
'turns': self._serialize_data([]),
|
|
245
|
+
'updated_at': datetime.now().isoformat()
|
|
246
|
+
}
|
|
247
|
+
if chatbot_id is not None:
|
|
248
|
+
mapping['chatbot_id'] = str(chatbot_id)
|
|
249
|
+
await self.redis.hset(key, mapping=mapping)
|
|
250
|
+
else:
|
|
251
|
+
history = await self.get_history(user_id, session_id, chatbot_id)
|
|
252
|
+
if history:
|
|
253
|
+
history.clear_turns()
|
|
254
|
+
await self.update_history(history)
|
|
255
|
+
|
|
256
|
+
async def list_sessions(
|
|
257
|
+
self,
|
|
258
|
+
user_id: str,
|
|
259
|
+
chatbot_id: Optional[str] = None
|
|
260
|
+
) -> List[str]:
|
|
261
|
+
"""List all session IDs for a user."""
|
|
262
|
+
sessions = await self.redis.smembers(
|
|
263
|
+
self._get_user_sessions_key(user_id, chatbot_id)
|
|
264
|
+
)
|
|
265
|
+
# Since decode_responses=True, sessions should already be strings
|
|
266
|
+
return list(sessions)
|
|
267
|
+
|
|
268
|
+
async def delete_history(
|
|
269
|
+
self,
|
|
270
|
+
user_id: str,
|
|
271
|
+
session_id: str,
|
|
272
|
+
chatbot_id: Optional[str] = None
|
|
273
|
+
) -> bool:
|
|
274
|
+
"""Delete a conversation history entirely."""
|
|
275
|
+
key = self._get_key(user_id, session_id, chatbot_id)
|
|
276
|
+
result = await self.redis.delete(key)
|
|
277
|
+
await self.redis.srem(
|
|
278
|
+
self._get_user_sessions_key(user_id, chatbot_id),
|
|
279
|
+
session_id
|
|
280
|
+
)
|
|
281
|
+
return result > 0
|
|
282
|
+
|
|
283
|
+
async def close(self):
|
|
284
|
+
"""Close the Redis connection."""
|
|
285
|
+
try:
|
|
286
|
+
await self.redis.close()
|
|
287
|
+
except Exception as e:
|
|
288
|
+
self.logger.error(f"Error closing Redis connection: {e}")
|
|
289
|
+
|
|
290
|
+
async def ping(self) -> bool:
|
|
291
|
+
"""Test Redis connection."""
|
|
292
|
+
try:
|
|
293
|
+
await self.redis.ping()
|
|
294
|
+
return True
|
|
295
|
+
except Exception as e:
|
|
296
|
+
self.logger.error(f"Error pinging Redis: {e}")
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
# Additional utility methods for debugging
|
|
300
|
+
async def get_raw_data(
|
|
301
|
+
self,
|
|
302
|
+
user_id: str,
|
|
303
|
+
session_id: str,
|
|
304
|
+
chatbot_id: Optional[str] = None
|
|
305
|
+
) -> Optional[Dict]:
|
|
306
|
+
"""Get raw data from Redis for debugging."""
|
|
307
|
+
key = self._get_key(user_id, session_id, chatbot_id)
|
|
308
|
+
|
|
309
|
+
if self.use_hash_storage:
|
|
310
|
+
return await self.redis.hgetall(key)
|
|
311
|
+
data = await self.redis.get(key)
|
|
312
|
+
return {"raw_data": data} if data else None
|
|
313
|
+
|
|
314
|
+
async def debug_conversation(
|
|
315
|
+
self,
|
|
316
|
+
user_id: str,
|
|
317
|
+
session_id: str,
|
|
318
|
+
chatbot_id: Optional[str] = None
|
|
319
|
+
) -> Dict[str, Any]:
|
|
320
|
+
"""Debug method to inspect conversation data."""
|
|
321
|
+
raw_data = await self.get_raw_data(user_id, session_id, chatbot_id)
|
|
322
|
+
history = await self.get_history(user_id, session_id, chatbot_id)
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
"raw_data": raw_data,
|
|
326
|
+
"parsed_history": history.to_dict() if history else None,
|
|
327
|
+
"turns_count": len(history.turns) if history else 0,
|
|
328
|
+
"storage_method": "hash" if self.use_hash_storage else "string"
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async def list_sessions_by_chatbot(
|
|
332
|
+
self,
|
|
333
|
+
chatbot_id: str,
|
|
334
|
+
user_id: Optional[str] = None
|
|
335
|
+
) -> List[str]:
|
|
336
|
+
"""List all sessions for a specific chatbot.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
chatbot_id: The chatbot identifier
|
|
340
|
+
user_id: Optional user filter
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
List of session IDs
|
|
344
|
+
"""
|
|
345
|
+
if user_id:
|
|
346
|
+
# Get sessions for specific user and chatbot
|
|
347
|
+
return await self.list_sessions(user_id, chatbot_id)
|
|
348
|
+
|
|
349
|
+
# Get all sessions for this chatbot across all users
|
|
350
|
+
pattern = f"{self.key_prefix}:{chatbot_id}:*"
|
|
351
|
+
sessions = []
|
|
352
|
+
cursor = 0
|
|
353
|
+
|
|
354
|
+
while True:
|
|
355
|
+
cursor, keys = await self.redis.scan(
|
|
356
|
+
cursor,
|
|
357
|
+
match=pattern,
|
|
358
|
+
count=100
|
|
359
|
+
)
|
|
360
|
+
for key in keys:
|
|
361
|
+
# Extract session_id from key
|
|
362
|
+
# Format: conversation:chatbot_id:user_id:session_id
|
|
363
|
+
parts = key.split(':')
|
|
364
|
+
if len(parts) >= 4:
|
|
365
|
+
sessions.append(parts[3])
|
|
366
|
+
|
|
367
|
+
if cursor == 0:
|
|
368
|
+
break
|
|
369
|
+
|
|
370
|
+
return sessions
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
async def get_chatbot_stats(self, chatbot_id: str) -> Dict[str, Any]:
|
|
374
|
+
"""Get statistics for a specific chatbot.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Dictionary with conversation counts, active users, etc.
|
|
378
|
+
"""
|
|
379
|
+
pattern = f"{self.key_prefix}:{chatbot_id}:*"
|
|
380
|
+
total_conversations = 0
|
|
381
|
+
total_turns = 0
|
|
382
|
+
unique_users = set()
|
|
383
|
+
cursor = 0
|
|
384
|
+
|
|
385
|
+
while True:
|
|
386
|
+
cursor, keys = await self.redis.scan(
|
|
387
|
+
cursor,
|
|
388
|
+
match=pattern,
|
|
389
|
+
count=100
|
|
390
|
+
)
|
|
391
|
+
total_conversations += len(keys)
|
|
392
|
+
|
|
393
|
+
for key in keys:
|
|
394
|
+
# Extract user_id
|
|
395
|
+
parts = key.split(':')
|
|
396
|
+
if len(parts) >= 3:
|
|
397
|
+
unique_users.add(parts[2])
|
|
398
|
+
|
|
399
|
+
# Count turns
|
|
400
|
+
if self.use_hash_storage:
|
|
401
|
+
turns_data = await self.redis.hget(key, 'turns')
|
|
402
|
+
if turns_data:
|
|
403
|
+
turns = self._deserialize_data(turns_data)
|
|
404
|
+
total_turns += len(turns)
|
|
405
|
+
|
|
406
|
+
if cursor == 0:
|
|
407
|
+
break
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
'chatbot_id': chatbot_id,
|
|
411
|
+
'total_conversations': total_conversations,
|
|
412
|
+
'total_turns': total_turns,
|
|
413
|
+
'unique_users': len(unique_users),
|
|
414
|
+
'avg_turns_per_conversation': total_turns / total_conversations if total_conversations > 0 else 0
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
async def delete_all_chatbot_conversations(
|
|
419
|
+
self,
|
|
420
|
+
chatbot_id: str,
|
|
421
|
+
user_id: Optional[str] = None
|
|
422
|
+
) -> int:
|
|
423
|
+
"""Delete all conversations for a chatbot.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
chatbot_id: The chatbot identifier
|
|
427
|
+
user_id: Optional user filter
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Number of conversations deleted
|
|
431
|
+
"""
|
|
432
|
+
if user_id:
|
|
433
|
+
pattern = f"{self.key_prefix}:{chatbot_id}:{user_id}:*"
|
|
434
|
+
else:
|
|
435
|
+
pattern = f"{self.key_prefix}:{chatbot_id}:*"
|
|
436
|
+
|
|
437
|
+
deleted_count = 0
|
|
438
|
+
cursor = 0
|
|
439
|
+
|
|
440
|
+
while True:
|
|
441
|
+
cursor, keys = await self.redis.scan(
|
|
442
|
+
cursor,
|
|
443
|
+
match=pattern,
|
|
444
|
+
count=100
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
if keys:
|
|
448
|
+
deleted_count += await self.redis.delete(*keys)
|
|
449
|
+
|
|
450
|
+
if cursor == 0:
|
|
451
|
+
break
|
|
452
|
+
|
|
453
|
+
# Also clean up session sets
|
|
454
|
+
if user_id:
|
|
455
|
+
await self.redis.delete(self._get_user_sessions_key(user_id, chatbot_id))
|
|
456
|
+
else:
|
|
457
|
+
# Clean all user session sets for this chatbot
|
|
458
|
+
session_pattern = f"{self.key_prefix}_sessions:{chatbot_id}:*"
|
|
459
|
+
cursor = 0
|
|
460
|
+
while True:
|
|
461
|
+
cursor, session_keys = await self.redis.scan(
|
|
462
|
+
cursor,
|
|
463
|
+
match=session_pattern,
|
|
464
|
+
count=100
|
|
465
|
+
)
|
|
466
|
+
if session_keys:
|
|
467
|
+
await self.redis.delete(*session_keys)
|
|
468
|
+
if cursor == 0:
|
|
469
|
+
break
|
|
470
|
+
|
|
471
|
+
return deleted_count
|
|
472
|
+
|
|
473
|
+
async def _update_chatbot_index(
|
|
474
|
+
self,
|
|
475
|
+
chatbot_id: str,
|
|
476
|
+
user_id: str,
|
|
477
|
+
session_id: str,
|
|
478
|
+
operation: str = 'add'
|
|
479
|
+
) -> None:
|
|
480
|
+
"""Maintain reverse index for fast chatbot queries.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
chatbot_id: The chatbot identifier
|
|
484
|
+
user_id: The user identifier
|
|
485
|
+
session_id: The session identifier
|
|
486
|
+
operation: 'add' or 'remove'
|
|
487
|
+
"""
|
|
488
|
+
# Index: all users who interacted with this chatbot
|
|
489
|
+
users_key = f"{self.key_prefix}_index:chatbot_users:{chatbot_id}"
|
|
490
|
+
|
|
491
|
+
if operation == 'add':
|
|
492
|
+
await self.redis.sadd(users_key, user_id)
|
|
493
|
+
# Set expiry if needed
|
|
494
|
+
# await self.redis.expire(users_key, 86400 * 30) # 30 days
|
|
495
|
+
elif operation == 'remove':
|
|
496
|
+
# Check if user has any other sessions with this chatbot
|
|
497
|
+
sessions = await self.list_sessions(user_id, chatbot_id)
|
|
498
|
+
if not sessions:
|
|
499
|
+
await self.redis.srem(users_key, user_id)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
async def get_chatbot_users(self, chatbot_id: str) -> List[str]:
|
|
503
|
+
"""Get all users who have interacted with a chatbot."""
|
|
504
|
+
users_key = f"{self.key_prefix}_index:chatbot_users:{chatbot_id}"
|
|
505
|
+
users = await self.redis.smembers(users_key)
|
|
506
|
+
return list(users)
|
|
507
|
+
|
|
508
|
+
# Example usage and testing
|
|
509
|
+
async def test_redis_conversation():
|
|
510
|
+
"""Enhanced test with multiple chatbots and users."""
|
|
511
|
+
redis_memory = RedisConversation(use_hash_storage=True)
|
|
512
|
+
|
|
513
|
+
if not await redis_memory.ping():
|
|
514
|
+
print("Redis connection failed!")
|
|
515
|
+
return
|
|
516
|
+
|
|
517
|
+
chatbot1 = "sales_bot"
|
|
518
|
+
chatbot2 = "support_bot"
|
|
519
|
+
user1 = "user_alice"
|
|
520
|
+
user2 = "user_bob"
|
|
521
|
+
|
|
522
|
+
try:
|
|
523
|
+
# Test 1: Multiple bots with same user
|
|
524
|
+
print("\n=== Test 1: Same user, different bots ===")
|
|
525
|
+
h1 = await redis_memory.create_history(user1, "session1", chatbot1)
|
|
526
|
+
h2 = await redis_memory.create_history(user1, "session2", chatbot2)
|
|
527
|
+
|
|
528
|
+
print(f"Created sessions: {h1.session_id}, {h2.session_id}")
|
|
529
|
+
|
|
530
|
+
# Test 2: List sessions by chatbot
|
|
531
|
+
print("\n=== Test 2: List sessions by chatbot ===")
|
|
532
|
+
sessions1 = await redis_memory.list_sessions(user1, chatbot1)
|
|
533
|
+
sessions2 = await redis_memory.list_sessions(user1, chatbot2)
|
|
534
|
+
|
|
535
|
+
print(f"User {user1} sessions with {chatbot1}: {sessions1}")
|
|
536
|
+
print(f"User {user1} sessions with {chatbot2}: {sessions2}")
|
|
537
|
+
|
|
538
|
+
# Test 3: Add turns to different bots
|
|
539
|
+
print("\n=== Test 3: Add turns ===")
|
|
540
|
+
turn1 = ConversationTurn(
|
|
541
|
+
turn_id="t1",
|
|
542
|
+
user_id=user1,
|
|
543
|
+
user_message="What's your price?",
|
|
544
|
+
assistant_response="Our starting price is $99/month."
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
turn2 = ConversationTurn(
|
|
548
|
+
turn_id="t2",
|
|
549
|
+
user_id=user1,
|
|
550
|
+
user_message="I need help",
|
|
551
|
+
assistant_response="How can I assist you today?"
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
await redis_memory.add_turn(user1, "session1", turn1, chatbot1)
|
|
555
|
+
await redis_memory.add_turn(user1, "session2", turn2, chatbot2)
|
|
556
|
+
|
|
557
|
+
# Test 4: Retrieve and verify isolation
|
|
558
|
+
print("\n=== Test 4: Verify conversation isolation ===")
|
|
559
|
+
conv1 = await redis_memory.get_history(user1, "session1", chatbot1)
|
|
560
|
+
conv2 = await redis_memory.get_history(user1, "session2", chatbot2)
|
|
561
|
+
|
|
562
|
+
print(f"Sales bot conversation: {conv1.turns[0].assistant_response}")
|
|
563
|
+
print(f"Support bot conversation: {conv2.turns[0].assistant_response}")
|
|
564
|
+
|
|
565
|
+
# Test 5: Cross-contamination check
|
|
566
|
+
print("\n=== Test 5: Cross-contamination check ===")
|
|
567
|
+
wrong_bot = await redis_memory.get_history(user1, "session1", chatbot2)
|
|
568
|
+
print(f"Trying to access sales session with support bot ID: {wrong_bot}") # Should be None
|
|
569
|
+
|
|
570
|
+
# Test 6: Same session_id, different chatbots
|
|
571
|
+
print("\n=== Test 6: Session ID collision test ===")
|
|
572
|
+
h3 = await redis_memory.create_history(user2, "common_session", chatbot1)
|
|
573
|
+
h4 = await redis_memory.create_history(user2, "common_session", chatbot2)
|
|
574
|
+
|
|
575
|
+
turn3 = ConversationTurn(
|
|
576
|
+
turn_id="t3",
|
|
577
|
+
user_id=user2,
|
|
578
|
+
user_message="Hello sales",
|
|
579
|
+
assistant_response="Welcome to sales!"
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
turn4 = ConversationTurn(
|
|
583
|
+
turn_id="t4",
|
|
584
|
+
user_id=user2,
|
|
585
|
+
user_message="Hello support",
|
|
586
|
+
assistant_response="Welcome to support!"
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
await redis_memory.add_turn(user2, "common_session", turn3, chatbot1)
|
|
590
|
+
await redis_memory.add_turn(user2, "common_session", turn4, chatbot2)
|
|
591
|
+
|
|
592
|
+
c1 = await redis_memory.get_history(user2, "common_session", chatbot1)
|
|
593
|
+
c2 = await redis_memory.get_history(user2, "common_session", chatbot2)
|
|
594
|
+
|
|
595
|
+
print(f"Sales bot (common session): {c1.turns[0].assistant_response}")
|
|
596
|
+
print(f"Support bot (common session): {c2.turns[0].assistant_response}")
|
|
597
|
+
|
|
598
|
+
# Cleanup
|
|
599
|
+
print("\n=== Cleanup ===")
|
|
600
|
+
await redis_memory.delete_history(user1, "session1", chatbot1)
|
|
601
|
+
await redis_memory.delete_history(user1, "session2", chatbot2)
|
|
602
|
+
await redis_memory.delete_history(user2, "common_session", chatbot1)
|
|
603
|
+
await redis_memory.delete_history(user2, "common_session", chatbot2)
|
|
604
|
+
|
|
605
|
+
print("All tests passed! ✓")
|
|
606
|
+
|
|
607
|
+
finally:
|
|
608
|
+
await redis_memory.close()
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
if __name__ == "__main__":
|
|
612
|
+
import asyncio
|
|
613
|
+
asyncio.run(test_redis_conversation())
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Models for the Parrot application.
|
|
3
|
+
Includes definitions for various data structures used in the application,
|
|
4
|
+
such as responses, outputs, and configurations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .basic import OutputFormat, ToolCall, CompletionUsage
|
|
8
|
+
from .responses import (
|
|
9
|
+
AIMessage,
|
|
10
|
+
SourceDocument,
|
|
11
|
+
AIMessageFactory,
|
|
12
|
+
MessageResponse,
|
|
13
|
+
StreamChunk,
|
|
14
|
+
)
|
|
15
|
+
from .outputs import (
|
|
16
|
+
StructuredOutputConfig,
|
|
17
|
+
BoundingBox,
|
|
18
|
+
ObjectDetectionResult,
|
|
19
|
+
ImageGenerationPrompt,
|
|
20
|
+
SpeakerConfig,
|
|
21
|
+
SpeechGenerationPrompt,
|
|
22
|
+
VideoGenerationPrompt
|
|
23
|
+
)
|
|
24
|
+
from .google import GoogleModel, TTSVoice
|
|
25
|
+
from .generation import VideoGenerationPrompt
|
|
26
|
+
|
|
27
|
+
__all__ = (
|
|
28
|
+
"OutputFormat",
|
|
29
|
+
"ToolCall",
|
|
30
|
+
"CompletionUsage",
|
|
31
|
+
"AIMessage",
|
|
32
|
+
"AIMessageFactory",
|
|
33
|
+
"SourceDocument",
|
|
34
|
+
"MessageResponse",
|
|
35
|
+
"StreamChunk",
|
|
36
|
+
"StructuredOutputConfig",
|
|
37
|
+
"BoundingBox",
|
|
38
|
+
"ObjectDetectionResult",
|
|
39
|
+
"ImageGenerationPrompt",
|
|
40
|
+
"SpeakerConfig",
|
|
41
|
+
"SpeechGenerationPrompt",
|
|
42
|
+
"VideoGenerationPrompt",
|
|
43
|
+
"GoogleModel",
|
|
44
|
+
"TTSVoice",
|
|
45
|
+
"VideoGenerationPrompt",
|
|
46
|
+
)
|